Skip to content

Skip the structure analysis when a phase-stability check is disabled - #288

Merged
srmnitc merged 1 commit into
ICAMS:mainfrom
pmrv:skip-disabled-phase-checks
Nov 7, 2026
Merged

srmnitc merged 1 commit into
ICAMS:mainfrom
pmrv:skip-disabled-phase-checks

Conversation

@pmrv

@pmrv pmrv commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Problem

Phase.check_if_melted and Phase.check_if_solidfied always compute the solid fraction with pyscal3 (helpers.find_solid_fraction) and only then compare it with the tolerance. With tolerance.solid_fraction: 0 (melt detection off) or liquid_fraction: 1 (solidification detection off), that comparison can never trigger, yet the analysis still runs.

That would only cost time, except that pyscal3 segfaults in find.neighbors on some LAMMPS triclinic dumps. In a ts run the check runs after every backward sweep's middle equilibration, on the freshly dumped traj.temp.dat. A segfault there kills the whole job, with exit 139 and no Python traceback, over a check that was switched off.

Where it showed up

Mg2Si (antifluorite) as a 3993-atom 60° primitive cell, GRACE potential, mode: ts, tolerance.solid_fraction: 0 (compounds: the solid-fraction criterion does not apply to them):

  • Three separate runs of this cell segfaulted at exactly the same step: the start of a backward sweep, after its middle equilibration.
    • They used two numeric precisions (TF32 / FP32) and two temperature windows (500→1400 K, 900→1400 K).
    • The crash came in iteration 3 in two runs and iteration 1 in the third.
  • pyscal3.System(traj.temp.dat).find.neighbors(method="cutoff", cutoff=5.0) reproduces the segfault on CPU from the saved dumps.
  • What passes, which suggests a pyscal3 issue with how certain LAMMPS triclinic dump bounds are read, not with the box type:
    • an Al2Ca dump in the same kind of triclinic box;
    • synthetic disordered Mg2Si cells (triclinic and cubic);
    • the crash snapshots round-tripped through ASE.

Change

Each check returns immediately when its tolerance disables it:

check skipped when
check_if_melted solid_fraction <= 0
check_if_solidfied liquid_fraction >= 1
  • Behaviour with the check enabled is unchanged.
  • liquid.py's melting-cycle check, which needs the analysis, is untouched.
  • mode: melting_temperature force-enables detection, so it is unaffected.

Tests

tests/test_disabled_phase_checks.py:

  • The disabled checks must not call find_solid_fraction.
  • The enabled checks still do.
  • Run against this branch: 4 passed. On the unpatched code the disabled-check test fails, as it should.

The pyscal3 crash itself is worth a separate report upstream (it is reproducible from the saved dump).

🤖 Generated with Claude Code

https://claude.ai/code/session_01RebMnzKCNqF5XyBrSLhBnU

check_if_melted / check_if_solidfied computed the solid fraction with pyscal3
even with `tolerance.solid_fraction: 0` / `liquid_fraction: 1`, where the
comparison that follows can never trigger. The analysis is not free, and
pyscal3 segfaults in find.neighbors on some LAMMPS triclinic dumps. That killed
reversible-scaling runs at the start of a backward sweep, right after the
middle equilibration dumps traj.temp.dat, over a check that could not have
stopped them.

Seen on Mg2Si (antifluorite, 3993-atom 60-degree primitive cell, GRACE
potential): the run segfaulted at that point in three separate runs, and
pyscal3 reproduces the crash on CPU from the saved traj.temp.dat. An Al2Ca dump
in the same kind of box, and synthetic disordered Mg2Si cells, pass. So it
depends on the dump, not on the box type alone.

Enabled checks are unchanged; liquid.py's melting-cycle check (which needs
the analysis) is untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RebMnzKCNqF5XyBrSLhBnU
@pmrv

pmrv commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

The crash that motivated this PR is gone with pyscal3 ≥ 4.0.0, which main now requires (2d3118c, "Port to pyscal 4").

Root cause, in pyscal3 3.3.2. The cell list that find_neighbors uses above 250 atoms (set_up_cells in neighbor.cpp) bins Cartesian coordinates into an orthogonal grid.

  • When a box has xy + xz > |a|, an atom in the far corner gets cell index = n.
  • The write that follows goes past the end of the cell vector, and heap corruption then decides whether it segfaults.
  • Our Mg2Si cell has xy/|a| = 0.5065, with a Si atom on the corner site.

pyscal3 4.0.0 fixed this in e1aa5b2 ("build cell lists on fractional coordinates so triclinic cells work").

Side note for anyone still on calphy ≤ 2.0.1, which pins the 3.x API. Even when 3.3.2 does not crash, it returns wrong neighbour lists for any triclinic box above 250 atoms.

  • Al2Ca, 4374 atoms, 5 Å cutoff: 33 % of atoms have the wrong count compared with ase.neighborlist.
  • find_solid_fraction reads 10–20 % low on triclinic cells.

This change is still worth having on its own. With solid_fraction: 0 or liquid_fraction: 1, the analysis cannot change the outcome, so running it costs time for nothing. A future reader bug would also not take down a run whose check is switched off.

@srmnitc
srmnitc merged commit 27367ad into ICAMS:main Nov 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants