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.
- calphad_io-0.1.1/.gitignore +55 -0
- calphad_io-0.1.1/CHANGELOG.md +62 -0
- calphad_io-0.1.1/CONTRIBUTING.md +98 -0
- calphad_io-0.1.1/LICENSE +21 -0
- calphad_io-0.1.1/PKG-INFO +308 -0
- calphad_io-0.1.1/README.md +275 -0
- calphad_io-0.1.1/pyproject.toml +84 -0
- calphad_io-0.1.1/src/calphad_io/__init__.py +244 -0
- calphad_io-0.1.1/src/calphad_io/cli.py +325 -0
- calphad_io-0.1.1/src/calphad_io/dat/__init__.py +48 -0
- calphad_io-0.1.1/src/calphad_io/dat/layout.py +217 -0
- calphad_io-0.1.1/src/calphad_io/dat/project.py +174 -0
- calphad_io-0.1.1/src/calphad_io/dat/reader.py +685 -0
- calphad_io-0.1.1/src/calphad_io/dat/tokens.py +186 -0
- calphad_io-0.1.1/src/calphad_io/dat/writer.py +413 -0
- calphad_io-0.1.1/src/calphad_io/errors.py +88 -0
- calphad_io-0.1.1/src/calphad_io/model.py +315 -0
- calphad_io-0.1.1/src/calphad_io/py.typed +0 -0
- calphad_io-0.1.1/src/calphad_io/tdb/__init__.py +699 -0
- calphad_io-0.1.1/src/calphad_io/validate.py +705 -0
- calphad_io-0.1.1/tests/conftest.py +95 -0
- calphad_io-0.1.1/tests/data/Al-Cu-Y.tdb +475 -0
- calphad_io-0.1.1/tests/data/Al-Fe_sundman2009.tdb +413 -0
- calphad_io-0.1.1/tests/data/Al-Mg_Zhong.tdb +103 -0
- calphad_io-0.1.1/tests/data/AlMg-Liang.dat +314 -0
- calphad_io-0.1.1/tests/data/COST507.tdb +9129 -0
- calphad_io-0.1.1/tests/data/CsI-Pham.dat +211 -0
- calphad_io-0.1.1/tests/data/CuFeC-Kang.dat +491 -0
- calphad_io-0.1.1/tests/data/Dixon-Na-K-Cl-I.dat +697 -0
- calphad_io-0.1.1/tests/data/FeMnCaS-1.dat +672 -0
- calphad_io-0.1.1/tests/data/FeTiVO.dat +692 -0
- calphad_io-0.1.1/tests/data/HO.dat +120 -0
- calphad_io-0.1.1/tests/data/KF-NIF2_switched.dat +223 -0
- calphad_io-0.1.1/tests/data/Kaye_Pd-Ru-Tc-Mo.dat +582 -0
- calphad_io-0.1.1/tests/data/MQMQA-tern-tests.dat +434 -0
- calphad_io-0.1.1/tests/data/NobleMetals-Kaye.dat +582 -0
- calphad_io-0.1.1/tests/data/Ocadiz-Flores.dat +317 -0
- calphad_io-0.1.1/tests/data/PdRuTcMo.dat +2597 -0
- calphad_io-0.1.1/tests/data/Shishin_Fe-Sb-O-S_slag.dat +142 -0
- calphad_io-0.1.1/tests/data/Viitala.dat +409 -0
- calphad_io-0.1.1/tests/data/ZIRC-test64.dat +393 -0
- calphad_io-0.1.1/tests/data/ZrH-Dupin.dat +354 -0
- calphad_io-0.1.1/tests/data/al_parameter.tdb +6 -0
- calphad_io-0.1.1/tests/data/alfe_sei.TDB +178 -0
- calphad_io-0.1.1/tests/data/corrupt/duplicate_parameter.TDB +34 -0
- calphad_io-0.1.1/tests/data/corrupt/duplicate_phase.TDB +35 -0
- calphad_io-0.1.1/tests/data/corrupt/empty.TDB +0 -0
- calphad_io-0.1.1/tests/data/corrupt/truncated_counts.dat +120 -0
- calphad_io-0.1.1/tests/data/corrupt/unbalanced_parens.TDB +33 -0
- calphad_io-0.1.1/tests/data/corrupt/undefined_constituent.TDB +33 -0
- calphad_io-0.1.1/tests/data/corrupt/undefined_element.dat +120 -0
- calphad_io-0.1.1/tests/data/corrupt/undefined_phase.TDB +33 -0
- calphad_io-0.1.1/tests/data/corrupt/undefined_type_definition.TDB +31 -0
- calphad_io-0.1.1/tests/data/crfe_bcc_magnetic.tdb +33 -0
- calphad_io-0.1.1/tests/data/diffusion.tdb +98 -0
- calphad_io-0.1.1/tests/data/ionic_liquid_metal_minimal.tdb +48 -0
- calphad_io-0.1.1/tests/test_api.py +228 -0
- calphad_io-0.1.1/tests/test_roundtrip.py +125 -0
- 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.
|
calphad_io-0.1.1/LICENSE
ADDED
|
@@ -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
|
+
[](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).
|