Skip to content

Commit f52165b

Browse files
committed
removed numerical derivatives in Brosh-Calphad EoS
1 parent a81e4e0 commit f52165b

File tree

2 files changed

+126
-14
lines changed

2 files changed

+126
-14
lines changed

burnman/eos/brosh_calphad.py

Lines changed: 124 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
from __future__ import absolute_import
2-
31
# This file is part of BurnMan - a thermoelastic and thermodynamic toolkit for
42
# the Earth and Planetary Sciences
5-
# Copyright (C) 2012 - 2021 by the BurnMan team, released under the GNU
3+
# Copyright (C) 2012 - 2025 by the BurnMan team, released under the GNU
64
# GPL v2 or later.
75

6+
from __future__ import absolute_import
87
import numpy as np
98
import warnings
109
import pkgutil
@@ -44,11 +43,7 @@ def volume(self, pressure, temperature, params):
4443
)
4544

4645
nu = self._theta(pressure, params) / temperature
47-
dP = 1000.0
48-
dthetadP = (
49-
self._theta(pressure + dP / 2.0, params)
50-
- self._theta(pressure - dP / 2.0, params)
51-
) / dP
46+
dthetadP = self._dtheta_dP(pressure, params)
5247
V_qh = (
5348
3.0
5449
* params["n"]
@@ -76,6 +71,50 @@ def volume(self, pressure, temperature, params):
7671
# V = dG_c/dP + dG_qh/dP - C_T*(dI_P/dP)
7772
return V_c + V_qh + V_th
7873

74+
def dvolume_dP(self, pressure, temperature, params):
75+
K_0 = params["K_0"]
76+
n = params["n"]
77+
b = params["b"][1]
78+
delta = params["delta"][1]
79+
80+
dVc_dP = 0.0
81+
82+
for i in range(2, 6):
83+
a_i = params["a"][i - 2]
84+
c_i = params["c"][i - 2]
85+
power_term = 1.0 + i * pressure / (3.0 * a_i * K_0)
86+
z_i = power_term ** (1.0 / i)
87+
x_i = 1.0 / (1.0 - a_i + a_i * z_i)
88+
dx_i_dP = -(x_i**2) / (3.0 * K_0) * power_term ** ((1.0 / i) - 1.0)
89+
dVc_dP_i = c_i * 3.0 * x_i**2 * dx_i_dP
90+
dVc_dP += dVc_dP_i
91+
92+
dVc_dP *= params["V_0"]
93+
94+
theta = self._theta(pressure, params)
95+
dthetadP = self._dtheta_dP(pressure, params)
96+
d2thetadP2 = self._d2theta_dP2(pressure, params)
97+
98+
nu = theta / temperature
99+
exp_neg_nu = np.exp(-nu)
100+
denom = 1.0 - exp_neg_nu
101+
102+
factor1 = -(exp_neg_nu) / (denom**2) * (dthetadP**2) / temperature
103+
factor2 = (exp_neg_nu) / denom * d2thetadP2
104+
105+
dV_qh_dP = 3.0 * n * gas_constant * (factor1 + factor2)
106+
107+
alpha = 2.0 * b * (1.0 + delta) / K_0
108+
f = np.sqrt(1.0 + alpha * pressure)
109+
exp_term = np.exp((1.0 - f) / b)
110+
111+
numerator = self._C_T(temperature, params) * (1.0 + delta) ** 2 * exp_term
112+
denominator = K_0**2 * (1.0 + b) * f
113+
114+
dV_th_dP = -numerator / denominator
115+
116+
return dVc_dP + dV_qh_dP + dV_th_dP
117+
79118
def pressure(self, temperature, volume, params):
80119
def _delta_volume(pressure):
81120
return self.volume(pressure, temperature, params) - volume
@@ -96,12 +135,7 @@ def isothermal_bulk_modulus_reuss(self, pressure, temperature, volume, params):
96135
as a function of pressure :math:`[Pa]`,
97136
temperature :math:`[K]` and volume :math:`[m^3]`.
98137
"""
99-
dP = 1000.0
100-
dV = self.volume(pressure + dP / 2.0, temperature, params) - self.volume(
101-
pressure - dP / 2.0, temperature, params
102-
)
103-
104-
return -volume * dP / dV
138+
return -volume / self.dvolume_dP(pressure, temperature, params)
105139

106140
def shear_modulus(self, pressure, temperature, volume, params):
107141
"""
@@ -217,6 +251,28 @@ def d(k, Xn):
217251
)
218252
) # eq. A9, CHECKED
219253

254+
def _dGamma_dXT2(self, n, an, Xn):
255+
total = 0.0
256+
for k in range(0, n + 1):
257+
coeff = binom(n, k) * (an - 1.0) ** (n - k)
258+
if k != 3:
259+
term = (3.0 - k) * Xn ** (2.0 - k) * k / (k - 3.0)
260+
else:
261+
term = -3.0 / Xn
262+
total += coeff * term
263+
return 3.0 * an ** (1.0 - n) / float(n) * total
264+
265+
def _d2Gamma_dXT2_2(self, n, an, Xn):
266+
total = 0.0
267+
for k in range(0, n + 1):
268+
coeff = binom(n, k) * (an - 1.0) ** (n - k)
269+
if k != 3:
270+
term = (2.0 - k) * (3.0 - k) * Xn ** (1.0 - k) * k / (k - 3.0)
271+
else:
272+
term = 3.0 / (Xn**2)
273+
total += coeff * term
274+
return 3.0 * an ** (1.0 - n) / float(n) * total
275+
220276
def _theta(self, pressure, params):
221277
# Theta (for quasiharmonic term)
222278
ab2 = 1.0 / (3.0 * params["b"][0] - 1.0)
@@ -232,6 +288,60 @@ def _theta(self, pressure, params):
232288
* (self._Gamma(2, ab2, XT2) - self._Gamma(2, ab2, 1.0))
233289
)
234290

291+
def _dtheta_dP(self, pressure, params):
292+
ab2 = 1.0 / (3.0 * params["b"][0] - 1.0)
293+
K0b = params["K_0"] / (1.0 + params["delta"][0])
294+
A = 2.0 / (3.0 * ab2 * K0b)
295+
y = np.sqrt(1.0 + A * pressure)
296+
XT2 = 1.0 / (1.0 - ab2 + ab2 * y)
297+
298+
dG_dXT2 = self._dGamma_dXT2(2, ab2, XT2)
299+
300+
# dXT2/dy
301+
dXT2_dy = -ab2 / (1.0 - ab2 + ab2 * y) ** 2
302+
303+
# dy/dP
304+
dy_dP = 0.5 * A / np.sqrt(1.0 + A * pressure)
305+
306+
# Prefactor
307+
B = params["grueneisen_0"] / (1.0 + params["delta"][0])
308+
309+
# Theta
310+
theta_val = self._theta(pressure, params)
311+
312+
return theta_val * B * dG_dXT2 * dXT2_dy * dy_dP
313+
314+
def _d2theta_dP2(self, pressure, params):
315+
ab2 = 1.0 / (3.0 * params["b"][0] - 1.0)
316+
K0b = params["K_0"] / (1.0 + params["delta"][0])
317+
A = 2.0 / (3.0 * ab2 * K0b)
318+
y = np.sqrt(1.0 + A * pressure)
319+
dy_dP = 0.5 * A / np.sqrt(1.0 + A * pressure)
320+
d2y_dP2 = -0.25 * A * A / (1.0 + A * pressure) ** (3.0 / 2.0)
321+
322+
XT2 = 1.0 / (1.0 - ab2 + ab2 * y)
323+
D = 1.0 - ab2 + ab2 * y
324+
dXT2_dy = -ab2 / (D**2)
325+
d2XT2_dy2 = 2.0 * ab2 * ab2 / (D**3)
326+
327+
XT2_prime = dXT2_dy * dy_dP
328+
XT2_double_prime = d2XT2_dy2 * (dy_dP**2) + dXT2_dy * d2y_dP2
329+
330+
# Gamma derivatives
331+
dGamma_dXT2 = self._dGamma_dXT2(2, ab2, XT2)
332+
d2Gamma_dXT2_2 = self._d2Gamma_dXT2_2(2, ab2, XT2)
333+
334+
B = params["grueneisen_0"] / (1.0 + params["delta"][0])
335+
theta_val = self._theta(pressure, params)
336+
dtheta_dP = self._dtheta_dP(pressure, params)
337+
338+
return (
339+
theta_val
340+
* B
341+
* (d2Gamma_dXT2_2 * XT2_prime**2 + dGamma_dXT2 * XT2_double_prime)
342+
+ dtheta_dP * B * dGamma_dXT2 * XT2_prime
343+
)
344+
235345
def _interpolating_function(self, pressure, params):
236346
f = np.sqrt(
237347
1.0

tests/test_endmembers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import burnman
88
from burnman import Mineral, CombinedMineral
99
from burnman.utils.chemistry import dictionarize_formula, formula_mass
10+
from burnman.tools.eos import check_eos_consistency
1011

1112

1213
class forsterite(Mineral):
@@ -136,6 +137,7 @@ def test_brosh_pressure(self):
136137
self.assertTrue(
137138
abs(fcc.pressure - fcc.method.pressure(500.0, fcc.V, fcc.params)) < 1000.0
138139
)
140+
self.assertTrue(check_eos_consistency(fcc, including_shear_properties=False))
139141

140142

141143
if __name__ == "__main__":

0 commit comments

Comments
 (0)