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

#ifndef DCS_MATH_STATS_DISTRIBUTION_NORMAL_HPP
#define DCS_MATH_STATS_DISTRIBUTION_NORMAL_HPP

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

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

#include <boost/math/distributions/normal.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include <cstddef>
#include <dcs/math/policies/policy.hpp>
#include <iostream>
#include <vector>

namespace dcs { namespace math { namespace stats {

using ::std::size_t;

/**
 * The Normal distribution with parameter \f$\mu\f$ (the mean) and \f$\sigma\f$
 * (the standard deviation).
 *
 * \tparam RealT The type used for real numbers.
 * \tparam PolicyT The policy type.
 *
 * \author Marco Guazzone (marco.guazzone@gmail.com)
 */
template < typename RealT=double, typename PolicyT=::dcs::math::policies::policy<> >
class normal_distribution
{
	public: typedef RealT support_type;
	public: typedef RealT value_type;
	public: typedef PolicyT policy_type;

	public: explicit normal_distribution(support_type mean=0, support_type stddev=1)
		: dist_(mean,stddev)
//		: mean_(mean),
//		  stddev_(stddev)
	{
		// empty
	}

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

	/**
	 * \brief Generate a vector of random numbers distributed according to this
	 * normal distribution.
	 *
	 * \param rng A uniform random number generator.
	 * \param n The number of random numbers to generate.
	 * \return A random number distributed according to this normal
	 * distribution.
	 *
	 * A \c normal random number distribution produces random numbers * \f$x\f$
	 * distributed according to the probability density function:
	 * \f[
	 *   \Pr(x|\mu,\sigma) = \frac{1}{\sigma\sqrt{2\pi}}\exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)
	 * \f]
	 */
	public: template <typename UniformRandomGeneratorT>
		support_type rand(UniformRandomGeneratorT& rng) const
	{
		typedef ::boost::normal_distribution<support_type> rdist_type;
		typedef ::boost::variate_generator<UniformRandomGeneratorT&, rdist_type> variate_type;

		return variate_type(rng, rdist_type(dist_.mean(), dist_.standard_deviation()))();
	}

	/**
	 * \brief Generate a vector of random numbers distributed according to this
	 * normal distribution.
	 *
	 * \param rng A uniform random number generator.
	 * \param n The number of random numbers to generate.
	 * \return A vector of random numbers distributed according to this
	 * normal distribution.
	 *
	 * A \c normal random number distribution produces random numbers * \f$x\f$
	 * distributed according to the probability density function:
	 * \f[
	 *   \Pr(x|\mu,\sigma) = \frac{1}{\sigma\sqrt{2\pi}}\exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)
	 * \f]
	 */
	public: template <typename UniformRandomGeneratorT>
		::std::vector<support_type> rand(UniformRandomGeneratorT& rng, size_t n)
	{
		typedef ::boost::normal_distribution<support_type> rdist_type;
        typedef ::boost::variate_generator<UniformRandomGeneratorT&, rdist_type> variate_type;

		::std::vector<support_type> rnds(n);

        for ( ; n > 0; --n)
		{
			rnds.push_back(variate_type(rng, rdist_type(dist_.mean(), dist_.standard_deviation()))());
		}

		return rnds;
	}

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

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

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

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

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

	//private: support_type mean_;
	//private: support_type stddev_;
	private: ::boost::math::normal_distribution<value_type,policy_type> dist_;
};

template <
	typename CharT,
	typename CharTraitsT,
	typename RealT,
	typename PolicyT
>
::std::basic_ostream<CharT,CharTraitsT>& operator<<(::std::basic_ostream<CharT,CharTraitsT>& os, normal_distribution<RealT,PolicyT> const& dist)
{
	return os << "Normal("
			  << "mean=" <<  dist.mean()
			  << ", stddev=" <<  dist.stddev()
			  << ")";
}

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

#endif // DCS_MATH_STATS_DISTRIBUTION_NORMAL_HPP


//
// Created by matt on 20/01/2021.
//

#include <iostream>

#include <Eigen/Core>

#include "simple_kf.h"
#include "standard_kf.h"
#include "extended_kf.h"
#include "unscented_kf.h"
#include "system.h"
#include "numeric_differentiation.h"

int main() {

  const int iterations = 3;
  using SS = systems::SimpleSystem;
  const SS::MeasurementVector z(1.0, 2.0);

  ////////////////////////////////////////////////////////////////////
  std::cout << "Simple kalman filter\n";
  kf0::KalmanFilter skf;
  skf.SetCov(systems::SimpleSystem::StateMatrix::Identity());
  std::cout << "Intial state:\n";
  std::cout << skf.GetState().transpose() << '\n';
  std::cout << skf.GetCov() << '\n';
  for (int i = 0; i < iterations; i++) {
    std::cout << "Iteration: " << i << '\n';
    std::cout << "Predict: " << skf.Predict().transpose() << '\n';
    std::cout << skf.GetCov() << '\n';
    std::cout << "Measurement: " << (z * (double) i).transpose() << "\n";
    std::cout << "Update: " << skf.Update(z * (double) i).transpose() << "\n";
    std::cout << skf.GetCov() << "\n\n";
  }

  ////////////////////////////////////////////////////////////////////
  std::cout << "Templated kalman filter 1\n";

  kf1::KalmanFilter<4> kf1;
  kf1::ProcessModel<4> process = [](const Eigen::Matrix<double, 4, 1>& x) -> Eigen::Matrix<double, 4, 1> {
    Eigen::Matrix<double, 4, 4> F = Eigen::Matrix<double, 4, 4>::Identity();
    F(0, 2) = 0.1; // px' = px + vx*dt
    F(1, 3) = 0.1; // py' = py + vy*dt
    return F * x;
  };
  kf1::MeasurementModel<4, 2> measurement_model = [](
      const Eigen::Matrix<double, 4, 1>& x) -> Eigen::Matrix<double, 2, 1> {
    Eigen::Matrix<double, 2, 4> H = Eigen::Matrix<double, 2, 4>::Zero();
    // Measure position only
    H(0, 0) = 1.0;
    H(1, 1) = 1.0;
    return H * x;
  };

  kf1.SetCov(Eigen::Matrix4d::Identity());
  Eigen::Matrix4d Q = Eigen::Matrix4d::Identity() * 2.0;
  Eigen::Matrix2d R = Eigen::Matrix2d::Identity();

  std::cout << kf1.GetState().transpose() << '\n';
  std::cout << kf1.GetCov() << '\n';
  for (int i = 0; i < iterations; i++) {
    std::cout << "Iteration: " << i << '\n';
    kf1.Predict(process, Q);
    std::cout << "Predict: " << kf1.GetState().transpose() << '\n';
    std::cout << kf1.GetCov() << '\n';
    kf1.Update<2>(measurement_model, z * (double) i, R);
    std::cout << "Measurement: " << (z * (double) i).transpose() << "\n";
    std::cout << "Update: " << kf1.GetState().transpose() << "\n\n";
    std::cout << kf1.GetCov() << '\n';
  }

  ////////////////////////////////////////////////////////////////////

  std::cout << "Templated kalman filter 2\n";

  kf2::KalmanFilter<4, 2>::ProcessModel process_model_2 = [](
      const Eigen::Matrix<double, 4, 1>& x) -> Eigen::Matrix<double, 4, 1> {
    Eigen::Matrix<double, 4, 4> F = Eigen::Matrix<double, 4, 4>::Identity();
    F(0, 2) = 0.1; // px' = px + vx*dt
    F(1, 3) = 0.1; // py' = py + vy*dt
    return F * x;
  };
  kf2::KalmanFilter<4, 2>::MeasurementModel measurement_model_2 = [](
      const Eigen::Matrix<double, 4, 1>& x) -> Eigen::Matrix<double, 2, 1> {
    Eigen::Matrix<double, 2, 4> H = Eigen::Matrix<double, 2, 4>::Zero();
    // Measure position only
    H(0, 0) = 1.0;
    H(1, 1) = 1.0;
    return H * x;
  };

  kf2::KalmanFilter<4, 2> kf2(process_model_2, measurement_model_2);
  kf2.SetCov(Eigen::Matrix4d::Identity());

  for (int i = 0; i < iterations; i++) {
    std::cout << "Iteration: " << i << '\n';
    kf2.Predict(Q);
    std::cout << "Predict: " << kf2.GetState().transpose() << '\n';
    kf2.Update(z * (double) i, R);
    std::cout << "Measurement: " << (z * (double) i).transpose() << "\n";
    std::cout << "Update: " << kf2.GetState().transpose() << "\n\n";
  }

  ////////////////////////////////////////////////////////////////////
  std::cout << "Templated kalman filter 3\n";

  kf3::KalmanFilter<4, 2, SS> kf_3;
  kf_3.SetCov(Eigen::Matrix4d::Identity());

  for (int i = 0; i < iterations; i++) {
    std::cout << "Iteration: " << i << '\n';
    kf_3.Predict(Q);
    std::cout << "Predict: " << kf_3.GetState().transpose() << '\n';
    kf_3.Update(z * (double) i, R);
    std::cout << "Measurement: " << (z * (double) i).transpose() << "\n";
    std::cout << "Update: " << kf_3.GetState().transpose() << "\n\n";
  }

  ////////////////////////////////////////////////////////////////////
  std::cout << "Templated kalman filter 4\n";

  kf4::KalmanFilter<SS> kf_4;
  kf_4.SetCov(Eigen::Matrix4d::Identity());

  for (int i = 0; i < iterations; i++) {
    std::cout << "Iteration: " << i << '\n';
    kf_4.Predict(Q);
    std::cout << "Predict: " << kf_4.GetState().transpose() << '\n';
    kf_4.Update(z * (double) i, R);
    std::cout << "Measurement: " << (z * (double) i).transpose() << "\n";
    std::cout << "Update: " << kf_4.GetState().transpose() << "\n\n";
  }

  ////////////////////////////////////////////////////////////////////
  std::cout << "Another system\n";

  const Eigen::Vector3d z3(1.0, 2.0, 0.5);
  Eigen::Matrix<double, 6, 6> Q3 = Eigen::Matrix<double, 6, 6>::Identity() * 2.0;
  Eigen::Matrix3d R3 = Eigen::Matrix3d::Identity();

  kf4::KalmanFilter<systems::AnotherSystem> kf_5;
  for (int i = 0; i < iterations; i++) {
    std::cout << "Iteration: " << i << '\n';
    kf_5.Predict(Q3);
    std::cout << "Predict: " << kf_5.GetState().transpose() << '\n';
    kf_5.Update(z3 * (double) i, R3);
    std::cout << "Measurement: " << (z3 * (double) i).transpose() << "\n";
    std::cout << "Update: " << kf_5.GetState().transpose() << "\n\n";
  }

  ////////////////////////////////////////////////////////////////////
  std::cout << "Kalman filter using dynamic sized matrices\n";

  Eigen::VectorXd starting_state = Eigen::VectorXd::Zero(4);
  kf5::KalmanFilter<systems_dynamic::SimpleSystem> kf_6(starting_state);
  kf_6.SetCov(Eigen::MatrixXd::Identity(4, 4));

  for (int i = 0; i < iterations; i++) {
    std::cout << "Iteration: " << i << '\n';
    kf_6.Predict(Q);
    std::cout << "Predict: " << kf_6.GetState().transpose() << '\n';
    kf_6.Update(z * (double) i, R);
    std::cout << "Measurement: " << (z * (double) i).transpose() << "\n";
    std::cout << "Update: " << kf_6.GetState().transpose() << "\n\n";
  }

  ////////////////////////////////////////////////////////////////////

  Eigen::Vector4d current_state = Eigen::Vector4d::Zero();

  SS::StateMatrix JF = numeric_differentiation::CalculateJacobian<SS::kStateSize, SS::kStateSize>(current_state, SS::processModel);
  std::cout << JF << "\n\n";
  Eigen::Matrix<double, SS::kMeasurementSize, SS::kStateSize> JH =
      numeric_differentiation::CalculateJacobian<SS::kStateSize, SS::kMeasurementSize>(current_state, SS::measurementModel);
  std::cout << JH << "\n\n";

  ////////////////////////////////////////////////////////////////////
  std::cout << "EKF 1 (with linear system) \n";

  ekf1::ExtendedKalmanFilter<SS> ekf_1;
  ekf_1.SetCov(SS::StateMatrix::Identity());

  for (int i = 0; i < iterations; i++) {
    std::cout << "Iteration: " << i << '\n';
    ekf_1.Predict(Q);
    std::cout << "Predict: " << ekf_1.GetState().transpose() << '\n';
    ekf_1.Update(z * (double) i, R);
    std::cout << "Measurement: " << (z * (double) i).transpose() << "\n";
    std::cout << "Update: " << ekf_1.GetState().transpose() << "\n\n";
  }

  ////////////////////////////////////////////////////////////////////
  std::cout << "EKF 1 (with non-linear system) \n";

  using NLS = systems::NonLinearSystem;
  ekf1::ExtendedKalmanFilter<NLS> ekf_2;
  NLS::ProcessNoiseMatrix Q_nl = NLS::ProcessNoiseMatrix::Identity() * 10.0;
  NLS::MeasurementNoiseMatrix R_nl = NLS::MeasurementNoiseMatrix::Identity() * 1.0;

  ekf_2.SetCov(Q_nl);
  for (int i = 0; i < iterations; i++) {
    std::cout << "Iteration: " << i << '\n';
    ekf_2.Predict(Q_nl);
    std::cout << "Predict: " << ekf_2.GetState().transpose() << '\n';
    ekf_2.Update(z, R_nl);
    std::cout << "Measurement: " << z.transpose() << "\n";
    std::cout << "Update: " << ekf_2.GetState().transpose() << "\n\n";
  }

 // velocity is estimated correctly after a few iterations
 // why doesn't heading work out?

  ////////////////////////////////////////////////////////////////////
  std::cout << "Unscented kalman filter 1\n";

  ukf1::UnscentedKalmanFilter<SS> ukf_1;
  ukf_1.SetCov(SS::StateMatrix::Identity());

  std::cout << ukf_1.GetState().transpose() << '\n';
  std::cout << ukf_1.GetCov() << '\n';
  for (int i = 0; i < iterations; i++) {
    std::cout << "Iteration: " << i << '\n';
    ukf_1.Predict(Q);
    std::cout << "Predict: " << ukf_1.GetState().transpose() << '\n';
    ukf_1.Update(z * (double) i, R);
    std::cout << "Measurement: " << (z * (double) i).transpose() << "\n";
    std::cout << "Update: " << ukf_1.GetState().transpose() << "\n";
  }

  return 0;
}


/* test_exponential.cpp
 *
 * Copyright Steven Watanabe 2011
 * 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)
 *
 * $Id$
 *
 */

#include <boost/random/exponential_distribution.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/math/distributions/exponential.hpp>

#define BOOST_RANDOM_DISTRIBUTION boost::random::exponential_distribution<>
#define BOOST_RANDOM_DISTRIBUTION_NAME exponential
#define BOOST_MATH_DISTRIBUTION boost::math::exponential
#define BOOST_RANDOM_ARG1_TYPE double
#define BOOST_RANDOM_ARG1_NAME lambda
#define BOOST_RANDOM_ARG1_DEFAULT 1000.0
#define BOOST_RANDOM_ARG1_DISTRIBUTION(n) boost::uniform_real<>(0.0001, n)

#include "test_real_distribution.ipp"


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG, www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also tools/license/license.mtl.txt in the distribution.

#ifndef MTL_MAT_KRON_INCLUDE
#define MTL_MAT_KRON_INCLUDE

#include <cstddef>

#include <boost/numeric/mtl/matrix/dense2D.hpp>

// #include <boost/numeric/mtl/operation/set_to_zero.hpp>

namespace mtl { namespace mat {

/// Kronecker product (prototype implementation)
// A full-fledged version will be in MTL5 (with expression templates and allowing for arbitrary mixed types)
template <typename Value, typename Para>
dense2D<Value, Para> kron(const dense2D<Value, Para>& A, const dense2D<Value, Para>& B)
{
    using std::size_t;
    size_t nra= num_rows(A), nca= num_cols(A), nrb= num_rows(B), ncb= num_cols(B);

    dense2D<Value, Para> P(nra*nrb, nca*ncb);
    for (size_t ra= 0; ra < nra; ++ra)
        for (size_t ca= 0; ca < nca; ++ca)
            for (size_t rb= 0; rb < nrb; ++rb)
                for (size_t cb= 0; cb < ncb; ++cb)
                    P[ra*nrb+rb][ca*ncb+cb]= A[ra][ca] * B[rb][cb];
    return P;
}

}} // namespace mtl::mat

#endif // MTL_MAT_KRON_INCLUDE


#include <iostream>
#include <dlib/matrix.h>

int main()
{
    // declare a matrix
    dlib::matrix<double, 3, 1> y;
    dlib::matrix<double, 3, 3> M;
    // initialize matrices
    M = 54.2, 65.2, 43,
        23.4, 12.3, 55.4,
        11, 34.6, 78.9;
    y = 3.5,
        1.2,
        6.7;
    // solve this linear system
    dlib::matrix<double> x = dlib::inv(M)*y;
    std::cout << "x: \n" << x << "\n";
    // check if the calculation is correct
    std::cout << "Check: M*x- y= \n" << M*x - y << "\n";

    // sum the matrix
    double sumM = 0;
    for (int r = 0; r < M.nr(); ++r)
    {
        for (int c = 0; c < M.nc(); ++c)
        {
            sumM += M(r, c);
        }
    }
    std::cout << "Sum of matrix by looping= " << sumM << "\n";
    std::cout << "Sum of matrix by sum() function= " << dlib::sum(M) << "\n";
    // print using comma separator
    std::cout <<"print matrix M using comma delimiter: \n" <<dlib::csv << M << "\n";

    std::system("pause");
    return 0;
}


/**
 * @file gradientflow.h
 * @brief NPDE homework GradientFlow code
 * @author Unknown, Oliver Rietmann
 * @date 04.04.2021
 * @copyright Developed at ETH Zurich
 */

#include "gradientflow.h"

#include <Eigen/Core>
#include <cmath>
#include <vector>

namespace GradientFlow {

/* SAM_LISTING_BEGIN_0 */
Eigen::MatrixXd ButcherMatrix() {
  Eigen::MatrixXd A(6, 5);
  // clang-format off
  A <<       0.25,          0.,       0.,       0.,   0.,
    0.5,        0.25,       0.,       0.,   0.,
    17./50.,     -1./25.,     0.25,       0.,   0.,
    371./1360., -137./2720., 15./544.,     0.25,   0.,
    25./24.,    -49./48., 125./16., -85./12., 0.25,
    25./24.,    -49./48., 125./16., -85./12., 0.25;
  // clang-format on
  return A;
}
/* SAM_LISTING_END_0 */

/* SAM_LISTING_BEGIN_1 */
std::vector<Eigen::VectorXd> solveGradientFlow(const Eigen::VectorXd &d,
                                               double lambda,
                                               const Eigen::VectorXd &y,
                                               double T, unsigned int N) {
  std::vector<Eigen::VectorXd> Y(N + 1);
  // TO DO (0-2.h)
#if SOLUTION
  // Define the right hand side of the ODE y' = f(y), and the Jacobian of f.
  auto f = [d, lambda](const Eigen::VectorXd &yt) {
    Eigen::VectorXd val =
        -2. * std::cos(yt.squaredNorm()) * yt - 2. * lambda * d.dot(yt) * d;
    return val;
  };
  auto J = [d, lambda](const Eigen::VectorXd &yt) {
    int dim = yt.size();
    Eigen::MatrixXd term1 =
        4. * std::sin(yt.squaredNorm()) * yt * yt.transpose();
    Eigen::MatrixXd term2 =
        -2. * std::cos(yt.squaredNorm()) * Eigen::MatrixXd::Identity(dim, dim);
    Eigen::MatrixXd term3 = -2. * lambda * d * d.transpose();
    Eigen::MatrixXd Jval = term1 + term2 + term3;
    return Jval;
  };

  // Split the interval [0,T] into N intervals of size h.
  double h = T / N;
  Eigen::VectorXd yt = y;
  Y.at(0) = y;
  // Evolve up to time T:
  for (int i = 1; i <= N; i++) {
    yt = discEvolSDIRK(f, J, yt, h);
    Y.at(i) = yt;
  }
#else
  //====================
  // Your code goes here
  //====================
#endif
  return Y;
}
/* SAM_LISTING_END_1 */

}  // namespace GradientFlow


//          Copyright Gunter Winkler 2004 - 2009.
// 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/ublas/matrix.hpp>
#include <boost/numeric/ublas/triangular.hpp>

#include <boost/numeric/ublas/io.hpp>

using std::cout;
using std::endl;

namespace ublas = boost::numeric::ublas;

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

  ublas::matrix<double> M (3, 3);
  for (std::size_t i=0; i < M.data().size(); ++i) { M.data()[i] = 1+i ; }

  std::cout << "full         M  = " << M << "\n" ;

  ublas::triangular_matrix<double, ublas::lower> L;
  ublas::triangular_matrix<double, ublas::unit_lower> UL;
  ublas::triangular_matrix<double, ublas::strict_lower> SL;

  L  = ublas::triangular_adaptor<ublas::matrix<double>, ublas::lower> (M);
  SL = ublas::triangular_adaptor<ublas::matrix<double>, ublas::strict_lower> (M);
  UL = ublas::triangular_adaptor<ublas::matrix<double>, ublas::unit_lower> (M);

  std::cout << "lower        L  = " << L << "\n"
            << "strict lower SL = " << SL << "\n"
            << "unit lower   UL = " << UL << "\n" ;

  ublas::triangular_matrix<double, ublas::upper> U;
  ublas::triangular_matrix<double, ublas::unit_upper> UU;
  ublas::triangular_matrix<double, ublas::strict_upper> SU;

  U =  ublas::triangular_adaptor<ublas::matrix<double>, ublas::upper> (M);
  SU = ublas::triangular_adaptor<ublas::matrix<double>, ublas::strict_upper> (M);
  UU = ublas::triangular_adaptor<ublas::matrix<double>, ublas::unit_upper> (M);

  std::cout << "upper        U  = " << U << "\n"
            << "strict upper SU = " << SU << "\n"
            << "unit upper   UU = " << UU << "\n" ;

  std::cout << "M = L + SU ? " << ((norm_inf( M - (L + SU) ) == 0.0)?"ok":"failed") << "\n";
  std::cout << "M = U + SL ? " << ((norm_inf( M - (U + SL) ) == 0.0)?"ok":"failed") << "\n";

}


/**
 * @file matode.cc
 * @brief NPDE homework MatODE code
 * @copyright Developed at ETH Zurich
 */

#include <Eigen/Dense>

namespace MatODE {

/* SAM_LISTING_BEGIN_3 */
	Eigen::MatrixXd eeulstep(const Eigen::MatrixXd &A, const Eigen::MatrixXd &Y0,
	                         double h) {
		return Y0 + A * Y0 * h;
	}
/* SAM_LISTING_END_3 */

/* SAM_LISTING_BEGIN_4 */
	Eigen::MatrixXd ieulstep(const Eigen::MatrixXd &A, const Eigen::MatrixXd &Y0,
	                         double h) {

		Eigen::MatrixXd Y1 = (Eigen::MatrixXd::Identity(A.rows(), A.cols()) - h * A).lu().solve(Y0);

		return Y1;
	}
/* SAM_LISTING_END_4 */

/* SAM_LISTING_BEGIN_5 */
	Eigen::MatrixXd impstep(const Eigen::MatrixXd &A, const Eigen::MatrixXd &Y0,
	                        double h) {

		Eigen::MatrixXd I = Eigen::MatrixXd::Identity(A.rows(), A.cols());

		return 	(I - 0.5 * h * A).lu().solve((I + 0.5 * h * A) * Y0);
	}
/* SAM_LISTING_END_5 */

}  // namespace MatODE


#include <boost/log/trivial.hpp>
#include "../multivariate_guassian.hpp"
#include "gtest/gtest.h"

namespace FilterModel {
const double ERROR = 0.0001;

TEST(density, OneDimensionalAtZeroCorrect) {
    MultivariateGuassian normal({0}, {{1}});
    ASSERT_NEAR(normal.density({0.0}), 1.0 / std::sqrt(2.0 * M_PI), ERROR);
}

TEST(density, OneDimensionalAtOneCorrect) {
    MultivariateGuassian normal({0}, {{1}});
    ASSERT_NEAR(normal.density({1.0}), std::exp(-1.0 / 2.0) / std::sqrt(2.0 * M_PI), ERROR);
}

TEST(density, OneDimensionalAtTwoCorrect) {
    MultivariateGuassian normal({0}, {{1}});
    ASSERT_NEAR(normal.density({2.0}), std::exp(-4.0 / 2.0) / std::sqrt(2.0 * M_PI), ERROR);
}

TEST(density, TwoDimensionalAtZeroCorrect) {
    MultivariateGuassian normal({0, 0}, {{1, 0}, {0, 1}});
    ASSERT_NEAR(normal.density({0.0, 0.0}), std::exp(0) / (2.0 * M_PI), ERROR);
}

}  // namespace FilterModel


#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 */


/* boost random/negative_binomial_distribution.hpp header file
 *
 * Copyright Steven Watanabe 2010
 * 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)
 *
 * See http://www.boost.org for most recent version including documentation.
 *
 * $Id$
 */

#ifndef BOOST_RANDOM_NEGATIVE_BINOMIAL_DISTRIBUTION_HPP_INCLUDED
#define BOOST_RANDOM_NEGATIVE_BINOMIAL_DISTRIBUTION_HPP_INCLUDED

#include <iosfwd>

#include <boost/limits.hpp>
#include <boost/random/detail/config.hpp>
#include <boost/random/gamma_distribution.hpp>
#include <boost/random/poisson_distribution.hpp>

namespace boost {
namespace random {

/**
 * The negative binomial distribution is an integer valued
 * distribution with two parameters, @c k and @c p.  The
 * distribution produces non-negative values.
 *
 * The distribution function is
 * \f$\displaystyle P(i) = {k+i-1\choose i}p^k(1-p)^i\f$.
 *
 * This implementation uses a gamma-poisson mixture.
 */
template<class IntType = int, class RealType = double>
class negative_binomial_distribution {
public:
    typedef IntType result_type;
    typedef RealType input_type;

    class param_type {
    public:
        typedef negative_binomial_distribution distribution_type;
        /**
         * Construct a param_type object.  @c k and @c p
         * are the parameters of the distribution.
         *
         * Requires: k >=0 && 0 <= p <= 1
         */
        explicit param_type(IntType k_arg = 1, RealType p_arg = RealType (0.5))
          : _k(k_arg), _p(p_arg)
        {}
        /** Returns the @c k parameter of the distribution. */
        IntType k() const { return _k; }
        /** Returns the @c p parameter of the distribution. */
        RealType p() const { return _p; }
#ifndef BOOST_RANDOM_NO_STREAM_OPERATORS
        /** Writes the parameters of the distribution to a @c std::ostream. */
        template<class CharT, class Traits>
        friend std::basic_ostream<CharT,Traits>&
        operator<<(std::basic_ostream<CharT,Traits>& os,
                   const param_type& parm)
        {
            os << parm._p << " " << parm._k;
            return os;
        }

        /** Reads the parameters of the distribution from a @c std::istream. */
        template<class CharT, class Traits>
        friend std::basic_istream<CharT,Traits>&
        operator>>(std::basic_istream<CharT,Traits>& is, param_type& parm)
        {
            is >> parm._p >> std::ws >> parm._k;
            return is;
        }
#endif
        /** Returns true if the parameters have the same values. */
        friend bool operator==(const param_type& lhs, const param_type& rhs)
        {
            return lhs._k == rhs._k && lhs._p == rhs._p;
        }
        /** Returns true if the parameters have different values. */
        friend bool operator!=(const param_type& lhs, const param_type& rhs)
        {
            return !(lhs == rhs);
        }
    private:
        IntType _k;
        RealType _p;
    };

    /**
     * Construct a @c negative_binomial_distribution object. @c k and @c p
     * are the parameters of the distribution.
     *
     * Requires: k >=0 && 0 <= p <= 1
     */
    explicit negative_binomial_distribution(IntType k_arg = 1,
                                            RealType p_arg = RealType(0.5))
      : _k(k_arg), _p(p_arg)
    {}

    /**
     * Construct an @c negative_binomial_distribution object from the
     * parameters.
     */
    explicit negative_binomial_distribution(const param_type& parm)
      : _k(parm.k()), _p(parm.p())
    {}

    /**
     * Returns a random variate distributed according to the
     * negative binomial distribution.
     */
    template<class URNG>
    IntType operator()(URNG& urng) const
    {
        gamma_distribution<RealType> gamma(_k, (1-_p)/_p);
        poisson_distribution<IntType, RealType> poisson(gamma(urng));
        return poisson(urng);
    }

    /**
     * Returns a random variate distributed according to the negative
     * binomial distribution with parameters specified by @c param.
     */
    template<class URNG>
    IntType operator()(URNG& urng, const param_type& parm) const
    {
        return negative_binomial_distribution(parm)(urng);
    }

    /** Returns the @c k parameter of the distribution. */
    IntType k() const { return _k; }
    /** Returns the @c p parameter of the distribution. */
    RealType p() const { return _p; }

    /** Returns the smallest value that the distribution can produce. */
    IntType min BOOST_PREVENT_MACRO_SUBSTITUTION() const { return 0; }
    /** Returns the largest value that the distribution can produce. */
    IntType max BOOST_PREVENT_MACRO_SUBSTITUTION() const
    { return (std::numeric_limits<IntType>::max)(); }

    /** Returns the parameters of the distribution. */
    param_type param() const { return param_type(_k, _p); }
    /** Sets parameters of the distribution. */
    void param(const param_type& parm)
    {
        _k = parm.k();
        _p = parm.p();
    }

    /**
     * Effects: Subsequent uses of the distribution do not depend
     * on values produced by any engine prior to invoking reset.
     */
    void reset() { }

#ifndef BOOST_RANDOM_NO_STREAM_OPERATORS
    /** Writes the parameters of the distribution to a @c std::ostream. */
    template<class CharT, class Traits>
    friend std::basic_ostream<CharT,Traits>&
    operator<<(std::basic_ostream<CharT,Traits>& os,
               const negative_binomial_distribution& bd)
    {
        os << bd.param();
        return os;
    }

    /** Reads the parameters of the distribution from a @c std::istream. */
    template<class CharT, class Traits>
    friend std::basic_istream<CharT,Traits>&
    operator>>(std::basic_istream<CharT,Traits>& is,
               negative_binomial_distribution& bd)
    {
        bd.read(is);
        return is;
    }
#endif

    /** Returns true if the two distributions will produce the same
        sequence of values, given equal generators. */
    friend bool operator==(const negative_binomial_distribution& lhs,
                           const negative_binomial_distribution& rhs)
    {
        return lhs._k == rhs._k && lhs._p == rhs._p;
    }
    /** Returns true if the two distributions could produce different
        sequences of values, given equal generators. */
    friend bool operator!=(const negative_binomial_distribution& lhs,
                           const negative_binomial_distribution& rhs)
    {
        return !(lhs == rhs);
    }

private:

    /// @cond \show_private

    template<class CharT, class Traits>
    void read(std::basic_istream<CharT, Traits>& is) {
        param_type parm;
        if(is >> parm) {
            param(parm);
        }
    }

    // parameters
    IntType _k;
    RealType _p;

    /// @endcond
};

}

}

#endif


/*
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;
}


// test computeCost
#include <cassert>
#include <armadillo>
#include "../../ex1/computeCost.hpp"
#include "../../ex1/util.hpp"

using namespace std;
using namespace arma;

int main()
{
    fmat X;
    fvec y, theta;

    parse_mat(&X, "[1,2; 1,3; 1,4; 1,5]");
    parse_mat(&y, "[7;6;5;4]");
    parse_mat(&theta, "[0.1;0.2]");

    auto J = computeCost(X, y, theta);
    cout << "J = " << J << endl;
    assert(J == 11.945f);

    parse_mat(&X, "[1,2,3; 1,3,4; 1,4,5; 1,5,6]");
    parse_mat(&y, "[7;6;5;4]");
    parse_mat(&theta, "[0.1;0.2;0.3]");

    J = computeCost(X, y, theta);
    cout << "J = " << J << endl;
    assert(J == 7.0175f);

    return 0;
}


#include <iostream>
#include <string>
#include <algorithm>
#include <utility> // std::pair, std::make_pair
#include <cmath> // float comparison
#include <limits>
#include <cstdint>  // for std::uint8_t

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE "persistent_cohomology"
#include <boost/test/unit_test.hpp>

#include <gudhi/graph_simplicial_complex.h>
#include <gudhi/reader_utils.h>
#include <gudhi/Simplex_tree.h>
#include <gudhi/Persistent_cohomology.h>

using namespace Gudhi;
using namespace Gudhi::persistent_cohomology;
using namespace boost::unit_test;

typedef Simplex_tree<> typeST;

std::string test_rips_persistence(int coefficient, int min_persistence) {
  // file is copied in CMakeLists.txt
  std::ifstream simplex_tree_stream;
  simplex_tree_stream.open("simplex_tree_file_for_unit_test.txt");
  typeST st;
  simplex_tree_stream >> st;
  simplex_tree_stream.close();

  // Display the Simplex_tree
  std::cout << "The complex contains " << st.num_simplices() << " simplices" << " - dimension= " << st.dimension()
      << std::endl;

  // Check
  BOOST_CHECK(st.num_simplices() == 98);
  BOOST_CHECK(st.dimension() == 3);

  // Sort the simplices in the order of the filtration
  st.initialize_filtration();

  // Compute the persistence diagram of the complex
  Persistent_cohomology<Simplex_tree<>, Field_Zp> pcoh(st);

  pcoh.init_coefficients( coefficient );  // initializes the coefficient field for homology
  // Check infinite rips
  pcoh.compute_persistent_cohomology( min_persistence );  // Minimal lifetime of homology feature to be recorded.
  std::ostringstream ossInfinite;

  pcoh.output_diagram(ossInfinite);
  std::string strInfinite = ossInfinite.str();
  return strInfinite;
}

void test_rips_persistence_in_dimension(int dimension) {
  std::string value0("  0 0.02 1.12");
  std::string value1("  0 0.03 1.13");
  std::string value2("  0 0.04 1.14");
  std::string value3("  0 0.05 1.15");
  std::string value4("  0 0.06 1.16");
  std::string value5("  0 0.07 1.17");
  std::string value6("  0 0.08 1.18");
  std::string value7("  0 0.09 1.19");
  std::string value8("  0 0 inf"    );
  std::string value9("  0 0.01 inf" );

  value0.insert(0,std::to_string(dimension));
  value1.insert(0,std::to_string(dimension));
  value2.insert(0,std::to_string(dimension));
  value3.insert(0,std::to_string(dimension));
  value4.insert(0,std::to_string(dimension));
  value5.insert(0,std::to_string(dimension));
  value6.insert(0,std::to_string(dimension));
  value7.insert(0,std::to_string(dimension));
  value8.insert(0,std::to_string(dimension));
  value9.insert(0,std::to_string(dimension));

  std::cout << "********************************************************************" << std::endl;
  std::cout << "TEST OF RIPS_PERSISTENT_COHOMOLOGY_SINGLE_FIELD DIM=" << dimension << " MIN_PERS=0" << std::endl;

  std::string str_rips_persistence = test_rips_persistence(dimension, 0);
  std::cout << str_rips_persistence << std::endl;

  BOOST_CHECK(str_rips_persistence.find(value0) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value1) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value2) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value3) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value4) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value5) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value6) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value7) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value8) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value9) != std::string::npos); // Check found
  std::cout << "str_rips_persistence=" << str_rips_persistence << std::endl;

  std::cout << "********************************************************************" << std::endl;
  std::cout << "TEST OF RIPS_PERSISTENT_COHOMOLOGY_SINGLE_FIELD DIM=" << dimension << " MIN_PERS=1" << std::endl;

  str_rips_persistence = test_rips_persistence(dimension, 1);

  BOOST_CHECK(str_rips_persistence.find(value0) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value1) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value2) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value3) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value4) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value5) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value6) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value7) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value8) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value9) != std::string::npos); // Check found
  std::cout << "str_rips_persistence=" << str_rips_persistence << std::endl;

  std::cout << "********************************************************************" << std::endl;
  std::cout << "TEST OF RIPS_PERSISTENT_COHOMOLOGY_SINGLE_FIELD DIM=" << dimension << " MIN_PERS=2" << std::endl;

  str_rips_persistence = test_rips_persistence(dimension, 2);

  BOOST_CHECK(str_rips_persistence.find(value0) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value1) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value2) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value3) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value4) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value5) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value6) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value7) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value8) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value9) != std::string::npos); // Check found
  std::cout << "str_rips_persistence=" << str_rips_persistence << std::endl;

  std::cout << "********************************************************************" << std::endl;
  std::cout << "TEST OF RIPS_PERSISTENT_COHOMOLOGY_SINGLE_FIELD DIM=" << dimension << " MIN_PERS=Inf" << std::endl;

  str_rips_persistence = test_rips_persistence(dimension, (std::numeric_limits<int>::max)());

  BOOST_CHECK(str_rips_persistence.find(value0) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value1) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value2) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value3) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value4) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value5) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value6) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value7) == std::string::npos); // Check not found
  BOOST_CHECK(str_rips_persistence.find(value8) != std::string::npos); // Check found
  BOOST_CHECK(str_rips_persistence.find(value9) != std::string::npos); // Check found
  std::cout << "str_rips_persistence=" << str_rips_persistence << std::endl;
}

BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_1 )
{
  test_rips_persistence_in_dimension(1);
}

BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_2 )
{
  test_rips_persistence_in_dimension(2);
}

BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_3 )
{
  test_rips_persistence_in_dimension(3);
}

BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_5 )
{
  test_rips_persistence_in_dimension(5);
}

// TODO(VR): not working from 6
// std::string str_rips_persistence = test_rips_persistence(6, 0);
// TODO(VR): division by zero
// std::string str_rips_persistence = test_rips_persistence(0, 0);

/** SimplexTree minimal options to test the limits.
 *
 * Maximum number of simplices to compute persistence is <CODE>std::numeric_limits<std::uint8_t>::max()<\CODE> = 256.*/
struct MiniSTOptions {
  typedef linear_indexing_tag Indexing_tag;
  typedef short Vertex_handle;
  typedef double Filtration_value;
  // Maximum number of simplices to compute persistence is 2^8 - 1 = 255. One is reserved for null_key
  typedef std::uint8_t Simplex_key;
  static const bool store_key = true;
  static const bool store_filtration = false;
  static const bool contiguous_vertices = false;
};

using Mini_simplex_tree = Gudhi::Simplex_tree<MiniSTOptions>;
using Mini_st_persistence =
    Gudhi::persistent_cohomology::Persistent_cohomology<Mini_simplex_tree, Gudhi::persistent_cohomology::Field_Zp>;

BOOST_AUTO_TEST_CASE( persistence_constructor_exception )
{
  Mini_simplex_tree st;

  // To make number of simplices = 255
  const short simplex_0[] = {0, 1, 2, 3, 4, 5, 6, 7};
  st.insert_simplex_and_subfaces(simplex_0);

  // Sort the simplices in the order of the filtration
  st.initialize_filtration();

  BOOST_CHECK(st.num_simplices() <= std::numeric_limits<MiniSTOptions::Simplex_key>::max());
  // Class for homology computation
  BOOST_CHECK_NO_THROW(Mini_st_persistence pcoh(st));

  st.insert_simplex({8});
  BOOST_CHECK(st.num_simplices() > std::numeric_limits<MiniSTOptions::Simplex_key>::max());
  // Class for homology computation
  BOOST_CHECK_THROW(Mini_st_persistence pcoh2(st), std::out_of_range);

}


#include <iostream>
#include <cmath>
#include <vector>
#include <boost/numeric/mtl/mtl.hpp>

int main(int, char**)
{
    using namespace mtl;
    dense_vector<double>                                        cd(5, 1.0);
    std::vector<dense_vector<double> >                          v(5, cd);

    for (unsigned i= 0, c= 1; i < size(v); ++i)
	for (unsigned j= 0; j < size(v[i]); ++j, c++)
	    v[i][j]= double((i + j) % 5);

    std::cout << "w initially\n";
    std::vector<dense_vector<double> > 				w(v),x(v);
    for (unsigned i= 0; i < size(w); ++i)
	std::cout << w[i] << "\n";

    orth(w);
    std::cout << "\nw orthogonalized\n";
    for (unsigned i= 0; i < size(w); ++i)
	std::cout << w[i] << "\n";

    std::cout << "\nTest: dot product of orthogonalized w\n";
    for (unsigned i= 0, c= 1; i < size(w); ++i) {
	for (unsigned j= 0; j < size(w); ++j, ++c)
	    std::cout << (dot(w[i], w[j])<1.e-8 ? 0 : dot(w[i], w[j]) )<< " " ;
	std::cout << "\n";
    }
    std::cout << "\nThe according factors are: \n" << orthogonalize_factors(v) << '\n';

    orth(x,0); orth(x,1);
    std::cout << "\nw(0), w(1) orthogonalized\n";
    for (unsigned i= 0; i < size(w); ++i)
	std::cout << x[i] << "\n";

    return 0;
}


#include "main.h"

#include <Eigen/CXX11/Tensor>

using Eigen::Tensor;
using Eigen::RowMajor;

static void test_comparison_sugar() {
  // we already trust comparisons between tensors, we're simply checking that
  // the sugared versions are doing the same thing
  Tensor<int, 3> t(6, 7, 5);

  t.setRandom();
  // make sure we have at least one value == 0
  t(0,0,0) = 0;

  Tensor<bool,0> b;

#define TEST_TENSOR_EQUAL(e1, e2) \
  b = ((e1) == (e2)).all();       \
  VERIFY(b())

#define TEST_OP(op) TEST_TENSOR_EQUAL(t op 0, t op t.constant(0))

  TEST_OP(==);
  TEST_OP(!=);
  TEST_OP(<=);
  TEST_OP(>=);
  TEST_OP(<);
  TEST_OP(>);
#undef TEST_OP
#undef TEST_TENSOR_EQUAL
}

static void test_scalar_sugar_add_mul() {
  Tensor<float, 3> A(6, 7, 5);
  Tensor<float, 3> B(6, 7, 5);
  A.setRandom();
  B.setRandom();

  const float alpha = 0.43f;
  const float beta = 0.21f;
  const float gamma = 0.14f;

  Tensor<float, 3> R = A.constant(gamma) + A * A.constant(alpha) + B * B.constant(beta);
  Tensor<float, 3> S = A * alpha + B * beta + gamma;
  Tensor<float, 3> T = gamma + alpha * A + beta * B;

  for (int i = 0; i < 6*7*5; ++i) {
    VERIFY_IS_APPROX(R(i), S(i));
    VERIFY_IS_APPROX(R(i), T(i));
  }
}

static void test_scalar_sugar_sub_div() {
  Tensor<float, 3> A(6, 7, 5);
  Tensor<float, 3> B(6, 7, 5);
  A.setRandom();
  B.setRandom();

  const float alpha = 0.43f;
  const float beta = 0.21f;
  const float gamma = 0.14f;
  const float delta = 0.32f;

  Tensor<float, 3> R = A.constant(gamma) - A / A.constant(alpha)
      - B.constant(beta) / B - A.constant(delta);
  Tensor<float, 3> S = gamma - A / alpha - beta / B - delta;

  for (int i = 0; i < 6*7*5; ++i) {
    VERIFY_IS_APPROX(R(i), S(i));
  }
}

void test_cxx11_tensor_sugar()
{
  CALL_SUBTEST(test_comparison_sugar());
  CALL_SUBTEST(test_scalar_sugar_add_mul());
  CALL_SUBTEST(test_scalar_sugar_sub_div());
}