In [1]:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
import emcee

Fitting with model uncertainty

In fitting spectrum routine, given an observed spectrum and a computational model to simulate the spectrum, we are interested in determining the parameters (with its error) that gives the most similar spectrum to the observed one.

If we know the "true" underlying model of the spectrum (i.e. we can simulate the spectrum exactly, given a set of parameters), obtaining the error of the parameters are just like a linear fit. However, if we only have an approximate model, the error can be severely underdetermined.

In this notebook, I will show the problem of fitting with an approximate model and how to solve it (hopefully).

The true model and the true data

In this notebook, we consider a simplified model of generating a spectrum. Let's say we have 4 parameters in which determining the values at $x = (0, 0.3, 0.7, 1.0)$. All the other values are obtained using the cubic spline interpolation. We call this the "cubic" model which is the true model. Suppose in an experiment, we can observe 1500 data points linearly spaced from $[0, 1.0]$, with some noise.


In [2]:
np.random.seed(123)
ndata = 1500
xparameters = [0, 0.3, 0.7, 1.0]
parameters = [0.1, 0.7, 0.2, 0.4]
sigma_noise = 0.05

finterp1 = interp1d(xparameters, parameters, kind="cubic")
xdata = np.linspace(0, 1, ndata)
ydata = finterp1(xdata) + np.random.randn(*xdata.shape) * sigma_noise

plt.plot(xdata, ydata, '.')
plt.show()


This is the data we observe (as an example).

Using the true model and known noise level

Assume that we are in an ideal world where we know the true model and we also know the noise level. The Bayesian inference of the parameters, $\mathbf{p}$, knowing the data, $\mathcal{D}$, can be written as

$$\begin{equation} P(\mathbf{p} | \mathcal{D}) \propto P(\mathcal{D} | \mathbf{p}) P(\mathbf{p}). \end{equation}$$

Take a uniform prior for the parameters and the likelihood is just a Gaussian distribution. Therefore,

$$\begin{equation} P(\mathbf{p} | \mathcal{D}) \propto \exp\left[-\sum_i\frac{\left(\mathbf{\hat{y_i}} - \mathbf{y_i(\mathbf{p})} \right)^2}{2\sigma^2}\right]. \end{equation}$$

We can use the emcee module to draw samples.


In [3]:
def get_ymodel1(xparams, params, xdata):
    finterp1 = interp1d(xparams, params, kind="cubic")
    return finterp1(xdata)

def lnprob1(params, xparams, xdata, ydata):
    ymodel = get_ymodel1(xparams, params, xdata)
    sigma = 0.05 # known noise level
    return -np.sum((ydata - ymodel)**2) / (2 * sigma**2)

In [4]:
ndim, nwalkers = 4, 100
p0 = np.random.random((nwalkers, ndim))
sampler1 = emcee.EnsembleSampler(nwalkers, ndim, lnprob1, args=[xparameters, xdata, ydata])
_ = sampler1.run_mcmc(p0, 1000)

In [5]:
nburn = 10000
params1 = sampler1.flatchain[nburn:,:]
plt.figure(figsize=(12,4))
for i in range(ndim):
    plt.subplot(1, ndim, i+1)
    ax = plt.gca()
    plt.hist(params1[:,i], 50, normed=True, range=parameters[i] + np.array([-1, 1])*0.05)
    ylim = ax.get_ylim()
    plt.plot(np.zeros(2) + parameters[i], ylim, 'k--')
    ax.set_ylim(ylim)
    plt.title("parameter #%d" % (i+1))

plt.show()

for i in range(ndim):
    print("parameter #%d: %f +- %f (true: %f)" % \
         (i+1, np.median(params1[:,i]),
          np.percentile(params1[:,i], 84) - np.percentile(params1[:,i], 16),
          parameters[i]))


parameter #1: 0.098821 +- 0.011969 (true: 0.100000)
parameter #2: 0.698691 +- 0.005246 (true: 0.700000)
parameter #3: 0.198065 +- 0.005179 (true: 0.200000)
parameter #4: 0.411266 +- 0.011669 (true: 0.400000)

In [6]:
plt.figure(figsize=(8,4))
plt.plot(xdata, ydata, '.')
plt.plot(xdata, get_ymodel1(xparameters, np.median(params1, 0), xdata), '-', linewidth=2)

# unseen on the graph because it is too narrow
plt.fill_between(xdata, 
                 get_ymodel1(xparameters, np.percentile(params1, 16, axis=0), xdata),
                 get_ymodel1(xparameters, np.percentile(params1, 84, axis=0), xdata), facecolor="orange")
plt.show()


As we can see, the retrieved parameters (i.e. the median) are very close to the true values.

Using an approximate model and known noise level

Now we are going to a slightly less ideal world where we used an approximate model with known noise level. The approximate model here uses the quadratic and linear spline interpolation (the "quadratic" and "linear" model) instead of the cubic spline interpolation (the "cubic" model). The inference is still the same as before, we just change how we calculate the model data in the lnprob1 function.


In [7]:
def get_ymodel2(xparams, params, xdata):
    finterp1 = interp1d(xparams, params, kind="linear")
    return finterp1(xdata)

def get_ymodel3(xparams, params, xdata):
    finterp1 = interp1d(xparams, params, kind="quadratic")
    return finterp1(xdata)

def lnprob2(params, xparams, xdata, ydata):
    ymodel = get_ymodel2(xparams, params, xdata)
    sigma = 0.05 # known noise level
    return -np.sum((ydata - ymodel)**2) / (2 * sigma**2)

def lnprob3(params, xparams, xdata, ydata):
    ymodel = get_ymodel3(xparams, params, xdata)
    sigma = 0.05 # known noise level
    return -np.sum((ydata - ymodel)**2) / (2 * sigma**2)

In [8]:
ndim, nwalkers = 4, 100
p0 = np.random.random((nwalkers, ndim))
sampler2 = emcee.EnsembleSampler(nwalkers, ndim, lnprob2, args=[xparameters, xdata, ydata])
_ = sampler2.run_mcmc(p0, 1000)
sampler3 = emcee.EnsembleSampler(nwalkers, ndim, lnprob3, args=[xparameters, xdata, ydata])
_ = sampler3.run_mcmc(p0, 1000)

In [9]:
nburn = 10000
params2 = sampler2.flatchain[nburn:,:]
params3 = sampler3.flatchain[nburn:,:]
plt.figure(figsize=(16,6))
for i in range(ndim):
    for j in range(2):
        paramss = params2 if j == 0 else params3
        
        plt.subplot(2, ndim, j*ndim+i+1)
        ax = plt.gca()
        plt.hist(paramss[:,i], 50, normed=True, range=parameters[i] + np.array([-1, 1])*0.2)
        ylim = ax.get_ylim()
        plt.plot(np.zeros(2) + parameters[i], ylim, 'k--')
        ax.set_ylim(ylim)
        if j == 0: 
            plt.title("parameter #%d" % (i+1))
        if i == 0:
            plt.ylabel("%s model" % ("linear" if j == 0 else "quadratic"))

plt.show()

for j in range(2):
    print("%s model" % ("linear" if j == 0 else "quadratic"))
    paramss = params2 if j == 0 else params3
    for i in range(ndim):
        print("parameter #%d: %f +- %f (true: %f)" % \
             (i+1, np.median(paramss[:,i]),
              np.percentile(paramss[:,i], 84) - np.percentile(paramss[:,i], 16),
              parameters[i]))


linear model
parameter #1: 0.254361 +- 0.009657 (true: 0.100000)
parameter #2: 0.789065 +- 0.006703 (true: 0.700000)
parameter #3: 0.150947 +- 0.006487 (true: 0.200000)
parameter #4: 0.275360 +- 0.009807 (true: 0.400000)
quadratic model
parameter #1: 0.159670 +- 0.011053 (true: 0.100000)
parameter #2: 0.709284 +- 0.005352 (true: 0.700000)
parameter #3: 0.187588 +- 0.005372 (true: 0.200000)
parameter #4: 0.350537 +- 0.010414 (true: 0.400000)

In [10]:
plt.figure(figsize=(16,4))
plt.subplot(1,2,1)
plt.plot(xdata, ydata, '.')
plt.plot(xdata, get_ymodel2(xparameters, np.median(params2, 0), xdata), '-', linewidth=2)
# unseen on the graph because it is too narrow
plt.fill_between(xdata, 
                 get_ymodel2(xparameters, np.percentile(params2, 16, axis=0), xdata),
                 get_ymodel2(xparameters, np.percentile(params2, 84, axis=0), xdata), facecolor="orange")
plt.subplot(1,2,2)
plt.plot(xdata, ydata, '.')
plt.plot(xdata, get_ymodel3(xparameters, np.median(params3, 0), xdata), '-', linewidth=2)
# unseen on the graph because it is too narrow
plt.fill_between(xdata, 
                 get_ymodel3(xparameters, np.percentile(params3, 16, axis=0), xdata),
                 get_ymodel3(xparameters, np.percentile(params3, 84, axis=0), xdata), facecolor="orange")
plt.show()


As we can see here, if we are using approximate models, the retrieved values of the parameteres are off while the produced error is still small. In other words, it is confident, but it is wrong. If we are using less accurate model, the effect is even worse: the predicted values are off by much and the error remains small.

The problem with the inference we used in this case is that we only assume that the data differ only by the Gaussian noise. This is not true since the data also differ due to the model inadequacy.

Max deviation inadequacy

In my latest paper, I assume the model inadequacy as stochastic variables with distribution

$$\begin{equation} P(\eta(x)) \propto \exp\left[-\frac{\max\left(\mathbf{\hat{y_i}} - \mathbf{y_i}\right)^2}{2d^2}\right] \end{equation}$$

with value of $d$ approximately the values of the noise.

Unfortunately, I didn't take into account the effect of the Gaussian noise. So the distribution above becomes the likelihood of observing the data.


In [11]:
def lnprob2_model1(params, xparams, xdata, ydata):
    ymodel = get_ymodel2(xparams, params, xdata)
    d = 0.05 # d approximately the noise level
    return -np.max((ydata - ymodel)**2) / (2 * d**2)

def lnprob3_model1(params, xparams, xdata, ydata):
    ymodel = get_ymodel3(xparams, params, xdata)
    d = 0.05 # d approximately the noise level
    return -np.max((ydata - ymodel)**2) / (2 * d**2)

In [12]:
ndim, nwalkers = 4, 100
p0 = np.random.random((nwalkers, ndim))
sampler2_model1 = emcee.EnsembleSampler(nwalkers, ndim, lnprob2_model1, args=[xparameters, xdata, ydata])
_ = sampler2_model1.run_mcmc(p0, 1000)
sampler3_model1 = emcee.EnsembleSampler(nwalkers, ndim, lnprob3_model1, args=[xparameters, xdata, ydata])
_ = sampler3_model1.run_mcmc(p0, 1000)

In [13]:
nburn = 10000
params2_model1 = sampler2_model1.flatchain[nburn:,:]
params3_model1 = sampler3_model1.flatchain[nburn:,:]
plt.figure(figsize=(16,6))
for i in range(ndim):
    for j in range(2):
        paramss = params2_model1 if j == 0 else params3_model1
        
        plt.subplot(2, ndim, j*ndim+i+1)
        ax = plt.gca()
        plt.hist(paramss[:,i], 50, normed=True, range=parameters[i] + np.array([-1, 1])*0.2)
        ylim = ax.get_ylim()
        plt.plot(np.zeros(2) + parameters[i], ylim, 'k--')
        ax.set_ylim(ylim)
        if j == 0: 
            plt.title("parameter #%d" % (i+1))
        if i == 0:
            plt.ylabel("%s model" % ("linear" if j == 0 else "quadratic"))

plt.show()

for j in range(2):
    print("%s model" % ("linear" if j == 0 else "quadratic"))
    paramss = params2_model1 if j == 0 else params3_model1
    for i in range(ndim):
        print("parameter #%d: %f +- %f (true: %f)" % \
             (i+1, np.median(paramss[:,i]),
              np.percentile(paramss[:,i], 84) - np.percentile(paramss[:,i], 16),
              parameters[i]))


linear model
parameter #1: 0.202079 +- 0.081411 (true: 0.100000)
parameter #2: 0.815644 +- 0.064171 (true: 0.700000)
parameter #3: 0.131717 +- 0.149363 (true: 0.200000)
parameter #4: 0.330355 +- 0.152373 (true: 0.400000)
quadratic model
parameter #1: 0.136628 +- 0.111322 (true: 0.100000)
parameter #2: 0.720176 +- 0.060927 (true: 0.700000)
parameter #3: 0.185541 +- 0.080441 (true: 0.200000)
parameter #4: 0.378302 +- 0.160431 (true: 0.400000)

In [15]:
plt.figure(figsize=(16,4))
plt.subplot(1,2,1)
plt.plot(xdata, ydata, '.', alpha=0.3)
plt.plot(xdata, get_ymodel2(xparameters, np.median(params2_model1, 0), xdata), '-', linewidth=2)
# unseen on the graph because it is too narrow
plt.fill_between(xdata, 
                 get_ymodel2(xparameters, np.percentile(params2_model1, 16, axis=0), xdata),
                 get_ymodel2(xparameters, np.percentile(params2_model1, 84, axis=0), xdata),
                 facecolor="orange", alpha=0.5)
plt.subplot(1,2,2)
plt.plot(xdata, ydata, '.', alpha=0.3)
plt.plot(xdata, get_ymodel3(xparameters, np.median(params3_model1, 0), xdata), '-', linewidth=2)
# unseen on the graph because it is too narrow
plt.fill_between(xdata, 
                 get_ymodel3(xparameters, np.percentile(params3_model1, 16, axis=0), xdata),
                 get_ymodel3(xparameters, np.percentile(params3_model1, 84, axis=0), xdata),
                 facecolor="orange", alpha=0.5)
plt.plot(xdata, get_ymodel1(xparameters, parameters, xdata), '-', linewidth=2)

nburn = 10000
params2 = sampler2.flatchain[nburn:,:]
params3 = sampler3.flatchain[nburn:,:]
plt.figure(figsize=(16,6))
for i in range(ndim):
    for j in range(2):
        paramss = params2 if j == 0 else params3
        
        plt.subplot(2, ndim, j*ndim+i+1)
        ax = plt.gca()
        plt.hist(paramss[:,i], 50, normed=True, range=parameters[i] + np.array([-1, 1])*0.2)
        ylim = ax.get_ylim()
        plt.plot(np.zeros(2) + parameters[i], ylim, 'k--')
        ax.set_ylim(ylim)
        if j == 0: 
            plt.title("parameter #%d" % (i+1))
        if i == 0:
            plt.ylabel("%s model" % ("linear" if j == 0 else "quadratic"))

plt.show()

for j in range(2):
    print("%s model" % ("linear" if j == 0 else "quadratic"))
    paramss = params2 if j == 0 else params3
    for i in range(ndim):
        print("parameter #%d: %f +- %f (true: %f)" % \
             (i+1, np.median(paramss[:,i]),
              np.percentile(paramss[:,i], 84) - np.percentile(paramss[:,i], 16),
              parameters[i]))
plt.show()


linear model
parameter #1: 0.254361 +- 0.009657 (true: 0.100000)
parameter #2: 0.789065 +- 0.006703 (true: 0.700000)
parameter #3: 0.150947 +- 0.006487 (true: 0.200000)
parameter #4: 0.275360 +- 0.009807 (true: 0.400000)
quadratic model
parameter #1: 0.159670 +- 0.011053 (true: 0.100000)
parameter #2: 0.709284 +- 0.005352 (true: 0.700000)
parameter #3: 0.187588 +- 0.005372 (true: 0.200000)
parameter #4: 0.350537 +- 0.010414 (true: 0.400000)

From the results above, this model of the model inadequacy works well if the model is approximately accurate. If the model is quite far from the true model, then the result is quite far off (compared to its standard deviation). Moreover, this model requires us to make an assumption on how right (or wrong) the model is by setting the value of $d$ manually.

What we want is minimal input from users and the model is automatically adjust the error on the retrieved parameters. If the model is less accurate, then the error on the retrieved parameters should be larger. If the model is more accurate, then the error should be smaller.

Gaussian process model inadequacy

Another way to model the model inadequacy is using Gaussian Process.


In [28]:
from scipy.stats import multivariate_normal

def lnprob2_model2(params, xparams, xdata, ydata, dist):
    d = params[-1]
    m = params[-2]
    sigma = 0.05
    if d > 1 or d < 0 or m < 0 or sigma < 0: return -np.inf
    
    ncut = 10
    cov = m*m * np.exp(-dist[::ncut,::ncut]**2/(2*d*d)) + np.eye(len(xdata[::ncut])) * (sigma*sigma + 1e-7)
    
    ymodel = get_ymodel2(xparams, params[:-3], xdata)
    obs = ymodel - ydata
    obs = obs[::ncut]
    try:
        return multivariate_normal.logpdf(obs, mean=np.zeros(obs.shape[0]), cov=cov) - np.log(sigma * m * d)
    except:
        return -np.inf

def lnprob3_model2(params, xparams, xdata, ydata, dist):
    d = params[-1]
    m = params[-2]
    sigma = 0.05
    if d > 1 or d < 0 or m < 0 or sigma < 0: return -np.inf
    
    ncut = 10
    cov = m*m * np.exp(-dist[::ncut,::ncut]**2/(2*d*d)) + np.eye(len(xdata[::ncut])) * (sigma*sigma + 1e-7)
    
    ymodel = get_ymodel3(xparams, params[:-3], xdata)
    obs = ymodel - ydata
    obs = obs[::ncut]
    try:
        return multivariate_normal.logpdf(obs, mean=np.zeros(obs.shape[0]), cov=cov) - np.log(sigma * m * d)
    except:
        return -np.inf

In [29]:
import scipy.spatial.distance
dist = scipy.spatial.distance.squareform(scipy.spatial.distance.pdist(np.expand_dims(xdata, 1)))

In [30]:
ndim, nwalkers = 4, 100
nhparams = 3
p0 = np.random.random((nwalkers, ndim+nhparams))
sampler2_model2 = emcee.EnsembleSampler(nwalkers, ndim+nhparams, lnprob2_model2, args=[xparameters, xdata, ydata, dist])
%time _ = sampler2_model2.run_mcmc(p0, 1000)
sampler3_model2 = emcee.EnsembleSampler(nwalkers, ndim+nhparams, lnprob3_model2, args=[xparameters, xdata, ydata, dist])
%time _ = sampler3_model2.run_mcmc(p0, 1000)


emcee: Exception while calling your likelihood function:
  params: [ -7.96407277e+01  -8.57259115e+01  -9.32760930e+01  -9.83836236e+01
   2.07362780e+05   4.19037138e+02   9.28407438e-01]
  args: [[0, 0.3, 0.7, 1.0], array([  0.00000000e+00,   6.67111408e-04,   1.33422282e-03, ...,
         9.98665777e-01,   9.99332889e-01,   1.00000000e+00]), array([ 0.04571847,  0.15315936,  0.12072241, ...,  0.39386413,
        0.42228107,  0.48764548]), array([[  0.00000000e+00,   6.67111408e-04,   1.33422282e-03, ...,
          9.98665777e-01,   9.99332889e-01,   1.00000000e+00],
       [  6.67111408e-04,   0.00000000e+00,   6.67111408e-04, ...,
          9.97998666e-01,   9.98665777e-01,   9.99332889e-01],
       [  1.33422282e-03,   6.67111408e-04,   0.00000000e+00, ...,
          9.97331554e-01,   9.97998666e-01,   9.98665777e-01],
       ..., 
       [  9.98665777e-01,   9.97998666e-01,   9.97331554e-01, ...,
          0.00000000e+00,   6.67111408e-04,   1.33422282e-03],
       [  9.99332889e-01,   9.98665777e-01,   9.97998666e-01, ...,
          6.67111408e-04,   0.00000000e+00,   6.67111408e-04],
       [  1.00000000e+00,   9.99332889e-01,   9.98665777e-01, ...,
          1.33422282e-03,   6.67111408e-04,   0.00000000e+00]])]
  kwargs: {}
  exception:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/emcee/ensemble.py", line 519, in __call__
    return self.f(x, *self.args, **self.kwargs)
  File "<ipython-input-28-18e3bc8893e4>", line 15, in lnprob2_model2
    return multivariate_normal.logpdf(obs, mean=np.zeros(obs.shape[0]), cov=cov) - np.log(sigma * m * d)
  File "/home/mfkasim/.local/lib/python2.7/site-packages/scipy/stats/_multivariate.py", line 480, in logpdf
    psd = _PSD(cov, allow_singular=allow_singular)
  File "/home/mfkasim/.local/lib/python2.7/site-packages/scipy/stats/_multivariate.py", line 157, in __init__
    raise np.linalg.LinAlgError('singular matrix')
LinAlgError: singular matrix
---------------------------------------------------------------------------
LinAlgError                               Traceback (most recent call last)
<ipython-input-30-a9095b7deaaf> in <module>()
      3 p0 = np.random.random((nwalkers, ndim+nhparams))
      4 sampler2_model2 = emcee.EnsembleSampler(nwalkers, ndim+nhparams, lnprob2_model2, args=[xparameters, xdata, ydata, dist])
----> 5 get_ipython().magic(u'time _ = sampler2_model2.run_mcmc(p0, 1000)')
      6 sampler3_model2 = emcee.EnsembleSampler(nwalkers, ndim+nhparams, lnprob3_model2, args=[xparameters, xdata, ydata, dist])
      7 get_ipython().magic(u'time _ = sampler3_model2.run_mcmc(p0, 1000)')

/home/mfkasim/.local/lib/python2.7/site-packages/IPython/core/interactiveshell.pyc in magic(self, arg_s)
   2158         magic_name, _, magic_arg_s = arg_s.partition(' ')
   2159         magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
-> 2160         return self.run_line_magic(magic_name, magic_arg_s)
   2161 
   2162     #-------------------------------------------------------------------------

/home/mfkasim/.local/lib/python2.7/site-packages/IPython/core/interactiveshell.pyc in run_line_magic(self, magic_name, line)
   2079                 kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
   2080             with self.builtin_trap:
-> 2081                 result = fn(*args,**kwargs)
   2082             return result
   2083 

<decorator-gen-60> in time(self, line, cell, local_ns)

/home/mfkasim/.local/lib/python2.7/site-packages/IPython/core/magic.pyc in <lambda>(f, *a, **k)
    186     # but it's overkill for just that one bit of state.
    187     def magic_deco(arg):
--> 188         call = lambda f, *a, **k: f(*a, **k)
    189 
    190         if callable(arg):

/home/mfkasim/.local/lib/python2.7/site-packages/IPython/core/magics/execution.pyc in time(self, line, cell, local_ns)
   1183         else:
   1184             st = clock2()
-> 1185             exec(code, glob, local_ns)
   1186             end = clock2()
   1187             out = None

<timed exec> in <module>()

/usr/local/lib/python2.7/dist-packages/emcee/sampler.pyc in run_mcmc(self, pos0, N, rstate0, lnprob0, **kwargs)
    170 
    171         for results in self.sample(pos0, lnprob0, rstate0, iterations=N,
--> 172                                    **kwargs):
    173             pass
    174 

/usr/local/lib/python2.7/dist-packages/emcee/ensemble.pyc in sample(self, p0, lnprob0, rstate0, blobs0, iterations, thin, storechain, mh_proposal)
    257                 for S0, S1 in [(first, second), (second, first)]:
    258                     q, newlnp, acc, blob = self._propose_stretch(p[S0], p[S1],
--> 259                                                                  lnprob[S0])
    260                     if np.any(acc):
    261                         # Update the positions, log probabilities and

/usr/local/lib/python2.7/dist-packages/emcee/ensemble.pyc in _propose_stretch(self, p0, p1, lnprob0)
    330         # Calculate the proposed positions and the log-probability there.
    331         q = c[rint] - zz[:, np.newaxis] * (c[rint] - s)
--> 332         newlnprob, blob = self._get_lnprob(q)
    333 
    334         # Decide whether or not the proposals should be accepted.

/usr/local/lib/python2.7/dist-packages/emcee/ensemble.pyc in _get_lnprob(self, pos)
    380 
    381         # Run the log-probability calculations (optionally in parallel).
--> 382         results = list(M(self.lnprobfn, [p[i] for i in range(len(p))]))
    383 
    384         try:

/usr/local/lib/python2.7/dist-packages/emcee/ensemble.pyc in __call__(self, x)
    517     def __call__(self, x):
    518         try:
--> 519             return self.f(x, *self.args, **self.kwargs)
    520         except:
    521             import traceback

<ipython-input-28-18e3bc8893e4> in lnprob2_model2(params, xparams, xdata, ydata, dist)
     13     obs = ymodel - ydata
     14     obs = obs[::ncut]
---> 15     return multivariate_normal.logpdf(obs, mean=np.zeros(obs.shape[0]), cov=cov) - np.log(sigma * m * d)
     16 
     17 def lnprob3_model2(params, xparams, xdata, ydata, dist):

/home/mfkasim/.local/lib/python2.7/site-packages/scipy/stats/_multivariate.pyc in logpdf(self, x, mean, cov, allow_singular)
    478         dim, mean, cov = self._process_parameters(None, mean, cov)
    479         x = self._process_quantiles(x, dim)
--> 480         psd = _PSD(cov, allow_singular=allow_singular)
    481         out = self._logpdf(x, mean, psd.U, psd.log_pdet, psd.rank)
    482         return _squeeze_output(out)

/home/mfkasim/.local/lib/python2.7/site-packages/scipy/stats/_multivariate.pyc in __init__(self, M, cond, rcond, lower, check_finite, allow_singular)
    155         d = s[s > eps]
    156         if len(d) < len(s) and not allow_singular:
--> 157             raise np.linalg.LinAlgError('singular matrix')
    158         s_pinv = _pinv_1d(s, eps)
    159         U = np.multiply(u, np.sqrt(s_pinv))

LinAlgError: singular matrix

In [ ]:
nburn = 10000
params2_model2 = sampler2_model2.flatchain[nburn:,:]
params3_model2 = sampler3_model2.flatchain[nburn:,:]
plt.figure(figsize=(16,6))
for i in range(ndim):
    for j in range(2):
        paramss = params2_model2 if j == 0 else params3_model2
        
        plt.subplot(2, ndim, j*ndim+i+1)
        ax = plt.gca()
        plt.hist(paramss[:,i], 50, normed=True, range=parameters[i] + np.array([-1, 1])*2)
        ylim = ax.get_ylim()
        plt.plot(np.zeros(2) + parameters[i], ylim, 'k--')
        ax.set_ylim(ylim)
        if j == 0: 
            plt.title("parameter #%d" % (i+1))
        if i == 0:
            plt.ylabel("%s model" % ("linear" if j == 0 else "quadratic"))

plt.show()

for j in range(2):
    print("%s model" % ("linear" if j == 0 else "quadratic"))
    paramss = params2_model2 if j == 0 else params3_model2
    for i in range(ndim):
        print("parameter #%d: %f +- %f (true: %f)" % \
             (i+1, np.median(paramss[:,i]),
              np.percentile(paramss[:,i], 84) - np.percentile(paramss[:,i], 16),
              parameters[i]))

In [ ]:
plt.figure(figsize=(16,4))
plt.subplot(1,2,1)
plt.plot(xdata, ydata, '.', alpha=0.3)
plt.plot(xdata, get_ymodel2(xparameters, np.median(params2_model2[:,:-nhparams], 0), xdata), '-', linewidth=2)
# unseen on the graph because it is too narrow
plt.fill_between(xdata, 
                 get_ymodel2(xparameters, np.percentile(params2_model2[:,:-nhparams], 16, axis=0), xdata),
                 get_ymodel2(xparameters, np.percentile(params2_model2[:,:-nhparams], 84, axis=0), xdata),
                 facecolor="orange", alpha=0.5)
plt.subplot(1,2,2)
plt.plot(xdata, ydata, '.', alpha=0.3)
plt.plot(xdata, get_ymodel3(xparameters, np.median(params3_model2[:,:-nhparams], 0), xdata), '-', linewidth=2)
# unseen on the graph because it is too narrow
plt.fill_between(xdata, 
                 get_ymodel3(xparameters, np.percentile(params3_model2[:,:-nhparams], 16, axis=0), xdata),
                 get_ymodel3(xparameters, np.percentile(params3_model2[:,:-nhparams], 84, axis=0), xdata),
                 facecolor="orange", alpha=0.5)
plt.plot(xdata, get_ymodel1(xparameters, parameters, xdata), '-', linewidth=2)
plt.show()

In [ ]: