# Vectorised implementation of splines ## 2D open or closed Spline ### Expression of the curve $\gamma$ $$ \gamma: t \in [0, 1] \longrightarrow \mathbb{R}^2 $$ $$ \gamma: t \longmapsto \sum_{k=0}^{M-1} \mathbf{c}[k]\ \varphi_{\text{per}}(Mt - k) $$ where $t$ is the _parameter_, where $\mathbf{c} \in \mathbb{R}^{M \times 2}$ are the spline coefficients also referred to _in this 2D case_ as _control points_, and where $\varphi_{\text{per}}$ is the periodisation of $\varphi$, a given basis function. ### Periodisation using wrapping Periodisation is needed in the case of closed spline to meet boundary conditions. One can define a wrapping function $\text{wrap}_{M}$ to easily make $\varphi$ periodic, that is: $$ \forall k \in [\![M]\!], \forall t \in [0,1] \quad \varphi_{\text{per}}(M t - k) = \varphi_{} \circ \text{wrap}_{M}(t,k) $$ Wrapping is used in this implementation. ### Sampling using matrices One can efficient sample a point $\gamma(t) \in \mathbb{R}^2 \approxeq \mathbb{R}^{1\times 2}$ using matrices multiplication. One can compute the vector: $$ \phi_M(t) \triangleq \left[\varphi_{\text{per}}(Mt - k)\right]_{k=0}^{M-1} \in \mathbb{R}^M \approxeq \mathbb{R}^{1\times M} $$ And gets: $$ \gamma(t) = \phi_M(t)\ \mathbf{c} $$ Thus, for a given number of points $N$, one can easily extend this scheme to sample a set $\mathbf{X}$ of points: $$ \mathbf X \triangleq [\gamma(t_i)]_{i=0}^{N-1} \in \mathbb{R}^{N\times 2} $$ by computing the matrix: $$ \mathbf{\Phi} \triangleq [\phi_M(t_i)]_{i=0}^{M-1} \mathbb{R}^{N\times M} $$ one gets: $$ \mathbf X = \mathbf{\Phi}\ \mathbf{c} $$ ### Sampling evenly on the curve $\gamma$ A normal sampling consider a range of $N$ evenly spread parameters $(t_i)_{i=0}^{N-1} \in [0, 1]^N$, that is: $$ t_i \triangleq \frac{i}{N-1}, \quad \forall i \in [\![N]\!] $$ This generally does not give evenly sampled points $(\gamma(t_i))_{i=0}^{N-1}$ on the curve, in the sense that: $$ || \gamma(t_{i+1}) - \gamma(t_{i})||_2 \approxeq d, \quad \forall i \in [\![N-1]\!] $$ where $$ d\triangleq \frac{L}{N} $$ $$ L \triangleq \text{length}_\gamma(1) $$ for: $$ \text{length}_\gamma: t \longmapsto \int_0^t ||\gamma'(t) ||_2 \; \text d t $$ To sample $(\gamma(t_i))_{i=0}^{N-1}$ evenly, one can define $(t_i)_{i=0}^{N-1} \in [0, 1]^N$ as follows: $$ t_i \triangleq \text{length}_\gamma^{-1}\left(\frac{i}{N-1} L \right), \quad \forall i \in [\![N]\!] $$ In practice, one does not compute $\text{length}_\gamma^{-1}$ but binary-searches for preimages of $\text{length}_\gamma$ after having it computed on a discretisation of $[0, 1]$ using $N_{\text{oversample}} = c \ N$ points, for a given integer $c$, for instance $c \triangleq 5$. This can be done efficiently for all the points using using numerical integration and a schema with the exact same matricial structure as presented above that considers $\varphi'$ instead of $\varphi$ for sampling $\gamma'$. ## 2-Sphere homeomorphic Spline ### Expresion of the surface $\eta$ A spline surface in the sphere parametrisation is given by: $$ \eta:[0, 1]^2 \longrightarrow \mathbb{R}^3 $$ $$ \mathbf{\eta}: (s, t) \longmapsto \sum_{l=-1}^{Ms} \sum_{k=0}^{M_t -1} \mathbf{c}[l, k] \varphi_{\frac{\pi}{M_s - 1}}((M_s - 1)s - l)\ \varphi_{\frac{2\pi}{M_t}, \text{per}}(M_t t - k) $$ where $s$ relates to the longitude, $t$ to the latitude, where we consider the spline coefficients: $$ \mathbf{c} \in \mathbb{R}^{(M_s + 2)\times M_t \times 3} $$ and exponential spline basis: \begin{align} \varphi_\alpha:x \longmapsto \begin{cases} \frac{\cos(\alpha|x|)\cos(\frac{\alpha}{2})-\cos(\alpha)}{\left(1-\cos(\alpha)\right)} & 0 \leq |x| < \frac{1}{2}\\ \frac{\left(1-\cos(\alpha(3/2-|x|))\right)}{2\left(1-\cos(\alpha)\right)} & \frac{1}{2} \leq |x| < \frac{3}{2} \\ 0 & \frac{3}{2} \leq |x| \end{cases}. \end{align} for $\alpha \in \left\{\frac{2\pi}{M_t}, \frac{\pi}{M_s - 1}\right\}$. ### On coefficients and control points The surface is fully determined by $M_t(M_s-2)+6$ points: - the north pole $\mathbf{c}_\mathrm{N}$ - the south pole $\mathbf{c}_\mathrm{S}$ - the tangents at the north pole $\mathbf{T}_{1,\mathrm{N}}$, $\mathbf{T}_{2,\mathrm{N}}$ - the tangents at the south pole $\mathbf{T}_{1,\mathrm{S}}$, $\mathbf{T}_{2,\mathrm{S}}$ - $(M_s - 2)\times M_t$ actual control points (excluding $\mathbf{c_N}$ and $\mathbf{c_S}$, the north and south pole), also referred to as `control_points` : $$ \mathbf{c}[l, k],\quad (l,k) \in [\![1, M_s - 2 ]\!] \times [\![M_t]\!] $$ Moreover: - $4\times M_t$ additional control points at the extremities of each open longitude to ensure a correct behaviour at the boundaries are used for continuity and smoothness conditions due to the support size of the exponential spline basis: $$ \mathbf{c}[l, k],\quad (l,k) \in \{-1, 0, M_s - 1, M_s \} \times [\![M_t]\!] $$ Their expression is given, for $k \in [\![M_t]\!]$, by: $$ \begin{array} \mathbf{c}[-1,k] &=& \mathbf{c}[1,k] - \frac{\mathbf{T}_{1,\mathrm{N}}c_{M_t}[k] + \mathbf{T}_{2,\mathrm{N}}s_{M_t}[k]}{(M_s-1)\varphi'_{\frac{\pi}{M_s-1}}(1)} \\ \mathbf{c}[0,k] &=& \frac{\mathbf{c}_\mathrm{N} - \varphi_{\frac{\pi}{M_s-1}}(1)(\mathbf{c}[-1,k]+\mathbf{c}[1,k])}{\varphi_{\frac{\pi}{M_s-1}}(0)} \\ \mathbf{c}[M_s,k] &=& \mathbf{c}[M_s-2,k] - \frac{\mathbf{T}_{1,\mathrm{S}}c_{M_t}[k] + \mathbf{T}_{2,\mathrm{S}}s_{M_t}[k]}{(M_s-1)\varphi'_{\frac{\pi}{M_s-1}}(1)}\\ \mathbf{c}[M_s-1,k] &=& \frac{\mathbf{c}_\mathrm{S} - \varphi_{\frac{\pi}{M_s-1}}(1)(\mathbf{c}[M_s-2,k]+\mathbf{c}[M_s,k])}{\varphi_{\frac{\pi}{M_s-1}}(0)} \end{array} $$ ### Perioding latitudes using argument wrapping As in the 2D case, ine can define a wrapping function $\text{wrap}_{M_t}$ to make $\varphi_{\frac{2\pi}{M_t}}$ periodic, that is $$ \forall k \in [\![M_t]\!], \forall t \in [0,1] \quad \varphi_{\frac{2\pi}{M_t}, \text{per}}(M_t t - k) = \varphi_{\frac{2\pi}{M_t}} \circ \text{wrap}_{M_t}(t,k) $$ Wrapping is used in this implementation. ### Matricial expression of the surface We can rewrite the expression $$ \mathbf{\eta} (s, t) = \sum_{l=-1}^{Ms} \sum_{k=0}^{M_t -1} \mathbf{c}[l, k] \varphi_{\frac{\pi}{M_s - 1}}((M_s - 1)s - l)\ \varphi_{\frac{2\pi}{M_t}, \text{per}}(M_t t - k) $$ as a matricial product. To do so, we first define the following matrix: $$ \begin{array} \ \Phi(s, t) &\triangleq& \left[\varphi_{\frac{\pi}{M_s - 1}}((M_s - 1)s - l) \ \varphi_{\frac{2\pi}{M_t}, \text{per}}(M_t t - k)\right]_{(l, k) \in [\![M_s + 2]\!]\times [\![M_t]\!]} &\in& \mathbb{R}^{(M_s + 2)\times M_t} \end{array} $$ This way we have: $$ \mathbf{\eta} (s, t) = \sum_{l=-1}^{Ms} \sum_{k=0}^{M_t -1} \Phi(s, t)[l, k] \ \mathbf{c}[l, k] $$ To combine both sum in one can, we define flattened version of $\Phi(s, t)$ and $\mathbf{c}$: $$ \begin{array} \ \tilde\Phi(s, t) &\triangleq& \text{flatten}(\Phi(s, t)) &\in & \mathbb{R}^{(M_s + 2)M_t} \approxeq \mathbb{R}^{1 \times (M_s + 2)M_t} \\ &=& \left[\varphi_{\frac{2\pi}{M_t}, \text{per}}(M_t t - k) \varphi_{\frac{\pi}{M_s - 1}}((M_s - 1)s - l)\right]_{i\triangleq (l\ M_t + k) \in [\![(M_s + 2) M_t ]\!]} \end{array} $$ and $$ \begin{array} \quad \mathbf{\tilde c} &\triangleq& \text{flatten}(\mathbf{c}) &\in& \mathbb{R}^{(M_s + 2)M_t \times 3} \\ &=& \left[\mathbf{c}[k,l]\right]_{i\triangleq (l\ M_t + k) \in [\![(M_s + 2) M_t ]\!]} \end{array} $$ So that we get the nice matricial expression: $$ \mathbf{\eta}(s, t) = \tilde\Phi(s, t)\ \mathbf{\tilde c} \in \mathbb{R}^{1\times 3} $$ ### Efficiently sampling using the matricial expression Now, for some sampling parameters $N_s, N_t$, how to obtain: $$ \ \mathbf{X} \triangleq \left(\eta(s_i, t_j)\right)_{(i, j) \in {[\![N_s]\!] \times [\![N_t]\!]}} \in \mathbb{R}^{N_sN_t\times 3} $$ ? And how to do it efficiently for several splines when $N_s, N_t$ are fixed? - Compute the tensor resulting from the concatenation of the points $\Phi$ matrices: $$ \mathbf{\Phi} \triangleq \left[\Phi(s_i, t_j) \right]_{(i, j) \in {[\![N_s]\!] \times [\![N_t]\!]}} \in \mathbb{R}^{N_s\times N_t\times (M_s + 2)\times M_t} $$ - Flatten it on its two first axis and on its two lasts, that is: $$ \begin{array} \mathbf{\tilde\Phi} &\triangleq& \text{flatten}(\mathbf{\Phi}) \in \mathbb{R}^{N_s N_t\times (M_s + 2)M_t} \\ &=& \left[\tilde\Phi(s_i, t_j) \right]_{p\triangleq(i N_t+ j) \in {[\![N_s N_t]\!]}} \end{array} $$ - Cache $\mathbf{\tilde\Phi}$ - Use $\mathbf{\tilde\Phi}$ on several splines' flattened coefficients $\mathbf{\tilde c}$: $$ \ \mathbf{X} = \mathbf{\tilde \Phi} \mathbf{\tilde c} \in \mathbb{R}^{N_sN_t \times 3} $$ ### Computing $\Phi$ efficiently: the tensor wizardry behind `_get_phi` First note that $\Phi(s, t) \in \mathbb{R}^{(M_s + 2) \times M_t}$ can be computed as the outter product of two vectors $$ \Phi(s, t) = \phi_{M_s}^{\text{long}}(s) \otimes \phi_{M_t}^{\text{lat}}(t) $$ where $$ \left\{ \begin{array} \ \phi_{M_s}^{\text{long}}(s) &\triangleq& \left[\varphi_{\frac{2\pi}{M_s -1}}((M_s - 1)s - l)\right]_{l \in [\![M_s + 2]\!]} &\in& \mathbb{R}^{M_s + 2} \\ \phi_{M_t}^{\text{lat}}(t) &\triangleq& \left[\varphi_{\frac{2\pi}{M_t}, \text{per}}(M_t t - k)\right]_{k \in [\![M_t]\!]} &\in& \mathbb{R}^{M_t} \end{array} \right. $$ This can be generalised on all the points $(i, j) \in {[\![N_s]\!] \times [\![N_t]\!]}$. Indeed suppose that we have the two following matrices which concatenates the vectors for each points each axis: $$ \left\{ \begin{array} \ \phi_{(N_s, M_s)}^{\text{long}} &\triangleq& \left[\phi_{M_s}^{\text{long}}(s_i)\right]_{i \in [\![N_s]\!]} &\in& \mathbb{R}^{N_s \times (M_s + 2)} \\ \phi_{(N_t, M_t)}^{\text{lat}} &\triangleq& \left[\phi_{M_t}^{\text{lat}}(t_j)\right]_{j \in [\![N_t]\!]} &\in& \mathbb{R}^{N_t \times M_t} \end{array} \right. $$ $\mathbf{\Phi}$ can be computed by performing the outer product w.r.t the two axis of those: $$ \mathbf{\Phi} = \left[\phi_{(N_s, M_s)}^{\text{long}}[i,:] \otimes \phi_{(N_t, M_t)}^{\text{lat}}[j,:]\right]_{(i, j) \in {[\![N_s]\!] \times [\![N_t]\!]}} \in \mathbb{R}^{N_s\times N_t\times (M_s + 2)\times M_t} $$ This can be computed in a call of `numpy.einsum` with the proper subscripts. See [this small numpy snippet](https://gitlab.ebi.ac.uk/-/snippets/51) to understand the implementation. ## About the implementation of $\text{flatten}$ $\text{flatten}$ is implemented using reshaping (e.g `np.reshape`) which are constant time and memory operations (solely the tensors strides are changed when reshaping). ## References