Skip to content

Commit 235800a

Browse files
committed
add set_state_with_volume to Mineral class
1 parent 3d00c6a commit 235800a

File tree

4 files changed

+96
-10
lines changed

4 files changed

+96
-10
lines changed

burnman/classes/material.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,20 @@ def set_state_with_volume(
186186
):
187187
"""
188188
This function acts similarly to set_state, but takes volume and
189-
temperature as input to find the pressure. In order to ensure
190-
self-consistency, this function does not use any pressure functions
191-
from the material classes, but instead finds the pressure using the
192-
brentq root-finding method.
189+
temperature as input to find the pressure.
190+
191+
In order to ensure self-consistency, this function does not
192+
use any pressure functions from the material classes,
193+
but instead finds the pressure using the brentq root-finding
194+
method. To provide more context, even if a mineral is being
195+
evaluated with a P(V, T) equation of state, there might be a
196+
property modifier G_mod(P, T) added on top - which might then
197+
introduce a pressure dependent V_mod(P, T).
198+
Thus, we must solve for the volume iteratively:
199+
V = V_i(P(V_i, T), T) + V_mod(P, T), where P(V_i, T) is
200+
solved for iteratively.
201+
202+
This function is overloaded by the Mineral class.
193203
194204
:param volume: The desired molar volume of the mineral [m^3].
195205
:type volume: float

burnman/classes/mineral.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from .material import Material, material_property
1414
from .. import eos
1515
from ..utils.misc import copy_documentation
16+
from ..utils.math import bracket
17+
from scipy.optimize import brentq
1618

1719

1820
class Mineral(Material):
@@ -145,6 +147,80 @@ def set_state(self, pressure, temperature):
145147
"no method set for mineral, or equation_of_state given in mineral.params"
146148
)
147149

150+
def set_state_with_volume(
151+
self, volume, temperature, pressure_guesses=[0.0e9, 10.0e9]
152+
):
153+
"""
154+
This function acts similarly to set_state, but takes volume and
155+
temperature as input to find the pressure.
156+
157+
In order to ensure self-consistency, this function does not
158+
generally use any pressure functions from the material classes,
159+
but instead finds the pressure using the brentq root-finding
160+
method. To provide more context, even if a mineral is being
161+
evaluated with a P(V, T) equation of state, there might be a
162+
property modifier G_mod(P, T) added on top - which might then
163+
introduce a pressure dependent V_mod(P, T).
164+
Thus, we must solve for the volume iteratively:
165+
V = V_i(P(V_i, T), T) + V_mod(P, T), where P(V_i, T) is
166+
solved for iteratively and V_i is calculated implicitly.
167+
168+
Exceptions to the above: If V_mod is constant,
169+
this is done more efficiently by setting V_i = V - V_mod,
170+
and then directly evaluating P(V_i, T) directly.
171+
172+
:param volume: The desired molar volume of the mineral [m^3].
173+
:type volume: float
174+
175+
:param temperature: The desired temperature of the mineral [K].
176+
:type temperature: float
177+
178+
:param pressure_guesses: A list of floats denoting the initial
179+
low and high guesses for bracketing of the pressure [Pa].
180+
These guesses should preferably bound the correct pressure,
181+
but do not need to do so. More importantly,
182+
they should not lie outside the valid region of
183+
the equation of state. Defaults to [0.e9, 10.e9].
184+
:type pressure_guesses: list
185+
"""
186+
187+
def _delta_volume(pressure, volume, temperature):
188+
# Try to set the state with this pressure,
189+
# but if the pressure is too low most equations of state
190+
# fail. In this case, treat the molar_volume as infinite
191+
# and brentq will try a larger pressure.
192+
try:
193+
self.set_state(pressure, temperature)
194+
return volume - self.molar_volume
195+
except Exception:
196+
return -np.inf
197+
198+
if (
199+
type(self.method) == str
200+
or not self.method.pressure
201+
or self.property_modifiers
202+
):
203+
# we need to have a sign change in [a,b] to find a zero.
204+
args = (volume, temperature)
205+
try:
206+
sol = bracket(
207+
_delta_volume, pressure_guesses[0], pressure_guesses[1], args
208+
)
209+
except ValueError:
210+
try: # Try again with 0 Pa lower bound on the pressure
211+
sol = bracket(_delta_volume, 0.0e9, pressure_guesses[1], args)
212+
except ValueError:
213+
raise Exception(
214+
"Cannot find a pressure, perhaps the volume or starting pressures "
215+
"are outside the range of validity for the equation of state?"
216+
)
217+
pressure = brentq(_delta_volume, sol[0], sol[1], args=args)
218+
self.set_state(pressure, temperature)
219+
else:
220+
self.set_state(
221+
self.method.pressure(temperature, volume, self.params), temperature
222+
)
223+
148224
"""
149225
Properties from equations of state
150226
We choose the P, T properties (e.g. Gibbs(P, T) rather than Helmholtz(V, T)),

misc/benchmarks/ab_initio_solids.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
pressures = np.empty_like(volumes)
5959
for temperature in temperatures:
6060
for i, volume in enumerate(volumes):
61-
pressures[i] = phase.method.pressure(temperature, volume, phase.params)
61+
phase.set_state_with_volume(volume, temperature)
62+
pressures[i] = phase.pressure
6263
ax_P.plot(
6364
volumes * 1e6,
6465
pressures / 1e9,
@@ -77,8 +78,7 @@
7778
energies = np.empty_like(volumes)
7879
for temperature in temperatures:
7980
for i, volume in enumerate(volumes):
80-
P = phase.method.pressure(temperature, volume, phase.params)
81-
phase.set_state(P, temperature)
81+
phase.set_state_with_volume(volume, temperature)
8282
energies[i] = phase.molar_internal_energy
8383
ax_E.plot(
8484
volumes * 1e6,

tests/test_anisotropicsolution.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,13 @@ def test_volume(self):
106106
def test_non_orthotropic_endmember_consistency(self):
107107
ss = make_nonorthotropic_solution()
108108
ss.set_composition([1.0, 0.0])
109-
self.assertTrue(check_anisotropic_eos_consistency(ss, P=2.0e10, tol=1.0e-6))
109+
self.assertTrue(check_anisotropic_eos_consistency(ss, P=2.0e10, tol=1.0e-5))
110110

111111
def test_non_orthotropic_solution_consistency(self):
112112
ss = make_nonorthotropic_solution()
113113
ss.set_composition([0.8, 0.2])
114114
self.assertTrue(
115-
check_anisotropic_eos_consistency(ss, P=2.0e10, T=1000.0, tol=1.0e-6)
115+
check_anisotropic_eos_consistency(ss, P=2.0e10, T=1000.0, tol=1.0e-5)
116116
)
117117

118118
def test_relaxed_non_orthotropic_solution_consistency(self):
@@ -122,7 +122,7 @@ def test_relaxed_non_orthotropic_solution_consistency(self):
122122
)
123123
ss.set_composition([0.8, 0.2], relaxed=False)
124124
self.assertTrue(
125-
check_anisotropic_eos_consistency(ss, P=2.0e10, T=1000.0, tol=1.0e-6)
125+
check_anisotropic_eos_consistency(ss, P=2.0e10, T=1000.0, tol=1.0e-5)
126126
)
127127

128128
def test_non_orthotropic_solution_clone(self):

0 commit comments

Comments
 (0)