#pragma once
#include <simcem/simcem.hpp>
#include <simcem/sundials.hpp>
#include <boost/math/tools/roots.hpp>
#include <boost/math/constants/constants.hpp>
#include <memory>
#include <numeric>

/*
  Notes:
  Theo's guide

  There are three PyKiln folders:
  - PyKiln - Output from each test
  - PyKiln2 - Output from each test
  - Optimize_Heat_Model - Theo's work on optimising the heat transfer coefficients

  You need to install assimulo to use it. Do this by downloading the
  source code, replacing all StrictVersion by LooseVersion in
  setup.py, then build/install.

  You also need to install PyChemEng. The folders in dropbox are not
  installable (old versions) It seems that PyChemEngLocal is the
  newest version, followed by PyChemEng-Marcus, then PyChemEng. You
  need to install from git@dynamomd.org:PyChemEng.

  You also need pyopt!

  Each PyKiln folder has the following structure:
  - Kiln_Classes.py - All pilot kiln trial data.
  - KilnSolverXXXX.py - The actual workhorse for fitting data, all copies of KilnSolverXXX.py
  - Kiln_Plotter.py - Plotting of data files from KilnSolver

  run PyKiln2/KilnSolverAssimulo.py, then run Kiln_Plotter.py
*/
namespace simcem {
  /*! \brief Heat transfer correlations and expressions. */
  namespace HT {
    /*! \brief Conductive heat transfer. */
    namespace conduction {
      /*! \brief Conduction resistance for a cylindrical shell of unit length.*/
      double R_cyl(const double Rout, const double Rin, const double k) {
	return std::log(Rout / Rin) / (2 * Database::pi * k);
      }

      /*! \brief The Maxwell model for combining thermal conductivities.

	This requires that one phase is identified as "continuous" and
	another phase is "discrete". Then the volume fraction of
	discontinuous phase is requred.
      */
      double maxwell(const double kcont, const double kdisc, const double discfrac) {
	return kcont * (2*kcont + kdisc + 2 * discfrac * (kdisc - kcont)) / (2 * kcont + kdisc + discfrac * (kcont - kdisc));
      }
    }

    /*! \brief Natural convection heat transfer. */
    namespace natConv {
      /*! \brief The overall convective heat transfer from smooth
	circular cylinders.

	Taken from Heat Transfer, J.P Holman pg 334, VT Morgan.

	A big collection of available expressions is in Boetcher_2014.
      */
      double Nu_horiz_cyl(const double Gr, const double Pr) {
	const double Ra = Gr * Pr;

	//A wide range expression (Eq.7-36 in Holman, Eq 5-35 in Perrys)
	//	if ((1e-5 <= Ra) && (Ra <= 1e12))
	//	  return std::pow(0.60 + 0.387*std::pow(Ra/std::pow(1+std::pow(0.559/Pr, 9.0/16), 16/9.0), 1.0/6), 2);

	//The table from Holman favouring the "preferred" expressions where possible
	if (Ra < 1e-10)
	  return 0.4;
	if (Ra < 1e-2)
	  return 0.675 * std::pow(Ra, 0.058);
	if (Ra < 1e2)
	  return 1.02 * std::pow(Ra, 0.148);
	if (Ra < 1e4)
	  return 0.850 * std::pow(Ra, 0.188);
	if (Ra < 1e7)
	  return 0.480 * std::pow(Ra, 1.0/4);
	if (Ra < 1e12)
	  return 0.125 * std::cbrt(Ra);

	stator_throw() << "Ra=" << Ra << " over upper range.";
      }
    }

    /*!\brief Radiative heat transfer. */
    namespace rad {

      /*! \brief Simple radiative heat transfer coefficient.

	The Steffan-Boltzmann coefficient
      */
      double h_rad_simple(const double T1, const double T2, const double emissivity,
			  const double steffanBoltzmannConstant = 5.670367e-8) {
	return emissivity *
	  steffanBoltzmannConstant
	  * (T1 * T1 + T2 * T2) * (T1 + T2);
      }

      /*! \brief Hottel calculation for the emissivity of gases
	containing CO2 and H2O.

	This is taken from Perry's Chemical Engineering Handbook,
	chapter 5. This assumes that the primary radiative
	properties of the gas are derived from the H2O and CO2
	components only. It also uses bilinear
	interpolation/extrapolation.

	Errors up to 20% are expected. The temperature argument is
	only provided for absorptivity calculations (which should be
	carried out via the Hottel_absorptivity function), and if it
	is left at the default of 0 it is taken from the gas phase
	automatically.
      */
      double Hottel_emissivity(const simcem::Model& gas, const double pathLength, double Tgas = 0) {
	if (Tgas == 0) Tgas = gas.T();
	//Fraction of H2O versus CO2+H2O
	const double pw_frac = gas["H2O"] / (gas["H2O"]+gas["CO2"]);

	//Partial pressure of H2O and CO2 in atms
	const double p = (gas["H2O"]+gas["CO2"]) / gas.N() * gas.p() / 1.01325e5;
	const double pL = p * pathLength;
	if (pL > 10)
	  stator_throw() << "pL out of range " << pL;

	if (pL < 0.005)
	  return 0; //This is an approximation!
	  //stator_throw() << "pL out of range " << pL;

	const double logpL = std::log10(pL);

	//Perform bilinear interpolation where x is T, and y is pw_frac.
	const double x_range[] = {1000, 1500, 2000};
	const double y_range[] = {0, 1.0/3.0, 1.0/2.0, 2.0/3.0, 3.0/4.0, 1.0};

	//Calculation of the indices of the data points are around the sample point
	size_t x_idx = 0;
	if (Tgas > x_range[1]) x_idx = 1;

	size_t y_idx = 0;
	if (pw_frac > y_range[1]) y_idx = 1;
	if (pw_frac > y_range[2]) y_idx = 2;
	if (pw_frac > y_range[3]) y_idx = 3;
	if (pw_frac > y_range[4]) y_idx = 4;

	static const double coeffs[] = { 2.2661, 0.1742, -0.0390,  0.0040,
					 2.3954, 0.2203, -0.0433,  0.00562,
					 2.4104, 0.2602, -0.0651, -0.00155,
					 2.5754, 0.2792, -0.0648,  0.0017,
					 2.6451, 0.3418, -0.0685, -0.0043,
					 2.6504, 0.4279, -0.0674, -0.0120,
					 2.6090, 0.2799, -0.0745, -0.0006,
					 2.6862, 0.3450, -0.0816, -0.0039,
					 2.7029, 0.4440, -0.0859, -0.0135,
					 2.6367, 0.2723, -0.0804,  0.0030,
					 2.7178, 0.3386, -0.0990, -0.0030,
					 2.7482, 0.4464, -0.1086, -0.0139,
					 2.6432, 0.2715, -0.0816,  0.0052,
					 2.7257, 0.3355, -0.0981,  0.0045,
					 2.7592, 0.4372, -0.1122, -0.0065,
					 2.5995, 0.3015, -0.0961,  0.0119,
					 2.7083, 0.3969, -0.1309,  0.00123,
					 2.7709, 0.5099, -0.1646, -0.0165};

	//A routine to calculate the offset for the coefficients
	auto C = [&](size_t x_idx, size_t y_idx) { return coeffs + 4 * (x_idx + 3 * y_idx); };

	//A calculation of the function at the index point
	auto f = [&](const double* C) { return C[0] + logpL * (C[1] + logpL * (C[2] + logpL * C[3])); };

	const double x = (Tgas - x_range[x_idx]) / (x_range[x_idx+1] - x_range[x_idx]);
	const double y = (pw_frac   - y_range[y_idx]) / (y_range[y_idx+1] - y_range[y_idx]);

	double log10eT =
	  f(C(x_idx, y_idx)) * (1 - x) * (1 - y)
	  + f(C(x_idx + 1, y_idx)) * x * (1 - y)
	  + f(C(x_idx, y_idx + 1)) * (1 - x) * y
	  + f(C(x_idx + 1, y_idx + 1)) * x * y;

	return std::pow(10, log10eT) / Tgas;
      }

      /* \brief Hottel calculation for the absorptivity of gases
	 containing CO2 and H2O.

	 This assumes ideal gas behaviour!
      */

      double Hottel_absorptivity(simcem::Model& gas, double pathLength, double Tsurface) {
	//Adjust pathlength, such that pL is scaled (this is a
	//horrible hack to adjust the pressure in the emissivity
	//calculations according to the ideal gas law!)
	const double L = pathLength * Tsurface / gas.T();
	const double e = Hottel_emissivity(gas, L, Tsurface);
	return e * std::sqrt(gas.T() / Tsurface);
      }

      /*! \brief Two-body radiation problem in third region.

	\image html twobodygas_radiation_network.svg

	We assume all resistances and the voltages \f$V_a\f$, \f$V_b\f$, and \f$V_c\f$ are known.
	The total currents at the nodes \f$\alpha\f$ and \f$\beta\f$ must sum to zero, thus:

	\f[
	(V_a - V_\alpha)R^{-1}_1 + (V_\beta - V_\alpha)R^{-1}_2 + (V_c - V_\alpha)R^{-1}_3 = 0
	\f]
	\f[
	(V_b - V_\beta)R^{-1}_4 + (V_\alpha - V_\beta)R^{-1}_2 + (V_c - V_\beta)R^{-1}_5 = 0
	\f]

	Solving for \f$V_\beta\f$:

	\f[
	V_\beta = \frac{m_2\,c_2+c_1}{1-m_1\,m_2}
	\f]
	\f[
	V_\alpha = m_2\, V_\beta + c_2
	\f]

	where:
	\f[
	m_1 = \frac{1}{R_2^{-1}}\left(R_1^{-1} + R_2^{-1} + R_3^{-1}\right)
	\f]
	\f[
	c_1 = -V_a\frac{R_1^{-1}}{R_2^{-1}} -V_c\frac{R_3^{-1}}{R_2^{-1}}
	\f]

	\f[
	m_2 = \frac{1}{R_2^{-1}}\left(R_4^{-1} + R_2^{-1} + R_5^{-1}\right)
	\f]
	\f[
	c_2 = -V_b\frac{R_4^{-1}}{R_2^{-1}} -V_c\frac{R_5^{-1}}{R_2^{-1}}
	\f]

	The currents are returned as follows:
	\f[
	I_a = R_1^{-1}(V_\alpha-V_a) \qquad I_b = R_4^{-1}(V_\beta-V_b) \qquad I_c = R_3^{-1}(V_\alpha-V_c) + R_5^{-1}(V_\beta-V_c)
	\f]

	As a test, we should have \f$I_a+I_b+I_c=0\f$.
      */
      std::array<double, 3> twobodygas_network(const std::array<double, 3> V, const std::array<double, 5> Rinv) {
	const double m1 = (Rinv[0]+Rinv[1]+Rinv[2]) / Rinv[1];
	const double m2 = (Rinv[3]+Rinv[1]+Rinv[4]) / Rinv[1];
	const double c1 = -V[0]*Rinv[0]/Rinv[1] - V[2] * Rinv[2] / Rinv[1];
	const double c2 = -V[1]*Rinv[3]/Rinv[1] - V[2] * Rinv[4] / Rinv[1];
	const double Vbeta = (m1 * c2 + c1) / (1 - m1 * m2);
	const double Valpha = m2 * Vbeta + c2;

	return std::array<double, 3>{
	  {Rinv[0] * (Valpha - V[0]),
	      Rinv[3] * (Vbeta - V[1]),
	      Rinv[2] * (Valpha - V[2]) + Rinv[4] * (Vbeta - V[2])
	      }
	};
      }
    }

    /*! \brief Specialised expressions for heat transfer in kilns. */
    namespace kiln {
      /*! \brief Nusselt number correlation for heat transfer between
	a cylindrical wall and a granular bed of material which
	covers it.

	This is taken from Li_etal_2005. The Nusselt number uses the
	gas thermal conductivity and particle diameter as
	normalisation factors.

	\param Peclet The Peclet number for the bed (see Pe_bed_wall_Li_etal_2005).
	\param chi A fitting parameter, which appears to be around 0.1, but is in the range 0.096-0.198.
      */
      double Nu_bed_wall_Li_etal_2005(const double Peclet, const double chi = 0.1) {
	return 1 / (chi + 0.5 * std::sqrt(Database::pi / Peclet));
      }

      /*! \brief Peclet number for Heat transfer between a cylindrical
	wall and a granular bed of material which covers it.

	This is taken from Li_etal_2005.

	\param dp Particle diameter \f$d_p\f$.
	\param kg Gas thermal conductivity \f$k_g\f$.
	\param kb Solid bed thermal conductivity \f$k_b\f$.
	\param vol_Cp Solid bed volumetric heat capacity, \f$\rho_b\,c_{p,b}\f$.
	\param centralangle Central angle of bed, \f$\theta\f$.
	\param angularvel Angular velocity of cylinder, \f$\omega\f$.
      */
      double Pe_bed_wall_Li_etal_2005(const double dp, const double kg, const double kb,
				      const double vol_Cp, const double centralangle, const double angularvel ) {
	return std::pow(dp/kg, 2) * vol_Cp * kb * angularvel / centralangle;
      }

      /*! \brief Heat transfer coefficient between a wall and a
	granular bed of material which covers it.

	This is taken from Li_etal_2005.

	\param dp Particle diameter \f$d_p\f$.
	\param kg Gas thermal conductivity \f$k_g\f$.
	\param kb Solid bed thermal conductivity \f$k_b\f$.
	\param vol_Cp Solid bed volumetric heat capacity, \f$\rho_b\,c_{p,b}\f$.
	\param centralangle Central angle of bed, \f$\theta\f$.
	\param angularvel Angular velocity of cylinder, \f$\omega\f$.
	\param chi A fitting parameter, which appears to be around 0.1, but is in the range 0.096-0.198.
      */
      double h_bed_wall_Li_etal_2005(const double dp, const double kg, const double kb,
				     const double vol_Cp, const double centralangle, const double angularvel,
				     const double chi = 0.1) {
	const double Pe = Pe_bed_wall_Li_etal_2005(dp, kg, kb, vol_Cp, centralangle, angularvel);
	const double Nu = Nu_bed_wall_Li_etal_2005(Pe, chi);
	return Nu * kg / dp;
      }

      /*! \brief Gas-wall Nusselt number for rotary kilns.

	\f[
	\text{Nu}_{g-ew} = 1.54\,\text{Re}_g^{0.575}\,\text{Re}_\omega^{-0.292}
	\f]

	This is taken from Eq.9 of Li_etal_2005. This requires
	\f$1600<\text{Re}_g<7800\f$ and \f$20<\text{Re}_\omega<800\f$.

	\param Re_g Reynolds number for the gas, \f$\text{Re}_g\f$, using the normal hydraulic diameter.
	\param Re_w Angular rotation Reynolds number, \f$\text{Re}_\omega\f$.
      */
      double Nu_gas_wall_Li_etal_2005(const double Re_g, const double Re_w) {
#ifndef NDEBUG
	//if (!((1600 < Re_g) && (Re_g < 7800)))
	//stator_throw() << "Out of range for Re_g " << Re_g;

	//if (!((20 < Re_w) && (Re_w < 800)))
	//stator_throw() << "Out of range for Re_w " << Re_w;
#endif

	return 1.54 * std::pow(Re_g, 0.575) * std::pow(Re_w, -0.292);
      }

      /*! \brief Gas-bed Nusselt number for rotary kilns.

	\f[
	\text{Nu}_{g-eb} = 0.46\,\text{Re}_g^{0.535}\,\text{Re}_\omega^{0.104}\,\eta^{-0.341}
	\f]

	This is taken from Eq.8 of Li_etal_2005. This requires
	\f$1600<\text{Re}_g<7800\f$ and \f$20<\text{Re}_\omega<800\f$.

	\param Re_g Reynolds number for the gas, \f$\text{Re}_g\f$, using the normal hydraulic diameter.
	\param Re_w Angular rotation Reynolds number, \f$\text{Re}_\omega\f$.
	\param bed_frac Fraction of kiln filled with the bed, \f$\eta\f$.
      */
      double Nu_gas_bed_Li_etal_2005(const double Re_g, const double Re_w, const double bed_frac) {
#ifndef NDEBUG
//	if (!((1600 < Re_g) && (Re_g < 7800)))
//	  stator_throw() << "Out of range for Re_g " << Re_g;
//
//	if (!((20 < Re_w) && (Re_w < 800)))
//	  stator_throw() << "Out of range for Re_w " << Re_w;
#endif
	return 0.46 * std::pow(Re_g, 0.535) * std::pow(Re_w, 0.104) * std::pow(bed_frac, -0.341);
      }

      /*! \brief Correlation for mean beam length in a rotary kiln.

	This is taken from Eq.50 of Gorog_etal_1981.

	\param R Kiln radius.
	\param h Bed height (at its deepest/central point);
      */
      double pathLength(const double R, const double h) {
	return 2 * R * 0.95 * (1 - h / (2 * R));
      }
    }
  }

  namespace kiln {
    struct Slice {
      Slice(simcem::shared_ptr<simcem::ModelIdealGasTp> gas,
	    simcem::shared_ptr<simcem::ModelIncompressible> solid,
	    double Z = 0
	    ):
	_Z(Z)
      {
	//Here we copy the phases to ensure the slice has a unique copy
	_gas = simcem::shared_ptr<simcem::ModelIdealGasTp>(new simcem::ModelIdealGasTp(*gas));
	_solid = simcem::shared_ptr<simcem::ModelIncompressible>(new simcem::ModelIncompressible(*solid));

	_T_ext_shell = 0.75*298.15 + 0.25 * _gas->T();
	_T_wall = 0.25*298.15 + 0.75 * _gas->T();
      }

      Slice(const Slice& s):
	_gas_k(s._gas_k),
	_gas_visc(s._gas_visc),
	_gas_density(s._gas_density),
	_gas_vel(s._gas_vel),
	_solid_k(s._solid_k),
	_solid_Cp(s._solid_Cp),
	_T_ext_shell(s._T_ext_shell),
	_T_wall(s._T_wall),
	_Z(s._Z)
      {
	_gas = simcem::shared_ptr<simcem::ModelIdealGasTp>(new simcem::ModelIdealGasTp(*s._gas));
	_solid = simcem::shared_ptr<simcem::ModelIncompressible>(new simcem::ModelIncompressible(*s._solid));
      }

      Slice(const simcem::Components solid,
	    const double volAirFlow,
	    const double volGasFlow,
	    const double volO2Flow,
	    const double volSO2Flow,
	    const double Tsolid,
	    const double Tgas,
	    const double Z0,
	    const simcem::shared_ptr<simcem::Database> db):
	_Z(Z0)
      {
	const simcem::ModelIdealGasTp air(db, Database::getDryAir(), 298.15, 1.01325e5);
	const simcem::Components inletair = Database::getDryAir() * ((volAirFlow) / air.V());

	const simcem::ModelIdealGasTp oxy(db, Components({{"O2",1}}), 298.15, 1.01325e5);
	const simcem::Components inletoxy = Components({{"O2",1}}) * ((volO2Flow) / oxy.V());

	const simcem::ModelIdealGasTp so2(db, Components({{"SO2",1}}), 298.15, 1.01325e5);
	const simcem::Components inletso2 = Components({{"SO2",1}}) * ((volSO2Flow) / oxy.V());

	const simcem::ModelIdealGasTp fuel(db, {{"CH4",100.0}}, 298.15, 1.01325e5);
	const simcem::Components inletfuel = Components(fuel) * (volGasFlow / fuel.V());

	const simcem::Components combustion_outputs({{"H2O",0}, {"CO2", 0}});

	_gas = simcem::shared_ptr<simcem::ModelIdealGasTp>(new simcem::ModelIdealGasTp(db, inletfuel + inletair + inletoxy + inletso2 + combustion_outputs, 298.15, 1.01325e5));
	simcem::System sys(simcem::Objective_t::p, simcem::Objective_t::H, true);
	sys.push_back(_gas);

	try {
	  sys.equilibrate();
	} catch (std::exception& e) {
	  std::cout << "Failed to equilibrate initial gas " << _gas->str() << std::endl;
	}

	_solid = simcem::shared_ptr<simcem::ModelIncompressible>(new simcem::ModelIncompressible(db, solid, 298.15, 1.01325e5, "solid"));

	//Value of Tgas=0 means retain the adiabatic temperature
	if (Tgas != 0)
	  _gas->set(simcem::Objective_t::T, Tgas, simcem::Objective_t::p);

	_solid->set(simcem::Objective_t::T, Tsolid, simcem::Objective_t::p);

	_T_ext_shell = 0.75*298.15 + 0.25 * _gas->T();
	_T_wall = 0.25*298.15 + 0.75 * _gas->T();
      }

      simcem::shared_ptr<simcem::ModelIdealGasTp> _gas;
      simcem::shared_ptr<simcem::ModelIncompressible> _solid;

      /*! \brief Precompute a few frequently used values for the slice. */
      void compute_properties(const double gas_area, const double solid_k) {
	//Gas properties
	std::tie(_gas_visc, _gas_k) = simcem::trans::NASA_transport(_gas);
	_gas_density = _gas->M() / _gas->V();

	//Solid properties
	_solid_Cp = _solid->Cp() / _solid->M();
	_gas_vel = _gas->V() / gas_area;
	_solid_k = solid_k;
      }

      //Cached values of the computed properties of the phases
      double _gas_k;
      double _gas_visc;
      double _gas_density;
      double _gas_vel;
      double _solid_k;
      double _solid_Cp;
      double _T_ext_shell;
      double _T_wall;
      double _Z;
    };

    /*! \brief Abstract base class for models describing the solid bed dynamics.*/
    class BedModel {
    public:
      virtual double bedHeight(const Slice& slice) const = 0;
    };

    class ConstantHeightBed : public BedModel {
    public:
      ConstantHeightBed(double height):
	_height(height)
      {}

      virtual double bedHeight(const Slice& slice) const { return _height; }

      static simcem::shared_ptr<BedModel> fromFillingFraction(double frac, double kiln_radius) {
	const double pi = boost::math::constants::pi<double>();

	std::pair<double, double> central_angle_limits
	  = boost::math::tools::bisect([pi, frac](double x) {return x - std::sin(x) - 2 * pi * frac;},
				       0.0, 2 * pi,
				       boost::math::tools::eps_tolerance<double>(10));

	double central_angle = (central_angle_limits.first + central_angle_limits.second) / 2;

	return simcem::shared_ptr<BedModel>
	  (new ConstantHeightBed(kiln_radius * (1.0 - std::cos(central_angle/2))));
      }

    protected:
      double _height;
    };

    /*! \brief A process model for a rotary kiln.

      \image html kiln_balance.svg

      Performing a differential balance of mass over a phase \f$\alpha\f$:
      \f[
      \Delta z\,A_\alpha\left(\rho_\alpha(z,\,t+\Delta t)-\rho_\alpha(z,\,t)\right)=  \Delta t\,A_\alpha\left(\dot{m}_\alpha\left(z\right)-\dot{m}_\alpha\left(z+\Delta z\right)\right) + \Delta t\,\Delta z\,\dot{m}_{\beta\to\alpha}
      \f]

      where \f$\dot{m}_{\beta\to\alpha}\f$ is the rate of mass
      transfer between the two phases per unit length of kiln and
      conservation of mass requires
      \f$\dot{m}_{\beta\to\alpha}=-\dot{m}_{\alpha\to\beta}\f$. Dividing
      by \f$\Delta t\,\Delta z\f$ and taking the limit as they go to
      zero:

      \f[
      \frac{{\rm d}\,A_\alpha\,\rho_\alpha}{{\rm d}t}= -\frac{{\rm d} A_\alpha\,\dot{m}_\alpha}{{\rm d}z} + \dot{m}_{\beta\to\alpha}
      \f]

      For enthalpy:
      \f[
      \Delta z\,A_\alpha\left(\rho_\alpha(z,\,t+\Delta t)\,h_\alpha(z,\,t+\Delta t)-\rho_\alpha(z,\,t)\,h_\alpha(z,\,t)\right)=  \Delta t\,A_\alpha\left(\dot{m}_\alpha\left(z\right)\,h_\alpha(z,\,t)-\dot{m}_\alpha\left(z+\Delta z\right)\,h_\alpha(z+\Delta z,\,t)\right) + \Delta t\,\Delta z\,Q_{\to\alpha}
      \f]
      where \f$Q_{\to\alpha}\f$ is the heat transfer rate to phase \f$\alpha\f$ per unit length of kiln. Again, a differential form results:
      \f[
      \frac{{\rm d}\,A_\alpha\,\rho_\alpha\,h_\alpha}{{\rm d}t}= -\frac{{\rm d}\,A_\alpha\,\dot{m}_\alpha\,h_\alpha}{{\rm d}z} + Q_{\to\alpha}

      \f]
    */
    class Kiln {
    public:
      struct Layer {
	std::string _material;
	double _thickness;
	sym::Expr _k;
      };

      Kiln(double RPM, double innerRadius, double length,
	   double particle_diam,
	   double shell_emissivity,
	   double bed_emissivity,
	   double wall_emissivity,
	   double solid_density,
	   double bed_void_frac,
	   sym::Expr solid_k,
	   simcem::shared_ptr<Database> db
	   ):
	_RPM(RPM), _innerRadius(innerRadius), _length(length),
	_particle_diam(particle_diam),
	_shell_emissivity(shell_emissivity),
	_bed_emissivity(bed_emissivity),
	_wall_emissivity(wall_emissivity),
	_solid_density(solid_density),
	_bed_void_frac(bed_void_frac),
	_solid_k(solid_k),
	_ambient(new simcem::ModelIdealGasTp(db, Database::getDryAir(), 298.15, 1.01325e5))
      {}

      void setFixedHeightBedModel(const double solidLoading) {
	_bedModel = simcem::kiln::ConstantHeightBed::fromFillingFraction(solidLoading, _innerRadius);
      }

      void add_layer(std::string material, double thickness, sym::Expr k) {
	_layers.push_back(Layer{material, thickness, k});
      }

      double solid_k(const double T) {
	return sym::fast_sub(_solid_k, sym::Var<sym::vidx<'T'>>() = T);
      }

      double innerRadius() const { return _innerRadius; }

      double outerRadius() const {
	return _innerRadius + std::accumulate(_layers.begin(), _layers.end(), 0.0, [](double a, Layer b){ return a + b._thickness; });
      }

      /*! \brief Conductive heat transfer resistance in the kiln wall. */
      double R_wall_shell(const Slice& slice) const {
	double Ri = _innerRadius;
	double Rtotal = 0;
	for (const auto& layer : _layers) {
	  const double Ro = Ri + layer._thickness;
	  const double k = sym::fast_sub(layer._k, sym::Var<sym::vidx<'T'>>() = slice._T_wall);
	  Rtotal += std::log(Ro / Ri) / (2 * simcem::Database::pi * k);
	  Ri = Ro;
	};

	return Rtotal;
      }

      const BedModel& bedModel() const { return *_bedModel; }

      const std::vector<Slice>& slices() const { return _slices; }
      std::vector<Slice>& slices() { return _slices; }

      /*! \brief The chord length of the bed.

	The chord length can be calculated in two ways:
	\f[
	L_{chord}= 2\,R\,\sin(\theta/2) = 2\sqrt{h(2*R-h)}
	\f]

	The latter is used here.
      */
      double chordLength(const Slice& slice) const {
	const double h = _bedModel->bedHeight(slice);
	return 2 * std::sqrt(h * (2 * _innerRadius - h));
      }

      /*! \brief The central angle is the radians of the cylinder
	covered by the bed.
      */
      double centralAngle(const Slice& slice) const {
	return 2 * std::asin(chordLength(slice) / 2 / _innerRadius);
      }

      double bedArea(const Slice& slice) const {
	const double cAngle = centralAngle(slice);
	return 0.5 * _innerRadius * _innerRadius * (cAngle - std::sin(cAngle));
      }

      double kilnArea() const {
	return simcem::Database::pi * _innerRadius * _innerRadius;
      }

      double gasArea(const Slice& slice) const {
	return kilnArea() - bedArea(slice);
      }

      double gasPerimeter(const Slice& slice) const {
	return (2 * simcem::Database::pi - centralAngle(slice)) * _innerRadius + chordLength(slice);
      }

      double hydraulicDiameter(const Slice& slice) const {
	return 4 * gasArea(slice) / gasPerimeter(slice);
      }

      double exposedWallPerimeter(const Slice& slice) const {
	const double pi = boost::math::constants::pi<double>();
	const double cAngle = centralAngle(slice);
	return (2 * pi - cAngle) * _innerRadius;
      }

      double coveredWallPerimeter(const Slice& slice) const {
	const double cAngle = centralAngle(slice);
	return cAngle * _innerRadius;
      }

      double angular_vel() const {
	return _RPM / 60.0 * 2 * simcem::Database::pi;
      }

      /*! \brief Heat transfer coefficient between the bed and the wall it covers, \f$h_{cw-s}\f$.
       */
      double h_bed_wall(const Slice& slice) const {
	const double Cp_per_vol = slice._solid_Cp * _solid_density;
	const double kb = HT::conduction::maxwell(slice._gas_k, slice._solid_k, _bed_void_frac);
	return simcem::HT::kiln::h_bed_wall_Li_etal_2005(_particle_diam, slice._gas_k, kb,
							 Cp_per_vol, centralAngle(slice), angular_vel());
      }

      /*! \brief \f$Q_{w\to s}^{cd}\f$ */
      double Q_wall_bed_cd(const Slice& slice) const {
	return h_bed_wall(slice) * coveredWallPerimeter(slice) * (slice._T_wall - slice._solid->T());
      }

      /*! \brief Free gas Reynolds number, \f$\text{Re}_g\f$.

	This is the Reynolds number for the gas above the solid bed.

	\f[
	\text{Re}_g = \frac{\rho_g\,v_g\,D_e}{\mu_g}
	\f]

	where \f$D_e\f$ is the hydraulic diameter.
      */
      double Re_g(const Slice& slice) const {
	const double De = hydraulicDiameter(slice);
	return slice._gas_density * slice._gas_vel * De / slice._gas_visc;
      }

      /*! \brief Rotational gas Reynolds number, \f$\text{Re}_\omega\f$.

	This is the Reynolds number for the gas within the bed itself.

	\f[
	\text{Re}_\omega = \frac{\rho_g\,\omega\,D_e^2}{\mu_g}
	\f]

	where \f$D_e\f$ is the hydraulic diameter. An example of this
	definition is given just below Eq.9 in Li_etal_2005.
      */
      double Re_w(const Slice& slice) const {
	const double De = hydraulicDiameter(slice);
	return std::pow(De, 2) * angular_vel() * slice._gas_density / slice._gas_visc;
      }

      /*! \brief Heat transfer coefficient between the bed and the gas.
       */
      double h_gas_bed(const Slice& slice) const {
	const double bed_frac = bedArea(slice) / kilnArea();
	const double Nu = simcem::HT::kiln::Nu_gas_bed_Li_etal_2005(Re_g(slice), Re_w(slice), bed_frac);
	const double De = hydraulicDiameter(slice);
	return Nu * slice._gas_k / De;
      }

      /*! \brief \f$Q_{g\to s}^{cv}\f$. */
      double Q_gas_bed_cv(const Slice& slice) const {
	return h_gas_bed(slice) * chordLength(slice) * (slice._gas->T() - slice._solid->T());
      }

      /*! \brief Heat transfer coefficient between the bed and the gas.
       */
      double h_gas_wall(const Slice& slice) const {
	const double Nu = simcem::HT::kiln::Nu_gas_wall_Li_etal_2005(Re_g(slice), Re_w(slice));
	const double De = hydraulicDiameter(slice);
	return Nu * slice._gas_k / De;
      }

      /*! \brief \f$Q_{g\to s}^{cv}\f$. */
      double Q_gas_wall_cv(const Slice& slice) const {
	return h_gas_wall(slice) * exposedWallPerimeter(slice) * (slice._gas->T() - slice._T_wall);
      }

      double h_ext_amb_conv(const Slice& slice) const {
	double amb_visc, amb_k;
	std::tie(amb_visc, amb_k) = simcem::trans::NASA_transport(_ambient);
	const double amb_dens = _ambient->M() / _ambient->V();

	const double outerDiam = 2 * outerRadius();
	const double Pr = (_ambient->Cp() / _ambient->M()) * amb_visc / amb_k;
	const double Tf = (_ambient->T() + slice._T_ext_shell) / 2.0;
	const double Gr = simcem::Database::g * std::pow(amb_dens, 2) * (1/Tf) * std::abs(slice._T_ext_shell - _ambient->T()) * std::pow(outerDiam, 3) / std::pow(amb_visc, 2);

	const double Nu = simcem::HT::natConv::Nu_horiz_cyl(Gr, Pr);

	return Nu * amb_k / outerDiam;
      }

      /*! \brief \f$Q_{g\to s}^{cv}\f$. */
      double R_sh_ext_cv(const Slice& slice) const {
	return 1.0 / (h_ext_amb_conv(slice) * outerRadius() * 2 * simcem::Database::pi);
      }

      /*! \brief External radiative heat transfer coefficient. */
      double h_ext_amb_rad(const Slice& slice) const {
	return simcem::HT::rad::h_rad_simple(_ambient->T(), slice._T_ext_shell,
					     _shell_emissivity,
					     _ambient->db()->steffanBoltzmannConstant);
      }

      double R_sh_ext_rd(const Slice& slice) const {
	return 1.0 / (h_ext_amb_rad(slice) * outerRadius() * 2 * simcem::Database::pi);
      }

      double Q_w_ext(const Slice& slice) const {
	const double Rtotal = R_wall_shell(slice) + 1.0 / (1.0 / R_sh_ext_rd(slice) + 1.0 / R_sh_ext_cv(slice));

	return (slice._T_wall - _ambient->T()) / Rtotal;
      }

      double Q_w_sh(const Slice& slice) const {
	return (slice._T_wall - slice._T_ext_shell) / R_wall_shell(slice);
      }

      /*! \brief One-zone radiative network for a kiln slice.

	The wall and bed are considered as two thermally homogeneous
	gray bodies which exchange radiative heat through a gray gas
	(gray implying that we are ignoring the frequency aspects of
	radiation).

	The same network as Fig.6 in Li_etal_2005, but this is also
	Fig.8-39 in JP Holman's Heat Transfer, 10th Ed.

	\image html kiln_radiation_network.svg

	The electrical resistance analogy is then used. The junctions
	(circles) are annotated with their "potentials"
	(\f$E_{bed},\,J_b,\,J_w,\,E_{gas}\f$) whereas the other
	annotations are resistances.  As the two nodes \f$J_b\f$ and
	\f$J_w\f$ are merely "virtual" junctions they cannot
	accumulate heat/current, so their heat/current must sum to
	zero:

	\f[
	\frac{E_{bed}-J_b}{\left(1-\varepsilon_b\right)\varepsilon_b^{-1}\,A_b^{-1}} + \frac{J_w-J_b}{\tau_g^{-1}\,F_{bw}^{-1}\,A_b^{-1}} + \frac{E_{gas}-J_b}{\varepsilon_g^{-1}\,F_{bg}^{-1}\,A_b^{-1}}=0
	\f]\f[
	\frac{E_{wall}-J_w}{\left(1-\varepsilon_w\right)\varepsilon_w^{-1}\,A_w^{-1}} + \frac{J_b-J_w}{\tau_g^{-1}\,F_{bw}^{-1}\,A_b^{-1}} + \frac{E_{gas}-J_w}{\varepsilon_g^{-1}\,F_{wg}^{-1}\,A_w^{-1}}=0
	\f]

	The emission fluxes (potentials) are already known:
	\f[
	E_{bed} = \sigma\,T^4_{bed} \qquad E_{gas} = \sigma\,T^4_{gas} \qquad E_{wall} = \sigma\,T^4_{wall}
	\f]

	Thus the task is to determine the radiosities \f$J_{bed}\f$
	and \f$J_{wall}\f$ by solving the two simultaneous equations
	above. Once this is complete the heat fluxes/currents from the wall and
	bed are obtainable from the network's potentials and
	resistances:

	\f[
	q_{bed} = \frac{E_{bed}-J_b}{\left(1-\varepsilon_b\right)^{-1}\varepsilon_b\,A_b}
	\qquad
	q_{wall} = \frac{E_{wall}-J_w}{\left(1-\varepsilon_w\right)^{-1}\varepsilon_w\,A_w}
	\f]
	\f[
	q_{gas} = \frac{J_b-E_{gas}}{\varepsilon_g^{-1}\,F_{bg}^{-1}\,A_b^{-1}} + \frac{J_w-E_{gas}}{\varepsilon_g^{-1}\,F_{wg}^{-1}\,A_w^{-1}} = -q_{bed} -q_{wall}
	\f]
	Although the reciprocity relationship
	(\f$F_{12}A_{1}=F_{21}A_{2}\f$) and summation rule (\f$\sum_b
	F_{ab}=1\f$) allow us to change the view factors, the
	selection above has been made as \f$F_{bw}=F_{bg}=F_{wg}=1\f$
	thus these can be eliminated.  For simplicity it is assumed
	that spectral effects on the gas absorptivity (resulting from
	the different temperatures of the wall and gas) can be ignored
	and \f$\tau_g=1-\varepsilon_g\f$. The areas are given in terms
	of per unit length of kiln, thus
	\f$A_w=R\left(2\,\pi-\theta\right)\f$ and \f$A_b\f$ is the
	chord length (Li_etal_2005 incorrectly uses the covered
	perimeter of the inner kiln surface). All the resistance terms
	can then be determined a priori.

      */
      std::array<double, 3> rad_fluxes(const Slice& slice) const {
	const double Aw = exposedWallPerimeter(slice);
	const double Abed = chordLength(slice);
	const double h = _bedModel->bedHeight(slice);
	const double pathLength = simcem::HT::kiln::pathLength(_innerRadius, h);
	const Model& gas = *slice._gas;
	const Model& solid = *slice._solid;
	const double e_gas = simcem::HT::rad::Hottel_emissivity(gas, pathLength);
	const double sigma = gas.db()->steffanBoltzmannConstant;

	const std::array<double, 3> V = {{
	    sigma * std::pow(solid.T(), 4),
	    sigma * std::pow(slice._T_wall, 4),
	    sigma * std::pow(gas.T(), 4)
	  }};

	const std::array<double, 5> Rinv = {{
	    _bed_emissivity * Abed / (1 - _bed_emissivity),
	    (1 - e_gas) * Abed,
	    e_gas * Abed,
	    _wall_emissivity * Aw / (1 - _wall_emissivity),
	    e_gas * Aw
	  }};

	return simcem::HT::rad::twobodygas_network(V, Rinv);
      }

      void printFluxes(const Slice& slice) const {
	auto rad = rad_fluxes(slice);
	const double Q_s_rad = rad[0], Q_w_rad = rad[1], Q_g_rad = rad[2];

	std::cout << "Z=" << slice._Z << ", Tg=" << slice._gas->T() << ", Ts=" << slice._solid->T() << ", Tw=" << slice._T_wall << ", Tshell=" << slice._T_ext_shell << std::endl;
	std::cout << "Q_s_rad=" << Q_s_rad << ", Q_w_rad="<<Q_w_rad<<", Q_g_rad="<<Q_g_rad<< std::endl;
	std::cout << "Q_gs_cv=" << Q_gas_bed_cv(slice) << std::endl;
	std::cout << "Q_ws_cd=" << Q_wall_bed_cd(slice) << std::endl;
	std::cout << "Q_gw_cv=" << Q_gas_wall_cv(slice) << std::endl;
	std::cout << "Q_w_sh=" << Q_w_sh(slice) << std::endl;
	std::cout << "Q_w_ext=" << Q_w_ext(slice) << std::endl;
      }

      /*! \brief Solves the system as a steady-state inert system.

	The differential equations to be solved are:

	\f[
	\dot{m}_s\,c_{p,s} \frac{\partial T_s}{\partial z} = Q_{g\to s}^{cv}+Q_{w\to s}^{cd}+Q_{g\to s}^{rd}+Q_{w\to s}^{rd}
	\f]
	\f[
	\dot{m}_g\,c_{p,g} \frac{\partial T_g}{\partial z} = -\left(Q_{g\to s}^{cv}+Q_{g\to w}^{cv}+Q_{g\to s}^{rd}+Q_{g\to w}^{rd}\right)
	\f]

	These equations require a solution for the wall \f$T_w\f$ and
	shell \f$T_{shell}\f$ temperatures, which are solved from the
	following algebraic equations:

	\f[
	Q_{sh\to ext} = Q_{w\to sh}
	\f]
	\f[
	Q_{w\to sh} = Q_{g\to w}^{rd} + Q_{g\to w}^{cv} - Q_{w\to s}^{rd} - Q_{w\to s}^{cd}
	\f]
      */
      void solve_SS_inert(Slice slice_in, std::vector<realtype> stop_points, bool store_intermediate) {
	using namespace sundials;

	//For debugging, output all kiln settings
	//std::cout << "Kiln setup" << std::endl;
	//std::cout << _RPM << " "
	//	  << _innerRadius << " "
	//	  << _length << " "
	//	  << _particle_diam << " "
	//	  << _shell_emissivity << " "
	//	  << _bed_emissivity << " "
	//	  << _wall_emissivity << " "
	//	  << _solid_density << " "
	//	  << _solid_k << " "
	//	  << _bedModel << " "
	//	  << _ambient << " "
	//	  << std::endl;
	//
	//for (const auto& layer : _layers)
	//  std::cout << "Layer: " << layer._thickness << " " << layer._k << std::endl;
	//
	// TODO: add slice data output

	struct SolverData {
	  Kiln& kiln;
	  Slice slice;

	  void save_state(Serial_Vector& T) const {
	    T[0] = slice._gas->T();
	    T[1] = slice._solid->T();
	    T[2] = slice._T_wall;
	    T[3] = slice._T_ext_shell;
	  }

	  void load_state(Serial_Vector& T, const double Z) {
	    slice._gas->set  (simcem::Objective_t::T, T[0], simcem::Objective_t::p);
	    slice._solid->set(simcem::Objective_t::T, T[1], simcem::Objective_t::p);
	    slice._T_wall = T[2];
	    slice._T_ext_shell = T[3];
	    slice.compute_properties(kiln.gasArea(slice), kiln.solid_k(T[1]));
	    slice._Z = Z;
	  }

	  static int resrob(realtype Z, N_Vector T_, N_Vector Tprime_, N_Vector residuals_, void *user_data) {
	    Serial_Vector T(T_), Tprime(Tprime_), residuals(residuals_);
	    SolverData& data = *((SolverData*)user_data);

	    //Load the slice state
	    data.load_state(T, Z);

	    auto rad = data.kiln.rad_fluxes(data.slice);
	    const double Q_s_rad = rad[0], Q_w_rad = rad[1], Q_g_rad = rad[2];

	    //The solid energy balance
	    //-ve as we're starting at the hot end
	    //The gas energy balance
	    residuals[0] = -(-data.kiln.Q_gas_bed_cv(data.slice) - data.kiln.Q_gas_wall_cv(data.slice) + Q_g_rad) - data.slice._gas->Cp() * Tprime[0];

	    residuals[1] = (data.kiln.Q_gas_bed_cv(data.slice) + data.kiln.Q_wall_bed_cd(data.slice) + Q_s_rad) - data.slice._solid->Cp() * Tprime[1];

	    //The shell-wall relationship
	    residuals[2] = Q_w_rad + data.kiln.Q_gas_wall_cv(data.slice) - data.kiln.Q_wall_bed_cd(data.slice) - data.kiln.Q_w_ext(data.slice);
	    residuals[3] = data.kiln.Q_w_sh(data.slice) - data.kiln.Q_w_ext(data.slice);

	    return 0;
	  }
	};

	//Set up the system data;
	SolverData data{*this, slice_in};

	Serial_Vector T(4);
	data.save_state(T);

	Serial_Vector Tprime(4);
	Tprime[0] = 0;
	Tprime[1] = 0;
	Tprime[2] = Tprime[3] = 0;

	IDA solver(slice_in._Z, SolverData::resrob, T, Tprime);

	solver.setTol(1e-4, 1e-2);
	solver.setUserData(data);
	solver.denseSolver(T);

	Serial_Vector Id(4);
	Id[0] = Id[1] = 1.0; //Differential
	Id[2] = Id[3] = 0.0; //Algebraic
	solver.setId(Id);

	solver.calcIC(IDA_YA_YDP_INIT, _length);

	Serial_Vector T_interp(4);
	Serial_Vector Tprime_interp(4);

	std::sort(stop_points.begin(), stop_points.end());
	auto stop_it = stop_points.begin();

	while (stop_it != stop_points.end()) {
	  const realtype Znext = solver.solve(*stop_it, T, Tprime, IDA_ONE_STEP);

	  while (Znext > *stop_it) {
	    solver.interpolate(*stop_it, T_interp, 0);
	    solver.interpolate(*stop_it, Tprime_interp, 1);
	    data.load_state(T_interp, *stop_it);
	    _slices.push_back(data.slice);
	    if (++stop_it == stop_points.end()) break;
	  }

	  if (store_intermediate && (stop_it != stop_points.end())) {
	    data.load_state(T, Znext);
	    _slices.push_back(data.slice);
	  }
	}
      }

      std::vector<Slice>& getSlices() { return _slices; }

      double length() const { return _length; }

    protected:

      const double _RPM;
      const double _innerRadius;
      const double _length;
      const double _particle_diam;
      const double _shell_emissivity;
      const double _bed_emissivity;
      const double _wall_emissivity;
      const double _solid_density;
      const double _bed_void_frac;

      sym::Expr _solid_k;

      std::vector<Layer> _layers;
      simcem::shared_ptr<BedModel> _bedModel;
      simcem::shared_ptr<simcem::ModelIdealGasTp> _ambient;
      std::vector<Slice> _slices;
    };
  }
}


#include <learning/independences/continuous/linearcorrelation.hpp>
#include <boost/math/distributions/students_t.hpp>

using boost::math::cdf, boost::math::complement;
using boost::math::students_t_distribution;

namespace learning::independences::continuous {

double cor_pvalue(double cor, int df) {
    double statistic = cor * sqrt(df) / sqrt(1 - cor * cor);
    students_t_distribution tdist(static_cast<double>(df));
    return 2 * cdf(complement(tdist, fabs(statistic)));
}

double LinearCorrelation::pvalue_cached(const std::string& v1, const std::string& v2) const {
    double cor = cor_0cond(m_cov, cached_index(v1), cached_index(v2));
    return cor_pvalue(cor, m_df->num_rows() - 2);
}

double LinearCorrelation::pvalue_impl(const std::string& v1, const std::string& v2) const {
    auto [cor, df] = [this, &v1, &v2]() {
        switch (m_df.col(v1)->type_id()) {
            case Type::DOUBLE: {
                auto cov_ptr = m_df.cov<arrow::DoubleType>(v1, v2);
                auto& cov = *cov_ptr;
                double cor = cor_0cond(cov, 0, 1);
                return std::make_pair(cor, m_df.valid_rows(v1, v2) - 2);
            }
            case Type::FLOAT: {
                auto cov_ptr = m_df.cov<arrow::FloatType>(v1, v2);
                auto& cov = *cov_ptr;
                double cor = cor_0cond(cov, 0, 1);
                return std::make_pair(cor, m_df.valid_rows(v1, v2) - 2);
            }
            default:
                throw std::invalid_argument("Column " + m_df.name(v1) + " is not continuous");
        }
    }();

    return cor_pvalue(cor, df);
}

double LinearCorrelation::pvalue_cached(const std::string& v1, const std::string& v2, const std::string& ev) const {
    double cor = cor_1cond(m_cov, cached_index(v1), cached_index(v2), cached_index(ev));
    return cor_pvalue(cor, m_df->num_rows() - 3);
}

double LinearCorrelation::pvalue_impl(const std::string& v1, const std::string& v2, const std::string& ev) const {
    auto [cor, df] = [this, &v1, &v2, &ev]() {
        switch (m_df.col(v1)->type_id()) {
            case Type::DOUBLE: {
                auto cov_ptr = m_df.cov<arrow::DoubleType>(v1, v2, ev);
                auto& cov = *cov_ptr;
                double cor = cor_general(cov);
                return std::make_pair(cor, m_df.valid_rows(v1, v2, ev) - 3);
            }
            case Type::FLOAT: {
                auto cov_ptr = m_df.cov<arrow::FloatType>(v1, v2, ev);
                auto& cov = *cov_ptr;
                double cor = cor_general(cov);
                return std::make_pair(cor, m_df.valid_rows(v1, v2, ev) - 3);
            }
            default:
                throw std::invalid_argument("Column " + m_df.name(v1) + " is not continuous");
        }
    }();

    return cor_pvalue(cor, df);
}

double LinearCorrelation::pvalue_cached(const std::string& v1,
                                        const std::string& v2,
                                        const std::vector<std::string>& ev) const {
    std::vector<int> cached_indices;

    cached_indices.push_back(cached_index(v1));
    cached_indices.push_back(cached_index(v2));

    for (auto it = ev.begin(), end = ev.end(); it != end; ++it) {
        cached_indices.push_back(cached_index(*it));
    }

    int k = cached_indices.size();
    MatrixXd cov(k, k);

    for (int i = 0; i < k; ++i) {
        cov(i, i) = m_cov(cached_indices[i], cached_indices[i]);
        for (int j = i + 1; j < k; ++j) {
            cov(i, j) = cov(j, i) = m_cov(cached_indices[i], cached_indices[j]);
        }
    }

    double cor = cor_general(cov);
    return cor_pvalue(cor, m_df->num_rows() - 2 - k);
}

double LinearCorrelation::pvalue_impl(const std::string& v1,
                                      const std::string& v2,
                                      const std::vector<std::string>& ev) const {
    auto [cor, df] = [this, &v1, &v2, &ev]() {
        int k = ev.size();
        switch (m_df.col(v1)->type_id()) {
            case Type::DOUBLE: {
                auto cov_ptr = m_df.cov<arrow::DoubleType>(v1, v2, ev);
                auto& cov = *cov_ptr;
                double cor = cor_general(cov);
                return std::make_pair(cor, m_df.valid_rows(v1, v2, ev) - 2 - k);
            }
            case Type::FLOAT: {
                auto cov_ptr = m_df.cov<arrow::FloatType>(v1, v2, ev);
                auto& cov = *cov_ptr;
                double cor = cor_general(cov);
                return std::make_pair(cor, m_df.valid_rows(v1, v2, ev) - 2 - k);
            }
            default:
                throw std::invalid_argument("Column " + m_df.name(v1) + " is not continuous");
        }
    }();

    return cor_pvalue(cor, df);
}

}  // namespace learning::independences::continuous


static char help[] = "hello neural network!\n";
#include <iostream>
#include <petsc.h>

//---------------------------------------------------------------------------
/* autodiff include */
#include <autodiff/reverse.hpp>
using namespace autodiff;

// The multi-variable function for which derivatives are needed
var f(var x, var y, var z)
{
    return 1 + x + y + z; // + x*y + y*z + x*z + x*y*z + exp(x/y + y/z);
}

//---------------------------------------------------------------------------
// boost automatic differentiation
#include <boost/math/differentiation/autodiff.hpp>
#include <boost/multiprecision/cpp_bin_float.hpp>
using namespace boost::math::differentiation;

template <typename W, typename X, typename Y, typename Z>
promote<W, X, Y, Z> f(const W& w, const X& x, const Y& y, const Z& z) {
  using namespace std;
  return exp(w * sin(x * log(y) / z) + sqrt(w * z / (x * y))) + w * w / tan(z);
}
//--------------------------------------------------------------------------

/* main */

int main(int argc, char **argv) {
  PetscErrorCode     ierr;
  Vec                b;
  PetscReal ab[4] = {11.0, 12.0, 13.0, 14.0};
  PetscInt i, j[4]={0, 1, 2, 3};
  PetscRandom    rand;
/*
  var x = 1.0;         // the input variable x
  var y = 2.0;         // the input variable y
  var z = 3.0;         // the input variable z
  var V[3] = {x,y,z};
  var u = f(x, y, z);  // the output variable u
  auto [ux, uy, uz] = derivatives(u, wrt(x, y, z)); // evaluate the derivatives of u with respect to x, y, z
  std::cout << "u = "  << u << std::endl;    // print the evaluated output u
  std::cout << "ux = " << ux << std::endl;  // print the evaluated derivative ux
  std::cout << "uy = " << uy << std::endl;  // print the evaluated derivative uy
  std::cout << "uz = " << uz << std::endl;  // print the evaluated derivative uz
*/

  ierr = PetscInitialize(&argc, &argv, (char*) 0, help); CHKERRQ(ierr);

  VecCreate(PETSC_COMM_WORLD, &b);
  VecSetSizes(b, PETSC_DECIDE, 3);
  VecSetFromOptions(b);

  PetscRandomCreate(PETSC_COMM_WORLD,&rand);
  PetscRandomSetFromOptions(rand);

  PetscInt nloc;
  VecGetLocalSize(b,&nloc);
  var Vs[nloc];

  double *vec_p;
  VecGetArray(b, &vec_p);
  for (i=0; i<nloc; i++){
	  PetscScalar val;
	  PetscRandomGetValue(rand,&val);
	  Vs[i] = val;
	  vec_p[i] = val;
  }
  VecRestoreArray(b, &vec_p);

  var u = f(Vs[0], Vs[1], Vs[2]);  // the output variable u
  auto [ux, uy, uz] = derivatives(u, wrt(Vs[0], Vs[1], Vs[2])); // evaluate the derivatives of u with respect to x, y, z
  std::cout << "u = "  << u << std::endl;    // print the evaluated output u
  std::cout << "ux = " << ux << std::endl;  // print the evaluated derivative ux
  std::cout << "uy = " << uy << std::endl;  // print the evaluated derivative uy
  std::cout << "uz = " << uz << std::endl;  // print the evaluated derivative uz

/*
  using float50 = boost::multiprecision::cpp_bin_float_50;
  // Declare 4 independent variables together into a std::tuple.
  auto const variables = make_ftuple<float50, 1, 1, 1, 1>(ab[0], ab[1], ab[2], ab[3]);//(11, 12, 13, 14);
  auto const& w = std::get<0>(variables);
  auto const& x = std::get<1>(variables);
  auto const& y = std::get<2>(variables);
  auto const& z = std::get<3>(variables);
  auto const v = f(w, x, y, z);
  // Calculated from Mathematica symbolic differentiation.
  std::cout << v.derivative(1, 1, 1, 1) << '\n';
*/

  VecDestroy(&b);
  PetscRandomDestroy(&rand);
  return PetscFinalize();
}


/*
* Copyright 2019 © Centre Interdisciplinaire de développement en Cartographie des Océans (CIDCO), Tous droits réservés
*/

#ifndef COORDINATETRANSFORM_HPP
#define COORDINATETRANSFORM_HPP

#include "../Position.hpp"
#include "../utils/Constants.hpp"
#include <Eigen/Dense>

/*!
* \brief Coordinate transform class
* \author Guillaume Labbe-Morissette, Jordan McManus
* \date September 13, 2018, 3:39 PM
*/
class CoordinateTransform {
public:

  // WGS84 ellipsoid Parameters

  /**WGS84 ellipsoid semi-major axis*/
  static constexpr double a = 6378137.0;

  /**WGS84 ellipsoid first eccentricity squared*/
  static constexpr double e2 = 0.081819190842622 * 0.081819190842622;

  /**WGS84 ellipsoid inverse flattening*/
  static constexpr double f = 1.0 / 298.257223563;

  /**WGS84 ellipsoid semi-minor axis*/
  static constexpr double b = a * (1-f); // semi-minor axis

  /**WGS84 ellipsoid second eccentricity squared*/
  static constexpr double epsilon = e2 / (1.0 - e2); // second eccentricity squared

  /**
  * Sets the position in navigation frame
  *
  * @param positionInNavigationFrame the value that needs to be set
  * @param positionGeographic the geographic position
  * @param navDCM the rotation matrix
  * @param originECEF the ECEF origin position
  */
  static void getPositionInNavigationFrame(Eigen::Vector3d & positionInNavigationFrame, Position & positionGeographic, Eigen::Matrix3d & navDCM, Eigen::Vector3d & originECEF) {
    Eigen::Vector3d positionECEF;
    getPositionECEF(positionECEF, positionGeographic);

    Eigen::Vector3d positionVector = navDCM * (positionECEF - originECEF);

    positionInNavigationFrame << positionVector(0), positionVector(1), positionVector(2);
  };

  /**
  * Sets the ECEF position by the given position
  *
  * @param positionECEF value that needs to be set
  * @param position the position used to get the ECEF position
  */
  static void getPositionECEF(Eigen::Vector3d & positionECEF, Position & position) {
    double N = a / (sqrt(1 - e2 * position.getSlat() * position.getSlat()));
    double xTRF = (N + position.getEllipsoidalHeight()) * position.getClat() * position.getClon();
    double yTRF = (N + position.getEllipsoidalHeight()) * position.getClat() * position.getSlon();
    double zTRF = (N * (1 - e2) + position.getEllipsoidalHeight()) * position.getSlat();

    positionECEF << xTRF, yTRF, zTRF;
  };

  /**
  * Gets the longitude, latitude and elevation of an ECEF position
  *
  * @param positionInNavigationFrame the position we need to get the latitude, longitude and elevation
  * @param positionGeographic the position where the latitude, longitude et elevation will be put in
  */
  static void convertECEFToLongitudeLatitudeElevation(Eigen::Vector3d & positionInNavigationFrame, Position & positionGeographic) {
    double x = positionInNavigationFrame(0);
    double y = positionInNavigationFrame(1);
    double z = positionInNavigationFrame(2);

    // Bowring (1985) algorithm
    double p2 = x * x + y*y;
    double r2 = p2 + z*z;
    double p = std::sqrt(p2);
    double r = std::sqrt(r2);

    double tanu = (1 - f) * (z / p) * (1 + epsilon * b / r);
    double tan2u = tanu * tanu;

    double cos2u = 1.0 / (1.0 + tan2u);
    double cosu = std::sqrt(cos2u);
    double cos3u = cos2u * cosu;

    double sinu = tanu * cosu;
    double sin2u = 1.0 - cos2u;
    double sin3u = sin2u * sinu;

    double tanlat = (z + epsilon * b * sin3u) / (p - e2 * a * cos3u);
    double tan2lat = tanlat * tanlat;
    double cos2lat = 1.0 / (1.0 + tan2lat);
    double sin2lat = 1.0 - cos2lat;

    double coslat = std::sqrt(cos2lat);
    double sinlat = tanlat * coslat;

    double longitude = std::atan2(y, x);
    double latitude = std::atan(tanlat);
    double height = p * coslat + z * sinlat - a * sqrt(1.0 - e2 * sin2lat);

    positionGeographic.setLatitude(latitude*R2D);
    positionGeographic.setLongitude(longitude*R2D);
    positionGeographic.setEllipsoidalHeight(height);
  }

  /**
  * Sets a terrestrial to a local geodetic reference frame matrix by the given position
  *
  * @param trf2lgf the terrestrial to local geodetic reference frame matrix that needs to be set
  * @param position the given position
  */
  static void getTerrestialToLocalGeodeticReferenceFrameMatrix(Eigen::Matrix3d & trf2lgf, Position & position) {
    double m00 = -position.getSlat() * position.getClon();
    double m01 = -position.getSlat() * position.getSlon();
    double m02 = position.getClat();

    double m10 = -position.getSlon();
    double m11 = position.getClon();
    double m12 = 0;

    double m20 = -position.getClat() * position.getClon();
    double m21 = -position.getClat() * position.getSlon();
    double m22 = -position.getSlat();

    trf2lgf <<
    m00, m01, m02,
    m10, m11, m12,
    m20, m21, m22;
  };

  /**
  * Sets rotation matrix from neutral/zero position to the given attitude
  *
  * @param outputMatrix matrix that needs to be set
  * @param attitude the given attitude
  */
  static void getDCM(Eigen::Matrix3d & outputMatrix,Attitude & attitude){
    outputMatrix <<         attitude.getCh()*attitude.getCp(),   attitude.getCh()*attitude.getSp()*attitude.getSr()-attitude.getCr()*attitude.getSh(), attitude.getCh()*attitude.getCr()*attitude.getSp()+attitude.getSr()*attitude.getSh(),
    attitude.getCp()*attitude.getSh(),   attitude.getCh()*attitude.getCr()+attitude.getSp()*attitude.getSr()*attitude.getSh(), attitude.getSh()*attitude.getCr()*attitude.getSp()-attitude.getCh()*attitude.getSr(),
    -attitude.getSp(),          attitude.getCp()*attitude.getSr(),        attitude.getCr()*attitude.getCp();
  }

  /**
  * NED Tangent plane at position to WGS84 ECEF
  *
  * @param outputMatrix the matrix that needs to be set
  * @param position the position to convert
  */
  static void ned2ecef(Eigen::Matrix3d & outputMatrix,Position & position){

    outputMatrix << -position.getClon()*position.getSlat(),-position.getSlon(),-position.getClat()*position.getClon(),
    -position.getSlat()*position.getSlon(),position.getClon(),-position.getClat()*position.getSlon(),
    position.getClat(),0,-position.getSlat();

  }

  /**
  * Converts spherical coordinates to cartesian
  *
  * @param outputVector the cartesian coordinates
  * @param theta the polar angle
  * @param phi the azimuthal angle
  * @param r the radial distance
  */
  static void spherical2cartesian(Eigen::Vector3d & outputVector,double theta,double phi,double r){
    outputVector(0) = r * sin(D2R*theta)*cos(D2R*phi);
    outputVector(1) = r * sin(D2R*theta)*sin(D2R*phi);
    outputVector(2) = r * cos(D2R*theta);
  }

  /**
  * Converts sonar coordinates (alpha,beta,r) to cartesian (NED)
  *
  * @param outputVector the cartesian coordinates
  * @param aphaDegrees the alpha degree
  * @param betaDegrees the beta degree
  * @param r the radical distance
  */
  static void sonar2cartesian(Eigen::Vector3d & outputVector,double alphaDegrees,double betaDegrees,double r){
    outputVector(0)=r * sin(alphaDegrees*D2R);
    outputVector(1)=r * cos(alphaDegrees*D2R) * sin(betaDegrees*D2R);
    outputVector(2)=r * cos(alphaDegrees*D2R) * cos(betaDegrees*D2R);
  }
};

#endif /* COORDINATETRANSFORM_HPP */


/*
You are asked to calculate factorials of some small positive integers.

 */

#include <iostream>
#include <boost/multiprecision/cpp_int.hpp>

using boost::multiprecision::cpp_int;
using namespace std;

int main() {

    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cpp_int t,n;
    cin>>t;
    while(t--){
        cin>>n;
        cpp_int fac = 1;
        if(n == 0 || n == 1)
            cout<<fac<<endl;
        else{
            for(cpp_int i = 2; i<=n; i++)
               fac = fac*i;
            cout<<fac<<endl;
        }
    }
    return 0;
}


/*! @file pitts_tensor2_qb_decomposition.hpp
* @brief QB-part of the SVQB orthogonalization algorithm (Stathopoulos and Wu, SISC 23 (6), pp. 2165-2182)
* @author Melven Roehrig-Zoellner <Melven.Roehrig-Zoellner@DLR.de>
* @date 2019-10-31
* @copyright Deutsches Zentrum fuer Luft- und Raumfahrt e. V. (DLR), German Aerospace Center
*
**/

// include guard
#ifndef PITTS_TENSOR2_QB_DECOMPOSITION_HPP
#define PITTS_TENSOR2_QB_DECOMPOSITION_HPP

// includes
#include <exception>
#include <cmath>
#include "pitts_tensor2.hpp"
#include "pitts_tensor2_eigen_adaptor.hpp"
#include "pitts_timer.hpp"
#pragma GCC push_options
#pragma GCC optimize("no-unsafe-math-optimizations")
#include <Eigen/Dense>
#pragma GCC pop_options

//! namespace for the library PITTS (parallel iterative tensor train solvers)
namespace PITTS
{
  //! QB-part in the SVQB orthogonalization algorithm from Stathopoulos and Wu, SISC 23 (6), pp. 2165-2182
  //!
  //! Computes the decomposition B^TB = M using a SVD of M for a symmetric positive semi-definite matrix M
  //!
  //! @tparam T     underlying data type (double, complex, ...)
  //!
  //! @param  M     Symmetric positive semi-definite input matrix M, overwritten with the output matrix B
  //! @param  Binv  Pseudo-Inverse of the output matrix
  //! @return       detected rank of the matrix
  //!
  template<typename T>
  auto qb_decomposition(const Tensor2<T>& M, Tensor2<T>& B, Tensor2<T>& Binv, T rankTolerance)
  {
    const auto timer = PITTS::timing::createScopedTimer<Tensor2<T>>();

    // get dimension
    if( M.r1() != M.r2() )
      throw std::invalid_argument("qb_decomposition requires a quadratic matrix!");
    const auto n = M.r1();

    // Helpful types
    using vec = Eigen::Matrix<T, Eigen::Dynamic, 1>;
    using mat = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>;
    using EigenSolver = Eigen::SelfAdjointEigenSolver<mat>;

    // use Eigen for matrix operations
    const auto mapM = ConstEigenMap(M);

    // compute sqrt(diag(M)) and its inverse
    vec d(n), dinv(n);
    for(int i = 0; i < n; i++)
    {
      if( std::abs(M(i,i)) <= rankTolerance )
      {
        d(i) = T(0);
        dinv(i) = T(0);
      }
      else
      {
        d(i) = std::sqrt(M(i,i));
        dinv(i) = T(1)/d(i);
      }
    }

    // scale input matrix from left and right by diag(dinv), so it's diagonal entries become 1
    const mat scaledM = dinv.asDiagonal() * mapM * dinv.asDiagonal();

    // compute eigenvalue decomposition
    EigenSolver eigSolv(scaledM);

    // determine rank of the input matrix and set w = sqrt(lambda), winv = 1/w
    // Eigen orders eigenvalues with increasing value (smallest first)
    int rank = n;
    const auto evMax = std::abs(eigSolv.eigenvalues()(n-1));
    vec w(n), winv(n);
    if( evMax <= rankTolerance )
    {
      w = vec::Zero(n);
      winv = vec::Zero(n);
      rank = 0;
    }
    else
    {
      for(int i = 0; i < n; i++)
      {
        if( eigSolv.eigenvalues()(i) <= rankTolerance*evMax )
        {
          rank--;
          w(i) = T(0);
          winv(i) = T(0);
        }
        else
        {
          w(i) = std::sqrt(eigSolv.eigenvalues()(i));
          winv(i) = T(1)/w(i);
        }
      }
    }

    // calculate B and Binv
    B.resize(n,n);
    Binv.resize(n,n);
    EigenMap(Binv) = dinv.asDiagonal() * eigSolv.eigenvectors() * winv.asDiagonal();
    EigenMap(B) = w.asDiagonal() * eigSolv.eigenvectors().transpose() * d.asDiagonal();

    // reorder, so the part corresponding to the largest eigenvalues are first
    for(int i = 0; i < n; i++)
    {
      for(int j = 0; j < n/2; j++)
      {
        int k = n-j-1;
        std::swap(B(j,i), B(k,i));
        std::swap(Binv(i,j), Binv(i,k));
      }
    }

    return rank;
  }

}

#endif // PITTS_TENSOR2_QB_DECOMPOSITION_HPP


#ifndef GENERATE_GALTON_WATSON_HPP
#define GENERATE_GALTON_WATSON_HPP

#include <cstdlib>
#include <string>
#include <queue>
#include <algorithm>
#include <numeric>
#include <cmath>

#ifdef _OPENMP
#include <omp.h>
#endif

#include <boost/random.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/math/special_functions/binomial.hpp>
#include <boost/math/special_functions/factorials.hpp>

/**
 * Given:
 * (1) Distribution: Poisson, Negative Geometric, Binomial
 * (2) Number of nodes for the conditioned Galton-Watson process.
 * (3) Parameters needed for the distributions:
 *    (a) Probability of success (p)
 *    (b) Lambda if using Poisson distribution
 *    (c) 'k', the total number of trials if we are using Binomial
 *    (d) Seed value for the random number
 */
struct generate_Galton_Watson_t {
  typedef boost::mt19937 engine_type;
  typedef boost::binomial_distribution<int, double> binomial_type;
  typedef boost::uniform_real<double> uni_real_type;
  typedef boost::exponential_distribution<double> exp_real_type;
  typedef boost::uniform_int<int> uni_int_type;

  typedef boost::adjacency_list
                   <boost::listS,
                    boost::vecS,
                    boost::bidirectionalS,
                    boost::property<boost::vertex_name_t, std::string,
                    boost::property<boost::vertex_distance_t, double> >,
                    boost::property<boost::edge_weight_t, double> > graph_type;
    typedef boost::property_map<graph_type, boost::vertex_index_t>::type
                                                vertex_index_map_t;
    typedef boost::property_map<graph_type, boost::vertex_name_t>::type
                                                vertex_name_map_t;
    typedef boost::property_map<graph_type, boost::vertex_distance_t>::type
                                                vertex_distance_map_t;
    typedef boost::property_map<graph_type, boost::edge_weight_t>::type
                                                edge_weight_map_t;
    typedef boost::graph_traits<graph_type>::vertex_descriptor vertex_t;

  private:

  struct always_1_t {
    typedef double result_type;
    result_type operator()() { return 1.0; }
  };

  struct Poisson_computer_t {
    typedef boost::poisson_distribution<int, double> dist_type;
    int lambda;

    Poisson_computer_t (int lambda) : lambda(lambda) {}

    double operator()(int i) const {
      return (pow(lambda,i)*exp(-lambda)) / boost::math::factorial<double>(i);
    }

    dist_type operator()() const { return dist_type(lambda); }
  };

  struct geometric_computer_t {
    typedef boost::geometric_distribution<int, double> dist_type;
    double p;
    geometric_computer_t (double p) : p(p) {}

    double operator()(int i) const { return pow((1-p), i) * p; }

    dist_type operator()() const { return dist_type(p); }
  };

  struct binomial_computer_t {
    typedef boost::binomial_distribution<int, double> dist_type;
    int k;
    double p;
    binomial_computer_t (int k, double p) : k(k), p(p) {}

    double operator()(int i) const {
      return
      boost::math::binomial_coefficient<double>(k,i)*pow(p,i)*pow((1-p),(k-i));
    }

    dist_type operator()() const { return dist_type(k,p); }
  };

  struct binary_0_2_computer_t {
    struct binary_0_2_distribution {
      typedef int result_type;
      boost::bernoulli_distribution<double> distro;

      binary_0_2_distribution(double p) : distro(p) {}

      template <typename EngineType>
      int operator()(EngineType& engine) {return (distro(engine))?2:0;}
    };
    typedef binary_0_2_distribution dist_type;

    binary_0_2_computer_t (double p) : p(p) {}

    double operator()(int i) const {
      if (2==i) return p;
      else if (0==i) return (1-p);
      else return 0;
    }

    dist_type operator()() const { return dist_type(p); }

    private:
    double p;
  };

  struct binary_0_1_2_computer_t {
    typedef boost::uniform_int<int> dist_type;
    double operator()(int i) const {
      if (0==i || 1==i || 2==i) return 1./3.;
      else return 0;
    }

    dist_type operator()() const { return dist_type(0,2); }
  };

  /**
   * Thin wrapper around boost::uniform<> so that it conforms to random_shuffle.
   */
  struct random_functor_t : std::unary_function<int, int> {
    engine_type engine;

    random_functor_t (engine_type engine) : engine(engine) {}

    int operator()(const int& ULIMIT) {
      uni_int_type dist (0, ULIMIT-1);
      return dist(engine);
    }
  };

  static void rotate_to_get_tree (std::vector<int>& psi_vec) {
    /** Get the vector S for the queues in the tree */
    size_t n = psi_vec.size();
    std::vector<int> S(n);

    for (size_t i=0; i<n; ++i) S[i] = ((0==i)?1:(S[i-1]))+(psi_vec[i]-1);

    int min_ele=std::numeric_limits<int>::max();
    int min_index=-1;
    for (size_t i=0; i<n; ++i)
      if (min_ele>S[i]) { min_ele=S[i]; min_index=i; }

    std::rotate(psi_vec.begin(), psi_vec.begin()+min_index+1, psi_vec.end());
  }

  template <typename WDistribution>
  static graph_type generate_impl (int n,
                                   const std::vector<int>& psi_vec,
                                   WDistribution w_prng,
                                   int verbosity) {
    graph_type G;
    vertex_name_map_t name_map = boost::get (boost::vertex_name, G);
    vertex_distance_map_t dist_map = boost::get (boost::vertex_distance, G);
    edge_weight_map_t weight_map = boost::get (boost::edge_weight, G);

    /** Set up a queue for the children and add the root vertex */
    vertex_t root = boost::add_vertex(G);
    boost::put (name_map, root, boost::lexical_cast<std::string>(0));
    boost::put (dist_map, root, 0);

    std::queue<vertex_t> vertex_queue;
    vertex_queue.push(root);

    /** Count the number of children left */
    int n_left = (n-1);

    /** Iterate till we are left with nothing or we have maxed out children */
    while ((0<=n_left) && (false==vertex_queue.empty())) {
      const vertex_t parent = vertex_queue.front();
      vertex_queue.pop();

#pragma omp critical
      if (1<verbosity) std::cout << "Popped " << parent << std::endl;

      int num_children = psi_vec[parent];
#pragma omp critical
      if (1<verbosity) std::cout << "num children of " << parent << " = "
                               << num_children << std::endl;

      for (int child_number=0; child_number<num_children; ++child_number) {
        const vertex_t child = boost::add_vertex(G);
        vertex_queue.push(child);
        boost::put (name_map,
                    child,
                    boost::get (name_map, parent) +
                    boost::lexical_cast<std::string>(child_number));

        /**
         * Duplicating a map here, but I think it's fine. We need the vertex
         * weights to easily be able to get the Dyck/Harris paths.
         */
        double edge_weight = w_prng();
        boost::put (dist_map, child, edge_weight);
        boost::put (weight_map,
                    (boost::add_edge (parent, child, G)).first,
                    edge_weight);

        --n_left;
      }

#pragma omp critical
      if (1<verbosity) std::cout << "N Left = " << n_left << std::endl;
    }

    return G;
  }

  template <typename ProbabilityComputer>
  static graph_type generate_tree_stupid (int n,
                                          int seed,
                                          ProbabilityComputer compute,
                                          bool use_random_weights,
                                          int verbosity) {
    // This is the stupid way of generating the required random tree.
    // We simply want to fill up the tree as long as it has at least
    // the required number of nodes, we are happy. Please use it only
    // as a counter example.

    // A vector to hold the number of children for each node
    std::vector<int> psi_vec (n, 0);

    // Get a new random engine
    engine_type engine(seed);

    // Get the distribution that we are interested in
    typename ProbabilityComputer::dist_type prng = compute();

    // Now, all we need to do is iterate over and over again
    int num_children;
    do {

      // Start with 0 edges in the beginning
      num_children = 0;

      // Loop to assign edges/children to nodes
      for (int i=0;
           (num_children<(n-1)) && (i<psi_vec.size());
           ++i) {

        // Figure out how many children we want to give this node.
        int num_children_for_i = prng(engine);
        if ((num_children_for_i+num_children) > (n-1)) {
          num_children_for_i = (n-1) - num_children;
        }

        num_children += num_children_for_i;
        psi_vec[i] = num_children_for_i;
      }

    } while (num_children != (n-1));

    random_functor_t shuffle_prng (engine);
    std::random_shuffle (psi_vec.begin(), psi_vec.end(), shuffle_prng);

    if (2<verbosity) {
      for (int i=0; i<n; ++i) std::cout<<psi_vec[i]<<" ";
      std::cout<<std::endl;
    }

    rotate_to_get_tree (psi_vec);

    if (2<verbosity) {
      for (int i=0; i<n; ++i) std::cout<<psi_vec[i]<<" ";
      std::cout<<std::endl;
    }

    if (use_random_weights) {
      boost::variate_generator<engine_type, uni_real_type>
                            w_prng (engine_type(), uni_real_type(0.0,1.0));
      return generate_impl (n, psi_vec, w_prng, verbosity);
    } else {
      return generate_impl (n, psi_vec, always_1_t(), verbosity);
    }
  }

  template <typename ProbabilityComputer>
  static graph_type generate_tree (int n,
                                   int seed,
                                   ProbabilityComputer compute,
                                   bool use_random_weights,
                                   int verbosity) {

    std::vector<int> N_j_vec;
    engine_type engine(seed);
    int j_times_N_j, j;

    do {

      j_times_N_j = j = 0;
      int sum_of_N_i = 0;
      int i = 0;
      double sum_of_p_i = 0;
      N_j_vec.resize(0);
      while (0 != (n-sum_of_N_i)) {
        const double p_i = compute(i);
        engine_type new_engine(engine());
        binomial_type dist((n-sum_of_N_i), p_i/(1-sum_of_p_i));
        const int N_j = dist(new_engine);
        N_j_vec.push_back (N_j);
        sum_of_N_i += N_j;
        sum_of_p_i += p_i;
        j_times_N_j += j*N_j;
        ++i;
        ++j;
      }

      if (2 < verbosity) std::cout << "j*N_j = " << j_times_N_j << std::endl;

    } while (j_times_N_j != (n-1));

    std::vector<int> psi_vec (n, 0);
    int offset = 0;
    for (int j=0; j<N_j_vec.size(); ++j) {
      for (int i=0; i<N_j_vec[j]; ++i) psi_vec[offset+i] = j;
      offset += N_j_vec[j];
    }

    random_functor_t shuffle_prng (engine);
    std::random_shuffle (psi_vec.begin(), psi_vec.end(), shuffle_prng);

    if (2<verbosity) {
      for (int i=0; i<n; ++i) std::cout<<psi_vec[i]<<" ";
      std::cout<<std::endl;
    }

    rotate_to_get_tree (psi_vec);

    if (2<verbosity) {
      for (int i=0; i<n; ++i) std::cout<<psi_vec[i]<<" ";
      std::cout<<std::endl;
    }

    if (use_random_weights) {
      boost::variate_generator<engine_type, uni_real_type>
                            w_prng (engine_type(), uni_real_type(0.0,1.0));
      return generate_impl (n, psi_vec, w_prng, verbosity);
    } else {
      return generate_impl (n, psi_vec, always_1_t(), verbosity);
    }
  }

  template <typename ProbabilityComputer>
  static graph_type generate_tree_dispatcher (int n,
                                              int seed,
                                              ProbabilityComputer compute,
                                              bool use_random_weights,
                                              int verbosity,
                                              bool use_stupid_tree_gen) {
    if (use_stupid_tree_gen) {
      return generate_tree_stupid (n, seed, compute, use_random_weights,
                                   verbosity);
    } else {
      return generate_tree (n, seed, compute, use_random_weights, verbosity);
    }
  }

  public:

  static graph_type generate_Poisson (int n,
                                      int seed,
                                      int lambda,
                                      bool use_random_weights,
                                      int verbosity,
                                      bool use_stupid_tree_gen) {
    return generate_tree_dispatcher (n, seed, Poisson_computer_t(lambda),
                          use_random_weights, verbosity, use_stupid_tree_gen);
  }

  static graph_type generate_Geometric (int n,
                                        int seed,
                                        double p,
                                        bool use_random_weights,
                                        int verbosity,
                                        bool use_stupid_tree_gen) {
    return generate_tree_dispatcher (n, seed, geometric_computer_t(p),
                          use_random_weights, verbosity, use_stupid_tree_gen);
  }

  static graph_type generate_Binomial (int n,
                                       int seed,
                                       int k,
                                       double p,
                                       bool use_random_weights,
                                       int verbosity,
                                       bool use_stupid_tree_gen) {
    return generate_tree_dispatcher (n, seed, binomial_computer_t(k, p),
                          use_random_weights, verbosity, use_stupid_tree_gen);
  }

  static graph_type generate_Binary_0_2 (int n,
                                         int seed,
                                         double p,
                                         bool use_random_weights,
                                         int verbosity,
                                         bool use_stupid_tree_gen) {
    return generate_tree_dispatcher (n, seed, binary_0_2_computer_t(p),
                          use_random_weights, verbosity, use_stupid_tree_gen);
  }

  static graph_type generate_Binary_0_1_2 (int n,
                                           int seed,
                                           bool use_random_weights,
                                           int verbosity,
                                           bool use_stupid_tree_gen) {
    return generate_tree_dispatcher (n, seed, binary_0_1_2_computer_t(),
               use_random_weights, verbosity, use_stupid_tree_gen);
  }
};

#endif // GENERATE_GALTON_WATSON_HPP


// Copyright (c) 2009  INRIA Sophia-Antipolis (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL$
// $Id$
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Sebastien Loriot, Sylvain Pion

#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/CGAL_Ipelet_base.h>
#include <CGAL/linear_least_squares_fitting_2.h>
#include <boost/utility.hpp>

namespace CGAL_pca{

//~ typedef CGAL::Exact_predicates_exact_constructions_kernel                 Kernel;
typedef CGAL::Simple_cartesian<double>                 Kernel;

const std::string Slab[] = {
  "PCA","Help"
};

const std::string Hmsg[] = {
  "(Principal Component Analysis) given a set of points, draw a segment that is on the line defined by the eigen vector associated to the highest eigen value of the covariance matrix of the input points"
};

class pcaIpelet
  : public CGAL::Ipelet_base<Kernel,2>{
public:
  pcaIpelet()
    :CGAL::Ipelet_base<Kernel,2>("PCA",Slab,Hmsg){}
  void protected_run(int);
};

void pcaIpelet::protected_run(int fn)
{
  if (fn==1) {
    show_help();
    return;
  }

  std::list<Point_2> pt_list;
  std::list<Circle_2> cir_list;
  std::list<Polygon_2> poly_list;
  std::list<Kernel::Triangle_2> tri_list;
  std::list<Segment_2> sg_list;

  Iso_rectangle_2 bbox=
    read_active_objects(
      CGAL::dispatch_or_drop_output<Point_2,Polygon_2,Circle_2,Segment_2>(
        std::back_inserter(pt_list),
        std::back_inserter(poly_list),
        std::back_inserter(cir_list),
        std::back_inserter(sg_list)
      )
    );

  for (std::list<Polygon_2>::iterator it=poly_list.begin();it!=poly_list.end();++it)
    if (it->size()==3){
      tri_list.push_back(Kernel::Triangle_2(*(it->vertices_begin()),
                                            *boost::next(it->vertices_begin()),
                                            *boost::next(it->vertices_begin(),2)
                                            ));
    }
    else{
      print_error_message("This implementation is limited to triangles");
      return;
    }

  int s=0;

  if (!pt_list.empty()) s=1;
  if (!cir_list.empty()) s+=2;
  if (!tri_list.empty()) s+=4;
  if (!sg_list.empty()) s+=8;

  if (s==0) {
    print_error_message("Nothing is selected");
    return;
  }

  Kernel::Line_2 line;
  Kernel::Point_2 centroid;

  switch (s){
    case 1://points
      linear_least_squares_fitting_2(pt_list.begin(),pt_list.end(),line,centroid,CGAL::Dimension_tag<0>());
      break;
    case 2://circles
      linear_least_squares_fitting_2(cir_list.begin(),cir_list.end(),line,centroid,CGAL::Dimension_tag<2>());
      break;
    case 4://triangles
      linear_least_squares_fitting_2(tri_list.begin(),tri_list.end(),line,centroid,CGAL::Dimension_tag<2>());
      break;
    case 8://segments
      linear_least_squares_fitting_2(sg_list.begin(),sg_list.end(),line,centroid,CGAL::Dimension_tag<1>());
      break;
    default:
      print_error_message("Please select a set of points or segments or triangles or circles");
      return;
  }

  CGAL::Object obj_cgal = CGAL::intersection(line,bbox);
  Segment_2 seg;
  if (CGAL::assign(seg, obj_cgal))
    draw_in_ipe(seg);

}

}

CGAL_IPELET(CGAL_pca::pcaIpelet)


/*
 * Copyright 2011-2013 Mario Mulansky
 * Copyright 2012 Karsten Ahnert
 *
 * 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)
 */

#include <iostream>

#include <boost/numeric/odeint.hpp>
#include <boost/numeric/ublas/vector.hpp>

typedef boost::numeric::ublas::vector< double > state_type;

void lorenz( const state_type &x , state_type &dxdt , const double t )
{
    const double sigma( 10.0 );
    const double R( 28.0 );
    const double b( 8.0 / 3.0 );

    dxdt[0] = sigma * ( x[1] - x[0] );
    dxdt[1] = R * x[0] - x[1] - x[0] * x[2];
    dxdt[2] = -b * x[2] + x[0] * x[1];
}

using namespace boost::numeric::odeint;

//[ublas_main
int main()
{
    state_type x(3);
    x[0] = 10.0; x[1] = 5.0 ; x[2] = 0.0;
    typedef runge_kutta_dopri5< state_type > stepper;
    integrate_const( make_dense_output< stepper >( 1E-6 , 1E-6 ) , lorenz , x ,
                     0.0 , 10.0 , 0.1 );
}
//]


#include <iostream>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/operations.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <random>
#include "mnist/reader.hpp"
#include "perceptron/PerceptronMonoLayer.hpp"
#include "perceptron/act_funs.hpp"
#include "perceptron/ItemNN.hpp"

using namespace cv;
using namespace boost;
using namespace boost::numeric;
using namespace std;

void randomizeMatrix(boost::numeric::ublas::matrix<double> &matrix);

void randomizeVector(boost::numeric::ublas::vector<double> &vect);

ublas::vector<double> imageToVector(cv::Mat const &mat) {
    size_t nbPixels = mat.rows * mat.cols;
    ublas::vector<double> v(nbPixels);
    for (size_t i = 0; i < nbPixels; i++) {
        auto a = (double) mat.at<unsigned char>(i);
        v.insert_element(i, a>(255./2.)?1:0);
    }
    return v;
}

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

    using namespace boost::numeric::ublas;

    int numCycles;
    if(argc > 1)
        numCycles = atoi(argv[1]);
    else
        numCycles =25;

    double (*seuil)(double) = act_f::noActFun<double>;

    string filename = "../res/train-images-idx3-ubyte";
    size_t image_size = 28 * 28;

    std::vector<cv::Mat> vec_entrees;
    std::vector<double> vec_labels;
    std::vector<cv::Mat> vec_entrees_test;
    std::vector<double> vec_labels_test;
    read_Mnist(filename, vec_entrees);
    read_Mnist_Label("../res/train-labels-idx1-ubyte", vec_labels);
    read_Mnist("../res/t10k-images-idx3-ubyte", vec_entrees_test);
    read_Mnist_Label("../res/t10k-labels-idx1-ubyte", vec_labels_test);

    std::vector<ItemNN<double>> vec_train;
    for (int i = 0; i < vec_entrees.size(); ++i) {
        vec_train.emplace_back(vec_labels[i], vec_entrees[i], imageToVector);
    }
    vec_entrees.clear();
    vec_labels.clear();
    std::vector<ItemNN<double>> vec_test;
    for (int i = 0; i < vec_entrees_test.size(); ++i) {
        vec_test.emplace_back(vec_labels_test[i], vec_entrees_test[i], imageToVector);
    }
    vec_entrees_test.clear();
    vec_labels_test.clear();

    std::vector<Neuron> neuronnes;
    for (int i = 0; i < 10; ++i) {
        ublas::vector<double> weigthN(image_size);
        randomizeVector(weigthN);
        neuronnes.emplace_back(weigthN, seuil);
    }

    PerceptronMonoLayer perceptronMonoLayer(neuronnes);
    perceptronMonoLayer.learn(vec_train,numCycles);
    cout << "End of learning" << endl;
    cout << "Begin tests" << endl;
    perceptronMonoLayer.test(vec_test);

    neuronnes.clear();

    cv::Mat imageTest = imread("../res/test.png",cv::ImreadModes::IMREAD_GRAYSCALE);

    ItemNN<double> itemNN(5,imageTest,imageToVector);

    cout << "test with gimp image(5):" <<(
         perceptronMonoLayer.test(itemNN)? "true":"false") << endl;

    return 0;
}

/**
 * Fonction qui permet d'initialiser un vecteur a des poids aléatoires
 * entre -1 et 1
 * @param vect à initialiser
 */
void randomizeVector(boost::numeric::ublas::vector<double> &vect) {
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_real_distribution<double> dist(0, 1);

    for (size_t i = 0; i < vect.size(); i++) {
        vect(i) = dist(mt);
    }
}


#include <seir.h>

#include <boost/numeric/odeint.hpp>

#include <iostream>
#include <functional>
#include <algorithm>
#include <limits>

#include <math.h>
#if !defined(max)
#define max(a, b) (a > b ? a : b)
#endif

#if !defined(min)
#define min(a, b) (a < b ? a : b)
#endif

#define NSTATES 9
typedef std::array<double, NSTATES> StateType;

// odeing namespace access
namespace odeint = boost::numeric::odeint;

SEIR::SEIR()
    : R0Table_(), Tinc_(2.5), Tinf_(5.0), Tfat_(30), Tmrec_(12), Threc_(30),
      pMild_(0.8), pFat_(0.01), duration_(300), population_(1000000), dt_(1),
      Hc_(1), pfat_increase_nohospital_(1.0)
{
    SetR0(2.5);
}

static bool operator<(const R0TableElement &a, const R0TableElement &b)
{
    return (a.time < b.time);
}

void SEIR::SetR0(double R0, double time)
{
    if (time < 0) {
        R0Table_.clear();
        R0Table_.push_back(R0TableElement(R0, 0));
        return;
    }
    R0Table_.push_back(R0TableElement(R0, time));
    R0Table_.sort();
}

ResultsType SEIR::compute(void)
{
    StateType state0;
    memset(state0.data(), 0, sizeof(double) * NSTATES);
    state0[0] = 1.0;
    state0[1] = 1.0 / (double)population_;

    typedef odeint::runge_kutta_cash_karp54<StateType, double, StateType,
                                            double>
        error_stepper_type;
    typedef odeint::controlled_runge_kutta<error_stepper_type>
        controlled_stepper_type;
    // typedef odeint::dense_output_runge_kutta<controlled_stepper_type>
    //   dense_stepper_type;

    // Create an output
    double nsteps = (double)duration_ / (double)dt_;
    if (::fmod(nsteps, 1.0) < std::numeric_limits<double>::epsilon())
        nsteps = nsteps + 1;
    else
        nsteps = ::ceil(nsteps);

    ResultsType results;
    results.resize((int)nsteps);
    uint32_t counter = 0;

    error_stepper_type stepper;
    odeint::integrate_const(
        stepper,
        [this](const StateType &state, StateType &statedot, double t) {
            // statedot.resize(NSTATES);

            double S = state[0];   // susceptible
            double E = state[1];   // exposed
            double Inf = state[2]; // Infectious
            double M = state[3];   // Mild case
            double Hrec = state[4];
            double Hfat = state[5];

            // Probability of fatal given hospitalized
            // and sufficient hospital capacity
            double pfs_hos = pFat_ / (1.0 - pMild_);

            // Probability of fatal given hospitalized,
            // and no hospital capacity
            double pfs_nohos = pfs_hos * (1.0 + pfat_increase_nohospital_);

            // weighted probability of fatal
            // given hospital capacity
            double H = Hrec + Hfat;
            double pfs = pfs_hos;
            if ((H > Hc_) && (H > 0)) {
                pfs = (Hc_ / H) * pfs_hos + (H - Hc_) / H * pfs_nohos;
            }

            // Get the current R0 from the table
            auto upper = std::lower_bound(R0Table_.begin(), R0Table_.end(),
                                          R0TableElement(0, t));
            //[](const R0TableElement &a, double b) { return a.second < b; });
            if (upper != R0Table_.begin())
                upper--;
            double R0 = upper->R0;

            // Update time derivates:

            // Susceptible population:
            // out: susceptible to exposed
            auto dS = -R0 / Tinf_ * Inf * S;
            // Exposed population:
            // in: susceptible to exposed
            // out: exposed to infectious
            auto dE = -dS - E / Tinc_;
            // Infectious population:
            // in: exposed to infectious
            // out: infectious to mild or hospital
            auto dI = E / Tinc_ - Inf / Tinf_;
            // Mild case population:
            // in: infectious to mild
            // out: mild to recovered (removed)
            auto dM = pMild_ * Inf / Tinf_ - M / Tmrec_;
            // Hospitalized population that will recover
            // in: infectious to hospitalized
            // out: hospitalized to recovered
            auto dHrec =
                (1.0 - pMild_) * (1.0 - pfs) * Inf / Tinf_ - Hrec / Threc_;
            // Hospitalized population that will not recover
            // in: infectious to hospitalized
            // out: hospitalized to fatal
            auto dHfat = (1.0 - pMild_) * pfs * Inf / Tinf_ - Hfat / Tfat_;
            // Fatal population:
            // in: hospitalized to fatal
            auto dF = Hfat / Tfat_;
            // Removed population:
            // in: mild to recovered
            // in: hospitalized to recovered
            auto dRm = M / Tmrec_;
            auto dRh = Hrec / Threc_;

            statedot[0] = dS;
            statedot[1] = dE;
            statedot[2] = dI;
            statedot[3] = dM;
            statedot[4] = dHrec;
            statedot[5] = dHfat;
            statedot[6] = dF;
            statedot[7] = dRm;
            statedot[8] = dRh;
        },
        state0, 0.0, (double)duration_, dt_,
        [this, &counter, &results](const StateType &s, double t) {
            results[counter] = std::vector<double>(s.begin(), s.end());
            counter++;
        });

    // Multiply results by population
    double population = this->population_;
    for (auto &res : results) {
        std::transform(res.begin(), res.end(), res.begin(),
                       [population](double &c) { return population * c; });
    }
    return results;
}

#if 0
int main(int argc, char *argv[])
{
    SEIR seir;
    seir.Hc_ = .001;
    seir.R0Table_.clear();
    seir.R0Table_.push_back(R0TableElement(4.5, 0));
    seir.R0Table_.push_back(R0TableElement(1.5, 150));

    auto results = seir.compute();
    auto lr = results[results.size() - 1];
#if 0
    for (int i = 0; i < NSTATES; i++) {
        std::cout << lr[i] << std::endl;
    }
#endif
    return 0;
}
#endif


#include "k_points_util.h"

#include <algorithm>
#include <boost/functional/hash.hpp>
#include <cmath>
#include <exception>
#include <unordered_set>
#include "../array_math.h"

int KPointsUtil::get_n_k_points(const double rcut) {
  int count = 0;
  const int n_max = std::floor(static_cast<float>(rcut));
  double rcut_square = rcut * rcut;
  for (int i = -n_max; i <= n_max; i++) {
    for (int j = -n_max; j <= n_max; j++) {
      for (int k = -n_max; k <= n_max; k++) {
        if (i * i + j * j + k * k > rcut_square) continue;
        count++;
      }
    }
  }
  return count;
}

std::vector<std::array<int8_t, 3>> KPointsUtil::generate_k_points(
    const double rcut) {
  std::vector<std::array<int8_t, 3>> k_points;
  const int n_max = std::floor(static_cast<float>(rcut));
  assert(n_max < 127);
  double rcut_square = rcut * rcut;
  for (int i = -n_max; i <= n_max; i++) {
    for (int j = -n_max; j <= n_max; j++) {
      for (int k = -n_max; k <= n_max; k++) {
        if (i * i + j * j + k * k > rcut_square) continue;
        k_points.push_back(std::array<int8_t, 3>({static_cast<int8_t>(i),
                                                  static_cast<int8_t>(j),
                                                  static_cast<int8_t>(k)}));
      }
    }
  }
  std::stable_sort(
      k_points.begin(),
      k_points.end(),
      [](const auto& a, const auto& b) -> bool {
        return squared_norm(a) < squared_norm(b);
      });
  return k_points;
}

std::vector<std::array<int8_t, 3>> KPointsUtil::get_k_diffs(
    const std::vector<std::array<int8_t, 3>>& k_points) {
  // Generate all possible differences between two different k points.
  std::unordered_set<std::array<int8_t, 3>, boost::hash<std::array<int8_t, 3>>>
      k_diffs_set;
  std::vector<std::array<int8_t, 3>> k_diffs;
  const int n_k_points = k_points.size();
  for (int p = 0; p < n_k_points; p++) {
    for (int q = 0; q < n_k_points; q++) {
      if (p == q) continue;
      const auto& diff_pq = k_points[q] - k_points[p];
      if (k_diffs_set.count(diff_pq) == 1) continue;
      k_diffs.push_back(diff_pq);
      k_diffs_set.insert(diff_pq);
    }
  }

  // Sort k_diffs into ascending order so that later sorting hci queue will be
  // faster.
  std::stable_sort(
      k_diffs.begin(), k_diffs.end(), [](const auto& a, const auto& b) -> bool {
        return squared_norm(a) < squared_norm(b);
      });

  return k_diffs;
}