|
13 | 13 | from .material import Material, material_property |
14 | 14 | from .. import eos |
15 | 15 | from ..utils.misc import copy_documentation |
| 16 | +from ..utils.math import bracket |
| 17 | +from scipy.optimize import brentq |
16 | 18 |
|
17 | 19 |
|
18 | 20 | class Mineral(Material): |
@@ -145,6 +147,73 @@ def set_state(self, pressure, temperature): |
145 | 147 | "no method set for mineral, or equation_of_state given in mineral.params" |
146 | 148 | ) |
147 | 149 |
|
| 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 | + If the mineral is an endmember that has a pressure(T, V) |
| 158 | + function and does not have property modifiers, |
| 159 | + this function evaluates the pressure directly. |
| 160 | + Otherwise, it finds the pressure using the brentq root-finding |
| 161 | + method on the molar_volume attribute of the mineral. |
| 162 | + This ensures self-consistency even if the mineral has a |
| 163 | + pressure-dependent volume modifier. |
| 164 | +
|
| 165 | + :param volume: The desired molar volume of the mineral [m^3]. |
| 166 | + :type volume: float |
| 167 | +
|
| 168 | + :param temperature: The desired temperature of the mineral [K]. |
| 169 | + :type temperature: float |
| 170 | +
|
| 171 | + :param pressure_guesses: A list of floats denoting the initial |
| 172 | + low and high guesses for bracketing of the pressure [Pa]. |
| 173 | + These guesses should preferably bound the correct pressure, |
| 174 | + but do not need to do so. More importantly, |
| 175 | + they should not lie outside the valid region of |
| 176 | + the equation of state. Defaults to [0.e9, 10.e9]. |
| 177 | + :type pressure_guesses: list |
| 178 | + """ |
| 179 | + |
| 180 | + def _delta_volume(pressure, volume, temperature): |
| 181 | + # Try to set the state with this pressure, |
| 182 | + # but if the pressure is too low most equations of state |
| 183 | + # fail. In this case, treat the molar_volume as infinite |
| 184 | + # and brentq will try a larger pressure. |
| 185 | + try: |
| 186 | + self.set_state(pressure, temperature) |
| 187 | + return volume - self.molar_volume |
| 188 | + except Exception: |
| 189 | + return -np.inf |
| 190 | + |
| 191 | + if ( |
| 192 | + type(self.method) == str # require an endmember method |
| 193 | + or not self.method.pressure # that has a P(V, T) function |
| 194 | + or self.property_modifiers # and that doesn't have modifiers |
| 195 | + ): |
| 196 | + # we need to have a sign change in [a,b] to find a zero. |
| 197 | + args = (volume, temperature) |
| 198 | + try: |
| 199 | + sol = bracket( |
| 200 | + _delta_volume, pressure_guesses[0], pressure_guesses[1], args |
| 201 | + ) |
| 202 | + except ValueError: |
| 203 | + try: # Try again with 0 Pa lower bound on the pressure |
| 204 | + sol = bracket(_delta_volume, 0.0e9, pressure_guesses[1], args) |
| 205 | + except ValueError: |
| 206 | + raise Exception( |
| 207 | + "Cannot find a pressure, perhaps the volume or starting pressures " |
| 208 | + "are outside the range of validity for the equation of state?" |
| 209 | + ) |
| 210 | + pressure = brentq(_delta_volume, sol[0], sol[1], args=args) |
| 211 | + self.set_state(pressure, temperature) |
| 212 | + else: |
| 213 | + self.set_state( |
| 214 | + self.method.pressure(temperature, volume, self.params), temperature |
| 215 | + ) |
| 216 | + |
148 | 217 | """ |
149 | 218 | Properties from equations of state |
150 | 219 | We choose the P, T properties (e.g. Gibbs(P, T) rather than Helmholtz(V, T)), |
|
0 commit comments