Skill Tracer
Skills › Scientific Agent Skills › pycalphad › model-and-validation.md

model-and-validation.md

169 lines · skills/pycalphad/references/model-and-validation.md at 5f2bc03

Open on GitHub
1# Composition basis and equilibrium validation
2

Composition basis and equilibrium validation

Raw lines 1 to 2
3## Original pedagogical database
4
5`assets/ideal-cu-ni.tdb` was written for this skill and is released under the skill's
6MIT license. Cu and Ni are labels for a hypothetical ideal binary; there are no fitted
7experimental data. The file defines one substitutional sublattice in FCC_A1 and LIQUID,
8with no excess mixing terms or pressure dependence. Its functions span 298.15–2000 K.
9
10With x = X(Ni), energies in J/mol, and R in J/(mol K):
12- G(FCC_A1) = R T [x ln x + (1-x) ln(1-x)].
13- G(LIQUID) = G(FCC_A1) + (1-x)(10000-10T) + x(12000-10T).
15At T=1100 K the liquid endmember offsets are -1000 and +1000 J/mol. Equality of both
16component chemical potentials gives x_liquid = 1/(1+exp(1000/(RT))) and
17x_solid = 1-x_liquid. The phase fraction of solid at bulk x between those endpoints is
18(x_bulk-x_liquid)/(x_solid-x_liquid). This is an independent analytic check of the
19numerical common tangent, phase compositions, and lever rule. The tests use pycalphad's
20R=8.3145 J/(mol K); the listed decimal values are package/model-specific regression values.
22The database intentionally lacks realistic heat capacities, lattice stabilities, excess
23mixing, magnetic terms, and pressure effects. Its apparent transitions are synthetic.
24Do not fit a real experiment to it or replace an assessed database with this example.

Original pedagogical database

assets/ideal-cu-ni.tdb was written for this skill and is released under the skill's MIT license. Cu and Ni are labels for a hypothetical ideal binary; there are no fitted experimental data. The file defines one substitutional sublattice in FCC_A1 and LIQUID, with no excess mixing terms or pressure dependence. Its functions span 298.15–2000 K.

With x = X(Ni), energies in J/mol, and R in J/(mol K):

  • G(FCC_A1) = R T [x ln x + (1-x) ln(1-x)].
  • G(LIQUID) = G(FCC_A1) + (1-x)(10000-10T) + x(12000-10T).

At T=1100 K the liquid endmember offsets are -1000 and +1000 J/mol. Equality of both component chemical potentials gives x_liquid = 1/(1+exp(1000/(RT))) and x_solid = 1-x_liquid. The phase fraction of solid at bulk x between those endpoints is (x_bulk-x_liquid)/(x_solid-x_liquid). This is an independent analytic check of the numerical common tangent, phase compositions, and lever rule. The tests use pycalphad's R=8.3145 J/(mol K); the listed decimal values are package/model-specific regression values.

The database intentionally lacks realistic heat capacities, lattice stabilities, excess mixing, magnetic terms, and pressure effects. Its apparent transitions are synthetic. Do not fit a real experiment to it or replace an assessed database with this example.

Raw lines 3 to 25
26## Basis conversions
28For measured elemental mass fractions w_i and molar masses M_i, calculate
29x_i = (w_i/M_i) / sum_j(w_j/M_j), including the dependent element. Weight percent is
30first divided by 100. Store the original composition, molar masses, and converted values
31with the run. The helper accepts only the resulting mole fractions and rejects a
32`mass_fraction` basis instead of treating weight percent as atomic fraction.
34`NP` is a molar phase fraction at total N=1. A mass phase fraction requires each phase's
35composition-weighted molar mass: w_phase = NP_phase*M_phase / sum(NP_j*M_j).
36Volume fractions require appropriate phase densities or molar volumes as well. Do not
37compare molar fractions directly to microscopy area fractions without explaining the
38conversion and sampling assumptions.
40For this elemental workflow, molar energies and amounts use a mole of non-vacancy
41atoms, not a mole of an arbitrary compound formula unit. `Model.GM` is normalized on
42that basis; `Model.G` is formula energy and can differ with sublattice site ratios.
43Site fractions `v.Y(phase, sublattice_index, species)` are internal degrees of freedom,
44not bulk mole fractions. Sublattice indices are zero-based.
46The tested conversion API is `v.get_mole_fractions({v.W('NI'): 0.5}, 'CU', db)`;
47it uses the TDB elemental masses and returns a condition mapping keyed by `v.X('NI')`.
48For the bundled masses, 50 wt% Ni is X(Ni)=0.5198487558, not 0.5. No conversion is
49performed by the JSON helper itself.

Basis conversions

For measured elemental mass fractions w_i and molar masses M_i, calculate x_i = (w_i/M_i) / sum_j(w_j/M_j), including the dependent element. Weight percent is first divided by 100. Store the original composition, molar masses, and converted values with the run. The helper accepts only the resulting mole fractions and rejects a mass_fraction basis instead of treating weight percent as atomic fraction.

NP is a molar phase fraction at total N=1. A mass phase fraction requires each phase's composition-weighted molar mass: w_phase = NP_phase*M_phase / sum(NP_j*M_j). Volume fractions require appropriate phase densities or molar volumes as well. Do not compare molar fractions directly to microscopy area fractions without explaining the conversion and sampling assumptions.

For this elemental workflow, molar energies and amounts use a mole of non-vacancy atoms, not a mole of an arbitrary compound formula unit. Model.GM is normalized on that basis; Model.G is formula energy and can differ with sublattice site ratios. Site fractions v.Y(phase, sublattice_index, species) are internal degrees of freedom, not bulk mole fractions. Sublattice indices are zero-based.

The tested conversion API is v.get_mole_fractions({v.W('NI'): 0.5}, 'CU', db); it uses the TDB elemental masses and returns a condition mapping keyed by v.X('NI'). For the bundled masses, 50 wt% Ni is X(Ni)=0.5198487558, not 0.5. No conversion is performed by the JSON helper itself.

Raw lines 26 to 50
51## Native model, property, and plotting contracts
53These examples were executed with pycalphad 0.11.2. Paths below are relative to the
54collection root. `Database(path)` parses local thermodynamic data; successful parsing
55does not establish that the assessment or every model feature is suitable. Review TDB
56warnings rather than suppressing all warnings. `db.elements`, `db.phases`, and each
57phase's `constituents`, `sublattices`, and `model_hints` expose the modeled system.
59`Model(db, components, phase)` builds a symbolic energy model, not an equilibrium.
60`calculate(..., output='GM', T=..., P=..., pdens=...)` samples its phase-constitution
61surface. Its `points` columns are ordered internal site fractions: use the model's
62`site_fractions` order; do not pass overall alloy compositions as points for a
63multisublattice model. In the one-sublattice teaching FCC model only, `[0.5, 0.5]`
64coincides with 50:50 bulk composition. `calculate` does not minimize across phases.
65Pass sampling options to `equilibrium` or `Workspace` through `calc_opts`, not as
66top-level `pdens`; unused equilibrium keyword arguments merely produce a warning.
68```python
69import numpy as np
70from pycalphad import Database, Workspace, variables as v
72db = Database('skills/pycalphad/assets/ideal-cu-ni.tdb')
73components = ['CU', 'NI', 'VA']
74phases = ['FCC_A1', 'LIQUID']
75conditions = {v.T: [900, 1100, 1300], v.P: 101325, v.N: 1, v.X('NI'): 0.5}
76wks = Workspace(db, components, phases, conditions, calc_opts={'pdens': 500})
77temperature = wks.get(v.T)
78phase_amounts = wks.get_dict('NP(*)')
79gm = wks.get('GM') # system molar Gibbs energy, J/mol
80x_solid = wks.get(v.X('FCC_A1', 'NI'))
81assert np.allclose(temperature, [900, 1100, 1300])
82assert np.isfinite(gm).all()
83assert np.isnan(x_solid[-1]) # absent solid; NaN is not a failed equilibrium
84```
86Use `get_dict` when phase multiplicity can change. A request such as `NP(FCC_A1)`
87or `X(FCC_A1,NI)` expands to `#1`, `#2`, ... properties for a miscibility gap; `get`
88can consequently return a list of arrays rather than one array. Preserve those sets
89and only sum their amounts when a total phase-name fraction is intended. Suffixes
90identify solver composition sets, not permanent ordered/disordered identities.
91Inspect compositions and site fractions before assigning physical labels. Property
92arrays squeeze singleton condition axes; use `wks.condition_axis_order` for grids.
94`GM` and `HM` are J/mol; `SM` and `CPM` are J/(mol K). A phase-qualified property,
95such as `GM(FCC_A1)`, is that stable composition set's property, whereas unqualified
96`GM` is phase-amount-weighted. `CPM` differentiates the model at fixed constitution;
97`wks.get('HM.T')` differentiates equilibrium enthalpy as temperature changes, including
98phase amounts and internal relaxation. They need not agree. At 1100 K the hypothetical
99two-phase example has `CPM=0` but `HM.T` approximately 1005.08 J/(mol K), matching a
100small central difference of equilibrium enthalpy. Derivatives at phase boundaries or
101invariant points require special care; a finite derivative is not a latent-heat integral.
103For a small binary phase diagram, the current top-level `binplot` uses the mapping API:
105```python
106import matplotlib.pyplot as plt
107from pycalphad import binplot
109ax, strategy = binplot(
110 db, components, phases,
111 {v.T: (900, 1300, 100), v.P: 101325, v.N: 1, v.X('NI'): (0.3, 0.7, 0.1)},
112 return_strategy=True,
114ax.set_title('Hypothetical ideal binary; not assessed Cu-Ni')
115ax.figure.savefig('hypothetical-binary.png', dpi=150, bbox_inches='tight')
116plt.close(ax.figure)
117```
119The default return is an Axes; `return_strategy=True` returns `(Axes, BinaryStrategy)`.
120Pass plot options in `plot_kwargs`; other keywords configure the mapper. `binplot`
121requires one varying composition and one varying potential coordinate. A fixed-bulk
122temperature sweep is better plotted directly from `Workspace` properties or the CSV.
123For `equilibrium`/`Workspace`, a tuple is `(start, stop, step)` with an excluded stop;
124a list is explicit values. Mapping uses tuple bounds and increments to follow phase
125boundaries, not just a Cartesian equilibrium grid. A smooth plot alone does not
126validate all stable fields; check representative points and refine difficult boundaries.
128Official sources checked for these contracts:
130- [Stable Model and variables source](https://github.com/pycalphad/pycalphad/tree/0.11.2/pycalphad)
131- [Stable Workspace and composition conditions](https://github.com/pycalphad/pycalphad/tree/0.11.2/pycalphad/core)
132- [Stable property framework](https://github.com/pycalphad/pycalphad/tree/0.11.2/pycalphad/property_framework)
133- [Stable mapping compatibility API](https://github.com/pycalphad/pycalphad/blob/0.11.2/pycalphad/mapping/compat_api.py)
134- [Current property and ordering workflow](https://pycalphad.org/docs/latest/examples/2_Computing_Properties/4_EquilibriumWithOrdering.html)

Native model, property, and plotting contracts

These examples were executed with pycalphad 0.11.2. Paths below are relative to the collection root. Database(path) parses local thermodynamic data; successful parsing does not establish that the assessment or every model feature is suitable. Review TDB warnings rather than suppressing all warnings. db.elements, db.phases, and each phase's constituents, sublattices, and model_hints expose the modeled system.

Model(db, components, phase) builds a symbolic energy model, not an equilibrium. calculate(..., output='GM', T=..., P=..., pdens=...) samples its phase-constitution surface. Its points columns are ordered internal site fractions: use the model's site_fractions order; do not pass overall alloy compositions as points for a multisublattice model. In the one-sublattice teaching FCC model only, [0.5, 0.5] coincides with 50:50 bulk composition. calculate does not minimize across phases. Pass sampling options to equilibrium or Workspace through calc_opts, not as top-level pdens; unused equilibrium keyword arguments merely produce a warning.

import numpy as np
from pycalphad import Database, Workspace, variables as v

db = Database('skills/pycalphad/assets/ideal-cu-ni.tdb')
components = ['CU', 'NI', 'VA']
phases = ['FCC_A1', 'LIQUID']
conditions = {v.T: [900, 1100, 1300], v.P: 101325, v.N: 1, v.X('NI'): 0.5}
wks = Workspace(db, components, phases, conditions, calc_opts={'pdens': 500})
temperature = wks.get(v.T)
phase_amounts = wks.get_dict('NP(*)')
gm = wks.get('GM')                  # system molar Gibbs energy, J/mol
x_solid = wks.get(v.X('FCC_A1', 'NI'))
assert np.allclose(temperature, [900, 1100, 1300])
assert np.isfinite(gm).all()
assert np.isnan(x_solid[-1])         # absent solid; NaN is not a failed equilibrium

Use get_dict when phase multiplicity can change. A request such as NP(FCC_A1) or X(FCC_A1,NI) expands to #1, #2, ... properties for a miscibility gap; get can consequently return a list of arrays rather than one array. Preserve those sets and only sum their amounts when a total phase-name fraction is intended. Suffixes identify solver composition sets, not permanent ordered/disordered identities. Inspect compositions and site fractions before assigning physical labels. Property arrays squeeze singleton condition axes; use wks.condition_axis_order for grids.

GM and HM are J/mol; SM and CPM are J/(mol K). A phase-qualified property, such as GM(FCC_A1), is that stable composition set's property, whereas unqualified GM is phase-amount-weighted. CPM differentiates the model at fixed constitution; wks.get('HM.T') differentiates equilibrium enthalpy as temperature changes, including phase amounts and internal relaxation. They need not agree. At 1100 K the hypothetical two-phase example has CPM=0 but HM.T approximately 1005.08 J/(mol K), matching a small central difference of equilibrium enthalpy. Derivatives at phase boundaries or invariant points require special care; a finite derivative is not a latent-heat integral.

For a small binary phase diagram, the current top-level binplot uses the mapping API:

import matplotlib.pyplot as plt
from pycalphad import binplot

ax, strategy = binplot(
    db, components, phases,
    {v.T: (900, 1300, 100), v.P: 101325, v.N: 1, v.X('NI'): (0.3, 0.7, 0.1)},
    return_strategy=True,
)
ax.set_title('Hypothetical ideal binary; not assessed Cu-Ni')
ax.figure.savefig('hypothetical-binary.png', dpi=150, bbox_inches='tight')
plt.close(ax.figure)

The default return is an Axes; return_strategy=True returns (Axes, BinaryStrategy). Pass plot options in plot_kwargs; other keywords configure the mapper. binplot requires one varying composition and one varying potential coordinate. A fixed-bulk temperature sweep is better plotted directly from Workspace properties or the CSV. For equilibrium/Workspace, a tuple is (start, stop, step) with an excluded stop; a list is explicit values. Mapping uses tuple bounds and increments to follow phase boundaries, not just a Cartesian equilibrium grid. A smooth plot alone does not validate all stable fields; check representative points and refine difficult boundaries.

Official sources checked for these contracts:

Raw lines 51 to 135
136## Phase selection and solver checks
138`VA` supports vacancy sublattices and is omitted from overall elemental mass balance.
139Select actual elements present in the database. A required vacancy or other constituent
140missing from every active species in one sublattice can make a phase unavailable.
141The helper rejects phase lists that pycalphad filters, including redundant coupled
142order/disorder selections, so the agent can review rather than silently alter them.
144Excluding a phase can lower the number of stable phases and change apparent solubility.
145An excluded phase list is therefore scientific provenance. Preserve each composition
146set in miscibility gaps: summing fractions by phase name is useful for totals but loses
147the two distinct tie-line endpoints. Never use a vertex index as a persistent phase ID.
149Mass balance, phase-sum checks, and finite Gibbs energy are necessary but not sufficient.
150Doubled `pdens` only tests one initialization-density change. Repeat with more density,
151inspect nearby conditions, compare alternative candidate phase sets, or use a trusted
152reference calculation when the result affects a scientific conclusion. Degenerate phase
153fractions at invariant points may be nonunique even when Gibbs energies agree. The
154helper's flags should be interpreted with that thermodynamic context.
156The native regression suite also loads the small `alni_dupin_2001.tdb` shipped in
157pycalphad 0.11.2, checks order/disorder filtering, and verifies an Al-Ni equilibrium
158point at 1000 K and X(Al)=0.25. That is a release integration check, not independent
159experimental validation of the database. No proprietary database is bundled or used.
161Parameter uncertainty is separate from numerical sampling sensitivity. Without an
162assessment's covariance/posterior or another justified uncertainty model, doubled
163`pdens` cannot yield confidence intervals. Database coverage, omitted phases, reference
164states, and extrapolation can dominate errors even when two numerical runs agree.
166Preserve the actual TDB with the output under its licensing terms. A SHA-256 identifies
167bytes but does not document assessment quality, reference-state conventions, or the
168validity range. Include the database authors' citation and uncertainty/coverage limits
169in the final scientific report.

Phase selection and solver checks

VA supports vacancy sublattices and is omitted from overall elemental mass balance. Select actual elements present in the database. A required vacancy or other constituent missing from every active species in one sublattice can make a phase unavailable. The helper rejects phase lists that pycalphad filters, including redundant coupled order/disorder selections, so the agent can review rather than silently alter them.

Excluding a phase can lower the number of stable phases and change apparent solubility. An excluded phase list is therefore scientific provenance. Preserve each composition set in miscibility gaps: summing fractions by phase name is useful for totals but loses the two distinct tie-line endpoints. Never use a vertex index as a persistent phase ID.

Mass balance, phase-sum checks, and finite Gibbs energy are necessary but not sufficient. Doubled pdens only tests one initialization-density change. Repeat with more density, inspect nearby conditions, compare alternative candidate phase sets, or use a trusted reference calculation when the result affects a scientific conclusion. Degenerate phase fractions at invariant points may be nonunique even when Gibbs energies agree. The helper's flags should be interpreted with that thermodynamic context.

The native regression suite also loads the small alni_dupin_2001.tdb shipped in pycalphad 0.11.2, checks order/disorder filtering, and verifies an Al-Ni equilibrium point at 1000 K and X(Al)=0.25. That is a release integration check, not independent experimental validation of the database. No proprietary database is bundled or used.

Parameter uncertainty is separate from numerical sampling sensitivity. Without an assessment's covariance/posterior or another justified uncertainty model, doubled pdens cannot yield confidence intervals. Database coverage, omitted phases, reference states, and extrapolation can dominate errors even when two numerical runs agree.

Preserve the actual TDB with the output under its licensing terms. A SHA-256 identifies bytes but does not document assessment quality, reference-state conventions, or the validity range. Include the database authors' citation and uncertainty/coverage limits in the final scientific report.

Raw lines 136 to 169