calphad-io 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. calphad_io-0.1.1/.gitignore +55 -0
  2. calphad_io-0.1.1/CHANGELOG.md +62 -0
  3. calphad_io-0.1.1/CONTRIBUTING.md +98 -0
  4. calphad_io-0.1.1/LICENSE +21 -0
  5. calphad_io-0.1.1/PKG-INFO +308 -0
  6. calphad_io-0.1.1/README.md +275 -0
  7. calphad_io-0.1.1/pyproject.toml +84 -0
  8. calphad_io-0.1.1/src/calphad_io/__init__.py +244 -0
  9. calphad_io-0.1.1/src/calphad_io/cli.py +325 -0
  10. calphad_io-0.1.1/src/calphad_io/dat/__init__.py +48 -0
  11. calphad_io-0.1.1/src/calphad_io/dat/layout.py +217 -0
  12. calphad_io-0.1.1/src/calphad_io/dat/project.py +174 -0
  13. calphad_io-0.1.1/src/calphad_io/dat/reader.py +685 -0
  14. calphad_io-0.1.1/src/calphad_io/dat/tokens.py +186 -0
  15. calphad_io-0.1.1/src/calphad_io/dat/writer.py +413 -0
  16. calphad_io-0.1.1/src/calphad_io/errors.py +88 -0
  17. calphad_io-0.1.1/src/calphad_io/model.py +315 -0
  18. calphad_io-0.1.1/src/calphad_io/py.typed +0 -0
  19. calphad_io-0.1.1/src/calphad_io/tdb/__init__.py +699 -0
  20. calphad_io-0.1.1/src/calphad_io/validate.py +705 -0
  21. calphad_io-0.1.1/tests/conftest.py +95 -0
  22. calphad_io-0.1.1/tests/data/Al-Cu-Y.tdb +475 -0
  23. calphad_io-0.1.1/tests/data/Al-Fe_sundman2009.tdb +413 -0
  24. calphad_io-0.1.1/tests/data/Al-Mg_Zhong.tdb +103 -0
  25. calphad_io-0.1.1/tests/data/AlMg-Liang.dat +314 -0
  26. calphad_io-0.1.1/tests/data/COST507.tdb +9129 -0
  27. calphad_io-0.1.1/tests/data/CsI-Pham.dat +211 -0
  28. calphad_io-0.1.1/tests/data/CuFeC-Kang.dat +491 -0
  29. calphad_io-0.1.1/tests/data/Dixon-Na-K-Cl-I.dat +697 -0
  30. calphad_io-0.1.1/tests/data/FeMnCaS-1.dat +672 -0
  31. calphad_io-0.1.1/tests/data/FeTiVO.dat +692 -0
  32. calphad_io-0.1.1/tests/data/HO.dat +120 -0
  33. calphad_io-0.1.1/tests/data/KF-NIF2_switched.dat +223 -0
  34. calphad_io-0.1.1/tests/data/Kaye_Pd-Ru-Tc-Mo.dat +582 -0
  35. calphad_io-0.1.1/tests/data/MQMQA-tern-tests.dat +434 -0
  36. calphad_io-0.1.1/tests/data/NobleMetals-Kaye.dat +582 -0
  37. calphad_io-0.1.1/tests/data/Ocadiz-Flores.dat +317 -0
  38. calphad_io-0.1.1/tests/data/PdRuTcMo.dat +2597 -0
  39. calphad_io-0.1.1/tests/data/Shishin_Fe-Sb-O-S_slag.dat +142 -0
  40. calphad_io-0.1.1/tests/data/Viitala.dat +409 -0
  41. calphad_io-0.1.1/tests/data/ZIRC-test64.dat +393 -0
  42. calphad_io-0.1.1/tests/data/ZrH-Dupin.dat +354 -0
  43. calphad_io-0.1.1/tests/data/al_parameter.tdb +6 -0
  44. calphad_io-0.1.1/tests/data/alfe_sei.TDB +178 -0
  45. calphad_io-0.1.1/tests/data/corrupt/duplicate_parameter.TDB +34 -0
  46. calphad_io-0.1.1/tests/data/corrupt/duplicate_phase.TDB +35 -0
  47. calphad_io-0.1.1/tests/data/corrupt/empty.TDB +0 -0
  48. calphad_io-0.1.1/tests/data/corrupt/truncated_counts.dat +120 -0
  49. calphad_io-0.1.1/tests/data/corrupt/unbalanced_parens.TDB +33 -0
  50. calphad_io-0.1.1/tests/data/corrupt/undefined_constituent.TDB +33 -0
  51. calphad_io-0.1.1/tests/data/corrupt/undefined_element.dat +120 -0
  52. calphad_io-0.1.1/tests/data/corrupt/undefined_phase.TDB +33 -0
  53. calphad_io-0.1.1/tests/data/corrupt/undefined_type_definition.TDB +31 -0
  54. calphad_io-0.1.1/tests/data/crfe_bcc_magnetic.tdb +33 -0
  55. calphad_io-0.1.1/tests/data/diffusion.tdb +98 -0
  56. calphad_io-0.1.1/tests/data/ionic_liquid_metal_minimal.tdb +48 -0
  57. calphad_io-0.1.1/tests/test_api.py +228 -0
  58. calphad_io-0.1.1/tests/test_roundtrip.py +125 -0
  59. calphad_io-0.1.1/tests/test_validate.py +178 -0
@@ -0,0 +1,55 @@
1
+ # Secrets -- never commit these
2
+ *.key
3
+ *.pem
4
+ *.p12
5
+ *.pfx
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+ secrets/
10
+ credentials.json
11
+ *.token
12
+
13
+ # Python
14
+ __pycache__/
15
+ *.py[cod]
16
+ *$py.class
17
+ *.so
18
+ .Python
19
+ build/
20
+ dist/
21
+ *.egg-info/
22
+ *.egg
23
+ .eggs/
24
+ wheels/
25
+
26
+ # Virtual environments
27
+ .venv/
28
+ venv/
29
+ env/
30
+ ENV/
31
+
32
+ # Tooling caches
33
+ .pytest_cache/
34
+ .mypy_cache/
35
+ .ruff_cache/
36
+ .coverage
37
+ .coverage.*
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ coverage.xml
42
+ *.cover
43
+
44
+ # Editors and OS
45
+ .idea/
46
+ .vscode/
47
+ *.swp
48
+ *.swo
49
+ *~
50
+ .DS_Store
51
+ Thumbs.db
52
+
53
+ # Scratch
54
+ /tmp/
55
+ scratch/
@@ -0,0 +1,62 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.1] - 2026-10-04
10
+
11
+ ### Changed
12
+
13
+ - Packaging and release metadata only; no code changes. Added complete PyPI
14
+ metadata (PEP 639 `license` expression, author, project URLs and classifiers)
15
+ and a Trusted Publishing (OIDC) release workflow that publishes on `v*` tags.
16
+ - README install instructions corrected: `calphad-io` is not yet published to
17
+ PyPI, so the non-working `pip install calphad-io` command was replaced with a
18
+ working install-from-source command.
19
+
20
+ ## [0.1.0] - 2026-09-29
21
+
22
+ Initial release.
23
+
24
+ ### Added
25
+
26
+ - **TDB reader and writer.** Parses `ELEMENT`, `SPECIES`, `FUNCTION`,
27
+ `TYPE_DEFINITION`, `DEFINE_SYSTEM_DEFAULT`, `DEFAULT_COMMAND`, `PHASE`,
28
+ `CONSTITUENT` and `PARAMETER`, honouring Thermo-Calc's abbreviated keywords
29
+ (`CONST`, `PARA`, `TYPE_DEF`, `TEMP_LIM`) and its `$` comment syntax. Commands
30
+ the library does not understand are preserved verbatim rather than discarded.
31
+ - **Byte-exact TDB round-trip.** A database that is read and written back
32
+ without modification is emitted from its original source text and is
33
+ byte-for-byte identical to the input.
34
+ - **DAT reader and writer.** Parses the ChemSage/FactSage format including
35
+ `IDMX`, `RKMP`, `RKMPM`, `SUBL`, `SUBLM`, `QKTO`, `SUBQ` and `SUBG` phase
36
+ models, with per-endmember thermodynamic data options, P-T molar-volume terms,
37
+ magnetic factors, QKTO chemical groups, MQMQA quadruplet coordinations and
38
+ chemical-group overrides.
39
+ - **Semantically exact DAT round-trip.** Every element, phase, endmember,
40
+ interval coefficient, excess term and magnetic term is preserved exactly, and
41
+ every number is emitted in a form that converts back to the identical
42
+ `float`.
43
+ - **Validator.** 20 finding codes across `error`, `warning` and `info`
44
+ severities, returned as structured findings. `validate()` never raises.
45
+ - **CLI** with `info`, `parse`, `validate` and `convert` subcommands, all
46
+ supporting `--json`, with stable exit codes.
47
+ - **Zero runtime dependencies.** Pure standard library, Python 3.10+.
48
+ - Test suite built on 26 real databases from pycalphad and Thermochimica
49
+ (9 TDB, 17 DAT; 25 round-trip cleanly and 1 is refused by design), plus
50
+ nine deliberately corrupted fixtures.
51
+
52
+ ### Notes
53
+
54
+ - Cross-format conversion (TDB to DAT and DAT to TDB) is deliberately refused.
55
+ Neither direction can be done faithfully from the information the other format
56
+ carries, and guessing would produce a file that computes the wrong energy.
57
+ - Round-trip fidelity is verified against `pycalphad` as an independent reader:
58
+ it reports all 25 regenerated databases it can read as semantically identical
59
+ to the originals, including model hints. (`FeMnCaS-1.dat` uses `SUBI`, which
60
+ pycalphad cannot read either, so it is refused rather than compared.)
61
+ - That comparison is reproducible end to end via `scripts/generate.py` and
62
+ `scripts/verify.py`; see `scripts/README.md`.
@@ -0,0 +1,98 @@
1
+ # Contributing
2
+
3
+ Thanks for considering a contribution. This library exists to make CALPHAD
4
+ databases safe to move between tools, and the most valuable contributions are
5
+ **real databases** and **bug reports backed by one**.
6
+
7
+ ## Before you write code, read this
8
+
9
+ `calphad-io` is a workaround, not a destination. `pycalphad` PR
10
+ [#422](https://github.com/pycalphad/pycalphad/pull/422) already implements much
11
+ of the same thing and has been open, unmerged and dormant since 2022. If you are
12
+ here to improve `.DAT` writing in general, **your work is probably more valuable
13
+ applied there than here.**
14
+
15
+ Concretely, if you are about to implement a feature:
16
+
17
+ 1. Check whether #422 already has it.
18
+ 2. If it does, consider contributing the *evidence* instead — a test fixture, a
19
+ round-trip case, a bug report with a real database — to that pull request.
20
+ 3. Only add it here if the goal is specifically to keep a standalone,
21
+ dependency-free library working.
22
+
23
+ Contributions that improve the format knowledge (new fixtures, newly documented
24
+ quirks, corrections to the `## Limitations` section) are welcome either way, and
25
+ are the ones most likely to outlive this project.
26
+
27
+ ## Getting set up
28
+
29
+ ```bash
30
+ git clone https://github.com/beduldul/calphad-io
31
+ cd calphad-io
32
+ python -m venv .venv && . .venv/bin/activate
33
+ pip install -e ".[dev]"
34
+ pytest
35
+ ```
36
+
37
+ The suite runs in under a second and needs no network access.
38
+
39
+ ## The two rules that matter
40
+
41
+ **1. Round-trip fidelity is the headline feature.** Any change to the readers or
42
+ writers must keep `pytest tests/test_roundtrip.py` green. If you add support for
43
+ a construct, add the file that exercises it to `tests/data/`.
44
+
45
+ **2. A validator that cries wolf is worse than none.** Every check must be
46
+ justified against a real file. Before adding a finding code, ask: *does a
47
+ healthy database ever trip this?* If yes, it is a `warning` or an `info`, not an
48
+ `error` -- or it is not a finding at all. Several checks in `validate.py` carry a
49
+ comment explaining the real file that taught us the rule; please do the same.
50
+
51
+ ## Reporting a bug
52
+
53
+ The single most useful thing you can attach is a database that reproduces it. If
54
+ you cannot share the file, reduce it to a minimal snippet and say which construct
55
+ is involved.
56
+
57
+ For a round-trip bug, please include:
58
+
59
+ ```python
60
+ import calphad_io
61
+ db = calphad_io.load("your-file.TDB")
62
+ print(calphad_io.dumps(db) == open("your-file.TDB").read())
63
+ ```
64
+
65
+ ## Adding a database fixture
66
+
67
+ 1. Drop the file in `tests/data/`.
68
+ 2. Check the licence permits redistribution, and note the source in your pull
69
+ request. Databases from pycalphad and Thermochimica are already present.
70
+ 3. Run the suite. A new file is automatically picked up by the parametrised
71
+ tests in `tests/conftest.py`.
72
+ 4. If the library refuses the file, add it to `UNSUPPORTED` in `conftest.py`
73
+ with the reason, and open an issue.
74
+
75
+ ## Style
76
+
77
+ - Typed throughout. `mypy --strict` and `ruff` are configured in
78
+ `pyproject.toml`; both must pass.
79
+ - No runtime dependencies. The formats are text; do not reach for NumPy.
80
+ - Frozen dataclasses for anything in the object model. Return new objects
81
+ instead of mutating.
82
+ - Comments should explain *why*, especially where a real file forced an
83
+ unexpected decision. Those comments are the most valuable documentation in
84
+ this codebase.
85
+
86
+ ## What is out of scope
87
+
88
+ - Thermodynamic evaluation, phase-diagram calculation, model fitting. This is an
89
+ I/O library; it deliberately has no opinion about whether a model is
90
+ physically sensible.
91
+ - Cross-format conversion. See the README for why it is refused.
92
+ - FactSage 8.1 and later DAT files.
93
+
94
+ ## Commits and pull requests
95
+
96
+ Conventional Commits (`feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `chore:`).
97
+ Keep pull requests focused. If you change the public API or add a finding code,
98
+ update `README.md` in the same pull request.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 beduldul
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.5
2
+ Name: calphad-io
3
+ Version: 0.1.1
4
+ Summary: Read, write and validate CALPHAD thermodynamic databases (Thermo-Calc .TDB and ChemSage/FactSage .DAT)
5
+ Project-URL: Homepage, https://github.com/beduldul/calphad-io
6
+ Project-URL: Repository, https://github.com/beduldul/calphad-io
7
+ Project-URL: Source, https://github.com/beduldul/calphad-io
8
+ Project-URL: Issues, https://github.com/beduldul/calphad-io/issues
9
+ Project-URL: Changelog, https://github.com/beduldul/calphad-io/blob/main/CHANGELOG.md
10
+ Author-email: Abdul Afif Al Kaysan <alkaysan07@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: calphad,chemsage,factsage,materials-science,phase-diagram,tdb,thermo-calc,thermodynamics
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
23
+ Classifier: Topic :: Scientific/Engineering :: Physics
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Provides-Extra: dev
27
+ Requires-Dist: build>=1.2; extra == 'dev'
28
+ Requires-Dist: mypy>=1.8; extra == 'dev'
29
+ Requires-Dist: pytest>=7; extra == 'dev'
30
+ Requires-Dist: ruff>=0.4; extra == 'dev'
31
+ Requires-Dist: twine>=5.0; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ [![CI](https://github.com/beduldul/calphad-io/actions/workflows/ci.yml/badge.svg)](https://github.com/beduldul/calphad-io/actions/workflows/ci.yml)
35
+ # calphad-io
36
+
37
+ **Read, write and validate CALPHAD thermodynamic databases — Thermo-Calc `.TDB`
38
+ and ChemSage/FactSage `.DAT` — with zero runtime dependencies.**
39
+
40
+ ## Read this first: this library is not the first attempt
41
+
42
+ Before you adopt `calphad-io`, you should know what it is and is not.
43
+
44
+ **There is already a ChemSage `.DAT` writer for pycalphad. It was written, and
45
+ then it was abandoned before it could be merged.**
46
+
47
+ * [`pycalphad` PR **#422**, "ENH: Implement writing of ChemSage DAT files"](https://github.com/pycalphad/pycalphad/pull/422)
48
+ is **open and unmerged**. It was opened **2022-06-17**, contains **97 commits,
49
+ all of them from 2022**, and is **+1042/−3 to a single file**.
50
+ * Its first review was `CHANGES_REQUESTED`, asking for tests. A maintainer
51
+ replied *"this is still high on my list"* in **August 2022**, and it has sat
52
+ since. The pull request has been dormant for four years.
53
+
54
+ So the honest framing is **not** "no tool exists". It is:
55
+
56
+ > **The tool was written and abandoned before merge, and is not installable by
57
+ > anyone.**
58
+
59
+ That distinction matters, and it changes what you should do with this project.
60
+
61
+ ### If you need a `.DAT` writer, consider helping #422 first
62
+
63
+ `calphad-io` exists because a usable artifact did not. It is not an argument that
64
+ the existing work should be discarded — it is a workaround for the fact that
65
+ #422 never landed. **If you use this library, please also consider contributing
66
+ to #422 or linking it from there.** The goal is for the CALPHAD community to have
67
+ a working DAT writer, not for this library to win. A revived #422 in pycalphad —
68
+ where the maintainers, the reviewers and the test suite already are — is a better
69
+ outcome than a parallel implementation.
70
+
71
+ The one thing `calphad-io` can offer that discussion is *evidence*: a corpus of
72
+ real databases and a round-trip harness that reads, writes and re-verifies them.
73
+ That evidence is more useful applied to #422 than hoarded here.
74
+
75
+ ## The problem
76
+
77
+ CALPHAD databases are the input to every thermodynamic calculation, and both
78
+ formats in circulation are text. Yet there is no general-purpose,
79
+ dependency-free library that reads *and writes* both:
80
+
81
+ * `pycalphad` reads `.TDB` and `.DAT` and writes `.TDB`, but has no `.DAT`
82
+ writer. Its maintainer explained why in
83
+ [pycalphad#413](https://github.com/pycalphad/pycalphad/issues/413) (open since
84
+ 2022-05-12): *"That was my main deterrent for not implementing a DAT writer, as
85
+ I don't have FactSage to test against as a reference implementation."*
86
+ * `Thermochimica` consumes `.DAT` and has no general writer.
87
+ * `OpenCalphad`'s `save_datformat` routine is marked in its own source as
88
+ *"writes a SOLGASMIX DAT format file. **not (ever?) finished**"*.
89
+ * The only widely-used tooling around the format is a syntax highlighter
90
+ ([`amkrajewski/TDB-Highlighter`](https://github.com/amkrajewski/TDB-Highlighter),
91
+ 13 stars).
92
+
93
+ `calphad-io` fills that gap. It is a parser, a writer and a validator. It does
94
+ **not** compute thermodynamics — it gets your database *to* the solver intact.
95
+
96
+ ## Scope, stated honestly
97
+
98
+ | | `.TDB` | `.DAT` |
99
+ |---|---|---|
100
+ | Read | yes | yes (subset — see Limitations) |
101
+ | Write | **byte-for-byte identical** round-trip | semantically identical round-trip |
102
+ | Validate | yes | yes |
103
+ | Convert to the other format | **refused, deliberately** | **refused, deliberately** |
104
+
105
+ * **TDB round-trip is byte-exact.** Nine real databases — including the 296 KB
106
+ `COST507.tdb` — parse and re-emit to the identical bytes.
107
+ * **DAT round-trip is semantically exact.** Every element, phase, endmember,
108
+ interval coefficient, excess term and magnetic term is preserved, and every
109
+ number is emitted in a form that converts back to the identical `float`.
110
+ Formatting and comments are not preserved: DAT files are hand-edited in
111
+ practice and no reader in the ecosystem depends on column positions.
112
+ * **Both are verified against an independent implementation.** `pycalphad` reads
113
+ the regenerated files as *identical* — same elements, species, phases,
114
+ sublattice constituents, model hints and every parameter expression — for
115
+ **all 25 regenerated files** — every file this library can read. This is
116
+ reproducible end to end: [`scripts/generate.py`](scripts/generate.py)
117
+ regenerates the corpus from `tests/data/` with this library, and
118
+ [`scripts/verify.py`](scripts/verify.py) re-checks it against the originals
119
+ with pycalphad. See [`scripts/README.md`](scripts/README.md).
120
+ * **Three of them were also compared thermodynamically.** For three regenerated
121
+ files the computed Gibbs energy was compared across their temperature ranges
122
+ and was **bit-identical** (relative difference exactly `0.00e+00`). This
123
+ comparison is not scripted in this repository.
124
+
125
+ ## Install
126
+
127
+ **Not yet on PyPI** — there is no `pip install calphad-io` yet. Install from
128
+ the repository:
129
+
130
+ ```bash
131
+ pip install git+https://github.com/beduldul/calphad-io.git
132
+ ```
133
+
134
+ To work on the library itself, clone it and install it editable, as described
135
+ in [`CONTRIBUTING.md`](CONTRIBUTING.md):
136
+
137
+ ```bash
138
+ git clone https://github.com/beduldul/calphad-io
139
+ cd calphad-io
140
+ pip install -e ".[dev]"
141
+ ```
142
+
143
+ No dependencies. Python 3.10+.
144
+
145
+ ## Use
146
+
147
+ ### Library
148
+
149
+ ```python
150
+ import calphad_io
151
+
152
+ db = calphad_io.load("steel.TDB")
153
+ print(db.summary())
154
+ # {'format': 'tdb', 'elements': 29, 'phases': 243, 'parameters': 1907, ...}
155
+
156
+ for phase in db.phases:
157
+ print(phase.name, phase.constituent_names())
158
+
159
+ # Round-trip: TDB comes back byte-for-byte
160
+ assert calphad_io.dumps(db) == open("steel.TDB").read()
161
+
162
+ # Validate before it reaches a solver
163
+ for finding in calphad_io.validate(db):
164
+ print(finding)
165
+ ```
166
+
167
+ ### Command line
168
+
169
+ ```bash
170
+ calphad-io info steel.TDB
171
+ calphad-io parse steel.TDB
172
+ calphad-io validate steel.TDB
173
+ calphad-io validate steel.TDB --json
174
+ calphad-io convert steel.TDB --to tdb -o canonical.TDB
175
+ ```
176
+
177
+ Exit codes are stable: `0` clean, `1` validation errors or a refused conversion,
178
+ `2` unparseable, `3` bad usage.
179
+
180
+ ## The object model
181
+
182
+ Eight immutable frozen dataclasses in `calphad_io.model` (`Format` is a plain
183
+ class of string constants, not a dataclass), shared by both formats. Nothing is
184
+ mutated after construction; transformations return new objects.
185
+
186
+ ```
187
+ Database
188
+ ├── format: "tdb" | "dat"
189
+ ├── elements: tuple[Element] name, reference_phase, mass, h298, s298
190
+ ├── species: tuple[Species] name, stoichiometry, charge
191
+ ├── phases: tuple[Phase] name, sublattices, type_code, model,
192
+ │ kind, is_dummy, is_stoichiometric
193
+ │ └── sublattices: tuple[Sublattice] constituents, ratio
194
+ ├── parameters: tuple[Parameter] kind, phase, constituents, order,
195
+ │ expression, source_line
196
+ ├── functions: tuple[Function] name, expression (TDB)
197
+ └── type_definitions: tuple[TypeDefinition] symbol, kind, options (TDB)
198
+ ```
199
+
200
+ Two design choices are worth calling out.
201
+
202
+ **Parameter expressions are text, never evaluated.** `calphad-io` checks that an
203
+ expression is well-formed enough to re-emit; it has no opinion about whether it
204
+ is thermodynamically sensible. That is the solver's job.
205
+
206
+ **`Database` is not the only record.** For DAT, the file's full structure — the
207
+ per-endmember thermodynamic data options, P-T molar-volume terms, magnetic
208
+ factors, QKTO chemical groups, MQMQA quadruplet coordinations — lives in a
209
+ `DatLayout` under `db.header["layout"]`. The generic `Parameter` list is a
210
+ *projection for inspection*, and the writer uses the layout. This is documented
211
+ in `calphad_io.dat.layout`.
212
+
213
+ ## Validation
214
+
215
+ `validate()` never raises. It returns `Finding` objects with a severity, a
216
+ stable code, a message and structured detail:
217
+
218
+ ```
219
+ ERROR duplicate_phase [phase BCC_A2]
220
+ phase 'BCC_A2' is declared 2 times
221
+ ERROR undefined_constituent [phase FCC_A1]
222
+ phase 'FCC_A1' references constituent 'XX', which is neither a
223
+ declared element nor a declared species
224
+ WARNING element_mass_missing [element CR]
225
+ element 'CR' has a mass of zero
226
+ ```
227
+
228
+ Run `calphad-io validate --json` for the same findings as machine-readable data.
229
+
230
+ The codes are: `duplicate_element`, `duplicate_phase`, `duplicate_parameter`,
231
+ `duplicate_function`, `duplicate_type_definition`, `no_phases`, `no_elements`,
232
+ `undefined_constituent`, `undefined_type_definition`, `unused_type_definition`,
233
+ `parameter_for_undefined_phase`, `empty_parameter_expression`,
234
+ `unbalanced_parentheses`, `constituent_count_mismatch`,
235
+ `phase_without_constituents`, `parameter_sublattice_mismatch`,
236
+ `suspicious_constituent_name`, `suspiciously_named_phase`,
237
+ `element_mass_missing`, `phase_has_no_parameters`.
238
+
239
+ ## Limitations
240
+
241
+ **Not supported at all:**
242
+
243
+ * **`SUBI` (ionic two-sublattice)** — not read, not written. Affects
244
+ `FeMnCaS-1.dat`, which this library refuses with a clear
245
+ `UnsupportedFeatureError`. `pycalphad` cannot read it either (pycalphad#418).
246
+ * **`IDVD` (real gas) and `IDWZ` (aqueous/Pitzer)** phase models.
247
+ * **Heat-capacity thermodynamic data options** (7–12). These express an
248
+ endmember as heat-capacity coefficients rather than a Gibbs energy
249
+ polynomial. The reader rejects them explicitly rather than mis-reading them;
250
+ `pycalphad` raises `NotImplementedError` here too.
251
+ * **Constant molar-volume options** (2, 5, 8, 11) and **P-T molar-volume
252
+ terms** are parsed but not interpreted.
253
+ * **FactSage ≥ 8.1 DAT.** Out of scope by design; the format changed and 8.0-era
254
+ tools cannot read the new files (pycalphad#413).
255
+ * **`DAT → TDB` and `TDB → DAT` conversion.** Refused, with an explanation of
256
+ exactly what would be lost. A TDB records no per-endmember thermodynamic data
257
+ option, no P-T molar-volume terms and no sublattice atom count; a DAT writer
258
+ needs all three. Guessing produces a file that looks right and computes the
259
+ wrong energy.
260
+
261
+ **Known caveats:**
262
+
263
+ * **No FactSage reference.** Correctness is evidenced by `pycalphad` reading the
264
+ output as identical (reproducible via [`scripts/verify.py`](scripts/verify.py))
265
+ and by bit-identical computed energies — *not* by FactSage accepting the file.
266
+ This is the same blocker the pycalphad maintainer named in 2022. It is reduced
267
+ but not eliminated, and the writer emits FactSage-8.0-style layout as
268
+ faithfully as the evidence allows.
269
+ * **The validator still over-reports in a few DAT cases.** Constituent names in
270
+ DAT are arbitrary human-readable labels with no enforced grammar
271
+ (pycalphad#419), so a name that is neither an element nor a resolvable formula
272
+ is reported as `undefined_constituent` even when a solver would accept it.
273
+ Treat DAT `undefined_constituent` findings as *review this* rather than
274
+ *this is broken*.
275
+ * **`duplicate_phase` for DAT is reported as `info`** when blocks differ, because
276
+ real databases (`Kaye_Pd-Ru-Tc-Mo.dat`, `PdRuTcMo.dat`) legitimately contain
277
+ several blocks with the same phase name.
278
+ * **No thermodynamic evaluation**, no phase-diagram calculation, no model
279
+ fitting. This is an I/O library.
280
+
281
+ ## Testing
282
+
283
+ ```bash
284
+ pip install -e ".[dev]"
285
+ pytest
286
+ ```
287
+
288
+ The test suite runs against **26 real databases** committed under `tests/data/`,
289
+ sourced from `pycalphad` and `Thermochimica`. **25 of them round-trip cleanly**:
290
+ the suite asserts, for every file this library can read, that a parse → write →
291
+ re-parse cycle preserves the model, and that all 9 TDB files come back
292
+ byte-for-byte. The 26th, `FeMnCaS-1.dat`, is refused by design rather than
293
+ round-tripped — it uses the `SUBI` model (see Limitations). Deliberately
294
+ corrupted copies under `tests/data/corrupt/` exercise the validator.
295
+
296
+ ## Prior art and credits
297
+
298
+ Built on the format knowledge accumulated in
299
+ [pycalphad](https://github.com/pycalphad/pycalphad) (Richard Otis, Brandon
300
+ Bocklund and contributors), particularly `pycalphad/io/cs_dat.py` and
301
+ `pycalphad/io/tdb.py`, and on the databases published by
302
+ [Thermochimica](https://github.com/ORNL-CEES/thermochimica) (ORNL-CEES). The
303
+ ChemSage `.DAT` layout was also cross-checked against OpenCalphad's unfinished
304
+ `save_datformat` routine.
305
+
306
+ ## License
307
+
308
+ MIT. See [LICENSE](LICENSE).