From ed0288ba6b9c59606d884cfa96d3606c596c9fc8 Mon Sep 17 00:00:00 2001 From: Brandon Bocklund Date: Tue, 27 Oct 2026 14:32:38 -0700 Subject: [PATCH 1/3] FIX: Fixed configuration residuals for databases that declare the electron Without phase models, FixedConfigurationPropertyResidual takes its components from database.elements. For databases that declare the electron (ELEMENT /-), that includes /-, and computing the residuals raised `RuntimeError: Not Implemented` from symengine. Exclude the electron from the components. --- .../non_equilibrium_thermochemical_error.py | 3 ++- tests/test_error_functions.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/espei/error_functions/non_equilibrium_thermochemical_error.py b/espei/error_functions/non_equilibrium_thermochemical_error.py index 0de62e7b..6d337ee5 100644 --- a/espei/error_functions/non_equilibrium_thermochemical_error.py +++ b/espei/error_functions/non_equilibrium_thermochemical_error.py @@ -475,7 +475,8 @@ def __init__( comps = sorted(phase_models.components) model_dict = phase_models.get_model_dict() else: - comps = sorted(database.elements) + # the electron (/-) is not a component + comps = sorted(set(database.elements) - {'/-'}) model_dict = dict() phases = sorted(filter_phases(database, unpack_species(database, comps), database.phases.keys())) if symbols_to_fit is None: diff --git a/tests/test_error_functions.py b/tests/test_error_functions.py index 289bd5d3..3c544d2b 100644 --- a/tests/test_error_functions.py +++ b/tests/test_error_functions.py @@ -128,6 +128,25 @@ def test_fixed_configuration_residual_with_internal_degrees_of_freedom(datasets_ assert np.allclose(residuals, [0.0]) +def test_fixed_configuration_residual_with_electron_element(datasets_db): + """Fixed configuration residuals work for databases that declare the electron (/-)""" + dbf = Database("ELEMENT /- ELECTRON_GAS 0.0 0.0 0.0 !\n" + A_B_REGULAR_SOLUTION_TDB) + datasets_db.insert({ + "components": ["A", "B"], "phases": ["ALPHA"], "output": "HM_MIX", + "solver": {"mode": "manual", "sublattice_site_ratios": [1], + "sublattice_configurations": [[["A", "B"]], [["A", "B"]]], + "sublattice_occupancies": [[[0.5, 0.5]], [[0.2, 0.8]]]}, + "conditions": {"P": 101325, "T": 600}, "values": [[[0.0, 0.0]]], "reference": "regular solution", + }) + + residual_func = FixedConfigurationPropertyResidual(dbf, datasets_db, phase_models=None, symbols_to_fit=['L_ALPHA']) + + # HM_MIX = L x_A x_B for a regular solution + residuals, weights = residual_func.get_residuals(np.asarray([2000.0])) + assert len(residuals) == len(weights) + assert np.allclose(residuals, [500.0, 320.0]) + + def test_get_thermochemical_data_filters_configurations_when_all_configurations_are_invalid(datasets_db): datasets_db.insert(CU_MG_HM_MIX_CUMG2_ALL_INVALID) # No valid configurations From 99b750815618fdded32817777f04666c4b6bf469 Mon Sep 17 00:00:00 2001 From: Brandon Bocklund Date: Tue, 27 Oct 2026 14:32:48 -0700 Subject: [PATCH 2/3] FIX: Empty ZPF residuals when there is no ZPF data ZPFResidual.get_residuals raised `ValueError: need at least one array to concatenate` when the datasets had no ZPF data, while get_likelihood returned 0.0. Return empty residuals and weights, as calculate_zpf_error does for its likelihood. --- espei/error_functions/zpf_error.py | 2 ++ tests/test_error_functions.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/espei/error_functions/zpf_error.py b/espei/error_functions/zpf_error.py index 12e915eb..7820e48b 100644 --- a/espei/error_functions/zpf_error.py +++ b/espei/error_functions/zpf_error.py @@ -436,6 +436,8 @@ def __init__( self.zpf_data = get_zpf_data(database, comps, phases, datasets, parameters, model_dict) def get_residuals(self, parameters: ArrayLike) -> Tuple[List[float], List[float]]: + if len(self.zpf_data) == 0: + return [], [] driving_forces, weights = calculate_zpf_driving_forces(self.zpf_data, parameters, short_circuit=True) # Driving forces and weights are 2D ragged arrays with the shape (len(zpf_data), len(zpf_data['values'])) residuals = np.concatenate(driving_forces).tolist() diff --git a/tests/test_error_functions.py b/tests/test_error_functions.py index 3c544d2b..385f2d39 100644 --- a/tests/test_error_functions.py +++ b/tests/test_error_functions.py @@ -359,6 +359,18 @@ def test_zpf_residual_function(datasets_db): assert np.isclose(likelihood, zero_error_prob, rtol=1e-6) +def test_zpf_residual_function_without_data(datasets_db): + """ZPF residuals are empty without ZPF data""" + dbf = Database(CU_MG_TDB) + + residual_func = ZPFResidual(dbf, datasets_db, phase_models=None, symbols_to_fit=[]) + + residuals, weights = residual_func.get_residuals(np.asarray([])) + assert residuals == [] + assert weights == [] + assert residual_func.get_likelihood(np.asarray([])) == 0.0 + + def test_subsystem_zpf_probability(datasets_db): """Test binary Cr-Ni data produces the same probability regardless of whether the main system is a binary or ternary.""" From 4a11cf3f44d14cea1df5744d519f5766572258e2 Mon Sep 17 00:00:00 2001 From: Brandon Bocklund Date: Tue, 27 Oct 2026 14:40:17 -0700 Subject: [PATCH 3/3] Remove comment --- espei/error_functions/non_equilibrium_thermochemical_error.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/espei/error_functions/non_equilibrium_thermochemical_error.py b/espei/error_functions/non_equilibrium_thermochemical_error.py index 6d337ee5..2df0e871 100644 --- a/espei/error_functions/non_equilibrium_thermochemical_error.py +++ b/espei/error_functions/non_equilibrium_thermochemical_error.py @@ -475,7 +475,6 @@ def __init__( comps = sorted(phase_models.components) model_dict = phase_models.get_model_dict() else: - # the electron (/-) is not a component comps = sorted(set(database.elements) - {'/-'}) model_dict = dict() phases = sorted(filter_phases(database, unpack_species(database, comps), database.phases.keys())) @@ -505,4 +504,4 @@ def get_likelihood(self, parameters) -> float: return likelihood -residual_function_registry.register(FixedConfigurationPropertyResidual) \ No newline at end of file +residual_function_registry.register(FixedConfigurationPropertyResidual)