//  (C) Copyright Nick Thompson 2020.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <iostream>
#include <string>
#include <fstream>
#include <map>
#include <cmath>
#include <vector>
#include <iomanip>
#include <boost/algorithm/string.hpp>
#include <boost/math/statistics/linear_regression.hpp>
#include <boost/assert.hpp>

int main(int argc, char** argv)
{
    if (argc != 2)
    {
        std::cout << "Usage: ./regress_accuracy.x foo.csv\n";
        return 1;
    }
    std::string filename = std::string(argv[1]);
    std::ifstream ifs(filename.c_str());
    if (!ifs.good())
    {
        std::cerr << "Couldn't find file " << filename << "\n";
        return 1;
    }
    std::map<std::string, std::vector<double>> m;

    std::string header_line;
    std::getline(ifs, header_line);
    std::cout << "Header line = " << header_line << "\n";
    std::vector<std::string> header_strs;
    boost::split(header_strs, header_line, boost::is_any_of(","));
    for (auto & s : header_strs) {
        boost::algorithm::trim(s);
    }

    std::string line;
    std::vector<double> r;
    std::vector<double> matched_holder;
    std::vector<double> linear;
    std::vector<double> quadratic_b_spline;
    std::vector<double> cubic_b_spline;
    std::vector<double> quintic_b_spline;
    std::vector<double> cubic_hermite;
    std::vector<double> pchip;
    std::vector<double> makima;
    std::vector<double> fotaylor;
    std::vector<double> quintic_hermite;
    std::vector<double> sotaylor;
    std::vector<double> totaylor;
    std::vector<double> septic_hermite;
    while(std::getline(ifs, line))
    {
        std::vector<std::string> strs;
        boost::split(strs, line, boost::is_any_of(","));
        for (auto & s : strs)
        {
            boost::algorithm::trim(s);
        }
        std::vector<double> v(strs.size(), std::numeric_limits<double>::quiet_NaN());
        for (size_t i = 0; i < v.size(); ++i)
        {
            v[i] = std::stod(strs[i]);
        }
        r.push_back(v[0]);
        matched_holder.push_back(std::log2(v[1]));
        linear.push_back(std::log2(v[2]));
        quadratic_b_spline.push_back(std::log2(v[3]));
        cubic_b_spline.push_back(std::log2(v[4]));
        quintic_b_spline.push_back(std::log2(v[5]));
        cubic_hermite.push_back(std::log2(v[6]));
        pchip.push_back(std::log2(v[7]));
        makima.push_back(std::log2(v[8]));
        fotaylor.push_back(std::log2(v[9]));
        if (v.size() > 10) {
            quintic_hermite.push_back(std::log2(v[10]));
            sotaylor.push_back(std::log2(v[11]));
        }
        if (v.size() > 12) {
            totaylor.push_back(std::log2(v[12]));
            septic_hermite.push_back(std::log2(v[13]));
        }
    }

    std::cout << std::fixed << std::setprecision(16);
    auto q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, matched_holder);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Matched Holder    : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, linear);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Linear            : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, quadratic_b_spline);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Quadratic B-spline: " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, cubic_b_spline);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Cubic B-spline    : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, quintic_b_spline);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Quintic B-spline  : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, cubic_hermite);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Cubic Hermite     : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, pchip);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "PCHIP             : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, makima);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Makima            : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, fotaylor);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "First-order Taylor: " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    if (sotaylor.size() > 0)
    {
    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, quintic_hermite);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Quintic Hermite   : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, sotaylor);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "2nd order Taylor  : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    }

    if (totaylor.size() > 0)
    {
    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, totaylor);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "3rd order Taylor  : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, septic_hermite);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Septic Hermite    : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    }

}


/**
cnt.cpp
Stores all relevant information for a carbon nanotube
*/

#include <iostream>
#include <numeric>
#include <armadillo>
#include <complex>
#include <stdexcept>

#include "constants.h"
#include "cnt.h"
#include "../helper/progress.hpp"

void cnt::get_parameters()
{
  // graphen unit vectors and reciprocal lattice vectors
  _a1 = arma::vec({_a_l*std::sqrt(3.0)/2.0, +_a_l/2.0});
  _a2 = arma::vec({_a_l*std::sqrt(3.0)/2.0, -_a_l/2.0});
  _b1 = arma::vec({1.0/sqrt(3.0)*2.0*constants::pi/_a_l, +2.0*constants::pi/_a_l});
  _b2 = arma::vec({1.0/sqrt(3.0)*2.0*constants::pi/_a_l, -2.0*constants::pi/_a_l});

  // carbon-carbon translation vector
  _aCC_vec = 1.0/3.0*(_a1+_a2);

  // cnt chirality vector and its length
	_ch_vec = double(_n) * _a1 + double(_m) * _a2;
	_ch_len = arma::norm(_ch_vec,2);

  // cnt radius
  _radius = _ch_len/2.0/constants::pi;

  // calculate cnt t_vector
  int dR = std::gcd(2*_n+_m,_n+2*_m);
	_t1 = +(2*_m+_n)/dR;
	_t2 = -(2*_n+_m)/dR;
  _t_vec = double(_t1)*_a1 + double(_t2)*_a2;

  // number of hexagons in cnt unit cells which is equal to number of carbon atoms divided by half
  _Nu = 2*(std::pow(_n,2)+std::pow(_m,2)+_n*_m)/dR;

  // rotate basis vectors so that ch_vec is along the x_axis and t_vec is along y_axis
	double cos_theta = _ch_vec(0)/arma::norm(_ch_vec);
	double sin_theta = _ch_vec(1)/arma::norm(_ch_vec);
	arma::mat rot = {{+cos_theta, +sin_theta},
                   {-sin_theta, +cos_theta}}; // rotation matrix

	_ch_vec = rot*_ch_vec;
	_t_vec = rot*_t_vec;
	_a1 = rot*_a1;
	_a2 = rot*_a2;
	_b1 = rot*_b1;
	_b2 = rot*_b2;
	_aCC_vec = rot*_aCC_vec;

  	//make 3d t_vec where the cnt axis is parallel to y-axis
	_t_vec_3d = arma::vec(3,arma::fill::zeros);
	_t_vec_3d(1) = _t_vec(1);

  std::cout << "\n...graphene unit cell vectors:\n";
  _a1.print("a1:");
  _a2.print("a2:");

  std::cout << "\n...graphene reciprocal lattice vectors:\n";
  _b1.print("b1:");
  _b2.print("b2:");

  std::cout << "\n...vector connecting basis carbon atoms:\n";
  _aCC_vec.print("aCC vector:");

  _ch_vec.print("chirality vector:");
  std::cout << "ch_vec length:\n   " << _ch_len << std::endl;

  _t_vec.print("t_vec:");
  _t_vec_3d.print("3d t_vec:");

	// calculate reciprocal lattice of CNT
	_K1 = (-double(_t2)*_b1 + double(_t1)*_b2)/(double(_Nu));
	_K2 = (double(_m)*_b1-double(_n)*_b2)/(double(_Nu));
  _K2_normed = arma::normalise(_K2);
  _nk_K1 = _number_of_cnt_unit_cells;
	_dk_l = _K2/(double(_nk_K1));

  std::cout << "\n...cnt reciprocal lattice vectors:\n";
  _K1.print("K1:");
  _K2.print("K2:");

	// calculate K2-extended representation parameters
  {
    double p_min = (1./double(_t1)+1./double(_n))/(double(_m)/double(_n)-double(_t2)/double(_t1));
    double p_max = (1./double(_t1)+double(_Nu)/double(_n))/(double(_m)/double(_n)-double(_t2)/double(_t1));

    bool found = false;

    for (int p=std::ceil(p_min); p<std::ceil(p_max); p++)
    {
      if (((1+_t2*p) % _t1) == 0)
      {
        int q = (1+_t2*p)/_t1;
        _M = _m*p - _n*q;
        _Q = std::gcd(_Nu,_M);
        std::cout << "\n...K2-extended representation parameters:\n M: " << _M << " ,Q: " << _Q << "\n";
        found = true;
        break;
      }
    }
    if (not found)
    {
      std::cout << "Failed to calculate p and q for K2-extended representation .... investigate! .... aborting the simulation!!!\n";
    }
  }

}

// calculates position of atoms and reciprocal lattice vectors
void cnt::get_atom_coordinates()
{

	// calculate positions of atoms in the cnt unit cell
	_pos_a = arma::mat(_Nu,2,arma::fill::zeros);
	_pos_b = arma::mat(_Nu,2,arma::fill::zeros);

	int k = 0;

	for (int i=0; i<=_t1+_n; i++)
	{
		for (int j=_t2; j<=_m; j++)
		{
			bool flag1 = double(_t2*i)/(double)_t1 <= double(j);
			bool flag2 = double(_m*i)/(double)_n >= double(j);
			bool flag3 = double(_t2*(i-_n))/double(_t1) > double(j-_m);
			bool flag4 = double(_m*(i-_t1))/double(_n) < double(j-_t2);

			if(flag1 && flag2 && flag3 && flag4)
			{
        _pos_a.row(k) = double(i)*_a1.t() + double(j)*_a2.t();
        _pos_b.row(k) = _pos_a.row(k) + _aCC_vec.t();

				if(_pos_a(k,0) > _ch_vec(0))
          _pos_a(k,0) -= _ch_vec(0);
				if(_pos_a(k,0) < 0.0)
          _pos_a(k,0) += _ch_vec(0);
				if(_pos_a(k,1) > _ch_vec(1))
          _pos_a(k,1) -= _ch_vec(1);
				if(_pos_a(k,1) < 0.0)
          _pos_a(k,1) += _ch_vec(1);

				if(_pos_b(k,0) > _ch_vec(0))
          _pos_b(k,0) -= _ch_vec(0);
				if(_pos_b(k,0) < 0.0)
          _pos_b(k,0) += _ch_vec(0);
				if(_pos_b(k,1) > _ch_vec(1))
          _pos_b(k,1) -= _ch_vec(1);
				if(_pos_b(k,1) < 0.0)
          _pos_b(k,1) += _ch_vec(1);

				k++;
			}
		}
	}

  std::cout << "\n...atom coordinates:\n";
  _pos_a.print("pos_a:");
  _pos_b.print("pos_b:");

	if (k != _Nu)
	{
		std::cout << "error in finding position of atoms in cnt unit cell!!!" << std::endl;
		std::cout << "Nu = " << _Nu << "  ,  k = " << k << std::endl;
		exit(1);
	}

	// put position of all atoms in a single variable in 2d space(unrolled graphene sheet)
	_pos_2d = arma::mat(2*_Nu,2,arma::fill::zeros);
  _pos_2d.rows(0,_Nu-1) = _pos_a;
  _pos_2d.rows(_Nu,2*_Nu-1) = _pos_b;

	// calculate position of all atoms in the 3d space (rolled graphene sheet)
	_pos_3d = arma::mat(2*_Nu,3,arma::fill::zeros);
	for (unsigned int i=0; i<_pos_3d.n_rows; i++)
	{
		_pos_3d(i,0) = _radius*cos(_pos_2d(i,0)/_radius);
		_pos_3d(i,1) = _pos_2d(i,1);
		_pos_3d(i,2) = _radius*sin(_pos_2d(i,0)/_radius);
	}

	// save coordinates of atoms in 2d space
  std::string filename = _directory.path() / "pos_2d.dat";
  _pos_2d.save(filename, arma::arma_ascii);

  // save coordinates of atoms in 3d space
  filename = _directory.path() / "pos_3d.dat";
  _pos_3d.save(filename, arma::arma_ascii);

  // put position of all graphene unit cells in 2d (unrolled graphene sheet) and 3d space (rolled graphene sheet)
	_pos_u_2d = _pos_a;
	_pos_u_3d = arma::mat(_Nu,3,arma::fill::zeros);
	for (unsigned int i=0; i<_pos_u_3d.n_rows; i++)
	{
		_pos_u_3d(i,0) = _radius*cos(_pos_u_2d(i,0)/_radius);
		_pos_u_3d(i,1) = _pos_u_2d(i,1);
		_pos_u_3d(i,2) = _radius*sin(_pos_u_2d(i,0)/_radius);
	}

}

// calculate electron energy dispersions in the K1-extended representation using full unit cell (2*Nu atoms)
void cnt::electron_full_unit_cell()
{

	// make the list of 1st nearest neighbor atoms
	arma::umat nn_list(2*_Nu,3,arma::fill::zeros); // contains index of the nearest neighbor atom
	arma::imat nn_tvec_index(2*_Nu,3,arma::fill::zeros); // contains the index of the cnt unit cell that the nearest neigbor atom is in.
	for (unsigned int i=0; i<_pos_3d.n_rows; i++)
	{
		int k=0;
		for (unsigned int j=0; j<_pos_3d.n_rows; j++)
		{
			for (int l=-1; l<=1; l++)
			{
        double dR = arma::norm(_pos_3d.row(i)-_pos_3d.row(j)+double(l)*_t_vec_3d.t());
				if ( (i!=j) && (dR<(1.4*_a_cc)) )
				{
					nn_list(i,k) = j;
					nn_tvec_index(i,k) = l;
					k++;
				}
			}
		}
    if (k != 3)
    {
      std::cout << "error: nearest neighbors partially found!!!\n";
      std::exit(1);
    }
	}

	arma::cx_mat H(2*_Nu, 2*_Nu, arma::fill::zeros);
	arma::cx_mat S(2*_Nu, 2*_Nu, arma::fill::zeros);
  arma::vec E;
  arma::cx_mat C;

	int NK = _nk_K1;

	arma::mat el_energy_full(2*_Nu, NK, arma::fill::zeros);
	arma::cx_cube el_psi_full(2*_Nu, 2*_Nu, NK, arma::fill::zeros);

	double t_len = arma::norm(_t_vec_3d);

	for (int n=0; n<NK; n++)
	{
		double wave_vec = double(n-_nk_K1/2)*arma::norm(_dk_l);

		H.zeros();
		S.zeros();

		for (int i=0; i<2*_Nu; i++)
		{
			H(i,i) = std::complex<double>(_e2p,0.e0);
			for (int k=0; k<3; k++)
			{
				int j = nn_list(i,k);
				int l = nn_tvec_index(i,k);

				H(i,j) += arma::cx_double(_t0,0.e0)*exp(arma::cx_double(0.0,wave_vec*double(l)*t_len));
				S(i,j) += arma::cx_double(_s0,0.e0)*exp(arma::cx_double(0.0,wave_vec*double(l)*t_len));
			}
		}

		arma::eig_sym(E, C, H);

		// fix the phase of the eigen vectors
		for (unsigned int i=0; i<C.n_cols; i++)
		{
			arma::cx_double phi = std::conj(C(0,i))/std::abs(C(0,i));
			for (unsigned int j=0; j<C.n_rows; j++)
			{
				C(j,i) *= phi;
			}
		}

    el_energy_full.col(n) = E;
    el_psi_full.slice(n) = C;

	}

  // save electron energy bands using full Brillouine zone
  std::cout << "saved electron energy dispersion in K1-extended representation\n";
  std::string filename = _directory.path() / "el_energy_full.dat";
  el_energy_full.save(filename, arma::arma_ascii);

  // // save electron wavefunctions using full Brillouine zone
  // filename = _directory.path()/"el_psi_full.dat";
  // el_psi_full.save(filename, arma::arma_ascii);

}

// calculate electron dispersion energies for an input range of ik and mu
cnt::el_energy_struct cnt::electron_energy(const std::array<int,2>& ik_range, const std::array<int,2>& mu_range, const std::string& name)
{
  int number_of_bands = 2;
  int number_of_atoms_in_graphene_unit_cell = number_of_bands;

  int nk = ik_range[1] - ik_range[0];
  int n_mu = mu_range[1] - mu_range[0];

  arma::cube energy(number_of_bands, nk, n_mu, arma::fill::zeros);
  arma::field<arma::cx_cube> wavefunc(n_mu); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently
  wavefunc.for_each([&](arma::cx_cube& c){c.zeros(number_of_atoms_in_graphene_unit_cell, number_of_bands, nk);}); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently

  const std::complex<double> i1(0,1);

  const int ic = 1;
  const int iv = 0;

  const int iA = 0;
  const int iB = 1;

  for (int mu=mu_range[0]; mu<mu_range[1]; mu++)
  {
    for (int ik=ik_range[0]; ik<ik_range[1]; ik++)
    {
      arma::vec k_vec = double(mu)*_K1 + double(ik)*_dk_l;
      std::complex<double> fk = std::exp(std::complex<double>(0,arma::dot(k_vec,(_a1+_a2)/3.))) + \
                                std::exp(std::complex<double>(0,arma::dot(k_vec,(_a1-2.*_a2)/3.))) + \
                                std::exp(std::complex<double>(0,arma::dot(k_vec,(_a2-2.*_a1)/3.)));

      energy(ic,ik-ik_range[0],mu-mu_range[0]) = +_t0*std::abs(fk);
      energy(iv,ik-ik_range[0],mu-mu_range[0]) = -_t0*std::abs(fk);

      (wavefunc(mu-mu_range[0]))(iA,ic,ik-ik_range[0]) = +1./std::sqrt(2.); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently
      (wavefunc(mu-mu_range[0]))(iA,iv,ik-ik_range[0]) = +1./std::sqrt(2.); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently
      (wavefunc(mu-mu_range[0]))(iB,ic,ik-ik_range[0]) = -1./std::sqrt(2.)*std::conj(fk)/std::abs(fk); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently
      (wavefunc(mu-mu_range[0]))(iB,iv,ik-ik_range[0]) = +1./std::sqrt(2.)*std::conj(fk)/std::abs(fk); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently
    }
  }

  // save electron energy bands using full Brillouine zone
  std::string filename = _directory.path() / (name +".el_energy.dat");
  energy.save(filename,arma::arma_ascii);

  std::cout << "\n...calculated " + name + " electron dispersion\n";

  el_energy_struct energy_s;
  energy_s.name = name;
  energy_s.energy = energy;
  energy_s.wavefunc = wavefunc;
  energy_s.ik_range = ik_range;
  energy_s.mu_range = mu_range;
  energy_s.nk = nk;
  energy_s.n_mu = n_mu;
  energy_s.no_of_atoms = number_of_atoms_in_graphene_unit_cell;
  energy_s.no_of_bands = number_of_bands;

  return energy_s;
}

void cnt::find_valleys(const cnt::el_energy_struct& elec_struct)
{

  // get the indicies of valleys
  std::vector<std::array<unsigned int,2>> ik_valley_idx;

  int iC = 1;
  for (int ik_idx=0; ik_idx<elec_struct.nk; ik_idx++)
  {
    for (int i_mu_idx=0; i_mu_idx<elec_struct.n_mu; i_mu_idx++)
    {
      int ik_idx_p1 = ik_idx + 1;
      int ik_idx_m1 = ik_idx - 1;
      while(ik_idx_p1 >= elec_struct.nk)
      {
        ik_idx_p1 -= elec_struct.nk;
      }
      while(ik_idx_m1 < 0)
      {
        ik_idx_m1 += elec_struct.nk;
      }

      if ((elec_struct.energy(iC,ik_idx,i_mu_idx) < elec_struct.energy(iC,ik_idx_m1,i_mu_idx)) \
            and (elec_struct.energy(iC,ik_idx,i_mu_idx) < elec_struct.energy(iC,ik_idx_p1,i_mu_idx)))
      {
        ik_valley_idx.push_back({(unsigned int)ik_idx, (unsigned int)i_mu_idx});
      }

    }
  }

  // sort valleys in order of their
  std::sort(ik_valley_idx.begin(),ik_valley_idx.end(), [&](const auto& s1, const auto& s2) {
                                    return elec_struct.energy(1,s1[0],s1[1]) < elec_struct.energy(1,s2[0],s2[1]);
                                  });

  // put them in a vector with each element containing the two equivalent valleys
  for (unsigned int i=0; i<ik_valley_idx.size()/2; i++)
  {
    std::array<std::array<unsigned int, 2>, 2> valley = {ik_valley_idx.at(2*i), ik_valley_idx.at(2*i+1)};
    _valleys_K2.push_back(valley);
  }

  std::cout << "\n...found and sorted indices of valleys:\n";
  for (const auto& valleys: _valleys_K2)
  {
    auto v1 = valleys.at(0);
    auto v2 = valleys.at(1);
    std::cout << "[" << v1[0] << "," << v1[1] << "] , [" << v2[0] << "," << v2[1] << "]\n";
  }

  std::cout << "number of valleys: " << _valleys_K2.size() << std::endl;

}

// find ik values that are energetically relevant around the bottom of the valley
void cnt::find_relev_ik_range(double delta_energy, const cnt::el_energy_struct& elec_struct)
{
  std::vector<std::vector<std::array<int,2>>> relev_ik_range(2);

  // first get ik for relevant states in the first valley
  int i_valley = 0;
  int ik_bottom = _valleys_K2[_i_sub][i_valley][0]+elec_struct.ik_range[0];
  int mu_bottom = _valleys_K2[_i_sub][i_valley][1]+elec_struct.mu_range[0];
  int iC = 1;
  double max_energy = elec_struct.energy(iC, ik_bottom-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) + delta_energy;

  relev_ik_range.at(i_valley).push_back({ik_bottom,mu_bottom});
  bool in_range = true;
  int count = 0;
  while (in_range)
  {
    in_range = false;
    count ++;
    int ik = ik_bottom + count;
    while(ik >= elec_struct.ik_range[1])
    {
      ik -= elec_struct.nk;
    }
    if (elec_struct.energy(iC, ik-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) < max_energy)
    {
      relev_ik_range.at(i_valley).push_back({ik,mu_bottom});
      in_range = true;
    }

    ik = ik_bottom - count;
    while(ik < elec_struct.ik_range[0])
    {
      ik += elec_struct.nk;
    }
    if (elec_struct.energy(iC, ik-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) < max_energy)
    {
      // std::array<int,2> relev_state = {ik,mu_bottom};
      relev_ik_range.at(i_valley).insert(relev_ik_range.at(i_valley).begin(), {ik,mu_bottom});
      in_range = true;
    }
  }

  // do the same thing for the second valley
  i_valley = 1;
  ik_bottom = _valleys_K2[_i_sub][i_valley][0]+elec_struct.ik_range[0];
  mu_bottom = _valleys_K2[_i_sub][i_valley][1]+elec_struct.mu_range[0];
  max_energy = elec_struct.energy(iC, ik_bottom-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) + delta_energy;

  relev_ik_range.at(i_valley).push_back({ik_bottom,mu_bottom});
  in_range = true;
  count = 0;
  while (in_range)
  {
    in_range = false;
    count ++;
    int ik = ik_bottom + count;
    while(ik >= elec_struct.ik_range[1])
    {
      ik -= elec_struct.nk;
    }
    if (elec_struct.energy(iC, ik-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) < max_energy)
    {
      relev_ik_range.at(i_valley).push_back({ik,mu_bottom});
      in_range = true;
    }

    ik = ik_bottom - count;
    while(ik < elec_struct.ik_range[0])
    {
      ik += elec_struct.nk;
    }
    if (elec_struct.energy(iC, ik-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) < max_energy)
    {
      relev_ik_range.at(i_valley).insert(relev_ik_range.at(i_valley).begin(), {ik,mu_bottom});
      in_range = true;
    }
  }

  std::cout << "\n...ik for relevant states calculated:\n";
  std::cout << "relev_ik_range has length of " << relev_ik_range[0].size() << std::endl;

  // i_valley = 0;
  // std::cout << "valley: " << i_valley << "\n";
  // for (const auto& state: relev_ik_range.at(i_valley))
  // {
  //   std::cout << "   [" << state.at(0) << "," << state.at(1) << "]\n";
  // }

  // i_valley = 1;
  // std::cout << "\nvalley: " << i_valley << "\n";
  // for (const auto& state: relev_ik_range.at(i_valley))
  // {
  //   std::cout << "   [" << state.at(0) << "," << state.at(1) << "]\n";
  // }

  _relev_ik_range = relev_ik_range;

}

// fourier transformation of the coulomb interaction a.k.a v(q)
cnt::vq_struct cnt::calculate_vq(const std::array<int,2> iq_range, const std::array<int,2> mu_range, unsigned int no_of_cnt_unit_cells)
{
  // primary checks for function input
  int nq = iq_range.at(1) - iq_range.at(0);
  if (nq <= 0) {
    throw "Incorrect range for iq!";
  }
  int n_mu = mu_range.at(1) - mu_range.at(0);
  if (n_mu <= 0) {
    throw "Incorrect range for mu_q!";
  }
  if (no_of_cnt_unit_cells % 2 == 0)  no_of_cnt_unit_cells ++;

  // calculate distances between atoms in a warped cnt unit cell.
	arma::mat pos_aa = arma::mat(_Nu,2,arma::fill::zeros);
	arma::mat pos_ab = arma::mat(_Nu,2,arma::fill::zeros);
	arma::mat pos_ba = arma::mat(_Nu,2,arma::fill::zeros);
  arma::mat pos_bb = arma::mat(_Nu,2,arma::fill::zeros);

	for (int i=0; i<_Nu; i++)
	{
    pos_aa.row(i) = _pos_a.row(i)-_pos_a.row(0);
    pos_ab.row(i) = _pos_a.row(i)-_pos_b.row(0);
    pos_ba.row(i) = _pos_b.row(i)-_pos_a.row(0);
    pos_bb.row(i) = _pos_b.row(i)-_pos_b.row(0);

		if(pos_aa(i,0) > _ch_vec(0)/2)
      pos_aa(i,0) -= _ch_vec(0);
		if(pos_ab(i,0) > _ch_vec(0)/2)
      pos_ab(i,0) -= _ch_vec(0);
		if(pos_ba(i,0) > _ch_vec(0)/2)
      pos_ba(i,0) -= _ch_vec(0);
		if(pos_bb(i,0) > _ch_vec(0)/2)
      pos_bb(i,0) -= _ch_vec(0);
	}

  arma::cube rel_pos(_Nu*no_of_cnt_unit_cells,2,4,arma::fill::zeros);
  for (int i=-std::floor(double(no_of_cnt_unit_cells)/2.); i<=std::floor(double(no_of_cnt_unit_cells)/2.); i++)
  {
    int idx = (i+std::floor(double(no_of_cnt_unit_cells)/2.))*_Nu;
    for (int j=0; j<_Nu; j++)
    {
      rel_pos.slice(0).row(idx+j) = pos_aa.row(j)+(i*_t_vec.t());
      rel_pos.slice(1).row(idx+j) = pos_ab.row(j)+(i*_t_vec.t());
      rel_pos.slice(2).row(idx+j) = pos_ba.row(j)+(i*_t_vec.t());
      rel_pos.slice(3).row(idx+j) = pos_bb.row(j)+(i*_t_vec.t());
    }
  }

  // calculate vq
  arma::cx_cube vq(nq,n_mu,4,arma::fill::zeros);
  arma::vec q_vec(nq,arma::fill::zeros);

  arma::vec q(2,arma::fill::zeros);
  const double coeff = std::pow(4.*constants::pi*constants::eps0*_Upp/constants::q0/constants::q0,2);
  const std::complex<double> i1(0.,1.);
  auto Uhno = [&](const arma::mat& R){
    return std::exp(i1*arma::dot(q,R))*_Upp/std::sqrt(coeff*(std::pow(R(0),2)+std::pow(R(1),2))+1);
  };

  progress_bar prog(nq, "vq");

  for (int iq=iq_range[0]; iq<iq_range[1]; iq++)
  {
    int iq_idx = iq-iq_range[0];

    prog.step();

    q_vec(iq_idx) = iq*arma::norm(_dk_l,2);
    for (int mu=mu_range[0]; mu<mu_range[1]; mu++)
    {
      int mu_idx = mu - mu_range[0];
      q = iq*_dk_l + mu*_K1;
      // std::cout << "after addition!\n";
      for (int i=0; i<4; i++)
      {
        for (unsigned int k=0; k<_Nu*no_of_cnt_unit_cells; k++)
        {
          vq(iq_idx,mu_idx,i) += Uhno(rel_pos.slice(i).row(k));
        }
      }
    }
  }

  vq = vq/(2*_Nu*no_of_cnt_unit_cells);

  std::cout << "\n...calculated vq\n";

  std::cout << "saved real part of vq\n";
  arma::cube vq_real = arma::real(vq);
  std::string filename = _directory.path()/"vq_real.dat";
  vq_real.save(filename, arma::arma_ascii);

  std::cout << "saved imaginary part of vq\n";
  arma::cube vq_imag = arma::imag(vq);
  filename = _directory.path()/"vq_imag.dat";
  vq_imag.save(filename, arma::arma_ascii);

  std::cout << "saved q_vector for vq\n";
  filename = _directory.path()/"vq_q_vec.dat";
  q_vec.save(filename, arma::arma_ascii);

  // make the vq_struct that is to be returned
  vq_struct vq_s;
  vq_s.data = vq;
  vq_s.iq_range = iq_range;
  vq_s.mu_range = mu_range;
  vq_s.nq = nq;
  vq_s.n_mu = n_mu;

  return vq_s;
}

// polarization of electronic states a.k.a PI(q)
cnt::PI_struct cnt::calculate_polarization(const std::array<int,2> iq_range, const std::array<int,2> mu_range, const cnt::el_energy_struct& elec_struct)
{
  // primary checks for function input
  int nq = iq_range.at(1) - iq_range.at(0);
  if (nq <= 0) {
    throw "Incorrect range for iq in calculate_polarization!";
  }
  int n_mu = mu_range.at(1) - mu_range.at(0);
  if (n_mu <= 0) {
    throw "Incorrect range for mu_q in calculate_polarization!";
  }

  int ikq, mu_kq;
  int ik, mu_k;
  int iq, mu_q;
  // lambda function to wrap iq+ik and mu_k+mu_q inside the K2-extended brillouine zone
  auto get_kq = [&](){
    mu_kq = mu_k+mu_q;
    ikq = ik+iq;
    while (mu_kq >= elec_struct.mu_range[1]) {
      mu_kq -= elec_struct.n_mu;
      ikq += _nk_K1*_M;
    }
    while (mu_kq < elec_struct.mu_range[0]) {
      mu_kq += elec_struct.n_mu;
      ikq -= _nk_K1*_M;
    }
    while (ikq >= elec_struct.ik_range[1]){
      ikq -= elec_struct.nk;
    }
    while (ikq < elec_struct.ik_range[0]){
      ikq += elec_struct.nk;
    }
  };

  arma::mat PI(nq,n_mu,arma::fill::zeros);
  arma::vec q_vec(nq,arma::fill::zeros);

  const int iv = 0;
  const int ic = 1;

  progress_bar prog(nq, "calculate polarization");

  int iq_idx, mu_q_idx;
  int ik_idx, mu_k_idx;
  int i_kq_idx, mu_kq_idx;
  for (iq=iq_range[0]; iq<iq_range[1]; iq++)
  {
    iq_idx = iq - iq_range[0];
    q_vec(iq_idx) = iq*arma::norm(_dk_l);

    prog.step(iq_idx);

    for (mu_q=mu_range[0]; mu_q<mu_range[1]; mu_q++)
    {
      mu_q_idx = mu_q - mu_range[0];
      for (ik=elec_struct.ik_range[0]; ik<elec_struct.ik_range[1]; ik++)
      {
        ik_idx = ik - elec_struct.ik_range[0];
        for (mu_k=elec_struct.mu_range[0]; mu_k<elec_struct.mu_range[1]; mu_k++)
        {
          mu_k_idx = mu_k - elec_struct.mu_range[0];
          get_kq();
          mu_kq_idx = mu_kq - elec_struct.mu_range[0];
          i_kq_idx = ikq - elec_struct.ik_range[0];

          PI(iq_idx,mu_q_idx) += std::pow(std::abs(arma::dot(arma::conj(elec_struct.wavefunc(mu_k_idx).slice(ik_idx).col(iv)),\
                                                             elec_struct.wavefunc(mu_kq_idx).slice(i_kq_idx).col(ic))),2)/ \
                                          (elec_struct.energy(ic,i_kq_idx,mu_kq_idx)-elec_struct.energy(iv,ik_idx,mu_k_idx)) + \
                                 std::pow(std::abs(arma::dot(arma::conj(elec_struct.wavefunc(mu_k_idx).slice(ik_idx).col(ic)), \
                                                             elec_struct.wavefunc(mu_kq_idx).slice(i_kq_idx).col(iv))),2)/ \
                                          (elec_struct.energy(ic,ik_idx,mu_k_idx)-elec_struct.energy(iv,i_kq_idx,mu_kq_idx));
        }
      }
    }
  }

  PI = 2*PI;

  std::cout << "\n...calculated polarization: PI(q)\n";

  std::cout << "saved PI\n";
  std::string filename = _directory.path()/"PI.dat";
  PI.save(filename, arma::arma_ascii);

  std::cout << "saved q_vector for PI\n";
  filename = _directory.path()/"PI_q_vec.dat";
  q_vec.save(filename, arma::arma_ascii);

  // make the vq_struct that is to be returned
  PI_struct PI_s;
  PI_s.data = PI;
  PI_s.iq_range = iq_range;
  PI_s.mu_range = mu_range;
  PI_s.nq = nq;
  PI_s.n_mu = n_mu;

  return PI_s;
}

// dielectric function a.k.a eps(q)
cnt::epsilon_struct cnt::calculate_dielectric(const std::array<int,2> iq_range, const std::array<int,2> mu_range)
{
  // check if vq has been calculated properly before
  if (not (in_range(iq_range,_vq.iq_range) and in_range(mu_range,_vq.mu_range))){
    throw std::logic_error("You need to calculate vq with correct range before \
                            trying to calculate dielectric function");
  }
  // check if PI has been calculated properly before
  if (not (in_range(iq_range,_PI.iq_range) and in_range(mu_range,_PI.mu_range))){
    throw std::logic_error("You need to calculate PI with correct range before \
                            trying to calculate dielectric function");
  }

  int nq = iq_range[1] - iq_range[0];
  int n_mu = mu_range[1] - mu_range[0];
  arma::mat eps = arma::real(arma::mean(_vq.data,2));
  eps = eps.submat(iq_range[0]-_vq.iq_range[0],mu_range[0]-_vq.mu_range[0],arma::size(nq,n_mu));
  eps %= _PI.data.submat(iq_range[0]-_PI.iq_range[0],mu_range[0]-_PI.mu_range[0],arma::size(nq,n_mu));
  std::cout << "size of dielectric function matrix: " << arma::size(eps) << std::endl;
  eps += 1.;

  arma::vec q_vec(nq);
  for (int iq=iq_range[0]; iq<iq_range[1]; iq++)
  {
    int iq_idx = iq - iq_range[0];
    q_vec(iq_idx) = iq*arma::norm(_dk_l);
  }

  std::cout << "\n...calculated dielectric function: epsilon(q)\n";

  std::cout << "saved epsilon\n";
  std::string filename = _directory.path()/"eps.dat";
  eps.save(filename, arma::arma_ascii);

  std::cout << "saved q_vector for epsilon\n";
  filename = _directory.path()/"eps_q_vec.dat";
  q_vec.save(filename, arma::arma_ascii);

  epsilon_struct eps_s;
  eps_s.data = eps;
  eps_s.iq_range = iq_range;
  eps_s.mu_range = mu_range;
  eps_s.nq = nq;
  eps_s.n_mu = n_mu;
  return eps_s;
}

// calculate exciton dispersion
std::vector<cnt::exciton_struct> cnt::calculate_A_excitons(const std::array<int,2> ik_cm_range, const cnt::el_energy_struct& elec_struct) {
  // some utility variables that are going to be used over and over again
  int ik_c, mu_c;
  int ik_v, mu_v;
  int ik_cp, mu_cp;
  int ik_vp, mu_vp;
  int ik_c_diff, mu_c_diff;
  int ik_cm, mu_cm;

  std::complex<double> dir_interaction;
  std::complex<double> xch_interaction;

  const int iv = 0;
  const int ic = 1;

  const int i_valley_1 = 0;
  const int i_valley_2 = 1;

  // lambda function to calculate direct interaction
  auto get_direct_interaction = [&](){
    ik_c_diff = ik_c-ik_cp;
    mu_c_diff = mu_c-mu_cp;
    while(ik_c_diff < elec_struct.ik_range[0]){
      ik_c_diff += elec_struct.nk;
    }
    while(ik_c_diff >= elec_struct.ik_range[1]){
      ik_c_diff -= elec_struct.nk;
    }

    dir_interaction = 0;
    for (int i=0; i<2; i++)
    {
      for (int j=0; j<2; j++)
      {
        dir_interaction += std::conj(elec_struct.wavefunc(mu_c -elec_struct.mu_range[0])(i,ic,ik_c -elec_struct.ik_range[0]))* \
                                     elec_struct.wavefunc(mu_v -elec_struct.mu_range[0])(j,iv,ik_v -elec_struct.ik_range[0]) * \
                                     elec_struct.wavefunc(mu_cp-elec_struct.mu_range[0])(i,ic,ik_cp-elec_struct.ik_range[0]) * \
                           std::conj(elec_struct.wavefunc(mu_vp-elec_struct.mu_range[0])(j,iv,ik_vp-elec_struct.ik_range[0]))* \
                                                          _vq.data(ik_c_diff-_vq.iq_range[0],mu_c_diff-_vq.mu_range[0],2*i+j)/ \
                                                             _eps.data(ik_c_diff-_eps.iq_range[0],mu_c_diff-_eps.mu_range[0]);
      }
    }
    return dir_interaction;
  };

  // lambda function to calculate exchange interaction
  auto get_exchange_interaction = [&](){
    xch_interaction = 0;
    for (int i=0; i<2; i++)
    {
      for (int j=0; j<2; j++)
      {
        xch_interaction += std::conj(elec_struct.wavefunc(mu_c -elec_struct.mu_range[0])(i,ic,ik_c -elec_struct.ik_range[0]))* \
                                     elec_struct.wavefunc(mu_v -elec_struct.mu_range[0])(i,iv,ik_v -elec_struct.ik_range[0]) * \
                                     elec_struct.wavefunc(mu_cp-elec_struct.mu_range[0])(j,ic,ik_cp-elec_struct.ik_range[0]) * \
                           std::conj(elec_struct.wavefunc(mu_vp-elec_struct.mu_range[0])(j,iv,ik_vp-elec_struct.ik_range[0]))* \
                                                                  _vq.data(ik_cm-_vq.iq_range[0],mu_cm-_vq.mu_range[0],2*i+j);
      }
    }
    return xch_interaction;
  };

  // get ik of valence band state by taking care of wrapping around K2-extended zone
  auto get_ikv = [&elec_struct](const int& ik_c, const int& ik_cm){
    int ik_v = ik_c - ik_cm;
    while (ik_v >= elec_struct.ik_range[1]){
      ik_v -= elec_struct.nk;
    }
    while (ik_v < elec_struct.ik_range[0]){
      ik_v += elec_struct.nk;
    }
    return ik_v;
  };

  // get ik of conduction band state by taking care of wrapping around K2-extended zone
  auto get_ikc = [&elec_struct](const int& ik_v, const int& ik_cm){
    int ik_c = ik_v + ik_cm;
    while (ik_c >= elec_struct.ik_range[1]){
      ik_c -= elec_struct.nk;
    }
    while (ik_c < elec_struct.ik_range[0]){
      ik_c += elec_struct.nk;
    }
    return ik_c;
  };

  int nk_cm = ik_cm_range[1] - ik_cm_range[0];
  int nk_relev = int(_relev_ik_range[0].size());
  int nk_c = 2*nk_relev;

  arma::mat ex_energy_A1(nk_cm,nk_relev,arma::fill::zeros);
  arma::mat ex_energy_A2_singlet(nk_cm,nk_relev,arma::fill::zeros);
  arma::mat ex_energy_A2_triplet(nk_cm,nk_relev,arma::fill::zeros);

  arma::cx_cube ex_psi_A1(nk_c,nk_relev,nk_cm, arma::fill::zeros);
  arma::cx_cube ex_psi_A2_singlet(nk_c,nk_relev,nk_cm, arma::fill::zeros);
  arma::cx_cube ex_psi_A2_triplet(nk_c,nk_relev,nk_cm, arma::fill::zeros);

  arma::ucube ik_idx(4, nk_c, nk_cm);

  arma::cx_mat kernel_11(nk_relev,nk_relev,arma::fill::zeros);
  arma::cx_mat kernel_12(nk_relev,nk_relev,arma::fill::zeros);
  arma::cx_mat kernel_exchange(nk_relev,nk_relev,arma::fill::zeros);
  arma::vec energy;
  arma::cx_mat psi;
  arma::vec k_cm_vec(nk_cm,arma::fill::zeros);

  progress_bar prog(nk_cm, "calculate ex_energy");

  // loop to calculate exciton dispersion
  for (ik_cm=ik_cm_range[0]; ik_cm<ik_cm_range[1]; ik_cm++)
  {
    kernel_11.zeros();
    kernel_12.zeros();
    kernel_exchange.zeros();
    mu_cm = 0;
    int ik_cm_idx = ik_cm - ik_cm_range[0];
    k_cm_vec(ik_cm_idx) = ik_cm*arma::norm(_dk_l);

    prog.step(ik_cm_idx);

    for (int ik_c_idx=0; ik_c_idx<nk_relev; ik_c_idx++)
    {
      ik_c = _relev_ik_range[i_valley_1][ik_c_idx][0];
      mu_c = _relev_ik_range[i_valley_1][ik_c_idx][1];
      ik_v = get_ikv(ik_c,ik_cm);
      mu_v = mu_c;

      kernel_11(ik_c_idx, ik_c_idx) += elec_struct.energy(ic,ik_c-elec_struct.ik_range[0],mu_c-elec_struct.mu_range[0]) - \
                                       elec_struct.energy(iv,ik_v-elec_struct.ik_range[0],mu_c-elec_struct.mu_range[0]);

      // interaction  between valley_1 and valley_1
      for (int ik_cp_idx=0; ik_cp_idx<=ik_c_idx; ik_cp_idx++)
      {
        ik_cp = _relev_ik_range[i_valley_1][ik_cp_idx][0];
        mu_cp = _relev_ik_range[i_valley_1][ik_cp_idx][1];
        ik_vp = get_ikv(ik_cp,ik_cm);
        mu_vp = mu_cp;

        kernel_11(ik_c_idx,ik_cp_idx) -= get_direct_interaction();
        kernel_exchange(ik_c_idx,ik_cp_idx) += std::complex<double>(2,0)*get_exchange_interaction();
      }

      // interaction  between valley_1 and valley_2
      for (int ik_vp_idx=ik_c_idx; ik_vp_idx<nk_relev; ik_vp_idx++)
      {
        ik_vp = _relev_ik_range[i_valley_2][ik_vp_idx][0];
        mu_vp = _relev_ik_range[i_valley_2][ik_vp_idx][1];
        ik_cp = get_ikc(ik_vp,ik_cm);
        mu_cp = mu_vp;

        kernel_12(ik_c_idx,nk_relev-1-ik_vp_idx) -= get_direct_interaction();
      }
    }

    kernel_11 += kernel_11.t();
    kernel_12 += kernel_12.t();
    kernel_exchange += kernel_exchange.t();
    for (int ik_c_idx=0; ik_c_idx<nk_relev; ik_c_idx++)
    {
      kernel_11(ik_c_idx,ik_c_idx) /= std::complex<double>(2,0);
      kernel_12(ik_c_idx,ik_c_idx) /= std::complex<double>(2,0);
      kernel_exchange(ik_c_idx,ik_c_idx) /= std::complex<double>(2,0);
    }

    arma::eig_sym(energy,psi,kernel_11-kernel_12);
    ex_energy_A1.row(ik_cm_idx) = energy.t();
    ex_psi_A1.slice(ik_cm_idx).head_rows(nk_relev) = (+1/std::sqrt(2.))*psi;
    ex_psi_A1.slice(ik_cm_idx).tail_rows(nk_relev) = (-1/std::sqrt(2.))*psi;

    // energy = arma::eig_sym(kernel_11+kernel_12);
    arma::eig_sym(energy,psi,kernel_11+kernel_12);
    ex_energy_A2_triplet.row(ik_cm_idx) = energy.t();
    ex_psi_A2_triplet.slice(ik_cm_idx).head_rows(nk_relev) = (+1/std::sqrt(2.))*psi;
    ex_psi_A2_triplet.slice(ik_cm_idx).tail_rows(nk_relev) = (+1/std::sqrt(2.))*psi;

    // energy = arma::eig_sym(kernel_11+kernel_12+std::complex<double>(2,0)*kernel_exchange);
    arma::eig_sym(energy,psi,kernel_11+kernel_12+std::complex<double>(2,0)*kernel_exchange);
    ex_energy_A2_singlet.row(ik_cm_idx) = energy.t();
    ex_psi_A2_singlet.slice(ik_cm_idx).head_rows(nk_relev) = (+1/std::sqrt(2.))*psi;
    ex_psi_A2_singlet.slice(ik_cm_idx).tail_rows(nk_relev) = (+1/std::sqrt(2.))*psi;

    // save the index of kc and kv states from i_valley_1
    for (int ik_c_idx=0; ik_c_idx<nk_relev; ik_c_idx++)
    {
      ik_c = _relev_ik_range[i_valley_1][ik_c_idx][0];
      mu_c = _relev_ik_range[i_valley_1][ik_c_idx][1];
      ik_v = get_ikv(ik_c,ik_cm);
      mu_v = mu_c;

      ik_idx(0,ik_c_idx,ik_cm_idx) = ik_c-elec_struct.ik_range[0];
      ik_idx(1,ik_c_idx,ik_cm_idx) = mu_c-elec_struct.mu_range[0];
      ik_idx(2,ik_c_idx,ik_cm_idx) = ik_v-elec_struct.ik_range[0];
      ik_idx(3,ik_c_idx,ik_cm_idx) = mu_v-elec_struct.mu_range[0];
    }

    // save the index of kc and kv states from i_valley_2
    for (int ik_v_idx=0; ik_v_idx<nk_relev; ik_v_idx++)
    {
      ik_v = _relev_ik_range[i_valley_2][ik_v_idx][0];
      mu_v = _relev_ik_range[i_valley_2][ik_v_idx][1];
      ik_c = get_ikc(ik_v,ik_cm);
      mu_c = mu_v;

      ik_idx(0,nk_c-1-ik_v_idx,ik_cm_idx) = ik_c-elec_struct.ik_range[0];
      ik_idx(1,nk_c-1-ik_v_idx,ik_cm_idx) = mu_c-elec_struct.mu_range[0];
      ik_idx(2,nk_c-1-ik_v_idx,ik_cm_idx) = ik_v-elec_struct.ik_range[0];
      ik_idx(3,nk_c-1-ik_v_idx,ik_cm_idx) = mu_v-elec_struct.mu_range[0];
    }

  }

  std::cout << "\n...calculated exciton dispersion\n";

  std::cout << "saved exciton dispersion: A2 singlet\n";
  std::string filename = _directory.path()/"ex_energy_A2_singlet.dat";
  ex_energy_A2_singlet.save(filename, arma::arma_ascii);

  std::cout << "saved exciton dispersion: A2 triplet\n";
  filename = _directory.path()/"ex_energy_A2_triplet.dat";
  ex_energy_A2_triplet.save(filename, arma::arma_ascii);

  std::cout << "saved exciton dispersion: A1\n";
  filename = _directory.path()/"ex_energy_A1.dat";
  ex_energy_A1.save(filename, arma::arma_ascii);

  std::cout << "saved k_vector for center of mass\n";
  filename = _directory.path()/"exciton_k_cm_vec.dat";
  k_cm_vec.save(filename, arma::arma_ascii);

  // prepare the values that are to be returned
  std::vector<exciton_struct> excitons(3,exciton_struct(this));

  excitons[0].name = "A1 exciton";
  excitons[0].energy = ex_energy_A1;
  excitons[0].spin = 0;
  excitons[0].mu_cm = 0;
  excitons[0].n_principal = nk_relev;
  excitons[0].nk_c = nk_c;
  excitons[0].nk_cm = nk_cm;
  excitons[0].psi = ex_psi_A1;
  excitons[0].ik_idx = ik_idx;
  excitons[0].ik_cm_range = ik_cm_range;

  excitons[1].name = "A2 triplet exciton";
  excitons[1].energy = ex_energy_A2_triplet;
  excitons[1].spin = 1;
  excitons[1].mu_cm = 0;
  excitons[1].n_principal = nk_relev;
  excitons[1].nk_c = nk_c;
  excitons[1].nk_cm = nk_cm;
  excitons[1].psi = ex_psi_A2_triplet;
  excitons[1].ik_idx = ik_idx;
  excitons[1].ik_cm_range = ik_cm_range;

  excitons[2].name = "A2 singlet exciton";
  excitons[2].energy = ex_energy_A2_singlet;
  excitons[2].spin = 0;
  excitons[2].mu_cm = 0;
  excitons[2].n_principal = nk_relev;
  excitons[2].nk_c = nk_c;
  excitons[2].nk_cm = nk_cm;
  excitons[2].psi = ex_psi_A2_singlet;
  excitons[2].ik_idx = ik_idx;
  excitons[2].ik_cm_range = ik_cm_range;

  return excitons;
}

// call this to do all the calculations at once
void cnt::calculate_exciton_dispersion()
{
  get_parameters();
  get_atom_coordinates();

  // calculate K2-extended representation of electron energy
  std::array<int,2> ik_range_K2 = {0,_Nu/_Q*_nk_K1};
  std::array<int,2> mu_range_K2 = {0,_Q};
  _elec_K2 = electron_energy(ik_range_K2, mu_range_K2, "K2_extended");

  // find valleys and select a range of relevant iks in the focus valleys
  find_valleys(_elec_K2);
  find_relev_ik_range(1.*constants::eV, _elec_K2);

  // calculate vq, and dielectric function for a sufficiently large range of mu and ik.
  std::array<int,2> iq_range = {-(_elec_K2.ik_range[1]-1),_elec_K2.ik_range[1]};
  std::array<int,2> mu_range = {-(_elec_K2.mu_range[1]-1),_elec_K2.mu_range[1]};
  _vq = calculate_vq(iq_range, mu_range, _number_of_cnt_unit_cells);
  _PI = calculate_polarization(iq_range, mu_range, _elec_K2);
  _eps = calculate_dielectric(iq_range, mu_range);

  // calculate exciton dispersions using the information calculated above
  std::array<int,2> ik_cm_range = {-int(_relev_ik_range[0].size()), int(_relev_ik_range[0].size())};
  _excitons = calculate_A_excitons(ik_cm_range, _elec_K2);

}


// Created by A. Aichert on Tue Aug 12th 2014
#ifndef __model_similarity3D
#define __model_similarity3D

#include <LibOpterix/ParameterModel.hxx>

#include <LibProjectiveGeometry/ProjectiveGeometry.hxx>

#include <Eigen/Geometry>

namespace Geometry {
	// Parametrization for a similarity transformation of 3-space
	struct ModelSimilarity3D : public LibOpterix::ParameterModel<Geometry::RP3Homography>
	{
		/// Human readable names of all parameters.
		static const std::vector<std::string>& ParameterNames()
		{
			static std::vector<std::string> names;
			if (names.empty())
			{
				names.resize(7);
				names[ 0]="Translation X";
				names[ 1]="Translation Y";
				names[ 2]="Translation Z";
				names[ 3]="Rotation X";
				names[ 4]="Rotation Y";
				names[ 5]="Rotation Z";
				names[ 6]="3D Scale";
			}
			return names;
		}

		/// Frequent sets of active parameters
		static const LibOpterix::ParameterSets& ParameterSets()
		{
			static LibOpterix::ParameterSets sets;
			if (sets.empty())
			{
				sets["3D Translation"].insert(0);
				sets["3D Translation"].insert(1);
				sets["3D Translation"].insert(2);
				sets["3D Rigid"].insert(0);
				sets["3D Rigid"].insert(1);
				sets["3D Rigid"].insert(2);
				sets["3D Rigid"].insert(3);
				sets["3D Rigid"].insert(4);
				sets["3D Rigid"].insert(5);
				sets["3D Similarity"].insert(0);
				sets["3D Similarity"].insert(1);
				sets["3D Similarity"].insert(2);
				sets["3D Similarity"].insert(3);
				sets["3D Similarity"].insert(4);
				sets["3D Similarity"].insert(5);
				sets["3D Similarity"].insert(6);
			}
			return sets;
		}

		ModelSimilarity3D(std::set<int> _active=std::set<int>())
			: LibOpterix::ParameterModel<Geometry::RP3Homography>(ParameterNames(),_active)
		{}

		// Compose a 3D homography from parametrization
		virtual Geometry::RP3Homography getInstance() const
		{
			using namespace Eigen;
			// Slicker naming of the parameter vector.
			const std::vector<double>& x(current_values);
			// We begin with an identity
			Matrix4d T=Matrix4d::Identity();
			// Rotation about X Y and Z
			if (x[3]!=0||x[4]!=0||x[5]!=0)
			{
				auto R=( AngleAxisd(x[3], Vector3d::UnitX())
						*AngleAxisd(x[4], Vector3d::UnitY())
						*AngleAxisd(x[5], Vector3d::UnitZ()));
				T.block<3,3>(0,0)=(Matrix3d)R;
			}
			// Center of rotation and translation
			T(0,3)=x[0];
			T(1,3)=x[1];
			T(2,3)=x[2];
			// Apply scaling
			if (x[6]!=0) T.block<3,3>(0,0)*=(1.0+x[6]);
			// Return result
			return T;
		}

	};

} // namespace Geometry

#endif // __model_similarity3D


//
// Created by krab1k on 31/10/18.
//

#include <vector>
#include <functional>
#include <Eigen/LU>

#include "eem.h"
#include "../parameters.h"
#include "../geometry.h"

CHARGEFW2_METHOD(EEM)

Eigen::VectorXd EEM::EE_system(const std::vector<const Atom *> &atoms, double total_charge) const {

    size_t n = atoms.size();

    Eigen::MatrixXd A = Eigen::MatrixXd::Zero(n + 1, n + 1);
    Eigen::VectorXd b = Eigen::VectorXd::Zero(n + 1);

    for (size_t i = 0; i < n; i++) {
        const auto &atom_i = *atoms[i];
        A(i, i) = parameters_->atom()->parameter(atom::B)(atom_i);
        b(i) = -parameters_->atom()->parameter(atom::A)(atom_i);
        for (size_t j = i + 1; j < n; j++) {
            const auto &atom_j = *atoms[j];
            auto x = parameters_->common()->parameter(common::kappa) / distance(atom_i, atom_j);
            A(i, j) = x;
            A(j, i) = x;
        }
    }
    A.row(n) = Eigen::VectorXd::Constant(n + 1, 1);
    A.col(n) = Eigen::VectorXd::Constant(n + 1, 1);
    A(n, n) = 0;
    b(n) = total_charge;

    return A.partialPivLu().solve(b).head(n);
}

std::vector<double> EEM::calculate_charges(const Molecule &molecule) const {
    auto f = [this](const std::vector<const Atom *> &atoms, double total_charge) -> Eigen::VectorXd {
        return EE_system(atoms, total_charge);
    };

    Eigen::VectorXd q = solve_EE(molecule, f);
    return std::vector<double>(q.data(), q.data() + q.size());
}


/* Copyright (c) 2015, Julian Straub <jstraub@csail.mit.edu>, Randi Cabezas <rcabezas@csail.mit.edu>
 * Licensed under the MIT license. See the license file LICENSE.
 */

#pragma once

#include <Eigen/Dense>

#include <boost/math/special_functions/gamma.hpp>
#include <boost/math/distributions/inverse_gamma.hpp>

#include <boost/random/chi_squared_distribution.hpp>
#include <boost/random/normal_distribution.hpp>

#include <dpMM/distribution.hpp>
#include <dpMM/normal.hpp>

using namespace Eigen;
using std::endl;
using std::cout;
using std::vector;

template<typename T>
class NIW : public Distribution<T>
{
public:
  Matrix<T,Dynamic,Dynamic> Delta_;
  Matrix<T,Dynamic,1> theta_;
  T nu_,kappa_;
  uint32_t D_;

  NIW(const Matrix<T,Dynamic,Dynamic>& Delta,
    const Matrix<T,Dynamic,Dynamic>& theta, T nu,  T kappa,
    boost::mt19937 *pRndGen);
  NIW(const NIW& niw);
  ~NIW();

  NIW<T>* copy();

  NIW<T> posterior(const Matrix<T,Dynamic,Dynamic>& x, const VectorXu& z,
    uint32_t k);
  NIW<T> posterior(const vector<Matrix<T,Dynamic,Dynamic> >&x, const
      VectorXu& z, uint32_t k);
  // assumes vector [N, sum(x), flatten(sum(outer(x,x)))]
  NIW<T> posteriorFromSS(const Matrix<T,Dynamic,1>& x);
  NIW<T> posteriorFromSS(const vector<Matrix<T,Dynamic,1> >&x, const
      VectorXu& z, uint32_t k);

  NIW<T> posterior() const;
  void resetSufficientStatistics();
  void getSufficientStatistics(const Matrix<T,Dynamic,Dynamic> &x,
    const VectorXu& z, uint32_t k);
  void getSufficientStatistics(const vector<Matrix<T,Dynamic,Dynamic> > &x,
    const VectorXu& z, uint32_t k);
  T logProb(const Matrix<T,Dynamic,Dynamic>& x_i) const;
  T logPosteriorProb(const Matrix<T,Dynamic,Dynamic>& x, VectorXu& z, uint32_t k,
    uint32_t i);

  Normal<T> sample();
  Normal<T> sampleFromPosterior();

  T logPdf(const Normal<T>& normal) const;
  T logPdfMarginalized() const; // log pdf of SS under NIW prior
  T logPdfUnderPriorMarginalizedMerged(const NIW<T>& other) const;

  T logLikelihoodMarginalized(const Matrix<T,Dynamic,Dynamic>& Scatter,
      const Matrix<T,Dynamic,1>& mean, T count) const;
  void print() const;

  virtual NIW<T>* merge(const NIW<T>& other);
  void fromMerge(const NIW<T>& niwA, const NIW<T>& niwB);

  const Matrix<T,Dynamic,Dynamic>& scatter() const {return scatter_;};
  Matrix<T,Dynamic,Dynamic>& scatter() {return scatter_;};
  const Matrix<T,Dynamic,1>& mean() const {return mean_;};
  Matrix<T,Dynamic,1>& mean() {return mean_;};
  T count() const {return count_;};
  T& count() {return count_;};

  void computeMergedSS( const NIW<T>& niwA,
    const NIW<T>& niwB, Matrix<T,Dynamic,Dynamic>& scatterM,
    Matrix<T,Dynamic,1>& muM, T& countM) const;

private:
  boost::random::normal_distribution<> gauss_;

  // sufficient statistics
  Matrix<T,Dynamic,Dynamic> scatter_;
  Matrix<T,Dynamic,1> mean_;
  T count_;

};

typedef NIW<double> NIWd;
typedef NIW<float> NIWf;


///////////////////////////////////////////////////////////////
//  Copyright 2011 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_

   //[cpp_dec_float_eg
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/math/special_functions/gamma.hpp>
#include <iostream>

int main()
{
   using namespace boost::multiprecision;

   // Operations at fixed precision and full numeric_limits support:
   cpp_dec_float_100 b = 2;
   std::cout << std::numeric_limits<cpp_dec_float_100>::digits << std::endl;
   // Note that digits10 is the same as digits, since we're base 10! :
   std::cout << std::numeric_limits<cpp_dec_float_100>::digits10 << std::endl;
   // We can use any C++ std lib function, lets print all the digits as well:
   std::cout << std::setprecision(std::numeric_limits<cpp_dec_float_100>::max_digits10)
      << log(b) << std::endl; // print log(2)
   // We can also use any function from Boost.Math:
   std::cout << boost::math::tgamma(b) << std::endl;
   // These even work when the argument is an expression template:
   std::cout << boost::math::tgamma(b * b) << std::endl;
   // And since we have an extended exponent range we can generate some really large
   // numbers here (4.0238726007709377354370243e+2564):
   std::cout << boost::math::tgamma(cpp_dec_float_100(1000)) << std::endl;
   return 0;
}
//]


//  Copyright (c) 2019 AUTHORS
//
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

// Original Fortran source: http://cococubed.asu.edu/research_pages/sedov.shtml

/* sedov3.f -- translated by f2c (version 20160102).
 You must link the resulting object file with libf2c:
 on Microsoft Windows system, link with libf2c.lib;
 on Linux or Unix systems, link with .../path/to/libf2c.a -lm
 or, if you install libf2c.a in a standard place, with -lf2c -lm
 -- in that order, at the end of the command line, as ina
 cc *.o -lf2c -lm
 Source for libf2c is in /netlib/f2c/libf2c.zip, e.g.,

 http://www.netlib.org/f2c/libf2c.zip
 */

#include <cmath>
#include <vector>
#include <unordered_map>
#include <memory>
//#include "f2c.h"
#include <memory>
#include <assert.h>
#if !defined(OCTOTIGER_HAVE_BOOST_MULTIPRECISION)
#include <quadmath.h>
using sed_real = __float128;

sed_real _exp(sed_real a) {
    return expq(a);
}

sed_real pow_dd(sed_real *a, sed_real *b) {
    return powq(*a, *b);
}

sed_real d_sign(sed_real *a, sed_real * b) {
    return copysignq(*a, *b);
}
#else
#include <boost/multiprecision/cpp_bin_float.hpp>
using sed_real = boost::multiprecision::cpp_bin_float_quad;

sed_real _exp(sed_real a) {
    return boost::multiprecision::exp(a);
}

sed_real pow_dd(sed_real *a, sed_real *b) {
    return boost::multiprecision::pow(*a, *b);
}

sed_real d_sign(sed_real *a, sed_real * b) {
    if ((*a > static_cast<sed_real>(0.) && (*b > static_cast<sed_real>(0.))) ||
        (*a < static_cast<sed_real>(0.) && (*b < static_cast<sed_real>(0.))))
    {
        return *a;
    }
    return *b;
}
#endif

/* Subroutine */int sed_1d__(sed_real *time, int *nstep,
        sed_real * xpos, sed_real *eblast, sed_real *omega_in__,
        sed_real * xgeom_in__, sed_real *rho0, sed_real *vel0,
        sed_real *ener0, sed_real *pres0, sed_real *cs0, sed_real *gam0,
        sed_real *den, sed_real *ener, sed_real *pres, sed_real *vel,
        sed_real *cs);
/* Common Block Declarations */

struct {
    sed_real gamma, gamm1, gamp1, gpogm, xgeom, xg2, rwant, r2, a0, a1, a2, a3,
            a4, a5, a_val__, b_val__, c_val__, d_val__, e_val__, omega, vv,
            xlam_want__, vwant, rvv;
    bool lsingular, lstandard, lvacuum, lomega2, lomega3;
} slap_;

#define slap_1 slap_

struct {
    sed_real gam_int__;
} cmidp_;

#define cmidp_1 cmidp_

/* Table of constant values */

using D_fp = sed_real (*)(sed_real*);
using S_fp = int (*)(
    D_fp, sed_real*, sed_real*, sed_real*, int*);    //Subroutine
using U_fp = int (*)();                              // Unknown procedure type

static sed_real c_b52 = 2.;
static sed_real c_b53 = 1e-10;
static sed_real c_b79 = 0.f;
static sed_real c_b80 = 1e-30;
static int c__3 = 3;
static int c__5 = 5;
static sed_real efun01_(sed_real *v);
static sed_real efun02_(sed_real *v);
static sed_real sed_v_find__(sed_real *v);
static sed_real sed_r_find__(sed_real *r__);
/* Subroutine */int sedov_funcs__(sed_real *v, sed_real *l_fun__, sed_real *dlamdv,
        sed_real *f_fun__, sed_real *g_fun__, sed_real *h_fun__);
/* Subroutine */static int midpnt_(D_fp func, sed_real *a, sed_real *b, sed_real *s,
        int *n);
/* Subroutine */static int midpowl_(D_fp funk, sed_real *aa, sed_real *bb,
        sed_real *s, int *n);
/* Subroutine */static int midpowl2_(D_fp funk, sed_real *aa, sed_real *bb,
        sed_real *s, int *n);
/* Subroutine */static int qromo_(D_fp func, sed_real *a, sed_real *b, sed_real *eps,
        sed_real *ss, S_fp choose);
/* Subroutine */static int polint_(sed_real *xa, sed_real *ya, int *n,
        sed_real *x, sed_real *y, sed_real *dy);
static sed_real zeroin_(sed_real *ax, sed_real *bx, D_fp f, sed_real *tol);

int pow_ii(int * a, int * b) {
    return std::pow(*a, *b);
}

/* Subroutine */int sed_1d__(sed_real *time, int *nstep, sed_real * xpos,
        sed_real *eblast, sed_real *omega_in__, sed_real * xgeom_in__, sed_real *rho0,
        sed_real *vel0, sed_real *ener0, sed_real *pres0, sed_real *cs0, sed_real *gam0,
        sed_real *den, sed_real *ener, sed_real *pres, sed_real *vel, sed_real *cs) {

    /* System generated locals */
    int i__1;
    sed_real d__1, d__2, d__3;

    /* Builtin functions */

    /* Local variables */
    static int i__;
    static sed_real p2, v0, u2, v2, us;
    static sed_real vat, rho1, rho2;
    static sed_real vmin, eval1, eval2, alpha, f_fun__;
    static sed_real g_fun__, h_fun__, l_fun__;
    static sed_real vstar, denom2, denom3, dlamdv;
    sed_real zeroin_(sed_real *ax, sed_real *bx, D_fp f, sed_real *tol);

    /* ..this routine produces 1d solutions for a sedov blast wave propagating */
    /* ..through a density gradient rho = rho**(-omega) */
    /* ..in planar, cylindrical or spherical geometry */
    /* ..for the standard, singular and vaccum cases. */
    /* ..standard case: a nonzero solution extends from the shock to the origin, */
    /* ..               where the pressure is finite. */
    /* ..singular case: a nonzero solution extends from the shock to the origin, */
    /* ..               where the pressure vanishes. */
    /* ..vacuum case  : a nonzero solution extends from the shock to a boundary point, */
    /* ..               where the density vanishes making the pressure meaningless. */
    /* ..input: */
    /* ..time     = temporal point where solution is desired seconds */
    /* ..xpos(i)  = spatial points where solution is desired cm */
    /* ..eblast   = energy of blast erg */
    /* ..rho0     = ambient density g/cm**3    rho = rho0 * r**(-omega_in) */
    /* ..omegain  = density power law _exponent rho = rho0 * r**(-omega_in) */
    /* ..vel0     = ambient material speed cm/s */
    /* ..pres0    = ambient pressure erg/cm**3 */
    /* ..cs0      = ambient sound speed cm/s */
    /* ..gam0   = gamma law equation of state */
    /* ..xgeom_in = geometry factor, 3=spherical, 2=cylindircal, 1=planar */
    /* ..for efficiency reasons (doing the energy integrals only once), */
    /* ..this routine returns the solution for an array of spatial points */
    /* ..at the desired time point. */
    /* ..output: */
    /* ..den(i)  = density  g/cm**3 */
    /* ..ener(i) = specific internal energy erg/g */
    /* ..pres(i) = presssure erg/cm**3 */
    /* ..vel(i)  = velocity cm/s */
    /* ..cs(i)   = sound speed cm/s */
    /* ..this routine is based upon two papers: */
    /* .."evaluation of the sedov-von neumann-taylor blast wave solution" */
    /* ..jim kamm, la-ur-00-6055 */
    /* .."the sedov self-similiar point blast solutions in nonuniform media" */
    /* ..david book, shock waves, 4, 1, 1994 */
    /* ..although the ordinary differential equations are analytic, */
    /* ..the sedov _expressions appear to become singular for various */
    /* ..combinations of parameters and at the lower limits of the integration */
    /* ..range. all these singularies are removable and done so by this routine. */
    /* ..these routines are written in sed_real*8 precision because the */
    /* ..sed_real*8 implementations simply run out of precision "near" the origin */
    /* ..in the standard case or the transition region in the vacuum case. */
    /* ..declare the pass */
    /* ..local variables */
    /* ..eps controls the integration accuracy, don't get too greedy or the number */
    /* ..of function evaluations required kills. */
    /* ..eps2 controls the root find accuracy */
    /* ..osmall controls the size of transition regions */
    /* ..common block communication */
    /* ..common block communication with the integration stepper */
    /* ..popular formats */
    /* ..initialize the solution */
    /* Parameter adjustments */
    --cs;
    --vel;
    --pres;
    --ener;
    --den;
    --xpos;

    /* Function Body */
    /* L87: */
    /* L88: */
    i__1 = *nstep;
    for (i__ = 1; i__ <= i__1; ++i__) {
        den[i__] = 0.f;
        vel[i__] = 0.f;
        pres[i__] = 0.f;
        ener[i__] = 0.f;
        cs[i__] = 0.f;
    }
    /* ..return on unphysical cases */
    /* ..infinite mass */
    if (*omega_in__ >= *xgeom_in__) {
        return 0;
    }
    /* ..transfer the pass to common block and create some frequent combinations */
    slap_1.gamma = *gam0;
    slap_1.gamm1 = slap_1.gamma - 1.f;
    slap_1.gamp1 = slap_1.gamma + 1.f;
    slap_1.gpogm = slap_1.gamp1 / slap_1.gamm1;
    slap_1.xgeom = *xgeom_in__;
    slap_1.omega = *omega_in__;
    slap_1.xg2 = slap_1.xgeom + 2.f - slap_1.omega;
    denom2 = slap_1.gamm1 * 2.f + slap_1.xgeom - slap_1.gamma * slap_1.omega;
    denom3 = slap_1.xgeom * (2.f - slap_1.gamma) - slap_1.omega;
    /* ..post shock location v2 and location of singular point vstar */
    /* ..kamm equation 18 and 19 */
    v2 = 4.f / (slap_1.xg2 * slap_1.gamp1);
    vstar = 2.f / (slap_1.gamm1 * slap_1.xgeom + 2.f);
    /* ..set two bools that determines the type of solution */
    slap_1.lstandard = false;
    slap_1.lsingular = false;
    slap_1.lvacuum = false;
    if ((d__1 = v2 - vstar, fabs(static_cast<double>(d__1))) <= 1e-4) {
        slap_1.lsingular = true;
    } else if (v2 < vstar - 1e-4) {
        slap_1.lstandard = true;
    } else if (v2 > vstar + 1e-4) {
        slap_1.lvacuum = true;
    }
    /* ..two apparent singularies, book's notation for omega2 and omega3 */
    slap_1.lomega2 = false;
    slap_1.lomega3 = false;
    if (fabs(static_cast<double>(denom2)) <= 1e-4) {
        slap_1.lomega2 = true;
        denom2 = 1e-8f;
    } else if (fabs(static_cast<double>(denom3)) <= 1e-4) {
        slap_1.lomega3 = true;
        denom3 = 1e-8f;
    }
    /* ..various _exponents, kamm equations 42-47 */
    /* ..in terms of book's notation: */
    /* ..a0=beta6 a1=beta1  a2=-beta2 a3=beta3 a4=beta4 a5=-beta5 */
    slap_1.a0 = 2.f / slap_1.xg2;
    slap_1.a2 = -slap_1.gamm1 / denom2;
    slap_1.a1 = slap_1.xg2 * slap_1.gamma / (slap_1.xgeom * slap_1.gamm1 + 2.f)
            * ((slap_1.xgeom * (2.f - slap_1.gamma) - slap_1.omega) * 2.f
                    / (slap_1.gamma * slap_1.xg2 * slap_1.xg2) - slap_1.a2);
    slap_1.a3 = (slap_1.xgeom - slap_1.omega) / denom2;
    slap_1.a4 = slap_1.xg2 * (slap_1.xgeom - slap_1.omega) * slap_1.a1 / denom3;
    slap_1.a5 = (slap_1.omega * slap_1.gamp1 - slap_1.xgeom * 2.f) / denom3;
    /* ..frequent combinations, kamm equations 33-37 */
    slap_1.a_val__ = slap_1.xg2 * .25f * slap_1.gamp1;
    slap_1.b_val__ = slap_1.gpogm;
    slap_1.c_val__ = slap_1.xg2 * .5f * slap_1.gamma;
    slap_1.d_val__ = slap_1.xg2 * slap_1.gamp1
            / (slap_1.xg2 * slap_1.gamp1
                    - (slap_1.xgeom * slap_1.gamm1 + 2.f) * 2.f);
    slap_1.e_val__ = (slap_1.xgeom * slap_1.gamm1 + 2.f) * .5f;
    /* ..evaluate the energy integrals */
    /* ..the singular case can be done by hand; save some cpu cycles */
    /* ..kamm equations 80, 81, and 85 */
    if (slap_1.lsingular) {
        /* Computing 2nd power */
        d__1 = slap_1.gamm1 * slap_1.xgeom + 2.f;
        eval2 = slap_1.gamp1 / (slap_1.xgeom * (d__1 * d__1));
        eval1 = 2.f / slap_1.gamm1 * eval2;
        /* Computing 2nd power */
        d__1 = slap_1.gamm1 * slap_1.xgeom + 2.f;
        alpha = slap_1.gpogm * pow_dd(&c_b52, &slap_1.xgeom)
                / (slap_1.xgeom * (d__1 * d__1));
        if (static_cast<int>(slap_1.xgeom) != 1) {
            alpha *= 3.1415926535897932384626433832795029;
        }
        /* ..for the standard or vacuum cases */
        /* ..v0 = post-shock origin v0 and vv = vacuum boundary vv */
        /* ..set the radius corespondin to vv to zero for now */
        /* ..kamm equations 18, and 28. */
    } else {
        v0 = 2.f / (slap_1.xg2 * slap_1.gamma);
        slap_1.vv = 2.f / slap_1.xg2;
        slap_1.rvv = 0.;
        if (slap_1.lstandard) {
            vmin = v0;
        }
        if (slap_1.lvacuum) {
            vmin = slap_1.vv;
        }
        /* ..the first energy integral */
        /* ..in the standard case the term (c_val*v - 1) might be singular at v=vmin */
        if (slap_1.lstandard) {
            cmidp_1.gam_int__ = slap_1.a3 - slap_1.a2 * slap_1.xg2 - 1.f;
            if (cmidp_1.gam_int__ >= 0.) {
                qromo_(static_cast<D_fp>(efun01_), &vmin, &v2, &c_b53, &eval1,
                        static_cast<S_fp>(midpnt_));
            } else {
                cmidp_1.gam_int__ = fabs(static_cast<double>(cmidp_1.gam_int__));
                qromo_(static_cast<D_fp>(efun01_), &vmin, &v2, &c_b53, &eval1,
                        static_cast<S_fp>(midpowl_));
            }
            /* ..in the vacuum case the term (1 - c_val/gamma*v) might be singular at v=vmin */
        } else if (slap_1.lvacuum) {
            cmidp_1.gam_int__ = slap_1.a5;
            if (cmidp_1.gam_int__ >= 0.) {
                qromo_(static_cast<D_fp>(efun01_), &vmin, &v2, &c_b53, &eval1,
                        static_cast<S_fp>(midpnt_));
            } else {
                cmidp_1.gam_int__ = fabs(static_cast<double>(cmidp_1.gam_int__));
                qromo_(static_cast<D_fp>(efun01_), &vmin, &v2, &c_b53, &eval1,
                        static_cast<S_fp>(midpowl2_));
            }
        }
        /* ..the second energy integral */
        /* ..in the standard case the term (c_val*v - 1) might be singular at v=vmin */
        if (slap_1.lstandard) {
            cmidp_1.gam_int__ = slap_1.a3 - slap_1.a2 * slap_1.xg2 - 2.f;
            if (cmidp_1.gam_int__ >= 0.) {
                qromo_(static_cast<D_fp>(efun02_), &vmin, &v2, &c_b53, &eval2,
                        static_cast<S_fp>(midpnt_));
            } else {
                cmidp_1.gam_int__ = fabs(static_cast<double>(cmidp_1.gam_int__));
                qromo_(static_cast<D_fp>(efun02_), &vmin, &v2, &c_b53, &eval2,
                        static_cast<S_fp>(midpowl_));
            }
            /* ..in the vacuum case the term (1 - c_val/gamma*v) might be singular at v=vmin */
        } else if (slap_1.lvacuum) {
            cmidp_1.gam_int__ = slap_1.a5;
            if (cmidp_1.gam_int__ >= 0.) {
                qromo_(static_cast<D_fp>(efun02_), &vmin, &v2, &c_b53, &eval2,
                        static_cast<S_fp>(midpnt_));
            } else {
                cmidp_1.gam_int__ = fabs(static_cast<double>(cmidp_1.gam_int__));
                qromo_(static_cast<D_fp>(efun02_), &vmin, &v2, &c_b53, &eval2,
                        static_cast<S_fp>(midpowl2_));
            }
        }
        /* ..kamm equations 57 and 58 for alpha, in a slightly different form. */
        if (static_cast<int>(slap_1.xgeom) == 1) {
            alpha = eval1 * .5f + eval2 / slap_1.gamm1;
        } else {
            alpha = (slap_1.xgeom - 1.f) * 3.1415926535897932384626433832795029
                    * (eval1 + eval2 * 2.f / slap_1.gamm1);
        }
    }
    /* ..write what we have for the energy integrals */
    if (true) {
//		s_wsfe(&io___42);
//		do_fio(&c__1, "xgeom =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &slap_1.xgeom, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "eblast=", (ftnlen) 7);
//		do_fio(&c__1, (char *) &(*eblast), (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "omega =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &slap_1.omega, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "alpha =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &alpha, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "j1    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &eval1, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "j2    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &eval2, (ftnlen) sizeof(sed_real));
//		e_wsfe();
    }
    /*      write(6,87) omega,alpha */
    /* ..immediate post-shock values */
    /* ..kamm page 14 or equations 14, 16, 5, 13 */
    /* ..r2 = shock position, u2 = shock speed, rho1 = pre-shock density, */
    /* ..u2 = post-shock material speed, rho2 = post-shock density, */
    /* ..p2 = post-shock pressure, e2 = post-shoock specific internal energy, */
    /* ..and cs2 = post-shock sound speed */
    d__1 = *eblast / (alpha * *rho0);
    d__2 = 1.f / slap_1.xg2;
    d__3 = 2.f / slap_1.xg2;
    slap_1.r2 = pow_dd(&d__1, &d__2) * pow_dd(time, &d__3);
    us = 2.f / slap_1.xg2 * slap_1.r2 / *time;
    d__1 = -slap_1.omega;
    rho1 = *rho0 * pow_dd(&slap_1.r2, &d__1);
    u2 = us * 2.f / slap_1.gamp1;
    rho2 = slap_1.gpogm * rho1;
    /* Computing 2nd power */
    d__1 = us;
    p2 = rho1 * 2.f * (d__1 * d__1) / slap_1.gamp1;
//	e2 = p2 / (slap_1.gamm1 * rho2);
//	cs2 = sqrt(slap_1.gamma * p2 / rho2);
    /* ..find the radius corresponding to vv */
    if (slap_1.lvacuum) {
        slap_1.vwant = slap_1.vv;
        slap_1.rvv = zeroin_(&c_b79, &slap_1.r2, static_cast<D_fp>(sed_r_find__), &c_b80);
    }
//	if (slap_1.lstandard) {
//		s_wsfe(&io___50);
//		do_fio(&c__1, "r2    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &slap_1.r2, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "rho2  =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &rho2, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "u2    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &u2, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "e2    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &e2, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "p2    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &p2, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "cs2   =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &cs2, (ftnlen) sizeof(sed_real));
//		e_wsfe();
//	}
//	if (slap_1.lvacuum) {
//		s_wsfe(&io___51);
//		do_fio(&c__1, "rv    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &slap_1.rvv, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "r2    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &slap_1.r2, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "rho2  =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &rho2, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "u2    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &u2, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "e2    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &e2, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "p2    =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &p2, (ftnlen) sizeof(sed_real));
//		do_fio(&c__1, "cs2   =", (ftnlen) 7);
//		do_fio(&c__1, (char *) &cs2, (ftnlen) sizeof(sed_real));
//		e_wsfe();
//	}
    /* ..now start the loop over spatial positions */
    i__1 = *nstep;
    for (i__ = 1; i__ <= i__1; ++i__) {
        slap_1.rwant = xpos[i__];
        /* ..if we are upstream from the shock front */
        if (slap_1.rwant > slap_1.r2) {
            d__1 = -slap_1.omega;
            den[i__] = *rho0 * pow_dd(&slap_1.rwant, &d__1);
            vel[i__] = *vel0;
            pres[i__] = *pres0;
            ener[i__] = *ener0;
            cs[i__] = *cs0;
            /* ..if we are between the origin and the shock front */
            /* ..find the correct similarity value for this radius in the standard or vacuum cases */
        } else {
            if (slap_1.lstandard) {
                d__1 = v0 * .9f;
                vat = zeroin_(&d__1, &v2, static_cast<D_fp>(sed_v_find__), &c_b80);
            } else if (slap_1.lvacuum) {
                d__1 = slap_1.vv * 1.2f;
                vat = zeroin_(&v2, &d__1, static_cast<D_fp>(sed_v_find__), &c_b80);
            }
            /* ..the physical solution */
            sedov_funcs__(&vat, &l_fun__, &dlamdv, &f_fun__, &g_fun__,
                    &h_fun__);
            den[i__] = rho2 * g_fun__;
            vel[i__] = u2 * f_fun__;
            pres[i__] = p2 * h_fun__;
            ener[i__] = 0.f;
            cs[i__] = 0.f;
            if (den[i__] != 0.f) {
                ener[i__] = pres[i__] / (slap_1.gamm1 * den[i__]);
                cs[i__] = sqrt(static_cast<double>(slap_1.gamma * pres[i__] / den[i__]));
            }
        }
        /* ..end of loop over positions */
    }
    return 0;
} /* sed_1d__ */

sed_real efun01_(sed_real *v) {
    /* System generated locals */
    sed_real ret_val, d__1, d__2;
    ret_val = 0;

    /* Builtin functions */
    sed_real pow_dd(sed_real *, sed_real *);

    /* Local variables */
    static sed_real f_fun__, g_fun__, h_fun__, l_fun__, dlamdv;

    /* ..evaluates the first energy integrand, kamm equations 67 and 10. */
    /* ..the (c_val*v - 1) term might be singular at v=vmin in the standard case. */
    /* ..the (1 - c_val/gamma * v) term might be singular at v=vmin in the vacuum case. */
    /* ..due care should be taken for these removable singularities by the integrator. */
    /* ..declare the pass */
    /* ..common block communication */
    /* ..local variables */
    /* ..go */
    sedov_funcs__(v, &l_fun__, &dlamdv, &f_fun__, &g_fun__, &h_fun__);
    d__1 = slap_1.xgeom + 1.f;
    /* Computing 2nd power */
    d__2 = *v;
    ret_val = dlamdv * pow_dd(&l_fun__, &d__1) * slap_1.gpogm * g_fun__
            * (d__2 * d__2);
    return ret_val;
} /* efun01_ */

sed_real efun02_(sed_real *v) {
    /* System generated locals */
    sed_real ret_val, d__1;
    ret_val = 0;

    /* Builtin functions */
    sed_real pow_dd(sed_real *, sed_real *);

    /* Local variables */
    static sed_real z__;
    static sed_real f_fun__, g_fun__, h_fun__, l_fun__, dlamdv;

    /* ..evaluates the second energy integrand, kamm equations 68 and 11. */
    /* ..the (c_val*v - 1) term might be singular at v=vmin in the standard case. */
    /* ..the (1 - c_val/gamma * v) term might be singular at v=vmin in the vacuum case. */
    /* ..due care should be taken for these removable singularities by the integrator. */
    /* ..declare the pass */
    /* ..common block communication */
    /* ..local variables */
    /* ..go */
    sedov_funcs__(v, &l_fun__, &dlamdv, &f_fun__, &g_fun__, &h_fun__);
    /* Computing 2nd power */
    d__1 = slap_1.xgeom + 2.f - slap_1.omega;
    z__ = 8.f / (d__1 * d__1 * slap_1.gamp1);
    d__1 = slap_1.xgeom - 1.f;
    ret_val = dlamdv * pow_dd(&l_fun__, &d__1) * h_fun__ * z__;
    return ret_val;
} /* efun02_ */

sed_real sed_v_find__(sed_real *v) {
    /* System generated locals */
    sed_real ret_val;
    ret_val = 0;

    /* Local variables */
    static sed_real f_fun__, g_fun__, h_fun__, l_fun__, dlamdv;

    /* ..given corresponding physical distances, find the similarity variable v */
    /* ..kamm equation 38 as a root find */
    /* ..declare the pass */
    /* ..common block communication */
    /* ..local variables */
    sedov_funcs__(v, &l_fun__, &dlamdv, &f_fun__, &g_fun__, &h_fun__);
    ret_val = slap_1.r2 * l_fun__ - slap_1.rwant;
    return ret_val;
} /* sed_v_find__ */

sed_real sed_r_find__(sed_real *r__) {
    /* System generated locals */
    sed_real ret_val;
    ret_val = 0;

    /* Local variables */
    static sed_real f_fun__, g_fun__, h_fun__, l_fun__, dlamdv;

    /* ..given the similarity variable v, find the corresponding physical distance */
    /* ..kamm equation 38 as a root find */
    /* ..declare the pass */
    /* ..common block communication */
    /* ..local variables */
    sedov_funcs__(&slap_1.vwant, &l_fun__, &dlamdv, &f_fun__, &g_fun__,
            &h_fun__);
    ret_val = slap_1.r2 * l_fun__ - *r__;
    return ret_val;
} /* sed_r_find__ */

/* Subroutine */int sedov_funcs__(sed_real *v, sed_real *l_fun__, sed_real *dlamdv,
        sed_real *f_fun__, sed_real *g_fun__, sed_real *h_fun__) {
    /* System generated locals */
    sed_real d__1, d__2, d__3;

    /* Builtin functions */
//	sed_real pow_dd(sed_real *, sed_real *), _exp(sed_real);

    /* Local variables */
    static sed_real y, z__, c2, c6, x1, x2, x3, x4, pp1, pp2, pp3, pp4, cbag,
            ebag, beta0, dx1dv, dx2dv, dx3dv, dx4dv, dpp2dv;

    /* ..given the similarity variable v, returns functions */
    /* ..lambda, f, g, and h and the derivative of lambda with v dlamdv */
    /* ..although the ordinary differential equations are analytic, */
    /* ..the sedov _expressions appear to become singular for various */
    /* ..combinations of parameters and at the lower limits of the integration */
    /* ..range. all these singularies are removable and done so by this routine. */
    /* ..declare the pass */
    /* ..common block communication */
    /* ..local variables */
    /* ..frequent combinations and their derivative with v */
    /* ..kamm equation 29-32, x4 a bit different to save a divide */
    /* ..x1 is book's F */
    x1 = slap_1.a_val__ * *v;
    dx1dv = slap_1.a_val__;
    /* Computing MAX */
    d__1 = 1e-30, d__2 = slap_1.c_val__ * *v - 1.f;
    cbag = fmax(static_cast<double>(d__1), static_cast<double>(d__2));
    x2 = slap_1.b_val__ * cbag;
    dx2dv = slap_1.b_val__ * slap_1.c_val__;
    ebag = 1.f - slap_1.e_val__ * *v;
    x3 = slap_1.d_val__ * ebag;
    dx3dv = -slap_1.d_val__ * slap_1.e_val__;
    x4 = slap_1.b_val__ * (1.f - slap_1.xg2 * .5f * *v);
    dx4dv = -slap_1.b_val__ * .5f * slap_1.xg2;
    /* ..transition region between standard and vacuum cases */
    /* ..kamm page 15 or equations 88-92 */
    /* ..lambda = l_fun is book's zeta */
    /* ..f_fun is books V, g_fun is book's D, h_fun is book's P */
    if (slap_1.lsingular) {
        *l_fun__ = slap_1.rwant / slap_1.r2;
        *dlamdv = 0.f;
        *f_fun__ = *l_fun__;
        d__1 = slap_1.xgeom - 2.f;
        *g_fun__ = pow_dd(l_fun__, &d__1);
        *h_fun__ = pow_dd(l_fun__, &slap_1.xgeom);
        /* ..for the vacuum case in the hole */
    } else if (slap_1.lvacuum && slap_1.rwant < slap_1.rvv) {
        *l_fun__ = 0.f;
        *dlamdv = 0.f;
        *f_fun__ = 0.f;
        *g_fun__ = 0.f;
        *h_fun__ = 0.f;
        /* ..omega = omega2 = (2*(gamma -1) + xgeom)/gamma case, denom2 = 0 */
        /* ..book _expressions 20-22 */
    } else if (slap_1.lomega2) {
        beta0 = 1.f / (slap_1.e_val__ * 2.f);
        pp1 = slap_1.gamm1 * beta0;
        c6 = slap_1.gamp1 * .5f;
        c2 = c6 / slap_1.gamma;
        y = 1.f / (x1 - c2);
        z__ = (1.f - x1) * y;
        pp2 = slap_1.gamp1 * beta0 * z__;
        dpp2dv = -slap_1.gamp1 * beta0 * dx1dv * y * (z__ + 1.f);
        pp3 = (4.f - slap_1.xgeom - slap_1.gamma * 2.f) * beta0;
        pp4 = -slap_1.xgeom * slap_1.gamma * beta0;
        d__1 = -slap_1.a0;
        *l_fun__ = pow_dd(&x1, &d__1) * pow_dd(&x2, &pp1) * _exp(pp2);
        *dlamdv = (-slap_1.a0 * dx1dv / x1 + pp1 * dx2dv / x2 + dpp2dv)
                * *l_fun__;
        *f_fun__ = x1 * *l_fun__;
        d__1 = slap_1.a0 * slap_1.omega;
        *g_fun__ = pow_dd(&x1, &d__1) * pow_dd(&x2, &pp3) * pow_dd(&x4, &
        slap_1.a5) * _exp(pp2 * -2.f);
        d__1 = slap_1.a0 * slap_1.xgeom;
        d__2 = slap_1.a5 + 1.f;
        *h_fun__ = pow_dd(&x1, &d__1) * pow_dd(&x2, &pp4) * pow_dd(&x4, &d__2);
        /* ..omega = omega3 = xgeom*(2 - gamma) case, denom3 = 0 */
        /* ..book _expressions 23-25 */
    } else if (slap_1.lomega3) {
        beta0 = 1.f / (slap_1.e_val__ * 2.f);
        pp1 = slap_1.a3 + slap_1.omega * slap_1.a2;
        pp2 = 1.f - beta0 * 4.f;
        c6 = slap_1.gamp1 * .5f;
        pp3 = -slap_1.xgeom * slap_1.gamma * slap_1.gamp1 * beta0 * (1.f - x1)
                / (c6 - x1);
        pp4 = (slap_1.xgeom * slap_1.gamm1 - slap_1.gamma) * 2.f * beta0;
        d__1 = -slap_1.a0;
        d__2 = -slap_1.a2;
        d__3 = -slap_1.a1;
        *l_fun__ = pow_dd(&x1, &d__1) * pow_dd(&x2, &d__2) * pow_dd(&x4, &d__3);
        *dlamdv = -(slap_1.a0 * dx1dv / x1 + slap_1.a2 * dx2dv / x2 +
        slap_1.a1 * dx4dv / x4) * *l_fun__;
        *f_fun__ = x1 * *l_fun__;
        d__1 = slap_1.a0 * slap_1.omega;
        *g_fun__ = pow_dd(&x1, &d__1) * pow_dd(&x2, &pp1) * pow_dd(&x4, &pp2)
                * _exp(pp3);
        d__1 = slap_1.a0 * slap_1.xgeom;
        *h_fun__ = pow_dd(&x1, &d__1) * pow_dd(&x4, &pp4) * _exp(pp3);
        /* ..for the standard or vacuum case not in the hole */
        /* ..kamm equations 38-41 */
    } else {
        d__1 = -slap_1.a0;
        d__2 = -slap_1.a2;
        d__3 = -slap_1.a1;
        *l_fun__ = pow_dd(&x1, &d__1) * pow_dd(&x2, &d__2) * pow_dd(&x3, &d__3);
        *dlamdv = -(slap_1.a0 * dx1dv / x1 + slap_1.a2 * dx2dv / x2 +
        slap_1.a1 * dx3dv / x3) * *l_fun__;
        *f_fun__ = x1 * *l_fun__;
        d__1 = slap_1.a0 * slap_1.omega;
        d__2 = slap_1.a3 + slap_1.a2 * slap_1.omega;
        d__3 = slap_1.a4 + slap_1.a1 * slap_1.omega;
        *g_fun__ = pow_dd(&x1, &d__1) * pow_dd(&x2, &d__2) * pow_dd(&x3, &d__3)
                * pow_dd(&x4, &slap_1.a5);
        d__1 = slap_1.a0 * slap_1.xgeom;
        d__2 = slap_1.a4 + slap_1.a1 * (slap_1.omega - 2.f);
        d__3 = slap_1.a5 + 1.f;
        *h_fun__ = pow_dd(&x1, &d__1) * pow_dd(&x3, &d__2) * pow_dd(&x4, &d__3);
    }
    return 0;
} /* sedov_funcs__ */

/* Subroutine */int midpnt_(D_fp func, sed_real *a, sed_real *b, sed_real *s,
        int *n) {
    /* System generated locals */
    int i__1;
    sed_real d__1;

    /* Builtin functions */
    int pow_ii(int *, int *);

    /* Local variables */
    static int j;
    static sed_real x;
    static int it;
    static sed_real del, tnm, sum, ddel;

    /* ..this routine computes the n'th stage of refinement of an extended midpoint */
    /* ..rule. func is input as the name of the function to be integrated between */
    /* ..limits a and b. when called with n=1, the routine returns as s the crudest */
    /* ..estimate of the integralof func from a to b. subsequent calls with n=2,3... */
    /* ..improve the accuracy of s by adding 2/3*3**(n-1) addtional interior points. */
    /* ..declare */
    if (*n == 1) {
        d__1 = (*a + *b) * .5f;
        *s = (*b - *a) * (*func)(&d__1);
    } else {
        i__1 = *n - 2;
        it = pow_ii(&c__3, &i__1);
        tnm = static_cast<sed_real>(it);
        del = (*b - *a) / (tnm * 3.f);
        ddel = del + del;
        x = *a + del * .5f;
        sum = 0.f;
        i__1 = it;
        for (j = 1; j <= i__1; ++j) {
            sum += (*func)(&x);
            x += ddel;
            sum += (*func)(&x);
            x += del;
        }
        *s = (*s + (*b - *a) * sum / tnm) / 3.f;
    }
    return 0;
} /* midpnt_ */

/* Subroutine */int midpowl_(D_fp funk, sed_real *aa, sed_real *bb, sed_real *s,
        int *n) {
    /* System generated locals */
    int i__1;
    sed_real d__1, d__2, d__3, d__4;

    /* Builtin functions */
    sed_real pow_dd(sed_real *, sed_real *);
    int pow_ii(int *, int *);

    /* Local variables */
    static sed_real a, b;
    static int j;
    static sed_real x;
    static int it;
    static sed_real del, tnm, sum, ddel;

    /* ..this routine is an exact replacement for midpnt, except that it allows for */
    /* ..an integrable power-law singularity of the form (x - a)**(-gam_int) */
    /* ..at the lower limit aa for 0 < gam_int < 1. */
    /* ..declare */
    /* ..common block communication */
    /* ..a little conversion, recipe equation 4.4.3 */
    d__1 = *bb - *aa;
    d__2 = 1.f - cmidp_1.gam_int__;
    b = pow_dd(&d__1, &d__2);
    a = 0.f;
    /* ..now exactly as midpnt */
    if (*n == 1) {
        d__1 = (a + b) * .5f;
        d__2 = cmidp_1.gam_int__ / (1.f - cmidp_1.gam_int__);
        d__4 = 1.f / (1.f - cmidp_1.gam_int__);
        d__3 = pow_dd(&d__1, &d__4) + *aa;
        *s = (b - a)
                * (1.f / (1.f - cmidp_1.gam_int__) * pow_dd(&d__1, &d__2)
                        * (*funk)(&d__3));
    } else {
        i__1 = *n - 2;
        it = pow_ii(&c__3, &i__1);
        tnm = static_cast<sed_real>(it);
        del = (b - a) / (tnm * 3.f);
        ddel = del + del;
        x = a + del * .5f;
        sum = 0.f;
        i__1 = it;
        for (j = 1; j <= i__1; ++j) {
            d__1 = cmidp_1.gam_int__ / (1.f - cmidp_1.gam_int__);
            d__3 = 1.f / (1.f - cmidp_1.gam_int__);
            d__2 = pow_dd(&x, &d__3) + *aa;
            sum += 1.f / (1.f - cmidp_1.gam_int__) * pow_dd(&x, &d__1)
                    * (*funk)(&d__2);
            x += ddel;
            d__1 = cmidp_1.gam_int__ / (1.f - cmidp_1.gam_int__);
            d__3 = 1.f / (1.f - cmidp_1.gam_int__);
            d__2 = pow_dd(&x, &d__3) + *aa;
            sum += 1.f / (1.f - cmidp_1.gam_int__) * pow_dd(&x, &d__1)
                    * (*funk)(&d__2);
            x += del;
        }
        *s = (*s + (b - a) * sum / tnm) / 3.f;
    }
    return 0;
} /* midpowl_ */

/* Subroutine */int midpowl2_(D_fp funk, sed_real *aa, sed_real *bb, sed_real *s,
        int *n) {
    /* System generated locals */
    int i__1;
    sed_real d__1, d__2, d__3, d__4;

    /* Builtin functions */

    /* Local variables */
    static sed_real a, b;
    static int j;
    static sed_real x;
    static int it;
    static sed_real del, tnm, sum, ddel;

    /* ..this routine is an exact replacement for midpnt, except that it allows for */
    /* ..an integrable power-law singularity of the form (a - x)**(-gam_int) */
    /* ..at the lower limit aa for 0 < gam_int < 1. */
    /* ..declare */
    /* ..common block communication */
    /* ..a little conversion, modulo recipe equation 4.4.3 */
    d__1 = *aa - *bb;
    d__2 = 1.f - cmidp_1.gam_int__;
    b = pow_dd(&d__1, &d__2);
    a = 0.f;
    /* ..now exactly as midpnt */
    if (*n == 1) {
        d__1 = (a + b) * .5f;
        d__2 = cmidp_1.gam_int__ / (1.f - cmidp_1.gam_int__);
        d__4 = 1.f / (1.f - cmidp_1.gam_int__);
        d__3 = *aa - pow_dd(&d__1, &d__4);
        *s = (b - a)
                * (1.f / (cmidp_1.gam_int__ - 1.f) * pow_dd(&d__1, &d__2)
                        * (*funk)(&d__3));
    } else {
        i__1 = *n - 2;
        it = pow_ii(&c__3, &i__1);
        tnm = static_cast<sed_real>(it);
        del = (b - a) / (tnm * 3.f);
        ddel = del + del;
        x = a + del * .5f;
        sum = 0.f;
        i__1 = it;
        for (j = 1; j <= i__1; ++j) {
            d__1 = cmidp_1.gam_int__ / (1.f - cmidp_1.gam_int__);
            d__3 = 1.f / (1.f - cmidp_1.gam_int__);
            d__2 = *aa - pow_dd(&x, &d__3);
            sum += 1.f / (cmidp_1.gam_int__ - 1.f) * pow_dd(&x, &d__1)
                    * (*funk)(&d__2);
            x += ddel;
            d__1 = cmidp_1.gam_int__ / (1.f - cmidp_1.gam_int__);
            d__3 = 1.f / (1.f - cmidp_1.gam_int__);
            d__2 = *aa - pow_dd(&x, &d__3);
            sum += 1.f / (cmidp_1.gam_int__ - 1.f) * pow_dd(&x, &d__1)
                    * (*funk)(&d__2);
            x += del;
        }
        *s = (*s + (b - a) * sum / tnm) / 3.f;
    }
    return 0;
} /* midpowl2_ */

/* Subroutine */int qromo_(D_fp func, sed_real *a, sed_real *b, sed_real *eps,
        sed_real *ss, S_fp choose) {
    /* Builtin functions */

    /* Local variables */
    static sed_real h__[15];
    static int j;
    static sed_real s[15], dss;

    /* Fortran I/O blocks */

    /* ..this routine returns as s the integral of the function func from a to b */
    /* ..with fractional accuracy eps. *//* ..jmax limits the number of steps; nsteps = 3**(jmax-1) */
    /* ..integration is done via romberg algorithm. */
    /* ..it is assumed the call to choose triples the number of steps on each call */
    /* ..and that its error series contains only even powers of the number of steps. */
    /* ..the external choose may be any of the above drivers, i.e midpnt,midinf... */
    /* ..declare */
    h__[0] = 1.f;
    for (j = 1; j <= 14; ++j) {
        (*choose)(static_cast<D_fp>(func), a, b, &s[j - 1], &j);
        if (j >= 5) {
            polint_(&h__[j - 5], &s[j - 5], &c__5, &c_b79, ss, &dss);
            if (fabs(static_cast<double>(dss)) <= *eps * fabs(static_cast<double>(*ss))) {
                return 0;
            }
        }
        s[j] = s[j - 1];
        h__[j] = h__[j - 1] / 9.f;
    }
    return 0;
} /* qromo_ */

/* Subroutine */int polint_(sed_real *xa, sed_real *ya, int *n, sed_real *x,
        sed_real *y, sed_real *dy) {
    /* System generated locals */
    int i__1, i__2;
    sed_real d__1;

    /* Local variables */
    static sed_real c__[20], d__[20];
    static int i__, m;
    static sed_real w, ho, hp;
    static int ns;
    static sed_real dif, den, dift;

    /* ..given arrays xa and ya of length n and a value x, this routine returns a */
    /* ..value y and an error estimate dy. if p(x) is the polynomial of degree n-1 */
    /* ..such that ya = p(xa) ya then the returned value is y = p(x) */
    /* ..declare */
    /* ..find the index ns of the closest table entry; initialize the c and d tables */
    /* Parameter adjustments */
    --ya;
    --xa;

    /* Function Body */
    ns = 1;
    dif = (d__1 = *x - xa[1], fabs(static_cast<double>(d__1)));
    i__1 = *n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        dift = (d__1 = *x - xa[i__], fabs(static_cast<double>(d__1)));
        if (dift < dif) {
            ns = i__;
            dif = dift;
        }
        c__[i__ - 1] = ya[i__];
        d__[i__ - 1] = ya[i__];
    }
    /* ..first guess for y */
    *y = ya[ns];
    /* ..for each column of the table, loop over the c's and d's and update them */
    --ns;
    i__1 = *n - 1;
    for (m = 1; m <= i__1; ++m) {
        i__2 = *n - m;
        for (i__ = 1; i__ <= i__2; ++i__) {
            ho = xa[i__] - *x;
            hp = xa[i__ + m] - *x;
            w = c__[i__] - d__[i__ - 1];
            den = ho - hp;
            if (den == 0.f) {
    //			s_stop(" 2 xa entries are the same in polint", (ftnlen) 36);
            }
            den = w / den;
            d__[i__ - 1] = hp * den;
            c__[i__ - 1] = ho * den;
        }
        /* ..after each column is completed, decide which correction c or d, to add */
        /* ..to the accumulating value of y, that is, which path to take in the table */
        /* ..by forking up or down. ns is updated as we go to keep track of where we */
        /* ..are. the last dy added is the error indicator. */
        if (ns << 1 < *n - m) {
            *dy = c__[ns];
        } else {
            *dy = d__[ns - 1];
            --ns;
        }
        *y += *dy;
    }
    return 0;
} /* polint_ */

sed_real zeroin_(sed_real *ax, sed_real *bx, D_fp f, sed_real *tol) {
    /* System generated locals */
    sed_real ret_val, d__1;
    ret_val = 0;

    /* Local variables */
    static sed_real a, b, c__, d__, e, p, q, r__, s, fa, fb, fc, xm, eps, tol1;

    /* ----------------------------------------------------------------------- */

    /* This subroutine solves for a zero of the function  f(x)  in the */
    /* interval ax,bx. */

    /*  input.. */

    /*  ax     left endpoint of initial interval */
    /*  bx     right endpoint of initial interval */
    /*  f      function subprogram which evaluates f(x) for any x in */
    /*         the interval  ax,bx */
    /*  tol    desired length of the interval of uncertainty of the */
    /*         final result ( .ge. 0.0) */

    /*  output.. */

    /*  zeroin abcissa approximating a zero of  f  in the interval ax,bx */

    /*      it is assumed  that   f(ax)   and   f(bx)   have  opposite  signs */
    /*  without  a  check.  zeroin  returns a zero  x  in the given interval */
    /*  ax,bx  to within a tolerance  4*macheps*fabs(x) + tol, where macheps */
    /*  is the relative machine precision. */
    /*      this function subprogram is a slightly  modified  translation  of */
    /*  the algol 60 procedure  zero  given in  richard brent, algorithms for */
    /*  minimization without derivatives, prentice - hall, inc. (1973). */

    /* ----------------------------------------------------------------------- */
    /* .... call list variables */

    /* ---------------------------------------------------------------------- */

    /*  compute eps, the relative machine precision */

    eps = 1.f;
    L10: eps /= 2.f;
    tol1 = eps + 1.f;
    if (tol1 > 1.f) {
        goto L10;
    }

    /* initialization */

    a = *ax;
    b = *bx;
    fa = (*f)(&a);
    fb = (*f)(&b);

    /* begin step */

    L20: c__ = a;
    fc = fa;
    d__ = b - a;
    e = d__;
    L30: if (fabs(static_cast<double>(fc)) >= fabs(static_cast<double>(fb))) {
        goto L40;
    }
    a = b;
    b = c__;
    c__ = a;
    fa = fb;
    fb = fc;
    fc = fa;

    /* convergence test */

    L40: tol1 = eps * 2.f * fabs(static_cast<double>(b)) + *tol * .5f;
    xm = (c__ - b) * .5f;
    if (fabs(static_cast<double>(xm)) <= tol1) {
        goto L90;
    }
    if (fb == 0.f) {
        goto L90;
    }

    /* is bisection necessary? */

    if (fabs(static_cast<double>(e)) < tol1) {
        goto L70;
    }
    if (fabs(static_cast<double>(fa)) <= fabs(static_cast<double>(fb))) {
        goto L70;
    }

    /* is quadratic interpolation possible? */

    if (a != c__) {
        goto L50;
    }

    /* linear interpolation */

    s = fb / fa;
    p = xm * 2.f * s;
    q = 1.f - s;
    goto L60;

    /* inverse quadratic interpolation */

    L50: q = fa / fc;
    r__ = fb / fc;
    s = fb / fa;
    p = s * (xm * 2.f * q * (q - r__) - (b - a) * (r__ - 1.f));
    q = (q - 1.f) * (r__ - 1.f) * (s - 1.f);

    /* adjust signs */

    L60: if (p > 0.f) {
        q = -q;
    }
    p = fabs(static_cast<double>(p));

    /* is interpolation acceptable? */

    if (p * 2.f >= xm * 3.f * q - (d__1 = tol1 * q, fabs(static_cast<double>(d__1)))) {
        goto L70;
    }
    if (p >= (d__1 = e * .5f * q, fabs(static_cast<double>(d__1)))) {
        goto L70;
    }
    e = d__;
    d__ = p / q;
    goto L80;

    /* bisection */

    L70: d__ = xm;
    e = d__;

    /* complete step */

    L80: a = b;
    fa = fb;
    if (fabs(static_cast<double>(d__)) > tol1) {
        b += d__;
    }
    if (fabs(static_cast<double>(d__)) <= tol1) {
        b += d_sign(&tol1, &xm);
    }
    fb = (*f)(&b);
    if (fb * (fc / fabs(static_cast<double>(fc))) > 0.f) {
        goto L20;
    }
    goto L30;

    /* done */

    L90: ret_val = b;
    return ret_val;
} /* zeroin_ */

#include <functional>
#include <mutex>

#ifndef NO_HPX
#include <hpx/synchronization/spinlock.hpp>
using mutex_type = hpx::lcos::local::spinlock;
#else
#include <unordered_map>
#include <memory>
#include <cassert>
using mutex_type = std::mutex;
#endif

namespace sedov {

void solution(double time, double r, double rmax, double& d, double& v, double& p, int ndim) {
	int nstep = 10000;
	constexpr int bw = 2;
	using function_type = std::function<void(double,double&,double&,double&)>;
	using map_type = std::unordered_map<double,std::shared_ptr<function_type>>;

	static map_type map;
	static mutex_type mutex;

	sed_real rho0 = 1.0;
	sed_real vel0 = 0.0;
	sed_real ener0 = 0.0;
	sed_real pres0 = 0.0;
	sed_real cs0 = 0.0;
	sed_real gamma = 7.0/5.0;
	sed_real omega = 0.0;
	sed_real eblast = 1.0;
	sed_real xgeom = sed_real(ndim);

	std::vector<sed_real> xpos(nstep+2*bw);
	std::vector<sed_real> den(nstep+2*bw);
	std::vector<sed_real> ener(nstep+2*bw);
	std::vector<sed_real> pres(nstep+2*bw);
	std::vector<sed_real> vel(nstep+2*bw);
	std::vector<sed_real> cs(nstep+2*bw);

	std::vector<double> den1(nstep+2*bw);
	std::vector<double> pres1(nstep+2*bw);
	std::vector<double> vel1(nstep+2*bw);

	std::shared_ptr<function_type> ptr;

	for( int i = 0; i < nstep + 2*bw; i++) {
		xpos[i] = (i - bw + 0.5)*rmax/(nstep);
	}
	nstep += bw;

	std::unique_lock<mutex_type> lock(mutex);
	auto iter = map.find(time);
	if (iter == map.end()) {
		sed_real sed_time = time;
		printf( "Computing sedov solution\n");
		sed_1d__(&sed_time, &nstep, xpos.data() + bw, &eblast, &omega, &xgeom, &rho0,
				&vel0, &ener0, &pres0, &cs0, &gamma, den.data() + bw, ener.data() + bw,
				pres.data() + bw, vel.data() + bw, cs.data() + bw);

		xpos[0] = -xpos[3];
		den[0] = den[3];
		ener[0] = ener[3];
		pres[0] = pres[3];
		vel[0] = -vel[3];
		cs[0] = cs[3];

		xpos[1] = -xpos[2];
		den[1] = den[2];
		ener[1] = ener[2];
		pres[1] = pres[2];
		vel[1] = -vel[2];
		cs[1] = cs[2];

#if defined(OCTOTIGER_HAVE_BOOST_MULTIPRECISION)
		std::transform(den.begin(), den.end(), den1.begin(),
			[](sed_real v) { return v.convert_to<double>(); });
		std::transform(vel.begin(), vel.end(), vel1.begin(),
			[](sed_real v) { return v.convert_to<double>(); });
		std::transform(pres.begin(), pres.end(), pres1.begin(),
			[](sed_real v) { return v.convert_to<double>(); });
#else
		std::copy(den.begin(), den.end(), den1.begin());
		std::copy(vel.begin(), vel.end(), vel1.begin());
		std::copy(pres.begin(), pres.end(), pres1.begin());
#endif

		function_type func = [nstep,rmax,den1,pres1,vel1,bw](double r, double& d, double& v, double & p) {
			double dr = rmax / (nstep);
			std::array<int,4> i;
			i[1] = (r + (bw - 0.5)*dr) / dr;
			i[0] = i[1] - 1;
			i[2] = i[1] + 1;
			i[3] = i[1] + 2;
			double r0 = (r - (i[1]-bw + 0.5)*dr)/dr;
	//		printf( "%i %e\n", i[0], r, dr );
			assert( i[0] >= 0 );
			assert( i[3] < int(vel1.size()));
			const auto interp = [r0,i](const std::vector<double>& data) {
				double sum = 0.0;
				sum += (-0.5 * data[i[0]] + 1.5 * data[i[1]] - 1.5 * data[i[2]] + 0.5 * data[i[3]]) * r0 * r0 * r0;
				sum += (+1.0 * data[i[0]] - 2.5 * data[i[1]] + 2.0 * data[i[2]] - 0.5 * data[i[3]]) * r0 * r0;
				sum += (-0.5 * data[i[0]]                   +  0.5 * data[i[2]]) * r0;
				sum += data[i[1]];
				return sum;
			};

			d = interp(den1);
			v = interp(vel1);
			p = interp(pres1);

		};

		ptr = std::make_shared<function_type>(std::move(func));
		map[time] = ptr;
		lock.unlock();
	} else {
		lock.unlock();
		ptr = iter->second;
	}

	const auto& func = *(ptr);

	func(r, d, v, p);
}

}