EOS table consistency
Thermodynamic identities of a regular (log10 P, log10 T) table, written as relative residuals that vanish for an exact equation of state: cp = T (ds/dT)_P, the Maxwell relation (ds/dP)_T = -alpha / rho, alpha = -(1/rho) (drho/dT)_P, (du/dT)_P = cp - P alpha / rho, nabla_ad = P alpha / (rho cp) and cp - cv = T alpha^2 / (rho kappa_T). The derivatives are central differences in ln P and ln T with a step of k grid cells; a stencil counts only when all its cells hold numbers of one phase, so a phase boundary or a missing cell never enters a derivative. The input is a table in the layout of zalmoxis.eos_export.load_paleos_all_properties.
check takes a table on a grid uniform in log10 P and log10 T (it rejects an axis whose log10 values lie more than 4e-9 off a linear fit; the coordinates of the PALEOS tables lie at most 2.1e-9 off, while the Chabrier 2021 tables, stored at fewer digits, lie about 1e-5 off and are not checked; a smooth drift of the step with a spread of 2e-6 lies 8e-7 off and would give false failures with the mean step. The floor adds the rounding of the stored coordinates in the algebraic factor of each identity, ln 10 times the departure of log10 P and log10 T from their fit, and in the mean step of the derivative axis, 2 eps over the span of the axis in ln units (5e-7 on a 9-point axis at a step of 0.001 dex, at most 2.4e-9 on the PALEOS axes), so an exact table whose stored axes lie up to 4e-9 off passes; the term decides where it is amplified, as by T alpha in the alpha identity (an exact table with T alpha = 200 and a T axis 2.1e-9 off its fit passes). The check assumes axes made by linspace or arange in log10 with the values computed at those points: if the true points depart from a uniform grid, the derivatives carry that error, and on a 1351 x 380 Murnaghan table at 9 digits with values computed at log10 P and log10 T moved by numpy.random.default_rng(seed).uniform(-1e-9, 1e-9), uniform axes stored, seeds 0 to 4 give 2,313 to 2,727 false cp, 3,620 to 4,641 maxwell, 10,835 to 15,004 dudt and 0 to 16 alpha failures) and tests each cell where 4 cells on each side along the axis of the derivative hold the differentiated column in one phase: a derivative identity fails where the Richardson residual R_x = (4 R_h - R_2h) / 3 exceeds the round-off floor of the stored values plus twice the h^4 term measured at that cell, (R_x(2h) - R_x(h)) / 15; nabla_ad is held to its floor alone. Each identity reads only its own columns (INPUTS), so a column it does not read never blocks it. floors gives that floor for a stored relative precision eps (5e-9 for 9 significant digits). signs flags cells with a non-positive density or heat capacity, cv above cp, or a density that falls with pressure, at the same floor; each condition tests the cells where its values are not NaN, and an infinite value breaks every condition but the density slope. A missing value (NaN) leaves its cells untested; a stored 0 or inf is a value. Where all values an identity reads are present but its residual or bound is not finite, the cell fails; the PALEOS H2O table stores cp = cv = alpha = 0 in 15,334 cells at 100 to 295 K, which fail in this way, and a column that is 0 or inf in every cell makes each identity whose normalization it zeroes or makes infinite fail in every present cell: with alpha = 0 everywhere, maxwell, alpha, nabla_ad and cp_cv fail, while dudt, normalized by T (cp - P alpha / rho), stays tested. An identity is skipped only when no cell holds all values it reads, on the full stencil of a derivative identity (4 cells on each side in one phase); a table where every identity is skipped raises. A tested cell whose bound is 1 or more cannot detect a missing derivative (a residual of -1 passes there); each identity counts such cells as blind, and the report tool prints them next to the present, tested and failing cells. The report tool gives the reason for a skipped identity (an input column without a value, or no full stencil), names an input column that is 0 or inf in every cell that holds a value, and names the columns without a value, if any, and what each identity needs when every identity is skipped.
Resolution at 9 stored digits, measured at one cell of an exact Murnaghan table at 150 points per decade (P = 46 MPa, T = 1,581 K): a single stored value off by 2e-8 (relative) or more fails at the cells one step from it, and by 3e-7 (entropy) or 1e-6 (density) or more also at the cells two steps from it. The exact ideal gas and Murnaghan tables give no failing cell at this precision. check raises a smaller eps to EPS_FLOOR = 5e-9, so every table, also one stored in full double precision, is checked at the 9-digit floor, and a single value off by about 1e-8 (relative) or less is out of reach on any table. cv enters only through cp - cv, whose floor at low pressure lies far above 1e-6, so a cv fault there is found only when it is large.
Detection of a 1e-6 fault depends on the grid: on the Aragog P-T layout (check_pt), on the 200 x 200 grid that PROTEUS writes (rows at 70, 176 and 281 GPa) a planted 1e-6 entropy was found at all four cells one and two steps away from about 2000 K up, at 1 to 3 of them at 730 K and at none at 443 K, where the linear temperature step is large against T; a planted 1e-6 density was found at all four from 730 K up and at 3 of 4 at 443 K; two such faults were missed on a 60 x 60 grid, where the measured h^4 term is larger. check_pt takes axes that are one-dimensional, finite, increasing (T positive, P not negative), with at least 2 points, and uniform or uniform in log within PT_GRID_FIT = 1e-6 of the step plus the stored precision; its floor adds the rounding of the stored P and T coordinates, taken as the departure of each coordinate from the fit of its axis, so an exact table with 9-digit axes passes and a planted 1e-6 entropy or density value is still found one and two steps away; heat capacity and thermal expansion enter only the algebraic factor: a planted 1e-6 value there is not found, and one of 1e-4 only at its own cell. On a geometric axis the measured h^4 term is larger, and a planted 1e-5 entropy value is still found. Each check_pt identity also gives fill, the number of fill cells (1e-15, written by eos_export for a cell without data) per file: a clipped block inside the valid region counts as missing, and the fill count shows its size. Tables that eos_export writes by linear interpolation of the PALEOS tables hold the identities only to the interpolation error: with the PROTEUS settings (2-phase MgSiO3, 200 x 200, P up to 350 GPa) 86 to 96 % of the tested cells fail, with residuals of about 1e-4. tools/validation/eos_table_consistency_report.py writes every failing cell of a PALEOS-format table to a CSV file.
consistency
Thermodynamic consistency residuals of a regular (log10 P, log10 T) EOS table.
Each identity is written as a relative residual that is 0 for an exact EOS. The derivatives are central differences in x = ln P and y = ln T with a step of k grid cells; a stencil counts only when all its cells hold numbers of one phase, so a phase boundary never enters a derivative.
check(table, eps)
Test each cell against the Richardson bound of each identity.
A derivative identity fails where |R_x(h)| > floor_x + FACTOR * est, with R_x(kh) = (4 R_k - R_2k) / 3, floor_x = (4 floor_1 + floor_2) / 3 and est = |R_x(2h) - R_x(h)| / 15, the measured h^4 term of R_x(h) at the cell. nabla_ad fails where |R| > floor. At 9 stored digits (eps 5e-9) the exact ideal gas and Murnaghan tables give no failing cell, and one planted value is found at the cells one and two steps from it.
The floor uses eps_eff = max(eps, EPS_FLOOR): every table is checked at the 9-digit
floor at best, so a single value off by about 1e-8 (relative) or less is out of reach on any
table. Each identity reads only its own columns (INPUTS). A derivative identity is
tested only where 4 cells on each side along its axis hold its differentiated column in one
phase; NaN marks a missing value, and a stored 0 or inf is a value. Where all values an
identity reads are present but its residual or bound is not finite, the cell fails; an
identity is skipped only when no cell holds all values it reads, on the full stencil of a
derivative identity (4 cells on each side in one phase). On a coarse grid a cell can lie
outside the range where the Richardson estimate holds (R_x(2h) / R_x(h) far from 16), and
an exact table can fail there: 1 cp cell of the Murnaghan table at 10 points per decade over
P 1e5 to 10^11.5 Pa and T 300 to 7.5e3 K, and 29 cp_cv cells of the 60 x 60 Murnaghan table
over P 1e5 to 1e11 Pa and T 500 to 5000 K, where cp and cv are positive.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
dict
|
As for |
required |
eps
|
float
|
Relative precision of the stored columns (0.5 x 10^(1 - n) for n significant digits). |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Identity name -> dict with |
Raises:
| Type | Description |
|---|---|
ValueError
|
If eps is not a real number in [0, 1), an axis has fewer than 2 points or is not finite, increasing and uniform, a column is off the shape of the grid, or every identity is skipped. |
Source code in src/zalmoxis/eos/consistency.py
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | |
check_pt(tables, eps)
check for the Aragog P-T tables that eos_export writes (no u, cv or nabla_ad).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tables
|
dict
|
|
required |
eps
|
float
|
Relative precision of the stored values; raised to |
required |
Returns:
| Type | Description |
|---|---|
dict
|
As for |
Raises:
| Type | Description |
|---|---|
ValueError
|
If eps is not a real number in [0, 1), an axis is not 1-D, has fewer than 2 points, is
not finite and increasing, or departs from a uniform or geometric grid by more than
|
Notes
The floor includes the rounding of the stored P and T coordinates: a central difference over x[i-k], x[i+k] gains |D| (e[i+k] + e[i-k]) / (x[i+k] - x[i-k]), with e the departure of each coordinate from the uniform or geometric fit of its axis plus 4 ulp.
Source code in src/zalmoxis/eos/consistency.py
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 | |
floors(table, eps, k=1)
Round-off bound of each residual at step k for columns stored to relative eps.
For R + 1 = D / Q the bound is (E / |Q| + |D / Q| q) eps + |D / Q| c, with E the error of the central difference from its two stored ends, q the relative error of Q, and c the relative error from the stored P and T coordinates: in Q (e_P and e_T, ln 10 times the departure of log10 P and log10 T from their linear fit) and in the mean step of the derivative axis (2 eps / span, span = (n - 1) h in ln units). nabla_ad, with no derivative, gets |R + 1| (4 eps + e_P) from its four stored factors and P.
Source code in src/zalmoxis/eos/consistency.py
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |
signs(table, eps)
Cells that break a sign condition beyond the stored precision eps.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
dict
|
As for |
required |
eps
|
float
|
Relative precision of the stored columns; raised to |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Condition name -> dict with |
Raises:
| Type | Description |
|---|---|
ValueError
|
If eps is not a real number in [0, 1), or the axes or shapes fail as for |
Source code in src/zalmoxis/eos/consistency.py
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | |
residuals(table, k=1)
Relative residuals of the identities at stencil step k cells.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
dict
|
|
required |
k
|
int
|
Stencil step in grid cells. |
1
|
Returns:
| Type | Description |
|---|---|
dict
|
Identity name -> residual array on the grid (NaN where it cannot be formed):
|
Source code in src/zalmoxis/eos/consistency.py
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | |
derivative(f, seg, axis, k, h)
d f / d(axis) with step k h, NaN where the stencil leaves one segment.
Source code in src/zalmoxis/eos/consistency.py
63 64 65 | |
segments(table, axis, cols=COLUMNS, valid=np.isfinite, code=None)
Label runs of cells of one phase along axis; cells where valid (a function of a
column) is false for one of cols get -1. code is _phase_code(table) if given.
Source code in src/zalmoxis/eos/consistency.py
34 35 36 37 38 39 40 41 42 | |