Python API

The supported Python contract begins at quantas.api. Applications, notebooks, services, the command-line interface, and a future GUI should import from this namespace rather than from implementation packages under quantas.core, quantas.modules, or quantas.renderers.

Public namespaces are organized by scientific domain:

from quantas.api import (
    elasticity,
    eos,
    ha,
    interop,
    profiles,
    qha,
    registry,
    rendering,
    seismic,
    thermoelasticity,
)

The API is deliberately not one flat collection of functions. Each namespace exposes the passive data contracts and operations appropriate to that scientific workflow.

The single-shot workflow pattern

Elasticity, SEISMIC, HA, QHA, and the calibration stage of Thermoelasticity use the same broad pattern:

from quantas.api import elasticity

options = elasticity.Options(calculate_2d=True)
result = elasticity.run("calcite.dat", options=options)
payload = elasticity.get_result(result)

The three objects have different responsibilities.

Options

A passive, validated description of scientific and numerical choices. It contains no terminal state and performs no calculation.

ResultData

The complete frontend-neutral envelope returned by the workflow. It records program and schema metadata, normalized input, resolved options, warnings, persistent events, and module-specific results.

Typed payload

The domain result returned by get_result. It provides named attributes and numerical arrays without requiring the caller to navigate a generic string-keyed mapping.

Use the module-specific get_result function whenever one is available. It checks that the envelope belongs to the expected module and that the payload has the correct type.

Reading and normalizing input

Public modules commonly expose two related operations:

read_input(path)

Parse one supported file format and return a validated passive input object.

normalize_input(value)

Accept either an existing input object or a path and return the normalized input contract used by the workflow.

For example:

source = elasticity.read_input("calcite.dat")
normalized = elasticity.normalize_input(source)
result = elasticity.run(normalized)

Reading the input explicitly is useful when a notebook needs to inspect or modify passive options before running. Passing the path directly to run is the shorter equivalent for ordinary calculations.

Reports and plots remain frontend-neutral

Scientific modules build neutral report tables and plot descriptions. A separate public rendering namespace turns those descriptions into concrete artifacts:

from pathlib import Path
from quantas.api import elasticity, rendering

options = elasticity.Options(calculate_2d=True)
result = elasticity.run("calcite.dat", options=options)

tables = elasticity.build_report(result)
report_text = rendering.render_tables(tables)
Path("calcite.log").write_text(report_text, encoding="utf-8")

plots = elasticity.build_plots(result)
rendered = rendering.render_plots(
    plots,
    output_dir="figures",
    preset="publication",
    close=True,
)

The calculator does not import Rich, Matplotlib, or a GUI toolkit. This separation is what allows CLI, notebooks, services, and graphical frontends to use the same numerical result.

Native result persistence

Single-shot modules expose matching read and write operations:

elasticity.write_result(
    result,
    "calcite_elasticity.hdf5",
    report_text=report_text,
)

restored = elasticity.read_result("calcite_elasticity.hdf5")
restored_payload = elasticity.get_result(restored)

HDF5 is the scientific source of truth. Display precision in a report or figure never changes the stored float64 values.

Domain-specific operations

A common vocabulary is used where the operation has the same meaning, but the API does not force scientifically different workflows into one artificial base class. Examples include:

Consult the domain page in the API overview reference rather than assuming that every namespace exposes the same functions.

EOS uses an archive lifecycle

EOS differs from the single-shot modules because one dataset may be fitted many times with different models, solvers, constraints, and data selections. Its HDF5 file therefore contains immutable fit records and mutable acceptance state rather than one result envelope.

A direct fit can be constructed as:

from quantas.api import eos

dataset = eos.read_input("PV_quartz.dat")
request = eos.FitRequest(model="BM3")
fit = eos.fit(dataset, request)

For persistent batch work:

plan = eos.BatchPlan(jobs=(eos.BatchJob(request=request),))
summary = eos.run_batch(
    dataset,
    plan,
    "quartz_eos.hdf5",
    overwrite=True,
)

Post-fit calculations operate on the stored archive:

diagnostics = eos.diagnose("quartz_eos.hdf5")
properties = eos.calculate(
    "quartz_eos.hdf5",
    pressure=[0.0, 5.0, 10.0],
)
plots = eos.build_plots("quartz_eos.hdf5")

The different lifecycle is scientific, not merely syntactic. It is explained in EOS fitting: implementation and workflow.

Cross-module transformations

Scientifically meaningful coupling is explicit in quantas.api.interop. For example, one thermoelastic state can be transformed into a SEISMIC input:

from quantas.api import interop, seismic

seismic_input = interop.thermoelastic_to_seismic(
    "dolomite_thermoelastic.hdf5",
    pressure=2.0,
    temperature=600.0,
    tensor_condition="adiabatic",
)
seismic_result = seismic.run(seismic_input)

The transformation centralizes unit conversion, coverage checks, tensor condition, and provenance instead of duplicating them in user code.

Events and progress

Public run functions accept frontend-neutral observers. They never create a terminal progress bar or GUI widget themselves:

from quantas.api import elasticity
from quantas.api.common import ListObserver

observer = ListObserver()
result = elasticity.run("calcite.dat", observer=observer)

for event in observer.events:
    print(event.level.value, event.message, event.progress)

A caller can map the same events to logging, a notebook display, a web service, a GUI progress indicator, or no visible output. Operational progress events are not persisted as scientific history.

Capability discovery

A frontend can discover supported modules and operations without importing private classes or hard-coding a single inheritance hierarchy:

from quantas.api import registry
from quantas.api.registry import Capability

for descriptor in registry.list_modules():
    capabilities = sorted(item.value for item in descriptor.capabilities)
    print(descriptor.name, capabilities)

qha = registry.get("qha")
run_qha = qha.operation(Capability.RUN)
options_type = qha.options_type

EOS declares FIT, BATCH, and ARCHIVE capabilities instead of pretending to be a single-shot RUN module. Native HDF5 files can also be dispatched from their metadata:

result_or_archive = registry.open_result("quantas_result.hdf5")

Errors and validation

Public operations raise Python exceptions for invalid inputs or unsupported scientific states. Do not suppress these errors indiscriminately. A useful application should:

  • show the exception message to the user;

  • preserve warnings and diagnostics from successful results;

  • distinguish invalid input from numerical non-convergence;

  • avoid converting a failed or extrapolated state into an accepted result without an explicit scientific decision.

Where to continue