Source code for Thermobar.two_pyroxene

import numpy as np
import matplotlib.pyplot as plt
from functools import partial
import inspect
import warnings as w
import numbers
import pandas as pd



from Thermobar.core import *

## Pressure equations for two pyroxenes


[docs] def P_Put2008_eq38(T=None, *, Na_Opx_cat_6ox, Al_IV_Opx_cat_6ox, Al_VI_Opx_cat_6ox, Ti_Opx_cat_6ox, Ca_Opx_cat_6ox, Cr_Opx_cat_6ox, Mg_Opx_cat_6ox, Fet_Opx_cat_6ox, Mn_Opx_cat_6ox, Ca_Cpx_cat_6ox, Fm2Si2O6, En_Opx, Di_Opx): ''' Two pyroxene barometer of Putirka (2008) Eq38. Calibrated on Mg#-rich systems (>0.75) :cite:`putirka2008thermometers` | SEE=+-3.7 kbar ''' Lindley_Fe3_Opx = (Na_Opx_cat_6ox + Al_IV_Opx_cat_6ox - Al_VI_Opx_cat_6ox - \ 2 * Ti_Opx_cat_6ox - Cr_Opx_cat_6ox) # This is cell FR Lindley_Fe3_Opx[Lindley_Fe3_Opx < 0] = 0 a_En_opx_mod = (((0.5 * Mg_Opx_cat_6ox / (0.5 * (Fet_Opx_cat_6ox - Lindley_Fe3_Opx) + 0.5 * Mg_Opx_cat_6ox + Na_Opx_cat_6ox +Ca_Opx_cat_6ox + Mn_Opx_cat_6ox))) * (0.5 * Mg_Opx_cat_6ox / (0.5 * Mg_Opx_cat_6ox + 0.5 * (Fet_Opx_cat_6ox - Lindley_Fe3_Opx) + Ti_Opx_cat_6ox + Al_VI_Opx_cat_6ox + Cr_Opx_cat_6ox + Lindley_Fe3_Opx))) Kf = Ca_Opx_cat_6ox / (1 - Ca_Cpx_cat_6ox) return (-279.8 + 293 * Al_VI_Opx_cat_6ox + 455 * Na_Opx_cat_6ox + 229 * Cr_Opx_cat_6ox + 519 * Fm2Si2O6 - 563 * En_Opx + 371 * Di_Opx + 327 * a_En_opx_mod + 1.19 / Kf)
[docs] def P_Put2008_eq39(T, *, Na_Opx_cat_6ox, Al_IV_Opx_cat_6ox, Al_VI_Opx_cat_6ox, Ti_Opx_cat_6ox, Cr_Opx_cat_6ox, Fet_Opx_cat_6ox, Mn_Opx_cat_6ox, Ca_Opx_cat_6ox, Mg_Opx_cat_6ox, Na_Cpx_cat_6ox, Al_IV_cat_6ox, Al_VI_cat_6ox, Ti_Cpx_cat_6ox, Ca_Cpx_cat_6ox, Mg_Cpx_cat_6ox, Mn_Cpx_cat_6ox, Fet_Cpx_cat_6ox, Cr_Cpx_cat_6ox, Fm2Si2O6, En_Opx, EnFs): ''' Two pyroxene barometer of Putirka (2008) Eq39. Similar to Eq38, but has a temperature term. :cite:`putirka2008thermometers` | SEE=+-2.8 kbar (Cpx Mg#>0.75) | SEE=+-3.2 kbar (all data) ''' Lindley_Fe3_Opx = Na_Opx_cat_6ox + Al_IV_Opx_cat_6ox - \ Al_VI_Opx_cat_6ox - 2 * Ti_Opx_cat_6ox - Cr_Opx_cat_6ox Lindley_Fe3_Opx[Lindley_Fe3_Opx < 0] = 0 a_En_opx_mod = (((0.5 * Mg_Opx_cat_6ox / (0.5 * (Fet_Opx_cat_6ox - Lindley_Fe3_Opx) + 0.5 * Mg_Opx_cat_6ox + Na_Opx_cat_6ox +Ca_Opx_cat_6ox + Mn_Opx_cat_6ox))) * (0.5 * Mg_Opx_cat_6ox / (0.5 * Mg_Opx_cat_6ox + 0.5 * (Fet_Opx_cat_6ox - Lindley_Fe3_Opx) + Ti_Opx_cat_6ox + Al_VI_Opx_cat_6ox + Cr_Opx_cat_6ox + Lindley_Fe3_Opx))) Lindley_Fe3_Cpx = Na_Cpx_cat_6ox + Al_IV_cat_6ox - \ Al_VI_cat_6ox - 2 * Ti_Cpx_cat_6ox - Cr_Cpx_cat_6ox Lindley_Fe3_Cpx[Lindley_Fe3_Cpx < 0] = 0 a_Di_cpx = Ca_Cpx_cat_6ox / (Ca_Cpx_cat_6ox + 0.5 * Mg_Cpx_cat_6ox + 0.5 * ( Fet_Cpx_cat_6ox - Lindley_Fe3_Cpx) + Mn_Cpx_cat_6ox + Na_Cpx_cat_6ox) Kf = Ca_Opx_cat_6ox / (1 - Ca_Cpx_cat_6ox) return (-94.25 + 0.045 * (T - 273.15) + 187.7 * Al_VI_Opx_cat_6ox + 246.8 * Fm2Si2O6 - 212.5 * En_Opx + 127.5 * a_En_opx_mod - 69.4 * EnFs - 133.9 * a_Di_cpx - 1.66 / Kf)
## Temperature equations for two pyroxenes
[docs] def T_Put2008_eq36(P, *, EnFs, Fm2Si2O6, Ca_Cpx_cat_6ox, CrCaTs, Mn_Opx_cat_6ox, Na_Opx_cat_6ox, En_Opx, Di_Opx): ''' Two-pyroxene thermometer of Putirka (2008) eq 36. Best for Cpx with Mg#>0.75, but calibrated using all. :cite:`putirka2008thermometers` SEE=+-45C for Cpx Mg#>0.75 SEE=+-56C for all data ''' return (273.15 + 10 ** 4 / (11.2 - 1.96 * np.log(EnFs.astype(float) / Fm2Si2O6.astype(float)) - 3.3 * Ca_Cpx_cat_6ox - 25.8 * CrCaTs + 33.2 * Mn_Opx_cat_6ox - 23.6 * Na_Opx_cat_6ox - 2.08 * En_Opx - 8.33 * Di_Opx - 0.05 * P))
[docs] def T_Put2008_eq37(P, *, EnFs, Di_Cpx, Fm2Si2O6, Mn_Opx_cat_6ox, FmAl2SiO6, Mg_Cpx_cat_6ox, Fet_Cpx_cat_6ox): ''' Two-pyroxene thermometer of Putirka (2008) eq 37. Calibrated on Cpx with Mg#>0.75 :cite:`putirka2008thermometers` SEE=+-38C for Cpx Mg#>0.75 SEE=+-60C for all data ''' return (273.15 + 10**4 / (13.4 - 3.4 * np.log(EnFs.astype(float) / Fm2Si2O6.astype(float)) + 5.59 * np.log(Mg_Cpx_cat_6ox.astype(float)) + 23.85 * Mn_Opx_cat_6ox +6.48 * FmAl2SiO6 - 2.38 * Di_Cpx - 0.044 * P - 8.8 * Mg_Cpx_cat_6ox / (Mg_Cpx_cat_6ox + Fet_Cpx_cat_6ox)))
[docs] def T_Brey1990(P, *, Fet_Cpx_cat_6ox, Ca_Cpx_cat_6ox, Mg_Cpx_cat_6ox, Na_Cpx_cat_6ox, Fet_Opx_cat_6ox, Mg_Opx_cat_6ox, Ca_Opx_cat_6ox, Na_Opx_cat_6ox): ''' Two-pyroxene thermometer of Brey and Kohler (1990). :cite:`brey1990geothermobarometry` SEE=+-50C for Cpx Mg#>0.75 SEE=+-70C for all data ''' return ((23664 + (24.9 + 126.3 * Fet_Cpx_cat_6ox / (Fet_Cpx_cat_6ox + Mg_Cpx_cat_6ox)) * P) / (13.38 + (np.log((1 - Ca_Cpx_cat_6ox.astype(float) /(1 - Na_Cpx_cat_6ox.astype(float))) / (1 - Ca_Opx_cat_6ox.astype(float) / (1 - Na_Opx_cat_6ox.astype(float)))))**2 + 11.59 * Fet_Opx_cat_6ox / (Fet_Opx_cat_6ox + Mg_Opx_cat_6ox)))
[docs] def T_Wood1973(P=None, *, Mg_Opx_cat_6ox, Ca_Opx_cat_6ox, Mn_Opx_cat_6ox, Fet_Opx_cat_6ox, Na_Opx_cat_6ox, Al_IV_Opx_cat_6ox, Al_VI_Opx_cat_6ox, Ti_Opx_cat_6ox, Cr_Opx_cat_6ox, Mg_Cpx_cat_6ox, Ca_Cpx_cat_6ox, Mn_Cpx_cat_6ox, Fet_Cpx_cat_6ox, Na_Cpx_cat_6ox, Al_IV_cat_6ox, Al_VI_cat_6ox, Ti_Cpx_cat_6ox, Cr_Cpx_cat_6ox): ''' Two-pyroxene thermometer of Wood and Banno (1973) :cite:`wood1973garnet` ''' # Opx parts Lindley_Fe3_Opx = Na_Opx_cat_6ox + Al_IV_Opx_cat_6ox - Al_VI_Opx_cat_6ox - \ 2 * Ti_Opx_cat_6ox - Cr_Opx_cat_6ox # This is cell FR Lindley_Fe3_Opx[Lindley_Fe3_Opx < 0] = 0 MgNo_WB_Opx = Mg_Opx_cat_6ox / \ (Mg_Opx_cat_6ox + (Fet_Opx_cat_6ox - Lindley_Fe3_Opx)) X_Mg_M2_Opx = (1 - Ca_Opx_cat_6ox - Na_Opx_cat_6ox - Mn_Opx_cat_6ox) * MgNo_WB_Opx # FL X_Fe_M2_Opx = (1 - Ca_Opx_cat_6ox - Na_Opx_cat_6ox - Mn_Opx_cat_6ox) * (1 - MgNo_WB_Opx) # FM X_Mg_M1_Opx = (1 - Lindley_Fe3_Opx - Al_VI_Opx_cat_6ox - Ti_Opx_cat_6ox - Cr_Opx_cat_6ox) * MgNo_WB_Opx # FJ X_Fe_M1_Opx = (1 - Lindley_Fe3_Opx - Al_VI_Opx_cat_6ox - Ti_Opx_cat_6ox - Cr_Opx_cat_6ox) * (1 - MgNo_WB_Opx) # FK MgNo_WB_Opx = Mg_Opx_cat_6ox / \ (Mg_Opx_cat_6ox + (Fet_Opx_cat_6ox - Lindley_Fe3_Opx)) a_opx_En = (X_Mg_M2_Opx / (X_Mg_M2_Opx + X_Fe_M2_Opx + Ca_Opx_cat_6ox + Na_Opx_cat_6ox + Mn_Opx_cat_6ox)) * \ (X_Mg_M1_Opx / (Lindley_Fe3_Opx + Ti_Opx_cat_6ox + Al_VI_Opx_cat_6ox + Cr_Opx_cat_6ox + X_Mg_M1_Opx + X_Fe_M1_Opx)) # Cpx parts Lindley_Fe3_Cpx = Na_Cpx_cat_6ox + Al_IV_cat_6ox - Al_VI_cat_6ox - \ 2 * Ti_Cpx_cat_6ox - Cr_Cpx_cat_6ox # This is cell FR Lindley_Fe3_Cpx[Lindley_Fe3_Cpx < 0] = 0 Fe2_WB_Cpx = Fet_Cpx_cat_6ox - Lindley_Fe3_Cpx MgNo_WB_Cpx = Mg_Cpx_cat_6ox / (Mg_Cpx_cat_6ox + Fe2_WB_Cpx) X_Mg_M2_Cpx = (1 - Ca_Cpx_cat_6ox - Na_Cpx_cat_6ox - Mn_Cpx_cat_6ox) * MgNo_WB_Cpx # FL X_Fe_M2_Cpx = (1 - Ca_Cpx_cat_6ox - Na_Cpx_cat_6ox - Mn_Cpx_cat_6ox) * (1 - MgNo_WB_Cpx) # FM X_Mg_M1_Cpx = (1 - Lindley_Fe3_Cpx - Al_VI_cat_6ox - Ti_Cpx_cat_6ox - Cr_Cpx_cat_6ox) * MgNo_WB_Cpx # FJ X_Fe_M1_Cpx = (1 - Lindley_Fe3_Cpx - Al_VI_cat_6ox - Ti_Cpx_cat_6ox - Cr_Cpx_cat_6ox) * (1 - MgNo_WB_Cpx) # FK a_cpx_En = (X_Mg_M2_Cpx / (X_Mg_M2_Cpx + X_Fe_M2_Cpx + Ca_Cpx_cat_6ox + Na_Cpx_cat_6ox + Mn_Cpx_cat_6ox)) * \ (X_Mg_M1_Cpx / (Lindley_Fe3_Cpx + Ti_Cpx_cat_6ox + Al_VI_cat_6ox + Cr_Cpx_cat_6ox + X_Mg_M1_Cpx + X_Fe_M1_Cpx)) return ((-10202 / (np.log(a_cpx_En.astype(float) / a_opx_En.astype(float)) - 7.65 * (1 - MgNo_WB_Opx) + 3.88 * (1 - MgNo_WB_Opx)**2 - 4.6)))
[docs] def T_Wells1977(P=None, *, Mg_Opx_cat_6ox, Ca_Opx_cat_6ox, Mn_Opx_cat_6ox, Fet_Opx_cat_6ox, Na_Opx_cat_6ox, Al_IV_Opx_cat_6ox, Al_VI_Opx_cat_6ox, Ti_Opx_cat_6ox, Cr_Opx_cat_6ox, Mg_Cpx_cat_6ox, Ca_Cpx_cat_6ox, Mn_Cpx_cat_6ox, Fet_Cpx_cat_6ox, Na_Cpx_cat_6ox, Al_IV_cat_6ox, Al_VI_cat_6ox, Ti_Cpx_cat_6ox, Cr_Cpx_cat_6ox): ''' Two-pyroxene thermometer of Wells 1977 :cite:`wells1977pyroxene` ''' # Opx parts Lindley_Fe3_Opx = Na_Opx_cat_6ox + Al_IV_Opx_cat_6ox - Al_VI_Opx_cat_6ox - \ 2 * Ti_Opx_cat_6ox - Cr_Opx_cat_6ox # This is cell FR Lindley_Fe3_Opx[Lindley_Fe3_Opx < 0] = 0 MgNo_WB_Opx = Mg_Opx_cat_6ox / \ (Mg_Opx_cat_6ox + (Fet_Opx_cat_6ox - Lindley_Fe3_Opx)) X_Mg_M2_Opx = (1 - Ca_Opx_cat_6ox - Na_Opx_cat_6ox - Mn_Opx_cat_6ox) * MgNo_WB_Opx # FL X_Fe_M2_Opx = (1 - Ca_Opx_cat_6ox - Na_Opx_cat_6ox - Mn_Opx_cat_6ox) * (1 - MgNo_WB_Opx) # FM X_Mg_M1_Opx = (1 - Lindley_Fe3_Opx - Al_VI_Opx_cat_6ox - Ti_Opx_cat_6ox - Cr_Opx_cat_6ox) * MgNo_WB_Opx # FJ X_Fe_M1_Opx = (1 - Lindley_Fe3_Opx - Al_VI_Opx_cat_6ox - Ti_Opx_cat_6ox - Cr_Opx_cat_6ox) * (1 - MgNo_WB_Opx) # FK MgNo_WB_Opx = Mg_Opx_cat_6ox / \ (Mg_Opx_cat_6ox + (Fet_Opx_cat_6ox - Lindley_Fe3_Opx)) a_opx_En = (X_Mg_M2_Opx / (X_Mg_M2_Opx + X_Fe_M2_Opx + Ca_Opx_cat_6ox + Na_Opx_cat_6ox + Mn_Opx_cat_6ox)) * \ (X_Mg_M1_Opx / (Lindley_Fe3_Opx + Ti_Opx_cat_6ox + Al_VI_Opx_cat_6ox + Cr_Opx_cat_6ox + X_Mg_M1_Opx + X_Fe_M1_Opx)) # Cpx parts Lindley_Fe3_Cpx = Na_Cpx_cat_6ox + Al_IV_cat_6ox - Al_VI_cat_6ox - \ 2 * Ti_Cpx_cat_6ox - Cr_Cpx_cat_6ox # This is cell FR Lindley_Fe3_Cpx[Lindley_Fe3_Cpx < 0] = 0 Fe2_WB_Cpx = Fet_Cpx_cat_6ox - Lindley_Fe3_Cpx MgNo_WB_Cpx = Mg_Cpx_cat_6ox / (Mg_Cpx_cat_6ox + Fe2_WB_Cpx) X_Mg_M2_Cpx = (1 - Ca_Cpx_cat_6ox - Na_Cpx_cat_6ox - Mn_Cpx_cat_6ox) * MgNo_WB_Cpx # FL X_Fe_M2_Cpx = (1 - Ca_Cpx_cat_6ox - Na_Cpx_cat_6ox - Mn_Cpx_cat_6ox) * (1 - MgNo_WB_Cpx) # FM X_Mg_M1_Cpx = (1 - Lindley_Fe3_Cpx - Al_VI_cat_6ox - Ti_Cpx_cat_6ox - Cr_Cpx_cat_6ox) * MgNo_WB_Cpx # FJ X_Fe_M1_Cpx = (1 - Lindley_Fe3_Cpx - Al_VI_cat_6ox - Ti_Cpx_cat_6ox - Cr_Cpx_cat_6ox) * (1 - MgNo_WB_Cpx) # FK a_cpx_En = (X_Mg_M2_Cpx / (X_Mg_M2_Cpx + X_Fe_M2_Cpx + Ca_Cpx_cat_6ox + Na_Cpx_cat_6ox + Mn_Cpx_cat_6ox)) * \ (X_Mg_M1_Cpx / (Lindley_Fe3_Cpx + Ti_Cpx_cat_6ox + Al_VI_cat_6ox + Cr_Cpx_cat_6ox + X_Mg_M1_Cpx + X_Fe_M1_Cpx)) return ((7341 / (3.355 + 2.44 * (1 - MgNo_WB_Opx) - np.log(a_cpx_En.astype(float) / a_opx_En.astype(float)))))
## Function for calculating Cpx-Opx pressure Cpx_Opx_P_funcs = {P_Put2008_eq38, P_Put2008_eq39} # put on outside Cpx_Opx_P_funcs_by_name = {p.__name__: p for p in Cpx_Opx_P_funcs}
[docs] def calculate_cpx_opx_press(*, cpx_comps=None, opx_comps=None, Two_Px_Match=None, equationP=None, eq_tests=False, T=None): ''' calculates pressure in kbar for Opx-Cpx pairs The function requires inputs of cpx_comps and opx_comps, or input of a combined dataframe of cpx-opx compositions (this is used for the calculate_cpx_opx_press_temp_matching function) Parameters ----------- cpx_comps: pandas.DataFrame Clinopyroxene compositions with column headings SiO2_Cpx, MgO_Cpx etc. opx_comps: pandas.DataFrame Opx compositions with column headings SiO2_Opx, MgO_Opx etc. Two_Px_Match: pandas.DataFrame Combined Cpx-Opx compositions instead of separate dataframes. Used for calculate Cpx_Opx_press_temp_matching function. equationP: str Choose from: | P_Put2008_eq38 (T-independent) | P_Put2008_eq39 (T-dependent) T: float, int, pd.Series, str ("Solve") Temperature in Kelvin to perform calculations at. Only needed for T-sensitive barometers. If T="Solve", returns a partial function. Else, enter an integer, float, or panda series. eq_tests: bool If False, just returns temperature in K (default) as a panda series. If True, returns pressure in kbar, Kd Fe-Mg for opx-cpx, and the user-entered cpx and opx comps as a panda dataframe. Returns ------- If eq_tests is False pandas.Series: Pressure in kbar If eq_tests is True pandas.DataFrame: Pressure in kbar + Kd-Fe-Mg + cpx+opx comps ''' try: func = Cpx_Opx_P_funcs_by_name[equationP] except KeyError: raise ValueError(f'{equationP} is not a valid equation') from None sig=inspect.signature(func) if sig.parameters['T'].default is not None: if T is None: raise ValueError(f'{equationP} requires you to enter T, or specify T="Solve"') # else: # if T is not None: # print('Youve selected a T-independent function') if isinstance(T, pd.Series): if cpx_comps is not None: if len(T) != len(cpx_comps): raise ValueError('The panda series entered for temperature isnt the' ' same length as the dataframe of Cpx compositions') if Two_Px_Match is None: if len(opx_comps)!=len(cpx_comps): raise ValueError('Opx comps need to be same length as Cpx comps. use the _matching function calculate_cpx_opx_press_temp_matching() instead if you want to consider all pairs') if Two_Px_Match is not None: two_pyx = Two_Px_Match if cpx_comps is not None: cpx_components = calculate_clinopyroxene_components(cpx_comps=cpx_comps) opx_components = calculate_orthopyroxene_components(opx_comps=opx_comps) two_pyx = pd.concat([cpx_components, opx_components], axis=1) kwargs = {name: two_pyx[name] for name, p in sig.parameters.items() if p.kind == inspect.Parameter.KEYWORD_ONLY} if isinstance(T, str) or T is None: if T == "Solve": P_kbar = partial(func, **kwargs) if T is None: P_kbar=func(**kwargs) else: P_kbar=func(T, **kwargs) if eq_tests is False: return P_kbar else: two_pyx = calculate_cpx_opx_eq_tests( cpx_comps=cpx_comps, opx_comps=opx_comps) two_pyx.insert(0, "P_kbar_calc", P_kbar) two_pyx.insert(2, "Equation Choice (P)", str(equationP)) two_pyx.replace([np.inf, -np.inf], np.nan, inplace=True) return two_pyx
#--------------------Function for solving for temperature for two pyroxenes-----------------------------------------------------# Cpx_Opx_T_funcs = {T_Put2008_eq36, T_Brey1990, T_Put2008_eq37, T_Wood1973, T_Wells1977} # put on outside Cpx_Opx_T_funcs_by_name = {p.__name__: p for p in Cpx_Opx_T_funcs}
[docs] def calculate_cpx_opx_temp(*, cpx_comps=None, opx_comps=None, Two_Px_Match=None, equationT=None, P=None, eq_tests=False): ''' calculates temperature in Kelvin for Opx-Cpx pairs. The function requires inputs of cpx_comps and opx_comps, or input of a combined dataframe of cpx-opx compositions (this is used for the calculate_cpx_opx_press_temp_matching function). Parameters ------------ cpx_comps: pandas.DataFrame Cpx compositions with column headings SiO2_Cpx, MgO_Cpx etc. opx_comps: pandas.DataFrame Opx compositions with column headings SiO2_Opx, MgO_Opx etc. Two_Px_Match: pandas.DataFrame Combined Cpx-Opx compositions. Used for "melt match" functionality. equationT: str Choose from: | T_Put2008_Eq36 (P-dependent) | T_Put2008_Eq37 (P-dependent) | T_Brey1990 (P-dependent) | T_Wood1973 (P-independent) | T_Wells1977 (P-independent) P: int, float, pandas.Series, str ("Solve") Pressure in kbar to perform calculations at. Can enter float or int to use same P for all calculations If "Solve", returns partial if function is P-dependent eq_tests: bool If False, just returns pressure in kbar (default) as a panda series If True, returns pressure in kbar, Kd Fe-Mg for opx-cpx, and the user-entered cpx and opx comps as a panda dataframe. Returns ------- If eq_tests is False pandas.Series: Temperature in K If eq_tests is True pandas.DataFrame: Temperature in K + Kd-Fe-Mg + cpx + opx comps ''' if Two_Px_Match is None: if len(opx_comps)!=len(cpx_comps): raise ValueError('Opx comps need to be same length as Cpx comps. use the _matching function calculate_cpx_opx_press_temp_matching() instead if you want to consider all pairs') try: func = Cpx_Opx_T_funcs_by_name[equationT] except KeyError: raise ValueError(f'{equationT} is not a valid equation') from None sig=inspect.signature(func) if sig.parameters['P'].default is not None: if P is None: raise ValueError(f'{equationT} requires you to enter P, or specify P="Solve"') else: if P is not None: print('Youve selected a P-independent function') if isinstance(P, pd.Series): if cpx_comps is not None: if len(P) != len(cpx_comps): raise ValueError('The panda series entered for Pressure isnt the same length as the dataframe of cpx compositions') if Two_Px_Match is not None: two_pyx = Two_Px_Match if cpx_comps is not None: cpx_components = calculate_clinopyroxene_components(cpx_comps=cpx_comps) opx_components = calculate_orthopyroxene_components(opx_comps=opx_comps) two_pyx = pd.concat([cpx_components, opx_components], axis=1) kwargs = {name: two_pyx[name] for name, p in sig.parameters.items() if p.kind == inspect.Parameter.KEYWORD_ONLY} if isinstance(P, str) or P is None: if P == "Solve": T_K = partial(func, **kwargs) if P is None: T_K=func(**kwargs) else: T_K=func(P, **kwargs) if eq_tests is False: if isinstance(T_K, partial): return T_K else: T_K_is_bad = (T_K == 0) | (T_K == 273.15) | (T_K == -np.inf) | (T_K == np.inf) T_K[T_K_is_bad] = np.nan return T_K else: two_pyx = calculate_cpx_opx_eq_tests( cpx_comps=cpx_comps, opx_comps=opx_comps) two_pyx.insert(0, "T_K_calc", T_K) two_pyx.insert(1, "Equation Choice (T)", str(equationT)) return two_pyx
## Iterative calculations of P and T
[docs] def calculate_cpx_opx_press_temp(*, cpx_comps=None, opx_comps=None, Two_Px_Match=None, equationP=None, equationT=None, iterations=30, T_K_guess=1300, eq_tests=False): ''' Solves simultaneous equations for temperature and pressure using clinopyroxene-orthopyroxene thermometers and barometers. The function requires inputs of cpx_comps and opx_comps, or input of a combined dataframe of cpx-opx compositions (this is used for the calculate_cpx_opx_press_temp_matching function). Parameters ----------- opx_comps: pandas.DataFrame Orthopyroxene compositions with column headings SiO2_Opx, MgO_Opx etc. cpx_comps: pandas.DataFrame Cpx compositions with column headings SiO2_Cpx, MgO_Cpx etc. meltmatch: pandas.DataFrame Combined dataframe of Opx-Cpx compositions (headings SiO2_Cpx, SiO2_Opx etc.). Used for calculate cpx_opx_press_temp_matching function. equationP: str Choose from: | P_Put2008_eq38 (T-independent) | P_Put2008_eq39 (T-dependent) equationT: str Choose from: | T_Put2008_Eq36 (P-dependent) | T_Put2008_Eq37 (P-dependent) | T_Brey1990 (P-dependent) | T_Wood1973 (P-independent) | T_Wells1977 (P-independent) Optional: iterations: int, Default = 20 Number of iterations used to converge to solution T_K_guess: int or float. Default = 1300K Initial guess of temperature to start iterations at eq_tests: bool If False, just returns pressure (default) as a panda series If True, returns pressure, Values of Eq tests, as well as user-entered opx and cpx comps and components. Returns ------- If eq_tests is False pandas.DataFrame: Temperature in Kelvin, pressure in kbar If eq_tests is True pandas.DataFrame: Temperature in Kelvin, pressure in kbar, eq Tests + opx+cpx comps + components ''' # Gives users flexibility to reduce or increase iterations if Two_Px_Match is None: if len(opx_comps)!=len(cpx_comps): raise ValueError('Opx comps need to be same length as Cpx comps. use the _matching function calculate_cpx_opx_press_temp_matching() instead if you want to consider all pairs') if Two_Px_Match is None: T_func = calculate_cpx_opx_temp( cpx_comps=cpx_comps, opx_comps=opx_comps, equationT=equationT, P="Solve") P_func = calculate_cpx_opx_press( cpx_comps=cpx_comps, opx_comps=opx_comps, equationP=equationP, T="Solve") if Two_Px_Match is not None: T_func = calculate_cpx_opx_temp( Two_Px_Match=Two_Px_Match, equationT=equationT, P="Solve") P_func = calculate_cpx_opx_press( Two_Px_Match=Two_Px_Match, equationP=equationP, T="Solve") # This bit checks if temperature is already a series - e.g., equations # with no pressure dependence if isinstance(T_func, pd.Series) and isinstance(P_func, pd.Series): P_guess = P_func T_K_guess = T_func if isinstance(T_func, pd.Series) and isinstance(P_func, partial): P_guess = P_func(T_func) T_K_guess = T_func if isinstance(P_func, pd.Series) and isinstance(T_func, partial): T_K_guess = T_func(P_func) P_guess = P_func if isinstance(P_func, partial) and isinstance(T_func, partial): # Gives users flexibility to add a different guess temperature count=0 for _ in range(iterations): P_guess = P_func(T_K_guess) T_K_guess = T_func(P_guess) if count==iterations-2: # On the second last step, save the pressure P_out_loop=P_guess.values T_out_loop=T_K_guess.values count=count+1 DeltaP=P_guess-P_out_loop DeltaT=T_K_guess-T_out_loop else: DeltaP=0 DeltaT=0 T_K_guess_is_bad = (T_K_guess == 0) | (T_K_guess == 273.15) | (T_K_guess == -np.inf) | (T_K_guess == np.inf) T_K_guess[T_K_guess_is_bad] = np.nan P_guess[T_K_guess_is_bad] = np.nan if eq_tests is False: PT_out = pd.DataFrame(data={'P_kbar_calc': P_guess, 'T_K_calc': T_K_guess, 'Delta_P_kbar_Iter': DeltaP, 'Delta_T_K_Iter': DeltaT}) return PT_out if eq_tests is True: two_pyx = calculate_cpx_opx_eq_tests( cpx_comps=cpx_comps, opx_comps=opx_comps) DeltaKd_HighT= np.abs( 1.09 - two_pyx['Kd_Fe_Mg_Cpx_Opx']) DeltaKd_SubSol= np.abs( 0.7 - two_pyx['Kd_Fe_Mg_Cpx_Opx']) two_pyx.insert(0, "T_K_calc", T_K_guess) two_pyx.insert(1, "P_kbar_calc", P_guess) two_pyx.insert(2, "Equation Choice (T)", str(equationT)) two_pyx.insert(3, "Equation Choice (P)", str(equationP)) two_pyx.insert(4, 'Delta Kd High T', DeltaKd_HighT) two_pyx.insert(6, 'Delta Kd Low T', DeltaKd_SubSol) two_pyx.replace([np.inf, -np.inf], np.nan, inplace=True) return two_pyx
# return P_func ## Two pyroxene matching
[docs] def calculate_cpx_opx_press_temp_matching(*, opx_comps, cpx_comps, equationT=None, equationP=None, Kd_Match=None, Kd_Err=None, Cpx_Quality=False, Opx_Quality=False, P=None, T=None, return_all_pairs=False, iterations=30): ''' Evaluates all possible Cpx-Opx pairs for user supplied dataframes of opx and cpx comps (can be different lengths). Returns P (kbar) and T (K) for those in Kd Fe-Mg equilibrium. Parameters ----------- opx_comps: pandas.DataFrame Panda DataFrame of opx compositions with column headings SiO2_Opx etc. cpx_comps: pandas.DataFrame Panda DataFrame of cpx compositions with column headings SiO2_Cpx etc. equationP: str Choose from: | P_Put2008_eq38 (T-independent) | P_Put2008_eq39 (T-dependent) equationT: str Choose from: | T_Put2008_Eq36 (P-dependent) | T_Put2008_Eq37 (P-dependent) | T_Brey1990 (P-dependent) | T_Wood1973 (P-independent) | T_Wells1977 (P-independent) P, T: float, int, pandas.Series. Instead of specifying equationP or equationT P is pressure in kbar to perform calculations at if equationP not specified T is temperature in Kelvin to perform calculations at if equationT not specified Kd_Match: str | If None, returns all cpx-opx pairs. | If "HighTemp", returns all cpxs-opxs within Kd cpx-opx=1.09+-0.14 suggested by Putirka (2008) | If "Subsolidus" returns all cpxs-opxs within Kd cpx-opx=0.7+-0.2 suggested by Putirka (2008) | If int or float, also need to specify Kd_Err. Returns all matches within Kd_Match +- Kd_Err Or specify return_all_pairs=True to get all matches Kd_Err: float or int (defaults given in Kd_Match) Optional input to change defaults. Returns all cpx-opx pairs within Kd_Match+-Kd_Err Cpx_Quality: bool Default False, no filter If True, filters out clinopyroxenes with cation sums outside of 4.02-3.99 (after Neave et al. 2017) Opx_Quality: bool Default False, no filter If True, filters out orthopyroxenes with cation sums outside of 4.02-3.99 Returns ---------- Dict with keys: 'Av_PTs_per_Cpx', 'All_PTs'. 'Av_PTs_perCpx': Average P and T for each cpx, e.g., if cpx1 matches Opx1, Opx4, Opx6, Opx10, returns mean and 1 sigma for each cpx. 'All_PTs': Returns output parameters for all matches, e.g, cpx1-opx1, cpx1-opx4 without any averaging. ''' if (Kd_Err is None and isinstance(Kd_Match, int)) or (Kd_Err is None and isinstance(Kd_Match, float)): raise Exception( 'You have entered a numerical value for Kd_Match, but have not' 'specified a Kd_Err to accept matches within Kd_Match+-Kd_Err') # calculating Cpx and opx components. Do before duplication to save # computation time myCPXs1_concat = calculate_clinopyroxene_components(cpx_comps=cpx_comps) myOPXs1_concat = calculate_orthopyroxene_components(opx_comps=opx_comps) # Adding an ID label to help with melt-cpx rematching later myCPXs1_concat['ID_CPX'] = myCPXs1_concat.index myOPXs1_concat['ID_OPX'] = myOPXs1_concat.index # Carry Sample_ID from input if present, otherwise use index if "Sample_ID_Cpx" in cpx_comps.columns: myCPXs1_concat['Sample_ID_Cpx'] = cpx_comps['Sample_ID_Cpx'].values elif "Sample_ID_Cpx" not in myCPXs1_concat.columns: myCPXs1_concat['Sample_ID_Cpx'] = myCPXs1_concat.index if "Sample_ID_Opx" in opx_comps.columns: myOPXs1_concat['Sample_ID_Opx'] = opx_comps['Sample_ID_Opx'].values elif "Sample_ID_Opx" not in myOPXs1_concat.columns: myOPXs1_concat['Sample_ID_Opx'] = myOPXs1_concat.index # Duplicate cpxs and opxs so end up with panda of all possible opx-cpx # matches # This duplicates CPXs, repeats cpx1-cpx1*N, cpx2-cpx2*N etc. # DupCPXs = pd.DataFrame(np.repeat(myCPXs1_concat.values, np.shape( # myOPXs1_concat)[0], axis=0)) # .astype('float64') # causes an issue in pandas3: # This duplicates CPXs properly without stripping types or repeating the wrong rows DupCPXs = myCPXs1_concat.loc[myCPXs1_concat.index.repeat(len(myOPXs1_concat))].reset_index(drop=True) DupCPXs.columns = myCPXs1_concat.columns # This duplicates opxs like opx1-opx2-opx3 for cpx1, opx1-opx2-opx3 for # cpx2 etc. DupOPXs = pd.concat([myOPXs1_concat] * np.shape(myCPXs1_concat)[0]).reset_index(drop=True) # Combines these merged opx and cpx dataframes Combo_opxs_cpxs = pd.concat([DupOPXs, DupCPXs], axis=1) Combo_opxs_cpxs_1 = Combo_opxs_cpxs.copy() LenCombo = str(np.shape(Combo_opxs_cpxs)[0]) LenCpx=len(cpx_comps) LenOpx=len(opx_comps) print("Considering N=" + str(LenCpx) + " Cpx & N=" + str(LenOpx) +" Opx, which is a total of N="+ str(LenCombo) + " Cpx-Opx pairs, be patient if this is >>1 million!") # calculate Kd for these pairs En = (Combo_opxs_cpxs.Fm2Si2O6 * (Combo_opxs_cpxs.Mg_Opx_cat_6ox / (Combo_opxs_cpxs.Mg_Opx_cat_6ox +Combo_opxs_cpxs.Fet_Cpx_cat_6ox + Combo_opxs_cpxs.Mn_Cpx_cat_6ox))) Combo_opxs_cpxs['Kd_Fe_Mg_Cpx_Opx'] = ((Combo_opxs_cpxs['Fet_Cpx_cat_6ox'] / Combo_opxs_cpxs['Mg_Cpx_cat_6ox'])) / (Combo_opxs_cpxs['Fet_Opx_cat_6ox'] / Combo_opxs_cpxs['Mg_Opx_cat_6ox']) Combo_opxs_cpxs['Kd_Fe_Mg_Cpx_Opx'] = Combo_opxs_cpxs['Kd_Fe_Mg_Cpx_Opx'].astype(float) if Kd_Match == "Subsolidus": #print('made it here') Combo_opxs_cpxs = Combo_opxs_cpxs.copy() Combo_opxs_cpxs['Delta_Kd_Fe_Mg_Cpx_Opx'] = np.abs( 0.7 - Combo_opxs_cpxs['Kd_Fe_Mg_Cpx_Opx']) Combo_opxs_cpxs_1 = Combo_opxs_cpxs.loc[np.abs( Combo_opxs_cpxs['Delta_Kd_Fe_Mg_Cpx_Opx']) < 0.2].copy() # +- 0.2 suggested by Putirka spreadsheet #print(len(Combo_opxs_cpxs_1)) if Kd_Match == "HighTemp": Combo_opxs_cpxs['Delta_Kd_Fe_Mg_Cpx_Opx'] = np.abs( 1.09 - Combo_opxs_cpxs['Kd_Fe_Mg_Cpx_Opx']) # +- 0.14 suggested by Putirka spreadsheet Combo_opxs_cpxs_1 = Combo_opxs_cpxs.loc[np.abs( Combo_opxs_cpxs['Delta_Kd_Fe_Mg_Cpx_Opx']) < 0.14] if isinstance(Kd_Match, int) or isinstance( Kd_Match, float) and Kd_Err is not None: Combo_opxs_cpxs['Delta_Kd_Fe_Mg_Cpx_Opx'] = np.abs( Kd_Match - Combo_opxs_cpxs['Kd_Fe_Mg_Cpx_Opx']) Combo_opxs_cpxs_1 = Combo_opxs_cpxs.loc[np.abs( Combo_opxs_cpxs['Delta_Kd_Fe_Mg_Cpx_Opx']) < Kd_Err] # +- 0.14 suggested by Putirka if return_all_pairs is True: Combo_opxs_cpxs_1 = Combo_opxs_cpxs.copy() print('No Kd selected, all matches are shown') if return_all_pairs is False and Kd_Match is None: raise Exception('You havent specified what Kd filter you want. Either enter Kd_Match= "Subsolidus", "HighTemp", or Kd_Match=value and Kd_Err=val, or return_all_pairs=True') if len(Combo_opxs_cpxs_1) == 0: raise Exception('No matches found to the choosen Kd criteria.') Combo_opxs_cpxs_2 = Combo_opxs_cpxs_1.copy() # Uses neave method, filters out cation summs not between 4.02 and 3.99 if Cpx_Quality is True and Opx_Quality is False: Combo_opxs_cpxs_2 = Combo_opxs_cpxs_1.loc[(Combo_opxs_cpxs_1['Cation_Sum_Cpx'] < 4.02) & ( Combo_opxs_cpxs_1['Cation_Sum_Cpx'] > 3.99)] if Cpx_Quality is False and Opx_Quality is True: Combo_opxs_cpxs_2 = Combo_opxs_cpxs_1.loc[(Combo_opxs_cpxs_1['Cation_Sum_Opx'] < 4.02) & ( Combo_opxs_cpxs_1['Cation_Sum_Opx'] > 3.99)] if Cpx_Quality is True and Opx_Quality is True: Combo_opxs_cpxs_2 = Combo_opxs_cpxs1.loc[(Combo_opxs_cpxs_1['Cation_Sum_Opx'] < 4.02) & (Combo_opxs_cpxs_1['Cation_Sum_Opx'] > 3.99) & (Combo_opxs_cpxs_1['Cation_Sum_Cpx'] < 4.02) & (Combo_opxs_cpxs_1['Cation_Sum_Cpx'] > 3.99)] # Combo_opxs_cpxs_names= Combo_opxs_cpxs.copy() # Making a copy to keep # the names if len(Combo_opxs_cpxs_2) == 0: raise Exception('No matches found after Kd and quality filter.') Combo_opxs_cpxs_2_names = Combo_opxs_cpxs_2.copy() Combo_opxs_cpxs_2 = Combo_opxs_cpxs_2.drop( ['Sample_ID_Cpx', 'Sample_ID_Opx'], axis=1).astype('float64') # This gives users flexibility to input a constant P or T as well as # choosing equations if equationP is not None and P is not None: raise ValueError('You have entered an equation for P and specified a pressure. ' ' The code doesnt know what you want it to do. Either enter an equation, or choose a pressure. ') if equationT is not None and T is not None: raise ValueError('You have entered an equation for T and specified a temperature. ' 'The code doesnt know what you want it to do. Either enter an equation, or choose a temperature.') if equationP is not None and equationT is not None: PT_out = calculate_cpx_opx_press_temp( Two_Px_Match=Combo_opxs_cpxs_2, equationP=equationP, equationT=equationT, iterations=iterations) Combo_opxs_cpxs_2.insert(0, "T_K_calc", PT_out['T_K_calc']) Combo_opxs_cpxs_2.insert(1, "P_kbar_calc", PT_out['P_kbar_calc']) Combo_opxs_cpxs_2.insert(2, "Delta_T_K_Iter", PT_out['Delta_T_K_Iter'].astype(float)) Combo_opxs_cpxs_2.insert(3, "Delta_P_kbar_Iter", PT_out['Delta_P_kbar_Iter'].astype(float)) if P is not None: T_K_calc = calculate_cpx_opx_temp( Two_Px_Match=Combo_opxs_cpxs_2, equationT=equationT, P=P) Combo_opxs_cpxs_2.insert(0, "T_K_calc", T_K_calc) Combo_opxs_cpxs_2.insert(1, "P_kbar_input", P) Combo_opxs_cpxs_2.insert(2, "Delta_T_K_Iter", 0) Combo_opxs_cpxs_2.insert(3, "Delta_P_kbar_Iter", 0) if T is not None: P_kbar_calc = calculate_cpx_opx_press( Two_Px_Match=Combo_opxs_cpxs_2, equationP=equationP, T=T) Combo_opxs_cpxs_2.insert(0, "T_K_input", T) Combo_opxs_cpxs_2.insert(1, "P_kbar_calc", P_kbar_calc) Combo_opxs_cpxs_2.insert(2, "Delta_T_K_Iter", 0) Combo_opxs_cpxs_2.insert(3, "Delta_P_kbar_Iter", 0) cols_to_move = ['Kd_Fe_Mg_Cpx_Opx'] Combo_opxs_cpxs_2 = Combo_opxs_cpxs_2[cols_to_move + [ col for col in Combo_opxs_cpxs_2.columns if col not in cols_to_move]] Combo_opxs_cpxs_2.insert( 0, "Sample_ID_Opx", Combo_opxs_cpxs_2_names['Sample_ID_Opx']) Combo_opxs_cpxs_2.insert( 1, "Sample_ID_Cpx", Combo_opxs_cpxs_2_names['Sample_ID_Cpx']) # Final step, calcuate a 3rd output which is the average and standard # deviation for each CPx (e.g., CPx1-Melt1, CPx1-melt3 etc. ) CpxNumbers = Combo_opxs_cpxs_2['ID_CPX'].unique() Opx_sample_ID=Combo_opxs_cpxs_2["Sample_ID_Opx"] Combo_opxs_cpxs_2.drop(["Sample_ID_Opx"], axis=1, inplace=True) if len(CpxNumbers) > 0: groupby_keys = ['ID_CPX', 'Sample_ID_Cpx'] numeric_cols = Combo_opxs_cpxs_2.select_dtypes(include='number').columns.tolist() agg_cols = [c for c in numeric_cols if c not in groupby_keys] df1_Mean_nopref = Combo_opxs_cpxs_2.groupby( groupby_keys, as_index=False)[agg_cols].mean() df1_Std_nopref = Combo_opxs_cpxs_2.groupby( groupby_keys, as_index=False)[agg_cols].std() count = Combo_opxs_cpxs_2.groupby('ID_CPX').count() Sample_ID_Cpx_Mean = df1_Mean_nopref['Sample_ID_Cpx'] Sample_ID_Cpx_Std = df1_Std_nopref['Sample_ID_Cpx'] df1_Mean = df1_Mean_nopref.add_prefix('Mean_') df1_Std = df1_Std_nopref.add_prefix('Std_') df1_Mean = df1_Mean.drop(['Mean_Sample_ID_Cpx'], axis=1) df1_Std = df1_Std.drop(['Std_Sample_ID_Cpx'], axis=1) df1_Mean.rename(columns={"Mean_ID_CPX": "ID_CPX"}, inplace=True) df1_Std.rename(columns={"Std_ID_CPX": "ID_CPX"}, inplace=True) df1_M = pd.merge(df1_Mean, df1_Std, on=['ID_CPX']) df1_M['Sample_ID_Cpx'] = Sample_ID_Cpx_Mean.values if equationT is not None and equationP is not None: cols_to_move = ['Sample_ID_Cpx', 'Mean_T_K_calc', 'Std_T_K_calc', 'Mean_P_kbar_calc', 'Std_P_kbar_calc'] if equationT is not None and equationP is None: cols_to_move = ['Sample_ID_Cpx', 'Mean_T_K_calc', 'Std_T_K_calc', 'Mean_P_kbar_input', 'Std_P_kbar_input'] if equationT is None and equationP is not None: cols_to_move = ['Sample_ID_Cpx', 'Mean_T_K_input', 'Std_T_K_input', 'Mean_P_kbar_calc', 'Std_P_kbar_calc'] df1_M = df1_M[cols_to_move + [col for col in df1_M.columns if col not in cols_to_move]] opxNumbers = Combo_opxs_cpxs_2['ID_OPX'].unique() Combo_opxs_cpxs_2['Sample_ID_Opx'] = Opx_sample_ID Combo_opxs_cpxs_2.drop(["Sample_ID_Cpx"], axis=1, inplace=True) if len(opxNumbers) > 0: groupby_keys_opx = ['ID_OPX', 'Sample_ID_Opx'] numeric_cols_opx = Combo_opxs_cpxs_2.select_dtypes(include='number').columns.tolist() agg_cols_opx = [c for c in numeric_cols_opx if c not in groupby_keys_opx] df1_2Mean_nopref = Combo_opxs_cpxs_2.groupby( groupby_keys_opx, as_index=False)[agg_cols_opx].mean() df1_2Std_nopref = Combo_opxs_cpxs_2.groupby( groupby_keys_opx, as_index=False)[agg_cols_opx].std() count = Combo_opxs_cpxs_2.groupby('ID_OPX').count() Sample_ID_Opx_Mean = df1_2Mean_nopref['Sample_ID_Opx'] Sample_ID_Opx_Std = df1_2Std_nopref['Sample_ID_Opx'] df1_2Mean = df1_2Mean_nopref.add_prefix('Mean_') df1_2Std = df1_2Std_nopref.add_prefix('Std_') df1_2Mean = df1_2Mean.drop(['Mean_Sample_ID_Opx'], axis=1) df1_2Std = df1_2Std.drop(['Std_Sample_ID_Opx'], axis=1) df1_2Mean.rename(columns={"Mean_ID_OPX": "ID_OPX"}, inplace=True) df1_2Std.rename(columns={"Std_ID_OPX": "ID_OPX"}, inplace=True) df1_2M = pd.merge(df1_2Mean, df1_2Std, on=['ID_OPX']) df1_2M['Sample_ID_Opx'] = Sample_ID_Opx_Mean.values if equationT is not None and equationP is not None: cols_to_move = ['Sample_ID_Opx', 'Mean_T_K_calc', 'Std_T_K_calc', 'Mean_P_kbar_calc', 'Std_P_kbar_calc'] if equationT is not None and equationP is None: cols_to_move = ['Sample_ID_Opx', 'Mean_T_K_calc', 'Std_T_K_calc', 'Mean_P_kbar_input', 'Std_P_kbar_input'] if equationT is None and equationP is not None: cols_to_move = ['Sample_ID_Opx', 'Mean_T_K_input', 'Std_T_K_input', 'Mean_P_kbar_calc', 'Std_P_kbar_calc'] df1_2M = df1_2M[cols_to_move + [col for col in df1_2M.columns if col not in cols_to_move]] else: raise Exception( 'No Matches - you may need to set less strict filters, e.g.,' 'you could edit Kd_Match is None and Kd_Err to get more matches') print('Done!!! I found a total of N=' + str(len(Combo_opxs_cpxs_2)) + ' Cpx-Opx matches using the specified filter. ') print('N=' + str(len(df1_M)) + ' Cpx out of the N=' + str(LenCpx) + ' Cpx that you input matched to 1 or more Opx') print('N=' + str(len(df1_2M)) + ' Opx out of the N=' + str(LenOpx) + ' Opx that you input matched to 1 or more Cpx') print('Done!') if equationT is not None: df1_M.insert(4, "Equation Choice (T)", str(equationT)) df1_2M.insert(4, "Equation Choice (T)", str(equationT)) Combo_opxs_cpxs_2.insert(4, "Equation Choice (T)", str(equationT)) if equationP is not None: df1_M.insert(5, "Equation Choice (P)", str(equationP)) df1_2M.insert(5, "Equation Choice (P)", str(equationP)) Combo_opxs_cpxs_2.insert(5, "Equation Choice (P)", str(equationP)) Combo_opxs_cpxs_2['Sample_ID_Opx'] = Opx_sample_ID return {'Av_PTs_perCPX': df1_M, 'Av_PTs_perOPX': df1_2M, 'All_PTs': Combo_opxs_cpxs_2}