// Work started 08/14/2012
// New code cleaned on June 10, 2013.
// For past cpmd codes refer to versions before June 5, 2013
// NB joined fun 05/01/2017.
// This is main.
// This is (Car Parrinello) molecular dynamics for electrostatics on membranes

// VJ Lessons Learned, other notes (pre-May 2017):
/*
Lessons:

1. Do not use exit(1) to exit the program. This leads to memory leaks.

Other Notes:
Aug 22 2012 - NOTE THREADSIZE different in different regions of code.
for precal it is 8, for fmd forces, energies it is 4.
Testing parameters: echo 3 4 100 35 0.0001 300 0.8 0 1 1
*/

//  NB Changes (May 2017 - Present):
/* Changes :

2017.05.12:  NB has edited main.cpp to incorporate a single (1) type of fractionally ionizable group with parameter (alpha). This includes ability to echo (input) this new parameter (alpha) below and to call function "new assign_random_q_values" (found in Interface.cpp) using this parameter. [Preliminary studies reporting multiple groups have assumed the groups to have the same alpha values, i.e. one group is basic the other acidic and you are at pH = Mean[pKa1, pKa2].]

2017.06.05:  NB has added working area constraint functions (SHAKE_for_area & RATTLE_for_area).  Area is conserved to within a fraction of a percent with default masses = 1.0, etc.

                2017.09.20:  SHAKE_for_area & RATTLE_for_area found to be in error, math mistake.  Only coincide

2017.06.13:  NB has added a pre-MD line below to dump the initial state in the movie file as well; it calls this timestep "-1" in the movie file due to the way the function "interface_movie" is defined.

2017.06.26:  NB is working to make the optimization dynamics a real-time study.  Because the temperature uniformly started at (.5T), an excess factor of 2 in the denominator of the standard deviation of the initial velocity distribution "p_sigma" was removed (within "functions.cpp").  The temperature now begins sufficiently close to the intended temperature (within 10% @ t = 1 step).  Energy conservation is improved after this correction. [Adding a factor of 2 in the denominator of all DOF's supplied to the thermostat also had this effect, but it seems correcting "p_sigma" - not thermostat DOF's - is the correct solution.  Some sources imply DOF's should be 3N - 1 due to constraint, however energy conservation seems to work marginally better without subtracting the constraint DOF, so subtraction may be wrong.]

2017.06.28:  NB has finished adapting the code to a real-time study (sans barostat).  The parameters that optimally conserve energy for discretizations up to (3,8) are {C = 5,  T = 1.0, Q = 0.01, M = 1.0, dt = .00001, DOF = 3N}.

2017.07.04:  NB has added a for statement allowing "assign_random_q_values" to create 5 divisions.

2017.07.08:  NB made a few trivial changes such as 1) making "total_time" now specify the number of steps to run itself rather than a function of (dt) because it would have an error if the ratio became to large and 2) eliminating the unused variable M introduced to specify vertex mass (we must keep as 1.0).  Additionally, a character flag ("annealFlag") has been added (that must be echoed after all numerical parameters) that signifies if annealing should occur ('y' or 'n').

2017.07.09:  NB is refining functionality to continue a simulation from a pre-existing off-file ("Optimized.off", must have this name). This involved adding a second character flag (that also must be echoed, after the numerical parameters and annealing flag) signifying if said off-file is to be loaded. If you use Nicholas' automated-PBS script generator, it will automatically denote the values of these flags in the cluster job name (and warn you if you are submitting a RT job that anneals).

2017.08.08:  NB has randomized the charge distribution assignment scheme (rather than using permutations as it introduces significant stripes and polar deficits in charge density & potential, though it produces similar shapes for the case tested.)  Also, I am changing the function for generation of axially distributed patches of charge (spherical caps).

2017.09.07:  NB has verified all mathematical components of the volume and area constraints.  All are correct, a factor of (1/2) is missing in the face area gradient within the "compute_neg_VGrads" function; however, adding it here causes major issues, meaning it is probably within those functions already (must check).  It is only used in bending energy calculations and (if area constrained) the "SHAKE/RATTLE_for_area" functions.  Adding (1/2) factors in the appropriate place within the "SHAKE/RATTLE_for_area" seems to be fine.

*/

#include "utility.h"
#include "interface.h"
#include "vertex.h"
#include "edge.h"
#include "face.h"
#include "particle.h"
#include "control.h"
#include "functions.h"
#include "thermostat.h"
#include <boost/filesystem/operations.hpp>
#include <boost/program_options.hpp>

using namespace boost::program_options;

// Declaring function to initiate and propagate the MD:
void md_interface(INTERFACE &, vector<PARTICLE> &, vector<THERMOSTAT> &, vector<THERMOSTAT> &, CONTROL &, char, char, char, const double scalefactor, double box_halflength);

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

int main(int argc, const char *argv[]) {
    // (2017.09.14 NB added.)  Delete existing outfiles directory if it exists, then recreate it empty.
    if (world.rank() == 0) {
        if (boost::filesystem::remove_all("outfiles") == 0) perror("Error deleting outfiles directory");
        else cout << "Pre-existing outfiles directory successfully, replaced with an empty directory." << endl;
        boost::filesystem::create_directory("outfiles");
    }
    // Declare variables to make the system:
    double ein = epsilon_water;        // permittivity of inside medium
    double eout = epsilon_water;        // permittivity of outside medium
    double T, Q_mesh, Q_ion;                        // Temperature of the system, mass of thermostate (0 if inactive), timestep.
    unsigned int chain_length;    // length of nose-hoover thermostat chain, 1 minimum, 1 means no thermostat

    INTERFACE boundary;                    // interface (modelled as soft LJ wall)
    unsigned int disc1, disc2;
    double lambda_a, lambda_v;          // lambdas measuring strength of area & volume constraints  (unused)

    double unit_radius_sphere, youngsModulus, q_strength, alpha, conc_out, z_out; // radius (in nm), net charge (if all charged), fractional q-occupancy, salt conc (MOLAR), salt valency
    int numPatches;
    double fracChargedPatch;
    char randomFlag, offFlag, geomConstraint, bucklingFlag, constraintForm, counterionFlag, functionFlag;  // functionFlag indicates different pattern initializations, -y for yingyang pattern, -c for cube formation.
    string externalPattern;

    // Counterion Initializations:
    double packing_fraction, box_halflength;
    int counterion_valency;
    double counterion_diameter = 0.6; // Counterion diameter in nanometers.
    vector<PARTICLE> counterions;

    // Control of the dynamics:
    CONTROL mdremote;

    // Specify variables via command line (-X x):
    options_description desc("Usage:\nrandom_mesh <options>");
    desc.add_options()
            ("help,h", "print usage message")
                 // Physical Parameters:
            ("unitRadius,R", value<double>(&unit_radius_sphere)->default_value(10),
             "Radius of the initial sphere & simulation unit of length (in nanometers).")
            ("netCharge,q", value<double>(&q_strength)->default_value(600),
             "Net NP charge, if fully occupied (elementary charges).")
            ("saltConc,c", value<double>(&conc_out)->default_value(0.005),
             "Salt concentration (Molar).")
            ("tensSigma,t", value<double>(&boundary.sigma_a)->default_value(1),
             "Surface tension constant (dynes/cm).")
            ("volTensSigma,v", value<double>(&boundary.sigma_v)->default_value(1),
             "Volume tension constant (atm/nm^3).")
            ("bending,b", value<double>(&boundary.bkappa)->default_value(40),
             "The bending modulus of the particle (kB*T).")
            ("Stretching,s", value<double>(&boundary.sconstant)->default_value(40),
             "Reduced stretching modulus of the particle (kB*T/R0^2).")
            ("GeomConstraint,G", value<char>(&geomConstraint)->default_value('N'),
             "Specification of rigid geometric constraints, 'V' for volume.")
            ("functionFlag,H", value<char>(&functionFlag)->default_value('n'),
             "specification of function, 'y' for yinyang function.")
            ("bucklingFlag,B", value<char>(&bucklingFlag)->default_value('n'),
             "Specification of stretching form, if spontaneous buckling should occur.")
                 // Physical Parameters for patterned (Janus, Striped, Polyhedral) particles:
            ("numPatches,N", value<int>(&numPatches)->default_value(1),
             "The number of distinct charge patches (of tunable size if N = 2).")
            ("fracChargePatch,p", value<double>(&fracChargedPatch)->default_value(0.5),
             "Surface area fraction of the patch (if N = 2, otherwise irrelevant).")
            ("externalPattern,E", value<string>(&externalPattern)->default_value("None"),
             "Filename of pattern to be imported (without the '.dat' extension).")
                // Physical Parameters for counterion condensation:
            ("counterionFlag,I", value<char>(&counterionFlag)->default_value('n'),
             "Flag specifying if explicit counterions should be present, 'y' for yes.")
            ("packingFraction,P", value<double>(&packing_fraction)->default_value(0.01),
             "NP packing fraction determining total box size.")
            ("counterionValency,Z", value<int>(&counterion_valency)->default_value(1),
             "Valency of the counterions.")
                // Virtual Parameters:
            ("totalTime,S", value<int>(&mdremote.steps)->default_value(250000),
             "Duration of the simulation (total timesteps).")
            ("timestep,d", value<double>(&mdremote.timestep)->default_value(0.001),
             "Time step used in the simulation.")
            ("initTemp,T", value<double>(&T)->default_value(0.001),
             "Target initial temperature (prior to annealing).")
            ("disc2,D", value<unsigned int>(&disc2)->default_value(8),
             "Discretization parameter 2")
            ("chain_length,C", value<unsigned int>(&chain_length)->default_value(5),
             "Number of thermostat particles in the Nose-Hoover chain (plus 1).")
            ("meshThermostatMass,Q", value<double>(&Q_mesh)->default_value(.01),
             "Mass of the mesh thermostat (higher means less strongly coupled).")
            ("ionsThermostatMass,Y", value<double>(&Q_ion)->default_value(.01),
             "Mass of the ions thermostat (higher means less strongly coupled).")
                 // Annealing Parameters:
            ("annealFlag,a", value<char>(&mdremote.anneal)->default_value('y'),
             "If annealing occurs or not ('y' for yes).")
            ("anneal_freq,f", value<int>(&mdremote.annealfreq)->default_value(50000),
             "Interval number of steps between which annealing commences.")
            ("anneal_dura,u", value<int>(&mdremote.annealDuration)->default_value(5000),
             "Number of steps over which to reduce (T = fT*T) & (Q_mesh = fQ*Q_mesh), same as before if 1")
            ("anneal_Tfac,e", value<double>(&mdremote.TAnnealFac)->default_value(1.25),
             "Fold reduction to temperature at initial annealing call.  Was 10 before (and constant throughout)")
                 // Runtime & Output Parameters:
            ("randomFlag,F", value<char>(&randomFlag)->default_value('y'),
             "If predefined shape used or not ('y' for yes).")
            ("offFlag,o", value<char>(&offFlag)->default_value('n'),
             "If predefined shape used or not ('y' for yes).")
            ("moviestart,m", value<int>(&mdremote.moviestart)->default_value(1),
             "The starting point of the movie")
            ("offfreq,O", value<int>(&mdremote.offfreq)->default_value(2500),
             "The frequency of making off files")
            ("moviefreq,M", value<int>(&mdremote.moviefreq)->default_value(1000),
             "The frequency of shooting the movie")
            ("povfreq,V", value<int>(&mdremote.povfreq)->default_value(100000),
             "The frequency of making povray files")
            ("writedata,W", value<int>(&mdremote.writedata)->default_value(500),
             "frequency of dumping thermo time series (after 1K steps)");

    variables_map vm;
    store(parse_command_line(argc, argv, desc), vm);
    notify(vm);

    // Compute the box radius (where NP radius is unit length so it is not found here):
    //if(counterionFlag != 'y') packing_fraction = 1.0;
    //box_halflength = pow(1 / packing_fraction,1.0/3.0);
    box_halflength = 1.0;
    // Reducing the counterion diameter:
    counterion_diameter = (counterion_diameter / unit_radius_sphere);

    // Compute the 2D Young's Modulus (in kB*T/nm^2) from the reduced stretching constant specified as input:
    youngsModulus = boundary.sconstant / (unit_radius_sphere * unit_radius_sphere);  // For information purposes only.

    // The dimensionless form of the tension factors are a function of radius:
    double dynePerCm_Scalefactor = pow(10,-14)*(pow(unit_radius_sphere, 2)/unitenergy);
    boundary.sigma_a = (dynePerCm_Scalefactor * boundary.sigma_a);    // Coefficient is 1 (dyne/cm) in (kB T_room/(R0 nm^2)).
    //double dynePerUmFifth_Scalefactor = pow(10,-4)*pow(10,-18)*(pow(unit_radius_sphere, 6)/unitenergy);
    //boundary.sigma_v = (dynePerUmFifth_Scalefactor * boundary.sigma_v);
    double atmPerNmThird_Scalefactor = (1.01325 * pow(10,5)) * pow(10,-27) * (pow(unit_radius_sphere, 6) / (unitenergy * pow(10, -7)));
    boundary.sigma_v = atmPerNmThird_Scalefactor * boundary.sigma_v;

    // The scale factor for electrostatic interactions is a function of radius:
    const double scalefactor = epsilon_water * lB_water / unit_radius_sphere;

    disc1 = 3;                                          //  Discretization parameters (h, k).
    alpha = 1.0;                                        //  Fractional charge occupancy.
    if (chain_length == 1) Q_mesh = 0;                  //  Reduced mass of the thermostat particle(s)
    if (chain_length == 1) Q_ion = 0;                   //  Reduced mass of the thermostat particle(s)

    mdremote.QAnnealFac =
            1.00 + (mdremote.TAnnealFac / 10.0);        // Maintain previous scaling, same as before (fQ = 2) if fT = 10.

    // Flags for rigid geometric constraint and form:
    constraintForm = 'L';                               // Enforce a linear constraint (not quadratic).

    // ### Simulation setup: ###
    // Unused quantities for antiquated constraints & energy functionals:
    lambda_v = 0;                        // hardwiring volume constraint. Unused.
    lambda_a = 0;                        // hardwiring area constraint. Unused.
    boundary.lambda_l = 0;            // forgot what was this?!  NB:  I think it's an edge line tension force. Unused.

    // Electrostatics setup (membrane, implicit salt ions):
    z_out = 1;                        // Valency of implicit (salt) ions.
    boundary.ein = ein;               // Permittivity inside the membrane.
    boundary.eout = eout;             // Permittivity outside the membrane.  (Both equal by default.)
    boundary.set_up(unit_radius_sphere);
    // Assigning the Debye length outside (in reduced units):
    if (conc_out == 0)
        boundary.inv_kappa_out = 100000000;  // Effectively infinite, no screening.
    else
        boundary.inv_kappa_out =
                (0.257 / sqrt(z_out * boundary.lB_out * unit_radius_sphere * conc_out)) / unit_radius_sphere;

    // Generate the membrane, assign charge to vertices:
    boundary.discretize(disc1, disc2);            // discretize the interface
    if (disc1 != 0 || disc2 != 0) {
        if (externalPattern == "None")
			  boundary.assign_random_q_values(q_strength, alpha, numPatches, fracChargedPatch, randomFlag, functionFlag);
        else
			  boundary.assign_external_q_values(q_strength, externalPattern);
	 }

    //  Assess total actual charge on the membrane for record & to place the correct number of counterions:
    double q_actual = 0;
    for (unsigned int i = 0; i < boundary.V.size(); i++) {
        q_actual += boundary.V[i].q;
    }

    // Populate the box with counterions, if requested:
    if(counterionFlag == 'y') {
        boundary.put_counterions(q_actual, unit_radius_sphere, counterion_diameter, box_halflength, counterions, counterion_valency);
        cout << "Counterions have been placed inside the box." << endl;
    }

    boundary.dressup(lambda_a, lambda_v);            // Compute initial normals, areas, and volumes.

    boundary.ref_area = boundary.total_area; // NB changed from (4 * pi), discrete membrane differs.
    boundary.ref_volume = boundary.total_volume;
    //boundary.ref_Area_Vertices = boundary.total_Area_Vertices;    // (2017.09.19 NB added.)  Initial area by vertices.

    // LJ parameters (mesh-mesh almost never invoked, present for security reasons):
    double lj_length_mesh_mesh_coeff = 0.1;
    boundary.lj_length_mesh_mesh = lj_length_mesh_mesh_coeff * boundary.avg_edge_length;
    double meshPointRadius = 0.5 * boundary.avg_edge_length;
    boundary.lj_length_mesh_ions = ((2.0 * meshPointRadius + counterion_diameter) / 2.0);
    boundary.elj = 1.0;

    // Thermostat for MD of the interface:
    vector<THERMOSTAT> mesh_bath, ions_bath;        // thermostat for real system (interface + ions)
    if (chain_length == 1) {
        mesh_bath.push_back(THERMOSTAT(0, T, 3 * boundary.V.size(), 0.0, 0, 0));
        ions_bath.push_back(THERMOSTAT(0, T, 3 * counterions.size(), 0.0, 0, 0));
    }
    else {
        mesh_bath.push_back(THERMOSTAT(Q_mesh, T, 3 * boundary.V.size(), 0, 0, 0));
        ions_bath.push_back(THERMOSTAT(Q_ion, T, 3 * counterions.size(), 0, 0, 0));
        while (mesh_bath.size() != chain_length - 1)
            mesh_bath.push_back(THERMOSTAT(Q_mesh, T, 1, 0, 0, 0));
        while (ions_bath.size() != chain_length - 1)
            ions_bath.push_back(THERMOSTAT(Q_ion, T, 1, 0, 0, 0));
        mesh_bath.push_back(THERMOSTAT(0, T, 3 * boundary.V.size(), 0.0, 0, 0));    // dummy bath (has 0 mass)
        ions_bath.push_back(THERMOSTAT(0, T, 3 * counterions.size(), 0.0, 0, 0));
    }

    // Assign masses to vertices
    for (unsigned int i = 0; i < boundary.V.size(); i++)
        boundary.V[i].m = 1.0;      // NB:  Energy conservation fails invariably if not = 1.0 .

    // NB added following lines to load off-file (stops if the file is not found; specify correctly):
    if (offFlag == 'y') boundary.load_configuration("380000.off");

    int numOfNodes = world.size();
    if (world.rank() == 0) {
#pragma omp parallel default(shared)
        {
            if (omp_get_thread_num() == 0) {
                printf("Number of MPI processes used %d\n", numOfNodes);
                printf("Number of OpenMP threads per MPI process %d\n", omp_get_num_threads());
            }
        }
    }

    if (world.rank() == 0) {
        // Define an output stream to provide the same things as the (*.out) file on the HPC cluster (minus run outputs):
        // Useful in automated analysis when console output is not saved by default on local PC.
        ofstream list_out("outfiles/simulation.out", ios::app);

        // ### Output to the console (or HPC *.out file) & backup "simulation.out" file) the simulation parameters: ###

        cout << "\n===================\nMembrane\n===================\n";
        cout << "Unit radius of the sphere (in nm): " << unit_radius_sphere << endl;
        cout << "Number of vertices: " << boundary.V.size() << endl;
        cout << "Number of edges: " << boundary.E.size() << endl;
        cout << "Number of faces: " << boundary.F.size() << endl;
        cout << "Lambda_v: " << lambda_v << endl;
        cout << "Lambda_a: " << lambda_a << endl;
        cout << "Lambda_l: " << boundary.lambda_l << endl;
        cout << "Total intial volume: " << boundary.total_volume << "  Sphere volume: " << (4. / 3.) * pi
             << "  Ref volume: " << boundary.ref_volume << endl;
        cout << "Total intial face area: " << boundary.total_area << "  Sphere area: " << 4 * pi << "  Ref area: "
             << boundary.ref_area << endl;
        cout << "Mesh point radius: " << meshPointRadius << endl;
        cout << "Bending rigidity: " << boundary.bkappa << endl;
        cout << "Young's Modulus (2D): " << youngsModulus << endl;
        cout << "Stretching constant: " << boundary.sconstant << endl;
        cout << "Surface Tension constant: " << (boundary.sigma_a / dynePerCm_Scalefactor) << endl;
        cout << "Volume Tension constant: " << (boundary.sigma_v / atmPerNmThird_Scalefactor)<< endl;
        cout << "Unstretched edge length: " << boundary.avg_edge_length
             << endl; // NB uncommented & replaced the output value.
        cout << "LJ strength: " << boundary.elj << endl;
        cout << "LJ mesh-mesh distance cutoff: " << boundary.lj_length_mesh_mesh << endl;
        cout << "LJ mesh-ion distance cutoff: " << boundary.lj_length_mesh_ions << endl;
        cout << "Rigid geometric constraint: " << geomConstraint << endl;

        cout << "\n===================\nElectrostatics\n===================\n";
        cout << "Total charge on membrane (if homogeneously charged): " << q_strength << endl;
        cout << "Final actual charge on membrane: " << q_actual << endl;
        cout << "Number of simulated counterions: " << counterions.size() << endl;
        cout << "Fractional charge occupancy (alpha): " << alpha << endl;
        cout << "The number of patches is: " << numPatches << endl;
        cout << "The size of each patch is: " << fracChargedPatch << endl;
        cout << "The name of the external file used to specify charge patterning is: " << externalPattern << endl;
        cout << "Salt concentration: " << conc_out << endl;
        cout << "Debye length: " << boundary.inv_kappa_out << endl;
        cout << "Bjerrum length: " << boundary.lB_out << endl;

        cout << "\n===================\nThermodynamics\n===================\n";
        cout << "Initial Temperature (T): " << T << endl;
        cout << "Thermostat mass (Q_mesh): " << mesh_bath[0].Q << endl;
        cout << "Number of thermostats (C): " << mesh_bath.size() << endl;
        cout << "Timestep (dt): " << mdremote.timestep << "  &  Total steps: " << mdremote.steps << "." << endl;
        cout << "Annealing: " << mdremote.anneal << endl;
        cout << "Anneal frequency: " << mdremote.annealfreq << endl;
        cout << "Anneal duration: " << mdremote.annealDuration << endl;
        cout << "Temperature annealing decrement: " << mdremote.TAnnealFac << endl;
        cout << "Thermostat mass decrement: " << mdremote.QAnnealFac << endl;
        cout << "Randomness during run: " << randomFlag << endl;
        cout << "Off-file loaded: " << offFlag << endl;

        // Same thing in a file even if you're not on an HPC that can store the console output automatically on request:
        list_out << "===================\nMembrane\n===================\n";
        list_out << "Unit radius of the sphere (in nm): " << unit_radius_sphere
                 << endl; //NB added in observing size change.
        list_out << "Number of vertices: " << boundary.V.size() << endl;
        list_out << "Number of edges: " << boundary.E.size() << endl;
        list_out << "Number of faces: " << boundary.F.size() << endl;
        list_out << "Lambda_v: " << lambda_v << endl;
        list_out << "Lambda_a: " << lambda_a << endl;
        list_out << "Lambda_l: " << boundary.lambda_l << endl;
        list_out << "Total intial volume: " << boundary.total_volume << "  Sphere volume: " << (4. / 3.) * pi
                 << "  Ref volume: " << boundary.ref_volume << endl;
        list_out << "Total intial face area: " << boundary.total_area << "  Sphere area: " << 4 * pi << "  Ref area: "
                 << boundary.ref_area << endl;
        list_out << "Mesh point radius: " << meshPointRadius << endl;
        list_out << "Bending rigidity: " << boundary.bkappa << endl;
        list_out << "Young's Modulus (2D): " << youngsModulus << endl;
        list_out << "Stretching constant: " << boundary.sconstant << endl;
        list_out << "Surface Tension constant: " << (boundary.sigma_a / dynePerCm_Scalefactor) << endl;
        list_out << "Volume Tension constant: " << (boundary.sigma_v / atmPerNmThird_Scalefactor) << endl;
        list_out << "LJ strength: " << boundary.elj << endl;
        list_out << "LJ distance cutoff: " << boundary.lj_length_mesh_mesh << endl;
        list_out << "Rigid geometric constraint: " << geomConstraint << endl;

        list_out << "\n===================\nElectrostatics\n===================\n";
        list_out << "Total charge on membrane: " << q_strength << endl;
        list_out << "Final actual charge on membrane: " << q_actual << endl;
        list_out << "Number of simulated counterions: " << counterions.size() << endl;
        list_out << "Fractional charge occupancy (alpha): " << alpha << endl;
        list_out << "The number of patches is: " << numPatches << endl;
        list_out << "The size of each patch is: " << fracChargedPatch << endl;
        list_out << "The name of the external file used to specify charge patterning is: " << externalPattern << endl;
        list_out << "Salt concentration: " << conc_out << endl;
        list_out << "Debye length: " << boundary.inv_kappa_out << endl;
        list_out << "Bjerrum length: " << boundary.lB_out << endl;

        list_out << "\n===================\nThermodynamics\n===================\n";
        list_out << "Initial Temperature (T): " << T << endl;
        list_out << "Thermostat mass (Q_mesh): " << mesh_bath[0].Q << endl;
        list_out << "Number of thermostats (C): " << mesh_bath.size() << endl;
        list_out << "Timestep (dt): " << mdremote.timestep << "  &  Total steps: " << mdremote.steps << "." << endl;
        list_out << "Annealing: " << mdremote.anneal << endl;
        list_out << "Anneal frequency: " << mdremote.annealfreq << endl;
        list_out << "Anneal duration: " << mdremote.annealDuration << endl;
        list_out << "Temperature annealing decrement: " << mdremote.TAnnealFac << endl;
        list_out << "Thermostat mass decrement: " << mdremote.QAnnealFac << endl;
        list_out << "Randomness during run: " << randomFlag << endl;
        list_out << "Off-file loaded: " << offFlag << endl;

        // (NB added) Print the initial state in the movie file, prior to MD:
        interface_movie(0, boundary.V, counterions, box_halflength);  // NB added.  Note, calls this "time step -1" in movie file.

    }
    // Initiate MD of the boundary/membrane:
    boundary.dressup(lambda_a, lambda_v);
    boundary.output_configuration();    // NB added.  More information on the initial state.
    /*boundary.compute_local_energies(scalefactor);  // Returns 'nan' at the moment as energies... should fix.
    boundary.compute_local_energies_by_component();
    if (world.rank() == 0) {
        rename("outfiles/local_electrostatic_E.off", "outfiles/local_electrostatic_E_initial.off");
        rename("outfiles/local_elastic_E.off", "outfiles/local_elastic_E_initial.off");
        rename("outfiles/local_stretching_E.off", "outfiles/local_stretching_E_initial.off");
        rename("outfiles/local_bending_E.off", "outfiles/local_bending_E_initial.off");
    }*/
    //MPI Boundary calculation for ions
    /*
    unsigned int rangeMesh = boundary.V.size() / world.size() + 1.5;
    lowerBoundMesh = world.rank() * rangeMesh;
    upperBoundMesh = (world.rank() + 1) * rangeMesh - 1;
    extraElementsMesh = world.size() * rangeMesh - boundary.V.size();
    sizFVecMesh = upperBoundMesh - lowerBoundMesh + 1;
    if (world.rank() == world.size() - 1) {
        upperBoundMesh = boundary.V.size() - 1;
        sizFVecMesh = upperBoundMesh - lowerBoundMesh + 1 + extraElementsMesh;
    }
    if (world.size() == 1) {
        lowerBoundMesh = 0;
        upperBoundMesh = boundary.V.size() - 1;
    }
*/
    // MPI Boundary calculations for the mesh:
    unsigned int rangeMesh = (boundary.V.size() + world.size() - 1) / (1.0 * world.size());
    lowerBoundMesh = world.rank() * rangeMesh;
    upperBoundMesh = (world.rank() + 1) * rangeMesh - 1;
    //extraElementsMesh = world.size() * rangeMesh - boundary.V.size();
    sizFVecMesh = rangeMesh;
    if (world.rank() == world.size() - 1) {
        upperBoundMesh = boundary.V.size() - 1;
    }

    // MPI Boundary calculations for the ions:
    int rangeIons = (counterions.size() + world.size() - 1) / (1.0 * world.size());
    lowerBoundIons = world.rank() * rangeIons;
    upperBoundIons = (world.rank() + 1) * rangeIons - 1;
    extraElementsIons = world.size() * rangeIons - counterions.size();
    sizFVecIons = rangeIons;
    if (world.rank() == world.size() - 1) {
        upperBoundIons = counterions.size() - 1; // With zero counterions, this is negative, preventing loop evaluation.
    }

    md_interface(boundary, counterions, mesh_bath, ions_bath, mdremote, geomConstraint, bucklingFlag, constraintForm, scalefactor, box_halflength);
    boundary.compute_local_energies(scalefactor);
    boundary.compute_local_energies_by_component();

    //newly added by FS on 2021/2/5
    //condensation input file generation

    double box_halflength_new = pow(((4.0 / 3.0) * 3.1415926 * unit_radius_sphere * unit_radius_sphere * unit_radius_sphere) / packing_fraction, 1.0 / 3.0) / (2.0 * unit_radius_sphere);
    double qLJ = charge_e / pow(4.0 * pi * epsilon_0 * KB_raw * room_temperature * unit_radius_sphere * pow(10.0, -9), 1.0 / 2.0);
    cout << "box radius: " << box_halflength_new << endl;
    cout << "qLJ: " << qLJ << endl;

    boundary.assign_dual_initial();
    boundary.reassign_charges();
    cout << "Finish assigning duals and recomputing all the charges" << endl;
    boundary.put_counterions(q_actual, unit_radius_sphere, counterion_diameter, box_halflength_new, counterions, counterion_valency);
    cout << "Finish putting ions" << endl;
    create_input_coordinate(boundary.V, boundary.Dual, counterions, box_halflength_new, qLJ, 0.6/unit_radius_sphere);
    cout << "Finish generating condensation input file " << endl;

    return 0;
}
// End of main


//
// Copyright (c) 2015-2017, Deutsches Forschungszentrum für Künstliche Intelligenz GmbH.
// Copyright (c) 2015-2017, University of Bremen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// 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 __MAPS_LINESEGMENT_HPP__
#define __MAPS_LINESEGMENT_HPP__

/** Boost serialization **/
#include <boost/serialization/access.hpp>
#include <boost/serialization/nvp.hpp>

/** Eigen **/
#include <Eigen/Core>
#include <Eigen/Geometry>

/** Base types **/
#include <base/Float.hpp>

/** Std **/
#include <math.h>
#include <utility>

namespace maps { namespace geometric
{
    /**@brief LineSegment class IEEE 1873 standard
     * adapted to lines in D-space.
     * T type (e.g. float or double)
     * D int specification for the dimensional space
     * **/
    template <typename T, int D>
    class LineSegment: public Eigen::ParametrizedLine<T, D>
    {
    public:
        typedef typename Eigen::ParametrizedLine<T, D>::VectorType VectorType;

    private:
        /**  Origin (psi_a) and End (psi_b) point of the line
         * m_origin form the parent class is the psi_a **/
        VectorType _psi_b;

    public:
        inline LineSegment() {}

        LineSegment(const LineSegment<T, D>& other)
            : Eigen::ParametrizedLine<T, D>(other.origin(), other.direction()),
            _psi_b(other.psi_b())
        {}

        LineSegment(const VectorType &psi_a, const VectorType &psi_b)
            : Eigen::ParametrizedLine<T, D>(psi_a, (psi_b-psi_a).normalized()),
            _psi_b(psi_b)
        {}

        inline const VectorType& psi_a() const
        {
            return const_cast<LineSegment<T, D>& >(*this).origin();
        }

        inline VectorType& psi_a()
        {
            return static_cast<VectorType&>(Eigen::ParametrizedLine<T, D>::origin());
        }

        inline const VectorType& psi_b() const
        {
            return const_cast<VectorType& >(_psi_b);
        }

        void psi_b(const VectorType &other_psi_b)
        {
            this->_psi_b = other_psi_b;
            this->direction() =  (this->_psi_b - this->origin()).normalized();
            return;
        }

        inline const VectorType& direction() const
        {
            return const_cast<LineSegment<T, D>& >(*this).direction();
        }

        inline VectorType& direction()
        {
            return static_cast<VectorType&>(Eigen::ParametrizedLine<T, D>::direction());
        }

        /** Perpendicular distance from the origin of the local coordinate
         * system of the map to the line segment. Unit is meter. **/
        inline T rho() const
        {
            /** The distance of zero to its projection onto the line **/
            T dist = this->distance(VectorType::Zero());

            /** In case the projected distance is zero the line is passing by
             * the origin of the local coordinate system**/
            if (dist == 0.00)
            {
                dist += this->origin().norm();
            }

            return dist;
        }

        /** Angle in radians [0, 2*pi] for the orientation of a vector
         * representing the perpendicular distance to the line segment, measured
         * counterclockwise from horizontal x-axis of the local coordinate
         * system of the map.**/
        T alpha() const
        {

            /** The slope is given when the dimension D equal to 2**/
            if (D == 2)
            {
                /** Normally Alpha is the angle given by the slope m = tan(alpha) **/
                T alpha = atan2(this->direction()[1], this->direction()[0]);

                /** Convert alpha between [0, 2pi] **/
                alpha = (alpha >= 0 ? alpha : (2.0*M_PI + alpha));

                /** IEEE Std 1873-2015 request alpha as the perpendicular to it
                 * It means the angle of the rho_axis wrt to the x-axis of the
                 * local coordinate frame **/
                alpha += M_PI/2.0;

                /** Convert between [0, 2*pi] **/
                return (alpha > 2.0*M_PI ? alpha - (2.0*M_PI): alpha);
            }
            return base::NaN<T>();
        }

    protected:
        /** Grants access to boost serialization */
        friend class boost::serialization::access;

        /** Serializes the members of this class*/
        template <typename Archive>
        void serialize(Archive &ar, const unsigned int version)
        {
            ar & BOOST_SERIALIZATION_NVP(this->m_origin);
            ar & BOOST_SERIALIZATION_NVP(this->m_direction);
            ar & BOOST_SERIALIZATION_NVP(this->_psi_b);
        }

    };

    typedef LineSegment<double, 2> LineSegment2d;
    typedef LineSegment<double, 3> LineSegment3d;
    typedef LineSegment<float, 2> LineSegment2f;
    typedef LineSegment<float, 3> LineSegment3f;
}}
#endif /* __MAPS_LINESEGMENT_HPP__ */


#include <iostream>
#include <stdio.h>
#include <cstring>
#include <cmath>
#include <fstream>
#include <string>
#include <regex>
#include <iterator>
#include <python3.7m/Python.h>
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>

#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/implicit.hpp>
#include <boost/python/extract.hpp>
#include <arpa/inet.h>
#include <netinet/in.h>

namespace p = boost::python;
namespace np = boost::python::numpy;

inline void destroyManagerCObject(PyObject *obj)
{
    double *b = reinterpret_cast<double *>(PyCapsule_GetPointer(obj, NULL));
    if (b)
    {
        delete[] b;
    }
}

struct AdvParser
{
    bool checkBase = true;
    bool checkNodes = true;
    bool shapeCreated = true;
    int xnodes, ynodes, znodes;
    double xbase, ybase, zbase;
    AdvParser()
    {
        xnodes = 0;
        ynodes = 0;
        znodes = 0;

        xbase = 0.0;
        ybase = 0.0;
        zbase = 0.0;
    };

    void setXnodes(int val) { this->xnodes = val; }
    void setYnodes(int val) { this->ynodes = val; }
    void setZnodes(int val) { this->znodes = val; }
    void setXbase(int val) { this->xbase = val; }
    void setYbase(int val) { this->ybase = val; }
    void setZbase(int val) { this->zbase = val; }
    int getXnodes() { return xnodes; }
    int getYnodes() { return ynodes; }
    int getZnodes() { return znodes; }
    double getXbase() { return xbase; }
    double getYbase() { return ybase; }
    double getZbase() { return zbase; }

    np::ndarray getOmfToList(std::string path)
    {
        // is a text file
        std::ifstream miffile;
        miffile.open(path, std::ios::out);
        int buffer_size = getMifHeader(miffile);

        int num_vectors = xnodes * ynodes * znodes;
        double *fut_ndarray = (double *)(malloc(sizeof(double) * num_vectors * 3));
        int i = 0;
        if (miffile.is_open())
        {
            std::string line;
            while (std::getline(miffile, line))
            {
                std::istringstream iss(line);
                if (line[0] == '#')
                    continue;
                else
                {
                    double x, y, z;
                    if (!(iss >> x >> y >> z))
                        break;
                    fut_ndarray[i + 0] = x;
                    fut_ndarray[i + 1] = y;
                    fut_ndarray[i + 2] = z;
                    i += 3;
                }
            }
        }
        miffile.close();
        boost::python::numpy::dtype dt1 = boost::python::numpy::dtype::get_builtin<double>();
        boost::python::tuple shape = boost::python::make_tuple(num_vectors, 3);
        boost::python::tuple stride = boost::python::make_tuple(3 * sizeof(double), sizeof(double));
        boost::python::numpy::ndarray vectorData = boost::python::numpy::from_data(fut_ndarray,
                                                                                dt1,
                                                                                shape,
                                                                                stride,
                                                                                boost::python::object());
        // last entry is object owner
        return vectorData;
    }

    int getMifHeader(std::ifstream &miffile)
    {
        std::string line;
        int buffer_size = 0;

        std::string node_reg("# xnodes:");
        std::string base_reg("# xbase:");

        // we have to change strategy here
        std::string data_text("# Begin: Data Text");
        if (miffile.is_open())
        {
            while (std::getline(miffile, line))
            {
                if (line.at(0) == '#')
                {
                    if (line == "# Begin: Data Binary 8")
                    {
                        buffer_size = 8;
                        break;
                    }
                    else if (line == "# Begin: Data Binary 4")
                    {
                        buffer_size = 4;
                        break;
                    }
                    else if (data_text.compare(line.substr(0, data_text.length())) == 0)
                    {
                        buffer_size = 1;
                        break;
                    }

                    if (checkNodes && line.substr(0, node_reg.length()) == node_reg)
                    {

                        if (node_reg.at(2) == 'x')
                        {
                            xnodes = std::stoi(line.substr(node_reg.length(), line.length()));
                            node_reg.at(2) = 'y';
                        }
                        else if (node_reg.at(2) == 'y')
                        {
                            ynodes = std::stoi(line.substr(node_reg.length(), line.length()));
                            node_reg.at(2) = 'z';
                        }
                        else
                        {
                            znodes = std::stoi(line.substr(node_reg.length(), line.length()));
                            checkNodes = false;
                        }
                    }
                    if (checkBase && line.substr(0, base_reg.length()) == base_reg)
                    {

                        if (base_reg.at(2) == 'x')
                        {
                            xbase = std::stod(line.substr(base_reg.length(), line.length()));
                            base_reg.at(2) = 'y';
                        }
                        else if (base_reg.at(2) == 'y')
                        {
                            ybase = std::stod(line.substr(base_reg.length(), line.length()));
                            base_reg.at(2) = 'z';
                        }
                        else
                        {
                            zbase = std::stod(line.substr(base_reg.length(), line.length()));
                            checkBase = false;
                        }
                    }
                }
                else
                    break;
            }
            if (buffer_size <= 0)
            {
                throw std::runtime_error("Invalid buffer size");
            }
            char IEEE_BUF[buffer_size + 1];

            miffile.read(IEEE_BUF, buffer_size);
            if (buffer_size == 4)
            {
                float IEEE_val;
                float IEEE_VALIDATION = 1234567.0;

                std::memcpy(&IEEE_val, IEEE_BUF, sizeof(float));
                if (IEEE_val != IEEE_VALIDATION)
                {
                    printf("%f\n", IEEE_val);
                    throw std::runtime_error("IEEE value not consistent");
                }
            }
            else if (buffer_size == 8)
            {
                double IEEE_val;
                double IEEE_VALIDATION = 123456789012345.0;
                std::memcpy(&IEEE_val, IEEE_BUF, sizeof(double));

                if (IEEE_val != IEEE_VALIDATION)
                {
                    printf("%f\n", IEEE_val);
                    throw std::runtime_error("IEEE value not consistent");
                }
            }
            else if (buffer_size == 1)
            {
                // this is a text file
                // actaully I don't know what I should do here
            }
            else
            {
                throw std::runtime_error("Unspecified or invalid buffer size for binary input data");
            }

            return buffer_size;
        }
        else
        {
            throw std::runtime_error("Invalid mif file");
        }
        return 0;
    }

    void matrix_vector_mul(double mat[3][3], double vec[3])
    {
        // only 3 x 1, inplace
        double c[3] = {vec[0], vec[1], vec[2]};
        vec[0] = mat[0][0] * c[0] + mat[0][1] * c[1] + mat[0][2] * c[2];
        vec[1] = mat[1][0] * c[0] + mat[1][1] * c[1] + mat[1][2] * c[2];
        vec[2] = mat[2][0] * c[0] + mat[2][1] * c[1] + mat[2][2] * c[2];
    }

    void matrix_vector_cpy(double mat[3][3], double c[3], double vec[3])
    {
        // only 3 x 1, not inplace
        vec[0] = mat[0][0] * c[0] + mat[0][1] * c[1] + mat[0][2] * c[2];
        vec[1] = mat[1][0] * c[0] + mat[1][1] * c[1] + mat[1][2] * c[2];
        vec[2] = mat[2][0] * c[0] + mat[2][1] * c[1] + mat[2][2] * c[2];
    }

    void a_cross_b(double a[3], double b[3], double c[3])
    {
        c[0] = a[1] * b[2] - a[2] * b[1];
        c[1] = a[2] * b[0] - a[0] * b[2];
        c[2] = a[0] * b[1] - a[1] * b[0];
    }

    void generateVBO(double *vbo,
                     int *offset,
                     int *normal_offset,
                     double position[3],
                     double vector[3], // from omf
                     double color[3],  // from omf
                     double t_rotation[3][3],
                     double height,
                     double radius,
                     int resolution)
    {
        double height_operator[3] = {0, 0, height * 1.5};
        double mag = sqrt(pow(vector[0], 2) +
                          pow(vector[1], 2) +
                          pow(vector[2], 2));
        double phi = acos(vector[2] / mag);         // z rot
        double theta = atan2(vector[1], vector[0]); // y rot
        double ct = cos(theta);
        double st = sin(theta);
        double cp = cos(phi);
        double sp = sin(phi);
        double rotation_matrix[3][3] = {
            {ct, -st * cp, st * sp},
            {st, cp * ct, -sp * ct},
            {0, sp, cp}};
        double origin_base[3], cpy[3];
        double cyllinder_co_rot[3] = {radius, radius, 0};
        double cone_co_rot[3] = {2 * radius, 2 * radius, 0};
        std::memcpy(origin_base, position, sizeof(double) * 3);
        double n[3], u[3], v[3];
        int prev_off = 36;
        for (int i = 0; i < (resolution - 1); i++)
        {
            // colors first
            std::memcpy(vbo + *offset, color, sizeof(double) * 3);
            std::memcpy(vbo + *offset + 9, color, sizeof(double) * 3);
            std::memcpy(vbo + *offset + 18, color, sizeof(double) * 3);
            std::memcpy(vbo + *offset + 27, color, sizeof(double) * 3);
            // bottom triangle - cyllinder
            matrix_vector_cpy(rotation_matrix, cyllinder_co_rot, cpy);
            vbo[*offset + 3] = origin_base[0] + cpy[0];
            vbo[*offset + 4] = origin_base[1] + cpy[1];
            vbo[*offset + 5] = origin_base[2] + cpy[2];

            // bottom triangle - cone
            cone_co_rot[2] += height;
            matrix_vector_cpy(rotation_matrix, cone_co_rot, cpy);
            vbo[*offset + 6] = origin_base[0] + cpy[0];
            vbo[*offset + 7] = origin_base[1] + cpy[1];
            vbo[*offset + 8] = origin_base[2] + cpy[2];

            // top triangle - cyllinder
            cyllinder_co_rot[2] += height;
            matrix_vector_cpy(rotation_matrix, cyllinder_co_rot, cpy);
            vbo[*offset + 21] = origin_base[0] + cpy[0];
            vbo[*offset + 22] = origin_base[1] + cpy[1];
            vbo[*offset + 23] = origin_base[2] + cpy[2];

            // top triangle - cone
            matrix_vector_mul(rotation_matrix, height_operator);
            vbo[*offset + 24] = origin_base[0] + height_operator[0];
            vbo[*offset + 25] = origin_base[1] + height_operator[1];
            vbo[*offset + 26] = origin_base[2] + height_operator[2];
            height_operator[0] = 0;
            height_operator[1] = 0;
            height_operator[2] = 1.5 * height;
            cyllinder_co_rot[2] -= height;
            cone_co_rot[2] -= height;

            matrix_vector_mul(t_rotation, cyllinder_co_rot);
            matrix_vector_mul(t_rotation, cone_co_rot);

            if (i > 0)
            {
                // firstly compute the later indices for vertex
                // because we will use them to compute the normals

                u[0] = vbo[*offset + 3] - vbo[*offset - prev_off + 3];
                u[1] = vbo[*offset + 4] - vbo[*offset - prev_off + 4];
                u[2] = vbo[*offset + 5] - vbo[*offset - prev_off + 5];

                v[0] = vbo[*offset - prev_off + 21] - vbo[*offset - prev_off + 3];
                v[1] = vbo[*offset - prev_off + 22] - vbo[*offset - prev_off + 4];
                v[2] = vbo[*offset - prev_off + 23] - vbo[*offset - prev_off + 5];

                // cross product
                a_cross_b(u, v, n);
                vbo[*normal_offset + 0] = n[0];
                vbo[*normal_offset + 1] = n[1];
                vbo[*normal_offset + 2] = n[2];

                vbo[*normal_offset + 18] = n[0];
                vbo[*normal_offset + 19] = n[1];
                vbo[*normal_offset + 20] = n[2];
                // normals to the cone triangle
                u[0] = vbo[*offset + 6] - vbo[*offset - prev_off + 6];
                u[1] = vbo[*offset + 7] - vbo[*offset - prev_off + 7];
                u[2] = vbo[*offset + 8] - vbo[*offset - prev_off + 8];

                v[0] = vbo[*offset - prev_off + 24] - vbo[*offset - prev_off + 6];
                v[1] = vbo[*offset - prev_off + 25] - vbo[*offset - prev_off + 7];
                v[2] = vbo[*offset - prev_off + 26] - vbo[*offset - prev_off + 8];

                // cross product
                a_cross_b(u, v, n);
                vbo[*normal_offset + 3] = n[0];
                vbo[*normal_offset + 4] = n[1];
                vbo[*normal_offset + 5] = n[2];

                vbo[*normal_offset + 21] = n[0];
                vbo[*normal_offset + 22] = n[1];
                vbo[*normal_offset + 23] = n[2];
                *normal_offset += 18;
                *normal_offset += 18;
            }
            *offset += prev_off;
        }
        // colors first
        std::memcpy(vbo + *offset, color, sizeof(double) * 3);
        std::memcpy(vbo + *offset + 9, color, sizeof(double) * 3);
        std::memcpy(vbo + *offset + 18, color, sizeof(double) * 3);
        std::memcpy(vbo + *offset + 27, color, sizeof(double) * 3);
        // reset rotational vectors to their defaults
        cyllinder_co_rot[0] = radius;
        cyllinder_co_rot[1] = radius;
        cyllinder_co_rot[2] = 0;
        matrix_vector_mul(rotation_matrix, cyllinder_co_rot);
        vbo[*offset + 3] = origin_base[0] + cyllinder_co_rot[0];
        vbo[*offset + 4] = origin_base[1] + cyllinder_co_rot[1];
        vbo[*offset + 5] = origin_base[2] + cyllinder_co_rot[2];

        cone_co_rot[0] = 2 * radius;
        cone_co_rot[1] = 2 * radius;
        cone_co_rot[2] = height;
        matrix_vector_mul(rotation_matrix, cone_co_rot);
        vbo[*offset + 6] = origin_base[0] + cone_co_rot[0];
        vbo[*offset + 7] = origin_base[1] + cone_co_rot[1];
        vbo[*offset + 8] = origin_base[2] + cone_co_rot[2];

        cyllinder_co_rot[0] = radius;
        cyllinder_co_rot[1] = radius;
        cyllinder_co_rot[2] += height;
        matrix_vector_mul(rotation_matrix, cyllinder_co_rot);
        vbo[*offset + 21] = origin_base[0] + cyllinder_co_rot[0];
        vbo[*offset + 22] = origin_base[1] + cyllinder_co_rot[1];
        vbo[*offset + 23] = origin_base[2] + cyllinder_co_rot[2];

        height_operator[0] = 0;
        height_operator[1] = 0;
        height_operator[2] = 1.5 * height;
        matrix_vector_mul(rotation_matrix, height_operator);
        vbo[*offset + 24] = origin_base[0] + height_operator[0];
        vbo[*offset + 25] = origin_base[1] + height_operator[1];
        vbo[*offset + 26] = origin_base[2] + height_operator[2];

        // normals to the cyllinder triangle
        u[0] = vbo[*offset + 3] - vbo[*offset - prev_off + 3];
        u[1] = vbo[*offset + 4] - vbo[*offset - prev_off + 4];
        u[2] = vbo[*offset + 5] - vbo[*offset - prev_off + 5];

        v[0] = vbo[*offset - prev_off + 21] - vbo[*offset - prev_off + 3];
        v[1] = vbo[*offset - prev_off + 22] - vbo[*offset - prev_off + 4];
        v[2] = vbo[*offset - prev_off + 23] - vbo[*offset - prev_off + 5];

        // cross product
        a_cross_b(u, v, n);
        vbo[*normal_offset + 0] = n[0];
        vbo[*normal_offset + 1] = n[1];
        vbo[*normal_offset + 2] = n[2];

        vbo[*normal_offset + 18] = n[0];
        vbo[*normal_offset + 19] = n[1];
        vbo[*normal_offset + 20] = n[2];

        // normals to the cone triangle
        u[0] = vbo[*offset + 6] - vbo[*offset - prev_off + 6];
        u[1] = vbo[*offset + 7] - vbo[*offset - prev_off + 7];
        u[2] = vbo[*offset + 8] - vbo[*offset - prev_off + 8];

        v[0] = vbo[*offset - prev_off + 24] - vbo[*offset - prev_off + 6];
        v[1] = vbo[*offset - prev_off + 25] - vbo[*offset - prev_off + 7];
        v[2] = vbo[*offset - prev_off + 26] - vbo[*offset - prev_off + 8];

        // cross product
        a_cross_b(u, v, n);
        vbo[*normal_offset + 3] = n[0];
        vbo[*normal_offset + 4] = n[1];
        vbo[*normal_offset + 5] = n[2];

        vbo[*normal_offset + 21] = n[0];
        vbo[*normal_offset + 22] = n[1];
        vbo[*normal_offset + 23] = n[2];
        *normal_offset += 18;
        *normal_offset += 18;

        // LAST NORMALS MUST CATCH UP and are the same as previous
        vbo[*normal_offset + 0] = n[0];
        vbo[*normal_offset + 1] = n[1];
        vbo[*normal_offset + 2] = n[2];
        vbo[*normal_offset + 3] = n[0];
        vbo[*normal_offset + 4] = n[1];
        vbo[*normal_offset + 5] = n[2];
        *normal_offset += 18;
        vbo[*normal_offset + 0] = n[0];
        vbo[*normal_offset + 1] = n[1];
        vbo[*normal_offset + 2] = n[2];
        vbo[*normal_offset + 3] = n[0];
        vbo[*normal_offset + 4] = n[1];
        vbo[*normal_offset + 5] = n[2];
        *normal_offset += 18;
        *offset += prev_off;
    }

    void generateCubes2(double *sh, double position[3], double dimensions[3], int current_pos)
    {
        double arr[144] = {
            //TOP FACE
            position[0] + dimensions[0], position[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0], position[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0], position[1] + dimensions[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0] + dimensions[0], position[1] + dimensions[1], position[2] + dimensions[2],
            0, 0, 0,
            //BOTTOM FACE
            position[0] + dimensions[0], position[1], position[2],
            0, 0, 0,
            position[0], position[1], position[2],
            0, 0, 0,
            position[0], position[1] + dimensions[1], position[2],
            0, 0, 0,
            position[0] + dimensions[0], position[1] + dimensions[1], position[2],
            0, 0, 0,
            //FRONT FACE
            position[0] + dimensions[0], position[1] + dimensions[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0], position[1] + dimensions[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0], position[1] + dimensions[1], position[2],
            0, 0, 0,
            position[0] + dimensions[0], position[1] + dimensions[1], position[2],
            0, 0, 0,
            //BACK FACE
            position[0] + dimensions[0], position[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0], position[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0], position[1], position[2],
            0, 0, 0,
            position[0] + dimensions[0], position[1], position[2],
            0, 0, 0,
            //RIGHT FACE
            position[0] + dimensions[0], position[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0] + dimensions[0], position[1] + dimensions[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0] + dimensions[0], position[1] + dimensions[1], position[2],
            0, 0, 0,
            position[0] + dimensions[0], position[1], position[2],
            0, 0, 0,
            //LEFT FACE
            position[0], position[1] + dimensions[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0], position[1], position[2] + dimensions[2],
            0, 0, 0,
            position[0], position[1], position[2],
            0, 0, 0,
            position[0], position[1] + dimensions[1], position[2],
            0, 0, 0};

        // normals here
        double ux, uy, uz;
        double vx, vy, vz;
        int offset = 0;
        for (int i = 0; i < 6; i++)
        {
            ux = arr[offset + 0] - arr[offset + 6]; // first vertex
            uy = arr[offset + 1] - arr[offset + 7];
            uz = arr[offset + 2] - arr[offset + 8];

            vx = arr[offset + 0] - arr[offset + 12]; // second vertex
            vy = arr[offset + 1] - arr[offset + 13];
            vz = arr[offset + 2] - arr[offset + 14];

            // cross product
            for (int j = 0; j < 4; j++)
            {
                arr[offset + 3 + j * 6 + 0] = uy * vz - vy * uz;
                arr[offset + 3 + j * 6 + 1] = ux * uz - vx - vz;
                arr[offset + 3 + j * 6 + 2] = ux * vy - uy * vx;
            }
            offset += 24;
        }
        std::memcpy(sh + current_pos, arr, sizeof(double) * 144);
    }

    np::ndarray generateIndices(int N, int index_required)
    {
        int start_index = 0;
        int *indices = (int *)malloc(sizeof(int) * (3 * (index_required - 2) * N));
        for (int n = 0; n < N; n++)
        {
            start_index = n * index_required + 3;
            for (int i = 0; i < (index_required - 2); i++)
            {
                indices[n * (index_required - 2) * 3 + i * 3 + 0] = start_index + i - 3;
                indices[n * (index_required - 2) * 3 + i * 3 + 1] = start_index + i - 2;
                indices[n * (index_required - 2) * 3 + i * 3 + 2] = start_index + i - 1;
            }
        }
        np::dtype dt = np::dtype::get_builtin<int>();
        p::tuple shape = p::make_tuple(3 * (index_required - 2) * N);
        p::tuple stride = p::make_tuple(sizeof(int));
        p::handle<> h(::PyCapsule_New((void *)indices,
                                      NULL,
                                      (PyCapsule_Destructor)&destroyManagerCObject));
        return np::from_data(indices,
                             dt,
                             shape,
                             stride,
                             p::object(h));
    }
    np::ndarray getCubeOutline(int xn, int yn, int zn,
                               double xb, double yb, double zb,
                               int sampling,
                               int start_layer, int stop_layer)
    {
        /*
        Cube MIF OUTLINE
        2 VBOs because the geometry is constant
        1) Geometry + Normal VBO
        2) Color VBO
        */
        int per_vertex = 144;
        // per_vertex = 72;
        double *sh = (double *)(malloc(sizeof(double) * xn * yn * zn * per_vertex));
        double dimensions[3] = {xb * sampling, yb * sampling, zb};
        int current_pos = 0;
        for (int z = start_layer; z < stop_layer; z += 1)
        {
            for (int y = 0; y < ynodes; y += sampling)
            {
                for (int x = 0; x < xnodes; x += sampling)
                {
                    double position[3] = {xb * (x % xn) - xn * xb / 2,
                                          yb * (y % yn) - yn * yb / 2,
                                          zb * (z % zn) - zn * zb / 2};
                    generateCubes2(sh, position, dimensions, current_pos);
                    current_pos += per_vertex;
                }
            }
        }

        np::dtype dt = np::dtype::get_builtin<double>();
        p::tuple shape = p::make_tuple(xn * yn * zn * per_vertex);
        p::tuple stride = p::make_tuple(sizeof(double));
        p::handle<> h(::PyCapsule_New((void *)sh,
                                      NULL,
                                      (PyCapsule_Destructor)&destroyManagerCObject));
        return np::from_data(sh,
                             dt,
                             shape,
                             stride,
                             p::object(h));
    }
    void getHeader(std::string filepath)
    {
        std::ifstream miffile;
        miffile.open(filepath, std::ios::out | std::ios_base::binary);
        getMifHeader(miffile);
        miffile.close();
    }

    np::ndarray getShapeAsNdarray(double xb, double yb, double zb)
    {
        double *sh = (double *)(malloc(sizeof(double) * xnodes * ynodes * znodes * 3));
        int current_pos = 0;
        for (int z = 0; z < znodes; z++)
        {
            for (int y = 0; y < ynodes; y++)
            {
                for (int x = 0; x < xnodes; x++)
                {
                    sh[current_pos + 0] = xb * (x % xnodes) - xnodes * xb / 2;
                    sh[current_pos + 1] = yb * (y % ynodes) - ynodes * yb / 2;
                    sh[current_pos + 2] = zb * (z % znodes) - znodes * zb / 2;
                    current_pos += 3;
                }
            }
        }
        np::dtype dt = np::dtype::get_builtin<double>();
        p::tuple shape = p::make_tuple(current_pos);
        p::tuple stride = p::make_tuple(sizeof(double));
        p::handle<> h(::PyCapsule_New((void *)sh,
                                      NULL,
                                      (PyCapsule_Destructor)&destroyManagerCObject));
        return np::from_data(sh,
                             dt,
                             shape,
                             stride,
                             p::object(h));
    }

    np::ndarray getMifAsNdarrayWithColor(std::string path,
                                         p::list color_vector_l,
                                         p::list positive_color_l,
                                         p::list negative_color_l,
                                         int sampling,
                                         int start_layer,
                                         int stop_layer,
                                         int binary)
    {
        double color_vector[3] = {
            p ::extract<double>(color_vector_l[0]),
            p ::extract<double>(color_vector_l[1]),
            p ::extract<double>(color_vector_l[2])};
        double positive_color[3] = {
            p ::extract<double>(positive_color_l[0]),
            p ::extract<double>(positive_color_l[1]),
            p ::extract<double>(positive_color_l[2])};

        double negative_color[3] = {
            p ::extract<double>(negative_color_l[0]),
            p ::extract<double>(negative_color_l[1]),
            p ::extract<double>(negative_color_l[2])};

        std::ifstream miffile;
        std::ios::openmode openmod;
        if (binary)
        {
            openmod = std::ios::out | std::ios_base::binary;
        }
        else
        {
            openmod = std::ios::out;
        }
        miffile.open(path, openmod);
        int buffer_size = getMifHeader(miffile);
        if (buffer_size == 0)
        {
            miffile.close();
            throw std::runtime_error("Invalid mif/ovf file");
        }

        int lines = znodes * xnodes * ynodes;

        double *vals;
        vals = (double *)malloc(sizeof(double) * lines * 3);

        if (buffer_size == 4)
        {
            char buffer[buffer_size * lines * 3];
            miffile.read(buffer, buffer_size * lines * 3);
            float fvals[lines * 3];
            std::memcpy(fvals, buffer, lines * 3 * sizeof(float));
            for (int i = 0; i < lines * 3; i++)
            {
                vals[i] = fvals[i];
            }
        }
        else if (buffer_size == 8)
        {
            char buffer[buffer_size * lines * 3];
            miffile.read(buffer, buffer_size * lines * 3);
            vals = (double *)(buffer);
        }
        else if (buffer_size == 1)
        {
            int i = 0;
            if (miffile.is_open())
            {
                std::string line;
                while (std::getline(miffile, line))
                {
                    std::istringstream iss(line);
                    if (line[0] == '#')
                        continue;
                    else
                    {
                        double x, y, z;
                        if (!(iss >> x >> y >> z))
                            break;
                        vals[i + 0] = x;
                        vals[i + 1] = y;
                        vals[i + 2] = z;
                        i += 3;
                    }
                }
            }
        }
        miffile.close();

        int inflate = 24; // 24 vetrtivesd in a cube

        double *fut_ndarray = (double *)(malloc(sizeof(double) * znodes * xnodes * ynodes * 3 * inflate));
        double mag, dot;
        double *array_to_cpy = (double *)(malloc(sizeof(double) * 3));
        int offset = 0;
        int index = 0;

        for (int z = start_layer; z < stop_layer; z += 1)
        {
            for (int y = 0; y < ynodes; y += sampling)
            {
                for (int x = 0; x < xnodes; x += sampling)
                {
                    index = 3 * (x + xnodes * y + xnodes * ynodes * z);
                    mag = sqrt(pow(vals[index + 0], 2) +
                               pow(vals[index + 1], 2) +
                               pow(vals[index + 2], 2));
                    if (mag != 0.0)
                    {
                        dot = (vals[index + 0] / mag) * color_vector[0] +
                              (vals[index + 1] / mag) * color_vector[1] +
                              (vals[index + 2] / mag) * color_vector[2];

                        if (dot > 0)
                        {
                            array_to_cpy[0] = positive_color[0] * dot + (1.0 - dot);
                            array_to_cpy[1] = positive_color[1] * dot + (1.0 - dot);
                            array_to_cpy[2] = positive_color[2] * dot + (1.0 - dot);
                        }
                        else
                        {
                            dot *= -1;

                            array_to_cpy[0] = negative_color[0] * dot + (1.0 - dot);
                            array_to_cpy[1] = negative_color[1] * dot + (1.0 - dot);
                            array_to_cpy[2] = negative_color[2] * dot + (1.0 - dot);
                        }
                    }
                    else
                    {
                        array_to_cpy[0] = 0.0;
                        array_to_cpy[1] = 0.0;
                        array_to_cpy[2] = 0.0;
                    }

                    for (int inf = 0; inf < inflate; inf++)
                    {
                        std::memcpy(fut_ndarray + offset, array_to_cpy, sizeof(double) * 3);
                        offset += 3;
                    }
                }
            }
        }

        np::dtype dt = np::dtype::get_builtin<double>();
        p::tuple shape = p::make_tuple(offset);
        p::tuple stride = p::make_tuple(sizeof(double));
        p::handle<> h(::PyCapsule_New((void *)fut_ndarray,
                                      NULL,
                                      (PyCapsule_Destructor)&destroyManagerCObject));
        return np::from_data(fut_ndarray,
                             dt,
                             shape,
                             stride,
                             p::object(h));
    }

    np::ndarray getMifVBO(std::string path,
                          int resolution,
                          p::list color_vector_l,
                          p::list positive_color_l,
                          p::list negative_color_l,
                          int sampling,
                          double height,
                          double radius,
                          int start_layer,
                          int stop_layer,
                          int xscaler,
                          int yscaler,
                          int zscaler,
                          int binary)
    {
        if (start_layer < 0 || start_layer > stop_layer)
        {
            throw std::invalid_argument("Start layer cannot be smaller than stop layer");
        }
        if (stop_layer > znodes)
        {
            throw std::invalid_argument("Stop layer too large");
        }
        double color_vector[3] = {
            p ::extract<double>(color_vector_l[0]),
            p ::extract<double>(color_vector_l[1]),
            p ::extract<double>(color_vector_l[2])};
        double positive_color[3] = {
            p ::extract<double>(positive_color_l[0]),
            p ::extract<double>(positive_color_l[1]),
            p ::extract<double>(positive_color_l[2])};

        double negative_color[3] = {
            p ::extract<double>(negative_color_l[0]),
            p ::extract<double>(negative_color_l[1]),
            p ::extract<double>(negative_color_l[2])};

        std::ifstream miffile;
        std::ios::openmode openmod;
        if (binary)
        {
            openmod = std::ios::out | std::ios_base::binary;
        }
        else
        {
            openmod = std::ios::out;
        }
        miffile.open(path, openmod);
        int buffer_size = getMifHeader(miffile);
        if (buffer_size == 0)
        {
            miffile.close();
            throw std::runtime_error("Invalid mif/ovf file");
        }

        int lines = znodes * xnodes * ynodes;
        double *vals;
        vals = (double *)malloc(sizeof(double) * lines * 3);
        if (buffer_size == 4)
        {
            char buffer[buffer_size * lines * 3];
            miffile.read(buffer, buffer_size * lines * 3);
            float fvals[lines * 3];
            std::memcpy(fvals, buffer, lines * 3 * sizeof(float));
            for (int i = 0; i < lines * 3; i++)
            {
                vals[i] = fvals[i];
            }
        }
        else if (buffer_size == 8)
        {
            char buffer[buffer_size * lines * 3];
            miffile.read(buffer, buffer_size * lines * 3);
            // research why this casting with memcpy causes SIGSEV
            // std::memcpy(vals, buffer, lines * 3 * sizeof(double));
            vals = (double *)(buffer);
        }
        else if (buffer_size == 1)
        {
            int i = 0;
            if (miffile.is_open())
            {
                std::string line;
                while (std::getline(miffile, line))
                {
                    std::istringstream iss(line);
                    if (line[0] == '#')
                        continue;
                    else
                    {
                        double x, y, z;
                        if (!(iss >> x >> y >> z))
                            break;
                        vals[i + 0] = x;
                        vals[i + 1] = y;
                        vals[i + 2] = z;
                        i += 3;
                    }
                }
            }
        }
        miffile.close();

        int size = xnodes * ynodes * znodes * resolution * 10 * 3;
        double *fut_ndarray = (double *)(malloc(sizeof(double) * size));
        // double fut_ndarray[size];
        if (fut_ndarray == NULL)
        {
            throw std::runtime_error("Failed to allocate memory for a large array");
        }
        double pos[3], vec[3], col[3];
        double mag, dot;
        int offset = 0;
        int index = 0;
        int normal_offset = 12;

        double theta = 2 * M_PI / resolution;
        double c = cos(theta);
        double s = sin(theta);
        double t_rotation[3][3] = {
            {c, -s, 0},
            {s, c, 0},
            {0, 0, 1}};

        double xb = xscaler * 1e9 * xbase / sampling;
        double yb = yscaler * 1e9 * ybase / sampling;
        double zb = zscaler * 1e9 * zbase / sampling;

        double xoffset = xnodes * xb / 2;
        double yoffset = ynodes * yb / 2;
        double zoffset = znodes * zb / 2;
        for (int z = start_layer; z < stop_layer; z += 1)
        {
            for (int y = 0; y < ynodes; y += sampling)
            {
                for (int x = 0; x < xnodes; x += sampling)
                {
                    index = 3 * (x + xnodes * y + xnodes * ynodes * z);
                    mag = sqrt(pow(vals[index + 0], 2) +
                               pow(vals[index + 1], 2) +
                               pow(vals[index + 2], 2));
                    if (mag == 0.0)
                        continue;

                    pos[0] = xb * (x % xnodes) - xoffset;
                    pos[1] = yb * (y % ynodes) - yoffset;
                    pos[2] = zb * (z % znodes) - zoffset;
                    vec[0] = vals[index + 0] / mag;
                    vec[1] = vals[index + 1] / mag;
                    vec[2] = vals[index + 2] / mag;
                    dot = vec[0] * color_vector[0] +
                          vec[1] * color_vector[1] +
                          vec[2] * color_vector[2];

                    if (dot > 0)
                    {
                        col[0] = positive_color[0] * dot + (1.0 - dot);
                        col[1] = positive_color[1] * dot + (1.0 - dot);
                        col[2] = positive_color[2] * dot + (1.0 - dot);
                    }
                    else
                    {
                        dot *= -1;
                        col[0] = negative_color[0] * dot + (1.0 - dot);
                        col[1] = negative_color[1] * dot + (1.0 - dot);
                        col[2] = negative_color[2] * dot + (1.0 - dot);
                    }
                    generateVBO(
                        fut_ndarray,
                        &offset,
                        &normal_offset,
                        pos,
                        vec,
                        col,
                        t_rotation,
                        height,
                        radius,
                        resolution);
                }
            }
        }
        np::dtype dt = np::dtype::get_builtin<double>();
        p::tuple shape = p::make_tuple(offset);
        p::tuple stride = p::make_tuple(sizeof(double));
        p::handle<> h(::PyCapsule_New((void *)fut_ndarray,
                                      NULL,
                                      (PyCapsule_Destructor)&destroyManagerCObject));
        return np::from_data(fut_ndarray,
                             dt,
                             shape,
                             stride,
                             p::object(h));
    }

    np::ndarray getMifAsNdarray(std::string path)
    {

        std::ifstream miffile;
        miffile.open(path, std::ios::out | std::ios_base::binary);
        int buffer_size = getMifHeader(miffile);
        if (buffer_size == 0)
        {
            miffile.close();
            throw std::runtime_error("Invalid mif file");
        }

        int lines = znodes * xnodes * ynodes;
        char buffer[buffer_size * lines * 3];
        miffile.read(buffer, buffer_size * lines * 3);
        double *vals = (double *)buffer;
        double *fut_ndarray = (double *)(malloc(sizeof(double) * lines * 3));
        double mag;
        for (int i = 0; i < lines * 3; i += 3)
        {
            mag = sqrt(pow(vals[i + 0], 2) + pow(vals[i + 1], 2) + pow(vals[i + 2], 2));
            if (mag == 0.0)
                mag = 1.0;
            fut_ndarray[i + 0] = vals[i + 0] / mag;
            fut_ndarray[i + 1] = vals[i + 1] / mag;
            fut_ndarray[i + 2] = vals[i + 2] / mag;
        }
        miffile.close();
        // use explicit namespace here to make sure it does not mix the functions
        np::dtype dt1 = np::dtype::get_builtin<double>();
        p::tuple shape = p::make_tuple(lines, 3);
        p::tuple stride = p::make_tuple(3 * sizeof(double), sizeof(double));
        p::handle<> h(::PyCapsule_New((void *)fut_ndarray,
                                      NULL,
                                      (PyCapsule_Destructor)&destroyManagerCObject));
        np::ndarray vectorData = np::from_data(fut_ndarray,
                                               dt1,
                                               shape,
                                               stride,
                                               p::object(h));
        // last entry is object owner
        return vectorData;
    }
};

BOOST_PYTHON_MODULE(AdvParser)
{
    // avoids the SIGSEV on dtype in numpy initialization
    boost::python::numpy::initialize();

    using namespace boost::python;

    class_<AdvParser>("AdvParser")
        .def(init<>())
        .def(init<AdvParser>())

        .def("getMifAsNdarrayWithColor", &AdvParser::getMifAsNdarrayWithColor)
        .def("getMifVBO", &AdvParser::getMifVBO)
        .def("getCubeOutline", &AdvParser::getCubeOutline)
        .def("getOmfToList", &AdvParser::getOmfToList)
        .def("getMifAsNdarray", &AdvParser::getMifAsNdarray)

        .def("getHeader", &AdvParser::getHeader)
        .def("getShapeAsNdarray", &AdvParser::getShapeAsNdarray)
        .def("generateIndices", &AdvParser::generateIndices)

        .add_property("xnodes", &AdvParser::getXnodes, &AdvParser::setXnodes)
        .add_property("ynodes", &AdvParser::getYnodes, &AdvParser::setYnodes)
        .add_property("znodes", &AdvParser::getZnodes, &AdvParser::setZnodes)

        .add_property("xbase", &AdvParser::getXbase, &AdvParser::setXbase)
        .add_property("ybase", &AdvParser::getYbase, &AdvParser::setYbase)
        .add_property("zbase", &AdvParser::getZbase, &AdvParser::setZbase);
}


//---------------------------------------------------------------------------//
//!
//! \file   MonteCarlo_DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution_def.hpp
//! \author Alex Robinson
//! \brief  The decoupled complete Doppler broadened photon energy dist. def.
//!
//---------------------------------------------------------------------------//

#ifndef MONTE_CARLO_DECOUPLED_STANDARD_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_DEF_HPP
#define MONTE_CARLO_DECOUPLED_STANDARD_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_DEF_HPP

// Std Lib Includes
#include <algorithm>

// Boost Includes
#include <boost/function.hpp>
#include <boost/bind.hpp>

// FRENSIE Includes
#include "MonteCarlo_DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution.hpp"
#include "Utility_GaussKronrodIntegrator.hpp"
#include "Utility_DiscreteDistribution.hpp"
#include "Utility_ExplicitTemplateInstantiationMacros.hpp"
#include "Utility_DesignByContract.hpp"

namespace MonteCarlo{

// Constructor
/*! \details The Compton profile grids must be in me*c units (not atomic
 * units). The Compton profiles must be in inverse me*c units (not inverse
 * atomic units). Only half profiles should be provided (grid ranges from 0.0
 * to 1.0).
 */
template<typename ComptonProfilePolicy>
DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution(
   const std::vector<double>& endf_subshell_occupancies,
   const std::vector<Data::SubshellType>& endf_subshell_order,
   const std::vector<double>& old_subshell_binding_energies,
   const std::vector<double>& old_subshell_occupancies,
   const std::shared_ptr<const ComptonProfileSubshellConverter>&
   subshell_converter,
   const CompleteDopplerBroadenedPhotonEnergyDistribution::ComptonProfileArray&
   compton_profile_array )
  : StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>(
                                                     endf_subshell_occupancies,
                                                     endf_subshell_order,
                                                     subshell_converter,
                                                     compton_profile_array ),
  d_old_subshell_occupancy_distribution(),
  d_old_subshell_binding_energy( old_subshell_binding_energies ),
  d_old_subshell_occupancies( old_subshell_occupancies ),
  d_min_binding_energy_index( 0 )
{
  // Make sure the old shell data is valid
  testPrecondition( old_subshell_binding_energies.size() > 0 );
  testPrecondition( old_subshell_occupancies.size() ==
		    old_subshell_binding_energies.size() );
  // Make sure the comptron profile array is valid
  testPrecondition( compton_profile_array.size() ==
		    old_subshell_binding_energies.size() );

  // Create the old subshell interaction distribution
  std::vector<double> dummy_indep_vals( old_subshell_occupancies.size() );

  d_old_subshell_occupancy_distribution.reset(
	       new Utility::DiscreteDistribution( dummy_indep_vals,
					          old_subshell_occupancies ) );

  // Calculate the min binding energy index
  std::vector<double>::iterator min_binding_energy_it =
    std::min_element( d_old_subshell_binding_energy.begin(),
                      d_old_subshell_binding_energy.end() );

  d_min_binding_energy_index =
    std::distance( d_old_subshell_binding_energy.begin(),
                   min_binding_energy_it );
}

// Return the binding energy of a subshell
template<typename ComptonProfilePolicy>
double DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::getSubshellBindingEnergy(
                                            const Data::SubshellType subshell ) const
{
  // Make sure the subshell is valid
  testPrecondition( this->isValidSubshell( subshell ) );

  unsigned old_subshell_index = this->getOldSubshellIndex( subshell );

  return d_old_subshell_binding_energy[old_subshell_index];
}

// Return the occupancy of a subshell (default is the ENDF occupancy)
template<typename ComptonProfilePolicy>
double DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::getSubshellOccupancy(
                                            const Data::SubshellType subshell ) const
{
  // Make sure the subshell is valid
  testPrecondition( this->isValidSubshell( subshell ) );

  unsigned old_subshell_index = this->getOldSubshellIndex( subshell );

  return d_old_subshell_occupancies[old_subshell_index];
}

// Evaluate the distribution with electron momentum projection
/*! \details Because the old subshell binding energies and subshell occupancy
 * data are used the procedure for evaluating the total cross section must be
 * changed. The electron momentum projection must be in me*c units
 * (a momentum value of me*c kg*m/s is 1.0 in me*c units). The distribution
 * will have units of barns since the unitless momentum is being used.
 */
template<typename ComptonProfilePolicy>
double DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateWithElectronMomentumProjection(
                                   const double incoming_energy,
                                   const double electron_momentum_projection,
                                   const double scattering_angle_cosine ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the electron momentum projection is valid
  testPrecondition( electron_momentum_projection >= -1.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  // The total double differential cross section
  double cross_section = 0.0;

  // Evaluate each subshell
  for( unsigned i = 0; i < d_old_subshell_binding_energy.size(); ++i )
  {
    // Get the subshell binding energy
    const double subshell_binding_energy = d_old_subshell_binding_energy[i];

    // Calculate the max electron momentum projection
    ComptonProfile::MomentumQuantity max_electron_momentum_projection =
      calculateMaxElectronMomentumProjection( incoming_energy,
                                              subshell_binding_energy,
                                              scattering_angle_cosine )*
    ComptonProfile::MomentumUnit();

    // Get the subshell occupancy
    const double subshell_occupancy = d_old_subshell_occupancies[i];

    // Get the subshell Compton profile
    const ComptonProfile& compton_profile = this->getComptonProfile( i );

    cross_section += subshell_occupancy*
      ComptonProfilePolicy::evaluateWithPossibleLimit(
                   compton_profile,
                   electron_momentum_projection*ComptonProfile::MomentumUnit(),
                   max_electron_momentum_projection ).value();
  }

  cross_section *= this->evaluateMultiplier( incoming_energy,
                                             scattering_angle_cosine )*
    this->evaluateRelativisticTerm( incoming_energy,
                                    scattering_angle_cosine );

  // Make sure the cross section is valid
  testPostcondition( cross_section >= 0.0 );

  return cross_section;
}

// Evaluate the exact distribution
/*! \details Because the old subshell binding energies and subshell occupancy
 * data are used the procedure for evaluating the total cross section must be
 * changed. The distribution has units of barns/MeV.
 */
template<typename ComptonProfilePolicy>
double DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateExact(
				   const double incoming_energy,
				   const double outgoing_energy,
				   const double scattering_angle_cosine ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the outgoing energy is valid
  testPrecondition( outgoing_energy <= incoming_energy );
  testPrecondition( outgoing_energy >= 0.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  // Calculate the electron momentum projection
  const ComptonProfile::MomentumQuantity electron_momentum_projection =
    ComptonProfile::MomentumUnit()*
    calculateElectronMomentumProjection( incoming_energy,
                                         outgoing_energy,
                                         scattering_angle_cosine );

  // The total double differential cross section
  double cross_section = 0.0;

  // Evaluate each subshell
  for( unsigned i = 0; i < d_old_subshell_binding_energy.size(); ++i )
  {
    // Get the subshell binding energy
    const double subshell_binding_energy = d_old_subshell_binding_energy[i];

    // Get the subshell occupancy
    const double subshell_occupancy = d_old_subshell_occupancies[i];

    // Get the subshell Compton profile
    const ComptonProfile& compton_profile = this->getComptonProfile( i );

    if( outgoing_energy <= incoming_energy - subshell_binding_energy )
    {
      cross_section += subshell_occupancy*
        ComptonProfilePolicy::evaluate( compton_profile,
                                        electron_momentum_projection ).value();
    }
  }

  cross_section *= this->evaluateMultiplierExact( incoming_energy,
                                                  outgoing_energy,
                                                  scattering_angle_cosine )*
    this->evaluateRelativisticTermExact( incoming_energy,
                                         outgoing_energy,
                                         scattering_angle_cosine );

  // Make sure the cross section is valid
  testPostcondition( cross_section >= 0.0 );

  return cross_section;
}

// Evaluate the integrated cross section
template<typename ComptonProfilePolicy>
double DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateIntegratedCrossSection(
                                         const double incoming_energy,
                                         const double scattering_angle_cosine,
                                         const double precision ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  boost::function<double (double x)> double_diff_cs_wrapper =
    boost::bind<double>( &DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution::evaluateWithElectronMomentumProjection,
                         boost::cref( *this ),
                         incoming_energy,
                         _1,
                         scattering_angle_cosine );

  // Get the min subshell binding energy
  const double subshell_binding_energy =
      d_old_subshell_binding_energy[d_min_binding_energy_index];

  // Get the subshell Compton profile
  const ComptonProfile& compton_profile =
    this->getComptonProfile( d_min_binding_energy_index );

  // Calculate the max electron momentum projection
  double pz_max =
    calculateMaxElectronMomentumProjection( incoming_energy,
                                            subshell_binding_energy,
                                            scattering_angle_cosine );

  // Don't go above the table max (profile will evaluate to zero beyond it)
  pz_max = ComptonProfilePolicy::getUpperLimitOfIntegration(
                               compton_profile,
                               pz_max*ComptonProfile::MomentumUnit() ).value();

  // Calculate the min electron momentum projection
  double pz_min = ComptonProfilePolicy::getLowerLimitOfIntegration(
                               pz_max*ComptonProfile::MomentumUnit() ).value();

  // Calculate the absolute error and the integrated cross section
  double abs_error, diff_cs;

  Utility::GaussKronrodIntegrator<double> quadrature_set( precision );

  if( pz_min < pz_max )
  {
    quadrature_set.integrateAdaptively<15>( double_diff_cs_wrapper,
                                            pz_min,
                                            pz_max,
                                            diff_cs,
                                            abs_error );
  }
  else
  {
    abs_error = 0.0;
    diff_cs = 0.0;
  }

  // Make sure that the differential cross section is valid
  testPostcondition( diff_cs >= 0.0 );

  return diff_cs;
}

  // Evaluate the exact integrated cross section
template<typename ComptonProfilePolicy>
double DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateIntegratedCrossSectionExact(
                                         const double incoming_energy,
                                         const double scattering_angle_cosine,
                                         const double precision ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  boost::function<double (double x)> double_diff_cs_wrapper =
    boost::bind<double>( &DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution::evaluateExact,
                         boost::cref( *this ),
                         incoming_energy,
                         _1,
                         scattering_angle_cosine );

  // Get the min subshell binding energy
  const double subshell_binding_energy =
    d_old_subshell_binding_energy[d_min_binding_energy_index];

  // Calculate the max energy
  double energy_max = incoming_energy - subshell_binding_energy;

  // Calculate the max electron momentum projection
  double pz_max =
    calculateMaxElectronMomentumProjection( incoming_energy,
                                            subshell_binding_energy,
                                            scattering_angle_cosine );

  // Get the subshell Compton profile
  const ComptonProfile& compton_profile =
    this->getComptonProfile( d_min_binding_energy_index );

  // Calculate the max table energy
  const double pz_table_max =
    ComptonProfilePolicy::getUpperBoundOfMomentum( compton_profile ).value();

  if( pz_max > pz_table_max )
  {
    bool energetically_possible;

    energy_max = calculateDopplerBroadenedEnergy( pz_table_max,
                                                  incoming_energy,
                                                  scattering_angle_cosine,
                                                  energetically_possible );
  }

  // Calculate the absolute error and the integrated cross section
  double abs_error, diff_cs;

  Utility::GaussKronrodIntegrator<double> quadrature_set( precision );

  quadrature_set.integrateAdaptively<15>( double_diff_cs_wrapper,
                                          0.0,
                                          energy_max,
                                          diff_cs,
                                          abs_error );

  // Make sure that the differential cross section is valid
  testPostcondition( diff_cs >= 0.0 );

  return diff_cs;
}

// Sample an interaction subshell
/*! \details The old subshell index used to select the Compton profile and
 * and the binding energy is not the same as the subshell (each are sampled
 * separately - i.e. they are decoupled).
 */
template<typename ComptonProfilePolicy>
void DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::sampleInteractionSubshell(
                                           size_t& old_subshell_index,
                                           double& subshell_binding_energy,
                                           Data::SubshellType& subshell ) const
{
  old_subshell_index = this->sampleOldInteractionSubshell();

  subshell_binding_energy = d_old_subshell_binding_energy[old_subshell_index];

  subshell = this->sampleENDFInteractionSubshell();
}

// Sample the old subshell that is interacted with
template<typename ComptonProfilePolicy>
size_t DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::sampleOldInteractionSubshell() const
{
  size_t old_subshell_of_interaction;

  d_old_subshell_occupancy_distribution->sampleAndRecordBinIndex(
                                                 old_subshell_of_interaction );

  return old_subshell_of_interaction;
}

EXTERN_EXPLICIT_TEMPLATE_CLASS_INST( DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<FullComptonProfilePolicy> );
EXTERN_EXPLICIT_TEMPLATE_CLASS_INST( DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<HalfComptonProfilePolicy> );
EXTERN_EXPLICIT_TEMPLATE_CLASS_INST( DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution<DoubledHalfComptonProfilePolicy> );

} // end MonteCarlo namespace

#endif // end MONTE_CARLO_DECOUPLED_STANDARD_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_DEF_HPP

//---------------------------------------------------------------------------//
// end MonteCarlo_DecoupledStandardCompleteDopplerBroadenedPhotonEnergyDistribution_def.hpp
//---------------------------------------------------------------------------//