# MRA FIT

Fit curves with high precision using [Multi-resolution analysis](https://www.sciencedirect.com/topics/mathematics/multiresolution-analysis) and [Wavelet transform](https://en.wikipedia.org/wiki/Wavelet_transform). The module directly implements Orthogonal Gausslets as described in the [paper](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.99.081110), however, in addition, the module also has hat basis functions and allows for the construction of custom Gausslets.

## Installation

To install the latest version

```pip install mrafit```

To install a specific version (example 1.1.2)

```pip install mrafit==1.1.2```

## Use cases

### Approximating curve using basis function
    
mrafit allows reconstruction of a curve by expressing it in terms of localized basis functions. In other words, a curve represented by a list of points $x_i$, $y_i$ can be represented as a much smaller list of coefficients $c_i$, which reduces the dimensionality of representation. The same can be done for any 3-d curve represented by $x_i$, $y_i$, $z_i$.
 
### Quantum chemistry and molecular structure
Quantum chemistry involves calculating electronic states in Atoms and molecules with a large number of electrons using Schrödinger's equation, which makes it particularly challenging due to the complexity involved in electron-electron interactions. Orthogonal wavelet transformation can drastically simplify quantum chemistry calculations by reducing complexity from O(4) to O(2) while calculating the electron interaction Potential 

### ML and AI
Since any 2-d, 3-d curve can be reduced to a 1-d representation of coefficients, this can provide a massive computational advantage in ML problems where parametric learning of curves is required. Instead of learning points, one can simply apply mrafit and learn mra coefficients instead.

## Examples

### Most common use case

- To fit any function (type Callable) using a basis WaveletBasis (class Wavelet)

```
from mrafit.wavelet_basis.WaveletBasis
basis = WaveletBasis()
coeff, Y, error = basis.get_mra_approx(func, X)

```
In the above example 
coeffs: values of coefficients of given basis function (numpy array)
approx_function: values of approximated function using given basis (numpy array)
error: error of the approximate values with respect to the original value of the function

- To reconstruct any function of the coefficients of a basis is provided

```
Y = basis.get_reconstructed_func(coeffs, X)
```

- Example use case with GaussletBasis

```
import mrafit.wavelet_bases as wavelet_bases
import numpy as np

# From wavelet_bases, an instance of any available basis can be created. For example, to use the orthogonal Gausslet basis, we can define


gb = wavelet_bases.GaussletBasis()
func = lambda x : np.exp(-x**2/3) * (x**2 - x + 1)

# To approximate any given function defined over domain \(-1, 1\) with respect to a basis


X = np.linspace(-1, 1, 100)
coeffs, approx_func, error = gb.get_mra_approx(func, X)


# The example below includes a list of all steps to fit a synthetic function using mrafit

""" This parameter controls how precisely you want to approximate a function; the smaller the value better the approximation."22"
resolution = 0.5

wid = 10
N = 800
error_bound = 10e-2 * resolution

""" Use this section if you want to test gausslet with Stephen White's coefficients"""
gb = wavelet_bases.GaussletBasis(resolution=resolution)

""" Sample function to be approximated, you can change it as per your need"""
# func = lambda x : np.exp(-x**2/3) * (x**2 - x + 1) + (x + x**2)/10

""" Finally applying the mra approximation"""
X = np.linspace(-wid, wid, N)
coeffs, approx_func, error = gb.get_mra_approx(func, X)

plt.plot(X, approx_func)
plt.plot(X, np.vectorize(func)(X))

```

The image below shows the approximate function vs the actual function
![alt text](https://github.com/utksara/mrafit/blob/main/images/output.png)

### Evaluation of a basis 

Apart from the error obtained from get_mra_approx function, one can determine the performance of a basis function using get_orthongonality and get_completeness functions. This is especially useful for the bases which are known to be orthogonal or complete, but due to numerical transformation can give unexpected results.

```
from mrafit.wavelet_basis.WaveletBasis
basis = WaveletBasis()
coeff, Y, error = basis.get_mra_approx(func, X)

orthogonality = basis.get_orthogonality()
completness = basis.get_completeness()
```
Both returns score between 0(least complete) to 1 (most complete)

## Adjusting accuracy using grid spacing and resolution

In the get_mra_approx function, we input a parameter called X : a uniform one-dimensional grid expressed as a numpy array. The accuracy of how well a curve can be fit using a basis is highly sensitive to X. If X is a fine grid, it results in high accuracy but also incurs a high computational cost. On the other hand, even a coarse grid can provide fairly accurate results in many cases. But X alone doesn't determine accuracy; when initializing a basis, there is an optional parameter resolution of type float. It determines the support of a single basis function : if a resolution = 1, it means a single basis function has a support of width 1, and if resolution = 0.5, the same basis function is shrunk to half making its support of width 0.5. This means resolution size can be reduced to achieve further gain in accuracy. By default ```resolution = 1``` for most bases. There is an upper bound on accuracy that a basis can achieve with a given resolution, i.e. increasing grid resolution leads to asymptotic increase in accuracy.

But the more critical part is choosing the right X for a given resolution. If resolution is too small and grid size is relatively large, the get_approx_func will result in a highly erroneous fit. A good rule is to assign values to grid spacing and resolution proportionately, for example we can have resolution as a single independent variable and rest of the parameters dependent on it as

```
resolution = 0.1
N = int(100/resolution)
X = np.linspace(-5, 5, N)
```
To see this in practice, refer to [effect_of_resolution.ipynb](https://github.com/utksara/mrafit/blob/main/examples/effect_of_resolution.ipynb) notebook in examples

## List and plot of wavelet basis in the module

- HaarBasis
![HaarBasis](https://github.com/utksara/mrafit/blob/main/images/Haar.png)
- HatBasis
![HatBasis](https://github.com/utksara/mrafit/blob/main/images/HatBasis.png)
- GaussianBasis
![GaussianBasis](https://github.com/utksara/mrafit/blob/main/images/GaussianBasis.png)
- GaussletBasis
![GaussletBasis](https://github.com/utksara/mrafit/blob/main/images/GaussletBasis.png)
- PlateauGaussletBasis
![PlateauGaussletBasis](https://github.com/utksara/mrafit/blob/main/images/PlateauGaussletBasis.png)
- SharpGaussletBasis
![SharpGaussletBasis](https://github.com/utksara/mrafit/blob/main/images/SharpGaussletBasis.png)