// ====================================================================
// This file is part of FlexibleSUSY.
//
// FlexibleSUSY is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// FlexibleSUSY is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FlexibleSUSY.  If not, see
// <http://www.gnu.org/licenses/>.
// ====================================================================

// File generated at Thu 10 May 2018 15:06:30

#ifndef lowMSSM_SLHA_IO_H
#define lowMSSM_SLHA_IO_H

#include "lowMSSM_mass_eigenstates.hpp"
#include "lowMSSM_model_slha.hpp"
#include "lowMSSM_info.hpp"
#include "lowMSSM_observables.hpp"
#include "lowMSSM_physical.hpp"
#include "problems.hpp"
#include "spectrum_generator_problems.hpp"
#include "standard_model_two_scale_model.hpp"
#include "slha_io.hpp"
#include "ckm.hpp"
#include "ew_input.hpp"
#include "lowe.h"

#include <Eigen/Core>
#include <string>
#include <tuple>
#include <utility>

#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>

#define Pole(p) physical.p
#define PHYSICAL(p) model.get_physical().p
#define PHYSICAL_SLHA(p) model.get_physical_slha().p
#define LOCALPHYSICAL(p) physical.p
#define MODEL model
#define MODELPARAMETER(p) model.get_##p()
#define EXTRAPARAMETER(p) model.get_##p()
#define OBSERVABLES observables
#define LowEnergyConstant(p) Electroweak_constants::p
#define SCALES(p) scales.p

namespace flexiblesusy {

struct lowMSSM_input_parameters;
class Spectrum_generator_settings;

template <class T>
class lowMSSM;

struct lowMSSM_scales {
   double HighScale{0.}, SUSYScale{0.}, LowScale{0.};
   double pole_mass_scale{0.};
};

class lowMSSM_slha_io {
public:
   lowMSSM_slha_io();

   void clear();

   void fill(softsusy::QedQcd& qedqcd) const { slha_io.fill(qedqcd); }
   void fill(lowMSSM_input_parameters&) const;
   void fill(lowMSSM_mass_eigenstates&) const;
   template <class Model> void fill(lowMSSM_slha<Model>&) const;
   void fill(Physical_input&) const;
   void fill(Spectrum_generator_settings&) const;
   double get_parameter_output_scale() const;
   const SLHA_io& get_slha_io() const { return slha_io; }
   void read_from_file(const std::string&);
   void read_from_source(const std::string&);
   void read_from_stream(std::istream&);
   void set_block(const std::string& str, SLHA_io::Position position = SLHA_io::back) { slha_io.set_block(str, position); }
   void set_blocks(const std::vector<std::string>& vec, SLHA_io::Position position = SLHA_io::back) { slha_io.set_blocks(vec, position); }
   template <class Model> void set_extra(const lowMSSM_slha<Model>&, const lowMSSM_scales&, const lowMSSM_observables&);
   void set_input(const lowMSSM_input_parameters&);
   void set_modsel(const SLHA_io::Modsel&);
   void set_physical_input(const Physical_input&);
   void set_settings(const Spectrum_generator_settings&);
   void set_sminputs(const softsusy::QedQcd&);
   template <class... Ts> void set_spectrum(const std::tuple<Ts...>&);
   template <class Model> void set_spectrum(const lowMSSM_slha<Model>&);
   template <class T> void set_spectrum(const lowMSSM<T>&);
   void set_spectrum(const standard_model::Standard_model& m) { slha_io.set_spectrum(m); }
   void set_spinfo(const Spectrum_generator_problems&);
   void set_spinfo(const Problems&);
   void set_spinfo(const std::vector<std::string>&, const std::vector<std::string>&);
   void set_print_imaginary_parts_of_majorana_mixings(bool);
   void write_to(const std::string&) const;
   void write_to_file(const std::string& file_name) const { slha_io.write_to_file(file_name); }
   void write_to_stream(std::ostream& ostr = std::cout) const { slha_io.write_to_stream(ostr); }

   static void fill_minpar_tuple(lowMSSM_input_parameters&, int, double);
   static void fill_extpar_tuple(lowMSSM_input_parameters&, int, double);
   static void fill_imminpar_tuple(lowMSSM_input_parameters&, int, double);
   static void fill_imextpar_tuple(lowMSSM_input_parameters&, int, double);

   template <class Model>
   static void fill_slhaea(SLHAea::Coll&, const lowMSSM_slha<Model>&, const softsusy::QedQcd&, const lowMSSM_scales&, const lowMSSM_observables&);

   template <class Model>
   static SLHAea::Coll fill_slhaea(const lowMSSM_slha<Model>&, const softsusy::QedQcd&, const lowMSSM_scales&, const lowMSSM_observables&);

private:
   SLHA_io slha_io; ///< SLHA io class
   bool print_imaginary_parts_of_majorana_mixings;

   void set_extpar(const lowMSSM_input_parameters&);
   void set_imminpar(const lowMSSM_input_parameters&);
   void set_imextpar(const lowMSSM_input_parameters&);
   void set_minpar(const lowMSSM_input_parameters&);
   void set_mass(const lowMSSM_physical&, bool);
   void set_mixing_matrices(const lowMSSM_physical&, bool);
   template <class Model> void set_model_parameters(const lowMSSM_slha<Model>&);
   void set_ckm(const Eigen::Matrix<std::complex<double>,3,3>&, double);
   void set_pmns(const Eigen::Matrix<std::complex<double>,3,3>&, double);
   double read_scale() const;
   void fill_drbar_parameters(lowMSSM_mass_eigenstates&) const;
   void fill_physical(lowMSSM_physical&) const;
};

/**
 * Reads DR-bar parameters, pole masses and mixing matrices from a
 * SLHA output file.
 */
template <class Model>
void lowMSSM_slha_io::fill(lowMSSM_slha<Model>& model) const
{
   fill(static_cast<lowMSSM_mass_eigenstates&>(model));
   fill_physical(model.get_physical_slha());
}

template <class Model>
void lowMSSM_slha_io::fill_slhaea(
   SLHAea::Coll& slhaea, const lowMSSM_slha<Model>& model,
   const softsusy::QedQcd& qedqcd, const lowMSSM_scales& scales,
   const lowMSSM_observables& observables)
{
   lowMSSM_slha_io slha_io;
   const lowMSSM_input_parameters& input = model.get_input();
   const auto& problems = model.get_problems();
   const bool error = problems.have_problem();

   slha_io.set_spinfo(problems);
   slha_io.set_sminputs(qedqcd);
   slha_io.set_input(input);
   if (!error) {
      slha_io.set_spectrum(model);
      slha_io.set_extra(model, scales, observables);
   }

   slhaea = slha_io.get_slha_io().get_data();
}

template <class Model>
SLHAea::Coll lowMSSM_slha_io::fill_slhaea(
   const lowMSSM_slha<Model>& model, const softsusy::QedQcd& qedqcd,
   const lowMSSM_scales& scales, const lowMSSM_observables& observables)
{
   SLHAea::Coll slhaea;
   lowMSSM_slha_io::fill_slhaea(slhaea, model, qedqcd, scales, observables);

   return slhaea;
}

/**
 * Stores the model (DR-bar) parameters in the SLHA object.
 *
 * @param model model class
 */
template <class Model>
void lowMSSM_slha_io::set_model_parameters(const lowMSSM_slha<Model>& model)
{
   {
      std::ostringstream block;
      block << "Block gauge Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_ELEMENT(1, (MODELPARAMETER(g1) * 0.7745966692414834), "g1 * 0.7745966692414834")
            << FORMAT_ELEMENT(2, (MODELPARAMETER(g2)), "g2")
            << FORMAT_ELEMENT(3, (MODELPARAMETER(g3)), "g3")
      ;
      slha_io.set_block(block);
   }
   slha_io.set_block("Yu", ToMatrix(MODELPARAMETER(Yu_slha)), "Yu", model.get_scale());
   slha_io.set_block("Yd", ToMatrix(MODELPARAMETER(Yd_slha)), "Yd", model.get_scale());
   slha_io.set_block("Ye", ToMatrix(MODELPARAMETER(Ye_slha)), "Ye", model.get_scale());
   slha_io.set_block("Te", MODELPARAMETER(TYe_slha), "TYe", model.get_scale());
   slha_io.set_block("Td", MODELPARAMETER(TYd_slha), "TYd", model.get_scale());
   slha_io.set_block("Tu", MODELPARAMETER(TYu_slha), "TYu", model.get_scale());
   {
      std::ostringstream block;
      block << "Block HMIX Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_ELEMENT(1, (MODELPARAMETER(Mu)), "Mu")
            << FORMAT_ELEMENT(101, (MODELPARAMETER(BMu)), "BMu")
            << FORMAT_ELEMENT(102, (MODELPARAMETER(vd)), "vd")
            << FORMAT_ELEMENT(103, (MODELPARAMETER(vu)), "vu")
      ;
      slha_io.set_block(block);
   }
   slha_io.set_block("MSQ2", MODELPARAMETER(mq2_slha), "mq2", model.get_scale());
   slha_io.set_block("MSE2", MODELPARAMETER(me2_slha), "me2", model.get_scale());
   slha_io.set_block("MSL2", MODELPARAMETER(ml2_slha), "ml2", model.get_scale());
   slha_io.set_block("MSU2", MODELPARAMETER(mu2_slha), "mu2", model.get_scale());
   slha_io.set_block("MSD2", MODELPARAMETER(md2_slha), "md2", model.get_scale());
   {
      std::ostringstream block;
      block << "Block MSOFT Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_ELEMENT(21, (MODELPARAMETER(mHd2)), "mHd2")
            << FORMAT_ELEMENT(22, (MODELPARAMETER(mHu2)), "mHu2")
            << FORMAT_ELEMENT(1, (MODELPARAMETER(MassB)), "MassB")
            << FORMAT_ELEMENT(2, (MODELPARAMETER(MassWB)), "MassWB")
            << FORMAT_ELEMENT(3, (MODELPARAMETER(MassG)), "MassG")
      ;
      slha_io.set_block(block);
   }

   {
      std::ostringstream block;
      block << "Block Phases Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_ELEMENT(1, (Re(MODELPARAMETER(PhaseGlu))), "Re(PhaseGlu)")
      ;
      slha_io.set_block(block);
   }
   {
      std::ostringstream block;
      block << "Block IMPhases Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_ELEMENT(1, (Im(MODELPARAMETER(PhaseGlu))), "Im(PhaseGlu)")
      ;
      slha_io.set_block(block);
   }

}

/**
 * Writes extra SLHA blocks
 *
 * @param model model class
 * @param scales struct of boundary condition scales
 * @param observables struct of observables
 */
template <class Model>
void lowMSSM_slha_io::set_extra(
   const lowMSSM_slha<Model>& model, const lowMSSM_scales& scales,
   const lowMSSM_observables& observables)
{
   const lowMSSM_physical physical(model.get_physical_slha());

   {
      std::ostringstream block;
      block << "Block FlexibleSUSYOutput" << '\n'
            << FORMAT_ELEMENT(0, (SCALES(HighScale)), "HighScale")
            << FORMAT_ELEMENT(1, (SCALES(SUSYScale)), "SUSYScale")
            << FORMAT_ELEMENT(2, (SCALES(LowScale)), "LowScale")
      ;
      slha_io.set_block(block);
   }
   {
      std::ostringstream block;
      block << "Block FlexibleSUSYLowEnergy Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_ELEMENT(21, (OBSERVABLES.a_muon), "Delta(g-2)_muon/2 FlexibleSUSY")
      ;
      slha_io.set_block(block);
   }
   {
      std::ostringstream block;
      block << "Block EFFHIGGSCOUPLINGS" << '\n'
            << FORMAT_RANK_THREE_TENSOR(25, 22, 22, (Abs(OBSERVABLES.eff_cp_higgs_photon_photon(0))), "Abs(effective H-Photon-Photon coupling)")
            << FORMAT_RANK_THREE_TENSOR(35, 22, 22, (Abs(OBSERVABLES.eff_cp_higgs_photon_photon(1))), "Abs(effective H-Photon-Photon coupling)")
            << FORMAT_RANK_THREE_TENSOR(25, 21, 21, (Abs(OBSERVABLES.eff_cp_higgs_gluon_gluon(0))), "Abs(effective H-Gluon-Gluon coupling)")
            << FORMAT_RANK_THREE_TENSOR(35, 21, 21, (Abs(OBSERVABLES.eff_cp_higgs_gluon_gluon(1))), "Abs(effective H-Gluon-Gluon coupling)")
            << FORMAT_RANK_THREE_TENSOR(36, 22, 22, (Abs(OBSERVABLES.eff_cp_pseudoscalar_photon_photon)), "Abs(effective A-Photon-Photon coupling)")
            << FORMAT_RANK_THREE_TENSOR(36, 21, 21, (Abs(OBSERVABLES.eff_cp_pseudoscalar_gluon_gluon)), "Abs(effective A-Gluon-Gluon coupling)")
      ;
      slha_io.set_block(block);
   }
   {
      std::ostringstream block;
      block << "Block ALPHA" << '\n'
            << FORMAT_NUMBER((ArcSin(Pole(ZH(1,1)))), "ArcSin(Pole(ZH(2,2)))")
      ;
      slha_io.set_block(block);
   }
   {
      std::ostringstream block;
      block << "Block HMIX Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_ELEMENT(1, (MODELPARAMETER(Mu)), "Mu")
            << FORMAT_ELEMENT(2, (MODELPARAMETER(vu)/MODELPARAMETER(vd)), "vu/vd")
            << FORMAT_ELEMENT(3, (Sqrt(Sqr(MODELPARAMETER(vd)) + Sqr(MODELPARAMETER(vu)))), "Sqrt(Sqr(vd) + Sqr(vu))")
            << FORMAT_ELEMENT(4, (Sqr(MODELPARAMETER(MAh)(1))), "Sqr(MAh(2))")
            << FORMAT_ELEMENT(101, (MODELPARAMETER(BMu)), "BMu")
            << FORMAT_ELEMENT(102, (MODELPARAMETER(vd)), "vd")
            << FORMAT_ELEMENT(103, (MODELPARAMETER(vu)), "vu")
      ;
      slha_io.set_block(block);
   }
   {
      std::ostringstream block;
      block << "Block Au Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_MIXING_MATRIX(1, 1, (MODELPARAMETER(TYu)(0,0)/MODELPARAMETER(Yu)(0,0)), "TYu(1,1)/Yu(1,1)")
            << FORMAT_MIXING_MATRIX(2, 2, (MODELPARAMETER(TYu)(1,1)/MODELPARAMETER(Yu)(1,1)), "TYu(2,2)/Yu(2,2)")
            << FORMAT_MIXING_MATRIX(3, 3, (MODELPARAMETER(TYu)(2,2)/MODELPARAMETER(Yu)(2,2)), "TYu(3,3)/Yu(3,3)")
      ;
      slha_io.set_block(block);
   }
   {
      std::ostringstream block;
      block << "Block Ad Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_MIXING_MATRIX(1, 1, (MODELPARAMETER(TYd)(0,0)/MODELPARAMETER(Yd)(0,0)), "TYd(1,1)/Yd(1,1)")
            << FORMAT_MIXING_MATRIX(2, 2, (MODELPARAMETER(TYd)(1,1)/MODELPARAMETER(Yd)(1,1)), "TYd(2,2)/Yd(2,2)")
            << FORMAT_MIXING_MATRIX(3, 3, (MODELPARAMETER(TYd)(2,2)/MODELPARAMETER(Yd)(2,2)), "TYd(3,3)/Yd(3,3)")
      ;
      slha_io.set_block(block);
   }
   {
      std::ostringstream block;
      block << "Block Ae Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_MIXING_MATRIX(1, 1, (MODELPARAMETER(TYe)(0,0)/MODELPARAMETER(Ye)(0,0)), "TYe(1,1)/Ye(1,1)")
            << FORMAT_MIXING_MATRIX(2, 2, (MODELPARAMETER(TYe)(1,1)/MODELPARAMETER(Ye)(1,1)), "TYe(2,2)/Ye(2,2)")
            << FORMAT_MIXING_MATRIX(3, 3, (MODELPARAMETER(TYe)(2,2)/MODELPARAMETER(Ye)(2,2)), "TYe(3,3)/Ye(3,3)")
      ;
      slha_io.set_block(block);
   }
   {
      std::ostringstream block;
      block << "Block MSOFT Q= " << FORMAT_SCALE(model.get_scale()) << '\n'
            << FORMAT_ELEMENT(1, (MODELPARAMETER(MassB)), "MassB")
            << FORMAT_ELEMENT(2, (MODELPARAMETER(MassWB)), "MassWB")
            << FORMAT_ELEMENT(3, (MODELPARAMETER(MassG)), "MassG")
            << FORMAT_ELEMENT(21, (MODELPARAMETER(mHd2)), "mHd2")
            << FORMAT_ELEMENT(22, (MODELPARAMETER(mHu2)), "mHu2")
            << FORMAT_ELEMENT(31, (SignedAbsSqrt(MODELPARAMETER(ml2)(0,0))), "SignedAbsSqrt(ml2(1,1))")
            << FORMAT_ELEMENT(32, (SignedAbsSqrt(MODELPARAMETER(ml2)(1,1))), "SignedAbsSqrt(ml2(2,2))")
            << FORMAT_ELEMENT(33, (SignedAbsSqrt(MODELPARAMETER(ml2)(2,2))), "SignedAbsSqrt(ml2(3,3))")
            << FORMAT_ELEMENT(34, (SignedAbsSqrt(MODELPARAMETER(me2)(0,0))), "SignedAbsSqrt(me2(1,1))")
            << FORMAT_ELEMENT(35, (SignedAbsSqrt(MODELPARAMETER(me2)(1,1))), "SignedAbsSqrt(me2(2,2))")
            << FORMAT_ELEMENT(36, (SignedAbsSqrt(MODELPARAMETER(me2)(2,2))), "SignedAbsSqrt(me2(3,3))")
            << FORMAT_ELEMENT(41, (SignedAbsSqrt(MODELPARAMETER(mq2)(0,0))), "SignedAbsSqrt(mq2(1,1))")
            << FORMAT_ELEMENT(42, (SignedAbsSqrt(MODELPARAMETER(mq2)(1,1))), "SignedAbsSqrt(mq2(2,2))")
            << FORMAT_ELEMENT(43, (SignedAbsSqrt(MODELPARAMETER(mq2)(2,2))), "SignedAbsSqrt(mq2(3,3))")
            << FORMAT_ELEMENT(44, (SignedAbsSqrt(MODELPARAMETER(mu2)(0,0))), "SignedAbsSqrt(mu2(1,1))")
            << FORMAT_ELEMENT(45, (SignedAbsSqrt(MODELPARAMETER(mu2)(1,1))), "SignedAbsSqrt(mu2(2,2))")
            << FORMAT_ELEMENT(46, (SignedAbsSqrt(MODELPARAMETER(mu2)(2,2))), "SignedAbsSqrt(mu2(3,3))")
            << FORMAT_ELEMENT(47, (SignedAbsSqrt(MODELPARAMETER(md2)(0,0))), "SignedAbsSqrt(md2(1,1))")
            << FORMAT_ELEMENT(48, (SignedAbsSqrt(MODELPARAMETER(md2)(1,1))), "SignedAbsSqrt(md2(2,2))")
            << FORMAT_ELEMENT(49, (SignedAbsSqrt(MODELPARAMETER(md2)(2,2))), "SignedAbsSqrt(md2(3,3))")
      ;
      slha_io.set_block(block);
   }

}

/**
 * Stores the model (DR-bar) parameters, masses and mixing matrices of
 * all given models in the SLHA object.
 *
 * @todo Use generic lambda instead of Set_spectrum in C++14
 *
 * @param models model classes
 */
template <class... Ts>
void lowMSSM_slha_io::set_spectrum(const std::tuple<Ts...>& models)
{
   Set_spectrum<lowMSSM_slha_io> ss(this);
   boost::fusion::for_each(models, ss);
}

/**
 * Stores the model (DR-bar) parameters, masses and mixing matrices in
 * the SLHA object.
 *
 * @param model model class in BPMZ convention
 */
template <class T>
void lowMSSM_slha_io::set_spectrum(const lowMSSM<T>& model)
{
   set_spectrum(lowMSSM_slha<lowMSSM<T> >(model));
}

/**
 * Stores the model (DR-bar) parameters, masses and mixing matrices in
 * the SLHA object.
 *
 * @param model model class in SLHA convention
 */
template <class Model>
void lowMSSM_slha_io::set_spectrum(const lowMSSM_slha<Model>& model)
{
   const lowMSSM_physical physical(model.get_physical_slha());
   const bool write_sm_masses = model.do_calculate_sm_pole_masses();

   set_model_parameters(model);
   set_mass(physical, write_sm_masses);
   set_mixing_matrices(physical, write_sm_masses);

   if (slha_io.get_modsel().quark_flavour_violated)
      set_ckm(model.get_ckm_matrix(), model.get_scale());

   if (slha_io.get_modsel().lepton_flavour_violated)
      set_pmns(model.get_pmns_matrix(), model.get_scale());
}

} // namespace flexiblesusy

#undef Pole
#undef PHYSICAL
#undef PHYSICAL_SLHA
#undef LOCALPHYSICAL
#undef MODEL
#undef MODELPARAMETER
#undef EXTRAPARAMETER
#undef OBSERVABLES
#undef LowEnergyConstant
#undef SCALES

#endif


/* Copyright (C) 2012-2015 David Bond, All Rights Reserved */

#include "COCOMOII.hpp"
#include <boost/program_options.hpp>
#include <iostream>
#include <iomanip>

namespace mspe {

namespace team {
  static const std::string nominal = "nominal";
  static const std::string expert = "expert";
  static const std::string good = "good";
}

namespace defaults {
  static const std::string team = team::nominal;
  static constexpr float salary = 100000;
  static constexpr float overhead = 1.4;
}

class Arguments {

  public:

    Arguments() = default;

    virtual ~Arguments() = default;

    Arguments(Arguments&&) = delete;

    Arguments& operator=(Arguments&&) = delete;

    Arguments(const Arguments&) = delete;

    Arguments& operator=(const Arguments&) = delete;

    bool parse(int argc, char* argv[])
    {
      boost::program_options::options_description options;

      options.add_options()
        ("help", "outputs usage")
        ("sloc", boost::program_options::value<float>(&sloc)->required(),
           "total source lines of code")
        ("team", boost::program_options::value<std::string>(&team)->
           default_value(defaults::team), "team skill level")
        ("salary", boost::program_options::value<float>(&salary)->
           default_value(defaults::salary), "team average salary")
        ("overhead", boost::program_options::value<float>(&overhead)->
           default_value(defaults::overhead),
           "team salary overhead multiplier");

      try {
        boost::program_options::variables_map vm;
        auto parsed = boost::program_options::parse_command_line(argc, argv,
                                                                 options);
        boost::program_options::store(parsed, vm);

        if (vm.count("help")) {
          std::cout << options << std::endl;
          return false;
        }
        boost::program_options::notify(vm);

        return true;
      } catch(const boost::program_options::error& e) {
        std::cerr << "invalid argument: " << e.what() << std::endl
                  << options << std::endl;
        throw;
      }
    }

    std::string team;

    float sloc;

    float salary;

    float overhead;
};

} // namespace mspe

/* A simple program which implements an api to use the COCOMO II model. */
int main(int argc, char* argv[]) {
  try {
    mspe::Arguments args;

    if (!args.parse(argc, argv)) {
      return EXIT_FAILURE;
    }

    mspe::COCOMOII c;
    if(args.team == mspe::team::good) {
      c.setEAF(mspe::RELY, mspe::HIGH);
      c.setEAF(mspe::RUSE, mspe::HIGH);
      c.setEAF(mspe::CPLX, mspe::VERY_HIGH ) ;
      c.setEAF(mspe::TIME, mspe::HIGH);
      c.setEAF(mspe::PVOL, mspe::LOW);
    } else if(args.team == mspe::team::expert) {
      c.setEAF(mspe::SCED, mspe::NOMINAL);
      c.setEAF(mspe::RELY, mspe::NOMINAL);
      c.setEAF(mspe::DATA, mspe::NOMINAL);
      c.setEAF(mspe::RUSE, mspe::NOMINAL);
      c.setEAF(mspe::DOCU, mspe::NOMINAL);
      c.setEAF(mspe::CPLX, mspe::NOMINAL);
      c.setEAF(mspe::TIME, mspe::NOMINAL);
      c.setEAF(mspe::STOR, mspe::NOMINAL);
      c.setEAF(mspe::PVOL, mspe::NOMINAL);
      c.setEAF(mspe::ACAP, mspe::VERY_HIGH);
      c.setEAF(mspe::PCAP, mspe::VERY_HIGH);
      c.setEAF(mspe::PCON, mspe::VERY_HIGH);
      c.setEAF(mspe::AEXP, mspe::VERY_HIGH);
      c.setEAF(mspe::PEXP, mspe::VERY_HIGH);
      c.setEAF(mspe::LTEX, mspe::VERY_HIGH);
      c.setEAF(mspe::TOOL, mspe::VERY_HIGH);
      c.setEAF(mspe::SITE, mspe::EXTRA_HIGH);
      c.setSF(mspe::PREC, mspe::NOMINAL);
      c.setSF(mspe::FLEX, mspe::EXTRA_HIGH);
      c.setSF(mspe::ARCH, mspe::EXTRA_HIGH);
      c.setSF(mspe::COHE, mspe::EXTRA_HIGH);
      c.setSF(mspe::MATU, mspe::EXTRA_HIGH);
    } else if(args.team == mspe::team::nominal) {
      // defaults already set
    } else {
      std::cerr << "unknown team skill level" << std::endl;
      return EXIT_FAILURE;
    }

    c.setSLOC(args.sloc/1000.0);
    c.setOverhead(args.overhead);
    c.setSalary(args.salary);

    std::cout << std::fixed << std::setprecision(2)
              << "Person Months: " << c.EFFORT()
              << std::endl
              << std::fixed << std::setprecision(0)
              << "Cost: $" << c.COST()
              << std::endl
              << "Cost & Overhead: $" << c.COSTANDOVERHEAD()
              << std::endl
              << "Cost & Overhead Per LSLOC: $"
              << c.COSTANDOVERHEAD()/args.sloc
              << std::endl;

    return EXIT_SUCCESS;
  } catch (...) {
    return EXIT_FAILURE;
  }
}


/* Copyright 2017 - BSD-3-Clause
 *
 * Copyright Holder (alphabetical):
 *
 * Cramer, Simon
 *
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 *    disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
 *    products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * @file QuantityOfInterest.cpp
 * @author Cramer
 * @date 11 May 2018
 * @brief This file belongs to the SABCEMM projekt. See github.com/SABCEMM/SABCEMM
 */

#include "QuantityOfInterest.h"
#include "QoiCalculatorExcessKurtosis.h"
#include "QoiCalculatorKurtosis.h"
#include "QoiCalculatorMaximum.h"
#include "QoiCalculatorMean.h"
#include "QoiCalculatorMinimum.h"
#include "QoiCalculatorMoment.h"
#include "QoiCalculatorQuantile.h"
#include "QoiCalculatorSkew.h"
#include "QoiCalculatorVariance.h"
#include "QoiCalculatorFull.h"
#include "../Writer/Writer.h"
#include "QoiCalculatorHill.h"
#include "QoiCalculatorAutocorrelation.h"

#include <iostream>

#include <boost/assign.hpp>

using namespace std;

const std::map<std::string, QuantityOfInterest::Method> QuantityOfInterest::stringToMethod =
        boost::assign::map_list_of
                ("full", QuantityOfInterest::Method::full)
                ("mean", QuantityOfInterest::Method::mean)
                ("variance", QuantityOfInterest::Method::variance)
                ("kurtosis", QuantityOfInterest::Method::kurtosis)
                ("excesskurtosis", QuantityOfInterest::Method::excessKurtosis)
                ("moment", QuantityOfInterest::Method::moment)
                ("skew", QuantityOfInterest::Method::skew)
                ("minimum", QuantityOfInterest::Method::minimum)
                ("maximum", QuantityOfInterest::Method::maximum)
                ("quantile", QuantityOfInterest::Method::quantile)
                ("hill", QuantityOfInterest::Method::hill)
                ("autocorrelation", QuantityOfInterest::Method::autocorrelation);
const std::map<QuantityOfInterest::Method, std::string> QuantityOfInterest::methodToString =
        boost::assign::map_list_of
                (QuantityOfInterest::Method::full, "full")
                (QuantityOfInterest::Method::mean, "mean")
                (QuantityOfInterest::Method::variance, "variance")
                (QuantityOfInterest::Method::kurtosis, "kurtosis")
                (QuantityOfInterest::Method::excessKurtosis, "excesskurtosis")
                (QuantityOfInterest::Method::moment, "moment")
                (QuantityOfInterest::Method::skew, "skew")
                (QuantityOfInterest::Method::minimum, "minimum")
                (QuantityOfInterest::Method::maximum, "maximum")
                (QuantityOfInterest::Method::quantile, "quantile")
                (QuantityOfInterest::Method::hill, "hill")
                (QuantityOfInterest::Method::autocorrelation, "autocorrelation");
const std::map<std::string, QuantityOfInterest::Quantity> QuantityOfInterest::stringToQuantity =
        boost::assign::map_list_of
                ("cash", QuantityOfInterest::Quantity::cash)
                ("stock", QuantityOfInterest::Quantity::stock)
                ("embgamma", QuantityOfInterest::Quantity::embGamma)
                ("excessdemand", QuantityOfInterest::Quantity::excessDemand)
                ("fwshares_chartist", QuantityOfInterest::Quantity::fwSharesChartist)
                ("fwshares_fundamentalist", QuantityOfInterest::Quantity::fwSharesFundamentalist)
                ("harrask", QuantityOfInterest::Quantity::harrasK)
                ("price", QuantityOfInterest::Quantity::price)
                ("switchableshares", QuantityOfInterest::Quantity::switchableShares)
                ("wealth", QuantityOfInterest::Quantity::wealth)
                ("return", QuantityOfInterest::Quantity::return_)
                ("absreturn", QuantityOfInterest::Quantity::absReturn)
                ("logreturn", QuantityOfInterest::Quantity::logReturn)
                ("abslogreturn", QuantityOfInterest::Quantity::absLogReturn);

const std::map<QuantityOfInterest::Quantity, std::string> QuantityOfInterest::quantityToString =
        boost::assign::map_list_of
                (QuantityOfInterest::Quantity::cash, "cash")
                (QuantityOfInterest::Quantity::stock, "stock")
                (QuantityOfInterest::Quantity::embGamma, "embgamma")
                (QuantityOfInterest::Quantity::excessDemand, "excessdemand")
                (QuantityOfInterest::Quantity::fwSharesChartist, "fwshares_chartist")
                (QuantityOfInterest::Quantity::fwSharesFundamentalist, "fwshares_fundamentalist")
                (QuantityOfInterest::Quantity::harrasK, "harrask")
                (QuantityOfInterest::Quantity::price, "price")
                (QuantityOfInterest::Quantity::switchableShares, "switchableshares")
                (QuantityOfInterest::Quantity::wealth, "wealth")
                (QuantityOfInterest::Quantity::return_, "return")
                (QuantityOfInterest::Quantity::absReturn, "absreturn")
                (QuantityOfInterest::Quantity::logReturn, "logreturn")
                (QuantityOfInterest::Quantity::absLogReturn, "abslogreturn");

QuantityOfInterest::QuantityOfInterest(QuantityOfInterest::Quantity quantity,
                                       DataItemCollector *dataItemCollector) :
        dataItemCollector(dataItemCollector), quantity(quantity) {
    calculators.clear();
    groupToTrack = -1;
    name = "NONE";
}

QuantityOfInterest::QuantityOfInterest(std::string quantity,
                                       DataItemCollector *dataItemCollector) :
        QuantityOfInterest(
                QuantityOfInterest::stringToQuantity.at(quantity),
                dataItemCollector) {

}

QuantityOfInterest::~QuantityOfInterest() {
    for (auto& calculator: calculators) {
        delete calculator;
        calculator = nullptr;
    }
    calculators.clear();
}

QuantityOfInterest *QuantityOfInterest::factory(DataItemCollector *dataItemCollector, Input &QOIinput) {
    std::string type = QOIinput.getName();
    std::string name_ = QOIinput.getName();
    if (QOIinput("quantity")){
        type = QOIinput["quantity"].getString();
    }

    QuantityOfInterest::Quantity quantity;
    try {
        quantity = QuantityOfInterest::stringToQuantity.at(type);
    }
    catch (std::out_of_range &e) {
        throw("QoI unknown!");
    }

    QuantityOfInterest *quantityOfInterest = new QuantityOfInterest(type, dataItemCollector);

    quantityOfInterest->setName(name_);
    std::vector<QoiCalculator *> calculators;
    for (auto& method_: QOIinput.getChildren()) {
        QuantityOfInterest::Method method;
        try {
            method = QuantityOfInterest::stringToMethod.at(method_.getName());
        }
        catch (std::out_of_range &e) {
            continue;
        }

        QoiCalculator *calculator = nullptr;
        switch (method) {
            case QuantityOfInterest::Method::mean:
                calculator = new QoiCalculatorMean();
                break;
            case QuantityOfInterest::Method::variance:
                calculator = new QoiCalculatorVariance();
                break;
            case QuantityOfInterest::Method::kurtosis:
                calculator = new QoiCalculatorKurtosis();
                break;
            case QuantityOfInterest::Method::excessKurtosis:
                calculator = new QoiCalculatorExcessKurtosis();
                break;
            case QuantityOfInterest::Method::moment:
                if(method_.hasChildren()){
                    for(auto& m: method_.getChildren()){
                        calculator = new QoiCalculatorMoment(m.getInt());
                        calculators.push_back(calculator);
                    }
                    continue;
                }
                else{
                    calculator = new QoiCalculatorMoment(method_.getInt());
                }
                break;
            case QuantityOfInterest::Method::skew:
                calculator = new QoiCalculatorSkew();
                break;
            case QuantityOfInterest::Method::minimum:
                calculator = new QoiCalculatorMinimum();
                break;
            case QuantityOfInterest::Method::maximum:
                calculator = new QoiCalculatorMaximum();
                break;
            case QuantityOfInterest::Method::quantile:
                calculator = new QoiCalculatorQuantile();
                break;
            case QuantityOfInterest::Method::full:
                calculator = new QoiCalculatorFull();
                break;
            case QuantityOfInterest::Method::hill:
                if(method_.hasChildren()){
                    for(auto& m: method_.getChildren()){
                        calculator = new QoiCalculatorHill(m.getDouble());
                        calculators.push_back(calculator);
                    }
                    continue;
                }
                else {
                    calculator = new QoiCalculatorHill(method_.getDouble());
                }
                break;
            case QuantityOfInterest::Method::autocorrelation:
                if(method_.hasChildren()){
                    for(auto& m: method_.getChildren()){
                        calculator = new QoiCalculatorAutocorrelation(m.getSizeT());
                        calculators.push_back(calculator);
                    }
                    continue;
                }
                else {
                    calculator = new QoiCalculatorAutocorrelation(method_.getSizeT());
                }
                break;
            default:
                cerr << "Quantity Of Interest " << QuantityOfInterest::methodToString.at(method)
                     << " not implemented yet. It's ignored." << endl;
                break;
        }
        calculators.push_back(calculator);

    }

    quantityOfInterest->setCalculators(calculators);

    return quantityOfInterest;
}

void QuantityOfInterest::setCalculators(std::vector<QoiCalculator *> calculators) {
    this->calculators = calculators;
}

void QuantityOfInterest::calculateQoI() {
    std::vector<std::vector<double>>* data = dataItemCollector->getData();

    switch (quantity) {
        case QuantityOfInterest::Quantity::logReturn:
            assert(data->size() == 1);
            assert(data->at(0).size() > 1);

            for(size_t i = 0; i < data->at(0).size()-1; i++){
                // equivalent to log(data->at(0).at(i+1)) - log(data->at(0).at(i) ) but only one expensive logarithm needs to be computed
                data->at(0).at(i) = log(data->at(0).at(i+1) / data->at(0).at(i) );
            }

            // Delete last element. We can't compute return here because we are missing data.
            data->at(0).pop_back();
            break;
        case QuantityOfInterest::Quantity::absLogReturn:
            assert(data->size() == 1);
            assert(data->at(0).size() > 1);

            for(size_t i = 0; i < data->at(0).size()-1; i++){
                data->at(0).at(i) = abs( log( data->at(0).at(i+1) / data->at(0).at(i) ) );
            }

            // Delete last element. We can't compute return here because we are missing data.
            data->at(0).pop_back();
            break;
        case QuantityOfInterest::Quantity::return_:
            assert(data->size() == 1);
            assert(data->at(0).size() > 1);

            for(size_t i = 0; i < data->at(0).size()-1; i++){
                data->at(0).at(i) = ( data->at(0).at(i+1) / data->at(0).at(i) ) -1 ;
            }

            // Delete last element. We can't compute return here because we are missing data.
            data->at(0).pop_back();
            break;
        case QuantityOfInterest::Quantity::absReturn:
            assert(data->size() == 1);
            assert(data->at(0).size() > 1);

            for(size_t i = 0; i < data->at(0).size()-1; i++){
                data->at(0).at(i) = abs( data->at(0).at(i+1) / data->at(0).at(i) -1 );
            }

            // Delete last element. We can't compute return here because we are missing data.
            data->at(0).pop_back();
            break;
        default:
            assert(data->size() > 0);
            assert(data->at(0).size() > 0);

    }

    for (auto calculator : calculators) {
        calculator->calculate(data);
    }
    dataItemCollector->clearData();
}

void QuantityOfInterest::write(Writer* writer){
    for(auto calculator : calculators){
        std::string method_name = QuantityOfInterest::methodToString.at(calculator->getMethod());

        if (calculator->getMethod() == QuantityOfInterest::Method::hill ||
            calculator->getMethod() == QuantityOfInterest::Method::autocorrelation ||
            calculator->getMethod() == QuantityOfInterest::Method::moment){
            method_name += "_"+std::to_string(calculator->getParameter());
        }
        writer->addQoI(method_name, quantity, groupToTrack,
                calculator->getResult(), this->name);
    }
}

const string &QuantityOfInterest::getName() const {
    return name;
}

void QuantityOfInterest::setName(const string &name) {
    this->name = name;
}

int QuantityOfInterest::getGroupToTrack() const {
    return groupToTrack;
}

void QuantityOfInterest::setGroupToTrack(int groupToTrack) {
    QuantityOfInterest::groupToTrack = groupToTrack;
}


//=============================================================================================================
/**
* @file     sphere.cpp
* @author   Christoph Dinh <chdinh@nmr.mgh.harvard.edu>;
*           Matti Hamalainen <msh@nmr.mgh.harvard.edu>
* @version  1.0
* @date     April, 2016
*
* @section  LICENSE
*
* Copyright (C) 2016, Christoph Dinh and Matti Hamalainen. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that
* the following conditions are met:
*     * Redistributions of source code must retain the above copyright notice, this list of conditions and the
*       following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*       the following disclaimer in the documentation and/or other materials provided with the distribution.
*     * Neither the name of MNE-CPP authors nor the names of its contributors may be used
*       to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @brief    Implementation of the Sphere Class.
*
*/

//*************************************************************************************************************
//=============================================================================================================
// INCLUDES
//=============================================================================================================

#include "sphere.h"
#include "simplex_algorithm.h"

//*************************************************************************************************************
//=============================================================================================================
// Eigen INCLUDES
//=============================================================================================================

#include <Eigen/Dense>

//*************************************************************************************************************
//=============================================================================================================
// STL INCLUDES
//=============================================================================================================

#include <iostream>

//*************************************************************************************************************
//=============================================================================================================
// USED NAMESPACES
//=============================================================================================================

using namespace UTILSLIB;
using namespace Eigen;

//*************************************************************************************************************
//=============================================================================================================
// DEFINE MEMBER METHODS
//=============================================================================================================

Sphere::Sphere( const Vector3f& center, float radius )
: m_center(center)
, m_r(radius)
{
}

//*************************************************************************************************************

Sphere Sphere::fit_sphere(const MatrixX3f& points)
{
    const VectorXf& x = points.col(0);
    const VectorXf& y = points.col(1);
    const VectorXf& z = points.col(2);

    VectorXf point_means = points.colwise().mean();

    VectorXf x_mean_free = x.array() - point_means(0);
    VectorXf y_mean_free = y.array() - point_means(1);
    VectorXf z_mean_free = z.array() - point_means(2);

    Matrix3f A;
    A << (x.cwiseProduct(x_mean_free)).mean(), 2*(x.cwiseProduct(y_mean_free)).mean(), 2*(x.cwiseProduct(z_mean_free)).mean(),
                                            0,   (y.cwiseProduct(y_mean_free)).mean(), 2*(y.cwiseProduct(z_mean_free)).mean(),
                                            0,                                      0,   (z.cwiseProduct(z_mean_free)).mean();

    Matrix3f A_T = A.transpose();
    A += A_T;

    Vector3f b;
    VectorXf sq_sum = x.array().pow(2)+y.array().pow(2)+z.array().pow(2);
    b << (sq_sum.cwiseProduct(x_mean_free)).mean(),
         (sq_sum.cwiseProduct(y_mean_free)).mean(),
         (sq_sum.cwiseProduct(z_mean_free)).mean();

    Vector3f center = A.ldlt().solve(b);

    MatrixX3f tmp(points.rows(),3);
    tmp.col(0) = x.array() - center(0);
    tmp.col(1) = y.array() - center(1);
    tmp.col(2) = z.array() - center(2);

    float r = sqrt(tmp.array().pow(2).rowwise().sum().mean());

    return Sphere(center, r);
}

//*************************************************************************************************************

Sphere Sphere::fit_sphere_simplex(const MatrixX3f& points, double simplex_size)
{
    VectorXf center;
    float R;
    fit_sphere_to_points( points, simplex_size, center, R);

    return Sphere(center, R);
}

//*************************************************************************************************************

bool Sphere::fit_sphere_to_points ( const MatrixXf &rr, float simplex_size, VectorXf &r0, float &R )
{
//    int   np = rr.rows();

    /*
    * Find the optimal sphere origin
    */
    fitUserRecNew user;
    float      ftol            = 1e-5f;
    int        max_eval        = 500;
    int        report_interval = -1;
    int        neval;
    MatrixXf   init_simplex;
    VectorXf   init_vals(4);

    VectorXf   cm(3);
    float      R0 = 0.1f;

    user.rr = rr;

    calculate_cm_ave_dist(rr, cm, R0);// [done]

    init_simplex = make_initial_simplex( cm, simplex_size );

    user.report = false;

    for (int k = 0; k < 4; k++) {
        init_vals[k] = fit_eval( static_cast<VectorXf>(init_simplex.row(k)), &user );
    }

    user.report = false;

    //Start the minimization
    if(!SimplexAlgorithm::simplex_minimize<float>(  init_simplex,   /* The initial simplex */
                                                    init_vals,      /* Function values at the vertices */
                                                    ftol,           /* Relative convergence tolerance */
                                                    fit_eval,       /* The function to be evaluated */
                                                    &user,          /* Data to be passed to the above function in each evaluation */
                                                    max_eval,       /* Maximum number of function evaluations */
                                                    neval,          /* Number of function evaluations */
                                                    report_interval,/* How often to report (-1 = no_reporting) */
                                                    report_func))   /* The function to be called when reporting */
        return false;

    r0 = init_simplex.row(0);
    R = opt_rad(r0, &user);

    return true;
}

//*************************************************************************************************************

bool Sphere::report_func(int loop, const VectorXf &fitpar, double fval)
{
    /*
    * Report periodically
    */
    const VectorXf& r0 = fitpar;

    std::cout << "loop: " << loop << "; r0: " << 1000*r0[0] << ", r1: " << 1000*r0[1] << ", r2: " << 1000*r0[2] << "; fval: " << fval << std::endl;

    return true;
}

//*************************************************************************************************************

void Sphere::calculate_cm_ave_dist (const MatrixXf &rr, VectorXf &cm, float &avep)
{
    cm = rr.colwise().mean();
    MatrixXf diff = rr.rowwise() - cm.transpose();
    avep = diff.rowwise().norm().mean();
}

//*************************************************************************************************************

MatrixXf Sphere::make_initial_simplex( const VectorXf &pars, float size )
{
    /*
    * Make the initial tetrahedron
    */
    int npar = pars.size();

    MatrixXf simplex = MatrixXf::Zero(npar+1,npar);

    simplex.rowwise() += pars.transpose();

    for (int k = 1; k < npar+1; k++) {
        simplex(k,k-1) += size;
    }

    return simplex;
}

//*************************************************************************************************************

float Sphere::fit_eval ( const VectorXf &fitpar, const void  *user_data)
{
    /*
    * Calculate the cost function value
    * Optimize for the radius inside here
    */
    const fitUserNew& user = (fitUserNew)user_data;
    const VectorXf& r0 = fitpar;

    float F;

    MatrixXf diff = user->rr.rowwise() - r0.transpose();
    VectorXf one = diff.rowwise().norm();

    float sum = one.sum();
    float sum2 = one.dot(one);

    F = sum2 - sum*sum/user->rr.rows();

    if(user->report)
        std::cout << "r0: " << 1000*r0[0] << ", r1: " << 1000*r0[1] << ", r2: " << 1000*r0[2] << "; R: " << 1000*sum/user->rr.rows() << "; fval: "<<F<<std::endl;

    return F;
}

//*************************************************************************************************************

float Sphere::opt_rad(const VectorXf &r0,const fitUserNew user)
{
  MatrixXf diff = user->rr.rowwise() - r0.transpose();
  return diff.rowwise().norm().mean();
}


//          Copyright surrealwaffle 2018 - 2020.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          https://www.boost.org/LICENSE_1_0.txt)

#pragma once

#include <cstddef>

#include <algorithm>
#include <functional>
#include <iterator>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/iterator/transform_iterator.hpp>

#include <sentinel/structures/object.hpp>
#include <sentinel/tags/object.hpp>
#include <sentinel/tags/collision_model.hpp>

#include "utility.hpp"
#include "bsp_interface.hpp"

namespace simulacrum {

struct this_collision_bsp_tag { };
inline constexpr this_collision_bsp_tag this_collision_bsp = {};

struct collision_hierarchy_entry {
    std::reference_wrapper<sentinel::object> object;
    std::reference_wrapper<sentinel::tags::object> object_definition;
    std::reference_wrapper<sentinel::tags::collision_model> collision_model;
};

boost::geometry::index::rtree<
    std::pair<
        boost::geometry::model::box<sentinel::real3d>,
        collision_hierarchy_entry>,
    boost::geometry::index::rstar<8>>
build_dynamic_collision_hierarchy();

/** \brief Implements an adjacency list that cannot be modified once constructed.
 *
 * The user is required to implement the equality comparison operator for \a Node and
 * the specialization `std::hash<Node>`.
 */
template<
    class Node,
    class Edge = empty_struct
> class compiled_adjacency_list {
public:
    struct edge_type;
    using edge_iterator  = const edge_type*;
    using edge_list_type = rough_span<edge_iterator>;
    using nodes_container_type = std::unordered_map<Node, edge_list_type>;
    using edge_container_type  = std::vector<edge_type>;

    using hasher     = typename nodes_container_type::hasher;
    using size_type  = typename nodes_container_type::size_type;
    using value_type = typename nodes_container_type::value_type;

    /** \brief The iterator type for the elements of the adjacency structure.
     *         Elements referred to by such iterators are constant pairings of
     *         `(Node, edge_list_type)`.
     */
    using iterator  = typename nodes_container_type::const_iterator;

    /** \brief Represents a directed edge in the graph.
     */
    struct edge_type {
        iterator source; ///< The node the edge starts from.
        iterator target; ///< The node the edge goes to.
        Edge     user;   ///< User data for the edge.

        /** \brief Conversion to user edge data.
         */
        operator Edge&() noexcept { return user; }

        /** \brief Conversion to user edge data.
         */
        operator const Edge&() const noexcept { return user; }

        /** \brief Member access for user edge data.
         */
        Edge* operator->() noexcept { return std::addressof(user); }

        /** \brief Member access for user edge data.
         */
        const Edge* operator->() const noexcept { return std::addressof(user); }
    };

    /** \brief Constructs to an empty graph.
     */
    compiled_adjacency_list() = default;

    /** \brief Constructs a graph where the structure is taken in as node pairings.
     *
     * The edges are described as \a Node pairings `(x,y)`, with the range of pairings
     * supplied through `[neighbors_begin, neighbors_end)`.
     * Every node `x` and `y` is interned and the edges are formed by the expression
     * `form_edge(u, v)` where `u`, `v` and the interned counterparts of `x`, `y`.
     *
     * From the time the edges are formed, it is guaranteed that references to the
     * interned nodes will not be invalidated, until the destructor is called.
     */
    template<class ForwardIt, class FormEdge>
    compiled_adjacency_list(ForwardIt neighbors_begin, ForwardIt neighbors_end,
                            const FormEdge& form_edge);

    /** \brief Accesses the internal node counterpart for \a node.
     *
     * Has constant (on average) time-complexity.
     *
     * \return An optional containing a constant reference to the interned node, or
     *         `std::nullopt` if \a node is not interned.
     */
    std::optional<std::reference_wrapper<const Node>>
    intern(const Node& node) const;

    /** \brief Queries the graph to determine if there is a directed edge from
     *         one node to another.
     *
     * Has logarithmic time-complexity.
     * For consistency with #intern, this returns references to the user-domain
     * \a Edge, not the implementation #edge_type.
     *
     * \return An optional containing a constant reference to the edge, or
     *         `std::nullopt` if there is no directed edge between \a from and \a to.
     */
    std::optional<std::reference_wrapper<const Edge>>
    adjacent(const Node& from, const Node& to) const;

    /** \brief Accesses the outgoing edges of a node.
     *
     * Has constant (on average) time-complexity.
     * Unlike #adjacent, the
     *
     * \return An iterable span of the outgoing edges of \a node, or
     *         an empty span if \a node is not in the graph.
     */
    edge_list_type
    egress_edges(const Node& node) const;

    /** \brief Returns an iterator to the first element of the adjacency structure.
     */
    iterator begin() const noexcept { return nodes.begin(); }

    /** \brief Returns an iterator to the element following the last element of the
     *         adjacency structure.
     */
    iterator end() const noexcept { return nodes.end(); }

    /** \brief Gets an iterator to a given node.
     *
     * \return An iterator to the graph vertex of \a node if it is in the graph, or
     *         `end()` if \a node is not in the graph.
     */
    iterator find(const Node& node) const noexcept { return nodes.find(node); }

    /** \brief Returns the number of nodes in the graph.
     */
    size_type size() const noexcept { return nodes.size(); }

    /** \brief Returns the function that hashes the nodes.
     */
    hasher hash_function() const { return nodes.hash_function(); }

private:
    nodes_container_type nodes; ///< A mapping between nodes and sorted, contiguous
                                ///< regions in #edges consisting of node out-edges.
    edge_container_type  edges; ///< A collection of edges between the graph nodes.
                                ///< Internally sorted by
};

struct navigation_graph_node {
    sentinel::real3d point; ///< The node position.

    enum {
        type_surface, ///< Indicates #bsp_index refers to a surface in the CBSP.
        type_edge     ///< Indicates #bsp_index refers to an edge in the CBSP.
    } cbsp_type;     ///< Determines the type of element #cbsp_index refers to.
    long cbsp_index; ///< The index of the CBSP element this node is based on.

    bool operator==(const navigation_graph_node& other) const noexcept {
        return cbsp_type == other.cbsp_type && cbsp_index == other.cbsp_index;
    }

    bool operator!=(const navigation_graph_node& other) const noexcept {
        return !(*this == other);
    }

    std::size_t hash_value() const;
};

} // namespace simulacrum

// --------------------------------------------------
// std::hash specialization for navigation_graph_node

namespace std {

template<>
struct hash<::simulacrum::navigation_graph_node> {
    std::size_t
    operator()(const ::simulacrum::navigation_graph_node& node) const noexcept
    {
        return node.hash_value();
    }
};

} // namespace std

namespace simulacrum {

struct navigation_graph_edge {
    sentinel::direction3d direction;
    float                 distance;
};

class navigation_graph {
public:
    using node_type = navigation_graph_node;
    using edge_type = navigation_graph_edge;
    using graph_type = compiled_adjacency_list<node_type, edge_type>;
    using iterator = graph_type::iterator;

    navigation_graph() = default;

    navigation_graph(const utility::interface_cbsp& cbsp);

    navigation_graph(this_collision_bsp_tag);

    iterator begin() const noexcept { return graph.begin(); }
    iterator end()   const noexcept { return graph.end();   }

    std::optional<iterator>
    nearest_node(const sentinel::real3d& pos) const;

    std::optional<iterator>
    get_node(const node_type& node) const;

    const graph_type&
    get_graph() const { return graph; }

private:
    using spatial_point_type
        = sentinel::real3d;
    using spatial_indexable
        = std::pair<spatial_point_type, iterator>;
    using spatial_index_type
        = boost::geometry::index::rtree<spatial_indexable,
                                        boost::geometry::index::rstar<8>>;

    graph_type         graph;
    spatial_index_type spatial_index;
};

template<class Node, class Edge>
template<class ForwardIt, class FormEdge>
compiled_adjacency_list<Node, Edge>::compiled_adjacency_list(
        ForwardIt neighbors_begin, ForwardIt neighbors_end,
        const FormEdge& form_edge)
    : nodes()
    , edges()
{
    // insert nodes with empty spans, to allocate
    for (auto it = neighbors_begin; it != neighbors_end; ++it) {
        const auto& [node, neighbor] = *it;

        nodes.insert({node,     edge_list_type{nullptr, nullptr}});
        nodes.insert({neighbor, edge_list_type{nullptr, nullptr}});
    }

    // form edges
    edges.reserve(std::distance(neighbors_begin, neighbors_end));
    for (auto it = neighbors_begin; it != neighbors_end; ++it) {
        const auto& [node, neighbor] = *it;
        const auto interned_node_it = nodes.find(node);
        const auto interned_neighbor_it = nodes.find(neighbor);
        edges.push_back({interned_node_it,
                         interned_neighbor_it,
                         form_edge(interned_node_it->first,
                                   interned_neighbor_it->first)});
    }

    auto edge_source = [] (const edge_type& edge) -> const Node& { return edge.source->first; };
    auto edge_target = [] (const edge_type& edge) -> const Node& { return edge.target->first; };
    auto interned_cmp = [] (const Node& a, const Node& b) -> bool { return std::addressof(a) < std::addressof(b); };

    auto cmp_edge_sources = [&edge_source, &interned_cmp] (const edge_type& a, const edge_type& b) {
        return interned_cmp(edge_source(a), edge_source(b));
    };
    auto cmp_edge_targets = [&edge_target, &interned_cmp] (const edge_type& a, const edge_type& b) {
        return interned_cmp(edge_target(a), edge_target(b));
    };

    // sort edges into spans sorted on the source node
    std::sort(edges.begin(), edges.end(), cmp_edge_sources);

    // properly set the edge list spans in the map
    for (auto& [node, span] : nodes) {
        auto [begin, end] = std::equal_range(boost::transform_iterator(edges.begin(), edge_source),
                                             boost::transform_iterator(edges.end(),   edge_source),
                                             node, interned_cmp);
        span = edge_list_type {
            std::addressof(*(begin.base())),
            std::addressof(*(end.base()))
        };

        // put the edge list in sorted order, for lookup later
        std::sort(begin.base(), end.base(), cmp_edge_targets);
    }
}

template<class Node, class Edge>
std::optional<std::reference_wrapper<const Node>>
compiled_adjacency_list<Node, Edge>::intern(const Node& node) const
{
    if (auto it = nodes.find(node); it != nodes.end())
        return std::ref(it->first);
    return std::nullopt;
}

template<class Node, class Edge>
std::optional<std::reference_wrapper<const Edge>>
compiled_adjacency_list<Node, Edge>::adjacent(const Node& from, const Node& to_) const
{
    auto to = intern(to_);
    auto source_it = nodes.find(from);

    if (source_it == nodes.end() || !to)
        return std::nullopt;

    const auto& [node, span] = *source_it;
    auto cmp = [] (const edge_type& edge, const Node& node) {
        return std::addressof(edge.target->first) < std::addressof(node);
    };
    auto edge_it = std::lower_bound(span.begin(), span.end(), to.value(), cmp);
    return edge_it != span.end() ? std::ref(edge_it->user) : std::nullopt;
}

template<class Node, class Edge>
typename compiled_adjacency_list<Node, Edge>::edge_list_type
compiled_adjacency_list<Node, Edge>::egress_edges(const Node& node) const
{
    auto it = nodes.find(node);
    return it != nodes.end() ? it->second
                             : edge_list_type{edges.end(), edges.end()};
}

template<
    class MapIterator,
    class ValueKeyHasher = void
> struct map_iterator_hasher {
    using key_reference  = decltype(std::declval<MapIterator>()->first);
    using key_value_type = std::remove_cv_t<std::remove_reference_t<key_reference>>;
    using hasher_type = std::conditional_t<
                            std::is_void_v<ValueKeyHasher>,
                            std::hash<key_value_type>,
                            ValueKeyHasher>;

    hasher_type hasher = hasher_type();

    std::size_t operator()(const MapIterator& it) const {
        return hasher(it->first);
    }
};

template<class Vertex, class Distance>
struct astar_search_entry {
    Vertex   predecessor;
    Distance distance; // from start
    bool open;
};

template<
    class Node, class Edge,
    class Heuristic,
    class Visitor,
    class EdgePredicate,
    class Distance = decltype(std::declval<const Heuristic&>()(std::declval<const Node&>(), std::declval<const Node&>())),
    class Vertex   = typename compiled_adjacency_list<Node, Edge>::iterator
>
std::unordered_map<
    Vertex,
    astar_search_entry<Vertex, Distance>,
    map_iterator_hasher<Vertex>
>
astar_search(const compiled_adjacency_list<Node, Edge>& graph,
             const Vertex& start,
             const Vertex& goal,
             const Heuristic& heuristic, // invoked as heuristic(node, goal_node)
             Visitor visitor,  // invoked as visitor(predecessor_node, node), returns false to halt evaluation
             const EdgePredicate& edge_predicate) // invoked as edge_predicate(node, node, edge)
{
    using graph_type = compiled_adjacency_list<Node, Edge>;
    using map_entry = astar_search_entry<Vertex, Distance>;
    struct queue_entry {
        Distance priority;
        Vertex   vertex;
    };

    std::unordered_map<Vertex, map_entry, map_iterator_hasher<Vertex>> search_map;
    std::priority_queue frontier = [] {
        return std::priority_queue(
            [] (const queue_entry& a, const queue_entry& b) { return a.priority > b.priority; },
            std::vector<queue_entry>());
    }();

    auto push = [heuristic, &goal, &search_map, &frontier]
                (Vertex   predecessor,
                 Vertex   vertex,
                 Distance distance) {
        bool opened = false;

        if (auto it = search_map.find(vertex); it == search_map.end())
            search_map.emplace(vertex, map_entry{predecessor, distance, opened = true});
        else if (distance < it->second.distance)
            it->second = {predecessor, distance, opened = true};

        if (opened)
            frontier.push({distance + heuristic(vertex->first, goal->first), vertex});
    };

    push(start, start, static_cast<Distance>(0));
    while (!frontier.empty()) {
        const Vertex vertex = frontier.top().vertex;
        const auto& [node, edges] = *vertex;
        const map_entry& entry = search_map.at(vertex);
        frontier.pop();

        if (!entry.open)
            continue;

        const Distance distance = entry.distance;
        if (!visitor(entry.predecessor->first, node) || vertex == goal)
            break;

        for (const typename graph_type::edge_type& edge : edges)
            if (edge_predicate(edge.source->first, edge.target->first, edge))
                push(edge.source, edge.target, distance + edge->distance);
    }

    return search_map;
}

template<class Vertex, class Distance, class... MapArgs>
std::optional<std::vector<Vertex>>
get_path(const Vertex& start,
         const Vertex& goal,
         const std::unordered_map<Vertex, astar_search_entry<Vertex, Distance>,
                                  MapArgs...>& search_map)
{
    auto it = search_map.find(goal);
    auto end = search_map.find(start);

    if (it == search_map.end() || end == search_map.end())
        return std::nullopt;

    std::vector<Vertex> path;
    for (; it != end; it = search_map.find(it->second.predecessor))
        path.push_back(it->first);
    std::reverse(path.begin(), path.end());

    return std::make_optional(path);
}

} // namespace simulacrum


#ifndef _ANALYSIS_H_
#define _ANALYSIS_H_

#include <fstream>
#include <tuple>
#include <float.h>
#include "boost/algorithm/string.hpp"
#include "boost/filesystem.hpp"
#include "boost/asio.hpp"
#include "TH2.h"
#include "TGraph.h"
#include "TF1.h"
#include "TSystem.h"
#include "TVector.h"
#include "TLatex.h"
#include "TMathText.h"
#include "TLorentzVector.h"
#include "TVector2.h"
#include "TProfile.h"
#include "TClonesArray.h"
#include "sys/stat.h"
#include "TTF.h"
#include "TCanvas.h"
#include <boost/timer/timer.hpp>

#include "classes/DelphesClasses.h"
#include "ExRootAnalysis/ExRootTreeReader.h"
#include "ExRootAnalysis/ExRootTreeWriter.h"
#include "ExRootAnalysis/ExRootTreeBranch.h"
#include "ExRootAnalysis/ExRootResult.h"
#include "ExRootAnalysis/ExRootUtilities.h"

using namespace std;

class Analysis {

private:
    Analysis();
    Analysis(const Analysis& rhs);
    void operator = (const Analysis& rhs);

     // args: inputfilename, proc_id
    vector<tuple<string, int>>* m_input;
    typedef vector<tuple<string, int>>::const_iterator itr_s;

    // args: processfilename, proc_id, n_proc, cross_section, uncertainty, weight
    vector<tuple<string, int, int, double, double, double>>* m_processes;

    vector<Electron*>* m_electrons;
    vector<GenParticle*>* m_truthElectrons;
    vector<Muon*>* m_muons;
    vector<GenParticle*>* m_truthMuons;
    vector<Jet*>* m_jets;
    vector<GenParticle*>* m_truthBquarks;
    //
    // vector<bool>* m_electron_truth_tags;
    // vector<bool>* m_muon_truth_tags;

    GenParticle* m_hardTop;
    GenParticle* m_hardTbar;
    GenParticle* m_hardB;
    GenParticle* m_hardBbar;
    GenParticle* m_hardLepP;
    GenParticle* m_hardLepM;
    GenParticle* m_hardNu;
    GenParticle* m_hardNuBar;

    vector<bool>* m_lepton_truth_tags;
    vector<bool>* m_electron_truth_tags;
    vector<bool>* m_muon_truth_tags;
    vector<bool>* m_jet_truth_tags;

    string m_inputFileName;
    string m_processfilename;
    string m_model;
    string m_process;
    string m_options;
    int m_energy;
    int m_luminosity;
    string m_tag;
    string m_pdf = "CT14LL";
    bool m_useMassSlices = false;

    bool m_xSec;
    const string m_reconstruction;
    bool m_useLumi;
    const bool m_debug;
    string m_channel;

    double m_crossSection;
    Long64_t m_nevents;
    Long64_t m_nevents_max;
    double m_event_time;

    vector<double> iteration_weights;
    string m_dataDirectory;
    string m_outputFileName;
    TFile* m_outputFile;
    TChain* m_chain;
    ExRootTreeReader* m_tree;

    vector<int> m_cutflow;
    vector<string> m_cutNames;
    vector<string> m_cutTitles;
    enum m_cutlist{
        c_events,
        c_twoLeptons,
        c_oppositeCharge,
        c_sufficientMll,
        c_outsideZmassWindow,
        c_sufficientMET,
        c_sufficientJets,
        c_sufficientBtags,
        c_sufficientHT,
        c_validSolution,
        m_cuts // Keep as last entry
    };

    const double m_pi = 3.14159265358979323846;
    const double m_mass_b = 4.18, m_mass_W = 80.4, m_mass_Z = 91.19, m_mass_top = 172.5;

    const int m_minBtags;
    int m_bTags;

    vector<double>* m_dR_lb_truth;
    vector<double>* m_mass_ttbar_truth;

    // Histograms
    TH1D* h_cutflow;
    TProfile* h_eff_cuts_mass_ttbar_truth;
    TProfile* h_eff_cuts_pT_top_truth;
    TProfile* h_eff_cuts_pT_tbar_truth;
    TProfile* h_eff_cut_2l_mass_ttbar_truth;
    TProfile* h_eff_cut_oc_mass_ttbar_truth;
    TProfile* h_eff_cut_mll_mass_ttbar_truth;
    TProfile* h_eff_cut_mZ_mass_ttbar_truth;
    TProfile* h_eff_cut_ETmiss_mass_ttbar_truth;
    TProfile* h_eff_cut_HT_mass_ttbar_truth;
    TProfile* h_eff_cut_2j_mass_ttbar_truth;
    TProfile* h_eff_cut_2b_mass_ttbar_truth;
    TProfile* h_eff_reco_mass_ttbar_truth;
    TProfile* h_eff_reco_pT_top_truth;
    TProfile* h_eff_reco_pT_tbar_truth;

    TH1D* h_pT_l1;
    TH1D* h_eta_l1;
    TH1D* h_pT_l2;
    TH1D* h_eta_l2;

    TH1D* h_pT_jets;
    TH1D* h_eta_jets;
    TH1D* h_pT_bjets;
    TH1D* h_eta_bjets;
    TH1D* h_pT_qjets;
    TH1D* h_eta_qjets;

    TH1D* h_pT_alljets;
    TH1D* h_pT_allel;
    TH1D* h_pT_allmu;
    TH1D* h_eta_alljets;
    TH1D* h_eta_allel;
    TH1D* h_eta_allmu;

    TH1D* h_HT;
    TH1D* h_HT_truth;
    TH1D* h_HTmet_truth;
    TH1D* h_HTmet;
    TH1D* h_HTjMET;
    TH1D* h_KT_truth;
    TH1D* h_KT;
    TH1D* h_KTj;
    TH1D* h_mass_vis;
    TH1D* h_mass_bbll;

    TH1D* h_HT_all;
    TH1D* h_KT_all;
    TH1D* h_mass_vis_all;

    TH1D* h_deltaPhi_ll;
    TH1D* h_cosPhi;

    TH1D* h_mass_W1;
    TH1D* h_mass_W2;

    // top
    TH1D* h_pT_top;
    TH1D* h_eta_top;
    TH1D* h_y_top;
    TH1D* h_phi_top;
    TH1D* h_mass_top;
    TH1D* h_E_top;
    TH1D* h_pT_top_truth;
    TH1D* h_eta_top_truth;
    TH1D* h_y_top_truth;
    TH1D* h_phi_top_truth;
    TH1D* h_mass_top_truth;

    // tbar
    TH1D* h_pT_tbar;
    TH1D* h_eta_tbar;
    TH1D* h_y_tbar;
    TH1D* h_phi_tbar;
    TH1D* h_mass_tbar;
    TH1D* h_E_tbar;
    TH1D* h_pT_tbar_truth;
    TH1D* h_eta_tbar_truth;
    TH1D* h_y_tbar_truth;
    TH1D* h_phi_tbar_truth;
    TH1D* h_mass_tbar_truth;

    // ttbar
    TH1D* h_pT_ttbar;
    TH1D* h_pT_ttbar_truth;
    TH1D* h_eta_ttbar;
    TH1D* h_eta_ttbar_truth;
    TH1D* h_phi_ttbar;
    TH1D* h_phi_ttbar_truth;
    TH1D* h_mass_ttbar;
    TH1D* h_mass_ttbar_truth;
    TH1D* h_y_ttbar;
    TH1D* h_y_ttbar_truth;

    // delta
    TH1D* h_deltaPhi_tt_truth;
    TH1D* h_deltaPhi_tt;
    TH1D* h_deltaEta_tt_truth;
    TH1D* h_deltaEta_tt;

    // dR between truth and reco
    TH1D* h_dR_top;
    TH1D* h_dR_tbar;
    TH1D* h_dR_ttbar;

    // dR between each truth lepton and its closest reco lepton
    TH1D* h_dR_l;

    // dR between truth leptons and b-quarks and top quarks
    TH1D* h_dR_t1t2_truth;
    TH1D* h_dR_lb_truth;
    // TH1D* h_dR_l1b1_truth;
    // TH1D* h_dR_l2b2_truth;
    TH1D* h_dR_t1l1_truth;
    TH1D* h_dR_t2l2_truth;
    TH1D* h_dR_t1b1_truth;
    TH1D* h_dR_t2b2_truth;

    TH2D* h2_dR_l1b1_pTl_truth;
    TH2D* h2_dR_l2b2_pTl_truth;
    TH2D* h2_dR_lb_mtt_truth;
    // TH2D* h2_dR_l1b1_mtt_truth;
    // TH2D* h2_dR_l2b2_mtt_truth;
    TH2D* h2_dR_t1l1_mtt_truth;
    TH2D* h2_dR_t2l2_mtt_truth;
    TH2D* h2_dR_t1b1_mtt_truth;
    TH2D* h2_dR_t2b2_mtt_truth;

    TH1D* h_ETmiss;
    TH1D* h_ETmiss_truth;
    TH2D* h2_mtt_truth_ETmiss_truth;

    // performance plots
    TH1D* h_perf_mass_top;
    TH1D* h_perf_pT_top;
    TH1D* h_perf_eta_top;
    TH1D* h_perf_phi_top;
    TH1D* h_perf_mass_tbar;
    TH1D* h_perf_pT_tbar;
    TH1D* h_perf_eta_tbar;
    TH1D* h_perf_phi_tbar;
    TH1D* h_perf_mass_ttbar;
    TH1D* h_perf_pT_ttbar;
    TH1D* h_perf_eta_ttbar;
    TH1D* h_perf_phi_ttbar;
    TH2D* h2_perf_mass_ttbar;
    TH2D* h2_perf_mass_ttbar_pTtop;
    TH2D* h2_perf_mass_ttbar_pTtbar;

    // resolution
    TH1D* h_res_pT_bjets;
    TH2D* h2_resPtBjets_pT;

    TH1D* h_res_pT_tops;
    TH1D* h_res_y_tops;
    TH1D* h_res_phi_tops;
    TH1D* h_res_eta_tops;

    TH1D* h_res_pT_ttbar;
    TH1D* h_res_y_ttbar;
    TH1D* h_res_mass_ttbar;
    TH1D* h_res_eta_ttbar;

    TProfile* h_reco_quality;

    TH2D* h2_mass_top_TvR;
    TH2D* h2_pT_top_TvR;
    TH2D* h2_eta_top_TvR;
    TH2D* h2_phi_top_TvR;
    TH2D* h2_mass_tbar_TvR;
    TH2D* h2_pT_tbar_TvR;
    TH2D* h2_eta_tbar_TvR;
    TH2D* h2_phi_tbar_TvR;
    TH2D* h2_mass_ttbar_TvR;
    TH2D* h2_pT_ttbar_TvR;
    TH2D* h2_eta_ttbar_TvR;
    TH2D* h2_phi_ttbar_TvR;

    // charge asymmetries
    TH1D* h_mtt_tF;
    TH1D* h_mtt_tB;
    TH1D* h_mtt_lF;
    TH1D* h_mtt_lB;
    TH1D* h_HT_lF;
    TH1D* h_HT_lB;
    TH1D* h_KT_lF;
    TH1D* h_KT_lB;
    TH1D* h_mtt_tCF;
    TH1D* h_mtt_tCB;
    TH1D* h_mtt_lCF;
    TH1D* h_mtt_lCB;
    TH1D* h_HT_lCF;
    TH1D* h_HT_lCB;
    TH1D* h_KT_lCF;
    TH1D* h_KT_lCB;

    // TH1D* h_mtt_tlF;
    // TH1D* h_mtt_tlB;

    // top polarisation
    TH1D* h_mtt_c1F;
    TH1D* h_mtt_c1B;
    TH1D* h_mtt_c2F;
    TH1D* h_mtt_c2B;
    // TH1D* h_mtt_philF;
    // TH1D* h_mtt_philB;
    // TH1D* h_mtt_ElF;
    // TH1D* h_mtt_ElB;

    // spin correlation
    TH1D* h_mtt_c1c2F;
    TH1D* h_mtt_c1c2B;
    TH1D* h_mtt_cPhiF;
    TH1D* h_mtt_cPhiB;
    TH1D* h_mtt_DphiF;
    TH1D* h_mtt_DphiB;
    TH1D* h_HT_DphiF;
    TH1D* h_HT_DphiB;
    TH1D* h_KT_DphiF;
    TH1D* h_KT_DphiB;

    TH1D* h_cosTheta1_truth;
    TH1D* h_cosTheta2_truth;
    TH1D* h_cosTheta;
    TH1D* h_cosTheta1;
    TH1D* h_cosTheta2;
    TH1D* h_cos1cos2;
    TH1D* h_cosThetaStar;
    TH1D* h_cosTheta_ttbar;
    TH1D* h_cosTheta_l;
    TH1D* h_cosThetaStar_l;
    TH1D* h_deltaY_top;
    TH1D* h_deltaY_top_truth;
    TH1D* h_deltaEta_l;

    TH1D* h_n_electrons;
    TH1D* h_n_muons;
    TH1D* h_n_jets;
    TH1D* h_n_bJets;

    TProfile* h_lepton_purity;
    TProfile* h_jet_purity;

    TH1D* h_n_truthElectrons;
    TH1D* h_n_truthMuons;
    TH1D* h_n_truthBquarks;

    TH1D* h_n_selElectrons;
    TH1D* h_n_selMuons;
    TH1D* h_n_selJets;

    TH1D* h_n_jets_no_truth_tag;
    TH1D* h_n_uniqueElectrons;
    TH1D* h_n_uniqueMuons;
    TH1D* h_n_uniqueJets;

    TH1D* h_n_electrons_truth_tagged;
    TH1D* h_n_selElectrons_truth_tagged;
    TH1D* h_n_uniqueElectrons_truth_tagged;

    TH1D* h_n_muons_truth_tagged;
    TH1D* h_n_selMuons_truth_tagged;
    TH1D* h_n_uniqueMuons_truth_tagged;

    TH1D* h_n_jets_truth_tagged;
    TH1D* h_n_selJets_truth_tagged;
    TH1D* h_n_uniqueJets_truth_tagged;
    TH1D* h_ntracks_truth_tagged_jets;

    TH1D* h_n_passElectrons;
    TH1D* h_n_passMuons;
    TH1D* h_n_passJets;
    TH1D* h_n_passBjets;

    TH2D* h2_mtt_cosThetaStar;
    TH2D* h2_mtt_delta_yt;
    TH2D* h2_mtt_cosThetaStar_ll;
    TH2D* h2_mtt_delta_abs_etal;
    TH2D* h2_HT_delta_abs_etal;
    TH2D* h2_KT_delta_abs_yt;
    TH2D* h2_HT_cosThetaStar_ll;
    TH2D* h2_KT_cosThetaStar_ll;
    TH2D* h2_mtt_deltaPhi;
    TH2D* h2_mtt_cosTheta1;
    TH2D* h2_mtt_cosTheta2;
    TH2D* h2_mtt_cos1cos2;
    TH2D* h2_mvis_deltaPhi;
    TH2D* h2_HT_deltaPhi;
    TH2D* h2_HTmet_deltaPhi;
    TH2D* h2_KT_deltaPhi;

protected:
    void SetupTreesForNewFile(const string&);
    void CleanUp();
    void SetupInputFiles();
    void SetupInputFile();
    bool SetupOutputFile();

    void PreLoop();
    void PreLoopSingle();
    void Loop();
    void PostLoop();

    void EachEvent(double);
    void EveryEvent(double);
    void CleanupEvent();
    void GetHardParticles();
    void GetTruthParticles();
    // void GetElectrons();
    // void GetMuons();
    void SelectElectrons();
    void SelectMuons();
    void SelectJets();
    void IsolateElectrons();
    void IsolateMuons();
    void OverlapRemoval();
    void RemoveJetsCloseToElectrons();
    void RemoveJetsCloseToMuons();
    void RemoveElectronsInsideJets();
    void RemoveMuonsInsideJets();
    void TruthTagLeptons();
    void TruthTagJets();
    void FillPurities(int);
    void AssignChannel();
    void FillTaggedHistograms();
    pair<TLorentzVector, TLorentzVector> GetLeptonMomenta();

    // histograms
    void MakeHistograms();
    void MakeDistributions();
    void WriteEfficiency(TH1D*, const string&, const string&);
    void MakeDistribution1D(TH1D*, const string&, bool normalise = false);
    void MakeDistribution2D(TH2D*, string, string, string, string);
    void MakeDistributionAL(TH2D*, const string&, const string&);
    void NormalizeSliceY(TH2D*);
    void AverageEachXbin(TH2D*);

    void GetGenerationCrossSection(int);
    void GetProcessWeight(int);
    void SetDataDirectory();
    void AsymmetryUncertainty(TH1D*, TH1D*, TH1D*);
    void GetBranches();
    void EachFile(const string&);

    // cutflow
    void InitialiseCutflow();
    void PrintCutflow();
    void UpdateCutflow(int, bool);
    void FillCutsEfficiencies(const vector<double>&, const int);
    void FillRecoEfficiencies(const vector<double>&, const int);

    // event selection
    bool PassesEventSelection();
    bool ExactlyTwoLeptons();
    bool OppositeCharge();
    bool SufficientJets();
    bool SufficientBtags();
    bool SufficientHT(double);
    bool SufficientMET(double);
    bool SufficientMll(const pair<TLorentzVector, TLorentzVector>&);
    bool OutsideZmassWindow(const pair<TLorentzVector, TLorentzVector>&);

    Long64_t TotalEvents();
    Long64_t IncrementEvent(Long64_t i);
    double TotalAsymmetry(TH1D* h_A, TH1D* h_B);
    void Asymmetry(const string&, const string&, const string&, TH1D*, TH1D*);

    // tuple
    TClonesArray* b_Particle;
    TClonesArray* b_Jet;
    TClonesArray* b_Electron;
    TClonesArray* b_Muon;
    TClonesArray* b_MissingET;
    TClonesArray* b_EFlowTrack;
    TClonesArray* b_EFlowPhoton;
    TClonesArray* b_EFlowNeutralHadron;
    // TClonesArray* b_ScalarHT;
    TClonesArray* b_Track;

public:
    Analysis(const string& model, const string& process, const string& options, const int energy, const int luminosity, const int minBtags, const string& reconstruction, const string& tag, const bool slice):
        m_inputFileName(""),
        m_processfilename(""),
        m_model(model),
        m_process(process),
        m_options(options),
        m_energy(energy),
        m_luminosity(luminosity),
        m_minBtags(minBtags),
        m_reconstruction(reconstruction),
        m_tag(tag),
        m_useMassSlices(slice),
        m_xSec(false),
        m_debug(false),
        m_outputFile(nullptr),
        m_input(nullptr),
        m_processes(nullptr),
        m_chain(nullptr),
        m_tree(nullptr)
    {
        this->PreLoop();
    }

    Analysis(const string& inputfilename, const string& processfilename, const int luminosity, const int minBtags, const string& reconstruction, const Long64_t nevents_max = 0, const string& tag = "", const bool slice = false):
        m_inputFileName(inputfilename),
        m_processfilename(processfilename),
        m_model(""),
        m_process(""),
        m_options(""),
        m_energy(0),
        m_luminosity(luminosity),
        m_minBtags(minBtags),
        m_reconstruction(reconstruction),
        m_nevents_max(nevents_max),
        m_tag(tag),
        m_useMassSlices(slice),
        m_xSec(false),
        m_debug(false),
        m_outputFile(nullptr),
        m_input(nullptr),
        m_processes(nullptr),
        m_chain(nullptr),
        m_tree(nullptr)
    {
        this->PreLoopSingle();
    }
    virtual ~Analysis();
    void Run();
};
#endif