#include "../gain_utils.h"
#include "coela_utility/src/string_utils.h"

#include "coela_utility/src/misc_math.h"

#include "coela_utility/src/microstats.h"

#include <boost/math/distributions/poisson.hpp>
#include <Minuit2/MnMigrad.h>
#include <Minuit2/FunctionMinimum.h>
#include <Minuit2/MnPrint.h>
#include <iostream>
#include <stdexcept>
#include <Minuit2/MnMinimize.h>
#include <Minuit2/MnSimplex.h>
#include <Minuit2/MnScan.h>
#include <boost/math/distributions/poisson.hpp>
#include <boost/math/distributions/hypergeometric.hpp>

using namespace std;
namespace coela {
namespace gain_utils {
//=====================================================================================================================

void append_histogram_data_from_float_bitmap_region(const CcdImage<float>& bmp,
        const PixelRange& rgn, HistogramContainer14bit& hist)
{
    assert(hist.minimum_has_been_set());
    assert(rgn.is_valid());
    for (int y=rgn.low.y; y<=rgn.high.y; ++y) {
        for (int x=rgn.low.x; x<=rgn.high.x; ++x) {
            hist.count_rounded_value(bmp.pix(x,y));
        }
    }//end pixel scan
    return;
}

void append_histogram_data_from_float_bitmap_region(const CcdImage<float>& bmp,
        const PixelRange& rgn, HistogramContainer10bit& hist)
{
    assert(hist.minimum_has_been_set());
    assert(rgn.is_valid());
    for (int y=rgn.low.y; y<=rgn.high.y; ++y) {
        for (int x=rgn.low.x; x<=rgn.high.x; ++x) {
            hist.count_rounded_value(bmp.pix(x,y));
        }
    }//end pixel scan
    return;
}

map<int,long> load_histogram_data_from_file(const string& filename)
{
    ifstream datafile(filename.c_str());
    if (!datafile) { throw runtime_error("Could not load histogram data from \""+filename+"\""); }
    int value; long count;
    map <int,long> histogram_data;
    while ((datafile>>value)) {
        datafile>>count;
        histogram_data[value] = count;
    }
    return histogram_data;
}

double histogram_differences_squared(const map<int,long>& data,
                                     const map<int,double>& model)
{
    assert(data.size()==model.size());
    double diff_sq_sum=0.0;
    double diff;
    for (map<int,long>::const_iterator it=data.begin(); it!=data.end(); ++it) {
        map<int,double>::const_iterator model_it = model.find(it->first);
        if (model_it==model.end()) { throw runtime_error("Gain utils: model/data mismatch during fitting"); }
        diff = it->second - model_it->second;
        diff_sq_sum += diff*diff;
    }
    return diff_sq_sum;
}

double poisson_noise_weighted_histogram_difference(const map<int,long>& data,
        const map<int,double>& model)
{
    assert(data.size()>=model.size());
    double diff_sq_sum=0.0;
    double diff;

//    pair<int,long> data_min_pair = get_min_value_pair(data);
//    pair<int,double> model_min_pair = get_min_value_pair(model);
//    cerr<<"Data / model min "<< data_min_pair.first<<" / "<< model_min_pair.first<<endl;

    for (map<int,double>::const_iterator model_it=model.begin(); model_it!=model.end();
            ++model_it) {
//        map<int,double>::const_iterator model_it = model.find(it->first);
//        if (model_it==model.end()) throw runtime_error("Gain utils: model/data mismatch during fitting");
        map<int,long>::const_iterator data_it = data.find(model_it->first);
        if (data_it==data.end()) throw
            runtime_error("Gain utils: model/data mismatch during fitting;"
                          "could not find datapoint for value " + string_utils::itoa(model_it->first));
        diff = (data_it->second - model_it->second);
        diff_sq_sum += diff*diff / max(data_it->second,
                                       1l);  //Poisson distribution for each histogram entry, so variance == probability
//        diff_sq_sum += pow(fabs(diff),1.5) / sqrt(model_it->second); //This method less sensitive to noise spikes at the end of the histogram section
    }
    return diff_sq_sum;
}

double log_space_histogram_difference(const map<int,long>& data,
                                      const map<int,double>& model)
{
    assert(data.size()>=model.size());
    double diff_sq_sum=0.0;
    double diff;
    for (map<int,double>::const_iterator model_it=model.begin(); model_it!=model.end();
            ++model_it) {
        map<int,long>::const_iterator data_it = data.find(model_it->first);
        if (data_it==data.end()) throw
            runtime_error("Gain utils: model/data mismatch during fitting;"
                          "could not find datapoint for value " + string_utils::itoa(model_it->first));
        diff = (log(data_it->second) - log(model_it->second));
        diff_sq_sum += diff*diff ;
    }
    return diff_sq_sum;
}

map<int, double> convolve_histogram_with_gaussian(const map<int, double>& input_hist,
        const double readout_sigma,
        const int kernel_half_width)
{
    map<int, double> readout_kernel;

    const double unity_area_gaussian_height = 1.0 / (sqrt(2.0*M_PI)*readout_sigma);

    const int kernel_min(-kernel_half_width), kernel_max(kernel_half_width);
    for (int value = kernel_min ; value <= kernel_max; ++value) {
        readout_kernel[value] = misc_math::gaussian_1d_function(value,
                                unity_area_gaussian_height,
                                readout_sigma);
    }

    const int input_min= get_min_value_pair(input_hist).first ;
    const int input_max= get_max_value_pair(input_hist).first ;

//    cerr<<"Kernel min / max" << kernel_min <<" / "<< kernel_max<<endl;
//    cerr<<"Input min / max" << input_min <<" / "<< input_max<<endl;

    map<int, double> convolved_hist;
    for (map<int,double>::const_iterator input_it=input_hist.begin();
            input_it!=input_hist.end(); ++input_it) {
        const int input_value= input_it->first;
        if (input_value + kernel_min >= input_min &&
                input_value + kernel_max <= input_max) {
            if (convolved_hist.find(input_value) == input_hist.end()) {
                convolved_hist[input_value ] = 0.0;
            }
            for (map<int,double>::const_iterator kernel_it=readout_kernel.begin();
                    kernel_it!=readout_kernel.end(); ++kernel_it) {

                int kernel_offset_input = kernel_it->first + input_value;
                convolved_hist[ input_value ] +=
                    input_hist.find(kernel_offset_input)->second * kernel_it->second;

            }
        }
//        else{
//            cerr<<"rejecting input val " << input_value<<endl;
//        }

    }
    return convolved_hist;
}

//==================================================================================

double GaussianHistogramFitFCN::operator()(const std::vector<double>& gauss_pars) const
{
    assert(gauss_pars.size()==3);
    map<int,double> model=get_model_histogram(gauss_pars);
//            return histogram_differences_squared(data, model);
//            cerr<<"Pars: "<< gauss_pars[0]<<" " <<gauss_pars[1]<<" "<<gauss_pars[2]<<endl;
    return poisson_noise_weighted_histogram_difference(data, model);

}

map<int,double> GaussianHistogramFitFCN::get_model_histogram(
    const std::vector<double>& gauss_pars) const
{
    map<int,double>model;
    for (map<int,long>::const_iterator it=data.begin(); it!=data.end(); ++it) {
        model[it->first] = misc_math::gaussian_1d_function((double)it->first - gauss_pars[0],
                           gauss_pars[1],
                           gauss_pars[2]);  //FIXME -- should perhaps be measuring distance from centre of bin? (so +0.5 to it->first). but not sure.
    }
    return model;
}

//==================================================================================
map<int,double> ExponentialHistogramFitFCN::get_model_histogram(
    const std::vector<double>& exp_pars) const
{
    assert(exp_pars.size()==2);
    map<int,double>model;
    const double& amplitude = exp_pars[0];
    const double& gain = exp_pars[1];
    for (map<int,long>::const_iterator it=data.begin(); it!=data.end(); ++it) {
        model[it->first] = amplitude*exp(-1.0*it->first/gain);
    }
    return model;
}

double  ExponentialHistogramFitFCN::operator()(const std::vector<double>& exp_pars)
const
{
    assert(exp_pars.size()==2);
    map<int,double> model=get_model_histogram(exp_pars);
//    return log_space_histogram_difference(data, model);
    return poisson_noise_weighted_histogram_difference(data, model) / (model.size() - 1);

}
//==================================================================================
//map<int,double> full_EMCCD_low_light_level_histogram_model::get_model_histogram(
//        const GainData& inf){
//
//}
//

//==================================================================================
FullEmccdHistogramFitFCN::FullEmccdHistogramFitFCN(const map<int,long>& histogram)
    :
    error_def(1.0), data_(histogram)
{
    fit_rgn_min_ = get_min_value_pair(data_).first;
    fit_rgn_max_ = get_max_value_pair(data_).first;
}

FullEmccdHistogramFitFCN::FullEmccdHistogramFitFCN(const map<int,long>& histogram,
        const int fit_region_min, const int fit_region_max):
    error_def(1.0), data_(histogram),
    fit_rgn_min_(fit_region_min), fit_rgn_max_(fit_region_max)
{}

double FullEmccdHistogramFitFCN::operator()(const std::vector<double>& pars) const
{
    assert(pars.size()==npars_);

//    cout<<"Params: ";
//    for (size_t i=0; i!=pars.size();++i){
//        cout<<pars[i]<<" ";
//    }
//
    map<int,double> model = get_model_histogram(pars);
    double val = poisson_noise_weighted_histogram_difference(data_, model);
//
//    cout<<" : "<<val;
//    cout<<endl;
//
    double normalised = val / (model.size() - (npars_ - 1));

    return normalised;
}
vector<double> FullEmccdHistogramFitFCN::generate_pars_vec(const GainData& inf)
{
    vector<double> pars;
    pars.push_back(inf.bias_pedestal);
    pars.push_back(inf.N_dark_pix);
    pars.push_back(inf.readout_sigma);
    pars.push_back(inf.N_light_pix);
    pars.push_back(inf.gain);
    pars.push_back(inf.N_serial_CIC_pix);
//    pars.push_back(inf.serial_CIC_gain);
    return pars;
}

map<int,double> FullEmccdHistogramFitFCN::get_CICIR_model_histogram(
    const double gain,
    const double N_CICIR_pix,
    const int N_EM_stages,
    const int max_val_to_generate)
{

    int min_N_stages=1;
    double prob_per_stage = pow(gain, 1.0/N_EM_stages);

    map<int,double> gain_for_N_stages;
    for (int stages_passed = 1; stages_passed<=N_EM_stages; ++stages_passed) {
        gain_for_N_stages[stages_passed] = pow(prob_per_stage, stages_passed);
    }

//    cerr<<"gain at 604:"<< gain_for_N_stages[604]<<endl;
//    const double frequency_normalisation =  N_CICIR_pix ;
    const double frequency_normalisation = N_CICIR_pix / (N_EM_stages - min_N_stages);

    map<int,double> CICIR_histogram;
    for (int register_output_val=1; register_output_val<=max_val_to_generate;
            ++register_output_val) {
        double output_frequency=0.0;
        for (int CICIR_stages_passed = min_N_stages; CICIR_stages_passed<=N_EM_stages;
                ++CICIR_stages_passed) {
            const double & g = gain_for_N_stages[CICIR_stages_passed];

            output_frequency +=  exp((0.5-register_output_val)/g) / g;
        }
        CICIR_histogram[register_output_val]= frequency_normalisation * output_frequency;
    }
    return CICIR_histogram;
}

map<int,double> FullEmccdHistogramFitFCN::get_model_histogram(
    const std::vector<double>& pars) const
{
    assert(pars.size()==npars_);

    const double& bias_pedestal = pars[0];
    const double& dark_pix = pars[1];
    const double& RO_sigma = pars[2];
    const double& N_photon_events = pars[3];
    const double& phot_gain = pars[4];
    const double& N_serial_CIC_pix = pars[5];
//    const double& serial_CIC_gain = pars[6];
//    const double serial_CIC_gain = sqrt(phot_gain);

    int convolution_half_width=60;

//   int data_start = get_min_value_pair(data).first;
    int data_end = get_max_value_pair(data_).first;
    int tail_range = data_end - bias_pedestal;

    double total_pix_calc = N_photon_events + N_serial_CIC_pix + dark_pix;
    double photon_freq_est = N_photon_events / total_pix_calc;
//   cerr<<"Photon freq est: " << photon_freq_est<<", calc total: " << total_pix_calc<<endl;

    //See Basden et al, 2003 for equations

//   double serial_CIC_amplitude =
//    N_serial_CIC_pix * exp(0.5/serial_CIC_gain)/ serial_CIC_gain;

    double p1_amplitude, p2_amplitude;
    if (photon_freq_est>0) {
        boost::math::poisson_distribution<> pois_dist(photon_freq_est);
        double p1_prob = boost::math::pdf(pois_dist, 1);
        double p2_prob = boost::math::pdf(pois_dist, 2);

        //Note exp( 0.5 / phot_gain) term corrects ~1% normalisation error;
        // since integral from 1 to +inf of the PDF otherwise  =  exp(-1/g)
        p1_amplitude = (p1_prob / (p1_prob +p2_prob)) *
                       N_photon_events * exp(0.5/phot_gain) /  phot_gain;

        //Note normalisation correction not reqd here, more complex to calc. but also much smaller effect.
        p2_amplitude = (p2_prob / (p1_prob +p2_prob)) *
                       N_photon_events / (phot_gain*phot_gain);
    } else {
        p1_amplitude=0.0;
        p2_amplitude=0.0;
    }

    map<int,double> register_output_model;
    int fit_rgn_extension_below_bias_pedestal = bias_pedestal - fit_rgn_min_;
    if (fit_rgn_extension_below_bias_pedestal > 0) {
        for (int i= -(convolution_half_width + fit_rgn_extension_below_bias_pedestal); i<0; ++i) {
            register_output_model[i]=0;
        }
    } else {
        for (int i= -convolution_half_width; i<0; ++i) {
            register_output_model[i]=0;
        }
    }

    register_output_model[0]  = dark_pix;

    map<int,double> CICIR_register_component =
        FullEmccdHistogramFitFCN::get_CICIR_model_histogram(
            phot_gain, N_serial_CIC_pix,
            FullEmccdHistogramFitFCN::N_EM_serial_register_stages,
            tail_range + convolution_half_width
        );

    for (int register_output_val=1; register_output_val<=tail_range + convolution_half_width;
            ++register_output_val) {
        double photon_exponential =  exp(-1.0* register_output_val /phot_gain);
        //Contribution from 1 and 2 photo-electron events
        register_output_model[register_output_val] =
            p1_amplitude*photon_exponential;
        register_output_model[register_output_val] +=
            p2_amplitude*(register_output_val - 1)*photon_exponential;

        //Contribution from CICIR events
//        register_output_model[register_output_val] +=
//                serial_CIC_amplitude*exp(  -1.0* register_output_val /serial_CIC_gain );
        register_output_model[register_output_val] +=
            CICIR_register_component[register_output_val];
    }

    map<int,double> RO_model =
        convolve_histogram_with_gaussian(register_output_model, RO_sigma,
                                         convolution_half_width);

    map<int,double> biased_RO_model;
    for (map<int,double>::const_iterator it=RO_model.begin(); it!=RO_model.end(); ++it) {
        int output_value = it->first + bias_pedestal;
        if (output_value>=fit_rgn_min_ && output_value <= fit_rgn_max_) {
            biased_RO_model[output_value] = it->second;
        }
    }

    return biased_RO_model;
}

//==================================================================================

//double calculate_thresholded_SNR(const double readout_noise_in_ADU,
//                                const double mean_photon_gain_in_ADU,
//                                const double CIC_event_rate_per_pixel_readout,
//                                const double light_level,
//                                const double threshold_in_photo_electrons,
//                                const bool output_to_screen){
//    map<int,long> histogram_min_max;
//    histogram_min_max[-readout_noise_in_ADU*5]=0;
//    histogram_min_max[10000]=0;
//
//    FullEmccdHistogramFitFCN full_model(histogram_min_max);
//    GainData full_pars;
//    full_pars.bias_pedestal = 0.0;
//    full_pars.readout_sigma = readout_noise_in_ADU;
//    full_pars.gain = mean_photon_gain_in_ADU;
//    full_pars.N_light_pix = 0.0;
//    full_pars.N_serial_CIC_pix = 0.0;
//    full_pars.N_dark_pix = 0.0;
////                - mean_photon_gain_in_ADU*CIC_event_rate_per_pixel_readout);
//
//    GainData photon_hist_pars(full_pars);
//    photon_hist_pars.N_light_pix=1.0;
//    map<int,double> light_model = full_model.get_model_histogram(
//        full_model.generate_pars_vec(photon_hist_pars));
//
//    double all_counts = gain_utils::sum_counts_in_histogram(light_model);
//    double thresh_counts = gain_utils::sum_counts_in_histogram_above_threshold(
//            light_model, threshold_in_photo_electrons*mean_photon_gain_in_ADU);
//
//    double pass_rate_for_photon_events = thresh_counts / all_counts;
//
//    if (output_to_screen){
////        gain_utils::write_histogram_data_to_file(light_model, "photons_only.txt");
//        cerr<<"photons hist sum:" << all_counts << endl;
//        cerr<<"Filtered photon fraction: "<< pass_rate_for_photon_events<<endl;
//    }
//
////    GainData CICIR_hist_pars(full_pars);
////    CICIR_hist_pars.N_serial_CIC_pix=1.0;
////    map<int,double> CICIR_model = full_model.get_model_histogram(
////        full_model.generate_pars_vec(CICIR_hist_pars));
////
////    all_counts = gain_utils::sum_counts_in_histogram(CICIR_model);
////    thresh_counts = gain_utils::sum_counts_in_histogram_above_threshold(
////            CICIR_model, threshold_in_photo_electrons*mean_photon_gain_in_ADU);
////
////    double CICIR_fraction = thresh_counts / all_counts;
////
////    if (output_to_screen){
////        gain_utils::write_histogram_data_to_file(CICIR_model, "CICIR_only.txt");
////        cerr<<"CICIR hist sum:" << all_counts << endl;
////        cerr<<"Filtered CICIR fraction: "<< CICIR_fraction<<endl;
////    }
////
////    GainData RO_hist_pars(full_pars);
////    RO_hist_pars.N_dark_pix=1.0;
////    map<int,double> RO_model = full_model.get_model_histogram(
////        full_model.generate_pars_vec(RO_hist_pars));
////
////    all_counts = gain_utils::sum_counts_in_histogram(RO_model);
////    thresh_counts = gain_utils::sum_counts_in_histogram_above_threshold(
////            RO_model, threshold_in_photo_electrons*mean_photon_gain_in_ADU);
////
////    double RO_fraction = thresh_counts / all_counts;
////
////    if (output_to_screen){
////        gain_utils::write_histogram_data_to_file(RO_model, "RO_only.txt");
////        cerr<<"RO hist sum:" << all_counts << endl;
////        cerr<<"Filtered RO fraction: "<< RO_fraction<<endl;
////    }
//
//    full_pars.N_light_pix = light_level;
//    full_pars.N_serial_CIC_pix = CIC_event_rate_per_pixel_readout;
//    full_pars.N_dark_pix = 1.0 - (light_level + CIC_event_rate_per_pixel_readout);
//
//    map<int,double> full_model_hist = full_model.get_model_histogram(
//        full_model.generate_pars_vec(full_pars));
//
//    all_counts = gain_utils::sum_counts_in_histogram(full_model_hist);
//    thresh_counts = gain_utils::sum_counts_in_histogram_above_threshold(
//            full_model_hist, threshold_in_photo_electrons*mean_photon_gain_in_ADU);
//
//    double pass_rate_for_all_events = thresh_counts / all_counts;
//    if (output_to_screen){
//        cerr<<"Noise hist sum:" << all_counts << endl;
//        cerr<<"Filtered noise fraction: "<< pass_rate_for_all_events <<endl;
//    }
//
//    double SNR = pass_rate_for_photon_events*light_level /
//        sqrt( pass_rate_for_all_events );
//
//    if (output_to_screen){
//        cerr<<"SNR = " << SNR<<endl;
//    }
//    return SNR;
//}

//==================================================================================

ThresholdedSnrCalculator::ThresholdedSnrCalculator(
    const double RO,
    const double gain,
    const double light_level,
    const double CICIR_rate
):gain_(gain), light_level_(light_level)
{
    if ((light_level) > 0.5) {
        throw runtime_error("ThresholdedSnrCalculator() ---"
                            "Light level too high");
    }

    map<int,long> histogram_min_max;
    histogram_min_max[-RO*5]=0;
    histogram_min_max[10000]=0;

    FullEmccdHistogramFitFCN full_model(histogram_min_max);
    GainData full_pars;
    full_pars.bias_pedestal = 0.0;
    full_pars.readout_sigma = RO;
    full_pars.gain = gain;
    full_pars.N_light_pix = 0.0;
    full_pars.N_serial_CIC_pix = 0.0;
    full_pars.N_dark_pix = 0.0;
//                - mean_photon_gain_in_ADU*CIC_event_rate_per_pixel_readout);

    GainData photon_hist_pars(full_pars);
    photon_hist_pars.N_light_pix=1.0;

    photon_distribution_ = full_model.get_model_histogram(
                               full_model.generate_pars_vec(photon_hist_pars));

    full_pars.N_light_pix = light_level;
    full_pars.N_serial_CIC_pix = CICIR_rate;
    full_pars.N_dark_pix = 1.0 - (light_level + CICIR_rate);

    full_distribution_ = full_model.get_model_histogram(
                             full_model.generate_pars_vec(full_pars));

//    gain_utils::write_histogram_data_to_file(photon_distribution_, "photons.txt");
//    gain_utils::write_histogram_data_to_file(full_distribution_, "full_dist.txt");

}

double ThresholdedSnrCalculator::calc_SNR_at_threshold(
    const double threshold_in_photo_electrons) const
{

    double all_counts = gain_utils::sum_counts_in_histogram(photon_distribution_);
    double thresh_counts = gain_utils::sum_counts_in_histogram_above_threshold(
                               photon_distribution_, threshold_in_photo_electrons*gain_);
    double fraction_photon_events_passed = thresh_counts / all_counts;

    all_counts = gain_utils::sum_counts_in_histogram(full_distribution_);
    thresh_counts = gain_utils::sum_counts_in_histogram_above_threshold(
                        full_distribution_, threshold_in_photo_electrons*gain_);

    double fraction_of_all_events_passed = thresh_counts / all_counts;
//    cerr<<"Gain"<< gain_<<endl;
//    cerr<<"photon sum "<<all_counts<<endl;
//    cerr<<"Counted over thresh:"<<thresh_counts<<endl;
//    cerr<<"photon frac"<<pass_rate_for_photon_events<<endl;
//    cerr<<"Total sum"<<all_counts<<endl;
//    cerr<<"Total frac"<<pass_rate_for_all_events<<endl;

    double SNR = fraction_photon_events_passed*light_level_ /
                 sqrt(exp(fraction_of_all_events_passed) - 1.0);
    return SNR;
}

double ThresholdedSnrCalculator::find_optimum_threshold(
    const double search_min,
    const double search_max,
    const double stepsize
) const
{
    double max_SNR = calc_SNR_at_threshold(search_min);
    double best_thresh = search_min;
    for (double t = search_min; t<=search_max; t+=stepsize) {
        double s = calc_SNR_at_threshold(t);
        if (s > max_SNR) {
            best_thresh = t;
            max_SNR = s;
        }
    }
    return best_thresh;
}

double calculate_ideal_photon_counter_SNR(const double light_level)
{
    return light_level /
           sqrt(exp(light_level) - 1.0);
}

double calculate_linear_mode_SNR(const double readout_noise_in_ADU,
                                 const double mean_photon_gain_in_ADU,
                                 const double light_level,
                                 const double CICIR_event_rate_per_pixel_readout
                                )
{

    ///Calculate CICIR_signal relative to a photo-electron signal:
    double CICIR_signal =  CICIR_event_rate_per_pixel_readout /
                           log(mean_photon_gain_in_ADU);
    double normed_readout = readout_noise_in_ADU / mean_photon_gain_in_ADU;
    return light_level /
           sqrt(2.0*(light_level + CICIR_signal)  + normed_readout*normed_readout);
}

//==================================================================================
pair<int,long> get_mode(const map<int,long>& histogram_data)
{
    int peak_value(histogram_data.begin()->first);
    long peak_count(histogram_data.begin()->second);
    for (map<int,long>::const_iterator it=histogram_data.begin(); it!=histogram_data.end();
            ++it) {
        if (it->second > peak_count) {
            peak_value  =it->first; peak_count = it->second;
        }
    }
    pair<int,long> peak_pair(peak_value, peak_count);
    return peak_pair;
}

float get_gaussian_HWHM(const map<int,long>& histogram_data)
{
    pair<int,long> peak = get_mode(histogram_data);
    float best_distance_to_HM=peak.second/2.0;
    int best_value = peak.first;
    for (map<int,long>::const_iterator it=histogram_data.begin(); it!=histogram_data.end();
            ++it) {
        if (it->first < peak.first) {
            float current_dist_to_HM = fabs(it->second - peak.second/2.0);
            if (current_dist_to_HM < best_distance_to_HM) {
                best_distance_to_HM=current_dist_to_HM;
                best_value =it->first;
            }
        }
    }
//            cout <<"Half peak value: " <<best_value<<endl;
//            cout <<"HWHM "<<fabs(best_value -peak.first)<<endl;
    return fabs(best_value -peak.first);
}

map<int, long> get_gaussian_fitting_section(const map<int,long>& histogram_data)
{
    map<int,long> gaussian_section;
    pair<int,long> peak = get_mode(histogram_data);
    long count_threshold = max((long)(peak.second*0.005), 300l);
    for (map<int,long>::const_iterator it=histogram_data.begin(); it!=histogram_data.end();
            ++it) {
        if (it->first < (peak.first+10) && it->second > count_threshold) { gaussian_section[it->first]=it->second; }
    }
    return gaussian_section;
}

map<int, long> get_tail_fitting_section(const map<int,long>& histogram_data,
                                        const double g_sigma_est, const bool output_to_screen)
{
    pair<int,long>peak =get_mode(histogram_data);
    long count_threshold=max((long)25,
                             (long)floor(peak.second/6e6)); //FIXME - this may need adjusting
    if (output_to_screen) { cout <<"\nTail count cutoff: " << count_threshold<<endl; }
    int tail_section_end_value = peak.first;
    for (map<int,long>::const_iterator it=histogram_data.begin(); it!=histogram_data.end();
            ++it) {
        if (it->first > peak.first && it->second >count_threshold) {
            tail_section_end_value= max(tail_section_end_value, it->first);
        }
    }
    tail_section_end_value-=10; //jump back, otherwise may get extended by a noise spike extending the range to... a noise spike!
    if (output_to_screen) { cout <<"Resulting in cutoff value: "<<tail_section_end_value<<endl; }
    map<int,long> tail_section;
    int curve_range = tail_section_end_value - peak.first;
    int peak_offset = max(curve_range*0.25, g_sigma_est*8);
    int tail_start = (peak.first + peak_offset);
    if (output_to_screen) { cout <<"Tail_start at: "<<tail_start<<endl; }
    if (tail_start >= tail_section_end_value -5) { throw runtime_error("Not enough data to fit tail of histogram"); }
    for (map<int,long>::const_iterator it=histogram_data.begin(); it!=histogram_data.end();
            ++it) {
        if (it->first >= tail_start && it->first <=tail_section_end_value) { tail_section[it->first] = it->second; }
    }
    return tail_section;
}

map<int,long> get_serial_CIC_model_fitting_section(const map<int,long>& histogram_data,
        const double bias_pedestal,
        const double readout_sigma)
{
    int low_cutoff = bias_pedestal + 4.0 * readout_sigma;
    int high_cutoff = bias_pedestal + 12*readout_sigma;
    map<int, long> fitting_region;
    for (map<int,long>::const_iterator it=histogram_data.begin(); it!=histogram_data.end();
            ++it) {
        if (it->first > low_cutoff && it->first <=high_cutoff) { fitting_region[it->first] = it->second; }
    }
    return fitting_region;
}

map<int,long> get_full_model_fitting_section(const map<int,long>& histogram_data,
        const double bias_pedestal,
        const double readout_sigma)
{
//    pair<int,long>peak =get_mode(histogram_data);
//    long count_threshold=max((long)50, (long)floor(peak.second/1e6));

    map<int, long> tail_region = get_tail_fitting_section(histogram_data,
                                 readout_sigma);
    int tail_end = get_max_value_pair(tail_region).first;

    int low_cutoff = bias_pedestal - 0.5 * readout_sigma;
//    cerr<<"low cuttoff, tail end: " <<low_cutoff <<" , "<< tail_end <<endl;

    map<int, long> fitting_region;
    for (map<int,long>::const_iterator it=histogram_data.begin(); it!=histogram_data.end();
            ++it) {
        if (it->first > low_cutoff && it->first <=tail_end) { fitting_region[it->first] = it->second; }
    }
    return fitting_region;
}

map<int, long> get_points_at_values_greater_than(const map<int,long>& histogram_data,
        int zero_point)
{
    map<int, long> positive_section;
    for (map<int,long>::const_iterator it=histogram_data.begin(); it!=histogram_data.end();
            ++it) {
        if ((it->first > zero_point)) { positive_section[it->first]=it->second; }
    }
    return positive_section;
}

map<int, long> threshold_above_count(const map<int,long>& histogram_data,
                                     int count_threshold)
{
    map<int, long> threshed;
    for (map<int,long>::const_iterator it=histogram_data.begin(); it!=histogram_data.end();
            ++it) {
        if ((it->second > count_threshold)) { threshed[it->first]=it->second; }
    }
    return threshed;
}

float estimate_log_slope(const map<int,long>& tail_section)
{
    pair<int,long> tail_section_peak(get_mode(tail_section)),
         tail_end(get_max_value_pair(tail_section));
    return (log(tail_end.second) - log(tail_section_peak.second))/
           (tail_end.first - tail_section_peak.first);
}

//Returns gauss_par vector: peak value (zero value), peak count, sigma (readout sigma);
vector<double> fit_readout_gaussian(const map<int,long>& histogram_data,
                                    const bool output_to_screen)
{
    pair<int,long> peak = get_mode(histogram_data);
    if (output_to_screen) { cout <<"Data peak count at " <<peak.first<<" : "<<peak.second<<endl; }
    map<int, long> gaussian_section = get_gaussian_fitting_section(histogram_data);
    gaussian_section.erase(
        0); //remove weird spike at 0 value. fixme! - track down the origin of this.

    GaussianHistogramFitFCN hump_fitter(gaussian_section);
    ROOT::Minuit2::MnUserParameters gauss_pars;
    gauss_pars.Add("HistPeakValue", peak.first, 2);
    gauss_pars.SetLimits("HistPeakValue", -100.0, 20000.0);
    gauss_pars.Add("HistPeakCount", peak.second, peak.second*0.1);
//            gauss_pars.SetLimits("HistPeakCount", peak.first*0.5, peak.first*2.0);
    gauss_pars.SetLowerLimit("HistPeakCount",5);
    float gauss_sigma_est = get_gaussian_HWHM(histogram_data) * 2 /
                            misc_math::gaussian_fwhm_to_sigma_ratio;
    if (output_to_screen) { cout <<"Sigma init est. " << gauss_sigma_est <<endl; }

    gauss_pars.Add("HistGaussSigma", gauss_sigma_est, gauss_sigma_est*0.3);
    gauss_pars.SetLowerLimit("HistGaussSigma",0.1);

//            gauss_pars.SetLimits("HistGaussSigma",gauss_sigma_est*0.5, gauss_sigma_est*2.0);
    ROOT::Minuit2::MnMinimize min_finder(hump_fitter, gauss_pars);
    ROOT::Minuit2::FunctionMinimum min=min_finder();
    if (output_to_screen) { cout <<min<<endl; }
    vector<double> end_pars;
    end_pars=min.UserParameters().Params();
    return end_pars;
}

//Returns amplitude, gain
vector<double> fit_exponential_tail(const map<int,long>& histogram_data,
                                    const double gaussian_sigma_estimate,
                                    const bool output_to_screen)
{
    if (output_to_screen) { cout <<"Fitting tail"<<endl; }
    pair<int,long>peak =get_mode(histogram_data);
    map<int,long> tail_section = get_tail_fitting_section(histogram_data,
                                 gaussian_sigma_estimate ,output_to_screen);

    ExponentialHistogramFitFCN tail_fitter(tail_section);
    double log_slope_estimate = estimate_log_slope(tail_section);
    if (output_to_screen) { cout <<"Initial slope estimate: " <<log_slope_estimate<<endl; }
    float gain_init_est = -1.0/log_slope_estimate;
    if (output_to_screen) { cout <<" - gain therefore est. at "<<gain_init_est<<endl; }
    ROOT::Minuit2::MnUserParameters tail_par;
//            tail_par.SetPrecision(1e-7);

    double amplitude_estimate=peak.second*0.1 / exp(
                                  -1.0*peak.first/gain_init_est);     //assumes 1/10th of all pixels contain a photon event
    if (output_to_screen) { cout <<"Initial amp estimate: " <<amplitude_estimate<< endl; }
    tail_par.Add("Amp", amplitude_estimate, amplitude_estimate*0.5);
    tail_par.SetLowerLimit("Amp", 0);
    tail_par.Add("Gain", gain_init_est, gain_init_est*0.1);
    tail_par.SetLimits("Gain", 1.0,1000.0);
    tail_par.Fix("Gain"); //Gain estimate likely much more accurate than amplitude estimate, so fix this for first run
    ROOT::Minuit2::MnMinimize amp_fitter(tail_fitter, tail_par);
    ROOT::Minuit2::FunctionMinimum min = amp_fitter();
    tail_par.SetValue("Amp",min.UserParameters().Value("Amp"));
    tail_par.Release("Gain");

    ROOT::Minuit2::MnMinimize amp_gain_fitter(tail_fitter, tail_par);
    min = amp_gain_fitter();
    if (output_to_screen) { cout <<min<<endl; }
    ;
    vector<double> fitted_tail_pars = min.UserParameters().Params();
    return fitted_tail_pars;
}

vector<double> fit_exponential_tail_fixed_gain(const map<int,long>& histogram_data,
        const double gaussian_sigma_estimate, const double gain_estimate,
        const bool output_to_screen)
{
    if (output_to_screen) { cout <<"Fitting tail"<<endl; }
    pair<int,long>peak =get_mode(histogram_data);
    map<int,long> tail_section = get_tail_fitting_section(histogram_data,
                                 gaussian_sigma_estimate ,output_to_screen);

    ExponentialHistogramFitFCN tail_fitter(tail_section);

    double gain_init_est=gain_estimate;
    double amplitude_estimate=peak.second*0.1 / exp(
                                  -1.0*peak.first/gain_init_est);     //assumes 1/10th of all pixels contain a photon event
    ROOT::Minuit2::MnUserParameters tail_par;
    if (output_to_screen) { cout <<"Initial amp estimate: " <<amplitude_estimate<< ", est err: "<< peak.second*0.1<< endl; }
    tail_par.Add("Amp", amplitude_estimate, peak.second*0.1);
    tail_par.SetLowerLimit("Amp", 0);
    tail_par.Add("Gain", gain_init_est, gain_init_est*0.05);
    tail_par.SetLowerLimit("Gain", 1.0);
    tail_par.Fix("Gain");
    ROOT::Minuit2::MnMinimize amp_finder(tail_fitter, tail_par);
    ROOT::Minuit2::FunctionMinimum min = amp_finder();
    if (output_to_screen) { cout <<min<<endl; }
//
//            if (output_to_screen)cout <<"Refitting gain only"<<endl;
//            tail_par.Fix("Amp");
//            ROOT::Minuit2::MnMinimize gain_min_finder(tail_fitter, tail_par, 2);
//            min = gain_min_finder();
//            if (output_to_screen)cout <<min<<endl;
    vector<double> fitted_tail_pars = min.UserParameters().Params();
    return fitted_tail_pars;
}

GainData fit_full_CCD_model(const map<int,long>& histogram_data,
                             const GainData& approx_fit_init,
                             const bool output_to_screen
//        , const bool sqrt_serial_CIC_gain_mode
                            )
{
    //Hardcoded initial estimates (fixme?)
    GainData approx_fit(approx_fit_init);

    approx_fit.N_serial_CIC_pix= approx_fit.actual_number_pix_events_recorded -
                                 (approx_fit.N_dark_pix + approx_fit.N_light_pix);
//    approx_fit.serial_CIC_gain = sqrt(approx_fit.gain);

//    gain_utils::write_histogram_data_to_file(full_fitting_region, "full_fitting_region.txt");

    ROOT::Minuit2::MnUserParameters mnpars;
    const string key_BiasPedestal = "BiasPedestal";
    const string key_NDarkPix = "NDarkPix";
    const string key_ROSigma = "ROSigma";
    const string key_NPhotPix = "NPhotPix";
    const string key_PhotGain = "PhotGain";
    const string key_NSerialCICPix = "NSerCICPix";
//    const string key_SerialCICGain = "SerCICGain";

    //NB! Ensure ordering matches definition for FullEmccdHistogramFitFCN::operator() !!!

    mnpars.Add(key_BiasPedestal, approx_fit.bias_pedestal, 2);
    mnpars.SetLimits(key_BiasPedestal, approx_fit.bias_pedestal-2,
                     approx_fit.bias_pedestal+2);

    mnpars.Add(key_NDarkPix, approx_fit.N_dark_pix, approx_fit.N_dark_pix*0.1);
    mnpars.SetLowerLimit(key_NDarkPix,5);
//    if (output_to_screen) cout <<"Sigma init est. " << gauss_sigma_est <<endl;

    mnpars.Add(key_ROSigma, approx_fit.readout_sigma, approx_fit.readout_sigma*0.1);
    mnpars.SetLimits(key_ROSigma, approx_fit.readout_sigma*0.8, approx_fit.readout_sigma*1.2);

    mnpars.Add(key_NPhotPix, approx_fit.N_light_pix, approx_fit.N_light_pix*0.1);
    mnpars.SetLowerLimit(key_NPhotPix, 0);

    mnpars.Add(key_PhotGain, approx_fit.gain, approx_fit.gain*0.05);
    mnpars.SetLimits(key_PhotGain, 1.0, 1000);

    mnpars.Add(key_NSerialCICPix, approx_fit.N_serial_CIC_pix,
               approx_fit.actual_number_pix_events_recorded*0.2);
    mnpars.SetLowerLimit(key_NSerialCICPix, 0.0);

//    mnpars.Add(key_SerialCICGain, approx_fit.serial_CIC_gain,
//            approx_fit.serial_CIC_gain);
//    mnpars.SetLowerLimit(key_SerialCICGain, 1.0);

    //--------------------------------------------------------------------------------
    //Fit just the serial CIC

    mnpars.Fix(key_BiasPedestal);
    mnpars.Fix(key_NDarkPix);
    mnpars.Fix(key_ROSigma);
    mnpars.Fix(key_NPhotPix);
    mnpars.Fix(key_PhotGain);

//    if (sqrt_serial_CIC_gain_mode){
//        mnpars.SetValue(key_SerialCICGain, sqrt(mnpars.Value(key_PhotGain)));
//        mnpars.Fix(key_SerialCICGain);
//    }
//    mnpars.Fix(key_NSerialCICPix); //Just leave this free to start:

    map<int, long> serial_CIC_region =
        get_serial_CIC_model_fitting_section(histogram_data,
                approx_fit.bias_pedestal, approx_fit.readout_sigma);

    FullEmccdHistogramFitFCN CIC_fit_fcn(serial_CIC_region);

    if (output_to_screen) cout<<"---------------------------------\n"
                                  << "Init params:" << mnpars<<endl;
//    gain_utils::write_histogram_data_to_file(serial_CIC_region.get_model_histogram(mnpars.Params()), "initial_model.txt" );
//    ROOT::Minuit2::MnScan serial_CIC_fitter(CIC_fit_fcn, mnpars);
//    serial_CIC_fitter.Scan(5);

    ROOT::Minuit2::MnMinimize serial_CIC_fitter(CIC_fit_fcn, mnpars);

    ROOT::Minuit2::FunctionMinimum min0 = serial_CIC_fitter();
//    if (output_to_screen)cout <<min<<endl;

    ROOT::Minuit2::MnUserParameters fitted_pars = min0.UserParameters();
    if (output_to_screen) { cout<<"Params after fit 0:" << fitted_pars << endl; }

    //--------------------------------------------------------------------------------
    map<int, long> full_fitting_region =
        get_full_model_fitting_section(histogram_data,
                                       approx_fit.bias_pedestal, approx_fit.readout_sigma);
//
    FullEmccdHistogramFitFCN full_fit_fcn(full_fitting_region);

    mnpars = fitted_pars;
    mnpars.Release(key_BiasPedestal);
    mnpars.Release(key_NDarkPix);
    mnpars.Release(key_ROSigma);
    mnpars.Release(key_NPhotPix);
//    if (sqrt_serial_CIC_gain_mode == false){
    mnpars.Release(key_PhotGain);
//    }
//    mnpars.Release(key_NSerialCICPix); //Already free
//    mnpars.Release(key_SerialCICGain); //Already free
    mnpars.SetError(key_NDarkPix, mnpars.Value(key_NDarkPix)*0.1);
    mnpars.SetError(key_NPhotPix, mnpars.Value(key_NPhotPix)*0.1);
    mnpars.SetError(key_PhotGain, mnpars.Value(key_PhotGain)*0.05);
    mnpars.SetError(key_NSerialCICPix, mnpars.Value(key_NSerialCICPix)*0.1);
//    mnpars.SetError(key_SerialCICGain, mnpars.Value(key_SerialCICGain)*0.1);

    if (output_to_screen) cout<<"---------------------------------\n"
                                  << "Init params:" << mnpars<<endl;
    ROOT::Minuit2::MnMinimize free_fitter(full_fit_fcn, mnpars);
    ROOT::Minuit2::FunctionMinimum min3 = free_fitter();
    if (output_to_screen) { cout <<min3<<endl; }
    fitted_pars = min3.UserParameters();
    if (output_to_screen) { cout<<"Params after fit 3:" << fitted_pars << endl; }
    //--------------------------------------------------------------------------------
    GainData fitted_info(approx_fit);
    if (min3.IsValid()) {
        fitted_info.bias_pedestal = fitted_pars.Value(key_BiasPedestal);
        fitted_info.readout_sigma = fitted_pars.Value(key_ROSigma);
        fitted_info.N_dark_pix = fitted_pars.Value(key_NDarkPix);
        fitted_info.gain = fitted_pars.Value(key_PhotGain);
        fitted_info.N_light_pix = fitted_pars.Value(key_NPhotPix);
        fitted_info.N_serial_CIC_pix = fitted_pars.Value(key_NSerialCICPix);
//        fitted_info.serial_CIC_gain = fitted_pars.Value(key_SerialCICGain);

        const double total_calculated_hist_area = fitted_info.N_dark_pix +
                fitted_info.N_serial_CIC_pix  + fitted_info.N_light_pix;

        fitted_info.photon_event_freq = fitted_info.N_light_pix /
                                        total_calculated_hist_area;

        fitted_info.serial_CIC_rate =  fitted_info.N_serial_CIC_pix /
                                       (fitted_info.N_serial_CIC_pix +
                                        fitted_info.N_dark_pix); //NB light pixels do not show up in serial CIC rate count!
        if (output_to_screen) {
            cerr<<"Calculated hist area / actual counts " << total_calculated_hist_area
                <<" / "<<fitted_info.actual_number_pix_events_recorded<<endl;

            cerr<<"Calculated N_dark pix / light pix / n_CIC_pix"<< fitted_info.N_dark_pix
                << " / "<<fitted_info.N_light_pix
                << " / "<<fitted_info.N_serial_CIC_pix
                <<endl;
        }
    }

    return fitted_info;
}

GainData fit_CCD_histogram(const map<int,long>& histogram_bins,
                            const bool perform_advanced_fit,
                            const bool fixed_gain_mode, const double gain_value,
                            const bool output_to_screen
//        , const bool sqrt_serial_CIC_gain_mode
                           )
{
    if (fixed_gain_mode &&  gain_value==0.0) { throw logic_error("Must supply a gain value for fitting in fixed gain mode"); }
    vector<double> gauss_pars = gain_utils::fit_readout_gaussian(histogram_bins);
    vector<double> exp_pars;
    if (fixed_gain_mode) { exp_pars = gain_utils::fit_exponential_tail_fixed_gain(histogram_bins, gauss_pars.back(), gain_value, true); }
    else { exp_pars= gain_utils::fit_exponential_tail(histogram_bins, gauss_pars.back(), output_to_screen && !perform_advanced_fit); }

    GainData approx_results;
    approx_results.bias_pedestal = gauss_pars[0];
    double peak_gaussian_count = gauss_pars[1];
    approx_results.readout_sigma = gauss_pars[2];
    approx_results.gain = exp_pars[1];

    approx_results.N_dark_pix= peak_gaussian_count*approx_results.readout_sigma *sqrt(
                                   2*M_PI); //area under a gaussian

    double area_under_exponential_curve = approx_results.gain *
                                          exp_pars.front()* exp(
                                                  -1.0*approx_results.bias_pedestal/approx_results.gain);      //Integral from bias pedestal to infinity
    approx_results.N_light_pix=area_under_exponential_curve;

    approx_results.actual_number_pix_events_recorded = sum_counts_in_histogram(
                histogram_bins);

    approx_results.photon_event_freq =
        approx_results.N_light_pix /
        (approx_results.N_light_pix+approx_results.N_dark_pix);

//    approx_results.N_serial_CIC_pix =
//        approx_results.actual_number_pix_events_recorded -
//        (approx_results.N_dark_pix+approx_results.N_light_pix);

//    approx_results.serial_CIC_rate =
//            approx_results.N_serial_CIC_pix /
//            approx_results.actual_number_pix_events_recorded;

//    cerr<<"Approx fit:" << approx_results<<endl;

    if (perform_advanced_fit) {
//    cerr<<"Running advanced fit...";
        //Put in initial estimates:
        GainData full_fit = fit_full_CCD_model(histogram_bins,
                                                approx_results,
                                                output_to_screen
//                , sqrt_serial_CIC_gain_mode
                                               );

        return full_fit;
//        cerr<<"Done"<<endl;
    } else {

        return approx_results;
    }
//    vector<double> full_pars = gauss_pars; // zero_val, peak_count, sigma,
//
//    full_pars.push_back(exp_pars[0]); //photon_amplitude
//    full_pars.push_back(exp_pars[1]); //photon_gain,
//    full_pars.push_back(0.00); //serial_CIC_amplitude;
//    approx_results.photon_event_freq = area_under_exponential_curve / (area_under_gaussian_curve + area_under_exponential_curve);

}

//==================================================================================
GainData::GainData()
    : bias_pedestal(-1),readout_sigma(-1),gain(-1),
      N_dark_pix(-1), N_light_pix(-1), N_serial_CIC_pix(-1),
      photon_event_freq(-1), serial_CIC_rate(-1),
      actual_number_pix_events_recorded(0)
{}

void GainData::write_to_file(const std::string& filename)
{
    ofstream outputfile(filename.c_str());
    if (outputfile) {
        outputfile<<"#"+get_column_headers()<<endl;
        outputfile<<*this<<endl;
    } else { throw runtime_error("Cannot write to gain_inf file" + filename); }
}

GainData::GainData(const string& filename)
{
    using namespace string_utils;
    ifstream datafile(filename.c_str());
    vector<string> lines;
    string line;
    while (getline(datafile,line)) { lines.push_back(line); }
//             for (size_t i=0; i!=lines.size();++i) cout <<lines[i]<<endl;
    if (lines.size()!=2) { throw runtime_error("\nIncorrect file format for gain info: " + filename +"\n"); }
    vector<string> line_segments = tokenize_and_strip_spaces(lines[1], ",");
    if (line_segments.size()!=4) { throw runtime_error("\nIncorrect file format for gain info: " + filename +"\n"); }
    bias_pedestal=atof(line_segments[0]);
    readout_sigma=atof(line_segments[1]);
    gain=atof(line_segments[2]);
    photon_event_freq=atof(line_segments[3]);
    serial_CIC_rate=atof(line_segments[4]);
}

std::ostream& operator<<(std::ostream& os, const GainData& g_inf)
{
    os<<g_inf.bias_pedestal<<" "<<g_inf.readout_sigma<<" "
      <<g_inf.gain<<" "
      <<g_inf.photon_event_freq<<" "
      <<g_inf.serial_CIC_rate<<" "
//            <<g_inf.serial_CIC_gain<<" "
      <<g_inf.actual_number_pix_events_recorded<<" "
      ;
    return os;
}

std::string GainData::get_column_headers()
{
    return "bias_pedestal ""readout_sigma "
           "gain "
           "photon_event_frequency "
           "serial_CIC_rate "
//            "serial_CIC_gain "
           "N_pix_recorded "
           ;
}

//==================================================================================

template<typename input_datatype>
CcdImage<input_datatype>& normalise_CCD_with_uniform_gain(CcdImage<input_datatype>& input,
        const GainData& det_info)
{
    input.pix-=det_info.bias_pedestal;
    input.pix/=det_info.gain;
    return input;
}
template
CcdImage<float>& normalise_CCD_with_uniform_gain(CcdImage<float>& input,
        const GainData& det_info);

template
CcdImage<double>& normalise_CCD_with_uniform_gain(CcdImage<double>& input,
        const GainData& det_info);

//double_bitmap& normalise_CCD_with_gain_map(double_bitmap& input, const GainData& det_info, const double_bitmap& gain_map){
////    input.add_normalisation_flag();
//    input-=det_info.bias_pedestal;
//    input/=gain_map;
//    return input;
//}

//==================================================================================

CcdImage<float> threshold_bitmap(const CcdImage<float>& input,
                                 const double threshold_level)
{
    CcdImage<float> thresholded(input);
//    assert(input.key_exists("CCDNORMD"));
    for (PixelIterator i(input.pix.range()); i!=i.end; ++i) {
        input.pix(i)>threshold_level ? thresholded.pix(i)=1.0f : thresholded.pix(i)=0.0f;
    }
//    thresholded.add_keyword("THRESHED", string_utils::ftoa(threshold_level),"Datacount Level at which data was thresholded");
    return thresholded;
}

CcdImage<float> create_threshold_mask(const CcdImage<float>& input,
                                      const float threshold_count)
{
    CcdImage<float> mask(input);
    mask.pix.assign(1.0);
//     if (!input.key_exists("CCDNORMD")) throw logic_error("create_threshold_mask takes normalised input");
    for (PixelIterator pix(input.pix.range()); pix!=pix.end; ++pix) {
        if (input.pix(pix)>threshold_count) {
            //If bright area
            PixelRange mask_region(pix,pix);
            mask_region = PixelRange::pad(mask_region, 4); //FIXME - hardcoded radius
            mask_region = PixelRange::overlap(mask_region, mask.pix.range());
            for (PixelIterator i(mask_region); i!=i.end; ++i) { mask.pix(i) = 0.0; }
        }
    }
    return mask;
}

GainData analyse_histogram(const map<int, long>& full_data_histogram ,
                            const string& output_dir)
{
    gain_utils::write_histogram_data_to_file(full_data_histogram,
            output_dir+"short_hist.dat");
    vector<double> hump_pars= gain_utils::fit_readout_gaussian(full_data_histogram, true);

    map<int,long> gauss_data = gain_utils::get_gaussian_fitting_section(full_data_histogram);
    gain_utils::GaussianHistogramFitFCN gauss_fitter(gauss_data);
    map<int,double> gauss_fitted_bit = gauss_fitter.get_model_histogram(hump_pars);
    gain_utils::write_histogram_data_to_file(gauss_data, output_dir+"hump_data.dat");
    gain_utils::write_histogram_data_to_file(gauss_fitted_bit,
            output_dir+"hump_model_fit.dat");

    map<int,long> threshed_data = gain_utils::threshold_above_count(full_data_histogram, 100);

    gain_utils::GaussianHistogramFitFCN gauss_full(threshed_data);
    map<int,double> gauss_model = gauss_full.get_model_histogram(hump_pars);
    gain_utils::write_histogram_data_to_file(gauss_model, output_dir+"hump_model_full.dat");

    vector<double> tail_pars = gain_utils::fit_exponential_tail(full_data_histogram,
                               hump_pars.back(), true);

    map<int, long> fitted_tail_data = gain_utils::get_tail_fitting_section(
                                          full_data_histogram, hump_pars.back());
    gain_utils::ExponentialHistogramFitFCN tail_fitter(fitted_tail_data);
    map<int, double> fitted_tail_model = tail_fitter.get_model_histogram(tail_pars);
    gain_utils::write_histogram_data_to_file(fitted_tail_data, output_dir+"tail_data.dat");
    gain_utils::write_histogram_data_to_file(fitted_tail_model,
            output_dir+"tail_model_fit.dat");

    map<int, long> postive_section = gain_utils::get_points_at_values_greater_than(
                                         full_data_histogram, hump_pars.front()+hump_pars.back()); //
    gain_utils::ExponentialHistogramFitFCN tail_model(postive_section);
    map<int, double>full_tail_model = tail_model.get_model_histogram(tail_pars);
    gain_utils::write_histogram_data_to_file(full_tail_model,
            output_dir+"tail_model_full.dat");

    gain_utils::write_histogram_data_to_file(gain_utils::sum_histograms(full_tail_model,
            gauss_model), output_dir+"combined_model.dat");

    map<int,long> unfitted_data;
    for (map<int,long>::const_iterator it=full_data_histogram.begin();
            it!=full_data_histogram.end(); ++it) {
        int value = it->first;
        if (gauss_data.count(value)==0 && fitted_tail_data.count(value)==0) {
            unfitted_data[value]=it->second;
        }
    }

    gain_utils::write_histogram_data_to_file(unfitted_data, output_dir+"unfitted_data.dat");

    GainData results;
    results.bias_pedestal = hump_pars.front();
    results.readout_sigma = hump_pars.back();
    results.gain = tail_pars.back();

    double peak_gaussian_count = hump_pars[1];
    double deduced_proportion_dark_pix = peak_gaussian_count*results.readout_sigma * sqrt(
            2*M_PI); //area under a gaussian
    double deduced_proportion_photon_pix = results.gain * tail_pars.front()* exp(
            -1.0*results.bias_pedestal/results.gain);      //Amplitude by gain (amplitude corrected for origin shift to gaussian peak)
    results.photon_event_freq = deduced_proportion_photon_pix / (deduced_proportion_dark_pix+
                                deduced_proportion_photon_pix);
    results.N_light_pix=deduced_proportion_photon_pix;
    results.N_dark_pix=deduced_proportion_dark_pix;
    return results;
}

//=====================================================================================================================
}//end namespace
}


// ====================================================================
// 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 14:43:46

/**
 * @file HSSUSY_mass_eigenstates.hpp
 *
 * @brief contains class for model with routines needed to solve boundary
 *        value problem using the two_scale solver by solving EWSB
 *        and determine the pole masses and mixings
 *
 * This file was generated at Thu 10 May 2018 14:43:46 with FlexibleSUSY
 * 2.0.1 (git commit: unknown) and SARAH 4.12.2 .
 */

#ifndef HSSUSY_MASS_EIGENSTATES_H
#define HSSUSY_MASS_EIGENSTATES_H

#include "HSSUSY_info.hpp"
#include "HSSUSY_physical.hpp"
#include "HSSUSY_soft_parameters.hpp"
#include "loop_corrections.hpp"
#include "threshold_corrections.hpp"
#include "error.hpp"
#include "problems.hpp"
#include "config.h"

#include <iosfwd>
#include <memory>
#include <string>

#include <Eigen/Core>

namespace flexiblesusy {

class HSSUSY_ewsb_solver_interface;
/**
 * @class HSSUSY_mass_eigenstates
 * @brief model class with routines for determing masses and mixinga and EWSB
 */
class HSSUSY_mass_eigenstates : public HSSUSY_soft_parameters {
public:
   explicit HSSUSY_mass_eigenstates(const HSSUSY_input_parameters& input_ = HSSUSY_input_parameters());
   HSSUSY_mass_eigenstates(const HSSUSY_mass_eigenstates&) = default;
   HSSUSY_mass_eigenstates(HSSUSY_mass_eigenstates&&) = default;
   virtual ~HSSUSY_mass_eigenstates() = default;
   HSSUSY_mass_eigenstates& operator=(const HSSUSY_mass_eigenstates&) = default;
   HSSUSY_mass_eigenstates& operator=(HSSUSY_mass_eigenstates&&) = default;

   /// number of EWSB equations
   static const int number_of_ewsb_equations = 1;

   void calculate_DRbar_masses();
   void calculate_pole_masses();
   void check_pole_masses_for_tachyons();
   virtual void clear() override;
   void clear_DRbar_parameters();
   Eigen::ArrayXd get_DRbar_masses() const;
   Eigen::ArrayXd get_DRbar_masses_and_mixings() const;
   Eigen::ArrayXd get_extra_parameters() const;
   void do_calculate_sm_pole_masses(bool);
   bool do_calculate_sm_pole_masses() const;
   void do_calculate_bsm_pole_masses(bool);
   bool do_calculate_bsm_pole_masses() const;
   void do_force_output(bool);
   bool do_force_output() const;
   void reorder_DRbar_masses();
   void reorder_pole_masses();
   void set_ewsb_iteration_precision(double);
   void set_ewsb_loop_order(int);
   void set_loop_corrections(const Loop_corrections&);
   const Loop_corrections& get_loop_corrections() const;
   void set_threshold_corrections(const Threshold_corrections&);
   const Threshold_corrections& get_threshold_corrections() const;
   void set_DRbar_masses(const Eigen::ArrayXd&);
   void set_DRbar_masses_and_mixings(const Eigen::ArrayXd&);
   void set_extra_parameters(const Eigen::ArrayXd&);
   void set_pole_mass_loop_order(int);
   int get_pole_mass_loop_order() const;
   void set_physical(const HSSUSY_physical&);
   double get_ewsb_iteration_precision() const;
   double get_ewsb_loop_order() const;
   const HSSUSY_physical& get_physical() const;
   HSSUSY_physical& get_physical();
   const Problems& get_problems() const;
   Problems& get_problems();
   void set_ewsb_solver(const std::shared_ptr<HSSUSY_ewsb_solver_interface>&);
   int solve_ewsb_tree_level();
   int solve_ewsb_one_loop();
   int solve_ewsb();            ///< solve EWSB at ewsb_loop_order level

   void calculate_spectrum();
   void clear_problems();
   std::string name() const;
   void run_to(double scale, double eps = -1.0) override;
   void print(std::ostream& out = std::cerr) const override;
   void set_precision(double);
   double get_precision() const;

   double get_MVG() const { return MVG; }
   double get_MHp() const { return MHp; }
   const Eigen::Array<double,3,1>& get_MFv() const { return MFv; }
   double get_MFv(int i) const { return MFv(i); }
   double get_MAh() const { return MAh; }
   double get_Mhh() const { return Mhh; }
   const Eigen::Array<double,3,1>& get_MFd() const { return MFd; }
   double get_MFd(int i) const { return MFd(i); }
   const Eigen::Array<double,3,1>& get_MFu() const { return MFu; }
   double get_MFu(int i) const { return MFu(i); }
   const Eigen::Array<double,3,1>& get_MFe() const { return MFe; }
   double get_MFe(int i) const { return MFe(i); }
   double get_MVWp() const { return MVWp; }
   double get_MVP() const { return MVP; }
   double get_MVZ() const { return MVZ; }

   const Eigen::Matrix<std::complex<double>,3,3>& get_Vd() const { return Vd; }
   std::complex<double> get_Vd(int i, int k) const { return Vd(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_Ud() const { return Ud; }
   std::complex<double> get_Ud(int i, int k) const { return Ud(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_Vu() const { return Vu; }
   std::complex<double> get_Vu(int i, int k) const { return Vu(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_Uu() const { return Uu; }
   std::complex<double> get_Uu(int i, int k) const { return Uu(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_Ve() const { return Ve; }
   std::complex<double> get_Ve(int i, int k) const { return Ve(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_Ue() const { return Ue; }
   std::complex<double> get_Ue(int i, int k) const { return Ue(i,k); }
   const Eigen::Matrix<double,2,2>& get_ZZ() const { return ZZ; }
   double get_ZZ(int i, int k) const { return ZZ(i,k); }

   double get_mass_matrix_VG() const;
   void calculate_MVG();
   double get_mass_matrix_Hp() const;
   void calculate_MHp();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fv() const;
   void calculate_MFv();
   double get_mass_matrix_Ah() const;
   void calculate_MAh();
   double get_mass_matrix_hh() const;
   void calculate_Mhh();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fd() const;
   void calculate_MFd();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fu() const;
   void calculate_MFu();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fe() const;
   void calculate_MFe();
   double get_mass_matrix_VWp() const;
   void calculate_MVWp();
   Eigen::Matrix<double,2,2> get_mass_matrix_VPVZ() const;
   void calculate_MVPVZ();

   double get_ewsb_eq_hh_1() const;

   double CphhHpconjHp() const;
   double CpbargWpgZHp() const;
   double CpbargZgWpconjHp() const;
   double CpbargWpCgZconjHp() const;
   double CpbargZgWpCHp() const;
   double CpconjHpVPVWp() const;
   double CpconjHpVWpVZ() const;
   double CpAhAhHpconjHp() const;
   double CphhhhHpconjHp() const;
   double CpHpHpconjHpconjHp() const;
   std::complex<double> CpAhconjHpVWp() const;
   double CphhconjHpVWp() const;
   double CpHpconjHpVP() const;
   double CpHpconjHpVZ() const;
   double CpHpconjHpconjVWpVWp() const;
   std::complex<double> CpHpconjHpVZVZ() const;
   std::complex<double> CpbarFdFuconjHpPR(int gI1, int gI2) const;
   std::complex<double> CpbarFdFuconjHpPL(int gI1, int gI2) const;
   double CpbarFeFvconjHpPR(int , int ) const;
   std::complex<double> CpbarFeFvconjHpPL(int gI1, int gI2) const;
   double CpAhAhhh() const;
   std::complex<double> CpbargWpgWpAh() const;
   std::complex<double> CpbargWpCgWpCAh() const;
   double CpAhAhAhAh() const;
   double CpAhAhhhhh() const;
   std::complex<double> CpAhhhVZ() const;
   std::complex<double> CpAhHpconjVWp() const;
   double CpAhAhconjVWpVWp() const;
   std::complex<double> CpAhAhVZVZ() const;
   std::complex<double> CpbarFdFdAhPR(int gI1, int gI2) const;
   std::complex<double> CpbarFdFdAhPL(int gI1, int gI2) const;
   std::complex<double> CpbarFeFeAhPR(int gI1, int gI2) const;
   std::complex<double> CpbarFeFeAhPL(int gI1, int gI2) const;
   std::complex<double> CpbarFuFuAhPR(int gI1, int gI2) const;
   std::complex<double> CpbarFuFuAhPL(int gI1, int gI2) const;
   double Cphhhhhh() const;
   double CphhVZVZ() const;
   double CphhconjVWpVWp() const;
   double CpbargWpgWphh() const;
   double CpbargWpCgWpChh() const;
   double CpbargZgZhh() const;
   double Cphhhhhhhh() const;
   double CphhHpconjVWp() const;
   double CphhhhconjVWpVWp() const;
   std::complex<double> CphhhhVZVZ() const;
   std::complex<double> CpbarFdFdhhPR(int gI1, int gI2) const;
   std::complex<double> CpbarFdFdhhPL(int gI1, int gI2) const;
   std::complex<double> CpbarFeFehhPR(int gI1, int gI2) const;
   std::complex<double> CpbarFeFehhPL(int gI1, int gI2) const;
   std::complex<double> CpbarFuFuhhPR(int gI1, int gI2) const;
   std::complex<double> CpbarFuFuhhPL(int gI1, int gI2) const;
   std::complex<double> CpVGVGVG() const;
   std::complex<double> CpbargGgGVG() const;
   double CpbarFdFdVGPL(int gI1, int gI2) const;
   double CpbarFdFdVGPR(int gI1, int gI2) const;
   double CpbarFuFuVGPL(int gI1, int gI2) const;
   double CpbarFuFuVGPR(int gI1, int gI2) const;
   double CpVGVGVGVG1() const;
   double CpVGVGVGVG2() const;
   double CpVGVGVGVG3() const;
   double CpHpconjVWpVP() const;
   double CpbargWpgWpVP() const;
   double CpbargWpCgWpCVP() const;
   std::complex<double> CpHpconjHpVPVP() const;
   double CpconjVWpVPVWp() const;
   double CpbarFdFdVPPL(int gI1, int gI2) const;
   double CpbarFdFdVPPR(int gI1, int gI2) const;
   double CpbarFeFeVPPL(int gI1, int gI2) const;
   double CpbarFeFeVPPR(int gI1, int gI2) const;
   double CpbarFuFuVPPL(int gI1, int gI2) const;
   double CpbarFuFuVPPR(int gI1, int gI2) const;
   double CpconjVWpVPVPVWp3() const;
   double CpconjVWpVPVPVWp1() const;
   double CpconjVWpVPVPVWp2() const;
   double CpHpconjVWpVZ() const;
   double CpbargWpgWpVZ() const;
   double CpbargWpCgWpCVZ() const;
   double CpconjVWpVWpVZ() const;
   double CpbarFdFdVZPL(int gI1, int gI2) const;
   double CpbarFdFdVZPR(int gI1, int gI2) const;
   double CpbarFeFeVZPL(int gI1, int gI2) const;
   double CpbarFeFeVZPR(int gI1, int gI2) const;
   double CpbarFuFuVZPL(int gI1, int gI2) const;
   double CpbarFuFuVZPR(int gI1, int gI2) const;
   double CpbarFvFvVZPL(int gI1, int gI2) const;
   double CpbarFvFvVZPR(int , int ) const;
   double CpconjVWpVWpVZVZ1() const;
   double CpconjVWpVWpVZVZ2() const;
   double CpconjVWpVWpVZVZ3() const;
   double CpbargPgWpconjVWp() const;
   double CpbargWpCgPconjVWp() const;
   double CpbargWpCgZconjVWp() const;
   double CpbargZgWpconjVWp() const;
   std::complex<double> CpbarFdFuconjVWpPL(int gI1, int gI2) const;
   double CpbarFdFuconjVWpPR(int , int ) const;
   std::complex<double> CpbarFeFvconjVWpPL(int gI1, int gI2) const;
   double CpbarFeFvconjVWpPR(int , int ) const;
   double CpconjVWpconjVWpVWpVWp2() const;
   double CpconjVWpconjVWpVWpVWp1() const;
   double CpconjVWpconjVWpVWpVWp3() const;
   std::complex<double> CpbarUFdFdAhPL(int gO2, int gI1) const;
   std::complex<double> CpbarUFdFdAhPR(int gO1, int gI1) const;
   std::complex<double> CpbarUFdFdhhPL(int gO2, int gI2) const;
   std::complex<double> CpbarUFdFdhhPR(int gO1, int gI2) const;
   std::complex<double> CpbarUFdFdVGPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFdFdVGPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFdFdVPPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFdFdVPPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFdFdVZPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFdFdVZPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFdFuconjHpPL(int gO2, int gI2) const;
   std::complex<double> CpbarUFdFuconjHpPR(int gO1, int gI2) const;
   double CpbarUFdFuconjVWpPR(int , int ) const;
   std::complex<double> CpbarUFdFuconjVWpPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFuFuAhPL(int gO2, int gI1) const;
   std::complex<double> CpbarUFuFuAhPR(int gO1, int gI1) const;
   std::complex<double> CpbarUFuFdHpPL(int gO2, int gI2) const;
   std::complex<double> CpbarUFuFdHpPR(int gO1, int gI2) const;
   double CpbarUFuFdVWpPR(int , int ) const;
   std::complex<double> CpbarUFuFdVWpPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFuFuhhPL(int gO2, int gI2) const;
   std::complex<double> CpbarUFuFuhhPR(int gO1, int gI2) const;
   std::complex<double> CpbarUFuFuVGPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFuFuVGPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFuFuVPPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFuFuVPPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFuFuVZPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFuFuVZPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFeFeAhPL(int gO2, int gI1) const;
   std::complex<double> CpbarUFeFeAhPR(int gO1, int gI1) const;
   std::complex<double> CpbarUFeFehhPL(int gO2, int gI2) const;
   std::complex<double> CpbarUFeFehhPR(int gO1, int gI2) const;
   std::complex<double> CpbarUFeFeVPPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFeFeVPPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFeFeVZPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFeFeVZPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFeFvconjHpPL(int gO2, int gI2) const;
   double CpbarUFeFvconjHpPR(int , int ) const;
   double CpbarUFeFvconjVWpPR(int , int ) const;
   double CpbarUFeFvconjVWpPL(int gO1, int gI2) const;
   double CpbarFvFeHpPL(int , int ) const;
   std::complex<double> CpbarFvFeHpPR(int gO1, int gI2) const;
   double CpbarFvFeVWpPR(int , int ) const;
   std::complex<double> CpbarFvFeVWpPL(int gO1, int gI2) const;
   std::complex<double> CpbarFuFdHpPL(int gO2, int gI2) const;
   std::complex<double> CpbarFuFdHpPR(int gO1, int gI2) const;
   double CpbarFuFdVWpPR(int , int ) const;
   std::complex<double> CpbarFuFdVWpPL(int gO1, int gI2) const;
   std::complex<double> self_energy_Hp_1loop(double p ) const;
   std::complex<double> self_energy_Ah_1loop(double p ) const;
   std::complex<double> self_energy_hh_1loop(double p ) const;
   std::complex<double> self_energy_VG_1loop(double p ) const;
   std::complex<double> self_energy_VP_1loop(double p ) const;
   std::complex<double> self_energy_VZ_1loop(double p ) const;
   std::complex<double> self_energy_VWp_1loop(double p ) const;
   std::complex<double> self_energy_Fd_1loop_1(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_1(double p) const;
   std::complex<double> self_energy_Fd_1loop_PR(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_PR(double p) const;
   std::complex<double> self_energy_Fd_1loop_PL(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_PL(double p) const;
   std::complex<double> self_energy_Fu_1loop_1(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_1(double p) const;
   std::complex<double> self_energy_Fu_1loop_PR(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PR(double p) const;
   std::complex<double> self_energy_Fu_1loop_PL(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PL(double p) const;
   std::complex<double> self_energy_Fe_1loop_1(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_1(double p) const;
   std::complex<double> self_energy_Fe_1loop_PR(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_PR(double p) const;
   std::complex<double> self_energy_Fe_1loop_PL(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_PL(double p) const;
   std::complex<double> self_energy_Fv_1loop_1(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fv_1loop_1(double p) const;
   std::complex<double> self_energy_Fv_1loop_PR(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fv_1loop_PR(double p) const;
   std::complex<double> self_energy_Fv_1loop_PL(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fv_1loop_PL(double p) const;
   std::complex<double> self_energy_Fd_1loop_1_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_1_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fd_1loop_PR_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_PR_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fd_1loop_PL_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_PL_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fe_1loop_1_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_1_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fe_1loop_PR_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_PR_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fe_1loop_PL_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_PL_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fu_1loop_1_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_1_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fu_1loop_PR_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PR_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fu_1loop_PL_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PL_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fu_1loop_1_heavy(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_1_heavy(double p) const;
   std::complex<double> self_energy_Fu_1loop_PR_heavy(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PR_heavy(double p) const;
   std::complex<double> self_energy_Fu_1loop_PL_heavy(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PL_heavy(double p) const;
   std::complex<double> tadpole_hh_1loop() const;

   /// calculates the tadpoles at current loop order
   void tadpole_equations(double[number_of_ewsb_equations]) const;
   /// calculates the tadpoles at current loop order
   Eigen::Matrix<double,number_of_ewsb_equations,1> tadpole_equations() const;
   /// calculates the tadpoles divided by VEVs at current loop order
   Eigen::Matrix<double,number_of_ewsb_equations,1> tadpole_equations_over_vevs() const;

   double self_energy_hh_2loop(double p) const;

   double self_energy_hh_3loop() const;

   void calculate_MVG_pole();
   void calculate_MFv_pole();
   void calculate_Mhh_pole();
   void calculate_MVP_pole();
   void calculate_MVZ_pole();
   void calculate_MFd_pole();
   void calculate_MFu_pole();
   void calculate_MFe_pole();
   void calculate_MVWp_pole();
   double calculate_MVWp_pole(double);
   double calculate_MVZ_pole(double);

   double calculate_MFv_DRbar(double, int) const;
   double calculate_MFe_DRbar(double, int) const;
   double calculate_MFu_DRbar(double, int) const;
   double calculate_MFd_DRbar(double, int) const;
   double calculate_MVP_DRbar(double);
   double calculate_MVZ_DRbar(double);
   double calculate_MVWp_DRbar(double);

   double ThetaW() const;

private:
   int ewsb_loop_order{2};           ///< loop order for EWSB
   int pole_mass_loop_order{2};      ///< loop order for pole masses
   bool calculate_sm_pole_masses{false};  ///< switch to calculate the pole masses of the Standard Model particles
   bool calculate_bsm_pole_masses{true};  ///< switch to calculate the pole masses of the BSM particles
   bool force_output{false};              ///< switch to force output of pole masses
   double precision{1.e-3};               ///< RG running precision
   double ewsb_iteration_precision{1.e-5};///< precision goal of EWSB solution
   HSSUSY_physical physical{}; ///< contains the pole masses and mixings
   Problems problems{HSSUSY_info::model_name,
                     &HSSUSY_info::particle_names_getter,
                     &HSSUSY_info::parameter_names_getter}; ///< problems
   Loop_corrections loop_corrections{}; ///< used pole mass corrections
   std::shared_ptr<HSSUSY_ewsb_solver_interface> ewsb_solver{};
   Threshold_corrections threshold_corrections{}; ///< used threshold corrections

   int get_number_of_ewsb_iterations() const;
   int get_number_of_mass_iterations() const;
   int solve_ewsb_tree_level_custom();
   void copy_DRbar_masses_to_pole_masses();

   // Passarino-Veltman loop functions
   double A0(double) const noexcept;
   double B0(double, double, double) const noexcept;
   double B1(double, double, double) const noexcept;
   double B00(double, double, double) const noexcept;
   double B22(double, double, double) const noexcept;
   double H0(double, double, double) const noexcept;
   double F0(double, double, double) const noexcept;
   double G0(double, double, double) const noexcept;

   // DR-bar masses
   double MVG{};
   double MHp{};
   Eigen::Array<double,3,1> MFv{Eigen::Array<double,3,1>::Zero()};
   double MAh{};
   double Mhh{};
   Eigen::Array<double,3,1> MFd{Eigen::Array<double,3,1>::Zero()};
   Eigen::Array<double,3,1> MFu{Eigen::Array<double,3,1>::Zero()};
   Eigen::Array<double,3,1> MFe{Eigen::Array<double,3,1>::Zero()};
   double MVWp{};
   double MVP{};
   double MVZ{};

   // DR-bar mixing matrices
   Eigen::Matrix<std::complex<double>,3,3> Vd{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> Ud{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> Vu{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> Uu{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> Ve{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> Ue{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<double,2,2> ZZ{Eigen::Matrix<double,2,2>::Zero()};

   // phases

   // extra parameters

};

std::ostream& operator<<(std::ostream&, const HSSUSY_mass_eigenstates&);

} // namespace flexiblesusy

#endif


/*
 dfc2.cpp

Copyright (c) 2016 Terumasa Tadano

This file is distributed under the terms of the MIT license.
Please see the file 'LICENCE.txt' in the root directory
or http://opensource.org/licenses/mit-license.php for information.
*/

#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <map>
#include <vector>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/foreach.hpp>
#include <boost/version.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>

#include "memory.h"
#include "xml_parser.h"
#include "dfc2.h"
#include "constants.h"
#include "mathfunctions.h"

using namespace std;

int main()
{
    cout << " DFC2 -- a generator of renormalized harmonic FCs from SCPH outputs." << endl;
    cout << " XML file containing original FC2 : ";
    cin >> original_xml;
    cout << " Output xml filename with anharmonic correction : ";
    cin >> new_xml;
    cout << " FC2 correction file from SCPH calculation : ";
    cin >> file_fc2_correction;
    cout << " Target temperature : ";
    cin >> temp;

    // Load original harmonic force constants and structure data of the supercell
    load_fc2_xml(original_xml);

    // Load anharmonic correction and structure data of the primitive lattice
    load_delta_fc2(file_fc2_correction, temp);

    // Initialize new fc2 by the original values
    fc2_new.clear();
    copy(fc2_orig.begin(), fc2_orig.end(), back_inserter(fc2_new));

    // Add delta_fc2 to fc2_new
    calculate_new_fc2(fc2_orig, delta_fc2, fc2_new);

    write_new_xml(fc2_new, new_xml);

    cout << endl << " New XML file " << new_xml << " was created successfully." << endl;

    deallocate(xr_s);
    deallocate(kd);
    deallocate(kd_symbol);
    deallocate(map_p2s);
    deallocate(map_s2p);
    deallocate(xr_p);
    deallocate(kd_p);
}

void load_fc2_xml(const std::string file_in)
{
    int i;
    using namespace boost::property_tree;

    ptree pt;
    int atm1, atm2, xyz1, xyz2, cell_s;
    stringstream ss1, ss2;
    FcsClassExtent fcext_tmp;

    map<string, int> dict_atomic_kind;

    try {
        read_xml(file_in, pt);
    }
    catch (exception &e) {
        cout << "Cannot open file " + file_in << endl;
        exit(EXIT_FAILURE);
    }

    nat = boost::lexical_cast<unsigned int>(
        get_value_from_xml(pt,
                           "Data.Structure.NumberOfAtoms"));
    nkd = boost::lexical_cast<unsigned int>(
        get_value_from_xml(pt,
                           "Data.Structure.NumberOfElements"));

    ntran = boost::lexical_cast<unsigned int>(
        get_value_from_xml(pt,
                           "Data.Symmetry.NumberOfTranslations"));

    natmin = nat / ntran;

    for (i = 0; i < 3; ++i) {
        ss1.str("");
        ss1.clear();
        ss1 << get_value_from_xml(pt,
                                  "Data.Structure.LatticeVector.a"
                                  + boost::lexical_cast<string>(i + 1));
        ss1 >> lavec_s[0][i] >> lavec_s[1][i] >> lavec_s[2][i];
    }

    // Parse atomic elements and coordinates

    allocate(xr_s, nat, 3);
    allocate(kd, nat);
    allocate(kd_symbol, nkd);

    i = 0;

    BOOST_FOREACH(const ptree::value_type& child_, pt.get_child("Data.Structure.AtomicElements")) {
        const ptree &child = child_.second;
        const unsigned int icount_kd = child.get<unsigned int>("<xmlattr>.number");
        dict_atomic_kind[boost::lexical_cast<string>(child_.second.data())] = icount_kd - 1;
        kd_symbol[i++] = boost::lexical_cast<string>(child_.second.data());
    }

    unsigned int index;

    BOOST_FOREACH(const ptree::value_type& child_, pt.get_child("Data.Structure.Position")) {
        const ptree &child = child_.second;
        const string str_index = child.get<string>("<xmlattr>.index");
        const string str_element = child.get<string>("<xmlattr>.element");

        ss1.str("");
        ss1.clear();
        ss1 << child.data();

        index = boost::lexical_cast<unsigned int>(str_index) - 1;

        if (index >= nat) {
            cout << "index is out of range" << endl;
            exit(EXIT_FAILURE);
        }

        kd[index] = dict_atomic_kind[str_element];
        ss1 >> xr_s[index][0] >> xr_s[index][1] >> xr_s[index][2];
    }

    dict_atomic_kind.clear();

    // Parse mapping information

    allocate(map_p2s, natmin, ntran);
    allocate(map_s2p, nat);

    unsigned int tran, atom_p, atom_s;

    BOOST_FOREACH(const ptree::value_type& child_, pt.get_child("Data.Symmetry.Translations")) {
        const ptree &child = child_.second;
        const string str_tran = child.get<string>("<xmlattr>.tran");
        const string str_atom = child.get<string>("<xmlattr>.atom");

        tran = boost::lexical_cast<unsigned int>(str_tran) - 1;
        atom_p = boost::lexical_cast<unsigned int>(str_atom) - 1;
        atom_s = boost::lexical_cast<unsigned int>(child.data()) - 1;

        if (tran >= ntran || atom_p >= natmin || atom_s >= nat) {
            cout << "index is out of range" << endl;
            exit(EXIT_FAILURE);
        }

        map_p2s[atom_p][tran] = atom_s;
        map_s2p[atom_s].atom_num = atom_p;
        map_s2p[atom_s].tran_num = tran;
    }

    BOOST_FOREACH(const ptree::value_type& child_, pt.get_child("Data.ForceConstants.HARMONIC")) {
        const ptree &child = child_.second;
        const string str_p1 = child.get<string>("<xmlattr>.pair1");
        const string str_p2 = child.get<string>("<xmlattr>.pair2");

        ss1.str("");
        ss2.str("");
        ss1.clear();
        ss2.clear();

        ss1 << str_p1;
        ss2 << str_p2;

        ss1 >> atm1 >> xyz1;
        ss2 >> atm2 >> xyz2 >> cell_s;

        fcext_tmp.atm1 = atm1 - 1;
        fcext_tmp.xyz1 = xyz1 - 1;
        fcext_tmp.atm2 = atm2 - 1;
        fcext_tmp.xyz2 = xyz2 - 1;
        fcext_tmp.cell_s = cell_s - 1;
        fcext_tmp.fcs_val = boost::lexical_cast<double>(child.data());

        fc2_orig.push_back(fcext_tmp);
    }
}

void load_delta_fc2(const std::string file_in, const double temp)
{
    int i;
    ifstream ifs_in;
    stringstream ss;

    // Restart
    ifs_in.open(file_in.c_str(), ios::in);
    if (!ifs_in) {
        cout << "Could not open " + file_in << endl;
        exit(EXIT_FAILURE);
    }

    // Check the consistency

    string line_tmp, str_tmp;
    vector<string> str_vec;

    int sx, sy, sz;
    int atm1, atm2, xyz1, xyz2;
    double dfc2_tmp;
    bool found_tag = false;

    // Get lattice vectors
    for (i = 0; i < 3; ++i) {
        ifs_in >> lavec_p[0][i] >> lavec_p[1][i] >> lavec_p[2][i];
    }
    recips(lavec_p, rlavec_p);

    ifs_in >> nat_p >> nkd_p;
    ifs_in.ignore();
    getline(ifs_in, line_tmp);

    allocate(xr_p, nat_p, 3);
    allocate(kd_p, nat_p);
    for (i = 0; i < nat_p; ++i) {
        ifs_in >> xr_p[i][0] >> xr_p[i][1] >> xr_p[i][2] >> kd_p[i];
    }
    ifs_in.ignore();

    while (getline(ifs_in, line_tmp)) {
        if (line_tmp[0] == '#') {
            boost::split(str_vec, line_tmp, boost::is_space());
            if (abs(boost::lexical_cast<double>(str_vec[3]) - temp) < eps) {
                found_tag = true;
                break;
            }
        }
    }
    if (!found_tag) {
        cout << "Could not find the # Temp tag for the target temperature" << endl;
        exit(EXIT_FAILURE);
    }

    delta_fc2.clear();

    while (getline(ifs_in, line_tmp)) {

        if (line_tmp[0] == '#') break;

        if (!line_tmp.empty()) {
            stringstream ss1;

            ss1 << line_tmp;
            ss1 >> sx >> sy >> sz >> atm1 >> xyz1 >> atm2 >> xyz2 >> dfc2_tmp;

            delta_fc2.push_back(DeltaFcs(sx, sy, sz, atm1, xyz1, atm2, xyz2, dfc2_tmp));
        }

    }

    ifs_in.close();
}

void calculate_new_fc2(std::vector<FcsClassExtent> fc2_in,
                       std::vector<DeltaFcs> delta_fc2,
                       std::vector<FcsClassExtent> &fc2_out)
{
    int i, j, k;
    int ix, iy, iz;
    int icell;
    double **xshift_s;

    allocate(xshift_s, 27, 3);

    for (i = 0; i < 3; ++i) xshift_s[0][i] = 0.0;

    icell = 0;

    for (ix = -1; ix <= 1; ++ix) {
        for (iy = -1; iy <= 1; ++iy) {
            for (iz = -1; iz <= 1; ++iz) {
                if (ix == 0 && iy == 0 && iz == 0) continue;

                ++icell;

                xshift_s[icell][0] = static_cast<double>(ix);
                xshift_s[icell][1] = static_cast<double>(iy);
                xshift_s[icell][2] = static_cast<double>(iz);
            }
        }
    }

    double mat_convert[3][3];

    for (i = 0; i < 3; ++i) {
        for (j = 0; j < 3; ++j) {
            mat_convert[i][j] = 0.0;
            for (k = 0; k < 3; ++k) {
                mat_convert[i][j] += rlavec_p[i][k] * lavec_s[k][j] / (2.0 * pi);
            }

            mat_convert[i][j] = static_cast<double>(nint(mat_convert[i][j]));
        }
    }

    //
    //    cout << "lavec super:" << endl;
    //    for (i = 0; i < 3; ++i) {
    //        for (j = 0; j < 3; ++j) {
    //            cout << setw(15) << lavec_s[i][j];
    //        }
    //        cout << endl;
    //    }
    //    cout << endl;
    //
    //    cout << "lavec primitive:" << endl;
    //    for (i = 0; i < 3; ++i) {
    //        for (j = 0; j < 3; ++j) {
    //            cout << setw(15) << lavec_p[i][j];
    //        }
    //        cout << endl;
    //    }
    //    cout << endl;
    //
    //
    //    cout << "Mat convert:" << endl;
    //    for (i = 0; i < 3; ++i) {
    //        for (j = 0; j < 3; ++j) {
    //            cout << setw(15) << mat_convert[i][j];
    //        }
    //        cout << endl;
    //    }
    //    cout << endl;

    double vec[3];

    int icount = 0;

    vector<int> arr_tmp;
    vector<FcsTrans> fc2_data;

    fc2_data.clear();

    for (auto it = fc2_in.cbegin(); it != fc2_in.cend(); ++it) {

        arr_tmp.clear();

        for (i = 0; i < 3; ++i) {
            vec[i] = xr_s[(*it).atm2][i] + xshift_s[(*it).cell_s][i]
                - xr_s[map_p2s[map_s2p[(*it).atm2].atom_num][0]][i];
        }

        rotvec(vec, vec, mat_convert);

        arr_tmp.push_back((*it).atm1);
        arr_tmp.push_back((*it).xyz1);
        arr_tmp.push_back(map_s2p[(*it).atm2].atom_num);
        arr_tmp.push_back((*it).xyz2);
        for (i = 0; i < 3; ++i) arr_tmp.push_back(nint(vec[i]));

        fc2_data.push_back(FcsTrans(arr_tmp, icount));
        ++icount;
    }

    std::sort(fc2_data.begin(), fc2_data.end());

    vector<FcsTrans>::iterator iter_found;
    int index_tmp = 0;

    for (auto it = delta_fc2.begin(); it != delta_fc2.end(); ++it) {

        if (abs((*it).dfc2) > eps10) {
            arr_tmp.clear();
            arr_tmp.push_back((*it).atm1);
            arr_tmp.push_back((*it).xyz1);
            arr_tmp.push_back((*it).atm2);
            arr_tmp.push_back((*it).xyz2);
            arr_tmp.push_back((*it).sx);
            arr_tmp.push_back((*it).sy);
            arr_tmp.push_back((*it).sz);

            iter_found = lower_bound(fc2_data.begin(), fc2_data.end(), FcsTrans(arr_tmp, index_tmp));

            if (iter_found != fc2_data.end() && arr_tmp == (*iter_found).arr) {
                fc2_new[(*iter_found).fcs_index].fcs_val += (*it).dfc2;
            } else {
                cout << "Warning: The following force constant doesn't exist in the original file:" << endl;
                cout << setw(5) << (*it).sx << setw(5) << (*it).sy << setw(5) << (*it).sz;
                cout << setw(5) << (*it).atm1 << setw(5) << (*it).xyz1;
                cout << setw(5) << (*it).atm2 << setw(5) << (*it).xyz2;
                cout << setw(15) << (*it).dfc2 << endl;
            }
        }
        ++index_tmp;

    }
}

void recips(double vec[3][3], double inverse[3][3])
{
    double det;
    det = vec[0][0] * vec[1][1] * vec[2][2]
        + vec[1][0] * vec[2][1] * vec[0][2]
        + vec[2][0] * vec[0][1] * vec[1][2]
        - vec[0][0] * vec[2][1] * vec[1][2]
        - vec[2][0] * vec[1][1] * vec[0][2]
        - vec[1][0] * vec[0][1] * vec[2][2];

    if (abs(det) < eps12) {
        cout << "Lattice vector is singular" << endl;
        exit(EXIT_FAILURE);
    }

    double factor = 2.0 * pi / det;

    inverse[0][0] = (vec[1][1] * vec[2][2] - vec[1][2] * vec[2][1]) * factor;
    inverse[0][1] = (vec[0][2] * vec[2][1] - vec[0][1] * vec[2][2]) * factor;
    inverse[0][2] = (vec[0][1] * vec[1][2] - vec[0][2] * vec[1][1]) * factor;

    inverse[1][0] = (vec[1][2] * vec[2][0] - vec[1][0] * vec[2][2]) * factor;
    inverse[1][1] = (vec[0][0] * vec[2][2] - vec[0][2] * vec[2][0]) * factor;
    inverse[1][2] = (vec[0][2] * vec[1][0] - vec[0][0] * vec[1][2]) * factor;

    inverse[2][0] = (vec[1][0] * vec[2][1] - vec[1][1] * vec[2][0]) * factor;
    inverse[2][1] = (vec[0][1] * vec[2][0] - vec[0][0] * vec[2][1]) * factor;
    inverse[2][2] = (vec[0][0] * vec[1][1] - vec[0][1] * vec[1][0]) * factor;
}

void write_new_xml(const std::vector<FcsClassExtent> fc2_in,
                   const std::string xml_out)
{
    // Write to XML file

    int i, j;
    using boost::property_tree::ptree;

    ptree pt;
    string str_pos[3], str_tmp;

    str_tmp.clear();

    pt.put("Data.OriginalFC2", original_xml);
    pt.put("Data.SCPH_file", file_fc2_correction);
    pt.put("Data.SCPH_Temperature", double2string(temp, 5));
    pt.put("Data.Structure.NumberOfAtoms", nat);
    pt.put("Data.Structure.NumberOfElements", nkd);

    for (i = 0; i < nkd; ++i) {
        ptree &child = pt.add("Data.Structure.AtomicElements.element", kd_symbol[i]);
        child.put("<xmlattr>.number", i + 1);
    }

    for (i = 0; i < 3; ++i) {
        str_pos[i].clear();
        for (j = 0; j < 3; ++j) {
            str_pos[i] += " " + double2string(lavec_s[j][i]);
        }
    }
    pt.put("Data.Structure.LatticeVector", "");
    pt.put("Data.Structure.LatticeVector.a1", str_pos[0]);
    pt.put("Data.Structure.LatticeVector.a2", str_pos[1]);
    pt.put("Data.Structure.LatticeVector.a3", str_pos[2]);

    pt.put("Data.Structure.Position", "");

    for (i = 0; i < nat; ++i) {
        str_tmp.clear();
        for (j = 0; j < 3; ++j) str_tmp += " " + double2string(xr_s[i][j]);
        ptree &child = pt.add("Data.Structure.Position.pos", str_tmp);
        child.put("<xmlattr>.index", i + 1);
        child.put("<xmlattr>.element", kd_symbol[kd[i]]);
    }

    pt.put("Data.Symmetry.NumberOfTranslations", ntran);
    for (i = 0; i < ntran; ++i) {
        for (j = 0; j < natmin; ++j) {
            ptree &child = pt.add("Data.Symmetry.Translations.map", map_p2s[j][i] + 1);
            child.put("<xmlattr>.tran", i + 1);
            child.put("<xmlattr>.atom", j + 1);
        }
    }

    pt.put("Data.ForceConstants", "");
    str_tmp.clear();

    for (auto it = fc2_in.begin(); it != fc2_in.end(); ++it) {
        ptree &child = pt.add("Data.ForceConstants.HARMONIC.FC2", double2string((*it).fcs_val));

        child.put("<xmlattr>.pair1", boost::lexical_cast<std::string>((*it).atm1 + 1)
                  + " " + boost::lexical_cast<std::string>((*it).xyz1 + 1));
        child.put("<xmlattr>.pair2", boost::lexical_cast<std::string>((*it).atm2 + 1)
                  + " " + boost::lexical_cast<std::string>((*it).xyz2 + 1)
                  + " " + boost::lexical_cast<std::string>((*it).cell_s + 1));
    }

    using namespace boost::property_tree::xml_parser;
    const int indent = 2;

#if BOOST_VERSION >= 105600
    write_xml(xml_out, pt, std::locale(),
              xml_writer_make_settings<ptree::key_type>(' ', indent, widen<std::string>("utf-8")));
#else
    write_xml(xml_out, pt, std::locale(),
        xml_writer_make_settings(' ', indent, widen<char>("utf-8")));
#endif
}

string double2string(const double d, const int nprec)
{
    std::string rt;
    std::stringstream ss;

    ss << std::scientific << std::setprecision(nprec) << d;
    ss >> rt;
    return rt;
}