PetThermoTools 0.2.40__py3-none-any.whl → 0.2.42__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.
- PetThermoTools/Barom.py +24 -1
- PetThermoTools/GenFuncs.py +74 -24
- PetThermoTools/Liq.py +46 -4
- PetThermoTools/MELTS.py +3 -1
- PetThermoTools/Melting.py +105 -52
- PetThermoTools/Path.py +60 -18
- PetThermoTools/PhaseDiagrams.py +32 -1
- PetThermoTools/Plotting.py +395 -164
- PetThermoTools/Saturation.py +5 -0
- PetThermoTools/_version.py +1 -1
- {PetThermoTools-0.2.40.dist-info → PetThermoTools-0.2.42.dist-info}/METADATA +2 -2
- PetThermoTools-0.2.42.dist-info/RECORD +20 -0
- PetThermoTools-0.2.40.dist-info/RECORD +0 -20
- {PetThermoTools-0.2.40.dist-info → PetThermoTools-0.2.42.dist-info}/LICENSE.txt +0 -0
- {PetThermoTools-0.2.40.dist-info → PetThermoTools-0.2.42.dist-info}/WHEEL +0 -0
- {PetThermoTools-0.2.40.dist-info → PetThermoTools-0.2.42.dist-info}/top_level.txt +0 -0
PetThermoTools/Plotting.py
CHANGED
@@ -3,128 +3,237 @@ import pandas as pd
|
|
3
3
|
from shapely.geometry import MultiPoint, Point, Polygon
|
4
4
|
import matplotlib.pyplot as plt
|
5
5
|
from matplotlib import cm
|
6
|
+
import matplotlib.colors as mc
|
6
7
|
from PetThermoTools.GenFuncs import *
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
Line
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
8
|
+
from itertools import zip_longest
|
9
|
+
|
10
|
+
def harker(Results=None, x_axis="MgO", y_axis=("SiO2", "TiO2", "Al2O3", "Cr2O3", "FeOt", "CaO", "Na2O", "K2O"),
|
11
|
+
phase="liquid1", line_color=None, data=None, d_color=None, d_marker=None,
|
12
|
+
legend=True, legend_loc=None,
|
13
|
+
xlim=None, ylim=None):
|
14
|
+
"""
|
15
|
+
Generates a Harker diagram (oxide vs. MgO or another oxide) from model results.
|
16
|
+
|
17
|
+
Parameters
|
18
|
+
----------
|
19
|
+
Results : dict or None, optional
|
20
|
+
MELTS or MAGEMin results dictionary. If multiple results, they are overlaid.
|
21
|
+
x_axis : str, default="MgO"
|
22
|
+
Oxide to use for the x-axis.
|
23
|
+
y_axis : tuple of str, default=("SiO2","TiO2","Al2O3","Cr2O3","FeOt","CaO","Na2O","K2O")
|
24
|
+
Oxides to plot on y-axes. Plotted in groups of three per row.
|
25
|
+
phase : str, default="liquid1"
|
26
|
+
Phase to plot (must be in PetThermoTools.GenFuncs.Names).
|
27
|
+
line_color : str or None, optional
|
28
|
+
Line color. If None, uses matplotlib's default cycle.
|
29
|
+
data : pd.DataFrame, dict of DataFrames, or str, optional
|
30
|
+
External data to plot for comparison. If str, treated as CSV path.
|
31
|
+
If dict, plots each DataFrame with different marker/color.
|
32
|
+
d_colors : list of str or None, optional
|
33
|
+
Colors for external datasets. Cycles if fewer than number of datasets.
|
34
|
+
d_markers : list of str or None, optional
|
35
|
+
Markers for external datasets. Cycles if fewer than number of datasets.
|
36
|
+
legend : bool, default=True
|
37
|
+
Whether to display a legend.
|
38
|
+
legend_loc : tuple(int, int) or None, optional
|
39
|
+
Location of legend in subplot grid, as (row, col). If None, placed on last axis.
|
40
|
+
xlim, ylim : tuple or None, optional
|
41
|
+
Limits for x and y axes.
|
42
|
+
|
43
|
+
Returns
|
44
|
+
-------
|
45
|
+
f : matplotlib.figure.Figure
|
46
|
+
Figure object.
|
47
|
+
a : np.ndarray of Axes
|
48
|
+
Array of Axes objects.
|
49
|
+
"""
|
41
50
|
if Results is None:
|
42
|
-
raise
|
43
|
-
|
44
|
-
if x_axis is None:
|
45
|
-
x_axis = "MgO"
|
46
|
-
|
47
|
-
if y_axis is None:
|
48
|
-
y_axis = ["SiO2", "TiO2", "Al2O3", "FeOt", "CaO", "Na2O"]
|
51
|
+
raise ValueError("Results cannot be None. Provide MELTS or MAGEMin results dictionary.")
|
49
52
|
|
50
|
-
if
|
51
|
-
|
53
|
+
if isinstance(y_axis, str):
|
54
|
+
y_axis = (y_axis,)
|
52
55
|
|
53
|
-
|
54
|
-
line_style = '-'
|
55
|
-
|
56
|
-
if line_color is None:
|
57
|
-
line_color = 'k'
|
56
|
+
y_axis = list(y_axis)
|
58
57
|
|
58
|
+
# Data loading
|
59
59
|
if data is not None:
|
60
|
-
if
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
60
|
+
if isinstance(data, str):
|
61
|
+
if data.contains('.csv'):
|
62
|
+
data = pd.read_csv(data)
|
63
|
+
else:
|
64
|
+
data = pd.read_excel(data)
|
65
|
+
elif isinstance(data, dict):
|
66
|
+
# validate
|
67
|
+
for k, v in data.items():
|
68
|
+
if not isinstance(v, pd.DataFrame):
|
69
|
+
raise ValueError(f"data['{k}'] must be a DataFrame, got {type(v)}")
|
70
|
+
|
71
|
+
# -------------------- filter y_axis to only include variables present --------------------
|
72
|
+
valid_y = []
|
73
|
+
for y in y_axis:
|
74
|
+
found = False
|
75
|
+
|
76
|
+
# Check Results
|
77
|
+
for r in (Results.keys() if isinstance(Results, dict) else [Results]):
|
78
|
+
Res = Results[r] if isinstance(Results, dict) else Results
|
79
|
+
if "All" in Res:
|
80
|
+
cols = Res["All"].columns
|
81
|
+
if (y + Names[phase]) in cols or y in cols:
|
82
|
+
if np.nanmax(Res["All"][y + Names[phase]]) > 0.0:
|
83
|
+
found = True
|
84
|
+
break
|
85
|
+
|
86
|
+
# Check data (dict or DataFrame)
|
87
|
+
if not found and data is not None:
|
88
|
+
if isinstance(data, dict):
|
89
|
+
for df in data.values():
|
90
|
+
cols = df.columns
|
91
|
+
if (y + Names[phase]) in cols or y in cols:
|
92
|
+
found = True
|
93
|
+
break
|
94
|
+
else:
|
95
|
+
cols = data.columns
|
96
|
+
if (y + Names[phase]) in cols or y in cols:
|
97
|
+
found = True
|
98
|
+
|
99
|
+
if found:
|
100
|
+
valid_y.append(y)
|
101
|
+
|
102
|
+
# Replace y_axis with only valid variables
|
103
|
+
y_axis = valid_y
|
104
|
+
|
105
|
+
# -------------------- subplot grid handling --------------------
|
106
|
+
n_vars = len(y_axis)
|
107
|
+
|
108
|
+
if n_vars == 4: # special case: 2x2 grid
|
109
|
+
ncols, nrows = 2, 2
|
110
|
+
rows = list(zip_longest(*[iter(y_axis)] * ncols, fillvalue=None))
|
71
111
|
else:
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
112
|
+
ncols = 3
|
113
|
+
nrows = int(np.ceil(n_vars / ncols))
|
114
|
+
rows = list(zip_longest(*[iter(y_axis)] * ncols, fillvalue=None))
|
115
|
+
|
116
|
+
# scale figure size dynamically
|
117
|
+
fig_width = 3.2 * ncols
|
118
|
+
fig_height = 3.0 * nrows
|
119
|
+
f, a = plt.subplots(nrows, ncols, figsize=(fig_width, fig_height))
|
120
|
+
a = np.atleast_2d(a)
|
121
|
+
|
122
|
+
# Color handling for models
|
123
|
+
color_cycle = plt.rcParams["axes.prop_cycle"].by_key()["color"]
|
124
|
+
if line_color is None:
|
125
|
+
def pick_color(i): return color_cycle[i % len(color_cycle)]
|
126
|
+
else:
|
127
|
+
def pick_color(i): return line_color
|
128
|
+
|
129
|
+
# Color/marker cycles for user data
|
130
|
+
d_color_cycle = d_color if d_color else color_cycle
|
131
|
+
d_marker_cycle = d_marker if d_marker else ["o", "s", "^", "D", "v", "P", "*"]
|
132
|
+
|
133
|
+
# -------------------- helper functions --------------------
|
134
|
+
def plot_panel(ax, x_var, y_var, idx=None, Res = None):
|
135
|
+
"""Plot one panel (model + data) on given Axes."""
|
136
|
+
suffix = Names[phase] if phase in Names else ""
|
137
|
+
xcol, ycol = x_var + suffix, y_var + suffix
|
138
|
+
|
139
|
+
# plot model data
|
140
|
+
if Res is not None:
|
141
|
+
if xcol in Results[Res]["All"] and ycol in Results[Res]["All"]:
|
142
|
+
x = Results[Res]["All"][xcol].values
|
143
|
+
y = Results[Res]["All"][ycol].values
|
144
|
+
ax.plot(x, y, color=pick_color(idx), label=Res)
|
145
|
+
else:
|
146
|
+
if xcol in Results["All"] and ycol in Results["All"]:
|
147
|
+
x = Results["All"][xcol].values
|
148
|
+
y = Results["All"][ycol].values
|
149
|
+
ax.plot(x, y, color='k')
|
150
|
+
|
151
|
+
def plot_data(ax, x_var, y_var):
|
152
|
+
# plot external data
|
153
|
+
suffix = Names[phase] if phase in Names else ""
|
154
|
+
xcol, ycol = x_var + suffix, y_var + suffix
|
155
|
+
|
156
|
+
if data is not None:
|
157
|
+
if isinstance(data, pd.DataFrame):
|
158
|
+
datasets = {"data": data}
|
159
|
+
else:
|
160
|
+
datasets = data
|
161
|
+
|
162
|
+
for k, df in datasets.items():
|
163
|
+
c = d_color_cycle[list(datasets.keys()).index(k) % len(d_color_cycle)]
|
164
|
+
m = d_marker_cycle[list(datasets.keys()).index(k) % len(d_marker_cycle)]
|
165
|
+
|
166
|
+
# try with suffix first
|
167
|
+
if xcol in df.columns:
|
168
|
+
dx = df[xcol].values
|
169
|
+
elif x_var in df.columns:
|
170
|
+
dx = df[x_var].values
|
171
|
+
else:
|
172
|
+
dx = None
|
173
|
+
print(f"x axis variable {x_var} not found in data")
|
95
174
|
|
175
|
+
if ycol in df.columns:
|
176
|
+
dy = df[ycol].values
|
177
|
+
elif y_var in df.columns:
|
178
|
+
dy = df[y_var].values
|
179
|
+
else:
|
180
|
+
dy = None
|
181
|
+
print(f"y axis variable {y_var} not found in data")
|
182
|
+
|
183
|
+
if dx is not None and dy is not None:
|
184
|
+
ax.plot(dx, dy, m,
|
185
|
+
markerfacecolor=c,
|
186
|
+
markeredgecolor="k",
|
187
|
+
markersize=4,
|
188
|
+
linestyle="None",
|
189
|
+
label=k)
|
190
|
+
|
191
|
+
ax.set(xlabel=x_var, ylabel=y_var)
|
192
|
+
|
193
|
+
if xlim:
|
194
|
+
ax.set_xlim(xlim)
|
195
|
+
if ylim:
|
196
|
+
ax.set_ylim(ylim)
|
197
|
+
|
198
|
+
# -------------------- main plotting --------------------
|
199
|
+
for i, row in enumerate(rows):
|
200
|
+
for j, y in enumerate(row):
|
201
|
+
if y is None:
|
202
|
+
a[i, j].axis("off")
|
203
|
+
continue
|
204
|
+
if 'All' in Results.keys():
|
205
|
+
if data is not None:
|
206
|
+
plot_data(a[i,j], x_axis, y)
|
207
|
+
plot_panel(a[i,j], x_axis, y)
|
208
|
+
else:
|
209
|
+
if data is not None:
|
210
|
+
plot_data(a[i,j], x_axis, y)
|
211
|
+
for idx, Res in enumerate(Results):
|
212
|
+
plot_panel(a[i, j], x_axis, y, idx=idx, Res = Res)
|
213
|
+
|
214
|
+
# -------------------- legend --------------------
|
215
|
+
empty_axes = []
|
216
|
+
for i in range(a.shape[0]):
|
217
|
+
for j in range(a.shape[1]):
|
218
|
+
if i * a.shape[1] + j >= len(y_axis): # beyond valid y variables
|
219
|
+
empty_axes.append(a[i][j])
|
220
|
+
|
221
|
+
handles, labels = a[0][0].get_legend_handles_labels()
|
222
|
+
if empty_axes and handles:
|
223
|
+
empty_axes[0].legend(handles, labels, loc = "center")
|
224
|
+
empty_axes[0].axis("off")
|
96
225
|
else:
|
97
|
-
if
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
a[i][j].set_ylabel(y_axis[i][j] + " wt%")
|
105
|
-
if i != np.shape(y_axis)[0] - 1:
|
106
|
-
if i == np.shape(y_axis)[0] - 2:
|
107
|
-
if y_axis[i+1,j] == "None":
|
108
|
-
a[i][j].set_xlabel(x_axis + " wt%")
|
109
|
-
else:
|
110
|
-
a[i][j].set_xlabel(x_axis + " wt%")
|
111
|
-
|
112
|
-
else:
|
113
|
-
a[i][j].axis('off')
|
114
|
-
|
115
|
-
for r in Results:
|
116
|
-
Res = Results[r].copy()
|
117
|
-
if type(phase) == str:
|
118
|
-
for i in range(np.shape(y_axis)[0]):
|
119
|
-
for j in range(np.shape(y_axis)[1]):
|
120
|
-
if y_axis[i,j] != "None":
|
121
|
-
a[i][j].plot(Res['All'][x_axis + Names[phase]], Res['All'][y_axis[i,j] + Names[phase]], line_style, linewidth = 2, label = r)
|
122
|
-
|
123
|
-
if label is not None:
|
124
|
-
a[0][0].legend()
|
226
|
+
if legend:
|
227
|
+
if legend_loc is None:
|
228
|
+
loc_i, loc_j = len(rows) - 1, 0
|
229
|
+
else:
|
230
|
+
loc_i, loc_j = legend_loc
|
231
|
+
a[loc_i, loc_j].legend()
|
125
232
|
|
126
233
|
f.tight_layout()
|
127
234
|
|
235
|
+
return f, a
|
236
|
+
|
128
237
|
def plot_surfaces(Results = None, P_bar = None, phases = None, H2O_Liq = None):
|
129
238
|
if H2O_Liq is None:
|
130
239
|
f, a = plt.subplots(1,1, figsize = (5,4))
|
@@ -358,69 +467,191 @@ def residualT_plot(Results = None, P_bar = None, phases = None, H2O_Liq = None,
|
|
358
467
|
a[i][j].plot_surface(X_new, Y_new, z_plot, cmap = 'viridis')
|
359
468
|
a[i][j].set_zlim([0,50])
|
360
469
|
|
361
|
-
def phase_plot(Results = None, y_axis = None, x_axis = None,
|
362
|
-
phases = ['Liq', 'Ol', 'Opx', 'Cpx', 'Sp', 'Grt'], cmap = "Reds",
|
363
|
-
title = None, figsize = None):
|
364
|
-
|
365
|
-
if type(Results) != list:
|
366
|
-
f, a = plt.subplots(1,1, figsize = (5, 8))
|
367
|
-
c = cm.get_cmap(cmap, len(phases))
|
368
|
-
x = c(np.arange(0,1,1/len(phases)))
|
369
470
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
471
|
+
def phase_plot(Results, x_axis = None, y_axis = None, cmap = "Reds"):
|
472
|
+
"""
|
473
|
+
Create stacked phase mass-fraction plots from thermodynamic model results.
|
474
|
+
|
475
|
+
This function generates diagrams of phase proportions from the results of
|
476
|
+
crystallization or melting simulations. Mass fractions of crystalline and
|
477
|
+
liquid phases are stacked either along the x-axis or y-axis, depending on
|
478
|
+
user specification, to visualize how phase proportions evolve with pressure,
|
479
|
+
temperature, or another variable.
|
480
|
+
|
481
|
+
Parameters
|
482
|
+
----------
|
483
|
+
Results : dict
|
484
|
+
Dictionary containing model outputs. It should have one of the following structures:
|
485
|
+
- **Single-run results**:
|
486
|
+
```
|
487
|
+
Results = {
|
488
|
+
"Mass": pandas.DataFrame, # columns = phase names or phase_cumsum
|
489
|
+
"All": pandas.DataFrame # contains the axis variable (e.g., T_C, P_bar)
|
490
|
+
}
|
491
|
+
```
|
492
|
+
- **Multi-run results**:
|
493
|
+
```
|
494
|
+
Results = {
|
495
|
+
run_label: {
|
496
|
+
"Mass": pandas.DataFrame,
|
497
|
+
"All": pandas.DataFrame
|
498
|
+
},
|
499
|
+
...
|
500
|
+
}
|
501
|
+
```
|
502
|
+
|
503
|
+
The `"Mass"` DataFrame must include mass fractions for each phase
|
504
|
+
(either raw values or cumulative values with `"_cumsum"` suffix).
|
505
|
+
The `"All"` DataFrame must contain the column specified by `x_axis` or `y_axis`.
|
506
|
+
|
507
|
+
x_axis : str, optional
|
508
|
+
Column name in `Results["All"]` (or equivalent) to plot on the x-axis.
|
509
|
+
If provided, `y_axis` must be `None`. Typically something like `"T_C"`.
|
510
|
+
|
511
|
+
y_axis : str, optional
|
512
|
+
Column name in `Results["All"]` (or equivalent) to plot on the y-axis.
|
513
|
+
If provided, `x_axis` must be `None`. Typically `"P_bar"`.
|
514
|
+
|
515
|
+
cmap : str, default = "Reds"
|
516
|
+
Matplotlib colormap used to assign colors to phases.
|
517
|
+
|
518
|
+
Returns
|
519
|
+
-------
|
520
|
+
fig : matplotlib.figure.Figure or list of Figures
|
521
|
+
- If `Results` contains a single run: a single `Figure`.
|
522
|
+
- If `Results` contains multiple runs: a list of `Figure` objects, one per run.
|
523
|
+
|
524
|
+
axes : matplotlib.axes.Axes or list of Axes
|
525
|
+
- If `Results` contains a single run: a single `Axes` object.
|
526
|
+
- If `Results` contains multiple runs: a list of `Axes` objects, one per run.
|
527
|
+
|
528
|
+
Notes
|
529
|
+
-----
|
530
|
+
- If both `x_axis` and `y_axis` are provided, the function raises a `ValueError`.
|
531
|
+
- Phases are automatically ordered:
|
532
|
+
1. By the index where they first appear (highest pressure or temperature).
|
533
|
+
2. By the order specified in the petthermotools dictionaries `Names` and `Names_MM`,
|
534
|
+
if available.
|
535
|
+
- The liquid phase (`"liq1"` or `"liquid1"`) is always plotted last.
|
536
|
+
- A legend with readable phase labels is added outside
|
537
|
+
the plot area.
|
538
|
+
|
539
|
+
Examples
|
540
|
+
--------
|
541
|
+
>>> fig, ax = phase_plot(Results, y_axis="P_bar")
|
542
|
+
# Plots stacked phase proportions vs. pressure
|
543
|
+
|
544
|
+
>>> fig, axes = phase_plot(MultiRunResults, x_axis="T_C")
|
545
|
+
# Creates one stacked phase plot per run, vs. temperature
|
546
|
+
"""
|
547
|
+
if x_axis is not None and y_axis is not None:
|
548
|
+
raise ValueError("Please provide either a x-axis or y-axis parameter to plot the mass fractions against")
|
549
|
+
|
550
|
+
def makeplot(Mass, Res, title = None):
|
551
|
+
# --- Identify whether _cumsum columns exist ---
|
552
|
+
use_cumsum = any(col.endswith("_cumsum") for col in Mass.columns)
|
553
|
+
suffix = "_cumsum" if use_cumsum else ""
|
554
|
+
|
555
|
+
# --- Identify phases ---
|
556
|
+
exclude_cols = {'T_C', None}
|
557
|
+
phases = [
|
558
|
+
col.replace(suffix, "")
|
559
|
+
for col in Mass.columns
|
560
|
+
if col.endswith(suffix) or (not use_cumsum and not col.endswith("_cumsum") and col not in exclude_cols)
|
561
|
+
]
|
562
|
+
|
563
|
+
# --- Handle liquid phase ---
|
564
|
+
liquid_name = None
|
565
|
+
for liq_name in ["liq1", "liquid1"]:
|
566
|
+
if liq_name in Mass.columns:
|
567
|
+
liquid_name = liq_name
|
568
|
+
if liq_name in phases:
|
569
|
+
phases.remove(liq_name)
|
570
|
+
break # use the first match
|
571
|
+
|
572
|
+
# --- Create dictionary priority (order in Names/Names_MM) ---
|
573
|
+
phase_priority = {}
|
574
|
+
for order_dict in [Names, Names_MM]:
|
575
|
+
for i, key in enumerate(order_dict.keys()):
|
576
|
+
phase_priority[key] = i # smaller i = higher priority in tie
|
577
|
+
|
578
|
+
# --- Sort crystalline phases ---
|
579
|
+
phase_first_index = {}
|
580
|
+
for p in phases:
|
581
|
+
col = p + suffix if (use_cumsum and p + suffix in Mass.columns) else p
|
582
|
+
vals = Mass[col].values
|
583
|
+
nz = np.flatnonzero(vals > 0)
|
584
|
+
phase_first_index[p] = nz[0] if len(nz) > 0 else np.inf
|
585
|
+
|
586
|
+
def sort_key(p):
|
587
|
+
return (phase_first_index[p], phase_priority.get(p, 1e6))
|
588
|
+
|
589
|
+
phases = sorted(phases, key=sort_key)
|
590
|
+
|
591
|
+
# --- Append liquid last ---
|
592
|
+
if liquid_name is not None:
|
593
|
+
phases.append(liquid_name)
|
594
|
+
|
595
|
+
# --- Assign colors ---
|
392
596
|
c = cm.get_cmap(cmap, len(phases))
|
393
|
-
|
394
|
-
|
395
|
-
PhaseList = {}
|
396
|
-
for idx, p in enumerate(phases):
|
397
|
-
PhaseList[p] = x[idx]
|
597
|
+
PhaseColors = {p: c(i) for i, p in enumerate(phases)}
|
398
598
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
599
|
+
# --- Setup figure ---
|
600
|
+
if y_axis == "P_bar":
|
601
|
+
f, a = plt.subplots(figsize=(4, 6))
|
602
|
+
else:
|
603
|
+
f, a = plt.subplots(figsize=(4, 3.5))
|
604
|
+
|
605
|
+
# --- Determine orientation ---
|
606
|
+
coord = Res[y_axis] if y_axis else Res[x_axis]
|
607
|
+
horizontal = y_axis is not None
|
608
|
+
|
609
|
+
Stop = np.zeros(len(Mass))
|
610
|
+
for p in phases:
|
611
|
+
col = p + suffix if use_cumsum else p
|
612
|
+
vals = Mass[col].values
|
613
|
+
|
614
|
+
# --- Legend label mapping ---
|
615
|
+
label = Names.get(p, Names_MM.get(p, p))[1:]
|
616
|
+
|
617
|
+
if horizontal:
|
618
|
+
a.fill_betweenx(coord, Stop, Stop + vals, color=PhaseColors[p], alpha=0.75, lw=0, label=label)
|
619
|
+
else:
|
620
|
+
a.fill_between(coord, Stop, Stop + vals, color=PhaseColors[p], alpha=0.75, lw=0, label=label)
|
621
|
+
Stop += vals
|
622
|
+
|
623
|
+
# --- Labels ---
|
624
|
+
if horizontal:
|
625
|
+
a.set_ylabel(y_axis)
|
626
|
+
a.set_xlabel("Mass fraction")
|
627
|
+
else:
|
628
|
+
a.set_xlabel(x_axis)
|
629
|
+
a.set_ylabel("Mass fraction")
|
405
630
|
|
406
|
-
|
631
|
+
if title is not None:
|
632
|
+
a.set_title(title)
|
407
633
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
a[i].set_ylabel('Mass Fraction')
|
634
|
+
# --- Remove whitespace ---
|
635
|
+
a.margins(0)
|
636
|
+
f.tight_layout()
|
412
637
|
|
413
|
-
|
414
|
-
a[i].set_xlim([np.nanmin(Results[i]['All']['P_bar']), np.nanmax(Results[i]['All']['P_bar'])])
|
638
|
+
a.legend(loc="center left", bbox_to_anchor=(1.02, 0.5))
|
415
639
|
|
640
|
+
return f, a
|
641
|
+
fig = []
|
642
|
+
axes = []
|
416
643
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
644
|
+
if 'All' in Results.keys():
|
645
|
+
fig, axes = makeplot(Results['Mass'], Results['All'])
|
646
|
+
else:
|
647
|
+
for val in Results.keys():
|
648
|
+
f, a = makeplot(Results[val]['Mass'],
|
649
|
+
Results[val]['All'],
|
650
|
+
title = val)
|
651
|
+
fig.append(f)
|
652
|
+
axes.append(a)
|
653
|
+
return fig, axes
|
422
654
|
|
423
|
-
return f, a
|
424
655
|
|
425
656
|
def plot_phaseDiagram(Model = "Holland", Combined = None, P_units = "bar", T_units = "C",
|
426
657
|
lines = None, T_C = None, P_bar = None, label = True, colormap = None):
|
PetThermoTools/Saturation.py
CHANGED
@@ -412,6 +412,11 @@ def saturation_pressure(Model = "MELTSv1.2.0", cores = multiprocessing.cpu_count
|
|
412
412
|
if fO2_buffer != "FMQ":
|
413
413
|
raise Warning("fO2 buffer specified is not an allowed input. This argument can only be 'FMQ' or 'NNO' \n if you want to offset from these buffers use the 'fO2_offset' argument.")
|
414
414
|
|
415
|
+
if "MELTS" not in Model:
|
416
|
+
if fO2_buffer == "FMQ":
|
417
|
+
fO2_buffer = "qfm"
|
418
|
+
if fO2_buffer == "NNO":
|
419
|
+
fO2_buffer = "nno"
|
415
420
|
# ensure the bulk composition has the correct headers etc.
|
416
421
|
comp = comp_fix(Model = Model, comp = comp, Fe3Fet_Liq = Fe3Fet_init, H2O_Liq = H2O_init, CO2_Liq = CO2_init)
|
417
422
|
|
PetThermoTools/_version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: PetThermoTools
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.42
|
4
4
|
Summary: PetThermoTools
|
5
5
|
Home-page: https://github.com/gleesonm1/PetThermoTools
|
6
6
|
Author: Matthew Gleeson
|
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Requires-Python: >=3.8
|
13
13
|
Description-Content-Type: text/markdown
|
14
14
|
Requires-Dist: pandas
|
15
|
-
Requires-Dist: numpy
|
15
|
+
Requires-Dist: numpy
|
16
16
|
Requires-Dist: matplotlib
|
17
17
|
Requires-Dist: scikit-learn
|
18
18
|
Requires-Dist: scipy
|
@@ -0,0 +1,20 @@
|
|
1
|
+
PetThermoTools/Barom.py,sha256=HKd1TTHHl0FnM_SGb0qC7KqH7rybyVixVMH7Wph-nk8,46613
|
2
|
+
PetThermoTools/Compositions.py,sha256=65NzfduzWdfHJ8VmHBN1Cv7fMz7kF3QbDVLei-e4v00,1483
|
3
|
+
PetThermoTools/GenFuncs.py,sha256=oMV3FYIpfHNpGZloN5a6Vr8Sv_5IRsKt4hHIUNbDu24,19996
|
4
|
+
PetThermoTools/Holland.py,sha256=udBFeVUyTBpSfLIhx7Hy6o0I8ApNCDvwU_gZa0diY5w,7251
|
5
|
+
PetThermoTools/Installation.py,sha256=UfVOW1NZFdzMWPyID5u7t0KwvpJA0AqYohzidXIAwYs,6098
|
6
|
+
PetThermoTools/Liq.py,sha256=4tOnVROXr7V6cfvZceKidFT9J20TZE3Om2oem92QC4s,36842
|
7
|
+
PetThermoTools/MELTS.py,sha256=2WB4Y11i7yfTM5dRjEr-KrJY2_7f1kLaW4V0di6Qnew,76511
|
8
|
+
PetThermoTools/Melting.py,sha256=D4mXTrKkoUPK8dAuHPQRAnK79owI23W7JBmUMvm1DVU,15332
|
9
|
+
PetThermoTools/Path.py,sha256=jUAVvKSDuVSYRiIZbL1J21E8X4Loc2xCkRTYyl4PzG4,38179
|
10
|
+
PetThermoTools/Path_wrappers.py,sha256=gUxs_4Qbk4MLlLl4iySxfbfKU34588bIJAYyhHmhFdc,30177
|
11
|
+
PetThermoTools/PhaseDiagrams.py,sha256=sjjX84LK34m_OjsKCV78zpzRDAXG7oaLu_Z5BxwB_3I,30298
|
12
|
+
PetThermoTools/Plotting.py,sha256=KxBNT2nqqrVn9dsAIjGk1hMUVRq7r-WrG_b9quNcRMo,37295
|
13
|
+
PetThermoTools/Saturation.py,sha256=3liK4WDVtl5DWpMpE_tQ_fONBCZTctkiNeCXCuNmXoM,29961
|
14
|
+
PetThermoTools/__init__.py,sha256=PbiwQj_mNNEwuIZOLETmtMMshiXa50wjCA6mfvpOpOs,2393
|
15
|
+
PetThermoTools/_version.py,sha256=gEQfVmDQZosUq8hqgOF7-NXfujY6yVc6ya0YiW2W8ZM,296
|
16
|
+
PetThermoTools-0.2.42.dist-info/LICENSE.txt,sha256=-mkx4iEw8Pk1RZUvncBhGLW87Uur5JB7FBQtOmX-VP0,1752
|
17
|
+
PetThermoTools-0.2.42.dist-info/METADATA,sha256=yUKuTP6xbaOHpYQ3g4D5T5qA5XyqfsB0Gs1aBTJF0YA,794
|
18
|
+
PetThermoTools-0.2.42.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
19
|
+
PetThermoTools-0.2.42.dist-info/top_level.txt,sha256=IqK8iYBR3YJozzMOTRZ8x8mU2k6x8ycoMBxZTm-I06U,15
|
20
|
+
PetThermoTools-0.2.42.dist-info/RECORD,,
|