Skip to content

Commit 0d7b01f

Browse files
authored
Merge pull request #710 from bobmyhill/equilibrate_relaxed_composite
Equilibrate relaxed composite
2 parents 8e4460f + b8a7fd6 commit 0d7b01f

File tree

4 files changed

+132
-82
lines changed

4 files changed

+132
-82
lines changed

burnman/classes/material.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ class Material(object):
5555
"""
5656

5757
def __init__(self):
58-
self._number_of_moles = 1.0
58+
if not hasattr(self, "number_of_moles"):
59+
self.number_of_moles = 1.0
5960
if not hasattr(self, "name"):
6061
# if a derived class decides to set .name before calling this
6162
# constructor (I am looking at you, SLB_2011.py!), do not
@@ -212,6 +213,7 @@ def number_of_moles(self, value):
212213
:param value: Number of moles.
213214
:type value: float
214215
"""
216+
self.reset()
215217
if value < 0:
216218
raise ValueError("The number of moles cannot be negative")
217219
self._number_of_moles = value

burnman/classes/relaxedcomposite.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,17 @@ def set_state(self, pressure, temperature, bulk_composition=None):
108108
)
109109
assert sol.success, "Equilibration failed in RelaxedComposite.set_state()"
110110

111+
self.copy_state_from_unrelaxed()
112+
113+
def copy_state_from_unrelaxed(self):
114+
"""
115+
Copies the state (P, T, molar_fractions) from the unrelaxed
116+
composite to the relaxed composite. This can be useful
117+
if the unrelaxed composite has been modified externally
118+
and the relaxed composite needs to be updated to match.
119+
"""
111120
# Set the relaxed composite to the equilibrated state
112-
Composite.set_state(self, pressure, temperature)
121+
Composite.set_state(self, self.unrelaxed.pressure, self.unrelaxed.temperature)
113122
Composite.set_fractions(self, self.unrelaxed.molar_fractions)
114123
self.number_of_moles = self.unrelaxed.number_of_moles
115124

burnman/tools/equilibration.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,6 @@ def set_compositions_and_state_from_parameters(assemblage, parameters):
159159
:param parameters: The current parameter values.
160160
:type parameters: numpy.array
161161
"""
162-
163-
assemblage.set_state(parameters[0] * P_scaling, parameters[1] * T_scaling)
164162
i = 2
165163
phase_amounts = np.zeros(len(assemblage.phases))
166164
for phase_idx, ph in enumerate(assemblage.phases):
@@ -179,6 +177,15 @@ def set_compositions_and_state_from_parameters(assemblage, parameters):
179177
phase_amounts = np.abs(phase_amounts)
180178
assemblage.number_of_moles = sum(phase_amounts)
181179
assemblage.set_fractions(phase_amounts / assemblage.number_of_moles)
180+
181+
# We set_state last because setting the state may depend on the
182+
# current composition of the assemblage. This would occur, for example,
183+
# in the case of relaxed composites, where the relaxed state depends
184+
# on the current bulk composition.
185+
# Note that in the case of relaxed composites, the equilibrate function
186+
# actually equilibrates the unrelaxed assemblage, and then copies the
187+
# state to the relaxed assemblage afterwards.
188+
assemblage.set_state(parameters[0] * P_scaling, parameters[1] * T_scaling)
182189
return None
183190

184191

@@ -922,7 +929,7 @@ def equilibrate(
922929
:type composition: dict
923930
924931
:param assemblage: The assemblage to be equilibrated.
925-
:type assemblage: :class:`burnman.Composite`
932+
:type assemblage: :class:`burnman.Composite` or :class:`burnman.RelaxedComposite`
926933
927934
:param equality_constraints: The list of equality constraints. See above
928935
for valid formats.
@@ -978,6 +985,11 @@ def equilibrate(
978985
outer_logger_level = logger.level
979986
logger.setLevel(logging.INFO if verbose else outer_logger_level)
980987

988+
relaxed_assemblage = None
989+
if hasattr(assemblage, "unrelaxed"):
990+
relaxed_assemblage = assemblage
991+
assemblage = assemblage.unrelaxed
992+
981993
for ph in assemblage.phases:
982994
if isinstance(ph, Solution) and not hasattr(ph, "molar_fractions"):
983995
raise Exception(
@@ -1155,7 +1167,11 @@ def lambda_bounds_fn(dx, x):
11551167
)
11561168
sol = sol3
11571169

1158-
if sol.success and len(assemblage.reaction_affinities) > 0.0:
1170+
if relaxed_assemblage is not None:
1171+
relaxed_assemblage.copy_state_from_unrelaxed()
1172+
assemblage = relaxed_assemblage
1173+
1174+
if sol.success and len(assemblage.reaction_affinities) > 0:
11591175
maxres = np.max(np.abs(assemblage.reaction_affinities)) + 1.0e-5
11601176
assemblage.equilibrium_tolerance = maxres
11611177

tests/test_composite.py

Lines changed: 99 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,43 @@
66
from burnman import minerals, Composite, RelaxedComposite
77
from burnman.minerals import HP_2011_ds62, mp50MnNCKFMASHTO
88
from burnman.tools.eos import check_eos_consistency
9+
from burnman.tools.equilibration import equilibrate
10+
11+
properties_extensive = [
12+
"internal_energy",
13+
"helmholtz",
14+
"gibbs",
15+
"enthalpy",
16+
"entropy",
17+
"volume",
18+
"heat_capacity_p",
19+
"heat_capacity_v",
20+
"mass",
21+
"H",
22+
"S",
23+
"V",
24+
"C_p",
25+
"C_v",
26+
]
27+
28+
properties_intensive = [
29+
"molar_internal_energy",
30+
"molar_helmholtz",
31+
"molar_gibbs",
32+
"molar_enthalpy",
33+
"molar_entropy",
34+
"molar_volume",
35+
"molar_heat_capacity_p",
36+
"molar_heat_capacity_v",
37+
"isothermal_bulk_modulus_reuss",
38+
"isentropic_bulk_modulus_reuss",
39+
"isothermal_compressibility_reuss",
40+
"isentropic_compressibility_reuss",
41+
"grueneisen_parameter",
42+
"thermal_expansivity",
43+
"molar_mass",
44+
"density",
45+
]
946

1047

1148
def setup_assemblage():
@@ -523,6 +560,37 @@ def test_single_phase_composite_aliases(self):
523560
for i in range(len(properties)):
524561
self.assertFloatEqual(rock_prps[i], alias_prps[i])
525562

563+
def test_number_of_moles_composite(self):
564+
min1 = minerals.SLB_2011.mg_perovskite()
565+
min2 = minerals.SLB_2011.periclase()
566+
molar_fractions = [0.5, 0.5]
567+
c1 = burnman.Composite([min1, min2], molar_fractions, fraction_type="molar")
568+
c2 = burnman.Composite([min1, min2], molar_fractions, fraction_type="molar")
569+
c1.set_state(40.0e9, 2000.0)
570+
c2.set_state(40.0e9, 2000.0)
571+
572+
for prop in properties_extensive:
573+
val1 = getattr(c1, prop)
574+
val2 = getattr(c2, prop)
575+
self.assertFloatEqual(val1, val2)
576+
577+
for prop in properties_intensive:
578+
val1 = getattr(c1, prop)
579+
val2 = getattr(c2, prop)
580+
self.assertFloatEqual(val1, val2)
581+
582+
c2.number_of_moles = 2.0
583+
584+
for prop in properties_extensive:
585+
val1 = getattr(c1, prop)
586+
val2 = getattr(c2, prop)
587+
self.assertFloatEqual(val1, val2 / 2.0)
588+
589+
for prop in properties_intensive:
590+
val1 = getattr(c1, prop)
591+
val2 = getattr(c2, prop)
592+
self.assertFloatEqual(val1, val2)
593+
526594
def test_relaxed_composite(self):
527595
assemblage = setup_assemblage()
528596
old_formula = assemblage.formula.copy()
@@ -595,47 +663,11 @@ def test_property_extensivity_composite(self):
595663
c1.set_state(40.0e9, 2000.0)
596664
c2.set_state(40.0e9, 2000.0)
597665

598-
properties_extensive = [
599-
"internal_energy",
600-
"helmholtz",
601-
"gibbs",
602-
"enthalpy",
603-
"entropy",
604-
"volume",
605-
"heat_capacity_p",
606-
"heat_capacity_v",
607-
"mass",
608-
"V",
609-
"S",
610-
"H",
611-
"C_v",
612-
"C_p",
613-
]
614-
615666
for prop in properties_extensive:
616667
val1 = getattr(c1, prop)
617668
val2 = getattr(c2, prop)
618669
self.assertFloatEqual(val1, val2 / 2.0)
619670

620-
properties_intensive = [
621-
"molar_internal_energy",
622-
"molar_helmholtz",
623-
"molar_gibbs",
624-
"molar_enthalpy",
625-
"molar_entropy",
626-
"molar_volume",
627-
"molar_heat_capacity_p",
628-
"molar_heat_capacity_v",
629-
"isothermal_bulk_modulus_reuss",
630-
"isentropic_bulk_modulus_reuss",
631-
"isothermal_compressibility_reuss",
632-
"isentropic_compressibility_reuss",
633-
"grueneisen_parameter",
634-
"thermal_expansivity",
635-
"molar_mass",
636-
"density",
637-
]
638-
639671
for prop in properties_intensive:
640672
val1 = getattr(c1, prop)
641673
val2 = getattr(c2, prop)
@@ -663,18 +695,28 @@ def test_formula_extensivity_relaxed_composite(self):
663695
formula = c1_unrelaxed.formula.copy()
664696
formula2 = c2_unrelaxed.formula.copy()
665697

666-
self.assertFloatEqual(formula["Mg"] * 2.0, formula2["Mg"])
698+
for element in ["Na", "Fe", "Mg", "Al", "Si", "O", "Cr"]:
699+
self.assertFloatEqual(formula[element], formula2[element] / 2.0)
667700

668701
c1 = RelaxedComposite(c1_unrelaxed, c1_unrelaxed.reaction_basis)
669702
c2 = RelaxedComposite(c2_unrelaxed, c2_unrelaxed.reaction_basis)
670703

704+
for element in ["Na", "Fe", "Mg", "Al", "Si", "O", "Cr"]:
705+
self.assertFloatEqual(c1.formula[element], c2.formula[element] / 2.0)
706+
self.assertFloatEqual(c1.unrelaxed.formula[element], c1.formula[element])
707+
self.assertFloatEqual(c2.unrelaxed.formula[element], c2.formula[element])
708+
671709
c1.set_state(40.0e9, 2000.0)
672710
c2.set_state(40.0e9, 2000.0)
673711

712+
self.assertArraysAlmostEqual(c1.molar_fractions, c2.molar_fractions)
713+
714+
for element in ["Na", "Fe", "Mg", "Al", "Si", "O", "Cr"]:
715+
self.assertFloatEqual(c1.formula[element], c2.formula[element] / 2.0)
716+
self.assertFloatEqual(c1.unrelaxed.formula[element], c1.formula[element])
717+
self.assertFloatEqual(c2.unrelaxed.formula[element], c2.formula[element])
718+
674719
self.assertFloatEqual(c1.number_of_moles, c2.number_of_moles / 2.0)
675-
self.assertFloatEqual(c1.formula["Mg"], c2.formula["Mg"] / 2.0)
676-
self.assertFloatEqual(c1.formula["Si"], c2.formula["Si"] / 2.0)
677-
self.assertFloatEqual(c1.formula["O"], c2.formula["O"] / 2.0)
678720
self.assertFloatEqual(c1.molar_mass, c2.molar_mass)
679721
self.assertFloatEqual(c1.mass, c2.mass / 2.0)
680722

@@ -699,52 +741,33 @@ def test_property_extensivity_relaxed_composite(self):
699741
c1.set_state(40.0e9, 2000.0)
700742
c2.set_state(40.0e9, 2000.0)
701743

702-
properties_extensive = [
703-
"internal_energy",
704-
"helmholtz",
705-
"gibbs",
706-
"enthalpy",
707-
"entropy",
708-
"volume",
709-
"heat_capacity_p",
710-
"heat_capacity_v",
711-
"mass",
712-
"H",
713-
"S",
714-
"V",
715-
"C_p",
716-
"C_v",
717-
]
718-
719744
for prop in properties_extensive:
720745
val1 = getattr(c1, prop)
721746
val2 = getattr(c2, prop)
722747
self.assertFloatEqual(val1, val2 / 2.0)
723748

724-
properties_intensive = [
725-
"molar_internal_energy",
726-
"molar_helmholtz",
727-
"molar_gibbs",
728-
"molar_enthalpy",
729-
"molar_entropy",
730-
"molar_volume",
731-
"molar_heat_capacity_p",
732-
"molar_heat_capacity_v",
733-
"isothermal_bulk_modulus_reuss",
734-
"isentropic_bulk_modulus_reuss",
735-
"isothermal_compressibility_reuss",
736-
"isentropic_compressibility_reuss",
737-
"grueneisen_parameter",
738-
"thermal_expansivity",
739-
"molar_mass",
740-
"density",
741-
]
742-
743749
for prop in properties_intensive:
744750
val1 = getattr(c1, prop)
745751
val2 = getattr(c2, prop)
746752
self.assertFloatEqual(val1, val2)
747753

754+
def test_equilibrate_relaxed_composite(self):
755+
756+
assemblage = setup_assemblage()
757+
relaxed_assemblage = RelaxedComposite(assemblage, assemblage.reaction_basis)
758+
equality_constraints = [["P", 0.5e9], ["T", 900.0]]
759+
equilibrate(
760+
relaxed_assemblage.formula, relaxed_assemblage, equality_constraints
761+
)
762+
f1 = relaxed_assemblage.molar_fractions.copy()
763+
764+
assemblage = setup_assemblage()
765+
relaxed_assemblage = RelaxedComposite(assemblage, assemblage.reaction_basis)
766+
relaxed_assemblage.set_state(0.5e9, 900.0)
767+
f2 = relaxed_assemblage.molar_fractions.copy()
768+
769+
self.assertArraysAlmostEqual(f1, f2)
770+
748771

749772
if __name__ == "__main__":
750773
unittest.main()

0 commit comments

Comments
 (0)