Skip to content

PALEOS-API cache resolver

On-disk cache for PALEOS-API:* tables under $ZALMOXIS_ROOT/data/EOS_PALEOS_API/. Converts a (material, GridSpec) cache key into a concrete .dat path, regenerating via paleos_api.py on cache miss. Cache key is (PALEOS_SHA, GridSpec.hash_short()), both stamped into the file header so a missing file or a SHA mismatch (upstream PALEOS upgraded) triggers regeneration. The SHA guard is the backstop for upstream PALEOS changes: renaming _phase_eos_map or a boundary function on upstream produces a different SHA and invalidates the disk file even if the path still exists.

paleos_api_cache

Cache resolver for PALEOS-API:* live-tabulated tables.

Converts a (material, GridSpec) or (GridSpec,) cache key to an on-disk .dat path, regenerating via paleos_api.py on cache miss.

Cache layout under $ZALMOXIS_ROOT/data/EOS_PALEOS_API/::

unified_<material>_<sha10>_<gridhash>.dat
2phase_solid_<sha10>_<gridhash>.dat
2phase_liquid_<sha10>_<gridhash>.dat

Cache key is (PALEOS_SHA, GridSpec.hash_short()). Both are stamped in the generated file's header, so:

  1. A missing file → regenerate.
  2. An existing file whose header SHA does not match the currently installed PALEOS → regenerate (caught by _header_sha_matches).

The SHA guard is the backstop for upstream PALEOS changes: renaming _phase_eos_map or a boundary function on upstream will produce a different SHA even if the on-disk file still exists.

resolve_paleos_api_unified(material, grid, *, h2o_table_path=None, force=False, n_workers=-1)

Return the path to a cached unified PALEOS-API table, regenerating if stale.

Parameters:

Name Type Description Default
material ('iron', 'mgsio3', 'h2o')
'iron'
grid GridSpec
required
h2o_table_path str or None

AQUA table path, passed through to the generator when material=='h2o'.

None
force bool

If True, regenerate even if a valid cached file exists. Intended for debugging / explicit cache-invalidation commands.

False
n_workers int

Parallelism for the generator on cache-miss. Default -1 = os.cpu_count() (cold-cache builds should use all cores; cache hits don't run the generator at all).

-1
Source code in src/zalmoxis/eos/paleos_api_cache.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def resolve_paleos_api_unified(
    material: str,
    grid: GridSpec,
    *,
    h2o_table_path: str | None = None,
    force: bool = False,
    n_workers: int = -1,
) -> Path:
    """Return the path to a cached unified PALEOS-API table, regenerating if stale.

    Parameters
    ----------
    material : {'iron', 'mgsio3', 'h2o'}
    grid : GridSpec
    h2o_table_path : str or None
        AQUA table path, passed through to the generator when ``material=='h2o'``.
    force : bool
        If True, regenerate even if a valid cached file exists. Intended for
        debugging / explicit cache-invalidation commands.
    n_workers : int
        Parallelism for the generator on cache-miss. Default ``-1`` =
        ``os.cpu_count()`` (cold-cache builds should use all cores; cache hits
        don't run the generator at all).
    """
    sha = paleos_installed_sha()
    sha_s = _sha_short(sha)
    grid_s = grid.hash_short()

    fname = f'unified_{material}_{sha_s}_{grid_s}.dat'
    path = _cache_root() / fname

    if not force and _header_sha_matches(path, sha):
        logger.debug('paleos_api_cache hit: %s', path)
        return path

    logger.info('paleos_api_cache miss: generating %s', path)
    path.parent.mkdir(parents=True, exist_ok=True)
    generate_paleos_api_unified_table(
        material=material,
        out_path=path,
        grid=grid,
        h2o_table_path=h2o_table_path,
        n_workers=n_workers,
    )
    return path

resolve_paleos_api_2phase_mgsio3(grid, *, force=False, n_workers=-1)

Return (solid_path, liquid_path) for cached 2-phase MgSiO3 tables.

Regenerates both files as a pair (same SHA + grid_hash) to ensure they are always from the same PALEOS commit. If only one side is stale, we still regenerate both — the generation cost is O(few min) and pairing them guarantees consistency for downstream mushy-zone mixing.

Parameters:

Name Type Description Default
grid GridSpec
required
force bool

Regenerate even when cached files look valid.

False
n_workers int

Parallelism for the generator on cache-miss. Default -1 = os.cpu_count().

-1
Source code in src/zalmoxis/eos/paleos_api_cache.py
124
125
126
127
128
129
130
131
132
133
134
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
162
163
164
165
def resolve_paleos_api_2phase_mgsio3(
    grid: GridSpec,
    *,
    force: bool = False,
    n_workers: int = -1,
) -> tuple[Path, Path]:
    """Return (solid_path, liquid_path) for cached 2-phase MgSiO3 tables.

    Regenerates both files as a pair (same SHA + grid_hash) to ensure they
    are always from the same PALEOS commit. If only one side is stale, we
    still regenerate both — the generation cost is O(few min) and pairing
    them guarantees consistency for downstream mushy-zone mixing.

    Parameters
    ----------
    grid : GridSpec
    force : bool
        Regenerate even when cached files look valid.
    n_workers : int
        Parallelism for the generator on cache-miss. Default ``-1`` =
        ``os.cpu_count()``.
    """
    sha = paleos_installed_sha()
    sha_s = _sha_short(sha)
    grid_s = grid.hash_short()

    solid = _cache_root() / f'2phase_solid_{sha_s}_{grid_s}.dat'
    liquid = _cache_root() / f'2phase_liquid_{sha_s}_{grid_s}.dat'

    if not force and _header_sha_matches(solid, sha) and _header_sha_matches(liquid, sha):
        logger.debug('paleos_api_cache 2-phase hit: %s, %s', solid, liquid)
        return solid, liquid

    logger.info('paleos_api_cache 2-phase miss: generating %s + %s', solid, liquid)
    solid.parent.mkdir(parents=True, exist_ok=True)
    generate_paleos_api_2phase_mgsio3_tables(
        out_solid=solid,
        out_liquid=liquid,
        grid=grid,
        n_workers=n_workers,
    )
    return solid, liquid

resolve_registry_entry(material_dict)

Materialise PALEOS-API:* / PALEOS-API-2phase:* registry entries in place.

Registry entries for the live-tabulated EoS carry grid + material metadata but no file path. This helper detects format=='paleos_api' or format=='paleos_api_2phase' in either the top-level dict (unified materials) or in the solid_mantle / melted_mantle sub-dicts (2-phase materials), calls the appropriate cache resolver, writes the resolved on-disk path into eos_file, and switches format to the matching downstream format (paleos_unified or paleos). Subsequent dispatch goes through the normal code path unchanged.

Rewriting in place (rather than returning a copy) is intentional: the registry entry is a mutable dict that downstream consumers hold a reference to. Once resolved, further lookups on the same entry are O(1) (plain dict access).

Parameters:

Name Type Description Default
material_dict dict

A registry entry. For unified materials the dict itself has format='paleos_api'. For 2-phase materials the outer dict has no format key but contains solid_mantle and melted_mantle sub-dicts that each have format='paleos_api_2phase'.

required

Returns:

Type Description
dict

The same material_dict (mutated in place). Returned for chaining.

Source code in src/zalmoxis/eos/paleos_api_cache.py
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def resolve_registry_entry(material_dict: dict) -> dict:
    """Materialise ``PALEOS-API:*`` / ``PALEOS-API-2phase:*`` registry entries in place.

    Registry entries for the live-tabulated EoS carry grid + material metadata
    but no file path. This helper detects ``format=='paleos_api'`` or
    ``format=='paleos_api_2phase'`` in either the top-level dict (unified
    materials) or in the ``solid_mantle`` / ``melted_mantle`` sub-dicts
    (2-phase materials), calls the appropriate cache resolver, writes the
    resolved on-disk path into ``eos_file``, and switches ``format`` to the
    matching downstream format (``paleos_unified`` or ``paleos``). Subsequent
    dispatch goes through the normal code path unchanged.

    Rewriting in place (rather than returning a copy) is intentional: the
    registry entry is a mutable dict that downstream consumers hold a
    reference to. Once resolved, further lookups on the same entry are O(1)
    (plain dict access).

    Parameters
    ----------
    material_dict : dict
        A registry entry. For unified materials the dict itself has
        ``format='paleos_api'``. For 2-phase materials the outer dict has
        no ``format`` key but contains ``solid_mantle`` and
        ``melted_mantle`` sub-dicts that each have ``format='paleos_api_2phase'``.

    Returns
    -------
    dict
        The same ``material_dict`` (mutated in place). Returned for chaining.
    """
    if material_dict is None:
        return material_dict

    if material_dict.get('format') == 'paleos_api':
        _resolve_unified_in_place(material_dict)
        return material_dict

    # 2-phase / Tdep layout: sub-dicts per phase.
    for sub_key in ('solid_mantle', 'melted_mantle'):
        sub = material_dict.get(sub_key)
        if isinstance(sub, dict) and sub.get('format') == 'paleos_api_2phase':
            _resolve_2phase_in_place(material_dict)
            break

    return material_dict

invalidate_cache(material=None)

Remove cache files for material (or all if None).

Returns the number of files removed. Intended for CLI / debugging use.

Source code in src/zalmoxis/eos/paleos_api_cache.py
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
def invalidate_cache(material: str | None = None) -> int:
    """Remove cache files for ``material`` (or all if ``None``).

    Returns the number of files removed. Intended for CLI / debugging use.
    """
    root = _cache_root()
    if not root.exists():
        return 0
    removed = 0
    for p in root.iterdir():
        if not p.is_file() or p.suffix != '.dat':
            continue
        if material is not None:
            # Match on the ``_<material>_`` segment (unified) or ``2phase_*`` (all).
            tag_unified = f'unified_{material}_'
            is_match = p.name.startswith(tag_unified)
            if material == 'mgsio3':
                is_match = is_match or p.name.startswith('2phase_')
            if not is_match:
                continue
        try:
            os.remove(p)
            removed += 1
        except OSError as e:
            logger.warning('paleos_api_cache: could not remove %s (%s)', p, e)
    return removed