In [1]:
%cat 0Source_Citation.txt


Source and citation

- This notebook is a part of the `pytheos` package.
- Website: http://github.com/SHDShim/pytheos.
- How to cite: S.-H. Shim (2017) Pytheos - a python tool set for equations of state. DOI: 10.5281/zenodo.802392

In [2]:
%matplotlib inline
# %matplotlib notebook # for interactive

For high dpi displays.


In [3]:
%config InlineBackend.figure_format = 'retina'

0. General note

  • Through high pressure experiments, we can measure the molar volume of minerals at high pressure. The data can be then fit to static equations of state, in order to obtain bulk modulus $(K_0)$ and pressure derivative of bulk modulus $(K'_0)$.

  • pytheos uses lmfit to provide equation of state fitting for static compression data. It has a few convenient features for the flexibility, such as fixing/varying particular parameters, propagating uncertainties, etc. This notebook shows a few examples.

1. Global setup


In [4]:
import numpy as np
import matplotlib.pyplot as plt
import pytheos as eos

2. Setup fitting models

Using MgO, we create models for three equations for static EOS. Volume can be either in unit-cell volume or molar volume. But v and v0 should be in the same unit.


In [5]:
v0 = 11.244; k0 = 160.; k0p = 4.0

Three different equations for static EOS of MgO.


In [6]:
exp_bm3 = eos.BM3Model()
exp_vinet = eos.VinetModel()
exp_kunc = eos.KuncModel(order=5) # this way I can handle order

Assign initial values to the parameters.


In [7]:
params = exp_bm3.make_params(v0=v0, k0=k0, k0p=k0p)

3. Synthesize data

We make data with random noise.


In [8]:
v_data = v0 * np.linspace(0.99,0.6,20)

Calculate pressures from three different equations.


In [9]:
p_bm3 = exp_bm3.eval(params, v=v_data)
p_vinet = exp_vinet.eval(params, v=v_data)
p_kunc = exp_kunc.eval(params, v=v_data)

Create random noise (noise) and add it to pressure value to simulate the data.


In [10]:
sp = np.random.normal(0.,2.,20)
p_data = p_bm3 + sp

Plot the synthetic data together with true values.


In [11]:
plt.plot(p_data, v_data, 'ko', label='data')
plt.plot(p_bm3, v_data, label='bm3')
plt.plot(p_vinet, v_data, label='vinet')
plt.plot(p_kunc, v_data, label='kunc')
plt.xlabel('Pressure (GPa)'); plt.ylabel('Molar volume (cm$^3$/mol)')
plt.legend();


The cell below shows the systematic differences between the equations.


In [12]:
plt.plot(p_bm3, p_vinet-p_bm3, label='Vinet - BM3')
plt.plot(p_bm3, p_kunc-p_bm3, label='Kunc - BM3')
plt.xlabel('Pressure (GPa)'); plt.ylabel('$\Delta P$')
plt.axhline(0, c='k', ls=':')
plt.legend();


4. EOS fitting

We fix $V_0$ as it is typically well known. We can also fix any parameters in lmfit.


In [13]:
params['v0'].vary = False
#params['k0'].vary = False

It is common practice not to include weight for the $P$-$V$ fit because it will de-emphasize the high pressure data points. But if you need, you can set weight in the following way. None performs unweighted fitting


In [14]:
weight = None # 1./sp#None

The cell below performs fitting with BM3 equation.


In [15]:
fitresult_bm3 = exp_bm3.fit(p_data, params, v=v_data, weights=weight)

You may print out the fitting result.


In [16]:
print(fitresult_bm3.fit_report())


[[Model]]
    Model(bm3_p)
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 9
    # data points      = 20
    # variables        = 2
    chi-square         = 82.7533163
    reduced chi-square = 4.59740646
    Akaike info crit   = 32.4026363
    Bayesian info crit = 34.3941009
[[Variables]]
    v0:   11.244 (fixed)
    k0:   161.184559 +/- 3.34337658 (2.07%) (init = 160)
    k0p:  3.97340075 +/- 0.08166897 (2.06%) (init = 4)
[[Correlations]] (unreported correlations are < 0.100)
    C(k0, k0p) = -0.974

Fitting can be performed for other equations.


In [17]:
fitresult_vinet = exp_vinet.fit(p_data, params, v=v_data, weights=weight)
print(fitresult_vinet.fit_report())


[[Model]]
    Model(vinet_p)
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 13
    # data points      = 20
    # variables        = 2
    chi-square         = 70.5501733
    reduced chi-square = 3.91945407
    Akaike info crit   = 29.2118372
    Bayesian info crit = 31.2033018
[[Variables]]
    v0:   11.244 (fixed)
    k0:   155.330988 +/- 3.46117323 (2.23%) (init = 160)
    k0p:  4.39367873 +/- 0.11120646 (2.53%) (init = 4)
[[Correlations]] (unreported correlations are < 0.100)
    C(k0, k0p) = -0.981


In [18]:
fitresult_kunc = exp_kunc.fit(p_data, params, v=v_data, weights=weight)
print(fitresult_kunc.fit_report())


[[Model]]
    Model(kunc_p, order='5')
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 10
    # data points      = 20
    # variables        = 2
    chi-square         = 78.0682269
    reduced chi-square = 4.33712372
    Akaike info crit   = 31.2370175
    Bayesian info crit = 33.2284820
[[Variables]]
    v0:   11.244 (fixed)
    k0:   158.739072 +/- 3.71323201 (2.34%) (init = 160)
    k0p:  4.13280871 +/- 0.11673120 (2.82%) (init = 4)
[[Correlations]] (unreported correlations are < 0.100)
    C(k0, k0p) = -0.981

5. Play with the results

The example below shows how to get individual parameters from the fitting result.


In [19]:
fitresult_vinet.params['k0p'].value


Out[19]:
4.393678725553541

You can also get estimated uncertainties.


In [20]:
fitresult_vinet.params['k0p'].stderr


Out[20]:
0.11120646147434275

The cells below show how to calculate fitted pressure values at the data points. First, we get the fit parameters.


In [21]:
v0_r = fitresult_vinet.params['v0']
k0_r = fitresult_vinet.params['k0']
k0p_r = fitresult_vinet.params['k0p']

Then we get the function used for the fitting.


In [22]:
f = fitresult_vinet.model

eval conducts calculation for given volume value.


In [23]:
f.eval(params, v=v_data)


Out[23]:
array([  1.64066312,   5.27636535,   9.30448249,  13.76766015,
        18.71382605,  24.19695044,  30.27793358,  37.0256445 ,
        44.518141  ,  52.84410719,  62.10455355,  72.41483479,
        83.90705452,  96.73294249, 111.06731255, 127.11223733,
       145.10211304, 165.30983535, 188.05437166, 213.71009878])

In [24]:
f.func(v_data, v0, k0, k0p)


Out[24]:
array([  1.64066312,   5.27636535,   9.30448249,  13.76766015,
        18.71382605,  24.19695044,  30.27793358,  37.0256445 ,
        44.518141  ,  52.84410719,  62.10455355,  72.41483479,
        83.90705452,  96.73294249, 111.06731255, 127.11223733,
       145.10211304, 165.30983535, 188.05437166, 213.71009878])

If you want to get array of (p, v) for plotting smooth fitting curve:


In [25]:
v_fitline = np.linspace(v0,v_data.min(),100)

In [26]:
p_fitline = f.func(v_fitline, v0_r, k0_r, k0p_r)

In [27]:
plt.plot(p_fitline, v_fitline)
plt.plot(p_data, v_data, 'k.', label='data')
plt.xlabel('Pressure (GPa)'); plt.ylabel('Molar volume (cm$^3$/mol)');


The cell below shows how to get the fitting results with uncertainties.


In [28]:
fitresult_bm3.params


Out[28]:
Parameters([('v0', <Parameter 'v0', value=11.244 (fixed), bounds=[0.0:inf]>),
            ('k0',
             <Parameter 'k0', value=161.1845592553341 +/- 3.34, bounds=[0.0:inf]>),
            ('k0p',
             <Parameter 'k0p', value=3.973400754970215 +/- 0.0817, bounds=[0.0:inf]>)])

The cell below shows how to get the uncertainties of the fit curve.


In [29]:
from uncertainties import ufloat
p_err = f.func(v_fitline, ufloat(fitresult_bm3.params['v0'].value, 0.),
         ufloat(fitresult_bm3.params['k0'].value, fitresult_bm3.params['k0'].stderr),
         ufloat(fitresult_bm3.params['k0p'].value, fitresult_bm3.params['k0p'].stderr))

The cell below shows the results in a table. It only shows the first five data points, but p_err contains 100 points as we setup that way above.


In [30]:
import pandas as pd
df = pd.DataFrame()
df['p'] = p_err
df.head()


Out[30]:
p
0 0.0+/-0
1 0.658+/-0.014
2 1.329+/-0.028
3 2.01+/-0.04
4 2.71+/-0.06

pytheos has internal plot script, which takes care of the plotting the fitting results with useful information.


In [31]:
eos.plot.static_fit_result(fitresult_bm3, p_err=sp)


How about the fit results for the other functions?


In [32]:
eos.plot.static_fit_result(fitresult_vinet)



In [33]:
eos.plot.static_fit_result(fitresult_kunc)


Why the estimated uncertainty shown in the bottom parts of the figures are greater in Vinet and Kunc fittings? It is perhaps due to the systematic differences among the equations. Note that the synthetic data set was created from BM3 equation.