Usage and Examples
==================

Some examples are also available in Jupyter Notebook format in the ``Examples/`` directory of the repository.

Basic Usage
-----------

.. code:: python

    from pymcr.mcr import McrAR
    mcrar = McrAR()
    
    # MCR assumes a system of the form: D = CS^T
    #
    # Data that you will provide (hyperspectral context):
    # D [n_pixels, n_frequencies]  # Hyperspectral image unraveled in space (2D)
    #
    # initial_spectra [n_components, n_frequencies]  ## S^T in the literature
    # OR
    # initial_conc [n_pixels, n_components]   ## C in the literature

    # If you have an initial estimate of the spectra
    mcrar.fit(D, ST=initial_spectra)

    # Otherwise, if you have an initial estimate of the concentrations
    mcrar.fit(D, C=initial_conc)


Default: Ordinary Least-Squares (OLS) with Non-Negativity Constraints
---------------------------------------------------------------------

Using OLS and post-iterative non-negativity constraints is a classic way of performing MCR-ALS. 
OLS algorithms are often faster in practice, but NNLS method may converge MCR faster.

.. code:: python

    from pymcr.mcr import McrAR
    mcrar = McrAR()

    # Equivalent to

    mcrar = McrAR(c_regr='OLS', st_regr='OLS')

    # Equivalent to

    from pymcr.mcr import McrAR
    from pymcr.regressors import OLS
    from pymcr.constraints import ConstraintNonneg

    mcrar = McrAR(c_regr=OLS(), st_regr=OLS(), c_constraints=[ConstraintNonneg()],
                  st_constraints=[ConstraintNonneg()])

    # Then use the fit method with initial guesses of ST or C.

Non-Negative Least-Squares (NNLS), No Constraints
--------------------------------------------------

Using OLS and post-iterative non-negativity constraints is a classic way of performing MCR-ALS. 
OLS algorithms are often faster in practice, but NNLS method may converge MCR faster.

.. code:: python

    from pymcr.mcr import McrAR
    from pymcr.regressors import NNLS

    mcrar = McrAR(c_regr=NNLS(), st_regr=NNLS(), c_constraints=[], st_constraints=[])

    # Then use the fit method with initial guesses of ST or C.

Mix NNLS and OLS Regressors, Non-Negativity and Sum-to-1 (i.e., Norm) Constraints
----------------------------------------------------------------------------------

In this example, NNLS is used to regress the signature/spectrum and OLS is used for
abundance/concentration. Additionally, the ConstraintNorm imposes that after each iteration
the summation of all concentrations for each data point sums-to-one. This is common and
physical for many use-cases such as spectroscopy (as long as all analytes have a signature
[i.e., no 0-intensity signatures])

.. code:: python

    from pymcr.mcr import McrAR
    from pymcr.regressors import NNLS, OLS
    from pymcr.constraints import ConstraintNonneg, ConstraintNorm

    # Note constraint order matters
    mcrar = McrAR(max_iter=100, st_regr='NNLS', c_regr='OLS', 
                  c_constraints=[ConstraintNonneg(), ConstraintNorm()])

    # Equivalent to

    # Note constraint order matters
    mcrar = McrAR(max_iter=100, st_regr=NNLS(), c_regr=OLS(), 
                  c_constraints=[ConstraintNonneg(), ConstraintNorm()])

    # Then use the fit method with initial guesses of ST or C.

Ridge-Regression from Scikit-Learn
----------------------------------

In this example, NNLS is used to regress the signatures/spectra but a ridge
regression regressor is imported from `scikit-learn <http://scikit-learn.org/stable/modules/linear_model.html#ridge-regression>`_

.. code:: python

    from pymcr.mcr import McrAR
    from pymcr.regressors import NNLS
    from pymcr.constraints import ConstraintNonneg, ConstraintNorm

    from sklearn.linear_model.ridge import Ridge

    # Note that an instance of Ridge can be instantiated (with hyperparameters)
    # within the instantiation of McrAR.
    mcrar = McrAR(max_iter=100, tol_increase=2, tol_err_change=1e-10,
                  c_regr=Ridge(alpha=1), st_regr=NNLS(), 
                  c_constraints=[ConstraintNonneg(), ConstraintNorm()])

    # Then use the fit method with initial guesses of ST or C.


Single-Gaussian Distribution Spectral Fitting with LMFIT
---------------------------------------------------------

In this example, the `LMFIT Library <https://lmfit.github.io/lmfit-py/>`_ is used to perform Gaussian distribution fitting on each spectral component. 
A longer example of this can be found in the ``Examples/NIST_JRes_Paper`` Jupyter Notebook.

.. code:: python

    import numpy as np

    from lmfit.models import GaussianModel

    from pymcr.mcr import McrAR
    from pymcr.constraints import Constraint, ConstraintNorm
    
    class ConstraintSingleGauss(Constraint):
        """
        Perform a nonlinear least-squares fitting to enforce a Gaussian. 

        Parameters
        ----------
        copy : bool
            Make copy of input data, A; otherwise, overwrite (if mutable)

        axis : int
            Axis to perform fitting over

        """
        def __init__(self, copy=False, axis=-1):
            """ A must be non-negative"""
            self.copy = copy
            self.axis = axis
            
        def transform(self, A):
            """ Fit """
            n_components = list(A.shape)
            x = np.arange(n_components[self.axis])
            n_components.pop(self.axis)
            assert len(n_components)==1, "Input must be 2D"
            n_components = n_components[0]
            
            A_fit = 0*A
            
            for num in range(n_components):
                if (self.axis == -1) | (self.axis == 1):
                    y = A[num, :]
                else:
                    y = A[:, num]
                    
                mod = GaussianModel()
                pars = mod.guess(y, x=x)
                out = mod.fit(y, pars, x=x)
                
                if (self.axis == -1) | (self.axis == 1):
                    A_fit[num,:] = 1*out.best_fit
                else:
                    A_fit[:, num] = 1*out.best_fit
                
            if self.copy:
                return A_fit
            else:
                A *= 0
                A += A_fit
                return A

    mcrar = McrAR(max_iter=1000, st_regr='NNLS', c_regr='NNLS', 
                  c_constraints=[ConstraintNorm()],
                  st_constraints=[ConstraintSingleGauss()])


Semi-Learned Fitting
---------------------

In semi-learned fitting, one has *a priori* knowledge about some or all of the spectra and/or concentration components. To use this information,
the known entities are put into the initial guesses and the "fix" parameter is used to tell the fit method the indices. Effectively the fit method
will **not** alter these values during iterations.


.. code:: python

    # Example 1: 2 known concentration maps
    # D: Mixture Spectra
    # C_guess: Concentration guesses, but the 0 and 1 indices are known ahead of time
    
    mcrar.fit(D, C=C_guess, c_fix=[0,1])

    # Example 1: 2 known concentration maps, 1 known spectrum
    # D: Mixture Spectra
    # C_guess: Concentration guesses, but the 0 and 1 indices are known ahead of time
    # St_guess: Spectra guesses, but the 0-index is known ahead of time
    # c_first: In first iteration, solve for C first (fixed St)?

    mcrar.fit(D_known, C=C_guess, ST=St_guess, c_fix=[0,1], st_fix=[0], c_first=True)