Skip to content

Commit 8277500

Browse files
authored
Merge pull request geodynamics#676 from bobmyhill/equilibration_tolerances
Improve equilibration tolerances
2 parents 7a06efe + 5c86daa commit 8277500

File tree

4 files changed

+90
-20
lines changed

4 files changed

+90
-20
lines changed

burnman/optimize/nonlinear_solvers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,11 @@ def __init__(
119119
), "The starting guess is outside the supplied constraints."
120120

121121
if not isinstance(self.tol, float):
122-
assert len(self.tol) < len(
122+
self.tol = np.asarray(self.tol, dtype=np.float64)
123+
assert self.tol.ndim == 1, "tol must be a float or a 1D array."
124+
assert len(self.tol) == len(
123125
self.guess
124-
), "tol must either be a float or an array like guess."
126+
), f"tol must either be a float or an array like guess. Got len(tol)={len(self.tol)} and len(guess)={len(self.guess)}."
125127

126128
def _constraints(self, x: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
127129
return np.dot(self.linear_constraints[0], x) + self.linear_constraints[1]

burnman/tools/equilibration.py

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -607,29 +607,48 @@ def get_equilibration_parameters(assemblage, composition, free_compositional_vec
607607
degrees of freedom for the equilibrium problem.
608608
:type free_compositional_vectors: list of dictionaries
609609
610-
:returns: A tuple with attributes n_parameters
611-
(the number of parameters for the current equilibrium problem)
612-
and phase_amount_indices (the indices of the parameters that
613-
correspond to phase amounts).
610+
:returns: A tuple with attributes:
611+
- parameter_names: list of strings, the names of the parameters
612+
for the current equilibrium problem
613+
- default_tolerances: numpy.array, the default tolerances for each parameter
614+
- n_parameters: int, the number of parameters for the current equilibrium problem
615+
- phase_amount_indices: list of int, the indices of the parameters that
616+
correspond to phase amounts
617+
- bulk_composition_vector: numpy.array, the bulk composition vector
618+
- free_compositional_vectors: 2D numpy.array, the free compositional vectors
619+
- reduced_composition_vector: numpy.array, the bulk composition vector
620+
reduced to the independent elements
621+
- reduced_free_composition_vectors: 2D numpy.array, the free compositional vectors
622+
reduced to the independent elements
623+
- constraint_vector: numpy.array, vector for the linear constraints bounding the
624+
valid parameter space (b in Ax + b < eps)
625+
- constraint_matrix: 2D numpy.array, matrix for the linear constraints bounding the
626+
valid parameter space (A in Ax + b < eps)
614627
:rtype: namedtuple
615628
"""
616629
# Initialize a named tuple for the equilibration parameters
617630
prm = namedtuple("assemblage_parameters", [])
618631

619632
# Process parameter names
620633
prm.parameter_names = ["Pressure (Pa)", "Temperature (K)"]
634+
prm.default_tolerances = [1.0, 1.0e-3]
621635
for i, n in enumerate(assemblage.endmembers_per_phase):
622636
prm.parameter_names.append("x({0})".format(assemblage.phases[i].name))
637+
prm.default_tolerances.append(1.0e-6)
623638
if n > 1:
624639
p_names = [
625640
"p({0} in {1})".format(n, assemblage.phases[i].name)
626641
for n in assemblage.phases[i].endmember_names[1:]
627642
]
628643
prm.parameter_names.extend(p_names)
644+
prm.default_tolerances.extend([1.0e-6] * len(p_names))
629645

630646
n_free_compositional_vectors = len(free_compositional_vectors)
631647
for i in range(n_free_compositional_vectors):
632648
prm.parameter_names.append(f"v_{i}")
649+
prm.default_tolerances.append(1.0e-6)
650+
651+
prm.default_tolerances = np.array(prm.default_tolerances)
633652

634653
prm.n_parameters = len(prm.parameter_names)
635654
prm.phase_amount_indices = [
@@ -797,7 +816,7 @@ def equilibrate(
797816
assemblage,
798817
equality_constraints,
799818
free_compositional_vectors=[],
800-
tol=1.0e-3,
819+
tol=None,
801820
store_iterates=False,
802821
store_assemblage=True,
803822
max_iterations=100.0,
@@ -866,8 +885,16 @@ def equilibrate(
866885
Vector given in atomic (molar) units of elements.
867886
:type free_compositional_vectors: list of dict
868887
869-
:param tol: The tolerance for the nonlinear solver.
870-
:type tol: float
888+
:param tol: The tolerance for the nonlinear solver. This can be a single float,
889+
which is then applied to all parameters, or a numpy array with the
890+
same length as the number of parameters in the solve (2 + number of
891+
phases + number of free compositional vectors). The default is None,
892+
which sets the tolerances to 1 Pa for pressure, 1e-3 K for temperature,
893+
1e-6 for phase amounts and 1e-6 for free compositional vectors.
894+
One of the termination criteria for the nonlinear solver is that
895+
the absolute change in each parameter is less than the corresponding
896+
tolerance.
897+
:type tol: float or numpy.array
871898
872899
:param store_iterates: Whether to store the parameter values for
873900
each iteration in each solution object.
@@ -932,6 +959,10 @@ def equilibrate(
932959
assemblage, composition, free_compositional_vectors
933960
)
934961

962+
# Set default tolerances if not provided
963+
if tol is None:
964+
tol = prm.default_tolerances
965+
935966
# Check equality constraints have the correct structure
936967
# Convert into the format readable by the function and jacobian functions
937968
eq_constraint_lists = process_eq_constraints(equality_constraints, assemblage, prm)

misc/benchmarks/slb_2024_benchmarks.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,13 @@ def check_fig_3_fcc_ferric_fper():
196196

197197
assemblage = Composite([bcc, fcc], [0.5, 0.5])
198198
equality_constraints = [["P", 1.0e5], ["phase_fraction", (bcc, 0.0)]]
199-
sol = equilibrate(composition, assemblage, equality_constraints, tol=1.0e-5)
199+
sol = equilibrate(composition, assemblage, equality_constraints)
200200
T_a_g = sol[0].assemblage.temperature
201201

202202
assemblage = Composite([bcc, fper], [0.5, 0.5])
203203
temperatures = np.linspace(T_a_wu_mag, T_a_g, 31)
204204
equality_constraints = [["P", 1.0e5], ["T", temperatures]]
205-
sol = equilibrate(composition, assemblage, equality_constraints, tol=1.0e-5)
205+
sol = equilibrate(composition, assemblage, equality_constraints)
206206
xs = np.empty_like(temperatures)
207207
for i, s in enumerate(sol[0]):
208208
xs[i] = -s.assemblage.phases[1].formula["Fe"] / 4.0
@@ -211,16 +211,17 @@ def check_fig_3_fcc_ferric_fper():
211211
assemblage = Composite([fcc, fper], [0.5, 0.5])
212212
temperatures = np.linspace(T_a_g, 1800.0, 31)
213213
equality_constraints = [["P", 1.0e5], ["T", temperatures]]
214-
sol = equilibrate(composition, assemblage, equality_constraints, tol=1.0e-5)
214+
sol = equilibrate(composition, assemblage, equality_constraints)
215215
xs = np.empty_like(temperatures)
216216
for i, s in enumerate(sol[0]):
217217
xs[i] = -s.assemblage.phases[1].formula["Fe"] / 4.0
218218
ax[0].plot(xs, temperatures, linestyle=":", linewidth=3.0)
219219

220+
composition = {"Fe": 1.0, "O": 1.3}
220221
assemblage = Composite([mag, fper], [0.5, 0.5])
221222
temperatures = np.linspace(T_a_wu_mag, 1800.0, 31)
222223
equality_constraints = [["P", 1.0e5], ["T", temperatures]]
223-
sol = equilibrate(composition, assemblage, equality_constraints, tol=1.0e-5)
224+
sol = equilibrate(composition, assemblage, equality_constraints)
224225
xs = np.empty_like(temperatures)
225226
for i, s in enumerate(sol[0]):
226227
xs[i] = -s.assemblage.phases[1].formula["Fe"] / 4.0
@@ -246,9 +247,7 @@ def check_fig_3_fcc_ferric_fper():
246247
for i, x_MgO in enumerate(x_MgOs):
247248
composition = {"Mg": x_MgO, "Fe": 2.0, "O": 1.0}
248249
try:
249-
sol = equilibrate(
250-
composition, assemblage, equality_constraints, tol=1.0e-5
251-
)
250+
sol = equilibrate(composition, assemblage, equality_constraints)
252251
if sol[0].success:
253252
f = assemblage.phases[0].formula
254253
x_Mgs[i] = f["Mg"] / 4.0
@@ -408,7 +407,7 @@ def check_fig_7_fO2():
408407
["phase_fraction", [a.phases[0], 0.0]],
409408
["T", 1500.0],
410409
]
411-
equilibrate(a.phases[0].formula, a, equality_constraints, tol=1.0e-5)
410+
equilibrate(a.phases[0].formula, a, equality_constraints)
412411
P_bounds[i][1] = a.pressure
413412
P_bounds[i + 1][0] = a.pressure
414413

@@ -437,9 +436,9 @@ def check_fig_7_fO2():
437436
for i, P in enumerate(pressures):
438437
assemblage.set_state(P, T)
439438
try:
440-
sol = equilibrate(
441-
assemblage.formula, assemblage, [["P", P], ["T", T]], tol=1.0e-7
442-
)[0]
439+
sol = equilibrate(assemblage.formula, assemblage, [["P", P], ["T", T]])[
440+
0
441+
]
443442
if sol.success:
444443
mu_O2 = assemblage.chemical_potential([{"O": 2.0}])[0]
445444
O2_gas.set_state(1.0e5, T)

tests/test_equilibration.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,44 @@ def test_binary_solution_convergence(self):
144144
sol, prm = equilibrate(composition, assemblage, equality_constraints)
145145
self.assertFalse(any(not s.success for s in sol))
146146

147+
def test_incorrect_tol(self):
148+
ol = SLB_2011.mg_fe_olivine()
149+
wad = SLB_2011.mg_fe_wadsleyite()
150+
151+
assemblage = burnman.Composite([ol, wad], [0.7, 0.3])
152+
ol.set_composition([0.5, 0.5])
153+
wad.set_composition([0.6, 0.4])
154+
155+
assemblage.set_state(10.0e9, 1200.0)
156+
equality_constraints = [
157+
("P", 10.0e9),
158+
(
159+
"phase_composition",
160+
(ol, (["Mg_A", "Fe_A"], [0.0, 1.0], [1.0, 1.0], 0.45)),
161+
),
162+
]
163+
composition = {"Mg": 1.0, "Fe": 1.0, "Si": 1.0, "O": 4.0}
164+
165+
# These should raise errors
166+
with self.assertRaises(AssertionError):
167+
_ = equilibrate(
168+
composition, assemblage, equality_constraints, tol=[1.0e-3, 1.0e-3]
169+
)
170+
with self.assertRaises(AssertionError):
171+
_ = equilibrate(
172+
composition, assemblage, equality_constraints, tol=[[1.0e-3] * 6] * 2
173+
)
174+
175+
# These should work
176+
_ = equilibrate(composition, assemblage, equality_constraints, tol=1.0e-3)
177+
_ = equilibrate(
178+
composition,
179+
assemblage,
180+
equality_constraints,
181+
tol=[1.0e-3] * 6,
182+
)
183+
_ = equilibrate(composition, assemblage, equality_constraints)
184+
147185

148186
if __name__ == "__main__":
149187
unittest.main()

0 commit comments

Comments
 (0)