# This file is part of BurnMan - a thermoelastic and thermodynamic toolkit for
# the Earth and Planetary Sciences
# Copyright (C) 2012 - 2025 by the BurnMan team, released under the GNU
# GPL v2 or later.
[docs]
class AnharmonicDebye:
"""
Class providing methods to compute the anharmonic contribution to the
Helmholtz free energy, pressure, entropy, isochoric heat capacity,
isothermal bulk modulus and thermal expansion coefficient
multiplied by the isothermal bulk modulus.
The Helmholtz energy is defined as
:math:`F(V, T) = A(V) (F_a(T, \\Theta(V)) - F_a(T_0, \\Theta(V)))`
where :math:`A(V)` is the anharmonic prefactor,
:math:`F_a(T, \\Theta)` is the anharmonic Helmholtz energy,
and :math:`\\Theta(V)` is the Debye temperature.
These three functions and their derivatives with respect to
their arguments are provided as class instances contained within
the params dictionary, with keys "anharmonic_prefactor_model",
"debye_temperature_model", and "anharmonic_thermal_model".
:return: _description_
:rtype: _type_
"""
[docs]
@staticmethod
def helmholtz_energy(temperature, volume, params):
x = volume / params["V_0"]
A = params["anharmonic_prefactor_model"].value(x, params)
theta_model = params["debye_temperature_model"]
anharmonic_model = params["anharmonic_thermal_model"]
debye_T = theta_model.value(x, params)
F_a = anharmonic_model.nondimensional_helmholtz_energy(
temperature, debye_T, params
)
F_a0 = anharmonic_model.nondimensional_helmholtz_energy(
params["T_0"], debye_T, params
)
return A * (F_a - F_a0)
[docs]
@staticmethod
def entropy(temperature, volume, params):
x = volume / params["V_0"]
A = params["anharmonic_prefactor_model"].value(x, params)
theta_model = params["debye_temperature_model"]
anharmonic_model = params["anharmonic_thermal_model"]
debye_T = theta_model.value(x, params)
S_a = anharmonic_model.nondimensional_entropy(temperature, debye_T, params)
return A * S_a
[docs]
@staticmethod
def heat_capacity_v(temperature, volume, params):
x = volume / params["V_0"]
A = params["anharmonic_prefactor_model"].value(x, params)
theta_model = params["debye_temperature_model"]
anharmonic_model = params["anharmonic_thermal_model"]
debye_T = theta_model.value(x, params)
Cv_a = anharmonic_model.nondimensional_heat_capacity(
temperature, debye_T, params
)
return A * Cv_a
[docs]
@staticmethod
def pressure(temperature, volume, params):
x = volume / params["V_0"]
A = params["anharmonic_prefactor_model"].value(x, params)
dAdV = params["anharmonic_prefactor_model"].dVrel(x, params) / params["V_0"]
theta_model = params["debye_temperature_model"]
anharmonic_model = params["anharmonic_thermal_model"]
debye_T = theta_model.value(x, params)
F_a = anharmonic_model.nondimensional_helmholtz_energy(
temperature, debye_T, params
)
F_a0 = anharmonic_model.nondimensional_helmholtz_energy(
params["T_0"], debye_T, params
)
F_ad = anharmonic_model.nondimensional_dhelmholtz_dTheta(
temperature, debye_T, params
)
F_ad0 = anharmonic_model.nondimensional_dhelmholtz_dTheta(
params["T_0"], debye_T, params
)
return -(
dAdV * (F_a - F_a0)
+ A * (theta_model.dVrel(x, params) / params["V_0"]) * (F_ad - F_ad0)
)
[docs]
@staticmethod
def isothermal_bulk_modulus(temperature, volume, params):
x = volume / params["V_0"]
A = params["anharmonic_prefactor_model"].value(x, params)
dAdV = params["anharmonic_prefactor_model"].dVrel(x, params) / params["V_0"]
d2AdV2 = (
params["anharmonic_prefactor_model"].dVrel2(x, params) / params["V_0"] ** 2
)
theta_model = params["debye_temperature_model"]
anharmonic_model = params["anharmonic_thermal_model"]
debye_T = theta_model.value(x, params)
F_a = anharmonic_model.nondimensional_helmholtz_energy(
temperature, debye_T, params
)
F_a0 = anharmonic_model.nondimensional_helmholtz_energy(
params["T_0"], debye_T, params
)
F_ad = anharmonic_model.nondimensional_dhelmholtz_dTheta(
temperature, debye_T, params
)
F_ad0 = anharmonic_model.nondimensional_dhelmholtz_dTheta(
params["T_0"], debye_T, params
)
F_add = anharmonic_model.nondimensional_d2helmholtz_dTheta2(
temperature, debye_T, params
)
F_add0 = anharmonic_model.nondimensional_d2helmholtz_dTheta2(
params["T_0"], debye_T, params
)
return volume * (
d2AdV2 * (F_a - F_a0)
+ 2 * dAdV * (F_ad - F_ad0) * theta_model.dVrel(x, params) / params["V_0"]
+ A * (F_add - F_add0) * (theta_model.dVrel(x, params) / params["V_0"]) ** 2
+ A * (F_ad - F_ad0) * theta_model.dVrel2(x, params) / params["V_0"] ** 2
)
[docs]
@staticmethod
def dSdV(temperature, volume, params):
x = volume / params["V_0"]
A = params["anharmonic_prefactor_model"].value(x, params)
dAdV = params["anharmonic_prefactor_model"].dVrel(x, params) / params["V_0"]
theta_model = params["debye_temperature_model"]
anharmonic_model = params["anharmonic_thermal_model"]
debye_T = theta_model.value(x, params)
S_a = anharmonic_model.nondimensional_entropy(temperature, debye_T, params)
S_ad = anharmonic_model.nondimensional_dentropy_dTheta(
temperature, debye_T, params
)
aK_T = dAdV * S_a + A * (theta_model.dVrel(x, params) / params["V_0"]) * S_ad
return aK_T
[docs]
@staticmethod
def validate_parameters(params):
# Check for all required keys
expected_keys = [
"debye_temperature_model",
"anharmonic_prefactor_model",
"anharmonic_thermal_model",
"V_0",
"T_0",
]
for key in expected_keys:
if key not in params:
raise AttributeError(f"params dictionary must contain an '{key}' key")
# Validate the three models:
models = [
params["debye_temperature_model"],
params["anharmonic_prefactor_model"],
params["anharmonic_thermal_model"],
]
for model in models:
model.validate_parameters(params)
# Check that the required methods are present in the
# debye_temperature_model
expected_methods = ["value", "dVrel", "dVrel2"]
for method in expected_methods:
if not hasattr(params["debye_temperature_model"], method) or not callable(
getattr(params["debye_temperature_model"], method)
):
raise AttributeError(
f"params['debye_temperature_model'] must have a {method} method that takes arguments Vrel and params"
)
# Check that the required methods are present in the
# anharmonic_prefactor_model
expected_methods = ["value", "dVrel", "dVrel2"]
for method in expected_methods:
if not hasattr(
params["anharmonic_prefactor_model"], method
) or not callable(getattr(params["anharmonic_prefactor_model"], method)):
raise AttributeError(
f"params['anharmonic_prefactor_model'] must have a {method} method that takes arguments Vrel and params"
)
# Check that the required methods are present in the
# anharmonic_thermal_model
expected_methods = [
"nondimensional_helmholtz_energy",
"nondimensional_dhelmholtz_dTheta",
"nondimensional_d2helmholtz_dTheta2",
"nondimensional_entropy",
"nondimensional_dentropy_dTheta",
"nondimensional_heat_capacity",
]
for method in expected_methods:
if not hasattr(params["anharmonic_thermal_model"], method) or not callable(
getattr(params["anharmonic_thermal_model"], method)
):
raise AttributeError(
f"params['anharmonic_thermal_model'] must have a {method} method that takes arguments temperature, debye_T, and params"
)