PetThermoTools 0.2.39__py3-none-any.whl → 0.2.40__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 CHANGED
@@ -25,6 +25,42 @@ import time
25
25
 
26
26
  def path_4_saturation_multi(q, index, *, Model = None, P_bar = None, comp = None, T_maxdrop_C = None, dt_C = None, T_initial_C = None, fO2_buffer = None,
27
27
  fO2_offset = 0.0, H2O_Sat = None, phases = None):
28
+ """
29
+ Run crystallization simulations at multiple pressures to determine pressure of mineral co-saturations using MELTS or MAGEMinCalc in parallel.
30
+
31
+ Parameters
32
+ ----------
33
+ q : multiprocessing.Queue
34
+ Queue object for inter-process communication (used to return results).
35
+ index : array-like
36
+ Index values to iterate over P_bar.
37
+ Model : str
38
+ Thermodynamic model to use.
39
+ P_bar : array-like
40
+ Pressures in bar at which to simulate crystallization paths.
41
+ comp : dict
42
+ Bulk composition of the starting material.
43
+ T_maxdrop_C : float
44
+ Maximum temperature drop below the liquidus and/or starting temperature in Celsius.
45
+ dt_C : float
46
+ Step size for temperature change (in °C).
47
+ T_initial_C : float
48
+ Starting temperature (°C) for simulations.
49
+ fO2_buffer : str, optional
50
+ Oxygen fugacity buffer ("FMQ" or "NNO").
51
+ fO2_offset : float, default 0.0
52
+ Offset in log units from the specified fO2 buffer.
53
+ H2O_Sat : bool
54
+ Whether the system is fluid-saturated.
55
+ phases : list of str
56
+ List of phases to monitor for saturation.
57
+
58
+ Returns
59
+ -------
60
+ None
61
+ Results are put in the multiprocessing queue `q` as a list: [idx, results].
62
+ """
63
+
28
64
  results = {}
29
65
  idx = []
30
66
 
@@ -83,7 +119,7 @@ def path_4_saturation_multi(q, index, *, Model = None, P_bar = None, comp = None
83
119
  idx.append(i)
84
120
 
85
121
 
86
- results[f"index = {i}"] = Results
122
+ results[f"Run {i}"] = Results
87
123
 
88
124
  if tr is False:
89
125
  break
@@ -98,8 +134,59 @@ def mineral_cosaturation(Model="MELTSv1.0.2", cores=int(np.floor(multiprocessing
98
134
  P_bar=np.linspace(250, 5000, 32), Fe3Fet_init=None, H2O_init=None,
99
135
  CO2_init=None, H2O_Sat=False, T_initial_C=None, dt_C=2,
100
136
  T_maxdrop_C=50, T_cut_C=20, find_range=True,
101
- find_min=True, fO2_buffer=None, fO2_offset=0.0, timeout = 90, multiprocesses = True):
137
+ find_min=True, fO2_buffer=None, fO2_offset=0.0, timeout = 90, multi_processing = True):
138
+ """
139
+ Determines the pressure at which two or more minerals co-saturate (following the method of Gualda et al. 2014).
102
140
 
141
+ Parameters
142
+ ----------
143
+ Model : str
144
+ Thermodynamic model to use.
145
+ cores : int
146
+ Number of parallel processes to use.
147
+ bulk : dict
148
+ Bulk composition of the starting material.
149
+ phases : list of str
150
+ Mineral phases to track for saturation.
151
+ P_bar : array-like
152
+ Pressures to test in bars.
153
+ Fe3Fet_init : float, optional
154
+ Initial Fe³⁺/Fe_total ratio.
155
+ H2O_init : float, optional
156
+ Initial H₂O content in the system (wt%).
157
+ CO2_init : float, optional
158
+ Initial CO₂ content in the system (wt%).
159
+ H2O_Sat : bool
160
+ If True, run at H₂O saturation.
161
+ T_initial_C : float, optional
162
+ Starting temperature in °C.
163
+ dt_C : float
164
+ Temperature step size (°C).
165
+ T_maxdrop_C : float
166
+ Maximum temperature drop below the liquidus to search for satuartion (°C).
167
+ T_cut_C : float
168
+ Maximum acceptable difference in phase saturation temperatures for "co-saturation" (°C).
169
+ find_range : bool
170
+ If True, analyze phase co-saturation ranges.
171
+ find_min : bool
172
+ If True, determine pressure where phase saturation temperature difference is minimized.
173
+ fO2_buffer : str, optional
174
+ Oxygen fugacity buffer (e.g., "FMQ", "NNO").
175
+ fO2_offset : float, default 0.0
176
+ Offset from the fO2 buffer.
177
+ timeout : int
178
+ Timeout (in seconds) for each process.
179
+ multi_processing : bool
180
+ If True, run simulations in parallel using multiprocessing.
181
+
182
+ Returns
183
+ -------
184
+ out : dict
185
+ Contains either 'Output' (DataFrame of phase saturation conditions) or both 'Output' and 'CurveMin'.
186
+ Results : dict
187
+ Raw simulation outputs for each pressure step.
188
+ """
189
+
103
190
  comp = bulk.copy()
104
191
  if H2O_Sat:
105
192
  comp['H2O_Liq'] = 20
@@ -108,7 +195,7 @@ def mineral_cosaturation(Model="MELTSv1.0.2", cores=int(np.floor(multiprocessing
108
195
  H2O_Liq=H2O_init, CO2_Liq=CO2_init)
109
196
 
110
197
  combined_results = {}
111
- if multiprocesses:
198
+ if multi_processing:
112
199
  index_in = np.arange(len(P_bar))
113
200
  index_out = np.array([], dtype=int)
114
201
 
@@ -254,6 +341,28 @@ def mineral_cosaturation(Model="MELTSv1.0.2", cores=int(np.floor(multiprocessing
254
341
  return out, Results
255
342
 
256
343
  def findmin(out = None, P_bar = None, T_cut_C = None):
344
+ """
345
+ Finds the minimum temperature offset between mineral saturation temperatures as a function of pressure.
346
+
347
+ Parameters
348
+ ----------
349
+ out : pd.DataFrame
350
+ DataFrame containing phase saturation temperatures.
351
+ P_bar : array-like
352
+ Pressure values (bar) corresponding to the rows in `out`.
353
+ T_cut_C : float
354
+ Maximum acceptable temperature difference (°C) for co-saturation to be considered valid.
355
+
356
+ Returns
357
+ -------
358
+ CurveMin : dict
359
+ Dictionary containing:
360
+ - 'P_min': Pressure of minimum saturation temperature difference.
361
+ - 'Res_min': Minimum temperature difference.
362
+ - 'y_new': Interpolated curve of temperature differences.
363
+ - 'P_new': Pressure array for interpolated curve.
364
+ - 'test': 'Pass' or 'Fail' depending on whether the result meets `T_cut_C`.
365
+ """
257
366
  Res = out.copy()
258
367
 
259
368
  if '3 Phase Saturation' in list(Res.keys()):
@@ -337,606 +446,612 @@ def findmin(out = None, P_bar = None, T_cut_C = None):
337
446
 
338
447
  return CurveMin
339
448
 
340
-
341
449
  def find_mineral_cosaturation(cores = None, Model = None, bulk = None, phases = None, P_bar = None, Fe3Fet_Liq = None,
342
450
  H2O_Liq = None, H2O_Sat = False, T_initial_C = None, dt_C = None, T_maxdrop_C = None,
343
451
  T_cut_C = None, find_range = None, find_min = None, fO2_buffer = None, fO2_offset = None):
344
- '''
345
- Carry out multiple calculations in parallel. Allows isobaric, polybaric and isochoric crystallisation to be performed as well as isothermal, isenthalpic or isentropic decompression. All temperature inputs/outputs are reported in degrees celcius and pressure is reported in bars.
346
-
347
- Parameters:
348
- ----------
349
- cores: int
350
- number of processes to run in parallel. Default will be determined using Multiprocessing.cpu_count().
351
-
352
- Model: string
353
- "MELTS" or "Holland". Dictates whether MELTS or MAGEMin calculations are performed. Default "MELTS".
354
- Version of melts can be specified "MELTSv1.0.2", "MELTSv1.1.0", "MELTSv1.2.0", or "pMELTS". Default "v.1.0.2".
355
-
356
- bulk: Dict or pd.DataFrame
357
- Initial compositon for calculations. If type == Dict, the same initial composition will be used in all calculations.
358
-
359
- phases: list
360
- length 2 or 3, contains the phases of the co-saturated magma. Default = ['quartz1', 'plagioclase1', 'k-feldspar1'].
361
-
362
- P_bar: np.ndarray
363
- Calculation pressure. Length determines the number of calculations to be performed.
364
-
365
- Fe3Fet_Liq: float or np.ndarray
366
- Initial Fe 3+/total ratio.
367
-
368
- H2O_Liq: float
369
- H2O content of the initial melt phase.
370
-
371
- T_initial_C: float
372
- Starting temperature for the liquidus calculations. Default = 1200
373
-
374
- T_maxdrop_C: float
375
- Max temperature drop of the calculations. Default = 25
376
-
377
- dt_C: float
378
- Temperature change at each model step. Default = 1
379
-
380
- T_cut_C: float
381
- Temperature offset used to indicate whether the model has succeeded or failed in finding a match. Default = 10.
382
-
383
- find_range: True/False
384
- If True a new DataFrame will be included in Results, indicating all cases where the minimum offset is less than T_cut_C.
385
-
386
- find_min: True/False
387
- If True, a spline fit will be applied to the data to find the minimum point.
388
-
389
- fO2_buffer: string
390
- If the oxygen fugacity of the system is to be buffered during crystallisation/decompression, then an offset to a known buffer must be specified. Here the user can define the known buffer as either "FMQ" or "NNO".
391
-
392
- fO2_offset: float
393
- Offset from the buffer spcified in fO2_buffer (log units).
394
-
395
- Returns:
396
- ----------
397
- Results: Dict
398
- Dictionary containing information regarding the saturation temperature of each phase and the residuals between the different phases
399
- '''
400
- print('This function will be removed following the update to v0.3.0. Please switch to using the mineral_cosaturation() function')
401
-
402
- try:
403
- from meltsdynamic import MELTSdynamic
404
- except:
405
- Warning('alphaMELTS for Python files are not on the python path. \n Please add these files to the path running \n import sys \n sys.path.append(r"insert_your_path_to_melts_here") \n You are looking for the location of the meltsdynamic.py file')
406
-
407
- T_step_C = dt_C
408
- dt_C = T_maxdrop_C
409
-
410
- comp = bulk.copy()
411
- if H2O_Sat is True:
412
- comp['H2O_Liq'] = 20
452
+ print("This function has been removed following update to v0.2.40. Please switch to using the mineral_cosaturation() function")
413
453
 
414
- # set default values if required
415
- if Model is None:
416
- Model == "MELTSv1.0.2"
454
+ return "This function has been removed following update to v0.2.40. Please switch to using the mineral_cosaturation() function"
417
455
 
418
- if cores is None:
419
- cores = multiprocessing.cpu_count()
456
+ # def find_mineral_cosaturation(cores = None, Model = None, bulk = None, phases = None, P_bar = None, Fe3Fet_Liq = None,
457
+ # H2O_Liq = None, H2O_Sat = False, T_initial_C = None, dt_C = None, T_maxdrop_C = None,
458
+ # T_cut_C = None, find_range = None, find_min = None, fO2_buffer = None, fO2_offset = None):
459
+ # '''
460
+ # Carry out multiple calculations in parallel. Allows isobaric, polybaric and isochoric crystallisation to be performed as well as isothermal, isenthalpic or isentropic decompression. All temperature inputs/outputs are reported in degrees celcius and pressure is reported in bars.
420
461
 
421
- # if comp is entered as a pandas series, it must first be converted to a dict
422
- if type(comp) == pd.core.series.Series:
423
- comp = comp.to_dict()
462
+ # Parameters:
463
+ # ----------
464
+ # cores: int
465
+ # number of processes to run in parallel. Default will be determined using Multiprocessing.cpu_count().
424
466
 
425
- # ensure the bulk composition has the correct headers etc.
426
- comp = comp_fix(Model = Model, comp = comp)
467
+ # Model: string
468
+ # "MELTS" or "Holland". Dictates whether MELTS or MAGEMin calculations are performed. Default "MELTS".
469
+ # Version of melts can be specified "MELTSv1.0.2", "MELTSv1.1.0", "MELTSv1.2.0", or "pMELTS". Default "v.1.0.2".
427
470
 
428
- # create base array to be filled
429
- if P_bar is None:
430
- P_bar = np.array([-1])
431
- elif type(P_bar) != np.ndarray:
432
- P_bar = np.array([P_bar])
471
+ # bulk: Dict or pd.DataFrame
472
+ # Initial compositon for calculations. If type == Dict, the same initial composition will be used in all calculations.
433
473
 
434
- if Fe3Fet_Liq is None:
435
- Fe3Fet_Liq = np.array([-1])
436
- elif type(Fe3Fet_Liq) != np.ndarray:
437
- Fe3Fet_Liq = np.array([Fe3Fet_Liq])
474
+ # phases: list
475
+ # length 2 or 3, contains the phases of the co-saturated magma. Default = ['quartz1', 'plagioclase1', 'k-feldspar1'].
438
476
 
439
- if H2O_Liq is None:
440
- H2O_Liq = np.array([-1])
441
- elif type(H2O_Liq) != np.ndarray:
442
- H2O_Liq = np.array([H2O_Liq])
477
+ # P_bar: np.ndarray
478
+ # Calculation pressure. Length determines the number of calculations to be performed.
443
479
 
444
- base_array = np.zeros((len(H2O_Liq),len(Fe3Fet_Liq),len(P_bar)))
480
+ # Fe3Fet_Liq: float or np.ndarray
481
+ # Initial Fe 3+/total ratio.
445
482
 
446
- if P_bar[0] == -1:
447
- P_bar = 1000
448
- if Fe3Fet_Liq[0] == -1:
449
- Fe3Fet_Liq = None
450
- if H2O_Liq[0] == -1:
451
- H2O_Liq = None
483
+ # H2O_Liq: float
484
+ # H2O content of the initial melt phase.
452
485
 
453
- # set default values for remaining parameters
454
- if T_initial_C is None:
455
- T_initial_C = 1200
486
+ # T_initial_C: float
487
+ # Starting temperature for the liquidus calculations. Default = 1200
456
488
 
457
- if dt_C is None:
458
- dt_C = 25
489
+ # T_maxdrop_C: float
490
+ # Max temperature drop of the calculations. Default = 25
459
491
 
460
- if T_step_C is None:
461
- T_step_C = 1
492
+ # dt_C: float
493
+ # Temperature change at each model step. Default = 1
462
494
 
463
- if T_cut_C is None:
464
- T_cut_C = 10
495
+ # T_cut_C: float
496
+ # Temperature offset used to indicate whether the model has succeeded or failed in finding a match. Default = 10.
465
497
 
466
- if phases is None:
467
- phases = ['quartz1', 'alkali-feldspar1', 'plagioclase1']
498
+ # find_range: True/False
499
+ # If True a new DataFrame will be included in Results, indicating all cases where the minimum offset is less than T_cut_C.
468
500
 
469
- # create main output dictionary
470
- Results = {}
471
- if len(phases) == 3:
472
- List = [phases[0], phases[1], phases[2], 'T_Liq', 'H2O_melt', '3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]]
473
- for l in List:
474
- Results[l] = base_array.copy()
475
- else:
476
- List = [phases[0], phases[1], 'T_Liq', 'H2O_melt', phases[0] + ' - ' + phases[1]]
477
- for l in List:
478
- Results[l] = base_array.copy()
479
-
480
- # run calculations if only one initial composition provided
481
- if type(comp) == dict:
482
- for i in range(np.shape(base_array)[0]):
483
- # if H2O specified set H2O on each iteration
484
- if H2O_Liq is not None:
485
- comp['H2O_Liq'] = H2O_Liq[i]
486
-
487
- for j in range(np.shape(base_array)[1]):
488
- #if Fe3Fet specified set Fe3Fet on each iteration
489
- if Fe3Fet_Liq is not None:
490
- comp['Fe3Fet_Liq'] = Fe3Fet_Liq[j]
491
-
492
- # determine how many processes to run in parallel
493
- if len(P_bar) > 1:
494
- A = len(P_bar)//cores
495
- B = len(P_bar) % cores
496
-
497
- if A > 0:
498
- Group = np.zeros(A) + cores
499
- if B > 0:
500
- Group = np.append(Group, B)
501
- else:
502
- Group = np.array([B])
501
+ # find_min: True/False
502
+ # If True, a spline fit will be applied to the data to find the minimum point.
503
503
 
504
- # initialise queue
505
- qs = []
506
- q = Queue()
504
+ # fO2_buffer: string
505
+ # If the oxygen fugacity of the system is to be buffered during crystallisation/decompression, then an offset to a known buffer must be specified. Here the user can define the known buffer as either "FMQ" or "NNO".
507
506
 
507
+ # fO2_offset: float
508
+ # Offset from the buffer spcified in fO2_buffer (log units).
508
509
 
509
- # run calculations
510
- for k in range(len(Group)):
511
- ps = []
510
+ # Returns:
511
+ # ----------
512
+ # Results: Dict
513
+ # Dictionary containing information regarding the saturation temperature of each phase and the residuals between the different phases
514
+ # '''
515
+ # print('This function will be removed following the update to v0.3.0. Please switch to using the mineral_cosaturation() function')
516
+
517
+ # try:
518
+ # from meltsdynamic import MELTSdynamic
519
+ # except:
520
+ # Warning('alphaMELTS for Python files are not on the python path. \n Please add these files to the path running \n import sys \n sys.path.append(r"insert_your_path_to_melts_here") \n You are looking for the location of the meltsdynamic.py file')
521
+
522
+ # T_step_C = dt_C
523
+ # dt_C = T_maxdrop_C
512
524
 
513
- for kk in range(int(cores*k), int(cores*k + Group[k])):
514
- p = Process(target = satTemperature, args = (q, kk),
515
- kwargs = {'Model': Model, 'comp': comp,
516
- 'T_initial_C': T_initial_C, 'T_step_C': T_step_C,
517
- 'dt_C': dt_C, 'P_bar': P_bar[kk], 'phases': phases,
518
- 'H2O_Liq': H2O_Liq, 'fO2_buffer': fO2_buffer, 'fO2_offset': fO2_offset})
525
+ # comp = bulk.copy()
526
+ # if H2O_Sat is True:
527
+ # comp['H2O_Liq'] = 20
519
528
 
520
- ps.append(p)
521
- p.start()
529
+ # # set default values if required
530
+ # if Model is None:
531
+ # Model == "MELTSv1.0.2"
532
+
533
+ # if cores is None:
534
+ # cores = multiprocessing.cpu_count()
535
+
536
+ # # if comp is entered as a pandas series, it must first be converted to a dict
537
+ # if type(comp) == pd.core.series.Series:
538
+ # comp = comp.to_dict()
522
539
 
523
- TIMEOUT = 300
540
+ # # ensure the bulk composition has the correct headers etc.
541
+ # comp = comp_fix(Model = Model, comp = comp)
524
542
 
525
- start = time.time()
526
- for p in ps:
527
- if time.time() - start < TIMEOUT - 10:
528
- try:
529
- ret = q.get(timeout = TIMEOUT - (time.time()-start) + 10)
530
- except:
531
- ret = []
532
- else:
533
- try:
534
- ret = q.get(timeout = 10)
535
- except:
536
- ret = []
537
-
538
- qs.append(ret)
539
-
540
- TIMEOUT = 5
541
- start = time.time()
542
- for p in ps:
543
- if p.is_alive():
544
- while time.time() - start <= TIMEOUT:
545
- if not p.is_alive():
546
- p.join()
547
- p.terminate()
548
- break
549
- time.sleep(.1)
550
- else:
551
- p.terminate()
552
- p.join(5)
553
- else:
554
- p.join()
555
- p.terminate()
556
-
557
-
558
- # for p in ps:
559
- # try:
560
- # ret = q.get(timeout = 180)
561
- # except:
562
- # ret = []
563
- #
564
- # qs.append(ret)
565
- #
566
- # TIMEOUT = 20
567
- # start = time.time()
568
- # for p in ps:
569
- # if p.is_alive():
570
- # time.sleep(.1)
571
- # while time.time() - start <= TIMEOUT:
572
- # if not p.is_alive():
573
- # p.join()
574
- # p.terminate()
575
- # break
576
- # time.sleep(.1)
577
- # else:
578
- # p.terminate()
579
- # p.join(5)
580
- # else:
581
- # p.join()
582
- # p.terminate()
583
-
584
- # # extract results
585
- for kk in range(len(qs)):
586
- if len(qs[kk]) > 0:
587
- Res, index = qs[kk]
588
- for l in Results:
589
- if l != 'sat_surface':
590
- Results[l][i,j,index] = Res[l]
591
-
592
- # covert any empty values to nan
593
- for l in Results:
594
- if l != 'sat_surface':
595
- Results[l][np.where(Results[l] == 0.0)] = np.nan
596
-
597
- if find_min is not None:
598
- if H2O_Liq is not None:
599
- Results = findMinimum(Results = Results, P_bar = P_bar, T_cut_C = T_cut_C, H2O_Liq = H2O_Liq, Fe3Fet_Liq = Fe3Fet_Liq, phases = phases)
600
- else:
601
- Results = findMinimum(Results = Results, P_bar = P_bar, T_cut_C = T_cut_C, H2O_Liq = H2O_Liq, Fe3Fet_Liq = Fe3Fet_Liq, phases = phases)
602
-
603
- if find_range is not None:
604
- Results['range'] = np.zeros(np.shape(Results[l]))
605
- if len(phases) == 3:
606
- Results['range'][np.where(Results['3 Phase Saturation'] <= T_cut_C)] = True
607
- Results['range'][np.where(Results['3 Phase Saturation'] > T_cut_C)] = False
608
- else:
609
- Results['range'][np.where(Results[phases[0] + ' - ' + phases[1]] <= T_cut_C)] = True
610
- Results['range'][np.where(Results[phases[0] + ' - ' + phases[1]] > T_cut_C)] = False
543
+ # # create base array to be filled
544
+ # if P_bar is None:
545
+ # P_bar = np.array([-1])
546
+ # elif type(P_bar) != np.ndarray:
547
+ # P_bar = np.array([P_bar])
548
+
549
+ # if Fe3Fet_Liq is None:
550
+ # Fe3Fet_Liq = np.array([-1])
551
+ # elif type(Fe3Fet_Liq) != np.ndarray:
552
+ # Fe3Fet_Liq = np.array([Fe3Fet_Liq])
553
+
554
+ # if H2O_Liq is None:
555
+ # H2O_Liq = np.array([-1])
556
+ # elif type(H2O_Liq) != np.ndarray:
557
+ # H2O_Liq = np.array([H2O_Liq])
558
+
559
+ # base_array = np.zeros((len(H2O_Liq),len(Fe3Fet_Liq),len(P_bar)))
560
+
561
+ # if P_bar[0] == -1:
562
+ # P_bar = 1000
563
+ # if Fe3Fet_Liq[0] == -1:
564
+ # Fe3Fet_Liq = None
565
+ # if H2O_Liq[0] == -1:
566
+ # H2O_Liq = None
567
+
568
+ # # set default values for remaining parameters
569
+ # if T_initial_C is None:
570
+ # T_initial_C = 1200
571
+
572
+ # if dt_C is None:
573
+ # dt_C = 25
574
+
575
+ # if T_step_C is None:
576
+ # T_step_C = 1
577
+
578
+ # if T_cut_C is None:
579
+ # T_cut_C = 10
580
+
581
+ # if phases is None:
582
+ # phases = ['quartz1', 'alkali-feldspar1', 'plagioclase1']
583
+
584
+ # # create main output dictionary
585
+ # Results = {}
586
+ # if len(phases) == 3:
587
+ # List = [phases[0], phases[1], phases[2], 'T_Liq', 'H2O_melt', '3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]]
588
+ # for l in List:
589
+ # Results[l] = base_array.copy()
590
+ # else:
591
+ # List = [phases[0], phases[1], 'T_Liq', 'H2O_melt', phases[0] + ' - ' + phases[1]]
592
+ # for l in List:
593
+ # Results[l] = base_array.copy()
594
+
595
+ # # run calculations if only one initial composition provided
596
+ # if type(comp) == dict:
597
+ # for i in range(np.shape(base_array)[0]):
598
+ # # if H2O specified set H2O on each iteration
599
+ # if H2O_Liq is not None:
600
+ # comp['H2O_Liq'] = H2O_Liq[i]
601
+
602
+ # for j in range(np.shape(base_array)[1]):
603
+ # #if Fe3Fet specified set Fe3Fet on each iteration
604
+ # if Fe3Fet_Liq is not None:
605
+ # comp['Fe3Fet_Liq'] = Fe3Fet_Liq[j]
606
+
607
+ # # determine how many processes to run in parallel
608
+ # if len(P_bar) > 1:
609
+ # A = len(P_bar)//cores
610
+ # B = len(P_bar) % cores
611
+
612
+ # if A > 0:
613
+ # Group = np.zeros(A) + cores
614
+ # if B > 0:
615
+ # Group = np.append(Group, B)
616
+ # else:
617
+ # Group = np.array([B])
618
+
619
+ # # initialise queue
620
+ # qs = []
621
+ # q = Queue()
622
+
623
+
624
+ # # run calculations
625
+ # for k in range(len(Group)):
626
+ # ps = []
627
+
628
+ # for kk in range(int(cores*k), int(cores*k + Group[k])):
629
+ # p = Process(target = satTemperature, args = (q, kk),
630
+ # kwargs = {'Model': Model, 'comp': comp,
631
+ # 'T_initial_C': T_initial_C, 'T_step_C': T_step_C,
632
+ # 'dt_C': dt_C, 'P_bar': P_bar[kk], 'phases': phases,
633
+ # 'H2O_Liq': H2O_Liq, 'fO2_buffer': fO2_buffer, 'fO2_offset': fO2_offset})
634
+
635
+ # ps.append(p)
636
+ # p.start()
637
+
638
+ # TIMEOUT = 300
639
+
640
+ # start = time.time()
641
+ # for p in ps:
642
+ # if time.time() - start < TIMEOUT - 10:
643
+ # try:
644
+ # ret = q.get(timeout = TIMEOUT - (time.time()-start) + 10)
645
+ # except:
646
+ # ret = []
647
+ # else:
648
+ # try:
649
+ # ret = q.get(timeout = 10)
650
+ # except:
651
+ # ret = []
652
+
653
+ # qs.append(ret)
654
+
655
+ # TIMEOUT = 5
656
+ # start = time.time()
657
+ # for p in ps:
658
+ # if p.is_alive():
659
+ # while time.time() - start <= TIMEOUT:
660
+ # if not p.is_alive():
661
+ # p.join()
662
+ # p.terminate()
663
+ # break
664
+ # time.sleep(.1)
665
+ # else:
666
+ # p.terminate()
667
+ # p.join(5)
668
+ # else:
669
+ # p.join()
670
+ # p.terminate()
671
+
672
+
673
+ # # for p in ps:
674
+ # # try:
675
+ # # ret = q.get(timeout = 180)
676
+ # # except:
677
+ # # ret = []
678
+ # #
679
+ # # qs.append(ret)
680
+ # #
681
+ # # TIMEOUT = 20
682
+ # # start = time.time()
683
+ # # for p in ps:
684
+ # # if p.is_alive():
685
+ # # time.sleep(.1)
686
+ # # while time.time() - start <= TIMEOUT:
687
+ # # if not p.is_alive():
688
+ # # p.join()
689
+ # # p.terminate()
690
+ # # break
691
+ # # time.sleep(.1)
692
+ # # else:
693
+ # # p.terminate()
694
+ # # p.join(5)
695
+ # # else:
696
+ # # p.join()
697
+ # # p.terminate()
698
+
699
+ # # # extract results
700
+ # for kk in range(len(qs)):
701
+ # if len(qs[kk]) > 0:
702
+ # Res, index = qs[kk]
703
+ # for l in Results:
704
+ # if l != 'sat_surface':
705
+ # Results[l][i,j,index] = Res[l]
706
+
707
+ # # covert any empty values to nan
708
+ # for l in Results:
709
+ # if l != 'sat_surface':
710
+ # Results[l][np.where(Results[l] == 0.0)] = np.nan
711
+
712
+ # if find_min is not None:
713
+ # if H2O_Liq is not None:
714
+ # Results = findMinimum(Results = Results, P_bar = P_bar, T_cut_C = T_cut_C, H2O_Liq = H2O_Liq, Fe3Fet_Liq = Fe3Fet_Liq, phases = phases)
715
+ # else:
716
+ # Results = findMinimum(Results = Results, P_bar = P_bar, T_cut_C = T_cut_C, H2O_Liq = H2O_Liq, Fe3Fet_Liq = Fe3Fet_Liq, phases = phases)
717
+
718
+ # if find_range is not None:
719
+ # Results['range'] = np.zeros(np.shape(Results[l]))
720
+ # if len(phases) == 3:
721
+ # Results['range'][np.where(Results['3 Phase Saturation'] <= T_cut_C)] = True
722
+ # Results['range'][np.where(Results['3 Phase Saturation'] > T_cut_C)] = False
723
+ # else:
724
+ # Results['range'][np.where(Results[phases[0] + ' - ' + phases[1]] <= T_cut_C)] = True
725
+ # Results['range'][np.where(Results[phases[0] + ' - ' + phases[1]] > T_cut_C)] = False
611
726
 
612
727
 
613
728
 
614
- return Results
615
-
616
- def findMinimum(Results = None, P_bar = None, T_cut_C = None, H2O_Liq = None, Fe3Fet_Liq = None, phases = None):
617
- '''
618
- Take the results of SatPress and search for the minimum point using a spline fit.
619
- '''
620
- if T_cut_C is None:
621
- T_cut_C = 10
622
-
623
- if H2O_Liq is None and Fe3Fet_Liq is None:
624
- if '3 Phase Saturation' in list(Results.keys()):
625
- Minimum = {'3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]}
626
- CurveMin = {}
627
- for m in Minimum:
628
- if len(Results[m][0,0,:][~np.isnan(Results[m][0,0,:])]) > 2:
629
- y = Results[m][0,0,:][(np.where(~np.isnan(Results[m][0,0,:]))) and (np.where(Results[m][0,0,:] < T_cut_C))]
630
- x = P_bar[(np.where(~np.isnan(Results[m][0,0,:]))) and (np.where(Results[m][0,0,:] < T_cut_C))]
631
-
632
- try:
633
- y_new = interpolate.UnivariateSpline(x, y, k = 5)
634
- except:
635
- y_new = interpolate.UnivariateSpline(x, y, k = 2)
636
-
637
- P_new = np.linspace(P_bar[np.where(P_bar == np.nanmin(P_bar[(np.where(~np.isnan(Results[m][0,0,:]))) and (np.where(Results[m][0,0,:] < T_cut_C))]))], P_bar[np.where(P_bar == np.nanmax(P_bar[(np.where(~np.isnan(Results[m][0,0,:]))) and (np.where(Results[m][0,0,:] < T_cut_C))]))], 200)
638
-
639
- NewMin = np.nanmin(y_new(P_new))
640
- P_min = P_new[np.where(y_new(P_new) == NewMin)][0]
641
- if NewMin < T_cut_C:
642
- Test = 'Pass'
643
- else:
644
- Test = 'Fail'
645
-
646
- CurveMin[m] = {'P_min': P_min, 'Res_min': NewMin, 'y_new': y_new(P_new), 'P_new': P_new, 'test': Test}
647
- else:
648
- y_new = np.nan
649
- P_new = np.nan
650
- NewMin = np.nan
651
- P_min = np.nan
652
- Test = 'Fail'
653
- CurveMin[m] = {'P_min': P_min, 'Res_min': NewMin, 'y_new': y_new, 'P_new': P_new, 'test': Test}
654
-
655
- Results['CurveMin'] = CurveMin
656
- else:
657
- m = phases[0] + ' - ' + phases[1]
658
- if len(Results[m][0,0,:][~np.isnan(Results[m][0,0,:])]) > 2:
659
- y = Results[m][0,0,:][np.where(~np.isnan(Results[m][0,0,:]))]
660
- x = P_bar[np.where(~np.isnan(Results[m][0,0,:]))]
661
-
662
- try:
663
- y_new = interpolate.UnivariateSpline(x, y, k=5)
664
- except:
665
- y_new = interpolate.UnivariateSpline(x, y, k = 2)
666
-
667
- P_new = np.linspace(P_bar[np.where(P_bar == np.nanmin(P_bar[np.where(~np.isnan(Results[m][0,0,:]))]))], P_bar[np.where(P_bar == np.nanmax(P_bar[np.where(~np.isnan(Results[m][0,0,:]))]))], 200)
668
-
669
- NewMin = np.nanmin(y_new(P_new))
670
- P_min = P_new[np.where(y_new(P_new) == NewMin)][0]
671
- if NewMin < T_cut_C:
672
- Test = 'Pass'
673
- else:
674
- Test = 'Fail'
675
- else:
676
- y_new = np.nan
677
- P_new = np.nan
678
- NewMin = np.nan
679
- P_min = np.nan
680
- Test = 'Fail'
681
-
682
- Results['CurveMin'] = {phases[0] + ' - ' + phases[1]: {'P_min': P_min, 'Res_min': NewMin, 'y_new': y_new, 'P_new': P_new, 'test': Test}}
683
-
684
- if H2O_Liq is not None and Fe3Fet_Liq is None:
685
- if '3 Phase Saturation' in list(Results.keys()):
686
- X, Y = np.meshgrid(P_bar, H2O_Liq)
687
- Y = Results['H2O_melt'][:,0,:].copy()
688
-
689
- Minimum = {'3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]}
690
- CurveMin = {}
691
- for m in Minimum:
692
- if len(Results[m][:,0,:][~np.isnan(Results[m][:,0,:])]) > 4:
693
- Res = Results[m][:,0,:].copy()
694
- Res[np.where(Res > T_cut_C*2)] = np.nan
695
- for i in range(len(H2O_Liq)):
696
- Res[i, :][np.where(Results['H2O_melt'][i,0,:] < 0.99*np.nanmax(Results['H2O_melt'][i,0,:]))] = np.nan
697
-
698
- A = Res.copy()
699
- Res[np.where(Res > T_cut_C)] = np.nan
700
- X, Y = np.meshgrid(P_bar, H2O_Liq)
701
- Y = Results['H2O_melt'][:,0,:]
702
-
703
- try:
704
- z_new = interpolate.SmoothBivariateSpline(X[np.where(~np.isnan(A) & ~np.isnan(Y))], Y[np.where(~np.isnan(A) & ~np.isnan(Y))], A[np.where(~np.isnan(A) & ~np.isnan(Y))], kx = 3, ky = 3)
705
- except:
706
- z_new = interpolate.SmoothBivariateSpline(X[np.where(~np.isnan(A) & ~np.isnan(Y))], Y[np.where(~np.isnan(A) & ~np.isnan(Y))], A[np.where(~np.isnan(A) & ~np.isnan(Y))], kx = 2, ky = 2)
707
-
708
- H2O_new = np.linspace(Y[np.where(Res == np.nanmin(Res))] - (H2O_Liq[1]-H2O_Liq[0]),
709
- Y[np.where(Res == np.nanmin(Res))] + (H2O_Liq[1]-H2O_Liq[0]), 20)
710
- P_new = np.linspace(X[np.where(Res == np.nanmin(Res))] - (P_bar[1]-P_bar[0]),
711
- X[np.where(Res == np.nanmin(Res))] + (P_bar[1]-P_bar[0]), 20)
712
-
713
- X_new, Y_new = np.meshgrid(P_new, H2O_new)
714
- x = X[~np.isnan(A)].flatten()
715
- y = Y[~np.isnan(A)].flatten()
716
-
717
- MyPoly = MultiPoint(list(zip(x, y))).convex_hull
718
-
719
- points = list(zip(X_new.flatten(), Y_new.flatten()))
720
- Include = np.zeros(len(X_new.flatten()))
721
- for i in range(len(points)):
722
- p = Point(points[i])
723
- Include[i] = p.within(MyPoly)
724
-
725
- YayNay = Include.reshape(X_new.shape)
726
- x_new = X_new[np.where(YayNay == True)].flatten()
727
- y_new = Y_new[np.where(YayNay == True)].flatten()
728
- Res_min = np.nanmin(z_new(x_new, y_new, grid = False))
729
- P_min = x_new[np.where(z_new(x_new, y_new, grid = False) == Res_min)]
730
- H2O_min = y_new[np.where(z_new(x_new, y_new, grid = False) == Res_min)]
731
- if Res_min < T_cut_C:
732
- Test = 'Pass'
733
- else:
734
- Test = 'Fail'
735
-
736
- CurveMin[m] = {'Res_min': Res_min, 'P_min': P_min[0], 'H2O_min': H2O_min[0], 'z_new': z_new, 'test': Test}
737
- else:
738
- CurveMin[m] = {'Res_min': np.nan, 'P_min': np.nan, 'H2O_min': np.nan, 'z_new': np.nan, 'test': 'Fail'}
739
-
740
- Results['CurveMin'] = CurveMin
741
-
742
- if H2O_Liq is not None and Fe3Fet_Liq is not None:
743
- if '3 Phase Saturation' in list(Results.keys()):
744
- X, Y = np.meshgrid(P_bar, H2O_Liq)
745
- Y = Results['H2O_melt'][:,0,:].copy()
746
-
747
- Minimum = {'3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]}
748
- CurveMin = {}
749
- for m in Minimum:
750
- Res_min_save = 50
751
- for w in range(len(Fe3Fet_Liq)):
752
- if len(Results[m][:,w,:][~np.isnan(Results[m][:,w,:])]) > 4:
753
- Res = Results[m][:,w,:].copy()
754
- Res[np.where(Res > T_cut_C*2)] = np.nan
755
- for i in range(len(H2O_Liq)):
756
- Res[i, :][np.where(Results['H2O_melt'][i,w,:] < 0.99*np.nanmax(Results['H2O_melt'][i,w,:]))] = np.nan
757
-
758
- A = Res.copy()
759
- X, Y = np.meshgrid(P_bar, H2O_Liq)
760
- Y = Results['H2O_melt'][:,w,:].copy()
761
-
762
- try:
763
- z_new = interpolate.SmoothBivariateSpline(X[np.where(~np.isnan(A) & ~np.isnan(Y))], Y[np.where(~np.isnan(A) & ~np.isnan(Y))], A[np.where(~np.isnan(A) & ~np.isnan(Y))], kx = 3, ky = 3)
764
- except:
765
- z_new = interpolate.SmoothBivariateSpline(X[np.where(~np.isnan(A) & ~np.isnan(Y))], Y[np.where(~np.isnan(A) & ~np.isnan(Y))], A[np.where(~np.isnan(A) & ~np.isnan(Y))], kx = 2, ky = 2)
766
-
767
- H2O_new = np.linspace(Y[np.where(Res == np.nanmin(Res))] - (H2O_Liq[1]-H2O_Liq[0]),
768
- Y[np.where(Res == np.nanmin(Res))] + (H2O_Liq[1]-H2O_Liq[0]), 20)
769
- P_new = np.linspace(X[np.where(Res == np.nanmin(Res))] - (P_bar[1]-P_bar[0]),
770
- X[np.where(Res == np.nanmin(Res))] + (P_bar[1]-P_bar[0]), 20)
771
-
772
- X_new, Y_new = np.meshgrid(P_new, H2O_new)
773
- x = X[~np.isnan(A)].flatten()
774
- y = Y[~np.isnan(A)].flatten()
775
-
776
- MyPoly = MultiPoint(list(zip(x, y))).convex_hull
777
-
778
- points = list(zip(X_new.flatten(), Y_new.flatten()))
779
- Include = np.zeros(len(X_new.flatten()))
780
- for i in range(len(points)):
781
- p = Point(points[i])
782
- Include[i] = p.within(MyPoly)
783
-
784
- YayNay = Include.reshape(X_new.shape)
785
- x_new = X_new[np.where(YayNay == True)].flatten()
786
- y_new = Y_new[np.where(YayNay == True)].flatten()
787
- Res_min = np.nanmin(z_new(x_new, y_new, grid = False))
788
- P_min = x_new[np.where(z_new(x_new, y_new, grid = False) == Res_min)]
789
- H2O_min = y_new[np.where(z_new(x_new, y_new, grid = False) == Res_min)]
790
- if Res_min < T_cut_C:
791
- Test = 'Pass'
792
- else:
793
- Test = 'Fail'
794
-
795
- if Res_min < Res_min_save:
796
- Res_min_save = Res_min
797
- CurveMin[m] = {'Res_min': Res_min, 'P_min': P_min[0], 'H2O_min': H2O_min[0], 'z_new': z_new, 'Fe3Fet_Liq': Fe3Fet_Liq[w], 'test': Test}
798
-
799
- Results['CurveMin'] = CurveMin
800
-
801
-
802
- return Results
803
-
804
- def polymin(P_bar = None, Res = None):
805
- '''
806
- Finds the minimum residual temperature using a 2nd degree polynomial.
807
- '''
808
- arr = np.sort(Res)
809
- Ind = np.where(Res == arr[0])[0][0]
810
-
811
- if P_bar[Ind] == np.nanmax(P_bar):
812
- p = np.array([0,1,0])
813
- p_min = np.array([P_bar[Ind]])
814
- elif P_bar[Ind] == np.nanmin(P_bar):
815
- p = np.array([0,1,0])
816
- p_min = np.array([P_bar[Ind]])
817
- else:
818
- p = np.polyfit(P_bar[np.array([Ind-1, Ind, Ind+1])],Res[np.array([Ind-1, Ind, Ind+1])],2)
819
-
820
- x = np.linspace(np.nanmin(P_bar),np.nanmax(P_bar),501)
821
- y = p[0]*x**2 + p[1]*x + p[2]
822
-
823
- p_min = x[np.where(y == np.nanmin(y))]
824
-
825
- return p, p_min
826
-
827
- def satTemperature(q, index, *, Model = None, comp = None, phases = None, T_initial_C = None, T_step_C = None, dt_C = None, P_bar = None, H2O_Liq = None, fO2_buffer = None, fO2_offset = None):
828
- '''
829
- Crystallisation calculations to be performed in parallel. Calculations may be either isobaric or isochoric.
830
-
831
- Parameters:
832
- ----------
833
- q: Multiprocessing Queue instance
834
- Queue instance to record the output variables
835
-
836
- index: int
837
- index of the calculation in the master code (e.g., position within a for loop) to aid indexing results after calculations are complete.
838
-
839
- Model: string
840
- "MELTS" or "Holland". Dictates whether MELTS or MAGEMin calculations are performed. Default "MELTS".
841
- Version of melts can be specified "MELTSv1.0.2", "MELTSv1.1.0", "MELTSv1.2.0", or "pMELTS". Default "v.1.0.2".
842
-
843
- comp: Dict
844
- Initial compositon for calculations.
845
-
846
- T_initial_C: float
847
- Initial guess for the liquidus temperature.
848
-
849
- T_step_C: float
850
- The temperature drop at each step of the calculation.
851
-
852
- dt_C: float
853
- Total temperature drop allowed duringmodel runs.
854
-
855
- P_bar: float
856
- Specifies the pressure of calculation (bar).
857
-
858
- Returns:
859
- ----------
860
- Results: Dict
861
- Dict containing a series of floats that represent the saturation temperature and residual temperature for each calculation.
862
-
863
- index: int
864
- index of the calculation
865
-
866
- '''
867
-
868
- Results = {}
869
- if "MELTS" in Model:
870
-
871
- try:
872
- Results = phaseSat_MELTS(Model = Model, comp = comp, phases = phases, T_initial_C = T_initial_C, T_step_C = T_step_C, dt_C = dt_C, P_bar = P_bar, H2O_Liq = H2O_Liq, fO2_buffer = fO2_buffer, fO2_offset = fO2_offset)
873
- except:
874
- Results = {phases[0]: np.nan, phases[1]: np.nan, phases[2]: np.nan, 'T_Liq': np.nan, 'H2O_melt': np.nan}
875
- if len(phases) == 2:
876
- del Results[phases[2]]
877
-
878
- if len(phases) == 3:
879
- Res = ['3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]]
880
- for R in Res:
881
- Results[R] = np.nan
882
-
883
- if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]) and ~np.isnan(Results[phases[2]]):
884
- Results['3 Phase Saturation'] = np.nanmax(np.array([abs(Results[phases[0]] - Results[phases[1]]), abs(Results[phases[0]] - Results[phases[2]]), abs(Results[phases[1]] - Results[phases[2]])]))
885
- if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]):
886
- Results[phases[0] + ' - ' + phases[1]] = abs(Results[phases[0]] - Results[phases[1]])
887
- if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[2]]):
888
- Results[phases[0] + ' - ' + phases[2]] = abs(Results[phases[0]] - Results[phases[2]])
889
- if ~np.isnan(Results[phases[1]]) and ~np.isnan(Results[phases[2]]):
890
- Results[phases[1] + ' - ' + phases[2]] = abs(Results[phases[1]] - Results[phases[2]])
891
- else:
892
- Results[phases[0] + ' - ' + phases[1]] = np.nan
893
- if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]):
894
- Results[phases[0] + ' - ' + phases[1]] = abs(Results[phases[0]] - Results[phases[1]])
895
-
896
- q.put([Results, index])
897
- return
898
-
899
- if Model == "Holland":
900
- import pyMAGEMINcalc as MM
901
- Results = {phases[0]: np.nan, phases[1]: np.nan, phases[2]: np.nan, 'T_Liq': np.nan, 'H2O_melt': np.nan}
902
- if len(phases) == 2:
903
- del Results[phases[2]]
904
-
905
- #try:
906
- Result = MM.path(comp = comp, phases = phases, T_min_C = dt_C, dt_C = T_step_C, P_bar = P_bar, find_liquidus = True)
907
- #Result = stich(Result, Model = Model)
908
-
909
- for i in range(len(phases)):
910
- try:
911
- Results[phases[i]] = Result['Conditions']['T_C'][Result[phases[i]+'_prop']['mass'] > 0.0].values[0]
912
- print(Results[phases[i]])
913
- except:
914
- Results[phases[i]] = np.nan
915
-
916
- Results['T_Liq'] = Result['Conditions']['T_C'].values[0]
917
- Results['H2O_melt'] = Result['liq']['H2O'].values[0]
918
-
919
- if len(phases) == 3:
920
- Res = ['3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]]
921
- for R in Res:
922
- Results[R] = np.nan
923
-
924
- if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]) and ~np.isnan(Results[phases[2]]):
925
- Results['3 Phase Saturation'] = np.nanmax(np.array([abs(Results[phases[0]] - Results[phases[1]]), abs(Results[phases[0]] - Results[phases[2]]), abs(Results[phases[1]] - Results[phases[2]])]))
926
- if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]):
927
- Results[phases[0] + ' - ' + phases[1]] = abs(Results[phases[0]] - Results[phases[1]])
928
- if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[2]]):
929
- Results[phases[0] + ' - ' + phases[2]] = abs(Results[phases[0]] - Results[phases[2]])
930
- if ~np.isnan(Results[phases[1]]) and ~np.isnan(Results[phases[2]]):
931
- Results[phases[1] + ' - ' + phases[2]] = abs(Results[phases[1]] - Results[phases[2]])
932
- else:
933
- Results[phases[0] + ' - ' + phases[1]] = np.nan
934
- if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]):
935
- Results[phases[0] + ' - ' + phases[1]] = abs(Results[phases[0]] - Results[phases[1]])
729
+ # return Results
730
+
731
+ # def findMinimum(Results = None, P_bar = None, T_cut_C = None, H2O_Liq = None, Fe3Fet_Liq = None, phases = None):
732
+ # '''
733
+ # Take the results of SatPress and search for the minimum point using a spline fit.
734
+ # '''
735
+ # if T_cut_C is None:
736
+ # T_cut_C = 10
737
+
738
+ # if H2O_Liq is None and Fe3Fet_Liq is None:
739
+ # if '3 Phase Saturation' in list(Results.keys()):
740
+ # Minimum = {'3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]}
741
+ # CurveMin = {}
742
+ # for m in Minimum:
743
+ # if len(Results[m][0,0,:][~np.isnan(Results[m][0,0,:])]) > 2:
744
+ # y = Results[m][0,0,:][(np.where(~np.isnan(Results[m][0,0,:]))) and (np.where(Results[m][0,0,:] < T_cut_C))]
745
+ # x = P_bar[(np.where(~np.isnan(Results[m][0,0,:]))) and (np.where(Results[m][0,0,:] < T_cut_C))]
746
+
747
+ # try:
748
+ # y_new = interpolate.UnivariateSpline(x, y, k = 5)
749
+ # except:
750
+ # y_new = interpolate.UnivariateSpline(x, y, k = 2)
751
+
752
+ # P_new = np.linspace(P_bar[np.where(P_bar == np.nanmin(P_bar[(np.where(~np.isnan(Results[m][0,0,:]))) and (np.where(Results[m][0,0,:] < T_cut_C))]))], P_bar[np.where(P_bar == np.nanmax(P_bar[(np.where(~np.isnan(Results[m][0,0,:]))) and (np.where(Results[m][0,0,:] < T_cut_C))]))], 200)
753
+
754
+ # NewMin = np.nanmin(y_new(P_new))
755
+ # P_min = P_new[np.where(y_new(P_new) == NewMin)][0]
756
+ # if NewMin < T_cut_C:
757
+ # Test = 'Pass'
758
+ # else:
759
+ # Test = 'Fail'
760
+
761
+ # CurveMin[m] = {'P_min': P_min, 'Res_min': NewMin, 'y_new': y_new(P_new), 'P_new': P_new, 'test': Test}
762
+ # else:
763
+ # y_new = np.nan
764
+ # P_new = np.nan
765
+ # NewMin = np.nan
766
+ # P_min = np.nan
767
+ # Test = 'Fail'
768
+ # CurveMin[m] = {'P_min': P_min, 'Res_min': NewMin, 'y_new': y_new, 'P_new': P_new, 'test': Test}
769
+
770
+ # Results['CurveMin'] = CurveMin
771
+ # else:
772
+ # m = phases[0] + ' - ' + phases[1]
773
+ # if len(Results[m][0,0,:][~np.isnan(Results[m][0,0,:])]) > 2:
774
+ # y = Results[m][0,0,:][np.where(~np.isnan(Results[m][0,0,:]))]
775
+ # x = P_bar[np.where(~np.isnan(Results[m][0,0,:]))]
776
+
777
+ # try:
778
+ # y_new = interpolate.UnivariateSpline(x, y, k=5)
779
+ # except:
780
+ # y_new = interpolate.UnivariateSpline(x, y, k = 2)
781
+
782
+ # P_new = np.linspace(P_bar[np.where(P_bar == np.nanmin(P_bar[np.where(~np.isnan(Results[m][0,0,:]))]))], P_bar[np.where(P_bar == np.nanmax(P_bar[np.where(~np.isnan(Results[m][0,0,:]))]))], 200)
783
+
784
+ # NewMin = np.nanmin(y_new(P_new))
785
+ # P_min = P_new[np.where(y_new(P_new) == NewMin)][0]
786
+ # if NewMin < T_cut_C:
787
+ # Test = 'Pass'
788
+ # else:
789
+ # Test = 'Fail'
790
+ # else:
791
+ # y_new = np.nan
792
+ # P_new = np.nan
793
+ # NewMin = np.nan
794
+ # P_min = np.nan
795
+ # Test = 'Fail'
796
+
797
+ # Results['CurveMin'] = {phases[0] + ' - ' + phases[1]: {'P_min': P_min, 'Res_min': NewMin, 'y_new': y_new, 'P_new': P_new, 'test': Test}}
798
+
799
+ # if H2O_Liq is not None and Fe3Fet_Liq is None:
800
+ # if '3 Phase Saturation' in list(Results.keys()):
801
+ # X, Y = np.meshgrid(P_bar, H2O_Liq)
802
+ # Y = Results['H2O_melt'][:,0,:].copy()
803
+
804
+ # Minimum = {'3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]}
805
+ # CurveMin = {}
806
+ # for m in Minimum:
807
+ # if len(Results[m][:,0,:][~np.isnan(Results[m][:,0,:])]) > 4:
808
+ # Res = Results[m][:,0,:].copy()
809
+ # Res[np.where(Res > T_cut_C*2)] = np.nan
810
+ # for i in range(len(H2O_Liq)):
811
+ # Res[i, :][np.where(Results['H2O_melt'][i,0,:] < 0.99*np.nanmax(Results['H2O_melt'][i,0,:]))] = np.nan
812
+
813
+ # A = Res.copy()
814
+ # Res[np.where(Res > T_cut_C)] = np.nan
815
+ # X, Y = np.meshgrid(P_bar, H2O_Liq)
816
+ # Y = Results['H2O_melt'][:,0,:]
817
+
818
+ # try:
819
+ # z_new = interpolate.SmoothBivariateSpline(X[np.where(~np.isnan(A) & ~np.isnan(Y))], Y[np.where(~np.isnan(A) & ~np.isnan(Y))], A[np.where(~np.isnan(A) & ~np.isnan(Y))], kx = 3, ky = 3)
820
+ # except:
821
+ # z_new = interpolate.SmoothBivariateSpline(X[np.where(~np.isnan(A) & ~np.isnan(Y))], Y[np.where(~np.isnan(A) & ~np.isnan(Y))], A[np.where(~np.isnan(A) & ~np.isnan(Y))], kx = 2, ky = 2)
822
+
823
+ # H2O_new = np.linspace(Y[np.where(Res == np.nanmin(Res))] - (H2O_Liq[1]-H2O_Liq[0]),
824
+ # Y[np.where(Res == np.nanmin(Res))] + (H2O_Liq[1]-H2O_Liq[0]), 20)
825
+ # P_new = np.linspace(X[np.where(Res == np.nanmin(Res))] - (P_bar[1]-P_bar[0]),
826
+ # X[np.where(Res == np.nanmin(Res))] + (P_bar[1]-P_bar[0]), 20)
827
+
828
+ # X_new, Y_new = np.meshgrid(P_new, H2O_new)
829
+ # x = X[~np.isnan(A)].flatten()
830
+ # y = Y[~np.isnan(A)].flatten()
831
+
832
+ # MyPoly = MultiPoint(list(zip(x, y))).convex_hull
833
+
834
+ # points = list(zip(X_new.flatten(), Y_new.flatten()))
835
+ # Include = np.zeros(len(X_new.flatten()))
836
+ # for i in range(len(points)):
837
+ # p = Point(points[i])
838
+ # Include[i] = p.within(MyPoly)
839
+
840
+ # YayNay = Include.reshape(X_new.shape)
841
+ # x_new = X_new[np.where(YayNay == True)].flatten()
842
+ # y_new = Y_new[np.where(YayNay == True)].flatten()
843
+ # Res_min = np.nanmin(z_new(x_new, y_new, grid = False))
844
+ # P_min = x_new[np.where(z_new(x_new, y_new, grid = False) == Res_min)]
845
+ # H2O_min = y_new[np.where(z_new(x_new, y_new, grid = False) == Res_min)]
846
+ # if Res_min < T_cut_C:
847
+ # Test = 'Pass'
848
+ # else:
849
+ # Test = 'Fail'
850
+
851
+ # CurveMin[m] = {'Res_min': Res_min, 'P_min': P_min[0], 'H2O_min': H2O_min[0], 'z_new': z_new, 'test': Test}
852
+ # else:
853
+ # CurveMin[m] = {'Res_min': np.nan, 'P_min': np.nan, 'H2O_min': np.nan, 'z_new': np.nan, 'test': 'Fail'}
854
+
855
+ # Results['CurveMin'] = CurveMin
856
+
857
+ # if H2O_Liq is not None and Fe3Fet_Liq is not None:
858
+ # if '3 Phase Saturation' in list(Results.keys()):
859
+ # X, Y = np.meshgrid(P_bar, H2O_Liq)
860
+ # Y = Results['H2O_melt'][:,0,:].copy()
861
+
862
+ # Minimum = {'3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]}
863
+ # CurveMin = {}
864
+ # for m in Minimum:
865
+ # Res_min_save = 50
866
+ # for w in range(len(Fe3Fet_Liq)):
867
+ # if len(Results[m][:,w,:][~np.isnan(Results[m][:,w,:])]) > 4:
868
+ # Res = Results[m][:,w,:].copy()
869
+ # Res[np.where(Res > T_cut_C*2)] = np.nan
870
+ # for i in range(len(H2O_Liq)):
871
+ # Res[i, :][np.where(Results['H2O_melt'][i,w,:] < 0.99*np.nanmax(Results['H2O_melt'][i,w,:]))] = np.nan
872
+
873
+ # A = Res.copy()
874
+ # X, Y = np.meshgrid(P_bar, H2O_Liq)
875
+ # Y = Results['H2O_melt'][:,w,:].copy()
876
+
877
+ # try:
878
+ # z_new = interpolate.SmoothBivariateSpline(X[np.where(~np.isnan(A) & ~np.isnan(Y))], Y[np.where(~np.isnan(A) & ~np.isnan(Y))], A[np.where(~np.isnan(A) & ~np.isnan(Y))], kx = 3, ky = 3)
879
+ # except:
880
+ # z_new = interpolate.SmoothBivariateSpline(X[np.where(~np.isnan(A) & ~np.isnan(Y))], Y[np.where(~np.isnan(A) & ~np.isnan(Y))], A[np.where(~np.isnan(A) & ~np.isnan(Y))], kx = 2, ky = 2)
881
+
882
+ # H2O_new = np.linspace(Y[np.where(Res == np.nanmin(Res))] - (H2O_Liq[1]-H2O_Liq[0]),
883
+ # Y[np.where(Res == np.nanmin(Res))] + (H2O_Liq[1]-H2O_Liq[0]), 20)
884
+ # P_new = np.linspace(X[np.where(Res == np.nanmin(Res))] - (P_bar[1]-P_bar[0]),
885
+ # X[np.where(Res == np.nanmin(Res))] + (P_bar[1]-P_bar[0]), 20)
886
+
887
+ # X_new, Y_new = np.meshgrid(P_new, H2O_new)
888
+ # x = X[~np.isnan(A)].flatten()
889
+ # y = Y[~np.isnan(A)].flatten()
890
+
891
+ # MyPoly = MultiPoint(list(zip(x, y))).convex_hull
892
+
893
+ # points = list(zip(X_new.flatten(), Y_new.flatten()))
894
+ # Include = np.zeros(len(X_new.flatten()))
895
+ # for i in range(len(points)):
896
+ # p = Point(points[i])
897
+ # Include[i] = p.within(MyPoly)
898
+
899
+ # YayNay = Include.reshape(X_new.shape)
900
+ # x_new = X_new[np.where(YayNay == True)].flatten()
901
+ # y_new = Y_new[np.where(YayNay == True)].flatten()
902
+ # Res_min = np.nanmin(z_new(x_new, y_new, grid = False))
903
+ # P_min = x_new[np.where(z_new(x_new, y_new, grid = False) == Res_min)]
904
+ # H2O_min = y_new[np.where(z_new(x_new, y_new, grid = False) == Res_min)]
905
+ # if Res_min < T_cut_C:
906
+ # Test = 'Pass'
907
+ # else:
908
+ # Test = 'Fail'
909
+
910
+ # if Res_min < Res_min_save:
911
+ # Res_min_save = Res_min
912
+ # CurveMin[m] = {'Res_min': Res_min, 'P_min': P_min[0], 'H2O_min': H2O_min[0], 'z_new': z_new, 'Fe3Fet_Liq': Fe3Fet_Liq[w], 'test': Test}
913
+
914
+ # Results['CurveMin'] = CurveMin
915
+
916
+
917
+ # return Results
918
+
919
+ # def polymin(P_bar = None, Res = None):
920
+ # '''
921
+ # Finds the minimum residual temperature using a 2nd degree polynomial.
922
+ # '''
923
+ # arr = np.sort(Res)
924
+ # Ind = np.where(Res == arr[0])[0][0]
925
+
926
+ # if P_bar[Ind] == np.nanmax(P_bar):
927
+ # p = np.array([0,1,0])
928
+ # p_min = np.array([P_bar[Ind]])
929
+ # elif P_bar[Ind] == np.nanmin(P_bar):
930
+ # p = np.array([0,1,0])
931
+ # p_min = np.array([P_bar[Ind]])
932
+ # else:
933
+ # p = np.polyfit(P_bar[np.array([Ind-1, Ind, Ind+1])],Res[np.array([Ind-1, Ind, Ind+1])],2)
934
+
935
+ # x = np.linspace(np.nanmin(P_bar),np.nanmax(P_bar),501)
936
+ # y = p[0]*x**2 + p[1]*x + p[2]
937
+
938
+ # p_min = x[np.where(y == np.nanmin(y))]
939
+
940
+ # return p, p_min
941
+
942
+ # def satTemperature(q, index, *, Model = None, comp = None, phases = None, T_initial_C = None, T_step_C = None, dt_C = None, P_bar = None, H2O_Liq = None, fO2_buffer = None, fO2_offset = None):
943
+ # '''
944
+ # Crystallisation calculations to be performed in parallel. Calculations may be either isobaric or isochoric.
945
+
946
+ # Parameters:
947
+ # ----------
948
+ # q: Multiprocessing Queue instance
949
+ # Queue instance to record the output variables
950
+
951
+ # index: int
952
+ # index of the calculation in the master code (e.g., position within a for loop) to aid indexing results after calculations are complete.
953
+
954
+ # Model: string
955
+ # "MELTS" or "Holland". Dictates whether MELTS or MAGEMin calculations are performed. Default "MELTS".
956
+ # Version of melts can be specified "MELTSv1.0.2", "MELTSv1.1.0", "MELTSv1.2.0", or "pMELTS". Default "v.1.0.2".
957
+
958
+ # comp: Dict
959
+ # Initial compositon for calculations.
960
+
961
+ # T_initial_C: float
962
+ # Initial guess for the liquidus temperature.
963
+
964
+ # T_step_C: float
965
+ # The temperature drop at each step of the calculation.
966
+
967
+ # dt_C: float
968
+ # Total temperature drop allowed duringmodel runs.
969
+
970
+ # P_bar: float
971
+ # Specifies the pressure of calculation (bar).
972
+
973
+ # Returns:
974
+ # ----------
975
+ # Results: Dict
976
+ # Dict containing a series of floats that represent the saturation temperature and residual temperature for each calculation.
977
+
978
+ # index: int
979
+ # index of the calculation
980
+
981
+ # '''
982
+
983
+ # Results = {}
984
+ # if "MELTS" in Model:
985
+
986
+ # try:
987
+ # Results = phaseSat_MELTS(Model = Model, comp = comp, phases = phases, T_initial_C = T_initial_C, T_step_C = T_step_C, dt_C = dt_C, P_bar = P_bar, H2O_Liq = H2O_Liq, fO2_buffer = fO2_buffer, fO2_offset = fO2_offset)
988
+ # except:
989
+ # Results = {phases[0]: np.nan, phases[1]: np.nan, phases[2]: np.nan, 'T_Liq': np.nan, 'H2O_melt': np.nan}
990
+ # if len(phases) == 2:
991
+ # del Results[phases[2]]
992
+
993
+ # if len(phases) == 3:
994
+ # Res = ['3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]]
995
+ # for R in Res:
996
+ # Results[R] = np.nan
997
+
998
+ # if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]) and ~np.isnan(Results[phases[2]]):
999
+ # Results['3 Phase Saturation'] = np.nanmax(np.array([abs(Results[phases[0]] - Results[phases[1]]), abs(Results[phases[0]] - Results[phases[2]]), abs(Results[phases[1]] - Results[phases[2]])]))
1000
+ # if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]):
1001
+ # Results[phases[0] + ' - ' + phases[1]] = abs(Results[phases[0]] - Results[phases[1]])
1002
+ # if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[2]]):
1003
+ # Results[phases[0] + ' - ' + phases[2]] = abs(Results[phases[0]] - Results[phases[2]])
1004
+ # if ~np.isnan(Results[phases[1]]) and ~np.isnan(Results[phases[2]]):
1005
+ # Results[phases[1] + ' - ' + phases[2]] = abs(Results[phases[1]] - Results[phases[2]])
1006
+ # else:
1007
+ # Results[phases[0] + ' - ' + phases[1]] = np.nan
1008
+ # if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]):
1009
+ # Results[phases[0] + ' - ' + phases[1]] = abs(Results[phases[0]] - Results[phases[1]])
1010
+
1011
+ # q.put([Results, index])
1012
+ # return
1013
+
1014
+ # if Model == "Holland":
1015
+ # import pyMAGEMINcalc as MM
1016
+ # Results = {phases[0]: np.nan, phases[1]: np.nan, phases[2]: np.nan, 'T_Liq': np.nan, 'H2O_melt': np.nan}
1017
+ # if len(phases) == 2:
1018
+ # del Results[phases[2]]
1019
+
1020
+ # #try:
1021
+ # Result = MM.path(comp = comp, phases = phases, T_min_C = dt_C, dt_C = T_step_C, P_bar = P_bar, find_liquidus = True)
1022
+ # #Result = stich(Result, Model = Model)
1023
+
1024
+ # for i in range(len(phases)):
1025
+ # try:
1026
+ # Results[phases[i]] = Result['Conditions']['T_C'][Result[phases[i]+'_prop']['mass'] > 0.0].values[0]
1027
+ # print(Results[phases[i]])
1028
+ # except:
1029
+ # Results[phases[i]] = np.nan
1030
+
1031
+ # Results['T_Liq'] = Result['Conditions']['T_C'].values[0]
1032
+ # Results['H2O_melt'] = Result['liq']['H2O'].values[0]
1033
+
1034
+ # if len(phases) == 3:
1035
+ # Res = ['3 Phase Saturation', phases[0] + ' - ' + phases[1], phases[0] + ' - ' + phases[2], phases[1] + ' - ' + phases[2]]
1036
+ # for R in Res:
1037
+ # Results[R] = np.nan
1038
+
1039
+ # if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]) and ~np.isnan(Results[phases[2]]):
1040
+ # Results['3 Phase Saturation'] = np.nanmax(np.array([abs(Results[phases[0]] - Results[phases[1]]), abs(Results[phases[0]] - Results[phases[2]]), abs(Results[phases[1]] - Results[phases[2]])]))
1041
+ # if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]):
1042
+ # Results[phases[0] + ' - ' + phases[1]] = abs(Results[phases[0]] - Results[phases[1]])
1043
+ # if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[2]]):
1044
+ # Results[phases[0] + ' - ' + phases[2]] = abs(Results[phases[0]] - Results[phases[2]])
1045
+ # if ~np.isnan(Results[phases[1]]) and ~np.isnan(Results[phases[2]]):
1046
+ # Results[phases[1] + ' - ' + phases[2]] = abs(Results[phases[1]] - Results[phases[2]])
1047
+ # else:
1048
+ # Results[phases[0] + ' - ' + phases[1]] = np.nan
1049
+ # if ~np.isnan(Results[phases[0]]) and ~np.isnan(Results[phases[1]]):
1050
+ # Results[phases[0] + ' - ' + phases[1]] = abs(Results[phases[0]] - Results[phases[1]])
936
1051
 
937
- q.put([Results, index])
938
- #except:
939
- # q.put([Results, index])
940
- return
1052
+ # q.put([Results, index])
1053
+ # #except:
1054
+ # # q.put([Results, index])
1055
+ # return
941
1056
 
942
1057