Skip to content

Commit dac8cd6

Browse files
authored
Merge pull request #554 from bobmyhill/fitsolution2
make fit solution more accessible
2 parents f1473a5 + 650bea8 commit dac8cd6

File tree

5 files changed

+126
-8
lines changed

5 files changed

+126
-8
lines changed

Diff for: burnman/classes/solution.py

+42
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import numpy as np
1010
from sympy import Matrix, nsimplify
11+
from collections import OrderedDict
12+
1113
from .material import material_property, cached_property
1214
from .mineral import Mineral
1315
from .solutionmodel import MechanicalSolution
@@ -116,9 +118,49 @@ def set_state(self, pressure, temperature):
116118
def formula(self):
117119
"""
118120
Returns molar chemical formula of the solution.
121+
:rtype: Counter
119122
"""
120123
return sum_formulae(self.endmember_formulae, self.molar_fractions)
121124

125+
@material_property
126+
def site_occupancies(self):
127+
"""
128+
:returns: The fractional occupancies of species on each site.
129+
:rtype: list of OrderedDicts
130+
"""
131+
occs = np.einsum(
132+
"ij, i", self.solution_model.endmember_occupancies, self.molar_fractions
133+
)
134+
site_occs = []
135+
k = 0
136+
for i in range(self.solution_model.n_sites):
137+
site_occs.append(OrderedDict())
138+
for j in range(len(self.solution_model.sites[i])):
139+
site_occs[-1][self.solution_model.sites[i][j]] = occs[k]
140+
k += 1
141+
142+
return site_occs
143+
144+
def site_formula(self, precision=2):
145+
"""
146+
Returns the molar chemical formula of the solution with site occupancies.
147+
For example, [Mg0.4Fe0.6]2SiO4.
148+
149+
:param precision: Precision with which to print the site occupancies
150+
:type precision: int
151+
152+
:returns: Molar chemical formula of the solution with site occupancies
153+
:rtype: str
154+
"""
155+
split_empty = self.solution_model.empty_formula.split("[")
156+
formula = split_empty[0]
157+
for i, site_occs in enumerate(self.site_occupancies):
158+
formula += "["
159+
for species, occ in site_occs.items():
160+
formula += f"{species}{occ:0.{precision}f}"
161+
formula += split_empty[i + 1]
162+
return formula
163+
122164
@material_property
123165
def activities(self):
124166
"""

Diff for: burnman/optimize/composition_fitting.py

+36-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,40 @@
66
from __future__ import absolute_import
77

88
import numpy as np
9+
from copy import deepcopy
10+
from collections import namedtuple
911
from .linear_fitting import weighted_constrained_least_squares
12+
from ..utils.chemistry import dictionarize_formula
13+
from ..utils.chemistry import process_solution_chemistry
14+
from ..classes.solution import Solution
15+
16+
17+
class DummyCompositionSolution(Solution):
18+
"""
19+
This is a dummy base class for a solution object to
20+
facilitate composition fitting when no solution model has
21+
been prepared. The model is initialized with appropriate
22+
chemical formulae for each endmember, and can do all basic
23+
compositional processing that doesn't involve any material
24+
properties.
25+
26+
:param endmember_element_formulae: Formulae for each of the independent endmembers.
27+
e.g. ['Mg2SiO4', 'Fe2SiO4'].
28+
:type endmember_element_formulae: list of str
29+
:param endmember_site_formulae: Site formulae for each of the independent endmembers,
30+
in the same order as endmember_element_formulae. e.g. ['[Mg]2SiO4', '[Fe]2SiO4'].
31+
:type endmember_site_formulae: list of str
32+
"""
33+
34+
def __init__(self, endmember_element_formulae, endmember_site_formulae):
35+
self.endmember_formulae = [
36+
dictionarize_formula(f) for f in endmember_element_formulae
37+
]
38+
self.solution_model = type(
39+
"Dimension", (object,), {"formulas": endmember_site_formulae}
40+
)()
41+
self.solution_model.endmembers = [None for f in endmember_site_formulae]
42+
process_solution_chemistry(self.solution_model)
1043

1144

1245
def fit_composition_to_solution(
@@ -59,17 +92,15 @@ def fit_composition_to_solution(
5992
:rtype: tuple of 1D numpy.array, 2D numpy.array and float
6093
"""
6194

62-
n_vars = len(fitted_variables)
63-
n_mbrs = len(solution.endmembers)
64-
65-
solution_variables = solution.elements
95+
solution_variables = deepcopy(solution.elements)
6696
solution_variables.extend(solution.solution_model.site_names)
6797

6898
solution_matrix = np.hstack(
6999
(solution.stoichiometric_matrix, solution.solution_model.endmember_noccupancies)
70100
)
71101

72-
n_sol_vars = solution_matrix.shape[1]
102+
n_vars = len(fitted_variables)
103+
n_mbrs, n_sol_vars = solution_matrix.shape
73104

74105
if variable_conversions is not None:
75106
solution_matrix = np.hstack(

Diff for: burnman/utils/chemistry.py

+14
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,12 @@ def process_solution_chemistry(solution_model):
308308
309309
* solution_formulae [list of dictionaries]
310310
List of endmember formulae in dictionary form.
311+
* empty_formula [string]
312+
Abbreviated chemical formula with sites denoted by empty
313+
square brackets.
314+
* general_formula [string]
315+
General chemical formula with sites denoted by
316+
square brackets filled with a comma-separated list of species
311317
* n_sites [integer]
312318
Number of sites in the solution.
313319
Should be the same for all endmembers.
@@ -442,6 +448,14 @@ def process_solution_chemistry(solution_model):
442448
"ij, ij->ij", endmember_occupancies, site_multiplicities
443449
)
444450

451+
solution_model.empty_formula = re.sub(
452+
"([\[]).*?([\]])", "\g<1>\g<2>", solution_model.formulas[0]
453+
)
454+
split_empty = solution_model.empty_formula.split("[")
455+
solution_model.general_formula = split_empty[0]
456+
for i in range(n_sites):
457+
solution_model.general_formula += f"[{','.join(sites[i])}{split_empty[i+1]}"
458+
445459

446460
def site_occupancies_to_strings(
447461
site_species_names, site_multiplicities, endmember_occupancies

Diff for: examples/example_fit_composition.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import itertools
3535

3636
from burnman import minerals
37+
from burnman.utils.chemistry import formula_to_string
3738
from burnman.optimize.composition_fitting import fit_composition_to_solution
39+
from burnman.optimize.composition_fitting import DummyCompositionSolution
3840
from burnman.optimize.composition_fitting import (
3941
fit_phase_proportions_to_bulk_composition,
4042
)
@@ -96,7 +98,6 @@
9698
popt, pcov, res = fit_composition_to_solution(
9799
gt, fitted_species, species_amounts, species_covariances, species_conversions
98100
)
99-
100101
# We can set the composition of gt using the optimized parameters
101102
gt.set_composition(popt)
102103

@@ -108,9 +109,36 @@
108109
f"{gt.molar_fractions[i]:.3f} +/- "
109110
f"{np.sqrt(pcov[i][i]):.3f}"
110111
)
111-
print()
112-
print(f"Weighted residual: {res:.3f}")
113112

113+
# If you don't have a full solution model prepared,
114+
# you can instead use the helper class
115+
# DummyCompositionSolution
116+
element_formulae = [
117+
"Mg3Al2Si3O12",
118+
"Fe3Al2Si3O12",
119+
"Ca3Al2Si3O12",
120+
"Ca3Fe2Si3O12",
121+
"Mg3Cr2Si3O12",
122+
]
123+
site_formulae = [
124+
"[Mg]3[Al]2Si3O12",
125+
"[Fe]3[Al]2Si3O12",
126+
"[Ca]3[Al]2Si3O12",
127+
"[Ca]3[Fef]2Si3O12",
128+
"[Mg]3[Cr]2Si3O12",
129+
]
130+
gt = DummyCompositionSolution(element_formulae, site_formulae)
131+
popt1, pcov1, res1 = fit_composition_to_solution(
132+
gt, fitted_species, species_amounts, species_covariances, species_conversions
133+
)
134+
135+
assert res == res1
136+
gt.set_composition(popt)
137+
138+
print("\nSite formula:")
139+
print(gt.site_formula(2))
140+
141+
print(f"\nWeighted residual: {res:.3f}")
114142
"""
115143
Example 2
116144
---------

Diff for: misc/ref/example_fit_composition.py.out

+3
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@ gr: 0.619 +/- 0.004
2323
andr: 0.048 +/- 0.004
2424
knor: 0.000 +/- 0.004
2525

26+
Site formula:
27+
[Mg0.00Fe0.33Ca0.67]3[Al0.95Fef0.05Cr0.00]2Si3O12
28+
2629
Weighted residual: 0.519

0 commit comments

Comments
 (0)