Grid Search - Romont - 6 free parameters

Romont site, MLE with 6 free parameters (grid search method).

For more info about the method used, see the notebook Inference_Notes.

This notebook has the following external dependencies:


In [1]:
import math
import csv

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
import seaborn as sns
import yaml

%matplotlib inline

The mathematical (Predictive) Model

The mathematical model is available in the models Python module (see the notebook Models)


In [2]:
import models

The Data


In [3]:
profile_data = pd.read_csv('profiles_data/romont_10Be_26Al_profile_data.csv',
                           index_col='sample',
                           delim_whitespace=True,
                           quoting=csv.QUOTE_NONNUMERIC, quotechar='\"',
                           dtype={'depth': 'f', 'depth_g-cm-2': 'f',
                                  'C': 'f', 'std': 'f'})

profile_data


Out[3]:
depth depth_g-cm-2 C std nuclide
sample
s01 750 1500 193732 5114 10Be
s02 650 1300 261858 6039 10Be
s03 550 1100 136098 13147 10Be
s04 450 900 186859 5153 10Be
s05 350 700 333915 7973 10Be
s06 310 620 654394 10387 10Be
s07 750 1500 702042 33437 26Al
s08 650 1300 992018 49251 26Al
s09 550 1100 467655 39998 26Al
s10 450 900 923354 45139 26Al
s11 350 700 1489555 126714 26Al
s12 310 620 2573447 301870 26Al

12 rows × 5 columns


In [4]:
with open('profiles_data/romont_10Be_26Al_settings.yaml') as f:
    romont_settings = yaml.load(f)

romont_settings


Out[4]:
{'P_0': 5.09, 'altitude': 109.0, 'latitude': 50.78, 'pressure': 1000.224}

The dataset is stored as a :class:pandas.DataFrame object.

Fitting the model

The grid search method is implemented in the gridsearch module (see the notebook Grid-Search for more info).


In [5]:
import gridsearch

Create a new object for setup and results


In [6]:
gstest = gridsearch.CosmogenicInferenceGC(description='Romont 6 parameters')

Set the data


In [7]:
gstest.set_profile_measured(
    profile_data['depth'].values,
    profile_data['C'].values,
    profile_data['std'].values,
    profile_data['nuclide'].values
)

Set the model. The fit is based on both 10Be and 26Al concentrations vs. depth. The model also takes into account an unknown period exposure_burial, which started when the measured depth profile has been buried by other deposits (instantaneous burial is assumed, and we also assume that no erosion took place since the burial). The total age of the deposits is therefore given by exposure + exposure_burial.


In [8]:
P0_26Al_10Be_ratio = 6.75       # ratio of 26Al / 10Be production rates
P_0_10Be = romont_settings['P_0']
P_0_26Al = P0_26Al_10Be_ratio * P_0_10Be

burial_depth = 300              # burial depth [cm]

# build slices to distinguish between 10Be and 26Al
i10Be = gstest.oprofile['nuclide'] == "10Be"
i26Al = gstest.oprofile['nuclide'] == "26Al"

s10Be = np.s_[i10Be,...]
s26Al = np.s_[i26Al,...]


def C_10Be_26Al_romont(depth, erosion,
                       exposure, exposure_burial,
                       density, inheritance_10Be,
                       inheritance_26Al):
    """
    10Be and 26Al for Romont with burial.
    """
    
    C = np.empty_like(depth)
    Cb = np.empty_like(depth)
    
    # "dummy" broadcast to create full arrays
    C = depth * erosion * exposure * exposure_burial \
        * density * inheritance_10Be * inheritance_26Al \
        * 0.
    Cb = np.zeros_like(C)
    
    C[s10Be] += models.C_10Be(depth[s10Be] - burial_depth,
                              erosion, exposure,
                              density, inheritance_10Be,
                              P_0=P_0_10Be)
    
    C[s26Al] += models.C_26Al(depth[s26Al] - burial_depth,
                              erosion, exposure,
                              density, inheritance_26Al,
                              P_0=P_0_26Al)
    
    Cb[s10Be] += models.C_10Be(depth[s10Be],
                               0., exposure_burial,
                               density, C[s10Be],
                               P_0=P_0_10Be)
    
    Cb[s26Al] += models.C_26Al(depth[s26Al],
                               0., exposure_burial,
                               density, C[s26Al],
                               P_0=P_0_26Al)
    
    return Cb 


gstest.set_profile_model(C_10Be_26Al_romont)

Define the parameters to fit and their search ranges / steps. The order must be the same than the order of the arguments of the function used for the model!


In [9]:
gstest.set_parameter(
    'erosion_rate',
    [0., 0.5e-3, 20j],
    stats.uniform(loc=0, scale=0.5e-3).pdf
)

gstest.set_parameter(
    'exposure_time',
    [1e5, 2.5e5, 20j],
    stats.uniform(loc=1e5, scale=2.5e5).pdf
)

gstest.set_parameter(
    'exposure_time_burial',
    [0., 7e5, 20j],
    stats.uniform(loc=0., scale=7e5).pdf
)

gstest.set_parameter(
    'soil_density',
    [2.0, 6.0, 20j],
    stats.uniform(loc=2.0, scale=6.0).pdf
)

gstest.set_parameter(
    'inheritance_10Be',
    [2e5, 2.6e5, 20j],
    stats.uniform(loc=2e5, scale=2.6e5).pdf
)

gstest.set_parameter(
    'inheritance_26Al',
    [0.6e6, 1.5e6, 20j],
    stats.uniform(loc=0.6e6, scale=1e6).pdf
)

Grid search setup summary


In [10]:
print gstest.setup_summary()


Modelling C profile (Bayes, Grid-Search)

DESCRIPTION:
Romont 6 parameters

MEASURED PROFILE (12 samples):
          C  depth nuclide     std
0    193732    750    10Be    5114
1    261858    650    10Be    6039
2    136098    550    10Be   13147
3    186859    450    10Be    5153
4    333915    350    10Be    7973
5    654394    310    10Be   10387
6    702042    750    26Al   33437
7    992018    650    26Al   49251
8    467655    550    26Al   39998
9    923354    450    26Al   45139
10  1489555    350    26Al  126714
11  2573447    310    26Al  301870

[12 rows x 4 columns]

PROFILE MODEL:
C_10Be_26Al_romont
10Be and 26Al for Romont with burial.

'UNKNOWN' PARAMETERS (6):
erosion_rate:
	prior: <bound method rv_frozen.pdf of <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f9d9109f0d0>>
	range: [0.0, 0.0005, 20j]
exposure_time:
	prior: <bound method rv_frozen.pdf of <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f9d9109f090>>
	range: [100000.0, 250000.0, 20j]
exposure_time_burial:
	prior: <bound method rv_frozen.pdf of <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f9d9109f250>>
	range: [0.0, 700000.0, 20j]
soil_density:
	prior: <bound method rv_frozen.pdf of <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f9d9109f290>>
	range: [2.0, 6.0, 20j]
inheritance_10Be:
	prior: <bound method rv_frozen.pdf of <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f9d9109f3d0>>
	range: [200000.0, 260000.0, 20j]
inheritance_26Al:
	prior: <bound method rv_frozen.pdf of <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f9d9109f510>>
	range: [600000.0, 1500000.0, 20j]

degrees of freedom: 6

GRID SEARCH:
nb. of nodes per parameter: [20, 20, 20, 20, 20, 20]
total nb. of nodes: 64000000


Perform Maximum likelihood estimation on the search grid


In [11]:
gstest.compute_mle()

Get the MLE (i.e., the parameter values at the maximum likelihood), in the same order than the definition of the parameters


In [12]:
gstest.mle


Out[12]:
[array([ 0.]),
 array([ 139473.68421053]),
 array([ 184210.52631579]),
 array([ 5.36842105]),
 array([ 228421.05263158]),
 array([ 884210.52631579])]

Plot the profile log-likelihood for each parameter. The blue lines represent the difference between the profile log-likelihood and the maximum log-likelihood, The intersections between the blue line and the black lines define the confidence intervals at the given confidence levels (based on the likelihood ratio test). The red lines indicate the true values.


In [13]:
%matplotlib inline

def plot_proflike1d(cobj, pname, clevels=[0.68, 0.95, 0.997],
                    true_val=None, ax=None):
    
    p = cobj.parameters[pname]
    pindex = cobj.parameters.keys().index(pname)
    
    x = cobj.grid[pindex].flatten()
    proflike = cobj.proflike1d[pindex]

    if ax is None:
        ax = plt.subplot(111)
    
    difflike = proflike - cobj.maxlike
    
    ax.plot(x, difflike, label='profile loglike')
    
    ccrit = gridsearch.profile_likelihood_crit(
        cobj.proflike1d[pindex],
        cobj.maxlike,
        clevels=clevels
    )
    ccrit -= cobj.maxlike
    
    for lev, cc in zip(clevels, ccrit):
        l = ax.axhline(cc, color='k')
        hpos = x.min() + (x.max() + x.min()) * 0.05
        ax.text(hpos, cc, str(lev * 100),
                size=9, color = l.get_color(),
                ha="center", va="center",
                bbox=dict(ec='1',fc='1'))
    
    if true_val is not None:
        ax.axvline(true_val, color='r')
    
    plt.setp(ax, xlabel=pname,
             ylabel='profile log-like - max log-like',
             xlim=p['range'][0:2],
             ylim=[ccrit[-1], 0.])
    

def plot_proflike1d_all(cobj, n_subplot_cols=2, **kwargs):
    
    n_subplots = len(cobj.parameters)
    n_subplot_rows = int(math.ceil(1. * 
                                   n_subplots /
                                   n_subplot_cols))
    
    fig, aax = plt.subplots(nrows=n_subplot_rows,
                            ncols=n_subplot_cols,
                            **kwargs)
    axes = aax.flatten()
    fig.text(0.5, 0.975,
             "Profile log-like: " + cobj.description,
             horizontalalignment='center',
             verticalalignment='top')
    
    for i, pname in enumerate(cobj.parameters.keys()):
        ax = axes[i]
        plot_proflike1d(cobj, pname,
                        ax=ax)
    
    plt.tight_layout()
    plt.subplots_adjust(top=0.93)


plot_proflike1d_all(gstest, figsize=(11, 10))


Show the profile log-likelihood for couples of parameters. Confidence regions are also shown (also based on the likelihood ratio test).


In [14]:
def plot_proflike2d(cobj, p1p2, ax=None,
                    cmap='Blues', show_colorbar=True):
    
    pname1, pname2 = p1p2
    idim = cobj.parameters.keys().index(pname2)
    jdim = cobj.parameters.keys().index(pname1)
    
    if ax is None:
        ax = plt.subplot(111)
    
    X, Y = np.meshgrid(cobj.grid[idim].flatten(),
                       cobj.grid[jdim].flatten())
    
    difflike = cobj.proflike2d[idim][jdim] - cobj.maxlike
    
    ccrit = gridsearch.profile_likelihood_crit(
        cobj.proflike2d[idim][jdim],
        cobj.maxlike,
        clevels=[0.68, 0.95]
    )
    ccrit -= cobj.maxlike
    
    contours = np.linspace(np.median(difflike),
                           0,
                           10)
    
    P2D = ax.contourf(Y, X, difflike,
                      contours,
                      cmap=plt.get_cmap(cmap))
    
    ci68 = ax.contour(Y, X, difflike,
                      [ccrit[0]], colors='w',
                      linestyles='solid')
    plt.clabel(ci68, fontsize=8, inline=True,
               fmt='68')
    ci95 = ax.contour(Y, X, difflike,
                      [ccrit[1]], colors=['k'],
                      linestyles='solid')
    plt.clabel(ci95, fontsize=8, inline=True,
               fmt='95')
    
    ax.scatter(cobj.mle[jdim], cobj.mle[idim],
               marker='*', s=60, c='w')
    
    plt.setp(ax, xlabel=pname1, ylabel=pname2)
    
    if show_colorbar:
        plt.colorbar(P2D, ax=ax)
    
    #ax.axhline(true_exposure, color='r')
    #ax.axvline(true_erosion, color='r')
    

fig = plt.figure(figsize=(11, 11))
ax = plt.subplot(421)
plot_proflike2d(gstest, ('erosion_rate', 'exposure_time'), ax=ax)
ax2 = plt.subplot(422)
plot_proflike2d(gstest, ('erosion_rate', 'exposure_time_burial'), ax=ax2)
ax3 = plt.subplot(423)
plot_proflike2d(gstest, ('exposure_time', 'inheritance_10Be'), ax=ax3)
ax4 = plt.subplot(424)
plot_proflike2d(gstest, ('exposure_time', 'inheritance_26Al'), ax=ax4)
ax5 = plt.subplot(425)
plot_proflike2d(gstest, ('exposure_time', 'soil_density'), ax=ax5)
ax6 = plt.subplot(426)
plot_proflike2d(gstest, ('soil_density', 'inheritance_26Al'), ax=ax6)
ax7 = plt.subplot(427)
plot_proflike2d(gstest, ('exposure_time_burial', 'inheritance_26Al'), ax=ax7)
ax8 = plt.subplot(428)
plot_proflike2d(gstest, ('exposure_time_burial', 'inheritance_10Be'), ax=ax8)
plt.tight_layout()


Plot the measured concentrations and the predicted profile corresponding to the best fitted data model


In [15]:
sns.set_context('notebook')

depths = np.linspace(profile_data['depth'].min(),
                     profile_data['depth'].max(),
                     100)

print 

Cm_fitted = C_10Be_26Al_romont(gstest.oprofile['depth'],
                               *gstest.mle)

plt.figure()

plt.plot(Cm_fitted[s10Be],
         -gstest.oprofile['depth'][s10Be],
         label='10Be best-fitted model')
plt.plot(Cm_fitted[s26Al],
         -gstest.oprofile['depth'][s26Al],
         label='26Al best-fitted model')

plt.errorbar(gstest.oprofile['C'][s10Be],
             -gstest.oprofile['depth'][s10Be],
             xerr=gstest.oprofile['std'][s10Be],
             fmt='o', markersize=4,
             label='10Be data')
plt.errorbar(gstest.oprofile['C'][s26Al],
             -gstest.oprofile['depth'][s26Al],
             xerr=gstest.oprofile['std'][s26Al],
             fmt='o', markersize=4,
             label='26Al data')

plt.setp(plt.gca(),
         xlabel='10Be or 26Al concentration [atoms g-1]',
         ylabel='-1 * depth [cm]',
         xlim=[0, None], ylim=[None, 0])

plt.legend(loc='lower right')



Out[15]:
<matplotlib.legend.Legend at 0x7f9d8eae4350>

Observations


In [15]: