External-code interfaces and parsers

Code-specific parsers live under quantas.interfaces. Their purpose is to translate external output syntax into explicit physical data while preserving provenance. They do not run Quantas calculations.

Separation of responsibilities

CRYSTAL / VASP / Phonopy output
             |
             v
quantas.interfaces.<code> parser
             |
             v
code-neutral parsed quantities
             |
             v
module input generator / normalizer
             |
             v
Quantas Input contract

This separation allows a second code to provide the same normalized module input without duplicating the scientific workflow.

Reader lifecycle

Concrete parsers usually derive from quantas.models.BasicReader and maintain:

completed

True only after all required data have been parsed and validated.

error

A concise contextual message when the reader could not complete.

load(path)

Reset state, identify the file, check completion, parse fields, validate semantics, and set completed.

A parser may expose typed properties for stiffness, density, volume, energy, pressure, structure, q-point data, or other source quantities. These properties must state units and conventions.

File recognition and completion

Do not parse every text file optimistically. Use stable markers to distinguish:

  • correct program and calculation type;

  • completed calculation;

  • required output section;

  • code version or format variant when relevant.

A file can be recognized but incomplete. Report these conditions separately so the user knows whether the wrong file was selected or the external calculation failed.

Units and conventions

Convert units at the parser or normalization boundary and document the target. Examples include:

  • VASP elastic constants from kbar to GPa;

  • crystal density to kg m-3;

  • static energy to hartree or another declared native input unit;

  • q-point weights exactly as supplied upstream;

  • Voigt order conversion between external and Quantas conventions.

Never assume that two codes use the same shear ordering, stress sign, crystallographic cell, or pre-stress correction.

Structures and symmetry

When a parser supplies a structure, preserve:

  • lattice vectors;

  • fractional or Cartesian coordinate convention;

  • atomic numbers and atom order;

  • primitive/conventional/supercell basis;

  • transformation or repetition matrix;

  • source symmetry information;

  • Quantas/spglib analysis settings and results when calculated.

Do not reorder atoms without recording and testing the mapping. Thermoelastic co-rotation and multi-volume structural paths depend on consistent atom identity.

Phonon eigenvectors and mode continuity

Phonon parsers follow the same separation of responsibilities, with one extra boundary:

external phonon output
        |
        v
code-specific parser
        |
        +-- frequencies
        +-- normalized eigenvectors
        +-- atom ordering and q metadata
        |
        v
PhononModeData
        |
        v
core.numerics phonon tracker
        |
        v
normalized PhononInputData / QHA input

The parser is responsible for reconstructing the eigenvector representation of the external code and documenting its normalization. It must not decide that a QHA branch correspondence is acceptable merely because two raw mode indices match.

The backend-neutral tracker receives float64 frequencies and complex128 unit-norm eigenvectors. It knows nothing about CRYSTAL markers, Phonopy YAML, or future VASP/QE syntax. Conversely, the CRYSTAL parser does not know QHA failure policy or CLI rendering.

For CRYSTAL, complex general-q vectors are reconstructed from in-phase and anti-phase components and converted to unit-norm mass-weighted directions before they leave the interface layer. Degenerate-subspace matching, Hungarian assignment, ambiguity classification, and leave-one-out validation belong to the numerical tracking layer.

Important

Do not move mode-continuity policy into a code-specific parser. A future interface must be able to supply the same neutral PhononModeData and obtain the same tracking result from the same normalized arrays.

When an external workflow has already established continuity, preserve that fact explicitly as source provenance rather than relabelling it as a Quantas tracking result. The crystal-qha path is the current example.

Provenance

Store the information required to understand the parsed quantity later:

  • source path and raw text when appropriate;

  • external code and calculation type;

  • relevant keyword values;

  • pressure correction or relaxation state;

  • cell normalization;

  • parser version or schema label;

  • warnings about missing optional fields.

For CRYSTAL elasticity, for example, the PRESSURE keyword and reported elastic pressure are scientifically relevant because they establish whether the output contains the stress-corrected coefficients required under hydrostatic pre-stress.

CRYSTAL static-energy semantics

CRYSTAL distinguishes the converged electronic SCF energy from the physical total energy used when a-posteriori corrections are active. Quantas preserves both quantities at the interface boundary:

SCF energy

The electronic energy printed on SCF ENDED - CONVERGENCE ON ENERGY and repeated by TOTAL ENERGY(DFT)(AU).

total energy

The corrected energy printed by CRYSTAL when available. Recognized forms include TOTAL ENERGY + DISP (AU), TOTAL ENERGY + GCP (AU), and TOTAL ENERGY + DISP + GCP (AU). If CRYSTAL prints no corrected total, the total energy is identical to the SCF energy.

The generic quantas.interfaces.crystal.output.CrystalOutputParser resolves these values state by state and does not attach a correction printed for one SCF calculation to a later state. The corrected total printed by the backend is authoritative; Quantas does not reconstruct it by summing empirical components. Correction labels and the difference between total and SCF energy are retained as provenance.

Scientific workflows consume the resolved total energy. In particular, CRYSTAL phonon input generation continues to use the CENTRAL POINT energy, which is the total energy attached by CRYSTAL to the undisplaced reference configuration. When it can be matched to the preceding SCF state, the uncorrected SCF energy is additionally retained in input provenance. CRYSTAL elastic readers likewise expose scf_energy and total_energy while the historical energy property is an alias for total_energy.

CRYSTAL elastic volume series

quantas.interfaces.crystal.read_crystal_elastic_series() composes a set of completed ELASTCON or ELAPIEZO outputs into the backend-neutral quantas.models.elastic_states.ElasticStateSeries contract. This is the interface boundary used by Kieffer and available to other multi-volume elastic workflows; it does not create a Kieffer-specific elastic format.

The importer requires finite volume, density, total static energy, and stiffness at every state. It sorts the resulting states by increasing volume and selects the minimum-static-energy state as the reference. Tensor axes remain in the CRYSTAL Cartesian frame.

For each elastic output, the structural state is the unstrained reference used to generate the elastic distortions. Quantas therefore restricts structure, static energy, density, and output-stress pressure collection to the part of the CRYSTAL output preceding the first STRAIN MATRIX. This distinction is important when COORPRT causes geometries from later strained or internally relaxed configurations to be printed. The selected lattice must also agree with the primitive-cell volume reported by the elastic module before it can be attached to an ElasticState.

Pressure selection is explicit:

auto

Preserve tensors already corrected by CRYSTAL when the PRESSURE keyword is present. For raw tensors, use the pressure printed for the unstrained stress tensor.

output_stress

Require raw tensors and explicitly use their reported unstrained-stress pressure.

manual

Require one finite pressure in GPa per input file. Values follow input-file order before volume sorting. Positive pressure denotes compression.

deferred

Retain a raw tensor without attaching pressure. This adapter-level route requires apply_prestress_correction=False and exists so a composing workflow can attach independently derived pressure provenance before a separate correction. It is not exposed as a user-facing add-kieffer pressure source.

By default, raw CRYSTAL energy–strain tensors are converted once with the finite-pressure transformation implemented by CRYSTAL itself [Erba2014]:

\[B_{ijkl}=C_{ijkl}+\frac{P}{2} \left(2\delta_{ij}\delta_{kl}-\delta_{il}\delta_{jk}-\delta_{ik}\delta_{jl}\right).\]

In CRYSTAL Voigt order this leaves C11, C22, and C33 unchanged, adds +P to C12, C13, and C23, and adds -P/2 to the three shear diagonals. This interface conversion is deliberately distinct from the Eulerian finite-strain Wallace term used internally by the QSA model. The pressure value, source, method, source tensor kind, and software applying the correction are retained in each state. Passing a non-auto pressure policy for a tensor already corrected by CRYSTAL is an error, preventing an accidental second correction.

[Erba2014]

A. Erba, A. Mahmoud, D. Belmonte, and R. Dovesi, J. Chem. Phys. 140, 124703 (2014), doi:10.1063/1.4869144.

from quantas.interfaces.crystal import read_crystal_elastic_series

series = read_crystal_elastic_series(
    ["state_01.out", "state_02.out", "state_03.out"],
    pressure_policy="output_stress",
)

If the structural block reconstructed from an output does not have the same volume as the final elastic scalar, the final elastic volume remains authoritative. The inconsistent lattice is not attached to the state; its volume and the failed consistency check are recorded in metadata. This avoids silently coupling a tensor to a stale geometry block while retaining the diagnostic needed to inspect the source output.

Kieffer input enrichment

The public HA and QHA APIs expose add_kieffer_input. Their shared implementation reads the phonon input and the CRYSTAL elastic volume series, builds the anisotropic acoustic averages, validates the appropriate HA or QHA applicability contract, and writes a new YAML file. The corresponding command is registered under both workflows:

quantas ha add-kieffer ha.yaml state.out -o ha-kieffer.yaml
quantas qha add-kieffer qha.yaml --elastic-list elastic-files.txt \
    --interface crystal -o qha-kieffer.yaml

Paths inside elastic-files.txt are resolved relative to the list file. Blank lines and lines beginning with # are ignored. This makes the list portable when the complete calculation directory is moved.

The default --pressure-source auto preserves tensors corrected by CRYSTAL’s PRESSURE keyword and otherwise uses pressure from the unstrained stress. Manual pressure values can be supplied in input-file order:

quantas qha add-kieffer qha.yaml --elastic-list elastic-files.txt \
    --pressure-source manual \
    --pressure 11.53 --pressure 8.718 --pressure 6.069 \
    -o qha-kieffer.yaml

For multi-volume QHA inputs, pressure may instead be evaluated from the static energy-volume arrays already present in the phonon input:

quantas qha add-kieffer qha.yaml --elastic-list elastic-files.txt \
    --interface crystal --pressure-source energy-eos --eos BM3 \
    -o qha-kieffer.yaml

quantas qha add-kieffer qha.yaml --elastic-list elastic-files.txt \
    --interface crystal --pressure-source energy-polynomial --degree 3 \
    -o qha-kieffer.yaml

The reusable fit operations live in quantas.core.physics.eos; pressure assignment and hydrostatic correction remain separate operations in quantas.core.physics.elasticity. This boundary lets tests verify that P(V) is attached to an unmodified raw tensor before the tensor is corrected exactly once.

The destination defaults to <input-stem>-kieffer.yaml and must differ from the source path. An existing Kieffer block is never replaced silently. The generated top-level kieffer mapping identifies the sine-wave method and its additive composition, declares canonical units, and stores one state per volume with:

  • cutoff frequencies in Hz;

  • effective slow-shear, fast-shear, and longitudinal velocities in km/s;

  • pressure and tensor convention;

  • elastic-state association;

  • spherical-quadrature diagnostics;

  • original phonon and elastic source paths.

The public read_kieffer_input operation restores this block as a validated quantas.models.kieffer.KiefferVolumeSeries. It can therefore be passed explicitly to the HA/QHA calculation APIs without reconstructing the elastic calculation.

Error handling

Catch only exceptions that can be converted into a useful parser error. Avoid a broad except Exception that turns programming errors into misleading input messages.

A useful error identifies:

  • the file;

  • the section or quantity;

  • the expected marker or shape;

  • the observed problem.

Do not return a zero-filled scientific array after a required section failed to parse.

Fixture strategy

Parser tests should include:

  • a small complete real output;

  • incomplete output;

  • wrong calculation type;

  • malformed numerical section;

  • optional section absent;

  • supported code-version variants;

  • unit and convention conversion;

  • structure and atom ordering;

  • integration through the normalized module input generator.

Keep fixtures scientifically recognizable but small enough for the repository. When a full external output is too large, retain the required sections without inventing numerical values.

Adding support for a new code

  1. Start with one mature workflow and one clearly defined output quantity.

  2. Implement the parser under quantas.interfaces.<code>.

  3. Add a code-neutral adapter to the existing module input contract.

  4. Add an interface selector only after the parser is tested.

  5. Document the supported code version, calculation settings, and limitations.

  6. Compare normalized outputs from two codes when equivalent datasets are available.

  7. Generalize shared parsing concepts only after two real implementations show the common abstraction.

Do not create a large universal parser hierarchy before the external formats have demonstrated a stable common structure.