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
87import numpy as np
98import warnings
109import 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
0 commit comments