# funspace
## Funspace
<img align="right" src="https://rustacean.net/assets/cuddlyferris.png" width="80">
Collection of function spaces.
## Bases
### Implemented bases:
- `Chebyshev` (Orthogonal), see [`chebyshev()`]
- `ChebDirichlet` (Composite), see [`cheb_dirichlet()`]
- `ChebNeumann` (Composite), see [`cheb_neumann()`]
- `ChebDirichletNeumann` (Composite), see [`cheb_dirichlet_neumann()`]
- `FourierC2c` (Orthogonal), see [`fourier_c2c()`]
- `FourierR2c` (Orthogonal), see [`fourier_r2c()`]
### Transform
A transformation describes a change from physical space to functional space.
For example, a Fourier transform transforms a function
on a equispaced grid into coefficients of sine/cosine polynomials.
This is analogous to other function spaces. The transformations are
implemented by the [`FunspaceElemental`] trait.
#### Example
Apply forward transform of 1d array in `cheb_dirichlet` space
```rust
use funspace::traits::FunspaceElemental;
use funspace::cheb_dirichlet;
use ndarray::{Array1, array};
let mut cd = cheb_dirichlet::<f64>(5);
let input = array![1., 2., 3., 4., 5.];
let output: Array1<f64> = cd.forward(&input, 0);
```
### Differentiation
An essential advantage of the representation of a function with coefficients in
the function space is its ease of differentiation. Differentiation in
Fourier space becomes multiplication with the wavenumber vector.
Differentiation in Chebyshev space is done by a recurrence
relation and almost as fast as in Fourier space.
Each base implements a differentiation method, which is applied to
an array of coefficents. This is defined by the [`FunspaceElemental`] trait.
#### Example
Apply differentiation
```rust
use funspace::fourier_r2c;
use funspace::traits::{FunspaceElemental, FunspaceExtended};
use ndarray::Array1;
use num_complex::Complex;
// Define base
let mut fo = fourier_r2c(8);
// Get coordinates in physical space
let x: Vec<f64> = fo.get_nodes().clone();
let v: Array1<f64> = x
.iter()
.map(|x| (2. * x).sin())
.collect::<Vec<f64>>()
.into();
// Transform to physical space
let vhat: Array1<Complex<f64>> = fo.forward(&v, 0);
// Apply differentiation twice along first axis
let dvhat = fo.differentiate(&vhat, 2, 0);
// Transform back to spectral space
let dv: Array1<f64> = fo.backward(&dvhat, 0);
// Compare with correct derivative
for (exp, ist) in x
.iter()
.map(|x| -4. * (2. * x).sin())
.collect::<Vec<f64>>()
.iter()
.zip(dv.iter())
{
assert!((exp - ist).abs() < 1e-5);
}
```
### Composite Bases
Function spaces such as those of Fourier polynomials or Chebyshev polynomials are
are considered orthogonal, i.e. the dot product of every single
polynomial with any other polynomial in its set vanishes. In this cases
the mass matrix is a purely diagonal matrix.
However, other function spaces can be constructed by a linear combination
of the orthogonal basis functions. In this way, bases can be constructed
that satisfy certain boundary conditions such as Dirichlet
(zero at the ends) or Neumann (zero derivative at the ends).
This is useful for solving partial differential equations (see *Galerkin* method),
since calculation in these bases automatically satisfy the boundary conditions.
To switch from its composite form to the orthogonal form, each base implements
a [`FunspaceElemental`] trait, which defines the transform `to_ortho` and `from_ortho`.
If the base is already orthogonal, the input will be returned, otherwise it
is transformed from the composite space to the orthogonal space (`to_ortho`), or vice versa
(`from_ortho`).
Note that the size of the composite space is usually
less than its orthogonal counterpart. In other words, the composite space is
usually a lower dimensional subspace of the orthogonal space. Therefore the number of coefficients
between orthogonal and composite space is different.
#### Example
Transform composite space `cheb_dirichlet` to its orthogonal counterpart
`chebyshev`. Note that `cheb_dirichlet` has 6 spectral coefficients,
while the `chebyshev` bases has 8.
```rust
use funspace::traits::{FunspaceElemental, FunspaceExtended};
use funspace::{cheb_dirichlet, chebyshev};
use std::f64::consts::PI;
use ndarray::prelude::*;
use ndarray::Array1;
use num_complex::Complex;
// Define base
let mut ch = chebyshev(8);
let mut cd = cheb_dirichlet(8);
// Get coordinates in physical space
let x: Vec<f64> = ch.get_nodes().clone();
let v: Array1<f64> = x
.iter()
.map(|x| (PI / 2. * x).cos())
.collect::<Vec<f64>>()
.into();
// Transform to physical space
let ch_vhat: Array1<f64> = ch.forward(&v, 0);
let cd_vhat: Array1<f64> = cd.forward(&v, 0);
// Send array to orthogonal space (cheb_dirichlet
// to chebyshev in this case)
let cd_vhat_ortho = cd.to_ortho(&cd_vhat, 0);
// Both arrays are equal, because field was
// initialized with correct boundary conditions,
// i.e. dirichlet ones
for (exp, ist) in ch_vhat.iter().zip(cd_vhat_ortho.iter()) {
assert!((exp - ist).abs() < 1e-5);
}
// However, if the physical field values do not
// satisfy dirichlet boundary conditions, they
// will be enforced by the transform to cheb_dirichle
// and ultimately the transformed values will deviate
// from a pure chebyshev transform (which does not)
// enfore the boundary conditions.
let mut v: Array1<f64> = x
.iter()
.map(|x| (PI / 2. * x).sin())
.collect::<Vec<f64>>()
.into();
let ch_vhat: Array1<f64> = ch.forward(&v, 0);
let cd_vhat: Array1<f64> = cd.forward(&v, 0);
let cd_vhat_ortho = cd.to_ortho(&cd_vhat, 0);
// They will deviate
println!("chebyshev : {:?}", ch_vhat);
println!("cheb_dirichlet: {:?}", cd_vhat_ortho);
```
### MPI Support (Feature)
`Funspace` comes with limited mpi support. Currently this is restricted
to 2D spaces. Under the hood it uses a fork of the rust mpi libary
<https://github.com/rsmpi/rsmpi> which requires an existing MPI implementation
and `libclang`.
Activate the feature in your ``Cargo.toml`
funspace = {version = "0.3", features = ["mpi"]}`
#### Examples
`examples/space_mpi.rs`
Install `cargo mpirun`, for example, and run
```rust
cargo mpirun --np 2 --example space_mpi --features="mpi"
```
## Versions
- v0.3.0: Major API changes + Performance improvements
License: MIT