Architecture and dependency rules

The central architectural rule is simple:

Scientific results must not depend on the frontend that requested them.

The Python API, Click CLI, and a future GUI must call the same normalized workflows and obtain the same float64 arrays.

Layered package model

external files
     |
     v
quantas.interfaces ------+
     |                    |
     v                    |
normalized contracts      |
     |                    |
     v                    |
quantas.modules ----------+----> ResultData / HDF5
     |                                  |
     +----------+-----------------------+
                |
     +----------+-----------+
     |                      |
     v                      v
quantas.api            report / plot specs
     |                      |
     +----------+-----------+
                |
       +--------+--------+
       |                 |
       v                 v
   quantas.cli      future GUI/notebook
quantas.core

Reusable scientific algorithms and policies. Core code does not know which module or frontend called it.

quantas.models

Shared passive containers and small active base contracts. This layer has no module-specific scientific policy.

quantas.interfaces

Code-specific parsing. Interfaces convert external syntax to explicit physical quantities and provenance; they do not run Quantas workflows.

quantas.modules

Scientific orchestration. A module validates normalized input, calls core routines, assembles typed results, emits events, persists payloads, builds reports and plot specifications, and exports data.

quantas.api

Stable application-facing facades. Applications use this layer instead of concrete calculators, readers, or exporters.

quantas.renderers

Concrete presentation of neutral tables and plots.

quantas.cli

Click adapters and Rich terminal behaviour. The CLI parses and validates user input, constructs public options, calls quantas.api, observes events, and renders the returned neutral objects.

Allowed dependency direction

A lower layer must not import a higher layer. The intended direction is:

core <- models <- interfaces/modules <- api <- cli or external application
                   |
                   +-> neutral report/plot models -> renderers

Important consequences are:

  • core never imports modules, interfaces, renderers, or cli;

  • one scientific module does not import another module directly;

  • cross-module transformations belong in quantas.api.interop or a shared lower-level contract, not in an accidental module-to-module import;

  • interface parsers do not import workflows;

  • module plot builders import neutral plot contracts, not Matplotlib;

  • the CLI calls public API functions rather than concrete calculators.

The architecture tests enforce these boundaries by inspecting imports.

Active objects and passive data

Use a normal class for an object that owns behaviour, state, or a lifecycle:

  • calculators;

  • readers and exporters;

  • observers;

  • workflow controllers;

  • numerical solvers;

  • persistent EOS sessions.

Use a dataclass for passive information:

  • normalized inputs;

  • options;

  • metadata;

  • scientific results;

  • report and plot specifications;

  • immutable requests and diagnostics.

A useful test is: if the object performs I/O, emits events, advances through stages, or mutates internal state, it is probably not a passive dataclass.

Scientific module isolation

Modules share core physics and common contracts but remain independent. QHA must not import Thermoelasticity merely because Thermoelasticity consumes QHA results. SEISMIC must not be hidden inside Elasticity merely because both use elastic tensors.

This separation preserves:

  • independent scientific testing;

  • explicit transformations between domains;

  • smaller import surfaces;

  • reuse by frontends;

  • the ability to replace one workflow without destabilizing another.

Precision and units

The authoritative numerical policy is defined in quantas.core.numerics.precision:

  • real calculations: numpy.float64;

  • complex calculations: numpy.complex128;

  • native HDF5 floating values: float64;

  • no runtime precision option.

Input units are normalized at workflow boundaries. Core functions should state and test the units they consume and return. Display precision and display-unit selection belong to reports, exporters, and renderers; they must never round or rescale the stored scientific arrays in place.

Error and warning ownership

Use exceptions for states in which the requested result cannot be interpreted safely. Use warnings for completed calculations that require scientific attention. Do not silently clip, repair, reorder, or extrapolate data unless the workflow explicitly defines that policy and records it in results or masks.

Frontend ownership

The CLI owns:

  • command names and option grouping;

  • string-to-type conversion;

  • prompts and overwrite confirmation;

  • output-path conventions;

  • Rich warnings, errors, progress, and tables.

The scientific workflow owns:

  • validation of scientific meaning;

  • numerical defaults and approximations;

  • warnings and failure policies;

  • result fields and masks;

  • report and plot data.

A future GUI may choose a different visual layout, but it must not reimplement or bypass the scientific decisions in the workflow.