---
# System prepended metadata

title: Preparation for ‘Parameter estimation for ODE models’

---

# Material for 'Parameter estimation for ODE models'

The main notebook and data is given at:
[github.com/SteffenPL/SeminarParameterEstimation](https://github.com/SteffenPL/SeminarParameterEstimation).

We will consider the following models:

## _Lotka-Volterra Predator-Prey_ model

$$
\begin{aligned}
\frac{\mathrm{d} x}{\mathrm{d} t} &= \alpha x - \beta x y, \newline 
\frac{\mathrm{d} y}{\mathrm{d} t} &= -\gamma y + \delta xy
\end{aligned}
$$
with $x(0) = 1, y(0) = 1$ for $t \in [0,10]$ and $\alpha = \frac{3}{2}, \beta = 1, \gamma = 3, \delta = 1$. 

### Datasets 

- `data_1.csv`
- `data_2.csv`
- `data_3.csv`
- `data_4.csv`

### Tasks:

- Try to estimate the correct parameters for all four datasets.
- Try different initial values, what do you notice?
- Plot a heatmap of the cost function.

Bonus:
- If you are interested in Bayesian inference, follow this tutorial:
https://turing.ml/dev/tutorials/10-bayesian-differential-equations/


## _FitzHugh–Nagumo_ neuron spike model

We consider the model
$$
\begin{aligned}
\frac{\mathrm{d} v}{\mathrm{d} t} &= \frac{1}{\varepsilon} ( f(v,\alpha) - w I_{\mathrm{app}} ), \newline 
\frac{\mathrm{d} w}{\mathrm{d} t} &= -v + \gamma w
\end{aligned}
$$
with $v(0) = 1, w(0) = 0$ and $t \in [0,10]$. The parameters are $\varepsilon  = 0.01$, $\alpha = 0.2, \gamma = 0.3$ and $I_{\mathrm{app}} = 0.8$ and 
$$
f(v,\alpha) = v (1-v) (v - \alpha).
$$

### Datasets

- `spikes.csv` (real data)


### Tasks:

- Try first $L^2$ loss functions. What do you observe?
- Modify the code to keep the parameter $\varepsilon$ constant.
- Develop ideas for a new `loss` function to improve the results.

---

# Useful code snippets

## Defining an ODE model

```julia
using OrdinaryDiffEq, Plots

function rhs!(du, u, p, t)
    du[1] = -p[1] * u[2]
    du[2] =  p[2] * u[1]
end

u0    = [1.0, 0.0]
tspan = [0.0, 10.0]
p     = [1.0, 1.0]

prob = ODEProblem(rhs!, u0, tspan, p)
sol = solve(prob, Tsit5())

plot(sol)
```

## Loading a CSV dataset

```julia
using CSV, DataFrames

df = CSV.read("data_1.csv", DataFrame)
df.t  # access column 't'
df.x  # access column 'x'
df.y  # access column 'y'
```

## Defining a $L^2$ cost function (without bounds)


```julia
using Optimization, OptimizationOptimJL

cost_function = build_loss_objective(prob, Tsit5(), 
                    L2Loss(df.t, [df.x'; df.y']),
                    Optimization.AutoForwardDiff(),
                    maxiters = 10000)

p_init = [1.0, 1.0, 2.0, 1.0]

optprob = OptimizationProblem(cost_function, 
                              p_init)

optsol = solve(optprob, BFGS())    
```

## Defining a $L^2$ cost function (with bounds)


```julia
using Optimization, OptimizationOptimJL, OptimizationMultistartOptimization

cost_function = build_loss_objective(prob, Tsit5(), 
                    L2Loss(df.t, [df.x'; df.y']),
                    Optimization.AutoForwardDiff(),
                    maxiters = 10000)

p_init = [1.0, 1.0, 2.0, 1.0]
ub = [3.0, 3.0, 4.0, 4.0]
lb = [0.0, 0.0, 0.0, 0.0]

optprob = OptimizationProblem(cost_function, 
                              p_init, ub = ub, lb = lb)

optsol = solve(optprob, MultistartOptimization.TikTak(100), LBFGS())    
```

## Plot heatmap of `cost_function`

Given a model with four parameters. Create a plot for different parameters $0 < p_1,p_2 < 2$ and fixed $p_3 = p_4 = 1$.

```julia
P1 = LinRange(0.0, 2.0, 60)
P2 = LinRange(0.0, 2.0, 60)
costs = [cost_function([p1, p2, 1.0, 1.0])) for p1 in P1, p2 in P2]

heatmap(P1, P2, costs)
title!("Cost of parameters α and σ")
```

## Define custom loss function


Here, we only measure the distance from the data to the first component of the solution `sol[1,:]`.

```julia 
using LinearAlgebra

function compute_distance(sol, data)
    return norm(sol[1,:] - data.x)
end

function loss(opt_params, data)

	# solve ODE
	ode_params = convert_to_ode_params(opt_params)
	sol = solve(prob, Tsit5(), p = ode_params, saveat = data.t)

	# check if solving the ODE was possible
	if sol.retcode != ReturnCode.Success 
		return Inf64
	end

	# compute distance to data
	return compute_distance(sol, data)
end

function convert_to_ode_params(opt_params)
    ode_params = opt_params  # or something else if needed
    return ode_params
end

df = CSV.read("data.csv", DataFrame)

adtype = Optimization.AutoForwardDiff()
optfnc = Optimization.OptimizationFunction((x, p) -> loss(x, df), adtype)


p_init = [0.01, 0.01, 0.1]
optprob = Optimization.OptimizationProblem(optfnc, p_init, 
	lb = [0.0, 0.0, 0.0], 
	ub = [0.5, 0.5, 1.0])


optsol = solve(optprob, BFGS())
```

---


# Preparation for 'Parameter estimation for ODE models'

In this lecture, we will learn how to fit ODE models to data.

## Reading material

If you are interested in practical aspects of parameter optimisation, then I recommend this article:
- [Lessons Learned from Quantitative Dynamical Modeling in Systems Biology](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0074335).

More in detail textbook is:
- [A Guide to Numerical Modelling in Systems Biology](https://link-springer-com.kyoto-u.idm.oclc.org/book/10.1007/978-3-319-20059-0) (see **Chapter 3, Sections 3.4 and 3.5**)


## Requirements for attending the course
1. You need a laptop with a working installation of **Julia** (or **Python**). I recommend to use Julia.
2. You should know how to solve simple ordinary differential questions with [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/getting_started/#Example-1-:-Solving-Scalar-Equations) (or [scipy.odeint](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html)). 

You can find installation instructions for under the follwing links:
- **Julia**: [Modern Julia (Installation Instructions)](https://modernjuliaworkflows.github.io/pages/writing/writing/#installation)
- Python: [Anaconda (Installation Instructions)](https://docs.anaconda.com/free/anaconda/install/index.html)

As an editor I recommend [Visual Studio Code](https://code.visualstudio.com/).

## Task

1. Please prepare yourself by simulating the famous _Lotka-Volterra Predator-Prey_ model

$$
\begin{aligned}
\frac{\mathrm{d} x}{\mathrm{d} t} &= \alpha x - \beta x y, \newline 
\frac{\mathrm{d} y}{\mathrm{d} t} &= -\gamma y + \delta xy
\end{aligned}
$$
with $x(0) = 1, y(0) = 1$ for $t \in [0,10]$ and $\alpha = \frac{3}{2}, \beta = 1, \gamma = 3, \delta = 1$. 


If you are looking for some hints: You can follow this tutorial: [SciML: Getting started](https://docs.sciml.ai/Overview/stable/getting_started/first_simulation/) 
(or in python: [SciPy: Lotka-Volterra equations](https://scientific-python.readthedocs.io/en/latest/notebooks_rst/3_Ordinary_Differential_Equations/02_Examples/Lotka_Volterra_model.html)).


2. Please read or recall the **[Newton's method](https://en.wikipedia.org/wiki/Newton%27s_method)** for optimisation problems such as $\min f(x)$ for $f: \mathbb{R}^n \to \mathbb{R}$. 