Source code for meltPT.backtrack_compositions

"""
======================
backtrack_compositions
======================

Backtrack sample compositions to 'primary' compositions.
"""

import warnings

import numpy as np
import pandas as pd

import copy

MAJOR_OXIDES = [
    'SiO2','Al2O3','FeO','Fe2O3','MgO','CaO','Na2O','K2O','TiO2','MnO',
    'Cr2O3', 'P2O5', 'NiO', 'CoO', 'H2O', 'CO2']

[docs]def normalise(in_comp): """ Normalise dictionary so that values sum to 100%. Parameters --------- in_comp : dict The values to be normalised. Returns ------- out_comp : dict The normalised values. """ out_comp = {} total = sum(in_comp.values()) for phase in in_comp: out_comp[phase] = in_comp[phase] / total * 100. return out_comp
[docs]def fill_dict_with_nans(in_dict): """ Fill a dictionary with numpy.nan values. Parameters ---------- in_dict : dict The dictionary to be filled with nans. Returns out_dict : dict The dictionary with all values replaces with nan. """ out_dict = {} for key in in_dict: out_dict[key] = np.nan return out_dict
[docs]class BacktrackOlivineFractionation: """ Correct sample compositions for fractional olivine crystallisation. Follows the scheme of Lee et al. (2009, EPSL) to correct major-element compositions of erupted basalts for the effects of fractional olivine crystallisation. Silicon, iron and magnesium in proportions corresponding to olvine in equilibrium with the sample are added iteratively until the composition reaches some forsterite number representative of the mantle source. The partition coefficient, Kd, can be set to a constant value or be allowed to vary as a function of melt magnesium number, according to the scheme of Tamura et al. (2000, J. Pet). Parameters ---------- Kd : float or None If float, sets the value of the partition coefficient. If None, the partition coefficient is allowed to vary as a function of melt magnesium number. dm : float The mass increment to use during iterative olivine addition. verbose : bool If True, progress messages are printed during iterative addition of olivine. max_olivine_addition : float The maximum proportion of olivine to add before abandoning. """ def __init__( self, Kd=None, dm=0.0005, verbose=False, max_olivine_addition=0.3): self.fixed_Kd = Kd self.dm = dm self.verbose = verbose self.max_olivine_addition = max_olivine_addition @property def cation_mole(self): """ Convert oxide concentrations in weight percent to cation concentrations. Returns ------- out_comp : dict The cation concentrations. """ weights = { # Combined atomic weights, e.g., SiO2 = Si + O + O = 60.08 (g/mol). # Atomic weights given per cation, e.g., Al2O3 is divided by 2. 'SiO2': 60.08, 'Al2O3': 101.96 / 2., 'FeO': 71.84, 'Fe2O3': 159.69 / 2., 'MgO': 40.3, 'CaO': 56.08, 'Na2O': 61.98 / 2., 'K2O': 94.2 / 2., 'TiO2': 79.86, 'MnO': 70.94, 'Cr2O3': 151.99 / 2., 'P2O5': 141.942524 / 2., 'NiO': 74.69239999999999, 'CoO': 74.932195, 'H2O': 18.014680000000002 / 2., 'CO2': 44.009 } keys = { 'SiO2': 'Si', 'Al2O3': 'Al', 'FeO': 'FeII', 'Fe2O3': 'FeIII', 'MgO': 'Mg', 'CaO': 'Ca', 'Na2O': 'Na', 'K2O': 'K', 'TiO2': 'Ti', 'MnO': 'Mn', 'Cr2O3': 'Cr', 'P2O5': 'P', 'NiO': 'Ni', 'CoO': 'Co', 'H2O': 'H', 'CO2': 'C' } out_comp = {} for phase in self.oxide_wt_hydrous: out_comp[keys[phase]] = self.oxide_wt_hydrous[phase] / weights[phase] return normalise(out_comp) @property def Kd(self): """ Compute the partition coefficient. If a fixed value has been specified, it is returned. Otherwise, calculate as function of Mg & FeII content, using expression from Tamura et al. (2000, J. Pet), via Lee et al. (2009) spreadsheet. """ if self.fixed_Kd: return self.fixed_Kd else: cation = self.cation_mole return 0.25324 + 0.0033663*(cation['Mg'] + 0.33*cation['FeII']) @property def Fo(self): """ Compute forsterite number from oxide weight compositions. Returns ------- Fo : float The calculated forsterite number. """ cation = self.cation_mole Fo = 1. / (1. + (self.Kd * cation['FeII'] / cation['Mg'])) return Fo
[docs] def add_olivine(self): """ Add olivine in equilibrium with given melt composition. Returns ------- out_comp : dict The updated concentrations. """ Fo = self.Fo # Compute olivine composition in equilibrium with melt oxide_wt_olivine = { 'FeO': 2. * (1 - Fo) * 71.85, 'MgO': 2. * Fo * 40.3, 'SiO2': 60.08 } oxide_wt_olivine = normalise(oxide_wt_olivine) # loop over phases adding olivine out_comp = {} for phase in self.oxide_wt_hydrous: if phase == 'SiO2' or phase == 'FeO' or phase == 'MgO': out_comp[phase] = ( (self.oxide_wt_hydrous[phase] + self.dm*oxide_wt_olivine[phase]) / (1. + self.dm) ) else: out_comp[phase] = self.oxide_wt_hydrous[phase] / (1. + self.dm) return normalise(out_comp)
[docs] def backtrack_sample_composition(self, df, return_all=False): """ Backtrack composition to desired mantle forsterite number. Iteratively adds olivine in equilibrium with melt until desired composition is reached. Parameters ---------- df : pandas dataframe Dataframe containing the initial composition to be backtracked and the target forsterite number. Should contain only one row. To use with a multi-row dataframe use df.apply(). return_all : bool Return intermediate backtracking compositions. Returns ------- primary_oxide : df The backtracked compositions. composition_through_addition : list If return_all is True, returns list of dictionaries containing intermediate backtracking compositions. """ # Get major oxides from data frame self.oxide_wt_hydrous = {} for ox in MAJOR_OXIDES: self.oxide_wt_hydrous[ox] = df[ox] self.oxide_wt_hydrous = normalise(self.oxide_wt_hydrous) # Check Fo is below mantle Fo if df['src_Fo']-self.Fo < 0.001: self.oxide_wt_hydrous = fill_dict_with_nans(self.oxide_wt_hydrous) dm_tot = np.nan message = str(df.Sample) + ": backtracking failed! Starting Fo above mantle Fo." warnings.warn(message) # Otherwise add olvine until primary Fo is reached else: if self.verbose: print("Backtracking sample %s to primary composition:" % df.Sample) dm_tot = 0. composition_through_addition = [] while df['src_Fo'] - self.Fo > 0.0002: self.oxide_wt_hydrous = self.add_olivine() comp = copy.copy(self.oxide_wt_hydrous) comp['Fo'] = self.Fo composition_through_addition.append(comp) dm_tot += self.dm if self.verbose: print( " - %.2f%% olivine added, melt Fo = %.4f, Kd = %.4f." % (dm_tot/(1.+dm_tot)*100., self.Fo, self.Kd) ) if dm_tot/(1. + dm_tot) > self.max_olivine_addition: self.oxide_wt_hydrous = fill_dict_with_nans(self.oxide_wt_hydrous) message = ( df.Sample + ": backtracking failed! Olivine addition exceeding %d%%" % (self.max_olivine_addition*100.)) warnings.warn(message) break # Package up primary_oxide = {} for phase in self.oxide_wt_hydrous: primary_oxide[phase + "_primary_wt"] = self.oxide_wt_hydrous[phase] # Add amount olivine added primary_oxide['ol_added'] = dm_tot / (1. + dm_tot) if not return_all: return primary_oxide else: return primary_oxide, composition_through_addition