def calculate_dissolved_volatiles(self,pressure,sample,X_fluid=1.0,**kwargs): """Calculates the dissolved CO2 concentration using Eqn (3) of Dixon (1997). Parameters ---------- pressure float Total pressure in bars. sample Sample class Magma major element composition. X_fluid float The mol fraction of CO2 in the fluid. Returns ------- float The CO2 concentration in wt%. """ if X_fluid < 0 or X_fluid > 1: raise core.InputError("X_fluid must have a value between 0 and 1.") if pressure < 0: raise core.InputError("Pressure must be positive.") # if type(sample) != dict and type(sample) != pd.core.series.Series: # raise core.InputError("sample must be a dict or pandas Series") if sample.check_oxide('SiO2') == False: raise core.InputError("sample must contain SiO2.") if pressure == 0: return 0 XCO3 = self.molfrac_molecular(pressure=pressure,sample=sample,X_fluid=X_fluid,**kwargs) return (4400 * XCO3) / (36.594- 44*XCO3) # Following Dixon 1997 setting Mr as constant
def calculate_saturation_pressure(self, sample, **kwargs): """ Calculates the pressure at which a pure H2O fluid is saturated, for the given sample composition and H2O concentration. Calls the scipy.root_scalar routine, which makes repeated calls to the calculate_dissolved_volatiles method. Parameters ---------- sample Sample class Magma major element composition (including H2O). Returns ------- float Saturation pressure in bar """ if sample.check_oxide('H2O') == False: raise core.InputError("sample must contain H2O") if sample.get_composition('H2O') < 0: raise core.InputError( "H2O concentration must be greater than 0 wt%.") if sample.get_composition('H2O') < self.calculate_dissolved_volatiles( sample=sample, pressure=0, **kwargs): return np.nan try: satP = root_scalar(self.root_saturation_pressure, bracket=[1e-15, 1e5], args=(sample, kwargs)).root except: w.warn("Saturation pressure not found.", RuntimeWarning, stacklevel=2) satP = np.nan return satP
def calculate_saturation_pressure(self, sample, X_fluid=1.0, **kwargs): """ Calculates the pressure at which a pure H2O fluid is saturated, for the given sample composition and H2O concentration. Calls the scipy.root_scalar routine, which makes repeated called to the calculate_dissolved_volatiles method. Parameters ---------- sample Sample class Magma major element composition (including H2O). X_fluid float The mole fraction of H2O in the fluid. Default is 1.0. Returns ------- float Calculated saturation pressure in bars. """ if sample.check_oxide('H2O') is False: raise core.InputError("sample must contain H2O") if sample.get_composition('H2O') < 0: raise core.InputError( "H2O concentration must be greater than 0 wt%.") try: satP = root_scalar(self.root_saturation_pressure, x0=100.0, x1=1000.0, args=(sample, kwargs)).root except Exception: w.warn("Saturation pressure not found.", RuntimeWarning, stacklevel=2) satP = np.nan return np.real(satP)
def calculate_dissolved_volatiles(self,pressure,sample,X_fluid=1.0,**kwargs): """Calculates the dissolved H2O concentration using Eqns (5) and (6) of Dixon (1997). Parameters ---------- pressure float Total pressure in bars. sample Sample class Magma major element composition. X_fluid float The mol fraction of H2O in the fluid. Returns ------- float The H2O concentration in wt%. """ if isinstance(sample,sample_class.Sample) == False: raise core.InputError("Sample must be an instance of the Sample class.") if sample.check_oxide('SiO2') == False: raise core.InputError("sample must contain SiO2.") if pressure < 0: raise core.InputError("Pressure must be positive") if X_fluid < 0 or X_fluid > 1: raise core.InputError("X_fluid must have a value between 0 and 1.") if pressure == 0: return 0 XH2O = self.molfrac_molecular(pressure=pressure,sample=sample,X_fluid=X_fluid,**kwargs) XOH = self.XOH(pressure=pressure,sample=sample,X_fluid=X_fluid,**kwargs) XB = XH2O + 0.5*XOH return 1801.5*XB/(36.594-18.579*XB) # Following Dixon spreadsheet
def NBO_O(self, sample, coeffs='webapp'): """ Calculates NBO/O according to Appendix A.1. of Iacono-Marziano et al. (2012). NBO/O is calculated on either a hydrous or anhyrous basis, as set when initialising the Model class. Parameters ---------- sample pandas Series or dict Major element oxides in wt% (including H2O if using the hydrous parameterization). coeffs str One of: - 'webapp' or 'manuscript' to include H2O in NBO/O - 'anhydrous' to exclude H2O from NBO/O Returns ------- float NBO/O. """ if isinstance(sample, sample_class.Sample) is False: raise core.InputError( "Sample must be an instance of the Sample class.") if all( sample.check_oxide(ox) for ox in [ 'K2O', 'Na2O', 'CaO', 'MgO', 'FeO', 'Al2O3', 'SiO2', 'TiO2' ]) is False: raise core.InputError( "Sample must contain K2O, Na2O, CaO, MgO, FeO, Al2O3, SiO2, " "and TiO2.") X = sample.get_composition(units='mol_oxides', oxide_masses=self.IM_oxideMasses) if 'Fe2O3' in X: Fe2O3 = X['Fe2O3'] else: Fe2O3 = 0 NBO = 2 * (X['K2O'] + X['Na2O'] + X['CaO'] + X['MgO'] + X['FeO'] + 2 * Fe2O3 - X['Al2O3']) Ox = (2 * X['SiO2'] + 2 * X['TiO2'] + 3 * X['Al2O3'] + X['MgO'] + X['FeO'] + 2 * Fe2O3 + X['CaO'] + X['Na2O'] + X['K2O']) if coeffs == 'webapp' or coeffs == 'manuscript': if 'H2O' not in X: raise core.InputError( "sample must contain H2O if using the hydrous" " parameterization.") NBO = NBO + 2 * X['H2O'] Ox = Ox + X['H2O'] return NBO / Ox
def check_inputs(custom_H2O, custom_CO2): if custom_H2O is not None: if custom_CO2 is None: raise core.InputError("If x data is passed, y data must also be passed.") else: if len(custom_H2O) == len(custom_CO2): pass else: raise core.InputError("x and y data must be same length") if custom_CO2 is not None: if custom_H2O is None: raise core.InputError("If y data is passed, x data must also be passed.")
def calculate_saturation_pressure(self, temperature, sample, X_fluid=1.0, **kwargs): """ Calculates the pressure at which a an CO2-bearing fluid is saturated. Calls the scipy.root_scalar routine, which makes repeated called to the calculate_dissolved_volatiles method. Parameters ---------- sample: Sample class Magma major element composition. temperature float Temperature in degrees C. X_fluid float OPTIONAL. Default is 0. Mole fraction of CO2 in the H2O-CO2 fluid. Returns ------- float Calculated saturation pressure in bars. """ temperatureK = temperature + 273.15 if temperatureK <= 0.0: raise core.InputError("Temperature must be greater than 0K.") if X_fluid < 0 or X_fluid > 1: raise core.InputError("X_fluid must have a value between 0 and 1.") if isinstance(sample, sample_class.Sample) is False: raise core.InputError( "Sample must be an instance of the Sample class.") if sample.check_oxide('CO2') is False: raise core.InputError("sample must contain CO2.") if sample.get_composition('CO2') < 0.0: raise core.InputError( "Dissolved CO2 concentration must be greater than 0 wt%.") try: satP = root_scalar(self.root_saturation_pressure, args=(temperature, sample, X_fluid, kwargs), x0=10.0, x1=2000.0).root except Exception: w.warn("Saturation pressure not found.", RuntimeWarning, stacklevel=2) satP = np.nan return np.real(satP)
def calculate_dissolved_volatiles(self, pressure, sample, X_fluid=1.0, **kwargs): """Calculates the dissolved H2O concentration using Eqn (9) of Shishkina et al. (2014). Parameters ---------- pressure float Total pressure in bars sample Sample class Magma major element composition. X_fluid float The mol fraction of H2O in the fluid Returns ------- float The H2O concentration in wt% """ if isinstance(sample, sample_class.Sample) == False: raise core.InputError( "Sample must be an instance of the Sample class.") if sample.check_oxide('Na2O') == False or sample.check_oxide( 'K2O') == False: raise core.InputError("Na2O and K2O must be present in sample.") if pressure < 0: raise core.InputError("Pressure must be positive.") _mols = sample.get_composition(units='mol_cations') _mol_volatiles = 0 if 'H' in _mols: _mol_volatiles += _mols['H'] if 'C' in _mols: _mol_volatiles += _mols['C'] total_alkalis = (_mols['Na'] + _mols['K']) / (1 - _mol_volatiles) fugacity = self.fugacity_model.fugacity(pressure, X_fluid=X_fluid, **kwargs) a = 3.36e-7 * (fugacity / 10)**3 - 2.33e-4 * ( fugacity / 10)**2 + 0.0711 * (fugacity / 10) - 1.1309 b = -1.2e-5 * (fugacity / 10)**2 + 0.0196 * (fugacity / 10) + 1.1297 return a * total_alkalis + b
def NBO_O(self, sample, hydrous_coeffs=True): """ Calculates NBO/O according to Appendix A.1. of Iacono-Marziano et al. (2012). NBO/O is calculated on either a hydrous or anhyrous basis, as set when initialising the Model class. Parameters ---------- sample pandas Series or dict Major element oxides in wt% (including H2O if using the hydrous parameterization). Returns ------- float NBO/O. """ if isinstance(sample, sample_class.Sample) == False: raise core.InputError( "Sample must be an instance of the Sample class.") if all( sample.check_oxide(ox) for ox in [ 'K2O', 'Na2O', 'CaO', 'MgO', 'FeO', 'Al2O3', 'SiO2', 'TiO2' ]) == False: raise core.InputError( "sample must contain K2O, Na2O, CaO, MgO, FeO, Al2O3, SiO2, and TiO2." ) # X = sample.get_composition(units='mol_oxides',normalization='additionalvolatiles') X = sample.get_composition(units='mol_oxides') if 'Fe2O3' in X: Fe2O3 = X['Fe2O3'] else: Fe2O3 = 0 NBO = 2 * (X['K2O'] + X['Na2O'] + X['CaO'] + X['MgO'] + X['FeO'] + 2 * Fe2O3 - X['Al2O3']) O = 2 * X['SiO2'] + 2 * X['TiO2'] + 3 * X['Al2O3'] + X['MgO'] + X[ 'FeO'] + 2 * Fe2O3 + X['CaO'] + X['Na2O'] + X['K2O'] if hydrous_coeffs == True: if 'H2O' not in X: raise core.InputError( "sample must contain H2O if using the hydrous parameterization." ) NBO = NBO + 2 * X['H2O'] O = O + X['H2O'] return NBO / O
def scatterplot(custom_x, custom_y, xlabel=None, ylabel=None, **kwargs): """ Custom x-y plotting using VESIcal's built-in plot() function, built Matplotlib's plot and scatter functions. Parameters ---------- custom_x: list List of groups of x-values to plot as points or lines custom_y: list List of groups of y-values to plot as points or lines xlabel: str OPTIONAL. What to display along the x-axis. ylabel: str OPTIONAL. What to display along the y-axis. kwargs: Can take in any key word agruments that can be passed to `plot()`. Returns ------- fig, ax matplotlib objects X-y plot with custom x and y axis values and labels. """ if isinstance(custom_x, list) and isinstance(custom_y, list): if len(custom_x) != len(custom_y): raise core.InputError("X and y lists must be same length") if xlabel is not None: if isinstance(xlabel, str): pass else: raise core.InputError("xlabel must be string") if ylabel is not None: if isinstance(ylabel, str): pass else: raise core.InputError("ylabel must be string") return plot(custom_x=custom_x, custom_y=custom_y, xlabel=xlabel, ylabel=ylabel, **kwargs)
def set_default_normalization(self, default_normalization): """ Set the default type of normalization to use with the get_composition() method. Parameters ---------- default_normalization: str The type of normalization to apply to the data. One of: - 'none' (no normalization) - 'standard' (default): Normalizes an input composition to 100%. - 'fixedvolatiles': Normalizes major element oxides to 100 wt%, including volatiles. The volatile wt% will remain fixed, whilst the other major element oxides are reduced proportionally so that the total is 100 wt%. - 'additionalvolatiles': Normalises major element oxide wt% to 100%, assuming it is volatile-free. If H2O or CO2 are passed to the function, their un-normalized values will be retained in addition to the normalized non-volatile oxides, summing to >100%. """ if default_normalization in [ 'none', 'standard', 'fixedvolatiles', 'additionalvolatiles' ]: self.default_normalization = default_normalization else: raise core.InputError( "The normalization method must be one of 'none', 'standard', 'fixedvolatiles',\ or 'additionalvolatiles'.")
def PiStar(self, sample): """Shishkina et al. (2014) Eq (11) Calculates the Pi* parameter for use in calculating CO2 solubility. Parameters ---------- sample: Sample class The magma composition stored in a Sample class. Returns ------- float The value of the Pi* compositional parameter. """ _mols = sample.get_composition(units='mol_cations') if all(cation in _mols for cation in ['Ca', 'K', 'Na', 'Mg', 'Fe', 'Si', 'Al']) == False: raise core.InputError( "To calculate PiStar, values for CaO, K2O, Na2O, MgO, FeO, SiO2, and Al2O3\ must be provided in sample.") _pi = (_mols['Ca'] + 0.8*_mols['K'] + 0.7*_mols['Na'] + 0.4*_mols['Mg'] + 0.4*_mols['Fe'])/\ (_mols['Si']+_mols['Al']) return _pi
def calculate_saturation_pressure(self, temperature, sample, **kwargs): """ Calculates the pressure at which a pure H2O fluid is saturated, for the given sample composition and H2O concentration. Calls the scipy.root_scalar routine, which makes repeated called to the calculate_dissolved_volatiles method. Parameters ---------- temperature float The temperature of the system in C. sample pandas Series or dict Major element oxides in wt% (including H2O). X_fluid float The mole fraction of H2O in the fluid. Default is 1.0. Returns ------- float Calculated saturation pressure in bars. """ if isinstance(sample, sample_class.Sample) is False: raise core.InputError( "Sample must be an instance of the Sample class.") if sample.check_oxide('H2O') is False: raise core.InputError("sample must contain H2O.") if sample.get_composition('H2O') < 0.0: raise core.InputError("Dissolved H2O must be greater than 0 wt%.") try: # Checks whether the upper bound for the numerical solver returns a positive H2O # concentration, and if it doesn't, it will progressively decrease the bound until # it does. upperbound = 1e5 while self.calculate_dissolved_volatiles(upperbound, temperature, sample, **kwargs) < 0: upperbound = upperbound * 0.9 satP = root_scalar(self.root_saturation_pressure, args=(temperature, sample, kwargs), bracket=[1e-15, upperbound]).root except Exception: w.warn("Saturation pressure not found.", RuntimeWarning, stacklevel=2) satP = np.nan return satP
def calculate_saturation_pressure(self, sample, temperature=1200, X_fluid=1.0, **kwargs): """ Calculates the pressure at which a pure CO2 fluid is saturated, for the given sample composition and CO2 concentration. Calls the scipy.root_scalar routine, which makes repeated called to the calculate_dissolved_volatiles method. Parameters ---------- temperature float The temperature of the system in C. sample: Sample class Magma major element composition (including CO2). X_fluid float The mole fraction of H2O in the fluid. Default is 1.0. Returns ------- float Calculated saturation pressure in bars. """ if X_fluid < 0 or X_fluid > 1: raise core.InputError("X_fluid must have a value between 0 and 1.") if isinstance(sample, sample_class.Sample) is False: raise core.InputError( "Sample must be an instance of the Sample class.") if sample.check_oxide('CO2') is False: raise core.InputError("sample must contain CO2.") if sample.get_composition('CO2') < 0.0: raise core.InputError( "Dissolved CO2 concentration must be greater than 0 wt%.") try: satP = root_scalar(self.root_saturation_pressure, args=(temperature, sample, X_fluid, kwargs), x0=1000.0, x1=2000.0).root except Exception: w.warn("Saturation pressure not found.", RuntimeWarning, stacklevel=2) satP = np.nan return satP
def check_colors(custom_colors): if custom_colors == "VESIcal": use_colors = color_list elif isinstance(custom_colors, list): use_colors = custom_colors else: raise core.InputError("Argument custom_colors must be type list. Just passing one item? Try putting square brackets, [], around it.") return use_colors
def calculate_dissolved_volatiles(self, pressure, sample, X_fluid=1, **kwargs): """ Calculates the dissolved CO2 concentration in wt%, using equation (13) of Shishkina et al. (2014). Parameters ---------- pressure: float (Total) pressure in bars. sample: Sample class Magma composition. X_fluid: float The mol-fraction of the fluid that is CO2. Default is 1, i.e. a pure CO2 fluid. Returns ------- float The dissolved CO2 concentration in wt%. """ if X_fluid < 0 or X_fluid > 1: raise core.InputError("X_fluid must have a value between 0 and 1.") if pressure < 0: raise core.InputError("pressure must be a positive value.") PiStar = self.PiStar(sample) fugacity = self.fugacity_model.fugacity(pressure=pressure, X_fluid=X_fluid, **kwargs) A = 1.150 B = 6.71 C = -1.345 if fugacity == 0: return 0 else: return np.exp(A * np.log(fugacity / 10) + B * PiStar + C) / 1e4
def calculate_saturation_pressure(self, temperature, sample, **kwargs): """ Calculates the pressure at which a pure CO2 fluid is saturated, for the given sample composition and CO2 concentration. Calls the scipy.root_scalar routine, which makes repeated called to the calculate_dissolved_volatiles method. Parameters ---------- temperature float The temperature of the system in C. sample Sample class Magma major element composition (including CO2). Returns ------- float Calculated saturation pressure in bars. """ if temperature <= 0: raise core.InputError("Temperature must be greater than 0K.") if isinstance(sample, sample_class.Sample) is False: raise core.InputError( "Sample must be an instance of the Sample class.") if sample.check_oxide('CO2') is False: raise core.InputError("sample must contain CO2") if sample.get_composition('CO2') < 0: raise core.InputError("Dissolved CO2 must be greater than 0 wt%.") try: satP = root_scalar(self.root_saturation_pressure, args=(temperature, sample, kwargs), bracket=[1e-15, 1e5]).root except Exception: w.warn("Saturation pressure not found.", RuntimeWarning, stacklevel=2) satP = np.nan return satP
def _normalize_FixedVolatiles(self, composition, units='wtpt_oxides'): """ Normalizes major element oxides to 100 wt%, including volatiles. The volatile wt% will remain fixed, whilst the other major element oxides are reduced proportionally so that the total is 100 wt%. Intended to be called only by the get_composition() method. Parameters ---------- composition: pandas Series Major element composition units: str The units of composition. Should be one of: - wtpt_oxides (default) - mol_oxides - mol_cations Returns ------- pandas Series Normalized major element oxides. """ comp = composition.copy() normalized = pd.Series({}, dtype=float) volatiles = 0 if 'CO2' in list(comp.index): volatiles += comp['CO2'] if 'H2O' in list(comp.index): volatiles += comp['H2O'] for ox in list(comp.index): if ox != 'H2O' and ox != 'CO2': normalized[ox] = comp[ox] if units == 'wtpt_oxides': normalized = normalized / np.sum(normalized) * (100 - volatiles) elif units == 'mol_oxides' or units == 'mol_cations': normalized = normalized / np.sum(normalized) * (1 - volatiles) else: raise core.InputError( "Units must be one of 'wtpt_oxides', 'mol_oxides', or 'mol_cations'." ) if 'CO2' in list(comp.index): normalized['CO2'] = comp['CO2'] if 'H2O' in list(comp.index): normalized['H2O'] = comp['H2O'] return normalized
def __init__(self, sample, model='MagmaSat', silence_warnings=False, **kwargs): """ Initializes the calculation. Parameters ---------- sample: Sample class The rock composition as a Sample object. model: string or Model class Which model to use for the calculation. If passed a string, it will look up the name in the default_models dictionary. Default is MagmaSat. silence_warnings: bool Silence warnings about calibration ranges. Default is False. preprocess_sample: bool Before running the calculation, run the sample through the preprocessing routine. As of Feb 2021 this functionality should be redundant. """ self.model_name = model if model == 'MagmaSat': self.model = magmasat.MagmaSat() elif type(model) == str: if model in models.default_models.keys(): self.model = models.default_models[model] else: raise core.InputError("The model name given is not recognised." " Run the method get_model_names() to " "find allowed names.") else: self.model = model self.sample = sample self.result = self.calculate(sample=self.sample, **kwargs) self.calib_check = self.check_calibration_range(sample=self.sample, **kwargs) if self.calib_check is not None and silence_warnings is False: if self.calib_check != '': w.warn(self.calib_check, RuntimeWarning)
def _normalize_AdditionalVolatiles(self, composition, units='wtpt_oxides'): """ Normalises major element oxide wt% to 100%, assuming it is volatile-free. If H2O or CO2 are passed to the function, their un-normalized values will be retained in addition to the normalized non-volatile oxides, summing to >100%. Intended to be called only by the get_composition() method. Parameters ---------- composition: pandas.Series Major element composition units: str The units of composition. Should be one of: - wtpt_oxides (default) - mol_oxides - mol_cations Returns ------- pandas.Series Normalized major element oxides. """ comp = composition.copy() normalized = pd.Series({}, dtype=float) for ox in list(comp.index): if ox != 'H2O' and ox != 'CO2': normalized[ox] = comp[ox] if units == 'wtpt_oxides': normalized = normalized / np.sum(normalized) * 100 elif units == 'mol_oxides' or units == 'mol_cations': normalized = normalized / np.sum(normalized) else: raise core.InputError( "Units must be one of 'wtpt_oxides', 'mol_oxides', or 'mol_cations'." ) if 'H2O' in comp.index: normalized['H2O'] = comp['H2O'] if 'CO2' in comp.index: normalized['CO2'] = comp['CO2'] return normalized
def calculate(self, sample, pressure='saturation', fractionate_vapor=0.0, final_pressure=100.0, **kwargs): check = getattr(self.model, "calculate_degassing_path", None) if callable(check): data = self.model.calculate_degassing_path( sample=sample, pressure=pressure, fractionate_vapor=fractionate_vapor, final_pressure=final_pressure, **kwargs) return data else: raise core.InputError( "This model does not have a calculate_isobars_and_isopleths method built in, most likely because it is a pure fluid model." )
def set_default_units(self, default_units): """ Set the default units of composition to return when using the get_composition() method. Parameters ---------- default_units str The type of composition to return, one of: - wtpt_oxides (default) - mol_oxides - mol_cations - mol_singleO """ if default_units in [ 'wtpt_oxides', 'mol_oxides', 'mol_cations', 'mol_singleO' ]: self.default_units = default_units else: raise core.InputError( "The units must be one of 'wtpt_oxides','mol_oxides','mol_cations','mol_singleO'." )
def calculate(self, sample, pressure_list, isopleth_list=[0, 1], points=101, **kwargs): check = getattr(self.model, "calculate_isobars_and_isopleths", None) if callable(check): # samplenorm = sample.copy() # samplenorm = normalize_AdditionalVolatiles(samplenorm) isobars, isopleths = self.model.calculate_isobars_and_isopleths( sample=self.sample, pressure_list=pressure_list, isopleth_list=isopleth_list, points=points, **kwargs) return isobars, isopleths else: raise core.InputError( "This model does not have a calculate_isobars_and_isopleths method built in, most likely because it is a pure fluid model." )
def _normalize_Standard(self, composition, units='wtpt_oxides'): """ Normalizes the given composition to 100 wt%, including volatiles. This method is intended only to be called by the get_composition() method. Parameters ---------- composition: pandas.Series A rock composition with oxide names as keys and concentrations as values. units: str The units of composition. Should be one of: - wtpt_oxides (default) - mol_oxides - mol_cations Returns ------- pandas.Series Normalized oxides in wt%. """ comp = composition.copy() comp = dict(comp) if units == 'wtpt_oxides': normed = pd.Series( {k: 100.0 * v / sum(comp.values()) for k, v in comp.items()}) elif units == 'mol_oxides' or units == 'mol_cations': normed = pd.Series( {k: v / sum(comp.values()) for k, v in comp.items()}) else: raise core.InputError( "Units must be one of 'wtpt_oxides', 'mol_oxides', or 'mol_cations'." ) return normed
def change_composition(self, new_composition, units='wtpt_oxides', inplace=True): """ Change the concentration of some component of the composition. If the units are moles, they are read as moles relative to the present composition, i.e. if you wish to double the moles of MgO, if the present content is 0.1 moles, you should provide {'MgO':0.2}. The composition will then be re-normalized. If the original composition was provided in un-normalized wt%, the unnormalized total will be lost. Parameters ---------- new_composition: dict or pandas.Series The components to be updated. units: str The units of new_composition. Should be one of: - wtpt_oxides (default) - mol_oxides - mol_cations inplace: bool If True the object will be modified in place. If False, a copy of the Sample object will be created, modified, and then returned. Returns ------- Sample class Modified Sample class. """ # if new_composition is pandas.Series, convert to dict if isinstance(new_composition, pd.Series): new_composition = dict(new_composition) if inplace == False: newsample = deepcopy(self) return newsample.change_composition(new_composition, units=units) if units == 'wtpt_oxides': for ox in new_composition: self._composition[ox] = new_composition[ox] elif units == 'mol_oxides': _comp = self.get_composition(units='mol_oxides') for ox in new_composition: _comp[ox] = new_composition[ox] self._composition = self._molOxides_to_wtpercentOxides(_comp) elif units == 'mol_cations': _comp = self.get_composition(units='mol_cations') for el in new_composition: _comp[el] = new_composition[el] self._composition = self._molCations_to_wtpercentOxides(_comp) else: raise core.InputError( "Units must be one of 'wtpt_oxides', 'mol_oxides', or 'mol_cations'." ) return self
def __init__(self, composition, units='wtpt_oxides', default_normalization='none', default_units='wtpt_oxides'): """ Initialises the sample class. The composition is stored as wtpt. If the composition is provided as wtpt, no normalization will be applied. If the composition is supplied as mols, the composition will be normalized to 100 wt%. Parameters ---------- composition dict or pandas.Series The composition of the sample in the format specified by the composition_type parameter. Default is oxides in wtpt. units str Specifies the units and type of compositional information passed in the composition parameter. Choose from 'wtpt_oxides', 'mol_oxides', 'mol_cations'. default_normalization: None or str The type of normalization to apply to the data by default. One of: - None (no normalization) - 'standard' (default): Normalizes an input composition to 100%. - 'fixedvolatiles': Normalizes major element oxides to 100 wt%, including volatiles. The volatile wt% will remain fixed, whilst the other major element oxides are reduced proportionally so that the total is 100 wt%. - 'additionalvolatiles': Normalises major element oxide wt% to 100%, assuming it is volatile-free. If H2O or CO2 are passed to the function, their un-normalized values will be retained in addition to the normalized non-volatile oxides, summing to >100%. default_units str The type of composition to return by default, one of: - wtpt_oxides (default) - mol_oxides - mol_cations - mol_singleO """ composition = deepcopy(composition) if isinstance(composition, dict): composition = pd.Series(composition, dtype='float64') elif isinstance(composition, pd.Series) == False: raise core.InputError( "The composition must be given as either a dictionary or a pandas Series." ) if units == 'wtpt_oxides': self._composition = composition elif units == 'mol_oxides': self._composition = self._molOxides_to_wtpercentOxides(composition) elif units == 'mol_cations': self._composition = self._molCations_to_wtpercentOxides( composition) else: raise core.InputError( "Units must be one of 'wtpt_oxides', 'mol_oxides', or 'mol_cations'." ) self.set_default_normalization(default_normalization) self.set_default_units(default_units)
def get_composition(self, species=None, normalization=None, units=None, exclude_volatiles=False, asSampleClass=False): """ Returns the composition in the format requested, normalized as requested. Parameters ---------- species: NoneType or str The name of the oxide or cation to return the concentration of. If NoneType (default) the whole composition will be returned as a pandas.Series. If an oxide is passed, the value in wtpt will be returned unless units is set to 'mol_oxides', even if the default units for the sample object are mol_oxides. If an element is passed, the concentration will be returned as mol_cations, unless 'mol_singleO' is specified as units, even if the default units for the sample object are mol_singleO. Unless normalization is specified in the method call, none will be applied. normalization: NoneType or str The type of normalization to apply to the data. One of: - 'none' (no normalization) - 'standard' (default): Normalizes an input composition to 100%. - 'fixedvolatiles': Normalizes major element oxides to 100 wt%, including volatiles. The volatile wt% will remain fixed, whilst the other major element oxides are reduced proportionally so that the total is 100 wt%. - 'additionalvolatiles': Normalises major element oxide wt% to 100%, assuming it is volatile-free. If H2O or CO2 are passed to the function, their un-normalized values will be retained in addition to the normalized non-volatile oxides, summing to >100%. If NoneType is passed the default normalization option will be used (self.default_normalization). units: NoneType or str The units of composition to return, one of: - wtpt_oxides (default) - mol_oxides - mol_cations - mol_singleO If NoneType is passed the default units option will be used (self.default_type). exclude_volatiles bool If True, volatiles will be excluded from the returned composition, prior to normalization and conversion. asSampleClass: bool If True, the sample composition will be returned as a sample class, with default options. In this case any normalization instructions will be ignored. Returns ------- pandas.Series, float, or Sample class The sample composition, as specified. """ # Fetch the default return types if not specified in function call if normalization == None and species == None: normalization = self.default_normalization if units == None and species == None: units = self.default_units # Check whether to exclude volatiles # note that here composition is gotten as wtpt_oxides if exclude_volatiles == True: composition = self._composition.copy() if 'H2O' in composition.index: composition = composition.drop(index='H2O') if 'CO2' in composition.index: composition = composition.drop(index='CO2') else: composition = self._composition.copy() # Check for a species being provided, if so, work out which units to return. if isinstance(species, str): if species in composition.index: # if the requested species has a value, proceed if species in core.oxides: if units in ['mol_cations, mol_singleO'] or units == None: units = 'wtpt_oxides' elif species in core.cations_to_oxides: if units in ['wtpt_oxides', 'mol_oxides'] or units == None: units = 'mol_cations' else: raise core.InputError( species + " was not recognised, check spelling, capitalization and stoichiometry." ) if normalization == None: normalization = 'none' else: return 0.0 # if the requested species has no set value, return a float of 0.0 elif species != None: raise core.InputError( "Species must be either a string or a NoneType.") # Get the requested type of composition if units == 'wtpt_oxides': converted = composition elif units == 'mol_oxides': converted = self._wtpercentOxides_to_molOxides(composition) elif units == 'mol_cations': converted = self._wtpercentOxides_to_molCations(composition) elif units == 'mol_singleO': converted = self._wtpercentOxides_to_molSingleO(composition) else: raise core.InputError( "The units must be one of 'wtpt_oxides', 'mol_oxides', 'mol_cations', \ or 'mol_singleO'.") # Do requested normalization if normalization == 'none': final = converted elif normalization == 'standard': final = self._normalize_Standard(converted, units=units) elif normalization == 'fixedvolatiles': final = self._normalize_FixedVolatiles(converted, units=units) elif normalization == 'additionalvolatiles': final = self._normalize_AdditionalVolatiles(converted, units=units) else: raise core.InputError( "The normalization method must be one of 'none', 'standard', 'fixedvolatiles',\ or 'additionalvolatiles'.") if species == None: if asSampleClass == False: return final else: return Sample(final) elif isinstance(species, str): if asSampleClass == True: w.warn( "Cannot return single species as Sample class. Returning as float.", RuntimeWarning, stacklevel=2) return final[species]
def calculate_dissolved_volatiles(self, pressure, temperature, sample, X_fluid=1, coeffs='webapp', **kwargs): """ Calculates the dissolved CO2 concentration, using Eq (12) of Iacono-Marziano et al. (2012). If using the hydrous parameterization, it will use the scipy.root_scalar routine to find the root of the root_dissolved_volatiles method. Parameters ---------- pressure float Total pressure in bars. temperature float Temperature in C sample Sample class Magma major element composition. X_fluid float Mole fraction of H2O in the fluid. Default is 1.0. coeffs str Which set of coefficients should be used for H2O calculations: - 'webapp' (default) for the hydrous NBO/O parameterisation coefficients used in the Iacono-Marziano webapp. - 'manuscript' for the hydrous NBO/O parameterisation coefficients given in the Iacono-Marziano et al. (2012) manuscript. - 'anhydrous' for the anhydrous NBO/O parameterisation coefficients given in the Iacono-Marziano et al. (2012) manuscript. Returns ------- float Dissolved H2O concentration in wt%. """ if coeffs not in ['webapp', 'manuscript', 'anhydrous']: raise core.InputError( "The coeffs argument must be one of 'webapp', 'manuscript', " "or 'anhydrous'") temperature = temperature + 273.15 # translate T from C to K if isinstance(sample, sample_class.Sample) is False: raise core.InputError( "Sample must be an instance of the Sample class.") if pressure < 0: raise core.InputError("Pressure must be positive.") if temperature <= 0: raise core.InputError("Temperature must be greater than 0K.") if X_fluid < 0 or X_fluid > 1: raise core.InputError("X_fluid must have a value between 0 and 1.") if pressure == 0: return 0 if coeffs == 'webapp' or coeffs == 'manuscript': im_h2o_model = water() h2o = im_h2o_model.calculate_dissolved_volatiles( pressure=pressure, temperature=temperature - 273.15, sample=sample, X_fluid=1 - X_fluid, coeffs=coeffs, **kwargs) sample_h2o = sample.change_composition({'H2O': h2o}, inplace=False) d = np.array([-16.4, 4.4, -17.1, 22.8]) a = 1.0 b = 17.3 B = -6.0 C = 0.12 NBO_O = self.NBO_O(sample=sample_h2o, coeffs=coeffs) else: im_h2o_model = water() h2o = im_h2o_model.calculate_dissolved_volatiles( pressure=pressure, temperature=temperature - 273.15, sample=sample, X_fluid=1 - X_fluid, coeffs=coeffs, **kwargs) sample_h2o = sample.change_composition({'H2O': h2o}, inplace=False) d = np.array([2.3, 3.8, -16.3, 20.1]) a = 1.0 b = 15.8 B = -5.3 C = 0.14 NBO_O = self.NBO_O(sample=sample, coeffs=coeffs) fugacity = self.fugacity_model.fugacity(pressure=pressure, X_fluid=X_fluid, temperature=temperature - 273.15, **kwargs) if fugacity == 0: return 0 molarProps = sample_h2o.get_composition( units='mol_oxides', oxide_masses=self.IM_oxideMasses) if all(ox in molarProps for ox in [ 'Al2O3', 'CaO', 'K2O', 'Na2O', 'FeO', 'MgO', 'Na2O', 'K2O' ]) is False: raise core.InputError( "sample must contain Al2O3, CaO, K2O, Na2O, FeO, MgO, Na2O, " "and K2O.") if 'Fe2O3' in molarProps: Fe2O3 = molarProps['Fe2O3'] else: Fe2O3 = 0 x = list() if 'H2O' in molarProps: x.append(molarProps['H2O']) else: x.append(0.0) x.append(molarProps['Al2O3'] / (molarProps['CaO'] + molarProps['K2O'] + molarProps['Na2O'])) x.append((molarProps['FeO'] + Fe2O3 * 2 + molarProps['MgO'])) x.append((molarProps['Na2O'] + molarProps['K2O'])) x = np.array(x) CO3 = np.exp( np.sum(x * d) + a * np.log(fugacity) + b * NBO_O + B + C * pressure / temperature) CO2 = CO3 / 1e4 return CO2
def calculate_dissolved_volatiles(self, pressure, temperature, sample, X_fluid=1.0, coeffs='webapp', **kwargs): """ Calculates the dissolved H2O concentration, using Eq (13) of Iacono-Marziano et al. (2012). If using the hydrous parameterization, it will use the scipy.root_scalar routine to find the root of the root_dissolved_volatiles method. Parameters ---------- pressure float Total pressure in bars. temperature float Temperature in C sample pandas Series or dict Major element oxides in wt%. X_fluid float Mole fraction of H2O in the fluid. Default is 1.0. coeffs str Which set of coefficients should be used in the calculations: - 'webapp' (default) for the hydrous NBO/O parameterisation coefficients used in the Iacono-Marziano webapp. - 'manuscript' for the hydrous NBO/O parameterisation coefficients given in the Iacono-Marziano et al. (2012) manuscript, but were incorrect (pers.comm.). - 'anhydrous' for the anhydrous NBO/O parameterisation coefficients given in the Iacono-Marziano et al. (2012) manuscript. Returns ------- float Dissolved H2O concentration in wt%. """ if coeffs not in ['webapp', 'manuscript', 'anhydrous']: raise core.InputError( "The coeffs argument must be one of 'webapp', 'manuscript', " "or 'anhydrous'") temperature = temperature + 273.15 # translate T from C to K if isinstance(sample, sample_class.Sample) is False: raise core.InputError( "Sample must be an instance of the Sample class.") if pressure < 0: raise core.InputError("Pressure must be positive.") if X_fluid < 0 or X_fluid > 1: raise core.InputError("X_fluid must have a value between 0 and 1.") if pressure == 0: return 0 if coeffs == 'webapp' or coeffs == 'manuscript': if X_fluid == 0: return 0 H2O = root_scalar(self.root_dissolved_volatiles, args=(pressure, temperature, sample, X_fluid, coeffs, kwargs), x0=1.0, x1=2.0).root return H2O else: a = 0.54 b = 1.24 B = -2.95 C = 0.02 fugacity = self.fugacity_model.fugacity(pressure=pressure, X_fluid=X_fluid, temperature=temperature - 273.15, **kwargs) if fugacity == 0: return 0 NBO_O = self.NBO_O(sample=sample, coeffs=coeffs) H2O = np.exp(a * np.log(fugacity) + b * NBO_O + B + C * pressure / temperature) return H2O
def calculate_dissolved_volatiles(self, pressure, temperature=1200, sample=None, X_fluid=1.0, **kwargs): """ Calclates the dissolved CO2 concentration using (Eqns) 2-7 or 10-11 from Allison et al. (2019). Parameters ---------- pressure float Pressure in bars. temperature float Temperature in C. sample NoneType or Sample class Magma major element composition. Not required for this model, therefore None may be passed. X_fluid float The mole fraction of CO2 in the fluid. Default is 1.0. Returns ------- float Dissolved CO2 concentration in wt%. """ # temperature = 1200 #temp in degrees C temperature = temperature + 273.15 # translate T from C to K if pressure < 0.0: raise core.InputError("Pressure must be positive.") if X_fluid < 0 or X_fluid > 1: raise core.InputError("X_fluid must have a value between 0 and 1.") if self.model_fit not in ['power', 'thermodynamic']: raise core.InputError( "model_fit must be one of 'power', or 'thermodynamic'.") if self.model_loc not in [ 'sunset', 'sfvf', 'erebus', 'vesuvius', 'etna', 'stromboli' ]: raise core.InputError( "model_loc must be one of 'sunset', 'sfvf', 'erebus', ", "'vesuvius', 'etna', or 'stromboli'.") if pressure == 0: return 0 if self.model_fit == 'thermodynamic': P0 = 1000 # bar params = dict({ 'sunset': [16.4, -14.67], 'sfvf': [15.02, -14.87], 'erebus': [15.83, -14.65], 'vesuvius': [24.42, -14.04], 'etna': [21.59, -14.28], 'stromboli': [14.93, -14.68] }) DV = params[self.model_loc][0] lnK0 = params[self.model_loc][1] lnK = lnK0 - (pressure - P0) * DV / (10 * 8.3141 * temperature) fCO2 = self.fugacity_model.fugacity(pressure=pressure, temperature=temperature - 273.15, X_fluid=X_fluid, **kwargs) Kf = np.exp(lnK) * fCO2 XCO3 = Kf / (1 - Kf) FWone = 36.594 wtCO2 = (44.01 * XCO3) / ((44.01 * XCO3) + (1 - XCO3) * FWone) * 100 return wtCO2 if self.model_fit == 'power': params = dict({ 'stromboli': [1.05, 0.883], 'etna': [2.831, 0.797], 'vesuvius': [4.796, 0.754], 'sfvf': [3.273, 0.74], 'sunset': [4.32, 0.728], 'erebus': [5.145, 0.713] }) fCO2 = self.fugacity_model.fugacity(pressure=pressure, temperature=temperature - 273.15, X_fluid=X_fluid, **kwargs) return params[self.model_loc][0] * fCO2**params[ self.model_loc][1] / 1e4