Imports


In [1]:
# PyABC imports
from pyabc import (ABCSMC, Distribution, RV,
                   History, MedianEpsilon)
from pyabc.populationstrategy import ConstantPopulationSize, AdaptivePopulationSize
from pyabc.epsilon import MedianEpsilon
from pyabc.sampler import MulticoreEvalParallelSampler

In [2]:
# Custom imports
from ionchannelABC import (ion_channel_sum_stats_calculator,
                           IonChannelAcceptor,
                           IonChannelDistance,
                           EfficientMultivariateNormalTransition,
                           calculate_parameter_sensitivity,
                           plot_parameter_sensitivity,
                           plot_regression_fit,
                           plot_distance_weights,
                           plot_sim_results,
                           plot_parameters_kde)


INFO:myokit:Loading Myokit version 1.27.4

In [3]:
# Other necessary imports
import numpy as np
import subprocess
import pandas as pd
import io
import os
import tempfile

In [4]:
# Plotting imports
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
%config Inline.Backend.figure_format = 'retina'

Create ion channel model


In [5]:
from channels.ina_generic import ina as model
model.sample({})


Out[5]:
x y exp
0 -90.000000 -5.265994e-06 0
1 -80.000000 -4.897003e-04 0
2 -70.000000 -4.349885e-02 0
3 -60.000000 -3.229475e+00 0
4 -50.000000 -1.253934e+02 0
5 -40.000000 -1.124747e+03 0
6 -30.000000 -2.303487e+03 0
7 -20.000000 -2.370812e+03 0
8 -10.000000 -1.810363e+03 0
9 0.000000 -9.120791e+02 0
10 10.000000 -2.089897e+02 0
11 20.000000 -6.752381e+01 0
12 30.000000 -4.648884e+01 0
13 40.000000 -3.403091e+01 0
14 50.000000 -2.195417e+01 0
15 60.000000 -9.887582e+00 0
0 -80.000000 1.229266e-07 1
1 -70.000000 1.170939e-05 1
2 -60.000000 9.371509e-04 1
3 -50.000000 3.946606e-02 1
4 -40.000000 3.867190e-01 1
5 -30.000000 8.726577e-01 1
6 -20.000000 1.000000e+00 1
7 -10.000000 8.612569e-01 1
8 0.000000 4.975367e-01 1
9 10.000000 1.335928e-01 1
0 -130.000000 1.000000e+00 2
1 -120.000000 9.990614e-01 2
2 -110.000000 9.930046e-01 2
3 -100.000000 9.623476e-01 2
4 -90.000000 8.244802e-01 2
5 -80.000000 4.298163e-01 2
6 -70.000000 7.218805e-02 2
7 -60.000000 4.471400e-03 2
8 -50.000000 2.064676e-04 2
9 -40.000000 1.128375e-05 2
10 -30.000000 9.096492e-07 2
0 10.000000 8.085055e-01 3
1 20.000000 8.391606e-01 3
2 30.000000 8.648947e-01 3
3 40.000000 8.865057e-01 3
4 50.000000 9.046487e-01 3
5 60.000000 9.198774e-01 3
6 70.000000 9.326494e-01 3
7 80.000000 9.433830e-01 3
0 0.000000 -1.204232e-15 4
1 0.057971 -1.501550e-01 4
2 0.173913 -7.563070e-01 4
3 0.289855 -9.766492e-01 4
4 0.391304 -9.982068e-01 4
5 0.579710 -9.409386e-01 4
6 0.782609 -8.620501e-01 4
7 1.072464 -7.586103e-01 4
8 1.637681 -5.904609e-01 4
9 2.521739 -3.989836e-01 4
10 3.478261 -2.614479e-01 4
11 4.695652 -1.528383e-01 4
12 5.956522 -8.743034e-02 4
13 7.115942 -5.224579e-02 4
14 9.637681 -1.708058e-02 4

Get experimental measurements


In [6]:
measurements = model.get_experiment_data()
obs = measurements.to_dict()['y']
exp = measurements.to_dict()['exp']
errs = measurements.to_dict()['errs']

Set limits and generate uniform initial priors


In [7]:
limits = dict(g_Na=(0, 10),
              Vhalf_m=(-100,0),
              k_m=(0,20),
              c_bm=(0,0.1),
              c_am=(0,1),
              Vmax_m=(-100,0),
              sigma_m=(0,100),
              Vhalf_h=(-100,0),
              k_h=(-20,0),
              c_bh=(0,0.1),
              c_ah=(0,50),
              Vmax_h=(-100,0),
              sigma_h=(0,100),
              c_bj=(0,10),
              c_aj=(50,100),
              Vmax_j=(-100,0),
              sigma_j=(0,100))
prior = Distribution(**{key: RV("uniform", a, b - a)
                        for key, (a,b) in limits.items()})

Test parameter sensitivity


In [8]:
parameters = ['ina.'+k for k in limits.keys()]

In [9]:
distance_fn=IonChannelDistance(
    obs=obs,
    exp_map=exp,
    err_bars=errs,
    err_th=0.1)

In [10]:
sns.set_context('talk')
g = plot_distance_weights(model, distance_fn)
g.savefig('results/ina-generic/dist_weights.pdf')



In [14]:
fitted, regression_fit, r2 = calculate_parameter_sensitivity(
    model,
    parameters,
    distance_fn,
    sigma=0.05,
    n_samples=1000)

In [15]:
sns.set_context('talk')
grid1 = plot_parameter_sensitivity(fitted, plot_cutoff=0.05)



In [16]:
grid2 = plot_regression_fit(regression_fit, r2)



In [17]:
grid1.savefig('results/ina-generic/sensitivity.pdf')
grid2.savefig('results/ina-generic/sensitivity_fit.pdf')

In [32]:
# Finding insensitive parameters
cutoff = 0.05
fitted_pivot = fitted.pivot(index='param',columns='exp')
insensitive_params = fitted_pivot[(abs(fitted_pivot['beta'][0])<cutoff) & (abs(fitted_pivot['beta'][1])<cutoff) &
             (abs(fitted_pivot['beta'][2])<cutoff) & (abs(fitted_pivot['beta'][3])<cutoff) &
             (abs(fitted_pivot['beta'][4])<cutoff)].index.values

In [42]:
insensitive_params


Out[42]:
array(['ina.Vmax_h', 'ina.Vmax_m', 'ina.c_ah', 'ina.c_aj', 'ina.c_am',
       'ina.k_h', 'ina.sigma_h', 'ina.sigma_m'], dtype=object)

In [43]:
insensitive_params = np.delete(insensitive_params,5)

In [91]:
insensitive_params


Out[91]:
array(['ina.Vmax_h', 'ina.Vmax_m', 'ina.c_ah', 'ina.c_aj', 'ina.c_am',
       'ina.sigma_h', 'ina.sigma_m'], dtype=object)

In [92]:
insensitive_limits = dict((k, limits[k[4:]]) for k in insensitive_params)
insensitive_prior = Distribution(**{key: RV("uniform", a, b - a)
                                 for key, (a,b) in insensitive_limits.items()})

In [93]:
# Generate random samples for insensitive parameters
def generate_sample(insensitive_prior, n):
    samples = [dict() for i in range(n)]
    for i in range(n):
        parameters = insensitive_prior.rvs()
        sample = {key: value for key, value in parameters.items()}
        samples[i].update(sample)
    return samples

In [94]:
samples = generate_sample(insensitive_prior, 1000)

In [95]:
model.add_external_par_samples(samples)

In [96]:
limits = dict((k, limits[k]) for k in limits if 'ina.'+k not in insensitive_params)

In [97]:
limits


Out[97]:
{'g_Na': (0, 100),
 'Vhalf_m': (-100, 100),
 'k_m': (0, 50),
 'c_bm': (0, 0.1),
 'Vhalf_h': (-100, 100),
 'k_h': (-50, 0),
 'c_bh': (0, 10),
 'c_bj': (0, 50),
 'Vmax_j': (-500, 500),
 'sigma_j': (0, 500)}

In [8]:
prior = Distribution(**{key: RV("uniform", a, b - a)
                        for key, (a,b) in limits.items()})

Initialise pyabc database


In [9]:
db_path = ('sqlite:///' + 
           os.path.join(tempfile.gettempdir(), "hl-1_ina-generic3000.db"))
print(db_path)


sqlite:////scratch/cph211/tmp/hl-1_ina-generic3000.db

In [10]:
# Let's log all the sh!t
import logging
logging.basicConfig()
abc_logger = logging.getLogger('ABC')
abc_logger.setLevel(logging.DEBUG)
eps_logger = logging.getLogger('Epsilon')
eps_logger.setLevel(logging.DEBUG)
cv_logger = logging.getLogger('CV Estimation')
cv_logger.setLevel(logging.DEBUG)

In [11]:
from pyabc.populationstrategy import ConstantPopulationSize

In [12]:
abc = ABCSMC(models=model,
             parameter_priors=prior,
             distance_function=IonChannelDistance(
                 obs=obs,
                 exp_map=exp,
                 err_bars=errs,
                 err_th=0.1),
             population_size=ConstantPopulationSize(3000),
             #population_size=AdaptivePopulationSize(
             #    start_nr_particles=1000,
             #    mean_cv=0.2,
             #    max_population_size=1000,
             #    min_population_size=100),
             summary_statistics=ion_channel_sum_stats_calculator,
             transitions=EfficientMultivariateNormalTransition(scaling=0.8),
             eps=MedianEpsilon(),
             sampler=MulticoreEvalParallelSampler(n_procs=12),
             acceptor=IonChannelAcceptor())


DEBUG:ABC:ion channel weights: {0: 0.05482047444775661, 1: 0.05482047444775661, 2: 0.05482047444775661, 3: 0.05482047444775661, 4: 0.05482047444775661, 5: 0.05482047444775661, 6: 0.04793030963340064, 7: 0.04260405991323703, 8: 0.0479278046958729, 9: 0.0536798519659175, 10: 0.05482047444775661, 11: 0.05482047444775661, 12: 0.05482047444775661, 13: 0.05482047444775661, 14: 0.05482047444775661, 15: 0.05482047444775661, 16: 0.8326197611814873, 17: 0.8326197611814873, 18: 0.8326197611814873, 19: 0.8326197611814873, 20: 0.8326197611814873, 21: 0.8326197611814873, 22: 0.6017669628485173, 23: 0.8326197611814873, 24: 0.8326197611814873, 25: 0.8326197611814873, 26: 0.7463215252566501, 27: 0.7463215252566501, 28: 0.7463215252566501, 29: 0.7463215252566501, 30: 0.7463215252566501, 31: 0.7463215252566501, 32: 0.7463215252566501, 33: 0.7463215252566501, 34: 0.7463215252566501, 35: 0.7463215252566501, 36: 0.7463215252566501, 37: 5.779317930650152, 38: 6.604934777885866, 39: 3.3021281756664393, 40: 2.802012133256525, 41: 3.082204391891905, 42: 3.302354310107496, 43: 3.5562410629310297, 44: 3.5567656596359885, 45: 0.7239448164725362, 46: 0.7239448164725362, 47: 0.7239448164725362, 48: 0.7239448164725362, 49: 0.7239448164725362, 50: 0.7239448164725362, 51: 0.7239448164725362, 52: 0.7239448164725362, 53: 0.7239448164725362, 54: 0.7239448164725362, 55: 0.7239448164725362, 56: 0.7239448164725362, 57: 0.7239448164725362, 58: 0.7239448164725362, 59: 0.7239448164725362}
DEBUG:Epsilon:init quantile_epsilon initial_epsilon=from_sample, quantile_multiplier=1

In [13]:
abc_id = abc.new(db_path, obs)


INFO:History:Start <ABCSMC(id=1, start_time=2018-11-24 12:26:38.369078, end_time=None)>
INFO:Epsilon:initial epsilon is 18.18499730711697

In [ ]:
history = abc.run(minimum_epsilon=0.05, max_nr_populations=30, min_acceptance_rate=0.001)


INFO:ABC:t:34 eps:0.8819761559946571
DEBUG:ABC:now submitting population 34
DEBUG:ABC:population 34 done
DEBUG:ABC:
total nr simulations up to t =34 is 4687012
DEBUG:Epsilon:new eps, t=35, eps=0.8454461577176619
INFO:ABC:t:35 eps:0.8454461577176619
DEBUG:ABC:now submitting population 35
DEBUG:ABC:population 35 done
DEBUG:ABC:
total nr simulations up to t =35 is 5138415
DEBUG:Epsilon:new eps, t=36, eps=0.8228707556785062
INFO:ABC:t:36 eps:0.8228707556785062
DEBUG:ABC:now submitting population 36
DEBUG:ABC:population 36 done
DEBUG:ABC:
total nr simulations up to t =36 is 5437951
DEBUG:Epsilon:new eps, t=37, eps=0.8034040164763968
INFO:ABC:t:37 eps:0.8034040164763968
DEBUG:ABC:now submitting population 37
DEBUG:ABC:population 37 done
DEBUG:ABC:
total nr simulations up to t =37 is 5968798
DEBUG:Epsilon:new eps, t=38, eps=0.7766161569137482
INFO:ABC:t:38 eps:0.7766161569137482
DEBUG:ABC:now submitting population 38
DEBUG:ABC:population 38 done
DEBUG:ABC:
total nr simulations up to t =38 is 6385314
DEBUG:Epsilon:new eps, t=39, eps=0.7531408265705272
INFO:ABC:t:39 eps:0.7531408265705272
DEBUG:ABC:now submitting population 39

In [ ]:
history = abc.run(minimum_epsilon=0.05, max_nr_populations=30, min_acceptance_rate=0.001)

Results analysis


In [37]:
db_path = 'sqlite:////scratch/cph211/tmp/hl-1_ina-generic3000.db'
history = History(db_path)
history.all_runs()


Out[37]:
[<ABCSMC(id=1, start_time=2018-11-24 12:26:38.369078, end_time=2018-11-25 00:43:20.805728)>]

In [38]:
history.id = 1

In [47]:
sns.set_context('talk')
evolution = history.get_all_populations()
grid = sns.relplot(x='t', y='epsilon', size='samples', data=evolution[evolution.t>=2])
grid.savefig('results/ina-generic/eps_evolution.pdf')



In [48]:
df, w = history.get_distribution(m=0)

In [49]:
g = plot_parameters_kde(df, w, limits, aspect=12, height=0.6)



In [50]:
g.savefig('results/ina-generic/parameters_kde.pdf')

Samples for quantitative analysis


In [51]:
# Generate parameter samples
n_samples = 100
df, w = history.get_distribution(m=0)
th_samples = df.sample(n=n_samples, weights=w, replace=True).to_dict(orient='records')

In [52]:
# Generate sim results samples
samples = pd.DataFrame({})
for i, th in enumerate(th_samples):
    output = model.sample(pars=th, n_x=50)
    output['sample'] = i
    output['distribution'] = 'post'
    samples = samples.append(output, ignore_index=True)

In [53]:
from ionchannelABC import plot_sim_results
sns.set_context('talk')
g = plot_sim_results(samples, obs=measurements)

# Set axis labels
xlabels = ["voltage, mV", "voltage, mV", "voltage, mV", "time, ms", "time, ms"]
ylabels = ["current density, pA/pF", "activation", "inactivation", "recovery", "normalised current trace"]
for ax, xl in zip(g.axes.flatten(), xlabels):
    ax.set_xlabel(xl)
for ax, yl in zip(g.axes.flatten(), ylabels):
    ax.set_ylabel(yl)



In [54]:
g.savefig('results/ina-generic/ina_sim_results.pdf')

In [55]:
def plot_sim_results_all(samples: pd.DataFrame):
    with sns.color_palette("gray"):
        grid = sns.relplot(x='x', y='y',
                           col='exp',
                           units='sample',
                           kind='line',
                           data=samples,
                           estimator=None, lw=0.5,
                           alpha=0.5,
                           #estimator=np.median,
                           facet_kws={'sharex': 'col',
                                      'sharey': 'col'})
    return grid

In [56]:
grid2 = plot_sim_results_all(samples)



In [57]:
grid2.savefig('results/ina-generic/ina_sim_results_all.pdf')

In [113]:
# Require discrete samples for exact measurements at -20mV
discrete_samples = pd.DataFrame({})
for i, th in enumerate(th_samples):
    output = model.sample(pars=th)
    output['sample'] = i
    output['distribution'] = 'post'
    discrete_samples = discrete_samples.append(output, ignore_index=True)

In [114]:
# Amplitude at -20 mV
grouped = discrete_samples[discrete_samples['exp']==0].groupby('sample')
def get_amplitude(group):
    return group.loc[group.x==-20]['y']
print(grouped.apply(get_amplitude).mean())
print(grouped.apply(get_amplitude).std())


-163.74165119888335
0.14129370301730562

In [115]:
import scipy.stats as st
peak_current = discrete_samples[discrete_samples['exp']==0].groupby('sample').apply(get_amplitude).tolist()
rv = st.rv_discrete(values=(peak_current, [1/len(peak_current),]*len(peak_current)))
print("median: {}".format(rv.median()))
print("95% CI: {}".format(rv.interval(0.95)))


median: -163.81705987307208
95% CI: (-163.85178097293564, -163.4658692841079)

In [116]:
# Voltage and slope factor at half-activation
from scipy.optimize import curve_fit
grouped = samples[samples['exp']==1].groupby('sample')
def fit_boltzmann(group):
    def boltzmann(V, Vhalf, Shalf):
        return 1/(1+np.exp((Vhalf-V)/Shalf))
    guess = (-50, 10)
    popt, _ = curve_fit(boltzmann, group.x, group.y, guess)
    return popt
output = grouped.apply(fit_boltzmann).apply(pd.Series)

In [117]:
print(output.mean())
print(output.std())


0   -34.955683
1     7.200565
dtype: float64
0    0.015053
1    0.010894
dtype: float64

In [118]:
Vhalf = output[0].tolist()
rv = st.rv_discrete(values=(Vhalf, [1/len(Vhalf),]*len(Vhalf)))
print("median: {}".format(rv.median()))
print("95% CI: {}".format(rv.interval(0.95)))


median: -34.96014346870338
95% CI: (-34.97005619255574, -34.926599614270145)

In [119]:
slope = output[1].tolist()
rv = st.rv_discrete(values=(slope, [1/len(slope),]*len(slope)))
print("median: {}".format(rv.median()))
print("95% CI: {}".format(rv.interval(0.95)))


median: 7.197369620500938
95% CI: (7.19090481887031, 7.224585984649747)

In [120]:
# Voltage and slope factor at half-inactivation
grouped = samples[samples['exp']==2].groupby('sample')
def fit_boltzmann(group):
    def boltzmann(V, Vhalf, Shalf):
        return 1/(1+np.exp((V-Vhalf)/Shalf))
    guess = (-50, 10)
    popt, _ = curve_fit(boltzmann, group.x, group.y, guess)
    return popt
output = grouped.apply(fit_boltzmann).apply(pd.Series)

In [121]:
print(output.mean())
print(output.std())


0   -72.193234
1     6.109548
dtype: float64
0    0.125658
1    0.078064
dtype: float64

In [122]:
Vhalf = output[0].tolist()
rv = st.rv_discrete(values=(Vhalf, [1/len(Vhalf),]*len(Vhalf)))
print("median: {}".format(rv.median()))
print("95% CI: {}".format(rv.interval(0.95)))


median: -72.21669685772069
95% CI: (-72.44020974010718, -71.87864783039117)

In [123]:
slope = output[1].tolist()
rv = st.rv_discrete(values=(slope, [1/len(slope),]*len(slope)))
print("median: {}".format(rv.median()))
print("95% CI: {}".format(rv.interval(0.95)))


median: 6.154343522380662
95% CI: (5.931996934878577, 6.207057392589782)

In [ ]: