From 5fabfb5b7f3d52eee7d27e7bf03480fed771b7e8 Mon Sep 17 00:00:00 2001 From: Brandon Bocklund Date: Wed, 26 Aug 2026 19:08:44 -0700 Subject: [PATCH 1/2] use database test fixturing --- tests/{ => databases}/alzn_mey.tdb | 0 tests/{ => databases}/sl_chen.tdb | 0 tests/fixtures.py | 30 +++++++++++++++++++++++++++++ tests/test_chen2009.py | 23 +++++++++++++--------- tests/test_scheil_solidification.py | 28 ++++++++++++++++----------- 5 files changed, 61 insertions(+), 20 deletions(-) rename tests/{ => databases}/alzn_mey.tdb (100%) rename tests/{ => databases}/sl_chen.tdb (100%) create mode 100644 tests/fixtures.py diff --git a/tests/alzn_mey.tdb b/tests/databases/alzn_mey.tdb similarity index 100% rename from tests/alzn_mey.tdb rename to tests/databases/alzn_mey.tdb diff --git a/tests/sl_chen.tdb b/tests/databases/sl_chen.tdb similarity index 100% rename from tests/sl_chen.tdb rename to tests/databases/sl_chen.tdb diff --git a/tests/fixtures.py b/tests/fixtures.py new file mode 100644 index 0000000..bb814a5 --- /dev/null +++ b/tests/fixtures.py @@ -0,0 +1,30 @@ +import os +import pytest +from pycalphad import Database + +DATABASE_DIR = os.path.join(os.path.dirname(__file__), 'databases') + + +@pytest.fixture(scope="session") +def load_database(request): + """ + Helper fixture to load a database (parameterized by the value of `request`). + """ + db = Database(os.path.join(DATABASE_DIR, request.param)) + def _load_database(): + return db + return _load_database + + +def select_database(path): + """ + Decorator to facilitate safe, fast loading of database objects. Use as + + ``` + @select_database("filename.tdb") # matches a file in the tests/databases directory + def test_name_of_my_test(load_database): + dbf = load_database() # equivalent to `dbf = Database("filename.tdb")` + # ... implement test below + ``` + """ + return pytest.mark.parametrize("load_database", [path], indirect=True) diff --git a/tests/test_chen2009.py b/tests/test_chen2009.py index 83cc1ac..a0ea013 100644 --- a/tests/test_chen2009.py +++ b/tests/test_chen2009.py @@ -5,17 +5,18 @@ from pycalphad import Database, variables as v from scheil import simulate_scheil_solidification import pytest +from fixtures import select_database, load_database, DATABASE_DIR -DB_CHEN = Database(os.path.join(os.path.dirname(__file__), 'sl_chen.tdb')) - -def test_binary_A_B(): +@select_database("sl_chen.tdb") +def test_binary_A_B(load_database): """Tests for the Scheil properties of the A-B binary peritectic system""" + dbf = load_database() comp = {v.X('B'): 0.5} start = 800 # Kelvin step = 5.0 - sol_res = simulate_scheil_solidification(DB_CHEN, ['A', 'B'], ['ALPHA', 'BETA', 'LIQUID'], comp, start, step_temperature=step, stop=1e-8) + sol_res = simulate_scheil_solidification(dbf, ['A', 'B'], ['ALPHA', 'BETA', 'LIQUID'], comp, start, step_temperature=step, stop=1e-8) print(f"Converged to stopping criteria: {sol_res.converged}") phase_amnts = sol_res.phase_amounts @@ -53,7 +54,7 @@ def test_binary_A_B(): ) def test_binary_A_B_with_ordering(phases): """Tests for the Scheil properties of the A-B binary peritectic system with an ordered phase ORD_BETA""" - with open(os.path.join(os.path.dirname(__file__), 'sl_chen.tdb')) as fp: + with open(os.path.join(DATABASE_DIR, 'sl_chen.tdb')) as fp: tdb_str = fp.read() # Append an ordered phase, 'ORD_BETA' with no ordering parameters ordered_phase_str = """ @@ -98,13 +99,15 @@ def test_binary_A_B_with_ordering(phases): assert np.isclose(sol_res.temperatures[idx_first_beta], 647, atol=step * 1.1) -def test_binary_A_C(): +@select_database("sl_chen.tdb") +def test_binary_A_C(load_database): """Tests for the Scheil properties of the A-C binary eutectic system""" + dbf = load_database() comp = {v.X('C'): 0.5} start = 900 # Kelvin step = 5.0 - sol_res = simulate_scheil_solidification(DB_CHEN, ['A', 'C'], ['ALPHA', 'BETA', 'LIQUID'], comp, start, step_temperature=step, stop=1e-8) + sol_res = simulate_scheil_solidification(dbf, ['A', 'C'], ['ALPHA', 'BETA', 'LIQUID'], comp, start, step_temperature=step, stop=1e-8) print(f"Converged to stopping criteria: {sol_res.converged}") phase_amnts = sol_res.phase_amounts @@ -126,7 +129,8 @@ def test_binary_A_C(): assert np.isclose(sol_res.temperatures[-1], 820, atol=step * 1.1) -def test_ternary_A_B_C(): +@select_database("sl_chen.tdb") +def test_ternary_A_B_C(load_database): """Tests for the Scheil properties of the A-B-C ternary system There is a transition point, t, where the solidification changes from the @@ -134,11 +138,12 @@ def test_ternary_A_B_C(): around this point. """ t_temp = 683.7 # Eutectic-like to peritectic-like transition temperature, in Kelvin + dbf = load_database() comp = {v.X('B'): 0.25, v.X('C'): 0.1} start = 800 # Kelvin step = 5 - sol_res = simulate_scheil_solidification(DB_CHEN, ['A', 'B', 'C'], ['ALPHA', 'BETA', 'LIQUID'], comp, start, step_temperature=step, stop=1e-8) + sol_res = simulate_scheil_solidification(dbf, ['A', 'B', 'C'], ['ALPHA', 'BETA', 'LIQUID'], comp, start, step_temperature=step, stop=1e-8) print(f"Converged to stopping criteria: {sol_res.converged}") phase_amnts = sol_res.phase_amounts diff --git a/tests/test_scheil_solidification.py b/tests/test_scheil_solidification.py index 1ea6ac4..f4203a4 100644 --- a/tests/test_scheil_solidification.py +++ b/tests/test_scheil_solidification.py @@ -1,18 +1,21 @@ -import os import json import numpy as np -from pycalphad import Database, variables as v +import pytest +from pycalphad import variables as v from scheil import simulate_scheil_solidification, simulate_equilibrium_solidification, SolidificationResult +from fixtures import select_database, load_database -def test_scheil_solidification_result_properties(): + +@select_database("alzn_mey.tdb") +def test_scheil_solidification_result_properties(load_database): """Test that SolidificationResult objects produced by Scheil solidification have the required properties.""" # Required properties are # 1. the shape of the output arrays are matching # 2. the final fraction of solid is 1.0 and fraction of liquid is 0.0 # 3. the sum total of the final (solid) phase amounts is 1.0 - dbf = Database(os.path.join(os.path.dirname(__file__), 'alzn_mey.tdb')) + dbf = load_database() comps = ['AL', 'ZN', 'VA'] phases = sorted(dbf.phases.keys()) @@ -60,9 +63,10 @@ def test_scheil_solidification_result_properties(): -def test_scheil_solidification_custom_properties(): +@select_database("alzn_mey.tdb") +def test_scheil_solidification_custom_properties(load_database): """Test that SolidificationResult objects produced by Scheil solidification calculations with custom outputs have the right shape.""" - dbf = Database(os.path.join(os.path.dirname(__file__), 'alzn_mey.tdb')) + dbf = load_database() comps = ['AL', 'ZN', 'VA'] phases = sorted(dbf.phases.keys()) @@ -99,7 +103,7 @@ def test_scheil_solidification_custom_properties(): assert set(sol_res.output.keys()) == set(rnd_trip_sol_res.output.keys()) for ky in sol_res.output.keys(): np.testing.assert_almost_equal(rnd_trip_sol_res.output[ky], sol_res.output[ky]) - + # Test to_dataframe doesn't raise sol_res.to_dataframe(include_zero_phases=True) df = sol_res.to_dataframe(include_zero_phases=False) @@ -107,13 +111,14 @@ def test_scheil_solidification_custom_properties(): assert "HM" in df.columns -def test_equilibrium_solidification_result_properties(): +@select_database("alzn_mey.tdb") +def test_equilibrium_solidification_result_properties(load_database): """Test that SolidificationResult objects produced by equilibrium have the required properties.""" # Required properties are that the shape of the output arrays are matching # NOTE: final phase amounts are not tested because they are not guaranteed # to be 0.0 or 1.0 in the same way as in the Scheil simulations. - dbf = Database(os.path.join(os.path.dirname(__file__), 'alzn_mey.tdb')) + dbf = load_database() comps = ['AL', 'ZN', 'VA'] phases = sorted(dbf.phases.keys()) @@ -156,9 +161,10 @@ def test_equilibrium_solidification_result_properties(): sol_res.to_dataframe(include_zero_phases=False) -def test_equilibrium_solidification_custom_properties(): +@select_database("alzn_mey.tdb") +def test_equilibrium_solidification_custom_properties(load_database): """Test that SolidificationResult objects produced by equilibrium solidification calculations with custom outputs have the right shape.""" - dbf = Database(os.path.join(os.path.dirname(__file__), 'alzn_mey.tdb')) + dbf = load_database() comps = ['AL', 'ZN', 'VA'] phases = sorted(dbf.phases.keys()) From 0f88e3025718255ac672567294b0e8474f58eda1 Mon Sep 17 00:00:00 2001 From: Brandon Bocklund Date: Wed, 26 Aug 2026 19:10:00 -0700 Subject: [PATCH 2/2] FIX: raise if there is no initial liquid in Scheil and equilibrium simulations --- scheil/simulate.py | 13 ++++++++++++- tests/test_scheil_solidification.py | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/scheil/simulate.py b/scheil/simulate.py index 9e3169b..e3919aa 100644 --- a/scheil/simulate.py +++ b/scheil/simulate.py @@ -198,7 +198,7 @@ def simulate_scheil_solidification(dbf, comps, phases, composition, print('done') converged = False - phases_seen = {liquid_phase_name, ''} + phases_seen = {''} liquid_comp = composition wks = Workspace(dbf, comps, phases, calc_opts=eq_kwargs.get("calc_opts")) last_converged_wks = None @@ -230,6 +230,10 @@ def simulate_scheil_solidification(dbf, comps, phases, composition, if verbose: print(f'(Convergence failure) ', end='') if T_STEP_ORIG / step_temperature > MAXIMUM_STEP_SIZE_REDUCTION: + if liquid_phase_name not in phases_seen: + if len(found_ph) == 0: + raise ValueError(f"No equilibrium calculations converged near the starting temperature ({start_temperature} K). The starting temperature must be at or above the liquidus.") + raise ValueError(f"No stable liquid phase ({liquid_phase_name}) found at the starting temperature ({start_temperature} K); found phases {found_ph}. The starting temperature must be at or above the liquidus.") # Only found solid phases and the step size has already been reduced. Stop running without converging. if verbose: print('Maximum step size reduction exceeded. Stopping.') @@ -431,6 +435,13 @@ def simulate_equilibrium_solidification(dbf, comps, phases, composition, temperatures.append(current_T) current_T -= step_temperature else: + if len(temperatures) == 0: + # Liquid was not stable at the starting temperature, so there is no liquid-bearing + # upper bound for the binary search and no solidification path to compute. + found_ph = stable_phases - {''} + if len(found_ph) == 0: + raise ValueError(f"No equilibrium calculations converged at the starting temperature ({start_temperature} K). The starting temperature must be at or above the liquidus.") + raise ValueError(f"No stable liquid phase ({liquid_phase_name}) found at the starting temperature ({start_temperature} K); found phases {found_ph}. The starting temperature must be at or above the liquidus.") # binary search to find the solidus T_high = current_T + step_temperature # High temperature, liquid T_low = current_T # Low temperature, solids only diff --git a/tests/test_scheil_solidification.py b/tests/test_scheil_solidification.py index f4203a4..69c2c90 100644 --- a/tests/test_scheil_solidification.py +++ b/tests/test_scheil_solidification.py @@ -5,6 +5,21 @@ from scheil import simulate_scheil_solidification, simulate_equilibrium_solidification, SolidificationResult from fixtures import select_database, load_database +@select_database("sl_chen.tdb") +def test_scheil_solidification_raises_without_initial_liquid(load_database): + """Scheil solidification should raise a clear error if no liquid is stable at the start temperature.""" + dbf = load_database() + # For X(B)=0.5 the A-B system is fully solid below 600 K, so 500 K has no stable liquid + with pytest.raises(ValueError, match="[Ll]iquid"): + simulate_scheil_solidification(dbf, ['A', 'B'], ['ALPHA', 'BETA', 'LIQUID'], {v.X('B'): 0.5}, 500.0, step_temperature=5.0) + + +@select_database("sl_chen.tdb") +def test_equilibrium_solidification_raises_without_initial_liquid(load_database): + """Equilibrium solidification should raise a clear error if no liquid is stable at the start temperature.""" + dbf = load_database() + with pytest.raises(ValueError, match="[Ll]iquid"): + simulate_equilibrium_solidification(dbf, ['A', 'B'], ['ALPHA', 'BETA', 'LIQUID'], {v.X('B'): 0.5}, 500.0, step_temperature=5.0) @select_database("alzn_mey.tdb")