// This is main.
// This is Car Parrinello molecular dynamics for simulating dynamics of ions near a nanoparticle (NP) surface
// This will power app 2: nanosphere electrostatics lab. the app is part of the nanoparticle characterization framework.
// The framework is expected to launch nanorod electrostatics lab, nanodisc electrostatics lab etc. apps
// Problem : Compute the density profile of ions around a NP; and estimate a zeta potential or effective charge of the ion
/* Useful studies :
		     1. Role of dielectric contrast
		     2. Role of valency of ions
		     3. Role of varying salt concentration
		     4. Role of NP charge
*/

/* @kadupitiya
 * e = E = 78.5 does simple MD; e != E invokes cpmd
 * these parameters produce good converged results that can be compared against the available data (which is plotted in the image sent separately):
 * -a 2.6775 -b 14.28 -e 2 -E 78.5 -V -60 -v 1 -g 1082 -m 6 -t 0.001 -s 10000 -p 100 -f 10 -M 6 -T 0.001 -k 0.0025 -q 0.001 -L 5 -l 5 -S 10000000 -P 100000 -F 100 -X 10000 -U 1000 -Y 500000 -W 1000000 -B 0.025
 * change valency v to 1 (red in image), 2 (green), 3 (blue); everything else can remain the same
 * m = M, k, t = T, q depend on g (the grid size); g depends on e & E, and a. higher the dielectric contrast, that is difference between e and E, higher g is needed to resolve the induced charge density.
 * the selection of these 5 parameters is made manually by monitoring energies, trial and error-- I spent a lot of time just doing that before submitting a useful run to get the ion density. i think there is a possibility of using ML to select these 5 CPMD parameters judiciously. we can pursue this tangentially to nanohub project; hoping it spirals into one of our collaborative hpc/ml projects.
 * the default parameters right now in boost are for a fast simulation that works. as you see they are different than the above parameters for different g etc.:
 * -a 2.6775 -b 14.28 -e 2 -E 78.5 -V -60 -v 1 -g 132 -m 1 -t 0.001 -s 10000 -p 100 -f 10 -M 1 -T 0.001 -k 0.01 -q 1 -L 5 -l 5 -S 50000 -P 10000 -F 100 -X 1000 -U 1000 -Y 10000 -W 10000 -B 0.1
 * the quick_check_polarized_data has a short run from these paramaters for you to perform a quick check against any code changes, if you want.
 * the following is slightly slower but produces a better profile (still far from converged):
 * -a 2.6775 -b 14.28 -e 2 -E 78.5 -V -60 -v 1 -g 132 -m 1 -t 0.001 -s 10000 -p 100 -f 10 -M 1 -T 0.001 -k 0.01 -q 1 -L 5 -l 5 -S 200000 -P 100000 -F 100 -X 10000 -U 10000 -Y 100000 -W 10000 -B 0.1
 * for cpmd, successful simulation demands more than energy conservation:
 * 	1. _ind_*.dat files in verifiles should roughly match the _cpmd_*.dat files in computedfiles
 * 	2. total_induced_charge.dat in outfiles should be very close to 0
 * 	3. track_deviation.dat in outfiles should be small (< 1) and stable-- usually this will happen if the above 2 criteria hold; see also average deviation before R in the output at the end-- should be small
 * 	4. and of course, R should be small like before (could be a bit higher than normal MD)
 */

#include <boost/program_options.hpp>
#include "functions.h"
#include "precalculations.h"
#include "NanoParticleDisk.h"
#include "NanoParticleSphere.h"

//MPI boundary parameters
unsigned int lowerBoundIons;
unsigned int upperBoundIons;
unsigned int sizFVecIons;
unsigned int extraElementsIons;
unsigned int lowerBoundMesh;
unsigned int upperBoundMesh;
unsigned int sizFVecMesh;
unsigned int extraElementsMesh;
mpi::environment env;
mpi::communicator world;

vector<int> condensedIonsPerStep; // Number of condensed ions per step (after equilibrium) at specified frequency

using namespace boost::program_options;

int main(int argc, char *argv[]) {

    // Electrostatic system variables
    double radius;        // radius of the dielectric sphere
    double ein;            // permittivity of inside medium
    double eout;            // permittivity of outside medium
    int counterion_valency;    // counterion valency (positive by convention)
    double counterion_diameter;    // counterion diameter
    int salt_valency_in;        // salt valency inside
    int salt_valency_out;        // salt valency outside
    double salt_conc_in;        // salt concentration outside	(enter in M)
    double salt_conc_out;        // salt concentration outside	(enter in M)
    double saltion_diameter_in;    // inside salt ion diameter	(positive and negative ions assumed to have same diameter at this point)
    double saltion_diameter_out;  // outside salt ion diameter	(positive and negative ions assumed to have same diameter at this point)
    double real_T;            // temperature at which the system of ions is
    double nanoparticle_bare_charge; // bare charge of the NP

    // Simulation related variables
    int total_gridpoints;        // total number of grid points that discretize the continuous interface
    double Q;            // thermostat mass required to generate canonical ensemble
    double fake_T;            // fake temperature useful for Car Parrinello dynamics
    double fake_Q;            // fake thermostat mass required to generate canonical ensemble for fake degrees
    unsigned int chain_length_real;
    unsigned int chain_length_fake;
    double box_radius;        // simulation box size, measured as radius in case of a sphere
    double bin_width_R;        // width of the bins (in R direction) used to compute density profiles
    double bin_width_Z;        // width of the bins (in Z direction) used to compute density profiles
    CONTROL fmdremote;        // remote control for fmd
    CONTROL cpmdremote;        // remote control for cpmd

    // Different parts of the system
    //INTERFACE nanoparticle;        // interface(s)
    vector<PARTICLE> counterion;    // counterions
    vector<PARTICLE> saltion_in;    // salt ions inside
    vector<PARTICLE> saltion_out;    // salt ions outside
    vector<PARTICLE> ion;        // all ions in the system
    vector<VERTEX> s;        // all vertices

    // Analysis
    string np_shape; // np shape
    NanoParticle *nanoParticle;
    VECTOR3D np_pos(0, 0, 0);

    // Get input values from the user
    options_description desc("Usage:\nrandom_mesh <options>");
    desc.add_options()
            ("help,h", "print usage message")
            ("radius,a", value<double>(&radius)->default_value(2.6775),
             "sphere radius")                // enter in nanometers
            ("epsilon_in,e", value<double>(&ein)->default_value(78.5), "dielectric const inside")
            ("epsilon_out,E", value<double>(&eout)->default_value(78.5), "dielectric const outside")
            ("counterion_valency,v", value<int>(&counterion_valency)->default_value(1), "counterion valency")
            ("nanoparticle_charge,V", value<double>(&nanoparticle_bare_charge)->default_value(-60),
             "nanoparticle charge")
            ("salt_valency_in,z", value<int>(&salt_valency_in)->default_value(1), "salt valency inside")
            ("salt_valency_out,Z", value<int>(&salt_valency_out)->default_value(1), "salt valency outside")
            ("salt_conc_in,c", value<double>(&salt_conc_in)->default_value(0.0), "salt concentration inside")
            ("salt_conc_out,C", value<double>(&salt_conc_out)->default_value(0.0), "salt concentration outside")
            ("counterion_diameter,x", value<double>(&counterion_diameter)->default_value(0.357),
             "counterion diameter")            // enter in nanometers
            ("saltion_diameter_in,d", value<double>(&saltion_diameter_in)->default_value(0.357),
             "salt ion diameter inside")        // enter in nanometers
            ("saltion_diameter_out,D", value<double>(&saltion_diameter_out)->default_value(0.357),
             "salt ion diameter outside")        // enter in nanometers
            ("total_gridpoints,g", value<int>(&total_gridpoints)->default_value(132), "gridpoints")
            ("thermostat_mass,Q", value<double>(&Q)->default_value(1.0), "thermostat mass")
            ("chain_length_real,L", value<unsigned int>(&chain_length_real)->default_value(5),
             "chain length for real system: enter L+1 if you want L thermostats")
            ("fake_temperature,k", value<double>(&fake_T)->default_value(0.01), "fake temperature")
            ("fake_thermostat_mass,q", value<double>(&fake_Q)->default_value(1.0), "fake thermostat mass")
            ("chain_length_fake,l", value<unsigned int>(&chain_length_fake)->default_value(5),
             "chain length for fake system: enter L+1 if you want L thermostats")
            ("box_radius,b", value<double>(&box_radius)->default_value(14.28),
             "simulation box radius")        // enter in nanometers
            ("bin_width_R,R", value<double>(&bin_width_R)->default_value(0.1), "bin width R")
            ("bin_width_Z,B", value<double>(&bin_width_Z)->default_value(0.2), "bin width Z")
            ("anneal_fmd,A", value<char>(&fmdremote.anneal)->default_value('n'), "anneal in fmd on?")
            ("fmd_fake_mass,m", value<double>(&fmdremote.fakemass)->default_value(1.0), "fmd fake mass")
            ("cpmd_fake_mass,M", value<double>(&cpmdremote.fakemass)->default_value(1.0), "cpmd fake mass")
            ("fmd_timestep,t", value<double>(&fmdremote.timestep)->default_value(0.001), "time step used in fmd")
            ("cpmd_timestep,T", value<double>(&cpmdremote.timestep)->default_value(0.001), "time step used in cpmd")
            ("fmd_steps,s", value<int>(&fmdremote.steps)->default_value(10000), "steps used in fmd")
            ("cpmd_steps,S", value<int>(&cpmdremote.steps)->default_value(50000), "steps used in cpmd")
            ("fmd_eqm,p", value<int>(&fmdremote.hiteqm)->default_value(100), "production begin (fmd)")
            ("cpmd_eqm,P", value<int>(&cpmdremote.hiteqm)->default_value(10000), "production begin (cpmd)")
            ("fmd_freq,f", value<int>(&fmdremote.freq)->default_value(10), "sample frequency (fmd)")
            ("cpmd_freq,F", value<int>(&cpmdremote.freq)->default_value(100), "sample frequency (cpmd)")
            ("fmd_verify,y", value<int>(&fmdremote.verify)->default_value(0), "verify (fmd)")
            ("cpmd_verify,Y", value<int>(&cpmdremote.verify)->default_value(10000), "verify (cpmd)")
            ("cpmd_writedata,U", value<int>(&cpmdremote.writedata)->default_value(1000), "write data files")
            ("cpmd_extra_compute,X", value<int>(&cpmdremote.extra_compute)->default_value(1000),
             "compute additional (cpmd)")
            ("cpmd_writedensity,W", value<int>(&cpmdremote.writedensity)->default_value(10000), "write density files")
            ("np_shape,G", value<string>(&np_shape)->default_value("Sphere"), "nanoparticle shape")
            ("verbose,I", value<bool>(&cpmdremote.verbose)->default_value(true),
             "verbose true: provides detailed output");

    variables_map vm;
    store(parse_command_line(argc, argv, desc), vm);
    notify(vm);
    if (vm.count("help")) {
        if (world.rank() == 0)
            std::cout << desc << "\n";
        return 0;
    }

    if (world.rank() == 0)
        cout << "\nProgram starts\n";

    int numOfNodes = world.size();
    if (world.rank() == 0) {
#pragma omp parallel default(shared)
        {
            if (omp_get_thread_num() == 0) {
                printf("The app comes with MPI and OpenMP (Hybrid) parallelization)\n");
                printf("Number of MPI processes used %d\n", numOfNodes);
                printf("Number of OpenMP threads per MPI process %d\n", omp_get_num_threads());
                printf("Make sure that number of grid points / ions is greater than %d\n",
                       omp_get_num_threads() * numOfNodes);
            }
        }
    }

    //serve different NP for density bin
    if (np_shape.compare("Sphere") == 0) {
        //Sphere
        vector<BinShell> bin_pos, bin_neg;
        vector<double> sample_density_pos, sample_density_neg;
        NanoParticleSphere np("Sphere", bin_pos, bin_neg, bin_width_R, ion, sample_density_pos, sample_density_neg, 0,
                              0, cpmdremote,
                              np_pos, radius / unitlength, ein, eout,
                              nanoparticle_bare_charge);

        nanoParticle = &np;

    } else {
        //disk

        vector<vector<double> > density_pos, density_neg;
        vector<vector<BinRing> > bin_disk_pos, bin_disk_neg;
        NanoParticleDisk np("Disk", bin_disk_pos, bin_disk_neg, bin_width_R, bin_width_Z, ion, density_pos, density_neg,
                            0, 0, cpmdremote,
                            np_pos, radius / unitlength, ein, eout,
                            nanoparticle_bare_charge);

        nanoParticle = &np;

    }

    // Set up the system
    real_T = 1;

    // set temperature
    // make interface
    nanoParticle->set_up(salt_conc_in, salt_conc_out, salt_valency_in, salt_valency_out, total_gridpoints,
                         box_radius / unitlength);    // set up properties inside and outside the interface

    // If running a standard simulation (charged particle and/or with salt) populate the ions:
    if (abs(nanoparticle_bare_charge) > 0)
        nanoParticle->put_counterions(counterion, counterion_valency, counterion_diameter, ion);                    // put counterions	Note: ion contains all ions
    if (salt_conc_in > 0)
        nanoParticle->put_saltions_inside(saltion_in, salt_valency_in, salt_conc_in, saltion_diameter_in, ion);                // put salt ions inside
    if (salt_conc_out > 0)
        nanoParticle->put_saltions_outside(saltion_out, salt_valency_out, salt_conc_out, saltion_diameter_out, ion);            // put salt ions outside

    //  If the charge is set to zero (for testing), insert test two test ions at chosen positions:
    /*if (nanoparticle_bare_charge == 0)
    {
        ion.push_back(PARTICLE(int(ion.size()) + 1, counterion_diameter, counterion_valency, counterion_valency * 1.0, 1.0, eout, VECTOR3D(5 / unitlength,0,0)));
        ion.push_back(PARTICLE(int(ion.size()) + 1, counterion_diameter, counterion_valency, counterion_valency * 1.0, 1.0, eout, VECTOR3D(6 / unitlength,0,0)));
    }*/

    nanoParticle->discretize(s,radius / unitlength);                                // discretize interface

    // if dielectric environment inside and outside NP are different, NPs get polarized
    if (nanoParticle->ein == nanoParticle->eout)
        nanoParticle->POLARIZED = false;
    else
        nanoParticle->POLARIZED = true;

    if (world.rank() == 0) {
        if (nanoParticle->POLARIZED)
            cout << "NP is polarized " << endl;
        else
            cout << "NP is not polarized " << endl;
    }

    nanoParticle->RANDOMIZE_ION_FEATURES = false;

    // NOTE: sizing the arrays employed in precalculate functions
    for (unsigned int k = 0; k < s.size(); k++) {
        s[k].presumgwEw.resize(s.size());
        s[k].presumgEwEq.resize(s.size());
        s[k].presumgEwEw.resize(s.size());
        s[k].presumfwEw.resize(s.size());
        s[k].presumfEwEq.resize(s.size());
        s[k].presumhEqEw.resize(s.size());
    }

    // could only do precalculate if CPMD
    if (nanoParticle->POLARIZED)
        precalculate(s, nanoParticle);                        // precalculate

    for (unsigned int k = 0; k < s.size(); k++)               // get polar coordinates for the vertices
        s[k].get_polar();

    //make bins
    nanoParticle->make_bins();

    if (world.rank() == 0) {
        // output to screen the parameters of the problem
        cout << "\n";
        if (cpmdremote.verbose)
            cout << "Reduced units: scalefactor entering in Coloumb interaction is " << scalefactor << endl;
        cout << "Units : length (cms) " << unitlength * pow(10.0, -7) << " | " << "energy(ergs) " << unitenergy
             << " | " << "mass(g) " << unitmass << " | " << "time(s) " << unittime << endl;
        cout << "Radius of the dielectric sphere (interface) " << nanoParticle->radius << endl;
        cout << "Nanoparticle charge " << nanoParticle->bare_charge << endl;
        cout << "Permittivity inside " << nanoParticle->ein << endl;
        cout << "Permittivity outside " << nanoParticle->eout << endl;
        if (cpmdremote.verbose)
            cout << "Contrast strength "
                 << 2 * (nanoParticle->eout - nanoParticle->ein) / (nanoParticle->eout + nanoParticle->ein)
                 << endl;
        cout << "Counterion valency " << counterion_valency << endl;
        if (cpmdremote.verbose) {
            cout << "Salt ion valency inside " << salt_valency_in << endl;
            cout << "Salt ion valency outside " << salt_valency_out << endl;
            cout << "Counterion diameter " << counterion_diameter / unitlength << endl;
            cout << "Salt ion diameter inside " << saltion_diameter_in / unitlength << endl;
            cout << "Salt ion diameter outside " << saltion_diameter_out / unitlength << endl;
            cout << "Salt concentration inside " << salt_conc_in << endl;
            cout << "Salt concentration outside " << salt_conc_out << endl;
            cout << "Debye length inside " << nanoParticle->inv_kappa_in << endl;
            cout << "Debye length outside " << nanoParticle->inv_kappa_out << endl;
            cout << "Mean separation inside " << nanoParticle->mean_sep_in << endl;
            cout << "Mean separation outside " << nanoParticle->mean_sep_out << endl;
        }
        cout << "Simulation box (spherical) radius " << nanoParticle->box_radius << endl;
        cout << "Number of counterions " << counterion.size() << endl;
        if (cpmdremote.verbose) {
            cout << "Number of salt ions inside " << saltion_in.size() << endl;
            cout << "Number of salt ions outside " << saltion_out.size() << endl;
            cout << "Number of points discretizing the interface " << s.size() << endl;
            //cout << "Binning width (uniform) " << bin_disk[0][0].width_R << endl;
            nanoParticle->printBinSize();
        }
    }
    // write to files
    // initial configuration
    ofstream initial_configuration("outfiles/initialconfig.dat");
    if (world.rank() == 0)
        for (unsigned int i = 0; i < ion.size(); i++)
            initial_configuration << "ion" << setw(5) << ion[i].id << setw(15) << "charge" << setw(5) << ion[i].q
                                  << setw(15) << "position" << setw(15) << ion[i].posvec << endl;
    initial_configuration.close();

    // initial density
    nanoParticle->compute_initial_density_profile();

    // some calculations before simulation begins
    if (world.rank() == 0)
        cout << "Total charge inside the sphere " << nanoParticle->total_charge_inside(ion) << endl;

    // NEW NOTE : resizing the member arrays Gion and gradGion to store dynamic precalculations in fmd and cpmd force routines
    for (unsigned int k = 0; k < s.size(); k++) {
        s[k].Gion.resize(ion.size());
        s[k].gradGion.resize(ion.size());
    }

    //MPI Boundary calculation for ions
    unsigned int rangeIons = ion.size() / world.size() + 1.5;
    lowerBoundIons = world.rank() * rangeIons;
    upperBoundIons = (world.rank() + 1) * rangeIons - 1;
    extraElementsIons = world.size() * rangeIons - ion.size();
    sizFVecIons = upperBoundIons - lowerBoundIons + 1;
    if (world.rank() == world.size() - 1) {
        upperBoundIons = ion.size() - 1;
        sizFVecIons = upperBoundIons - lowerBoundIons + 1 + extraElementsIons;
    }
    if (world.size() == 1) {
        lowerBoundIons = 0;
        upperBoundIons = ion.size() - 1;
    }

    //MPI Boundary calculation for meshPoints
    unsigned int rangeMesh = s.size() / world.size() + 1.5;
    lowerBoundMesh = world.rank() * rangeMesh;
    upperBoundMesh = (world.rank() + 1) * rangeMesh - 1;
    extraElementsMesh = world.size() * rangeMesh - s.size();
    sizFVecMesh = upperBoundMesh - lowerBoundMesh + 1;
    if (world.rank() == world.size() - 1) {
        upperBoundMesh = s.size() - 1;
        sizFVecMesh = upperBoundMesh - lowerBoundMesh + 1 + extraElementsMesh;
    }
    if (world.size() == 1) {
        lowerBoundMesh = 0;
        upperBoundMesh = s.size() - 1;
    }

    for (unsigned int k = 0; k < s.size(); k++) {
        s[k].w = 0.0;                                // Initialize fake degree value		(unconstrained)
        s[k].wmean = 0.0;
    }

    // Fictitious molecular dynamics
    if (nanoParticle->POLARIZED) {
        if (world.rank() == 0)
            cout << "Polarized charges detected; simulation will proceed using dynamical optimization framework (CPMD)"
                 << endl;
        fmd(s, ion, nanoParticle, fmdremote, cpmdremote);
    } else if (world.rank() == 0)
        cout << "no induced charges; simulation will proceed using simple MD" << endl;

    ofstream induced_density("outfiles/induced_density.dat");
    if (world.rank() == 0)
        // result of fmd
        for (unsigned int k = 0; k < s.size(); k++)
            induced_density << k + 1 << setw(15) << s[k].theta << setw(15) << s[k].phi << setw(15) << s[k].w << setw(15)
                            << s[k].wmean << endl;

    induced_density.close();
    // prepare for cpmd : make real and fake baths

    vector<THERMOSTAT> real_bath;
    if (chain_length_real == 1)
        real_bath.push_back(THERMOSTAT(0, real_T, 3 * ion.size(), 0.0, 0, 0));
    else {
        real_bath.push_back(THERMOSTAT(Q, real_T, 3 * ion.size(), 0, 0, 0));
        while (real_bath.size() != chain_length_real - 1)
            real_bath.push_back(THERMOSTAT(Q / (3 * ion.size()), real_T, 1, 0, 0, 0));
        real_bath.push_back(THERMOSTAT(0, real_T, 3 * ion.size(), 0.0, 0,
                                       0));            // finally, the coding trick: dummy bath (dummy bath always has zero mass)
    }

    vector<THERMOSTAT> fake_bath;
    if (!nanoParticle->POLARIZED)
        fake_T = 0;
//K is used for thermostat fake_T is k
    if (chain_length_fake == 1)
        fake_bath.push_back(THERMOSTAT(0, fake_T, s.size(), 0.0, 0, 0));
    else {
        fake_bath.push_back(THERMOSTAT(fake_Q, fake_T, s.size(), 0, 0, 0));
        while (fake_bath.size() != chain_length_fake - 1)
            fake_bath.push_back(THERMOSTAT(fake_Q / s.size(), fake_T, 1, 0, 0, 0));
        fake_bath.push_back(THERMOSTAT(0, fake_T, s.size(), 0.0, 0,
                                       0));            // finally, the coding trick: dummy bath (dummy bath always has zero mass)
    }
    if (world.rank() == 0 && cpmdremote.verbose) {
        cout << "Number of chains for real system" << setw(3) << real_bath.size() - 1 << endl;
        cout << "Number of chains for fake system" << setw(3) << fake_bath.size() - 1 << endl;
    }

    // Car-Parrinello Molecular Dynamics
    cpmd(ion, s, nanoParticle, real_bath, fake_bath, fmdremote, cpmdremote);

    if (world.rank() == 0) {
        // Post simulation analysis (useful for short runs, but performed otherwise too)
        if (cpmdremote.verbose)
            cout << "MD trust factor R (should be < 0.05) is " << compute_MD_trust_factor_R(cpmdremote.hiteqm) << endl;
        if (nanoParticle->POLARIZED && cpmdremote.verbose)
            cout << "MD trust factor RV (should be < 0.15) is " << compute_MD_trust_factor_R_v(cpmdremote.hiteqm)
                 << endl;
        //auto_correlation_function();
        cout << "Program ends" << endl;
        cout << endl;
    }
    return 0;
}
// End of main


/**
 * @file propagator.h
 * @brief NPDE homework NonLinSchroedingerEquation code
 * @author Oliver Rietmann
 * @date 04.05.2020
 * @copyright Developed at ETH Zurich
 */

#include "propagator.h"

#include <cmath>
#include <complex>

#include <Eigen/Core>
#include <Eigen/SparseCore>
#include <Eigen/SparseLU>

namespace NonLinSchroedingerEquation {

// KineticPropagator
/* SAM_LISTING_BEGIN_1 */
KineticPropagator::KineticPropagator(const SparseMatrixXd &A,
                                     const SparseMatrixXcd &M, double tau) {
  //====================
  // Your code goes here
  //====================
}

Eigen::VectorXcd KineticPropagator::operator()(
    const Eigen::VectorXcd &mu) const {
  //====================
  // Your code goes here
  // Replace mu by its value after a timestep tau
  return mu;
  //====================
}
/* SAM_LISTING_END_1 */

// InteractionPropagator
/* SAM_LISTING_BEGIN_2 */
InteractionPropagator::InteractionPropagator(double tau) {
  //====================
  // Your code goes here
  //====================
}

Eigen::VectorXcd InteractionPropagator::operator()(
    const Eigen::VectorXcd &mu) const {
  //====================
  // Your code goes here
  // Replace mu by its value after a timestep tau
  return mu;
  //====================
}
/* SAM_LISTING_END_2 */

/* SAM_LISTING_BEGIN_3 */
//====================
// Your code goes here
// Change this dummy implementation of the constructor:
SplitStepPropagator::SplitStepPropagator(const SparseMatrixXd &A,
                                         const SparseMatrixXcd &M, double tau) {
}
//====================

Eigen::VectorXcd SplitStepPropagator::operator()(
    const Eigen::VectorXcd &mu) const {
  Eigen::VectorXcd nu(mu.size());
  //====================
  // Your code goes here
  // Implement the Strang splitting
  //====================
  return nu;
}
/* SAM_LISTING_END_3 */

}  // namespace NonLinSchroedingerEquation


// this file mainly refers to libint2's example

#include "jlcxx/jlcxx.hpp"
#include <libint2.hpp>
#include <iostream>
#include <thread>
#include <mutex>
#include <Eigen/Dense>

using Matrix = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;

static std::mutex mtx;

size_t nbasis(const std::vector<libint2::Shell> &shells)
{
    size_t n = 0;
    for (const auto &shell : shells)
        n += shell.size();
    return n;
}

size_t max_nprim(const std::vector<libint2::Shell> &shells)
{
    size_t n = 0;
    for (auto shell : shells)
        n = std::max(shell.nprim(), n);
    return n;
}

int max_l(const std::vector<libint2::Shell> &shells)
{
    int l = 0;
    for (auto shell : shells)
        for (auto c : shell.contr)
            l = std::max(c.l, l);
    return l;
}

std::vector<libint2::Shell> construct_shells(int Nshell, int *l, double *alpha, double *coeff, double *cart)
{
    std::vector<libint2::Shell> shells;
    for (int i = 0; i < Nshell; ++i)
    {
        double *a = alpha + 3 * i;
        double *d = coeff + 3 * i;
        double *r = cart + 3 * i;
        shells.push_back({{a[0], a[1], a[2]},
                          {{l[i], false, {d[0], d[1], d[2]}}},
                          {{r[0], r[1], r[2]}}});
    }
    return shells;
}

std::vector<size_t> map_shell_to_basis_function(const std::vector<libint2::Shell>& shells) {
  std::vector<size_t> result;
  result.reserve(shells.size());

  size_t n = 0;
  for (auto shell: shells) {
    result.push_back(n);
    n += shell.size();
  }

  return result;
}

void Ve(double *ve, int Nshell, int *l, double *alpha, double *d, double *R) {
  const auto shells = construct_shells(Nshell, l, alpha, d, R);
  const auto n = nbasis(shells);
  auto index = [&n](int i, int j, int k, int l) {
        return n * (n * (n * l + k) + j) + i;
  };
  std::memset(ve, 0, n*n*n*n*sizeof(double));
  libint2::Engine engine(libint2::Operator::coulomb, max_nprim(shells), max_l(shells), 0);

  auto shell2bf = map_shell_to_basis_function(shells);

  const auto& buf = engine.results();

  for(auto s1=0; s1!=shells.size(); ++s1) {

    auto bf1_first = shell2bf[s1];
    auto n1 = shells[s1].size();

    for(auto s2=0; s2!=shells.size(); ++s2) {

      auto bf2_first = shell2bf[s2];
      auto n2 = shells[s2].size();

      for(auto s3=0; s3!=shells.size(); ++s3) {

        auto bf3_first = shell2bf[s3];
        auto n3 = shells[s3].size();

        for(auto s4=0; s4!=shells.size(); ++s4) {

          auto bf4_first = shell2bf[s4];
          auto n4 = shells[s4].size();

          engine.compute(shells[s1], shells[s2], shells[s3], shells[s4]);
          const auto* buf_1234 = buf[0];
          if (buf_1234 == nullptr)
            continue;
          for(auto f1=0, f1234=0; f1!=n1; ++f1) {
            const auto bf1 = f1 + bf1_first;
            for(auto f2=0; f2!=n2; ++f2) {
              const auto bf2 = f2 + bf2_first;
              for(auto f3=0; f3!=n3; ++f3) {
                const auto bf3 = f3 + bf3_first;
                for(auto f4=0; f4!=n4; ++f4, ++f1234) {
                  const auto bf4 = f4 + bf4_first;
                  ve[index(bf1, bf4, bf3, bf2)] = buf_1234[f1234];
                }
              }
            }
          }
        }
      }
    }
  }
}

// copy from libint2's example
void compute_2body_fock(double *veff, double* rho, int Nshell, int *l, double *alpha, double *d, double *R) {
  const auto shells = construct_shells(Nshell, l, alpha, d, R);
  const auto n = nbasis(shells);
  Matrix G = Matrix::Zero(n,n);
  Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>> D(rho, n, n);

  // construct the 2-electron repulsion integrals engine
  libint2::Engine engine(libint2::Operator::coulomb, max_nprim(shells), max_l(shells), 0);

  auto shell2bf = map_shell_to_basis_function(shells);

  const auto& buf = engine.results();

  // The problem with the simple Fock builder is that permutational symmetries of the Fock,
  // density, and two-electron integrals are not taken into account to reduce the cost.
  // To make the simple Fock builder efficient we must rearrange our computation.
  // The most expensive step in Fock matrix construction is the evaluation of 2-e integrals;
  // hence we must minimize the number of computed integrals by taking advantage of their permutational
  // symmetry. Due to the multiplicative and Hermitian nature of the Coulomb kernel (and realness
  // of the Gaussians) the permutational symmetry of the 2-e ints is given by the following relations:
  //
  // (12|34) = (21|34) = (12|43) = (21|43) = (34|12) = (43|12) = (34|21) = (43|21)
  //
  // (here we use chemists' notation for the integrals, i.e in (ab|cd) a and b correspond to
  // electron 1, and c and d -- to electron 2).
  //
  // It is easy to verify that the following set of nested loops produces a permutationally-unique
  // set of integrals:
  // foreach a = 0 .. n-1
  //   foreach b = 0 .. a
  //     foreach c = 0 .. a
  //       foreach d = 0 .. (a == c ? b : c)
  //         compute (ab|cd)
  //
  // The only complication is that we must compute integrals over shells. But it's not that complicated ...
  //
  // The real trick is figuring out to which matrix elements of the Fock matrix each permutationally-unique
  // (ab|cd) contributes. STOP READING and try to figure it out yourself. (to check your answer see below)

  // loop over permutationally-unique set of shells
  for(auto s1=0; s1!=shells.size(); ++s1) {

    auto bf1_first = shell2bf[s1]; // first basis function in this shell
    auto n1 = shells[s1].size();   // number of basis functions in this shell

    for(auto s2=0; s2<=s1; ++s2) {

      auto bf2_first = shell2bf[s2];
      auto n2 = shells[s2].size();

      for(auto s3=0; s3<=s1; ++s3) {

        auto bf3_first = shell2bf[s3];
        auto n3 = shells[s3].size();

        const auto s4_max = (s1 == s3) ? s2 : s3;
        for(auto s4=0; s4<=s4_max; ++s4) {

          auto bf4_first = shell2bf[s4];
          auto n4 = shells[s4].size();

          // compute the permutational degeneracy (i.e. # of equivalents) of the given shell set
          auto s12_deg = (s1 == s2) ? 1.0 : 2.0;
          auto s34_deg = (s3 == s4) ? 1.0 : 2.0;
          auto s12_34_deg = (s1 == s3) ? (s2 == s4 ? 1.0 : 2.0) : 2.0;
          auto s1234_deg = s12_deg * s34_deg * s12_34_deg;

          engine.compute(shells[s1], shells[s2], shells[s3], shells[s4]);
          const auto* buf_1234 = buf[0];
          if (buf_1234 == nullptr)
            continue; // if all integrals screened out, skip to next quartet

          // ANSWER
          // 1) each shell set of integrals contributes up to 6 shell sets of the Fock matrix:
          //    F(a,b) += (ab|cd) * D(c,d)
          //    F(c,d) += (ab|cd) * D(a,b)
          //    F(b,d) -= 1/4 * (ab|cd) * D(a,c)
          //    F(b,c) -= 1/4 * (ab|cd) * D(a,d)
          //    F(a,c) -= 1/4 * (ab|cd) * D(b,d)
          //    F(a,d) -= 1/4 * (ab|cd) * D(b,c)
          // 2) each permutationally-unique integral (shell set) must be scaled by its degeneracy,
          //    i.e. the number of the integrals/sets equivalent to it
          // 3) the end result must be symmetrized
          for(auto f1=0, f1234=0; f1!=n1; ++f1) {
            const auto bf1 = f1 + bf1_first;
            for(auto f2=0; f2!=n2; ++f2) {
              const auto bf2 = f2 + bf2_first;
              for(auto f3=0; f3!=n3; ++f3) {
                const auto bf3 = f3 + bf3_first;
                for(auto f4=0; f4!=n4; ++f4, ++f1234) {
                  const auto bf4 = f4 + bf4_first;

                  const auto value = buf_1234[f1234];

                  const auto value_scal_by_deg = value * s1234_deg;

                  G(bf1,bf2) += D(bf3,bf4) * value_scal_by_deg;
                  G(bf3,bf4) += D(bf1,bf2) * value_scal_by_deg;
                  G(bf1,bf3) -= 0.25 * D(bf2,bf4) * value_scal_by_deg;
                  G(bf2,bf4) -= 0.25 * D(bf1,bf3) * value_scal_by_deg;
                  G(bf1,bf4) -= 0.25 * D(bf2,bf3) * value_scal_by_deg;
                  G(bf2,bf3) -= 0.25 * D(bf1,bf4) * value_scal_by_deg;
                }
              }
            }
          }

        }
      }
    }
  }

  // symmetrize the result
  for(int i=0; i<n; ++i)
  {
    for(int j=0; j<n; ++j)
    {
      veff[i*n + j] = (G(i,j) + G(j,i))/4.;
    }
  }
}

JLCXX_MODULE define_julia_module(jlcxx::Module &mod)
{
    mod.method("initialize", [](){libint2::initialize();});
    mod.method("Ve", &Ve);
    mod.method("Veff", &compute_2body_fock);
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_BESSEL_FUNCTIONS_SCALAR_Y0_HPP_INCLUDED
#define NT2_BESSEL_FUNCTIONS_SCALAR_Y0_HPP_INCLUDED

#include <nt2/bessel/functions/y0.hpp>
#include <nt2/include/constants/digits.hpp>
#include <nt2/include/constants/real.hpp>
#include <nt2/include/functions/scalar/abs.hpp>
#include <nt2/include/functions/scalar/oneminus.hpp>
#include <nt2/include/functions/scalar/sqr.hpp>
#include <nt2/include/functions/scalar/sqrt.hpp>
#include <nt2/include/functions/scalar/log.hpp>
#include <nt2/include/functions/scalar/j0.hpp>
#include <nt2/include/functions/scalar/rec.hpp>
#include <nt2/include/functions/scalar/sin.hpp>
#include <boost/simd/sdk/math.hpp>

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is arithmetic_
/////////////////////////////////////////////////////////////////////////////
namespace nt2 { namespace ext
{
  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::y0_, tag::cpu_
                            , (A0)
                            , (scalar_< arithmetic_<A0> >)
                            )
  {

    typedef typename boost::dispatch::meta::as_floating<A0>::type result_type;

    NT2_FUNCTOR_CALL(1)
    {
      return nt2::y0(result_type(a0));
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is double
/////////////////////////////////////////////////////////////////////////////
namespace nt2 { namespace ext
{
  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::y0_, tag::cpu_
                            , (A0)
                            , (scalar_< double_<A0> >)
                            )
  {

    typedef A0 result_type;

    NT2_FUNCTOR_CALL(1)
    {
      if (is_ltz(a0)||is_nan(a0)) return Nan<result_type>();
      if (is_inf(a0)) return Zero<result_type>();
      if (is_eqz(a0)) return Minf<result_type>();
#if defined(BOOST_SIMD_HAS__Y0)
      return ::_y0(a0);
#elif defined(BOOST_SIMD_HAS_Y0)
      return ::y0(a0);
#else
#error y0 not supported
#endif
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is float
/////////////////////////////////////////////////////////////////////////////
namespace nt2 { namespace ext
{
  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::y0_, tag::cpu_
                            , (A0)
                            , (scalar_< single_<A0> >)
                            )
  {

    typedef A0 result_type;

    NT2_FUNCTOR_CALL(1)
    {
      typedef typename meta::scalar_of<A0>::type stype;
      if (is_ltz(a0)||is_nan(a0)) return Nan<result_type>();
      if (is_inf(a0)) return Zero<result_type>();
      if (is_eqz(a0)) return Minf<result_type>();
      if (a0 <= Two<A0>())
      {
        A0 z = sqr(a0);
        A0 p2 = (z-single_constant<A0, 0x3edd4b3a>())*
          horner< NT2_HORNER_COEFF_T(stype, 5,
                                     (0x33cb0920,
                                      0xb71ded71,
                                      0x3a0c1a3e,
                                      0xbc81c8f4,
                                      0x3e2edb4f
                                       ) ) > (z);
        return p2+single_constant<A0, 0x3f22f983>()*nt2::log(a0)*nt2::j0(a0);
      }
      A0 q = rec(a0);
      A0 w = nt2::sqrt(q);
      A0 p3 = w *
        horner< NT2_HORNER_COEFF_T(stype, 8,
                                   (0xbd8c100e,
                                    0x3e3ef887,
                                    0xbe5ba616,
                                    0x3df54214,
                                    0xbb69539e,
                                    0xbd4b8bc1,
                                    0xb6612dc2,
                                    0x3f4c422a
                                     ) ) > (q);
       w = sqr(q);
       A0 xn =  q*
         horner< NT2_HORNER_COEFF_T(stype, 8,
                                    (0x4201aee0,
                                     0xc2113945,
                                     0x418c7f6a,
                                     0xc09f3306,
                                     0x3f8040aa,
                                     0xbe46a57f,
                                     0x3d84ed6e,
                                     0xbdffff97
                                      ) ) > (w)-Pio_4<A0>();
      return p3*nt2::sin(xn+a0);
    }
  };
} }

#endif


/* Copyright (C) 5/23/18 Julian Stobbe - All Rights Reserved
 * You may use, distribute and modify this code under the
 * terms of the MIT license.
 *
 * You should have received a copy of the MIT license with
 * this file.
 */

#ifndef SRC_BLACKSCHOLES_NETWORK_HPP_
#define SRC_BLACKSCHOLES_NETWORK_HPP_

#define USE_SPARSE_INTERNAL 1

#include <stdexcept>
#include <algorithm>

#include "easylogging++.h"
// Tina's Random Number Generator
#include "trng/yarn2.hpp"
#include "trng/uniform01_dist.hpp"
#include "trng/lognormal_dist.hpp"
#include "trng/correlated_normal_dist.hpp"
#include "Eigen/Dense"
#include "Eigen/Sparse"
#include <Eigen/SparseLU>

#ifdef USE_MPI
#include <boost/mpi.hpp>
#endif

#include <boost/serialization/vector.hpp>
#include <boost/serialization/optional.hpp>

#include "ValuationConfig.h"
#include "StatAcc.hpp"
#include "RndGraphGen.hpp"

struct BSParameters
{
    double T, r, sigma, S0, default_prob_scale;
};

struct NetworkParameters
{
    int N, which_to_set;
    double conn, colSums;
};

class BlackScholesNetwork
{
    using Mat = Eigen::MatrixXd;
    using Vec = Eigen::VectorXd;
private:
    double T, r, exprt;
    int N;
    bool initialized;
    Vec x, S0, St, St_full, debt, solvent, sigma;
    bool jacobian_set;
    //Mat sigma_diag;
#if USE_SPARSE_INTERNAL
    Eigen::SparseLU<Eigen::SparseMatrix<double, Eigen::ColMajor>> lu;
    Eigen::SparseMatrix<double, Eigen::ColMajor> Id;
    Eigen::SparseMatrix<double, Eigen::ColMajor> M;
    //Eigen::SparseMatrix<double, Eigen::ColMajor> GreekMat;
    //Eigen::SparseMatrix<double, Eigen::ColMajor> Jrs;
    //Eigen::SparseMatrix<double, Eigen::ColMajor> J_a;
    //Eigen::SparseMatrix<double, Eigen::ColMajor> Z;
#else
    Mat M;
    //Mat J_a;
    Mat Id;
    Eigen::PartialPivLU<Eigen::MatrixXd> lu;
#endif
    Mat GreekMat;

    void set_solvent();

public:
    BlackScholesNetwork(const BlackScholesNetwork&) = delete;

    /* BlackScholesNetwork& operator=(const BlackScholesNetwork& rhs) = delete;
    {
        T = rhs.T;
        r = rhs.r;
        N = rhs.N;
        M = rhs.M;
        x = rhs.x;
        S0 = rhs.S0;
        St = rhs.St;
        debt = rhs.debt;
        solvent = rhs.solvent;
        exprt = rhs.exprt;
    }*/

    //BlackScholesNetwork()
    //{
        //LOG(WARNING) << "Default constructor for BlackScholesNetwork used. This could be unintentional.";
    //    initialized = false;
    //}

    /**
     * @brief
     * @param T         maturity
     * @param r         interest rate
     */
    BlackScholesNetwork(const Eigen::Ref<Vec>& S0, const Eigen::Ref<Vec>& debt, const Eigen::Ref<Vec>& sigma_, const double T,const double r);

    /**
     * @brief
     * @param M         Combined cross equity and cross debt matrix
     * @param assets    exogenous assets
     * @param debt      debts
     * @param T         maturity
     * @param r         interest rate
     */
    BlackScholesNetwork(const Eigen::Ref<Mat>& M, const Eigen::Ref<Vec>& S0, const Eigen::Ref<Vec>& assets, const Eigen::Ref<Vec>& debt, const Eigen::Ref<Vec>& sigma_, const double T, const double r);

    /**
     * @brief               Finds the fixed point of the cross holding problem at maturity T.
     * @param iterations    maximum number of self consistency iterations.
     * @return              returns vector of value of debt and value of equity.
     */
    const Mat run_valuation(unsigned int iterations);

    inline void set_St(const Vec &st) {
        if(st.size() != N)
            throw std::logic_error("Mismatch between cross ownership matrix and assets!");
        St = st;
        St_full = S0.array()*St.array();
    }

    void re_init(const Eigen::Ref<const Mat>& M_new)
    {
        if(M_new.rows() != S0.size()) throw std::logic_error("re-initialized with wrong M size!");
#if USE_SPARSE_INTERNAL
        M = M_new.sparseView();
        M.makeCompressed();
#else
        M = M_new;
#endif
    }

    void re_init(const Eigen::Ref<const Mat>& M_new, const Eigen::Ref<const Vec> &s0, const Eigen::Ref<const Vec> &d, const Eigen::Ref<const Vec> &sigma_) {
        N = M_new.rows();
        //Jrs.resize(2*N, 2*N);
        //J_a.resize(2*N, N);
        x.resize(2*N);
        x = Eigen::VectorXd::Zero(2*N);
#if USE_SPARSE_INTERNAL
        M = M_new.sparseView();
        M.makeCompressed();
        Id.resize(2*N, 2*N);
        Id.setIdentity();
#else
        M = M_new;
        lu = Eigen::PartialPivLU<Eigen::MatrixXd>(2*N);
        Id.resize(2*N, 2*N);
        Id.setIdentity();
#endif
        St.resize(N);
        St_full.resize(N);
        St = Eigen::VectorXd::Constant(N, 1.0);
        if(s0.size() != N)
            throw std::logic_error("Mismatch between cross ownership matrix and assets prefactor!");
        S0 = s0;
        sigma.resize(N);
        sigma = sigma_;
        if(d.size() != N)
            throw std::logic_error("Mismatch between cross ownership matrix and debts!");
        debt = d;
        jacobian_set = false;
        initialized = true;
    }

    //@TODO: consistent return typex
    inline const Vec get_S0() const {
        return S0;
    }

    inline const Vec get_St() const {
        LOG(WARNING) << "St does NOT contain S0!";
        return St;
    }

    inline const Vec get_debt() const {
        return debt;
    }

    inline const Mat get_M() const {
#if USE_SPARSE_INTERNAL
        return Eigen::MatrixXd(M);
#else
        return M;
#endif
    }

    const Vec get_assets();

    //@TODO: move implementation to *.cpp
    const Vec get_rs() {
        return x;
    }

    const Vec get_valuation() {
        Vec res = (x.head(N) + x.tail(N));
        return res;
    }

   const Vec get_solvent() {
        return solvent;
   }

    void set_jacobian();

    Mat get_delta_v1() const;

    Mat get_vega(const Eigen::MatrixXd Z) const;

    Mat get_theta(const Eigen::MatrixXd Z) const;

    Mat get_rho() const;
    /*
    std::vector<double> ret;
    ret.resize(N);
    Eigen::VectorXd::Map(&ret[0], N) = x.head(N) + x.tail(N);
    return ret;*/

    Eigen::MatrixXd get_pi() const;

    Eigen::MatrixXd get_scalar_allGreeks(const Eigen::Ref<const Mat>& Z) const;

    void debug_print();

};
#endif // SRC_MULTIVAR_BLACKSCHOLES_HPP_


/**
 * @file OcpDescription.hpp
 * @author Brahayam Ponton (brahayam.ponton@tuebingen.mpg.de)
 * @license License BSD-3-Clause
 * @copyright Copyright (c) 2019, New York University and Max Planck Gesellschaft.
 * @date 2019-10-07
 */

#pragma once

#include <vector>
#include <Eigen/Dense>
#include <solver_lqr/SolverLqrSetting.hpp>

namespace solverlqr {

  //! Class to define State of OCP
  class StateBase
  {
    public:
	  StateBase(){}
	  StateBase(int xdim) { state_ = Eigen::VectorXd(xdim).setZero(); }
	  ~StateBase(){}

	  StateBase& operator= (const StateBase& rhs) { state_ = rhs.stateVector(); return *this; }
	  StateBase  operator* (double scalar) const { StateBase new_state; new_state.stateVector() = scalar*state_; return new_state; }
	  StateBase& operator*=(double scalar) { state_ *= scalar; return *this; }
	  StateBase  operator+ (const StateBase& rhs) const { StateBase new_state; new_state.stateVector() = state_+rhs.stateVector(); return new_state; }
	  StateBase& operator+=(const StateBase& rhs) { state_ += rhs.stateVector(); return *this; }
	  StateBase  operator/ (double scalar) const { StateBase new_state; new_state.stateVector() = state_/scalar; return new_state; }
	  StateBase& operator/=(double scalar) { state_ /= scalar; return *this; }

      Eigen::VectorXd& stateVector() { return state_; }
      const Eigen::VectorXd& stateVector() const { return state_; }
      void resize(int xdim) { state_ = Eigen::VectorXd(xdim).setZero(); }

    private:
      Eigen::VectorXd state_;
  };
  StateBase operator* (double scalar, const StateBase& rhs);
  StateBase operator- (const StateBase& lhs, const StateBase& rhs);

  class StateSequence
  {
    public:
      StateSequence(){}
      StateSequence(int tdim, int xdim) { this->resize(tdim, xdim); }
      ~StateSequence(){}

      void resize(int tdim, int xdim);
      void setRandom(double scaling = 1.0);
      int size() const { return stateseq_.size(); }
      StateBase& state(int id) { return stateseq_[id]; }
      const StateBase& state(int id) const { return stateseq_[id]; }

    private:
      std::vector<StateBase> stateseq_;
  };

  //! Class to define Control of OCP
  class ControlBase
  {
    public:
      ControlBase(){}
      ControlBase(int xdim, int udim);
      ~ControlBase(){}

      ControlBase& operator= (const ControlBase& rhs);
      ControlBase  operator* (double scalar) const;
      ControlBase& operator*=(double scalar);
      ControlBase  operator+ (const ControlBase& rhs) const;
      ControlBase& operator+=(const ControlBase& rhs);

      void setZero() { feedforward_.setZero(); }
      Eigen::MatrixXd& feedback() { return feedback_; }
      Eigen::VectorXd& feedforward() { return feedforward_; }
      const Eigen::MatrixXd& feedback() const { return feedback_; }
      const Eigen::VectorXd& feedforward() const { return feedforward_; }
      void setRandom(double scaling = 1.0) { feedforward_.setRandom(); feedforward_ *= scaling; }

    private:
      Eigen::MatrixXd feedback_;
      Eigen::VectorXd feedforward_;
  };
  ControlBase operator*(double lhs_scalar, const ControlBase& rhs);

  class ControlSequence
  {
    public:
      ControlSequence(){}
      ControlSequence(int tdim, int xdim, int udim) { this->resize(tdim, xdim, udim); }
      ~ControlSequence(){}

      void setRandom(double scaling = 1.0);
      void resize(int tdim, int xdim, int udim);
      int size() const { return controlseq_.size(); }
      ControlBase& control(int id) { return controlseq_[id]; }
      const ControlBase& control(int id) const { return controlseq_[id]; }

      ControlSequence& operator= (const ControlSequence& rhs);
      ControlSequence  operator* (double scalar) const;
      ControlSequence& operator*=(double scalar);
      ControlSequence  operator+ (const ControlSequence& rhs) const;
      ControlSequence& operator+=(const ControlSequence& rhs);

      static double controlSequenceGradientNorm(const ControlSequence& u1, const ControlSequence& u2);

    private:
      std::vector<ControlBase> controlseq_;
  };
  ControlSequence operator*(double lhs_scalar, const ControlSequence& rhs);

  //! Optimal Control Problem Description Class
  class OcpBase
  {
    public:
	  OcpBase(){}
      ~OcpBase(){}
      void initialize(const SolverLqrSetting& setting);

      const double& dt() const { return this->getLqrSetting().get(SolverLqrDoubleParam_TimeStep); }
      const int& tdim() const { return this->getLqrSetting().get(SolverLqrIntParam_TimeDimension); }
      const int& xdim() const { return this->getLqrSetting().get(SolverLqrIntParam_StateDimension); }
      const int& udim() const { return this->getLqrSetting().get(SolverLqrIntParam_ControlDimension); }

      StateSequence& stateSeq() { return stateseq_; }
      ControlSequence& controlSeq() { return controlseq_; }
      const StateSequence& stateSeq() const { return stateseq_; }
      const ControlSequence& controlSeq() const { return controlseq_; }
      const SolverLqrSetting& getLqrSetting() const { return *setting_; }

      virtual void configure(const YAML::Node& user_parameters) = 0;
      virtual Eigen::MatrixXd processNoiseFilter(int time_id) const;
      virtual Eigen::MatrixXd measurementNoiseFilter(int time_id) const;
      virtual StateBase dynamics(const StateBase& state, const ControlBase& control, int time_id) = 0;
      void internal_dynamics(const StateBase& state, const ControlBase& control, StateBase& new_state, int time_id);
      virtual double objective(const StateBase& state, const ControlBase& control, int time_id, bool is_final_timestep) = 0;

    private:
      friend class Estimator;
      friend class ForwardPass;
      friend class BackwardPass;
      friend class FiniteDifferences;

    private:
      StateSequence stateseq_;
      ControlSequence controlseq_;
      const SolverLqrSetting* setting_;
  };

}


/**
 This file is part of Poisson Image Editing.

 Copyright Christoph Heindl 2015

 Poisson Image Editing 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.

 Poisson Image Editing 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 Poisson Image Editing.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <blend/clone.h>
#include <blend/poisson_solver.h>
#include <opencv2/opencv.hpp>
#pragma warning (push)
#pragma warning (disable: 4244)
#include <Eigen/Sparse>
#include <Eigen/Dense>
#pragma warning (pop)

namespace blend {

    namespace detail {
        bool findOverlap(cv::InputArray background,
                         cv::InputArray foreground,
                         int offsetX, int offsetY,
                         cv::Rect &rBackground,
                         cv::Rect &rForeground)
        {
            cv::Mat bg = background.getMat();
            cv::Mat fg = foreground.getMat();

            rBackground = cv::Rect(0, 0, bg.cols, bg.rows) &
                          cv::Rect(offsetX, offsetY, fg.cols, fg.rows);

            // Compensate for negative offsets. If offset < 0, offset in foreground is positive.
            rForeground = cv::Rect(std::max<int>(-offsetX, 0),
                                   std::max<int>(-offsetY, 0),
                                   rBackground.width,
                                   rBackground.height);

            return rForeground.area() > 0;

        }

        void computeMixedGradientVectorField(cv::InputArray background,
                                             cv::InputArray foreground,
                                             cv::OutputArray vx_,
                                             cv::OutputArray vy_)
        {
            cv::Mat bg = background.getMat();
            cv::Mat fg = foreground.getMat();

            const int channels = bg.channels();

            vx_.create(bg.size(), CV_MAKETYPE(CV_32F, channels));
            vy_.create(bg.size(), CV_MAKETYPE(CV_32F, channels));

            cv::Mat vx = vx_.getMat();
            cv::Mat vy = vy_.getMat();

            cv::Mat kernelx = (cv::Mat_<float>(1, 3) << -0.5, 0, 0.5);
            cv::Mat kernely = (cv::Mat_<float>(3, 1) << -0.5, 0, 0.5);

            cv::Mat vxf, vyf, vxb, vyb;
            cv::filter2D(fg, vxf, CV_32F, kernelx, cv::Point(-1,-1), 0, cv::BORDER_REPLICATE);
            cv::filter2D(fg, vyf, CV_32F, kernely, cv::Point(-1,-1), 0, cv::BORDER_REPLICATE);
            cv::filter2D(bg, vxb, CV_32F, kernelx, cv::Point(-1,-1), 0, cv::BORDER_REPLICATE);
            cv::filter2D(bg, vyb, CV_32F, kernely, cv::Point(-1,-1), 0, cv::BORDER_REPLICATE);

            for(int id = 0; id <= (vx.rows * vx.cols * channels - channels); ++id)
            {
                const cv::Vec2f g[2] = {
                    cv::Vec2f(vxf.ptr<float>()[id], vyf.ptr<float>()[id]),
                    cv::Vec2f(vxb.ptr<float>()[id], vyb.ptr<float>()[id])
                };

                int which = (g[0].dot(g[0]) > g[1].dot(g[1])) ? 0 : 1;

                vx.ptr<float>()[id] = g[which][0];
                vy.ptr<float>()[id] = g[which][1];
            }
        }

        void computeWeightedGradientVectorField(cv::InputArray background,
                                                cv::InputArray foreground,
                                                cv::OutputArray vx,
                                                cv::OutputArray vy,
                                                float weightForeground)
        {

            cv::Mat bg = background.getMat();
            cv::Mat fg = foreground.getMat();

            cv::Mat kernelx = (cv::Mat_<float>(1, 3) << -0.5, 0, 0.5);
            cv::Mat kernely = (cv::Mat_<float>(3, 1) << -0.5, 0, 0.5);

            cv::Mat vxf, vyf, vxb, vyb;
            cv::filter2D(fg, vxf, CV_32F, kernelx, cv::Point(-1,-1), 0, cv::BORDER_REPLICATE);
            cv::filter2D(fg, vyf, CV_32F, kernely, cv::Point(-1,-1), 0, cv::BORDER_REPLICATE);
            cv::filter2D(bg, vxb, CV_32F, kernelx, cv::Point(-1,-1), 0, cv::BORDER_REPLICATE);
            cv::filter2D(bg, vyb, CV_32F, kernely, cv::Point(-1,-1), 0, cv::BORDER_REPLICATE);

            cv::addWeighted(vxf, weightForeground, vxb, 1.f - weightForeground, 0, vx);
            cv::addWeighted(vyf, weightForeground, vyb, 1.f - weightForeground, 0, vy);
        }
    }

    void seamlessClone(cv::InputArray background,
                       cv::InputArray foreground,
                       cv::InputArray foregroundMask,
                       int offsetX,
                       int offsetY,
                       cv::OutputArray destination,
                       CloneType type)
    {

        // Copy original background as we only solve for the overlapping area of the translated foreground mask.
        background.getMat().copyTo(destination);

        // Find overlapping region. We will only perform on this region
        cv::Rect rbg, rfg;
        if (!detail::findOverlap(background, foreground, offsetX, offsetY, rbg, rfg))
            return;

        // Compute the guidance vector field
        cv::Mat vx, vy;
        switch (type) {
            case CLONE_FOREGROUND_GRADIENTS:
                detail::computeWeightedGradientVectorField(background.getMat()(rbg),
                                                           foreground.getMat()(rfg),
                                                           vx, vy,
                                                           1.f);
                break;

            case CLONE_AVERAGED_GRADIENTS:
                detail::computeWeightedGradientVectorField(background.getMat()(rbg),
                                                           foreground.getMat()(rfg),
                                                           vx, vy,
                                                           0.5f);
                break;

            case CLONE_MIXED_GRADIENTS:
                detail::computeMixedGradientVectorField(background.getMat()(rbg),
                                                        foreground.getMat()(rfg),
                                                        vx, vy);
                break;

            default:
                break;
        }

        // For the Poisson equation the divergence of the guidance field is necessary.
        cv::Mat vxx, vyy;
        cv::Mat kernelx = (cv::Mat_<float>(1, 3) << -0.5, 0, 0.5);
        cv::Mat kernely = (cv::Mat_<float>(3, 1) << -0.5, 0, 0.5);
        cv::filter2D(vx, vxx, CV_32F, kernelx);
        cv::filter2D(vy, vyy, CV_32F, kernely);

        cv::Mat f = vxx + vyy;

        cv::Mat boundaryMask(rfg.size(), CV_8UC1);
        cv::threshold(foregroundMask.getMat()(rfg), boundaryMask, constants::UNKNOWN, constants::DIRICHLET_BD, cv::THRESH_BINARY_INV);
        cv::rectangle(boundaryMask, cv::Rect(0, 0, boundaryMask.cols, boundaryMask.rows), constants::DIRICHLET_BD, 1);

        cv::Mat boundaryValues(rfg.size(), CV_MAKETYPE(CV_32F, background.channels()));
        background.getMat()(rbg).convertTo(boundaryValues, CV_32F);

        // Solve Poisson equation
        cv::Mat result;
        solvePoissonEquations(f,
                              boundaryMask,
                              boundaryValues,
                              result);

        // Copy result to destination image.
        result.convertTo(destination.getMat()(rbg), CV_8U);

    }

}


//
// $Id: Interpolator.hpp 9294 2016-01-15 20:00:16Z chambm $
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// The Original Code is the Quameter software.
//
// The Initial Developer of the Original Code is Matt Chambers.
//
// Copyright 2011 Vanderbilt University
//
// Contributor(s):
//

#include "spline.hpp"
#include <vector>
#include <boost/shared_array.hpp>

namespace freicore {

using std::vector;

struct Interpolator
{
    Interpolator(const vector<double>& x, const vector<double>& y)
    {
        _size = x.size();

        if (_size < 4)
            return;

        BOOST_ASSERT(x.size() == y.size());

        //_ypp.reset(spline_cubic_set(_size, const_cast<double*>(&x[0]), const_cast<double*>(&y[0]), 1, 0, 1, 0));

        _ypp.reset(new double[_size]);
        spline_pchip_set(_size, const_cast<double*>(&x[0]), const_cast<double*>(&y[0]), _ypp.get());
    }

    // uses interpolation on piecewise cubic splines to make an f(x) function evenly spaced on the x axis
    void resample(vector<double>& x, vector<double>& y) const
    {
        BOOST_ASSERT(_size == x.size());
        BOOST_ASSERT(_size == y.size());

        if (x.size() < 4)
            return;

        //double minSampleSize = x[1] - x[0];
        //for (int i=2; i < _size; ++i)
            //minSampleSize = min(minSampleSize, x[i] - x[i-1]);

        vector<double> sampleDeltas(_size, 0);
        for (int i=1; i < _size; ++i)
            sampleDeltas[i] = x[i] - x[i-1];
        sort(sampleDeltas.begin(), sampleDeltas.end());

        double minSampleSize = sampleDeltas[min(size_t(_size - 1), size_t(_size*0.01 + 1))];

        size_t newSize = (x.back() - x.front()) / minSampleSize;

        //double ypval, yppval;
        vector<double> newX, newY;
        newX.reserve(newSize);
        newY.reserve(newSize);
        newX.push_back(x[0]);
        newY.push_back(y[0]);
        for (size_t i=1; newX.back() < x.back(); ++i)
        {
            newX.push_back(newX.back() + minSampleSize);
            newY.push_back(0);
            int left = i-1;
            //spline_cubic_val2(_size, &x[0], newX.back(), &left, &y[0], _ypp.get(), &newY.back(), &ypval, &yppval);
            spline_pchip_val(_size, &x[0], &y[0], _ypp.get(), 1, &newX.back(), &newY.back());
        }
        swap(x, newX);
        swap(y, newY);
    }

    double interpolate(const vector<double>& xs, const vector<double>& ys, double x) const
    {
        if (x < xs.front() || x > xs.back() || xs.size() < 4)
            return 0;

        //double ypval, yppval;
        //return spline_cubic_val(_size, const_cast<double*>(&xs[0]), const_cast<double*>(&ys[0]), _ypp.get(), x, &ypval, &yppval);

        double y;
        spline_pchip_val(_size, const_cast<double*>(&xs[0]), const_cast<double*>(&ys[0]), _ypp.get(), 1, &x, &y);
        return y;
    }

    private:
    boost::shared_array<double> _ypp;
    int _size;
};

} // namespace freicore


#include <deal.II/base/utilities.h>

#include "poisson.h"

/**
 * \mainpage Adaptive FEM for Poisson problem
 *
 * This is the starting code for laboratory number 7 of the course "Theory and
 * Practice of Finite Element methods".
 *
 * We solve the Poisson problem
 *
 * \[
 * \begin{split}
 * -\Delta u &= f \quad \text{ in } \Omega\\
 * n\cdot \nabla u &= g_N \quad \text{ on } \partial\Omega_N\\
 *  u &= g_D \quad \text{ on } \partial\Omega_D
 * \end{split}
 * \]
 *
 * on convex and Lipschitz domains $\Omega$, using the Finite Element method,
 * and the deal.II library (www.dealii.org).
 */

int
main(int argc, char **argv)
{
  Utilities::MPI::MPI_InitFinalize init(argc, argv);
  std::string                      par_name = "";
  if (argc > 1)
    par_name = argv[1];

  deallog.depth_console(2);
  Poisson<2> laplace_problem;
  laplace_problem.initialize(par_name);
  laplace_problem.run();
  return 0;
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_CONSTANT_PIO_6_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_PIO_6_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!

    @ingroup group-constant

    Constant \f$\frac\pi{6}\f$.

    @par Semantic:

    For type T:

    @code
    T r = Pio_6<T>();
    @endcode

    is similar to:

    @code
    T r = Pi<T>()/Six<T>();
    @endcode

    @return a value of type T

**/
  template<typename T> T Pio_6();

  namespace functional
  {
    /*!
      @ingroup group-callable-constant

      Constant \f$\frac\pi{6}\f$.

      Generate the  constant pio_6.

      @return The Pio_6 constant for the proper type
    **/
    const boost::dispatch::functor<tag::pio_6_> pio_6 = {};
  }
} }
#endif

#include <boost/simd/constant/definition/pio_6.hpp>
#include <boost/simd/arch/common/scalar/constant/constant_value.hpp>
#include <boost/simd/arch/common/simd/constant/constant_value.hpp>

#endif


#include "optimization_utils.hpp"
#include <boost/algorithm/clamp.hpp>
#include <random>

namespace optimization
{
std::vector<std::vector<double>> get_initial_positions(double left_bound, double right_bound, int dimension, int number_of_agents)
{
    std::vector<std::vector<double>> positions(number_of_agents, std::vector<double>(dimension, 0.));
    for (auto &agent : positions)
        for (auto &coord : agent)
            coord = get_random(left_bound, right_bound);

    return positions;
}

double get_random(double left_bound, double right_bound)
{
    static std::random_device rd;
    static std::mt19937 gen(rd());
    std::uniform_real_distribution<double> dist(left_bound, right_bound);

    return dist(gen);
}

std::vector<std::vector<double>> &clip_positions(std::vector<std::vector<double>> &positions, double left_bound, double right_bound)
{
    for (auto &agent : positions)
        for (auto &coord : agent)
            coord = boost::algorithm::clamp(coord, left_bound, right_bound);

    return positions;
}
}


/*

Copyright (c) 2005-2015, University of Oxford.
All rights reserved.

University of Oxford means the Chancellor, Masters and Scholars of the
University of Oxford, having an administrative office at Wellington
Square, Oxford OX1 2JD, UK.

This file is part of Chaste.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 * Neither the name of the University of Oxford nor the names of its
   contributors may be used to endorse or promote products derived from this
   software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#ifndef NAGAIHONDAFORCE_HPP_
#define NAGAIHONDAFORCE_HPP_

#include "ChasteSerialization.hpp"
#include <boost/serialization/base_object.hpp>
#include "Exception.hpp"

#include "AbstractForce.hpp"
#include "VertexBasedCellPopulation.hpp"

#include <iostream>

/**
 * A force class for use in vertex-based simulations, based on a mechanical
 * model proposed by T. Nagai and H. Honda ("A dynamic cell model for the formation
 * of epithelial tissues", Philosophical Magazine Part B 81:699-719). In contrast to the force proposed
 * by Nagai and Honda this force has an additional force term implemented that scales with the perimeter
 * of a cell to simulate the surface membrane energy. This particular perimeter force term in turn differs from the one
 * proposed by Farhadifar et al (2007) in the sense that it employs a target perimeter.
 *
 * Each of the model parameter member variables are rescaled such that mDampingConstantNormal
 * takes the default value 1, whereas Nagai and Honda (who denote the parameter by
 * nu) take the value 0.01.
 */
template<unsigned DIM>
class NagaiHondaForce  : public AbstractForce<DIM>
{
friend class TestForces;

private:

    friend class boost::serialization::access;
    /**
     * Boost Serialization method for archiving/checkpointing.
     * Archives the object and its member variables.
     *
     * @param archive  The boost archive.
     * @param version  The current version of this class.
     */
    template<class Archive>
    void serialize(Archive & archive, const unsigned int version)
    {
        archive & boost::serialization::base_object<AbstractForce<DIM> >(*this);
        archive & mNagaiHondaDeformationEnergyParameter;
        archive & mNagaiHondaMembraneSurfaceEnergyParameter;
        archive & mNagaiHondaCellCellAdhesionEnergyParameter;
        archive & mNagaiHondaCellBoundaryAdhesionEnergyParameter;
    }

protected:

    /**
     * Cell deformation energy parameter. Has units of kg s^-2 (cell size at equilibrium rest length)^-1.
     */
    double mNagaiHondaDeformationEnergyParameter;

    /**
     * Cell membrane energy parameter. Has units of kg (cell size at equilibrium rest length) s^-2.
     */
    double mNagaiHondaMembraneSurfaceEnergyParameter;

    /**
     * Cell-cell adhesion energy parameter. Has has units of kg (cell size at equilibrium rest length)^2 s^-2.
     * This parameter corresponds to 1/2 of the Lambda parameter in forces proposed by Farhadifar et al (2007).
     * This slight difference comes from the fact that when we apply the forces to a particular node, each
     * edge is visited twice - and hence the force originating from that edge is applied twice.
     */
    double mNagaiHondaCellCellAdhesionEnergyParameter;

    /**
     * Cell-boundary adhesion energy parameter. Has units of kg (cell size at equilibrium rest length)^2 s^-2.
     */
    double mNagaiHondaCellBoundaryAdhesionEnergyParameter;

public:

    /**
     * Constructor.
     */
    NagaiHondaForce();

    /**
     * Destructor.
     */
    virtual ~NagaiHondaForce();

    /**
     * Overridden AddForceContribution() method.
     *
     * Calculates the force on each node in the vertex-based cell population based on the
     * Nagai Honda model.
     *
     * @param rCellPopulation reference to the cell population
     */
    virtual void AddForceContribution(AbstractCellPopulation<DIM>& rCellPopulation);

    /**
     * Get the adhesion parameter for the edge between two given nodes.
     *
     * @param pNodeA one node
     * @param pNodeB the other node
     * @param rVertexCellPopulation reference to the cell population
     *
     * @return the adhesion parameter for this edge.
     */
    virtual double GetAdhesionParameter(Node<DIM>* pNodeA, Node<DIM>* pNodeB, VertexBasedCellPopulation<DIM>& rVertexCellPopulation);

    /**
     * @return mNagaiHondaDeformationEnergyParameter
     */
    double GetNagaiHondaDeformationEnergyParameter();

    /**
     * @return mNagaiHondaMembraneSurfaceEnergyParameter
     */
    double GetNagaiHondaMembraneSurfaceEnergyParameter();

    /**
     * @return mCellCellAdhesionEnergyParameter
     */
    double GetNagaiHondaCellCellAdhesionEnergyParameter();

    /**
     * @return mNagaiHondaCellBoundaryAdhesionEnergyParameter
     */
    double GetNagaiHondaCellBoundaryAdhesionEnergyParameter();

    /**
     * Set mNagaiHondaDeformationEnergyParameter.
     *
     * @param nagaiHondaDeformationEnergyParameter the new value of mNagaiHondaDeformationEnergyParameter
     */
    void SetNagaiHondaDeformationEnergyParameter(double nagaiHondaDeformationEnergyParameter);

    /**
     * Set mNagaiHondaMembraneSurfaceEnergyParameter.
     *
     * @param nagaiHondaMembraneSurfaceEnergyParameter the new value of mNagaiHondaMembraneSurfaceEnergyParameter
     */
    void SetNagaiHondaMembraneSurfaceEnergyParameter(double nagaiHondaMembraneSurfaceEnergyParameter);

    /**
     * Set mNagaiHondaCellCellAdhesionEnergyParameter. This parameter corresponds to 1/2 of the Lambda parameter in the forces by
     * Farhadifar et al (2007).
     *
     * @param nagaiHondaCellCellAdhesionEnergyEnergyParameter the new value of mNagaiHondaCellCellAdhesionEnergyParameter
     */
    void SetNagaiHondaCellCellAdhesionEnergyParameter(double nagaiHondaCellCellAdhesionEnergyEnergyParameter);

    /**
     * Set mNagaiHondaCellBoundaryAdhesionEnergyParameter.
     *
     * @param nagaiHondaCellBoundaryAdhesionEnergyParameter the new value of mNagaiHondaCellBoundaryAdhesionEnergyParameter
     */
    void SetNagaiHondaCellBoundaryAdhesionEnergyParameter(double nagaiHondaCellBoundaryAdhesionEnergyParameter);

    /**
     * Overridden OutputForceParameters() method.
     *
     * @param rParamsFile the file stream to which the parameters are output
     */
    void OutputForceParameters(out_stream& rParamsFile);
};

#include "SerializationExportWrapper.hpp"
EXPORT_TEMPLATE_CLASS_SAME_DIMS(NagaiHondaForce)

#endif /*NAGAIHONDAFORCE_HPP_*/


/**
 * \file dcs/math/stats/distribution/gamma.hpp
 *
 * \brief The Gamma probability distribution.
 *
 * \author Marco Guazzone (marco.guazzone@gmail.com)
 *
 * <hr/>
 *
 * Copyright 2009 Marco Guazzone (marco.guazzone@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef DCS_MATH_STATS_DISTRIBUTION_GAMMA_HPP
#define DCS_MATH_STATS_DISTRIBUTION_GAMMA_HPP

#include <dcs/detail/config/boost.hpp>

#if !DCS_DETAIL_CONFIG_BOOST_CHECK_VERSION(103500) // 1.35
# 	error "Required Boost library version >= 1.35"
#endif

#include <boost/math/distributions/gamma.hpp>
//#include <boost/random/gamma_distribution.hpp>
//#include <boost/random/variate_generator.hpp>
#include <cmath>
#include <cstddef>
#include <dcs/math/policies/policy.hpp>
#include <dcs/math/random/uniform_01_adaptor.hpp>
//#include <dcs/math/stats/distribution/detail/rgamma.cpp>
//#include <dcs/math/constants.hpp>
#include <iostream>
#include <vector>

namespace dcs { namespace math { namespace stats {

namespace detail {

} // Namespace detail

/**
 * \brief The Gamma distribution with shape parameter \f$k\f$ and scale
 *  parameter \f$\theta\f$.
 *
 * \tparam RealT The type used for real numbers.
 * \tparam PolicyT The policy type.
 *
 * The probability density function (pdf):
 * \f[
 *   \Pr(x|k,\theta) = x^{k-1} \frac{\exp{\left(-x/\theta\right)}}{\Gamma(k)\,\theta^k}
 * \f]
 *
 * \author Cosimo Anglano (cosimo.anglano@mfn.unipmn.it)
 * \author Marco Guazzone (marco.guazzone@gmail.com)
 */
template < typename RealT=double, typename PolicyT=::dcs::math::policies::policy<> >
class gamma_distribution
{
	public: typedef RealT support_type;
	public: typedef RealT value_type;
	public: typedef PolicyT policy_type;

	public: explicit gamma_distribution(support_type shape, support_type scale=1)
		: dist_(shape, scale)
	{
		// empty
	}

	// compiler-generated copy ctor and assignment operator are fine

	/**
	 * \brief Generate a random number distributed according to this
	 * gamma distribution.
	 *
	 * \param rng A uniform random number generator.
	 * \return A random number distributed according to this gamma
	 * distribution.
	 *
	 * A \c gamma random number distribution produces random numbers
	 * \f$x \ge 0\f$ distributed according to the probability density function:
	 * \f[
	 *   \Pr(x|k,\theta) = x^{k-1} \frac{\exp{\left(-x/\theta\right)}}{\Gamma(k)\,\theta^k}
	 * \f]
	 */
	public: template <typename UniformRandomGeneratorT>
		support_type rand(UniformRandomGeneratorT& rng) const
	{
/* Don't work: Actually (2010-01-21), Boost uses Gamma with only the parameter
 * shape. In principle this wouldn't be a problem since we may use the scaling
 * property gamma_rand(a,b)==b*gamma_rand(a); but for shape==1, it uses
 * Exp(rng()) which is wrong when b!=1 since Gamma(1,b) ~ Exp(1/b)
 *
		typedef ::boost::gamma_distribution<support_type> rdist_type;
		typedef ::boost::variate_generator<UniformRandomGeneratorT&, rdist_type> variate_type;

		return dist_.scale() * variate_type(rng, rdist_type(dist_.shape()))();
*/
		::dcs::math::random::uniform_01_adaptor<UniformRandomGeneratorT&, support_type> eng(rng);
		value_type r(0);
		//value_type ishape = static_cast<unsigned long>(dist_.shape()); // Not safe
		value_type ishape = ::std::floor(dist_.shape());

		if (dist_.shape() >= value_type(1))
		{
			value_type acc(0);
			for (::std::size_t i = 0; i < ishape; ++i)
			{
				acc += ::std::log(eng());
			}
			r += -acc;
		}
		value_type diff = dist_.shape()-ishape;
		if (diff > 0)
		{
/*
			// Acceptance-rejection method (see: http://en.wikipedia.org/wiki/Gamma_distribution#Generating_gamma-distributed_random_variables)

			value_type v0 = ::dcs::math::constants::e<value_type>::value/(::dcs::math::constants::e<value_type>::value + diff);

			value_type csi;
			value_type eta;

			do
			{
				value_type u1 = eng();
				value_type u2 = eng();
				value_type u3 = eng();

				if (u1 <= v0)
				{
					csi = ::std::pow(u2, value_type(1)/diff);
					eta = u3*::std::pow(csi, diff-value_type(1));
				}
				else
				{
					csi = value_type(1)-::std::log(u2);
					eta = u3*::std::exp(-csi);
				}

			}
			while (eta > (::std::pow(eta, diff-value_type(1))*::std::exp(-csi)));

			r = (csi - r);
*/

			// this is the original code taken from rnd.cc (Anglano, 2005).
			value_type x;
			value_type y;

			do {
				value_type u1 = eng();
				value_type u2 = eng();
				x = ::std::pow(u1, value_type(1)/diff);
				y = ::std::pow(u2, value_type(1)/dist_.scale());
			} while ((x+y)>1);

			x = (x/(x+y));
			y = -::std::log(eng());

			r += x*y;
		}
		return dist_.scale()*r;

/*
		// This uses a function coming from R
		::dcs::math::random::uniform_01_adaptor<UniformRandomGeneratorT&, support_type> eng(rng);
		return detail::rgamma(dist_.shape(), dist_.scale(), eng);
*/
	}

	/**
	 * \brief Generate a vector of random numbers distributed according to this
	 * gamma distribution.
	 *
	 * \param rng A uniform random number generator.
	 * \param n The number of random numbers to generate.
	 * \return A vector of random numbers distributed according to this
	 * gamma distribution.
	 *
	 * A \c gamma random number distribution produces random numbers
	 * \f$x \ge 0\f$ distributed according to the probability density function:
	 * \f[
	 *   \Pr(x|k,\theta) = x^{k-1} \frac{\exp{\left(-x/\theta\right)}}{\Gamma(k)\,\theta^k}
	 * \f]
	 */
	public: template <typename UniformRandomGeneratorT>
		::std::vector<support_type> rand(UniformRandomGeneratorT& rng, ::std::size_t n)
	{
		::std::vector<support_type> rnds(n);

        for ( ; n > 0; --n)
		{
			rnds.push_back(rand(rng));
		}

		return rnds;
	}

	public: support_type shape() const
	{
		return dist_.shape();
	}

	public: support_type scale() const
	{
		return dist_.scale();
	}

	public: support_type location() const
	{
		return support_type(0);
	}

	public: support_type quantile(value_type p) const
	{
		return ::boost::math::quantile(dist_, p);
	}

	private: ::boost::math::gamma_distribution<value_type,policy_type> dist_;
};

template <
	typename CharT,
	typename CharTraitsT,
	typename RealT,
	typename PolicyT
>
::std::basic_ostream<CharT,CharTraitsT>& operator<<(::std::basic_ostream<CharT,CharTraitsT>& os, gamma_distribution<RealT,PolicyT> const& dist)
{
	return os << "Gamma("
			  << "shape=" <<  dist.shape()
			  << ", scale=" <<  dist.scale()
			  << ")";
}

}}} // Namespace dcs::math::stats

#endif // DCS_MATH_STATS_DISTRIBUTION_GAMMA_HPP


/*
 Copyright (C) 2019 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <qle/instruments/brlcdiswap.hpp>

#include <qle/cashflows/brlcdicouponpricer.hpp>
#include <qle/cashflows/couponpricer.hpp>
#include <ql/cashflows/overnightindexedcoupon.hpp>
#include <ql/cashflows/simplecashflow.hpp>
#include <ql/time/schedule.hpp>
#include <ql/time/daycounters/business252.hpp>
#include <boost/assign/list_of.hpp>

using namespace QuantLib;
using boost::assign::list_of;
using std::vector;
using std::pow;

namespace QuantExt {

// Reason for use of convert_to_container:
// https://stackoverflow.com/a/17805923/1771882
BRLCdiSwap::BRLCdiSwap(Type type, Real nominal, const Date& startDate, const Date& endDate, Rate fixedRate,
    const boost::shared_ptr<BRLCdi>& overnightIndex, Spread spread, bool telescopicValueDates)
    : OvernightIndexedSwap(type, nominal, Schedule(list_of(startDate)(endDate).convert_to_container<vector<Date> >(),
        NullCalendar(), QuantLib::Unadjusted, QuantLib::Unadjusted, 100 * Years), fixedRate,
        overnightIndex->dayCounter(), overnightIndex, spread, 0, ModifiedFollowing, overnightIndex->fixingCalendar(),
        telescopicValueDates), startDate_(startDate), endDate_(endDate), index_(overnightIndex) {

    // Need to overwrite the fixed leg with the correct fixed leg for a standard BRL CDI swap
    // Fixed leg is of the form: N [(1 + k) ^ \delta - 1]
    // where \delta is the number of BRL business days in the period divided by 252 i.e.
    // the day count fraction for the period on a Business252 basis.
    Time dcf = index_->dayCounter().yearFraction(startDate_, endDate_);
    Real fixedLegPayment = nominal * (pow(1.0 + fixedRate, dcf) - 1.0);
    Date paymentDate = legs_[0].back()->date();
    boost::shared_ptr<CashFlow> fixedCashflow = boost::make_shared<SimpleCashFlow>(fixedLegPayment, paymentDate);
    legs_[0].clear();
    legs_[0].push_back(fixedCashflow);
    registerWith(fixedCashflow);

    // Set the pricer on the BRL CDI coupon
    QL_REQUIRE(legs_[1].size() == 1, "BRLCdiSwap expected exactly one overnight coupon");
    boost::shared_ptr<OvernightIndexedCoupon> coupon = boost::dynamic_pointer_cast<OvernightIndexedCoupon>(legs_[1][0]);
    coupon->setPricer(boost::make_shared<BRLCdiCouponPricer>());
}

Real BRLCdiSwap::fixedLegBPS() const {

    calculate();

    static Spread basisPoint = 1.0e-4;
    if (!close(endDiscounts_[0], 0.0) && endDiscounts_[0] != Null<DiscountFactor>()) {
        DiscountFactor df = endDiscounts_[0];
        Time dcf = index_->dayCounter().yearFraction(startDate_, endDate_);
        legBPS_[0] = df * nominal() * (pow(1.0 + fixedRate() + basisPoint, dcf) - pow(1.0 + fixedRate(), dcf));
        return legBPS_[0];
    }

    QL_FAIL("BRLCdiSwap cannot calculate fixed leg BPS because end discount is not populated");
}

Real BRLCdiSwap::fairRate() const {

    calculate();

    if (!close(endDiscounts_[0], 0.0) && endDiscounts_[0] != Null<DiscountFactor>()) {
        DiscountFactor df = endDiscounts_[0];
        Time dcf = index_->dayCounter().yearFraction(startDate_, endDate_);
        return pow(overnightLegNPV() / (nominal() * df) + 1.0, 1.0 / dcf) - 1.0;
    }

    QL_FAIL("BRLCdiSwap cannot calculate fair rate because end discount is not populated");
}

}