MELTS: Calculate Olivine Control Line#

Extrapolates a melt composition to a more primitive composition under the assumption that only olivine has crystallised from the melt. This calculation might be used for estimating primary melt compositions, or the effect of post-entrapment crystallisation on olivine-hosted melt inclusions.

The calculation uses the rhyoliteMELTS v1.2 model.

Open this code in an executable MyBinder instance (MyBinder links may be slow to load– please be patient!):

https://mybinder.org/badge_logo.svg

Initialization#

Import the necessary packages

from thermoengine import model, magmaforge, redox
from thermoengine.core import chem
import pandas as pd
import numpy as np
from scipy.optimize import root_scalar
import matplotlib.pyplot as plt

Retrieve the MELTS v1.2 database and extract the olivine and liquid models.

db = model.Database("MELTS_v1_2")
liq = db.get_phase('Liq')
olv = db.get_phase('Ol')

Set up the Calculations#

This section sets up the algorithm for incrementally finding the equilibrium olivine composition at the liquidus, adding a small amount of it into the melt, and repeating.

You must run the cells below, but you do not need to change anything inside them to run a calculation.

def calculate_olivine_control_line(
        starting_comp : pd.Series,
        P_bar : float,
        target_Mgn : float,
        step_size : float = 0.005
        ) -> tuple[pd.DataFrame,
                   pd.DataFrame]:
    """
    Calculate an olivine control line, starting at the start composition and
    ending when the equilibrium olivine has the target Mgn. The Fe3/tFe ratio
    must be set in the starting_comp.

    Inputs
    ------
    starting_comp : pd.Series
        The starting composition in weight percent (or grams) of oxides
    P_bar : float
        The pressure in bars
    target_Mgn : float
        The olivine Mgn at which to terminate the calculation
    step_size : float
        The moles of olivine to add to the liquid composition at each step

    Outputs
    -------
    pd.DataFrame
        The melt compositions at each step on the olivine control line
    pd.DataFrame
        The olivine compositions at each step on the olivine control line
    """

    if 'Fe2O3' not in starting_comp or starting_comp['Fe2O3'] == 0.0:
        print("Warning: Fe2O3 set to 0.0")
    if 'FeO' not in starting_comp or starting_comp['FeO'] == 0.0:
        print("Warning: FeO is set to 0.0")

    if target_Mgn > 1 and target_Mgn < 100:
        target_Mgn = target_Mgn / 100
    elif target_Mgn > 100 or target_Mgn < 0:
        assert False, "Bad target Mg number."

    start_comp_arr = np.zeros(len(chem.OXIDE_ORDER))
    for i, ox in zip(range(len(chem.OXIDE_ORDER)), chem.OXIDE_ORDER):
        if ox in starting_comp:
            start_comp_arr[i] = starting_comp[ox]
        else:
            start_comp_arr[i] = 0.0
    start_comp_arr = start_comp_arr / np.sum(start_comp_arr) * 100

    liq_moles = magmaforge.System.convert_wtoxides_to_liquid_endmems(
        starting_comp, liq, oxides=starting_comp.index)

    olv_omni_comps = _calc_omni_comps_for_phases(olv, liq)


    T, olv_mols = find_liquidus_olivine(P_bar, liq_moles, olv_omni_comps)
    Mgn = olv_mols[5]/(olv_mols[1]+olv_mols[5])

    MAX_ITER = 1000
    iternum = 0

    check_phase_sat(T, P_bar, starting_comp)

    # Initialise dataframes for results
    liq_comps = np.zeros([MAX_ITER, len(chem.OXIDE_ORDER)])
    olv_comps = np.zeros([MAX_ITER, olv.endmember_num+1])
    liq_comps[0,:] = start_comp_arr
    olv_comps[0,:-1] = olv_mols
    olv_comps[0,-1] = Mgn
    T_seq = [T]

    while Mgn < target_Mgn and iternum < MAX_ITER:
        liq_moles += step_size * olv_omni_comps.T.dot(olv_mols)

        T, olv_mols = find_liquidus_olivine(P_bar, liq_moles, olv_omni_comps, T0=T)
        Mgn = olv_mols[5]/(olv_mols[1]+olv_mols[5])

        liq_mol_ox = liq.endmember_mol_oxide_comps.T.dot(liq_moles)
        liq_wtpt = chem.mol_to_wt_oxide(liq_mol_ox, chem.OXIDE_ORDER)
        liq_wtpt = liq_wtpt/np.sum(liq_wtpt) * 100
        liq_comps[iternum+1,:] = liq_wtpt


        olv_comps[iternum+1,:-1] = olv_mols
        olv_comps[iternum+1, -1] = Mgn
        T_seq.append(T)
        iternum += 1

    liq_comps = pd.DataFrame(liq_comps[:iternum+1,:], columns=chem.OXIDE_ORDER, index=T_seq)
    olv_comps = pd.DataFrame(olv_comps[:iternum+1,:],
                             columns=list(olv.endmember_names)+['Mg#'],
                             index=T_seq)

    return liq_comps, olv_comps


def find_liquidus_olivine(P, liq_moles, olv_omni_comps, T0=1500.0):
    res = root_scalar(_affncomp_root, x0=T0, args=(P, liq_moles, olv_omni_comps))
    assert res.converged, "Something has gone wrong when finding the temperature, check inputs."
    T = res.root
    affn, comp = get_olv_affncomp_from_liq(T, P, liq_moles, olv_omni_comps)
    return T, comp


def _affncomp_root(T, P, liq_moles, omni_comps):
    return get_olv_affncomp_from_liq(T, P, liq_moles, omni_comps)[0]

def get_olv_affncomp_from_liq(T, P, liq_moles, omni_comps):
    liq_mu = liq.chem_potential(T, P, mol=liq_moles)
    olv_mu = omni_comps.dot(liq_mu)
    affn, comp = olv.affinity_and_comp(T, P, olv_mu)
    return (affn, comp)

def _calc_omni_comps_for_phases(phase, omni_phase):
    """
    Calculates the matrix for conversion of phase compositions into
    omni_component endmembers.
    """

    omni_element_comps = omni_phase.endmember_element_comps
    inv_omni_element_comps = np.linalg.pinv(omni_element_comps)

    TOL = 1e-12

    phs_element_comps = phase.endmember_element_comps
    phs_omni_comps = phs_element_comps.dot(inv_omni_element_comps)

    phs_omni_comps[np.abs(phs_omni_comps) <= TOL] = 0

    return phs_omni_comps

def check_phase_sat(T, P, oxide_wtpt):
    sys = magmaforge.System(oxide_wtpt, T_K=T, P_bar=P, database='MELTS_v1_2')
    phs_mass = sys._system_calculator._total_mass_of_every_phase

    for phs in phs_mass:
        if phs not in ['Liquid', 'Olivine'] and phs_mass[phs] > 0.0:
            print(f"Warning: {phs} is saturated: {phs_mass[phs]:.2f} wt% present at olvine-saturation temperature.")

User Inputs#

Provide the starting composition of the magma here. You must have already determined the amount of FeO and Fe2O3 in the magma.

oxide_comp = pd.Series({
                        'SiO2':  48.32,
                        'TiO2':   0.93,
                        'Al2O3': 15.99,
                        'Fe2O3':  0.67,
                        'FeO':    8.36,
                        'MnO':    0.16,
                        'MgO':   10.56,
                        'CaO':   14.01,
                        'Na2O':   1.72,
                        'K2O':    0.05,
                        'P2O5':   0.03,
                        'H2O':    0.3,
                        'CO2':    0.05
                        })
# Composition of olivine-saturated glass from Kistufell, Iceland (Breddam, 2002)

Provide the pressure of crystallisation and the target Mg# for the equilibrium olivine:

pressure_bar = 1000.0
target_Mgn = 90.0

Run the calculation:

liq_comp, olv_comp = calculate_olivine_control_line(
                                            oxide_comp,
                                            P_bar=1000.0,
                                            target_Mgn=90.0
                                            )
[[1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0. ]
 [0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0. ]
 [0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0. ]
 [0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0. ]
 [0.  0.  0.  0.  1.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0. ]
 [1.  0.  0.  0.  0.  2.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0. ]
 [0.5 0.  0.  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0. ]
 [1.  0.  0.  0.  0.  0.  0.  2.  0.  0.  0.  0.  0.  0.  0.  0. ]
 [0.5 0.  0.  0.  0.  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0. ]
 [0.5 0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  0.  0.  0.  0.  0. ]
 [1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  0.  0.  0.  0. ]
 [1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  0.  0.  0. ]
 [1.  0.  0.5 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.5 0.  0.  0. ]
 [0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  3.  0.  0.  1.  0.  0. ]
 [0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  0. ]
 [0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1. ]]
(16, 16)

Results#

We can see the results as tables of the liquid and olivine compositions along the extrapolated liquid line of descent:

liq_comp
SiO2 TiO2 Al2O3 Fe2O3 Cr2O3 FeO MnO MgO NiO CoO CaO Na2O K2O P2O5 H2O CO2
1499.443504 47.770638 0.919427 15.808206 0.662383 0.0 8.264953 0.158181 10.439941 0.000000e+00 0.000000e+00 13.850717 1.700445 0.049432 0.029659 0.296589 0.049432
1506.843444 47.714975 0.912681 15.692221 0.657523 0.0 8.298730 0.158723 10.702024 7.191558e-14 1.495236e-14 13.753162 1.687969 0.049069 0.029441 0.294413 0.049069
1514.052214 47.660542 0.906041 15.578063 0.652739 0.0 8.330148 0.159229 10.961531 1.465331e-13 2.968716e-14 13.657094 1.675689 0.048712 0.029227 0.292271 0.048712
1521.078311 47.607292 0.899505 15.465683 0.648030 0.0 8.359348 0.159701 11.218464 2.238331e-13 4.420950e-14 13.562476 1.663601 0.048360 0.029016 0.290163 0.048360
1527.929701 47.555178 0.893069 15.355034 0.643394 0.0 8.386461 0.160139 11.472831 3.037349e-13 5.852427e-14 13.469269 1.651698 0.048014 0.028809 0.288087 0.048014
1534.613862 47.504160 0.886732 15.246070 0.638828 0.0 8.411609 0.160548 11.724643 3.862413e-13 7.263620e-14 13.377439 1.639977 0.047674 0.028604 0.286043 0.047674
1541.137830 47.454196 0.880490 15.138749 0.634332 0.0 8.434903 0.160927 11.973911 4.710762e-13 8.654988e-14 13.286951 1.628433 0.047338 0.028403 0.284029 0.047338
1547.508210 47.405250 0.874341 15.033029 0.629902 0.0 8.456448 0.161278 12.220653 5.582096e-13 1.002697e-13 13.197772 1.617061 0.047008 0.028205 0.282046 0.047008
1553.731227 47.357285 0.868283 14.928871 0.625537 0.0 8.476341 0.161603 12.464886 6.477613e-13 1.138000e-13 13.109872 1.605857 0.046682 0.028009 0.280091 0.046682
1559.812738 47.310269 0.862314 14.826237 0.621237 0.0 8.494671 0.161903 12.706628 7.394809e-13 1.271448e-13 13.023219 1.594817 0.046361 0.027817 0.278166 0.046361
1565.758290 47.264169 0.856431 14.725090 0.616999 0.0 8.511523 0.162180 12.945901 8.333135e-13 1.403082e-13 12.937785 1.583937 0.046045 0.027627 0.276268 0.046045
1571.573099 47.218956 0.850633 14.625395 0.612821 0.0 8.526975 0.162435 13.182727 9.290937e-13 1.532941e-13 12.853541 1.573213 0.045733 0.027440 0.274398 0.045733
1577.262098 47.174600 0.844917 14.527118 0.608703 0.0 8.541102 0.162668 13.417129 1.026887e-12 1.661062e-13 12.770461 1.562642 0.045426 0.027255 0.272554 0.045426
1582.829955 47.131076 0.839281 14.430225 0.604644 0.0 8.553970 0.162880 13.649131 1.126524e-12 1.787482e-13 12.688519 1.552219 0.045123 0.027074 0.270736 0.045123
1588.281088 47.088356 0.833725 14.334686 0.600640 0.0 8.565646 0.163073 13.878757 1.227947e-12 1.912236e-13 12.607688 1.541942 0.044824 0.026894 0.268943 0.044824
1593.619682 47.046416 0.828245 14.240470 0.596693 0.0 8.576188 0.163248 14.106034 1.331045e-12 2.035358e-13 12.527945 1.531808 0.044529 0.026718 0.267176 0.044529
1598.849698 47.005234 0.822840 14.147547 0.592799 0.0 8.585655 0.163406 14.330987 1.435738e-12 2.156882e-13 12.449266 1.521812 0.044239 0.026543 0.265432 0.044239
1603.974914 46.964785 0.817510 14.055889 0.588958 0.0 8.594099 0.163546 14.553642 1.541952e-12 2.276840e-13 12.371629 1.511953 0.043952 0.026371 0.263713 0.043952
1608.998902 46.925050 0.812251 13.965468 0.585170 0.0 8.601571 0.163671 14.774027 1.649741e-12 2.395263e-13 12.295010 1.502227 0.043669 0.026202 0.262016 0.043669
1613.925060 46.886007 0.807062 13.876258 0.581432 0.0 8.608117 0.163780 14.992167 1.758862e-12 2.512183e-13 12.219389 1.492631 0.043390 0.026034 0.260343 0.043390


olv_comp
Tephroite Fayalite Coolivine Niolivine Monticellite Forsterite Mg#
1499.443504 0.002446 0.133909 2.033307e-14 9.808708e-14 0.014781 0.848864 0.863744
1506.843444 0.002403 0.131136 2.033307e-14 1.032369e-13 0.014584 0.851877 0.866598
1514.052214 0.002361 0.128498 2.033307e-14 1.084382e-13 0.014396 0.854745 0.869312
1521.078311 0.002322 0.125984 2.033307e-14 1.136049e-13 0.014210 0.857484 0.871899
1527.929701 0.002284 0.123585 2.033307e-14 1.188510e-13 0.014032 0.860100 0.874365
1534.613862 0.002247 0.121293 2.033307e-14 1.237821e-13 0.013859 0.862601 0.876721
1541.137830 0.002212 0.119102 2.033307e-14 1.287378e-13 0.013691 0.864995 0.878974
1547.508210 0.002178 0.117003 2.033307e-14 1.339315e-13 0.013528 0.867291 0.881130
1553.731227 0.002146 0.114991 2.033307e-14 1.388337e-13 0.013370 0.869494 0.883197
1559.812738 0.002114 0.113061 2.033307e-14 1.437184e-13 0.013216 0.871610 0.885179
1565.758290 0.002084 0.111206 2.033307e-14 1.484219e-13 0.013066 0.873644 0.887083
1571.573099 0.002055 0.109423 2.033307e-14 1.532773e-13 0.012920 0.875602 0.888913
1577.262098 0.002027 0.107708 2.033307e-14 1.579412e-13 0.012777 0.877488 0.890674
1582.829955 0.002000 0.106056 2.033307e-14 1.625689e-13 0.012639 0.879306 0.892369
1588.281088 0.001973 0.104463 2.033307e-14 1.670823e-13 0.012504 0.881060 0.894002
1593.619682 0.001948 0.102927 2.033307e-14 1.715245e-13 0.012372 0.882753 0.895578
1598.849698 0.001923 0.101444 2.033307e-14 1.758948e-13 0.012243 0.884390 0.897098
1603.974914 0.001899 0.100011 2.033307e-14 1.803950e-13 0.012117 0.885972 0.898567
1608.998902 0.001876 0.098626 2.033307e-14 1.845653e-13 0.011994 0.887504 0.899986
1613.925060 0.001853 0.097287 2.033307e-14 1.887416e-13 0.011874 0.888986 0.901359


We can save the tables (which can be downloaded if you are running this in a binder):

liq_comp.to_csv('liquid_compositions.csv')
olv_comp.to_csv('olivine_compositions.csv')

But we can also plot the results:

fig, ax = plt.subplots(3,1, figsize=(3.75, 7))

ax[0].plot(liq_comp['MgO'], liq_comp['FeO'], marker='s', markersize=3)
ax[1].plot(liq_comp['MgO'], olv_comp['Mg#']*100, marker='s', markersize=3)
ax[2].plot(liq_comp['MgO'], liq_comp.index - 273.15, marker='s', markersize=3)

for a in ax:
    a.set_xlabel('MgO (wt%)')

ax[0].set_ylabel('FeO (wt%)')
ax[1].set_ylabel('Fo (mol%)')
ax[2].set_ylabel('Temperature (°C)')

fig.tight_layout()

plt.show()
plot olivine control line

Total running time of the script: (0 minutes 0.306 seconds)

Gallery generated by Sphinx-Gallery