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/tcapi.py
ADDED
|
@@ -0,0 +1,1681 @@
|
|
|
1
|
+
"""THERMOCALC API classes.
|
|
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
|
+
Todo:
|
|
7
|
+
* Implement own class for divariant fields
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# author: Ondrej Lexa
|
|
12
|
+
# website: https://github.com/ondrolexa/pypsbuilder
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import os
|
|
16
|
+
import subprocess
|
|
17
|
+
|
|
18
|
+
# import itertools
|
|
19
|
+
# import re
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
# from collections import OrderedDict
|
|
23
|
+
import numpy as np
|
|
24
|
+
from .psclasses import TCResult, TCResultSet
|
|
25
|
+
|
|
26
|
+
popen_kw = dict(
|
|
27
|
+
stdout=subprocess.PIPE,
|
|
28
|
+
stdin=subprocess.PIPE,
|
|
29
|
+
stderr=subprocess.STDOUT,
|
|
30
|
+
universal_newlines=False,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_tcapi(workdir=".", TCenc="mac-roman"):
|
|
35
|
+
workdir = Path(workdir).resolve()
|
|
36
|
+
try:
|
|
37
|
+
errinfo = "Initialize project error!"
|
|
38
|
+
tcexe = None
|
|
39
|
+
drexe = None
|
|
40
|
+
# default exe
|
|
41
|
+
if sys.platform.startswith("win"):
|
|
42
|
+
tcpat = "tc3*.exe"
|
|
43
|
+
else:
|
|
44
|
+
tcpat = "tc3*"
|
|
45
|
+
# THERMOCALC exe
|
|
46
|
+
for p in workdir.glob(tcpat):
|
|
47
|
+
if p.is_file() and os.access(str(p), os.X_OK):
|
|
48
|
+
tcexe = p.resolve()
|
|
49
|
+
break
|
|
50
|
+
# default exe
|
|
51
|
+
if sys.platform.startswith("win"):
|
|
52
|
+
drpat = "dr1*.exe"
|
|
53
|
+
else:
|
|
54
|
+
drpat = "dr1*"
|
|
55
|
+
# DRAWPD exe
|
|
56
|
+
for p in workdir.glob(drpat):
|
|
57
|
+
if p.is_file() and os.access(str(p), os.X_OK):
|
|
58
|
+
drexe = p.resolve()
|
|
59
|
+
break
|
|
60
|
+
if not tcexe:
|
|
61
|
+
raise InitError("No THERMOCALC executable in {} directory.".format(workdir))
|
|
62
|
+
# TC version
|
|
63
|
+
errinfo = "THERMOCALC executable test."
|
|
64
|
+
if sys.platform.startswith("win"):
|
|
65
|
+
startupinfo = subprocess.STARTUPINFO()
|
|
66
|
+
startupinfo.dwFlags = 1
|
|
67
|
+
startupinfo.wShowWindow = 0
|
|
68
|
+
else:
|
|
69
|
+
startupinfo = None
|
|
70
|
+
p = subprocess.Popen(
|
|
71
|
+
str(tcexe), cwd=str(workdir), startupinfo=startupinfo, **popen_kw
|
|
72
|
+
)
|
|
73
|
+
out, err = p.communicate(input="kill\n\n".encode(TCenc))
|
|
74
|
+
if err is not None:
|
|
75
|
+
print(err.decode("utf-8"))
|
|
76
|
+
sys.stdout.flush()
|
|
77
|
+
output = out.decode(TCenc)
|
|
78
|
+
if float(output.split("\n")[0].split()[1]) < 3.5:
|
|
79
|
+
api = TC34API(workdir, tcexe, drexe)
|
|
80
|
+
if api.OK:
|
|
81
|
+
return api, True
|
|
82
|
+
else:
|
|
83
|
+
return api.status, False
|
|
84
|
+
else:
|
|
85
|
+
api = TC35API(workdir, tcexe, drexe)
|
|
86
|
+
if api.OK:
|
|
87
|
+
return api, True
|
|
88
|
+
else:
|
|
89
|
+
return api.status, False
|
|
90
|
+
except BaseException as e:
|
|
91
|
+
if (
|
|
92
|
+
isinstance(e, InitError)
|
|
93
|
+
or isinstance(e, ScriptfileError)
|
|
94
|
+
or isinstance(e, TCError)
|
|
95
|
+
):
|
|
96
|
+
status = "{}: {}".format(type(e).__name__, str(e))
|
|
97
|
+
else:
|
|
98
|
+
status = "{}: {} {}".format(type(e).__name__, str(e), errinfo)
|
|
99
|
+
return status, False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class InitError(Exception):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ScriptfileError(Exception):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TCError(Exception):
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TCAPI(object):
|
|
115
|
+
"""THERMOCALC working directory API.
|
|
116
|
+
|
|
117
|
+
Attributes:
|
|
118
|
+
workdir (pathlib.Path): Path instance pointing to working directory.
|
|
119
|
+
tcexe (pathlib.Path): Path instance pointing to *THERMOCALC* executable.
|
|
120
|
+
drexe (pathlib.Path): Path instance pointing to *dawpd* executable
|
|
121
|
+
name (str): Basename of the project.
|
|
122
|
+
axname (str): Name of a-x file in use.
|
|
123
|
+
OK (bool): Boolean value. True when all settings are correct and
|
|
124
|
+
THERMOCALC is ready to be used by builders.
|
|
125
|
+
excess (set): Set of excess phases from scriptfile.
|
|
126
|
+
trange (tuple): Tuple of temperature window from setdefTwindow
|
|
127
|
+
prange (tuple): Tuple of pressure window from setdefPwindow
|
|
128
|
+
bulk (list): List of bulk composition(s).
|
|
129
|
+
ptx_steps (int): Number of compositional steps for T-X and P-X sections.
|
|
130
|
+
phases (list): List of names of available phases.
|
|
131
|
+
TCenc (str): Encoding used for THERMOCALC output text files.
|
|
132
|
+
Default 'mac-roman'.
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
InitError: An error occurred during initialization of working dir.
|
|
136
|
+
ScriptfileError: Error or problem in scriptfile.
|
|
137
|
+
TCError: THERMOCALC bombed.
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def __str__(self):
|
|
142
|
+
return str(self.workdir)
|
|
143
|
+
|
|
144
|
+
def __repr__(self):
|
|
145
|
+
if self.OK:
|
|
146
|
+
return "\n".join(
|
|
147
|
+
[
|
|
148
|
+
"{}".format(self.tcversion),
|
|
149
|
+
"Working directory: {}".format(self.workdir),
|
|
150
|
+
"Scriptfile: {}".format("tc-" + self.name + ".txt"),
|
|
151
|
+
"AX file: {}".format("tc-" + self.axname + ".txt"),
|
|
152
|
+
"Status: {}".format(self.status),
|
|
153
|
+
]
|
|
154
|
+
)
|
|
155
|
+
else:
|
|
156
|
+
return "\n".join(
|
|
157
|
+
[
|
|
158
|
+
"Uninitialized working directory {}".format(self.workdir),
|
|
159
|
+
"Status: {}".format(self.status),
|
|
160
|
+
]
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def scriptfile(self):
|
|
165
|
+
"""pathlib.Path: Path to scriptfile."""
|
|
166
|
+
return self.workdir.joinpath("tc-" + self.name + ".txt")
|
|
167
|
+
|
|
168
|
+
def read_scriptfile(self):
|
|
169
|
+
with self.scriptfile.open("r", encoding=self.TCenc) as f:
|
|
170
|
+
r = f.read()
|
|
171
|
+
return r
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def drfile(self):
|
|
175
|
+
"""pathlib.Path: Path to -dr output file."""
|
|
176
|
+
return self.workdir.joinpath("tc-" + self.name + "-dr.txt")
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def logfile(self):
|
|
180
|
+
"""pathlib.Path: Path to THERMOCALC log file."""
|
|
181
|
+
return self.workdir.joinpath("tc-log.txt")
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def icfile(self):
|
|
185
|
+
"""pathlib.Path: Path to ic file."""
|
|
186
|
+
return self.workdir.joinpath("tc-" + self.name + "-ic.txt")
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def itfile(self):
|
|
190
|
+
"""pathlib.Path: Path to it file."""
|
|
191
|
+
return self.workdir.joinpath("tc-" + self.name + "-it.txt")
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def ofile(self):
|
|
195
|
+
"""pathlib.Path: Path to project output file."""
|
|
196
|
+
return self.workdir.joinpath("tc-" + self.name + "-o.txt")
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def csvfile(self):
|
|
200
|
+
"""pathlib.Path: Path to csv file."""
|
|
201
|
+
return self.workdir.joinpath("tc-" + self.name + "-csv.txt")
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def drawpdfile(self):
|
|
205
|
+
"""pathlib.Path: Path to drawpd file."""
|
|
206
|
+
return self.workdir.joinpath("dr-" + self.name + ".txt")
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def axfile(self):
|
|
210
|
+
"""pathlib.Path: Path to used a-x file."""
|
|
211
|
+
return self.workdir.joinpath("tc-" + self.axname + ".txt")
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def prefsfile(self):
|
|
215
|
+
"""pathlib.Path: Path to THERMOCALC prefs file."""
|
|
216
|
+
return self.workdir.joinpath("tc-prefs.txt")
|
|
217
|
+
|
|
218
|
+
def read_prefsfile(self):
|
|
219
|
+
with self.prefsfile.open("r", encoding=self.TCenc) as f:
|
|
220
|
+
r = f.read()
|
|
221
|
+
return r
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def tcversion(self):
|
|
225
|
+
"""str: THERMOCALC version string"""
|
|
226
|
+
return self.tcout.split("\n")[0]
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def tcnewversion(self):
|
|
230
|
+
"""bool: False for THERMOCALC older than 3.5."""
|
|
231
|
+
return not float(self.tcversion.split()[1]) < 3.5
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def datasetfile(self):
|
|
235
|
+
"""pathlib.Path: Path to dataset file."""
|
|
236
|
+
return self.workdir.joinpath(self.dataset.split(" produced")[0])
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def dataset(self):
|
|
240
|
+
"""str: Version identification of thermodynamic dataset in use."""
|
|
241
|
+
return self.tcout.split("using ")[1].split("\n")[0]
|
|
242
|
+
|
|
243
|
+
def parse_dogmin(self):
|
|
244
|
+
"""Dogmin parser."""
|
|
245
|
+
try:
|
|
246
|
+
with self.icfile.open("r", encoding=self.TCenc) as f:
|
|
247
|
+
resic = f.read()
|
|
248
|
+
except Exception:
|
|
249
|
+
resic = None
|
|
250
|
+
try:
|
|
251
|
+
with self.logfile.open("r", encoding=self.TCenc) as f:
|
|
252
|
+
output = f.read().split(
|
|
253
|
+
"##########################################################\n"
|
|
254
|
+
)[-1]
|
|
255
|
+
except Exception:
|
|
256
|
+
output = None
|
|
257
|
+
return output, resic
|
|
258
|
+
|
|
259
|
+
def runtc(self, instr="kill\n\n"):
|
|
260
|
+
"""Low-level method to actually run THERMOCALC.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
instr (str): String to be passed to standard input for session.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
str: THERMOCALC standard output
|
|
267
|
+
"""
|
|
268
|
+
if sys.platform.startswith("win"):
|
|
269
|
+
startupinfo = subprocess.STARTUPINFO()
|
|
270
|
+
startupinfo.dwFlags = 1
|
|
271
|
+
startupinfo.wShowWindow = 0
|
|
272
|
+
else:
|
|
273
|
+
startupinfo = None
|
|
274
|
+
p = subprocess.Popen(
|
|
275
|
+
str(self.tcexe), cwd=str(self.workdir), startupinfo=startupinfo, **popen_kw
|
|
276
|
+
)
|
|
277
|
+
output, err = p.communicate(input=instr.encode(self.TCenc))
|
|
278
|
+
if err is not None:
|
|
279
|
+
print(err.decode("utf-8"))
|
|
280
|
+
sys.stdout.flush()
|
|
281
|
+
return output.decode(self.TCenc)
|
|
282
|
+
|
|
283
|
+
def rundr(self):
|
|
284
|
+
"""Method to run drawpd."""
|
|
285
|
+
if self.drexe:
|
|
286
|
+
instr = "\n" + self.name + "\n"
|
|
287
|
+
if sys.platform.startswith("win"):
|
|
288
|
+
startupinfo = subprocess.STARTUPINFO()
|
|
289
|
+
startupinfo.dwFlags = 1
|
|
290
|
+
startupinfo.wShowWindow = 0
|
|
291
|
+
else:
|
|
292
|
+
startupinfo = None
|
|
293
|
+
p = subprocess.Popen(
|
|
294
|
+
str(self.drexe),
|
|
295
|
+
cwd=str(self.workdir),
|
|
296
|
+
startupinfo=startupinfo,
|
|
297
|
+
**popen_kw
|
|
298
|
+
)
|
|
299
|
+
p.communicate(input=instr.encode(self.TCenc))
|
|
300
|
+
sys.stdout.flush()
|
|
301
|
+
return True
|
|
302
|
+
else:
|
|
303
|
+
print("No drawpd executable identified in working directory.")
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class TC35API(TCAPI):
|
|
308
|
+
def __init__(self, workdir, tcexe, drexe, encoding="mac-roman"):
|
|
309
|
+
self.workdir = Path(workdir).resolve()
|
|
310
|
+
self.tcexe = tcexe
|
|
311
|
+
self.drexe = drexe
|
|
312
|
+
self.TCenc = encoding
|
|
313
|
+
try:
|
|
314
|
+
# TC version
|
|
315
|
+
errinfo = "THERMOCALC executable test."
|
|
316
|
+
self.tcout = self.runtc("\nkill\n\n")
|
|
317
|
+
# tc-prefs file
|
|
318
|
+
if not self.workdir.joinpath("tc-prefs.txt").exists():
|
|
319
|
+
raise InitError("No tc-prefs.txt file in working directory.")
|
|
320
|
+
errinfo = "tc-prefs.txt file in working directory cannot be accessed."
|
|
321
|
+
for line in self.workdir.joinpath("tc-prefs.txt").open(
|
|
322
|
+
"r", encoding=self.TCenc
|
|
323
|
+
):
|
|
324
|
+
kw = line.split()
|
|
325
|
+
if kw != []:
|
|
326
|
+
if kw[0] == "scriptfile":
|
|
327
|
+
self.name = kw[1]
|
|
328
|
+
if not self.scriptfile.exists():
|
|
329
|
+
raise InitError(
|
|
330
|
+
"tc-prefs: scriptfile tc-"
|
|
331
|
+
+ self.name
|
|
332
|
+
+ ".txt does not exists in your working directory."
|
|
333
|
+
)
|
|
334
|
+
if len(self.name) > 15:
|
|
335
|
+
raise InitError(
|
|
336
|
+
"tc-prefs: scriptfile name is longer than 15 characters. It cause troubles for THERMOCALC. Please rename scriptfile to shorter name."
|
|
337
|
+
)
|
|
338
|
+
if kw[0] == "calcmode":
|
|
339
|
+
if kw[1] != "1":
|
|
340
|
+
raise InitError("tc-prefs: calcmode must be 1.")
|
|
341
|
+
if kw[0] == "dontwrap":
|
|
342
|
+
if kw[1] != "no":
|
|
343
|
+
raise InitError("tc-prefs: dontwrap must be no.")
|
|
344
|
+
# defaults
|
|
345
|
+
self.ptx_steps = 20 # IS IT NEEDED ????
|
|
346
|
+
# Checks run output
|
|
347
|
+
if "exit, and correct the scriptfile ?" in self.tcout:
|
|
348
|
+
raise ScriptfileError(
|
|
349
|
+
self.tcout.split("exit, and correct the scriptfile ?")[0].split(
|
|
350
|
+
"\n"
|
|
351
|
+
)[-4]
|
|
352
|
+
)
|
|
353
|
+
# Checks various settings
|
|
354
|
+
errinfo = "Scriptfile error!"
|
|
355
|
+
with self.scriptfile.open("r", encoding=self.TCenc) as f:
|
|
356
|
+
r = f.read()
|
|
357
|
+
lines = [ln.strip() for ln in r.splitlines() if ln.strip() != ""]
|
|
358
|
+
lines = lines[: lines.index("*")] # remove part not used by TC
|
|
359
|
+
# Check pypsbuilder blocks
|
|
360
|
+
if not ("%{PSBGUESS-BEGIN}" in lines and "%{PSBGUESS-END}" in lines):
|
|
361
|
+
raise ScriptfileError(
|
|
362
|
+
"There are not {PSBGUESS-BEGIN} and {PSBGUESS-END} tags in your scriptfile."
|
|
363
|
+
)
|
|
364
|
+
if not ("%{PSBCALC-BEGIN}" in lines and "%{PSBCALC-END}" in lines):
|
|
365
|
+
raise ScriptfileError(
|
|
366
|
+
"There are not {PSBCALC-BEGIN} and {PSBCALC-END} tags in your scriptfile."
|
|
367
|
+
)
|
|
368
|
+
if not ("%{PSBBULK-BEGIN}" in lines and "%{PSBBULK-END}" in lines):
|
|
369
|
+
raise ScriptfileError(
|
|
370
|
+
"There are not {PSBBULK-BEGIN} and {PSBBULK-END} tags in your scriptfile."
|
|
371
|
+
)
|
|
372
|
+
# Create scripts directory
|
|
373
|
+
scripts = {}
|
|
374
|
+
for ln in lines:
|
|
375
|
+
ln_clean = ln.split("%")[0].strip()
|
|
376
|
+
if ln_clean != "":
|
|
377
|
+
tokens = ln_clean.split(maxsplit=1)
|
|
378
|
+
if len(tokens) > 1:
|
|
379
|
+
if tokens[0] in scripts:
|
|
380
|
+
scripts[tokens[0]].append(tokens[1].strip())
|
|
381
|
+
else:
|
|
382
|
+
scripts[tokens[0]] = [tokens[1].strip()]
|
|
383
|
+
else:
|
|
384
|
+
scripts[tokens[0]] = []
|
|
385
|
+
# axfile
|
|
386
|
+
if "axfile" not in scripts:
|
|
387
|
+
raise ScriptfileError("No axfile script, axfile is mandatory script.")
|
|
388
|
+
errinfo = "Missing argument for axfile script in scriptfile."
|
|
389
|
+
self.axname = scripts["axfile"][0]
|
|
390
|
+
if not self.axfile.exists():
|
|
391
|
+
raise ScriptfileError(
|
|
392
|
+
"axfile "
|
|
393
|
+
+ str(self.axfile)
|
|
394
|
+
+ " does not exists in working directory"
|
|
395
|
+
)
|
|
396
|
+
# diagramPT
|
|
397
|
+
if "diagramPT" not in scripts:
|
|
398
|
+
raise ScriptfileError(
|
|
399
|
+
"No diagramPT script, diagramPT is mandatory script."
|
|
400
|
+
)
|
|
401
|
+
errinfo = "Wrong arguments for diagramPT script in scriptfile."
|
|
402
|
+
pmin, pmax, tmin, tmax = scripts["diagramPT"][0].split()
|
|
403
|
+
self.prange = float(pmin), float(pmax)
|
|
404
|
+
self.trange = float(tmin), float(tmax)
|
|
405
|
+
# bulk
|
|
406
|
+
errinfo = "Wrong bulk in scriptfile."
|
|
407
|
+
if "bulk" not in scripts:
|
|
408
|
+
raise ScriptfileError("No bulk script, bulk must be provided.")
|
|
409
|
+
if not (1 < len(scripts["bulk"]) < 4):
|
|
410
|
+
raise ScriptfileError("Bulk script must have 2 or 3 lines.")
|
|
411
|
+
self.bulk = []
|
|
412
|
+
self.bulk.append(scripts["bulk"][0].split())
|
|
413
|
+
self.bulk.append(scripts["bulk"][1].split())
|
|
414
|
+
if len(scripts["bulk"]) == 3:
|
|
415
|
+
self.bulk.append(
|
|
416
|
+
scripts["bulk"][2].split()[: len(self.bulk[0])]
|
|
417
|
+
) # remove possible number of steps
|
|
418
|
+
# try to get actual bulk from output
|
|
419
|
+
self.usedbulk = None
|
|
420
|
+
if "specification of bulk composition" in self.tcout:
|
|
421
|
+
try:
|
|
422
|
+
part = self.tcout.split("specification of bulk composition")[
|
|
423
|
+
1
|
|
424
|
+
].split(
|
|
425
|
+
"<==========================================================>"
|
|
426
|
+
)[
|
|
427
|
+
0
|
|
428
|
+
]
|
|
429
|
+
lns = [ln for ln in part.split(os.linesep) if ln.startswith(" ")]
|
|
430
|
+
self.usedbulk = []
|
|
431
|
+
self.usedbulk.append(lns[0].split())
|
|
432
|
+
self.usedbulk.append(lns[1].split())
|
|
433
|
+
if len(lns) == 3:
|
|
434
|
+
self.usedbulk.append(lns[2].split()[: len(self.usedbulk[0])])
|
|
435
|
+
except BaseException as e:
|
|
436
|
+
self.usedbulk = None
|
|
437
|
+
# inexcess
|
|
438
|
+
errinfo = "Wrong inexcess in scriptfile."
|
|
439
|
+
if "setexcess" in scripts:
|
|
440
|
+
raise ScriptfileError(
|
|
441
|
+
"setexcess script depreceated, use inexcess instead."
|
|
442
|
+
)
|
|
443
|
+
if "inexcess" in scripts:
|
|
444
|
+
if scripts["inexcess"]:
|
|
445
|
+
self.excess = set(scripts["inexcess"][0].split()) - set(["no"])
|
|
446
|
+
else:
|
|
447
|
+
raise ScriptfileError(
|
|
448
|
+
"In case of no excess phases, use inexcess no or remove script"
|
|
449
|
+
)
|
|
450
|
+
else:
|
|
451
|
+
self.excess = set()
|
|
452
|
+
# omit
|
|
453
|
+
errinfo = "Wrong omit in scriptfile."
|
|
454
|
+
if "omit" in scripts:
|
|
455
|
+
self.omit = set(scripts["omit"][0].split())
|
|
456
|
+
else:
|
|
457
|
+
self.omit = set()
|
|
458
|
+
# autoexit
|
|
459
|
+
if "autoexit" not in scripts:
|
|
460
|
+
raise ScriptfileError("No autoexit script, autoexit must be provided.")
|
|
461
|
+
# acceptvar
|
|
462
|
+
if "acceptvar" in scripts:
|
|
463
|
+
if scripts["acceptvar"][0] == "no":
|
|
464
|
+
raise ScriptfileError("Acceptvar script must be yes or removed")
|
|
465
|
+
# with
|
|
466
|
+
if "with" in scripts:
|
|
467
|
+
if scripts["with"][0].split()[0] == "someof":
|
|
468
|
+
raise ScriptfileError(
|
|
469
|
+
"Pypsbuilder does not support with sameof <phase list>. Use omit with and omit."
|
|
470
|
+
)
|
|
471
|
+
# samecoding
|
|
472
|
+
if "samecoding" in scripts:
|
|
473
|
+
self.samecoding = [set(sc.split()) for sc in scripts["samecoding"]]
|
|
474
|
+
else:
|
|
475
|
+
self.samecoding = [set()]
|
|
476
|
+
# pseudosection
|
|
477
|
+
if "pseudosection" not in scripts:
|
|
478
|
+
raise ScriptfileError(
|
|
479
|
+
"No pseudosection script, pseudosection is mandatory script."
|
|
480
|
+
)
|
|
481
|
+
# dogmin
|
|
482
|
+
if "dogmin" in scripts:
|
|
483
|
+
raise ScriptfileError(
|
|
484
|
+
"Dogmin script should be removed from scriptfile."
|
|
485
|
+
)
|
|
486
|
+
# union ax phases and samecoding and diff omit
|
|
487
|
+
if "BOMBED" in self.tcout:
|
|
488
|
+
raise TCError(self.tcout.split("BOMBED")[1].split(os.linesep)[0])
|
|
489
|
+
else:
|
|
490
|
+
ax_phases = set(
|
|
491
|
+
self.tcout.split("reading ax:")[1].split(2 * os.linesep)[0].split()
|
|
492
|
+
)
|
|
493
|
+
self.phases = ax_phases.union(*self.samecoding) - self.omit
|
|
494
|
+
# OK
|
|
495
|
+
self.status = "Initial check done."
|
|
496
|
+
self.OK = True
|
|
497
|
+
except BaseException as e:
|
|
498
|
+
if (
|
|
499
|
+
isinstance(e, InitError)
|
|
500
|
+
or isinstance(e, ScriptfileError)
|
|
501
|
+
or isinstance(e, TCError)
|
|
502
|
+
):
|
|
503
|
+
self.status = "{}: {}".format(type(e).__name__, str(e))
|
|
504
|
+
else:
|
|
505
|
+
exc_type, exc_obj, exc_tb = sys.exc_info()
|
|
506
|
+
self.status = "{}: {} on line {}".format(
|
|
507
|
+
type(e).__name__, str(e), exc_tb.tb_lineno
|
|
508
|
+
)
|
|
509
|
+
self.OK = False
|
|
510
|
+
|
|
511
|
+
def parse_logfile(self, **kwargs):
|
|
512
|
+
"""Parser for THERMOCALC 3.50beta output.
|
|
513
|
+
|
|
514
|
+
It parses the outputs of THERMOCALC after calculation.
|
|
515
|
+
|
|
516
|
+
Args:
|
|
517
|
+
output (str): When not None, used as content of logfile. Default None.
|
|
518
|
+
resic (str): When not None, used as content of icfile. Default None.
|
|
519
|
+
get_phases (bool): When true returns also tuple (phases, out, calcs). Default False
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
status (str): Result of parsing. 'ok', 'nir' (nothing in range) or 'bombed'.
|
|
523
|
+
results (TCResultSet): Results of TC calculation.
|
|
524
|
+
output (str): Full nonparsed THERMOCALC output.
|
|
525
|
+
|
|
526
|
+
Example:
|
|
527
|
+
Parse output after univariant line calculation in P-T pseudosection::
|
|
528
|
+
|
|
529
|
+
>>> tc = TCAPI('pat/to/dir')
|
|
530
|
+
>>> status, result, output = tc.parse_logfile()
|
|
531
|
+
"""
|
|
532
|
+
output = kwargs.get("output", None)
|
|
533
|
+
resic = kwargs.get("resic", None)
|
|
534
|
+
get_phases = kwargs.get("get_phases", False)
|
|
535
|
+
try:
|
|
536
|
+
if output is None:
|
|
537
|
+
with self.logfile.open("r", encoding=self.TCenc) as f:
|
|
538
|
+
output = f.read().split(
|
|
539
|
+
"^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n"
|
|
540
|
+
)[1]
|
|
541
|
+
lines = [ln for ln in output.splitlines() if ln != ""]
|
|
542
|
+
results = None
|
|
543
|
+
do_parse = True
|
|
544
|
+
if resic is None:
|
|
545
|
+
if not self.icfile.exists():
|
|
546
|
+
if [ix for ix, ln in enumerate(lines) if "BOMBED" in ln]:
|
|
547
|
+
status = "bombed"
|
|
548
|
+
else:
|
|
549
|
+
status = "nir"
|
|
550
|
+
do_parse = False
|
|
551
|
+
else:
|
|
552
|
+
with self.icfile.open("r", encoding=self.TCenc) as f:
|
|
553
|
+
resic = f.read()
|
|
554
|
+
if do_parse:
|
|
555
|
+
lines = [ln for ln in output.splitlines() if ln != ""]
|
|
556
|
+
# parse ptguesses
|
|
557
|
+
bstarts = [
|
|
558
|
+
ix
|
|
559
|
+
for ix, ln in enumerate(lines)
|
|
560
|
+
if ln.startswith(
|
|
561
|
+
"------------------------------------------------------------"
|
|
562
|
+
)
|
|
563
|
+
]
|
|
564
|
+
bstarts.append(len(lines))
|
|
565
|
+
ptguesses = []
|
|
566
|
+
corrects = []
|
|
567
|
+
for bs, be in zip(bstarts[:-1], bstarts[1:]):
|
|
568
|
+
block = lines[bs:be]
|
|
569
|
+
if block[2].startswith("#"):
|
|
570
|
+
corrects.append(False)
|
|
571
|
+
else:
|
|
572
|
+
corrects.append(True)
|
|
573
|
+
xyz = [
|
|
574
|
+
ix for ix, ln in enumerate(block) if ln.startswith("xyzguess")
|
|
575
|
+
]
|
|
576
|
+
gixs = [
|
|
577
|
+
ix for ix, ln in enumerate(block) if ln.startswith("ptguess")
|
|
578
|
+
][0] - 3
|
|
579
|
+
gixe = xyz[-1] + 2
|
|
580
|
+
ptguesses.append(block[gixs:gixe])
|
|
581
|
+
# parse icfile
|
|
582
|
+
blocks = resic.split(
|
|
583
|
+
"\n===========================================================\n\n"
|
|
584
|
+
)[1:]
|
|
585
|
+
# done
|
|
586
|
+
if len(blocks) > 0:
|
|
587
|
+
rlist = [
|
|
588
|
+
TCResult.from_block(block, ptguess)
|
|
589
|
+
for block, ptguess, correct in zip(blocks, ptguesses, corrects)
|
|
590
|
+
if correct
|
|
591
|
+
]
|
|
592
|
+
if len(rlist) > 0:
|
|
593
|
+
status = "ok"
|
|
594
|
+
results = TCResultSet(rlist)
|
|
595
|
+
else:
|
|
596
|
+
status = "nir"
|
|
597
|
+
else:
|
|
598
|
+
status = "nir"
|
|
599
|
+
if get_phases:
|
|
600
|
+
phases, out = None, None
|
|
601
|
+
with self.scriptfile.open("r", encoding=self.TCenc) as f:
|
|
602
|
+
scf = f.read()
|
|
603
|
+
_, rem = scf.split("%{PSBCALC-BEGIN}")
|
|
604
|
+
calc, _ = rem.split("%{PSBCALC-END}")
|
|
605
|
+
calcs = calc.splitlines()
|
|
606
|
+
for ln in calcs:
|
|
607
|
+
script = ln.split("%")[0].strip()
|
|
608
|
+
if script.startswith("with"):
|
|
609
|
+
phases = set(script.split("with", 1)[1].split()).union(
|
|
610
|
+
self.excess
|
|
611
|
+
)
|
|
612
|
+
if script.startswith("zeromodeisopleth"):
|
|
613
|
+
out = set(script.split("zeromodeisopleth", 1)[1].split())
|
|
614
|
+
return status, results, output, (phases, out, calcs)
|
|
615
|
+
else:
|
|
616
|
+
return status, results, output
|
|
617
|
+
except Exception:
|
|
618
|
+
if get_phases:
|
|
619
|
+
return "bombed", None, None, (None, None, [])
|
|
620
|
+
else:
|
|
621
|
+
return "bombed", None, None
|
|
622
|
+
|
|
623
|
+
def parse_logfile_backup(self, **kwargs):
|
|
624
|
+
output = kwargs.get("output", None)
|
|
625
|
+
resic = kwargs.get("resic", None)
|
|
626
|
+
if output is None:
|
|
627
|
+
with self.logfile.open("r", encoding=self.TCenc) as f:
|
|
628
|
+
output = f.read()
|
|
629
|
+
lines = [ln for ln in output.splitlines() if ln != ""]
|
|
630
|
+
results = None
|
|
631
|
+
do_parse = True
|
|
632
|
+
if resic is None:
|
|
633
|
+
if not self.icfile.exists():
|
|
634
|
+
if [ix for ix, ln in enumerate(lines) if "BOMBED" in ln]:
|
|
635
|
+
status = "bombed"
|
|
636
|
+
else:
|
|
637
|
+
status = "nir"
|
|
638
|
+
do_parse = False
|
|
639
|
+
else:
|
|
640
|
+
with self.icfile.open("r", encoding=self.TCenc) as f:
|
|
641
|
+
resic = f.read()
|
|
642
|
+
if do_parse:
|
|
643
|
+
lines = [ln for ln in output.splitlines() if ln != ""]
|
|
644
|
+
# parse ptguesses
|
|
645
|
+
bstarts = [
|
|
646
|
+
ix
|
|
647
|
+
for ix, ln in enumerate(lines)
|
|
648
|
+
if ln.startswith(
|
|
649
|
+
"--------------------------------------------------------------------"
|
|
650
|
+
)
|
|
651
|
+
]
|
|
652
|
+
bstarts.append(len(lines))
|
|
653
|
+
ptguesses = []
|
|
654
|
+
corrects = []
|
|
655
|
+
for bs, be in zip(bstarts[:-1], bstarts[1:]):
|
|
656
|
+
block = lines[bs:be]
|
|
657
|
+
if block[2].startswith("#"):
|
|
658
|
+
corrects.append(False)
|
|
659
|
+
else:
|
|
660
|
+
corrects.append(True)
|
|
661
|
+
xyz = [ix for ix, ln in enumerate(block) if ln.startswith("xyzguess")]
|
|
662
|
+
gixs = [ix for ix, ln in enumerate(block) if ln.startswith("ptguess")][
|
|
663
|
+
0
|
|
664
|
+
] - 3
|
|
665
|
+
gixe = xyz[-1] + 2
|
|
666
|
+
ptguesses.append(block[gixs:gixe])
|
|
667
|
+
# parse icfile
|
|
668
|
+
blocks = resic.split(
|
|
669
|
+
"\n===========================================================\n\n"
|
|
670
|
+
)[1:]
|
|
671
|
+
# done
|
|
672
|
+
if len(blocks) > 0:
|
|
673
|
+
rlist = [
|
|
674
|
+
TCResult.from_block(block, ptguess)
|
|
675
|
+
for block, ptguess, correct in zip(blocks, ptguesses, corrects)
|
|
676
|
+
if correct
|
|
677
|
+
]
|
|
678
|
+
if len(rlist) > 0:
|
|
679
|
+
status = "ok"
|
|
680
|
+
results = TCResultSet(rlist)
|
|
681
|
+
else:
|
|
682
|
+
status = "nir"
|
|
683
|
+
else:
|
|
684
|
+
status = "nir"
|
|
685
|
+
return status, results, output
|
|
686
|
+
|
|
687
|
+
def update_scriptfile(self, **kwargs):
|
|
688
|
+
"""Method to update scriptfile.
|
|
689
|
+
|
|
690
|
+
This method is used to programatically edit scriptfile.
|
|
691
|
+
|
|
692
|
+
Kwargs:
|
|
693
|
+
calcs: List of lines defining fully hands-off calculations. Default None.
|
|
694
|
+
get_old_calcs: When True method returns existing calcs lines
|
|
695
|
+
before possible modification. Default False.
|
|
696
|
+
guesses: List of lines defining ptguesses. If None guesses
|
|
697
|
+
are not modified. Default None.
|
|
698
|
+
get_old_guesses: When True method returns existing ptguess lines
|
|
699
|
+
before possible modification. Default False.
|
|
700
|
+
bulk: List of lines defining bulk composition. Default None.
|
|
701
|
+
xsteps: Number of compositional steps between two bulks.
|
|
702
|
+
Default 20.
|
|
703
|
+
"""
|
|
704
|
+
calcs = kwargs.get("calcs", None)
|
|
705
|
+
get_old_calcs = kwargs.get("get_old_calcs", False)
|
|
706
|
+
guesses = kwargs.get("guesses", None)
|
|
707
|
+
get_old_guesses = kwargs.get("get_old_guesses", False)
|
|
708
|
+
bulk = kwargs.get("bulk", None)
|
|
709
|
+
xsteps = kwargs.get("xsteps", None)
|
|
710
|
+
with self.scriptfile.open("r", encoding=self.TCenc) as f:
|
|
711
|
+
scf = f.read()
|
|
712
|
+
changed = False
|
|
713
|
+
scf_1, rem = scf.split("%{PSBCALC-BEGIN}")
|
|
714
|
+
old, scf_2 = rem.split("%{PSBCALC-END}")
|
|
715
|
+
old_calcs = old.strip().splitlines()
|
|
716
|
+
if calcs is not None:
|
|
717
|
+
scf = (
|
|
718
|
+
scf_1
|
|
719
|
+
+ "%{PSBCALC-BEGIN}\n"
|
|
720
|
+
+ "\n".join(calcs)
|
|
721
|
+
+ "\n%{PSBCALC-END}"
|
|
722
|
+
+ scf_2
|
|
723
|
+
)
|
|
724
|
+
changed = True
|
|
725
|
+
scf_1, rem = scf.split("%{PSBGUESS-BEGIN}")
|
|
726
|
+
old, scf_2 = rem.split("%{PSBGUESS-END}")
|
|
727
|
+
old_guesses = old.strip().splitlines()
|
|
728
|
+
if guesses is not None:
|
|
729
|
+
scf = (
|
|
730
|
+
scf_1
|
|
731
|
+
+ "%{PSBGUESS-BEGIN}\n"
|
|
732
|
+
+ "\n".join(guesses)
|
|
733
|
+
+ "\n%{PSBGUESS-END}"
|
|
734
|
+
+ scf_2
|
|
735
|
+
)
|
|
736
|
+
changed = True
|
|
737
|
+
if bulk is not None:
|
|
738
|
+
scf_1, rem = scf.split("%{PSBBULK-BEGIN}")
|
|
739
|
+
old, scf_2 = rem.split("%{PSBBULK-END}")
|
|
740
|
+
bulk_lines = []
|
|
741
|
+
if len(bulk) == 2:
|
|
742
|
+
bulk_lines.append("bulk {}".format(" ".join(bulk[0])))
|
|
743
|
+
bulk_lines.append("bulk {}".format(" ".join(bulk[1])))
|
|
744
|
+
else:
|
|
745
|
+
bulk_lines.append("bulk {}".format(" ".join(bulk[0])))
|
|
746
|
+
bulk_lines.append("bulk {}".format(" ".join(bulk[1])))
|
|
747
|
+
bulk_lines.append("bulk {} {}".format(" ".join(bulk[2]), xsteps))
|
|
748
|
+
scf = (
|
|
749
|
+
scf_1
|
|
750
|
+
+ "%{PSBBULK-BEGIN}\n"
|
|
751
|
+
+ "\n".join(bulk_lines)
|
|
752
|
+
+ "\n%{PSBBULK-END}"
|
|
753
|
+
+ scf_2
|
|
754
|
+
)
|
|
755
|
+
changed = True
|
|
756
|
+
if xsteps is not None:
|
|
757
|
+
bulk_lines = []
|
|
758
|
+
scf_1, rem = scf.split("%{PSBBULK-BEGIN}")
|
|
759
|
+
old, scf_2 = rem.split("%{PSBBULK-END}")
|
|
760
|
+
if len(self.bulk) == 3:
|
|
761
|
+
bulk_lines.append("bulk {}".format(" ".join(self.bulk[0])))
|
|
762
|
+
bulk_lines.append("bulk {}".format(" ".join(self.bulk[1])))
|
|
763
|
+
bulk_lines.append("bulk {} {}".format(" ".join(self.bulk[2]), xsteps))
|
|
764
|
+
scf = (
|
|
765
|
+
scf_1
|
|
766
|
+
+ "%{PSBBULK-BEGIN}\n"
|
|
767
|
+
+ "\n".join(bulk_lines)
|
|
768
|
+
+ "\n%{PSBBULK-END}"
|
|
769
|
+
+ scf_2
|
|
770
|
+
)
|
|
771
|
+
changed = True
|
|
772
|
+
if changed:
|
|
773
|
+
with self.scriptfile.open("w", encoding=self.TCenc) as f:
|
|
774
|
+
f.write(scf)
|
|
775
|
+
if get_old_calcs and get_old_guesses:
|
|
776
|
+
return old_calcs, old_guesses
|
|
777
|
+
elif get_old_calcs:
|
|
778
|
+
return old_calcs
|
|
779
|
+
elif get_old_guesses:
|
|
780
|
+
return old_guesses
|
|
781
|
+
else:
|
|
782
|
+
return None
|
|
783
|
+
|
|
784
|
+
def calc_t(self, phases, out, **kwargs):
|
|
785
|
+
"""Method to run THERMOCALC to find univariant line using Calc T at P strategy.
|
|
786
|
+
|
|
787
|
+
Args:
|
|
788
|
+
phases (set): Set of present phases
|
|
789
|
+
out (set): Set of single zero mode phase
|
|
790
|
+
prange (tuple): Temperature range for calculation
|
|
791
|
+
trange (tuple): Pressure range for calculation
|
|
792
|
+
steps (int): Number of steps
|
|
793
|
+
|
|
794
|
+
Returns:
|
|
795
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
796
|
+
Input ans could be used to reproduce calculation.
|
|
797
|
+
"""
|
|
798
|
+
prange = kwargs.get("prange", self.prange)
|
|
799
|
+
trange = kwargs.get("trange", self.trange)
|
|
800
|
+
steps = kwargs.get("steps", 50)
|
|
801
|
+
step = (prange[1] - prange[0]) / steps
|
|
802
|
+
calcs = [
|
|
803
|
+
"calcP {:g} {:g} {:g}".format(*prange, step),
|
|
804
|
+
"calcT {:g} {:g}".format(*trange),
|
|
805
|
+
"calctatp yes",
|
|
806
|
+
"with {}".format(" ".join(phases - self.excess)),
|
|
807
|
+
"zeromodeisopleth {}".format(" ".join(out)),
|
|
808
|
+
]
|
|
809
|
+
self.update_scriptfile(calcs=calcs)
|
|
810
|
+
tcout = self.runtc()
|
|
811
|
+
return tcout, calcs
|
|
812
|
+
|
|
813
|
+
def calc_p(self, phases, out, **kwargs):
|
|
814
|
+
"""Method to run THERMOCALC to find univariant line using Calc P at T strategy.
|
|
815
|
+
|
|
816
|
+
Args:
|
|
817
|
+
phases (set): Set of present phases
|
|
818
|
+
out (set): Set of single zero mode phase
|
|
819
|
+
prange (tuple): Temperature range for calculation
|
|
820
|
+
trange (tuple): Pressure range for calculation
|
|
821
|
+
steps (int): Number of steps
|
|
822
|
+
|
|
823
|
+
Returns:
|
|
824
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
825
|
+
Input ans could be used to reproduce calculation.
|
|
826
|
+
"""
|
|
827
|
+
prange = kwargs.get("prange", self.prange)
|
|
828
|
+
trange = kwargs.get("trange", self.trange)
|
|
829
|
+
steps = kwargs.get("steps", 50)
|
|
830
|
+
step = (trange[1] - trange[0]) / steps
|
|
831
|
+
calcs = [
|
|
832
|
+
"calcP {:g} {:g}".format(*prange),
|
|
833
|
+
"calcT {:g} {:g} {:g}".format(*trange, step),
|
|
834
|
+
"calctatp no",
|
|
835
|
+
"with {}".format(" ".join(phases - self.excess)),
|
|
836
|
+
"zeromodeisopleth {}".format(" ".join(out)),
|
|
837
|
+
]
|
|
838
|
+
self.update_scriptfile(calcs=calcs)
|
|
839
|
+
tcout = self.runtc()
|
|
840
|
+
return tcout, calcs
|
|
841
|
+
|
|
842
|
+
def calc_pt(self, phases, out, **kwargs):
|
|
843
|
+
"""Method to run THERMOCALC to find invariant point.
|
|
844
|
+
|
|
845
|
+
Args:
|
|
846
|
+
phases (set): Set of present phases
|
|
847
|
+
out (set): Set of two zero mode phases
|
|
848
|
+
prange (tuple): Temperature range for calculation
|
|
849
|
+
trange (tuple): Pressure range for calculation
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
853
|
+
Input ans could be used to reproduce calculation.
|
|
854
|
+
"""
|
|
855
|
+
prange = kwargs.get("prange", self.prange)
|
|
856
|
+
trange = kwargs.get("trange", self.trange)
|
|
857
|
+
calcs = [
|
|
858
|
+
"calcP {:g} {:g}".format(*prange),
|
|
859
|
+
"calcT {:g} {:g}".format(*trange),
|
|
860
|
+
"with {}".format(" ".join(phases - self.excess)),
|
|
861
|
+
"zeromodeisopleth {}".format(" ".join(out)),
|
|
862
|
+
]
|
|
863
|
+
self.update_scriptfile(calcs=calcs)
|
|
864
|
+
tcout = self.runtc()
|
|
865
|
+
return tcout, calcs
|
|
866
|
+
|
|
867
|
+
def calc_tx(self, phases, out, **kwargs):
|
|
868
|
+
"""Method to run THERMOCALC for T-X pseudosection calculations.
|
|
869
|
+
|
|
870
|
+
Args:
|
|
871
|
+
phases (set): Set of present phases
|
|
872
|
+
out (set): Set of zero mode phases
|
|
873
|
+
prange (tuple): Temperature range for calculation
|
|
874
|
+
trange (tuple): Pressure range for calculation
|
|
875
|
+
xvals (tuple): range for X variable
|
|
876
|
+
steps (int): Number of steps
|
|
877
|
+
|
|
878
|
+
Returns:
|
|
879
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
880
|
+
Input ans could be used to reproduce calculation.
|
|
881
|
+
"""
|
|
882
|
+
prange = kwargs.get("prange", self.prange)
|
|
883
|
+
trange = kwargs.get("trange", self.trange)
|
|
884
|
+
xvals = kwargs.get("xvals", (0, 1))
|
|
885
|
+
steps = kwargs.get("steps", 20)
|
|
886
|
+
step = (prange[1] - prange[0]) / steps
|
|
887
|
+
if prange[0] == prange[1]:
|
|
888
|
+
calcs = [
|
|
889
|
+
"calcP {:g} {:g}".format(*prange),
|
|
890
|
+
"calcT {:g} {:g}".format(*trange),
|
|
891
|
+
"calctatp yes",
|
|
892
|
+
"with {}".format(" ".join(phases - self.excess)),
|
|
893
|
+
"zeromodeisopleth {}".format(" ".join(out)),
|
|
894
|
+
"bulksubrange {:g} {:g}".format(*xvals),
|
|
895
|
+
]
|
|
896
|
+
else:
|
|
897
|
+
calcs = [
|
|
898
|
+
"calcP {:g} {:g} {:g}".format(*prange, step),
|
|
899
|
+
"calcT {:g} {:g}".format(*trange),
|
|
900
|
+
"calctatp yes",
|
|
901
|
+
"with {}".format(" ".join(phases - self.excess)),
|
|
902
|
+
"zeromodeisopleth {}".format(" ".join(out)),
|
|
903
|
+
"bulksubrange {:g} {:g}".format(*xvals),
|
|
904
|
+
]
|
|
905
|
+
self.update_scriptfile(calcs=calcs, xsteps=steps)
|
|
906
|
+
tcout = self.runtc()
|
|
907
|
+
return tcout, calcs
|
|
908
|
+
|
|
909
|
+
def calc_px(self, phases, out, **kwargs):
|
|
910
|
+
"""Method to run THERMOCALC for p-X pseudosection calculations.
|
|
911
|
+
|
|
912
|
+
Args:
|
|
913
|
+
phases (set): Set of present phases
|
|
914
|
+
out (set): Set of zero mode phases
|
|
915
|
+
prange (tuple): Temperature range for calculation
|
|
916
|
+
trange (tuple): Pressure range for calculation
|
|
917
|
+
xvals (tuple): range for X variable
|
|
918
|
+
steps (int): Number of steps
|
|
919
|
+
|
|
920
|
+
Returns:
|
|
921
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
922
|
+
Input ans could be used to reproduce calculation.
|
|
923
|
+
"""
|
|
924
|
+
prange = kwargs.get("prange", self.prange)
|
|
925
|
+
trange = kwargs.get("trange", self.trange)
|
|
926
|
+
xvals = kwargs.get("xvals", (0, 1))
|
|
927
|
+
steps = kwargs.get("steps", 20)
|
|
928
|
+
step = (trange[1] - trange[0]) / steps
|
|
929
|
+
if trange[0] == trange[1]:
|
|
930
|
+
calcs = [
|
|
931
|
+
"calcP {:g} {:g}".format(*prange),
|
|
932
|
+
"calcT {:g} {:g}".format(*trange),
|
|
933
|
+
"calctatp no",
|
|
934
|
+
"with {}".format(" ".join(phases - self.excess)),
|
|
935
|
+
"zeromodeisopleth {}".format(" ".join(out)),
|
|
936
|
+
"bulksubrange {:g} {:g}".format(*xvals),
|
|
937
|
+
]
|
|
938
|
+
else:
|
|
939
|
+
calcs = [
|
|
940
|
+
"calcP {:g} {:g}".format(*prange),
|
|
941
|
+
"calcT {:g} {:g} {:g}".format(*trange, step),
|
|
942
|
+
"calctatp no",
|
|
943
|
+
"with {}".format(" ".join(phases - self.excess)),
|
|
944
|
+
"zeromodeisopleth {}".format(" ".join(out)),
|
|
945
|
+
"bulksubrange {:g} {:g}".format(*xvals),
|
|
946
|
+
]
|
|
947
|
+
self.update_scriptfile(calcs=calcs, xsteps=steps)
|
|
948
|
+
tcout = self.runtc()
|
|
949
|
+
return tcout, calcs
|
|
950
|
+
|
|
951
|
+
def calc_assemblage(self, phases, p, t, onebulk=None):
|
|
952
|
+
"""Method to run THERMOCALC to calculate compositions of stable assemblage.
|
|
953
|
+
|
|
954
|
+
Args:
|
|
955
|
+
phases (set): Set of present phases
|
|
956
|
+
p (float): Temperature for calculation
|
|
957
|
+
t (float): Pressure for calculation
|
|
958
|
+
|
|
959
|
+
Returns:
|
|
960
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
961
|
+
Input ans could be used to reproduce calculation.
|
|
962
|
+
"""
|
|
963
|
+
calcs = [
|
|
964
|
+
"calcP {}".format(p),
|
|
965
|
+
"calcT {}".format(t),
|
|
966
|
+
"with {}".format(" ".join(phases - self.excess)),
|
|
967
|
+
]
|
|
968
|
+
if onebulk is not None:
|
|
969
|
+
calcs.append("onebulk {}".format(onebulk))
|
|
970
|
+
self.update_scriptfile(calcs=calcs)
|
|
971
|
+
tcout = self.runtc("\nkill\n\n")
|
|
972
|
+
return tcout, calcs
|
|
973
|
+
|
|
974
|
+
def dogmin(self, phases, p, t, variance, doglevel=1, onebulk=None):
|
|
975
|
+
"""Run THERMOCALC dogmin session.
|
|
976
|
+
|
|
977
|
+
Args:
|
|
978
|
+
variance (int): Maximum variance to be considered
|
|
979
|
+
|
|
980
|
+
Returns:
|
|
981
|
+
str: THERMOCALC standard output
|
|
982
|
+
"""
|
|
983
|
+
calcs = [
|
|
984
|
+
"calcP {}".format(p),
|
|
985
|
+
"calcT {}".format(t),
|
|
986
|
+
"dogmin yes {}".format(doglevel),
|
|
987
|
+
"with {}".format(" ".join(phases - self.excess)),
|
|
988
|
+
"maxvar {}".format(variance),
|
|
989
|
+
]
|
|
990
|
+
if onebulk is not None:
|
|
991
|
+
calcs.append("onebulk {}".format(onebulk))
|
|
992
|
+
old_calcs = self.update_scriptfile(get_old_calcs=True, calcs=calcs)
|
|
993
|
+
tcout = self.runtc("\nkill\n\n")
|
|
994
|
+
self.update_scriptfile(calcs=old_calcs)
|
|
995
|
+
return tcout
|
|
996
|
+
|
|
997
|
+
def calc_variance(self, phases):
|
|
998
|
+
"""Get variance of assemblage.
|
|
999
|
+
|
|
1000
|
+
Args:
|
|
1001
|
+
phases (set): Set of present phases
|
|
1002
|
+
|
|
1003
|
+
Returns:
|
|
1004
|
+
int: variance
|
|
1005
|
+
"""
|
|
1006
|
+
variance = None
|
|
1007
|
+
calcs = [
|
|
1008
|
+
"calcP {} {}".format(*self.prange),
|
|
1009
|
+
"calcT {} {}".format(*self.trange),
|
|
1010
|
+
"with {}".format(" ".join(phases - self.excess)),
|
|
1011
|
+
"acceptvar no",
|
|
1012
|
+
]
|
|
1013
|
+
old_calcs = self.update_scriptfile(get_old_calcs=True, calcs=calcs)
|
|
1014
|
+
tcout = self.runtc("kill\n\n")
|
|
1015
|
+
self.update_scriptfile(calcs=old_calcs)
|
|
1016
|
+
for ln in tcout.splitlines():
|
|
1017
|
+
if "variance of required equilibrium" in ln:
|
|
1018
|
+
variance = int(ln[ln.index("(") + 1 : ln.index("?")])
|
|
1019
|
+
break
|
|
1020
|
+
return variance
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
class TC34API(TCAPI):
|
|
1024
|
+
def __init__(self, workdir, tcexe, drexe, encoding="mac-roman"):
|
|
1025
|
+
self.workdir = Path(workdir).resolve()
|
|
1026
|
+
self.tcexe = tcexe
|
|
1027
|
+
self.drexe = drexe
|
|
1028
|
+
self.TCenc = encoding
|
|
1029
|
+
try:
|
|
1030
|
+
# TC version
|
|
1031
|
+
errinfo = "THERMOCALC executable test."
|
|
1032
|
+
self.tcout = self.runtc()
|
|
1033
|
+
# tc-prefs file
|
|
1034
|
+
if not self.workdir.joinpath("tc-prefs.txt").exists():
|
|
1035
|
+
raise InitError("No tc-prefs.txt file in working directory.")
|
|
1036
|
+
errinfo = "tc-prefs.txt file in working directory cannot be accessed."
|
|
1037
|
+
for line in self.workdir.joinpath("tc-prefs.txt").open(
|
|
1038
|
+
"r", encoding=self.TCenc
|
|
1039
|
+
):
|
|
1040
|
+
kw = line.split()
|
|
1041
|
+
if kw != []:
|
|
1042
|
+
if kw[0] == "scriptfile":
|
|
1043
|
+
self.name = kw[1]
|
|
1044
|
+
if not self.scriptfile.exists():
|
|
1045
|
+
raise InitError(
|
|
1046
|
+
"tc-prefs: scriptfile tc-"
|
|
1047
|
+
+ self.name
|
|
1048
|
+
+ ".txt does not exists in your working directory."
|
|
1049
|
+
)
|
|
1050
|
+
if kw[0] == "calcmode":
|
|
1051
|
+
if kw[1] != "1":
|
|
1052
|
+
raise InitError("tc-prefs: calcmode must be 1.")
|
|
1053
|
+
|
|
1054
|
+
errinfo = "Scriptfile error!"
|
|
1055
|
+
self.excess = set()
|
|
1056
|
+
self.trange = (200.0, 1000.0)
|
|
1057
|
+
self.prange = (0.1, 20.0)
|
|
1058
|
+
self.bulk = []
|
|
1059
|
+
self.ptx_steps = 20
|
|
1060
|
+
check = {
|
|
1061
|
+
"axfile": False,
|
|
1062
|
+
"setbulk": False,
|
|
1063
|
+
"printbulkinfo": False,
|
|
1064
|
+
"setexcess": False,
|
|
1065
|
+
"printxyz": False,
|
|
1066
|
+
}
|
|
1067
|
+
errinfo = "Check your scriptfile."
|
|
1068
|
+
with self.scriptfile.open("r", encoding=self.TCenc) as f:
|
|
1069
|
+
lines = f.readlines()
|
|
1070
|
+
gsb, gse = False, False
|
|
1071
|
+
dgb, dge = False, False
|
|
1072
|
+
bub, bue = False, False
|
|
1073
|
+
for line in lines:
|
|
1074
|
+
kw = line.split("%")[0].split()
|
|
1075
|
+
if "{PSBGUESS-BEGIN}" in line:
|
|
1076
|
+
gsb = True
|
|
1077
|
+
if "{PSBGUESS-END}" in line:
|
|
1078
|
+
gse = True
|
|
1079
|
+
if "{PSBDOGMIN-BEGIN}" in line:
|
|
1080
|
+
dgb = True
|
|
1081
|
+
if "{PSBDOGMIN-END}" in line:
|
|
1082
|
+
dge = True
|
|
1083
|
+
if "{PSBBULK-BEGIN}" in line:
|
|
1084
|
+
bub = True
|
|
1085
|
+
if "{PSBBULK-END}" in line:
|
|
1086
|
+
bue = True
|
|
1087
|
+
if kw == ["*"]:
|
|
1088
|
+
break
|
|
1089
|
+
if kw:
|
|
1090
|
+
if kw[0] == "axfile":
|
|
1091
|
+
errinfo = "Wrong argument for axfile keyword in scriptfile."
|
|
1092
|
+
self.axname = kw[1]
|
|
1093
|
+
if not self.axfile.exists():
|
|
1094
|
+
raise ScriptfileError(
|
|
1095
|
+
"Axfile "
|
|
1096
|
+
+ str(self.axfile)
|
|
1097
|
+
+ " does not exists in working directory"
|
|
1098
|
+
)
|
|
1099
|
+
check["axfile"] = True
|
|
1100
|
+
elif kw[0] == "setdefTwindow":
|
|
1101
|
+
errinfo = (
|
|
1102
|
+
"Wrong arguments for setdefTwindow keyword in scriptfile."
|
|
1103
|
+
)
|
|
1104
|
+
self.trange = (float(kw[-2]), float(kw[-1]))
|
|
1105
|
+
elif kw[0] == "setdefPwindow":
|
|
1106
|
+
errinfo = (
|
|
1107
|
+
"Wrong arguments for setdefPwindow keyword in scriptfile."
|
|
1108
|
+
)
|
|
1109
|
+
self.prange = (float(kw[-2]), float(kw[-1]))
|
|
1110
|
+
elif kw[0] == "setbulk":
|
|
1111
|
+
errinfo = "Wrong arguments for setbulk keyword in scriptfile."
|
|
1112
|
+
bulk = kw[1:]
|
|
1113
|
+
if "ask" in bulk:
|
|
1114
|
+
raise ScriptfileError("Setbulk must not be set to ask.")
|
|
1115
|
+
if "yes" in bulk:
|
|
1116
|
+
bulk.remove("yes")
|
|
1117
|
+
if "no" not in bulk:
|
|
1118
|
+
if len(self.bulk) == 1:
|
|
1119
|
+
if len(self.bulk[0]) < len(bulk):
|
|
1120
|
+
self.ptx_steps = int(bulk[-1])
|
|
1121
|
+
bulk = bulk[:-1]
|
|
1122
|
+
self.bulk.append(bulk)
|
|
1123
|
+
check["setbulk"] = True
|
|
1124
|
+
|
|
1125
|
+
elif kw[0] == "setexcess":
|
|
1126
|
+
errinfo = "Wrong argument for setexcess keyword in scriptfile."
|
|
1127
|
+
self.excess = set(kw[1:])
|
|
1128
|
+
if "yes" in self.excess:
|
|
1129
|
+
self.excess.remove("yes")
|
|
1130
|
+
if "no" in self.excess:
|
|
1131
|
+
self.excess = set()
|
|
1132
|
+
if "ask" in self.excess:
|
|
1133
|
+
raise ScriptfileError("Setexcess must not be set to ask.")
|
|
1134
|
+
check["setexcess"] = True
|
|
1135
|
+
elif kw[0] == "calctatp":
|
|
1136
|
+
errinfo = "Wrong argument for calctatp keyword in scriptfile."
|
|
1137
|
+
if not kw[1] == "ask":
|
|
1138
|
+
raise ScriptfileError("Calctatp must be set to ask.")
|
|
1139
|
+
# elif kw[0] == 'drawpd':
|
|
1140
|
+
# errinfo = 'Wrong argument for drawpd keyword in scriptfile.'
|
|
1141
|
+
# if kw[1] == 'no':
|
|
1142
|
+
# raise ScriptfileError('Drawpd must be set to yes.')
|
|
1143
|
+
# check['drawpd'] = True
|
|
1144
|
+
elif kw[0] == "printbulkinfo":
|
|
1145
|
+
errinfo = (
|
|
1146
|
+
"Wrong argument for printbulkinfo keyword in scriptfile."
|
|
1147
|
+
)
|
|
1148
|
+
if kw[1] == "no":
|
|
1149
|
+
raise ScriptfileError("Printbulkinfo must be set to yes.")
|
|
1150
|
+
check["printbulkinfo"] = True
|
|
1151
|
+
elif kw[0] == "printxyz":
|
|
1152
|
+
errinfo = "Wrong argument for printxyz keyword in scriptfile."
|
|
1153
|
+
if kw[1] == "no":
|
|
1154
|
+
raise ScriptfileError("Printxyz must be set to yes.")
|
|
1155
|
+
check["printxyz"] = True
|
|
1156
|
+
elif kw[0] == "dogmin":
|
|
1157
|
+
errinfo = "Wrong argument for dogmin keyword in scriptfile."
|
|
1158
|
+
if not kw[1] == "no":
|
|
1159
|
+
raise ScriptfileError("Dogmin must be set to no.")
|
|
1160
|
+
elif kw[0] == "fluidpresent":
|
|
1161
|
+
raise ScriptfileError(
|
|
1162
|
+
"Fluidpresent must be deleted from scriptfile."
|
|
1163
|
+
)
|
|
1164
|
+
elif kw[0] == "seta":
|
|
1165
|
+
errinfo = "Wrong argument for seta keyword in scriptfile."
|
|
1166
|
+
if not kw[1] == "no":
|
|
1167
|
+
raise ScriptfileError("Seta must be set to no.")
|
|
1168
|
+
elif kw[0] == "setmu":
|
|
1169
|
+
errinfo = "Wrong argument for setmu keyword in scriptfile."
|
|
1170
|
+
if not kw[1] == "no":
|
|
1171
|
+
raise ScriptfileError("Setmu must be set to no.")
|
|
1172
|
+
elif kw[0] == "usecalcq":
|
|
1173
|
+
errinfo = "Wrong argument for usecalcq keyword in scriptfile."
|
|
1174
|
+
if kw[1] == "ask":
|
|
1175
|
+
raise ScriptfileError("Usecalcq must be yes or no.")
|
|
1176
|
+
elif kw[0] == "pseudosection":
|
|
1177
|
+
errinfo = (
|
|
1178
|
+
"Wrong argument for pseudosection keyword in scriptfile."
|
|
1179
|
+
)
|
|
1180
|
+
if kw[1] == "ask":
|
|
1181
|
+
raise ScriptfileError("Pseudosection must be yes or no.")
|
|
1182
|
+
elif kw[0] == "zeromodeiso":
|
|
1183
|
+
errinfo = (
|
|
1184
|
+
"Wrong argument for zeromodeiso keyword in scriptfile."
|
|
1185
|
+
)
|
|
1186
|
+
if not kw[1] == "yes":
|
|
1187
|
+
raise ScriptfileError("Zeromodeiso must be set to yes.")
|
|
1188
|
+
elif kw[0] == "setmodeiso":
|
|
1189
|
+
errinfo = "Wrong argument for setmodeiso keyword in scriptfile."
|
|
1190
|
+
if not kw[1] == "yes":
|
|
1191
|
+
raise ScriptfileError("Setmodeiso must be set to yes.")
|
|
1192
|
+
elif kw[0] == "convliq":
|
|
1193
|
+
raise ScriptfileError("Convliq not yet supported.")
|
|
1194
|
+
elif kw[0] == "setiso":
|
|
1195
|
+
errinfo = "Wrong argument for setiso keyword in scriptfile."
|
|
1196
|
+
if kw[1] != "no":
|
|
1197
|
+
raise ScriptfileError("Setiso must be set to no.")
|
|
1198
|
+
|
|
1199
|
+
if not check["axfile"]:
|
|
1200
|
+
raise ScriptfileError("Axfile name must be provided in scriptfile.")
|
|
1201
|
+
if not check["setbulk"]:
|
|
1202
|
+
raise ScriptfileError("Setbulk must be provided in scriptfile.")
|
|
1203
|
+
if not check["setexcess"]:
|
|
1204
|
+
raise ScriptfileError(
|
|
1205
|
+
"Setexcess must not be set to ask. To suppress this error put empty setexcess keyword to your scriptfile."
|
|
1206
|
+
)
|
|
1207
|
+
# if not check['drawpd']:
|
|
1208
|
+
# raise ScriptfileError('Drawpd must be set to yes. To suppress this error put drawpd yes keyword to your scriptfile.')
|
|
1209
|
+
if not check["printbulkinfo"]:
|
|
1210
|
+
raise ScriptfileError(
|
|
1211
|
+
"Printbulkinfo must be set to yes. To suppress this error put printbulkinfo yes keyword to your scriptfile."
|
|
1212
|
+
)
|
|
1213
|
+
if not check["printxyz"]:
|
|
1214
|
+
raise ScriptfileError(
|
|
1215
|
+
"Printxyz must be set to yes. To suppress this error put printxyz yes keyword to your scriptfile."
|
|
1216
|
+
)
|
|
1217
|
+
if not (gsb and gse):
|
|
1218
|
+
raise ScriptfileError(
|
|
1219
|
+
"There are not {PSBGUESS-BEGIN} and {PSBGUESS-END} tags in your scriptfile."
|
|
1220
|
+
)
|
|
1221
|
+
if not (dgb and dge):
|
|
1222
|
+
raise ScriptfileError(
|
|
1223
|
+
"There are not {PSBDOGMIN-BEGIN} and {PSBDOGMIN-END} tags in your scriptfile."
|
|
1224
|
+
)
|
|
1225
|
+
if not (bub and bue):
|
|
1226
|
+
raise ScriptfileError(
|
|
1227
|
+
"There are not {PSBBULK-BEGIN} and {PSBBULK-END} tags in your scriptfile."
|
|
1228
|
+
)
|
|
1229
|
+
|
|
1230
|
+
# TC
|
|
1231
|
+
if "BOMBED" in self.tcout:
|
|
1232
|
+
raise TCError(self.tcout.split("BOMBED")[1].split("\n")[0])
|
|
1233
|
+
else:
|
|
1234
|
+
self.phases = set(
|
|
1235
|
+
self.tcout.split("choose from:")[1].split("\n")[0].split()
|
|
1236
|
+
)
|
|
1237
|
+
# OK
|
|
1238
|
+
self.status = "Initial check done."
|
|
1239
|
+
self.OK = True
|
|
1240
|
+
except BaseException as e:
|
|
1241
|
+
if (
|
|
1242
|
+
isinstance(e, InitError)
|
|
1243
|
+
or isinstance(e, ScriptfileError)
|
|
1244
|
+
or isinstance(e, TCError)
|
|
1245
|
+
):
|
|
1246
|
+
self.status = "{}: {}".format(type(e).__name__, str(e))
|
|
1247
|
+
else:
|
|
1248
|
+
self.status = "{}: {} {}".format(type(e).__name__, str(e), errinfo)
|
|
1249
|
+
self.OK = False
|
|
1250
|
+
|
|
1251
|
+
def parse_logfile(self, **kwargs):
|
|
1252
|
+
"""Parser for THERMOCALC 3.4x output.
|
|
1253
|
+
|
|
1254
|
+
It parses the outputs of THERMOCALC after calculation.
|
|
1255
|
+
|
|
1256
|
+
Args:
|
|
1257
|
+
output (str): When not None, used as content of logfile. Default None.
|
|
1258
|
+
resic (str): When not None, used as content of icfile. Default None.
|
|
1259
|
+
get_phases (bool): When true returns also tuple (phases, out, calcs). Default False
|
|
1260
|
+
|
|
1261
|
+
Returns:
|
|
1262
|
+
status (str): Result of parsing. 'ok', 'nir' (nothing in range) or 'bombed'.
|
|
1263
|
+
results (TCResultSet): Results of TC calculation.
|
|
1264
|
+
output (str): Full nonparsed THERMOCALC output.
|
|
1265
|
+
|
|
1266
|
+
Example:
|
|
1267
|
+
Parse output after univariant line calculation in P-T pseudosection::
|
|
1268
|
+
|
|
1269
|
+
>>> tc = TCAPI('pat/to/dir')
|
|
1270
|
+
>>> status, result, output = tc.parse_logfile()
|
|
1271
|
+
"""
|
|
1272
|
+
output = kwargs.get("output", None)
|
|
1273
|
+
get_phases = kwargs.get("get_phases", False)
|
|
1274
|
+
try:
|
|
1275
|
+
if output is None:
|
|
1276
|
+
with self.logfile.open("r", encoding=self.TCenc) as f:
|
|
1277
|
+
output = f.read()
|
|
1278
|
+
lines = [
|
|
1279
|
+
"".join([c for c in ln if ord(c) < 128])
|
|
1280
|
+
for ln in output.splitlines()
|
|
1281
|
+
if ln != ""
|
|
1282
|
+
]
|
|
1283
|
+
pts = []
|
|
1284
|
+
res = []
|
|
1285
|
+
variance = -1
|
|
1286
|
+
if [ix for ix, ln in enumerate(lines) if "BOMBED" in ln]:
|
|
1287
|
+
status = "bombed"
|
|
1288
|
+
else:
|
|
1289
|
+
for ln in lines:
|
|
1290
|
+
if "variance of required equilibrium" in ln:
|
|
1291
|
+
variance = int(ln[ln.index("(") + 1 : ln.index("?")])
|
|
1292
|
+
break
|
|
1293
|
+
bstarts = [
|
|
1294
|
+
ix for ix, ln in enumerate(lines) if ln.startswith(" P(kbar)")
|
|
1295
|
+
]
|
|
1296
|
+
bstarts.append(len(lines))
|
|
1297
|
+
for bs, be in zip(bstarts[:-1], bstarts[1:]):
|
|
1298
|
+
block = lines[bs:be]
|
|
1299
|
+
pts.append([float(n) for n in block[1].split()[:2]])
|
|
1300
|
+
xyz = [
|
|
1301
|
+
ix for ix, ln in enumerate(block) if ln.startswith("xyzguess")
|
|
1302
|
+
]
|
|
1303
|
+
gixs = [
|
|
1304
|
+
ix for ix, ln in enumerate(block) if ln.startswith("ptguess")
|
|
1305
|
+
][0] - 3
|
|
1306
|
+
gixe = xyz[-1] + 2
|
|
1307
|
+
ptguess = block[gixs:gixe]
|
|
1308
|
+
data = {}
|
|
1309
|
+
rbix = [
|
|
1310
|
+
ix for ix, ln in enumerate(block) if ln.startswith("rbi yes")
|
|
1311
|
+
][0]
|
|
1312
|
+
phases = block[rbix - 1].split()[1:]
|
|
1313
|
+
for phase, val in zip(phases, block[rbix].split()[2:]):
|
|
1314
|
+
data[phase] = dict(mode=float(val))
|
|
1315
|
+
for ix in xyz:
|
|
1316
|
+
lbl = block[ix].split()[1]
|
|
1317
|
+
phase, comp = (
|
|
1318
|
+
lbl[lbl.find("(") + 1 : lbl.find(")")],
|
|
1319
|
+
lbl[: lbl.find("(")],
|
|
1320
|
+
)
|
|
1321
|
+
if phase not in data:
|
|
1322
|
+
raise Exception(
|
|
1323
|
+
"Check model {} in your ax file. Commonly liq coded as L for starting guesses.".format(
|
|
1324
|
+
phase
|
|
1325
|
+
)
|
|
1326
|
+
)
|
|
1327
|
+
data[phase][comp] = float(block[ix].split()[2])
|
|
1328
|
+
rbiox = block[rbix + 1].split()[2:]
|
|
1329
|
+
for delta in range(len(phases)):
|
|
1330
|
+
rbi = {
|
|
1331
|
+
c: float(v)
|
|
1332
|
+
for c, v in zip(
|
|
1333
|
+
rbiox, block[rbix + 2 + delta].split()[2:-2]
|
|
1334
|
+
)
|
|
1335
|
+
}
|
|
1336
|
+
rbi["H2O"] = float(block[rbix + 2 + delta].split()[1])
|
|
1337
|
+
# data[phases[delta]]['rbi'] = comp
|
|
1338
|
+
data[phases[delta]].update(rbi)
|
|
1339
|
+
res.append(dict(data=data, ptguess=ptguess))
|
|
1340
|
+
if res:
|
|
1341
|
+
status = "ok"
|
|
1342
|
+
pp, TT = np.array(pts).T
|
|
1343
|
+
results = TCResultSet(
|
|
1344
|
+
[
|
|
1345
|
+
TCResult(
|
|
1346
|
+
T,
|
|
1347
|
+
p,
|
|
1348
|
+
variance=variance,
|
|
1349
|
+
c=0.0,
|
|
1350
|
+
data=r["data"],
|
|
1351
|
+
ptguess=r["ptguess"],
|
|
1352
|
+
)
|
|
1353
|
+
for (r, p, T) in zip(res, pp, TT)
|
|
1354
|
+
]
|
|
1355
|
+
)
|
|
1356
|
+
else:
|
|
1357
|
+
status = "nir"
|
|
1358
|
+
results = None
|
|
1359
|
+
if get_phases:
|
|
1360
|
+
phases, out = None, None
|
|
1361
|
+
for ln in output.splitlines():
|
|
1362
|
+
if ln.startswith("which phases"):
|
|
1363
|
+
phases = set(ln.split(":", 1)[1].split())
|
|
1364
|
+
if ln.startswith("which to set"):
|
|
1365
|
+
out = set(ln.split(":", 1)[1].split())
|
|
1366
|
+
return status, results, output, (phases, out, [])
|
|
1367
|
+
else:
|
|
1368
|
+
return status, results, output
|
|
1369
|
+
except Exception:
|
|
1370
|
+
if get_phases:
|
|
1371
|
+
return "bombed", None, None, (None, None, "")
|
|
1372
|
+
else:
|
|
1373
|
+
return "bombed", None, None
|
|
1374
|
+
|
|
1375
|
+
def update_scriptfile(self, **kwargs):
|
|
1376
|
+
"""Method to update scriptfile.
|
|
1377
|
+
|
|
1378
|
+
This methodcould be used to read or update ptguess or dogmin settings.
|
|
1379
|
+
|
|
1380
|
+
Args:
|
|
1381
|
+
guesses (list): List of lines defining ptguesses. If None guesses
|
|
1382
|
+
are not modified. Default None.
|
|
1383
|
+
get_old_guesses (bool): When True method returns existing ptguess
|
|
1384
|
+
before possible modification. Default False.
|
|
1385
|
+
dogmin (str): Argument of dogmin script. Could be 'no' or 'yes' or
|
|
1386
|
+
'yes X', where X is log level. When None no modification is
|
|
1387
|
+
done. Default None.
|
|
1388
|
+
which (set): Set of phases used for dogmin.
|
|
1389
|
+
bulk (list): Bulk composition. Default None.
|
|
1390
|
+
xvals (tuple): x values for compositions. Default (0, 1)
|
|
1391
|
+
xsteps (int): Number of compositional steps between two bulks.
|
|
1392
|
+
Default 20.
|
|
1393
|
+
p (float): Pressure for dogmin calculation
|
|
1394
|
+
T (float): Temperature for dogmin calculation
|
|
1395
|
+
"""
|
|
1396
|
+
guesses = kwargs.get("guesses", None)
|
|
1397
|
+
get_old_guesses = kwargs.get("get_old_guesses", False)
|
|
1398
|
+
dogmin = kwargs.get("dogmin", None) # None or 'no' or 'yes 1'
|
|
1399
|
+
which = kwargs.get("which", None)
|
|
1400
|
+
bulk = kwargs.get("bulk", None)
|
|
1401
|
+
xvals = kwargs.get("xvals", (0, 1))
|
|
1402
|
+
xsteps = kwargs.get("xsteps", 20)
|
|
1403
|
+
p = kwargs.get("p", None)
|
|
1404
|
+
T = kwargs.get("T", None)
|
|
1405
|
+
with self.scriptfile.open("r", encoding=self.TCenc) as f:
|
|
1406
|
+
sc = f.readlines()
|
|
1407
|
+
changed = False
|
|
1408
|
+
gsb = [ix for ix, ln in enumerate(sc) if ln.startswith("%{PSBGUESS-BEGIN}")]
|
|
1409
|
+
gse = [ix for ix, ln in enumerate(sc) if ln.startswith("%{PSBGUESS-END}")]
|
|
1410
|
+
if get_old_guesses:
|
|
1411
|
+
if gsb and gse:
|
|
1412
|
+
old_guesses = [ln.strip() for ln in sc[gsb[0] + 1 : gse[0]]]
|
|
1413
|
+
if guesses is not None:
|
|
1414
|
+
if gsb and gse:
|
|
1415
|
+
sc = sc[: gsb[0] + 1] + [gln + "\n" for gln in guesses] + sc[gse[0] :]
|
|
1416
|
+
changed = True
|
|
1417
|
+
if dogmin is not None:
|
|
1418
|
+
dgb = [
|
|
1419
|
+
ix for ix, ln in enumerate(sc) if ln.startswith("%{PSBDOGMIN-BEGIN}")
|
|
1420
|
+
]
|
|
1421
|
+
dge = [ix for ix, ln in enumerate(sc) if ln.startswith("%{PSBDOGMIN-END}")]
|
|
1422
|
+
dglines = []
|
|
1423
|
+
dglines.append("dogmin {}\n".format(dogmin))
|
|
1424
|
+
if which is not None:
|
|
1425
|
+
dglines.append("which {}\n".format(" ".join(which)))
|
|
1426
|
+
dglines.append("setPwindow {} {}\n".format(p, p))
|
|
1427
|
+
dglines.append("setTwindow {} {}\n".format(T, T))
|
|
1428
|
+
if dgb and dge:
|
|
1429
|
+
sc = sc[: dgb[0] + 1] + dglines + sc[dge[0] :]
|
|
1430
|
+
changed = True
|
|
1431
|
+
if bulk is not None:
|
|
1432
|
+
bub = [ix for ix, ln in enumerate(sc) if ln.startswith("%{PSBBULK-BEGIN}")]
|
|
1433
|
+
bue = [ix for ix, ln in enumerate(sc) if ln.startswith("%{PSBBULK-END}")]
|
|
1434
|
+
bulines = []
|
|
1435
|
+
if len(bulk) == 2:
|
|
1436
|
+
bulines.append(
|
|
1437
|
+
"setbulk yes {} % x={:g}\n".format(" ".join(bulk[0]), xvals[0])
|
|
1438
|
+
)
|
|
1439
|
+
bulines.append(
|
|
1440
|
+
"setbulk yes {} {:d} % x={:g}\n".format(
|
|
1441
|
+
" ".join(bulk[1]), xsteps, xvals[1]
|
|
1442
|
+
)
|
|
1443
|
+
)
|
|
1444
|
+
else:
|
|
1445
|
+
bulines.append("setbulk yes {}\n".format(" ".join(bulk[0])))
|
|
1446
|
+
if bub and bue:
|
|
1447
|
+
sc = sc[: bub[0] + 1] + bulines + sc[bue[0] :]
|
|
1448
|
+
changed = True
|
|
1449
|
+
if changed:
|
|
1450
|
+
with self.scriptfile.open("w", encoding=self.TCenc) as f:
|
|
1451
|
+
for ln in sc:
|
|
1452
|
+
f.write(ln)
|
|
1453
|
+
if get_old_guesses:
|
|
1454
|
+
return old_guesses
|
|
1455
|
+
else:
|
|
1456
|
+
return None
|
|
1457
|
+
|
|
1458
|
+
def interpolate_bulk(self, x):
|
|
1459
|
+
if len(self.bulk) == 2:
|
|
1460
|
+
new_bulk = []
|
|
1461
|
+
try:
|
|
1462
|
+
_ = (e for e in x)
|
|
1463
|
+
except TypeError:
|
|
1464
|
+
b1 = np.array([float(v) for v in self.bulk[0]])
|
|
1465
|
+
b2 = np.array([float(v) for v in self.bulk[1]])
|
|
1466
|
+
db = b2 - b1
|
|
1467
|
+
bi = b1 + x * db
|
|
1468
|
+
new_bulk.append(["{:g}".format(v) for v in bi])
|
|
1469
|
+
else:
|
|
1470
|
+
for x_val in x:
|
|
1471
|
+
b1 = np.array([float(v) for v in self.bulk[0]])
|
|
1472
|
+
b2 = np.array([float(v) for v in self.bulk[1]])
|
|
1473
|
+
db = b2 - b1
|
|
1474
|
+
bi = b1 + x_val * db
|
|
1475
|
+
new_bulk.append(["{:g}".format(v) for v in bi])
|
|
1476
|
+
else:
|
|
1477
|
+
new_bulk = self.bulk[0]
|
|
1478
|
+
return new_bulk
|
|
1479
|
+
|
|
1480
|
+
def parse_kwargs(self, **kwargs):
|
|
1481
|
+
prange = kwargs.get("prange", self.prange)
|
|
1482
|
+
trange = kwargs.get("trange", self.trange)
|
|
1483
|
+
steps = kwargs.get("steps", 50)
|
|
1484
|
+
if np.diff(prange)[0] < 0.001:
|
|
1485
|
+
prec = kwargs.get(
|
|
1486
|
+
"prec", max(int(2 - np.floor(np.log10(np.diff(trange)[0]))), 0) + 1
|
|
1487
|
+
)
|
|
1488
|
+
elif np.diff(trange)[0] < 0.001:
|
|
1489
|
+
prec = kwargs.get(
|
|
1490
|
+
"prec", max(int(2 - np.floor(np.log10(np.diff(prange)[0]))), 0) + 1
|
|
1491
|
+
)
|
|
1492
|
+
else:
|
|
1493
|
+
prec = kwargs.get(
|
|
1494
|
+
"prec",
|
|
1495
|
+
max(
|
|
1496
|
+
int(
|
|
1497
|
+
2
|
|
1498
|
+
- np.floor(
|
|
1499
|
+
np.log10(min(np.diff(trange)[0], np.diff(prange)[0]))
|
|
1500
|
+
)
|
|
1501
|
+
),
|
|
1502
|
+
0,
|
|
1503
|
+
)
|
|
1504
|
+
+ 1,
|
|
1505
|
+
)
|
|
1506
|
+
return prange, trange, steps, prec
|
|
1507
|
+
|
|
1508
|
+
def calc_t(self, phases, out, **kwargs):
|
|
1509
|
+
"""Method to run THERMOCALC to find univariant line using Calc T at P strategy.
|
|
1510
|
+
|
|
1511
|
+
Args:
|
|
1512
|
+
phases (set): Set of present phases
|
|
1513
|
+
out (set): Set of single zero mode phase
|
|
1514
|
+
prange (tuple): Temperature range for calculation
|
|
1515
|
+
trange (tuple): Pressure range for calculation
|
|
1516
|
+
steps (int): Number of steps
|
|
1517
|
+
|
|
1518
|
+
Returns:
|
|
1519
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
1520
|
+
Input ans could be used to reproduce calculation.
|
|
1521
|
+
"""
|
|
1522
|
+
prange, trange, steps, prec = self.parse_kwargs(**kwargs)
|
|
1523
|
+
step = (prange[1] - prange[0]) / steps
|
|
1524
|
+
tmpl = "{}\n\n{}\ny\n{:.{prec}f} {:.{prec}f}\n{:.{prec}f} {:.{prec}f}\n{:g}\nn\n\nkill\n\n"
|
|
1525
|
+
ans = tmpl.format(
|
|
1526
|
+
" ".join(phases), " ".join(out), *prange, *trange, step, prec=prec
|
|
1527
|
+
)
|
|
1528
|
+
tcout = self.runtc(ans)
|
|
1529
|
+
return tcout, ans
|
|
1530
|
+
|
|
1531
|
+
def calc_p(self, phases, out, **kwargs):
|
|
1532
|
+
"""Method to run THERMOCALC to find univariant line using Calc P at T strategy.
|
|
1533
|
+
|
|
1534
|
+
Args:
|
|
1535
|
+
phases (set): Set of present phases
|
|
1536
|
+
out (set): Set of single zero mode phase
|
|
1537
|
+
prange (tuple): Temperature range for calculation
|
|
1538
|
+
trange (tuple): Pressure range for calculation
|
|
1539
|
+
steps (int): Number of steps
|
|
1540
|
+
|
|
1541
|
+
Returns:
|
|
1542
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
1543
|
+
Input ans could be used to reproduce calculation.
|
|
1544
|
+
"""
|
|
1545
|
+
prange, trange, steps, prec = self.parse_kwargs(**kwargs)
|
|
1546
|
+
step = (trange[1] - trange[0]) / steps
|
|
1547
|
+
tmpl = "{}\n\n{}\nn\n{:.{prec}f} {:.{prec}f}\n{:.{prec}f} {:.{prec}f}\n{:g}\nn\n\nkill\n\n"
|
|
1548
|
+
ans = tmpl.format(
|
|
1549
|
+
" ".join(phases), " ".join(out), *trange, *prange, step, prec=prec
|
|
1550
|
+
)
|
|
1551
|
+
tcout = self.runtc(ans)
|
|
1552
|
+
return tcout, ans
|
|
1553
|
+
|
|
1554
|
+
def calc_pt(self, phases, out, **kwargs):
|
|
1555
|
+
"""Method to run THERMOCALC to find invariant point.
|
|
1556
|
+
|
|
1557
|
+
Args:
|
|
1558
|
+
phases (set): Set of present phases
|
|
1559
|
+
out (set): Set of two zero mode phases
|
|
1560
|
+
prange (tuple): Temperature range for calculation
|
|
1561
|
+
trange (tuple): Pressure range for calculation
|
|
1562
|
+
steps (int): Number of steps
|
|
1563
|
+
|
|
1564
|
+
Returns:
|
|
1565
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
1566
|
+
Input ans could be used to reproduce calculation.
|
|
1567
|
+
"""
|
|
1568
|
+
prange, trange, steps, prec = self.parse_kwargs(**kwargs)
|
|
1569
|
+
tmpl = (
|
|
1570
|
+
"{}\n\n{}\n{:.{prec}f} {:.{prec}f} {:.{prec}f} {:.{prec}f}\nn\n\nkill\n\n"
|
|
1571
|
+
)
|
|
1572
|
+
ans = tmpl.format(" ".join(phases), " ".join(out), *trange, *prange, prec=prec)
|
|
1573
|
+
tcout = self.runtc(ans)
|
|
1574
|
+
return tcout, ans
|
|
1575
|
+
|
|
1576
|
+
def calc_tx(self, phases, out, **kwargs):
|
|
1577
|
+
"""Method to run THERMOCALC for T-X pseudosection calculations.
|
|
1578
|
+
|
|
1579
|
+
Args:
|
|
1580
|
+
phases (set): Set of present phases
|
|
1581
|
+
out (set): Set of zero mode phases
|
|
1582
|
+
prange (tuple): Temperature range for calculation
|
|
1583
|
+
trange (tuple): Pressure range for calculation
|
|
1584
|
+
steps (int): Number of steps
|
|
1585
|
+
|
|
1586
|
+
Returns:
|
|
1587
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
1588
|
+
Input ans could be used to reproduce calculation.
|
|
1589
|
+
"""
|
|
1590
|
+
prange, trange, steps, prec = self.parse_kwargs(**kwargs)
|
|
1591
|
+
if len(out) > 1:
|
|
1592
|
+
tmpl = "{}\n\n{}\n{:.{prec}f} {:.{prec}f} {:.{prec}f} {:.{prec}f}\nn\n\nkill\n\n"
|
|
1593
|
+
ans = tmpl.format(
|
|
1594
|
+
" ".join(phases), " ".join(out), *trange, *prange, prec=prec
|
|
1595
|
+
)
|
|
1596
|
+
else:
|
|
1597
|
+
tmpl = "{}\n\n{}\ny\n\n{:.{prec}f} {:.{prec}f}\nn\nkill\n\n"
|
|
1598
|
+
ans = tmpl.format(" ".join(phases), " ".join(out), *trange, prec=prec)
|
|
1599
|
+
tcout = self.runtc(ans)
|
|
1600
|
+
return tcout, ans
|
|
1601
|
+
|
|
1602
|
+
def calc_px(self, phases, out, **kwargs):
|
|
1603
|
+
"""Method to run THERMOCALC for P-X pseudosection calculations.
|
|
1604
|
+
|
|
1605
|
+
Args:
|
|
1606
|
+
phases (set): Set of present phases
|
|
1607
|
+
out (set): Set of zero mode phases
|
|
1608
|
+
prange (tuple): Temperature range for calculation
|
|
1609
|
+
trange (tuple): Pressure range for calculation
|
|
1610
|
+
steps (int): Number of steps
|
|
1611
|
+
|
|
1612
|
+
Returns:
|
|
1613
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
1614
|
+
Input ans could be used to reproduce calculation.
|
|
1615
|
+
"""
|
|
1616
|
+
prange, trange, steps, prec = self.parse_kwargs(**kwargs)
|
|
1617
|
+
if len(out) > 1:
|
|
1618
|
+
tmpl = "{}\n\n{}\n{:.{prec}f} {:.{prec}f} {:.{prec}f} {:.{prec}f}\nn\n\nkill\n\n"
|
|
1619
|
+
ans = tmpl.format(
|
|
1620
|
+
" ".join(phases), " ".join(out), *trange, *prange, prec=prec
|
|
1621
|
+
)
|
|
1622
|
+
else:
|
|
1623
|
+
tmpl = "{}\n\n{}\nn\n\n{:.{prec}f} {:.{prec}f}\nn\nkill\n\n"
|
|
1624
|
+
ans = tmpl.format(" ".join(phases), " ".join(out), *prange, prec=prec)
|
|
1625
|
+
tcout = self.runtc(ans)
|
|
1626
|
+
return tcout, ans
|
|
1627
|
+
|
|
1628
|
+
def calc_assemblage(self, phases, p, t):
|
|
1629
|
+
"""Method to run THERMOCALC to calculate compositions of stable assemblage.
|
|
1630
|
+
|
|
1631
|
+
Args:
|
|
1632
|
+
phases (set): Set of present phases
|
|
1633
|
+
p (float): Temperature for calculation
|
|
1634
|
+
t (float): Pressure for calculation
|
|
1635
|
+
|
|
1636
|
+
Returns:
|
|
1637
|
+
tuple: (tcout, ans) standard output and input for THERMOCALC run.
|
|
1638
|
+
Input ans could be used to reproduce calculation.
|
|
1639
|
+
"""
|
|
1640
|
+
tmpl = "{}\n\n\n{}\n{}\nkill\n\n"
|
|
1641
|
+
ans = tmpl.format(" ".join(phases), p, t)
|
|
1642
|
+
tcout = self.runtc(ans)
|
|
1643
|
+
return tcout, ans
|
|
1644
|
+
|
|
1645
|
+
def dogmin(self, phases, p, t, variance, doglevel=1, onebulk=None):
|
|
1646
|
+
"""Run THERMOCALC dogmin session.
|
|
1647
|
+
|
|
1648
|
+
Args:
|
|
1649
|
+
variance (int): Maximum variance to be considered
|
|
1650
|
+
|
|
1651
|
+
Returns:
|
|
1652
|
+
str: THERMOCALC standard output
|
|
1653
|
+
"""
|
|
1654
|
+
self.update_scriptfile(
|
|
1655
|
+
dogmin="yes {}".format(doglevel),
|
|
1656
|
+
which=phases,
|
|
1657
|
+
T="{:.3f}".format(t),
|
|
1658
|
+
p="{:.3f}".format(p),
|
|
1659
|
+
)
|
|
1660
|
+
tmpl = "{}\n\nn\n\n"
|
|
1661
|
+
ans = tmpl.format(variance)
|
|
1662
|
+
tcout = self.runtc(ans)
|
|
1663
|
+
self.update_scriptfile(dogmin="no")
|
|
1664
|
+
return tcout
|
|
1665
|
+
|
|
1666
|
+
def calc_variance(self, phases):
|
|
1667
|
+
"""Get variance of assemblage.
|
|
1668
|
+
|
|
1669
|
+
Args:
|
|
1670
|
+
phases (set): Set of present phases
|
|
1671
|
+
|
|
1672
|
+
Returns:
|
|
1673
|
+
int: variance
|
|
1674
|
+
"""
|
|
1675
|
+
variance = None
|
|
1676
|
+
tcout = self.runtc("{}\nkill\n\n".format(" ".join(phases)))
|
|
1677
|
+
for ln in tcout.splitlines():
|
|
1678
|
+
if "variance of required equilibrium" in ln:
|
|
1679
|
+
variance = int(ln[ln.index("(") + 1 : ln.index("?")])
|
|
1680
|
+
break
|
|
1681
|
+
return variance
|