Source code for MagmaPandas.thermometers.melt

"""
Sub-module with melt-only thermometers
"""

import inspect
import sys

import elementMass as e
import numpy as np
import pandas as pd

from MagmaPandas.core.magma_protocol import Magma
from MagmaPandas.parse_io import check_components
from MagmaPandas.thermometers.validate_temperatures import _check_temperature
from MagmaPandas.tools.calibration_range import _check_calibration_range
from MagmaPandas.tools.modify_compositions import (
    _anhydrous_composition,
    _get_elements,
    _remove_elements,
    cation_moles_per_oxygen,
)

calibration_range = {
    "putirka2008_14": [
        ["SiO2", 31, 73.64],
        [["Na2O", "K2O"], 0, 14.3],
        ["H2O", 0, 18.6],
    ],
    "putirka2008_15": [
        ["SiO2", 31, 73.64],
        [["Na2O", "K2O"], 0, 14.3],
        ["H2O", 0, 18.6],
    ],
}

errors = pd.Series(
    {
        "putirka2008_13": 71,
        "putirka2008_14": 58,
        "putirka2008_15": 46,
        "putirka2008_16": 26,
        "putirka2008_22": 32,  # from Wieser et al. (2025) Treatise of Geochemistry
        "sun2020": 49,
        "shea2022": 13,  # on calibration dataset, not validated
        "sugawara2000_3": 33,  # on calibration dataset
        "sugawara2000_6a": 30,  # on calibration dataset
    }
)

components = {
    "putirka2008_13": ["MgO"],
    "putirka2008_14": ["MgO", "FeO", "Na2O", "K2O", "H2O"],
    "putirka2008_15": ["MgO", "FeO", "Na2O", "K2O", "H2O"],
    "putirka2008_16": ["SiO2", "Al2O3", "MgO"],
    "putirka2008_22": ["SiO2", "FeO", "MnO", "MgO", "CaO", "CoO", "NiO", "H2O"],
    "sun2020": ["MgO", "CaO", "K2O", "TiO2", "FeO", "CO2", "H2O"],
    "shea2022": ["MgO"],
    "sugawara2000_3": ["MgO"],
    "sugawara2000_6a": ["MgO", "FeO", "CaO", "SiO2"],
}

_Beattie_constants = pd.DataFrame(
    {
        "A": [1.0, 0.259, 0.299, 0.786, 3.346],
        "B": [0.0, -4.9e-2, 2.7e-2, -0.385, -3.665],
    },
    index=["Mg", "Mn", "Fe", "Co", "Ni"],
)


[docs] def putirka2008_13( melt: Magma, offset: float = 0.0, *args, **kwargs ) -> float | pd.Series: """ melt-only thermometer Equation 13 from :cite:t:`Putirka2008a` calculates liquidus temperatures based on melt compositions. Requires saturation in olivine. SEE = 71 degrees Parameters ---------- melt : Magma melt compositions in oxide wt. % offset : float offset value in standard deviations. Temperatures are calculated as ``temnperature + offset * thermometer error (SEE)``. Returns ------- temperatures : float, pandas Series liquidus temperatures in Kelvin. """ composition = check_components( composition=melt, components=components["putirka2008_13"] ) T_K = 26.3 * composition["MgO"] + 994.4 + 273.15 _check_temperature(T_K) T_K = T_K + errors["putirka2008_13"] * offset return pd.Series(T_K, name="T_K").squeeze()
[docs] def putirka2008_14( melt: Magma, warn=True, offset: float = 0.0, *args, **kwargs ) -> float | pd.Series: """ melt-only thermometer Equation 14 from :cite:t:`Putirka2008a` calculates liquidus temperatures based on melt compositions. Requires saturation in olivine. SEE = 58 degrees Applicable between: - 0 - 14.4 GPa - 729 - 2000 degrees C - 31 - 73.64 wt. % SiO\ :sub:`2` - 0 - 14.3 wt. % Na\ :sub:`2`\ O + K\ :sub:`2`\ O - 0 - 18.6 wt. % H\ :sub:`2`\ O Parameters ---------- melt : Magma melt compositions in oxide wt. % offset : float offset value in standard deviations. Temperatures are calculated as ``temnperature + offset * thermometer error (SEE)``. Returns ------- temperatures : float, pandas Series liquidus temperatures in Kelvin. """ elements = _get_elements(melt) composition = check_components( composition=melt, components=components["putirka2008_14"] ) if warn: _check_calibration_range( melt=composition, calibration_range=calibration_range["putirka2008_14"] ) # oxides_needed = set(["MgO", "FeO", "Na2O", "K2O"]) # composition = parse_data(melt, oxides_needed) H2O = composition["H2O"].copy() anhydrous_composition = _anhydrous_composition(composition, normalise=False) # Calculate molar oxide fractions mol_fractions = anhydrous_composition.moles() # Melt Mg# Mg_no = mol_fractions["MgO"] / ( mol_fractions["MgO"] + mol_fractions["FeO"] ) # Putirka doesn't specify whether this should be Fe2+ or Fe(total) if "Fe2O3" in elements: FeO_w, Fe2O3_w = e.compound_weights(["FeO", "Fe2O3"]) composition["FeO"] = composition["FeO"] + ( 2 * composition["Fe2O3"] * (FeO_w / Fe2O3_w) ) T_K = ( 754 + 190.6 * Mg_no + 25.52 * composition["MgO"] + 9.585 * composition["FeO"] + 14.87 * (composition["Na2O"] + composition["K2O"]) - 9.176 * H2O ) + 273.15 _check_temperature(T_K) T_K = T_K + errors["putirka2008_14"] * offset return pd.Series(T_K, name="T_K").squeeze()
[docs] def putirka2008_15( melt: Magma, P_bar: float | pd.Series, offset: float = 0.0, warn=True, **kwargs, ) -> float | pd.Series: """ melt-only thermometer Equation 15 from :cite:t:`Putirka2008a` calculates liquidus temperatures based on melt compositions. Requires saturation in olivine. SEE = 46 degrees Applicable between: - 0 - 14.4 GPa - 729 - 2000 degrees C - 31 - 73.64 wt. % SiO\ :sub:`2` - 0 - 14.3 wt. % Na\ :sub:`2`\ O + K\ :sub:`2`\ O - 0 - 18.6 wt. % H\ :sub:`2`\ O Parameters ---------- melt : Magma melt compositions in oxide wt. % P_bar : float, pandas Series pressures in bar. offset : float offset value in standard deviations. Temperatures are calculated as ``temnperature + offset * thermometer error (SEE)``. Returns ------- temperatures : float, pandas Series liquidus temperatures in Kelvin. """ elements = _get_elements(melt) composition = check_components( composition=melt, components=components["putirka2008_15"] ) if warn: _check_calibration_range( melt=composition, calibration_range=calibration_range["putirka2008_15"] ) # oxides_needed = set(["MgO", "FeO", "Na2O", "K2O"]) # composition = parse_data(melt, oxides_needed) H2O = composition["H2O"].copy() anhydrous_composition = _anhydrous_composition(composition, normalise=False) P_GPa = P_bar / 1e4 # Calculate molar oxide fractions mol_fractions = anhydrous_composition.moles() # Melt Mg# Mg_no = mol_fractions["MgO"] / ( mol_fractions["MgO"] + mol_fractions["FeO"] ) # Putirka doesn't specify whether this should be Fe2+ or Fe(total) if "Fe2O3" in elements: FeO_w, Fe2O3_w = e.compound_weights(["FeO", "Fe2O3"]) composition["FeO"] = composition["FeO"] + ( 2 * composition["Fe2O3"] * (FeO_w / Fe2O3_w) ) T_K = ( 815.3 + 265.5 * Mg_no + 15.37 * composition["MgO"] + 8.61 * composition["FeO"] + 6.646 * (composition["Na2O"] + composition["K2O"]) + 39.16 * P_GPa - 12.83 * H2O ) + 273.15 _check_temperature(T_K) T_K = T_K + errors["putirka2008_15"] * offset return pd.Series(T_K, name="T_K").squeeze()
[docs] def putirka2008_16( melt: Magma, P_bar: float | pd.Series, offset: float = 0.0, **kwargs ) -> float | pd.Series: """ melt-only thermometer Equation 16 from :cite:t:`Putirka2008a` calculates liquiqdus temperatures based on melt compositions. Requires equilibrium with olivine + plagioclase + clinopyroxene and saturation with additional phases drastically increases the standard error of estimate. SEE = 26 degrees (saturation in olivine + plagioclase + clinopyroxene) = 60 degrees (saturation with additional phases) Parameters ---------- melt : Magma melt compositions in oxide wt. % P_bar : float, pandas Series pressures in bar offset : float offset value in standard deviations. Temperatures are calculated as ``temnperature + offset * thermometer error (SEE)``. Returns ------- temperatures : float, pandas Series liquidus temperatures in Kelvin. """ elements = _get_elements(melt) composition = check_components( composition=melt, components=components["putirka2008_16"] ) # oxides_needed = set(["SiO2", "Al2O3", "MgO"]) # composition = parse_data(melt, oxides_needed) # import MagmaPandas as mp if isinstance(P_bar, pd.Series): if not melt.index.equals(P_bar.index): raise RuntimeError("Melt and P_bar indices don't match") if "H2O" in elements: composition = _anhydrous_composition(composition, normalise=False) # Convert pressure from bars to GPa P_GPa = P_bar / 1e4 # Calculate molar oxide fractions mol_fractions = composition.moles() part_1 = ( -583 + 3141 * mol_fractions["SiO2"] + 15779 * mol_fractions["Al2O3"] + 1338.6 * mol_fractions["MgO"] ) part_2 = -31440 * mol_fractions["SiO2"] * mol_fractions["Al2O3"] + 77.67 * P_GPa T_K = part_1 + part_2 + 273.15 _check_temperature(T_K) T_K = T_K + errors["putirka2008_16"] * offset return pd.Series(T_K, name="T_K").squeeze()
[docs] def putirka2008_22( melt: Magma, P_bar: float | pd.Series, offset: float = 0.0, **kwargs, ) -> float | pd.Series: """ melt-only thermometer Equation 22 from :cite:t:`Putirka2008a`, combined with equation 12 from Beattie (1993) calculates liquidus temperatures based on melt compositions. #TODO add reference link Parameters ---------- melt : Magma melt compositions in oxide wt. % P_bar : float, pandas Series pressures in bar. offset : float offset value in standard deviations. Temperatures are calculated as ``temnperature + offset * thermometer error (SEE)``. Returns ------- temperatures : float, pandas Series liquidus temperatures in Kelvin. """ axis = [0, 1][isinstance(melt, pd.DataFrame)] composition = check_components( composition=melt, components=components["putirka2008_22"] ) H2O = composition["H2O"].copy() composition = _anhydrous_composition(composition) # if warn: # _check_calibration_range( # melt=composition, calibration_range=calibration_range["putirka2008_22"] # ) P_GPa = P_bar / 1e4 cations = composition.cations() lnD_Mg = np.log( ( 0.666 - cations[_Beattie_constants.index] .mul(_Beattie_constants.loc[:, "B"], axis=axis) .sum(axis=axis) ) / cations[_Beattie_constants.index] .mul(_Beattie_constants.loc[:, "A"], axis=axis) .sum(axis=axis) ) # equation 12 from Beattie (1993) C_NM = (cations[["Fe", "Mn", "Mg", "Ca", "Co", "Ni"]]).sum(axis=axis) NF = 7 / 2 * np.log(1 - cations["Al"]) + 7 * np.log( 1 - cations["Ti"] ) # Table 1 from Putirka (2008) T_K = (15294.6 + 1318.8 * P_GPa + 2.4834 * P_GPa**2) / ( 8.048 + 2.8352 * lnD_Mg + 2.097 * np.log(1.5 * C_NM) + 2.575 * np.log(3 * cations["Si"]) - 1.41 * NF + 0.222 * H2O + 0.5 * P_GPa ) + 273.15 # equation 22 from Putirka (2008) _check_temperature(T_K) T_K = T_K + errors["putirka2008_22"] * offset return pd.Series(T_K, name="T_K").squeeze()
[docs] def sun2020(melt, P_bar: float | pd.Series, offset: float = 0.0, **kwargs): """ Equation 6 from: :cite:t:`Sun2020a` Calibrated at: ~ 2 - 10 GPa ~ 950 - 1600 degrees C SEE: 49 degrees C Parameters ---------- melt : Magma melt compositions in oxide wt. % P_bar : pressures in bar offset : float offset value in standard deviations. Temperatures are calculated as ``temnperature + offset * thermometer error (SEE)``. Returns ------- temperatures : float, pandas Series liquidus temperatures in Kelvin. """ P_GPa = P_bar / 1e4 composition = check_components(composition=melt, components=components["sun2020"]) moles = composition.moles() composition_volatile_free = _remove_elements( composition=moles, drop=["H2O", "CO2", "F", "S", "Cl"] ) moles_unit_oxygen = cation_moles_per_oxygen(moles=composition_volatile_free) Omega = ( 2.59 + 3.5 * (moles_unit_oxygen["Ca1O"] - 2 * moles_unit_oxygen["K2O"]) + 4.85 * moles_unit_oxygen["Ti1/2O"] + 1.4 * ( moles_unit_oxygen["Mg1O"] / (moles_unit_oxygen["Mg1O"] + moles_unit_oxygen["Fe1O"]) ) + 0.5 * moles_unit_oxygen["Mg1O"] * np.sqrt(composition["CO2"]) + 5.7e-2 * composition["H2O"] ) T_K = 1e4 / ( Omega - 0.34 * np.sqrt(P_GPa) - 1.26 * np.log(moles_unit_oxygen["Mg1O"]) ) _check_temperature(T_K) T_K = T_K + errors["sun2020"] * offset return pd.Series(T_K, name="T_K").squeeze()
[docs] def shea2022(melt, offset: float = 0.0, **kwargs): """ Equation 1 from :cite:t:`Shea2022` Calibrated at: 1 bar 1060 - 1500 degrees C SEE: 13 degrees C (on calibration dataset, not validated) Parameters ---------- melt : Magma melt compositions in oxide wt. % offset : float offset value in standard deviations. Temperatures are calculated as ``temnperature + offset * thermometer error (SEE)``. Returns ------- temperatures : float, pandas Series liquidus temperatures in Kelvin. """ composition = check_components(composition=melt, components=components["shea2022"]) T_K = 21.2 * composition["MgO"] + 1017 + 273.15 _check_temperature(T_K) T_K = T_K + errors["shea2022"] * offset return pd.Series(T_K, name="T_K").squeeze()
[docs] def sugawara2000_3(melt, P_bar: float | pd.Series, offset: float = 0.0, **kwargs): """ Equation 3 from :cite:t:`Sugawara2000` with olivine-liquid parameters and corrected for H2O according to equation 7a. Calibrated at: <= 3.5 GPa 1266 - 1873 C SEE: 33 degrees C Parameters ---------- melt : Magma melt compositions in oxide wt. % P_bar : pressures in bar offset : float offset value in standard deviations. Temperatures are calculated as ``temnperature + offset * thermometer error (SEE)``. Returns ------- temperatures : float, pandas Series liquidus temperatures in Kelvin. """ composition = check_components( composition=melt, components=components["sugawara2000_3"] ) anhydrous_composition = _anhydrous_composition(composition=composition) moles_percent = anhydrous_composition.moles() * 100 A = 1293 B = 14.60 C = 5.5e-3 T_K = A + B * moles_percent["MgO"] + C * P_bar if "H2O" in composition.elements: T_K = T_K - 5.403 * composition.moles()["H2O"] * 100 _check_temperature(T_K) T_K = T_K + errors["sugawara2000_3"] * offset return pd.Series(T_K, name="T_K").squeeze()
[docs] def sugawara2000_6a(melt, P_bar: float | pd.Series, offset: float = 0.0, **kwargs): """ Equation 6a corrected for H2O according to equation 7a from :cite:t:`Sugawara2000` Calibrated at: <= 3.5 GPa 1266 - 1873 C SEE: 30 degrees C Parameters ---------- melt : Magma melt compositions in oxide wt. % P_bar : pressures in bar offset : float offset value in standard deviations. Temperatures are calculated as ``temnperature + offset * thermometer error (SEE)``. Returns ------- temperatures : float, pandas Series liquidus temperatures in Kelvin. """ composition = check_components( composition=melt, components=components["sugawara2000_6a"] ) anhydrous_composition = _anhydrous_composition(composition=composition) anhydrous_moles_percent = anhydrous_composition.moles() * 100 T_K = ( 1466 - 1.44 * anhydrous_moles_percent["SiO2"] - 0.5 * anhydrous_moles_percent["FeO"] + 12.32 * anhydrous_moles_percent["MgO"] - 3.899 * anhydrous_moles_percent["CaO"] + 4.3e-3 * P_bar ) if "H2O" in composition.elements: T_K = T_K - 5.403 * composition.moles()["H2O"] * 100 _check_temperature(T_K) T_K = T_K + errors["sugawara2000_6a"] * offset return pd.Series(T_K, name="T_K").squeeze()
_module = sys.modules[__name__] _funcs = inspect.getmembers(_module, inspect.isfunction) # collect all melt thermometers in a dictionary melt_thermometers_dict = { f[0]: f[1] for f in _funcs if f[1].__module__ == _module.__name__ }