pypsbuilder 2.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- AUTHORS.md +9 -0
- CHANGELOG.md +215 -0
- CONTRIBUTING.md +73 -0
- pypsbuilder/__init__.py +29 -0
- pypsbuilder/images/__init__.py +0 -0
- pypsbuilder/images/ptbuilder.png +0 -0
- pypsbuilder/images/pxbuilder.png +0 -0
- pypsbuilder/images/pypsbuilder.png +0 -0
- pypsbuilder/images/txbuilder.png +0 -0
- pypsbuilder/psbuilders.py +5289 -0
- pypsbuilder/psclasses.py +1093 -0
- pypsbuilder/psexplorer.py +3494 -0
- pypsbuilder/tcapi.py +1681 -0
- pypsbuilder/tcinit.py +992 -0
- pypsbuilder/tests/__init__.py +0 -0
- pypsbuilder/tests/test_pclasses.py +279 -0
- pypsbuilder/ui/addinv.ui +113 -0
- pypsbuilder/ui/adduni.ui +119 -0
- pypsbuilder/ui/compile_ui.sh +9 -0
- pypsbuilder/ui/ptbuilder.ui +1265 -0
- pypsbuilder/ui/pxbuilder.ui +1245 -0
- pypsbuilder/ui/txbuilder.ui +1251 -0
- pypsbuilder/ui/uniguess.ui +85 -0
- pypsbuilder/ui_addinv.py +67 -0
- pypsbuilder/ui_adduni.py +67 -0
- pypsbuilder/ui_ptbuilder.py +934 -0
- pypsbuilder/ui_pxbuilder.py +920 -0
- pypsbuilder/ui_txbuilder.py +922 -0
- pypsbuilder/ui_uniguess.py +52 -0
- pypsbuilder-2.5.1.dist-info/AUTHORS.md +9 -0
- pypsbuilder-2.5.1.dist-info/LICENSE +22 -0
- pypsbuilder-2.5.1.dist-info/METADATA +95 -0
- pypsbuilder-2.5.1.dist-info/RECORD +35 -0
- pypsbuilder-2.5.1.dist-info/WHEEL +4 -0
- pypsbuilder-2.5.1.dist-info/entry_points.txt +10 -0
pypsbuilder/psclasses.py
ADDED
|
@@ -0,0 +1,1093 @@
|
|
|
1
|
+
"""pypsbuilder classes used by builders.
|
|
2
|
+
|
|
3
|
+
This module contains classes and tools providing API to THERMOCALC, parsing of
|
|
4
|
+
outputs and storage of calculated invariant points and univariant lines.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# author: Ondrej Lexa
|
|
9
|
+
# website: https://github.com/ondrolexa/pypsbuilder
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import cPickle as pickle
|
|
13
|
+
except ImportError:
|
|
14
|
+
import pickle
|
|
15
|
+
import gzip
|
|
16
|
+
import numpy as np
|
|
17
|
+
import matplotlib.pyplot as plt
|
|
18
|
+
from matplotlib.patches import PathPatch
|
|
19
|
+
from matplotlib.path import Path
|
|
20
|
+
from shapely.geometry import LineString, Point, MultiPoint
|
|
21
|
+
from shapely.ops import polygonize, linemerge # unary_union
|
|
22
|
+
|
|
23
|
+
polymorphs = [
|
|
24
|
+
{"sill", "and"},
|
|
25
|
+
{"ky", "and"},
|
|
26
|
+
{"sill", "ky"},
|
|
27
|
+
{"q", "coe"},
|
|
28
|
+
{"diam", "gph"},
|
|
29
|
+
]
|
|
30
|
+
"""list: List of two-element sets containing polymorphs."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PseudoBase:
|
|
34
|
+
"""Base class with common methods for InvPoint and UniLine."""
|
|
35
|
+
|
|
36
|
+
def label(self, excess={}):
|
|
37
|
+
"""str: full label with space delimeted phases - zero mode phase."""
|
|
38
|
+
phases_lbl = " ".join(sorted(list(self.phases.difference(excess))))
|
|
39
|
+
out_lbl = " ".join(sorted(list(self.out)))
|
|
40
|
+
return "{} - {}".format(phases_lbl, out_lbl)
|
|
41
|
+
|
|
42
|
+
def annotation(self, state=0):
|
|
43
|
+
"""str: String representation of ID with possible zermo mode phase."""
|
|
44
|
+
if state == 1:
|
|
45
|
+
return "{}".format(" ".join(self.out))
|
|
46
|
+
elif state == 2:
|
|
47
|
+
return "{:d} {}".format(self.id, " ".join(self.out))
|
|
48
|
+
else:
|
|
49
|
+
return "{:d}".format(self.id)
|
|
50
|
+
|
|
51
|
+
def ptguess(self, **kwargs):
|
|
52
|
+
"""list: Get stored ptguesses.
|
|
53
|
+
|
|
54
|
+
InvPoint has just single ptguess, but for UniLine idx need to be
|
|
55
|
+
specified. If omitted, the middle point from calculated ones is used.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
idx (int): index which guesses to get.
|
|
59
|
+
"""
|
|
60
|
+
idx = kwargs.get("idx", self.midix)
|
|
61
|
+
return self.results[idx].ptguess
|
|
62
|
+
|
|
63
|
+
def datakeys(self, phase=None):
|
|
64
|
+
"""list: Get list of variables for phase.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
phase (str): name of phase
|
|
68
|
+
"""
|
|
69
|
+
if phase is None:
|
|
70
|
+
return list(self.results[self.midix].data.keys())
|
|
71
|
+
else:
|
|
72
|
+
return list(self.results[self.midix].data[phase].keys())
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class InvPoint(PseudoBase):
|
|
76
|
+
"""Class to store invariant point
|
|
77
|
+
|
|
78
|
+
Attributes:
|
|
79
|
+
id (int): Invariant point identification
|
|
80
|
+
phases (set): set of present phases
|
|
81
|
+
out (set): set of zero mode phases
|
|
82
|
+
cmd (str): THERMOCALC standard input to calculate this point
|
|
83
|
+
variance (int): variance
|
|
84
|
+
x (numpy.array): Array of x coordinates
|
|
85
|
+
(even if only one, it is stored as array)
|
|
86
|
+
y (numpy.array): Array of x coordinates
|
|
87
|
+
(even if only one, it is stored as array)
|
|
88
|
+
results (list): List of results dicts with data and ptgues keys.
|
|
89
|
+
output (str): Full THERMOCALC output
|
|
90
|
+
manual (bool): True when inavariant point is user-defined and not
|
|
91
|
+
calculated
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, **kwargs):
|
|
95
|
+
assert "phases" in kwargs, "Set of phases must be provided"
|
|
96
|
+
assert "out" in kwargs, "Set of zero phase must be provided"
|
|
97
|
+
self.id = kwargs.get("id", 0)
|
|
98
|
+
self.phases = kwargs.get("phases")
|
|
99
|
+
self.out = kwargs.get("out")
|
|
100
|
+
self.cmd = kwargs.get("cmd", "")
|
|
101
|
+
self.variance = kwargs.get("variance", 0)
|
|
102
|
+
self.x = kwargs.get("x", [])
|
|
103
|
+
self.y = kwargs.get("y", [])
|
|
104
|
+
self.results = kwargs.get("results", None)
|
|
105
|
+
self.output = kwargs.get("output", "User-defined")
|
|
106
|
+
self.manual = kwargs.get("manual", False)
|
|
107
|
+
|
|
108
|
+
def __repr__(self):
|
|
109
|
+
return "Inv: {}".format(self.label())
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def midix(self):
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def _x(self):
|
|
117
|
+
"""X coordinate as float"""
|
|
118
|
+
return self.x[0]
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def _y(self):
|
|
122
|
+
"""Y coordinate as float"""
|
|
123
|
+
return self.y[0]
|
|
124
|
+
|
|
125
|
+
def shape(self):
|
|
126
|
+
"""Return shapely Point representing invariant point."""
|
|
127
|
+
return Point(self._x, self._y)
|
|
128
|
+
|
|
129
|
+
def all_unilines(self):
|
|
130
|
+
"""Return four tuples (phases, out) indicating possible four
|
|
131
|
+
univariant lines passing trough this invariant point"""
|
|
132
|
+
a, b = self.out
|
|
133
|
+
aset, bset = set([a]), set([b])
|
|
134
|
+
aphases, bphases = self.phases.difference(aset), self.phases.difference(bset)
|
|
135
|
+
# Check for polymorphs
|
|
136
|
+
fix = False
|
|
137
|
+
for poly in polymorphs:
|
|
138
|
+
if poly.issubset(self.phases):
|
|
139
|
+
fix = True
|
|
140
|
+
break
|
|
141
|
+
triple = set(["ky", "and", "sill"])
|
|
142
|
+
if fix and (poly != self.out) and not triple.issubset(self.phases):
|
|
143
|
+
yespoly = poly.intersection(self.out)
|
|
144
|
+
nopoly = self.out.difference(yespoly)
|
|
145
|
+
aphases = self.phases.difference(yespoly)
|
|
146
|
+
bphases = self.phases.difference(poly.difference(self.out))
|
|
147
|
+
return (
|
|
148
|
+
(aphases, nopoly),
|
|
149
|
+
(bphases, nopoly),
|
|
150
|
+
(self.phases, yespoly),
|
|
151
|
+
(self.phases.difference(nopoly), yespoly),
|
|
152
|
+
)
|
|
153
|
+
else:
|
|
154
|
+
return (
|
|
155
|
+
(self.phases, aset),
|
|
156
|
+
(self.phases, bset),
|
|
157
|
+
(bphases, aset),
|
|
158
|
+
(aphases, bset),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class UniLine(PseudoBase):
|
|
163
|
+
"""Class to store univariant line
|
|
164
|
+
|
|
165
|
+
Attributes:
|
|
166
|
+
id (int): Invariant point identification
|
|
167
|
+
phases (set): set of present phases
|
|
168
|
+
out (set): set of zero mode phase
|
|
169
|
+
cmd (str): THERMOCALC standard input to calculate this point
|
|
170
|
+
variance (int): variance
|
|
171
|
+
_x (numpy.array): Array of x coordinates (all calculated)
|
|
172
|
+
_y (numpy.array): Array of x coordinates (all calculated)
|
|
173
|
+
results (list): List of results dicts with data and ptgues keys.
|
|
174
|
+
output (str): Full THERMOCALC output
|
|
175
|
+
manual (bool): True when inavariant point is user-defined and not
|
|
176
|
+
calculated
|
|
177
|
+
begin (int): id of invariant point defining begining of the line.
|
|
178
|
+
0 for no begin
|
|
179
|
+
end (int): id of invariant point defining end of the line.
|
|
180
|
+
0 for no end
|
|
181
|
+
used (slice): slice indicating which point on calculated line are
|
|
182
|
+
between begin and end
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def __init__(self, **kwargs):
|
|
186
|
+
assert "phases" in kwargs, "Set of phases must be provided"
|
|
187
|
+
assert "out" in kwargs, "Set of zero phase must be provided"
|
|
188
|
+
self.id = kwargs.get("id", 0)
|
|
189
|
+
self.phases = kwargs.get("phases")
|
|
190
|
+
self.out = kwargs.get("out")
|
|
191
|
+
self.cmd = kwargs.get("cmd", "")
|
|
192
|
+
self.variance = kwargs.get("variance", 0)
|
|
193
|
+
self._x = kwargs.get("x", np.array([]))
|
|
194
|
+
self._y = kwargs.get("y", np.array([]))
|
|
195
|
+
self.results = kwargs.get("results", None)
|
|
196
|
+
self.output = kwargs.get("output", "User-defined")
|
|
197
|
+
self.manual = kwargs.get("manual", False)
|
|
198
|
+
self.begin = kwargs.get("begin", 0)
|
|
199
|
+
self.end = kwargs.get("end", 0)
|
|
200
|
+
self.used = slice(0, len(self._x))
|
|
201
|
+
self.x = self._x.copy()
|
|
202
|
+
self.y = self._y.copy()
|
|
203
|
+
|
|
204
|
+
def __repr__(self):
|
|
205
|
+
return "Uni: {}".format(self.label())
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def midix(self):
|
|
209
|
+
return int((self.used.start + self.used.stop) // 2)
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def connected(self):
|
|
213
|
+
return 2 - [self.begin, self.end].count(0)
|
|
214
|
+
|
|
215
|
+
def _shape(self, ratio=None, tolerance=None):
|
|
216
|
+
"""Return shapely LineString representing univariant line.
|
|
217
|
+
|
|
218
|
+
This method is using all calculated points.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
ratio: y-coordinate multiplier to scale coordinates. Default None
|
|
222
|
+
tolerance: tolerance x coordinates. Simplified object will be within
|
|
223
|
+
the tolerance distance of the original geometry. Default None
|
|
224
|
+
"""
|
|
225
|
+
if ratio is None:
|
|
226
|
+
return LineString(np.array([self._x, self._y]).T)
|
|
227
|
+
else:
|
|
228
|
+
if tolerance is None:
|
|
229
|
+
return LineString(np.array([self._x, self._y]).T)
|
|
230
|
+
else:
|
|
231
|
+
ln = LineString(np.array([self._x, ratio * self._y]).T).simplify(
|
|
232
|
+
tolerance
|
|
233
|
+
)
|
|
234
|
+
x, y = np.array(ln.coords).T
|
|
235
|
+
return LineString(np.array([x, y / ratio]).T)
|
|
236
|
+
|
|
237
|
+
def shape(self, ratio=None, tolerance=None):
|
|
238
|
+
"""Return shapely LineString representing univariant line.
|
|
239
|
+
|
|
240
|
+
This method is using trimmed points.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
ratio: y-coordinate multiplier to scale coordinates. Default None
|
|
244
|
+
tolerance: tolerance x coordinates. Simplified object will be within
|
|
245
|
+
the tolerance distance of the original geometry. Default None
|
|
246
|
+
"""
|
|
247
|
+
if ratio is None:
|
|
248
|
+
return LineString(np.array([self.x, self.y]).T)
|
|
249
|
+
else:
|
|
250
|
+
if tolerance is None:
|
|
251
|
+
return LineString(np.array([self.x, self.y]).T)
|
|
252
|
+
else:
|
|
253
|
+
ln = LineString(np.array([self.x, ratio * self.y]).T).simplify(
|
|
254
|
+
tolerance
|
|
255
|
+
)
|
|
256
|
+
x, y = np.array(ln.coords).T
|
|
257
|
+
return LineString(np.array([x, y / ratio]).T)
|
|
258
|
+
|
|
259
|
+
def contains_inv(self, ip):
|
|
260
|
+
"""Check whether invariant point theoretically belong to univariant line.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
ip (InvPoint): Invariant point
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
bool: True for yes, False for no. Note that metastability is not checked.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
def checkme(uphases, uout, iphases, iout):
|
|
270
|
+
triple = set(["ky", "and", "sill"])
|
|
271
|
+
if triple.issubset(iphases):
|
|
272
|
+
iphases = iphases.difference(triple.difference(iout))
|
|
273
|
+
a, b = iout
|
|
274
|
+
aset, bset = set([a]), set([b])
|
|
275
|
+
aphases, bphases = iphases.difference(aset), iphases.difference(bset)
|
|
276
|
+
candidate = False
|
|
277
|
+
if iphases == uphases and len(iout.difference(uout)) == 1:
|
|
278
|
+
candidate = True
|
|
279
|
+
if bphases == uphases and aset == uout:
|
|
280
|
+
candidate = True
|
|
281
|
+
if aphases == uphases and bset == uout:
|
|
282
|
+
candidate = True
|
|
283
|
+
return candidate
|
|
284
|
+
|
|
285
|
+
# Check for polymorphs
|
|
286
|
+
for poly in polymorphs:
|
|
287
|
+
fixi, fixu = False, False
|
|
288
|
+
if (
|
|
289
|
+
poly.issubset(ip.phases)
|
|
290
|
+
and (poly != ip.out)
|
|
291
|
+
and (not ip.out.isdisjoint(poly))
|
|
292
|
+
):
|
|
293
|
+
fixi = True
|
|
294
|
+
if poly.issubset(self.phases) and not self.out.isdisjoint(poly):
|
|
295
|
+
fixu = True
|
|
296
|
+
# check invs
|
|
297
|
+
candidate = checkme(self.phases, self.out, ip.phases, ip.out)
|
|
298
|
+
if fixi and not candidate:
|
|
299
|
+
candidate = checkme(
|
|
300
|
+
self.phases,
|
|
301
|
+
self.out,
|
|
302
|
+
ip.phases,
|
|
303
|
+
ip.out.difference(poly).union(poly.difference(ip.out)),
|
|
304
|
+
)
|
|
305
|
+
if fixu and not candidate:
|
|
306
|
+
candidate = checkme(
|
|
307
|
+
self.phases, poly.difference(self.out), ip.phases, ip.out
|
|
308
|
+
)
|
|
309
|
+
if candidate:
|
|
310
|
+
break
|
|
311
|
+
return candidate
|
|
312
|
+
|
|
313
|
+
def get_label_point(self):
|
|
314
|
+
"""Returns coordinate tuple of labeling point for univariant line."""
|
|
315
|
+
if len(self.x) > 1:
|
|
316
|
+
dx = np.diff(self.x)
|
|
317
|
+
dy = np.diff(self.y)
|
|
318
|
+
d = np.sqrt(dx**2 + dy**2)
|
|
319
|
+
sd = np.sum(d)
|
|
320
|
+
if sd > 0:
|
|
321
|
+
cl = np.append([0], np.cumsum(d))
|
|
322
|
+
ix = np.interp(sd / 2, cl, range(len(cl)))
|
|
323
|
+
cix = int(ix)
|
|
324
|
+
return (
|
|
325
|
+
self.x[cix] + (ix - cix) * dx[cix],
|
|
326
|
+
self.y[cix] + (ix - cix) * dy[cix],
|
|
327
|
+
)
|
|
328
|
+
else:
|
|
329
|
+
return self.x[0], self.y[0]
|
|
330
|
+
else:
|
|
331
|
+
return self.x[0], self.y[0]
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class Dogmin:
|
|
335
|
+
def __init__(self, **kwargs):
|
|
336
|
+
assert "output" in kwargs, "Dogmin output must be provided"
|
|
337
|
+
assert "resic" in kwargs, "ic file content must be provided"
|
|
338
|
+
self.id = kwargs.get("id", 0)
|
|
339
|
+
self.phases = set()
|
|
340
|
+
output = kwargs.get("output")
|
|
341
|
+
if "assemblage" in output:
|
|
342
|
+
self.output = output
|
|
343
|
+
self.resic = kwargs.get("resic")
|
|
344
|
+
self.phases = set(
|
|
345
|
+
self.output.split("assemblage")[1].split("\n")[0].split()
|
|
346
|
+
) # TC 35
|
|
347
|
+
else:
|
|
348
|
+
self.resic = ""
|
|
349
|
+
asm = output.split("Gibbs energy minimisation info\n")[-1]
|
|
350
|
+
if len(asm.split("\n #")) > 1:
|
|
351
|
+
best = int(asm.split("\n #")[1].splitlines()[1].split()[0])
|
|
352
|
+
for blk in output.split("phases :")[1:]:
|
|
353
|
+
head = blk.splitlines()[0]
|
|
354
|
+
if "#" in head.split()[-1]:
|
|
355
|
+
if best == int(head.split()[-1].replace("#", "")):
|
|
356
|
+
if "or" in head:
|
|
357
|
+
head = head.split("or")[0]
|
|
358
|
+
else:
|
|
359
|
+
head = head.split("#")[0]
|
|
360
|
+
if "(" in head:
|
|
361
|
+
head = head.split("(")[0]
|
|
362
|
+
self.phases = set(head.split())
|
|
363
|
+
self.output = (
|
|
364
|
+
"Gibbs energy minimisation info\n"
|
|
365
|
+
+ asm
|
|
366
|
+
+ "\n"
|
|
367
|
+
+ "phases :"
|
|
368
|
+
+ blk
|
|
369
|
+
)
|
|
370
|
+
break
|
|
371
|
+
if (
|
|
372
|
+
not self.phases
|
|
373
|
+
): # If last block is the best, use it (TC do not output #number)
|
|
374
|
+
if "or" in head:
|
|
375
|
+
head = head.split("or")[0]
|
|
376
|
+
else:
|
|
377
|
+
head = head.split("#")[0]
|
|
378
|
+
if "(" in head:
|
|
379
|
+
head = head.split("(")[0]
|
|
380
|
+
self.phases = set(head.split())
|
|
381
|
+
self.output = (
|
|
382
|
+
"Gibbs energy minimisation info\n"
|
|
383
|
+
+ asm
|
|
384
|
+
+ "\n"
|
|
385
|
+
+ "phases :"
|
|
386
|
+
+ blk
|
|
387
|
+
)
|
|
388
|
+
self.x = kwargs.get("x", None)
|
|
389
|
+
self.y = kwargs.get("y", None)
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def out(self):
|
|
393
|
+
return set()
|
|
394
|
+
|
|
395
|
+
def label(self, excess={}):
|
|
396
|
+
"""str: full label with space delimeted phases."""
|
|
397
|
+
return " ".join(sorted(list(self.phases.difference(excess))))
|
|
398
|
+
|
|
399
|
+
def annotation(self, show_out=False, excess={}):
|
|
400
|
+
"""str: String representation of ID with possible zermo mode phase."""
|
|
401
|
+
if show_out:
|
|
402
|
+
return self.label(excess=excess)
|
|
403
|
+
else:
|
|
404
|
+
return "{:d}".format(self.id)
|
|
405
|
+
|
|
406
|
+
def ptguess(self):
|
|
407
|
+
block = [ln for ln in self.output.splitlines() if ln != ""]
|
|
408
|
+
xyz = [ix for ix, ln in enumerate(block) if ln.startswith("xyzguess")]
|
|
409
|
+
gixs = [ix for ix, ln in enumerate(block) if ln.startswith("ptguess")][0] - 1
|
|
410
|
+
gixe = xyz[-1] + 2
|
|
411
|
+
return block[gixs:gixe]
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class TCResult:
|
|
415
|
+
def __init__(self, T, p, variance=0, c=0, data={}, ptguess=[""]):
|
|
416
|
+
self.data = data
|
|
417
|
+
self.ptguess = ptguess
|
|
418
|
+
self.T = T
|
|
419
|
+
self.p = p
|
|
420
|
+
self.variance = variance
|
|
421
|
+
self.c = c
|
|
422
|
+
|
|
423
|
+
@classmethod
|
|
424
|
+
def from_block(cls, block, ptguess):
|
|
425
|
+
info, ax, sf, bulk, rbi, mode, factor, td, sys, *mems, pems = block.split(
|
|
426
|
+
"\n\n"
|
|
427
|
+
)
|
|
428
|
+
if "var = 2; seen" in info:
|
|
429
|
+
# no step in bulk
|
|
430
|
+
info, ax, sf, rbi, mode, factor, td, sys, *mems, pems = block.split("\n\n")
|
|
431
|
+
bulk = "\n".join(rbi.split("\n")[:3])
|
|
432
|
+
rbi = "\n".join(rbi.split("\n")[3:])
|
|
433
|
+
# heading
|
|
434
|
+
data = {phase: {} for phase in info.split("{")[0].split()}
|
|
435
|
+
p, T = (float(v.strip()) for v in info.split("{")[1].split("}")[0].split(","))
|
|
436
|
+
# var or ovar?
|
|
437
|
+
variance = int(info.split("var = ")[1].split(" ")[0].replace(";", ""))
|
|
438
|
+
# a-x variables
|
|
439
|
+
for head, vals in zip(ax.split("\n")[::2], ax.split("\n")[1::2]):
|
|
440
|
+
phase, *names = head.split()
|
|
441
|
+
data[phase].update(
|
|
442
|
+
{
|
|
443
|
+
name.replace("({})".format(phase), ""): float(val)
|
|
444
|
+
for name, val in zip(names, vals.split())
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
# site fractions
|
|
448
|
+
for head, vals in zip(
|
|
449
|
+
sf.split("\n")[1::2], sf.split("\n")[2::2]
|
|
450
|
+
): # skip site fractions row
|
|
451
|
+
phase, *names = head.split()
|
|
452
|
+
data[phase].update(
|
|
453
|
+
{name: float(val) for name, val in zip(names, vals.split())}
|
|
454
|
+
)
|
|
455
|
+
# bulk composition
|
|
456
|
+
bulk_vals = {}
|
|
457
|
+
oxhead, vals = bulk.split("\n")[1:] # skip oxide compositions row
|
|
458
|
+
for ox, val in zip(oxhead.split(), vals.split()[1:]):
|
|
459
|
+
bulk_vals[ox] = float(val)
|
|
460
|
+
data["bulk"] = bulk_vals
|
|
461
|
+
# x for TX and pX
|
|
462
|
+
if "step" in vals:
|
|
463
|
+
c = float(vals.split("step")[1].split(", x =")[1])
|
|
464
|
+
else:
|
|
465
|
+
c = 0
|
|
466
|
+
# rbi
|
|
467
|
+
for row in rbi.split("\n"):
|
|
468
|
+
phase, *vals = row.split()
|
|
469
|
+
data[phase].update(
|
|
470
|
+
{ox: float(val) for ox, val in zip(oxhead.split(), vals)}
|
|
471
|
+
)
|
|
472
|
+
# modes (zero mode is empty field in tc350 !!!)
|
|
473
|
+
head, vals = mode.split("\n")
|
|
474
|
+
phases = head.split()[1:]
|
|
475
|
+
# fixed width parsing !!!
|
|
476
|
+
valsf = [
|
|
477
|
+
(
|
|
478
|
+
float(vals[6:][12 * i : 12 * (i + 1)].strip())
|
|
479
|
+
if vals[6:][12 * i : 12 * (i + 1)].strip() != ""
|
|
480
|
+
else 0.0
|
|
481
|
+
)
|
|
482
|
+
for i in range(len(phases))
|
|
483
|
+
]
|
|
484
|
+
for phase, val in zip(phases, valsf):
|
|
485
|
+
data[phase].update({"mode": float(val)})
|
|
486
|
+
# factors
|
|
487
|
+
head, vals = factor.split("\n")
|
|
488
|
+
phases = head.split()[1:]
|
|
489
|
+
valsf = [
|
|
490
|
+
(
|
|
491
|
+
float(vals[6:][12 * i : 12 * (i + 1)].strip())
|
|
492
|
+
if vals[6:][12 * i : 12 * (i + 1)].strip() != ""
|
|
493
|
+
else 0.0
|
|
494
|
+
)
|
|
495
|
+
for i in range(len(phases))
|
|
496
|
+
]
|
|
497
|
+
for phase, val in zip(phases, valsf):
|
|
498
|
+
data[phase].update({"factor": float(val)})
|
|
499
|
+
# thermodynamic state
|
|
500
|
+
head, *rows = td.split("\n")
|
|
501
|
+
for row in rows:
|
|
502
|
+
phase, *vals = row.split()
|
|
503
|
+
data[phase].update(
|
|
504
|
+
{name: float(val) for name, val in zip(head.split(), vals)}
|
|
505
|
+
)
|
|
506
|
+
# bulk thermodynamics
|
|
507
|
+
sys = {}
|
|
508
|
+
for name, val in zip(head.split(), row.split()[1:]):
|
|
509
|
+
sys[name] = float(val)
|
|
510
|
+
data["sys"] = sys
|
|
511
|
+
# model end-members
|
|
512
|
+
if len(mems) > 0:
|
|
513
|
+
_, mem0 = mems[0].split("\n", maxsplit=1)
|
|
514
|
+
head = ["ideal", "gamma", "activity", "prop", "mu", "RTlna"]
|
|
515
|
+
mems[0] = mem0
|
|
516
|
+
for mem in mems:
|
|
517
|
+
ems = mem.split("\n")
|
|
518
|
+
phase, ems0 = ems[0].split(maxsplit=1)
|
|
519
|
+
ems[0] = ems0
|
|
520
|
+
for row in ems:
|
|
521
|
+
em, *vals = row.split()
|
|
522
|
+
phase_em = "{}({})".format(phase, em)
|
|
523
|
+
data[phase_em] = {name: float(val) for name, val in zip(head, vals)}
|
|
524
|
+
# pure end-members
|
|
525
|
+
for row in pems.split("\n")[:-1]:
|
|
526
|
+
pem, val = row.split()
|
|
527
|
+
data[pem].update({"mu": float(val)})
|
|
528
|
+
# Finally
|
|
529
|
+
return cls(T, p, variance=variance, c=c, data=data, ptguess=ptguess)
|
|
530
|
+
|
|
531
|
+
def __repr__(self):
|
|
532
|
+
return "p:{:g} T:{:g} V:{} c:{:g}, Phases: {}".format(
|
|
533
|
+
self.p, self.T, self.variance, self.c, " ".join(self.phases)
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
def __getitem__(self, key):
|
|
537
|
+
if isinstance(key, str):
|
|
538
|
+
if key not in self.phases:
|
|
539
|
+
raise IndexError("The index ({}) do not exists.".format(key))
|
|
540
|
+
return self.data[key]
|
|
541
|
+
else:
|
|
542
|
+
raise TypeError("Invalid argument type.")
|
|
543
|
+
|
|
544
|
+
@property
|
|
545
|
+
def phases(self):
|
|
546
|
+
return set(self.data.keys())
|
|
547
|
+
|
|
548
|
+
def rename_phase(self, old, new):
|
|
549
|
+
self.data[new] = self.data.pop(old)
|
|
550
|
+
for ix, ln in enumerate(self.ptguess):
|
|
551
|
+
self.ptguess[ix] = ln.replace("({})".format(old), "({})".format(new))
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
class TCResultSet:
|
|
555
|
+
def __init__(self, results):
|
|
556
|
+
self.results = results
|
|
557
|
+
|
|
558
|
+
def __repr__(self):
|
|
559
|
+
return "{} results".format(len(self.results))
|
|
560
|
+
|
|
561
|
+
def __len__(self):
|
|
562
|
+
return len(self.results)
|
|
563
|
+
|
|
564
|
+
def __getitem__(self, key):
|
|
565
|
+
if isinstance(key, slice):
|
|
566
|
+
# Get the start, stop, and step from the slice
|
|
567
|
+
return TCResultSet(self.results[key])
|
|
568
|
+
elif isinstance(key, int):
|
|
569
|
+
if key < 0: # Handle negative indices
|
|
570
|
+
key += len(self.results)
|
|
571
|
+
if key < 0 or key >= len(self.results):
|
|
572
|
+
raise IndexError("The index ({}) is out of range.".format(key))
|
|
573
|
+
return self.results[key]
|
|
574
|
+
elif isinstance(key, list):
|
|
575
|
+
return TCResultSet([self.results[ix] for ix in key])
|
|
576
|
+
else:
|
|
577
|
+
raise TypeError("Invalid argument type.")
|
|
578
|
+
|
|
579
|
+
@property
|
|
580
|
+
def x(self):
|
|
581
|
+
return np.array([res.T for res in self.results])
|
|
582
|
+
|
|
583
|
+
@property
|
|
584
|
+
def y(self):
|
|
585
|
+
return np.array([res.p for res in self.results])
|
|
586
|
+
|
|
587
|
+
@property
|
|
588
|
+
def variance(self):
|
|
589
|
+
return self.results[0].variance
|
|
590
|
+
|
|
591
|
+
@property
|
|
592
|
+
def c(self):
|
|
593
|
+
return np.array([res.c for res in self.results])
|
|
594
|
+
|
|
595
|
+
@property
|
|
596
|
+
def phases(self):
|
|
597
|
+
return self.results[0].phases
|
|
598
|
+
|
|
599
|
+
def ptguess(self, ix):
|
|
600
|
+
try:
|
|
601
|
+
return self.results[ix].ptguess
|
|
602
|
+
except Exception:
|
|
603
|
+
return None
|
|
604
|
+
|
|
605
|
+
def rename_phase(self, old, new):
|
|
606
|
+
for r in self.results:
|
|
607
|
+
r.rename_phase(old, new)
|
|
608
|
+
|
|
609
|
+
def insert(self, ix, result):
|
|
610
|
+
self.results.insert(ix, result)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
class SectionBase:
|
|
614
|
+
"""Base class for PTsection, TXsection and PXsection"""
|
|
615
|
+
|
|
616
|
+
def __init__(self, **kwargs):
|
|
617
|
+
self.excess = kwargs.get("excess", set())
|
|
618
|
+
self.invpoints = {}
|
|
619
|
+
self.unilines = {}
|
|
620
|
+
self.dogmins = {}
|
|
621
|
+
|
|
622
|
+
def __repr__(self):
|
|
623
|
+
return "\n".join(
|
|
624
|
+
[
|
|
625
|
+
"{}".format(self.type),
|
|
626
|
+
"Univariant lines: {}".format(len(self.unilines)),
|
|
627
|
+
"Invariant points: {}".format(len(self.invpoints)),
|
|
628
|
+
"{} range: {} {}".format(self.x_var, *self.xrange),
|
|
629
|
+
"{} range: {} {}".format(self.y_var, *self.yrange),
|
|
630
|
+
]
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
@property
|
|
634
|
+
def type(self):
|
|
635
|
+
return type(self).__name__
|
|
636
|
+
|
|
637
|
+
@property
|
|
638
|
+
def ratio(self):
|
|
639
|
+
return (self.xrange[1] - self.xrange[0]) / (self.yrange[1] - self.yrange[0])
|
|
640
|
+
|
|
641
|
+
@property
|
|
642
|
+
def range_shapes(self):
|
|
643
|
+
# default p-t range boundary
|
|
644
|
+
bnd = [
|
|
645
|
+
LineString(
|
|
646
|
+
[(self.xrange[0], self.yrange[0]), (self.xrange[1], self.yrange[0])]
|
|
647
|
+
),
|
|
648
|
+
LineString(
|
|
649
|
+
[(self.xrange[1], self.yrange[0]), (self.xrange[1], self.yrange[1])]
|
|
650
|
+
),
|
|
651
|
+
LineString(
|
|
652
|
+
[(self.xrange[1], self.yrange[1]), (self.xrange[0], self.yrange[1])]
|
|
653
|
+
),
|
|
654
|
+
LineString(
|
|
655
|
+
[(self.xrange[0], self.yrange[1]), (self.xrange[0], self.yrange[0])]
|
|
656
|
+
),
|
|
657
|
+
]
|
|
658
|
+
return bnd, polygonize(bnd)[0]
|
|
659
|
+
|
|
660
|
+
def add_inv(self, id, inv):
|
|
661
|
+
if inv.manual:
|
|
662
|
+
inv.results = None
|
|
663
|
+
else: # temporary compatibility with 2.2.0
|
|
664
|
+
if not isinstance(inv.results, TCResultSet):
|
|
665
|
+
inv.results = TCResultSet(
|
|
666
|
+
[
|
|
667
|
+
TCResult(
|
|
668
|
+
float(x),
|
|
669
|
+
float(y),
|
|
670
|
+
variance=inv.variance,
|
|
671
|
+
data=r["data"],
|
|
672
|
+
ptguess=r["ptguess"],
|
|
673
|
+
)
|
|
674
|
+
for r, x, y in zip(inv.results, inv.x, inv.y)
|
|
675
|
+
]
|
|
676
|
+
)
|
|
677
|
+
self.invpoints[id] = inv
|
|
678
|
+
self.invpoints[id].id = id
|
|
679
|
+
|
|
680
|
+
def add_uni(self, id, uni):
|
|
681
|
+
if uni.manual:
|
|
682
|
+
uni.results = None
|
|
683
|
+
else: # temporary compatibility with 2.2.0
|
|
684
|
+
if not isinstance(uni.results, TCResultSet):
|
|
685
|
+
uni.results = TCResultSet(
|
|
686
|
+
[
|
|
687
|
+
TCResult(
|
|
688
|
+
float(x),
|
|
689
|
+
float(y),
|
|
690
|
+
variance=uni.variance,
|
|
691
|
+
data=r["data"],
|
|
692
|
+
ptguess=r["ptguess"],
|
|
693
|
+
)
|
|
694
|
+
for r, x, y in zip(uni.results, uni._x, uni._y)
|
|
695
|
+
]
|
|
696
|
+
)
|
|
697
|
+
self.unilines[id] = uni
|
|
698
|
+
self.unilines[id].id = id
|
|
699
|
+
|
|
700
|
+
def add_dogmin(self, id, dgm):
|
|
701
|
+
self.dogmins[id] = dgm
|
|
702
|
+
self.dogmins[id].id = id
|
|
703
|
+
|
|
704
|
+
def cleanup_data(self):
|
|
705
|
+
for id_uni, uni in self.unilines.items():
|
|
706
|
+
if not uni.manual:
|
|
707
|
+
keep = slice(
|
|
708
|
+
max(uni.used.start - 1, 0), min(uni.used.stop + 1, len(uni._x))
|
|
709
|
+
)
|
|
710
|
+
uni._x = uni._x[keep]
|
|
711
|
+
uni._y = uni._y[keep]
|
|
712
|
+
uni.results = uni.results[keep]
|
|
713
|
+
else:
|
|
714
|
+
uni.cmd = ""
|
|
715
|
+
uni.variance = 0
|
|
716
|
+
uni._x = np.array([])
|
|
717
|
+
uni._y = np.array([])
|
|
718
|
+
uni.results = [dict(data=None, ptguess=None)]
|
|
719
|
+
uni.output = "User-defined"
|
|
720
|
+
uni.used = slice(0, 0)
|
|
721
|
+
uni.x = np.array([])
|
|
722
|
+
uni.y = np.array([])
|
|
723
|
+
self.trim_uni(id_uni)
|
|
724
|
+
|
|
725
|
+
def getidinv(self, inv=None):
|
|
726
|
+
"""Return id of either new or existing invariant point"""
|
|
727
|
+
ids = 0
|
|
728
|
+
# collect polymorphs identities
|
|
729
|
+
if inv is not None:
|
|
730
|
+
outs = [inv.out]
|
|
731
|
+
for poly in polymorphs:
|
|
732
|
+
if poly.issubset(inv.phases):
|
|
733
|
+
switched = inv.out.difference(poly).union(poly.difference(inv.out))
|
|
734
|
+
if switched:
|
|
735
|
+
outs.append(switched)
|
|
736
|
+
|
|
737
|
+
for iid, cinv in self.invpoints.items():
|
|
738
|
+
if inv is not None:
|
|
739
|
+
if cinv.phases == inv.phases:
|
|
740
|
+
if cinv.out in outs:
|
|
741
|
+
inv.out = cinv.out # switch to already used ??? Needed ???
|
|
742
|
+
return False, iid
|
|
743
|
+
ids = max(ids, iid)
|
|
744
|
+
return True, ids + 1
|
|
745
|
+
|
|
746
|
+
def getiduni(self, uni=None):
|
|
747
|
+
"""Return id of either new or existing univariant line"""
|
|
748
|
+
ids = 0
|
|
749
|
+
# collect polymorphs identities
|
|
750
|
+
if uni is not None:
|
|
751
|
+
outs = [uni.out]
|
|
752
|
+
for poly in polymorphs:
|
|
753
|
+
if poly.issubset(uni.phases):
|
|
754
|
+
outs.append(poly.difference(uni.out))
|
|
755
|
+
|
|
756
|
+
for uid, cuni in self.unilines.items():
|
|
757
|
+
if uni is not None:
|
|
758
|
+
if cuni.phases == uni.phases:
|
|
759
|
+
if cuni.out in outs:
|
|
760
|
+
uni.out = cuni.out # switch to already used ??? Needed ???
|
|
761
|
+
return False, uid
|
|
762
|
+
ids = max(ids, uid)
|
|
763
|
+
return True, ids + 1
|
|
764
|
+
|
|
765
|
+
def trim_uni(self, id):
|
|
766
|
+
uni = self.unilines[id]
|
|
767
|
+
if not uni.manual:
|
|
768
|
+
if uni.begin > 0:
|
|
769
|
+
p1 = Point(
|
|
770
|
+
self.invpoints[uni.begin].x,
|
|
771
|
+
self.ratio * self.invpoints[uni.begin].y,
|
|
772
|
+
)
|
|
773
|
+
else:
|
|
774
|
+
p1 = Point(uni._x[0], self.ratio * uni._y[0])
|
|
775
|
+
if uni.end > 0:
|
|
776
|
+
p2 = Point(
|
|
777
|
+
self.invpoints[uni.end].x, self.ratio * self.invpoints[uni.end].y
|
|
778
|
+
)
|
|
779
|
+
else:
|
|
780
|
+
p2 = Point(uni._x[-1], self.ratio * uni._y[-1])
|
|
781
|
+
#
|
|
782
|
+
xy = np.array([uni._x, self.ratio * uni._y]).T
|
|
783
|
+
line = LineString(xy)
|
|
784
|
+
# vertex distances
|
|
785
|
+
vdst = np.array([line.project(Point(*v)) for v in xy])
|
|
786
|
+
d1 = line.project(p1)
|
|
787
|
+
d2 = line.project(p2)
|
|
788
|
+
# switch if needed
|
|
789
|
+
if d1 > d2:
|
|
790
|
+
d1, d2 = d2, d1
|
|
791
|
+
uni.begin, uni.end = uni.end, uni.begin
|
|
792
|
+
# get slice of points to keep
|
|
793
|
+
uni.used = slice(
|
|
794
|
+
np.flatnonzero(vdst >= d1)[0].item(),
|
|
795
|
+
np.flatnonzero(vdst <= d2)[-1].item() + 1,
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
# concatenate begin, keep, end
|
|
799
|
+
if uni.begin > 0:
|
|
800
|
+
x1, y1 = self.invpoints[uni.begin].x, self.invpoints[uni.begin].y
|
|
801
|
+
else:
|
|
802
|
+
x1, y1 = [], []
|
|
803
|
+
if uni.end > 0:
|
|
804
|
+
x2, y2 = self.invpoints[uni.end].x, self.invpoints[uni.end].y
|
|
805
|
+
else:
|
|
806
|
+
x2, y2 = [], []
|
|
807
|
+
if not uni.manual:
|
|
808
|
+
xx = uni._x[uni.used]
|
|
809
|
+
yy = uni._y[uni.used]
|
|
810
|
+
else:
|
|
811
|
+
xx, yy = [], []
|
|
812
|
+
|
|
813
|
+
# store trimmed
|
|
814
|
+
uni.x = np.hstack((x1, xx, x2))
|
|
815
|
+
uni.y = np.hstack((y1, yy, y2))
|
|
816
|
+
|
|
817
|
+
def create_shapes(self, tolerance=None):
|
|
818
|
+
def splitme(edges):
|
|
819
|
+
"""Recursive boundary splitter"""
|
|
820
|
+
for idx, edge in enumerate(edges):
|
|
821
|
+
for _, l in lns:
|
|
822
|
+
if edge.intersects(l):
|
|
823
|
+
m = linemerge([edge, l])
|
|
824
|
+
if m.geom_type == "MultiLineString":
|
|
825
|
+
p = edge.intersection(l)
|
|
826
|
+
if (
|
|
827
|
+
p.geom_type == "LineString"
|
|
828
|
+
): # seems to occur whe duplicate points are on line
|
|
829
|
+
p = MultiPoint(p.coords)
|
|
830
|
+
if p.geom_type == "MultiPoint":
|
|
831
|
+
pts = [l.interpolate(l.project(pt)) for pt in p.geoms]
|
|
832
|
+
pts = sorted(pts, key=lambda pt: edge.project(pt))
|
|
833
|
+
else:
|
|
834
|
+
pts = [l.interpolate(l.project(p))]
|
|
835
|
+
edges.pop(idx)
|
|
836
|
+
pts = (
|
|
837
|
+
[Point(edge.coords[0])] + pts + [Point(edge.coords[-1])]
|
|
838
|
+
)
|
|
839
|
+
for start, stop in zip(pts[:-1], pts[1:]):
|
|
840
|
+
p_seg = LineString([start, stop])
|
|
841
|
+
if p_seg.is_valid:
|
|
842
|
+
edges.append(p_seg)
|
|
843
|
+
return edges, True
|
|
844
|
+
return edges, False
|
|
845
|
+
|
|
846
|
+
# define bounds and area
|
|
847
|
+
edges, area = self.range_shapes
|
|
848
|
+
lns = []
|
|
849
|
+
log = []
|
|
850
|
+
# trim univariant lines
|
|
851
|
+
for uni in self.unilines.values():
|
|
852
|
+
ln = area.intersection(uni.shape(ratio=self.ratio, tolerance=tolerance))
|
|
853
|
+
if ln.geom_type == "LineString" and not ln.is_empty:
|
|
854
|
+
lns.append((uni.id, ln))
|
|
855
|
+
if ln.geom_type == "MultiLineString":
|
|
856
|
+
for ln_part in ln.geoms:
|
|
857
|
+
if ln_part.geom_type == "LineString" and not ln_part.is_empty:
|
|
858
|
+
lns.append((uni.id, ln_part))
|
|
859
|
+
# split boundaries
|
|
860
|
+
do = True
|
|
861
|
+
while do:
|
|
862
|
+
edges, do = splitme(edges)
|
|
863
|
+
# polygonize
|
|
864
|
+
polys = list(polygonize(edges + [l for _, l in lns]))
|
|
865
|
+
# create shapes
|
|
866
|
+
shapes = {}
|
|
867
|
+
unilists = {}
|
|
868
|
+
tosolve = []
|
|
869
|
+
for ix, poly in enumerate(polys):
|
|
870
|
+
unilist = []
|
|
871
|
+
for uni_id, ln in lns:
|
|
872
|
+
if ln.relate_pattern(poly, "*1*F*****"):
|
|
873
|
+
unilist.append(uni_id)
|
|
874
|
+
if unilist:
|
|
875
|
+
phases = set.intersection(*(self.unilines[id].phases for id in unilist))
|
|
876
|
+
vd = [
|
|
877
|
+
phases.symmetric_difference(self.unilines[id].phases)
|
|
878
|
+
== self.unilines[id].out
|
|
879
|
+
or not phases.symmetric_difference(self.unilines[id].phases)
|
|
880
|
+
or phases.symmetric_difference(self.unilines[id].phases).union(
|
|
881
|
+
self.unilines[id].out
|
|
882
|
+
)
|
|
883
|
+
in polymorphs
|
|
884
|
+
for id in unilist
|
|
885
|
+
]
|
|
886
|
+
if all(vd):
|
|
887
|
+
if frozenset(phases) in shapes:
|
|
888
|
+
# multivariant field crossed just by single univariant line
|
|
889
|
+
if len(unilist) == 1:
|
|
890
|
+
if self.unilines[unilist[0]].out.issubset(phases):
|
|
891
|
+
phases = phases.difference(
|
|
892
|
+
self.unilines[unilist[0]].out
|
|
893
|
+
)
|
|
894
|
+
shapes[frozenset(phases)] = poly
|
|
895
|
+
unilists[frozenset(phases)] = unilist
|
|
896
|
+
elif len(unilists[frozenset(phases)]) == 1:
|
|
897
|
+
if self.unilines[
|
|
898
|
+
unilists[frozenset(phases)][0]
|
|
899
|
+
].out.issubset(phases):
|
|
900
|
+
orig_unilist = unilists[frozenset(phases)]
|
|
901
|
+
shapes[frozenset(phases)] = poly
|
|
902
|
+
unilists[frozenset(phases)] = unilist
|
|
903
|
+
phases = phases.difference(
|
|
904
|
+
self.unilines[orig_unilist[0]].out
|
|
905
|
+
)
|
|
906
|
+
shapes[frozenset(phases)] = poly
|
|
907
|
+
unilists[frozenset(phases)] = orig_unilist
|
|
908
|
+
else:
|
|
909
|
+
shapes[frozenset(phases)] = (
|
|
910
|
+
shapes[frozenset(phases)].union(poly).buffer(0.00001)
|
|
911
|
+
)
|
|
912
|
+
log.append(
|
|
913
|
+
"Area defined by unilines {} is self-intersecting with {}.".format(
|
|
914
|
+
" ".join([str(id) for id in unilist]),
|
|
915
|
+
" ".join(
|
|
916
|
+
[str(id) for id in unilists[frozenset(phases)]]
|
|
917
|
+
),
|
|
918
|
+
)
|
|
919
|
+
)
|
|
920
|
+
unilists[frozenset(phases)] = list(
|
|
921
|
+
set(unilists[frozenset(phases)] + unilist)
|
|
922
|
+
)
|
|
923
|
+
else:
|
|
924
|
+
if len(unilist) == 1:
|
|
925
|
+
tosolve.append((unilist, phases, poly))
|
|
926
|
+
else:
|
|
927
|
+
shapes[frozenset(phases)] = poly
|
|
928
|
+
unilists[frozenset(phases)] = unilist
|
|
929
|
+
else:
|
|
930
|
+
log.append(
|
|
931
|
+
"Area defined by unilines {} is not valid field.".format(
|
|
932
|
+
" ".join([str(id) for id in unilist])
|
|
933
|
+
)
|
|
934
|
+
)
|
|
935
|
+
for unilist, phases, poly in tosolve:
|
|
936
|
+
if len(unilist) == 1 and self.unilines[unilist[0]].out.issubset(
|
|
937
|
+
set.union(*polymorphs)
|
|
938
|
+
):
|
|
939
|
+
phases = self.unilines[unilist[0]].phases.difference(
|
|
940
|
+
self.unilines[unilist[0]].out
|
|
941
|
+
)
|
|
942
|
+
if frozenset(phases) in shapes: # switch polymorph
|
|
943
|
+
for pp in polymorphs:
|
|
944
|
+
if self.unilines[unilist[0]].out.issubset(pp):
|
|
945
|
+
phases = self.unilines[unilist[0]].phases.difference(
|
|
946
|
+
pp.difference(self.unilines[unilist[0]].out)
|
|
947
|
+
)
|
|
948
|
+
break
|
|
949
|
+
shapes[frozenset(phases)] = poly
|
|
950
|
+
unilists[frozenset(phases)] = unilist
|
|
951
|
+
elif frozenset(phases) in shapes:
|
|
952
|
+
phases = frozenset(phases.difference(self.unilines[unilist[0]].out))
|
|
953
|
+
shapes[frozenset(phases)] = poly
|
|
954
|
+
unilists[frozenset(phases)] = unilist
|
|
955
|
+
else:
|
|
956
|
+
shapes[frozenset(phases)] = poly
|
|
957
|
+
unilists[frozenset(phases)] = unilist
|
|
958
|
+
return shapes, unilists, log
|
|
959
|
+
|
|
960
|
+
def show(self):
|
|
961
|
+
for ln in self.unilines.values():
|
|
962
|
+
plt.plot(ln.x, ln.y, "k-")
|
|
963
|
+
|
|
964
|
+
for ln in self.invpoints.values():
|
|
965
|
+
plt.plot(ln.x, ln.y, "ro")
|
|
966
|
+
|
|
967
|
+
plt.xlim(self.xrange)
|
|
968
|
+
plt.ylim(self.yrange)
|
|
969
|
+
plt.xlabel(self.x_var_label)
|
|
970
|
+
plt.xlabel(self.y_var_label)
|
|
971
|
+
plt.show()
|
|
972
|
+
|
|
973
|
+
@staticmethod
|
|
974
|
+
def read_file(projfile):
|
|
975
|
+
with gzip.open(str(projfile), "rb") as stream:
|
|
976
|
+
data = pickle.load(stream)
|
|
977
|
+
return data
|
|
978
|
+
|
|
979
|
+
@staticmethod
|
|
980
|
+
def from_file(projfile):
|
|
981
|
+
with gzip.open(str(projfile), "rb") as stream:
|
|
982
|
+
data = pickle.load(stream)
|
|
983
|
+
return data["section"]
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
class PTsection(SectionBase):
|
|
987
|
+
"""P-T pseudosection class"""
|
|
988
|
+
|
|
989
|
+
def __init__(self, **kwargs):
|
|
990
|
+
self.xrange = kwargs.get("trange", (200.0, 1000.0))
|
|
991
|
+
self.yrange = kwargs.get("prange", (0.1, 20.0))
|
|
992
|
+
self.x_var = "T"
|
|
993
|
+
self.x_var_label = "Temperature [C]"
|
|
994
|
+
self.x_var_res = 0.01
|
|
995
|
+
self.y_var = "p"
|
|
996
|
+
self.y_var_label = "Pressure [kbar]"
|
|
997
|
+
self.y_var_res = 0.001
|
|
998
|
+
super(PTsection, self).__init__(**kwargs)
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
class TXsection(SectionBase):
|
|
1002
|
+
"""T-X pseudosection class"""
|
|
1003
|
+
|
|
1004
|
+
def __init__(self, **kwargs):
|
|
1005
|
+
self.xrange = kwargs.get("trange", (200.0, 1000.0))
|
|
1006
|
+
self.yrange = (0.0, 1.0)
|
|
1007
|
+
self.x_var = "T"
|
|
1008
|
+
self.x_var_label = "Temperature [C]"
|
|
1009
|
+
self.x_var_res = 0.01
|
|
1010
|
+
self.y_var = "C"
|
|
1011
|
+
self.y_var_label = "Composition"
|
|
1012
|
+
self.y_var_res = 0.001
|
|
1013
|
+
super(TXsection, self).__init__(**kwargs)
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
class PXsection(SectionBase):
|
|
1017
|
+
"""P-X pseudosection class"""
|
|
1018
|
+
|
|
1019
|
+
def __init__(self, **kwargs):
|
|
1020
|
+
self.xrange = (0.0, 1.0)
|
|
1021
|
+
self.yrange = kwargs.get("prange", (0.1, 20.0))
|
|
1022
|
+
self.x_var = "C"
|
|
1023
|
+
self.x_var_label = "Composition"
|
|
1024
|
+
self.x_var_res = 0.001
|
|
1025
|
+
self.y_var = "p"
|
|
1026
|
+
self.y_var_label = "Pressure [kbar]"
|
|
1027
|
+
self.y_var_res = 0.001
|
|
1028
|
+
super(PXsection, self).__init__(**kwargs)
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
# Descartes https://github.com/benjimin/descartes
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
class Polygon(object):
|
|
1035
|
+
# Adapt Shapely or GeoJSON/geo_interface polygons to a common interface
|
|
1036
|
+
def __init__(self, context):
|
|
1037
|
+
if hasattr(context, "interiors"):
|
|
1038
|
+
self.context = context
|
|
1039
|
+
else:
|
|
1040
|
+
self.context = getattr(context, "__geo_interface__", context)
|
|
1041
|
+
|
|
1042
|
+
@property
|
|
1043
|
+
def geom_type(self):
|
|
1044
|
+
return getattr(self.context, "geom_type", None) or self.context["type"]
|
|
1045
|
+
|
|
1046
|
+
@property
|
|
1047
|
+
def exterior(self):
|
|
1048
|
+
return getattr(self.context, "exterior", None) or self.context["coordinates"][0]
|
|
1049
|
+
|
|
1050
|
+
@property
|
|
1051
|
+
def interiors(self):
|
|
1052
|
+
value = getattr(self.context, "interiors", None)
|
|
1053
|
+
if value is None:
|
|
1054
|
+
value = self.context["coordinates"][1:]
|
|
1055
|
+
return value
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def PolygonPath(polygon):
|
|
1059
|
+
"""Constructs a compound matplotlib path from a Shapely or GeoJSON-like
|
|
1060
|
+
geometric object"""
|
|
1061
|
+
this = Polygon(polygon)
|
|
1062
|
+
assert this.geom_type == "Polygon"
|
|
1063
|
+
|
|
1064
|
+
def coding(ob):
|
|
1065
|
+
# The codes will be all "LINETO" commands, except for "MOVETO"s at the
|
|
1066
|
+
# beginning of each subpath
|
|
1067
|
+
n = len(getattr(ob, "coords", None) or ob)
|
|
1068
|
+
vals = np.ones(n, dtype=Path.code_type) * Path.LINETO
|
|
1069
|
+
vals[0] = Path.MOVETO
|
|
1070
|
+
return vals
|
|
1071
|
+
|
|
1072
|
+
vertices = np.concatenate(
|
|
1073
|
+
[np.asarray(this.exterior.coords)[:, :2]]
|
|
1074
|
+
+ [np.asarray(r.coords)[:, :2] for r in this.interiors]
|
|
1075
|
+
)
|
|
1076
|
+
codes = np.concatenate(
|
|
1077
|
+
[coding(this.exterior)] + [coding(r) for r in this.interiors]
|
|
1078
|
+
)
|
|
1079
|
+
return Path(vertices, codes)
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def PolygonPatch(polygon, **kwargs):
|
|
1083
|
+
"""Constructs a matplotlib patch from a geometric object
|
|
1084
|
+
|
|
1085
|
+
The `polygon` may be a Shapely or GeoJSON-like object with or without holes.
|
|
1086
|
+
The `kwargs` are those supported by the matplotlib.patches.Polygon class
|
|
1087
|
+
constructor. Returns an instance of matplotlib.patches.PathPatch.
|
|
1088
|
+
Example (using Shapely Point and a matplotlib axes):
|
|
1089
|
+
>>> b = Point(0, 0).buffer(1.0)
|
|
1090
|
+
>>> patch = PolygonPatch(b, fc='blue', ec='blue', alpha=0.5)
|
|
1091
|
+
>>> axis.add_patch(patch)
|
|
1092
|
+
"""
|
|
1093
|
+
return PathPatch(PolygonPath(polygon), **kwargs)
|