#ifndef _NAMASTER_H_
#define _NAMASTER_H_

#ifndef NO_DOXY
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <time.h>
#include <complex.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_integration.h>
#include <fftw3.h>
#endif //NO_DOXY

#define NMT_MAX(a,b)  (((a)>(b)) ? (a) : (b)) // maximum
#define NMT_MIN(a,b)  (((a)<(b)) ? (a) : (b)) // minimum

#ifdef _SPREC
typedef float flouble;
typedef float complex fcomplex;
#else //_SPREC
typedef double flouble;
typedef double complex fcomplex;
#endif //_SPREC

/*! \mainpage NaMaster C API
 *
 * Welcome to the documentation of NaMaster's C API. Navigate through the tabs above to learn more
 * about the different functionality implemented in the code.
 *
 * \section general_notes General notes
 *   - Most users will prefer to use the python wrapper "pymaster", which mostly calls the
       C-based functions.
 *   - NaMaster uses a "row-major" order to define the ordering of power spectra into vectors.
       E.g. the cross-correlation of two spin-2 fields 'a' and 'b' would give rise to 4 power
       spectra: Ea-Eb, Ea-Bb, Ba-Eb and Ba-Bb. These are stored into 1-dimensional arrays using
       exactly that order. For the case of a spin-0 - spin-2 correlation, the ordering is
       [T-E, T-B], where T is the spin-0 field and (E,B) are the harmonic components of the
       spin-2 field.
 *   - The abbreviation MCM will often be used instead of "mode-coupling matrix".
 *   - SHT will sometimes be used for "Spherical Harmonic Transform". In the context of flat-sky
       fields, this should be understood as a standard Fast Fourier Transform (FFT) (with
       appropriate trigonometric factors if dealing with spin-2 fields).
 *   - FWHM will sometimes be used for "Full-width at half-max".
 *
 * \section more_info More info
 *
 * Please refer to the README and LICENSE files for further information on installation,
 * credits and licensing. Do not hesitate to contact the authors (preferably via github
 * issues on https://github.com/LSSTDESC/NaMaster) if you encounter any problems using
 * the code.
 */

/**
 * @brief Flat-sky bandpowers.
 *
 * This structure defines bandpowers for flat-sky power spectra.
 * These are currently defined only by band edges (assumed
 * flat weights within band).
 */
typedef struct {
  int n_bands; //!< Number of bandpowers stored
  flouble *ell_0_list; //!< Lower edge of each bandpower
  flouble *ell_f_list; //!< Upper edge of each bandpower
} nmt_binning_scheme_flat;

/**
 * @brief nmt_binning_scheme_flat constructor for constant bandpowers
 *
 * nmt_binning_scheme_flat constructor for bandpowers with
 * constant width \p nlb, from ell = 2 to ell = \p lmax.
 * @param nlb Constant band width
 * @param lmax Maximum multipole
 * @return Allocated binning structure.
 */
nmt_binning_scheme_flat *nmt_bins_flat_constant(int nlb,flouble lmax);

/**
 * @brief nmt_binning_scheme_flat generic constructor.
 *
 * @param nell Number of bandpowers
 * @param l0 Lower edge of all bandpowers (should be allocated to nell elements).
 * @param lf Lower edge of all bandpowers (should be allocated to nell elements).
 * @return Allocated binning structure.
 */
nmt_binning_scheme_flat *nmt_bins_flat_create(int nell,flouble *l0,flouble *lf);

/**
 * @brief nmt_binning_scheme_flat destructor
 */
void nmt_bins_flat_free(nmt_binning_scheme_flat *bin);

/**
 * @brief Returns average of input power spectrum into bandpowers.
 *
 * @param bin nmt_binning_scheme_flat structure defining the bandpowers.
 * @param nl Number of elements in the input power spectra.
 * @param larr Array containing the \p nl multipoles at which the input power
 *        spectrum is defined.
 * @param cls_in Array of \p ncls input power spectra.
 * @param cls_out Array of \p ncls averaged output power spectra.
 *        Should be allocated to the number of bandpowers defined \p bin.
 * @param ncls Number of input/output power spectra.
 */
void nmt_bin_cls_flat(nmt_binning_scheme_flat *bin,int nl,flouble *larr,flouble **cls_in,
		      flouble **cls_out,int ncls);

/**
 * @brief Returns binned power spectra interpolated into an given set of multipoles.
 *
 * Nearest-neighbours interpolation is used.
 * @param bin nmt_binning_scheme_flat structure defining the bandpowers.
 * @param cls_in Array of \p ncls input power spectra. Must have the same number of
 *        elements as bandpowers defined by \p bin.
 * @param nl Number of elements in the output power spectra.
 * @param larr Array containing the \p nl multipoles at which the output power
 *        spectrum are requested.
 * @param cls_out Array of \p ncls interpolated output power spectra.
 * @param ncls Number of input/output power spectra.
 */
void nmt_unbin_cls_flat(nmt_binning_scheme_flat *bin,flouble **cls_in,
			int nl,flouble *larr,flouble **cls_out,int ncls);

/**
 * @brief Returns effective multipoles.
 *
 * Returns the mid point of each bandpower defined in \p bin.
 * @param bin nmt_binning_scheme_flat structure defining the bandpowers.
 * @param larr Output array containing mid-points of the bandpowers.
 *        Should be preallocated to the correct number of bandpowers.
 */
void nmt_ell_eff_flat(nmt_binning_scheme_flat *bin,flouble *larr);

/**
 * @brief Fast bin-searching routine for flat-sky bandpowers
 *
 * Returns the bandpower index in which a given ell falls. The functions is designed
 * to be fast if a good guess for the bandpower index is supplied. A typical use would
 * be to iterate over ell values and pass, as a guess index, the index found in the
 * previous iteration.
 * @param bin nmt_binning_scheme_flat structure defining the bandpowers.
 * @param l Multipole for which you want the bandpower index.
 * @param il Guessed bandpower index.
 * @return Bandpower index.
 */
int nmt_bins_flat_search_fast(nmt_binning_scheme_flat *bin,flouble l,int il);

/**
 * @brief Full-sky bandpowers.
 *
 * This structure defines bandpowers for full-sky power spectra.
 * Although a given multipole ell can only contribute to one bandpower,
 * the distribution of ells per bandpower and their relative weights
 * is left completely free.
 */
typedef struct {
  int n_bands; //!< Number of bandpowers.
  int *nell_list; //!< Number of multipoles belonging to each bandpower.
  int **ell_list; //!< List of multipoles in each bandpowers.
  flouble **w_list; //!< List of weights associated to each multipole in \p ell_list.
  flouble **f_ell; //!< Multiplicative ell factor
  int ell_max; //!< Maximum multipole included.
} nmt_binning_scheme;

/**
 * @brief nmt_binning_scheme constructor for constant bandpowers.
 *
 * nmt_binning_scheme constructor for bandpowers with constant
 * width \p nlb, from ell = 2 to ell = \p lmax.
 * @param nlb Constant band width
 * @param lmax Maximum multipole
 * @param is_l2 If not zero, will assume l*(l+1)/2pi weighting
 * @return Allocated binning structure.
 */
nmt_binning_scheme *nmt_bins_constant(int nlb,int lmax,int is_l2);

/**
 * @brief  nmt_binning_scheme generic constructor.
 *
 * @param nell Number of elements in all subsequent arrays.
 * @param bpws Array of bandpower indices.
 * @param ells Array of multipole values. This function collects all multipoles
 *        into their associated bandpowers.
 * @param weights Array of weights associated to each multipole. Weights are
 *        normalized to 1 within each bandpower.
 * @param f_ell Array of ell-dependent prefactor (e.g. l*(l+1)/2pi is a typical choice).
 *        Pass NULL if you don't want any prefactor.
 *        normalized to 1 within each bandpower.
 * @param lmax Maximum multipole to consider.
 * @return Allocated binning structure.
 */
nmt_binning_scheme *nmt_bins_create(int nell,int *bpws,int *ells,flouble *weights,
				    flouble *f_ell,int lmax);

/**
 * @brief nmt_binning_scheme constructor from file
 *
 * Builds a nmt_binning_scheme structure from an ASCII file.
 * @param fname Path to file containing information to build bandpowers.
 *        The file should contain three columns, corresponding to:
 *        bandpower index, multipole and weight (in this order).
 *        See definition of nmt_bins_create().
 * @param lmax Maximum multipole to be considered.
 * @return Allocated binning structure.
 */
nmt_binning_scheme *nmt_bins_read(char *fname,int lmax);

/**
 * @brief nmt_binning_scheme destructor
 */
void nmt_bins_free(nmt_binning_scheme *bin);

/**
 * @brief Returns average of input power spectrum into bandpowers.
 *
 * @param bin nmt_binning_scheme structure defining the bandpowers.
 * @param cls_in Array of \p ncls input power spectra. They should be
 *        defined in all ells that go into any bandpower defined by \p bin.
 * @param cls_out Array of \p ncls averaged output power spectra.
 *        Should be allocated to the number of bandpowers defined \p bin.
 * @param ncls Number of input/output power spectra.
 */
void nmt_bin_cls(nmt_binning_scheme *bin,flouble **cls_in,flouble **cls_out,int ncls);

/**
 * @brief Returns binned power spectra interpolated into output multipoles.
 *
 * Top-hat interpolation is used (i.e. a given ell is associated with the binned power
 * spectrum value at the bandpower that ell corresponds to).
 * @param bin nmt_binning_scheme structure defining the bandpowers.
 * @param cls_in Array of \p ncls input power spectra. Must have the same number of
 *        elements as bandpowers defined by \p bin.
 * @param cls_out Array of \p ncls interpolated output power spectra.
 * @param ncls Number of input/output power spectra.
 */
void nmt_unbin_cls(nmt_binning_scheme *bin,flouble **cls_in,flouble **cls_out,int ncls);

/**
 * @brief Returns effective multipoles.
 *
 * Return the weighted average multipole values within each bandpower defined by \p bin.
 * @param bin nmt_binning_scheme structure defining the bandpowers.
 * @param larr Output array containing the effective multipole in each bandpower.
 *        Should be preallocated to the correct number of bandpowers.
 */
void nmt_ell_eff(nmt_binning_scheme *bin,flouble *larr);

/**
 * @brief Flat-sky Fourier-space function
 *
 * Unlike multipoles in harmonic space, in the case of full-sky operations,
 * wavenumbers k in Fourier space for flat-sky fields are in general continuous
 * variables. This structure helps define functions of these continuous variables.
 */
typedef struct {
  int is_const; //!< If >0, this function is just a constant
  flouble x0; //!< Lower edge of spline interpolation
  flouble xf; //!< Upper edge of spline interpolation
  flouble y0; //!< Function will take this value for x < \p x0
  flouble yf; //!< Function will take this value for x > \p xf
  gsl_spline *spl; //!< GSL spline interpolator.
} nmt_k_function;

/**
 * @brief nmt_k_function creator.
 *
 * @param nk Number of elements in input arrays.
 * @param karr k-values at which the input function is sampled.
 * @param farr Function values at k = \p karr.
 * @param y0 Constant function value below interpolation range.
 * @param yf Constant function value above interpolation range.
 * @param is_const If non-zero, will create a constant function.
 *        In this case all previous arguments other than \p y0 are ignored
 *        and the function will take this value for all k.
 */
nmt_k_function *nmt_k_function_alloc(int nk,flouble *karr,flouble *farr,
				     flouble y0,flouble yf,int is_const);

/**
 * @brief nmt_k_function destructor
 */
void nmt_k_function_free(nmt_k_function *f);

/**
 * @brief nmt_k_function evaluator.
 *
 * Returns value of function at \p k.
 * @param f nmt_k_function to evaluate.
 * @param k Value of k for which you want f(k).
 * @param intacc GSL interpolation accelerator. If you don't want any, just pass a NULL pointer.
 */
flouble nmt_k_function_eval(nmt_k_function *f,flouble k,gsl_interp_accel *intacc);

/**
 * @brief Flat-sky information.
 *
 * This structure contains all the information defining a given rectangular flat-sky patch.
 * The structure also contains information about the optimal way of sampling the Fourier
 * version of this patch into rings of |k|.
 */
typedef struct {
  int nx; //!< Number of grid points in the x dimension
  int ny; //!< Number of grid points in the y dimension
  long npix; //!< Total number of pixels (given by \p nx * \p ny
  flouble lx; //!< Length of the x dimension (in steradians)
  flouble ly; //!< Length of the y dimension (in steradians)
  flouble pixsize; //!< Pixel area (given by \p lx * \p ly / ( \p nx * \p ny))
  int n_ell; //!< Number of |k|-values for Fourier-space sampling.
  flouble dell; //!< Width of the Fourier-space rings. This is found as min(2 π / \p lx,2 π / \p ly).
  flouble i_dell; //!< 1 / \p dell
  flouble *ell_min; //!< Array of \p n_ell values containing the lower edges of each of the |k| rings.
  //  int *n_cells;
} nmt_flatsky_info;

/**
 * @brief nmt_flatsky_info constructor
 *
 * Builds nmt_flatsky_info from patch dimensions.
 * @param nx Number of grid points in the x dimension
 * @param ny Number of grid points in the y dimension
 * @param lx Length of the x dimension (in steradians)
 * @param ly Length of the y dimension (in steradians)
 * @return Allocated nmt_flatsky_info structure.
 */
nmt_flatsky_info *nmt_flatsky_info_alloc(int nx,int ny,flouble lx,flouble ly);

/**
 * @brief nmt_flatsky_info destructor.
 */
void nmt_flatsky_info_free(nmt_flatsky_info *fs);

/**
 * @brief Flat-sky field
 *
 * This structure contains all the information defining a spin-s flat-sky field.
 * This includes field values, masking, purification and contamination.
 */
typedef struct {
  nmt_flatsky_info *fs; //!< Structure defining patch geometry.
  long npix; //!< Number of pixels in all maps (also contained in \p fs).
  int pure_e; //!< >0 if E-modes have been purified.
  int pure_b; //!< >0 if B-modes have been purified.
  flouble *mask; //!< Field's mask (an array of \p npix values).
  fcomplex **a_mask; //!< Fourier transform of the mask. Only computed if E or B are purified.
  int spin; //!< field's spin (>=0).
  int nmaps; //!< Number of maps in the field (2 for spin-2, 1 for spin-0).
  flouble **maps; //!< Observed field values. When initialized, these maps are already multiplied by the mask, contaminant deprojected and purified if requested.
  fcomplex **alms; //!< Fourier-transfoms of the maps.
  int ntemp; //!< Number of contaminant templates
  flouble ***temp; //!< Contaminant template maps (mask-multiplied but NOT purified).
  fcomplex ***a_temp; //!< Fourier-transfomrs of template maps (mask-multiplied AND purified if requested).
  gsl_matrix *matrix_M; //!< Inverse contaminant covariance matrix (see scientific documentation or companion paper).
  nmt_k_function *beam; //!< Function defining a circularly-symmetric beam function. Power spectra will be beam-deconvolved.
  int lite; //!< lightweight field (no maps, temp, a_temp or a_mask)
  int mask_only; //!< this field only contains a mask, and beam. No alms, maps or anything else.
} nmt_field_flat;

/**
 * @brief nmt_field_flat destructor
 */
void nmt_field_flat_free(nmt_field_flat *fl);

/**
 * @brief nmt_field_flat constructor
 *
 * Builds an nmt_field_flat structure from input maps and patch parameters.
 * @param nx Number of grid points in the x dimension.
 * @param ny Number of grid points in the y dimension.
 * @param lx Length of the x dimension (in steradians).
 * @param ly Length of the y dimension (in steradians).
 * @param mask Field's mask (an array of \p nx * \p ny values).
 * @param spin Field's spin.
 * @param maps Observed field values BEFORE multiplying by the mask
          (this is irrelevant for binary masks).
 * @param ntemp Number of contaminant templates affecting this field.
 * @param temp Contaminant template maps (again, NOT multiplied by the mask).
 * @param nl_beam Number of multipole values defining this field's beam.
 * @param l_beam Multipole values at which this field's beam is defined.
 * @param beam Beam values at ell = \p l_beam. Pass a NULL pointer if you don't
          want any beam (\p nl_beam and \p l_beam will be ignored).
 * @param pure_e Set to >0 if you want purified E-modes.
 * @param pure_b Set to >0 if you want purified B-modes.
 * @param tol_pinv Contaminant deprojection requires the inversion of the template
          covariance matrix. This could be ill-defined if some templates are linearly
	  related. In this case we use a pseudo-inverse that accounts for this
	  possibility in a consistent way. Effectively this is a singular-value
	  decomposition. All eigenvalues that are smaller than \p tol_pinv the largest
	  eigenvalue will be discarded.
 * @param masked_input if not 0, input maps and templates have already been masked.
          This is not advisable if using purification.
 * @param is_lite if not 0, only the map alms and the mask will be stored. You can then
          use this field to compute the standard pseudo-C_ell with deprojection and purification,
          but you won't be able to compute the deprojection bias or examine any maps.
 * @param mask_only if not 0, this field will only store a mask and a beam. You will
          be able to use it to compute the PCL and covariance mode coupling matrices, but that's
          it (no actual power spectra, deprojection biases etc.).
 */
nmt_field_flat *nmt_field_flat_alloc(int nx,int ny,flouble lx,flouble ly,
				     flouble *mask,int spin,flouble **maps,int ntemp,flouble ***temp,
				     int nl_beam,flouble *l_beam,flouble *beam,
				     int pure_e,int pure_b,double tol_pinv,int masked_input,
                                     int is_lite,int mask_only);
/**
 * @brief Gaussian realizations of flat-sky fields
 *
 * Generates a Gaussian realization of an arbitrary list of possibly-correlated
 * fields with different spins.
 * @param nx Number of grid points in the x dimension.
 * @param ny Number of grid points in the y dimension.
 * @param lx Length of the x dimension (in steradians).
 * @param ly Length of the y dimension (in steradians).
 * @param nfields Number of fields to generate.
 * @param spin_arr Array (size \p nfields) containing the spins of the fields to be generated.
 * @param nl_beam Number of multipoles at which the field beams are defined.
 * @param l_beam Array of multipoles at which the field beams are defined.
 * @param beam_fields Array of beams (one per field).
 * @param nl_cell Number of multipole values at which the input power spectra are provided.
 * @param l_cell Array of multipole values at which the input power spectra are provided.
 * @param cell_fields Array of input power spectra. Shape should be [\p n_cls][\p nl_cell],
          where \p n_cls is the number of power spectra needed to define all the fields.
	  This should be \p n_cls = n_maps * (n_maps + 1) / 2, where n_maps is the total
	  number of maps required (1 for each spin-0 field, 2 for each spin-2 field). Power
	  spectra must be provided only for the upper-triangular part in row-major order
	  (e.g. if n_maps is 3, there will be 6 power spectra ordered as [1-1,1-2,1-3,2-2,2-3,3-3].
 * @param seed Seed for this particular realization.
 * @return Gaussian realization.
 */
flouble **nmt_synfast_flat(int nx,int ny,flouble lx,flouble ly,int nfields,int *spin_arr,
			   int nl_beam,flouble *l_beam,flouble **beam_fields,
			   int nl_cell,flouble *l_cell,flouble **cell_fields,
			   int seed);

/**
 * @brief E- or B-mode purifies a given pair of flat-sky (Q,U) maps.
 *
 * This function is mostly used internally by NaMaster, and its standalone use is discouraged.
 * @param fl nmt_field_flat containing information about what should be purified.
 * @param mask Sky mask (should be appropriately apodized - see scientific documentation).
 * @param walm0 Fourier transform of the mask.
 * @param maps_in Maps to be purified (should NOT be mask-multiplied).
 * @param maps_out Output purified maps.
 * @param alms Fourier transform of the output purified maps.
 */
void nmt_purify_flat(nmt_field_flat *fl,flouble *mask,fcomplex **walm0,
		     flouble **maps_in,flouble **maps_out,fcomplex **alms);

/**
 * @brief Curved-sky information.
 *
 * This structure contains all the information defining a given full-sky patch.
 * It describes either a HEALPix grid (in which case is_healpix!=0) or a CAR
 * patch (for is_healpix==0). If the latter, then the CAR pixelization must
 * conform to the Clenshaw-Curtis sampling. In this case the colatitude theta
 * must be sampled at N points going from 0 to pi (including both), separated
 * by an interval Dtheta = pi/(N-1). Not all iso-latitude rings must be stored
 * in the patch (i.e. ny!=N necessarily). See the documentation for
 * nmt_curvedsky_info_alloc for further information on the constraints that
 * some of the members of this structure must fulfill.
 */
typedef struct {
  int is_healpix; //!< is this HEALPix pixelization?
  long n_eq; //!< equivalent of nside, number of pixels in the equatorial ring
  int lmax_sht; //!< Maximum multipole to compute spherical harmonic transform
  int nx_short; //!< Number of grid points in the x dimension before completing the circle
  int nx; //!< Number of grid points in the phi dimension
  int ny; //!< Number of grid points in the theta dimension
  long npix; //!< Total number of pixels (given by \p nx * \p ny
  flouble Delta_theta; //!< pixel size in theta direction
  flouble Delta_phi; //!< pixel size in phi direction
  flouble phi0; // longitude of first pixel
  flouble theta0; // colatitude of last ring
} nmt_curvedsky_info;

/**
 * @brief Makes a copy of a nmt_curvedsky_info structure
 *
 * @param cs_in input structure to be copied.
 * @return copy of input nmt_curvedsky_info structure.
 */
nmt_curvedsky_info *nmt_curvedsky_info_copy(nmt_curvedsky_info *cs_in);

/**
 * @brief nmt_curvedsky_info creator
 *
 * If generating a Clenshaw-Curtis grid, then Dtheta and Dphi must be (close to)
 * exact divisor of pi and 2pi respectively. Likewise, theta0 must be an integer
 * multiple of Dtheta, and the number of pixels in the theta direction must be
 * such that the map actually fits on the sphere (i.e. theta0-(ny-1)*Dtheta >=0).
 * @param is_healpix is this HEALPix pixelization.
 * @param nside if is_healpix, this should be the HEALPix Nside parameter.
 * @param lmax_sht maximum multipole up to which spherical harmonic transforms will be computed.
 * @param nx0 number of pixels in the phi direction.
 * @param ny0 number of pixels in the theta direction.
 * @param Dtheta pixel size in the theta direction. In radians. Must be positive.
 * @param Dphi pixel size in the phi direction. In radians, must be positive.
 * @param theta0 colatitude of the last ring in the map. In radians.
 * @param phi0 minimum azimuth covered by the map. In radians.
 * @return nmt_curvedsky_info struct.
 */
nmt_curvedsky_info *nmt_curvedsky_info_alloc(int is_healpix,long nside,
					     int lmax_sht,
					     int nx0,int ny0,flouble Dtheta,flouble Dphi,
					     flouble phi0,flouble theta0);

/**
 * @brief Compare two nmt_curvedsky_info structs.
 *
 * @return true (!=0) if both structs are equivalent, and false (0) if they aren't.
 */
int nmt_diff_curvedsky_info(nmt_curvedsky_info *c1, nmt_curvedsky_info *c2);

/**
 * @brief Extend CAR map to cover the full circle.
 *
 * CAR maps only cover a particular part of the sky, but the SHT routines need as
 * input maps that are complete in the azimuth direction. This routine takes in
 * a raw CAR map with its corresponding nmt_curvedsky_info and returns the
 * phi-complete map (with zeros in all pixels outside the original map).
 * If the input map is in HEALPix, this routine just returns a copy of it.
 * @param cs curved sky geometry info.
 * @param map_in input incomplete map.
 * @return phi-complete map.
 */
flouble *nmt_extend_CAR_map(nmt_curvedsky_info *cs,flouble *map_in);

/**
 * @brief Full-sky field
 *
 * This structure contains all the information defining a spin-s full-sky field.
 * This includes field values, masking, purification and contamination.
 */
typedef struct {
  nmt_curvedsky_info *cs; //!< pixelization parameters
  long npix; //!< Number of pixels in all maps
  long nalms; //!< Number of complex harmonic coefficients
  int lmax; //!< Maximum multipole used
  int pure_e; //!< >0 if E-modes have been purified
  int pure_b; //!< >0 if B-modes have been purified
  flouble *mask; //!< Field's mask (an array of \p npix values).
  fcomplex **a_mask; //!< Spherical transform of the mask. Only computed if E or B are purified.
  int spin; //!< field's spin (>=0).
  int nmaps; //!< Number of maps in the field (2 for spin-2, 1 for spin-0).
  flouble **maps; //!< Observed field values. When initialized, these maps are already multiplied by the mask, contaminant-deprojected and purified if requested.
  fcomplex **alms; //!< Spherical harmonic transfoms of the maps.
  int ntemp; //!< Number of contaminant templates
  flouble ***temp; //!< Contaminant template maps (mask-multiplied but NOT purified).
  fcomplex ***a_temp; //!< Spherical harmonic transfomrs of template maps (mask-multiplied AND purified if requested).
  gsl_matrix *matrix_M; //!< Inverse contaminant covariance matrix (see scientific documentation or companion paper).
  flouble *beam; //!< Field's beam (defined on all multipoles up to \p lmax).
  int lite; //!< lightweight field (no maps, temp, a_temp or a_mask)
  int mask_only; //!< this field only contains a mask, and beam. No alms, maps or anything else.
} nmt_field;

/**
 * @brief nmt_field destructor.
 */
void nmt_field_free(nmt_field *fl);

/**
 * @brief nmt_field constructor
 *
 * Builds an nmt_field structure from input maps and resolution parameters.
 * @param cs curved sky geometry info.
 * @param mask Field's mask.
 * @param spin Field's spin.
 * @param maps Observed field values BEFORE multiplying by the mask
          (this is irrelevant for binary masks).
 * @param ntemp Number of contaminant templates affecting this field.
 * @param temp Contaminant template maps (again, NOT multiplied by the mask).
 * @param beam Harmonic coefficients of the beam (defined for all multipoles up to
 *        the maximum multipole sampled by the map). Pass a NULL pointer if you don't want any beam.
 * @param pure_e Set to >0 if you want purified E-modes.
 * @param pure_b Set to >0 if you want purified B-modes.
 * @param n_iter_mask_purify E/B purification requires a number of harmonic-space
          operations on an appropriately apodized mask. This parameter sets the
          number of iterations requested to compute the spherical harmonic transform
          of the field's mask. Higher values will produce more accurate results (at
	  the cost of computational time).
 * @param tol_pinv Contaminant deprojection requires the inversion of the template
          covariance matrix. This could be ill-defined if some templates are linearly
	  related. In this case we use a pseudo-inverse that accounts for this
	  possibility in a consistent way. Effectively this is a singular-value
	  decomposition. All eigenvalues that are smaller than \p tol_pinv the largest
	  eigenvalue will be discarded.
 * @param niter number of iterations when computing alms (for all transforms other than the mask's).
 * @param masked_input if not 0, input maps and templates have already been masked.
          This is not advisable if using purification.
 * @param is_lite if not 0, only the map alms and the mask will be stored. You can then
          use this field to compute the standard pseudo-C_ell with deprojection and purification,
          but you won't be able to compute the deprojection bias or examine any maps.
 * @param mask_only if not 0, this field will only store a mask and a beam. You will
          be able to use it to compute the PCL and covariance mode coupling matrices, but that's
          it (no actual power spectra, deprojection biases etc.).
 */
nmt_field *nmt_field_alloc_sph(nmt_curvedsky_info *cs,flouble *mask,int spin,flouble **maps,
			       int ntemp,flouble ***temp,flouble *beam,
			       int pure_e,int pure_b,int n_iter_mask_purify,double tol_pinv,
			       int niter,int masked_input,int is_lite,int mask_only);

/**
 * @brief nmt_field constructor from file.
 *
 * Builds an nmt_field structure from data written in files.
 * @param is_healpix is the map stored in healpix format?
 * @param fname_mask Path to FITS file containing the field's mask (single HEALPix map).
 * @param spin Field's spin.
 * @param fname_maps Path to FITS file containing the field's observed maps
          (1(2) maps if \p spin=0(!=0)).
 * @param fname_temp Path to FITS file containing the field's contaminant templates.
          If \p spin > 0, the file should contain an even number
          of files. Each consecutive pair of maps will be interpreted as the Q and U
	  components of a given contaminant. Pass "none" if you don't want any contaminants.
 * @param fname_beam Path to ASCII file containing the field's beam. The file should
          contain two columns: l (multipole) and b_l (beam SHT at that multipole).
	  Pass "none if you don't want a beam.
 * @param pure_e >0 if you want E-mode purification.
 * @param pure_b >0 if you want B-mode purification.
 * @param n_iter_mask_purify E/B purification requires a number of harmonic-space
          operations on an appropriately apodized mask. This parameter sets the
          number of iterations requested to compute the spherical harmonic transform
          of the field's mask. Higher values will produce more accurate results (at
	  the cost of computational time).
 * @param tol_pinv Contaminant deprojection requires the inversion of the template
          covariance matrix. This could be ill-defined if some templates are linearly
	  related. In this case we use a pseudo-inverse that accounts for this
	  possibility in a consistent way. Effectively this is a singular-value
	  decomposition. All eigenvalues that are smaller than \p tol_pinv the largest
	  eigenvalue will be discarded.
 * @param niter number of iterations when computing alms (other than the mask's).
 */
nmt_field *nmt_field_read(int is_healpix,char *fname_mask,char *fname_maps,char *fname_temp,
			  char *fname_beam,int spin,int pure_e,int pure_b,
			  int n_iter_mask_purify,double tol_pinv,int niter);

/**
 * @brief Gaussian realizations of full-sky fields
 *
 * Generates a Gaussian realization of an arbitrary list of possibly-correlated fields with different spins.
 * @param cs curved sky geometry info.
 * @param lmax Maximum multipole used.
 * @param nfields Number of fields to generate.
 * @param spin_arr Array (size \p nfields) containing the spins of the fields to be generated.
 * @param beam_fields Array of beams (one per field). Must be defined at all ell <= \p lmax.
 * @param cells Array of input power spectra (defined at all ell <= \p lmax). Shape
          should be [\p n_cls][\p lmax+1], where \p n_cls is the number of power spectra
	  needed to define all the fields. This should be \p n_cls = n_maps * (n_maps + 1) / 2,
	  where n_maps is the total number of maps required (1 for each spin-0 field, 2 for
	  each spin-2 field). Power spectra must be provided only for the upper-triangular part
	  in row-major order (e.g. if n_maps is 3, there will be 6 power spectra ordered as
	  [1-1,1-2,1-3,2-2,2-3,3-3].
 * @param seed Seed for this particular realization.
 * @return Gaussian realization.
 */
flouble **nmt_synfast_sph(nmt_curvedsky_info *cs,int nfields,int *spin_arr,int lmax,
			  flouble **cells,flouble **beam_fields,int seed);

/**
 * @brief E- or B-mode purifies a given pair of full-sky (Q,U) maps.
 *
 * This function is mostly used internally by NaMaster, and its standalone use is discouraged.
 * @param fl nmt_field containing information about what should be purified.
 * @param mask Sky mask (should be appropriately apodized - see scientific documentation).
 * @param walm0 Spherical harmonic transform of the mask.
 * @param maps_in Maps to be purified (should NOT be mask-multiplied).
 * @param maps_out Output purified maps.
 * @param alms Spherical harmonic transform of the output purified maps.
 * @param niter number of iterations when computing alms.
 */
void nmt_purify(nmt_field *fl,flouble *mask,fcomplex **walm0,
		flouble **maps_in,flouble **maps_out,fcomplex **alms,int niter);

/**
 * @brief Apodize full-sky mask.
 *
 * Produces apodized version of a full-sky mask for a number of apodization schemes.
 * @param nside HEALPix resolution parameter.
 * @param mask_in Input mask to be apodized.
 * @param mask_out Output apodized mask.
 * @param aposize Apodization scale (in degrees).
 * @param apotype String defining the apodization procedure. Three values allowed: 'C1', 'C2' and 'Smooth'. These correspond to:
 *    - \p apotype = "C1". All pixels are multiplied by a factor \f$f\f$, given by:
 *\f[
 *   f=\left\{
 *     \begin{array}{cc}
 *       x-\sin(2\pi x)/(2\pi) & x<1\\
 *       1 & {\rm otherwise}
 *     \end{array}
 *     \right.,
 * \f]
        where \f$x=\sqrt{(1-\cos\theta)/(1-\cos(\theta_*))}\f$, \f$\theta_*\f$ is the
	apodization scale and \f$\theta\f$ is the angular separation between a pixel and
	the nearest masked pixel (i.e. where the mask takes a zero value).
 *    - \p apotype = "C2". The same as the C1 case, but the function in this case is:
 *\f[
 *   f=\left\{
 *     \begin{array}{cc}
 *       \frac{1}{2}\left[1-\cos(\pi x)\right] & x<1\\
 *       1 & {\rm otherwise}
 *     \end{array}
 *     \right.,
 * \f]
 *    - \p apotype = "Smooth". This apodization is carried out in three steps:
 *         -# All pixels within a disc of radius \f$2.5\theta_*\f$ of a masked pixel are masked.
 *         -# The resulting map is smooth with a Gaussian window function with standard
              deviation \f$\sigma=\theta_*\f$.
 *         -# One final pass is made through all pixels to ensure that all originally masked
 *            pixels are still masked after the smoothing operation.
 */
void nmt_apodize_mask(long nside,flouble *mask_in,flouble *mask_out,flouble aposize,char *apotype);

/**
 * @brief Apodize flat-sky mask.
 *
 * Produces apodized version of a flat-sky mask for a number of apodization schemes.
 * @param nx Number of grid points in the x dimension
 * @param ny Number of grid points in the y dimension
 * @param lx Length of the x dimension (in steradians)
 * @param ly Length of the y dimension (in steradians)
 * @param mask_in Input mask to be apodized.
 * @param mask_out Output apodized mask.
 * @param aposize Apodization scale (in degrees).
 * @param apotype String defining the apodization procedure. See definitions of nmt_apodize_mask().
 */
void nmt_apodize_mask_flat(int nx,int ny,flouble lx,flouble ly,
			   flouble *mask_in,flouble *mask_out,flouble aposize,char *apotype);

/**
 * @brief Flat-sky mode-coupling matrix.
 *
 * Structure containing information about the mode-coupling matrix (MCM) for flat-sky pseudo-CLs.
 */
typedef struct {
  int ncls; //!< Number of power spectra (1, 2 or 4 depending of the spins of the fields being correlated.
  flouble ellcut_x[2]; //!< Range of ells in the x direction to be masked in Fourie space
  flouble ellcut_y[2]; //!< Range of ells in the y direction to be masked in Fourie space
  int pe1; //!< Is the E-mode component of the first field purified?
  int pe2; //!< Is the E-mode component of the second field purified?
  int pb1; //!< Is the B-mode component of the first field purified?
  int pb2; //!< Is the B-mode component of the second field purified?
  nmt_flatsky_info *fs; //!< Contains information about rectangular flat-sky patch.
  int is_teb; //!< Does it hold all MCM elements to compute all of spin0-spin0, 0-2 and 2-2 correlations?
  int *n_cells; //!< Number of unmasked Fourier-space grid points contributing to a given bandpower
  flouble **coupling_matrix_unbinned; //!< Unbinned MCM
  flouble **coupling_matrix_binned; //!< Binned MCM
  nmt_binning_scheme_flat *bin; //!< Bandpowers defining the binning
  flouble lmax; //!< Maximum k-mode used
  gsl_matrix *coupling_matrix_binned_gsl; //!< GSL version of MCM (prepared for inversion)
  gsl_permutation *coupling_matrix_perm; //!< Complements \p coupling_matrix_binned_gsl for inversion.
} nmt_workspace_flat;

/**
 * @brief nmt_workspace_flat destructor
 */
void nmt_workspace_flat_free(nmt_workspace_flat *w);

/**
 * @brief Computes mode-coupling matrix.
 *
 * Computes MCM for a given pair of flat-sky fields.
 * @param fl1 nmt_field_flat structure defining the first field to correlate.
 * @param fl2 nmt_field_flat structure defining the second field to correlate.
 * @param bin nmt_binning_scheme_flat defining the power spectrum bandpowers.
 * @param lmn_x Lower end of the range of multipoles in the x direction that should be masked.
 * @param lmx_x Upper end of the range of multipoles in the x direction that should be masked.
 *        if \p lmx_x < \p lmn_x, no Fourier-space masked is performed.
 * @param lmn_y Same as \p lmn_x for the y direction.
 * @param lmx_y Same as \p lmx_x for the y direction.
 * @param is_teb if !=0, all mode-coupling matrices (0-0,0-2,2-2) will be computed at the same time.
 */
nmt_workspace_flat *nmt_compute_coupling_matrix_flat(nmt_field_flat *fl1,nmt_field_flat *fl2,
						     nmt_binning_scheme_flat *bin,
						     flouble lmn_x,flouble lmx_x,
						     flouble lmn_y,flouble lmx_y,int is_teb);

/**
 * @brief Computes deprojection bias.
 *
 * Computes contaminant deprojection bias for a pair of fields.
 * See notes about power spectrum ordering in the main page of this documentation.
 * @param fl1 nmt_field_flat structure defining the first field to correlate.
 * @param fl2 nmt_field_flat structure defining the second field to correlate.
 * @param bin nmt_binning_scheme_flat defining the power spectrum bandpowers.
 * @param lmn_x Lower end of the range of multipoles in the x direction that should be masked.
 * @param lmx_x Upper end of the range of multipoles in the x direction that should be masked.
 *        if \p lmx_x < \p lmn_x, no Fourier-space masked is performed.
 * @param lmn_y Same as \p lmn_x for the y direction.
 * @param lmx_y Same as \p lmx_x for the y direction.
 * @param nl_prop Number of multipoles over which the proposed power spectrum is defined.
 * @param l_prop Array of multipoles over which the proposed power spectrum is defined.
 * @param cl_proposal Proposed power spectrum. Should have shape [ncls][\p nl_prop], where
          \p ncls is the appropriate number of power spectra given the spins of the input
	  fields (e.g. \p ncls = 2*2 = 4 if both fields have spin=2).
 * @param cl_bias Ouptput deprojection bias. Should be allocated to shape [ncls][nbpw],
          where \p ncls is defined above and \p nbpw is the number of bandpowers
	  defined by \p bin.
 */
void nmt_compute_deprojection_bias_flat(nmt_field_flat *fl1,nmt_field_flat *fl2,
					nmt_binning_scheme_flat *bin,
					flouble lmn_x,flouble lmx_x,flouble lmn_y,flouble lmx_y,
					int nl_prop,flouble *l_prop,flouble **cl_proposal,
					flouble **cl_bias);

/**
 * @brief Mode-couples an input power spectrum
 *
 * This function applies the effects of the mode-coupling the pseudo-CL estimator for a given
 * input power spectrum. This function should be used in conjunction with nmt_decouple_cl_l_flat()
 * to compute the theory prediction of the pseudo-CL estimator. See the scientific documentation
 * or the companion paper for further details on how this is done in particular for the flat-sky
 * approximation.
 * See notes about power spectrum ordering in the main page of this documentation.
 * @param w nmt_workspace_flat structure containing the mode-coupling matrix
 * @param nl Number of multipoles on which the input power spectrum is defined.
 * @param larr Array of multipoles on which the input power spectrum is defined.
 * @param cl_in Array of input power spectra. Should have shape [ncls][nl], where ncls is the
          appropriate number of power spectra given the fields being correlated (e.g. ncls=4=2*2
	  for two spin-2 fields.
 * @param cl_out Array of output power spectra. Should have shape [ncls][nbpw], where ncls is
          defined above and nbpw is the number of bandpowers used to define \p w.
 */
void nmt_couple_cl_l_flat_fast(nmt_workspace_flat *w,int nl,flouble *larr,flouble **cl_in,
				 flouble **cl_out);
/**
 * @brief Mode-couples an input power spectrum
 *
 * Faster (but less accurate) version of nmt_couple_cl_l_flat_fast().
 * @param w nmt_workspace_flat structure containing the mode-coupling matrix
 * @param nl Number of multipoles on which the input power spectrum is defined.
 * @param larr Array of multipoles on which the input power spectrum is defined.
 * @param cl_in Array of input power spectra. Should have shape [ncls][nl], where ncls is the
          appropriate number of power spectra given the fields being correlated (e.g. ncls=4=2*2
	  for two spin-2 fields.
 * @param cl_out Array of output power spectra. Should have shape [ncls][nbpw], where ncls is
          defined above and nbpw is the number of bandpowers used to define \p w.
 */
void nmt_couple_cl_l_flat_quick(nmt_workspace_flat *w,int nl,flouble *larr,flouble **cl_in,
				flouble **cl_out);

/**
 * @brief Inverts mode-coupling matrix
 *
 * Multiplies coupled power spectra by inverse mode-coupling matrix.
 * See notes about power spectrum ordering in the main page of this documentation.
 * @param w nmt_workspace_flat containing the mode-coupling matrix.
 * @param cl_in Input coupled power spectra. Should have shape [ncls][nbpw], where
          \p ncls is the appropriate number of power spectra given the fields used
	  to define \p w (e.g. 4=2*2 for two spin-2 fields) and \p nbpw is the number
	  of bandpowers used when defining \p w.
 * @param cl_noise_in Noise bias (same shape as \p cl_in).
 * @param cl_bias Deprojection bias (same shape as \p cl_in, see nmt_compute_deprojection_bias_flat()).
 * @param cl_out Mode-decoupled power spectrum (same shape as \p cl_in).
 */
void nmt_decouple_cl_l_flat(nmt_workspace_flat *w,flouble **cl_in,flouble **cl_noise_in,
			    flouble **cl_bias,flouble **cl_out);

/**
 * @brief Coupled pseudo-CL
 *
 * Computes the pseudo-CL power spectrum of two fields without accounting for the mode-coupling
 * matrix.
 * See notes about power spectrum ordering in the main page of this documentation.
 * @param fl1 nmt_field_flat structure defining the first field to correlate.
 * @param fl2 nmt_field_flat structure defining the second field to correlate.
 * @param bin nmt_binning_scheme_flat defining the power spectrum bandpowers.
 * @param lmn_x Lower end of the range of multipoles in the x direction that should be masked.
 * @param lmx_x Upper end of the range of multipoles in the x direction that should be masked.
 *        if \p lmx_x < \p lmn_x, no Fourier-space masked is performed.
 * @param lmn_y Same as \p lmn_x for the y direction.
 * @param lmx_y Same as \p lmx_x for the y direction.
 * @param cl_out Ouptput power spectrum. Should be allocated to shape [ncls][nbpw], where
          \p ncls is the appropriate number of power spectra (e.g. 4=2*2 for two spin-2
	  fields), and \p nbpw is the number of bandpowers defined by \p bin.
 */
void nmt_compute_coupled_cell_flat(nmt_field_flat *fl1,nmt_field_flat *fl2,
				   nmt_binning_scheme_flat *bin,flouble **cl_out,
				   flouble lmn_x,flouble lmx_x,flouble lmn_y,flouble lmx_y);

/**
 * @brief Computes pseudo-CL specrum.
 *
 * Wrapper function containing all the steps to compute a power spectrum. For performance
 * reasons, the blind use of this function is discouraged against a smarter combination of
 * nmt_workspace_flat structures and nmt_compute_coupled_cell_flat().
 * See notes about power spectrum ordering in the main page of this documentation.
 * @param fl1 nmt_field_flat structure defining the first field to correlate.
 * @param fl2 nmt_field_flat structure defining the second field to correlate.
 * @param bin nmt_binning_scheme_flat defining the power spectrum bandpowers.
 * @param lmn_x Lower end of the range of multipoles in the x direction that should be masked.
 * @param lmx_x Upper end of the range of multipoles in the x direction that should be masked.
 *        if \p lmx_x < \p lmn_x, no Fourier-space masked is performed.
 * @param lmn_y Same as \p lmn_x for the y direction.
 * @param lmx_y Same as \p lmx_x for the y direction.
 * @param w0 nmt_workspace_flat structure containing the mode-coupling matrix. If NULL, a new
          computation of the MCM will be carried out and stored in the output nmt_workspace_flat.
	  Otherwise, \p w0 will be used and returned by this function.
 * @param nl_prop Number of multipoles over which the proposed power spectrum is defined.
 * @param l_prop Array of multipoles over which the proposed power spectrum is defined.
 * @param cl_prop Proposed power spectrum. Should have shape [ncls][\p nl_prop], where
          \p ncls is the appropriate number of power spectra given the spins of the input
	  fields (e.g. \p ncls = 2*2 = 4 if both fields have spin=2).
 * @param cl_noise Noise bias. Should have shape [ncls][nbpw], where \p ncls is
 *        defined above and \p nbpw is the number of bandpowers defined by \p bin.
 * @param cl_out Ouptput power spectrum. Should be allocated to shape [ncls][nbpw],
          where \p ncls is defined above and \p nbpw is the number of bandpowers defined
	  by \p bin.
 * @return Newly allocated nmt_workspace_flat structure containing the mode-coupling matrix
           if \p w0 is NULL (will return \p w0 otherwise).
 */
nmt_workspace_flat *nmt_compute_power_spectra_flat(nmt_field_flat *fl1,nmt_field_flat *fl2,
						   nmt_binning_scheme_flat *bin,
						   flouble lmn_x,flouble lmx_x,
						   flouble lmn_y,flouble lmx_y,
						   nmt_workspace_flat *w0,flouble **cl_noise,
						   int nl_prop,flouble *l_prop,flouble **cl_prop,
						   flouble **cl_out);

/**
 * @brief Full-sky mode-coupling matrix.
 *
 * Structure containing information about the mode-coupling matrix (MCM) for full-sky pseudo-CLs.
 */
typedef struct {
  int lmax; //!< Maximum multipole used
  int lmax_fields; //!< Resolution of fields being correlated.
  int lmax_mask; //!< Mask resolution
  int is_teb; //!< Does it hold all MCM elements to compute all of spin0-spin0, 0-2 and 2-2 correlations?
  int ncls; //!< Number of power spectra (1, 2 or 4 depending of the spins of the fields being correlated.
  nmt_curvedsky_info *cs; //!< curved sky geometry information.
  flouble *beam_prod; //!< Product of field beams.
  flouble *pcl_masks; //!< Pseudo-CL of the masks.
  flouble **coupling_matrix_unbinned; //!< Unbinned mode-coupling matrix
  nmt_binning_scheme *bin; //!< Bandpowers defining the binning
  gsl_matrix *coupling_matrix_binned; //!< GSL version of MCM (prepared for inversion)
  gsl_permutation *coupling_matrix_perm; //!< Complements \p coupling_matrix_binned_gsl for inversion.
} nmt_workspace;

typedef struct {
  int lmax;
  int lmax_mask;
  int npcl;
  int s1;
  int s2;
  int has_00;
  flouble ***xi_00;
  int has_0s;
  flouble ****xi_0s;
  int has_ss;
  flouble ****xi_pp;
  flouble ****xi_mm;
  int pure_e1;
  int pure_e2;
  int pure_b1;
  int pure_b2;
  int pure_any;
  int npure_0s;
  int npure_ss;
} nmt_master_calculator;

nmt_master_calculator *nmt_compute_master_coefficients(int lmax, int lmax_mask,
                                                       int npcl, flouble **pcl_masks,
                                                       int s1, int s2,
                                                       int pure_e1, int pure_b1,
                                                       int pure_e2, int pure_b2,
                                                       int do_teb, int l_toeplitz,
                                                       int l_exact, int dl_band);
void nmt_master_calculator_free(nmt_master_calculator *c);

/**
 * @brief Computes mode-coupling matrix.
 *
 * Computes MCM for a given pair of full-sky fields.
 * @param fl1 nmt_field structure defining the first field to correlate.
 * @param fl2 nmt_field structure defining the second field to correlate.
 * @param bin nmt_binning_scheme defining the power spectrum bandpowers.
 * @param is_teb if !=0, all mode-coupling matrices (0-0,0-2,2-2) will be computed at the same time.
 * @param niter number of iterations when computing alms.
 * @param lmax_mask maximum multipole to which the masks should be resolved. If smaller than the maximum multipole of fl1/fl2, it will be set to that.
 * @return Newly allocated nmt_workspace structure containing the mode-coupling matrix.
 */
nmt_workspace *nmt_compute_coupling_matrix(nmt_field *fl1,nmt_field *fl2,nmt_binning_scheme *bin,
					   int is_teb,int niter,int lmax_mask,
                                           int l_toeplitz,int l_exact,int dl_band);

/**
 * @brief Updates the mode coupling matrix with a new one.Saves nmt_workspace structure to file
 *
 * The new matrix must be provided as a single 1D array of size n_rows\f$^2\f$.
 * Here n_rows=n_cls * n_ell is the size of the flattened power spectra, where n_cls is the number
 * of power spectra (1, 2 or 4 for spin0-0, spin0-2 and spin2-2 correlations) and n_ells=lmax+1
 * (by default lmax=3*nside-1 for HEALPix, and pi/dx for CAR (where dx is the minimum angular pixel size)). The ordering of the power spectra should be such that the
 * l-th element of the i-th power spectrum is stored with index l * n_cls + i.
 * @param w nmt_workspace to be updated.
 * @param n_rows size of the flattened power spectra.
 * @param new_matrix new mode-coupling matrix (flattened).
 */
void nmt_update_coupling_matrix(nmt_workspace *w,int n_rows,double *new_matrix);

/**
 * @brief Updates the binning scheme associated to this workspace.
 *
 * Also rebins the MCM and re-inverts it.
 * @param w nmt_workspace to be updated.
 * @param bin new nmt_binning_scheme.
 */
void nmt_workspace_update_binning(nmt_workspace *w,
				  nmt_binning_scheme *bin);

/**
 * @brief Updates the beams associated to this workspace.
 *
 * Also recomputes the binned MCM and its inverse
 * @param w workspace.
 * @param nl1 Number of elements of b1.
 * @param b1 First field's beam (harmonic space). One element per multipole.
 * @param nl2 Number of elements of b1.
 * @param b2 Second field's beam (harmonic space). One element per multipole.
 */
void nmt_workspace_update_beams(nmt_workspace *w,
				int nl1,double *b1,
				int nl2,double *b2);

/**
 * @brief nmt_workspace destructor
 */
void nmt_workspace_free(nmt_workspace *w);

/**
 * @brief Computes deprojection bias.
 *
 * Computes contaminant deprojection bias for a pair of fields.
 * See notes about power spectrum ordering in the main page of this documentation.
 * @param fl1 nmt_field structure defining the first field to correlate.
 * @param fl2 nmt_field structure defining the second field to correlate.
 * @param cl_proposal Proposed power spectrum. Should have shape [ncls][lmax+1], where
          \p ncls is the appropriate number of power spectra given the spins of the input
	  fields (e.g. \p ncls = 2*2 = 4 if both fields have spin=2).
 * @param cl_bias Ouptput deprojection bias. Should be allocated to shape [ncls][lmax+1],
          where \p ncls is defined above.
 * @param niter number of iterations when computing alms.
 */
void nmt_compute_deprojection_bias(nmt_field *fl1,nmt_field *fl2,
				   flouble **cl_proposal,flouble **cl_bias,int niter);

/**
 * @brief Noise bias from uncorrelated noise map
 *
 * Computes deprojection bias due to an source of uncorrelated noise given an input noise variance map.
 * See companion paper for more details.
 * @param fl1 nmt_field structure defining the properties of the field for which this noise bias
          applies.
 * @param map_var Noise variance map (should contain per-pixel noise variance).
 * @param cl_bias Ouptput noise bias. Should be allocated to shape [ncls][lmax+1],
          where \p ncls is the appropriate number of power spectra given the spins of the input
	  fields (e.g. \p ncls = 2*2 = 4 if both fields have spin=2).
 * @param niter number of iterations when computing alms.
 */
void nmt_compute_uncorr_noise_deprojection_bias(nmt_field *fl1,flouble *map_var,flouble **cl_bias,
						int niter);

/**
 * @brief Mode-couples an input power spectrum
 *
 * This function applies the effects of the mode-coupling the pseudo-CL estimator for a given
 * input power spectrum. This function should be used in conjunction with nmt_decouple_cl_l()
 * to compute the theory prediction of the pseudo-CL estimator.
 * See notes about power spectrum ordering in the main page of this documentation.
 * @param w nmt_workspace structure containing the mode-coupling matrix
 * @param cl_in Array of input power spectra. Should have shape [ncls][lmax+1], where ncls
          is the appropriate number of power spectra given the fields being correlated
	  (e.g. ncls=4=2*2 for two spin-2 fields).
 * @param cl_out Array of output power spectra. Should have shape [ncls][lmax+1], where
          ncls is defined above.
 */
void nmt_couple_cl_l(nmt_workspace *w,flouble **cl_in,flouble **cl_out);

/**
 * @brief Inverts mode-coupling matrix
 *
 * Multiplies coupled power spectra by inverse mode-coupling matrix.
 * See notes about power spectrum ordering in the main page of this documentation.
 * @param w nmt_workspace containing the mode-coupling matrix.
 * @param cl_in Input coupled power spectra. Should have shape [ncls][lmax+1], where
          \p ncls is the appropriate number of power spectra given the fields used
	  to define \p w (e.g. 4=2*2 for two spin-2 fields).
 * @param cl_noise_in Noise bias (same shape as \p cl_in).
 * @param cl_bias Deprojection bias (same shape as \p cl_in, see nmt_compute_deprojection_bias()).
 * @param cl_out Mode-decoupled power spectrum. Should have shape [ncls][nbpw], where
          ncls is defined above and nbpw is the number of bandpowers used to define \p w.
 */
void nmt_decouple_cl_l(nmt_workspace *w,flouble **cl_in,flouble **cl_noise_in,
		       flouble **cl_bias,flouble **cl_out);

/**
 * @brief Returns the bandpower window functions for this workspace.
 *
 * This function returns, in a flattened array, the bandpower window functions associated with the
 * mode-coupling matrix stored in this workspace. The effect of the PCL estimator on the unbinned
 * theory prediction can be fully accounted for by convolving it with these window functions.
 * @param w nmt_workspace containing the mode-coupling matrix.
 * @param bpw_win_out output 1D array allocated to the right size (n_cls * n_bpw * n_cls * (lmax+1)).
 */
void nmt_compute_bandpower_windows(nmt_workspace *w,double *bpw_win_out);

/**
 * @brief Coupled pseudo-CL
 *
 * Computes the pseudo-CL power spectrum of two fields without accounting for the mode-coupling
 * matrix. This is essentially equivalent to running HEALPix's 'anafast' on the purified and
 * contaminant-deprojected input fields.
 * See notes about power spectrum ordering in the main page of this documentation.
 * @param fl1 nmt_field structure defining the first field to correlate.
 * @param fl2 nmt_field structure defining the second field to correlate.
 * @param cl_out Ouptput power spectrum. Should be allocated to shape [ncls][lmax+1], where
          \p ncls is the appropriate number of power spectra (e.g. 4=2*2 for two spin-2 fields).
 */
void nmt_compute_coupled_cell(nmt_field *fl1,nmt_field *fl2,flouble **cl_out);

/**
 * @brief Computes pseudo-CL specrum.
 *
 * Wrapper function containing all the steps to compute a power spectrum. For performance
 * reasons, the blind use of this function is discouraged against a smarter combination of
 * nmt_workspace structures and nmt_compute_coupled_cell().
 * See notes about power spectrum ordering in the main page of this documentation.
 * @param fl1 nmt_field structure defining the first field to correlate.
 * @param fl2 nmt_field structure defining the second field to correlate.
 * @param bin nmt_binning_scheme defining the power spectrum bandpowers.
 * @param w0 nmt_workspace structure containing the mode-coupling matrix. If NULL, a new
          computation of the MCM will be carried out and stored in the output nmt_workspace.
	  Otherwise, \p w0 will be used and returned by this function.
 * @param cl_proposal Proposed power spectrum. Should have shape [ncls][lmax+1], where
          \p ncls is the appropriate number of power spectra given the spins of the input
	  fields (e.g. \p ncls = 2*2 = 4 if both fields have spin=2).
 * @param cl_noise Noise bias (same shape as \p cl_prop).
 * @param cl_out Ouptput power spectrum. Should be allocated to shape [ncls][nbpw],
          where \p ncls is defined above and \p nbpw is the number of bandpowers defined
	  by \p bin.
 * @param niter number of iterations when computing alms.
 * @param lmax_mask maximum multipole to which the masks should be resolved. If smaller than the maximum multipole of fl1/fl2, it will be set to that.
 * @return Newly allocated nmt_workspace structure containing the mode-coupling matrix
           if \p w0 is NULL (will return \p w0 otherwise).
 */
nmt_workspace *nmt_compute_power_spectra(nmt_field *fl1,nmt_field *fl2,
					 nmt_binning_scheme *bin,nmt_workspace *w0,
					 flouble **cl_noise,flouble **cl_proposal,flouble **cl_out,
					 int niter,int lmax_mask,int l_toeplitz,
                                         int l_exact,int dl_band);

/**
 * @brief Flat-sky Gaussian covariance matrix
 *
 * Structure containing the information necessary to compute Gaussian covariance matrices
 * for the pseudo-CL spectra of two flat-sky spin-0 fields.
 *
 */
typedef struct {
  nmt_binning_scheme_flat *bin; //!< Bandpowers defining the binning
  flouble **xi00_1122; //!< First (a1b1-a2b2), 00, mode coupling matrix (see scientific documentation)
  flouble **xi00_1221; //!< Second (a1b2-a2b1), 00, mode coupling matrix (see scientific documentation)
  flouble **xi02_1122; //!< First (a1b1-a2b2), 02, mode coupling matrix (see scientific documentation)
  flouble **xi02_1221; //!< Second (a1b2-a2b1), 02, mode coupling matrix (see scientific documentation)
  flouble **xi22p_1122; //!< First (a1b1-a2b2), 22p, mode coupling matrix (see scientific documentation)
  flouble **xi22p_1221; //!< Second (a1b2-a2b1), 22p, mode coupling matrix (see scientific documentation)
  flouble **xi22m_1122; //!< First (a1b1-a2b2), 22m, mode coupling matrix (see scientific documentation)
  flouble **xi22m_1221; //!< Second (a1b2-a2b1), 22m, mode coupling matrix (see scientific documentation)
} nmt_covar_workspace_flat;

/**
 * @brief nmt_covar_workspace_flat destructor.
 */
void nmt_covar_workspace_flat_free(nmt_covar_workspace_flat *cw);

/**
 * @brief nmt_covar_workspace_flat constructor
 *
 * Builds an nmt_covar_workspace_flat structure from two nmt_workspace_flat structures, corresponding
 * to the two sets of power spectra for which the covariance is required.
 * @param fla1 nmt_field_field for the first field going into the first (a-th) power spectrum.
 * @param fla2 nmt_field_field for the second field going into the first (a-th) power spectrum.
 * @param flb1 nmt_field_field for the first field going into the second (b-th) power spectrum.
 * @param flb2 nmt_field_field for the second field going into the second (b-th) power spectrum.
 * @param ba nmt_binning_scheme_flat used for the first power spectrum.
 * @param bb nmt_binning_scheme_flat used for the second power spectrum.
 */
nmt_covar_workspace_flat *nmt_covar_workspace_flat_init(nmt_field_flat *fla1,nmt_field_flat *fla2,
							nmt_binning_scheme_flat *ba,
							nmt_field_flat *flb1,nmt_field_flat *flb2,
							nmt_binning_scheme_flat *bb);

/**
 * @brief Compute flat-sky Gaussian covariance matrix
 *
 * Computes the covariance matrix for two sets of power spectra given input predicted spectra
 * and two nmt_covar_workspace_flat structures.
 * @param cw nmt_covar_workspace_flat structure containing the information necessary to compute the
          covariance matrix.
 * @param spin_a field a spin.
 * @param spin_b field b spin.
 * @param spin_c field c spin.
 * @param spin_d field d spin.
 * @param wa nmt_workspace_flat structure containing the mode-coupling matrix for the first power spectra (between fields a and b).
 * @param wb nmt_workspace_flat structure containing the mode-coupling matrix for the second power spectra (between fields c and d).
 * @param nl Number of multipoles in which input power spectra are computed.
 * @param larr Array of multipoles in which input power spectra are computed.
 * @param clac Cross-power spectra between field 1 in the first set and field 1 in the second set (ac)
 * @param clad Cross-power spectra between field 1 in the first set and field 2 in the second set (ad)
 * @param clbc Cross-power spectra between field 2 in the first set and field 1 in the second set (bc)
 * @param clbd Cross-power spectra between field 2 in the first set and field 2 in the second set (bd)
 * @param covar_out flattened covariance matrix. Should be allocated to shape [ncls_1 * nbpw_1 * ncls_2 * nbpw_2],
          where nbpw_X and ncls_X are the number of bandpowers and different power spectra in the X-th set of fields.
 */
void nmt_compute_gaussian_covariance_flat(nmt_covar_workspace_flat *cw,
					  int spin_a,int spin_b,int spin_c,int spin_d,
					  nmt_workspace_flat *wa,nmt_workspace_flat *wb,
					  int nl,flouble *larr,
					  flouble **clac,flouble **clad,
					  flouble **clbc,flouble **clbd,flouble *covar_out);

/**
 * @brief Full-sky Gaussian covariance matrix
 *
 * Structure containing the information necessary to compute Gaussian covariance matrices
 * for the pseudo-CL spectra of two full-sky spin-0 fields.
 */
typedef struct {
  int lmax; //!< Maximum multipole for the first set of power spectra
  flouble **xi00_1122; //!< First (a1b1-a2b2), 00, mode coupling matrix (see scientific documentation)
  flouble **xi00_1221; //!< Second (a1b2-a2b1), 00, mode coupling matrix (see scientific documentation)
  flouble **xi02_1122; //!< First (a1b1-a2b2), 02, mode coupling matrix (see scientific documentation)
  flouble **xi02_1221; //!< Second (a1b2-a2b1), 02, mode coupling matrix (see scientific documentation)
  flouble **xi22p_1122; //!< First (a1b1-a2b2), 22+, mode coupling matrix (see scientific documentation)
  flouble **xi22p_1221; //!< Second (a1b2-a2b1), 22+, mode coupling matrix (see scientific documentation)
  flouble **xi22m_1122; //!< First (a1b1-a2b2), 22-, mode coupling matrix (see scientific documentation)
  flouble **xi22m_1221; //!< Second (a1b2-a2b1), 22-, mode coupling matrix (see scientific documentation)
} nmt_covar_workspace;

/**
 * @brief nmt_covar_workspace destructor.
 */
void nmt_covar_workspace_free(nmt_covar_workspace *cw);

/**
 * @brief nmt_covar_workspace constructor
 *
 * Builds an nmt_covar_workspace structure from two pairs of nmt_field structures, corresponding
 * to the two sets of power spectra for which the covariance is required.
 * @param fla1 nmt_field for the first field going into the first (a-th) power spectrum.
 * @param fla2 nmt_field for the second field going into the first (a-th) power spectrum.
 * @param flb1 nmt_field for the first field going into the second (b-th) power spectrum.
 * @param flb2 nmt_field for the second field going into the second (b-th) power spectrum.
 * @param lmax maximum multipole up to which the coupling coefficients will be calculated.
 * @param niter number of iterations when computing alms.
 */
nmt_covar_workspace *nmt_covar_workspace_init(nmt_field *fla1,nmt_field *fla2,
					      nmt_field *flb1,nmt_field *flb2,
					      int lmax,int niter,
                                              int l_toeplitz,int l_exact,int dl_band);

/**
 * @brief Compute full-sky Gaussian covariance matrix
 *
 * Computes the covariance matrix for two sets of power spectra given input predicted spectra
 * and a nmt_covar_workspace structure.
 * @param cw nmt_covar_workspace structure containing the information necessary to compute the
          covariance matrix.
 * @param spin_a field a spin.
 * @param spin_b field b spin.
 * @param spin_c field c spin.
 * @param spin_d field d spin.
 * @param wa nmt_workspace structure containing the mode-coupling matrix for the first power spectra.
 * @param wb nmt_workspace structure containing the mode-coupling matrix for the second power spectra.
 * @param clac Cross-power spectra between field 1 in the first set and field 1 in the second set (ac)
          All power spectra should be defined for all ell < lmax.
 * @param clad Cross-power spectra between field 1 in the first set and field 2 in the second set (ad)
 * @param clbc Cross-power spectra between field 2 in the first set and field 1 in the second set (bc)
 * @param clbd Cross-power spectra between field 2 in the first set and field 2 in the second set (bd)
 * @param covar_out flattened covariance matrix. Should be allocated to shape [ncls_1 * nbpw_1 * ncls_2 * nbpw_2],
          where nbpw_X and ncls_X are the number of bandpowers and different power spectra in the X-th set of fields.
 */
void  nmt_compute_gaussian_covariance(nmt_covar_workspace *cw,
				      int spin_a,int spin_b,int spin_c,int spin_d,
				      nmt_workspace *wa,nmt_workspace *wb,
				      flouble **clac,flouble **clad,
				      flouble **clbc,flouble **clbd,
				      flouble *covar_out);

/**
 * @brief Compute full-sky Gaussian covariance matrix
 *
 * Computes the covariance matrix for two sets of power spectra given input predicted spectra
 * and a nmt_covar_workspace structure. Calculation done for the mode-coupled pseudo-Cls.
 * @param cw nmt_covar_workspace structure containing the information necessary to compute the
          covariance matrix.
 * @param spin_a field a spin.
 * @param spin_b field b spin.
 * @param spin_c field c spin.
 * @param spin_d field d spin.
 * @param wa nmt_workspace structure containing the mode-coupling matrix for the first power spectra.
 * @param wb nmt_workspace structure containing the mode-coupling matrix for the second power spectra.
 * @param clac Cross-power spectra between field 1 in the first set and field 1 in the second set (ac)
          All power spectra should be defined for all ell < lmax.
 * @param clad Cross-power spectra between field 1 in the first set and field 2 in the second set (ad)
 * @param clbc Cross-power spectra between field 2 in the first set and field 1 in the second set (bc)
 * @param clbd Cross-power spectra between field 2 in the first set and field 2 in the second set (bd)
 * @param covar_out flattened covariance matrix. Should be allocated to shape [ncls_1 * nbpw_1 * ncls_2 * nbpw_2],
          where nbpw_X and ncls_X are the number of bandpowers and different power spectra in the X-th set of fields.
 */
void  nmt_compute_gaussian_covariance_coupled(nmt_covar_workspace *cw,
                                              int spin_a,int spin_b,int spin_c,int spin_d,
                                              nmt_workspace *wa,nmt_workspace *wb,
                                              flouble **clac,flouble **clad,
                                              flouble **clbc,flouble **clbd,
                                              flouble *covar_out);

/**
 * @brief Saves nmt_workspace structure to file
 *
 * The output file uses the FITS standard. In combination with nmt_workspace_read_fits(),
 * this can be used to save the information contained in a given workspace and reuse it for
 * future power spectrum computations. The same workspace can be used on any pair of fields
 * with the same masks.
 * @param w nmt_workspace to be saved.
 * @param fname Path to output file.
 */
void nmt_workspace_write_fits(nmt_workspace *w,char *fname);

/**
 * @brief Builds nmt_workspace structure from file
 *
 * The input file uses the FITS standard. In combination with nmt_workspace_write_fits(),
 * this can be used to save the information contained in a given workspace and reuse it for
 * future power spectrum computations. The same workspace can be used on any pair of fields
 * with the same masks.
 * @param fname Path to input file.
 */
nmt_workspace *nmt_workspace_read_fits(char *fname);

/**
 * @brief Builds nmt_workspace_flat structure from file
 *
 * The input file uses the FITS standard. In combination with nmt_workspace_flat_write_fits(),
 * this can be used to save the information contained in a given workspace and reuse it for
 * future power spectrum computations. The same workspace can be used on any pair of fields
 * with the same masks.
 * @param fname Path to input file.
 */
nmt_workspace_flat *nmt_workspace_flat_read_fits(char *fname);

/**
 * @brief Saves nmt_workspace_flat structure to file
 *
 * The output file uses the FITS standard. In combination with nmt_workspace_flat_read_fits(),
 * this can be used to save the information contained in a given workspace and reuse it for
 * future power spectrum computations. The same workspace can be used on any pair of fields
 * with the same masks.
 * @param w nmt_workspace_flat to be saved.
 * @param fname Path to output file.
 */
void nmt_workspace_flat_write_fits(nmt_workspace_flat *w,char *fname);

/**
 * @brief Saves nmt_covar_workspace structure to file
 *
 * The output file uses the FITS standard. In combination with nmt_covar_workspace_read_fits(),
 * this can be used to save the information contained in a given workspace and reuse it for
 * future covariance matrix computations. The same workspace can be used on any pair of power spectra
 * between fields with the same masks.
 * @param cw nmt_covar_workspace to be saved.
 * @param fname Path to output file.
 */
void nmt_covar_workspace_write_fits(nmt_covar_workspace *cw,char *fname);

/**
 * @brief Builds nmt_covar_workspace structure from file
 *
 * The input file uses the FITS standard. In combination with nmt_covar_workspace_write_fits(),
 * this can be used to save the information contained in a given workspace and reuse it for
 * future covariance matrix computations. The same workspace can be used on any pair of power spectra
 * between fields with the same masks.
 * @param fname Path to input file.
 */
nmt_covar_workspace *nmt_covar_workspace_read_fits(char *fname);

/**
 * @brief Saves nmt_covar_workspace_flat structure to file
 *
 * The output file uses the FITS standard. In combination with nmt_covar_workspace_flat_read_fits(),
 * this can be used to save the information contained in a given workspace and reuse it for
 * future covariance matrix computations. The same workspace can be used on any pair of power spectra
 * between fields with the same masks.
 * @param cw nmt_covar_workspace_flat to be saved.
 * @param fname Path to output file.
 */
void nmt_covar_workspace_flat_write_fits(nmt_covar_workspace_flat *cw,char *fname);

/**
 * @brief Builds nmt_covar_workspace_flat structure from file
 *
 * The input file uses the FITS standard. In combination with nmt_covar_workspace_flat_write_fits(),
 * this can be used to save the information contained in a given workspace and reuse it for
 * future covariance matrix computations. The same workspace can be used on any pair of power spectra
 * between fields with the same masks.
 * @param fname Path to input file.
 */
nmt_covar_workspace_flat *nmt_covar_workspace_flat_read_fits(char *fname);

#endif //_NAMASTER_H_


#include <petsc.h>

static PetscErrorCode SetInitialCondition(PetscInt dim, PetscReal time, const PetscReal x[], PetscInt Nf, PetscScalar *u, void *ctx){

    u[0] = x[0];
    return 0;
}

static PetscErrorCode PrintFVGradients(DM dm){
    PetscFunctionBeginUser;
    PetscErrorCode ierr;

    // Convert the dm to a plex
    DM plex;
    DMConvert(dm, DMPLEX, &plex);

    // Get the start/end
    PetscInt fStart;
    PetscInt fEnd;
    ierr = DMPlexGetHeightStratum(plex, 1, &fStart, &fEnd);CHKERRQ(ierr);

    // Extract the cell geometry, and the dm that holds the information
    Vec faceGeometry;
    DM dmFace;
    ierr = DMPlexGetGeometryFVM(plex, &faceGeometry, NULL, NULL);CHKERRQ(ierr);

    ierr = VecGetDM(faceGeometry, &dmFace);CHKERRQ(ierr);

    const PetscScalar *facegeom;
    ierr = VecGetArrayRead(faceGeometry, &facegeom);CHKERRQ(ierr);

    // Get the dim
    PetscInt dim;
    ierr = DMGetDimension(dm, &dim);CHKERRQ(ierr);

    // March over each cell volume
    for (PetscInt f = fStart; f < fEnd; ++f) {
        PetscFVFaceGeom       *fg;

        ierr = DMPlexPointLocalRead(dmFace, f, facegeom, &fg);CHKERRQ(ierr);

        printf("Face Grad %d: ", f);
        for(PetscInt d =0; d< dim; d++){
            printf("%f/%f, ", fg->grad[0][d], fg->grad[1][d]);
        }
        printf("\n");
    }

    PetscFunctionReturn(0);
}

int main(int argc, char **argv)
{
    DM             dm;
    PetscErrorCode ierr;

    ierr = PetscInitialize(&argc, &argv, NULL, "HELP");if (ierr) return(ierr);
    ierr = DMPlexCreateBoxMesh(PETSC_COMM_WORLD, 2, PETSC_FALSE, NULL, NULL, NULL, NULL, PETSC_TRUE, &dm);CHKERRQ(ierr);
    ierr = DMViewFromOptions(dm, NULL, "-dm_view");CHKERRQ(ierr);
    ierr = DMSetFromOptions(dm);CHKERRQ(ierr);

    {
        DM gdm;

        ierr = DMPlexConstructGhostCells(dm, NULL, NULL, &gdm);CHKERRQ(ierr);
        ierr = DMDestroy(&dm);CHKERRQ(ierr);
        dm   = gdm;
    }
    ierr = DMViewFromOptions(dm, NULL, "-dm_view_ghost");CHKERRQ(ierr);

    PetscFV fvm;
    PetscDS ds;

    ierr = PetscFVCreate(PETSC_COMM_WORLD, &fvm);CHKERRQ(ierr);
    ierr = PetscFVSetFromOptions(fvm);CHKERRQ(ierr);
    ierr = PetscFVSetNumComponents(fvm, 1);CHKERRQ(ierr);
    ierr = PetscFVSetSpatialDimension(fvm, 2);CHKERRQ(ierr);
    ierr = PetscObjectSetName((PetscObject) fvm, "outputField");CHKERRQ(ierr);

    /* FV is now structured with one field having all physics as components */
    ierr = DMAddField(dm, NULL, (PetscObject) fvm);CHKERRQ(ierr);
    ierr = PetscFVDestroy(&fvm);CHKERRQ(ierr);
    ierr = DMCreateDS(dm);CHKERRQ(ierr);
    ierr = DMGetDS(dm, &ds);CHKERRQ(ierr);

    Vec X;
    PetscErrorCode     (*func[1]) (PetscInt dim, PetscReal time, const PetscReal x[], PetscInt Nf, PetscScalar *u, void *ctx) = {SetInitialCondition};

    ierr = DMCreateGlobalVector(dm, &X);CHKERRQ(ierr);
    ierr = PetscObjectSetName((PetscObject) X, "solution");CHKERRQ(ierr);
    ierr = DMProjectFunction(dm, 0.0, func, NULL, INSERT_ALL_VALUES, X);CHKERRQ(ierr);
    ierr = VecViewFromOptions(X, NULL, "-vec_view");CHKERRQ(ierr);

    PrintFVGradients(dm);

    ierr = VecDestroy(&X);CHKERRQ(ierr);
    ierr = DMDestroy(&dm);CHKERRQ(ierr);
    return PetscFinalize();
}


#include <stdio.h>
#include "sweeny_bfs.h"
#include "../src/queue_2.h"
#include <ctype.h>
#include <strings.h>
#include <unistd.h>
#include <math.h>
#include <gsl/gsl_rng.h>

// Define Macro SEQUENTIAL to use sequential BFS; Default is interleaved BFS

#define MIN(a,b) a<=b ? a: b
#define MAX(a,b) a>=b? a:b

static char setup=0; // 0 not setup; 1 ibfs; 2 sbfs
static char verbose=0;
static double rcWeights[4]; // array with precalculated mc weights
static double p_min_del,p_max_del, p_min_ins,p_max_ins;
static __s8 dN,dB;
static char equilibration=1;
static __u32 DX;
static __u32 N;
static __u32 seed;
static double q;
static double coupling;
static double beta;
static double v;
static double K;
static __u32 steps;
static __s8 *bonds;
static gsl_rng * r;
static __u32 edge[2];
static __s32 adj1[4];
static __s32 adj2[4];
static __u32 offset_1;
static __u32 offset_2;
static __u32 cs2;
static struct queue_2_node *todo_pool;
static __u32 *visited;
static __u32 cs1;
static __u32 activeEdges=0;
static __u32 cutoff;
static __u32 *num_bonds , *num_cluster, *size_giant;
static __u64 *sec_cs_moment, *four_cs_moment;

/******************************************************************************
 *****************************************************************************/
static char init(void)
{
    K=coupling*beta;
    v = exp(K) - 1.0;
    N = DX*DX;
    // Deletion accept. ratios
    rcWeights[0] = pow(v,-1); //db==-1,dN==0
    rcWeights[1] = rcWeights[0]*q; //db==-1,dN=1
    // Insertion accept. ratios
    rcWeights[2] = v; //db==1,dN==0
    rcWeights[3] = v*pow(q,-1); //db==1,dN==-1

    p_min_del = MIN(rcWeights[0],rcWeights[1]);
    p_max_del = MAX(rcWeights[0],rcWeights[1]);
    p_min_ins = MIN(rcWeights[2],rcWeights[3]);
    p_max_ins = MAX(rcWeights[2],rcWeights[3]);

    r = gsl_rng_alloc (gsl_rng_mt19937);
    if(!r)
        return 0;
    gsl_rng_set(r,seed);
    __u32 i;
    bonds = malloc(sizeof(*bonds)*2*N);
    if(!bonds) {
        return 0;
    }
    for(i=0;i<2*N;i++) bonds[i] = -1;
    visited = calloc(N,sizeof(*visited));
    if(!visited) {
        return 0;
    }
    todo_pool = malloc(N*sizeof(struct queue_2_node));
    if(!todo_pool) {
        return 0;
    }
    return 1;

}
/******************************************************************************
 *****************************************************************************/
static void destroy(void)
{
    if(!setup)return;
    free(todo_pool);
    free(bonds);
    gsl_rng_free(r);
    free(visited);
}
/******************************************************************************
 *****************************************************************************/
static inline __u32 ltcXnext(__u32 idx)
{
    if((idx+1) % DX)
        return idx+1;
    else
        return idx +1- DX ;
}
/******************************************************************************
 *****************************************************************************/
static inline __u32 ltcXprev(__u32 idx)
{
    if((idx%DX))
        return idx-1;
    else
        return idx + DX  -1;
}
/******************************************************************************
 *****************************************************************************/
 static inline __u32 ltcYnext(__u32 idx)
{

    if(idx <= N -1 && idx >= N - DX)
        return idx +DX- N ;
    else
        return idx+DX;
}
/******************************************************************************
 *****************************************************************************/
static inline __u32 ltcYprev(__u32 idx)
{
    if(idx <= DX - 1)
        return idx + N - DX;
    else
        return idx - DX;

}
/******************************************************************************
 *****************************************************************************/
static void Adjacent(__u32 bidx)
{

  if(bidx%2) {
    bidx = (bidx -1)/2;
    edge[0] = bidx;
    edge[1] = ltcXnext(bidx);
  }
  else {
    bidx = bidx/2;
    edge[0] = bidx;
    edge[1] = ltcYprev(bidx);
  }

}
/******************************************************************************
 *****************************************************************************/
static void neighbours(__u32 idx,__u8 a) {
  if(a == 1)
  {
    if(bonds[2*idx] == 1)  adj1[0] = ltcYprev(idx);
    else  adj1[0] = -1;
    if(bonds[(2*idx)+1] == 1)  adj1[1] = ltcXnext(idx);
    else adj1[1] = -1;
    if(bonds[2*ltcYnext(idx)] == 1)	adj1[2] = ltcYnext(idx);
    else adj1[2] = -1;
    if(bonds[(2*ltcXprev(idx))+1] ==1)adj1[3] = ltcXprev(idx);
    else adj1[3] = -1;
  }
  else {
    if(bonds[2*idx] == 1)adj2[0] = ltcYprev(idx);
    else adj2[0] = -1;
    if(bonds[(2*idx)+1] == 1)adj2[1] = ltcXnext(idx);
    else adj2[1] = -1;
    if(bonds[2*ltcYnext(idx)] == 1)	adj2[2] = ltcYnext(idx);
    else adj2[2] = -1;
    if(bonds[(2*ltcXprev(idx))+1] == 1)adj2[3] = ltcXprev(idx);
    else adj2[3] = -1;
  }

}
/******************************************************************************
 *****************************************************************************/
static __u8 breadthFirstSearch_s(__u32 start, __u32 goal)
{
  static struct queue_2 todo1;
  __u32 i=0,activeP1=0;
  init_queue_2(&todo1); //todo_pool);
  cs1=0;
  enqueue_2(&todo1,start);
  cs1++; // Increase cluster size of bfs 1
  visited[start] = offset_1; // mark starting point as visited
    while(!queue_2_empty_p(&todo1)) {
      dequeue_2(&todo1,&activeP1);
      neighbours(activeP1,1);
      for(i=0;i<4;i++) {
        if(adj1[i] != -1) {
            if(visited[adj1[i]] == offset_1) continue;
            if((__u32)adj1[i] == goal) {
                    while(!queue_2_empty_p(&todo1)){ dequeue_2(&todo1,&activeP1);}
                    return 1;
                }
            enqueue_2(&todo1,adj1[i]);
            visited[adj1[i]] = offset_1;
            cs1++;
        }
      }
  }
  return 0;
}
/******************************************************************************
 *****************************************************************************/
static __u8 breadthFirstSearch(__u32 start, __u32 goal)
{
  static struct queue_2 todo1,todo2;
  __u32 i=0,activeP1=0,activeP2=0;
  init_queue_2(&todo1);
  init_queue_2(&todo2);
  cs1=0;
  cs2=0;
  enqueue_2(&todo1,start); // Put starting point onto queue_2 1
  cs1++; // Increase cluster size of bfs 1
  visited[start] = offset_1; // mark starting point as visited
  enqueue_2(&todo2,goal); // Put goal point onto queue_2 2
  visited[goal] = offset_2; // mark goal point as visited
  cs2++; // increase cluster size of bfs 2
    while(!queue_2_empty_p(&todo1) && !queue_2_empty_p(&todo2)) {
      dequeue_2(&todo2,&activeP2); // get next vertex of bfs 2
      neighbours(activeP2,2);	 // get all adjacent vertices of current vertex
      for(i=0;i<4;i++) {

        if(adj2[i] != -1) { // if accessable (edge exists)
	  if(visited[adj2[i]] == offset_2) continue; // already visited
	  if((__u32)adj2[i] == start || visited[adj2[i]] == offset_1) { // reconnected

            while(!queue_2_empty_p(&todo1)){ dequeue_2(&todo1,&activeP1);}
            while(!queue_2_empty_p(&todo2)){ dequeue_2(&todo2,&activeP2);}
	    return 1; // 1 indicates success
	  }
	  enqueue_2(&todo2,adj2[i]);
	  visited[adj2[i]] = offset_2; // mark as visited
	  cs2++; // increase cluster size
	  }
      }
      dequeue_2(&todo1,&activeP1);
      neighbours(activeP1,1);
      for(i=0;i<4;i++) {
	if(adj1[i] != -1) {
	  if(visited[adj1[i]] == offset_1) continue;
	  if((__u32)adj1[i] == goal || visited[adj1[i]] == offset_2) {
            while(!queue_2_empty_p(&todo1)){ dequeue_2(&todo1,&activeP1);}
            while(!queue_2_empty_p(&todo2)){ dequeue_2(&todo2,&activeP2);}
			return 1;
	  }
	  enqueue_2(&todo1,adj1[i]);
	  visited[adj1[i]] = offset_1;
	  cs1++;
	}
      }
  }
  while(!queue_2_empty_p(&todo1)){dequeue_2(&todo1,&activeP1);}
  while(!queue_2_empty_p(&todo2)){dequeue_2(&todo2,&activeP2);}
  return 0;
}
/******************************************************************************
 *****************************************************************************/
static inline __u8 connected(__u32 start, __u32 goal) {
    offset_1+=2;
    offset_2+=2;
    return breadthFirstSearch(start,goal);
}

/******************************************************************************
 *****************************************************************************/
__u8 static inline connected_s(__u32 start, __u32 goal) {
    ++offset_1;
    return breadthFirstSearch_s(start,goal);
}
/******************************************************************************
 *****************************************************************************/
static inline void   sweep_ibfs(void)
{
    static __u32 bidx;
    static double rnd_num;
    __u32 i=0;

    for(;i<2*N ;++i) {
        bidx = gsl_rng_uniform_int(r,2*N);
        rnd_num  = gsl_rng_uniform(r);
        Adjacent(bidx);
        if(bonds[bidx] == 1) { //edge is active hence delete it
            dB = -1;
            if(rnd_num < p_min_del) { // accept bond deletion
                bonds[bidx]*=-1;
                activeEdges--;
            }
            else {
                if(rnd_num < p_max_del) {
                    bonds[bidx]*=-1;
                    dN = (connected(edge[0],edge[1])? 0 : 1);
                    if(rnd_num >= rcWeights[dB == -1? dN : 2-dN]) bonds[bidx]*=-1; // undo bond deletion
                    else activeEdges--; // accept bond deletion
                }

            }

        }
        else {
            // Insert cedge. If adjacent vertices are already connected, the new
            // edge will be a non-tree edge, i.e. dN = 0
            dB = 1;
            if(rnd_num < p_min_ins) { // accept bond insertion
                bonds[bidx] *=-1;
                activeEdges++;
            }
            else {
                if(rnd_num < p_max_ins) {
                    dN = ( connected(edge[0],edge[1]) ?0 : -1);
                    if(rnd_num < rcWeights[dB == -1? dN : 2-dN]) {
                        bonds[bidx]*=-1;
                        activeEdges++;
                    }
                }

            }
        }
    }
}
/******************************************************************************
 *****************************************************************************/
static inline void   sweep_sbfs(void)
{
    static __u32 bidx;
    static double rnd_num;
    __u32 i=0;

    for(;i<2*N ;++i) {
        bidx = gsl_rng_uniform_int(r,2*N);
        rnd_num  = gsl_rng_uniform(r);
        Adjacent(bidx);
        if(bonds[bidx] == 1) { //edge is active hence delete it
            dB = -1;
            if(rnd_num < p_min_del) { // accept bond deletion
                bonds[bidx]*=-1;
                activeEdges--;
            }
            else {
                if(rnd_num < p_max_del) {
                    bonds[bidx]*=-1;
                    dN = (connected_s(edge[0],edge[1])? 0 : 1);
                    if(rnd_num >= rcWeights[dB == -1? dN : 2-dN]) bonds[bidx]*=-1; // undo bond deletion
                    else activeEdges--; // accept bond deletion
                }

            }

        }
        else {
            // Insert cedge. If adjacent vertices are already connected, the new
            // edge will be a non-tree edge, i.e. dN = 0
            dB = 1;
            if(rnd_num < p_min_ins) { // accept bond insertion
                bonds[bidx] *=-1;
                activeEdges++;
            }
            else {
                if(rnd_num < p_max_ins) {
                    dN = ( connected_s(edge[0],edge[1]) ?0 : -1);
                    if(rnd_num < rcWeights[dB == -1? dN : 2-dN]) {
                        bonds[bidx]*=-1;
                        activeEdges++;
                    }
                }

            }
        }
    }
}
/******************************************************************************
 *****************************************************************************/
static void extract_observables(__u32 i) {
    static __u32 j=0;
    static __u32 clust_cnt=0;
    static __u64 sum=0,sum_2;
    static __u32 maxc=0,chksum=0;
    num_bonds[i] = activeEdges;
    offset_1=0;
    for(j=0;j<N;++j) visited[j] = offset_1;
    offset_1=1;
    for(j=0;j<N&&chksum<=N;++j) {
        if(!visited[j]){
            breadthFirstSearch_s(j,N);
            clust_cnt++;
            if(cs1 > maxc) maxc = cs1;
            sum+=cs1*cs1;
            sum_2+=pow(cs1,4);
            chksum+=cs1;
        }

    }
    num_cluster[i] = clust_cnt;
    size_giant[i] = maxc;
    sec_cs_moment[i] = sum;
    four_cs_moment[i] = sum_2;
    maxc=clust_cnt=sum=sum_2=chksum=0;
    offset_1=3;
    offset_2=2;
}
/******************************************************************************
 *****************************************************************************/
static void generateTimeSeries(void)
{

  __u32 i;
  if(setup==1) {
    for(i=0;i<cutoff;++i)sweep_ibfs();
    equilibration=0;
    if(verbose)
        printf("Equilibration done!\n");
    for(i=0;i<steps;++i) {
        sweep_ibfs();
        extract_observables(i);
    }
  }
  if(setup==2) {
    for(i=0;i<cutoff;++i)sweep_sbfs();
    equilibration=0;
    if(verbose)
        printf("Equilibration done!\n");
    for(i=0;i<steps;++i) {
        sweep_sbfs();
        extract_observables(i);
    }

  }

}
/******************************************************************************
 *****************************************************************************/
char init_sweeny_ibfs(double _q,unsigned int _l,double _beta,double _coupl,
        unsigned int _cutoff,unsigned _tslength,unsigned int rng_seed,
        void *ts_0,void *ts_1,void * ts_2,void *ts_3,void *ts_4) {
    setup=1;
    offset_1 =1;
    offset_2 = 2;
    activeEdges =0;
    q = _q;
    DX = _l;
    beta = _beta;
    coupling = _coupl;
    cutoff = _cutoff;
    steps = _tslength;
    seed = rng_seed;
    num_bonds = (__u32 *)ts_0;
    num_cluster = (__u32 *)ts_1;
    size_giant = (__u32 *)ts_2;
    sec_cs_moment = (__u64 *)ts_3;
    four_cs_moment = (__u64 *)ts_4;
    if(!init())
        setup=0;
    return setup;

}
/******************************************************************************
 *****************************************************************************/
char init_sweeny_sbfs(double _q,unsigned int _l,double _beta,double _coupl,
        unsigned int _cutoff,unsigned _tslength,unsigned int rng_seed,
        void *ts_0,void *ts_1,void * ts_2,void *ts_3, void *ts_4) {
    setup=init_sweeny_ibfs(_q,_l,_beta,_coupl,_cutoff,_tslength,rng_seed,
            ts_0,ts_1,ts_2,ts_3,ts_4);
    if(setup)
        return setup=2;
    else
        return setup;
}
/******************************************************************************
 *****************************************************************************/
void destroy_sweeny_ibfs(void) {
    if(setup) {
        destroy();
    }
    setup=0;
}
/******************************************************************************
 *****************************************************************************/
void destroy_sweeny_sbfs(void) {
    destroy_sweeny_ibfs();
}
/******************************************************************************
 *****************************************************************************/
char simulate_sweeny_ibfs(void) {
    if(setup!=1)
        return 0;
    else
        generateTimeSeries();
    return 1;
}
/******************************************************************************
 *****************************************************************************/
char simulate_sweeny_sbfs(void) {
    if(setup!=2)
        return 0;
    else
        generateTimeSeries();
    return 1;
}