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.
@@ -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
- def harker(Results = None, x_axis = None, y_axis = None, phase = None, line_style = None, line_color = None, data = None, d_color = None, d_marker = None, label = None):
9
- '''
10
- Construct harker plots.
11
-
12
- Parameters:
13
- -----------
14
- Results: dict
15
- Contains DataFrames with the results of the MELTS or MAGEMin calculations.
16
-
17
- x_axis: str
18
- Oxide to be placed on the x-axis, Default = "MgO".
19
-
20
- y_axis: list
21
- Oxides to be displayed on the y-axes.
22
-
23
- phase: str
24
- Phase compositions to be plotted. Plots the liquid component by default.
25
-
26
- line_style: str
27
- Line style to use for the MELTS/MAGEMin results. Default = '-'.
28
-
29
- line_color: str or tuple
30
- Color of the line used to display the MELTS/MAGEMin results. Black as default.
31
-
32
- data: DataFrame
33
- Optional. Include natural or experimental data to plot against the calculation results.
34
-
35
- d_color: str or tuple
36
- Color of the symbols used to display the natural/experimental data.
37
-
38
- d_marker: str
39
- Marker style for the natural/experimental data.
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 Exception("We need some results to work with!")
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 phase is None:
51
- phase = "liquid1"
53
+ if isinstance(y_axis, str):
54
+ y_axis = (y_axis,)
52
55
 
53
- if line_style is None:
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 d_color is None:
61
- d_color = 'red'
62
-
63
- if d_marker is None:
64
- d_marker = 'o'
65
-
66
- if type(y_axis) == list:
67
- y_axis = np.array(y_axis)
68
-
69
- if len(y_axis) % 3 == 0.0:
70
- y_axis = y_axis.reshape(len(y_axis)//3, 3)
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
- y_axis = np.append(y_axis, np.array(["None"] * (3 - (len(y_axis) % 3))))
73
- y_axis = y_axis.reshape(len(y_axis)//3, 3)
74
-
75
- f, a = plt.subplots(np.shape(y_axis)[0], np.shape(y_axis)[1], figsize = (3.5 * np.shape(y_axis)[1], 3.5 * np.shape(y_axis)[0]))
76
- if 'All' in list(Results.keys()):
77
- if type(phase) == str:
78
- for i in range(np.shape(y_axis)[0]):
79
- for j in range(np.shape(y_axis)[1]):
80
- if y_axis[i,j] != "None":
81
- if data is not None:
82
- a[i][j].plot(data.loc[:,data.columns.str.contains(x_axis)].values, data.loc[:,data.columns.str.contains(y_axis[i,j])].values, d_marker, markerfacecolor = d_color, markeredgecolor = 'k', markersize = 4)
83
-
84
- a[i][j].plot(Results['All'][x_axis + Names[phase]], Results['All'][y_axis[i,j] + Names[phase]], line_style, linewidth = 2, color = line_color)
85
- a[i][j].set_ylabel(y_axis[i][j] + " wt%")
86
- if i != np.shape(y_axis)[0] - 1:
87
- if i == np.shape(y_axis)[0] - 2:
88
- if y_axis[i+1,j] == "None":
89
- a[i][j].set_xlabel(x_axis + " wt%")
90
- else:
91
- a[i][j].set_xlabel(x_axis + " wt%")
92
-
93
- else:
94
- a[i][j].axis('off')
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 type(phase) == str:
98
- for i in range(np.shape(y_axis)[0]):
99
- for j in range(np.shape(y_axis)[1]):
100
- if y_axis[i,j] != "None":
101
- if data is not None:
102
- a[i][j].plot(data.loc[:,data.columns.str.contains(x_axis)], data.loc[:,data.columns.str.contains(y_axis[i,j])], d_marker, markerfacecolor = d_color, markeredgecolor = 'k', markersize = 4, label = "Data")
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
- PhaseList = {}
371
- for idx, p in enumerate(phases):
372
- PhaseList[p] = x[idx]
373
-
374
- Stop = np.zeros(len(Results['All']['P_bar']))
375
- for idx, p in enumerate(phases):
376
- if 'mass_' + p in Results['All'].keys():
377
- a.fill_betweenx(Results['All']['P_bar'], Stop,
378
- x2= Stop + Results['All']['mass_' + p], alpha = 0.75, color = PhaseList[p], lw = 0)
379
-
380
- Stop = Stop + Results['All']['mass_' + p]
381
-
382
- a.set_ylabel('Pressure (bars)')
383
- a.set_xlabel('Mass (g)')
384
-
385
- a.set_xlim([0,np.nanmax(Stop)])
386
- a.set_ylim([np.nanmax(Results['All']['P_bar']), np.nanmin(Results['All']['P_bar'])])
387
- else:
388
- if figsize is None:
389
- figsize = (10,2*len(Results))
390
-
391
- f, a = plt.subplots(len(Results), 1, figsize = figsize, sharex = True)
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
- x = c(np.arange(0,1,1/len(phases)))
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
- for i in range(len(Results)):
400
- Stop = np.zeros(len(Results[i]['All']['P_bar']))
401
- for idx, p in enumerate(phases):
402
- a[i].fill_between(Results[i]['All']['P_bar'], Stop,
403
- y2= Stop + Results[i]['All']['mass_' + p], alpha = 0.75, color = PhaseList[p], lw = 0,
404
- label = p)
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
- Stop = Stop + Results[i]['All']['mass_'+p]
631
+ if title is not None:
632
+ a.set_title(title)
407
633
 
408
- if i == len(Results)-1:
409
- a[i].set_xlabel('Pressure (bars)')
410
- a[i].legend(loc = "lower right")
411
- a[i].set_ylabel('Mass Fraction')
634
+ # --- Remove whitespace ---
635
+ a.margins(0)
636
+ f.tight_layout()
412
637
 
413
- a[i].set_ylim([0,np.nanmax(Stop)])
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
- if title is not None:
418
- if type(title) != list:
419
- a[i].set_title(title)
420
- else:
421
- a[i].set_title(title[i])
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):
@@ -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
 
@@ -5,4 +5,4 @@
5
5
  # 1) we don't load dependencies by storing it in __init__.py
6
6
  # 2) we can import it in setup.py for the same reason
7
7
  # 3) we can import it into your module
8
- __version__ = '0.2.40'
8
+ __version__ = '0.2.42'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PetThermoTools
3
- Version: 0.2.40
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<2
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,,