---
# System prepended metadata

title: 'Scalable optimal-transport based particle filter: assimilation update in `dapy`'

---

# Scalable optimal-transport based particle filter: assimilation update in `dapy`

$\newcommand{\range}[2][1]{#1{:}\texttt{#2}}$
The assimilation update is 

$$
  z^p_t(s_m) = 
  \sum_{q\in\range{P}} \sum_{b\in\range{B}}
    \hat{a}^{p,q}_{t,b} \phi_b(s_m) \vec{z}^q_{t}(s_m).
$$

Due to the requirement that the basis functions $\beta_{\range{M}}$ satisfy $\beta_m(s_m) = 1$ and $\beta_m(s_n) = 0~~\forall n \neq m$ we have that

$$
  \vec{z}^p_t(s_m) = \vec{x}^p_{t,m} \quad\text{and}\quad z^p_t(s_m) = x^p_{t,m}.
$$

If we also denote $\phi_{b,m} = \phi(s_m)$ then we have that

$$
  x^p_{t,m} = 
  \sum_{q\in\range{P}}\sum_{b\in\range{B}}
    \hat{a}^{p,q}_{t,b} \phi_{b,m} \vec{x}^q_{t,m} = 
  \sum_{b\in\range{B}}\sum_{q\in\range{P}}
    \hat{a}^{p,q}_{t,b} \phi_{b,m} \vec{x}^q_{t,m}.
$$

Translating to the syntax in the implementation in `dapy`, assuming the assimilation update is being performed for time index $\texttt{time_index} = t$ the statement

```
state_particle_patches = pou.split_into_patches_and_scale(state_particles_mesh)
```

computes the *non-zero elements* of the pointwise products $\phi_{b,m} \vec{x}^p_{t,m}$ with `state_particles_mesh` a $\texttt{P} \times \texttt{N} \times \texttt{M}$ dimensional array ($\texttt{P}$ number of particles, $\texttt{N}$ dimension of local state at each mesh node, $\texttt{M}$ number of spatial mesh nodes) such that 

$$\texttt{state_particles_mesh}[p, :, m] = \vec{x}^p_{t, m}$$ 

and `state_particle_patches` a $\texttt{P} \times \texttt{N} \times \texttt{B} \times \texttt{C}$ dimensional array ($\texttt{B}$ the number of patches in the POU, $\texttt{C} = | \{m \in \range{M}: \phi_{b,m} \neq 0 \} |$, i.e. the number of non-zero entries in $\phi_{b,\range{M}}$ which is assumed constant for each $b$) such that 

$$
  \texttt{state_particle_patches}[p, :, b, c] = 
  \phi_{b,m_b(c)}\vec{x}^p_{t, m_b(c)}
$$ 

where $m_b(c)$ maps $c \in \range{C}$ to each of the indices $\{m \in \range{M}: \phi_{b,m} \neq 0 \}$.

The statement

```
post_state_particle_patches = np.einsum(
    "bpq,qnbc->pnbc", per_patch_transform_matrices, state_particle_patches)
```

computes the linear transformations of $\phi_{b,m} \vec{x}^p_{t,m}$ by the per-patch transform matrices $\hat{a}^{p,q}_{t,b}$  with `per_patch_transform_matrices` a $\texttt{B} \times \texttt{P} \times \texttt{P}$ array such that $\texttt{per_patch_transform_matrices}[b, p, q] = \hat{a}^{p,q}_{t,b}$ and `post_state_particle_patches` a $\texttt{P} \times \texttt{N} \times \texttt{B} \times \texttt{C}$ dimensional array with

$$
  \texttt{post_state_particle_patches}[p, :, b, c] =
  \sum_{q\in\range{P}}\hat{a}^{p,q}_{t,b} \phi_{b,m_b(c)} \vec{x}^q_{t,m_b(c)}.
$$


The statement

````
post_state_particles_mesh = pou.combine_patches(post_state_particles_patches)
````

combines the posterior state particles patches by summing over all patches with non-zero values at a particle mesh node index with `post_state_particles_mesh` a $\texttt{P} \times \texttt{N} \times \texttt{M}$ dimensional array with

$$
   \texttt{post_state_particles_mesh}[p, :, m] = 
   \sum_{b\in\range{B}}\sum_{q\in\range{P}}
     \hat{a}^{p,q}_{t,b} \phi_{b,m} \vec{x}^q_{t,m}.
$$