diff --git a/espei/error_functions/non_equilibrium_thermochemical_error.py b/espei/error_functions/non_equilibrium_thermochemical_error.py index 0de62e7b..2df0e871 100644 --- a/espei/error_functions/non_equilibrium_thermochemical_error.py +++ b/espei/error_functions/non_equilibrium_thermochemical_error.py @@ -475,7 +475,7 @@ def __init__( comps = sorted(phase_models.components) model_dict = phase_models.get_model_dict() else: - comps = sorted(database.elements) + 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: @@ -504,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) 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 289bd5d3..385f2d39 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 @@ -340,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."""