Skip to content

Commit df720a9

Browse files
committed
add anharmonic parameter validation
1 parent 539c5a8 commit df720a9

File tree

7 files changed

+204
-70
lines changed

7 files changed

+204
-70
lines changed

burnman/eos/anharmonic_debye.py

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# This file is part of BurnMan - a thermoelastic and thermodynamic toolkit for the Earth and Planetary Sciences
1+
# This file is part of BurnMan - a thermoelastic and thermodynamic toolkit for
2+
# the Earth and Planetary Sciences
23
# Copyright (C) 2012 - 2025 by the BurnMan team, released under the GNU
34
# GPL v2 or later.
45

@@ -31,7 +32,7 @@ def helmholtz_energy(temperature, volume, params):
3132
A = params["anharmonic_prefactor_model"].value(x, params)
3233
theta_model = params["debye_temperature_model"]
3334
anharmonic_model = params["anharmonic_thermal_model"]
34-
debye_T = theta_model(x, params)
35+
debye_T = theta_model.value(x, params)
3536
F_a = anharmonic_model.nondimensional_helmholtz_energy(
3637
temperature, debye_T, params
3738
)
@@ -46,7 +47,7 @@ def entropy(temperature, volume, params):
4647
A = params["anharmonic_prefactor_model"].value(x, params)
4748
theta_model = params["debye_temperature_model"]
4849
anharmonic_model = params["anharmonic_thermal_model"]
49-
debye_T = theta_model(x, params)
50+
debye_T = theta_model.value(x, params)
5051
S_a = anharmonic_model.nondimensional_entropy(temperature, debye_T, params)
5152
return A * S_a
5253

@@ -56,7 +57,7 @@ def heat_capacity_v(temperature, volume, params):
5657
A = params["anharmonic_prefactor_model"].value(x, params)
5758
theta_model = params["debye_temperature_model"]
5859
anharmonic_model = params["anharmonic_thermal_model"]
59-
debye_T = theta_model(x, params)
60+
debye_T = theta_model.value(x, params)
6061
Cv_a = anharmonic_model.nondimensional_heat_capacity(
6162
temperature, debye_T, params
6263
)
@@ -69,7 +70,7 @@ def pressure(temperature, volume, params):
6970
dAdV = params["anharmonic_prefactor_model"].dVrel(x, params) / params["V_0"]
7071
theta_model = params["debye_temperature_model"]
7172
anharmonic_model = params["anharmonic_thermal_model"]
72-
debye_T = theta_model(x, params)
73+
debye_T = theta_model.value(x, params)
7374
F_a = anharmonic_model.nondimensional_helmholtz_energy(
7475
temperature, debye_T, params
7576
)
@@ -93,12 +94,11 @@ def isothermal_bulk_modulus(temperature, volume, params):
9394
A = params["anharmonic_prefactor_model"].value(x, params)
9495
dAdV = params["anharmonic_prefactor_model"].dVrel(x, params) / params["V_0"]
9596
d2AdV2 = (
96-
params["anharmonic_prefactor_model"].d2dVrel2(x, params)
97-
/ params["V_0"] ** 2
97+
params["anharmonic_prefactor_model"].dVrel2(x, params) / params["V_0"] ** 2
9898
)
9999
theta_model = params["debye_temperature_model"]
100100
anharmonic_model = params["anharmonic_thermal_model"]
101-
debye_T = theta_model(x, params)
101+
debye_T = theta_model.value(x, params)
102102

103103
F_a = anharmonic_model.nondimensional_helmholtz_energy(
104104
temperature, debye_T, params
@@ -123,7 +123,7 @@ def isothermal_bulk_modulus(temperature, volume, params):
123123
d2AdV2 * (F_a - F_a0)
124124
+ 2 * dAdV * (F_ad - F_ad0) * theta_model.dVrel(x, params) / params["V_0"]
125125
+ A * (F_add - F_add0) * (theta_model.dVrel(x, params) / params["V_0"]) ** 2
126-
+ A * (F_ad - F_ad0) * theta_model.d2dVrel2(x, params) / params["V_0"] ** 2
126+
+ A * (F_ad - F_ad0) * theta_model.dVrel2(x, params) / params["V_0"] ** 2
127127
)
128128

129129
@staticmethod
@@ -133,7 +133,7 @@ def dSdV(temperature, volume, params):
133133
dAdV = params["anharmonic_prefactor_model"].dVrel(x, params) / params["V_0"]
134134
theta_model = params["debye_temperature_model"]
135135
anharmonic_model = params["anharmonic_thermal_model"]
136-
debye_T = theta_model(x, params)
136+
debye_T = theta_model.value(x, params)
137137

138138
S_a = anharmonic_model.nondimensional_entropy(temperature, debye_T, params)
139139
S_ad = anharmonic_model.nondimensional_dentropy_dTheta(
@@ -143,3 +143,66 @@ def dSdV(temperature, volume, params):
143143
aK_T = dAdV * S_a + A * (theta_model.dVrel(x, params) / params["V_0"]) * S_ad
144144

145145
return aK_T
146+
147+
@staticmethod
148+
def validate_parameters(params):
149+
# Check for all required keys
150+
expected_keys = [
151+
"debye_temperature_model",
152+
"anharmonic_prefactor_model",
153+
"anharmonic_thermal_model",
154+
"V_0",
155+
"T_0",
156+
]
157+
for key in expected_keys:
158+
if key not in params:
159+
raise AttributeError(f"params dictionary must contain an '{key}' key")
160+
161+
# Validate the three models:
162+
models = [
163+
params["debye_temperature_model"],
164+
params["anharmonic_prefactor_model"],
165+
params["anharmonic_thermal_model"],
166+
]
167+
for model in models:
168+
model.validate_parameters(params)
169+
170+
# Check that the required methods are present in the
171+
# debye_temperature_model
172+
expected_methods = ["value", "dVrel", "dVrel2"]
173+
for method in expected_methods:
174+
if not hasattr(params["debye_temperature_model"], method) or not callable(
175+
getattr(params["debye_temperature_model"], method)
176+
):
177+
raise AttributeError(
178+
f"params['debye_temperature_model'] must have a {method} method that takes arguments Vrel and params"
179+
)
180+
181+
# Check that the required methods are present in the
182+
# anharmonic_prefactor_model
183+
expected_methods = ["value", "dVrel", "dVrel2"]
184+
for method in expected_methods:
185+
if not hasattr(
186+
params["anharmonic_prefactor_model"], method
187+
) or not callable(getattr(params["anharmonic_prefactor_model"], method)):
188+
raise AttributeError(
189+
f"params['anharmonic_prefactor_model'] must have a {method} method that takes arguments Vrel and params"
190+
)
191+
192+
# Check that the required methods are present in the
193+
# anharmonic_thermal_model
194+
expected_methods = [
195+
"nondimensional_helmholtz_energy",
196+
"nondimensional_dhelmholtz_dTheta",
197+
"nondimensional_d2helmholtz_dTheta2",
198+
"nondimensional_entropy",
199+
"nondimensional_dentropy_dTheta",
200+
"nondimensional_heat_capacity",
201+
]
202+
for method in expected_methods:
203+
if not hasattr(params["anharmonic_thermal_model"], method) or not callable(
204+
getattr(params["anharmonic_thermal_model"], method)
205+
):
206+
raise AttributeError(
207+
f"params['anharmonic_thermal_model'] must have a {method} method that takes arguments temperature, debye_T, and params"
208+
)

burnman/eos/anharmonic_prefactor_models.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@ def value(self, Vrel, params):
1616
def dVrel(self, Vrel, params):
1717
raise NotImplementedError("need to implement dVrel() in derived class!")
1818

19-
def d2dVrel2(self, Vrel, params):
20-
raise NotImplementedError("need to implement d2dVrel2() in derived class!")
19+
def dVrel2(self, Vrel, params):
20+
raise NotImplementedError("need to implement dVrel2() in derived class!")
21+
22+
def validate_parameters(self, params):
23+
raise NotImplementedError(
24+
"need to implement validate_parameters() in derived class!"
25+
)
2126

2227

2328
class PowerLaw(AnharmonicPrefactorModel):
@@ -38,14 +43,26 @@ def value(self, Vrel, params):
3843
def dVrel(self, Vrel, params):
3944
return params["a_anh"] * params["m_anh"] * np.power(Vrel, params["m_anh"] - 1)
4045

41-
def d2dVrel2(self, Vrel, params):
46+
def dVrel2(self, Vrel, params):
4247
return (
4348
params["a_anh"]
4449
* params["m_anh"]
4550
* (params["m_anh"] - 1)
4651
* np.power(Vrel, params["m_anh"] - 2)
4752
)
4853

54+
def validate_parameters(self, params):
55+
# Check for all required keys
56+
expected_keys = ["a_anh", "m_anh"]
57+
for key in expected_keys:
58+
if key not in params:
59+
raise AttributeError(f"params dictionary must contain a '{key}' key")
60+
61+
# Check that the required parameters are valid numbers
62+
for key in expected_keys:
63+
if not isinstance(params[key], (int, float)):
64+
raise TypeError(f"params['{key}'] must be a number")
65+
4966

5067
class Sigmoid(AnharmonicPrefactorModel):
5168
"""
@@ -74,10 +91,22 @@ def dVrel(self, Vrel, params):
7491
denom = (1 + (Vrel + b) ** c) ** 2
7592
return a * c * (Vrel + b) ** (c - 1) / denom
7693

77-
def d2dVrel2(self, Vrel, params):
94+
def dVrel2(self, Vrel, params):
7895
a = params["a_anh"]
7996
b = params["b_anh"]
8097
c = params["c_anh"]
8198
num = (c - 1) - (c + 1) * (Vrel + b) ** c
8299
denom = (1 + (Vrel + b) ** c) ** 3
83100
return a * c * (Vrel + b) ** (c - 2) * num / denom
101+
102+
def validate_parameters(self, params):
103+
# Check for all required keys
104+
expected_keys = ["a_anh", "b_anh", "c_anh"]
105+
for key in expected_keys:
106+
if key not in params:
107+
raise AttributeError(f"params dictionary must contain a '{key}' key")
108+
109+
# Check that the required parameters are valid numbers
110+
for key in expected_keys:
111+
if not isinstance(params[key], (int, float)):
112+
raise TypeError(f"params['{key}'] must be a number")

burnman/eos/anharmonic_thermal_models.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,9 @@ def nondimensional_dentropy_dTheta(self, T, debye_T, params=None):
305305
t = T / debye_T
306306
return _d2helmholtzdt2_pade(t) * t / debye_T
307307

308+
def validate_parameters(self, params):
309+
pass
310+
308311

309312
@singleton
310313
class LogNormal(object):
@@ -481,3 +484,15 @@ def nondimensional_dentropy_dTheta(self, T, debye_T, params):
481484
return (
482485
self._d2helmholtzdt2(t, params["mu_anh"], params["sigma_anh"]) * t / debye_T
483486
)
487+
488+
def validate_parameters(self, params):
489+
# Check for all required keys
490+
expected_keys = ["mu_anh", "sigma_anh"]
491+
for key in expected_keys:
492+
if key not in params:
493+
raise AttributeError(f"params dictionary must contain a '{key}' key")
494+
495+
# Check that the required parameters are valid numbers
496+
for key in expected_keys:
497+
if not isinstance(params[key], (int, float)):
498+
raise TypeError(f"params['{key}'] must be a number")

burnman/eos/debye_temperature_models.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class DebyeTemperatureModelBase(ABC):
2222
"""Abstract base class for Debye temperature models."""
2323

2424
@abstractmethod
25-
def __call__(self, Vrel: float, params: dict) -> float:
25+
def value(self, Vrel: float, params: dict) -> float:
2626
"""Return the Debye temperature for the given relative volume."""
2727

2828
@abstractmethod
@@ -35,13 +35,13 @@ def _gamma_prime(self, Vrel: float, params: dict) -> float:
3535

3636
def dVrel(self, Vrel: float, params: dict) -> float:
3737
"""First derivative of Debye temperature wrt relative volume."""
38-
theta = self.__call__(Vrel, params)
38+
theta = self.value(Vrel, params)
3939
gamma = self._gamma(Vrel, params)
4040
return -theta * gamma / Vrel
4141

42-
def d2dVrel2(self, Vrel: float, params: dict) -> float:
42+
def dVrel2(self, Vrel: float, params: dict) -> float:
4343
"""Second derivative of Debye temperature wrt relative volume."""
44-
theta = self.__call__(Vrel, params)
44+
theta = self.value(Vrel, params)
4545
gamma = self._gamma(Vrel, params)
4646
gamma_prime = self._gamma_prime(Vrel, params)
4747
return theta / Vrel**2 * (gamma * (gamma + 1.0) - Vrel * gamma_prime)
@@ -68,7 +68,7 @@ class SLB(DebyeTemperatureModelBase):
6868
:type params: dict
6969
"""
7070

71-
def __call__(self, Vrel, params):
71+
def value(self, Vrel, params):
7272
# This method computes the Debye temperature.
7373
a1_ii, a2_iikk, f = self.a1_a2_and_f(Vrel, params)
7474
nu_o_nu0_sq = 1.0 + a1_ii * f + (1.0 / 2.0) * a2_iikk * f * f
@@ -108,6 +108,13 @@ def a1_a2_and_f(self, Vrel, params):
108108
f = 0.5 * (pow(Vrel, -2.0 / 3.0) - 1.0)
109109
return a1_ii, a2_iikk, f
110110

111+
def validate_parameters(self, params):
112+
# Check for all required keys
113+
expected_keys = ["grueneisen_0", "q_0", "Debye_0"]
114+
for key in expected_keys:
115+
if key not in params:
116+
raise AttributeError(f"params dictionary must contain a '{key}' key")
117+
111118

112119
@singleton
113120
class PowerLawGammaSimple(DebyeTemperatureModelBase):
@@ -131,7 +138,7 @@ class PowerLawGammaSimple(DebyeTemperatureModelBase):
131138
:type params: dict
132139
"""
133140

134-
def __call__(self, Vrel, params):
141+
def value(self, Vrel, params):
135142
# This method computes the Debye temperature.
136143
return params["Debye_0"] * np.exp(
137144
-params["grueneisen_0"]
@@ -147,6 +154,13 @@ def _gamma_prime(self, Vrel, params):
147154
params["grueneisen_0"] * params["q_0"] * np.power(Vrel, params["q_0"] - 1.0)
148155
)
149156

157+
def validate_parameters(self, params):
158+
# Check for all required keys
159+
expected_keys = ["grueneisen_0", "q_0", "Debye_0"]
160+
for key in expected_keys:
161+
if key not in params:
162+
raise AttributeError(f"params dictionary must contain a '{key}' key")
163+
150164

151165
@singleton
152166
class PowerLawGamma(DebyeTemperatureModelBase):
@@ -170,7 +184,7 @@ class PowerLawGamma(DebyeTemperatureModelBase):
170184
:type params: dict
171185
"""
172186

173-
def __call__(self, Vrel, params):
187+
def value(self, Vrel, params):
174188
# This method computes the Debye temperature.
175189
return (
176190
params["Debye_0"]
@@ -192,3 +206,10 @@ def _gamma_prime(self, Vrel, params):
192206
return params["c_1"] * params["q_1"] * np.power(
193207
Vrel, params["q_1"] - 1.0
194208
) + params["c_2"] * params["q_2"] * np.power(Vrel, params["q_2"] - 1.0)
209+
210+
def validate_parameters(self, params):
211+
# Check for all required keys
212+
expected_keys = ["Debye_0", "grueneisen_0", "c_1", "c_2", "q_1", "q_2"]
213+
for key in expected_keys:
214+
if key not in params:
215+
raise AttributeError(f"params dictionary must contain a '{key}' key")

0 commit comments

Comments
 (0)