EOS diagnostics and calculator
This tutorial starts from an existing native EOS archive produced by
quantas eos run or quantas.api.eos.Session. The
post-fit tools never refit the data. They resolve one immutable successful
record, reconstruct its model and parameter covariance, and derive diagnostics
or properties from that record.
Selecting a result
When an archive contains exactly one accepted result, no explicit selector is
required. With several accepted targets, use a stable result slot such as
pv/volume, pv/a, vt/volume, or pvt/volume. An immutable
record_id may be selected instead when a historical candidate or superseded
fit must be inspected.
Residual and finite-strain diagnostics
The command
quantas eos diagnose quartz_EOS.hdf5 --slot pv/volume \
--output quartz_diagnostics.csv
reports observed and calculated values, physical residuals, standardized residuals when the solver supplied them, input selection/group metadata, and finite-strain diagnostics where a scientifically established transformation is available.
Normalized-pressure representations are provided for Birch–Murnaghan, natural-strain/Poirier–Tarantola, and Vinet pressure EOS families. Murnaghan and Tait still receive residual diagnostics, but Quantas does not invent a generic normalized-pressure transformation for them. At the exact reference state the normalized pressure is mathematically singular and is therefore reported as unavailable rather than replaced by a fitted modulus.
For an axial EOS, Quantas applies the same cubed-length convention used during the fit. The finite strain is evaluated from \(L^3/L_0^3\); consequently a normalized-pressure intercept corresponds to the auxiliary modulus \(M/3\), not directly to the reported linear modulus \(M\).
Property calculation
Evaluate a pressure grid from an accepted P–V record with
quantas eos calculate quartz_EOS.hdf5 --slot pv/volume \
--pressure-range 0:10:1 --output quartz_properties.csv
For a V–T result, provide temperatures:
quantas eos calculate rutile_EOS.hdf5 --slot vt/volume \
--temperature-range 20:1200:20
For P–V–T, pressure/volume and temperature ranges form a Cartesian grid by default:
quantas eos calculate spessartine_EOS.hdf5 --slot pvt/volume \
--pressure-range 0:15:1 --temperature-range 300:1200:100 \
--output spessartine_grid.csv
Use --pairwise to pair equal-length coordinate and temperature vectors
instead. --coordinate means volume for a volumetric record and the fitted
physical length for an axial P–V record.
The calculator returns the properties available for the selected domain:
P–V: volume or axis, modulus, and its first and second pressure derivatives;
V–T: structural value, thermal-expansion coefficient, and temperature derivative;
P–V–T: pressure, temperature, volume, \(K_T\), \(K'_T\), \(K''_T\), \(\alpha(P,T)\), \((\partial K/\partial T)_P\), zero-pressure reference properties, and thermal pressure when the selected coupling defines one.
Uncertainties and extrapolation
By default, one-sigma property uncertainties are propagated from the complete
fitted parameter covariance through a first-order numerical delta method.
Values supplied as independent state coordinates are not assigned parameter
uncertainties. Use --no-uncertainty to skip propagation or
--relative-step to control the parameter perturbation used by the delta
method.
These uncertainties represent fitted-parameter covariance only. They do not include uncertainty in the requested pressure, volume, or temperature, model selection, systematic experimental errors, or extrapolation. States outside the sampled ranges of the observations used by the fit are flagged explicitly.
Python API
The downloadable example performs the same workflow through the public API and writes both CSV files:
Download eos_diagnostics_calculator_api.py
1"""Inspect one fitted EOS record and calculate representative properties."""
2
3from __future__ import annotations
4
5import argparse
6from pathlib import Path
7
8from quantas.api import eos
9
10
11def parse_args() -> argparse.Namespace:
12 """Return command-line arguments for the example."""
13 parser = argparse.ArgumentParser()
14 parser.add_argument("archive", type=Path, help="Quantas EOS HDF5 archive")
15 parser.add_argument(
16 "--slot",
17 default=None,
18 help="Accepted DOMAIN/TARGET slot when the archive contains several results",
19 )
20 parser.add_argument(
21 "--record-id",
22 type=int,
23 default=None,
24 help="Explicit immutable record instead of the accepted result",
25 )
26 parser.add_argument(
27 "--output-directory",
28 type=Path,
29 default=Path("eos_postfit"),
30 help="Directory receiving diagnostic and calculator CSV files",
31 )
32 return parser.parse_args()
33
34
35def main() -> None:
36 """Evaluate diagnostics and a small domain-appropriate state grid."""
37 args = parse_args()
38 args.output_directory.mkdir(parents=True, exist_ok=True)
39
40 diagnostics = eos.diagnose(
41 args.archive,
42 slot=args.slot,
43 record_id=args.record_id,
44 )
45 diagnostic_path = eos.write_diagnostic_csv(
46 diagnostics,
47 args.output_directory / "diagnostics.csv",
48 overwrite=True,
49 )
50
51 domain = eos.record_domain(
52 args.archive,
53 slot=args.slot,
54 record_id=args.record_id,
55 ).value
56 calculation_kwargs: dict[str, list[float]]
57 if domain == "pv":
58 calculation_kwargs = {"pressure": [0.0, 5.0, 10.0]}
59 elif domain == "vt":
60 calculation_kwargs = {"temperature": [300.0, 600.0, 900.0]}
61 elif domain == "pvt":
62 calculation_kwargs = {
63 "pressure": [0.0, 5.0, 10.0],
64 "temperature": [300.0, 600.0, 900.0],
65 }
66 else: # pragma: no cover - guarded by the public API
67 raise RuntimeError(f"unsupported EOS domain: {domain}")
68 calculation = eos.calculate(
69 args.archive,
70 slot=args.slot,
71 record_id=args.record_id,
72 **calculation_kwargs,
73 )
74
75 calculation_path = eos.write_calculation_csv(
76 calculation,
77 args.output_directory / "calculated_properties.csv",
78 overwrite=True,
79 )
80
81 print("Quantas EOS post-fit analysis")
82 print("=============================")
83 print(f"Record : {calculation.record_id}")
84 print(f"Slot : {calculation.slot.key}")
85 print(f"Diagnostic observations : {diagnostics.nrows}")
86 print(f"Calculated states : {calculation.nrows}")
87 print(f"Diagnostics CSV : {diagnostic_path}")
88 print(f"Properties CSV : {calculation_path}")
89 if calculation.warnings:
90 print("Warnings:")
91 for warning in calculation.warnings:
92 print(f"- {warning}")
93
94
95if __name__ == "__main__":
96 main()