From 09df1a2567f8c9493c6202cbe9acbc3bb2d13a04 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 4 Nov 2026 22:11:40 -0500 Subject: [PATCH] Skip the structure analysis when a phase-stability check is disabled 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 Claude-Session: https://claude.ai/code/session_01RebMnzKCNqF5XyBrSLhBnU --- calphy/phase.py | 9 ++++++ tests/test_disabled_phase_checks.py | 46 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/test_disabled_phase_checks.py diff --git a/calphy/phase.py b/calphy/phase.py index 4845573..742ca8f 100644 --- a/calphy/phase.py +++ b/calphy/phase.py @@ -554,6 +554,11 @@ def check_if_melted(self, lmp, filename): aborted with a MeltedError. Detection can be turned off by setting ``tolerance.solid_fraction: 0``. """ + # Disabled (solid_fraction <= 0): the comparison below can never fire, so do not run the + # structure analysis at all. It is not free, and pyscal3 can segfault in find.neighbors on + # some LAMMPS triclinic dumps, killing a run over a check that could not have stopped it. + if self.calc.tolerance.solid_fraction <= 0: + return solids = ph.find_solid_fraction(os.path.join(self.simfolder, filename)) if solids / self.natoms < self.calc.tolerance.solid_fraction: self.lammps_close(lmp=lmp) @@ -569,7 +574,11 @@ def check_if_solidfied(self, lmp, filename): If the solid fraction in ``filename`` exceeds ``tolerance.liquid_fraction`` the run is aborted with a SolidifiedError. + Disabled (``liquid_fraction >= 1``), the analysis is skipped, as in + ``check_if_melted``. """ + if self.calc.tolerance.liquid_fraction >= 1: + return solids = ph.find_solid_fraction(os.path.join(self.simfolder, filename)) if solids / self.natoms > self.calc.tolerance.liquid_fraction: self.lammps_close(lmp=lmp) diff --git a/tests/test_disabled_phase_checks.py b/tests/test_disabled_phase_checks.py new file mode 100644 index 0000000..db2af59 --- /dev/null +++ b/tests/test_disabled_phase_checks.py @@ -0,0 +1,46 @@ +"""A disabled phase-stability check must not run the structure analysis. + +`tolerance.solid_fraction: 0` (or `liquid_fraction: 1`) makes the comparison in +check_if_melted / check_if_solidfied impossible to trigger, but the solid fraction +was still computed with pyscal3 -- which segfaults in find.neighbors on some LAMMPS +triclinic dumps (Mg2Si antifluorite, 60-degree primitive cell), killing the run. +""" +from types import SimpleNamespace + +import pytest + +import calphy.phase as phase_mod +from calphy.phase import Phase + + +def _fake(solid_fraction=0.0, liquid_fraction=1.0): + tol = SimpleNamespace(solid_fraction=solid_fraction, liquid_fraction=liquid_fraction) + return SimpleNamespace(calc=SimpleNamespace(tolerance=tol), simfolder="/nonexistent", natoms=10) + + +def _explode(_): + raise AssertionError("find_solid_fraction must not be called when the check is disabled") + + +def test_disabled_melt_check_skips_structure_analysis(monkeypatch): + monkeypatch.setattr(phase_mod.ph, "find_solid_fraction", _explode) + Phase.check_if_melted(_fake(solid_fraction=0.0), lmp=None, filename="traj.temp.dat") + + +def test_disabled_solidification_check_skips_structure_analysis(monkeypatch): + monkeypatch.setattr(phase_mod.ph, "find_solid_fraction", _explode) + Phase.check_if_solidfied(_fake(liquid_fraction=1.0), lmp=None, filename="traj.temp.dat") + + +def test_enabled_melt_check_still_runs(monkeypatch): + called = [] + monkeypatch.setattr(phase_mod.ph, "find_solid_fraction", lambda f: called.append(f) or 10) + Phase.check_if_melted(_fake(solid_fraction=0.7), lmp=None, filename="traj.temp.dat") + assert called == ["/nonexistent/traj.temp.dat"] + + +def test_enabled_solidification_check_still_runs(monkeypatch): + called = [] + monkeypatch.setattr(phase_mod.ph, "find_solid_fraction", lambda f: called.append(f) or 0) + Phase.check_if_solidfied(_fake(liquid_fraction=0.05), lmp=None, filename="traj.temp.dat") + assert called == ["/nonexistent/traj.temp.dat"]