Mineral Endmember Decomposition

A common task when working with mineral chemistry data is to take measured compositions and decompose these into relative proportions of mineral endmember compositions. pyrolite includes some utilities to achieve this and a limited mineral database for looking up endmember compositions.

import numpy as np
import pandas as pd

from pyrolite.mineral.mindb import get_mineral
from pyrolite.mineral.normative import endmember_decompose

First we’ll start with a composition of an unknown olivine:

comp = pd.Series({"MgO": 42.06, "SiO2": 39.19, "FeO": 18.75})

We can break this down into olivine endmebmers using the endmember_decompose() function:

ed = endmember_decompose(
    pd.DataFrame(comp).T, endmembers="olivine", order=1, molecular=True
)
ed
/home/docs/checkouts/readthedocs.org/user_builds/pyrolite/checkouts/main/pyrolite/comp/codata.py:88: UserWarning: Non-positive entries found in specified components. Negative values have been replaced with NaN. Renormalisation assumes all positive entries.
  warnings.warn(
/home/docs/checkouts/readthedocs.org/user_builds/pyrolite/checkouts/main/pyrolite/comp/codata.py:88: UserWarning: Non-positive entries found in specified components. Negative values have been replaced with NaN. Renormalisation assumes all positive entries.
  warnings.warn(
/home/docs/checkouts/readthedocs.org/user_builds/pyrolite/checkouts/main/pyrolite/comp/codata.py:43: UserWarning: Non-positive entries found. Closure operation assumes all positive entries.
  warnings.warn(
name forsterite fayalite
0 79.995 20.005


Equally, if you knew the likely endmembers beforehand, you could specify a list of endmembers:

ed = endmember_decompose(
    pd.DataFrame(comp).T, endmembers=["forsterite", "fayalite"], order=1, molecular=True
)
ed
/home/docs/checkouts/readthedocs.org/user_builds/pyrolite/checkouts/main/pyrolite/comp/codata.py:88: UserWarning: Non-positive entries found in specified components. Negative values have been replaced with NaN. Renormalisation assumes all positive entries.
  warnings.warn(
/home/docs/checkouts/readthedocs.org/user_builds/pyrolite/checkouts/main/pyrolite/comp/codata.py:88: UserWarning: Non-positive entries found in specified components. Negative values have been replaced with NaN. Renormalisation assumes all positive entries.
  warnings.warn(
forsterite fayalite
0 79.995 20.005


We can check this by recombining the components with these proportions. We can first lookup the compositions for our endmembers:

em = pd.DataFrame([get_mineral("forsterite"), get_mineral("fayalite")])
em.loc[:, ~(em == 0).all(axis=0)]  # columns not full of zeros
name group formula Mg Si O Fe
0 forsterite olivine Mg2SiO4 0.346 0.200 0.455 0.000
1 fayalite olivine Fe2SiO4 0.000 0.138 0.314 0.548


First we have to convert these element-based compositions to oxide-based compositions:

emvalues = (
    em.loc[:, ["Mg", "Si", "Fe"]]
    .pyrochem.to_molecular()
    .fillna(0)
    .pyrochem.convert_chemistry(to=["MgO", "SiO2", "FeO"], molecular=True)
    .fillna(0)
    .pyrocomp.renormalise(scale=1)
)
emvalues
/home/docs/checkouts/readthedocs.org/user_builds/pyrolite/checkouts/main/pyrolite/comp/codata.py:88: UserWarning: Non-positive entries found in specified components. Negative values have been replaced with NaN. Renormalisation assumes all positive entries.
  warnings.warn(
/home/docs/checkouts/readthedocs.org/user_builds/pyrolite/checkouts/main/pyrolite/comp/codata.py:88: UserWarning: Non-positive entries found in specified components. Negative values have been replaced with NaN. Renormalisation assumes all positive entries.
  warnings.warn(
MgO SiO2 FeO
0 0.667 0.333 0.000
1 0.000 0.333 0.667


These can now be used with our endmember proportions to regenerate a composition:

MgO SiO2 FeO
0 42.06 39.19 18.75


To make sure these compositions are within 0.01 percent:

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