Skip to content

Commit a3698f7

Browse files
committed
Removed bivariate example; updated formatting
1 parent 55394ba commit a3698f7

File tree

2 files changed

+315
-474
lines changed

2 files changed

+315
-474
lines changed

examples/time_series/Euler-Maruyama_and_SDEs.ipynb

Lines changed: 277 additions & 346 deletions
Large diffs are not rendered by default.

examples/time_series/Euler-Maruyama_and_SDEs.myst.md

Lines changed: 38 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,20 @@ run_control:
3232
slideshow:
3333
slide_type: '-'
3434
---
35+
import warnings
36+
3537
import arviz as az
3638
import matplotlib.pyplot as plt
3739
import numpy as np
3840
import pymc as pm
3941
import pytensor.tensor as pt
4042
import scipy as sp
43+
44+
# Ignore UserWarnings
45+
warnings.filterwarnings("ignore", category=UserWarning)
46+
47+
RANDOM_SEED = 8927
48+
np.random.seed(RANDOM_SEED)
4149
```
4250

4351
```{code-cell} ipython3
@@ -96,16 +104,19 @@ run_control:
96104
slideshow:
97105
slide_type: subslide
98106
---
99-
plt.figure(figsize=(10, 3))
100-
plt.plot(x_t[:30], "k", label="$x(t)$", alpha=0.5)
101-
plt.plot(z_t[:30], "r", label="$z(t)$", alpha=0.5)
102-
plt.title("Transient")
103-
plt.legend()
104-
plt.subplot(122)
105-
plt.plot(x_t[30:], "k", label="$x(t)$", alpha=0.5)
106-
plt.plot(z_t[30:], "r", label="$z(t)$", alpha=0.5)
107-
plt.title("All time")
108-
plt.legend();
107+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3))
108+
109+
ax1.plot(x_t[:30], "k", label="$x(t)$", alpha=0.5)
110+
ax1.plot(z_t[:30], "r", label="$z(t)$", alpha=0.5)
111+
ax1.set_title("Transient")
112+
ax1.legend()
113+
114+
ax2.plot(x_t[30:], "k", label="$x(t)$", alpha=0.5)
115+
ax2.plot(z_t[30:], "r", label="$z(t)$", alpha=0.5)
116+
ax2.set_title("All time")
117+
ax2.legend()
118+
119+
plt.tight_layout()
109120
```
110121

111122
+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}}
@@ -123,7 +134,7 @@ new_sheet: false
123134
run_control:
124135
read_only: false
125136
---
126-
def lin_sde(x, lam):
137+
def lin_sde(x, lam, s2):
127138
return lam * x, s2
128139
```
129140

@@ -144,11 +155,12 @@ slideshow:
144155
---
145156
with pm.Model() as model:
146157
# uniform prior, but we know it must be negative
147-
l = pm.Flat("l")
158+
l = pm.HalfCauchy("l", beta=1)
159+
s = pm.Uniform("s", 0.005, 0.5)
148160
149161
# "hidden states" following a linear SDE distribution
150162
# parametrized by time step (det. variable) and lam (random variable)
151-
xh = pm.EulerMaruyama("xh", dt=dt, sde_fn=lin_sde, sde_pars=(l,), shape=N)
163+
xh = pm.EulerMaruyama("xh", dt=dt, sde_fn=lin_sde, sde_pars=(-l, s**2), shape=N, initval=x_t)
152164
153165
# predicted observation
154166
zh = pm.Normal("zh", mu=xh, sigma=5e-3, observed=z_t)
@@ -166,7 +178,7 @@ run_control:
166178
read_only: false
167179
---
168180
with model:
169-
trace = pm.sample()
181+
trace = pm.sample(nuts_sampler="nutpie", random_seed=RANDOM_SEED, target_accept=0.99)
170182
```
171183

172184
+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}, "slideshow": {"slide_type": "subslide"}}
@@ -185,7 +197,7 @@ plt.plot(x_t, "r", label="$x(t)$")
185197
plt.legend()
186198
187199
plt.subplot(122)
188-
plt.hist(az.extract(trace.posterior)["l"], 30, label=r"$\hat{\lambda}$", alpha=0.5)
200+
plt.hist(-1 * az.extract(trace.posterior)["l"], 30, label=r"$\hat{\lambda}$", alpha=0.5)
189201
plt.axvline(lam, color="r", label=r"$\lambda$", alpha=0.5)
190202
plt.legend();
191203
```
@@ -218,119 +230,6 @@ plt.legend();
218230

219231
+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}}
220232

221-
Note that
222-
223-
- inference also estimates the initial conditions
224-
- the observed data $z(t)$ lies fully within the 95% interval of the PPC.
225-
- there are many other ways of evaluating fit
226-
227-
+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}, "slideshow": {"slide_type": "slide"}}
228-
229-
### Toy model 2
230-
231-
As the next model, let's use a 2D deterministic oscillator,
232-
\begin{align}
233-
\dot{x} &= \tau (x - x^3/3 + y) \\
234-
\dot{y} &= \frac{1}{\tau} (a - x)
235-
\end{align}
236-
237-
with noisy observation $z(t) = m x + (1 - m) y + N(0, 0.05)$.
238-
239-
```{code-cell} ipython3
240-
N, tau, a, m, s2 = 200, 3.0, 1.05, 0.2, 1e-1
241-
xs, ys = [0.0], [1.0]
242-
for i in range(N):
243-
x, y = xs[-1], ys[-1]
244-
dx = tau * (x - x**3.0 / 3.0 + y)
245-
dy = (1.0 / tau) * (a - x)
246-
xs.append(x + dt * dx + np.sqrt(dt) * s2 * np.random.randn())
247-
ys.append(y + dt * dy + np.sqrt(dt) * s2 * np.random.randn())
248-
xs, ys = np.array(xs), np.array(ys)
249-
zs = m * xs + (1 - m) * ys + np.random.randn(xs.size) * 0.1
250-
251-
plt.figure(figsize=(10, 2))
252-
plt.plot(xs, label="$x(t)$")
253-
plt.plot(ys, label="$y(t)$")
254-
plt.plot(zs, label="$z(t)$")
255-
plt.legend()
256-
```
257-
258-
+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}, "slideshow": {"slide_type": "subslide"}}
259-
260-
Now, estimate the hidden states $x(t)$ and $y(t)$, as well as parameters $\tau$, $a$ and $m$.
261-
262-
As before, we rewrite our SDE as a function returned drift & diffusion coefficients:
263-
264-
```{code-cell} ipython3
265-
---
266-
button: false
267-
new_sheet: false
268-
run_control:
269-
read_only: false
270-
---
271-
def osc_sde(xy, tau, a):
272-
x, y = xy[:, 0], xy[:, 1]
273-
dx = tau * (x - x**3.0 / 3.0 + y)
274-
dy = (1.0 / tau) * (a - x)
275-
dxy = pt.stack([dx, dy], axis=0).T
276-
return dxy, s2
277-
```
278-
279-
+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}}
280-
281-
As before, the Euler-Maruyama discretization of the SDE is written as a prediction of the state at step $i+1$ based on the state at step $i$.
282-
283-
+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}, "slideshow": {"slide_type": "subslide"}}
284-
285-
We can now write our statistical model as before, with uninformative priors on $\tau$, $a$ and $m$:
286-
287-
```{code-cell} ipython3
288-
---
289-
button: false
290-
new_sheet: false
291-
run_control:
292-
read_only: false
293-
---
294-
xys = np.c_[xs, ys]
295-
296-
with pm.Model() as model:
297-
tau_h = pm.Uniform("tau_h", lower=0.1, upper=5.0)
298-
a_h = pm.Uniform("a_h", lower=0.5, upper=1.5)
299-
m_h = pm.Uniform("m_h", lower=0.0, upper=1.0)
300-
xy_h = pm.EulerMaruyama(
301-
"xy_h", dt=dt, sde_fn=osc_sde, sde_pars=(tau_h, a_h), shape=xys.shape, initval=xys
302-
)
303-
zh = pm.Normal("zh", mu=m_h * xy_h[:, 0] + (1 - m_h) * xy_h[:, 1], sigma=0.1, observed=zs)
304-
```
305-
306-
```{code-cell} ipython3
307-
pm.__version__
308-
```
309-
310-
```{code-cell} ipython3
311-
---
312-
button: false
313-
new_sheet: false
314-
run_control:
315-
read_only: false
316-
---
317-
with model:
318-
pm.sample_posterior_predictive(trace, extend_inferencedata=True)
319-
```
320-
321-
```{code-cell} ipython3
322-
plt.figure(figsize=(10, 3))
323-
plt.plot(
324-
trace.posterior_predictive.quantile((0.025, 0.975), dim=("chain", "draw"))["zh"].values.T,
325-
"k",
326-
label=r"$z_{95\% PP}(t)$",
327-
)
328-
plt.plot(z_t, "r", label="$z(t)$")
329-
plt.legend();
330-
```
331-
332-
+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}}
333-
334233
Note that the initial conditions are also estimated, and that most of the observed data $z(t)$ lies within the 95% interval of the PPC.
335234

336235
Another approach is to look at draws from the sampling distribution of the data relative to the observed data. This too shows a good fit across the range of observations -- the posterior predictive mean almost perfectly tracks the data.
@@ -350,6 +249,17 @@ az.plot_ppc(trace)
350249
:filter: docname in docnames
351250
:::
352251

252+
## Authors
253+
- Authored by @maedoc in July 2016
254+
- Updated to PyMC v5 by @fonnesbeck in September 2024
255+
256+
+++
257+
258+
## References
259+
:::{bibliography}
260+
:filter: docname in docnames
261+
:::
262+
353263
```{code-cell} ipython3
354264
%load_ext watermark
355265
%watermark -n -u -v -iv -w

0 commit comments

Comments
 (0)