//Renata Kopecna

#include <fstream>

#include "folder.hh"
#include <funcs.hh>
#include <constants.hh>
#include <bu2kstarmumu_pdf.hh>
#include <pdf.hh>
#include <options.hh>
#include <values.hh>
#include <design.hh>
#include <integrals.hh>
#include <event.hh>
#include <helpers.hh>
#include <paths.hh>

#include <TMinuit.h>
#include <spdlog.h>

#include <TROOT.h>
#include <TFile.h>
#include <TStyle.h>


//Helper function to create a polynomial for the background

std::vector<double> init_ch_ctl(const fcnc::bu2kstarmumu_parameters* params){
    std::vector<double> bkg_ctl;
    bkg_ctl.push_back(params->cbkgctl0());
    bkg_ctl.push_back(params->cbkgctl1());
    bkg_ctl.push_back(params->cbkgctl2());
    bkg_ctl.push_back(params->cbkgctl3());
    bkg_ctl.push_back(params->cbkgctl4());
    return bkg_ctl;
}
std::vector<double> init_ch_ctk(const fcnc::bu2kstarmumu_parameters* params){
    std::vector<double> bkg_ctk;
    bkg_ctk.push_back(params->cbkgctk0());
    bkg_ctk.push_back(params->cbkgctk1());
    bkg_ctk.push_back(params->cbkgctk2());
    bkg_ctk.push_back(params->cbkgctk3());
    bkg_ctk.push_back(params->cbkgctk4());
    bkg_ctk.push_back(params->cbkgctk5());
    bkg_ctk.push_back(params->cbkgctk6());
    return bkg_ctk;
}
std::vector<double> init_ch_phi(const fcnc::bu2kstarmumu_parameters* params){
    std::vector<double> bkg_phi;
    bkg_phi.push_back(params->cbkgphi0());
    bkg_phi.push_back(params->cbkgphi1());
    bkg_phi.push_back(params->cbkgphi2());
    bkg_phi.push_back(params->cbkgphi3());
    bkg_phi.push_back(params->cbkgphi4());
    return bkg_phi;
}


//using namespace std;
using namespace fcnc;

//this is a wrapper for the complex error function
//which is needed for a gaussian convolution of exp*cos/sin
//this fortran implementation however is not thread safe!
extern "C" void wwerfwrap_(double *zr, double *zi, double *wr, double* wi);
//boost::mutex global_mutex_2;
std::mutex global_mutex_2;

bu2kstarmumu_pdf* bu2kstarmumu_pdf::current_pdf;

bu2kstarmumu_pdf::bu2kstarmumu_pdf(options* o, const bu2kstarmumu_parameters* params):
    opts(o)
{

    //vector of efficiency coefficients containing 1260 elements, coefficients stored in bu2kstarmumu_coefficients.cc
    //coeffs_eff_4d = fcnc::coefficients_nominal;
    //load_coeffs_eff_phsp_4d();
    fldr = new fcnc::folder(opts);
};

bu2kstarmumu_pdf::~bu2kstarmumu_pdf()
{
};

void bu2kstarmumu_pdf::get_moments(std::vector<fcnc::event> events, std::vector<double>& moments, std::vector<double>& covariance) const
{

    if(!opts->full_angular){
        spdlog::error("No folding implemented for moments. Debug: 1");
        assert(0);
    }

    std::vector<double> cov(NTERMS*NTERMS, 0.0);//the actual covariance matrix for the moments
    std::vector<double> m(NTERMS, 0.0);//need to first calculate the mean

    double nweight = 0.0; //sum of weights
    double nweightsquared = 0.0; //sum of weights squared, not used anywhere

    for (auto meas: events){
        double weight = meas.weight*meas.sweight;
        nweight += weight;
        nweightsquared += weight*weight;
    }
    for (auto meas: events){
        double weight = meas.weight*meas.sweight;
        weight /= nweight;//nominal
        //Calculate the angular terms for each parameter
        double f1=0.0, f2=0.0, f3=0.0, f4=0.0, f5=0.0, f6=0.0,
                f7=0.0, f8=0.0, f9=0.0, f10=0.0, f11=0.0, f12=0.0;
        fj(meas.costhetal, meas.costhetak, meas.phi,
           f1, f2, f3, f4, f5, f6,
           f7, f8, f9, f10, f11, f12);
        double ffs=0.0, fs1=0.0, fs2=0.0, fs3=0.0, fs4=0.0, fs5=0.0;
        if(opts->swave){
            swave_fj(meas.costhetal, meas.costhetak, meas.phi,
                     ffs, fs1, fs2, fs3, fs4, fs5);
        }
        //TODO when bored: this is very annoying and f_whatever should be a struct or so and one should loop
        m.at(0) += f1*weight;
        m.at(1) += f2*weight;
        m.at(2) += f3*weight;
        m.at(3) += f4*weight;
        m.at(4) += f5*weight;
        m.at(5) += f6*weight;
        m.at(6) += f7*weight;
        m.at(7) += f8*weight;
        m.at(8) += f9*weight;
        m.at(9) += f10*weight;
        m.at(10) += f11*weight;
        m.at(11) += f12*weight;
        if(opts->swave){
            m.at(12) += ffs*weight;
            m.at(13) += fs1*weight;
            m.at(14) += fs2*weight;
            m.at(15) += fs3*weight;
            m.at(16) += fs4*weight;
            m.at(17) += fs5*weight;
        }
    }

    //calculate rms
    for (auto meas: events){
        double weight = meas.weight*meas.sweight;//dangerous?
        weight /= nweight;//nominal
        double f1=0.0, f2=0.0, f3=0.0, f4=0.0, f5=0.0, f6=0.0,
                f7=0.0, f8=0.0, f9=0.0, f10=0.0, f11=0.0, f12=0.0;
        fj(meas.costhetal, meas.costhetak, meas.phi,
           f1, f2, f3, f4, f5, f6,
           f7, f8, f9, f10, f11, f12);
        double ffs=0.0, fs1=0.0, fs2=0.0, fs3=0.0, fs4=0.0, fs5=0.0;
        if(opts->swave){
            swave_fj(meas.costhetal, meas.costhetak, meas.phi,
                     ffs, fs1, fs2, fs3, fs4, fs5);
        }
        std::vector<double> diff(NTERMS*NTERMS, 0.0);
        diff.at(0) = f1 - m.at(0); //aaaaaaaarrrrggghhh
        diff.at(1) = f2 - m.at(1);
        diff.at(2) = f3 - m.at(2);
        diff.at(3) = f4 - m.at(3);
        diff.at(4) = f5 - m.at(4);
        diff.at(5) = f6 - m.at(5);
        diff.at(6) = f7 - m.at(6);
        diff.at(7) = f8 - m.at(7);
        diff.at(8) = f9 - m.at(8);
        diff.at(9) = f10 - m.at(9);
        diff.at(10) = f11 - m.at(10);
        diff.at(11) = f12 - m.at(11);
        if(opts->swave){
            diff.at(12) = ffs - m.at(12);
            diff.at(13) = fs1 - m.at(13);
            diff.at(14) = fs2 - m.at(14);
            diff.at(15) = fs3 - m.at(15);
            diff.at(16) = fs4 - m.at(16);
            diff.at(17) = fs5 - m.at(17);
        }
        //for swave==false, only loop over the first 12x12 elements
        //the matrix however stays in the 18x18 format, with 0.0 for all j > 11 || k > 11
        UInt_t mm = opts->swave ? NTERMS : NTERMS_P;
        for (unsigned int j = 0; j<mm; j++){
            for (unsigned int k = 0; k<mm; k++){
                cov.at(j*NTERMS+k) += (diff.at(j))*(diff.at(k))*sqr(weight);
            }
        }
    }
    moments = m;
    covariance = cov;
    return;
};

double corr_from_obs(std::vector<double> obs, std::vector<double> obscov, int i, int k){
    return obscov.at(i+NTERMS*k)/sqrt(obscov.at(k+NTERMS*k))/sqrt(obscov.at(i+NTERMS*i));
}

void bu2kstarmumu_pdf::save_moments_to_obs(bu2kstarmumu_parameters* params,
                                           std::vector<double> obs,
                                           std::vector<double> obscov) const
{
    assert(obs.size() == NTERMS);
    assert(obscov.size() == NTERMS*NTERMS);

    //**** Fl ****//
    params->Fl.set_value(1.0-4.0/3.0*obs.at(0));
    params->Fl.set_error(4.0/3.0*sqrt(obscov.at(0+NTERMS*0)));
    if(opts->fit_fl) params->Fl.set_step_size(0.0001);

    for(unsigned int i = 0; i < NTERMS; i++){
        if(i == 0) params->Fl.set_correlation(1.0, 0); //self correlation is one by definition
        else if(i == 7 && opts->fit_afb){
            //correlation to Afb is equal to correlation of S1s and S6s
            params->Fl.set_correlation(corr_from_obs(obs,obscov,7,0), 7);
        }
        else{
            params->Fl.set_correlation(corr_from_obs(obs,obscov,i,0)/sqrt(3.0/4.0), i);
        }
    }

    //**** S1s ****//
    params->S1s.set_value(obs.at(0));
    params->S1s.set_error(sqrt(obscov.at(0+NTERMS*0)));
    if(!opts->fit_fl) params->S1s.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++){
        params->S1s.set_correlation(corr_from_obs(obs,obscov,i,0), i);
    }
    //**** S3 ****//
    params->S3.set_value(obs.at(4));
    params->S3.set_error(sqrt(obscov.at(4+NTERMS*4)));
    params->S3.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++){
        params->S3.set_correlation(corr_from_obs(obs,obscov,i,4), i);
    }
    //**** S4 ****//
    params->S4.set_value(obs.at(5));
    params->S4.set_error(sqrt(obscov.at(5+NTERMS*5)));
    params->S4.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++)
        params->S4.set_correlation(corr_from_obs(obs,obscov,i,5), i);

    //**** S5 ****//
    params->S5.set_value(obs.at(6));
    params->S5.set_error(sqrt(obscov.at(6+NTERMS*6)));
    params->S5.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++)
        params->S5.set_correlation(corr_from_obs(obs,obscov,i,6), i);

    //**** Afb ****//
    params->Afb.set_value(3.0/4.0*obs.at(7));
    params->Afb.set_error(3.0/4.0*sqrt(obscov.at(7+NTERMS*7)));
    if(opts->fit_afb)
        params->Afb.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++){
        if(i == 7) params->Afb.set_correlation(1.0, 7);//self correlation is not scaled
        else if(i == 0 && opts->fit_afb){
            //correlation to Fl is equal to correlation of S1s and S6s
            params->Afb.set_correlation(corr_from_obs(obs,obscov,0,7), 0);
        }
        else params->Afb.set_correlation(corr_from_obs(obs,obscov,i,7)/sqrt(4.0/3.0), i);
    }

    //**** S6s ****//
    params->S6s.set_value(obs.at(7));
    params->S6s.set_error(sqrt(obscov.at(7+NTERMS*7)));
    if(!opts->fit_afb) params->S6s.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++) params->S6s.set_correlation(corr_from_obs(obs,obscov,i,7), i);

    //**** S7 ****//
    params->S7.set_value(obs.at(9));
    params->S7.set_error(sqrt(obscov.at(9+NTERMS*9)));
    params->S7.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++) params->S7.set_correlation(corr_from_obs(obs,obscov,i,9), i);

    //**** S8 ****//
    params->S8.set_value(obs.at(10));
    params->S8.set_error(sqrt(obscov.at(10+NTERMS*10)));
    params->S8.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++) params->S8.set_correlation(corr_from_obs(obs,obscov,i,10), i);

    //**** S9 ****//
    params->S9.set_value(obs.at(11));
    params->S9.set_error(sqrt(obscov.at(11+NTERMS*11)));
    params->S9.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++) params->S9.set_correlation(corr_from_obs(obs,obscov,i,11), i);

    //**** FS ****//
    params->FS.set_value(obs.at(12));
    params->FS.set_error(sqrt(obscov.at(12+NTERMS*12)));
    if(opts->swave) params->FS.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++) params->FS.set_correlation(corr_from_obs(obs,obscov,i,12), i);

    //**** SS1 ****//
    params->SS1.set_value(obs.at(13));
    params->SS1.set_error(sqrt(obscov.at(13+NTERMS*13)));
    if(opts->swave) params->SS1.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++) params->SS1.set_correlation(corr_from_obs(obs,obscov,i,13), i);

    //**** SS2 ****//
    params->SS2.set_value(obs.at(14));
    params->SS2.set_error(sqrt(obscov.at(14+NTERMS*14)));
    if(opts->swave) params->SS2.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++) params->SS2.set_correlation(corr_from_obs(obs,obscov,i,14), i);

    //**** SS3 ****//
    params->SS3.set_value(obs.at(15));
    params->SS3.set_error(sqrt(obscov.at(15+NTERMS*15)));
    if(opts->swave) params->SS3.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++) params->SS3.set_correlation(corr_from_obs(obs,obscov,i,15), i);

    //**** SS4 ****//
    params->SS4.set_value(obs.at(16));
    params->SS4.set_error(sqrt(obscov.at(16+NTERMS*16)));
    if(opts->swave)params->SS4.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++) params->SS4.set_correlation(corr_from_obs(obs,obscov,i,16), i);

    //**** SS5 ****//
    params->SS5.set_value(obs.at(17));
    params->SS5.set_error(sqrt(obscov.at(17+NTERMS*17)));
    if(opts->swave)  params->SS5.set_step_size(0.0001);
    for(unsigned int i = 0; i < NTERMS; i++) params->SS5.set_correlation(corr_from_obs(obs,obscov,i,17), i);


}

double bu2kstarmumu_pdf::prob(const bu2kstarmumu_parameters* params, const event& meas) const
{

    //safety checks:
    if(opts->fit_mkpi && opts->use_mkpi){
        spdlog::critical("Do not use the direct fit of m(Kpi) and the included fit of m(Kpi) in the angular s-Wave at the same time!");
        spdlog::critical("5D fit:\t'use_mkpi'");
        spdlog::critical("4D+1D fit:\t'fit_mkpi'");
        assert(0);
    }
    if(opts->only_mass2DFit && opts->only_Bmass){
        spdlog::critical("Cannot fit m(Kpi) and m(B) with option 'only_Bmass'!");
        assert(0);
    }
    if(opts->only_mass2DFit && !opts->fit_mkpi){
        spdlog::critical("When using the 2D mass fit, enable 'fit_mkpi' as well!");
        assert(0);
    }
    if(opts->only_mkpi && !opts->fit_mkpi){
        spdlog::critical("When using the m(Kpi) mass fit, enable 'fit_mkpi' as well!");
        assert(0);
    }
    if((opts->only_angles && opts->only_Bmass) || (opts->only_mkpi && opts->only_Bmass) || (opts->only_angles && opts->only_mkpi)){
        spdlog::critical("Do not use two or more of 'only_Bmass', 'only_mkpi' and 'only_angles' at the same time!");
        spdlog::critical("only_Bmass   = " + boolToString(opts->only_Bmass));
        spdlog::critical("only_mkpi   = " + boolToString(opts->only_mkpi));
        spdlog::critical("only_angles = " + boolToString(opts->only_angles));
        assert(0);
    }

    if(opts->use_angular_acc && opts->weighted_fit){
        spdlog::critical("Do not use 'use_angular_acc' and 'weighted_fit' at the same time!");
        spdlog::critical("'use_angular_acc'\tConvolute acceptance function into fit pdf");
        spdlog::critical("'weighted_fit'\tAssign per event weight according to acceptance");
        assert(0);
    }
    if(opts->twotailedcrystalball && opts->crystalball){
        spdlog::critical("Cannot use CB and two-tailed CB option. Choose only one and chose wisely:");
        spdlog::critical("Either 'twotailedcrystalball' or 'crystalball'");
        assert(0);
    }

    if(opts->full_angular && opts->folding > -1){
        spdlog::critical("Cannot run full angular fit and folding at the same time");
        assert(0);
    }

    //determine signal and background probability
    double fsig = (opts->extended_ml ? params->n_sig()/(params->n_sig()+params->n_bkg()) : params->f_sig());
    double sig_part = 0.0;
    double bkg_part = 0.0;

    if(fsig != 0.0)//signal part:
        sig_part += fsig
                * angular_sig_prob(params, meas)
                * m_sig_prob(params, meas)
                * mkpi_sig_prob(params, meas);

    if(fsig != 1.0)//background part:
        bkg_part += (1.0-fsig)
                * angular_bkg_prob(params, meas)
                * m_bkg_prob(params, meas)
                * mkpi_bkg_prob(params, meas);

    //force individual probabilities for signal and background to be positive
    if(opts->individual_penalties){
        if(sig_part < 0.0){
            sig_part = 1.0e-12;
            bkg_part = 0.0;
        }
        if(bkg_part < 0.0){
            bkg_part = 1.0e-12;
            sig_part = 0.0;
        }
    }

    //add background and signal parts:
    double result = sig_part + bkg_part;

    //check final probability
    if(result < 0.0){
        std::lock_guard<std::mutex> lock(global_mutex_2);
        spdlog::debug("Event has negative probability {0:e}", result);
        spdlog::debug("Signal_part:\t{0:e}", sig_part);
        spdlog::debug("m(B):\t\t{0:e}",m_sig_prob(params, meas)); //THIS one crashes and idk why
        spdlog::debug("m(Kpi):\t{0:e}",mkpi_sig_prob(params, meas));
        spdlog::debug("angles:\t{0:e}",angular_sig_prob(params, meas));
        spdlog::debug("FS:\t\t{0:e}", params->FS.get_value());
        spdlog::debug("Backgrnd_part:\t{0:e}", bkg_part);
        if (fsig != 1.0){
            spdlog::debug("m(B):\t{0:e}",m_bkg_prob(params, meas));
            spdlog::debug("m(Kpi):\t{0:e}",mkpi_bkg_prob(params, meas));
            spdlog::debug("angles:\t{0:e}",angular_bkg_prob(params, meas));
        }

        const bool variablepushback = false; //OMFG
        if(variablepushback){
            double baseline = 1.0e-12;
            double absres = fabs(result) > 1.0e-6 ? fabs(result) : 1.0e-6;
            double logres = log10(absres)+6.0;
            double factor = pow(10.0, -logres);
            result = baseline*factor;
        }
        else{
            result = 1.0e-12;
        }
    }
    else if(std::isinf(result) || std::isnan(result)){
        std::lock_guard<std::mutex> lock(global_mutex_2);
        params->print_parameters(false);
        spdlog::error("Event has probability {0:f}", result);
        spdlog::error("Signal_part:\t{0:f}", sig_part);
        spdlog::error("fsig:\t\t{0:f}", fsig);
        spdlog::error("m(B):\t\t{0:f}", m_sig_prob(params, meas));
        spdlog::error("m(Kpi):\t{0:e}", mkpi_sig_prob(params, meas));
        spdlog::error("angles:\t{0:e}",angular_sig_prob(params, meas));
        spdlog::error("FS:\t\t{0:f}", params->FS.get_value());
        spdlog::error("m(K*+) s-Wave NORM:\t{0:f}", mkpi_swave_2_norm);
        spdlog::error("m(K*+) p-Wave NORM:\t{0:f}", mkpi_pwave_2_norm);
        spdlog::error("Backgrnd_part:\t{0:f}", bkg_part);
        assert(0);
    }
    return result;
};

void bu2kstarmumu_pdf::init(parameters* params) {
    init(dynamic_cast<bu2kstarmumu_parameters*>(params));
    //update_cached_normalization(params);
};


struct bin_edges{ //This could've been a class in case someone is bored in the future

    //limits for all variable (range) (4D):
    double q2min, q2max;
    double ctkmin, ctkmax;
    double ctlmin, ctlmax;
    double phimin, phimax;

    unsigned int nq2bins, nctlbins, nctkbins, nphibins;

    //keep this as functions in case of modyfing the ranges during running the code!

    //range of each dimension (4D):
    double range_q2(){
        return q2max  - q2min;
    }
    double range_ctk(){
        return ctkmax - ctkmin;
    }
    double range_ctl(){
        return ctlmax - ctlmin;
    }
    double range_phi(){
        return phimax - phimin;
    }

    //bin width for each variable
    double binWidth_q2(){
        return range_q2()/nq2bins;
    }
    double binWidth_ctl(){
        return range_ctl()/nctlbins;
    }
    double binWidth_ctk(){
        return range_ctk()/nctkbins;
    }
    double binWidth_phi(){
        return range_phi()/nphibins;
    }

    //bin edges for each variable
    std::vector<double> edges_q2(){
        std::vector<double> tmp;
        for (unsigned int h = 0; h<=nq2bins; h++){
            tmp.push_back(q2min  +  h * binWidth_q2());
        }
        return tmp;
    }
    std::vector<double> edges_ctl(){
        std::vector<double> tmp;
        for (unsigned int h = 0; h<=nctlbins; h++){
            tmp.push_back(ctlmin  +  h * binWidth_ctl());
        }
        return tmp;
    }
    std::vector<double> edges_ctk(){
        std::vector<double> tmp;
        for (unsigned int h = 0; h<=nctkbins; h++){
            tmp.push_back(ctkmin  +  h * binWidth_ctk());
        }
        return tmp;
    }
    std::vector<double> edges_phi(){
        std::vector<double> tmp;
        for (unsigned int h = 0; h<=nphibins; h++){
            tmp.push_back(phimin  +  h * binWidth_phi());
        }
        return tmp;
    }

    //Total number of bins in 4D
    unsigned int getNbins(){
        return nq2bins*nctlbins*nctkbins*nphibins;
    }

    //constructor
    bin_edges(fcnc::options *opts, unsigned int nq2,unsigned int nctl,unsigned int nctk,unsigned int nphi){

        //get the limits for all variables (4D):
        q2min  = opts->TheQ2binsmin.front(); q2max  = opts->TheQ2binsmax.back();
        ctkmin = opts->ctk_min;              ctkmax = opts->ctk_max;
        ctlmin = opts->ctl_min;              ctlmax = opts->ctl_max;
        phimin = opts->phi_min;              phimax = opts->phi_max;

        nq2bins = nq2;
        nctlbins = nctl;
        nctkbins = nctk;
        nphibins = nphi;
    }

};

unsigned int get_bin_in4D(int h, int i, int j, int k, bin_edges edges){
    /// h = q2, i = ctl, j = ctk, k = phi

    //Return the bin number for given event evt in 4D (q2, thetal, thetak, phi)
    //Starting from 0!
    unsigned int bin = edges.nctlbins*edges.nctkbins*edges.nphibins*h //q2
            +edges.nctkbins*edges.nphibins*i        //thetaL
            +edges.nphibins*j                 //thetaK
            +k;                         //phi
    if (bin >= edges.getNbins()){
        spdlog::critical("Bin {0:d} is larger than the dimension of {1:d}!",bin,edges.getNbins());
        spdlog::error("h {0:d},\ti {1:d},\tj {2:d},\tk {3:d}", h, i, j, k);
        spdlog::error("q2 {0:d},\tctl {1:d},\tctk {2:d},\tphi {3:d}",
                      edges.nq2bins, edges.nctlbins, edges.nctkbins, edges.nphibins);
        assert(0);
    }
    else return bin;
}

unsigned int get_bin_in4D(event evt, bin_edges edges ){
    //Return the bin number for given event evt in 4D (q2, thetal, thetak, phi)
    //Starting from 0!
    unsigned int h = (evt.q2        - edges.q2min ) / edges.binWidth_q2(); //division here truncates the result"chi
    unsigned int i = (evt.costhetal - edges.ctlmin) / edges.binWidth_ctl();//eg 18/10 = 1
    unsigned int j = (evt.costhetak - edges.ctkmin) / edges.binWidth_ctk();
    unsigned int k = (evt.phi       - edges.phimin) / edges.binWidth_phi();

    return get_bin_in4D(h,i,j,k, edges);
}

unsigned int npolynom::get_bin_in4D(int h, int i, int j, int k){
    /// h = q2, i = ctl, j = ctk, k = phi

    //Return the bin number for given event evt in 4D (q2, thetal, thetak, phi)
    //Starting from 0!
    return ctl*ctk*phi*h //q2
            +ctk*phi*i   //thetaL
            +phi*j       //thetaK
            +k;          //phi
}

unsigned int get_bin_in3D(int i, int j, int k, npolynom npol){
    /// h = q2, i = ctl, j = ctk, k = phi

    //Return the bin number for given event evt in 4D (q2, thetal, thetak, phi)
    //Starting from 0!
    return npol.ctk*npol.phi*i        //thetaL
            +npol.phi*j                 //thetaK
            +k;                         //phi
}

unsigned int get_bin_in3D(int i, int j, int k, bin_edges edges){
    /// h = q2, i = ctl, j = ctk, k = phi

    //Return the bin number for given event evt in 4D (q2, thetal, thetak, phi)
    //Starting from 0!
    return edges.nctkbins*edges.nphibins*i        //thetaL
            +edges.nphibins*j                //thetaK
            +k;                         //phi
}

void get_chi2(bin_edges edges, npolynom npol,
              std::vector<event> eventsInQ2bin, std::vector<double> coeffs_moments,
              values* globalvalues,
              bool assumectleven, bool assumePhiEven, bool write_eps,
              std::string subfolder, std::string appendix){

    spdlog::info( "Starting chi2 calculation" );
    std::vector<double> nsel_hist(edges.getNbins(), 0.0);
    std::vector<double> nsel_hist_errsq(edges.getNbins(), 0.0);

    //this is actually not the correct error here, due to the weights!
    //should be sqrt(sum_i weights_i^2)

    double nsel = 0.0;
    int nEvents = eventsInQ2bin.size();
    for (auto &meas: eventsInQ2bin) {
        unsigned int bin = get_bin_in4D(meas,edges);
        nsel_hist.at(bin) += meas.weight;
        nsel += meas.weight;
        nsel_hist_errsq.at(bin) += sqr(meas.weight);
    }
    //Set errors
    for (auto err: nsel_hist_errsq){
        if (err == 0.0) err = 1.0;
    }

    //compare with prediction
    double neps = 0.0;
    std::vector<double> neps_hist(edges.getNbins(), 0.0);
    std::vector<double> q2_edges = edges.edges_q2();
    std::vector<double> ctl_edges = edges.edges_ctl();
    std::vector<double> ctk_edges = edges.edges_ctk();
    std::vector<double> phi_edges = edges.edges_phi();

    for (unsigned int h = 0; h<edges.nq2bins; h++){
        for (unsigned int i = 0; i<edges.nctlbins; i++){
            for (unsigned int j = 0; j<edges.nctkbins; j++){
                for (unsigned int k = 0; k<edges.nphibins; k++){
                    double integral = 0.0;
                    for (unsigned int l = 0; l<npol.q2; l++){
                        for (unsigned int m = 0; m<npol.ctl; m++){
                            for (unsigned int n = 0; n<npol.ctk; n++){
                                for (unsigned int o = 0; o<npol.phi; o++){
                                    double result = coeffs_moments.at(npol.get_bin_in4D(l,m,n,o))
                                            *(pow(q2_edges[h+1],l+1)  - pow(q2_edges[h],l+1))/(l+1.0)
                                            *(pow(ctl_edges[i+1],m+1) - pow(ctl_edges[i],m+1))/(m+1.0)
                                            *(pow(ctk_edges[j+1],n+1) - pow(ctk_edges[j],n+1))/(n+1.0)
                                            *(pow(phi_edges[k+1],o+1) - pow(phi_edges[k],o+1))/(o+1.0);
                                    integral += result;
                                }
                            }
                        }
                    }
                    neps_hist.at(get_bin_in4D(h,i,j,k,edges)) = integral;
                    neps += integral;
                }
            }
        }
    }
    spdlog::debug( "nsel {0:f}\n\t nEvents {1:d}\n\t neps {2:f}\n\t nsel/neps {3:f} ", nsel, nEvents, neps, nsel/neps );
    TH1D* hpull = new TH1D("hpull", ";(N_{sel}-N_{fit})/#sqrt{N_{sel}};",100, -5.0, +5.0);
    TH1D* hnsel = new TH1D("hnsel", ";N_{sel};",100, 0.0, +25.0);
    TH1D* hnfit = new TH1D("hnfit", ";N_{fit};",100, 0.0, +25.0);

    //rescale fitted efficiency to MC statistics
    //This would be nicer in a vector of vectors or so, but oh well
    for (unsigned int h = 0; h<edges.nq2bins; h++)
        for (unsigned int i = 0; i<edges.nctlbins; i++)
            for (unsigned int j = 0; j<edges.nctkbins; j++)
                for (unsigned int k = 0; k<edges.nphibins; k++)
                    neps_hist.at(get_bin_in4D(h,i,j,k, edges)) *= nsel/neps;

    //calculate chi2 and perform "pull" plot
    double chi2 = 0.0;

    //double errorscale = 1.01;
    for (unsigned int h = 0; h<edges.nq2bins; h++){
        for (unsigned int i = 0; i<edges.nctlbins; i++){
            for (unsigned int j = 0; j<edges.nctkbins; j++){
                for (unsigned int k = 0; k<edges.nphibins; k++){
                    unsigned int bin = get_bin_in4D(h,i,j,k, edges);
                    //nominal

                    chi2 += sqr(nsel_hist.at(bin)-neps_hist.at(bin))/fabs(neps_hist.at(bin));
                    hpull->Fill((nsel_hist.at(bin)-neps_hist.at(bin))/sqrt(fabs(neps_hist.at(bin))));
                    spdlog::trace("nsel_hist.at({0:d})\t{1:f}",bin,nsel_hist.at(bin));
                    spdlog::trace("neps_hist.at({0:d})\t{1:f}",bin,neps_hist.at(bin));
                    hnsel->Fill(nsel_hist.at(bin));
                    hnfit->Fill(neps_hist.at(bin));
                }
            }
        }
    }
    spdlog::debug( "chi2\t {0:f}", chi2 );

    //Calculate the number of parameters
    int nparams = npol.q2*npol.ctl*npol.ctk*npol.phi-1;
    spdlog::debug( "Nparams\t {0:d}", nparams );

    if (assumectleven && assumePhiEven){
        nparams = npol.q2*((npol.ctl+1)/2)*npol.ctk*((npol.phi+1)/2)-1;
    }
    else{
        if (assumectleven) nparams = npol.q2*((npol.ctl+1)/2)*npol.ctk*npol.phi-1;
        if (assumePhiEven) nparams = npol.q2*npol.ctl*npol.ctk*((npol.phi+1)/2)-1;
    }
    spdlog::debug( "Nparams (accounting for even ctl and phi)\t {0:d}", nparams );

    //Get degrees of freedom
    int nbins = edges.getNbins();
    spdlog::debug( "Nbins\t {0:d}", nbins);

    int ndof = nbins - nparams - 1;
    spdlog::debug( "ndof\t {0:d}", ndof );

    spdlog::debug( "chi2/ndof (accounting for even ctl and phi)\t {0:f}",chi2/ndof );
    spdlog::debug( "chi2/Nbins\t\t {0:f}",chi2/nbins );
    spdlog::debug( "pvalue using ndof\t {0:f}",TMath::Prob(chi2, ndof) );
    spdlog::debug( "pvalue using nbins\t {0:f}", TMath::Prob(chi2, nbins) );

    //=========================================
    //
    //  save chi2 to global values:
    //
    //=========================================

    globalvalues->angacccorr_chi2 = chi2;
    globalvalues->angacccorr_ndof = ndof;

    //recount of nparams
    int ncount = 0;
    for (unsigned int l = 0; l<npol.q2; l++)
        for (unsigned int m = 0; m<npol.ctl; m++)
            for (unsigned int n = 0; n<npol.ctk; n++)
                for (unsigned int o = 0; o<npol.phi; o++)
                    if (coeffs_moments.at(npol.get_bin_in4D(l,m,n,o)) != 0.0) ncount++;
    spdlog::debug( "Recount of Nparams gives:\t {0:d}",ncount );

    if(write_eps){
        plotAndSave(hpull,"cpull",subfolder + "pull"+appendix, "eps");
        plotAndSave(hnsel,"csel",subfolder + "nsel"+appendix, "eps");
        plotAndSave(hnfit,"cfit",subfolder + "nfit"+appendix, "eps");
    }
    delete hpull;
    delete hnsel;
    delete hnfit;

    return;


}


void get_legendre(bin_edges edges, npolynom npol,
                  std::vector<event> eventsInQ2bin, double nWeightedEvents,
                  std::vector<double> &poly_moments,
                  bool fejerkernel, bool assumectleven, bool assumePhiEven,
                  bool checkSignificance, bool calculateCovariance){
    //use Legendre polynomials
    spdlog::info("DOLEGENDRE start");

    //should def use this:
    //calculates all legendre polynomials up to including degree n
    //void fcnc::legendre(double x, int n, std::vector<double>& results)

    //this is not the best way to do things
    //should rather
    //loop over events
    //determine, for every event the legendre polynomials order 0 to nq2/nctl/nctk/nphi
    //loop over all orders
    //add weight*leg_q2.at(h)*leg_ctl.at(i)*leg_ctk.at(j)*leg_phi.at(k) in correct bin

    //optimized version
    std::vector<double> chijk_moments = std::vector<double>(npol.getSize(), 0.0);
    for (auto & meas: eventsInQ2bin){ //This is all computed then in the range of -1 to +1
        double q2  = 2.0 * (meas.q2        - edges.q2min ) / edges.range_q2() - 1.0;
        double ctl = 2.0 * (meas.costhetal - edges.ctlmin) / edges.range_ctl()- 1.0;
        double ctk = 2.0 * (meas.costhetak - edges.ctkmin) / edges.range_ctk()- 1.0;
        double phi = 2.0 * (meas.phi       - edges.phimin) / edges.range_phi()- 1.0;

        double weight = meas.weight;
        //calculates all legendre polynomials up to including degree n
        std::vector<double> leg_q2(npol.q2, 0.0);
        fcnc::legendre(q2, npol.q2, leg_q2);
        std::vector<double> leg_ctl(npol.ctl, 0.0);
        fcnc::legendre(ctl, npol.ctl, leg_ctl);
        std::vector<double> leg_ctk(npol.ctk, 0.0);
        fcnc::legendre(ctk, npol.ctk, leg_ctk);
        std::vector<double> leg_phi(npol.phi, 0.0);
        fcnc::legendre(phi, npol.phi, leg_phi);

        double result = 0.0;
        for (unsigned int h = 0; h<npol.q2; h++){
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        unsigned int bin = npol.get_bin_in4D(h,i,j,k);
                        result = 0.0;
                        //result
                        if (assumectleven && i%2 != 0 ) result = 0.0;
                        else if (assumePhiEven && k%2 != 0) result = 0.0;
                        else{
                            result =
                                    weight
                                    *leg_q2.at(h)*(2.0*h+1.0)/2.0
                                    *leg_ctl.at(i)*(2.0*i+1.0)/2.0
                                    *leg_ctk.at(j)*(2.0*j+1.0)/2.0
                                    *leg_phi.at(k)*(2.0*k+1.0)/2.0
                                    /nWeightedEvents;
                            if (fejerkernel)
                                result *= (1.0 - h/double(npol.q2-1))
                                        *(1.0 - i/double(npol.ctl-1))
                                        *(1.0 - j/double(npol.ctk-1))
                                        *(1.0 - k/double(npol.phi-1));
                        }
                        chijk_moments.at(bin) += result;
                    }
                }
            }
        }
    }

    //check significance
    //can be optimized as above
    if (checkSignificance){
        spdlog::info("starting CHECK SIGNIFICANT for LEGENDRE" );

        for (unsigned int h = 0; h<npol.q2; h++){
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        unsigned int bin = npol.get_bin_in4D(h,i,j,k);
                        double result = chijk_moments.at(bin);
                        double sigma = 0.0;
                        for (unsigned int e=0; e<eventsInQ2bin.size(); e++){
                            const event & meas = eventsInQ2bin.at(e);
                            double q2  = 2.0 * (meas.q2        - edges.q2min  ) / edges.range_q2()  - 1.0;
                            double ctl = 2.0 * (meas.costhetal - edges.ctlmin ) / edges.range_ctl() - 1.0;
                            double ctk = 2.0 * (meas.costhetak - edges.ctkmin ) / edges.range_ctk() - 1.0;
                            double phi = 2.0 * (meas.phi       - edges.phimin ) / edges.range_phi() - 1.0;

                            sigma += sqr(result - meas.weight // /nWeightedEvents
                                         *fcnc::legendre(q2, h)*(2.0*h+1.0)/2.0
                                         *fcnc::legendre(ctl, i)*(2.0*i+1.0)/2.0
                                         *fcnc::legendre(ctk, j)*(2.0*j+1.0)/2.0
                                         *fcnc::legendre(phi, k)*(2.0*k+1.0)/2.0
                                         );// /eventsInQ2bin.size();
                        }
                        sigma = sqrt(sigma);
                        sigma /= nWeightedEvents;
                        if (fabs(result/sigma) < 1.0) chijk_moments.at(bin) = 0.0;
                    }
                }
            }
        }
        spdlog::info( "finished CHECK SIGNIFICANT for LEGENDRE" );
    }

    //Log
    spdlog::debug( "//vector of legendre efficiency coefficients containing {0:d} elements", chijk_moments.size());
    spdlog::trace( "legendre_coeffs_eff_4d = { " );
    if (spdlog_trace()){
        for (unsigned int i=0; i<chijk_moments.size(); i++){
            std::cout<< std::setprecision(15) << std::scientific << chijk_moments.at(i) << (i<chijk_moments.size()-1 ? ", " : " ");
        }
    }


    //calculate covariance
    if (calculateCovariance){
        const unsigned int nsize = npol.getSize();
        spdlog::info( "Start COVARIANCE matrix for LEGENDRE polynoms" );
        spdlog::debug( "calculating {0:d} by {1:d} convariance matrix ({2:d} doubles)" , nsize, nsize, nsize*nsize );

        //optimized
        std::vector<double> covariance(nsize*nsize, 0.0);
        //startB = clock();
        for (unsigned int e=0; e<eventsInQ2bin.size(); e++){
            if (e % (eventsInQ2bin.size()/100) == 0)spdlog::debug( "{0:f}%", round(double(e*100)/eventsInQ2bin.size()));
            const event & meas = eventsInQ2bin.at(e);

            double q2  = 2.0 * (meas.q2        - edges.q2min ) / edges.range_q2() - 1.0;
            double ctl = 2.0 * (meas.costhetal - edges.ctlmin) / edges.range_ctl()- 1.0;
            double ctk = 2.0 * (meas.costhetak - edges.ctkmin) / edges.range_ctk()- 1.0;
            double phi = 2.0 * (meas.phi       - edges.phimin) / edges.range_phi()- 1.0;

            double weight = meas.weight;
            //calculates all legendre polynomials up to including degree n
            std::vector<double> leg_q2(npol.q2, 0.0);
            legendre(q2, npol.q2, leg_q2);
            std::vector<double> leg_ctl(npol.ctl, 0.0);
            legendre(ctl, npol.ctl, leg_ctl);
            std::vector<double> leg_ctk(npol.ctk, 0.0);
            legendre(ctk, npol.ctk, leg_ctk);
            std::vector<double> leg_phi(npol.phi, 0.0);
            legendre(phi, npol.phi, leg_phi);
            double wfactor = weight*weight/(nWeightedEvents*nWeightedEvents);
            double resulti=0.0, resultj=0.0, cov=0.0;
            unsigned int bini=0, binj=0;

            int hfactor=0, ifactor=0, jfactor=0, kfactor=0,
                    lfactor=0, mfactor=0, nfactor=0, ofactor=0,
                    hifactor=0, hijfactor=0, hijkfactor=0,
                    lmfactor=0, lmnfactor=0, lmnofactor=0;
            double aq2=0.0,aq2ctl=0.0,aq2ctlctk=0.0,aq2ctlctkphi=0.0;
            double bq2=0.0,bq2ctl=0.0,bq2ctlctk=0.0,bq2ctlctkphi=0.0;
            for (unsigned int h = 0; h<npol.q2; h++){
                hfactor = (2*h+1);
                aq2 = 0.0625*leg_q2[h];
                for (unsigned int i = 0; i<npol.ctl; i++){
                    ifactor = (2*i+1);
                    hifactor = hfactor*ifactor;
                    aq2ctl = aq2*leg_ctl[i];
                    for (unsigned int j = 0; j<npol.ctk; j++){
                        jfactor = (2*j+1);
                        hijfactor = hifactor*jfactor;
                        aq2ctlctk = aq2ctl*leg_ctk[j];
                        for (unsigned int k = 0; k<npol.phi; k++){
                            kfactor = (2*k+1);
                            hijkfactor = hijfactor*kfactor;
                            aq2ctlctkphi = aq2ctlctk*leg_phi[k];
                            bini = npol.get_bin_in4D(h,i,j,k);
                            resulti = chijk_moments[bini];
                            if (resulti != 0.0){
                                for (unsigned int l = 0; l<npol.q2; l++){
                                    lfactor = (2*l+1);
                                    bq2 = 0.0625*leg_q2[l];
                                    for (unsigned int m = 0; m<npol.ctl; m++)
                                    {
                                        mfactor = (2*m+1);
                                        lmfactor = lfactor*mfactor;
                                        bq2ctl = bq2*leg_ctl[m];
                                        for (unsigned int n = 0; n<npol.ctk; n++){
                                            nfactor = (2*n+1);
                                            lmnfactor = lmfactor*nfactor;
                                            bq2ctlctk = bq2ctl*leg_ctk[n];
                                            for (unsigned int o = 0; o<npol.phi; o++){
                                                ofactor = (2*o+1);
                                                lmnofactor = lmnfactor*ofactor;
                                                bq2ctlctkphi = bq2ctlctk*leg_phi[o];

                                                binj = npol.get_bin_in4D(l,m,n,o);
                                                resultj = chijk_moments[binj];
                                                if (binj <= bini && resultj != 0.0){
                                                    cov = (wfactor
                                                           *(resulti-hijkfactor*aq2ctlctkphi)
                                                           *(resultj-lmnofactor*bq2ctlctkphi)
                                                           );
                                                    covariance[nsize*bini+binj] += cov;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        for (unsigned int bini = 0; bini<nsize; bini++){
            for (unsigned int binj = 0; binj<nsize; binj++){
                //covariance matrix is symmetric
                if (binj < bini) covariance.at(nsize*binj+bini) = covariance.at(nsize*bini+binj);
            }
        }

        //Print uncertanities in a matrix
        spdlog::info("uncertainties[{0:0.8e}]", nsize);
        for (unsigned int i =0; i<nsize; i++) std::cout << covariance.at(nsize*i+i) << (i < nsize-1 ? ", " : " };");


        //Print covariance matrix
        spdlog::info("Covariance matrix  cov[{0:0.8e}]", nsize*nsize);
        for (unsigned int i =0; i<nsize; i++){
            for (unsigned int j =0; j<nsize; j++){
                std::cout <<covariance.at(nsize*i+j) << ", ";
            }
            std::cout << std::endl;
        }
        std::cout << "};" << std::endl;//needs to be converted into correct range and translated to monomial coefficients? no, should do these transformations after every acceptance randomization

        double correlation = 0.0;
        for (unsigned int i =0; i<nsize; i++){
            for (unsigned int j =0; j<nsize; j++){
                double corr = covariance.at(nsize*i+j)/sqrt(covariance.at(nsize*i+i))/sqrt(covariance.at(nsize*j+j));
                if (fabs(corr) > fabs(correlation) && i != j) correlation = corr;
            }
        }
        spdlog::info("Largest correlation found is {0:f}", correlation );
    }

    spdlog::debug("start CONVERT legendre to monomial coefficients" );
    //transform legendre coefficients to monomial coefficients


    for (unsigned int h = 0; h<npol.q2; h++)
        for (unsigned int i = 0; i<npol.ctl; i++)
            for (unsigned int j = 0; j<npol.ctk; j++)
                for (unsigned int k = 0; k<npol.phi; k++){
                    double tmp = chijk_moments.at(npol.get_bin_in4D(h,i,j,k));

                    std::vector<double> leg_coeffs_q2(npol.q2, 0.0);
                    std::vector<double> leg_coeffs_ctl(npol.ctl, 0.0);
                    std::vector<double> leg_coeffs_ctk(npol.ctk, 0.0);
                    std::vector<double> leg_coeffs_phi(npol.phi, 0.0);

                    std::vector<double> poly_coeffs_q2(npol.q2, 0.0);
                    std::vector<double> poly_coeffs_ctl(npol.ctl, 0.0);
                    std::vector<double> poly_coeffs_ctk(npol.ctk, 0.0);
                    std::vector<double> poly_coeffs_phi(npol.phi, 0.0);

                    leg_coeffs_q2.at(h) = 1.0;
                    leg_coeffs_ctl.at(i) = 1.0;
                    leg_coeffs_ctk.at(j) = 1.0;
                    leg_coeffs_phi.at(k) = 1.0;

                    legendre_to_poly(leg_coeffs_q2, poly_coeffs_q2);
                    legendre_to_poly(leg_coeffs_ctl, poly_coeffs_ctl);
                    legendre_to_poly(leg_coeffs_ctk, poly_coeffs_ctk);
                    legendre_to_poly(leg_coeffs_phi, poly_coeffs_phi);

                    for (unsigned int l = 0; l<=h; l++)
                        for (unsigned int m = 0; m<=i; m++)
                            for (unsigned int n = 0; n<=j; n++)
                                for (unsigned int o = 0; o<=k; o++){
                                    unsigned int bin = npol.get_bin_in4D(l,m,n,o);
                                    poly_moments.at(bin) += tmp*poly_coeffs_q2.at(l)*poly_coeffs_ctl.at(m)*poly_coeffs_ctk.at(n)*poly_coeffs_phi.at(o);
                                }
                }//tested, works
    spdlog::debug( "end CONVERT legendre to monomial coefficients" );
    spdlog::debug( "poly_moments size: {0:d}", poly_moments.size() );

    spdlog::info( "DOLEGENDRE end" );

}

void get_chebyschev(bin_edges edges, npolynom npol,
                    std::vector<event> eventsInQ2bin, double nWeightedEvents,
                    std::vector<double> &poly_moments,
                    bool fejerkernel, bool assumectleven, bool assumePhiEven,
                    bool checkSignificance, bool calculateCovariance
                    ){
    //use Legendre polynomials
    spdlog::info("CHEBYSCHEV start");
    std::vector<double> chijk_moments = std::vector<double>(npol.getSize(), 0.0);

    for (unsigned int h = 0; h<npol.q2; h++)
        for (unsigned int i = 0; i<npol.ctl; i++)
            for (unsigned int j = 0; j<npol.ctk; j++)
                for (unsigned int k = 0; k<npol.phi; k++){
                    double result = 0.0;
                    if (assumectleven && i%2 != 0)result = 0.0;
                    else if (assumePhiEven && k%2 != 0)result = 0.0;
                    else{
                        for (auto &meas: eventsInQ2bin){
                            double q2  = 2.0 * (meas.q2        - edges.q2min ) / edges.range_q2() - 1.0;
                            double ctl = 2.0 * (meas.costhetal - edges.ctlmin) / edges.range_ctl()- 1.0;
                            double ctk = 2.0 * (meas.costhetak - edges.ctkmin) / edges.range_ctk()- 1.0;
                            double phi = 2.0 * (meas.phi       - edges.phimin) / edges.range_phi()- 1.0;

                            result += meas.weight
                                    *fcnc::chebyshev(q2,  h)/sqrt(1.0-q2*q2)*(h == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                    *fcnc::chebyshev(ctl, i)/sqrt(1.0-ctl*ctl)*(i == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                    *fcnc::chebyshev(ctk, j)/sqrt(1.0-ctk*ctk)*(j == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                    *fcnc::chebyshev(phi, k)/sqrt(1.0-phi*phi)*(k == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                    /nWeightedEvents;// /eventsInQ2bin.size();
                        }
                        if (fejerkernel){
                            result *= (1.0 - h/double(npol.q2-1))
                                    *(1.0 - i/double(npol.ctl-1))
                                    *(1.0 - j/double(npol.ctk-1))
                                    *(1.0 - k/double(npol.phi-1));
                        }
                    }

                    //now have the "mean" in result
                    //mu = sum_i w_i/N
                    //sigma(mu) = sqrt(sum_i (w_i-mu)^2)/N
                    //else
                    chijk_moments.push_back(result);
                }
    //check significance
    if (checkSignificance){
        spdlog::info( "start CHECK SIGNIFICANCE for CHEBYSHEV polynoms" );

        for (unsigned int h = 0; h<npol.q2; h++)
            for (unsigned int i = 0; i<npol.ctl; i++)
                for (unsigned int j = 0; j<npol.ctk; j++)
                    for (unsigned int k = 0; k<npol.phi; k++){
                        unsigned int bin = npol.get_bin_in4D(h,i,j,k);
                        double result = chijk_moments.at(bin);
                        double sigma = 0.0;
                        for (auto &meas : eventsInQ2bin){
                            double q2  = 2.0 * (meas.q2        - edges.q2min ) / edges.range_q2() - 1.0;
                            double ctl = 2.0 * (meas.costhetal - edges.ctlmin) / edges.range_ctl()- 1.0;
                            double ctk = 2.0 * (meas.costhetak - edges.ctkmin) / edges.range_ctk()- 1.0;
                            double phi = 2.0 * (meas.phi       - edges.phimin) / edges.range_phi()- 1.0;
                            sigma += sqr(result - meas.weight // /nWeightedEvents
                                         *fcnc::chebyshev(q2, h)/sqrt(1-q2*q2)*(h == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                         *fcnc::chebyshev(ctl, i)/sqrt(1-ctl*ctl)*(i == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                         *fcnc::chebyshev(ctk, j)/sqrt(1-ctk*ctk)*(j == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                         *fcnc::chebyshev(phi, k)/sqrt(1-phi*phi)*(k == 0 ? 1.0/MY_PI : 2.0/MY_PI));// /eventsInQ2bin.size();
                        }
                        sigma = sqrt(sigma);
                        sigma /= nWeightedEvents;
                        if (fabs(result/sigma) < 1.0) chijk_moments.at(bin) = 0.0;
                    }
        spdlog::info( "end CHECK SIGNIFICANCE for CHEBYSHEV polynoms" );
    }
    //printchebyshev
    spdlog::debug( "//vector of chebyshev efficiency coefficients containing {0:d} elements",chijk_moments.size());
    spdlog::trace( "chebyshev_coeffs_eff_4d = { " );
    if (spdlog_trace()){
        for (unsigned int i=0; i<chijk_moments.size(); i++){
            std::cout<< std::setprecision(15) << std::scientific << chijk_moments.at(i) << (i<chijk_moments.size()-1 ? ", " : " ");
        }
    }


    //calculate covariance

    if (calculateCovariance){
        spdlog::debug( "calculating {0:d} by {1:d} convariance matrix ({2:d} doubles)" , npol.getSize(), npol.getSize(), npol.getSize2() );
        std::vector<double> covariance(npol.getSize2(), 0.0);
        for (unsigned int h = 0; h<npol.q2; h++){
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        unsigned int bini = npol.get_bin_in4D(h,i,j,k);
                        double resulti = chijk_moments.at(bini);
                        if (resulti != 0.0){
                            for (unsigned int l = 0; l<npol.q2; l++){
                                for (unsigned int m = 0; m<npol.ctl; m++){
                                    for (unsigned int n = 0; n<npol.ctk; n++){
                                        for (unsigned int o = 0; o<npol.phi; o++){
                                            unsigned int binj = npol.get_bin_in4D(l,m,n,o);
                                            if (binj > bini) continue;//covariance matrix is symmetric
                                            double resultj = chijk_moments.at(binj);
                                            if (((bini*npol.getSize()+binj) * 100) % (npol.getSize2()) == 0)
                                                spdlog::debug( "covariance matrix calculation {0:f}%", ((bini*npol.getSize()+binj) * 100) / (npol.getSize2()));
                                            if (resultj != 0.0){
                                                double cov = 0.0;
                                                for (auto &meas : eventsInQ2bin){
                                                    double q2  = 2.0 * (meas.q2        - edges.q2min ) / edges.range_q2() - 1.0;
                                                    double ctl = 2.0 * (meas.costhetal - edges.ctlmin) / edges.range_ctl()- 1.0;
                                                    double ctk = 2.0 * (meas.costhetak - edges.ctkmin) / edges.range_ctk()- 1.0;
                                                    double phi = 2.0 * (meas.phi       - edges.phimin) / edges.range_phi()- 1.0;


                                                    cov += ((resulti - meas.weight
                                                             *fcnc::chebyshev(q2, h)/sqrt(1-q2*q2)*(h == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                                             *fcnc::chebyshev(ctl, i)/sqrt(1-ctl*ctl)*(i == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                                             *fcnc::chebyshev(ctk, j)/sqrt(1-ctk*ctk)*(j == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                                             *fcnc::chebyshev(phi, k)/sqrt(1-phi*phi)*(k == 0 ? 1.0/MY_PI : 2.0/MY_PI))
                                                            *(resultj - meas.weight
                                                              *fcnc::chebyshev(q2, l)/sqrt(1-q2*q2)*(l == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                                              *fcnc::chebyshev(ctl, m)/sqrt(1-ctl*ctl)*(m == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                                              *fcnc::chebyshev(ctk, n)/sqrt(1-ctk*ctk)*(n == 0 ? 1.0/MY_PI : 2.0/MY_PI)
                                                              *fcnc::chebyshev(phi, o)/sqrt(1-phi*phi)*(o == 0 ? 1.0/MY_PI : 2.0/MY_PI))
                                                            );
                                                }
                                                //cov = sqrt(cov); //to get sigma on diagonal
                                                cov /= sqr(nWeightedEvents);
                                                covariance.at(npol.getSize()*bini+binj) = cov;
                                                covariance.at(npol.getSize()*binj+bini) = cov;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }


        spdlog::info("uncertainties[{0:0.8e}]", npol.getSize());
        if (spdlog_info()){
            for (unsigned int i =0; i<npol.getSize(); i++){
                std::cout <<covariance.at(npol.getSize()*i+i) << (i < npol.getSize()-1 ? ", " : " };");
            }
            std::cout << std::endl;//needs to be converted into correct range and translated to monomial coefficients?
        }

        spdlog::info("Covariance matrix  cov[{0:0.8e}]", npol.getSize2());
        for (unsigned int i =0; i<npol.getSize(); i++){
            for (unsigned int j =0; j<npol.getSize(); j++){
                std::cout << covariance.at(npol.getSize()*i+j) << ", ";
            }
            std::cout << std::endl;
        }
        std::cout <<"};" << std::endl;//needs to be converted into correct range and translated to monomial coefficients? no, should do these transformations after every acceptance randomization
        double correlation = 0.0;
        for (unsigned int i =0; i<npol.getSize(); i++)
            for (unsigned int j =0; j<npol.getSize(); j++)
            {
                double corr = covariance.at(npol.getSize()*i+j)/sqrt(covariance.at(npol.getSize()*i+i))/sqrt(covariance.at(npol.getSize()*j+j));
                if (fabs(corr) > fabs(correlation) && i != j)
                    correlation = corr;
            }
        spdlog::info( "Largest correlation found is {0:f}", correlation );
    }
    //transform chebyshev coefficients to monomial coefficients


    for (unsigned int h = 0; h<npol.q2; h++){
        for (unsigned int i = 0; i<npol.ctl; i++){
            for (unsigned int j = 0; j<npol.ctk; j++){
                for (unsigned int k = 0; k<npol.phi; k++){
                    double c = chijk_moments.at(npol.get_bin_in4D(h,i,j,k));

                    std::vector<double> ch_coeffs_q2(npol.q2, 0.0);
                    std::vector<double> ch_coeffs_ctl(npol.ctl, 0.0);
                    std::vector<double> ch_coeffs_ctk(npol.ctk, 0.0);
                    std::vector<double> ch_coeffs_phi(npol.phi, 0.0);

                    std::vector<double> poly_coeffs_q2(npol.q2, 0.0);
                    std::vector<double> poly_coeffs_ctl(npol.ctl, 0.0);
                    std::vector<double> poly_coeffs_ctk(npol.ctk, 0.0);
                    std::vector<double> poly_coeffs_phi(npol.phi, 0.0);

                    ch_coeffs_q2.at(h) = 1.0;
                    ch_coeffs_ctl.at(i) = 1.0;
                    ch_coeffs_ctk.at(j) = 1.0;
                    ch_coeffs_phi.at(k) = 1.0;

                    chebyshev_to_poly(ch_coeffs_q2, poly_coeffs_q2);
                    chebyshev_to_poly(ch_coeffs_ctl, poly_coeffs_ctl);
                    chebyshev_to_poly(ch_coeffs_ctk, poly_coeffs_ctk);
                    chebyshev_to_poly(ch_coeffs_phi, poly_coeffs_phi);

                    for (unsigned int l = 0; l<=h; l++){
                        for (unsigned int m = 0; m<=i; m++){
                            for (unsigned int n = 0; n<=j; n++){
                                for (unsigned int o = 0; o<=k; o++){
                                    unsigned int bin = npol.get_bin_in4D(l,m,n,o);
                                    poly_moments.at(bin) += c*poly_coeffs_q2.at(l)*poly_coeffs_ctl.at(m)*poly_coeffs_ctk.at(n)*poly_coeffs_phi.at(o);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

}


double integrate_4D(bin_edges edges, int h, int i, int j, int k){
    return  integrate_x_to_n(edges.q2min, edges.q2max,h)//careful with q2max
            *integrate_x_to_n(edges.ctlmin, edges.ctlmax,i)
            *integrate_x_to_n(edges.ctkmin, edges.ctkmax,j)
            *integrate_x_to_n(edges.phimin, edges.phimax,k);
}

double integrate_3D(bin_edges edges, int i, int j, int k){
    return  integrate_x_to_n(edges.ctlmin, edges.ctlmax,i)
            *integrate_x_to_n(edges.ctkmin, edges.ctkmax,j)
            *integrate_x_to_n(edges.phimin, edges.phimax,k);
}

void fill_moments_hist_1D(TH1D *hist_moments, std::vector<double> coeffs_moments,
                          bin_edges edges, npolynom npol, double q2low, double q2high){

    bool do_ctl = (std::string(hist_moments->GetName()).find("ctl") != std::string::npos);
    bool do_ctk = (std::string(hist_moments->GetName()).find("ctk") != std::string::npos);
    bool do_phi = (std::string(hist_moments->GetName()).find("phi") != std::string::npos);
    bool do_q2 = (std::string(hist_moments->GetName()).find("q2") != std::string::npos);

    for (int l=1; l<=hist_moments->GetNbinsX(); l++){
        double a = hist_moments->GetBinLowEdge(l);
        double b = hist_moments->GetBinLowEdge(l+1);
        double integral = 0.0;
        for (unsigned int h = 0; h<npol.q2; h++){
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        double result = coeffs_moments.at(npol.get_bin_in4D(h,i,j,k))
                                *(pow(do_q2 ? b : q2high ,h+1)/(h+1.0) - pow(do_q2 ? a : q2low   ,h+1)/(h+1.0)) //Possibly replace by get_result;
                                *(pow(do_ctl ? b : edges.ctlmax,i+1)/(i+1.0) - pow(do_ctl ? a : edges.ctlmin,i+1)/(i+1.0))
                                *(pow(do_ctk ? b : edges.ctkmax,j+1)/(j+1.0) - pow(do_ctk ? a : edges.ctkmin,j+1)/(j+1.0))
                                *(pow(do_phi ? b : edges.phimax,k+1)/(k+1.0) - pow(do_phi ? a : edges.phimin,k+1)/(k+1.0));
                        integral += result;


                    }
                }
            }
        }
        hist_moments->SetBinContent(l, integral);
    }
    return;

}


void fill_moments_hist_2D(TH2D *hist_moments, std::vector<double> coeffs_moments,
                          bin_edges edges, npolynom npol, double q2low, double q2high){

    bool do_ctl = (std::string(hist_moments->GetName()).find("ctl") != std::string::npos);
    bool do_ctk = (std::string(hist_moments->GetName()).find("ctk") != std::string::npos);
    bool do_phi = (std::string(hist_moments->GetName()).find("phi") != std::string::npos);
    bool do_q2 = (std::string(hist_moments->GetName()).find("q2") != std::string::npos);

    spdlog::trace("do_ctl\t"+boolToString(do_ctl));
    spdlog::trace("do_ctk\t"+boolToString(do_ctk));
    spdlog::trace("do_phi\t"+boolToString(do_phi));
    spdlog::trace("do_q2\t"+boolToString(do_q2));
    bool is_ctl_First = !do_q2;             //ctl is second if q2
    bool is_ctk_First = !(do_q2 || do_ctl); //ctk is second if ctl or q2
    //phi is always second and q2 always first
    spdlog::trace("is_ctl_First\t"+boolToString(is_ctl_First));
    spdlog::trace("is_ctk_First\t"+boolToString(is_ctk_First));

    for (int l=1; l<=hist_moments->GetNbinsX(); l++){
        double a = hist_moments->GetXaxis()->GetBinLowEdge(l);
        double b = hist_moments->GetXaxis()->GetBinLowEdge(l+1);
        for (int m=1; m<=hist_moments->GetNbinsY(); m++){
            double c = hist_moments->GetYaxis()->GetBinLowEdge(m);
            double d = hist_moments->GetYaxis()->GetBinLowEdge(m+1);
            double integral = 0.0;
            for (unsigned int h = 0; h<npol.q2; h++){
                for (unsigned int i = 0; i<npol.ctl; i++){
                    for (unsigned int j = 0; j<npol.ctk; j++){
                        for (unsigned int k = 0; k<npol.phi; k++){
                            double result = coeffs_moments.at(npol.get_bin_in4D(h,i,j,k))
                                    *integrate_x_to_n(do_q2 ? a : q2low, do_q2 ? b : q2high ,h)
                                    *integrate_x_to_n(do_ctl ? (is_ctl_First ? a : c) : edges.ctlmin,
                                                      do_ctl ? (is_ctl_First ? b : d) : edges.ctlmax,i)
                                    *integrate_x_to_n(do_ctk ? (is_ctk_First ? a : c) : edges.ctkmin,
                                                      do_ctk ? (is_ctk_First ? b : d) : edges.ctkmax, j)
                                    *integrate_x_to_n(do_phi ? c : edges.phimin,do_phi ? d : edges.phimax,k);
                            integral += result;
                        }
                    }
                }
            }
            spdlog::trace("\t*** fill_moments_hist_2D: ***\t´");
            spdlog::trace("l: {0:d}\tm: {1:d}\tint: {2:f}", l, m, integral);
            hist_moments->SetBinContent(l, m, integral);
        }
    }
    return;

}


int get_correct_index(std::string whichVar, int l, int m, int n, int o){
    if (whichVar == "q2") return l;
    if (whichVar == "ctl") return m;
    if (whichVar == "ctk") return n;
    if (whichVar == "phi") return o;
    return -1;
}
int change_index(std::string whichVar, npolynom npol, int p, int l, int m, int n, int o){
    if (whichVar == "q2") return  npol.get_bin_in4D(p,m,n,o);
    if (whichVar == "ctl") return npol.get_bin_in4D(l,p,n,o);
    if (whichVar == "ctk") return npol.get_bin_in4D(l,m,p,o);
    if (whichVar == "phi") return npol.get_bin_in4D(l,m,n,p);
    return -1;
}
void correct_coefficients(npolynom npol, bin_edges edges, std::vector<double> &out_poly_moments, std::vector<double> in_poly_moments, std::string whichVar){
    //Just rebin
    unsigned int  npol_var;
    double intvar_min;
    double intvar_max;
    if (whichVar == "q2"){ //If this would be na efin array, my life would be orders of magnitute easier
        npol_var = npol.q2;
        intvar_min = edges.q2min;
        intvar_max = edges.q2max;
    }
    else if (whichVar == "ctl") {
        npol_var = npol.ctl;
        intvar_min = edges.ctlmin;
        intvar_max = edges.ctlmax;
    }
    else if (whichVar == "ctk") {
        npol_var = npol.ctk;
        intvar_min = edges.ctkmin;
        intvar_max = edges.ctkmax;
    }
    else if (whichVar == "phi") {
        npol_var = npol.phi;
        intvar_min = edges.phimin;
        intvar_max = edges.phimax;
    }
    else{
        spdlog::error("Wrong index name! Either q2, ctl, ctk or phi!");
        assert (0);
    }
    spdlog::debug("npol_var = {0:d}\tintvar_min = {1:f}\tintvar_max = {2:f}", npol_var, intvar_min, intvar_max);
    for (unsigned int l = 0; l<npol.q2; l++){
        for (unsigned int m = 0; m<npol.ctl; m++){
            for (unsigned int n = 0; n<npol.ctk; n++){
                for (unsigned int o = 0; o<npol.phi; o++){
                    unsigned int bin = npol.get_bin_in4D(l,m,n,o);

                    spdlog::trace("l={0:d}\tm={1:d}\tn={2:d}\to={3:d}\t bin ={4:d}",l,m,n,o,bin);
                    std::vector<double> poly(npol_var, 0.0);
                    poly.at(get_correct_index(whichVar,l,m,n,o)) = in_poly_moments.at(bin);
                    std::vector<double> correct(npol_var, 0.0);
                    correct_poly(poly, correct, intvar_min, intvar_max);
                    for (unsigned int p = 0; p<npol_var; p++){
                        unsigned int tobin = change_index(whichVar,npol,p,l,m,n,o);
                        spdlog::trace("p={0:d}\t tobin ={1:d}",p,tobin);
                        out_poly_moments.at(tobin) += correct.at(p);
                    }
                }
            }
        }
    }
    return;
}


void bu2kstarmumu_pdf::parametrize_eff_phsp_4d(const std::vector<event>& events, values* globalvalues, int tagNumber,  bool assumePhiEven, bool checkSignificance, bool runMinuit, bool checkFactorization, bool do3Dmoments){

    spdlog::debug("----> start parametrization of angular acceptance correction coefficients with {0:d} events.", events.size());

    const UInt_t nBinsQ2     = 40;
    const UInt_t nBinsAngles = 40;

    bool ignore_q2weights = false;
    if (ignore_q2weights) spdlog::warn("Not using weights from genLvl PHSP to flatten the q2 distribution!!!");

    //Get the pointer to this class of bu2kstarmumu_pdf
    current_pdf = this;

    std::vector<double> q2binsmin = opts->TheQ2binsmin; //Check TheQ2binsmax and q2binsmin
    std::vector<double> q2binsmax = opts->TheQ2binsmax;
    spdlog::debug("Vector q2binsmin:\t"+convert_vector_to_string(q2binsmin));
    spdlog::debug("Vector q2binsmax:\t"+convert_vector_to_string(q2binsmax));

    //name tag for all plots:
    std::string appendix = "_"+DECAY_NAME
            + (opts->KS ? opts->DDLL+"_" : "_")
            + "Run" + std::to_string(opts->run)
            + ((opts->angacccorrperyear && opts->year != -1) ? "_"+std::to_string(opts->year) : "")
            + ((opts->folding != -1) ? "_folding"+std::to_string(opts->folding) : "")
            + ((opts->systematic == 3) ? "_"+std::to_string(opts->orderincrease)+"moreOrders" : "")
            + std::string(tagNumber != 0 ? "_"+std::to_string(tagNumber) : "");
    std::string subfolder = "plots/angular/" + std::string(tagNumber != 0 ? "test/" : "");
    spdlog::debug("Saving all plots into folder " + subfolder);

    //Set orders of chebyshev/legendre polynomials
    npolynom npol(opts); //Should be a const, but then all the future shuffling gets difficult

    //fold the events if needed:
    spdlog::debug("Init folder....");
//TODO remove    fcnc::folder fldr(opts);
    //only accept events inside q2min and q2max
    std::vector<event> filtered_events;
    //std::vector<event> fullrange_events;
    unsigned int ee;
    unsigned int N = events.size();
    TRandom3 * rnd = new TRandom3(15*opts->job_id);

    spdlog::debug("Filtering events...");
    for (unsigned int e=0; e<N; e++){
        //choose random events for bootstrapping
        if (opts->systematic == 1)  ee = int(rnd->Rndm() * N);
        else  ee = e;   //or the correct, nominal order
        fcnc::event evt = events.at(ee);
        if (!isEvtInAngleRange(&(evt), opts)) continue;
        filtered_events.push_back(evt);
        if(!opts->full_angular) fldr->fold(&filtered_events.back());
    }

    if (!ignore_q2weights){
        spdlog::debug("Getting q2 weights...");
        //weight for all events, reweight from PHSP q2 model to flat q2 distribution using histogram
        TFile* fphspweightq2 = new TFile(CONFIG_PHSP_WEIGHT.c_str());
        TH1D* phspweightq2 = dynamic_cast<TH1D*>(fphspweightq2->Get(opts->IsFlatQ2 ? "flatq2weights" : "phspweightq2"));

        bool keep_weights = opts->systematic != 2;
        bool vary_weights = opts->systematic == 4;

        //reweight filtered events
        for (auto evt: filtered_events){
            double q2 = evt.q2;

            if(vary_weights){
                evt.weight += evt.delta_weight * rnd->Gaus(0., 1.);
            }

            if (q2 < 0.2){
                if(keep_weights) evt.weight *= phspweightq2->GetBinContent(2);//keep loaded totalweight!
                else             evt.weight = phspweightq2->GetBinContent(2);
            }
            else{
                int bin = phspweightq2->FindFixBin(q2);
                if (bin >=1 && bin <= phspweightq2->GetNbinsX()){
                    if(keep_weights) evt.weight *= phspweightq2->GetBinContent(bin);//keep loaded totalweight!
                    else             evt.weight = phspweightq2->GetBinContent(bin);
                }
            }
        }

        delete phspweightq2;
        fphspweightq2->Close();
        delete fphspweightq2;
    }

    //for systematic 9: trigger weights, multiply existing weights by 1/L0Muon_eff
    if(opts->systematic == 9){
        spdlog::debug("Getting systematic 9");
        //get years from current data-set:
        std::vector<int> years; //empty vector
        for (auto event: filtered_events){
            bool year_found = false;
            for(auto yr: years){
                if(yr == event.year){
                    year_found = true;
                    break;
                }
            }
            if(!year_found) years.push_back(event.year); //First event always gets filled
            //Technically one should make an if to check it is all years, but given the size of the event sample this is okayish
        }
        assert(years.size());
        std::sort(years.begin(), years.end());

        //get L0Muon efficiency histo(s)
        TFile * f_L0_w[years.size()];
        TH1D * h_L0_w[years.size()];
        for(unsigned int y = 0; y < years.size(); y++){
            f_L0_w[y] = new TFile((std::string("config/")+(opts->KS?"KshortPiplus":"KplusPi0Resolved")+"_L0MuonEfficiency_"+std::to_string(years.at(y))+".root").c_str(), "READ");
            if(!f_L0_w[y]->IsOpen()){
                spdlog::critical( "Could not find file with L0Muon efficiencies: 'config/" + std::string(opts->KS ? "KshortPiplus" : "KplusPi0Resolved") + "_L0MuonEfficiency_{0:d}.root'", years.at(y) );
                assert(0);
            }
            h_L0_w[y] = dynamic_cast<TH1D*>(f_L0_w[y]->Get("L0MuonEffRatioPHSP"));
            spdlog::info("Succesfully loaded L0Muon efficiency reweighting histogram for year={0:d}", years.at(y));
        }

        for (unsigned int i=0; i<filtered_events.size(); i++){
            double max_mu_pt = filtered_events.at(i).max_mu_pt;
            unsigned int year_idx = 0;
            for(unsigned int y = 0; y < years.size(); y++){
                if(years.at(y) == filtered_events.at(i).year){
                    year_idx = y;
                    break;
                }
            }
            if(max_mu_pt > 20000.){
                filtered_events.at(i).weight *= h_L0_w[year_idx]->GetBinContent(h_L0_w[year_idx]->GetNbinsX());
            }
            else{
                int bin = h_L0_w[year_idx]->FindFixBin(max_mu_pt);
                filtered_events.at(i).weight *= h_L0_w[year_idx]->GetBinContent(bin);
            }
        }

        for(unsigned int y = 0; y < years.size(); y++){
            delete h_L0_w[y];
            f_L0_w[y]->Close();
            delete f_L0_w[y];
        }
    }

    //set the pointer that is accessible from the minuit fcn
    eff_events = filtered_events;

    //make plot to check weight distribution if running a test
    if(tagNumber >0){
        spdlog::debug("Plotting weights");
        TH1D* hweight = new TH1D("hweight", ";w;", 100, 0.0, 1.0/EFF_CUTOFF);
        for (unsigned int i=0; i<filtered_events.size(); i++){
            spdlog::trace("Weight of event {0:d}\t\t{1:f}", i , filtered_events.at(i).weight);
            hweight->Fill(filtered_events.at(i).weight);
        }
        TCanvas * cweights = new TCanvas();
        cweights->cd();
        hweight->Draw("");
        if(opts->write_eps)cweights->Print((subfolder + "weights"+appendix+".eps").c_str(), "eps");
        if(opts->write_eps)cweights->Print((subfolder + "weights"+appendix+".root").c_str(), "root");

        delete hweight;
        delete cweights;
    }

    //---------------------------------------
    //DETERMINATION FROM ACCEPTANCE HISTOGRAM
    //---------------------------------------
    unsigned int nq2bins  = 18;
    unsigned int nctlbins = 10;
    unsigned int nctkbins = 10;
    unsigned int nphibins = 5;

    bool adapt_bins_to_folding = false;
    if(adapt_bins_to_folding){
        nphibins /= 2;
        if(opts->folding > 0)nctlbins /= 2;
    }

    spdlog::debug("Options: ");
    spdlog::debug("q2min={0:f}\tq2max={1:f}", opts->TheQ2binsmin.front(),opts->TheQ2binsmax.back());
    spdlog::debug("ctkmin={0:f}\tctkmax={1:f}", opts->ctk_min, opts->ctk_max);
    spdlog::debug("ctlmin={0:f}\tctlmax={1:f}", opts->ctl_min, opts->ctl_max);
    spdlog::debug("phimin={0:f}\tphimax={1:f}", opts->phi_min, opts->phi_max);
    bin_edges edges (opts, nq2bins, nctlbins, nctkbins, nphibins);
    const std::vector<double> q2_edges = edges.edges_q2();
    const std::vector<double> ctl_edges = edges.edges_ctl();
    const std::vector<double> ctk_edges = edges.edges_ctk();
    const std::vector<double> phi_edges = edges.edges_phi(); //This could've been a struct/class

    spdlog::trace("q2 edges: " + convert_vector_to_string(q2_edges));
    spdlog::trace("ctl edges: " + convert_vector_to_string(ctl_edges));
    spdlog::trace("ctk edges: " + convert_vector_to_string(ctk_edges));
    spdlog::trace("phi edges: " + convert_vector_to_string(phi_edges));


    //should compare full 4D with
    //3D in q2 bins
    //3D factorizing in bins of q2

    spdlog::debug("Getting number of weighted events...");
    std::vector<double> eps_hist(edges.getNbins(), 0.0);
    double nWeightedEvents = 0.0;
    for (auto &meas: filtered_events){
        unsigned int bin = get_bin_in4D(meas,edges);
        eps_hist.at(bin) += meas.weight;
        nWeightedEvents += meas.weight;
    }

    //rescale efficiency
    spdlog::debug("Rescaling efficiency...");
    for (unsigned int h = 0; h<edges.nq2bins; h++){
        for (unsigned int i = 0; i<edges.nctlbins; i++){
            for (unsigned int j = 0; j<edges.nctkbins; j++){
                for (unsigned int k = 0; k<edges.nphibins; k++){
                    eps_hist.at(get_bin_in4D(h,i,j,k,edges)) *= edges.getNbins()/nWeightedEvents;
                }
            }
        }
    }

    //determine xis in bins of q2
    spdlog::debug("Getting xis in bins of q2...");
    std::vector<TH1D*> hxi_hist;
    for (auto angObs: get_angObser_withTeX_vec()){
        hxi_hist.push_back(new TH1D(("hxi"+angObs[0]+"hist").c_str(), (";q^{2};"+angObs[1]).c_str(),
                edges.nq2bins, edges.q2min, edges.q2max));
    }

    std::vector<int>int_xi(hxi_hist.size(),0);
    for (unsigned int h = 0; h<edges.nq2bins; h++){
        std::fill(int_xi.begin(), int_xi.end(), 0); //fill int_xi with zeroes
        for (unsigned int i = 0; i<edges.nctlbins; i++)
            for (unsigned int j = 0; j<edges.nctkbins; j++)
                for (unsigned int k = 0; k<edges.nphibins; k++){
                    unsigned int bin = get_bin_in4D(h,i,j,k,edges);
                    double eps = eps_hist.at(bin);
                    double f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12;
                    if(opts->full_angular){
                        integrated_fj_noacc(ctl_edges[i], ctl_edges[i+1],
                                ctk_edges[j], ctk_edges[j+1],
                                phi_edges[k], phi_edges[k+1],
                                f1, f2, f3, f4, f5, f6,
                                f7, f8, f9, f10, f11, f12);
                    }
                    else{
                        folded_integrated_fj_noacc(ctl_edges[i], ctl_edges[i+1],
                                ctk_edges[j], ctk_edges[j+1],
                                phi_edges[k], phi_edges[k+1],
                                f1, f2, f3, f4, f5, f6,
                                f7, f8, f9, f10, f11, f12);
                    }
                    int_xi[0] += eps*f1; //KILL ME
                    int_xi[1] += eps*f2;
                    int_xi[2] += eps*f3;
                    int_xi[3] += eps*f4;
                    int_xi[4] += eps*f5;
                    int_xi[5] += eps*f6;
                    int_xi[6] += eps*f7;
                    int_xi[7] += eps*f8;
                    int_xi[8] += eps*f9;
                    int_xi[9] += eps*f10;
                    int_xi[10] += eps*f11;
                    int_xi[11] += eps*f12;
                }
        for_indexed(auto hist: hxi_hist) hist->SetBinContent(h+1,int_xi[i]);
    }

    //-----------------------------------
    //DETERMINE XIS USING UNBINNED METHOD
    //-----------------------------------

    spdlog::debug("Getting xis using unbinned method...");
    std::vector<TH1D*> hxi_ub;
    for (auto angObs: get_angObser_withTeX_vec()){
        hxi_ub.push_back(new TH1D(("hxi"+angObs[0]+"ub").c_str(), (";q^{2};"+angObs[1]).c_str(),
                edges.nq2bins, edges.q2min, edges.q2max));
    }

    //make it pretty
    for (auto hist: hxi_ub) hist->SetLineColor(4);

    for (unsigned int h = 0; h<edges.nq2bins; h++){
        std::fill(int_xi.begin(), int_xi.end(), 0); //fill int_xi with zeroes, no need to initialize (see line 961)
        //double q2a = hxi_ub[0]->GetBinLowEdge(h); //check this is the same as q2_edges!!!!! should be, but to be sure
        //double q2b = hxi_ub[0]->GetBinLowEdge(h+1);
        for (auto &&meas: filtered_events){
            if (meas.q2 < q2_edges[h] || meas.q2 > q2_edges[h]) continue;
            double f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12;
            if(opts->full_angular){
                fj(meas.costhetal, meas.costhetak, meas.phi,
                   f1, f2, f3, f4, f5, f6,
                   f7, f8, f9, f10, f11, f12);
            }
            else{
                folded_fj(meas.costhetal, meas.costhetak, meas.phi,
                          f1, f2, f3, f4, f5, f6,
                          f7, f8, f9, f10, f11, f12);
            }

            double weight = meas.weight/nWeightedEvents*edges.nq2bins*(edges.range_ctl()*edges.range_ctk()*edges.range_phi());
            int_xi[0] += f1*weight; //KILL ME
            int_xi[1] += f2*weight;
            int_xi[2] += f3*weight;
            int_xi[3] += f4*weight;
            int_xi[4] += f5*weight;
            int_xi[5] += f6*weight;
            int_xi[6] += f7*weight;
            int_xi[7] += f8*weight;
            int_xi[8] += f9*weight;
            int_xi[9] += f10*weight;
            int_xi[10] += f11*weight;
            int_xi[11] += f12*weight;
        }

        for_indexed(auto hist: hxi_ub) hist->SetBinContent(h,int_xi[i]);

    }

    //------------------------------------------------------
    //DETERMINE POLYNOMIAL ACCEPTANCE WITH METHOD OF MOMENTS
    //------------------------------------------------------
    const bool calculateCovariance = false;
    const bool assumectleven       = opts->systematic == 2;
    const bool fejerkernel         = false; //see Alexander Weisse, Holger Feshke, Chebyshev expansion techniques
    const bool doLegendre          = true; //somehow, chebyshev is broken now, great job me

    std::vector<double> poly_moments(npol.getSize(), 0.0);

    if (doLegendre){
        get_legendre(edges, npol,
                     filtered_events, nWeightedEvents,
                     poly_moments,
                     fejerkernel, assumectleven, assumePhiEven,
                     checkSignificance, calculateCovariance);
    }
    else{
        get_chebyschev(edges, npol,
                       filtered_events, nWeightedEvents,
                       poly_moments,
                       fejerkernel, assumectleven, assumePhiEven,
                       checkSignificance, calculateCovariance);

    }

    spdlog::debug("Getting correct polynomial coefficients in cos(thetak) and cos(thetal)");
    spdlog::debug("poly_moments size:{0:d}", poly_moments.size());
    //correct coefficients from -1 +1 to ctlmin .. ctlmax for ctl, bit difficult, requires binomial coefficients
    std::vector<double> poly_moments_ctl(npol.getSize(), 0.0);
    std::vector<double> poly_moments_ctk(npol.getSize(), 0.0);
    std::vector<double> poly_moments_phi(npol.getSize(), 0.0);
    std::vector<double> poly_moments_q2(npol.getSize(), 0.0);

    correct_coefficients(npol,edges,poly_moments_ctl,poly_moments, "ctl");
    correct_coefficients(npol,edges,poly_moments_ctk,poly_moments_ctl, "ctk");
    correct_coefficients(npol,edges,poly_moments_phi,poly_moments_ctk, "phi");
    correct_coefficients(npol,edges,poly_moments_q2, poly_moments_phi, "q2");

    spdlog::debug("Getting moments....");

    std::vector<double> coeffs_moments(poly_moments_q2);
    //calculate norm after full integration
    double full_norm = 0.0;
    for (unsigned int h = 0; h<npol.q2; h++){
        for (unsigned int i = 0; i<npol.ctl; i++){
            for (unsigned int j = 0; j<npol.ctk; j++){
                for (unsigned int k = 0; k<npol.phi; k++){
                    full_norm += coeffs_moments.at(npol.get_bin_in4D(h,i,j,k))*integrate_4D(edges, h, i, j, k);
                }
            }
        }
    }
    //assume a flat efficiency of 1.0 -> scale by edges.range_q2()*RANGE_3D()/full_norm
    for (unsigned int h = 0; h<npol.q2; h++){
        for (unsigned int i = 0; i<npol.ctl; i++){
            for (unsigned int j = 0; j<npol.ctk; j++){
                for (unsigned int k = 0; k<npol.phi; k++){
                    coeffs_moments.at(npol.get_bin_in4D(h,i,j,k)) *= edges.range_q2()*RANGE_3D()/full_norm;
                }
            }
        }
    }
    //print to std::out

    if (spdlog_trace()){
        spdlog::trace( "//vector of efficiency coefficients containing {0:d} elements", coeffs_moments.size());
        spdlog::trace( "coeffs_eff_4d = { " );
        for (unsigned int i=0; i<coeffs_moments.size(); i++){
            std::cout << std::setprecision(5) << std::scientific  << coeffs_moments.at(i)<< (i<coeffs_moments.size()-1 ? ", " : " ");
        }
    }
    //set this as default coefficients now
    coeffs_eff_4d = coeffs_moments;

    //calculate the integrated angular terms int f_i dOmega (q2) = xi(q2)
    std::vector<TH1D*> hxi_moments;
    for (auto &angObs: get_angObser_withTeX_vec()){
        hxi_moments.push_back(new TH1D(("hxi"+angObs[0]+"_moments").c_str(), (";q^{2};"+angObs[1]).c_str(),
                edges.nq2bins, edges.q2min, edges.q2max));
    }

    for (int l=1; l<=hxi_moments[0]->GetNbinsX(); l++) {
        double a = hxi_moments[0]->GetXaxis()->GetBinLowEdge(l);
        double b = hxi_moments[0]->GetXaxis()->GetBinLowEdge(l+1);
        std::vector<double> integral_xi(hxi_moments.size(),0.0);

        for (unsigned int h = 0; h<npol.q2; h++){
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        unsigned int bin =npol.get_bin_in4D(h, i, j, k);
                        double coeff = C_PWAVE*coeffs_moments.at(bin)*pow(0.5*(a+b), h);
                        //f1 = c * sinthetak2; //j1s
                        integral_xi[0] += coeff
                                * integrate_x_to_n(edges.ctlmin, edges.ctlmax, i)
                                *(integrate_x_to_n(edges.ctkmin, edges.ctkmax, j) - integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2))
                                * integrate_x_to_n(edges.phimin, edges.phimax,k);
                        //f2 = c * costhetak2; //j1c
                        integral_xi[1] += coeff
                                *integrate_x_to_n(edges.ctlmin, edges.ctlmax, i)
                                *integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2)
                                *integrate_x_to_n(edges.phimin, edges.phimax,k);
                        //f3 = c * sinthetak2*cos2thetal; //j2s
                        integral_xi[2] += coeff
                                *(2.0*integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+2)-integrate_x_to_n(edges.ctlmin, edges.ctlmax, i))
                                *(integrate_x_to_n(edges.ctkmin, edges.ctkmax, j)-integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2))
                                *integrate_x_to_n(edges.phimin, edges.phimax,k);
                        //f4 = c * costhetak2*cos2thetal; //j2c
                        integral_xi[3] += coeff
                                *(2.0*integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+2)-integrate_x_to_n(edges.ctlmin, edges.ctlmax, i))
                                *integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2)
                                *integrate_x_to_n(edges.phimin, edges.phimax,k);
                        //f5 = c * sinthetak2*sinthetal2*cos(2.0*phi); //j3
                        integral_xi[4] += coeff
                                *(integrate_x_to_n(edges.ctlmin, edges.ctlmax, i)-integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+2))
                                *(integrate_x_to_n(edges.ctkmin, edges.ctkmax, j)-integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2))
                                *integrate_x_to_n_times_cos_2x(edges.phimin, edges.phimax,k);
                        //f6 = c * sin2thetak*sin2thetal*cos(phi); //j4
                        integral_xi[5] += coeff
                                *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctlmin, edges.ctlmax, i+1)
                                *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctkmin, edges.ctkmax, j+1)
                                *integrate_x_to_n_times_cos_x(edges.phimin, edges.phimax,k);
                        //f7 = c * sin2thetak*sinthetal*cos(phi); //j5
                        integral_xi[6] += coeff
                                *integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctlmin, edges.ctlmax, i)
                                *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctkmin, edges.ctkmax, j+1)
                                *integrate_x_to_n_times_cos_x(edges.phimin, edges.phimax,k);
                        //f8 = c * sinthetak2*costhetal; //j6s
                        integral_xi[7] += coeff
                                *integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+1)
                                *(integrate_x_to_n(edges.ctkmin, edges.ctkmax, j)-integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2))
                                *integrate_x_to_n(edges.phimin, edges.phimax,k);
                        //f9 = c * costhetak2*costhetal; //j6c
                        integral_xi[8] += coeff
                                *integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+1)
                                *integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2)
                                *integrate_x_to_n(edges.phimin, edges.phimax,k);
                        //f10 = c * sin2thetak*sinthetal*sin(phi); //j7
                        integral_xi[9] += coeff
                                *integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctlmin, edges.ctlmax, i)
                                *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctkmin, edges.ctkmax, j+1)
                                *integrate_x_to_n_times_sin_x(edges.phimin, edges.phimax,k);
                        //f11 = c * sin2thetak*sin2thetal*sin(phi); //j8
                        integral_xi[10] += coeff
                                *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctlmin, edges.ctlmax, i+1)
                                *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctkmin, edges.ctkmax, j+1)
                                *integrate_x_to_n_times_sin_x(edges.phimin, edges.phimax,k);
                        //f12 = c * sinthetak2*sinthetal2*sin(2.0*phi); //j9
                        integral_xi[11] += coeff
                                *(integrate_x_to_n(edges.ctlmin, edges.ctlmax, i) - integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+2))
                                *(integrate_x_to_n(edges.ctkmin, edges.ctkmax, j) - integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2))
                                *integrate_x_to_n_times_sin_2x(edges.phimin, edges.phimax,k);
                    }
                }
            }
        }
        for_indexed(auto hist: hxi_moments) hist->SetBinContent(l, is_param_folded(i,opts) ? 0.0 : integral_xi[i]);
    }


    //-----------------------------------------------------
    //DETERMINE POLYNOMIAL ACCEPTANCE USING UNBINNED ML FIT
    //-----------------------------------------------------
    std::vector<TH1D*> hxi_ml_hist;
    for (auto &angObs: get_angObser_withTeX_vec()){
        hxi_ml_hist.push_back(new TH1D(("hxi"+angObs[0]+"_ml").c_str(), (";q^{2};"+angObs[1]).c_str(),
                nBinsQ2, edges.q2min, edges.q2max));
    }
    for (auto &hist: hxi_ml_hist){
        hist->SetLineColor(3);
    }

    //possible TODO for when you're bored: move into it's own function
    //Running minuit is expensive, use only if necesary!
    if (runMinuit){
        spdlog::info("Running Minuit....");
        //setup minuit
        TMinuit* minuit_eff = new TMinuit(10000);
        int errorcode;
        minuit_eff->SetFCN(&(eff_fcn_phsp_4d));
        double strategy_list[1] = {2.0};
        minuit_eff->mnexcm("SET STR", strategy_list, 1, errorcode);
        minuit_eff->SetMaxIterations(1000000);
        minuit_eff->SetErrorDef(1.0);
        const bool second_run = false;
        //define params  (could also feed with starting values from method of moments)
        unsigned int q2_limit = 6;
        unsigned int ctl_limit = 3;
        unsigned int ctk_limit = 4;
        unsigned int phi_limit = 1;
        for (unsigned int h = 0; h<npol.q2; h++){
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        std::ostringstream sout;
                        sout << "c" << h << i << j << k;
                        if (h==0 && i==0 && j==0 && k==0){
                            minuit_eff->DefineParameter(npol.get_bin_in4D(h,i,j,k), sout.str().c_str(), 1.0, 0.0, 0.0, 0.0);
                        }
                        else if (h >= q2_limit || i >= ctl_limit || j >= ctk_limit || k >= phi_limit){
                            //this is to be able to do higher orders with the moments method and lower ones with minuit
                            minuit_eff->DefineParameter(npol.get_bin_in4D(h,i,j,k), sout.str().c_str(), 0.0, 0.0, 0.0, 0.0);
                        }
                        else if ((second_run && i == 0 && k == 0)|| (!second_run && i % 2 == 0 && k % 2 == 0)){
                            minuit_eff->DefineParameter(npol.get_bin_in4D(h,i,j,k), sout.str().c_str(), 0.0, 0.001, 0.0, 0.0);
                        }
                        else{
                            minuit_eff->DefineParameter(npol.get_bin_in4D(h,i,j,k), sout.str().c_str(), 0.0, 0.0, 0.0, 0.0);
                        }
                    }
                }
            }
        }
        double migrad_options[2] = {100000000.0, 0.1}; //{max calls, tolerance}
        spdlog::info( "Running Migrad to determine eff shape" );
        minuit_eff->mnset();
        minuit_eff->mnexcm("MIG", migrad_options, 2, errorcode);

        if (second_run){
            spdlog::info( "Running Migrad a second time releasing parameters" );
            //second run releasing some parameters
            for (unsigned int h = 0; h<npol.q2; h++)
                for (unsigned int i = 0; i<npol.ctl; i++)
                    for (unsigned int j = 0; j<npol.ctk; j++)
                        for (unsigned int k = 0; k<npol.phi; k++){
                            std::ostringstream sout;
                            sout << "c" << h << i << j << k;
                            if ((i > 0 || k > 0) && i % 2 == 0 && k % 2 == 0
                                    && h < q2_limit && i < ctl_limit && j < ctk_limit && k < phi_limit
                                    )// && (i+j+k)<=4)
                                minuit_eff->DefineParameter(npol.get_bin_in4D(h,i,j,k), sout.str().c_str(), 0.0, 0.001, 0.0, 0.0);
                        }
            spdlog::info( "Running Migrad to determine eff shape" );
            minuit_eff->mnexcm("MIG", migrad_options, 2, errorcode);
        }
        //extract fitted parameters
        spdlog::debug("Extracting fitted parameters.");
        std::vector<double> chijk_ml;
        for (unsigned int h = 0; h<npol.q2; h++)
            for (unsigned int i = 0; i<npol.ctl; i++)
                for (unsigned int j = 0; j<npol.ctk; j++)
                    for (unsigned int k = 0; k<npol.phi; k++){
                        double v,e;
                        minuit_eff->GetParameter(npol.get_bin_in4D(h,i,j,k), v, e);
                        chijk_ml.push_back(v);
                    }
        delete minuit_eff;
        spdlog::info( "Minuit finished" );

        //transform chebyshev coefficients to monomial coefficients
        std::vector<double> poly_ml(npol.getSize(), 0.0);
        for (unsigned int h = 0; h<npol.q2; h++)
            for (unsigned int i = 0; i<npol.ctl; i++)
                for (unsigned int j = 0; j<npol.ctk; j++)
                    for (unsigned int k = 0; k<npol.phi; k++){
                        double c = chijk_ml.at(npol.get_bin_in4D(h,i,j,k));
                        std::vector<double> ch_coeffs_q2(npol.q2, 0.0);
                        std::vector<double> ch_coeffs_ctl(npol.ctl, 0.0);
                        std::vector<double> ch_coeffs_ctk(npol.ctk, 0.0);
                        std::vector<double> ch_coeffs_phi(npol.phi,0.0);
                        ch_coeffs_q2.at(h) = 1.0;
                        ch_coeffs_ctl.at(i) = 1.0;
                        ch_coeffs_ctk.at(j) = 1.0;
                        ch_coeffs_phi.at(k) = 1.0;
                        std::vector<double> poly_coeffs_q2(npol.q2, 0.0);
                        std::vector<double> poly_coeffs_ctl(npol.ctl, 0.0);
                        std::vector<double> poly_coeffs_ctk(npol.ctk, 0.0);
                        std::vector<double> poly_coeffs_phi(npol.phi,0.0);
                        chebyshev_to_poly(ch_coeffs_q2, poly_coeffs_q2);
                        chebyshev_to_poly(ch_coeffs_ctl, poly_coeffs_ctl);
                        chebyshev_to_poly(ch_coeffs_ctk, poly_coeffs_ctk);
                        chebyshev_to_poly(ch_coeffs_phi, poly_coeffs_phi);
                        for (unsigned int l = 0; l<=h; l++)
                            for (unsigned int m = 0; m<=i; m++)
                                for (unsigned int n = 0; n<=j; n++)
                                    for (unsigned int o = 0; o<=k; o++){
                                        unsigned int bin = npol.get_bin_in4D(l,m,n,o);
                                        poly_ml.at(bin) += c*poly_coeffs_q2.at(l)*poly_coeffs_ctl.at(m)*poly_coeffs_ctk.at(n)*poly_coeffs_phi.at(o);
                                    }
                    }//tested, works

        //correct coefficients from -1 +1 to ctlmin .. ctlmax for ctl, bit difficult, requires binomial coefficients
        std::vector<double> poly_ml_ctl(npol.getSize(), 0.0);
        std::vector<double> poly_ml_ctk(npol.getSize(), 0.0);
        std::vector<double> poly_ml_phi(npol.getSize(), 0.0);
        std::vector<double> poly_ml_q2(npol.getSize(), 0.0);
        correct_coefficients(npol, edges, poly_ml_ctl, poly_ml, "ctl");
        correct_coefficients(npol, edges, poly_ml_ctk, poly_ml_ctl, "ctk");
        correct_coefficients(npol, edges, poly_ml_phi, poly_ml_ctk, "phi");
        correct_coefficients(npol, edges, poly_ml_q2,  poly_ml_phi, "q2");


        std::vector<double> coeffs_ml(poly_ml_q2);
        //calculate norm after full integration
        double full_norm = 0.0;
        for (unsigned int h = 0; h<npol.q2; h++){
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        full_norm += coeffs_ml.at(npol.get_bin_in4D(h,i,j,k))*integrate_4D(edges,h,i,j,k);
                    }
                }
            }
        }

        //assume a flat efficiency of 1.0 -> scale by edges.range_q2()*RANGE_3D()/full_norm
        for (unsigned int h = 0; h<npol.q2; h++){
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        coeffs_ml.at(npol.get_bin_in4D(h,i,j,k)) *= edges.range_q2()*RANGE_3D()/full_norm;
                        // delta_q2*delta_ctl*delta_ctk*delta_phi/full_norm;
                    }
                }
            }
        }

        //calculate the integrated angular terms int f_i dOmega (q2) = xi(q2)
        for (int l=1; l<=hxi_ml_hist[0]->GetNbinsX(); l++){
            double a = hxi_ml_hist[0]->GetXaxis()->GetBinLowEdge(l);
            double b = hxi_ml_hist[0]->GetXaxis()->GetBinLowEdge(l+1);
            std::vector<double> integral_xi(hxi_ml_hist.size(), 0.0);

            for (unsigned int h = 0; h<npol.q2; h++){
                for (unsigned int i = 0; i<npol.ctl; i++){
                    for (unsigned int j = 0; j<npol.ctk; j++){
                        for (unsigned int k = 0; k<npol.phi; k++){
                            unsigned int bin = npol.get_bin_in4D(h,i,j,k);
                            double coeff = C_PWAVE*coeffs_ml.at(bin)*pow(0.5*(a+b), h);
                            //f1 = c * sinthetak2; //j1s
                            integral_xi[0] += coeff
                                    *integrate_x_to_n(edges.ctlmin, edges.ctlmax, i)
                                    *(integrate_x_to_n(edges.ctkmin, edges.ctkmax, j)-integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2))
                                    *integrate_x_to_n(edges.phimin, edges.phimax,k);
                            //f2 = c * costhetak2; //j1c
                            integral_xi[1] += coeff
                                    *integrate_x_to_n(edges.ctlmin, edges.ctlmax, i)
                                    *integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2)
                                    *integrate_x_to_n(edges.phimin, edges.phimax,k);
                            //f3 = c * sinthetak2*cos2thetal; //j2s
                            integral_xi[2] += coeff
                                    *(2.0*integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+2)-integrate_x_to_n(edges.ctlmin, edges.ctlmax, i))
                                    *(integrate_x_to_n(edges.ctkmin, edges.ctkmax, j)-integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2))
                                    *integrate_x_to_n(edges.phimin, edges.phimax,k);
                            //f4 = c * costhetak2*cos2thetal; //j2c
                            integral_xi[3] += coeff
                                    *(2.0*integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+2)-integrate_x_to_n(edges.ctlmin, edges.ctlmax, i))
                                    *integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2)
                                    *integrate_x_to_n(edges.phimin, edges.phimax,k);
                            //f5 = c * sinthetak2*sinthetal2*cos(2.0*phi); //j3
                            integral_xi[4] += coeff
                                    *(integrate_x_to_n(edges.ctlmin, edges.ctlmax, i)-integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+2))
                                    *(integrate_x_to_n(edges.ctkmin, edges.ctkmax, j)-integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2))
                                    *integrate_x_to_n_times_cos_2x(edges.phimin, edges.phimax,k);
                            //f6 = c * sin2thetak*sin2thetal*cos(phi); //j4
                            integral_xi[5] += coeff
                                    *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctlmin, edges.ctlmax, i+1)
                                    *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctkmin, edges.ctkmax, j+1)
                                    *integrate_x_to_n_times_cos_x(edges.phimin, edges.phimax,k);
                            //f7 = c * sin2thetak*sinthetal*cos(phi); //j5
                            integral_xi[6] += coeff
                                    *integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctlmin, edges.ctlmax, i)
                                    *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctkmin, edges.ctkmax, j+1)
                                    *integrate_x_to_n_times_cos_x(edges.phimin, edges.phimax,k);
                            //f8 = c * sinthetak2*costhetal; //j6s
                            integral_xi[7] += coeff
                                    *integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+1)
                                    *(integrate_x_to_n(edges.ctkmin, edges.ctkmax, j)-integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2))
                                    *integrate_x_to_n(edges.phimin, edges.phimax,k);
                            //f9 = c * costhetak2*costhetal; //j6c
                            integral_xi[8] += coeff
                                    *integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+1)
                                    *integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2)
                                    *integrate_x_to_n(edges.phimin, edges.phimax,k);
                            //f10 = c * sin2thetak*sinthetal*sin(phi); //j7
                            integral_xi[9] += coeff
                                    *integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctlmin, edges.ctlmax, i)
                                    *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctkmin, edges.ctkmax, j+1)
                                    *integrate_x_to_n_times_sin_x(edges.phimin, edges.phimax,k);
                            //f11 = c * sin2thetak*sin2thetal*sin(phi); //j8
                            integral_xi[10] += coeff
                                    *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctlmin, edges.ctlmax, i+1)
                                    *2.0*integrate_x_to_n_times_sqrt_1_minus_x2(edges.ctkmin, edges.ctkmax, j+1)
                                    *integrate_x_to_n_times_sin_x(edges.phimin, edges.phimax,k);
                            //f12 = c * sinthetak2*sinthetal2*sin(2.0*phi); //j9
                            integral_xi[11] += coeff
                                    *(integrate_x_to_n(edges.ctlmin, edges.ctlmax, i) - integrate_x_to_n(edges.ctlmin, edges.ctlmax, i+2))
                                    *(integrate_x_to_n(edges.ctkmin, edges.ctkmax, j) - integrate_x_to_n(edges.ctkmin, edges.ctkmax, j+2))
                                    *integrate_x_to_n_times_sin_2x(edges.phimin, edges.phimax,k);
                        }
                    }
                }
            }
            for_indexed(auto hist: hxi_ml_hist) hist->SetBinContent(l, is_param_folded(i,opts) ? 0.0 : integral_xi[i]);
        }
    }

    //-----------------------
    // Factorization in q2 bins
    //-----------------------
    //std::vector<double> nfact();
    if (checkFactorization){ //possible TODO for when you're bored: move into it's own function
        spdlog::info( "CHECKFACTORIZATION start " );
        //fill histo for comparison
        std::vector<double> nsel_hist(edges.getNbins(), 0.0);
        std::vector<double> nsel_hist_errsq(edges.getNbins(), 0.0);
        for (auto &meas: filtered_events){
            unsigned int h = (meas.q2        - edges.q2min ) / edges.binWidth_q2();
            unsigned int i = (meas.costhetal - edges.ctlmin ) / edges.binWidth_ctl();
            unsigned int j = (meas.costhetak - edges.ctkmin ) / edges.binWidth_ctk();
            unsigned int k = (meas.phi       - edges.phimin ) / edges.binWidth_phi();

            unsigned int bin = get_bin_in4D(h,i,j,k,edges);
            nsel_hist.at(bin) += meas.weight;
            nsel_hist_errsq.at(bin) += sqr(meas.weight);
        }
        for (unsigned int i=0; i<nsel_hist_errsq.size(); i++){
            if (nsel_hist_errsq.at(i) == 0.0) nsel_hist_errsq.at(i) = 1.0;
        }

        //loop over all q2 bins
        double chi2 = 0.0;
        for (unsigned int h=0; h<q2_edges.size()-1; h++){
            double q2a = q2_edges.at(h);
            double q2b = q2_edges.at(h+1);
            std::vector<double> ctl_moments(npol.ctl, 0.0);
            std::vector<double> ctk_moments(npol.ctk, 0.0);
            std::vector<double> phi_moments(npol.phi,0.0);
            double nfactweighted = 0.0;
            //determine weighted number of events in this bin and fill control histos
            std::ostringstream sout;
            sout << h;
            sout << "_" << DECAY_NAME << (opts->KS ? opts->DDLL+"_" : "_")
                 << "Run" << opts->run
                 << ((opts->angacccorrperyear && opts->year != -1) ? "_"+std::to_string(opts->year) : "")
                 << "_" << tagNumber;
            TH1D* hctl = new TH1D("hctl", ";cos#Theta_{L};", nBinsAngles, edges.ctlmin, edges.ctlmax);
            TH1D* hctk = new TH1D("hctk", ";cos#Theta_{K};", nBinsAngles, edges.ctkmin, edges.ctkmax);
            TH1D* hphi = new TH1D("hphi", ";#phi [rad];"   , nBinsAngles, edges.phimin, edges.phimax);
            for (auto &meas : filtered_events){
                if (meas.q2 > q2a && meas.q2 < q2b){
                    nfactweighted += meas.weight;
                    hctl->Fill(meas.costhetal, meas.weight);
                    hctk->Fill(meas.costhetak, meas.weight);
                    hphi->Fill(meas.phi, meas.weight);
                }
            }
            //loop over events in this bin, determine factorizing moments
            for (auto & meas: filtered_events){
                if (meas.q2 > q2a && meas.q2 < q2b){
                    double weight = meas.weight;
                    double ctl = 2.0 * (meas.costhetal - edges.ctlmin ) / edges.range_ctl() - 1.0;
                    double ctk = 2.0 * (meas.costhetak - edges.ctkmin ) / edges.range_ctk() - 1.0;
                    double phi = 2.0 * (meas.phi       - edges.phimin ) / edges.range_phi() - 1.0;

                    for (unsigned int i = 0; i<npol.ctl; i++){
                        if (assumectleven && i%2 != 0){
                            ctl_moments.at(i) = 0.0;
                        }
                        else{
                            //ctl_moments.at(i)+=weight*fcnc::chebyshev(ctl,i)/sqrt(1-ctl*ctl)*(i==0?1.0/pi:2.0/pi)/nfactweighted;
                            ctl_moments.at(i) += weight*fcnc::legendre(ctl, i)*(2.0*i+1.0)/2.0/nfactweighted;
                        }
                    }
                    for (unsigned int j = 0; j<npol.ctk; j++){
                        //ctk_moments.at(j) += weight*fcnc::chebyshev(ctk, j)/sqrt(1-ctk*ctk)*(j == 0 ? 1.0/pi : 2.0/pi)/nfactweighted;
                        ctk_moments.at(j) += weight*fcnc::legendre(ctk, j)*(2.0*j+1.0)/2.0/nfactweighted;
                    }
                    for (unsigned int k = 0; k<npol.phi; k++){
                        if (assumePhiEven && k%2 != 0){
                            phi_moments.at(k) = 0.0;
                        }
                        else{
                            //phi_moments.at(k) += weight*fcnc::chebyshev(phi, k)/sqrt(1-phi*phi)*(k == 0 ? 1.0/pi : 2.0/pi)/nfactweighted;
                            phi_moments.at(k) += weight*fcnc::legendre(phi, k)*(2.0*k+1.0)/2.0/nfactweighted;
                        }
                    }
                }
            }

            //make polynomial
            std::vector<double> ctl_polys(npol.ctl, 0.0);
            std::vector<double> ctk_polys(npol.ctk, 0.0);
            std::vector<double> phi_polys(npol.phi,0.0);
            if (doLegendre){
                legendre_to_poly(ctl_moments, ctl_polys);
                legendre_to_poly(ctk_moments, ctk_polys);
                legendre_to_poly(phi_moments, phi_polys);
            }
            else{
                chebyshev_to_poly(ctl_moments, ctl_polys);
                chebyshev_to_poly(ctk_moments, ctk_polys);
                chebyshev_to_poly(phi_moments, phi_polys);
            }

            //transform angles from -1 +1 to angle_min angle_max
            std::vector<double> ctl_correct(npol.ctl, 0.0);
            std::vector<double> ctk_correct(npol.ctk, 0.0);
            std::vector<double> phi_correct(npol.phi,0.0);

            for (unsigned int i=0; i<ctk_polys.size(); i++) ctk_correct.at(i) = ctk_polys.at(i);
            for (unsigned int i=0; i<ctl_polys.size(); i++) ctl_correct.at(i) = ctl_polys.at(i);
            correct_poly(phi_polys, phi_correct, -TMath::Pi(), +TMath::Pi());

            //determine chi2 in this bin
            std::vector<double> neps_hist(nctlbins*nctkbins*nphibins, 0.0);
            for (unsigned int i = 0; i<nctlbins; i++){
                double ctla = edges.ctlmin + edges.range_ctl() *  i      /double(edges.nctlbins);
                double ctlb = edges.ctlmin + edges.range_ctl() * (i+1.0) /double(edges.nctlbins);
                for (unsigned int j = 0; j<nctkbins; j++){
                    double ctka = edges.ctkmin + edges.range_ctk() *  j      /double(edges.nctkbins);
                    double ctkb = edges.ctkmin + edges.range_ctk() * (j+1.0) /double(edges.nctkbins);
                    for (unsigned int k = 0; k<nphibins; k++){
                        double phi_a = edges.phimin + edges.range_phi() *  k      / double(edges.nphibins);
                        double phi_b = edges.phimin + edges.range_phi() * (k+1.0) / double(edges.nphibins);
                        double result_ctl = 0.0, result_ctk = 0.0, result_phi = 0.0;
                        for (unsigned int m = 0; m<npol.ctl; m++) result_ctl += ctl_correct.at(m)*integrate_x_to_n(ctla,ctlb,m);
                        for (unsigned int n = 0; n<npol.ctk; n++) result_ctk += ctk_correct.at(n)*integrate_x_to_n(ctka,ctkb,n);
                        for (unsigned int o = 0; o<npol.phi; o++) result_phi += phi_correct.at(o)*integrate_x_to_n(phi_a,phi_b,o);
                        neps_hist.at(get_bin_in3D(i,j,k,edges)) = result_ctl*result_ctk*result_phi;
                    }
                }
            }
            //rescale to be sure
            double nnorm_ctl = 0.0, nnorm_ctk = 0.0, nnorm_phi = 0.0;
            for (unsigned int m = 0; m<npol.ctl; m++) nnorm_ctl += ctl_correct.at(m)*integrate_x_to_n(edges.ctlmin, edges.ctlmax, m);
            for (unsigned int n = 0; n<npol.ctk; n++) nnorm_ctk += ctk_correct.at(n)*integrate_x_to_n(edges.ctkmin, edges.ctkmax, n);
            for (unsigned int o = 0; o<npol.phi; o++) nnorm_phi += phi_correct.at(o)*integrate_x_to_n(edges.phimin, edges.phimax, o);
            for (unsigned int i = 0; i<nctlbins; i++){
                for (unsigned int j = 0; j<nctkbins; j++){
                    for (unsigned int k = 0; k<nphibins; k++){
                        neps_hist.at(get_bin_in3D(i,j,k,edges)) *= (nfactweighted/nnorm_ctl/nnorm_ctk/nnorm_phi);
                    }
                }
            }
            //should scale nfactweighted/nfactfit

            //determine chi2 //TODO: not working?
            double curr_chi2 = 0.0;
            TH1D* hpull = new TH1D("hpull", ";(N_{sel}-N_{fit})/#sqrt{N_{sel}};", 100, -5.0, 5.0);
            for (unsigned int i = 0; i<nctlbins; i++){
                for (unsigned int j = 0; j<nctkbins; j++){
                    for (unsigned int k = 0; k<nphibins; k++){
                        unsigned int bin = get_bin_in4D(h,i,j,k,edges);
                        curr_chi2 += sqr(nsel_hist.at(bin)-neps_hist.at(get_bin_in3D(i,j,k,edges)))/fabs(neps_hist.at(get_bin_in3D(i,j,k,edges))); //TODO: h is a wrong bin!
                        hpull->Fill((nsel_hist.at(bin)-neps_hist.at(get_bin_in3D(i,j,k,edges)))/sqrt(fabs(neps_hist.at(get_bin_in3D(i,j,k,edges)))));//TODO: h is a wrong bin!
                    }
                }
            }
            //do plots
            TH1D* ectl = new TH1D("ectl", ";cos#Theta_{L};", nBinsAngles, edges.ctlmin, edges.ctlmax);
            TH1D* ectk = new TH1D("ectk", ";cos#Theta_{K};", nBinsAngles, edges.ctkmin, edges.ctkmax);
            TH1D* ephi = new TH1D("ephi", ";#phi [rad];"   , nBinsAngles, edges.phimin, edges.phimax);
            for (int l=1; l<=ectl->GetNbinsX(); l++){
                double integral = 0.0;
                for (unsigned int i = 0; i<npol.ctl; i++){
                    integral += ctl_correct.at(i)*integrate_x_to_n(ectl->GetBinLowEdge(l),ectl->GetBinLowEdge(l+1),i);
                }
                ectl->SetBinContent(l, integral);
            }
            for (int l=1; l<=ectk->GetNbinsX(); l++){
                double integral = 0.0;
                for (unsigned int j = 0; j<npol.ctk; j++){
                    integral += ctk_correct.at(j)*integrate_x_to_n(ectl->GetBinLowEdge(l),ectl->GetBinLowEdge(l+1),j);
                }
                ectk->SetBinContent(l, integral);
            }
            for (int l=1; l<=ephi->GetNbinsX(); l++){
                double integral = 0.0;
                for (unsigned int k = 0; k<npol.phi; k++){
                    integral += phi_correct.at(k)*integrate_x_to_n(ectl->GetBinLowEdge(l),ectl->GetBinLowEdge(l+1),k);
                }
                ephi->SetBinContent(l, integral);
            }
            TLatex *tex = getPrettyTex(0.06,31);

            std::ostringstream sregion;
            sregion << std::fixed << std::setprecision(2) << q2a << "<q^{2}<" << q2b;

            TCanvas* c_pull = new TCanvas("c_pull", "c_pull", 1600, 1200);
            c_pull->cd();
            hpull->Draw();
            tex->DrawLatex(0.85, 0.15, sregion.str().c_str());
            if(opts->write_eps)c_pull->Print((std::string(subfolder + "fact_pull")+sout.str()+std::string(".eps")).c_str(),"eps");

            plotAngular(hctl,ectl,opts->write_eps,"fact_ctleff",sout.str(),sregion.str(),tex,subfolder);
            plotAngular(hctk,ectk,opts->write_eps,"fact_ctkeff",sout.str(),sregion.str(),tex,subfolder);
            plotAngular(hphi,ephi,opts->write_eps,"fact_phieff",sout.str(),sregion.str(),tex,subfolder);

            delete hpull;
            delete ectl;
            delete ectk;
            delete ephi;
            delete hctl;
            delete hctk;
            delete hphi;

            delete c_pull;

            spdlog::debug( "fact chi2 in bin {0:d}: {1:f}", h, curr_chi2 );
            chi2 += curr_chi2;
        }
        spdlog::info( "fact total chi2: {0:f}",chi2 );
        spdlog::info( "uses {0:d}*{1:d}*{2:d}*{3:d}={4:d} bins",edges.nq2bins, edges.nctlbins, edges.nctkbins, edges.nphibins, edges.getNbins());
        spdlog::info( "Chi2/Nbins = {0:f}", chi2/(edges.getNbins()) );
        spdlog::info( "CHECKFACTORIZATION end " );
    }
    //-----------------------
    // 3D MOMENTS in q2 bins
    //-----------------------
    if (do3Dmoments){
        spdlog::info("DOTHREEDMOMENTS start ");
        //fill histo for comparison
        std::vector<double> nsel_hist(edges.getNbins(), 0.0);
        std::vector<double> nsel_hist_errsq(edges.getNbins(), 0.0);
        for (unsigned int e=0; e<filtered_events.size(); e++){
            const event & meas = filtered_events.at(e);
            unsigned int bin = get_bin_in4D(meas,edges);
            nsel_hist.at(bin) += meas.weight;
            nsel_hist_errsq.at(bin) += sqr(meas.weight);
        }
        for (unsigned int i=0; i<nsel_hist_errsq.size(); i++){
            if (nsel_hist_errsq.at(i) == 0.0) nsel_hist_errsq.at(i) = 1.0;
        }

        //loop over all q2 bins
        double chi2 = 0.0;
        for (unsigned int h=0; h<q2_edges.size()-1; h++){
            double q2a = q2_edges.at(h);
            double q2b = q2_edges.at(h+1);
            double nthreedweighted = 0.0;
            //determine weighted number of events in this bin and fill control histos
            std::ostringstream sout;
            sout << h;
            sout << "_" << DECAY_NAME << (opts->KS ? opts->DDLL+"_" : "_")
                 << "Run" << opts->run
                 << ((opts->angacccorrperyear && opts->year != -1) ? "_"+std::to_string(opts->year) : "");
            TH1D* hctl = new TH1D("hctl", ";cos#Theta_{L};", nBinsAngles, edges.ctlmin, edges.ctlmax);
            TH1D* hctk = new TH1D("hctk", ";cos#Theta_{K};", nBinsAngles, edges.ctkmin, edges.ctkmax);
            TH1D* hphi = new TH1D("hphi", ";#phi [rad];"   , nBinsAngles, edges.phimin, edges.phimax);
            for (auto &meas: filtered_events){
                if (meas.q2 > q2a && meas.q2 < q2b){
                    nthreedweighted += meas.weight;
                    hctl->Fill(meas.costhetal, meas.weight);
                    hctk->Fill(meas.costhetak, meas.weight);
                    hphi->Fill(meas.phi, meas.weight);
                }
            }
            //loop over events in this bin, determine factorizing moments
            spdlog::debug("Getting factorizing moments");
            std::vector<double> threed_moments(npol.ctl*npol.ctk*npol.phi,0.0);
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        for (auto &meas: filtered_events){
                            if (meas.q2 > q2a && meas.q2 < q2b){
                                double weight = meas.weight;
                                double ctl = 2.0 * (meas.costhetal - edges.ctlmin ) / edges.range_ctl() - 1.0;
                                double ctk = 2.0 * (meas.costhetak - edges.ctkmin ) / edges.range_ctk() - 1.0;
                                double phi = 2.0 * (meas.phi       - edges.phimin ) / edges.range_phi() - 1.0;

                                double result = 0.0;
                                if (assumectleven && i%2 != 0) result = 0.0;
                                else if (assumePhiEven && k%2 != 0) result = 0.0;
                                else {
                                    if (doLegendre){
                                        result = weight
                                                *fcnc::legendre(ctl, i)*(2.0*i+1.0)/2.0
                                                *fcnc::legendre(ctk, j)*(2.0*j+1.0)/2.0
                                                *fcnc::legendre(phi, k)*(2.0*k+1.0)/2.0
                                                /nthreedweighted;// /filtered_events.size();
                                    }
                                    else{
                                        result = weight
                                                *fcnc::chebyshev(ctl, i)/sqrt(1-ctl*ctl)*(i==0 ? 1.0/MY_PI : 2.0/MY_PI)
                                                *fcnc::chebyshev(ctk, j)/sqrt(1-ctk*ctk)*(j==0 ? 1.0/MY_PI : 2.0/MY_PI)
                                                *fcnc::chebyshev(phi, k)/sqrt(1-phi*phi)*(k==0 ? 1.0/MY_PI : 2.0/MY_PI)
                                                /nthreedweighted;// /filtered_events.size();
                                    }
                                }
                                threed_moments.at( get_bin_in3D(i,j,k,npol)) += result;
                            }
                        }
                    }
                }
            }
            //make polynomial

            //transform legendre coefficients to monomial coefficients
            spdlog::debug("Transforming legendre coefficients to monomial coefficients....");
            std::vector<double> threedpoly_moments(npol.ctl*npol.ctk*npol.phi,0.0);
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){

                        double tmp = threed_moments.at(get_bin_in3D(i,j,k,npol));
                        std::vector<double> leg_coeffs_ctl(npol.ctl, 0.0);
                        std::vector<double> leg_coeffs_ctk(npol.ctk, 0.0);
                        std::vector<double> leg_coeffs_phi(npol.phi,0.0);
                        leg_coeffs_ctl.at(i) = 1.0;
                        leg_coeffs_ctk.at(j) = 1.0;
                        leg_coeffs_phi.at(k) = 1.0;
                        std::vector<double> poly_coeffs_ctl(npol.ctl, 0.0);
                        std::vector<double> poly_coeffs_ctk(npol.ctk, 0.0);
                        std::vector<double> poly_coeffs_phi(npol.phi,0.0);
                        legendre_to_poly(leg_coeffs_ctl, poly_coeffs_ctl);
                        legendre_to_poly(leg_coeffs_ctk, poly_coeffs_ctk);
                        legendre_to_poly(leg_coeffs_phi, poly_coeffs_phi);

                        for (unsigned int m = 0; m<=i; m++){
                            for (unsigned int n = 0; n<=j; n++){
                                for (unsigned int o = 0; o<=k; o++){
                                    unsigned int bin = get_bin_in3D(m,n,o,npol);
                                    threedpoly_moments.at(bin) += tmp*poly_coeffs_ctl.at(m)*poly_coeffs_ctk.at(n)*poly_coeffs_phi.at(o);
                                }
                            }
                        }
                    }
                }
            }

            //correct coefficients from -1 +1 to -pi .. +pi for phi, this is easy
            std::vector<double> threedpoly_moments_phi(threedpoly_moments);
            for (unsigned int m = 0; m<npol.ctl; m++){
                for (unsigned int n = 0; n<npol.ctk; n++){
                    for (unsigned int o = 0; o<npol.phi; o++){
                        threedpoly_moments_phi.at(get_bin_in3D(m,n,o,npol)) *= pow(1.0/MY_PI, o);
                    }
                }
            }

            //calculate norm after full integration
            double full_norm = 0.0;
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        double result = threedpoly_moments_phi.at(get_bin_in3D(i,j,k,npol))*integrate_3D(edges,i,j,k);
                        full_norm += result;
                    }
                }
            }
            spdlog::debug("Rescaling...");
            //rescale to be sure
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        threedpoly_moments_phi.at(get_bin_in3D(i,j,k,npol)) *= nthreedweighted/full_norm;
                    }
                }
            }

            //determine nfit in this bin
            std::vector<double> neps_hist(nctlbins*nctkbins*nphibins, 0.0);
            for (unsigned int i = 0; i<nctlbins; i++){
                double ctla = edges.ctlmin +  i    * edges.range_ctl();
                double ctlb = edges.ctlmin + (i+1) * edges.range_ctl();
                for (unsigned int j = 0; j<nctkbins; j++){
                    double ctka = edges.ctkmin +  j    * edges.range_ctk();
                    double ctkb = edges.ctkmin + (j+1) * edges.range_ctk();
                    for (unsigned int k = 0; k<nphibins; k++){
                        double phi_a = edges.phimin +  k    * edges.range_phi();
                        double phi_b = edges.phimin + (k+1) * edges.range_phi();
                        double result = 0.0;
                        for (unsigned int m = 0; m<npol.ctl; m++)
                            for (unsigned int n = 0; n<npol.ctk; n++)
                                for (unsigned int o = 0; o<npol.phi; o++)
                                    result += threedpoly_moments_phi.at(get_bin_in3D(m,n,o,npol))
                                            *(pow(ctlb,m+1)/(m+1.0) - pow(ctla, m+1)/(m+1.0))
                                            *(pow(ctkb,n+1)/(n+1.0) - pow(ctka, n+1)/(n+1.0))
                                            *(pow(phi_b,o+1)/(o+1.0) - pow(phi_a, o+1)/(o+1.0));
                        unsigned int bin = get_bin_in3D(i,j,k,edges);
                        neps_hist.at(bin) = result;
                    }
                }
            }
            spdlog::debug("Getting chi2...");
            //determine chi2
            double curr_chi2 = 0.0;
            TH1D* hpull = new TH1D("hpull", ";(N_{sel}-N_{fit})/#sqrt{N_{sel}};", 100, -5.0, 5.0);
            for (unsigned int i = 0; i<nctlbins; i++)
                for (unsigned int j = 0; j<nctkbins; j++)
                    for (unsigned int k = 0; k<nphibins; k++){
                        unsigned int bin =get_bin_in4D(h,i,j,k,edges);
                        curr_chi2 += sqr(nsel_hist.at(bin)-neps_hist.at(get_bin_in3D(i,j,k,edges)))/fabs(neps_hist.at(get_bin_in3D(i,j,k,edges)));
                        hpull->Fill((nsel_hist.at(bin)-neps_hist.at(get_bin_in3D(i,j,k,edges)))/sqrt(fabs(neps_hist.at(get_bin_in3D(i,j,k,edges)))));
                    }
            //spdlog::debug( "ok" );
            //do plots
            TH1D* ectl = new TH1D("ectl", ";cos#Theta_{L};", nBinsAngles, edges.ctlmin, edges.ctlmax);
            TH1D* ectk = new TH1D("ectk", ";cos#Theta_{K};", nBinsAngles, edges.ctkmin, edges.ctkmax);
            TH1D* ephi = new TH1D("ephi", ";#phi [rad];"   , nBinsAngles, edges.phimin, edges.phimax);
            for (int l=1; l<=ectl->GetNbinsX(); l++){
                double a = ectl->GetBinLowEdge(l);
                double b = ectl->GetBinLowEdge(l+1);
                double integral = 0.0;
                for (unsigned int i = 0; i<npol.ctl; i++)
                    for (unsigned int j = 0; j<npol.ctk; j++)
                        for (unsigned int k = 0; k<npol.phi; k++){
                            double result = threedpoly_moments_phi.at(get_bin_in3D(i,j,k,npol))
                                    *(pow(b     ,i+1)/(i+1.0) - pow(a     ,i+1)/(i+1.0))
                                    *(pow(edges.ctkmax,j+1)/(j+1.0) - pow(edges.ctkmin,j+1)/(j+1.0))
                                    *(pow(edges.phimax,k+1)/(k+1.0) - pow(edges.phimin,k+1)/(k+1.0));
                            integral += result;
                        }
                ectl->SetBinContent(l, integral);
            }
            for (int l=1; l<=ectk->GetNbinsX(); l++){
                double a = ectk->GetBinLowEdge(l);
                double b = ectk->GetBinLowEdge(l+1);
                double integral = 0.0;
                for (unsigned int i = 0; i<npol.ctl; i++)
                    for (unsigned int j = 0; j<npol.ctk; j++)
                        for (unsigned int k = 0; k<npol.phi; k++){
                            double result = threedpoly_moments_phi.at(get_bin_in3D(i,j,k,npol))
                                    *(pow(edges.ctlmax,i+1)/(i+1.0) - pow(edges.ctlmin,i+1)/(i+1.0))
                                    *(pow(b     ,j+1)/(j+1.0) - pow(a     ,j+1)/(j+1.0))
                                    *(pow(edges.phimax,k+1)/(k+1.0) - pow(edges.phimin,k+1)/(k+1.0));
                            integral += result;
                        }
                ectk->SetBinContent(l, integral);
            }
            for (int l=1; l<=ephi->GetNbinsX(); l++){
                double a = ephi->GetBinLowEdge(l);
                double b = ephi->GetBinLowEdge(l+1);
                double integral = 0.0;
                for (unsigned int i = 0; i<npol.ctl; i++)
                    for (unsigned int j = 0; j<npol.ctk; j++)
                        for (unsigned int k = 0; k<npol.phi; k++){
                            double result = threedpoly_moments_phi.at(get_bin_in3D(i,j,k,npol))
                                    *(pow(edges.ctlmax,i+1)/(i+1.0) - pow(edges.ctlmin,i+1)/(i+1.0))
                                    *(pow(edges.ctkmax,j+1)/(j+1.0) - pow(edges.ctkmin,j+1)/(j+1.0))
                                    *(pow(b     ,k+1)/(k+1.0) - pow(a     ,k+1)/(k+1.0));
                            integral += result;
                        }
                ephi->SetBinContent(l, integral);
            }

            spdlog::debug("Plotting...");
            TLatex *tex = getPrettyTex(0.06,31);
            std::ostringstream sregion;
            sregion << std::fixed << std::setprecision(2) << q2a << "<q^{2}<" << q2b;

            TCanvas* c_zero = new TCanvas("c_zero", "c_zero", 1600, 1200);
            c_zero->cd();
            hpull->Draw();
            tex->DrawLatex(0.85, 0.15, sregion.str().c_str());
            if(opts->write_eps)c_zero->Print((std::string(subfolder + "3d_pull")+sout.str()+std::string(".eps")).c_str(),"eps");

            plotAngular(hctl,ectl,opts->write_eps,"3d_ctleff", sout.str(), sregion.str(),tex,subfolder);
            plotAngular(hctk,ectk,opts->write_eps,"3d_ctkeff", sout.str(), sregion.str(),tex,subfolder);
            plotAngular(hphi,ephi,opts->write_eps,"3d_phieff", sout.str(), sregion.str(),tex,subfolder);

            delete hpull;
            delete ectl;
            delete ectk;
            delete ephi;
            delete hctl;
            delete hctk;
            delete hphi;

            delete c_zero;

            spdlog::debug( "3d chi2 in bin {0:d}: {1:f}", h, curr_chi2 );
            chi2 += curr_chi2;
        }
        spdlog::debug( "3d total chi2: {0:f}",chi2 );
        spdlog::debug( "3d uses {0:d}*{1:d}*{2:d}*{3:d}={4_d} bins",edges.nq2bins, edges.nctlbins, edges.nctkbins, edges.nphibins, edges.getNbins());
        spdlog::debug( "3d chi2/Nbins = {0:f}", chi2/(edges.getNbins()) );
        spdlog::info( "DOTHREEDMOMENTS end " );

    }
    //-----------------------
    // CHI2 TEST FOR MOMENTS
    //-----------------------
    bool dochi2test = true;
    if(opts->only_4_1D_chi2)dochi2test = false;
    spdlog::debug("dochi2: "+boolToString(dochi2test));
    if (dochi2test) {
        get_chi2(edges, npol, filtered_events, coeffs_moments, globalvalues,
                 assumectleven, assumePhiEven, opts->write_eps, subfolder, appendix);
    }

    //-----------------------
    //MAKE NICE CONTROL PLOTS
    //-----------------------
    const bool do_plots = opts->write_eps || opts->only_4_1D_chi2;
    if (do_plots){

        spdlog::info( "Start PLOTS.." );

        gROOT->SetStyle("Plain");
        TPad foo;
        const int NRGBs = 5;
        const int NCont = 250;
        double stops[NRGBs] = { 0.00, 0.34, 0.61, 0.84, 1.00 };
        double red[NRGBs]   = { 0.00, 0.00, 0.87, 1.00, 0.51 };
        double green[NRGBs] = { 0.00, 0.81, 1.00, 0.20, 0.00 };
        double blue[NRGBs]  = { 0.51, 1.00, 0.12, 0.00, 0.00 };
        TColor::CreateGradientColorTable(NRGBs, stops, red, green, blue, NCont);
        gStyle->SetNumberContours(NCont);
        gStyle->SetLabelFont(132, "xyz");
        gStyle->SetTitleFont(132, "xyz");
        gStyle->SetLegendFont(132);
        gStyle->SetStatFont(132);
        //new histograms binned in q2
        //compare the three angular projections for data and moments

        for (unsigned int u=0; u<q2_edges.size()-1; u++){
            std::ostringstream sout;
            sout << u;
            std::string binno = sout.str();
            TH1D* hctl = new TH1D("hctl", ";cos#Theta_{L};", nBinsAngles, edges.ctlmin, edges.ctlmax);
            TH1D* hctk = new TH1D("hctk", ";cos#Theta_{K};", nBinsAngles, edges.ctkmin, edges.ctkmax);
            TH1D* hphi = new TH1D("hphi", ";#phi [rad];"   , nBinsAngles, edges.phimin, edges.phimax);
            TH2D* hctlctk = new TH2D("hctlctk", ";cos#Theta_{L};cos#Theta_{K}", nBinsAngles, edges.ctlmin, edges.ctlmax, nBinsAngles, edges.ctkmin, edges.ctkmax);
            TH2D* hctlphi = new TH2D("hctlphi", ";cos#Theta_{L};#phi [rad]"   , nBinsAngles, edges.ctlmin, edges.ctlmax, nBinsAngles, edges.phimin, edges.phimax);
            TH2D* hctkphi = new TH2D("hctkphi", ";cos#Theta_{K};#phi [rad]"   , nBinsAngles, edges.ctkmin, edges.ctkmax, nBinsAngles, edges.phimin, edges.phimax);
            double q2a = q2_edges.at(u);
            double q2b = q2_edges.at(u+1);
            for (auto & meas: filtered_events){
                if (meas.q2 > q2a && meas.q2 < q2b){
                    hctl->Fill(meas.costhetal, meas.weight);
                    hctk->Fill(meas.costhetak, meas.weight);
                    hphi->Fill(meas.phi, meas.weight);

                    hctlctk->Fill(meas.costhetal, meas.costhetak, meas.weight);
                    hctlphi->Fill(meas.costhetal, meas.phi, meas.weight);
                    hctkphi->Fill(meas.costhetak, meas.phi, meas.weight);
                }
            }

            TH1D* hctl_moments = new TH1D("hctl_moments", ";cos#Theta_{L};", nBinsAngles, edges.ctlmin, edges.ctlmax);
            TH1D* hctk_moments = new TH1D("hctk_moments", ";cos#Theta_{K};", nBinsAngles, edges.ctkmin, edges.ctkmax);
            TH1D* hphi_moments = new TH1D("hphi_moments", ";#phi [rad];"   , nBinsAngles, edges.phimin, edges.phimax);
            TH2D* hctlctk_moments = new TH2D("hctlctk_moments", ";cos#Theta_{L};cos#Theta_{K}", nBinsAngles, edges.ctlmin, edges.ctlmax, nBinsAngles, edges.ctkmin, edges.ctkmax);
            TH2D* hctlphi_moments = new TH2D("hctlphi_moments", ";cos#Theta_{L};#phi [rad]"   , nBinsAngles, edges.ctlmin, edges.ctlmax, nBinsAngles, edges.phimin, edges.phimax);
            TH2D* hctkphi_moments = new TH2D("hctkphi_moments", ";cos#Theta_{K};#phi [rad]"   , nBinsAngles, edges.ctkmin, edges.ctkmax, nBinsAngles, edges.phimin, edges.phimax);

            //1d plots
            fill_moments_hist_1D(hctl_moments, coeffs_moments, edges, npol, q2a, q2b);
            fill_moments_hist_1D(hctk_moments, coeffs_moments, edges, npol, q2a, q2b);
            fill_moments_hist_1D(hphi_moments, coeffs_moments, edges, npol, q2a, q2b);

            //2d plots
            fill_moments_hist_2D(hctlctk_moments, coeffs_moments, edges, npol, q2a, q2b);
            fill_moments_hist_2D(hctlphi_moments, coeffs_moments, edges, npol, q2a, q2b);
            fill_moments_hist_2D(hctkphi_moments, coeffs_moments, edges, npol, q2a, q2b);


            TLatex *tex = getPrettyTex(0.06,31);
            std::ostringstream sregion;
            sregion << std::fixed << std::setprecision(2) << q2a << "<q^{2}<" << q2b;

            plotAngular(hctl,hctl_moments,opts->write_eps,"ctleff",binno+appendix,sregion.str(), tex, subfolder);
            plotAngular(hctk,hctk_moments,opts->write_eps,"ctkeff",binno+appendix,sregion.str(), tex, subfolder);
            plotAngular(hphi,hphi_moments,opts->write_eps,"phieff",binno+appendix,sregion.str(), tex, subfolder);

            if (tagNumber < 0){ //Do not plot when testing quickly
                plotAngular(hctlctk,hctlctk_moments,opts->write_eps,"ctlctkeff",binno+appendix,
                            sregion.str(), tex, subfolder);
                plotAngular(hctlphi,hctlphi_moments,opts->write_eps,"ctlphieff",binno+appendix,
                            sregion.str(), tex, subfolder);
                plotAngular(hctkphi,hctkphi_moments,opts->write_eps,"ctkphieff",binno+appendix,
                            sregion.str(), tex, subfolder);
            }

            delete hctl;      delete hctl_moments;
            delete hctk;      delete hctk_moments;
            delete hphi;      delete hphi_moments;
            delete hctlctk;   delete hctlctk_moments;
            delete hctkphi;   delete hctkphi_moments;
            delete hctlphi;   delete hctlphi_moments;

        }//end extra plots in bins of q2

        //data histograms
        TH1D* hq2  = new TH1D("hq2" , ";q^{2} [GeV^{2}];"        , nBinsQ2    ,edges.q2min, edges.q2max);
        TH1D* hctl = new TH1D("hctl", ";cos(#Theta_{L});", nBinsAngles, edges.ctlmin, edges.ctlmax);
        TH1D* hctk = new TH1D("hctk", ";cos(#Theta_{K});", nBinsAngles, edges.ctkmin, edges.ctkmax);
        TH1D* hphi = new TH1D("hphi", ";#phi [rad];"   , nBinsAngles, edges.phimin, edges.phimax);
        TH2D* hctlctk = new TH2D("hctlctk", ";cos#Theta_{L};cos#Theta_{K}", nBinsAngles, edges.ctlmin, edges.ctlmax, nBinsAngles, edges.ctkmin, edges.ctkmax);
        TH2D* hctlphi = new TH2D("hctlphi", ";cos#Theta_{L};#phi [rad]"   , nBinsAngles, edges.ctlmin, edges.ctlmax, nBinsAngles, edges.phimin, edges.phimax);
        TH2D* hctkphi = new TH2D("hctkphi", ";cos#Theta_{K};#phi [rad]"   , nBinsAngles, edges.ctkmin, edges.ctkmax, nBinsAngles, edges.phimin, edges.phimax);
        TH2D* hq2ctl = new TH2D("hq2ctl", ";q^{2};cos#Theta_{L}", nBinsQ2, edges.q2min, edges.q2max, nBinsAngles, edges.ctlmin, edges.ctlmax);
        TH2D* hq2ctk = new TH2D("hq2ctk", ";q^{2};cos#Theta_{K}", nBinsQ2, edges.q2min, edges.q2max, nBinsAngles, edges.ctkmin, edges.ctkmax);
        TH2D* hq2phi = new TH2D("hq2phi", ";q^{2};#phi [rad]"   , nBinsQ2, edges.q2min, edges.q2max, nBinsAngles, edges.phimin, edges.phimax);
        hq2->Sumw2();
        hctl->Sumw2();
        hctk->Sumw2();
        hphi->Sumw2();
        for (unsigned int i=0; i<filtered_events.size(); i++){
            const event& meas = filtered_events.at(i);

            hq2->Fill(meas.q2, meas.weight);
            hctl->Fill(meas.costhetal, meas.weight);
            hctk->Fill(meas.costhetak, meas.weight);
            hphi->Fill(meas.phi, meas.weight);

            hctlctk->Fill(meas.costhetal, meas.costhetak, meas.weight);
            hctlphi->Fill(meas.costhetal, meas.phi, meas.weight);
            hctkphi->Fill(meas.costhetak, meas.phi, meas.weight);
            hq2ctl->Fill(meas.q2, meas.costhetal, meas.weight);
            hq2ctk->Fill(meas.q2, meas.costhetak, meas.weight);
            hq2phi->Fill(meas.q2, meas.phi, meas.weight);
        }
        //fitted histograms, moments method
        TH1D* hq2_moments  = new TH1D("hq2_moments" , ";q^{2} [GeV^{2}];"        , nBinsQ2, edges.q2min, edges.q2max);
        TH1D* hctl_moments = new TH1D("hctl_moments", ";cos(#Theta_{L});", nBinsAngles, edges.ctlmin, edges.ctlmax);
        TH1D* hctk_moments = new TH1D("hctk_moments", ";cos(#Theta_{K});", nBinsAngles, edges.ctkmin, edges.ctkmax);
        TH1D* hphi_moments = new TH1D("hphi_moments", ";#phi [rad];"   , nBinsAngles, edges.phimin, edges.phimax);
        TH2D* hctlctk_moments = new TH2D("hctlctk_moments", ";cos#Theta_{L};cos#Theta_{K}", nBinsAngles, edges.ctlmin, edges.ctlmax, nBinsAngles, edges.ctkmin, edges.ctkmax);
        TH2D* hctlphi_moments = new TH2D("hctlphi_moments", ";cos#Theta_{L};#phi [rad]"   , nBinsAngles, edges.ctlmin, edges.ctlmax, nBinsAngles, edges.phimin, edges.phimax);
        TH2D* hctkphi_moments = new TH2D("hctkphi_moments", ";cos#Theta_{K};#phi [rad]"   , nBinsAngles, edges.ctkmin, edges.ctkmax, nBinsAngles, edges.phimin, edges.phimax);
        TH2D* hq2ctl_moments = new TH2D("hq2ctl_moments", ";q^{2};cos#Theta_{L}", nBinsQ2, edges.q2min, edges.q2max, nBinsAngles, edges.ctlmin, edges.ctlmax);
        TH2D* hq2ctk_moments = new TH2D("hq2ctk_moments", ";q^{2};cos#Theta_{K}", nBinsQ2, edges.q2min, edges.q2max, nBinsAngles, edges.ctkmin, edges.ctkmax);
        TH2D* hq2phi_moments = new TH2D("hq2phi_moments", ";q^{2};#phi [rad]"   , nBinsQ2, edges.q2min, edges.q2max, nBinsAngles, edges.phimin, edges.phimax);

        //1d plots
        fill_moments_hist_1D(hq2_moments,coeffs_moments,edges,npol,edges.q2min,edges.q2max);
        fill_moments_hist_1D(hctl_moments,coeffs_moments,edges,npol,edges.q2min,edges.q2max);
        fill_moments_hist_1D(hctk_moments,coeffs_moments,edges,npol,edges.q2min,edges.q2max);
        fill_moments_hist_1D(hphi_moments,coeffs_moments,edges,npol,edges.q2min,edges.q2max);

        //2d plots
        fill_moments_hist_2D(hctlctk_moments,coeffs_moments,edges,npol,edges.q2min,edges.q2max);
        fill_moments_hist_2D(hctlphi_moments,coeffs_moments,edges,npol,edges.q2min,edges.q2max);
        fill_moments_hist_2D(hctkphi_moments,coeffs_moments,edges,npol,edges.q2min,edges.q2max);
        fill_moments_hist_2D(hq2ctl_moments,coeffs_moments,edges,npol,edges.q2min,edges.q2max);
        fill_moments_hist_2D(hq2ctk_moments,coeffs_moments,edges,npol,edges.q2min,edges.q2max);
        fill_moments_hist_2D(hq2phi_moments,coeffs_moments,edges,npol,edges.q2min,edges.q2max);

        if(opts->only_4_1D_chi2){
            //reset globalvalues
            globalvalues->chi2_4_1D     = 0.0;
            globalvalues->chi2_4_1D_ctk = 0.0;
            globalvalues->chi2_4_1D_ctl = 0.0;
            globalvalues->chi2_4_1D_phi = 0.0;
            globalvalues->chi2_4_1D_q2  = 0.0;
            globalvalues->ndof_4_1D     = 0;
            globalvalues->ndof_4_1D_ctk = 0;
            globalvalues->ndof_4_1D_ctl = 0;
            globalvalues->ndof_4_1D_phi = 0;
            globalvalues->ndof_4_1D_q2  = 0;

            //first scale moments to match integral of event histograms
            hctk_moments->Scale(hctk->Integral()/hctk_moments->Integral());
            hctl_moments->Scale(hctl->Integral()/hctl_moments->Integral());
            hphi_moments->Scale(hphi->Integral()/hphi_moments->Integral());
            hq2_moments ->Scale(hq2->Integral()/hq2_moments->Integral());

            //get chi2 from all 4 variables
            for(UInt_t b = 1; b <= nBinsAngles; b++){
                globalvalues->chi2_4_1D_ctk += sqr((hctk->GetBinContent(b) - hctk_moments->GetBinContent(b))) / fabs(hctk_moments->GetBinContent(b));
                globalvalues->chi2_4_1D_ctl += sqr((hctl->GetBinContent(b) - hctl_moments->GetBinContent(b))) / fabs(hctl_moments->GetBinContent(b));
                globalvalues->chi2_4_1D_phi += sqr((hphi->GetBinContent(b) - hphi_moments->GetBinContent(b))) / fabs(hphi_moments->GetBinContent(b));
            }
            for(UInt_t b = 1; b <= nBinsQ2; b++){
                globalvalues->chi2_4_1D_q2 += sqr((hq2->GetBinContent(b) - hq2_moments->GetBinContent(b))) / fabs(hq2_moments->GetBinContent(b));
            }
            //sum of all four 1D histograms
            globalvalues->chi2_4_1D = globalvalues->chi2_4_1D_ctk + globalvalues->chi2_4_1D_ctl + globalvalues->chi2_4_1D_phi + globalvalues->chi2_4_1D_q2;

            //determine ndof for all angles and q2
            //NOT sure if these ndofs are 100% correct, but good enough for a relative measure
            globalvalues->ndof_4_1D_ctk = nBinsAngles - (npol.ctk-1) - 1;
            globalvalues->ndof_4_1D_ctl = nBinsAngles - (npol.ctl-1) - 1;
            globalvalues->ndof_4_1D_phi = nBinsAngles - (npol.phi-1) - 1;
            globalvalues->ndof_4_1D_q2  = nBinsQ2 - (npol.q2-1) - 1;
            globalvalues->ndof_4_1D = 3*nBinsAngles + nBinsQ2 - 1 - (npol.ctk + npol.ctl + npol.phi + npol.q2 - 1);

        }

        //plot
        plotAngular(hq2,hq2_moments,opts->write_eps,"q2eff",appendix, subfolder);
        plotAngular(hctl,hctl_moments,opts->write_eps,"ctleff",appendix, subfolder);
        plotAngular(hctk,hctk_moments,opts->write_eps,"ctkeff",appendix, subfolder);
        plotAngular(hphi,hphi_moments,opts->write_eps,"phieff",appendix, subfolder);

        if (tagNumber < 0){ //do not plot when testing
            plotAngular(hctlctk,hctlctk_moments,opts->write_eps,"ctlctkeff",appendix, subfolder);
            plotAngular(hctlphi,hctlphi_moments,opts->write_eps,"ctlphieff",appendix, subfolder);
            plotAngular(hctkphi,hctkphi_moments,opts->write_eps,"ctkphieff",appendix, subfolder);
            plotAngular(hq2ctl,hq2ctl_moments,opts->write_eps,"q2ctleff",appendix, subfolder);
            plotAngular(hq2ctk,hq2ctk_moments,opts->write_eps,"q2ctkeff",appendix, subfolder);
            plotAngular(hq2phi,hq2phi_moments,opts->write_eps,"q2phieff",appendix, subfolder);
        }

        delete hq2;     delete hq2_moments;
        delete hctl;    delete hctl_moments;
        delete hctk;    delete hctk_moments;
        delete hphi;    delete hphi_moments;
        delete hctlctk; delete hctlctk_moments;
        delete hctkphi; delete hctkphi_moments;
        delete hctlphi; delete hctlphi_moments;
        delete hq2ctl;  delete hq2ctl_moments;
        delete hq2ctk;  delete hq2ctk_moments;
        delete hq2phi;  delete hq2phi_moments;


        hxi_moments[0]->SetMinimum(1.0); hxi_moments[0]->SetMaximum(+2.0);
        hxi_moments[1]->SetMinimum(0.25); hxi_moments[1]->SetMaximum(+1.25);
        hxi_moments[2]->SetMinimum(-1.0); hxi_moments[2]->SetMaximum(+0.0);
        hxi_moments[3]->SetMinimum(-0.75); hxi_moments[3]->SetMaximum(+0.25);

        for (int i = 4; i<12; i++){
            hxi_moments[i]->SetMinimum(-0.5);
            hxi_moments[i]->SetMaximum(+0.5);
        }

        for (auto hist: hxi_moments) hist->GetYaxis()->SetTitleOffset(1.4);

        TCanvas* c10 = new TCanvas("c10", "c10", 1600, 1200);
        c10->Divide(4,3);
        plotAngularInOne(c10,1,hxi_moments[0],hxi_ub[0],hxi_hist[0],hxi_ml_hist[0],runMinuit,edges.q2min,edges.q2max,1.5,false);
        plotAngularInOne(c10,2,hxi_moments[1],hxi_ub[1],hxi_hist[1],hxi_ml_hist[1],runMinuit,edges.q2min,edges.q2max,0.75,false);
        plotAngularInOne(c10,3,hxi_moments[2],hxi_ub[2],hxi_hist[2],hxi_ml_hist[2],runMinuit,edges.q2min,edges.q2max,-0.5,false);
        plotAngularInOne(c10,4,hxi_moments[3],hxi_ub[3],hxi_hist[3],hxi_ml_hist[3],runMinuit,edges.q2min,edges.q2max,-0.25,false);
        for (int i = 4; i<12; i++){
            plotAngularInOne(c10,i+1,hxi_moments[i],hxi_ub[i],hxi_hist[i],hxi_ml_hist[i],runMinuit,
                             edges.q2min,edges.q2max,0.0,is_param_folded(i,opts));
        }

        if(opts->write_eps)c10->Print((subfolder + "/xis"+appendix+".eps").c_str(),"eps");

        delete c10;

        spdlog::info( "end PLOTS" );

    }//end make plots

    //delete histograms
    for (auto hist: hxi_moments) delete hist;
    for (auto hist: hxi_ml_hist) delete hist;
    for (auto hist: hxi_hist) delete hist;
    for (auto hist: hxi_ub) delete hist;


}; //end parametrize_eff_phsp_4d()

double bu2kstarmumu_pdf::get_ang_eff(fcnc::options *opts,
                                     double q2, double ctl, double ctk, double phi) const{
    npolynom npol(opts);
    assert(coeffs_eff_4d.size() == npol.getSize());

    //Posibly can be faster, but with more memory consumption and unless I calculate this for like a million events it doesn't make a difference. This way the code is readable and usable at many places
    double eff = 0.0;

    for(unsigned int h = 0; h<npol.q2; h++){
        for(unsigned int i = 0; i<npol.ctl; i++){
            for(unsigned int j = 0; j<npol.ctk; j++){
                for(unsigned int k = 0; k<npol.phi; k++){
                    eff += coeffs_eff_4d[npol.get_bin_in4D(h,i,j,k)]
                            *pow(q2,h)*pow(ctl,i)*pow(ctk,j)*pow(phi,k);
                }
            }
        }
    }
    //spdlog::trace("ctl={0:f}\t ctk={1:f}\t phi={2:f}\t eff={3:f}", ctl, ctk, phi, eff);
    return eff;
}


double bu2kstarmumu_pdf::get_ang_eff(fcnc::options *opts,  fcnc::event evt, bool fa) const{
    //fa=true meansusing the evt. _fa values
    if (fa) return get_ang_eff(opts, evt.q2, evt.costhetal_fa, evt.costhetak_fa, evt.phi_fa);
    else return get_ang_eff(opts, evt.q2, evt.costhetal, evt.costhetak, evt.phi);
}

void bu2kstarmumu_pdf::eff_fcn(Int_t &npar, Double_t *grad, Double_t &lh, Double_t *params, Int_t iflag)
{
    //TODO full angular fit
    npolynom npol(current_pdf->opts);

    std::vector<double> chebyshev_costhetal(npol.ctl, 0.0);
    std::vector<double> chebyshev_costhetak(npol.ctk, 0.0);
    std::vector<double> chebyshev_phi(npol.phi, 0.0);

    for (unsigned int j = 0; j<npol.ctl; j++) chebyshev_costhetal.at(j) = params[j];
    for (unsigned int j = 0; j<npol.ctk; j++) chebyshev_costhetak.at(j) = params[npol.ctl+j];
    for (unsigned int j = 0; j<npol.phi; j++)       chebyshev_phi.at(j) = params[npol.ctl+npol.ctk+j];

    std::vector<double> coeffs_costhetal(npol.ctl, 0.0);
    std::vector<double> coeffs_costhetak(npol.ctk, 0.0);
    std::vector<double> coeffs_phi(npol.phi, 0.0);

    std::vector<double> temp_ctl(npol.ctl, 0.0);
    std::vector<double> temp_ctk(npol.ctk, 0.0);
    std::vector<double> temp_phi(npol.phi, 0.0);

    chebyshev_to_poly(chebyshev_costhetal, temp_ctl);
    chebyshev_to_poly(chebyshev_costhetak, temp_ctk);
    chebyshev_to_poly(chebyshev_phi, temp_phi);

    for (unsigned int i=0; i<coeffs_costhetak.size(); i++){
        coeffs_costhetak.at(i) = temp_ctk.at(i);
    }
    for (unsigned int i=0; i<coeffs_costhetal.size(); i++){
        coeffs_costhetal.at(i) = temp_ctl.at(i);
    }
    correct_poly(temp_phi, coeffs_phi, -TMath::Pi(), +TMath::Pi());

    double cijk = 0.0;
    double f1 = 0.0, f2 = 0.0, f3 = 0.0, f4 = 0.0, f5 = 0.0,
            f6 = 0.0, f7 = 0.0, f8 = 0.0, f9 = 0.0, f10 = 0.0, f11 = 0.0, f12 = 0.0;
    const double pi = MY_PI;
    double c = C_PWAVE;
    if(current_pdf->opts->full_angular)c *= 2.0; //folding in phi

    if (current_pdf->opts->full_angular)
    {
        for (unsigned int i = 0; i<npol.ctl; i++)
            for (unsigned int j = 0; j<npol.ctk; j++)
                for (unsigned int k = 0; k<npol.phi; k++)
                {
                    cijk = c*coeffs_costhetal.at(i)*coeffs_costhetak.at(j)*coeffs_phi.at(k);

                    f1 += cijk * (pow(-1,i)/(i+1.0)+1.0/(i+1.0))*(2.0*pow(-1,j)/(j*j+4.0*j+3.0)+2.0/(j*j+4.0*j+3.0))*(pow(-1,k)*pow(pi,k+1)/(k+1.0)+pow(pi,k+1)/(k+1.0));
                    f2 += cijk * (pow(-1,i)/(i+1.0)+1.0/(i+1.0))*(pow(-1,j)/(j+3.0)+1.0/(j+3.0))*(pow(-1,k)*pow(pi,k+1)/(k+1.0)+pow(pi,k+1)/(k+1.0));
                    f3 += cijk * (((i-1.0)*pow(-1,i)/(i*i+4.0*i+3.0)+(i-1.0)/(i*i+4.0*i+3.0))*(2.0*pow(-1,j)/(j*j+4.0*j+3.0)+2.0/(j*j+4.0*j+3.0))*(pow(-1,k)*pow(pi,k+1)/(k+1.0)+pow(pi,k+1)/(k+1.0)));
                    f4 += cijk * (((i-1.0)*pow(-1,i)/(i*i+4.0*i+3.0)+(i-1.0)/(i*i+4.0*i+3.0))*(pow(-1,j)/(j+3.0)+1/(j+3.0))*(pow(-1,k)*pow(pi,k+1.0)/(k+1.0)+pow(pi,k+1)/(k+1.0)));

                    f5 += cijk * (2.0*pow(-1,i)/(i*i+4.0*i+3.0)+2.0/(i*i+4.0*i+3.0))*(2.0*pow(-1,j)/(j*j+4.0*j+3.0)+2.0/(j*j+4.0*j+3.0))
                            *(integral_x_to_n_times_cos_2x(pi,k)-integral_x_to_n_times_cos_2x(-pi,k));
                    f6 += cijk * 4.0
                            *(integral_x_to_n_times_sqrt_1_minus_x2(1,j+1)-integral_x_to_n_times_sqrt_1_minus_x2(-1,j+1))
                            *(integral_x_to_n_times_sqrt_1_minus_x2(1,i+1)-integral_x_to_n_times_sqrt_1_minus_x2(-1,i+1))
                            *(integral_x_to_n_times_cos_x(pi,k)-integral_x_to_n_times_cos_x(-pi,k));
                    f7 += cijk * 2.0
                            *(integral_x_to_n_times_sqrt_1_minus_x2(1,j+1)-integral_x_to_n_times_sqrt_1_minus_x2(-1,j+1))
                            *(integral_x_to_n_times_sqrt_1_minus_x2(1,i)-integral_x_to_n_times_sqrt_1_minus_x2(-1,i))
                            *(integral_x_to_n_times_cos_x(pi,k)-integral_x_to_n_times_cos_x(-pi,k));
                    f8 += cijk * (1.0/(i+2.0)-pow(-1,i)/(i+2.0))*(2.0*pow(-1,j)/(j*j+4.0*j+3.0)+2.0/(j*j+4.0*j+3.0))*(pow(-1,k)*pow(pi,k+1)/(k+1.0)+pow(pi,k+1)/(k+1.0));

                    f9 += cijk * (1.0/(i+2.0)-pow(-1,i)/(i+2.0))*(pow(-1,j)/(j+3.0)+1.0/(j+3.0))*(pow(-1,k)*pow(pi,k+1)/(k+1.0)+pow(pi,k+1)/(k+1.0));
                    f10 += cijk * 2.0
                            *(integral_x_to_n_times_sqrt_1_minus_x2(1,j+1)-integral_x_to_n_times_sqrt_1_minus_x2(-1,j+1))
                            *(integral_x_to_n_times_sqrt_1_minus_x2(1,i)-integral_x_to_n_times_sqrt_1_minus_x2(-1,i))
                            *(integral_x_to_n_times_sin_x(pi,k)-integral_x_to_n_times_sin_x(-pi,k));
                    f11 += cijk * 4.0
                            *(integral_x_to_n_times_sqrt_1_minus_x2(1,j+1)-integral_x_to_n_times_sqrt_1_minus_x2(-1,j+1))
                            *(integral_x_to_n_times_sqrt_1_minus_x2(1,i+1)-integral_x_to_n_times_sqrt_1_minus_x2(-1,i+1))
                            *(integral_x_to_n_times_sin_x(pi,k)-integral_x_to_n_times_sin_x(-pi,k));
                    f12 += cijk * (2.0*pow(-1,i)/(i*i+4.0*i+3.0)+2.0/(i*i+4.0*i+3.0))*(2.0*pow(-1,j)/(j*j+4.0*j+3.0)+2.0/(j*j+4.0*j+3.0))
                            *(integral_x_to_n_times_sin_2x(pi,k)-integral_x_to_n_times_sin_2x(-pi,k));
                }
    }
    else
    {
        for (unsigned int i = 0; i<npol.ctl; i++)
            for (unsigned int j = 0; j<npol.ctk; j++)
                for (unsigned int k = 0; k<npol.phi; k++)
                {
                    cijk = c * coeffs_costhetal.at(i)*coeffs_costhetak.at(j)*coeffs_phi.at(k);
                    /*
          f1 += cijk * 2.0*(2.0*pow(-1,i)/(i*i+4.0*i+3.0)+2.0/(i*i+4.0*i+3.0))*(pow(-1,j)/(j+3.0)+1.0/(j+3.0))*pow(MY_PI,k+1)/(k+1.0);
          f2 += cijk * ((i+2.0)*pow(-1,i)/(i*i+4.0*i+3.0)+(i+2.0)/(i*i+4.0*i+3.0))*(2.0*pow(-1,j)/(j*j+4.0*j+3.0)+2.0/(j*j+4.0*j+3.0))*pow(MY_PI,k+1)/(k+1.0);
          f3 += cijk * (2.0*pow(-1,i)/(i*i+4.0*i+3.0)+2.0/(i*i+4.0*i+3.0))*(2.0*pow(-1,j)/(j*j+4.0*j+3.0)+2.0/(j*j+4.0*j+3.0))
        *(integral_x_to_n_times_cos_2x(MY_PI, k) - integral_x_to_n_times_cos_2x(0.0, k));
          f4 += cijk * 4.0*(1.0/(i+2.0)-pow(-1,i)/(i+2.0))*(2.0*pow(-1,j)/(j*j+4.0*j+3.0)+2.0/(j*j+4.0*j+3.0))*pow(MY_PI,k+1)/(3.0*(k+1.0));
          f5 += cijk * (2.0*pow(-1,i)/(i*i+4.0*i+3.0)+2.0/(i*i+4.0*i+3.0))*(2.0*pow(-1,j)/(j*j+4.0*j+3.0)+2.0/(j*j+4.0*j+3.0))
        *(integral_x_to_n_times_sin_2x(MY_PI, k) - integral_x_to_n_times_sin_2x(0.0, k));
*/
                    //TODO add f1 to f12 for new folding!

                    spdlog::error("Function 'eff_fcn' not yet implemented for folding");
                    assert(0);


                }
    }

    //calculate eps()
    std::vector<double> ch_costhetal;
    std::vector<double> ch_costhetak;
    std::vector<double> ch_phi;
    double lh_costhetal = 0.0;
    double lh_costhetak = 0.0;
    double lh_phi = 0.0;
    unsigned int nsize = current_pdf->eff_events.size();
    for (unsigned int i=0; i<nsize; i++)
    {
        event meas = current_pdf->eff_events.at(i);
        chebyshev(meas.costhetal, npol.ctl, ch_costhetal);
        chebyshev(meas.costhetak, npol.ctk, ch_costhetak);

        if (current_pdf->opts->full_angular)
            chebyshev(meas.phi/MY_PI, npol.phi, ch_phi);
        else
            chebyshev(2.0*meas.phi/MY_PI-1.0, npol.phi, ch_phi);
        //double result = 0.0;
        double result_costhetal = 0.0;
        for (unsigned int j = 0; j<npol.ctl; j++){
            result_costhetal += params[j]*ch_costhetal.at(j);// /norm_costhetal;
        }
        lh_costhetal += -2.0*TMath::Log(result_costhetal);
        double result_costhetak = 0.0;
        for (unsigned int j = 0; j<npol.ctk; j++){
            result_costhetak += params[npol.ctl+j]*ch_costhetak.at(j);// /norm_costhetak;
        }
        lh_costhetak += -2.0*TMath::Log(result_costhetak);
        double result_phi = 0.0;
        for (unsigned int j = 0; j<npol.phi; j++){
            result_phi += params[npol.ctl+npol.ctk+j]*ch_phi.at(j);// /norm_phi;
        }
        lh_phi += -2.0*TMath::Log(result_phi);
    }

    //multiply with the pdf
    const double j1s = current_pdf->eff_params->J1s();
    const double j1c = current_pdf->eff_params->J1c();
    const double j2s = current_pdf->eff_params->J2s();
    const double j2c = current_pdf->eff_params->J2c();
    const double j3 = current_pdf->eff_params->J3();
    const double j4 = current_pdf->eff_params->J4();
    const double j5 = current_pdf->eff_params->J5();
    const double j6s = current_pdf->eff_params->J6s();
    const double j6c = current_pdf->eff_params->J6c();
    const double j7 = current_pdf->eff_params->J7();
    const double j8 = current_pdf->eff_params->J8();
    const double j9 = current_pdf->eff_params->J9();

    double sig_norm = 0.0;
    sig_norm = (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4
                + f7*j5  + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9);
    double lh_sig = 0.0, prob = 0.0;

    for (unsigned int i=0; i<nsize; i++)
    {
        event meas = current_pdf->eff_events.at(i);
        if (current_pdf->opts->full_angular)
        {
            current_pdf->fj(meas.costhetal, meas.costhetak, meas.phi,
                            f1, f2, f3, f4, f5, f6,
                            f7, f8, f9, f10, f11, f12);
            prob = (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4
                    + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9)/sig_norm;
        }
        else
        {
            current_pdf->folded_fj(meas.costhetal, meas.costhetak, meas.phi, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12);
            /*
      prob = 9.0/16.0/MY_PI * (fl * f1
                     +ft * f2
                     +s3 * f3
                     +afb * f4
                     +aim * f5
                     )/sig_norm;
          */
            prob = (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4
                    + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9)/sig_norm;
        }
        lh_sig += -2.0*TMath::Log(prob);
    }
    //is the integral correct here?
    //need to do a nice integration over pdf_sig(costhetal,costhetak,phi)*eps(costhetal,costhetak,phi)
    lh = lh_sig + lh_costhetal + lh_costhetak + lh_phi;
};

//this does not assume factorisation of the three dependencies, but assumes phsp MC in 3D
void bu2kstarmumu_pdf::eff_fcn_phsp(Int_t &npar, Double_t *grad, Double_t &lh, Double_t *params, Int_t iflag)
{
    //TODO
    unsigned int ncosthetal = 5;
    unsigned int ncosthetak = 5;
    unsigned int nphi = 5;

    //make sure we are doing full angular acceptance, no phi folding
    assert(current_pdf->opts->full_angular);

    //need to account for the norm given the current parameters
    double norm = 0.0;
    for (unsigned int i = 0; i<ncosthetal; i++)
        for (unsigned int j = 0; j<ncosthetak; j++)
            for (unsigned int k = 0; k<nphi; k++)
            {
                double cijk = params[ncosthetak*nphi*i+nphi*j+k];
                norm += cijk
                        *(integral_chebyshev(+1.0, i)-integral_chebyshev(-1.0, i)) //TODO: check the -1,+1
                        *(integral_chebyshev(+1.0, j)-integral_chebyshev(-1.0, j))
                        *(integral_chebyshev(+1.0, k)-integral_chebyshev(-1.0, k));//there surely is a more efficient way
            }
    //this gives Sum_events Log(eps(event))
    lh = 0.0;
    unsigned int nsize = current_pdf->eff_events.size();
    //loop over all events
    for (unsigned int l=0; l<nsize; l++) {
        const event& meas = current_pdf->eff_events.at(l);
        std::vector<double> ch_costhetal;
        std::vector<double> ch_costhetak;
        std::vector<double> ch_phi;
        chebyshev(meas.costhetal, ncosthetal, ch_costhetal);
        chebyshev(meas.costhetak, ncosthetak, ch_costhetak);
        chebyshev(meas.phi/MY_PI, nphi, ch_phi);

        double result = 0.0;
        for (unsigned int i = 0; i<ncosthetal; i++)
            for (unsigned int j = 0; j<ncosthetak; j++)
                for (unsigned int k = 0; k<nphi; k++)
                {
                    double cijk  =  params[ncosthetak*nphi*i+nphi*j+k];
                    result += cijk*ch_costhetal.at(i)*ch_costhetak.at(j)*ch_phi.at(k);
                }
        if (result > 0.0 && !std::isnan(result) && !std::isinf(result))
            lh += -2.0*TMath::Log(result/norm) - (-2.0*TMath::Log(1.0/8.0));//can subtract
        else
            lh += (-100.0*2.0*TMath::Log(1.0/8.0));
    }
    spdlog::debug( "eff_fcn_phsp lh {0:f}\t npar {1:d}\tiflag {2:d}\tnorm {3:f}", lh, npar, iflag, norm);
};

void bu2kstarmumu_pdf::eff_fcn_phsp_4d(Int_t &npar, Double_t *grad, Double_t &lh, Double_t *params, Int_t iflag)
{
    npolynom npol(current_pdf->opts);

    double q2min  = current_pdf->opts->TheQ2binsmin.front(), q2max  = current_pdf->opts->TheQ2binsmax.back();
    double ctkmin = current_pdf->opts->ctk_min,              ctkmax = current_pdf->opts->ctk_max;
    double ctlmin = current_pdf->opts->ctl_min,              ctlmax = current_pdf->opts->ctl_max;
    double phimin = current_pdf->opts->phi_min,              phimax = current_pdf->opts->phi_max;

    spdlog::warn("The TMinuit version was not yet tested for folded analysis!");
    assert(current_pdf->opts->full_angular);

    //need to account for the norm given the current parameters
    double norm = 0.0;
    for (unsigned int h = 0; h<npol.q2; h++){
        for (unsigned int i = 0; i<npol.ctl; i++){
            for (unsigned int j = 0; j<npol.ctk; j++){
                for (unsigned int k = 0; k<npol.phi; k++){
                    double chijk  =  params[npol.get_bin_in4D(h,i,j,k)];
                    if (chijk == 0.0) continue;
                    norm += chijk
                            *(integral_chebyshev(+1.0, h)-integral_chebyshev(-1.0, h))
                            *(integral_chebyshev(+1.0, i)-integral_chebyshev(-1.0, i))
                            *(integral_chebyshev(+1.0, j)-integral_chebyshev(-1.0, j))
                            *(integral_chebyshev(+1.0, k)-integral_chebyshev(-1.0, k));//there surely is a more efficient way
                }
            }
        }
    }
    //this gives Sum_events Log(eps(event))
    lh = 0.0;
    unsigned int nsize = current_pdf->eff_events.size();
    //loop over all events
    for (unsigned int l=0; l<nsize; l++){
        const event& meas = current_pdf->eff_events.at(l);
        if (meas.q2 < q2min || meas.q2 > q2max) continue;

        std::vector<double> ch_q2;
        std::vector<double> ch_costhetal;
        std::vector<double> ch_costhetak;
        std::vector<double> ch_phi;
        chebyshev(2.0*(meas.q2        - q2min ) / (q2max  - q2min ) - 1.0, npol.q2,  ch_q2);
        chebyshev(2.0*(meas.costhetal - ctlmin) / (ctlmax - ctlmin) - 1.0, npol.ctl, ch_costhetal);
        chebyshev(2.0*(meas.costhetak - ctkmin) / (ctkmax - ctkmin) - 1.0, npol.ctk, ch_costhetak);
        chebyshev(2.0*(meas.phi       - phimin) / (phimax - phimin) - 1.0, npol.phi, ch_phi);
        /*
      chebyshev(meas.costhetal, ncosthetal, ch_costhetal);
      chebyshev(meas.costhetak, ncosthetak, ch_costhetak);
      chebyshev(meas.phi/pi, nphi, ch_phi);
*/
        double result = 0.0;
        for (unsigned int h = 0; h<npol.q2; h++)
            for (unsigned int i = 0; i<npol.ctl; i++)
                for (unsigned int j = 0; j<npol.ctk; j++)
                    for (unsigned int k = 0; k<npol.q2; k++)
                    {
                        double chijk  =  params[npol.get_bin_in4D(h,i,j,k)];
                        result += chijk*ch_q2.at(h)*ch_costhetal.at(i)*ch_costhetak.at(j)*ch_phi.at(k);
                    }
        if (result > 0.0 && !std::isnan(result) && !std::isinf(result)){
            lh += -2.0*meas.weight*TMath::Log(result/norm) - (-2.0*TMath::Log(1.0/16.0));
        }
        else{
            lh += (-100.0*2.0*TMath::Log(1.0/8.0));
        }
    }

    spdlog::debug( "eff_fcn_phsp_4d lh {0:f}\t npar {1:d}\tiflag {2:d}\tnorm {3:f}", lh, npar, iflag, norm);
};

void bu2kstarmumu_pdf::update_cached_normalization(const bu2kstarmumu_parameters* params)
{
    spdlog::trace("updating cached normalisation");
    //this caches the overall normalisation for both signal and background
    double q2 = params->eff_q2();
    //swave norm
    if (opts->use_mkpi || opts->fit_mkpi){
        double mkpi_min = opts->mkpi_min/1.0e+3;
        double mkpi_max = opts->mkpi_max/1.0e+3;
        //double q2 = params->eff_q2();
        double asphase = params->asphase();
        double a = params->a();
        double r = params->r();
        double gammakstar = params->gammakstar();
        double mkstar = params->mkstar();
        double gammakstarplus = params->gammakstarplus();
        double mkstarplus = params->mkstarplus();
        double R = params->R();

        double gammaf800 = params->gammaf800();
        double mf800 = params->mf800();
        double f800mag = params->f800mag();
        double f800phase = params->f800phase();
        //integrate over mkpi2
        const double mk   = PDGMASS_KST_KAON/1.0e+3;
        const double mpi  = PDGMASS_KST_PION/1.0e+3;
        const double mb   = PDGMASS_B/1.0e+3;
        const double from = opts->mkpi_full_range_norm ? (mk+mpi) : mkpi_min;
        const double to   = opts->mkpi_full_range_norm ?  mb      : mkpi_max;
        std::complex<double> mkpi_swavepwave_norm;
        if (opts->simple_mkpi){
            mkpi_swave_2_norm = int_mkpi_simple_kstarzero_amp_squared(from, to, q2,
                                                                      f800mag, f800phase, gammaf800, mf800, gammakstarplus, mkstarplus);
            mkpi_pwave_2_norm = int_mkpi_simple_bw_kstar_amp_squared(from, to, q2, gammakstar, mkstar);
            mkpi_swavepwave_norm = int_mkpi_simple_bw_kstar_amp_bar_kstarzero_amp(from, to, q2,
                                                                                  gammakstar, mkstar, asphase,
                                                                                  f800mag, f800phase, gammaf800, mf800, gammakstarplus, mkstarplus);
            mkpi_re_swavepwave_norm = mkpi_swavepwave_norm.real();
            mkpi_im_swavepwave_norm = mkpi_swavepwave_norm.imag();
        }
        else if (opts->isobar){
            mkpi_swave_2_norm = int_mkpi_kstarzero_isobar_amp_squared(from, to, q2,
                                                                      f800mag, f800phase, gammaf800, mf800, gammakstarplus, mkstarplus, R);
            mkpi_pwave_2_norm = int_mkpi_bw_kstar_amp_squared(from, to, q2, gammakstar, mkstar, R);
            mkpi_swavepwave_norm = int_mkpi_bw_kstar_amp_bar_kstarzero_isobar_amp(from, to, q2,
                                                                                  gammakstar, mkstar, asphase,
                                                                                  f800mag, f800phase, gammaf800, mf800, gammakstarplus, mkstarplus, R);
            mkpi_re_swavepwave_norm = mkpi_swavepwave_norm.real();
            mkpi_im_swavepwave_norm = mkpi_swavepwave_norm.imag();
        }
        else{//nominal lass
            mkpi_swave_2_norm = int_mkpi_kstarzero_lass_amp_squared(from, to, q2, asphase, a, r, gammakstarplus, mkstarplus, R);
            mkpi_pwave_2_norm = int_mkpi_bw_kstar_amp_squared(from, to, q2, gammakstar, mkstar, R);
            mkpi_swavepwave_norm = int_mkpi_bw_kstar_amp_bar_kstarzero_lass_amp(from, to, q2,
                                                                                gammakstar, mkstar,
                                                                                asphase, a, r, gammakstarplus, mkstarplus, R);
            mkpi_re_swavepwave_norm = mkpi_swavepwave_norm.real();
            mkpi_im_swavepwave_norm = mkpi_swavepwave_norm.imag();
        }

        if (opts->use_p2){
            //mkpi_bkg(
            std::vector<double> ch_p2(5, 0.0);
            ch_p2.at(0) = params->cbkgp20();
            ch_p2.at(1) = params->cbkgp21();
            ch_p2.at(2) = params->cbkgp22();
            ch_p2.at(3) = params->cbkgp23();
            ch_p2.at(4) = params->cbkgp24();
            std::vector<double> poly_p2(ch_p2.size(), 0.0);
            chebyshev_to_poly(ch_p2, poly_p2);
            std::vector<double> poly_corr_p2(ch_p2.size(), 0.0);
            correct_poly(poly_p2, poly_corr_p2, mkpi_min*mkpi_min, mkpi_max*mkpi_max);

            mkpi_bkg_norm = 0.0;
            for (unsigned int i=0; i<ch_p2.size(); i++)
                mkpi_bkg_norm += poly_corr_p2.at(i)*(pow(mkpi_max*mkpi_max,i+1)-pow(mkpi_min*mkpi_min,i+1))/(i+1.0);
        }
        else{
            std::vector<double> ch_mkpi(5, 0.0);
            ch_mkpi.at(0) = params->cbkgmkpi0();
            ch_mkpi.at(1) = params->cbkgmkpi1();
            ch_mkpi.at(2) = params->cbkgmkpi2();
            ch_mkpi.at(3) = params->cbkgmkpi3();
            ch_mkpi.at(4) = params->cbkgmkpi4();
            std::vector<double> poly_mkpi(ch_mkpi.size(), 0.0);
            chebyshev_to_poly(ch_mkpi, poly_mkpi);
            std::vector<double> poly_corr_mkpi(ch_mkpi.size(), 0.0);
            correct_poly(poly_mkpi, poly_corr_mkpi, mkpi_min, mkpi_max);

            mkpi_bkg_norm = 0.0;
            if (opts->mkpi_threshold){
                mkpi_bkg_norm = fcnc::int_threshold(mkpi_min, mkpi_max, 0.633, params->nthreshold());
            }
            else{
                for(unsigned int i=0; i<poly_mkpi.size(); i++)
                    mkpi_bkg_norm += poly_corr_mkpi.at(i)*(pow(mkpi_max,i+1)-pow(mkpi_min,i+1))/(i+1.0);
            }
        }
    }

    double f1=0.0, f2=0.0, f3=0.0, f4=0.0, f5=0.0, f6=0.0,
            f7=0.0, f8=0.0, f9=0.0, f10=0.0, f11=0.0, f12=0.0;

    double fs1=0.0, fs2=0.0, fs3=0.0, fs4=0.0, fs5=0.0, fs6=0.0;

    const double j1s = params->J1s();
    const double j1c = params->J1c();
    const double j2s = params->J2s();
    const double j2c = params->J2c();
    const double j3 = params->J3();
    const double j4 = params->J4();
    const double j5 = params->J5();
    const double j6s = params->J6s();
    const double j6c = params->J6c();
    const double j7 = params->J7();
    const double j8 = params->J8();
    const double j9 = params->J9();

    const double fs = params->FS();
    const double js1 = params->SS1();
    const double js2 = params->SS2();
    const double js3 = params->SS3();
    const double js4 = params->SS4();
    const double js5 = params->SS5();

    if(spdlog_trace()){ //print out the integrated angular moments. with and without weights, as well as their ratios

        load_coeffs_eff_phsp_4d();

        integrated_fj_chebyshev(f1, f2, f3, f4, f5, f6,
                                f7, f8, f9, f10, f11, f12, q2);
        double pwave_weighted = (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4
                                 + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9);

        integrated_fj_noacc(f1, f2, f3, f4, f5, f6,
                            f7, f8, f9, f10, f11, f12);
        double pwave_noweights = (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4
                                  + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9);

        swave_integrated_fj_chebyshev(fs1, fs2, fs3, fs4, fs5, fs6, q2);

        swave_integrated_fj_noacc(fs1, fs2, fs3, fs4, fs5, fs6);

        spdlog::trace("Integrated P-wave:");
        spdlog::trace("P-wave no weights: {0:0.5f}",pwave_noweights);
        spdlog::trace("P-wave weighted:   {0:0.5f}",pwave_weighted);
        spdlog::trace("P-wave ratio:      {0:0.5f}\n",pwave_weighted / pwave_noweights);

        if (opts->swave){
            double swave_noweights = (fs1*fs + fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5);
            double swave_weighted = (fs1*fs + fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5);
            spdlog::trace("Integrated S-wave:");
            spdlog::trace("S-wave weighted:   {0:0.5f}{0:0.5f}",swave_weighted);
            spdlog::trace("S-wave no weights: {0:0.5f}",swave_noweights);
            spdlog::trace("S-wave ratio:      {0:0.5f}\n",swave_weighted / swave_noweights);
            spdlog::trace("Double ratio (s/p):{0:0.5f}",(swave_weighted / swave_noweights) / (pwave_weighted / pwave_noweights));
        }

        //assert(0);

    }

    if(opts->full_angular){
        if(!opts->cache_fis){
            if(opts->use_angular_acc){
                integrated_fj_chebyshev(f1, f2, f3, f4, f5, f6,
                                        f7, f8, f9, f10, f11, f12, q2);
            }
            else{//this is also what the weighted fit uses!
                integrated_fj_noacc(f1, f2, f3, f4, f5, f6,
                                    f7, f8, f9, f10, f11, f12);
            }
            fbuffer_f1 = f1;
            fbuffer_f2 = f2;
            fbuffer_f3 = f3;
            fbuffer_f4 = f4;
            fbuffer_f5 = f5;
            fbuffer_f6 = f6;
            fbuffer_f7 = f7;
            fbuffer_f8 = f8;
            fbuffer_f9 = f9;
            fbuffer_f10 = f10;
            fbuffer_f11 = f11;
            fbuffer_f12 = f12;
        }
        else{
            f1 = fbuffer_f1;
            f2 = fbuffer_f2;
            f3 = fbuffer_f3;
            f4 = fbuffer_f4;
            f5 = fbuffer_f5;
            f6 = fbuffer_f6;
            f7 = fbuffer_f7;
            f8 = fbuffer_f8;
            f9 = fbuffer_f9;
            f10 = fbuffer_f10;
            f11 = fbuffer_f11;
            f12 = fbuffer_f12;
        }
        fnorm_sig = (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3  + f6*j4
                     +  f7*j5  + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9);
        if (opts->fit_asymmetries){
            fnorm_sig = (f1*j1s + f2*j1c + f3*j2s + f4*j2c);
        }

        bool swave = opts->swave;
        if (swave){
            if (!opts->cache_fis){
                if (opts->use_angular_acc){
                    swave_integrated_fj_chebyshev(fs1, fs2, fs3, fs4, fs5, fs6, q2);
                }
                else{//weighted fit and fit without acceptance
                    swave_integrated_fj_noacc(fs1, fs2, fs3, fs4, fs5, fs6);
                }

                fbuffer_fs1 = fs1;
                fbuffer_fs2 = fs2;
                fbuffer_fs3 = fs3;
                fbuffer_fs4 = fs4;
                fbuffer_fs5 = fs5;
                fbuffer_fs6 = fs6;
            }
            else{
                fs1 = fbuffer_fs1;
                fs2 = fbuffer_fs2;
                fs3 = fbuffer_fs3;
                fs4 = fbuffer_fs4;
                fs5 = fbuffer_fs5;
                fs6 = fbuffer_fs6;
            }
            fnorm_sig = (1.0-fs) * (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4 + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9)
                    + (fs1*fs + fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5);
            if (opts->fit_asymmetries){
                fnorm_sig = (1.0-fs)*(f1*j1s + f2*j1c + f3*j2s + f4*j2c)
                        + (fs1*fs + fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5);
            }
        }

        spdlog::trace("Fnormsig (full_angular) {0:f}",fnorm_sig );
        spdlog::trace("Normalization integrals P-wave");
        spdlog::trace("\tj1s {0:0.4f}",f1/fnorm_sig);
        spdlog::trace("\tj1c {0:0.4f}",f2/fnorm_sig);
        spdlog::trace("\tj2s {0:0.4f}",f3/fnorm_sig);
        spdlog::trace("\tj2c {0:0.4f}",f4/fnorm_sig);
        spdlog::trace("\tj3 {0:0.4f}",f5/fnorm_sig);
        spdlog::trace("\tj4 {0:0.4f}",f6/fnorm_sig);
        spdlog::trace("\tj5 {0:0.4f}",f7/fnorm_sig);
        spdlog::trace("\tj6s {0:0.4f}",f8/fnorm_sig);
        spdlog::trace("\tj6c {0:0.4f}",f9/fnorm_sig);
        spdlog::trace("\tj7 {0:0.4f}",f10/fnorm_sig);
        spdlog::trace("\tj8 {0:0.4f}",f11/fnorm_sig);
        spdlog::trace("\tj9 {0:0.4f}",f12/fnorm_sig);
        if(opts->swave){
            spdlog::trace("Normalization integrals S-wave");
            spdlog::trace("fs {0:0.4f}",fs1/fnorm_sig);
            spdlog::trace("\tas1 {0:0.4f}",fs2/fnorm_sig);
            spdlog::trace("\tas2 {0:0.4f}",fs3/fnorm_sig);
            spdlog::trace("\tas3 {0:0.4f}",fs4/fnorm_sig);
            spdlog::trace("\tas4 {0:0.4f}",fs5/fnorm_sig);
            spdlog::trace("\tas5 {0:0.4f}",fs6/fnorm_sig);
        }

        spdlog::trace("Angular amplitudes: ");
        spdlog::trace("\tj1s {0:0.4f}",j1s);
        spdlog::trace("\tj1c {0:0.4f}",j1c);
        spdlog::trace("\tj2s {0:0.4f}",j2s);
        spdlog::trace("\tj2c {0:0.4f}",j2c);
        spdlog::trace("\tj3 {0:0.4f}",j3);
        spdlog::trace("\tj4 {0:0.4f}",j4);
        spdlog::trace("\tj5 {0:0.4f}",j5);
        spdlog::trace("\tj6s {0:0.4f}",j6s);
        spdlog::trace("\tj6c {0:0.4f}",j6c);
        spdlog::trace("\tj7 {0:0.4f}",j7);
        spdlog::trace("\tj8 {0:0.4f}",j8);
        spdlog::trace("\tj9 {0:0.4f}",j9);

    }
    else //folded normalization!
    {
        if (!opts->cache_fis) {
            if (opts->use_angular_acc){
                folded_integrated_fj_chebyshev(f1, f2, f3, f4, f5, f6,
                                               f7, f8, f9, f10, f11, f12, q2);
            }
            else{//this is also what the weighted fit uses!
                folded_integrated_fj_noacc(f1, f2, f3, f4, f5, f6,
                                           f7, f8, f9, f10, f11, f12);
            }
            fbuffer_f1 = f1;
            fbuffer_f2 = f2;
            fbuffer_f3 = f3;
            fbuffer_f4 = f4;
            fbuffer_f5 = f5;
            fbuffer_f6 = f6;
            fbuffer_f7 = f7;
            fbuffer_f8 = f8;
            fbuffer_f9 = f9;
            fbuffer_f10 = f10;
            fbuffer_f11 = f11;
            fbuffer_f12 = f12;
        }
        else{
            f1 = fbuffer_f1;
            f2 = fbuffer_f2;
            f3 = fbuffer_f3;
            f4 = fbuffer_f4;
            f5 = fbuffer_f5;
            f6 = fbuffer_f6;
            f7 = fbuffer_f7;
            f8 = fbuffer_f8;
            f9 = fbuffer_f9;
            f10 = fbuffer_f10;
            f11 = fbuffer_f11;
            f12 = fbuffer_f12;
        }

        fnorm_sig = (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4
                     + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9);
        if (opts->fit_asymmetries){
            fnorm_sig = (f1*j1s + f2*j1c + f3*j2s + f4*j2c);
        }
        bool swave = opts->swave;
        if (swave){
            if (!opts->cache_fis) {
                if (opts->use_angular_acc ){
                    folded_swave_integrated_fj_chebyshev(fs1, fs2, fs3, fs4, fs5, fs6, q2);
                }
                else{//weighted fit and fit without acceptance
                    folded_swave_integrated_fj_noacc(fs1, fs2, fs3, fs4, fs5, fs6);
                }

                fbuffer_fs1 = fs1;
                fbuffer_fs2 = fs2;
                fbuffer_fs3 = fs3;
                fbuffer_fs4 = fs4;
                fbuffer_fs5 = fs5;
                fbuffer_fs6 = fs6;
            }
            else {
                fs1 = fbuffer_fs1;
                fs2 = fbuffer_fs2;
                fs3 = fbuffer_fs3;
                fs4 = fbuffer_fs4;
                fs5 = fbuffer_fs5;
                fs6 = fbuffer_fs6;
            }
            fnorm_sig = (1-fs) * (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4 + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9)
                    + (fs1*fs + fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5);
            if (opts->fit_asymmetries)
                fnorm_sig = (1-fs)*(f1*j1s + f2*j1c + f3*j2s + f4*j2c)
                        +(fs1*fs + fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5);
        }

        spdlog::trace("Fnormsig (folding {0:d}) {1:f}", opts->folding, fnorm_sig );
        spdlog::trace("Normalization integrals P-wave");
        spdlog::trace("\tj1s {0:0.4f}",f1/fnorm_sig);
        spdlog::trace("\tj1c {0:0.4f}",f2/fnorm_sig);
        spdlog::trace("\tj2s {0:0.4f}",f3/fnorm_sig);
        spdlog::trace("\tj2c {0:0.4f}",f4/fnorm_sig);
        spdlog::trace("\tj3 {0:0.4f}",f5/fnorm_sig);
        spdlog::trace("\tj4 {0:0.4f}",f6/fnorm_sig);
        spdlog::trace("\tj5 {0:0.4f}",f7/fnorm_sig);
        spdlog::trace("\tj6s {0:0.4f}",f8/fnorm_sig);
        spdlog::trace("\tj6c {0:0.4f}",f9/fnorm_sig);
        spdlog::trace("\tj7 {0:0.4f}",f10/fnorm_sig);
        spdlog::trace("\tj8 {0:0.4f}",f11/fnorm_sig);
        spdlog::trace("\tj9 {0:0.4f}",f12/fnorm_sig);

        if(opts->swave){
            spdlog::trace("Normalization integrals S-wave");
            spdlog::trace("fs {0:0.4f}",fs1/fnorm_sig);
            spdlog::trace("\tas1 {0:0.4f}",fs2/fnorm_sig);
            spdlog::trace("\tas2 {0:0.4f}",fs3/fnorm_sig);
            spdlog::trace("\tas3 {0:0.4f}",fs4/fnorm_sig);
            spdlog::trace("\tas4 {0:0.4f}",fs5/fnorm_sig);
            spdlog::trace("\tas5 {0:0.4f}",fs6/fnorm_sig);
        }

        spdlog::trace("Angular amplitudes: ");
        spdlog::trace("\tj1s {0:0.4f}",j1s);
        spdlog::trace("\tj1c {0:0.4f}",j1c);
        spdlog::trace("\tj2s {0:0.4f}",j2s);
        spdlog::trace("\tj2c {0:0.4f}",j2c);
        spdlog::trace("\tj3 {0:0.4f}",j3);
        spdlog::trace("\tj4 {0:0.4f}",j4);
        spdlog::trace("\tj5 {0:0.4f}",j5);
        spdlog::trace("\tj6s {0:0.4f}",j6s);
        spdlog::trace("\tj6c {0:0.4f}",j6c);
        spdlog::trace("\tj7 {0:0.4f}",j7);
        spdlog::trace("\tj8 {0:0.4f}",j8);
        spdlog::trace("\tj9 {0:0.4f}",j9);


    }

    //bkg norm
    //this is used to convolute the acceptance into the pdf
    if(opts->use_angular_acc && opts->use_weighted_bkg){ //weighted background pdf
        if (opts->full_angular){
            fnorm_bkg = integral_bkg_chebyshev(params, CTL_MIN, CTL_MAX, CTK_MIN, CTK_MAX, PHI_MIN, PHI_MAX, q2);
        }
        else if(opts->fit_full_angular_bkg){

            fnorm_bkg = integral_bkg_chebyshev(params, CTL_MIN, CTL_MAX, opts->ctk_min, opts->ctk_max, PHI_MIN, PHI_MAX, q2); //TODO: check here whether this makes any sense (https://gitlab.cern.ch/LHCb-RD/ewp-Bplus2Kstmumu-AngAna/-/merge_requests/21)
        }
        else{
            fnorm_bkg = integral_bkg_chebyshev(params, opts->ctl_min, opts->ctl_max, opts->ctk_min, opts->ctk_max, opts->phi_min, opts->phi_max, q2);
        }
    }
    else{ //non weighted background pdf
        if (opts->full_angular){
            if (opts->flat_bkg) fnorm_bkg = RANGE_3D();
            else                fnorm_bkg = integral_bkg(params, opts->ctl_min, opts->ctl_max,
                                                         opts->ctk_min, opts->ctk_max,
                                                         opts->phi_min, opts->phi_max);
        }
        else{ //folded angles
            if (opts->flat_bkg){
                fnorm_bkg = (opts->ctk_max - opts->ctk_min) * (opts->ctl_max - opts->ctl_min) * (opts->phi_max - opts->phi_min);
            }
            else{
                if(opts->fit_full_angular_bkg){
                    fnorm_bkg = integral_bkg(params, CTL_MIN, CTL_MAX, CTK_MIN, CTK_MAX, PHI_MIN, PHI_MAX);
                }
                else{
                    fnorm_bkg = integral_bkg(params, opts->ctl_min, opts->ctl_max, opts->ctk_min, opts->ctk_max, opts->phi_min, opts->phi_max);
                }
            }
        }
    }
}

void bu2kstarmumu_pdf::update_cached_integrated_fis(const bu2kstarmumu_parameters* params)
{
    if(opts->cache_fis){
        double q2 = params->eff_q2();
        if(opts->full_angular){
            if(opts->use_angular_acc){
                integrated_fj_chebyshev(fbuffer_f1, fbuffer_f2, fbuffer_f3, fbuffer_f4, fbuffer_f5, fbuffer_f6,
                                        fbuffer_f7, fbuffer_f8, fbuffer_f9, fbuffer_f10, fbuffer_f11, fbuffer_f12, q2);
                swave_integrated_fj_chebyshev(fbuffer_fs1, fbuffer_fs2, fbuffer_fs3, fbuffer_fs4, fbuffer_fs5, fbuffer_fs6, q2);
            }
            else{//this is also what the weighted fit uses!
                integrated_fj_noacc(fbuffer_f1, fbuffer_f2, fbuffer_f3, fbuffer_f4, fbuffer_f5, fbuffer_f6,
                                    fbuffer_f7, fbuffer_f8, fbuffer_f9, fbuffer_f10, fbuffer_f11, fbuffer_f12);
                swave_integrated_fj_noacc(fbuffer_fs1, fbuffer_fs2, fbuffer_fs3, fbuffer_fs4, fbuffer_fs5, fbuffer_fs6);
            }
        }
        else{ //folded
            if(opts->use_angular_acc){
                folded_integrated_fj_chebyshev(fbuffer_f1, fbuffer_f2, fbuffer_f3, fbuffer_f4, fbuffer_f5, fbuffer_f6,
                                               fbuffer_f7, fbuffer_f8, fbuffer_f9, fbuffer_f10, fbuffer_f11, fbuffer_f12, q2);
                folded_swave_integrated_fj_chebyshev(fbuffer_fs1, fbuffer_fs2, fbuffer_fs3, fbuffer_fs4, fbuffer_fs5, fbuffer_fs6, q2);
            }
            else{//this is also what the weighted fit uses!
                folded_integrated_fj_noacc(fbuffer_f1, fbuffer_f2, fbuffer_f3, fbuffer_f4, fbuffer_f5, fbuffer_f6,
                                           fbuffer_f7, fbuffer_f8, fbuffer_f9, fbuffer_f10, fbuffer_f11, fbuffer_f12);
                folded_swave_integrated_fj_noacc(fbuffer_fs1, fbuffer_fs2, fbuffer_fs3, fbuffer_fs4, fbuffer_fs5, fbuffer_fs6);
            }
        }
    }
}

double bu2kstarmumu_pdf::mkpi_pwave_2(const bu2kstarmumu_parameters* params, const event& meas) const
{
    double mkpi = meas.mkpi/1.0e+3;
    double q2 = params->eff_q2();
    //if (opts->weighted_fit) TODO
    //  q2 = meas.q2;
    double gammakstar = params->gammakstar();
    double mkstar = params->mkstar();
    double R = params->R();
    if (opts->simple_mkpi) return mkpi_simple_bw_kstar_amp_squared(mkpi, q2, gammakstar, mkstar);
    else                   return mkpi_bw_kstar_amp_squared(mkpi, q2, gammakstar, mkstar, R);
}

double bu2kstarmumu_pdf::mkpi_swave_2(const bu2kstarmumu_parameters* params, const event& meas) const
{
    double mkpi = meas.mkpi/1.0e+3;
    double q2 = params->eff_q2();
    //if (opts->weighted_fit) TODO
    //  q2 = meas.q2;
    double asphase = params->asphase();
    double a = params->a();
    double r = params->r();
    double R = params->R();
    double gammakstarplus = params->gammakstarplus();
    double mkstarplus = params->mkstarplus();

    double gammaf800 = params->gammaf800();
    double mf800 = params->mf800();
    double f800mag = params->f800mag();
    double f800phase = params->f800phase();
    if (opts->simple_mkpi){
        return mkpi_simple_kstarzero_amp_squared(mkpi, q2,
                                                 f800mag, f800phase, gammaf800, mf800,
                                                 gammakstarplus, mkstarplus);
    }
    else if (opts->isobar){
        return mkpi_kstarzero_isobar_amp_squared(mkpi, q2,
                                                 f800mag, f800phase, gammaf800, mf800,
                                                 gammakstarplus, mkstarplus, R);
    }
    else{//LASS
        return mkpi_kstarzero_lass_amp_squared(mkpi, q2, asphase, a, r, gammakstarplus, mkstarplus, R);
    }
}

double bu2kstarmumu_pdf::mkpi_re_swavepwave(const bu2kstarmumu_parameters* params, const event& meas) const
{
    double mkpi = meas.mkpi/1.0e+3;
    double q2 = params->eff_q2();
    //  if (opts->weighted_fit) //TODO
    //    q2 = meas.q2;
    double gammakstar = params->gammakstar();
    double mkstar = params->mkstar();
    double asphase = params->asphase();
    double a = params->a();
    double r = params->r();
    double gammakstarplus = params->gammakstarplus();
    double mkstarplus = params->mkstarplus();
    double R = params->R();

    double gammaf800 = params->gammaf800();
    double mf800 = params->mf800();
    double f800mag = params->f800mag();
    double f800phase = params->f800phase();

    std::complex<double> swavepwave;
    if (opts->simple_mkpi){
        swavepwave = mkpi_simple_bw_kstar_amp_bar_kstarzero_amp(mkpi, q2,
                                                                gammakstar, mkstar, asphase,
                                                                f800mag, f800phase, gammaf800, mf800,
                                                                gammakstarplus, mkstarplus);
    }
    else if (opts->isobar){
        swavepwave = mkpi_bw_kstar_amp_bar_kstarzero_isobar_amp(mkpi, q2,
                                                                gammakstar, mkstar, asphase,
                                                                f800mag, f800phase, gammaf800, mf800,
                                                                gammakstarplus, mkstarplus, R);
    }
    else{//LASS
        swavepwave = mkpi_bw_kstar_amp_bar_kstarzero_lass_amp(mkpi, q2, gammakstar, mkstar, asphase,
                                                              a, r, gammakstarplus, mkstarplus, R);
    }
    return swavepwave.real();
}

double bu2kstarmumu_pdf::mkpi_im_swavepwave(const bu2kstarmumu_parameters* params, const event& meas) const
{
    double mkpi = meas.mkpi/1.0e+3;
    double q2 = params->eff_q2();
    //if (opts->weighted_fit) //TODO
    //  q2 = meas.q2;
    double gammakstar = params->gammakstar();
    double mkstar = params->mkstar();
    double asphase = params->asphase();
    double a = params->a();
    double r = params->r();
    double gammakstarplus = params->gammakstarplus();
    double mkstarplus = params->mkstarplus();
    double R = params->R();

    double gammaf800 = params->gammaf800();
    double mf800 = params->mf800();
    double f800mag = params->f800mag();
    double f800phase = params->f800phase();

    std::complex<double> swavepwave;
    if (opts->simple_mkpi){
        swavepwave = mkpi_simple_bw_kstar_amp_bar_kstarzero_amp(mkpi, q2,
                                                                gammakstar, mkstar, asphase,
                                                                f800mag, f800phase, gammaf800, mf800,
                                                                gammakstarplus, mkstarplus);
    }
    else if (opts->isobar){
        swavepwave = mkpi_bw_kstar_amp_bar_kstarzero_isobar_amp(mkpi, q2,
                                                                gammakstar, mkstar, asphase,
                                                                f800mag, f800phase, gammaf800, mf800,
                                                                gammakstarplus, mkstarplus, R);
    }
    else{//LASS
        swavepwave = mkpi_bw_kstar_amp_bar_kstarzero_lass_amp(mkpi, q2, gammakstar, mkstar, asphase,
                                                              a, r, gammakstarplus, mkstarplus, R);
    }

    return swavepwave.imag();
}

double bu2kstarmumu_pdf::mkpi_bkg(const bu2kstarmumu_parameters* params, const event& meas) const
{
    double mkpi = meas.mkpi/1.0e+3;
    double mkpi_min = opts->mkpi_min/1.0e+3;
    double mkpi_max = opts->mkpi_max/1.0e+3;
    double probbkg = 0.0;
    if (opts->use_p2)    {
        std::vector<double> ch_p2(5, 0.0);
        ch_p2.at(0) = params->cbkgp20();
        ch_p2.at(1) = params->cbkgp21();
        ch_p2.at(2) = params->cbkgp22();
        ch_p2.at(3) = params->cbkgp23();
        ch_p2.at(4) = params->cbkgp24();
        std::vector<double> poly_p2(ch_p2.size(), 0.0);
        chebyshev_to_poly(ch_p2, poly_p2);
        std::vector<double> poly_corr_p2(ch_p2.size(), 0.0);
        correct_poly(poly_p2, poly_corr_p2, mkpi_min*mkpi_min, mkpi_max*mkpi_max);

        for (unsigned int i=0; i<ch_p2.size(); i++)
            probbkg += poly_corr_p2.at(i)*pow(mkpi*mkpi,i);
    }
    else
    {
        std::vector<double> ch_mkpi(5, 0.0);
        ch_mkpi.at(0) = params->cbkgmkpi0();
        ch_mkpi.at(1) = params->cbkgmkpi1();
        ch_mkpi.at(2) = params->cbkgmkpi2();
        ch_mkpi.at(3) = params->cbkgmkpi3();
        ch_mkpi.at(4) = params->cbkgmkpi4();
        std::vector<double> poly_mkpi(ch_mkpi.size(), 0.0);
        chebyshev_to_poly(ch_mkpi, poly_mkpi);
        std::vector<double> poly_corr_mkpi(ch_mkpi.size(), 0.0);
        correct_poly(poly_mkpi, poly_corr_mkpi, mkpi_min, mkpi_max);

        if (opts->mkpi_threshold){
            //threshold(double x, double thr, double n);
            probbkg = fcnc::threshold(mkpi, 0.633, params->nthreshold());
        }
        else{
            for (unsigned int i=0; i<ch_mkpi.size(); i++) probbkg += poly_corr_mkpi.at(i)*pow(mkpi,i);
        }
    }

    return probbkg;
}


void bu2kstarmumu_pdf::update_cached_xis(const bu2kstarmumu_parameters* params, std::vector<event>* events)
{
    spdlog::info("Updating cached xis");
    //this is used if per_event_norm is on
    //it stores the 18 xis for every event
    //otherwise using a different norm per event is computationally not possible
    //double q2 = params->eff_q2();
    assert(opts->use_event_norm);
    /*
    for (unsigned int e=0; e<events->size(); e++)
    {
    if ((e*100) % events->size() == 0)
    std::cout << (e*100)/events->size() << "% " << std::endl;
    event& meas = events->at(e);D
    double q2 = meas.q2;
    double f1=0.0, f2=0.0, f3=0.0, f4=0.0, f5=0.0, f6=0.0,
    f7=0.0, f8=0.0, f9=0.0, f10=0.0, f11=0.0, f12=0.0;
    integrated_fj_chebyshev(f1, f2, f3, f4, f5, f6,
    f7, f8, f9, f10, f11, f12, q2);
    meas.xis[0] = f1;
    meas.xis[1] = f2;
    meas.xis[2] = f3;
    meas.xis[3] = f4;
    meas.xis[4] = f5;
    meas.xis[5] = f6;
    meas.xis[6] = f7;
    meas.xis[7] = f8;
    meas.xis[8] = f9;
    meas.xis[9] = f10;
    meas.xis[10] = f11;
    meas.xis[11] = f12;
    bool swave = opts->swave;
    if (swave)
    {
    double fs1=0.0, fs2=0.0, fs3=0.0, fs4=0.0, fs5=0.0, fs6=0.0;
    swave_integrated_fj_chebyshev(fs1, fs2, fs3, fs4, fs5, fs6, q2);
    meas.xis[12] = fs1;
    meas.xis[13] = fs2;
    meas.xis[14] = fs3;
    meas.xis[15] = fs4;
    meas.xis[16] = fs5;
    meas.xis[17] = fs6;
    }
    }
  */
    /*
    for (unsigned int e=0; e<events->size(); e++)
    {
    event& meas = events->at(e);
    std::cout << meas.xis[0] << " "
    << meas.xis[1] << " "
    << meas.xis[2] << " "
    << meas.xis[3] << " "
    << meas.xis[4] << " "
    << meas.xis[5] << " "
    << meas.xis[6] << " "
    << meas.xis[7] << " "
    << meas.xis[8] << " "
    << meas.xis[9] << " "
    << meas.xis[10] << " "
    << meas.xis[11] << " "
    << meas.xis[12] << " "
    << meas.xis[13] << " "
    << meas.xis[14] << " "
    << meas.xis[15] << " "
    << meas.xis[16] << " "
    << meas.xis[17] << " q2 " << meas.q2 << std::endl;
    }
  */
    //std::cout << std::endl;
}

void bu2kstarmumu_pdf::update_cached_normalization(const parameters* params){
    update_cached_normalization(static_cast<const bu2kstarmumu_parameters*>(params));
}

void bu2kstarmumu_pdf::update_cached_integrated_fis(const parameters* params){
    update_cached_integrated_fis(static_cast<const bu2kstarmumu_parameters*>(params));
}


void bu2kstarmumu_pdf::update_cached_efficiencies(const parameters* params, std::vector<event>* events)
{
    update_cached_efficiencies(static_cast<const bu2kstarmumu_parameters*>(params), events);
}

void bu2kstarmumu_pdf::update_cached_xis(const parameters* params, std::vector<event>* events)
{
    update_cached_xis(static_cast<const bu2kstarmumu_parameters*>(params), events);
}


double bu2kstarmumu_pdf::m_sig_prob(const bu2kstarmumu_parameters* params, const event& meas) const{
    if (opts->only_angles || opts->only_mkpi) return 1.0;

    double mmin=params->m_b.get_min();
    double mmax=params->m_b.get_max();

    if(meas.m > mmax || meas.m < mmin){
        spdlog::error("m(B) outside of allowed range: mmin={0:f}, mmax={1:f} m={2:f}", mmin, mmax, meas.m);
        assert(0);
    }

    double result = 0.0;
    if(opts->crystalball){
        result = params->m_res_1()
                * crystalball(meas.m, params->m_b(), params->m_scale()*params->m_sigma_1(), params->alpha_1(), params->n_1())
                / integrate_crystalball(params->m_b(), params->m_scale()*params->m_sigma_1(), params->alpha_1(), params->n_1(), mmin, mmax);
        if(params->m_res_1() != 1.0)
            result += (1.0-params->m_res_1())
                    * crystalball(meas.m, params->m_b(), params->m_scale()*params->m_sigma_2(), params->alpha_2(), params->n_2())
                    / integrate_crystalball(params->m_b(), params->m_scale()*params->m_sigma_2(), params->alpha_2(), params->n_2(), mmin, mmax);
    }
    else if(opts->twotailedcrystalball){
        result = twotailedcrystalball(meas.m, params->m_b(), params->m_scale()*params->m_sigma_1(), params->alpha_1(), params->alpha_2(), params->n_1(), params->n_2())
                / integrate_twotailedcrystalball(params->m_b(), params->m_scale()*params->m_sigma_1(), params->alpha_1(), params->alpha_2(), params->n_1(), params->n_2(), mmin, mmax);
    }
    else{
        result = params->m_res_1()
                * normed_gauss(meas.m, params->m_scale()*params->m_sigma_1(), params->m_b(), mmin, mmax);
        if(params->m_res_1() != 1.0)
            result += (1.0-params->m_res_1())
                    * normed_gauss(meas.m, params->m_scale()*params->m_sigma_2(), params->m_b(), mmin, mmax);
    }
    return result;

};

double bu2kstarmumu_pdf::mkpi_sig_prob(const bu2kstarmumu_parameters* params, const event& meas) const
{
    if(!opts->fit_mkpi || opts->only_angles || opts->only_Bmass) return 1.0;

    assert(meas.mkpi <= opts->mkpi_max);
    assert(meas.mkpi >= opts->mkpi_min);

    double result = 0.0;

    double prob_mkpi_pwave = mkpi_pwave_2(params, meas);
    double prob_mkpi_swave = mkpi_swave_2(params, meas);
    double prob_mkpi_pwaveswave_re = mkpi_re_swavepwave(params, meas);
    double prob_mkpi_pwaveswave_im = mkpi_im_swavepwave(params, meas);
    double fs = params->FS();

    //only m(B) and m(K*+) fitted
    if(opts->only_mass2DFit){
        if(opts->swave){
            result = (1.0-fs)
                    * prob_mkpi_pwave/mkpi_pwave_2_norm
                    + fs
                    * prob_mkpi_swave/mkpi_swave_2_norm;
        }
        else{
            result = prob_mkpi_pwave/mkpi_pwave_2_norm;
        }
    }//end only m(B) and m(K*+)
    else{//angular dimensions included in the fit

        double q2 = meas.q2;

        //integrated S_i
        double xi1, xi2, xi3, xi4, xi5, xi6, xi7, xi8, xi9, xi10, xi11, xi12;
        if(opts->use_angular_acc) integrated_fj_chebyshev(xi1, xi2, xi3, xi4, xi5, xi6, xi7, xi8, xi9, xi10, xi11, xi12, q2);
        else                      integrated_fj_noacc(xi1, xi2, xi3, xi4, xi5, xi6, xi7, xi8, xi9, xi10, xi11, xi12);

        //integrated SS_i
        double sxi1, sxi2, sxi3, sxi4, sxi5, sxi6;
        if(opts->use_angular_acc) swave_integrated_fj_chebyshev(sxi1, sxi2, sxi3, sxi4, sxi5, sxi6, q2);
        else                      swave_integrated_fj_noacc(sxi1, sxi2, sxi3, sxi4, sxi5, sxi6);

        //load values for required parameters:
        const double s1s = params->S1s();
        const double s2s = s1s/3.0;
        const double s1c = 1.0 - 4.0/3.0 * s1s;
        const double s2c = -s1c;
        const double s3 = params->S3();
        const double s4 = params->S4();
        const double s5 = params->S5();
        const double s6s = params->S6s();
        const double s6c = 0.;
        const double s7 = params->S7();
        const double s8 = params->S8();
        const double s9 = params->S9();

        const double ss1 = params->SS1();
        const double ss2 = params->SS2();
        const double ss3 = params->SS3();
        const double ss4 = params->SS4();
        const double ss5 = params->SS5();

        double norm = (1 - fs) * (xi1*s1s + xi2*s1c + xi3*s2s + xi4*s2c + xi5*s3 + xi6*s4
                                  + xi7*s5 + xi8*s6s + xi9*s6c + xi10*s7 + xi11*s8 + xi12*s9)
                + (sxi1*fs)
                + (sxi2*ss1 + sxi3*ss2 + sxi4*ss3)
                + (sxi5*ss4 + sxi6*ss5);

        double A = (xi1*s1s + xi2*s1c + xi3*s2s + xi4*s2c + xi5*s3 + xi6*s4
                    +  xi7*s5 + xi8*s6s + xi9*s6c + xi10*s7 + xi11*s8 + xi12*s9)/norm;
        double B = sxi1/norm;
        double C = (sxi2*ss1 + sxi3*ss2 + sxi4*ss3)/norm;
        double D = (sxi5*ss4 + sxi6*ss5)/norm;

        result = (1.0-fs) * A * prob_mkpi_pwave/mkpi_pwave_2_norm
                +     fs  * B * prob_mkpi_swave/mkpi_swave_2_norm
                + C * prob_mkpi_pwaveswave_re/mkpi_re_swavepwave_norm
                + D * prob_mkpi_pwaveswave_im/mkpi_im_swavepwave_norm;
    }//end of prob with angular parts included
    return result;
};

double bu2kstarmumu_pdf::integral_m_sig_prob(const bu2kstarmumu_parameters* params, double ma, double mb) const
{

    double result = 0.0;
    if (opts->crystalball){
        double mmin=params->m_b.get_min(), mmax=params->m_b.get_max();
        result = params->m_res_1()*integrate_crystalball(params->m_b(), params->m_scale()*params->m_sigma_1(), params->alpha_1(), params->n_1(), ma, mb)
                / integrate_crystalball(params->m_b(), params->m_scale()*params->m_sigma_1(), params->alpha_1(), params->n_1(), mmin, mmax);
        if (params->m_res_1() != 1.0)
            result += (1.0-params->m_res_1()) * integrate_crystalball(params->m_b(), params->m_scale()*params->m_sigma_2(), params->alpha_2(), params->n_2(), ma, mb)
                    / integrate_crystalball(params->m_b(), params->m_scale()*params->m_sigma_2(), params->alpha_2(), params->n_2(), mmin, mmax);
    }
    else if(opts->twotailedcrystalball){
        double mmin=params->m_b.get_min(), mmax=params->m_b.get_max();
        result = integrate_twotailedcrystalball(params->m_b(), params->m_scale()*params->m_sigma_1(), params->alpha_1(), params->alpha_2(), params->n_1(), params->n_2(), ma, mb)
                / integrate_twotailedcrystalball(params->m_b(), params->m_scale()*params->m_sigma_1(), params->alpha_1(), params->alpha_2(), params->n_1(), params->n_2(), mmin, mmax);
    }
    else{
        double mean = params->m_b();
        double min = params->m_b.get_min();
        double max = params->m_b.get_max();
        double sigma1 = params->m_scale()*params->m_sigma_1();
        double sigma2 = params->m_scale()*params->m_sigma_2();
        double fres = params->m_res_1();
        double fullrange = (fres)*1.0/2.0*(TMath::Erf((max-mean)/(sqrt(2.0)*sigma1))-TMath::Erf((min-mean)/(sqrt(2.0)*sigma1)));
        if (params->m_res_1() != 1.0)
            fullrange += (1.0-fres)*1.0/2.0*(TMath::Erf((max-mean)/(sqrt(2.0)*sigma2))-TMath::Erf((min-mean)/(sqrt(2.0)*sigma2)));
        double integral = (fres)*1.0/2.0*(TMath::Erf((mb-mean)/(sqrt(2.0)*sigma1))-TMath::Erf((ma-mean)/(sqrt(2.0)*sigma1)));
        if (params->m_res_1() != 1.0)
            integral += (1.0-fres)*1.0/2.0*(TMath::Erf((mb-mean)/(sqrt(2.0)*sigma2))-TMath::Erf((ma-mean)/(sqrt(2.0)*sigma2)));
        result = integral/fullrange;
    }
    return result;

};


double bu2kstarmumu_pdf::m_bkg_prob(const bu2kstarmumu_parameters* params, const event& meas) const
{
    if (opts->only_angles || opts->only_mkpi) return 1.0;

    double mmin=params->m_b.get_min(), mmax=params->m_b.get_max();
    assert(meas.m <= mmax);
    assert(meas.m >= mmin);

    if(opts->cutsignalwindow){
        assert(meas.m <= opts->m_min || meas.m >= opts->m_max);
    }

    double fm1 = params->fm_tau();
    double tau1 = params->m_tau();
    double tau2 = params->m_tau_2();
    double lambda1 = params->m_lambda();
    double lambda2 = params->m_lambda_2();

    double result = 0.;

    if(opts->fit_lambda){
        if(lambda1 == 0.0)  return 1.0;
        double norm1 = 1.0/lambda1*(exp(mmax*lambda1) - exp(mmin*lambda1));
        if(opts->cutsignalwindow){
            norm1 -= 1.0/lambda1*(exp(opts->m_max*lambda1) - exp(opts->m_min*lambda1));
        }
        result = exp(meas.m*lambda1)/norm1;
        if (fm1 != 1.0){
            if(lambda2 == 0.0) return result;
            result *= fm1;
            double norm2 = 1.0/lambda2*(exp(mmax*lambda2) - exp(mmin*lambda2));
            if(opts->cutsignalwindow)
                norm2 -= 1.0/lambda2*(exp(opts->m_max*lambda2) - exp(opts->m_min*lambda2));
            result += (1.0-fm1)*exp(meas.m*lambda2)/norm2;
        }
    }
    else{
        double norm1 = tau1*(exp(-mmin/tau1) - exp(-mmax/tau1));
        if(opts->cutsignalwindow)
            norm1 -= tau1*(exp(-opts->m_min/tau1) - exp(-opts->m_max/tau1));
        result = exp(-meas.m/tau1)/norm1;
        if (fm1 != 1.0){
            result *= fm1;
            double norm2 = tau2*(exp(-mmin/tau2) - exp(-mmax/tau2));
            if(opts->cutsignalwindow)
                norm2 -= tau2*(exp(-opts->m_min/tau2) - exp(-opts->m_max/tau2));
            result += (1.0-fm1)*exp(-meas.m/tau2)/norm2;
        }
    }
    return result;
};

double bu2kstarmumu_pdf::mkpi_bkg_prob(const bu2kstarmumu_parameters* params, const event& meas) const
{
    if(!(opts->fit_mkpi || opts->use_mkpi) || opts->only_angles || opts->only_Bmass)
        return 1.0;

    assert(meas.mkpi <= opts->mkpi_max);
    assert(meas.mkpi >= opts->mkpi_min);

    double result = 0.0;

    double mkpi_min = opts->mkpi_min/1.0e+3;
    double mkpi_max = opts->mkpi_max/1.0e+3;
    double mkpi = meas.mkpi/1.0e+3;

    double normbkg=0.0;
    std::vector<double> ch_mkpi(5, 0.0);
    ch_mkpi.at(0) = params->cbkgmkpi0();
    ch_mkpi.at(1) = params->cbkgmkpi1();
    ch_mkpi.at(2) = params->cbkgmkpi2();
    ch_mkpi.at(3) = params->cbkgmkpi3();
    ch_mkpi.at(4) = params->cbkgmkpi4();
    std::vector<double> poly_mkpi(ch_mkpi.size(), 0.0);
    chebyshev_to_poly(ch_mkpi, poly_mkpi);
    std::vector<double> poly_corr_mkpi(ch_mkpi.size(), 0.0);
    correct_poly(poly_mkpi, poly_corr_mkpi, mkpi_min, mkpi_max);
    for(unsigned int i=0; i<poly_mkpi.size(); i++)
        normbkg += poly_corr_mkpi.at(i)*(pow(mkpi_max,i+1)-pow(mkpi_min,i+1))/(i+1.0);

    double probbkg = 0.0;
    for (unsigned int i=0; i<ch_mkpi.size(); i++)
        probbkg += poly_corr_mkpi.at(i)*pow(mkpi,i);

    result = probbkg/normbkg;

    return result;
};

double bu2kstarmumu_pdf::integral_m_bkg_prob(const bu2kstarmumu_parameters* params, double ma, double mb) const
{
    double mmin=params->m_b.get_min(), mmax=params->m_b.get_max();

    double bkg_frac = params->fm_tau();
    double tau1 = params->m_tau();
    double tau2 = params->m_tau_2();
    double lambda1 = params->m_lambda();
    double lambda2 = params->m_lambda_2();

    double result = 0.0;

    if(opts->fit_lambda){
        assert(lambda1 <= 0.);
        double fullrange1 = 1.0/lambda1*(exp(mmax*lambda1) - exp(mmin*lambda1));
        if(opts->cutsignalwindow){
            fullrange1 -= 1.0/lambda1*(exp(opts->m_max*lambda1) - exp(opts->m_min*lambda1));
        }
        double integral1 =  1.0/lambda1*(exp(mb*lambda1) - exp(ma*lambda1));
        result = integral1/fullrange1;
        if (bkg_frac != 1.0){
            assert(lambda2 <= 0.);
            result *= bkg_frac;
            double fullrange2 = 1.0/lambda2*(exp(mmax*lambda2) - exp(mmin*lambda2));
            if(opts->cutsignalwindow){
                fullrange2 -= 1.0/lambda2*(exp(opts->m_max*lambda2) - exp(opts->m_min*lambda2));
            }
            double integral2 = 1.0/lambda2*(exp(mb*lambda2) - exp(ma*lambda2));
            result += (1.0-bkg_frac)*integral2/fullrange2;
        }
    }
    else{
        double fullrange1 = tau1*(exp(-mmin/tau1) - exp(-mmax/tau1));
        if(opts->cutsignalwindow){
            fullrange1 -= tau1*(exp(opts->m_max/tau1) - exp(opts->m_min/tau1));
        }
        double integral1 = tau1*(exp(-ma/tau1) - exp(-mb/tau1));
        result = integral1/fullrange1;
        if (bkg_frac != 1.0){
            result *= bkg_frac;
            double fullrange2 = tau2*(exp(-mmin/tau2) - exp(-mmax/tau2));
            if(opts->cutsignalwindow){
                fullrange2 -= tau2*(exp(opts->m_max/tau2) - exp(opts->m_min/tau2));
            }
            double integral2 = tau2*(exp(-ma/tau2) - exp(-mb/tau2));
            result += (1.0-bkg_frac)*integral2/fullrange2;
        }
    }
    return result;
};

double bu2kstarmumu_pdf::angular_sig_prob(const bu2kstarmumu_parameters* params, const event& meas) const
{
    //Don't when only fitting the mass
    if (opts->only_Bmass || opts->only_mass2DFit || opts->only_mkpi) return 1.0;

    double ctl = meas.costhetal;
    double ctk = meas.costhetak;
    double phi = meas.phi;

    double result_swave = 0.0, result_pwave = 0.0;
    double result = 0.0;

    double f1=0.0, f2=0.0, f3=0.0, f4=0.0, f5=0.0, f6=0.0,
            f7=0.0, f8=0.0, f9=0.0, f10=0.0, f11=0.0, f12=0.0;
    const double j1s = params->J1s();
    const double j1c = params->J1c();
    const double j2s = params->J2s();
    const double j2c = params->J2c();
    const double j3 = params->J3();
    const double j4 = params->J4();
    const double j5 = params->J5();
    const double j6s = params->J6s();
    const double j6c = params->J6c();
    const double j7 = params->J7();
    const double j8 = params->J8();
    const double j9 = params->J9();

    if (opts->full_angular) fj(ctl, ctk, phi, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12);
    else             folded_fj(ctl, ctk, phi, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12);

    if (opts->fit_asymmetries && meas.cp_conjugate){
        result_pwave += (f1*j1s + f2*j1c + f3*j2s + f4*j2c - f5*j3  - f6*j4
                         - f7*j5  - f8*j6s - f9*j6c - f10*j7 - f11*j8 - f12*j9);
    }
    else{
        result_pwave += (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3  + f6*j4
                         + f7*j5  + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9);
    }

    if (opts->swave){
        double f1=0.0, f2=0.0, f3=0.0, f4=0.0, f5=0.0, f6=0.0;
        if(opts->full_angular) swave_fj(ctl, ctk, phi, f1, f2, f3, f4, f5, f6);
        else            folded_swave_fj(ctl, ctk, phi, f1, f2, f3, f4, f5, f6);
        const double fs = params->FS();
        const double js1 = params->SS1();
        const double js2 = params->SS2();
        const double js3 = params->SS3();
        const double js4 = params->SS4();
        const double js5 = params->SS5();
        result_pwave *= (1.0 - fs);
        result_swave += (fs*f1 + js1*f2 + js2*f3 + js3*f4 + js4*f5 + js5*f6);
        if (opts->use_mkpi){
            double prob_mkpi_pwave = mkpi_pwave_2(params, meas);
            double prob_mkpi_swave = mkpi_swave_2(params, meas);
            double prob_mkpi_re_swavepwave = mkpi_re_swavepwave(params, meas);
            double prob_mkpi_im_swavepwave = mkpi_im_swavepwave(params, meas);

            result_pwave *= prob_mkpi_pwave/mkpi_pwave_2_norm;
            if (std::isnan(result_pwave)) spdlog::warn("prob_mkpi_pwave {0:f}",prob_mkpi_pwave );

            result_swave = (fs*f1*prob_mkpi_swave/mkpi_swave_2_norm
                            + (js1*f2 + js2*f3 + js3*f4)*prob_mkpi_re_swavepwave/mkpi_re_swavepwave_norm
                            + (js4*f5 + js5*f6)*prob_mkpi_im_swavepwave/mkpi_im_swavepwave_norm);
        }
        if (std::isnan(result_swave) || std::isinf(result_swave)){
            spdlog::warn("\tresult_swave {0:0.6f}",result_swave);
            spdlog::warn("\t mkpi_swave_2_norm {0:0.6f}",mkpi_swave_2_norm);
            spdlog::warn("\t mkpi_pwave_2_norm {0:0.6f}",mkpi_pwave_2_norm);
            spdlog::warn("\t prob_mkpi_swave {0:0.6f}",mkpi_swave_2(params, meas));
            spdlog::warn("\t prob_mkpi_re_swavepwave {0:0.6f}",mkpi_re_swavepwave(params, meas));
            spdlog::warn("\t prob_mkpi_im_swavepwave {0:0.6f}",mkpi_im_swavepwave(params, meas));
            result_swave = 1.0e-12;
        }
    }

    result = result_pwave + result_swave;

    if(std::isnan(result) || std::isinf(result)){
        spdlog::error("result={0:0f}",result );
        spdlog::error("pwave={0:0f}",result_pwave );
        spdlog::error("swave={0:0f}",result_swave );
        assert(0);
    }

    //spdlog::trace("result={0:0f}",result );
    //spdlog::trace("pwave={0:0f}",result_pwave );
    //spdlog::trace("swave={0:0f}",result_swave );


    double eff = 1.0;
    if (opts->use_angular_acc){
        //for fixed effq2 the efficiency should have been calculated beforehand
        //for floating q2 should recalculate as done below
        //for event_norm, the efficiency should also have been calculated before
        bool q2fixed = (params->eff_q2.get_step_size() == 0.0);
        if ((q2fixed || opts->use_event_norm) && (!opts->mcweight))	{ //use event norm is only used when convoluting the acceptance into the pdf
            if (meas.weight > 0.0) eff = 1.0/meas.weight;
            spdlog::trace("eff={0:f}",eff);
        }
        else{
            double q2 = params->eff_q2();
            eff = this->get_ang_eff(opts,q2,meas.costhetal_fa, meas.costhetal_fa,meas.phi_fa);
            spdlog::trace("eff={0:f}",eff);
            spdlog::trace("q2={0:f}",q2);
        }
    }
    if(std::isnan(eff) || std::isinf(eff)){
        spdlog::error("eff={0:f}",eff );
        assert(0);
    }

    if(eff/fnorm_sig < 0.0){
        spdlog::info("negative acc corr and/or normalizing");
        spdlog::info("eff={0:f}",eff );
        spdlog::info("norm={0:f}",fnorm_sig );
        assert(eff > 0.0);
        assert(fnorm_sig > 0.0);
    }
    //spdlog::trace("eff={0:f},\t fnorm_sig={1:f}",eff, fnorm_sig);

    result *= eff/fnorm_sig;

    return result;
};

double bu2kstarmumu_pdf::angular_bkg_prob(const bu2kstarmumu_parameters* params, const event& meas) const{

    //Don't when only fitting the mass
    if (opts->only_Bmass || opts->only_mass2DFit || opts->only_mkpi) return 1.0;

    //calculate bkg prob for all 2(4) possible inversed folding angles:
    if(opts->fit_full_angular_bkg && !opts->flat_bkg && !opts->full_angular){
        fcnc::event e[3];

        fldr->invers_fold(&meas, &e[0], &e[1], &e[2]);

        double p1, p2, p3, p4;

        p1 = simple_angular_bkg_prob(params, meas);
        p4 = simple_angular_bkg_prob(params, e[2]);

        if(opts->folding > 0){
            p2 = simple_angular_bkg_prob(params, e[0]);
            p3 = simple_angular_bkg_prob(params, e[1]);
            return p1+p2+p3+p4;
        }
        else{
            return p1+p4;
        }
    }
    else{
        return simple_angular_bkg_prob(params, meas);
    }
}

double bu2kstarmumu_pdf::simple_angular_bkg_prob(const bu2kstarmumu_parameters* params, const event& meas) const{

    if (opts->only_Bmass) return 1.0;
    if (opts->only_mass2DFit) return 1.0; //Don't when fitting only mass

    double result = 1.0;

    double costhetal = meas.costhetal;
    double costhetak = meas.costhetak;
    double phi = meas.phi;

    npolynom npol(opts);
    assert(coeffs_eff_4d.size() == npol.getSize());

    //determine weight/efficiency for this event
    double eff = 1.0;
    if(opts->use_angular_acc && opts->use_weighted_bkg){
        //for fixed effq2 the efficiency should have been calculated beforehand
        //for floating q2 should recalculate as done below
        //for event_norm, the efficiency should also have been calculated before
        bool q2fixed = (params->eff_q2.get_step_size() == 0.0);
        if ((q2fixed || opts->use_event_norm) && (!opts->mcweight)){//use event norm is only used when convoluting the acceptance into the pdf
            if (meas.weight > 0.0) eff = 1.0/meas.weight;
        }
        else eff = this->get_ang_eff(opts,meas,true);
    }

    if (opts->flat_bkg){ //for a flat background, only - if at all - the angular acceptance correction is needed
        result *= eff/fnorm_bkg;
    }
    else{ //Chebyshev background

        //read angular bkg coefficients from parameter object
        std::vector<double> ch_ctl = init_ch_ctl(params);
        std::vector<double> ch_ctk = init_ch_ctk(params);
        std::vector<double> ch_phi = init_ch_phi(params);

        //reserve vectors for the (non-)Chebyshev polynomial coefficients
        std::vector<double> poly_ctl(ch_ctl.size(), 0.0);
        std::vector<double> poly_ctk(ch_ctk.size(), 0.0);
        std::vector<double> poly_phi(ch_phi.size(), 0.0);

        //these temporary coefficients are defined in the range from -1 to +1
        std::vector<double> temp_ctl(ch_ctl.size(), 0.0);
        std::vector<double> temp_ctk(ch_ctk.size(), 0.0);
        std::vector<double> temp_phi(ch_phi.size(), 0.0);

        //transform chebyshev polynomial coefficients to Standard Form polynomials
        chebyshev_to_poly(ch_ctl, temp_ctl);
        chebyshev_to_poly(ch_ctk, temp_ctk);
        chebyshev_to_poly(ch_phi, temp_phi);

        //stretch or squeeze the definition of the polynomials in phi
        //for (unsigned int i=0; i<poly_ctk.size(); i++) poly_ctk.at(i) = temp_ctk.at(i);
        for (unsigned int i=0; i<poly_ctk.size(); i++) poly_ctk.at(i) = temp_ctk.at(i);
        for (unsigned int i=0; i<poly_ctl.size(); i++) poly_ctl.at(i) = temp_ctl.at(i);
        correct_poly(temp_phi, poly_phi, -TMath::Pi(), +TMath::Pi());


        //add up all power terms of the polynomials
        double p_costhetal = 0.0;
        for (unsigned int i=0; i<ch_ctl.size(); i++) p_costhetal += poly_ctl.at(i)*pow(costhetal,i);
        double p_costhetak = 0.0;
        for (unsigned int i=0; i<ch_ctk.size(); i++) p_costhetak += poly_ctk.at(i)*pow(costhetak,i);
        double p_phi = 0.0;
        for (unsigned int i=0; i<ch_phi.size(); i++) p_phi += poly_phi.at(i)*pow(phi,i);

        //multiply the indidivual angular dimensions. divide by the normalisation to obtain the angular background probability
        result = p_costhetal*p_costhetak*p_phi*eff/fnorm_bkg;
    }
    return result;
};

double bu2kstarmumu_pdf::integral_bkg(const bu2kstarmumu_parameters* params, double ctl_a, double ctl_b, double ctk_a, double ctk_b, double phi_a, double phi_b) const
{
    if (opts->flat_bkg){
        return (ctl_b-ctl_a)*(ctk_b-ctk_a)*(phi_b-phi_a); // RANGE_3D() for non-folded
    }
    else{

        std::vector<double> ch_ctl = init_ch_ctl(params);
        std::vector<double> ch_ctk = init_ch_ctk(params);
        std::vector<double> ch_phi = init_ch_phi(params);

        std::vector<double> poly_ctl(ch_ctl.size(), 0.0);
        std::vector<double> poly_ctk(ch_ctk.size(), 0.0);
        std::vector<double> poly_phi(ch_phi.size(), 0.0);

        std::vector<double> temp_ctl(ch_ctl.size(), 0.0);
        std::vector<double> temp_ctk(ch_ctk.size(), 0.0);
        std::vector<double> temp_phi(ch_phi.size(), 0.0);

        chebyshev_to_poly(ch_ctl, temp_ctl);
        chebyshev_to_poly(ch_ctk, temp_ctk);
        chebyshev_to_poly(ch_phi, temp_phi);

        for (unsigned int i=0; i<poly_ctk.size(); i++) poly_ctk.at(i) = temp_ctk.at(i);
        for (unsigned int i=0; i<poly_ctl.size(); i++)poly_ctl.at(i) = temp_ctl.at(i);
        correct_poly(temp_phi, poly_phi, -TMath::Pi(), +TMath::Pi());


        double norm_costhetal = 0.0;
        for (unsigned int i=0; i<poly_ctl.size(); i++)norm_costhetal += poly_ctl.at(i)*(pow(ctl_b,i+1)-pow(ctl_a,i+1))/(i+1.0);
        double norm_costhetak = 0.0;
        for (unsigned int i=0; i<poly_ctk.size(); i++)norm_costhetak += poly_ctk.at(i)*(pow(ctk_b,i+1)-pow(ctk_a,i+1))/(i+1.0);
        double norm_phi = 0.0;
        for (unsigned int i=0; i<poly_phi.size(); i++)norm_phi += poly_phi.at(i)*(pow(phi_b,i+1)-pow(phi_a,i+1))/(i+1.0);

        return norm_costhetal*norm_costhetak*norm_phi;
    }
}

double bu2kstarmumu_pdf::integral_bkg_chebyshev(const bu2kstarmumu_parameters* params, double ctl_a, double ctl_b, double ctk_a, double ctk_b, double phi_a, double phi_b, double q2) const{

    double result = 0.0;

    if (opts->flat_bkg){

        double c = 1.0;

        //new improved non-factorizing acceptance

        npolynom npol = npolynom(opts);
        assert(coeffs_eff_4d.size() == npol.getSize());
        double chijk = 0.0;
        for (unsigned int h = 0; h<npol.q2; h++){
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        const unsigned int bin = npol.get_bin_in4D(h,i,j,k);
                        chijk = c*pow(q2, h)*coeffs_eff_4d.at(bin);
                        result += chijk
                                * (pow(ctl_b, i+1)/(i+1.0) - pow(ctl_a, i+1)/(i+1.0))
                                * (pow(ctk_b, j+1)/(j+1.0) - pow(ctk_a, j+1)/(j+1.0))
                                * (pow(phi_b, k+1)/(k+1.0) - pow(phi_a, k+1)/(k+1.0));
                    }
                }
            }
        }
    }
    else{
        //calculate integral of background with efficiency correction

        std::vector<double> ch_ctl = init_ch_ctl(params);
        std::vector<double> ch_ctk = init_ch_ctk(params);
        std::vector<double> ch_phi = init_ch_phi(params);

        std::vector<double> poly_ctl(ch_ctl.size(), 0.0);
        std::vector<double> poly_ctk(ch_ctk.size(), 0.0);
        std::vector<double> poly_phi(ch_phi.size(), 0.0);

        std::vector<double> temp_ctl(ch_ctl.size(), 0.0);
        std::vector<double> temp_ctk(ch_ctk.size(), 0.0);
        std::vector<double> temp_phi(ch_phi.size(), 0.0);

        chebyshev_to_poly(ch_ctl, temp_ctl);
        chebyshev_to_poly(ch_ctk, temp_ctk);
        chebyshev_to_poly(ch_phi, temp_phi);

        for (unsigned int i=0; i<poly_ctk.size(); i++) poly_ctk.at(i) = temp_ctk.at(i);
        for (unsigned int i=0; i<poly_ctl.size(); i++) poly_ctl.at(i) = temp_ctl.at(i);
        correct_poly(temp_phi, poly_phi, -TMath::Pi(), +TMath::Pi());

        double c = 1.0;

        //new improved non-factorizing acceptance
        //double q2 = params->eff_q2();
        npolynom npol(opts);
        assert(coeffs_eff_4d.size() == npol.getSize());
        double chijk = 0.0;

        for (unsigned int h = 0; h<npol.q2; h++){
            for (unsigned int i = 0; i<npol.ctl; i++){
                for (unsigned int j = 0; j<npol.ctk; j++){
                    for (unsigned int k = 0; k<npol.phi; k++){
                        const unsigned int bin = npol.get_bin_in4D(h,i,j,k);
                        chijk = c*pow(q2, h)*coeffs_eff_4d.at(bin);
                        for (unsigned int l=0; l<poly_ctl.size(); l++){
                            for (unsigned int m=0; m<poly_ctk.size(); m++){
                                for (unsigned int n=0; n<poly_phi.size(); n++){
                                    result += chijk * poly_ctl.at(l) * poly_ctk.at(m) * poly_phi.at(n)
                                            * (pow(ctl_b, i+l+1) - pow(ctl_a, i+l+1)) / (i+l+1.0)
                                            * (pow(ctk_b, j+m+1) - pow(ctk_a, j+m+1)) / (j+m+1.0)
                                            * (pow(phi_b, k+n+1) - pow(phi_a, k+n+1)) / (k+n+1.0);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return result;

}

double bu2kstarmumu_pdf::angular_bkg_norm(const bu2kstarmumu_parameters* params, bool weighted){
    npolynom npol(opts);
    assert(coeffs_eff_4d.size() == npol.getSize());

    //bkg norm
    if(weighted){ //weighted background pdf
        double q2 = params->eff_q2();
        if (opts->full_angular){
            if (opts->flat_bkg){
                double integral = 0.0;
                for (unsigned int h = 0; h<npol.q2; h++){
                    for (unsigned int i = 0; i<npol.ctl; i++){
                        for (unsigned int j = 0; j<npol.ctk; j++){
                            for (unsigned int k = 0; k<npol.phi; k++){
                                integral += pow(q2, h)*coeffs_eff_4d.at(npol.get_bin_in4D(h,i,j,k))
                                        * integrate_x_to_n(CTL_MIN,CTL_MAX,i)
                                        * integrate_x_to_n(CTK_MIN,CTK_MAX,j)
                                        * integrate_x_to_n(PHI_MIN,PHI_MAX,k);
                            }
                        }
                    }
                }
                return integral;
            }
            else{
                return integral_bkg_chebyshev(params, CTL_MIN, CTL_MAX, CTK_MIN, CTK_MAX, PHI_MIN, PHI_MAX, q2);
            }
        }
        else{ //folded angles
            if (opts->flat_bkg){
                double integral = 0.0;
                for (unsigned int h = 0; h<npol.q2; h++){
                    for (unsigned int i = 0; i<npol.ctl; i++){
                        for (unsigned int j = 0; j<npol.ctk; j++){
                            for (unsigned int k = 0; k<npol.phi; k++){
                                integral += pow(q2, h)*coeffs_eff_4d.at(npol.get_bin_in4D(h,i,j,k))
                                        * integrate_x_to_n(opts->ctl_min,opts->ctl_max,i)
                                        * integrate_x_to_n(opts->ctk_min,opts->ctk_max,j)
                                        * integrate_x_to_n(opts->phi_min,opts->phi_max,k);
                            }
                        }
                    }
                }
                return integral;
            }
            else{
                if(opts->fit_full_angular_bkg){
                    return integral_bkg_chebyshev(params, CTL_MIN, CTL_MAX, opts->ctk_min, opts->ctk_max, PHI_MIN, PHI_MAX, q2); //TODO: check if that makes sense (https://gitlab.cern.ch/LHCb-RD/ewp-Bplus2Kstmumu-AngAna/-/merge_requests/21)
                }
                else{
                    return integral_bkg_chebyshev(params, opts->ctl_min, opts->ctl_max, opts->ctk_min, opts->ctk_max, opts->phi_min, opts->phi_max, q2);
                }
            }
        }
    }
    else{ //non weighted background pdf
        if (opts->full_angular){
            if (opts->flat_bkg) return RANGE_3D();
            else                return integral_bkg(params, CTL_MIN, CTL_MAX, CTK_MIN, CTK_MAX, PHI_MIN, PHI_MAX);
        }
        else{ //folded angles
            if (opts->flat_bkg){
                return (opts->ctk_max - opts->ctk_min) * (opts->ctl_max - opts->ctl_min) * (opts->phi_max - opts->phi_min);
            }
            else{
                if(opts->fit_full_angular_bkg){
                    return integral_bkg(params, CTL_MIN, CTL_MAX, CTK_MIN, CTK_MAX, PHI_MIN, PHI_MAX);
                }
                else{
                    return integral_bkg(params, opts->ctl_min, opts->ctl_max, opts->ctk_min, opts->ctk_max, opts->phi_min, opts->phi_max);
                }
            }
        }
    }
}

double bu2kstarmumu_pdf::angular_sig_norm(const bu2kstarmumu_parameters* params, bool weighted){

    double f1=0.0, f2=0.0, f3=0.0, f4=0.0, f5=0.0, f6=0.0,
            f7=0.0, f8=0.0, f9=0.0, f10=0.0, f11=0.0, f12=0.0;

    double fs1=0.0, fs2=0.0, fs3=0.0, fs4=0.0, fs5=0.0, fs6=0.0;

    const double j1s = params->J1s();
    const double j1c = params->J1c();
    const double j2s = params->J2s();
    const double j2c = params->J2c();
    const double j3 = params->J3();
    const double j4 = params->J4();
    const double j5 = params->J5();
    const double j6s = params->J6s();
    const double j6c = params->J6c();
    const double j7 = params->J7();
    const double j8 = params->J8();
    const double j9 = params->J9();

    const double fs = params->FS();
    const double js1 = params->SS1();
    const double js2 = params->SS2();
    const double js3 = params->SS3();
    const double js4 = params->SS4();
    const double js5 = params->SS5();

    //sig norm
    double q2 = params->eff_q2();
    if (opts->full_angular){
        //get signal pdf
        if (weighted) integrated_fj_chebyshev(f1, f2, f3, f4, f5, f6,  f7, f8, f9, f10, f11, f12, q2);
        else          integrated_fj_noacc(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12);

        //swave
        if(opts->swave){
            if (weighted) swave_integrated_fj_chebyshev(fs1, fs2, fs3, fs4, fs5, fs6, q2);
            else          swave_integrated_fj_noacc(fs1, fs2, fs3, fs4, fs5, fs6);
        }

        //if asymmetries are fitted:
        if(opts->fit_asymmetries){
            if(opts->swave) return (1.0-fs) * (f1*j1s + f2*j1c + f3*j2s + f4*j2c) + (fs1*fs + fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5);
            else            return f1*j1s + f2*j1c + f3*j2s + f4*j2c;
        }

        if (opts->swave){
            return (1.0-fs) * (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4 + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9)
                    + (fs1*fs + fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5);
        }

        //signal pdf without swave, without asymmetries and without m(Kpi)
        return f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4 + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9;
    }
    else{ //folded angles
        if (weighted) folded_integrated_fj_chebyshev(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, q2);
        else          folded_integrated_fj_noacc(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12);

        if(opts->swave){
            if (weighted) folded_swave_integrated_fj_chebyshev(fs1, fs2, fs3, fs4, fs5, fs6, q2);
            else          folded_swave_integrated_fj_noacc(fs1, fs2, fs3, fs4, fs5, fs6);
        }

        if(opts->fit_asymmetries){
            if(opts->swave) return (1-fs) * (f1*j1s + f2*j1c + f3*j2s + f4*j2c) + (fs1*fs + fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5);
            else            return (f1*j1s + f2*j1c + f3*j2s + f4*j2c);
        }

        if(opts->swave){
            return (1-fs) * (f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4 + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9)
                    + (fs1*fs + fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5);
        }

        //signal pdf without swave, without asymmetries and without m(Kpi)
        return f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4 + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9;
    }
}

double bu2kstarmumu_pdf::angular_swave_norm(const bu2kstarmumu_parameters* params, bool weighted){

    double fs1=0.0, fs2=0.0, fs3=0.0, fs4=0.0, fs5=0.0, fs6=0.0;
    const double fs = params->FS();

    //sig norm
    double q2 = params->eff_q2();
    if (opts->full_angular){
        if (weighted) swave_integrated_fj_chebyshev(fs1, fs2, fs3, fs4, fs5, fs6, q2);
        else          swave_integrated_fj_noacc(fs1, fs2, fs3, fs4, fs5, fs6);
    }
    else{ //folded angles
        if (weighted) folded_swave_integrated_fj_chebyshev(fs1, fs2, fs3, fs4, fs5, fs6, q2);
        else          folded_swave_integrated_fj_noacc(fs1, fs2, fs3, fs4, fs5, fs6);
    }
    return fs1*fs;
}

double bu2kstarmumu_pdf::angular_pwave_norm(const bu2kstarmumu_parameters* params, bool weighted){

    double f1=0.0, f2=0.0, f3=0.0, f4=0.0, f5=0.0, f6=0.0,
            f7=0.0, f8=0.0, f9=0.0, f10=0.0, f11=0.0, f12=0.0;

    const double j1s = params->J1s();
    const double j1c = params->J1c();
    const double j2s = params->J2s();
    const double j2c = params->J2c();
    const double j3 = params->J3();
    const double j4 = params->J4();
    const double j5 = params->J5();
    const double j6s = params->J6s();
    const double j6c = params->J6c();
    const double j7 = params->J7();
    const double j8 = params->J8();
    const double j9 = params->J9();

    //sig norm
    double q2 = params->eff_q2();
    if (opts->full_angular){
        if (weighted) integrated_fj_chebyshev(f1, f2, f3, f4, f5, f6,  f7, f8, f9, f10, f11, f12, q2);
        else          integrated_fj_noacc(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12);
    }
    else{ //folded angles
        if (weighted) folded_integrated_fj_chebyshev(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, q2);
        else          folded_integrated_fj_noacc(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12);
    }
    //if asymmetries are fitted:
    if(opts->fit_asymmetries)return f1*j1s + f2*j1c + f3*j2s + f4*j2c;
    else return f1*j1s + f2*j1c + f3*j2s + f4*j2c + f5*j3 + f6*j4 + f7*j5 + f8*j6s + f9*j6c + f10*j7 + f11*j8 + f12*j9;
}

double bu2kstarmumu_pdf::angular_pswave_norm(const bu2kstarmumu_parameters* params, bool weighted){

    double fs1=0.0, fs2=0.0, fs3=0.0, fs4=0.0, fs5=0.0, fs6=0.0;

    const double js1 = params->SS1();
    const double js2 = params->SS2();
    const double js3 = params->SS3();
    const double js4 = params->SS4();
    const double js5 = params->SS5();

    //sig norm
    double q2 = params->eff_q2();
    if (opts->full_angular){
        if (weighted) swave_integrated_fj_chebyshev(fs1, fs2, fs3, fs4, fs5, fs6, q2);
        else          swave_integrated_fj_noacc(fs1, fs2, fs3, fs4, fs5, fs6);
    }
    else{ //folded angles
        if (weighted) folded_swave_integrated_fj_chebyshev(fs1, fs2, fs3, fs4, fs5, fs6, q2);
        else          folded_swave_integrated_fj_noacc(fs1, fs2, fs3, fs4, fs5, fs6);
    }
    return fs2*js1 + fs3*js2 + fs4*js3 + fs5*js4 + fs6*js5;
}

void bu2kstarmumu_pdf::update_cached_efficiencies(const bu2kstarmumu_parameters* params, std::vector<event>* events)
{
    if (opts->update_efficiencies){
        spdlog::info("Updating cached efficiencies");
        //assert(0);
        //three ways this is usefull
        //-q2_eff is fixed and you want to avoid recalculating this every time
        //-weighted fit to determine the event weight that needs to be used
        //-when using per event norm this can also be cached
        npolynom npol(opts);
        assert(coeffs_eff_4d.size() == npol.getSize());
        double q2 = params->eff_q2();

        unsigned int N = events->size();
        for (unsigned int e=0; e<N; e++){
            double eff = 0.0;
            event& meas = events->at(e);
            if (opts->weighted_fit || (opts->use_angular_acc && opts->use_event_norm)){
                q2 = meas.q2;
            }
            eff = this->get_ang_eff(opts,q2, meas.costhetal_fa, meas.costhetak_fa, meas.phi_fa);

            if (eff > EFF_CUTOFF){
                if (opts->multiply_eff) meas.weight *= 1.0/eff;
                else                    meas.weight = 1.0/eff;
            }
            else{
                spdlog::warn("Event with too low efficiency: {0:f}", eff);
                spdlog::warn("\t q2: {0:f} \tq2_eff {1:f} \tctl: {2:f} \tctk: {3:f} \tphi: {4:f} ",
                             meas.q2, q2, meas.costhetal_fa,  meas.costhetak_fa , meas.phi_fa);
                if (opts->multiply_eff) meas.weight *= 1.0;
                else                    meas.weight = 1.0;
            }
        }
        return;
    }
    else return;
}

////////////////////////
///   Full angular   ///
////////////////////////

//--------------------//
//   S-wave
//--------------------//

void bu2kstarmumu_pdf::swave_fj(double ctl, double ctk, double phi,
                                double& f1, double& f2, double& f3, double& f4, double& f5, double& f6) const{
    f1 = C_SWAVE * sintheta2(ctl);
    f2 = C_SWAVE * sintheta2(ctl)*ctk;
    f3 = C_SWAVE * sin2theta(ctl)*sintheta_abs(ctk)*cos(phi);
    f4 = C_SWAVE * sintheta_abs(ctl)*sintheta_abs(ctk)*cos(phi);
    f5 = C_SWAVE * sintheta_abs(ctl)*sintheta_abs(ctk)*sin(phi);
    f6 = C_SWAVE * sin2theta(ctl)*sintheta_abs(ctk)*sin(phi);
}

void bu2kstarmumu_pdf::swave_integrated_fj_noacc(double& f1, double& f2, double& f3, double& f4, double& f5, double& f6) const{
    f1 = 1.0;
    f2 = 0.0;
    f3 = 0.0;
    f4 = 0.0;
    f5 = 0.0;
    f6 = 0.0;

    swave_integrated_fj_noacc(opts->ctl_min, opts->ctl_max, opts->ctk_min, opts->ctk_max, opts->phi_min, opts->phi_max,
                              f1, f2, f3, f4, f5, f6);
}

void bu2kstarmumu_pdf::swave_integrated_fj_noacc(double ctl_a, double ctl_b, double ctk_a, double ctk_b, double phi_a, double phi_b,
                                                 double& f1, double& f2, double& f3, double& f4, double& f5, double& f6) const{
    f1 = C_SWAVE * integrate_s_f1(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f2 = C_SWAVE * integrate_s_f2(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f3 = C_SWAVE * integrate_s_f3(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f4 = C_SWAVE * integrate_s_f4(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f5 = C_SWAVE * integrate_s_f5(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f6 = C_SWAVE * integrate_s_f6(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    return;
}

void bu2kstarmumu_pdf::swave_integrated_fj_chebyshev(double ctl_a, double ctl_b, double ctk_a, double ctk_b, double phi_a, double phi_b,
                                                     double& f1, double& f2, double& f3, double& f4, double& f5, double& f6, double q2) const //TODO: modify this according to the normDebug branch
{

    f1 = 0.0; f2 = 0.0; f3 = 0.0; f4 = 0.0; f5 = 0.0; f6 = 0.0;

    npolynom npol(opts);
    assert(coeffs_eff_4d.size() == npol.getSize());
    double chijk = 0.0;
    for (unsigned int h = 0; h<npol.q2; h++){
        for (unsigned int i = 0; i<npol.ctl; i++){
            for (unsigned int j = 0; j<npol.ctk; j++){
                for (unsigned int k = 0; k<npol.phi; k++){
                    const unsigned int bin = npol.get_bin_in4D(h,i,j,k);
                    chijk = C_SWAVE*pow(q2, h)*coeffs_eff_4d.at(bin);

                    f1 += chijk * integrate_s_f1(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f2 += chijk * integrate_s_f2(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f3 += chijk * integrate_s_f3(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f4 += chijk * integrate_s_f4(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f5 += chijk * integrate_s_f5(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f6 += chijk * integrate_s_f6(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                }
            }
        }
    }
    return;
};

void bu2kstarmumu_pdf::swave_integrated_fj_chebyshev(double& f1, double& f2, double& f3, double& f4, double& f5, double& f6, double q2) const{
    f1 = 0.0; f2 = 0.0; f3 = 0.0; f4 = 0.0; f5 = 0.0; f6 = 0.0;
    swave_integrated_fj_chebyshev(CTL_MIN, CTL_MAX, CTK_MIN, CTK_MAX, PHI_MIN, PHI_MAX,
                                  f1, f2, f3, f4, f5, f6, q2);
    return;
};

//--------------------//
//  P-wave
//--------------------//

void bu2kstarmumu_pdf::fj(double ctl, double ctk, double phi,
                          double& f1, double& f2, double& f3, double& f4, double& f5, double& f6,
                          double& f7, double& f8, double& f9, double& f10, double& f11, double& f12) const{
    f1  = C_PWAVE * sintheta2(ctk); //j1s = 3/4*(1-FL)
    f2  = C_PWAVE * costheta2(ctk); //j1c = FL
    f3  = C_PWAVE * sintheta2(ctk)*cos2theta(ctl); //j2s = 1/4*(1-FL)
    f4  = C_PWAVE * costheta2(ctk)*cos2theta(ctl); //j2c = -FL
    f5  = C_PWAVE * sintheta2(ctk)*sintheta2(ctl)*cos(2.0*phi); //j3
    f6  = C_PWAVE * sin2theta(ctk)*sin2theta(ctl)*cos(phi); //j4
    f7  = C_PWAVE * sin2theta(ctk)*sintheta_abs(ctl)*cos(phi); //j5
    f8  = C_PWAVE * sintheta2(ctk)*ctl; //j6s
    f9  = C_PWAVE * costheta2(ctk)*ctl; //j6c
    f10 = C_PWAVE * sin2theta(ctk)*sintheta_abs(ctl)*sin(phi); //j7
    f11 = C_PWAVE * sin2theta(ctk)*sin2theta(ctl)*sin(phi); //j8
    f12 = C_PWAVE * sintheta2(ctk)*sintheta2(ctl)*sin(2.0*phi); //j9
};


void bu2kstarmumu_pdf::integrated_fj_noacc(double& f1, double& f2, double& f3, double& f4, double& f5, double& f6,
                                           double& f7, double& f8, double& f9, double& f10, double& f11, double& f12) const{
    //Normalization of the p-wave in principle
    f1 = 0.0; f2 = 0.0; f3 = 0.0; f4 = 0.0; f5 = 0.0; f6 = 0.0;
    f7 = 0.0; f8 = 0.0; f9 = 0.0; f10 = 0.0; f11 = 0.0; f12 = 0.0;
    integrated_fj_noacc(CTL_MIN, CTL_MAX, CTK_MIN, CTK_MAX, PHI_MIN, PHI_MAX,
                        f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12);
    return;
};

void bu2kstarmumu_pdf::integrated_fj_noacc(double ctl_a, double ctl_b, double ctk_a, double ctk_b, double phi_a, double phi_b,
                                           double& f1, double& f2, double& f3, double& f4, double& f5, double& f6,
                                           double& f7, double& f8, double& f9, double& f10, double& f11, double& f12) const{
    f1 =  C_PWAVE * integrate_f1(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f2 =  C_PWAVE * integrate_f2(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f3 =  C_PWAVE * integrate_f3(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f4 =  C_PWAVE * integrate_f4(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f5 =  C_PWAVE * integrate_f5(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f6 =  C_PWAVE * integrate_f6(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f7 =  C_PWAVE * integrate_f7(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f8 =  C_PWAVE * integrate_f8(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f9 =  C_PWAVE * integrate_f9(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f10 = C_PWAVE * integrate_f10(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f11 = C_PWAVE * integrate_f11(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f12 = C_PWAVE * integrate_f12(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    return;
};

void bu2kstarmumu_pdf::integrated_fj_chebyshev(double ctl_a, double ctl_b, double ctk_a, double ctk_b, double phi_a, double phi_b,
                                               double& f1, double& f2, double& f3, double& f4, double& f5, double& f6,
                                               double& f7, double& f8, double& f9, double& f10, double& f11, double& f12, double q2) const{

    f1 = 0.0;
    f2 = 0.0;
    f3 = 0.0;
    f4 = 0.0;
    f5 = 0.0;
    f6 = 0.0;
    f7 = 0.0;
    f8 = 0.0;
    f9 = 0.0;
    f10 = 0.0;
    f11 = 0.0;
    f12 = 0.0;

    //new improved non-factorizing acceptance
    npolynom npol = npolynom(opts);
    assert(coeffs_eff_4d.size() == npol.getSize());

    for (unsigned int h = 0; h<npol.q2; h++){
        for (unsigned int i = 0; i<npol.ctl; i++){
            for (unsigned int j = 0; j<npol.ctk; j++){
                for (unsigned int k = 0; k<npol.phi; k++){
                    const unsigned int bin = npol.get_bin_in4D(h,i,j,k);
                    double chijk = C_PWAVE*coeffs_eff_4d.at(bin)*pow(q2, h); //9/32pi * angCorr(bin) * q2^h (not integrating over q2)

                    f1 += chijk * integrate_f1(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f2 += chijk * integrate_f2(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f3 += chijk * integrate_f3(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f4 += chijk * integrate_f4(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f5 += chijk * integrate_f5(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f6 += chijk * integrate_f6(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f7 += chijk * integrate_f7(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f8 += chijk * integrate_f8(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f9 += chijk * integrate_f9(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f10 += chijk * integrate_f10(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f11 += chijk * integrate_f11(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f12 += chijk * integrate_f12(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);

                }
            }
        }
    }

}

void bu2kstarmumu_pdf::integrated_fj_chebyshev(double& f1, double& f2, double& f3, double& f4, double& f5, double& f6,
                                               double& f7, double& f8, double& f9, double& f10, double& f11, double& f12, double q2) const{
    f1 = 0.0; f2 = 0.0; f3 = 0.0; f4 = 0.0; f5 = 0.0; f6 = 0.0;
    f7 = 0.0; f8 = 0.0; f9 = 0.0; f10 = 0.0; f11 = 0.0; f12 = 0.0;
    integrated_fj_chebyshev(CTL_MIN, CTL_MAX, CTK_MIN, CTK_MAX, PHI_MIN, PHI_MAX,
                            f1,  f2,  f3,  f4,  f5,  f6,
                            f7,  f8,  f9,  f10,  f11, f12, q2);
    return;
};

/////////////////////
///Angular folding///
/////////////////////

//S-wave

void bu2kstarmumu_pdf::folded_swave_fj(double ctl, double ctk, double phi,
                                       double& f1, double& f2, double& f3, double& f4, double& f5, double& f6) const
{
    double c = C_SWAVE*2.0; // folding in phi
    if(opts->folding > 0) c *= 2.0;  //folding in cos(thetaL)

    f2 = 0.0; f3 = 0.0; f4 = 0.0; f5 = 0.0; f6 = 0.0;

    f1 = c * sintheta2(ctl);
    if(opts->folding != 4) f2 = c * sintheta2(ctl)*(ctk);
    if(opts->folding == 1) f3 = c * sintheta_abs(ctk)*sin2theta(ctl)*cos(phi);
    if(opts->folding == 2) f4 = c * sintheta_abs(ctk)*sintheta_abs(ctl)*cos(phi);
    if(opts->folding > 2)  f5 = c * sintheta_abs(ctk)*sintheta_abs(ctl)*sin(phi);
    //f6 always zero no matter what folding
}

void bu2kstarmumu_pdf::folded_swave_integrated_fj_noacc(double& f1, double& f2, double& f3, double& f4, double& f5, double& f6) const
{
    f1 = 1.0;
    f2 = 0.0;
    f3 = 0.0;
    f4 = 0.0;
    f5 = 0.0;
    f6 = 0.0;

    folded_swave_integrated_fj_noacc(opts->ctl_min, opts->ctl_max, opts->ctk_min, opts->ctk_max, opts->phi_min, opts->phi_max,
                                     f1, f2, f3, f4, f5, f6);

}

void bu2kstarmumu_pdf::folded_swave_integrated_fj_noacc(double ctl_a, double ctl_b, double ctk_a, double ctk_b, double phi_a, double phi_b,
                                                        double& f1, double& f2, double& f3, double& f4, double& f5, double& f6) const{
    double c = C_SWAVE*2.0; // folding in phi
    if(opts->folding > 0) c *= 2.0;  //folding in cos(thetaL)

    f2 = 0.0; f3 = 0.0; f4 = 0.0; f5 = 0.0;
    f1 = c * integrate_s_f1(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    if(opts->folding != 4) f2 = c * integrate_s_f2(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    if(opts->folding == 1) f3 = c * integrate_s_f3(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    if(opts->folding == 2) f4 = c * integrate_s_f4(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    if(opts->folding > 2)  f5 = c * integrate_s_f5(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f6 = 0.0;

    return;
}

void bu2kstarmumu_pdf::folded_swave_integrated_fj_chebyshev(double ctl_a, double ctl_b, double ctk_a, double ctk_b, double phi_a, double phi_b,
                                                            double& f1, double& f2, double& f3, double& f4, double& f5, double& f6, double q2) const{
    double c = C_SWAVE*2.0; // folding in phi
    if(opts->folding > 0) c *= 2.0;  //folding in cos(thetaL)

    f1 = 0.0; f2 = 0.0; f3 = 0.0; f4 = 0.0; f5 = 0.0; f6 = 0.0;

    npolynom npol = npolynom(opts);
    assert(coeffs_eff_4d.size() == npol.getSize());

    double chijk = 0.0;
    for (unsigned int h = 0; h<npol.q2; h++){
        for (unsigned int i = 0; i<npol.ctl; i++){
            for (unsigned int j = 0; j<npol.ctk; j++){
                for (unsigned int k = 0; k<npol.phi; k++){
                    const unsigned int bin = npol.get_bin_in4D(h,i,j,k);
                    chijk = c*pow(q2, h)*coeffs_eff_4d.at(bin);
                    f1 += chijk * integrate_s_f1(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    if(opts->folding != 4) f2 += chijk * integrate_s_f2(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    if(opts->folding == 1) f3 += chijk * integrate_s_f3(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    if(opts->folding == 2) f4 += chijk * integrate_s_f4(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    if(opts->folding > 2)  f5 += chijk * integrate_s_f5(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f6 = 0.0;

                }
            }
        }
    }
}

void bu2kstarmumu_pdf::folded_swave_integrated_fj_chebyshev(double& f1, double& f2, double& f3, double& f4, double& f5, double& f6, double q2) const{

    folded_swave_integrated_fj_chebyshev(opts->ctl_min, opts->ctl_max, opts->ctk_min, opts->ctk_max, opts->phi_min, opts->phi_max,
                                         f1, f2, f3, f4, f5, f6, q2);
    return;
}

//P-wave

void bu2kstarmumu_pdf::folded_fj(double ctl, double ctk, double phi, double& f1, double& f2, double& f3, double& f4, double& f5, double& f6, double& f7, double& f8, double& f9, double& f10, double& f11, double& f12) const
{
    //reset all coefficients that might not be calculated due to folding!
    f6 = 0.;
    f7 = 0.;
    f8 = 0.;
    f9 = 0.;
    f10 = 0.;
    f11 = 0.;
    f12 = 0.;

    //constant coefficient
    double c = 2.0*C_PWAVE; //multiply by 2 due to folding in phi
    if(opts->folding > 0) c *= 2.0;  //folding in cos(thetaL)

    f1 = c * sintheta2(ctk); //j1s = 3/4*(1-FL)
    f2 = c * costheta2(ctk); //j1c = FL
    f3 = c * sintheta2(ctk)*cos2theta(ctl); //j2s = 1/4*(1-FL)
    f4 = c * costheta2(ctk)*cos2theta(ctl); //j2c = -FL
    f5 = c * sintheta2(ctk)*sintheta2(ctl)*cos(2.0*phi); //j3
    if(opts->folding == 1) f6  = c * sin2theta(ctk)*sin2theta(ctl)*cos(phi); //j4
    if(opts->folding == 2) f7  = c * sin2theta(ctk)*sintheta_abs(ctl)*cos(phi); //j5
    if(opts->folding == 0){
        f8  = c * sintheta2(ctk)*ctl; //j6s
        f9  = c * costheta2(ctk)*ctl; //j6c
    }
    if(opts->folding == 3) f10 = c * sin2theta(ctk)*sintheta_abs(ctl)*sin(phi); //j7
    if(opts->folding == 4) f11 = c * sin2theta(ctk)*sin2theta(ctl)*sin(phi); //j8
    if(opts->folding == 0) f12 = c * sintheta2(ctk)*sintheta2(ctl)*sin(2.0*phi); //j9
};


void bu2kstarmumu_pdf::folded_integrated_fj_noacc(double& f1, double& f2, double& f3, double& f4, double& f5, double& f6, double& f7, double& f8, double& f9, double& f10, double& f11, double& f12) const
{
    folded_integrated_fj_noacc(opts->ctl_min, opts->ctl_max, opts->ctk_min, opts->ctk_max, opts->phi_min, opts->phi_max,
                               f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12);
};

void bu2kstarmumu_pdf::folded_integrated_fj_noacc(double ctl_a, double ctl_b, double ctk_a, double ctk_b, double phi_a, double phi_b,
                                                  double& f1, double& f2, double& f3, double& f4, double& f5, double& f6, double& f7, double& f8, double& f9, double& f10, double& f11, double& f12) const
{

    if(opts->full_angular){
        spdlog::error("Cannot use folded integrals for full angular fit");
        assert(0);
    }

    f1 = 0.0;
    f2 = 0.0;
    f3 = 0.0;
    f4 = 0.0;
    f5 = 0.0;
    f6 = 0.0;
    f7 = 0.0;
    f8 = 0.0;
    f9 = 0.0;
    f10 = 0.0;
    f11 = 0.0;
    f12 = 0.0;

    //copied from full angular integral:
    double c = C_PWAVE*2.; //folding in phi
    if(opts->folding  > 0)c *= 2.; //folding in cos(theta_L)

    f1 =  c * integrate_f1(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f2 =  c * integrate_f2(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f3 =  c * integrate_f3(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f4 =  c * integrate_f4(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    f5 =  c * integrate_f5(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    if(opts->folding == 0){
        f8 =  c * integrate_f8(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
        f9 =  c * integrate_f9(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
        f12 = c * integrate_f12(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    }
    else if(opts->folding == 1) f6 =  c * integrate_f6(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    else if(opts->folding == 2) f7 =  c * integrate_f7(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    else if(opts->folding == 3) f10 = c * integrate_f10(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);
    else if(opts->folding == 4) f11 = c * integrate_f11(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, 0, 0, 0);

    return;

};


void bu2kstarmumu_pdf::folded_integrated_fj_chebyshev(double ctl_a, double ctl_b, double ctk_a, double ctk_b, double phi_a, double phi_b,
                                                      double& f1, double& f2, double& f3, double& f4, double& f5, double& f6, double& f7, double& f8, double& f9, double& f10, double& f11, double& f12, double q2) const{

    if(opts->full_angular){
        spdlog::error("Cannot use folded integrals for full angular fit");
        assert(0);
    }

    double c = C_PWAVE* 2.; //folding in phi
    if(opts->folding > 0)c *= 2.; //folding in cos(theta_L)

    f1 = 0.0;
    f2 = 0.0;
    f3 = 0.0;
    f4 = 0.0;
    f5 = 0.0;
    f6 = 0.0;
    f7 = 0.0;
    f8 = 0.0;
    f9 = 0.0;
    f10 = 0.0;
    f11 = 0.0;
    f12 = 0.0;

    //new improved non-factorizing acceptance

    npolynom npol = npolynom(opts);
    assert(coeffs_eff_4d.size() == npol.getSize());
    double chijk = 0.0;
    for (unsigned int h = 0; h<npol.q2; h++)
        for (unsigned int i = 0; i<npol.ctl; i++)
            for (unsigned int j = 0; j<npol.ctk; j++)
                for (unsigned int k = 0; k<npol.phi; k++)
                {
                    const unsigned int bin = npol.get_bin_in4D(h,i,j,k);
                    chijk = c*pow(q2, h)*coeffs_eff_4d.at(bin);
                    f1 +=  chijk*integrate_f1(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f2 +=  chijk*integrate_f2(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f3 +=  chijk*integrate_f3(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f4 +=  chijk*integrate_f4(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    f5 +=  chijk*integrate_f5(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    if(opts->folding == 0){
                        f8 +=  chijk*integrate_f8(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                        f9 +=  chijk*integrate_f9(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                        f12 += chijk*integrate_f12(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    }
                    else if(opts->folding == 1) f6 +=  chijk*integrate_f6(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    else if(opts->folding == 2) f7 +=  chijk*integrate_f7(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    else if(opts->folding == 3) f10 += chijk*integrate_f10(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);
                    else if(opts->folding == 4) f11 += chijk*integrate_f11(ctl_a, ctl_b, ctk_a, ctk_b, phi_a, phi_b, i, j, k);

                }

};

void bu2kstarmumu_pdf::folded_integrated_fj_chebyshev(double& f1, double& f2, double& f3, double& f4, double& f5, double& f6, double& f7, double& f8, double& f9, double& f10, double& f11, double& f12, double q2) const{
    folded_integrated_fj_chebyshev(opts->ctl_min, opts->ctl_max, opts->ctk_min, opts->ctk_max, opts->phi_min, opts->phi_max,
                                   f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, q2);
    return;
};



void bu2kstarmumu_pdf::calculate_sweights(const bu2kstarmumu_parameters* parms, std::vector<event>* ev) const
{
    assert(opts->extended_ml);
    parameter* psig = parms->get_parameter("n_sig");
    parameter* pbkg = parms->get_parameter("n_bkg");
    if (psig != 0 && pbkg != 0){
        double nsig = psig->get_value();
        double nbkg = pbkg->get_value();

        double sweight = 0.0, prob_sig_m = 0.0, prob_bkg_m = 0.0;

        //obtain inverse variant matrix (V_{nj}^{-1})
        double inv_cov_matrix[2*2] = {0.0, 0.0, 0.0, 0.0};
        for (unsigned int i = 0; i<ev->size(); i++){
            const event& meas = ev->at(i);
            prob_sig_m = m_sig_prob(parms, meas);
            prob_bkg_m = m_bkg_prob(parms, meas);

            inv_cov_matrix[0] += prob_sig_m*prob_sig_m/sqr(nsig*prob_sig_m + nbkg*prob_bkg_m);
            inv_cov_matrix[1] += prob_sig_m*prob_bkg_m/sqr(nsig*prob_sig_m + nbkg*prob_bkg_m);
            inv_cov_matrix[2] += prob_bkg_m*prob_sig_m/sqr(nsig*prob_sig_m + nbkg*prob_bkg_m);
            inv_cov_matrix[3] += prob_bkg_m*prob_bkg_m/sqr(nsig*prob_sig_m + nbkg*prob_bkg_m);
        }
        spdlog::info("Inverse covariance matrix");
        spdlog::info("{0:0.8f} {1:.8f}", inv_cov_matrix[0],inv_cov_matrix[1] );
        spdlog::info("{0:0.8f} {1:.8f}", inv_cov_matrix[2],inv_cov_matrix[3] );

        //invert inserve matrix to get V_{nj}
        double cov_matrix[2*2] = {0.0, 0.0, 0.0, 0.0};
        cov_matrix[0] = +inv_cov_matrix[3]/(inv_cov_matrix[0]*inv_cov_matrix[3]-inv_cov_matrix[1]*inv_cov_matrix[2]);
        cov_matrix[1] = -inv_cov_matrix[1]/(inv_cov_matrix[0]*inv_cov_matrix[3]-inv_cov_matrix[1]*inv_cov_matrix[2]);
        cov_matrix[2] = -inv_cov_matrix[2]/(inv_cov_matrix[0]*inv_cov_matrix[3]-inv_cov_matrix[1]*inv_cov_matrix[2]);
        cov_matrix[3] = +inv_cov_matrix[0]/(inv_cov_matrix[0]*inv_cov_matrix[3]-inv_cov_matrix[1]*inv_cov_matrix[2]);
        spdlog::info("Covariance matrix");
        spdlog::info("{0:0.8f} {1:.8f}", cov_matrix[0], cov_matrix[1] );
        spdlog::info("{0:0.8f} {1:.8f}", cov_matrix[2], cov_matrix[3] );

        //get sWeights for all events
        for (unsigned int i = 0; i<ev->size(); i++){
            const event& meas = ev->at(i);
            prob_sig_m = m_sig_prob(parms, meas);
            prob_bkg_m = m_bkg_prob(parms, meas);
            sweight = (prob_sig_m * cov_matrix[0] + prob_bkg_m * cov_matrix[1]) / ( prob_sig_m * nsig + prob_bkg_m * nbkg );
            ev->at(i).sweight = sweight;
        }
    }
};

void bu2kstarmumu_pdf::save_coeffs_eff_phsp_4d(){
    std::ofstream ostr(coeffs_eff_phsp_4D(opts));
    for(unsigned int c = 0; c < this->coeffs_eff_4d.size(); c++){
        ostr << std::setprecision(15) << std::scientific << this->coeffs_eff_4d.at(c) << "\n";
    }
    spdlog::info("[SAVE]\t\tSaved a total of {0:d} angular acceptance correction coefficients",this->coeffs_eff_4d.size());
}

std::vector<double> bu2kstarmumu_pdf::read_coeffs_eff_phsp_4d(){

    unsigned int nCoeffs = opts->eff_order_q2 * opts->eff_order_costhetal * opts->eff_order_costhetak * opts->eff_order_phi;

    std::string filename = coeffs_eff_phsp_4D(opts);
    std::ifstream file(filename);
    if(!file.is_open())spdlog::error("File " + filename + " not found to load coefficients!");
    else spdlog::info("Loading a total of {0:d} coefficients from file " + filename + ".",nCoeffs);
    std::string line;

    double coeff;
    std::vector<double>coeffs;

    assert(file.is_open());

    while(1){
        getline(file, line);
        if(file.eof())break;
        std::istringstream istr(line);
        istr >> coeff;
        coeffs.push_back(coeff);
    }

    if(coeffs.size() != nCoeffs){
      spdlog::critical("Number of coefficients loaded from file ({0:d}) does not match the number of configurated coefficients ({1:d}). Abort", coeffs.size(), nCoeffs);
      assert(0);
    }


    return coeffs;
}

void bu2kstarmumu_pdf::load_coeffs_eff_phsp_4d(){
    this->coeffs_eff_4d = read_coeffs_eff_phsp_4d();
}

