/*============================================================================

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center (DKFZ)
All rights reserved.

Use of this source code is governed by a 3-clause BSD license that can be
found in the LICENSE file.

============================================================================*/

#include <mitkTestingMacros.h>
#include <mitkTestFixture.h>
#include "mitkIOUtil.h"
#include "itkArray2D.h"

#include <mitkLibSVMClassifier.h>
#include <itkLabelSampler.h>
#include <mitkImageCast.h>
#include <mitkStandaloneDataStorage.h>
#include <itkCSVArray2DFileReader.h>
#include <itkCSVArray2DDataObject.h>
#include <itkCSVNumericObjectFileWriter.h>

//#include <boost/algorithm/string.hpp>

class mitkLibSVMClassifierTestSuite : public mitk::TestFixture
{
  CPPUNIT_TEST_SUITE(mitkLibSVMClassifierTestSuite);
  MITK_TEST(TrainSVMClassifier_MatlabDataSet_shouldReturnTrue);
  MITK_TEST(TrainSVMClassifier_BreastCancerDataSet_shouldReturnTrue);
  CPPUNIT_TEST_SUITE_END();

private:

  typedef Eigen::Matrix<double ,Eigen::Dynamic,Eigen::Dynamic> MatrixDoubleType;
  typedef Eigen::Matrix<int, Eigen::Dynamic,Eigen::Dynamic> MatrixIntType;

  Eigen::MatrixXd m_TrainingMatrixX;
  Eigen::MatrixXi m_TrainingLabelMatrixY;
  Eigen::MatrixXd m_TestXPredict;
  Eigen::MatrixXi m_TestYPredict;

  mitk::LibSVMClassifier::Pointer classifier;

public:

  /*Reading an file, which includes the trainingdataset and the testdataset, and convert the
  content of the file into an 2dim matrixpair.
  There are an delimiter, which separates the matrix into an trainingmatrix and testmatrix */
  template<typename T>
  std::pair<Eigen::Matrix<T ,Eigen::Dynamic,Eigen::Dynamic>,Eigen::Matrix<T ,Eigen::Dynamic,Eigen::Dynamic> >convertCSVToMatrix(const std::string &path, char delimiter,double range, bool isXMatrix)
  {
    typename itk::CSVArray2DFileReader<T>::Pointer fr = itk::CSVArray2DFileReader<T>::New();
    fr->SetFileName(path);
    fr->SetFieldDelimiterCharacter(delimiter);
    fr->HasColumnHeadersOff();
    fr->HasRowHeadersOff();
    fr->Parse();
    try{
      fr->Update();
    }catch(itk::ExceptionObject& ex){
      cout << "Exception caught!" << std::endl;
      cout << ex << std::endl;
    }

    typename itk::CSVArray2DDataObject<T>::Pointer p = fr->GetOutput();
    unsigned int maxrowrange = p->GetMatrix().rows();
    unsigned int c = p->GetMatrix().cols();
    auto percentRange = (unsigned int)(maxrowrange*range);

    if(isXMatrix == true)
    {
      Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> trainMatrixX(percentRange,c);
      Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> testMatrixXPredict(maxrowrange-percentRange,c);

      for(unsigned int row = 0; row < percentRange; row++){
        for(unsigned int col = 0; col < c; col++){
          trainMatrixX(row,col) =  p->GetData(row,col);
        }
      }

      for(unsigned int row = percentRange; row < maxrowrange; row++){
        for(unsigned int col = 0; col < c; col++){
          testMatrixXPredict(row-percentRange,col) =  p->GetData(row,col);
        }
      }

      return std::make_pair(trainMatrixX,testMatrixXPredict);
    }
    else
    {
      Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> trainLabelMatrixY(percentRange,c);
      Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> testMatrixYPredict(maxrowrange-percentRange,c);

      for(unsigned int row = 0; row < percentRange; row++){
        for(unsigned int col = 0; col < c; col++){
          trainLabelMatrixY(row,col) =  p->GetData(row,col);
        }
      }

      for(unsigned int row = percentRange; row < maxrowrange; row++){
        for(unsigned int col = 0; col < c; col++){
          testMatrixYPredict(row-percentRange,col) =  p->GetData(row,col);
        }
      }

      return std::make_pair(trainLabelMatrixY,testMatrixYPredict);
    }
  }

  /*
  Reading an csv-data and transfer the included datas into an matrix.
  */
  template<typename T>
  Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> readCsvData(const std::string &path, char delimiter)
  {
    typename itk::CSVArray2DFileReader<T>::Pointer fr = itk::CSVArray2DFileReader<T>::New();
    fr->SetFileName(path);
    fr->SetFieldDelimiterCharacter(delimiter);
    fr->HasColumnHeadersOff();
    fr->HasRowHeadersOff();
    fr->Parse();
    try{
      fr->Update();
    }catch(itk::ExceptionObject& ex){
      cout << "Exception caught!" << std::endl;
      cout << ex << std::endl;
    }

    typename itk::CSVArray2DDataObject<T>::Pointer p = fr->GetOutput();
    unsigned int maxrowrange = p->GetMatrix().rows();
    unsigned int maxcols = p->GetMatrix().cols();
    Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> matrix(maxrowrange,maxcols);

    for(unsigned int rows = 0; rows < maxrowrange; rows++){
      for(unsigned int cols = 0; cols < maxcols; cols++ ){
        matrix(rows,cols) = p->GetData(rows,cols);
      }
    }

    return matrix;
  }

  /*
  Write the content of the array into an own csv-data in the following sequence:
  root.csv:   1    2    3    0   0    4
  writen.csv:     1  1:2  2:3  3:0 4:0  5:4
  */
  template<typename T>
  void writeMatrixToCsv(Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> paramMatrix,const std::string &path)
  {
    std::ofstream outputstream (path,std::ofstream::out);   // 682

    if(outputstream.is_open()){
      for(int i = 0; i < paramMatrix.rows(); i++){
        outputstream << paramMatrix(i,0);
        for(int j = 1; j < 11; j++){
          outputstream  << " " << j << ":" << paramMatrix(i,j);
        }
        outputstream << endl;
      }
      outputstream.close();
    }
    else{
      cout << "Unable to write into CSV" << endl;
    }
  }

  /*
  Train the classifier with an exampledataset of mattlab.
  Note: The included data are gaußan normaldistributed.
  */
  void TrainSVMClassifier_MatlabDataSet_shouldReturnTrue()
  {
    /* Declarating an featurematrixdataset, the first matrix
    of the matrixpair is the trainingmatrix and the second one is the testmatrix.*/
    std::pair<MatrixDoubleType,MatrixDoubleType> matrixDouble;
    matrixDouble = convertCSVToMatrix<double>(GetTestDataFilePath("Classification/FeaturematrixMatlab.csv"),';',0.5,true);
    m_TrainingMatrixX = matrixDouble.first;
    m_TestXPredict = matrixDouble.second;

    /* The declaration of the labelmatrixdataset is equivalent to the declaration
    of the featurematrixdataset.*/
    std::pair<MatrixIntType,MatrixIntType> matrixInt;
    matrixInt = convertCSVToMatrix<int>(GetTestDataFilePath("Classification/LabelmatrixMatlab.csv"),';',0.5,false);
    m_TrainingLabelMatrixY = matrixInt.first;
    m_TestYPredict = matrixInt.second;
    classifier = mitk::LibSVMClassifier::New();

    /* Setting of the SVM-Parameters*/
    classifier->SetGamma(1/(double)(m_TrainingMatrixX.cols()));
    classifier->SetSvmType(0);
    classifier->SetKernelType(0);

    /* Train the classifier, by giving trainingdataset for the labels and features.
    The result in an colunmvector of the labels.*/
    classifier->Train(m_TrainingMatrixX,m_TrainingLabelMatrixY);
    Eigen::MatrixXi classes = classifier->Predict(m_TestXPredict);

    /* Testing the matching between the calculated
    colunmvector and the result of the SVM */
    unsigned int maxrows = classes.rows();

    int count = 0;

    for (unsigned int i = 0; i < maxrows; i++)
    {
      if(classes(i, 0) == m_TestYPredict(i, 0))
        ++count;
    }

    MITK_INFO << 100*count/(double)(maxrows) << "%";
    MITK_TEST_CONDITION(isEqual<int>(m_TestYPredict,classes),"Expected vector and occured vector match.");
  }

  // Method of testing for assertions.
  template<typename T>
  bool isEqual(Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> expected, Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> actual)
  {
    bool isSimilar = true;
    unsigned int mrow = expected.rows();
    unsigned int mcol = expected.cols();
    for(unsigned int i = 0; i < mrow; i++){
      for(unsigned int j = 0; j < mcol; j++){
        if(expected(i,j) != actual(i,j)){
          isSimilar = false;
        }
      }
    }
    return isSimilar;
  }

  // Method of intervalltesting
  template<typename T>
  bool isIntervall(Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> expected, Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> actual, double lowrange, double toprange)
  {
    bool isInIntervall = false;
    int count = 0;
    unsigned int rowRange = expected.rows();
    unsigned int colRange = expected.cols();
    for(unsigned int i = 0; i < rowRange; i++){
      for(unsigned int j = 0; j < colRange; j++){
        if(expected(i,j) == actual(i,j)){
          count++;
        }
      }

      double valueOfMatch = 100*count/(double)(rowRange);
      if((lowrange <= valueOfMatch) && (toprange >= valueOfMatch)){
        isInIntervall = true;
      }
    }
    return isInIntervall;
  }

  /*
  Train the classifier with the dataset of breastcancer patients from the
  LibSVM Libary
  */
  void TrainSVMClassifier_BreastCancerDataSet_shouldReturnTrue()
  {
    /* Declarating an featurematrixdataset, the first matrix
    of the matrixpair is the trainingmatrix and the second one is the testmatrix.*/
    std::pair<MatrixDoubleType,MatrixDoubleType> matrixDouble;
    matrixDouble = convertCSVToMatrix<double>(GetTestDataFilePath("Classification/FeaturematrixBreastcancer.csv"),';',0.5,true);
    m_TrainingMatrixX = matrixDouble.first;
    m_TestXPredict = matrixDouble.second;

    /* The declaration of the labelmatrixdataset is equivalent to the declaration
    of the featurematrixdataset.*/
    std::pair<MatrixIntType,MatrixIntType> matrixInt;
    matrixInt = convertCSVToMatrix<int>(GetTestDataFilePath("Classification/LabelmatrixBreastcancer.csv"),';',0.5,false);
    m_TrainingLabelMatrixY = matrixInt.first;
    m_TestYPredict = matrixInt.second;

    /* Setting of the SVM-Parameters*/
    classifier = mitk::LibSVMClassifier::New();
    classifier->SetGamma(1/(double)(m_TrainingMatrixX.cols()));
    classifier->SetSvmType(0);
    classifier->SetKernelType(2);

    /* Train the classifier, by giving trainingdataset for the labels and features.
    The result in an colunmvector of the labels.*/
    classifier->Train(m_TrainingMatrixX,m_TrainingLabelMatrixY);
    Eigen::MatrixXi classes = classifier->Predict(m_TestXPredict);

    /* Testing the matching between the calculated colunmvector and the result
    of the SVM */
    unsigned int maxrows = classes.rows();

    int count = 0;

    for (unsigned int i = 0; i < maxrows; i++)
    {
      if (classes(i, 0) == m_TestYPredict(i, 0))
        ++count;
    }

    MITK_INFO << 100*count/(double)(maxrows) << "%";
    MITK_TEST_CONDITION(isIntervall<int>(m_TestYPredict,classes,75,100),"Testvalue is in range.");
  }

  void TestThreadedDecisionForest()
  {
  }
};

MITK_TEST_SUITE_REGISTRATION(mitkLibSVMClassifier)


/**
 * @ file transformedconslaw_main.cc
 * @ brief NPDE exam problem
 * @ author Oliver Rietmann
 * @ date 05.08.2021
 * @ copyright Developed at SAM, ETH Zurich
 */

#include <Eigen/Core>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <utility>

#include "transformedconslaw.h"

const static Eigen::IOFormat CSVFormat(Eigen::FullPrecision,
                                       Eigen::DontAlignCols, ", ", "\n");

int main() {
  // Setup
  TRFCL::NonStdCauchyProblemCL prb;
  int M = 500;
  int N = 500;

  // Compute cell points and inital data for plot
  std::pair<double, double> limits = prb.domain();
  Eigen::VectorXd x =
      Eigen::VectorXd::LinSpaced(N, limits.first, limits.second);
  auto z0 = [&prb](double y) { return prb.z0(y); };
  Eigen::VectorXd zeta0 = x.unaryExpr(z0);

  // Print conserved quantity
  auto rho = [&prb](double z) { return prb.rho(z); };
  auto rec = [rho, limits](double t, const Eigen::VectorXd &zeta) -> void {
    Eigen::VectorXd weighted_samples = zeta.unaryExpr(rho) / (zeta.size() - 1);
    double rho_z_integral =
        weighted_samples.sum() * (limits.second - limits.first);
    std::cout << "Integral of rho(z(x,t)) at time t = " << t << ":\t"
              << rho_z_integral << std::endl;
  };

  // Compute solution at final time
  std::cout << std::fixed;
  Eigen::VectorXd zetaT = solveCauchyPrb(M, N, prb, rec);

  // Write x, zeta0 and zetaT to .csv file
  std::ofstream solution_file;
  solution_file.open(CURRENT_BINARY_DIR "/solution.csv");
  solution_file << x.transpose().format(CSVFormat) << std::endl;
  solution_file << zeta0.transpose().format(CSVFormat) << std::endl;
  solution_file << zetaT.transpose().format(CSVFormat) << std::endl;
  solution_file.close();
  std::cout << "Generated " CURRENT_BINARY_DIR "/solution.csv" << std::endl;

  // Plot from .csv file using plot.py
  std::system("python3 " CURRENT_SOURCE_DIR "/plot.py " CURRENT_BINARY_DIR
              "/solution.csv " CURRENT_BINARY_DIR "/solution.eps");

  return 0;
}


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

#ifdef _MSC_VER
#  define _SCL_SECURE_NO_WARNINGS
#endif

#include "test.hpp"
#include <boost/multiprecision/mpfi.hpp>
#include <boost/multiprecision/random.hpp>

using namespace boost::multiprecision;
using namespace boost::random;

void test_exp()
{
   std::cout << "Testing exp\n";

   mpfr_float_50 val = 1.25;

   for(unsigned i = 0; i < 2000; ++i)
   {
      mpfr_float_100 a(val);
      mpfr_float_100 b = exp(a);
      mpfi_float_50 in = val;
      in = exp(in);
      BOOST_CHECK((boost::math::isfinite)(in));
      BOOST_CHECK(lower(in) <= b);
      BOOST_CHECK(upper(in) >= b);
      b = log(a);
      in = val;
      in = log(in);
      BOOST_CHECK((boost::math::isfinite)(in));
      BOOST_CHECK(lower(in) <= b);
      BOOST_CHECK(upper(in) >= b);
      val *= 1.01;
   }
   val = 1;
   for(unsigned i = 0; i < 2000; ++i)
   {
      mpfr_float_100 a(val);
      mpfr_float_100 b = exp(a);
      mpfi_float_50 in = val;
      in = exp(in);
      BOOST_CHECK((boost::math::isfinite)(in));
      BOOST_CHECK(lower(in) <= b);
      BOOST_CHECK(upper(in) >= b);
      b = log(a);
      in = val;
      in = log(in);
      BOOST_CHECK((boost::math::isfinite)(in));
      BOOST_CHECK(lower(in) <= b);
      BOOST_CHECK(upper(in) >= b);
      val /= 1.01;
   }
}

void test_pow()
{
   std::cout << "Testing pow function\n";

   mt19937 gen;
   uniform_real_distribution<mpfr_float_50> dist1(0, 400);

   for(unsigned i = 0; i < 5000; ++i)
   {
      mpfr_float_50 base, p;
      base = dist1(gen);
      p = dist1(gen);
      mpfr_float_100 a, b, r;
      a = base;
      b = p;
      r = pow(a, b);
      mpfi_float_50 ai, bi, ri;
      ai = base;
      bi = p;
      ri = pow(ai, bi);
      BOOST_CHECK((boost::math::isfinite)(ri));
      BOOST_CHECK(lower(ri) <= r);
      BOOST_CHECK(upper(ri) >= r);
   }
}

void test_trig()
{
   std::cout << "Testing trig functions\n";

   mt19937 gen;
   uniform_real_distribution<mpfr_float_50> dist1(-1.57079632679, 1.57079632679);
   uniform_real_distribution<mpfr_float_50> dist2(-1, 1);

   for(unsigned i = 0; i < 5000; ++i)
   {
      mpfr_float_50 val;
      val = dist1(gen);
      mpfr_float_100 a = val;
      mpfr_float_100 b = sin(a);
      mpfi_float_50 a2 = val;
      mpfi_float_50 b2 = sin(a2);
      BOOST_CHECK((boost::math::isfinite)(b2));
      BOOST_CHECK(lower(b2) <= b);
      BOOST_CHECK(upper(b2) >= b);
      b = cos(a);
      b2 = cos(a2);
      BOOST_CHECK((boost::math::isfinite)(b2));
      BOOST_CHECK(lower(b2) <= b);
      BOOST_CHECK(upper(b2) >= b);
      b = tan(a);
      b2 = tan(a2);
      BOOST_CHECK((boost::math::isfinite)(b2));
      BOOST_CHECK(lower(b2) <= b);
      BOOST_CHECK(upper(b2) >= b);
   }
   for(unsigned i = 0; i < 5000; ++i)
   {
      mpfr_float_50 val;
      val = dist2(gen);
      mpfr_float_100 a = val;
      mpfr_float_100 b = asin(a);
      mpfi_float_50 a2 = val;
      mpfi_float_50 b2 = asin(a2);
      BOOST_CHECK((boost::math::isfinite)(b2));
      BOOST_CHECK(lower(b2) <= b);
      BOOST_CHECK(upper(b2) >= b);
      b = acos(a);
      b2 = acos(a2);
      BOOST_CHECK((boost::math::isfinite)(b2));
      BOOST_CHECK(lower(b2) <= b);
      BOOST_CHECK(upper(b2) >= b);
      b = atan(a);
      b2 = atan(a2);
      BOOST_CHECK((boost::math::isfinite)(b2));
      BOOST_CHECK(lower(b2) <= b);
      BOOST_CHECK(upper(b2) >= b);
   }
}

void test_hyp()
{
   std::cout << "Testing hyperbolic trig functions\n";

   mt19937 gen;
   uniform_real_distribution<mpfr_float_50> dist1(-10, 10);
   uniform_real_distribution<mpfr_float_50> dist2(-1, 1);

   for(unsigned i = 0; i < 5000; ++i)
   {
      mpfr_float_50 val;
      val = dist1(gen);
      mpfr_float_100 a = val;
      mpfr_float_100 b = sinh(a);
      mpfi_float_50 a2 = val;
      mpfi_float_50 b2 = sinh(a2);
      BOOST_CHECK((boost::math::isfinite)(b2));
      BOOST_CHECK(lower(b2) <= b);
      BOOST_CHECK(upper(b2) >= b);
      b = cosh(a);
      b2 = cosh(a2);
      BOOST_CHECK((boost::math::isfinite)(b2));
      BOOST_CHECK(lower(b2) <= b);
      BOOST_CHECK(upper(b2) >= b);
      b = tanh(a);
      b2 = tanh(a2);
      BOOST_CHECK((boost::math::isfinite)(b2));
      BOOST_CHECK(lower(b2) <= b);
      BOOST_CHECK(upper(b2) >= b);
   }
}

void test_intervals()
{
   mpfi_float_50 a(1, 2);
   mpfi_float_50 b(1.5, 2.5);
   BOOST_CHECK_EQUAL(lower(a), 1);
   BOOST_CHECK_EQUAL(upper(a), 2);
   BOOST_CHECK_EQUAL(median(a), 1.5);
   BOOST_CHECK_EQUAL(width(a), 1);
   mpfi_float_50 r = intersect(a, b);
   BOOST_CHECK_EQUAL(lower(r), 1.5);
   BOOST_CHECK_EQUAL(upper(r), 2);
   r = hull(a, b);
   BOOST_CHECK_EQUAL(lower(r), 1);
   BOOST_CHECK_EQUAL(upper(r), 2.5);
   BOOST_CHECK(overlap(a, b));
   BOOST_CHECK(in(mpfr_float_50(1.5), a));
   BOOST_CHECK(in(mpfr_float_50(1), a));
   BOOST_CHECK(in(mpfr_float_50(2), a));
   BOOST_CHECK(!zero_in(a));
   b = mpfi_float_50(1.5, 1.75);
   BOOST_CHECK(subset(b, a));
   BOOST_CHECK(proper_subset(b, a));
   BOOST_CHECK(!empty(a));
   BOOST_CHECK(!singleton(a));
   b = mpfi_float_50(5, 6);
   r = intersect(a, b);
   BOOST_CHECK(empty(r));
}

#ifdef TEST_SPECIAL
#include "math/table_type.hpp"
#include <boost/math/special_functions.hpp>

#define T mpfi_float_50

typedef number<mpfi_float_backend<25> > mpfi_float_25;

void test_log1p_expm1()
{
#  include "../../math/test/log1p_expm1_data.ipp"

   std::cout << std::setprecision(std::numeric_limits<mpfi_float_25>::max_digits10);
   std::cout << "Testing log1p and expm1\n";

   for(unsigned i = 0; i < log1p_expm1_data.size(); ++i)
   {
      mpfi_float_25 in(log1p_expm1_data[i][0]);
      mpfi_float_25 out = boost::math::log1p(in);
      mpfi_float_25 expected(log1p_expm1_data[i][1]);
      if(!subset(expected, out))
      {
         std::cout << in << std::endl;
         std::cout << out << std::endl;
         std::cout << expected << std::endl;
         BOOST_CHECK(lower(out) <= lower(expected));
         BOOST_CHECK(upper(out) >= upper(expected));
      }
      out = boost::math::expm1(in);
      expected = mpfi_float_25(log1p_expm1_data[i][2]);
      if(!subset(expected, out))
      {
         std::cout << in << std::endl;
         std::cout << out << std::endl;
         std::cout << expected << std::endl;
         BOOST_CHECK(lower(out) <= lower(expected));
         BOOST_CHECK(upper(out) >= upper(expected));
      }
   }
}

void test_bessel()
{
#include "../../math/test/bessel_i_int_data.ipp"
#include "../../math/test/bessel_i_data.ipp"

   std::cout << std::setprecision(std::numeric_limits<mpfi_float_25>::max_digits10);
   std::cout << "Testing Bessel Functions\n";

   for(unsigned i = 0; i < bessel_i_int_data.size(); ++i)
   {
      int v = boost::lexical_cast<int>(static_cast<const char*>(bessel_i_int_data[i][0]));
      mpfi_float_25 in(bessel_i_int_data[i][1]);
      mpfi_float_25 out = boost::math::cyl_bessel_i(v, in);
      mpfi_float_25 expected(bessel_i_int_data[i][2]);
      if(!subset(expected, out))
      {
         std::cout << in << std::endl;
         std::cout << out << std::endl;
         std::cout << expected << std::endl;
         BOOST_CHECK(lower(out) <= lower(expected));
         BOOST_CHECK(upper(out) >= upper(expected));
      }
   }
}

#endif

int main()
{
#ifdef TEST_SPECIAL
   test_log1p_expm1();
   test_bessel();
#endif
   test_intervals();
   test_exp();
   test_pow();
   test_trig();
   test_hyp();
   return boost::report_errors();
}


//  (C) Copyright Gennadiy Rozental 2001-2005.
//  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/libs/test for the library home page.
//
//  File        : $RCSfile: floating_point_comparison.hpp,v $
//
//  Version     : $Revision: 1.23 $
//
//  Description : defines algoirthms for comparing 2 floating point values
// ***************************************************************************

#ifndef BOOST_TEST_FLOATING_POINT_COMPARISON_HPP_071894GER
#define BOOST_TEST_FLOATING_POINT_COMPARISON_HPP_071894GER

#include <boost/limits.hpp>  // for std::numeric_limits

#include <boost/test/utils/class_properties.hpp>

#include <boost/test/detail/suppress_warnings.hpp>

//____________________________________________________________________________//

namespace boost {

namespace test_tools {

using unit_test::readonly_property;

// ************************************************************************** //
// **************        floating_point_comparison_type        ************** //
// ************************************************************************** //

enum floating_point_comparison_type { FPC_STRONG, FPC_WEAK };

// ************************************************************************** //
// **************                    details                   ************** //
// ************************************************************************** //

namespace tt_detail {

template<typename FPT>
inline FPT
fpt_abs( FPT arg )
{
    return arg < 0 ? -arg : arg;
}

//____________________________________________________________________________//

// both f1 and f2 are unsigned here
template<typename FPT>
inline FPT
safe_fpt_division( FPT f1, FPT f2 )
{
    return  (f2 < 1 && f1 > f2 * (std::numeric_limits<FPT>::max)())               ? (std::numeric_limits<FPT>::max)()
            : ((f2 > 1 && f1 < f2 * (std::numeric_limits<FPT>::min)() || f1 == 0) ? 0
                                                                                : f1/f2 );
}

//____________________________________________________________________________//

} // namespace tt_detail

// ************************************************************************** //
// **************             close_at_tolerance               ************** //
// ************************************************************************** //

template<typename FPT, typename PersentType = FPT >
class close_at_tolerance {
public:
    // Public typedefs
    typedef bool result_type;

    // Constructor
    explicit    close_at_tolerance( PersentType percentage_tolerance, floating_point_comparison_type fpc_type = FPC_STRONG )
    : p_fraction_tolerance( static_cast<FPT>(0.01)*percentage_tolerance ), p_strong_or_weak( fpc_type ==  FPC_STRONG ) {}

    bool        operator()( FPT left, FPT right ) const
    {
        FPT diff = tt_detail::fpt_abs( left - right );
        FPT d1   = tt_detail::safe_fpt_division( diff, tt_detail::fpt_abs( right ) );
        FPT d2   = tt_detail::safe_fpt_division( diff, tt_detail::fpt_abs( left ) );

        return p_strong_or_weak ? (d1 <= p_fraction_tolerance.get() && d2 <= p_fraction_tolerance.get())
                                : (d1 <= p_fraction_tolerance.get() || d2 <= p_fraction_tolerance.get());
    }

    // Public properties
    readonly_property<FPT>  p_fraction_tolerance;
    readonly_property<bool> p_strong_or_weak;
};

//____________________________________________________________________________//

// ************************************************************************** //
// **************               check_is_close                 ************** //
// ************************************************************************** //

struct check_is_close_t {
    // Public typedefs
    typedef bool result_type;

    template<typename FPT, typename PersentType>
    bool
    operator()( FPT left, FPT right, PersentType percentage_tolerance, floating_point_comparison_type fpc_type = FPC_STRONG )
    {
        close_at_tolerance<FPT,PersentType> pred( percentage_tolerance, fpc_type );

        return pred( left, right );
    }
};

namespace {
check_is_close_t check_is_close;
}

//____________________________________________________________________________//

// ************************************************************************** //
// **************               check_is_small                 ************** //
// ************************************************************************** //

struct check_is_small_t {
    // Public typedefs
    typedef bool result_type;

    template<typename FPT>
    bool
    operator()( FPT fpv, FPT tolerance )
    {
        return tt_detail::fpt_abs( fpv ) < tolerance;
    }
};

namespace {
check_is_small_t check_is_small;
}

//____________________________________________________________________________//

} // namespace test_tools

} // namespace boost

//____________________________________________________________________________//

#include <boost/test/detail/enable_warnings.hpp>

// ***************************************************************************
//  Revision History :
//
//  $Log: floating_point_comparison.hpp,v $
//  Revision 1.23  2005/05/29 08:54:57  rogeeff
//  allow bind usage
//
//  Revision 1.22  2005/02/21 10:21:40  rogeeff
//  check_is_small implemented
//  check functions implemented as function objects
//
//  Revision 1.21  2005/02/20 08:27:05  rogeeff
//  This a major update for Boost.Test framework. See release docs for complete list of fixes/updates
//
//  Revision 1.20  2005/02/01 06:40:06  rogeeff
//  copyright update
//  old log entries removed
//  minor stilistic changes
//  depricated tools removed
//
//  Revision 1.19  2005/01/22 19:22:12  rogeeff
//  implementation moved into headers section to eliminate dependency of included/minimal component on src directory
//
// ***************************************************************************

#endif // BOOST_FLOATING_POINT_COMAPARISON_HPP_071894GER


///////////////////////////////////////////////////////////////////////////////
// Authors: Ilgweon Kang and Lutong Wang
//          (respective Ph.D. advisors: Chung-Kuan Cheng, Andrew B. Kahng),
//          based on Dr. Jingwei Lu with ePlace and ePlace-MS
//
//          Many subsequent improvements were made by Mingyu Woo
//          leading up to the initial release.
//
// BSD 3-Clause License
//
// Copyright (c) 2018, The Regents of the University of California
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
//   contributors may be used to endorse or promote products derived from
//   this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
///////////////////////////////////////////////////////////////////////////////

#include <climits>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <ctime>

#include "global.h"
#include "initPlacement.h"
#include "plot.h"

#include <Eigen/Core>
#include <Eigen/SparseCore>
#include <Eigen/IterativeLinearSolvers>
#include <unsupported/Eigen/IterativeSolvers>

void initial_placement() {
  using namespace Eigen;
  printf("PROC:  Conjugate Gradient (CG) method to obtain the IP\n");

  int itol = 1;
  int itmax = 100;
  int x_iter = 0, y_iter = 0;

  prec tol = 0.000001;
  prec target_tol = 0.000001;
  prec x_err = 0, y_err = 0;

  double time_s = 0;

  HPWL_count();

  if(isSkipIP) {
    return;
  }

  printf("INFO:  The Initial HPWL is %.6lf\n", tot_HPWL);

  if(tot_HPWL <= 0) {
    printf("ERROR: HPWL <= 0, skip initial QP\n");
    return;
  }

  printf("INFO:  The Matrix Size is %d\n", moduleCNT);
  fflush(stdout);

  // malloc to solve PCG
  //
  // Ax = b
  //
  // x : variable vector to solve
  // b : constant vector
  //

  setNbThreads(numThread);

  // BCGSTAB settings
  SMatrix eMatX(moduleCNT, moduleCNT), eMatY(moduleCNT, moduleCNT);

  VectorXf xcg_x(moduleCNT), xcg_b(moduleCNT), ycg_x(moduleCNT),
      ycg_b(moduleCNT);

  for(int i = 0;; i++) {
    if(i >= numInitPlaceIter) {
      break;
    }

    time_start(&time_s);
    CreateSparseMatrix(xcg_x, xcg_b, ycg_x, ycg_b, eMatX, eMatY);

    BiCGSTAB< SMatrix, IdentityPreconditioner > solver;
    solver.setMaxIterations(itmax);

    solver.compute(eMatX);
    xcg_x = solver.solveWithGuess(xcg_b, xcg_x);
    x_err = solver.error();

    solver.compute(eMatY);
    ycg_x = solver.solveWithGuess(ycg_b, ycg_x);
    y_err = solver.error();

    update_module(xcg_x, ycg_x);
    update_pin_by_module();
    update_net_by_pin();
    HPWL_count();

    if(isPlot && i % 5 == 0) {
      SaveCellPlotAsJPEG(string("FIP - Iter: ") + to_string(i), false,
                         string(dir_bnd) + string("/initPlace/initPlacement_") +
                             intoFourDigit(i));
      // SavePlot( string("FIP - Iter: ") + to_string(i) );
    }

    time_end(&time_s);
    printf("INFO:  IP%3d,  CG Error %.6lf,  HPWL %.6lf,  CPUtime %.2lf\n", i,
           max(x_err, y_err), tot_HPWL, time_s);
    fflush(stdout);

    if(fabs(x_err) < target_tol && fabs(y_err) < target_tol && i > 4) {
      break;
    }
  }
}

void build_data_struct(bool initCoordi) {
  MODULE *mdp = NULL;
  TERM *term = NULL;
  PIN *pin = NULL;
  NET *curNet = NULL;
  FPOS pof;

  prec min_x = 0;
  prec min_y = 0;
  prec max_x = 0;
  prec max_y = 0;

  for(int i = 0; i < terminalCNT; i++) {
    term = &terminalInstance[i];

    for(int j = 0; j < term->pinCNTinObject; j++) {
      pin = term->pin[j];
      pin->tier = 0;
    }

  }

  for(int i = 0; i < moduleCNT; i++) {
    mdp = &moduleInstance[i];

    if(initCoordi) {
      mdp->center = place.center;
    }

    mdp->pmin.x = mdp->center.x - 0.5 * mdp->size.x;
    mdp->pmin.y = mdp->center.y - 0.5 * mdp->size.y;

    mdp->pmax.x = mdp->center.x + 0.5 * mdp->size.x;
    mdp->pmax.y = mdp->center.y + 0.5 * mdp->size.y;

    for(int j = 0; j < mdp->pinCNTinObject; j++) {
      pof = mdp->pof[j];
      pin = mdp->pin[j];

      pin->fp.x = mdp->center.x + pof.x;
      pin->fp.y = mdp->center.y + pof.y;

      pin->X_MIN = 0;
      pin->X_MAX = 0;
      pin->Y_MIN = 0;
      pin->Y_MAX = 0;
    }
  }

  term_pmin.x = PREC_MAX;
  term_pmin.x = PREC_MAX;
  term_pmax.x = 0;
  term_pmax.y = 0;

  for(int i = 0; i < terminalCNT; i++) {
    term = &terminalInstance[i];

    term->pmin.x = term->center.x - 0.5 * term->size.x;
    term->pmin.y = term->center.y - 0.5 * term->size.y;

    term->pmax.x = term->center.x + 0.5 * term->size.x;
    term->pmax.y = term->center.y + 0.5 * term->size.y;

    if(term_pmin.x > term->pmin.x)
      term_pmin.x = term->pmin.x;
    if(term_pmin.y > term->pmin.y)
      term_pmin.y = term->pmin.y;
    if(term_pmax.x < term->pmax.x)
      term_pmax.x = term->pmax.x;
    if(term_pmax.y < term->pmax.y)
      term_pmax.y = term->pmax.y;

    for(int j = 0; j < term->pinCNTinObject; j++) {
      pof = term->pof[j];
      pin = term->pin[j];

      pin->fp.x = term->center.x + pof.x;
      pin->fp.y = term->center.y + pof.y;

      pin->X_MIN = 0;
      pin->X_MAX = 0;
      pin->Y_MIN = 0;
      pin->Y_MAX = 0;
    }
  }

  for(int i = 0; i < netCNT; i++) {
    curNet = &netInstance[i];

    min_x = PREC_MAX;
    min_y = PREC_MAX;
    max_x = PREC_MIN;
    max_y = PREC_MIN;

    PIN *pin_xmin = NULL;
    PIN *pin_ymin = NULL;
    PIN *pin_xmax = NULL;
    PIN *pin_ymax = NULL;

    for(int j = 0; j < curNet->pinCNTinObject; j++) {
      pin = curNet->pin[j];
      if(pin_xmin) {
        if(min_x > pin->fp.x) {
          min_x = pin->fp.x;
          pin_xmin->X_MIN = 0;
          pin_xmin = pin;
          pin->X_MIN = 1;
        }
      }
      else {
        min_x = pin->fp.x;  // mdp->center.x ;
        pin_xmin = pin;
        pin->X_MIN = 1;
      }

      if(pin_ymin) {
        if(min_y > pin->fp.y)  // mdp->center.y)
        {
          min_y = pin->fp.y;  // mdp->center.y ;
          pin_ymin->Y_MIN = 0;
          pin_ymin = pin;
          pin->Y_MIN = 1;
        }
      }
      else {
        min_y = pin->fp.y;  // mdp->center.y ;
        pin_ymin = pin;
        pin->Y_MIN = 1;
      }

      if(pin_xmax) {
        if(max_x < pin->fp.x)  // mdp->center.x)
        {
          max_x = pin->fp.x;  // mdp->center.x ;
          pin_xmax->X_MAX = 0;
          pin_xmax = pin;
          pin->X_MAX = 1;
        }
      }
      else {
        max_x = pin->fp.x;  // mdp->center.x ;
        pin_xmax = pin;
        pin->X_MAX = 1;
      }

      if(pin_ymax) {
        if(max_y < pin->fp.y)  // mdp->center.y)
        {
          max_y = pin->fp.y;  // mdp->center.y ;
          pin_ymax->Y_MAX = 0;
          pin_ymax = pin;
          pin->Y_MAX = 1;
        }
      }
      else {
        max_y = pin->fp.y;  // mdp->center.y ;
        pin_ymax = pin;
        pin->Y_MAX = 1;
      }
    }

    curNet->min_x = min_x;
    curNet->min_y = min_y;

    curNet->max_x = max_x;
    curNet->max_y = max_y;
  }
}

void update_module(VectorXf &xcg_x, VectorXf &ycg_x) {
  MODULE *mdp = NULL;
  for(int i = 0; i < moduleCNT; i++) {
    mdp = &moduleInstance[i];

    mdp->center.x = xcg_x(i);
    mdp->center.y = ycg_x(i);

    if((mdp->center.x + 0.5 * mdp->size.x) > place.end.x)
      mdp->center.x = place.end.x - 0.5 * mdp->size.x - Epsilon;

    if((mdp->center.y + 0.5 * mdp->size.y) > place.end.y)
      mdp->center.y = place.end.y - 0.5 * mdp->size.y - Epsilon;

    if((mdp->center.x - 0.5 * mdp->size.x) < place.org.x)
      mdp->center.x = place.org.x + 0.5 * mdp->size.x + Epsilon;

    if((mdp->center.y - 0.5 * mdp->size.y) < place.org.y)
      mdp->center.y = place.org.y + 0.5 * mdp->size.y + Epsilon;

    mdp->pmin.x = mdp->center.x - 0.5 * mdp->size.x;
    mdp->pmin.y = mdp->center.y - 0.5 * mdp->size.y;

    mdp->pmax.x = mdp->center.x + 0.5 * mdp->size.x;
    mdp->pmax.y = mdp->center.y + 0.5 * mdp->size.y;
  }
}

void update_pin_by_module(void) {
  FPOS pof;
  PIN *pin = NULL;
  MODULE *mdp = NULL;

  for(int i = 0; i < moduleCNT; i++) {
    mdp = &moduleInstance[i];
    for(int j = 0; j < mdp->pinCNTinObject; j++) {
      pof = mdp->pof[j];
      pin = mdp->pin[j];

      if(pin->moduleID != i || pin->pinIDinModule != j || pin->term == 1) {
        exit(1);
      }

      pin->fp.x = mdp->center.x + pof.x;
      pin->fp.y = mdp->center.y + pof.y;

      pin->X_MIN = 0;
      pin->X_MAX = 0;
      pin->Y_MIN = 0;
      pin->Y_MAX = 0;
    }
  }
}

void update_net_by_pin() {
  for(int i = 0; i < netCNT; i++) {
    NET *curNet = &netInstance[i];

    PIN *pin_xmin = NULL, *pin_ymin = NULL;
    PIN *pin_xmax = NULL, *pin_ymax = NULL;

    prec min_x = PREC_MAX, min_y = PREC_MAX;
    prec max_x = PREC_MIN, max_y = PREC_MIN;

    for(int j = 0; j < curNet->pinCNTinObject; j++) {
      PIN *pin = curNet->pin[j];

      if(pin_xmin) {
        if(min_x > pin->fp.x)  // mdp->center.x)
        {
          min_x = pin->fp.x;  // mdp->center.x ;
          pin_xmin->X_MIN = 0;
          pin_xmin = pin;
          pin->X_MIN = 1;
        }
      }
      else {
        min_x = pin->fp.x;  // mdp->center.x ;
        pin_xmin = pin;
        pin->X_MIN = 1;
      }

      if(pin_ymin) {
        if(min_y > pin->fp.y)  // mdp->center.y)
        {
          min_y = pin->fp.y;  // mdp->center.y ;
          pin_ymin->Y_MIN = 0;
          pin_ymin = pin;
          pin->Y_MIN = 1;
        }
      }
      else {
        min_y = pin->fp.y;  // mdp->center.y ;
        pin_ymin = pin;
        pin->Y_MIN = 1;
      }

      if(pin_xmax) {
        if(max_x < pin->fp.x)  // mdp->center.x)
        {
          max_x = pin->fp.x;  // mdp->center.x ;
          pin_xmax->X_MAX = 0;
          pin_xmax = pin;
          pin->X_MAX = 1;
        }
      }
      else {
        max_x = pin->fp.x;  // mdp->center.x ;
        pin_xmax = pin;
        pin->X_MAX = 1;
      }

      if(pin_ymax) {
        if(max_y < pin->fp.y)  // mdp->center.y)
        {
          max_y = pin->fp.y;  // mdp->center.y ;
          pin_ymax->Y_MAX = 0;
          pin_ymax = pin;
          pin->Y_MAX = 1;
        }
      }
      else {
        max_y = pin->fp.y;  // mdp->center.y ;
        pin_ymax = pin;
        pin->Y_MAX = 1;
      }
    }
    curNet->min_x = min_x;
    curNet->min_y = min_y;
    curNet->max_x = max_x;
    curNet->max_y = max_y;
  }
}

//
// CreateSparseMatrix Routine
//
// using current Pin's structure,
// based on the B2B models,
// it genereates Sparsematrix into Eigen formats.
//
void CreateSparseMatrix(VectorXf &xcg_x, VectorXf &xcg_b, VectorXf &ycg_x,
                        VectorXf &ycg_b, SMatrix &eMatX, SMatrix &eMatY) {
  int pinCNTinObject = 0;
  int moduleID1 = 0;
  int moduleID2 = 0;
  int is_term1 = 0;
  int is_term2 = 0;

  prec common1;
  prec common2;

  NET *tempNet = NULL;
  PIN *pin1 = NULL;
  PIN *pin2 = NULL;

  MODULE *mdp1 = NULL;
  MODULE *mdp2 = NULL;
  TERM *term1 = NULL;
  TERM *term2 = NULL;

  FPOS center1, center2;
  FPOS fp1, fp2;

  // to easily convert (i, j, value) -> CSR sparse Matrix.
  // using Eigen library..
  vector< T > tripletListX, tripletListY;
  tripletListX.reserve(10000000);
  tripletListY.reserve(10000000);

  // xcg_x & ycg_x update
  MODULE *curModule = NULL;
  for(int i = 0; i < moduleCNT; i++) {
    curModule = &moduleInstance[i];

    // 1d prec array
    xcg_x(i) = curModule->center.x;
    ycg_x(i) = curModule->center.y;

    xcg_b(i) = ycg_b(i) = 0;
  }

  for(int i = 0; i < netCNT; i++) {
    tempNet = &netInstance[i];
    pinCNTinObject = tempNet->pinCNTinObject;
    common1 = 1.0 / ((prec)pinCNTinObject - 1.0);

    for(int j = 0; j < pinCNTinObject; j++) {
      pin1 = tempNet->pin[j];
      moduleID1 = pin1->moduleID;
      fp1 = pin1->fp;

      // if is not Terminal -> moduleInstance
      if(!pin1->term) {
        mdp1 = &moduleInstance[moduleID1];
        center1 = mdp1->center;
        is_term1 = 0;
      }
      // is terminal -> terminalInstance
      else {
        term1 = &terminalInstance[moduleID1];
        center1 = term1->center;
        is_term1 = 1;
      }

      for(int k = j + 1; k < pinCNTinObject; k++) {
        pin2 = tempNet->pin[k];
        moduleID2 = pin2->moduleID;
        fp2 = pin2->fp;

        if(!pin2->term) {
          mdp2 = &moduleInstance[moduleID2];
          center2 = mdp2->center;
          is_term2 = 0;
        }
        else {
          term2 = &terminalInstance[moduleID2];
          center2 = term2->center;
          is_term2 = 1;
        }

        // there is no need to calculate (for same nodes)
        if(moduleID1 == moduleID2 && is_term1 == is_term2) {
          continue;
        }

        if(pin1->X_MIN || pin1->X_MAX || pin2->X_MIN || pin2->X_MAX) {
          prec len_x = fabs(fp1.x - fp2.x);

          prec wt_x = 0.0f;
          if(dge(len_x, MIN_LEN)) {
            wt_x = common1 / len_x;
          }
          else {
            wt_x = common1 / MIN_LEN;
          }

          common2 = (-1.0) * wt_x;

          if(wt_x < 0)
            printf("ERROR WEIGHT\n");

          // both is module
          if(!is_term1 && !is_term2) {
            tripletListX.push_back(T(moduleID1, moduleID1, wt_x));
            tripletListX.push_back(T(moduleID2, moduleID2, wt_x));
            tripletListX.push_back(T(moduleID1, moduleID2, common2));
            tripletListX.push_back(T(moduleID2, moduleID1, common2));

            xcg_b(moduleID1) +=
                common2 * ((fp1.x - center1.x) - (fp2.x - center2.x));
            xcg_b(moduleID2) +=
                common2 * ((fp2.x - center2.x) - (fp1.x - center1.x));
          }
          // 1 is terminal, 2 is module
          else if(is_term1 && !is_term2) {
            tripletListX.push_back(T(moduleID2, moduleID2, wt_x));
            xcg_b(moduleID2) += wt_x * (fp1.x - (fp2.x - center2.x));
          }
          // 2 is terminal, 1 is module
          else if(!is_term1 && is_term2) {
            tripletListX.push_back(T(moduleID1, moduleID1, wt_x));
            xcg_b(moduleID1) += wt_x * (fp2.x - (fp1.x - center1.x));
          }
        }

        if(pin1->Y_MIN || pin1->Y_MAX || pin2->Y_MIN || pin2->Y_MAX) {
          prec len_y = fabs(fp1.y - fp2.y);

          prec wt_y = 0.0f;
          if(dge(len_y, MIN_LEN)) {
            wt_y = common1 / len_y;
          }
          else {
            wt_y = common1 / MIN_LEN;
          }
          common2 = (-1.0) * wt_y;

          // both is module
          if(!is_term1 && !is_term2) {
            tripletListY.push_back(T(moduleID1, moduleID1, wt_y));
            tripletListY.push_back(T(moduleID2, moduleID2, wt_y));
            tripletListY.push_back(T(moduleID1, moduleID2, common2));
            tripletListY.push_back(T(moduleID2, moduleID1, common2));

            ycg_b(moduleID1) +=
                common2 * ((fp1.y - center1.y) - (fp2.y - center2.y));
            ycg_b(moduleID2) +=
                common2 * ((fp2.y - center2.y) - (fp1.y - center1.y));
          }
          // 1 is terminal, 2 is module
          else if(is_term1 && !is_term2) {
            tripletListY.push_back(T(moduleID2, moduleID2, wt_y));
            ycg_b(moduleID2) += wt_y * (fp1.y - (fp2.y - center2.y));
          }
          // 2 is terminal, 1 is module
          else if(!is_term1 && is_term2) {
            tripletListY.push_back(T(moduleID1, moduleID1, wt_y));
            ycg_b(moduleID1) += wt_y * (fp2.y - (fp1.y - center1.y));
          }
        }
      }
    }
  }

  eMatX.setFromTriplets(tripletListX.begin(), tripletListX.end());
  eMatY.setFromTriplets(tripletListY.begin(), tripletListY.end());
}


///////////////////////////////////////////////////////////////////////////////
// statistics::detail::accumulator::statistics::mean_var_accumulator.hpp     //
//                                                                           //
//  Copyright 2009 Erwann Rogard. 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)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_ACCUMULATOR_STATISTICS_MEAN_VAR_ACCUMULATOR_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_ACCUMULATOR_STATISTICS_MEAN_VAR_ACCUMULATOR_HPP_ER_2009
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/variance.hpp>
#include <boost/accumulators/statistics/stats.hpp>

namespace boost{
namespace functional{

    // This comes up often, so this saves a bit of time
    template<typename T>
    struct mean_var_accumulator{
        typedef accumulators::stats<
            accumulators::tag::mean,
            accumulators::tag::variance
        >  stat_;
        typedef accumulators::accumulator_set<T,stat_> type;
    };

}// meta
}// boost

#endif


#include "dSFMTsearch.hpp"
#include "Annihilate.h"
#include <errno.h>
#include <stdlib.h>
#include <getopt.h>
#include <errno.h>
#include <NTL/GF2X.h>
#include <NTL/GF2XFactoring.h>
#include <MTToolBox/period.hpp>
#include <MTToolBox/AlgorithmReducibleRecursionSearch.hpp>
#include "calc_fixpoint.h"

using namespace MTToolBox;
using namespace std;

w128_t calc_fixpoint(const dSFMT& dsfmt, const GF2X& irreducible,
                     const GF2X& quotient)
{
    GF2X a, b, d;
    dSFMT dsfmt_const(dsfmt);
    /* a*irreducible + b*quotient = d */
    XGCD(d, a, b, irreducible, quotient);
    if (deg(d) != 0) {
        cout << "failure d != 1" << endl;
        throw new logic_error("failure d != 1");
    }
    b *= quotient;
    a *= irreducible;
    dsfmt_const.setConst();
    annihilate<w128_t>(&dsfmt_const, b);

    GF2X t1(1, 1);
    SetCoeff(t1, 0);
    /* a*irreducible + b*t1 = d */
    XGCD(d, a, b, irreducible, t1);
    if (deg(d) != 0) {
        cout << "failure d != 1" << endl;
        cout << "deg(d) = " << dec << deg(d) << endl;
        cout << "deg(irreducible) = " << dec << deg(irreducible) << endl;
        cout << "deg(t1) = " << dec << deg(t1) << endl;
        throw new logic_error("failure d != 1");
    }
    annihilate<w128_t>(&dsfmt_const, b);
    return dsfmt_const.getParityValue();
}


/**
 * @file convolutional_network_test.cpp
 * @author Marcus Edel
 *
 * Tests the convolutional neural network.
 */
#include <mlpack/core.hpp>

#include <mlpack/methods/ann/activation_functions/rectifier_function.hpp>
#include <mlpack/methods/ann/activation_functions/logistic_function.hpp>

#include <mlpack/methods/ann/layer/one_hot_layer.hpp>
#include <mlpack/methods/ann/layer/conv_layer.hpp>
#include <mlpack/methods/ann/layer/pooling_layer.hpp>
#include <mlpack/methods/ann/layer/softmax_layer.hpp>
#include <mlpack/methods/ann/layer/bias_layer.hpp>
#include <mlpack/methods/ann/layer/linear_layer.hpp>
#include <mlpack/methods/ann/layer/base_layer.hpp>
#include <mlpack/methods/ann/layer/dropout_layer.hpp>

#include <mlpack/methods/ann/cnn.hpp>
#include <mlpack/methods/ann/trainer/trainer.hpp>
#include <mlpack/methods/ann/optimizer/ada_delta.hpp>
#include <mlpack/methods/ann/optimizer/rmsprop.hpp>
#include <mlpack/methods/ann/init_rules/zero_init.hpp>

#include <boost/test/unit_test.hpp>
#include "old_boost_test_definitions.hpp"

using namespace mlpack;
using namespace mlpack::ann;

BOOST_AUTO_TEST_SUITE(ConvolutionalNetworkTest);

/**
 * Train and evaluate a vanilla network with the specified structure.
 */
template<
    typename PerformanceFunction
>
void BuildVanillaNetwork()
{
  arma::mat X;
  X.load("mnist_first250_training_4s_and_9s.arm");

  // Normalize each point since these are images.
  arma::uword nPoints = X.n_cols;
  for (arma::uword i = 0; i < nPoints; i++)
  {
    X.col(i) /= norm(X.col(i), 2);
  }

  // Build the target matrix.
  arma::mat Y = arma::zeros<arma::mat>(10, nPoints);
  for (size_t i = 0; i < nPoints; i++)
  {
    if (i < nPoints / 2)
    {
      Y.col(i)(5) = 1;
    }
    else
    {
      Y.col(i)(8) = 1;
    }
  }

  arma::cube input = arma::cube(28, 28, nPoints);
  for (size_t i = 0; i < nPoints; i++)
    input.slice(i) = arma::mat(X.colptr(i), 28, 28);

  /*
   * Construct a convolutional neural network with a 28x28x1 input layer,
   * 24x24x6 convolution layer, 12x12x6 pooling layer, 8x8x12 convolution layer
   * and a 4x4x12 pooling layer which is fully connected with the output layer.
   * The network structure looks like:
   *
   * Input    Convolution  Pooling      Convolution  Pooling      Output
   * Layer    Layer        Layer        Layer        Layer        Layer
   *
   *          +---+        +---+        +---+        +---+
   *          | +---+      | +---+      | +---+      | +---+
   * +---+    | | +---+    | | +---+    | | +---+    | | +---+    +---+
   * |   |    | | |   |    | | |   |    | | |   |    | | |   |    |   |
   * |   +--> +-+ |   +--> +-+ |   +--> +-+ |   +--> +-+ |   +--> |   |
   * |   |      +-+   |      +-+   |      +-+   |      +-+   |    |   |
   * +---+        +---+        +---+        +---+        +---+    +---+
   */

  ConvLayer<RMSPROP> convLayer0(1, 8, 5, 5);
  BiasLayer2D<RMSPROP, ZeroInitialization> biasLayer0(8);
  BaseLayer2D<PerformanceFunction> baseLayer0;
  PoolingLayer<> poolingLayer0(2);

  ConvLayer<RMSPROP> convLayer1(8, 12, 5, 5);
  BiasLayer2D<RMSPROP, ZeroInitialization> biasLayer1(12);
  BaseLayer2D<PerformanceFunction> baseLayer1;
  PoolingLayer<> poolingLayer1(2);

  LinearMappingLayer<RMSPROP> linearLayer0(192, 10);
  BiasLayer<RMSPROP> biasLayer2(10);
  SoftmaxLayer<> softmaxLayer0;

  OneHotLayer outputLayer;

  auto modules = std::tie(convLayer0, biasLayer0, baseLayer0, poolingLayer0,
                          convLayer1, biasLayer1, baseLayer1, poolingLayer1,
                          linearLayer0, biasLayer2, softmaxLayer0);

  CNN<decltype(modules), decltype(outputLayer)>
      net(modules, outputLayer);

  Trainer<decltype(net)> trainer(net, 50, 1, 0.7);
  trainer.Train(input, Y, input, Y);

  BOOST_REQUIRE_LE(trainer.ValidationError(), 0.7);
}

/**
 * Train the vanilla network on a larger dataset.
 */
BOOST_AUTO_TEST_CASE(VanillaNetworkTest)
{
  BuildVanillaNetwork<LogisticFunction>();
}

/**
 * Train and evaluate a vanilla network with the specified structure.
 */
template<
    typename PerformanceFunction
>
void BuildVanillaDropoutNetwork()
{
  arma::mat X;
  X.load("mnist_first250_training_4s_and_9s.arm");

  // Normalize each point since these are images.
  arma::uword nPoints = X.n_cols;
  for (arma::uword i = 0; i < nPoints; i++)
  {
    X.col(i) /= norm(X.col(i), 2);
  }

  // Build the target matrix.
  arma::mat Y = arma::zeros<arma::mat>(10, nPoints);
  for (size_t i = 0; i < nPoints; i++)
  {
    if (i < nPoints / 2)
    {
      Y.col(i)(5) = 1;
    }
    else
    {
      Y.col(i)(8) = 1;
    }
  }

  arma::cube input = arma::cube(28, 28, nPoints);
  for (size_t i = 0; i < nPoints; i++)
    input.slice(i) = arma::mat(X.colptr(i), 28, 28);

  /*
   * Construct a convolutional neural network with a 28x28x1 input layer,
   * 24x24x6 convolution layer, 12x12x6 pooling layer, 8x8x12 convolution layer,
   * 8x8x12 Dropout Layer and a 4x4x12 pooling layer which is fully connected
   * with the output layer. The network structure looks like:
   *
   * Input    Convolution  Dropout      Pooling     Convolution,     Output
   * Layer    Layer        Layer        Layer       Dropout,         Layer
   *                                                Pooling Layer
   *          +---+        +---+        +---+
   *          | +---+      | +---+      | +---+
   * +---+    | | +---+    | | +---+    | | +---+                    +---+
   * |   |    | | |   |    | | |   |    | | |   |                    |   |
   * |   +--> +-+ |   +--> +-+ |   +--> +-+ |   +--> ............--> |   |
   * |   |      +-+   |      +-+   |      +-+   |                    |   |
   * +---+        +---+        +---+        +---+                    +---+
   */

  ConvLayer<AdaDelta> convLayer0(1, 4, 5, 5);
  BiasLayer2D<AdaDelta, ZeroInitialization> biasLayer0(4);
  DropoutLayer2D<> dropoutLayer0;
  BaseLayer2D<PerformanceFunction> baseLayer0;
  PoolingLayer<> poolingLayer0(2);

  ConvLayer<AdaDelta> convLayer1(4, 8, 5, 5);
  BiasLayer2D<AdaDelta, ZeroInitialization> biasLayer1(8);
  BaseLayer2D<PerformanceFunction> baseLayer1;
  PoolingLayer<> poolingLayer1(2);

  LinearMappingLayer<AdaDelta> linearLayer0(128, 10);
  BiasLayer<AdaDelta> biasLayer2(10);
  SoftmaxLayer<> softmaxLayer0;

  OneHotLayer outputLayer;

  auto modules = std::tie(convLayer0, biasLayer0, dropoutLayer0, baseLayer0,
                          poolingLayer0, convLayer1, biasLayer1, baseLayer1,
                          poolingLayer1, linearLayer0, biasLayer2,
                          softmaxLayer0);

  CNN<decltype(modules), decltype(outputLayer)>
      net(modules, outputLayer);

  Trainer<decltype(net)> trainer(net, 50, 1, 0.7);
  trainer.Train(input, Y, input, Y);

  BOOST_REQUIRE_LE(trainer.ValidationError(), 0.7);
}

/**
 * Train the network on a larger dataset using dropout.
 */
BOOST_AUTO_TEST_CASE(VanillaNetworkDropoutTest)
{
  BuildVanillaDropoutNetwork<RectifierFunction>();
}

BOOST_AUTO_TEST_SUITE_END();


///////////////////////////////////////////////////////////////////////////////
// kernel::functional::detail::mean_accumulator.hpp                          //
//                                                                           //
//  Copyright 2009 Erwann Rogard. 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)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_KERNEL_ESTIMATION_DETAIL_MEAN_ACCUMULATOR_H_ER_2009
#define BOOST_STATISTICS_DETAIL_KERNEL_ESTIMATION_DETAIL_MEAN_ACCUMULATOR_H_ER_2009
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>

namespace boost{
namespace statistics{
namespace detail{
namespace kernel{
namespace detail{

    // The Rosenblatt-Parzen estimator is a density estimator
    template<typename T>
    struct mean_accumulator{
        typedef accumulators::stats<
            accumulators::tag::mean
        >  stat_;
        typedef accumulators::accumulator_set<T,stat_> type;
    };

}// detail
}// kernel
}// detail
}// statistics
}// boost

#endif


#include "hops/FileReader/SbmlReader.hpp"
#include <Eigen/Core>
#include <Eigen/Sparse>
#include <sbml/SBMLTypes.h>
#include <sbml/packages/fbc/common/FbcExtensionTypes.h>

Eigen::SparseMatrix<double> parseStoichiometry(Model *model) {
    std::map<const std::string, unsigned int> speciesIdAttributeToIndex;
    for (unsigned int i = 0; i < model->getListOfSpecies()->size(); ++i) {
        Species *species = model->getSpecies(i);
        speciesIdAttributeToIndex.insert(std::make_pair(species->getIdAttribute(), i));
    }

    std::vector<Eigen::Triplet<double>> stoichiometricTriplets;
    for (unsigned int i = 0; i < model->getListOfReactions()->size(); ++i) {
        Reaction *reaction = model->getReaction(i);
        ListOfSpeciesReferences *reactants = reaction->getListOfReactants();
        for (unsigned int j = 0; j < reactants->size(); ++j) {
            SimpleSpeciesReference *reactant = reactants->get(j);
            std::string speciesIdAttribute;
            reactant->getAttribute("species", speciesIdAttribute);
            unsigned int k = speciesIdAttributeToIndex.find(speciesIdAttribute)->second;
            double value;
            reactant->getAttribute("stoichiometry", value);
            stoichiometricTriplets.emplace_back(k, i, -value);
        }
        ListOfSpeciesReferences *products = reaction->getListOfProducts();
        for (unsigned int j = 0; j < products->size(); ++j) {
            SimpleSpeciesReference *product = products->get(j);
            std::string speciesIdAttribute;
            product->getAttribute("species", speciesIdAttribute);
            unsigned int k = speciesIdAttributeToIndex.find(speciesIdAttribute)->second;
            double value;
            product->getAttribute("stoichiometry", value);
            stoichiometricTriplets.emplace_back(k, i, value);
        }
    }
    Eigen::SparseMatrix<double> stoichiometry(model->getListOfSpecies()->size(),
                                              model->getListOfReactions()->size());
    stoichiometry.setFromTriplets(stoichiometricTriplets.begin(), stoichiometricTriplets.end());
    return stoichiometry;
}

std::tuple<Eigen::SparseMatrix<double>, Eigen::VectorXd, Eigen::VectorXd, Eigen::VectorXd>
parseConstraints(Model *model) {
    unsigned int numberOfReactions = model->getListOfReactions()->size();
    Eigen::VectorXd b(numberOfReactions * 2);
    Eigen::VectorXd lb(numberOfReactions);
    Eigen::VectorXd ub(numberOfReactions);
    std::vector<Eigen::Triplet<double>> triplets;
    for (unsigned int i = 0; i < numberOfReactions; ++i) {
        Reaction *reaction = model->getReaction(i);
        FbcReactionPlugin *fluxBalanceConstraintsPlugin = dynamic_cast<FbcReactionPlugin *>(reaction->getPlugin("fbc"));
        ub(i) = model->getParameter(fluxBalanceConstraintsPlugin->getUpperFluxBound())->getValue();
        lb(i) = model->getParameter(fluxBalanceConstraintsPlugin->getLowerFluxBound())->getValue();
        b(i) = model->getParameter(fluxBalanceConstraintsPlugin->getUpperFluxBound())->getValue();
        b(i + numberOfReactions) = model->getParameter(fluxBalanceConstraintsPlugin->getLowerFluxBound())->getValue();
        triplets.emplace_back(i, i, 1);
        triplets.emplace_back(i + numberOfReactions, i, -1);
    }

    Eigen::SparseMatrix<double> C(2 * numberOfReactions, numberOfReactions);
    C.setFromTriplets(triplets.begin(), triplets.end());
    return std::make_tuple(C, b, ub, lb);
}

template<typename MatrixType, typename VectorType>
hops::SbmlModel<MatrixType, VectorType> hops::SbmlReader::readModel(const std::string &file) {
    auto document = std::unique_ptr<SBMLDocument>(readSBML(file.c_str()));

    if (document->getNumErrors() > 0) {
        std::cerr << "Encountered the following SBML errors:" << std::endl;
        document->printErrors(std::cerr);
        throw std::runtime_error("SBML errors.");
    }

    Model *model = document->getModel();

    if (!model) {
        throw std::runtime_error("No model present.");
    }

    SbmlModel<MatrixType, VectorType> sbmlModel;

    sbmlModel.setStoichiometry(MatrixType(parseStoichiometry(model).cast<typename MatrixType::Scalar>()));

    auto constraints = parseConstraints(model);
    sbmlModel.setLowerBounds(std::get<3>(constraints).cast<typename MatrixType::Scalar>());
    sbmlModel.setUpperBounds(std::get<2>(constraints).cast<typename MatrixType::Scalar>());
    sbmlModel.setConstraintVector(std::get<1>(constraints).cast<typename MatrixType::Scalar>());
    sbmlModel.setConstraintMatrix(std::get<0>(constraints).cast<typename MatrixType::Scalar>());

    return sbmlModel;
}

template hops::SbmlModel<Eigen::MatrixXi, Eigen::VectorXi> hops::SbmlReader::readModel(const std::string &file);

template hops::SbmlModel<Eigen::Matrix<long, Eigen::Dynamic, Eigen::Dynamic>, Eigen::Matrix<long, Eigen::Dynamic, 1>>
hops::SbmlReader::readModel(const std::string &file);

template hops::SbmlModel<Eigen::MatrixXf, Eigen::VectorXf> hops::SbmlReader::readModel(const std::string &file);

template hops::SbmlModel<Eigen::MatrixXd, Eigen::VectorXd> hops::SbmlReader::readModel(const std::string &file);

template hops::SbmlModel<Eigen::SparseMatrix<int>, Eigen::VectorXi>
hops::SbmlReader::readModel(const std::string &file);

template hops::SbmlModel<Eigen::SparseMatrix<long>, Eigen::Matrix<long, Eigen::Dynamic, 1>>
hops::SbmlReader::readModel(const std::string &file);

template hops::SbmlModel<Eigen::SparseMatrix<float>, Eigen::VectorXf>
hops::SbmlReader::readModel(const std::string &file);

template hops::SbmlModel<Eigen::SparseMatrix<double>, Eigen::VectorXd>
hops::SbmlReader::readModel(const std::string &file);


#include <iostream>
#include <cmath>
#include <random>
#include <chrono>
#include <dlib/matrix.h>

using namespace std;
using namespace dlib;

/*
int main()
{
    matrix<double,32,32> input;
    matrix<double,28,28> output;
    matrix<double,5,5> filter;

    double acc;

    input = 1;
    filter = 0.1;

    for (int i=0; i<28; i++)
    {
        for (int j=0; j<28; j++)
        {
            acc = 0;
            for (int r=0; r<5;  r++)
            {
                for (int k=0; k<5; k++)
                {
                    acc += input(i+r,j+k) * filter(r,k);
                }
            }
            output(i,j) = acc/25;
        }
    }
    cout << input << endl;
    cout << output << endl;
}
*/

int main()
{
    std::default_random_engine generator;
    std::uniform_real_distribution<double> distribution(-1.0,1.0);

    matrix<double,1,84> input;
    matrix<double,84,10> syn, tmp_syn, tmp;
    matrix<double,1,10> output, y, a, e, g, delta;

    for (int i=0; i<84; i++)
        for (int j=0; j<10; j++)
            syn(i,j) = distribution(generator);

    output(0) = 0;
    output(1) = 1;
    output(2) = 0;
    output(3) = -1;
    output(4) = 0;
    output(5) = 1;
    output(6) = 0;
    output(7) = -1;
    output(8) = 0;
    output(9) = 1;

    input = 1;

    // Learning
    for (int i=0; i<100000; i++)
    {
        y = input * syn;
        a = 1/(1+exp(-y));
        e = output - a;
        g = pointwise_multiply(a,(1 - a));
        delta = pointwise_multiply(g, e);
        //tmp = syn;
        //tmp_syn = trans(input) * delta;
        //syn = tmp + tmp_syn;
        syn += trans(input) * delta;
    }
    cout << syn << endl;
}


#include <iostream>
#include <mtl/mtl.h>
#include <mtl/dense1D.h>

using namespace mtl;

/*
  example output:

  5

  */

int
main()
{
  //begin
  mtl::dense1D<float> x(10, 0.0);
  x[5] = 1.0;
  int i = max_index(x);
  std::cout << i << std::endl;
  //end
  return 0;
}


#include "conex/block_triangular_operations.h"
#include "conex/supernodal_solver.h"

#include "gtest/gtest.h"
#include <Eigen/Dense>

namespace conex {

using Eigen::MatrixXd;
using T = TriangularMatrixOperations;
using B = BlockTriangularOperations;

void RunningIntersectionClosure(std::vector<Clique>* path) {
  if (path->size() < 2) {
    return;
  }
  int n = path->size();
  for (int i = 0; i < n - 2; i++) {
    for (int j = n - 1; j > i + 1; j--) {
      std::vector<int> temp;
      IntersectionOfSorted(path->at(i), path->at(j), &temp);
      if (temp.size() == 0) {
        continue;
      }
      for (int k = j - 1; k > i; k--) {
        path->at(k) = UnionOfSorted(path->at(k), temp);
      }
    }
  }
}

std::vector<int> ResidualSize(std::vector<Clique>& path) {
  std::vector<int> y;
  for (size_t j = 0; j < path.size() - 1; j++) {
    std::vector<int> temp;
    IntersectionOfSorted(path.at(j), path.at(j + 1), &temp);
    y.push_back(path.at(j).size() - temp.size());
  }
  y.push_back(path.back().size());
  return y;
}

SparseTriangularMatrix MakeSparseTriangularMatrix(
    int N, const std::vector<Clique>& path_) {
  auto path = path_;
  Sort(&path);
  RunningIntersectionClosure(&path);
  auto supernode_size = ResidualSize(path);
  return SparseTriangularMatrix(N, path, supernode_size);
}

SparseTriangularMatrix GetFillInPattern(
    int N, const std::vector<Clique>& cliques_input) {
  auto mat = MakeSparseTriangularMatrix(N, cliques_input);

  for (int j = static_cast<int>(mat.path.size()) - 1; j >= 0; j--) {
    // Initialize columns of super nodes.
    mat.supernodes.at(j).setConstant(1);
    mat.separator.at(j).setConstant(1);

    // Update other columns: the (seperator, seperator) components.
    int index = 0;
    auto s_s = mat.workspace_.seperator_diagonal.at(j);
    int n = mat.path.at(j).size();
    for (int i = mat.supernode_size.at(j); i < n; i++) {
      for (int k = i; k < n; k++) {
        *s_s.at(index++) += 1;
      }
    }
  }
  return mat;
}

SparseTriangularMatrix RandomSparseMatrix(
    int N, const std::vector<Clique>& cliques_input) {
  auto mat = MakeSparseTriangularMatrix(N, cliques_input);

  for (int j = static_cast<int>(mat.path.size()) - 1; j >= 0; j--) {
    // Initialize columns of super nodes.
    int r = mat.supernodes.at(j).rows();
    int c = mat.supernodes.at(j).cols();
    mat.supernodes.at(j) = MatrixXd::Random(r, c);
    r = mat.separator.at(j).rows();
    c = mat.separator.at(j).cols();
    mat.separator.at(j) = MatrixXd::Random(r, c);
  }
  return mat;
}

int GetMax(const std::vector<Clique>& cliques) {
  int max = cliques.at(0).at(0);
  for (const auto& c : cliques) {
    for (const auto ci : c) {
      if (ci > max) {
        max = ci;
      }
    }
  }
  return max;
}

void DoCholeskyTest(const std::vector<Clique>& cliques) {
  auto mat = GetFillInPattern(GetMax(cliques) + 1, cliques);
  for (auto& sn : mat.supernodes) {
    sn.diagonal().array() += 100;
  }

  Eigen::MatrixXd x = T::ToDense(mat);
  Eigen::LLT<MatrixXd> llt(x);
  MatrixXd L = llt.matrixL();
  EXPECT_TRUE(llt.info() == Eigen::Success);

  B::BlockCholeskyInPlace(&mat.workspace_);
  MatrixXd error = T::ToDense(mat) - L;
  error = error.triangularView<Eigen::Lower>();
  EXPECT_NEAR(error.norm(), 0, 1e-12);
}

GTEST_TEST(LowerTri, Cholesky) {
  DoCholeskyTest({{0, 1, 2}, {2}});
  DoCholeskyTest({{0, 1, 2, 4, 7}, {3, 4}, {5, 6, 7}});
  DoCholeskyTest({{0, 1, 5}, {1, 2, 5}, {3, 4, 5}});
  DoCholeskyTest({{0, 1, 2}, {1, 2, 3}, {3, 4, 2}});
  DoCholeskyTest({{0, 1}, {2, 4}, {3, 4}, {5, 6, 7}, {7, 8, 9, 10}});
}

void DoInverseTest(const std::vector<Clique>& cliques) {
  auto mat = GetFillInPattern(GetMax(cliques) + 1, cliques);
  for (auto& sn : mat.supernodes) {
    sn.diagonal().array() += 10;
  }

  Eigen::MatrixXd L = T::ToDense(mat).triangularView<Eigen::Lower>();
  Eigen::VectorXd b;
  b.setLinSpaced(L.rows(), -1, 1);

  Eigen::VectorXd y2 = b;
  B::ApplyBlockInverseInPlace(mat.workspace_, &y2);
  EXPECT_NEAR((L * y2 - b).norm(), 0, 1e-12);
}

GTEST_TEST(LowerTri, InverseTest) {
  DoInverseTest({{0, 1, 2, 3}, {3, 4, 5}});
  DoInverseTest({{0, 1, 2, 3}});
  DoInverseTest({{0, 1, 2, 3}, {3, 4}, {4, 5, 6}});
}

void DoInverseOfTransposeTest(const std::vector<Clique>& cliques) {
  auto mat = GetFillInPattern(GetMax(cliques) + 1, cliques);
  for (auto& sn : mat.supernodes) {
    sn.diagonal().array() += 10;
  }

  Eigen::MatrixXd L = T::ToDense(mat).triangularView<Eigen::Lower>();
  Eigen::VectorXd b;
  b.setLinSpaced(L.rows(), -1, 1);

  Eigen::VectorXd y2 = b;
  B::ApplyBlockInverseOfTransposeInPlace(mat.workspace_, &y2);
  EXPECT_NEAR((L.transpose() * y2 - b).norm(), 0, 1e-12);
}

GTEST_TEST(LowerTri, InverseOfTranspose) {
  DoInverseOfTransposeTest({{0, 1, 2, 5}, {3, 4, 5}});
  DoInverseOfTransposeTest({{0, 1, 2, 5}, {3, 4, 5}, {5, 6}});
  DoInverseOfTransposeTest({{0, 1, 2, 3}});
}

MatrixXd Submatrix(const MatrixXd& T, const Clique& c) {
  MatrixXd y(c.size(), c.size());
  int i = 0;
  for (auto ci : c) {
    int j = 0;
    for (auto cj : c) {
      y(i, j) = T(ci, cj);
      j++;
    }
    i++;
  }
  return y;
}

void DoLDLTTest(bool diagonal, const std::vector<Clique>& cliques) {
  auto mat = GetFillInPattern(GetMax(cliques) + 1, cliques);

  // Set to identity.
  for (auto& sn : mat.workspace_.diagonal) {
    if (diagonal) {
      sn.setZero();
    }
    int n = sn.diagonal().size();
    for (int i = 0; i < n; i++) {
      sn.diagonal()(i) = -101 + i * 100;
    }
  }

  if (diagonal) {
    for (auto& sn : mat.workspace_.off_diagonal) {
      sn.setZero();
    }
  }

  Eigen::MatrixXd X = T::ToDense(mat).selfadjointView<Eigen::Lower>();

  std::vector<Eigen::RLDLT<Eigen::Ref<MatrixXd>>> factorization;
  B::BlockLDLTInPlace(&mat.workspace_, &factorization);

  Eigen::VectorXd z = Eigen::VectorXd::Random(X.cols());
  z.setConstant(0);
  z(1) = 1;

  Eigen::VectorXd y = X * z;
  // X = M D M ^T z = y
  // z = inv(M^{T}) (MD)^{-1} y
  B::ApplyBlockInverseOfMD(mat.workspace_, factorization, &y);
  B::ApplyBlockInverseOfMTranspose(mat.workspace_, factorization, &y);
  EXPECT_NEAR((z - y).norm(), 0, 1e-12);
}

GTEST_TEST(LowerTri, LDLT) {
  bool diagonal = true;
  DoLDLTTest(diagonal, {{0, 1}});
  DoLDLTTest(diagonal, {{0, 1, 2}, {2}});
  DoLDLTTest(diagonal, {{0, 1, 2, 4, 7}, {3, 4}, {5, 6, 7}});
  DoLDLTTest(diagonal, {{0, 1, 5}, {1, 2, 5}, {3, 4, 5}});
  DoLDLTTest(diagonal, {{0, 1, 2}, {1, 2, 3}, {3, 4, 2}});
  DoLDLTTest(diagonal, {{0, 1}, {2, 4}, {3, 4}, {5, 6, 7}, {7, 8, 9, 10}});

  diagonal = false;
  DoLDLTTest(diagonal, {{0, 1, 2}, {2}});
  DoLDLTTest(diagonal, {{0, 1, 2, 4, 7}, {3, 4}, {5, 6, 7}});
  DoLDLTTest(diagonal, {{0, 1, 5}, {1, 2, 5}, {3, 4, 5}});
  DoLDLTTest(diagonal, {{0, 1, 2}, {1, 2, 3}, {3, 4, 2}});
  DoLDLTTest(diagonal, {{0, 1}, {2, 4}, {3, 4}, {5, 6, 7}, {7, 8, 9, 10}});
}

}  // namespace conex


#include "Sin.hh"
#include "TypesFunctions.hh"
#include <Eigen/Core>

/**
 * @brief Constructor.
 */
Sin::Sin() {
    transformation_("sin")
        .input("points")
	.output("result")
	.types(TypesFunctions::ifPoints<0>, TypesFunctions::pass<0>)
	.func(&Sin::calculate)
      ;
}

Sin::Sin(OutputDescriptor& output) : Sin() {
    transformations.front().inputs.front().connect(output);
}

/**
 * @brief Calculate the value of function.
 */
void Sin::calculate(FunctionArgs& fargs){
    fargs.rets[0].x = fargs.args[0].x.sin();
}


// ---------------------------------------------------------------------
//
// Copyright (c) 2019 - 2021 by the IBAMR developers
// All rights reserved.
//
// This file is part of IBAMR.
//
// IBAMR is free software and is distributed under the 3-clause BSD
// license. The full text of the license can be found in the file
// COPYRIGHT at the top level directory of IBAMR.
//
// ---------------------------------------------------------------------

#include "ibtk/FEMapping.h"
#include "ibtk/libmesh_utilities.h"

#include "tbox/Utilities.h"

#include "libmesh/dense_matrix.h"
#include "libmesh/elem.h"
#include "libmesh/enum_elem_type.h"
#include "libmesh/enum_fe_family.h"
#include "libmesh/enum_order.h"
#include "libmesh/enum_quadrature_type.h"
#include "libmesh/type_vector.h"
#include <libmesh/libmesh_version.h>
#include <libmesh/point.h>
#include <libmesh/quadrature.h>

#include <Eigen/Dense>

#include <algorithm>
#include <cmath>
#include <memory>

#include "ibtk/namespaces.h"

namespace IBTK
{
//
// Helper functions
//
template <int dim, int spacedim>
inline Eigen::Matrix<double, spacedim, dim>
getCovariant(const Eigen::Matrix<double, spacedim, dim>& contravariant)
{
    return contravariant * (contravariant.transpose() * contravariant).inverse();
}

template <int dim>
inline Eigen::Matrix<double, dim, dim>
getCovariant(const Eigen::Matrix<double, dim, dim>& contravariant)
{
    return contravariant.inverse().transpose();
}

//
// PointMap
//

template <int dim, int spacedim, int n_nodes>
PointMap<dim, spacedim, n_nodes>::PointMap(const libMesh::ElemType elem_type,
                                           const std::vector<libMesh::Point>& q_points)
    : d_reference_q_points(q_points)
{
    const int n_nodes_ = n_nodes == -1 ? get_n_nodes(elem_type) : n_nodes;
    const auto elem_order = get_default_order(elem_type);
    d_phi.resize(n_nodes_, d_reference_q_points.size());
    for (int i = 0; i < n_nodes_; ++i)
    {
        for (unsigned int q = 0; q < d_reference_q_points.size(); ++q)
        {
            using FE = libMesh::FE<dim, libMesh::LAGRANGE>;
            d_phi(i, q) = FE::shape(elem_type, elem_order, i, d_reference_q_points[q]);
        }
    }
}

template <int dim, int spacedim, int n_nodes>
void
PointMap<dim, spacedim, n_nodes>::getMappedQuadraturePoints(const libMesh::Point* nodes,
                                                            const libMesh::Point* nodes_end,
                                                            std::vector<libMesh::Point>& physical_q_points)
{
    if (n_nodes != -1) TBOX_ASSERT(nodes_end - nodes == n_nodes);
    const int n_nodes_ = n_nodes == -1 ? nodes_end - nodes : n_nodes;
    TBOX_ASSERT(d_reference_q_points.size() == physical_q_points.size());
    TBOX_ASSERT(n_nodes_ == d_phi.rows());
    // assumes same node ordering in the input node array as is stored in d_phi
    for (unsigned int q = 0; q < d_reference_q_points.size(); ++q)
    {
        physical_q_points[q] = 0.0;
        for (int i = 0; i < n_nodes_; ++i)
        {
            for (int d = 0; d < spacedim; ++d)
            {
                physical_q_points[q](d) += d_phi(i, q) * nodes[i](d);
            }
        }
    }
}

//
// QuadratureData
//

QuadratureData::QuadratureData(const QuadratureData::key_type quad_key) : d_key(quad_key)
{
    const ElemType elem_type = std::get<0>(d_key);
    const QuadratureType quad_type = std::get<1>(d_key);
    const Order order = std::get<2>(d_key);

    const int dim = get_dim(elem_type);

    std::unique_ptr<QBase> quad_rule = QBase::build(quad_type, dim, order);
    quad_rule->init(elem_type);
    d_points = quad_rule->get_points();
    d_weights = quad_rule->get_weights();
}

//
// FEMapping
//

template <>
std::unique_ptr<FEMapping<2, 2> >
FEMapping<2, 2>::build(const key_type key, const FEUpdateFlags update_flags)
{
    switch (std::get<0>(key))
    {
    case libMesh::ElemType::TRI3:
        return std::unique_ptr<FEMapping<2, 2> >(new Tri3Mapping(key, update_flags));
    case libMesh::ElemType::TRI6:
        return std::unique_ptr<FEMapping<2, 2> >(new Tri6Mapping(key, update_flags));
    case libMesh::ElemType::QUAD4:
        return std::unique_ptr<FEMapping<2, 2> >(new Quad4Mapping(key, update_flags));
    case libMesh::ElemType::QUAD9:
        return std::unique_ptr<FEMapping<2, 2> >(new Quad9Mapping(key, update_flags));
    default:
        return std::unique_ptr<FEMapping<2, 2> >(new FELagrangeMapping<2, 2>(key, std::get<0>(key), update_flags));
    }

    return {};
}

template <>
std::unique_ptr<FEMapping<3, 3> >
FEMapping<3, 3>::build(const key_type key, const FEUpdateFlags update_flags)
{
    switch (std::get<0>(key))
    {
    case libMesh::ElemType::TET4:
        return std::unique_ptr<FEMapping<3, 3> >(new Tet4Mapping(key, update_flags));
    case libMesh::ElemType::TET10:
        return std::unique_ptr<FEMapping<3, 3> >(new Tet10Mapping(key, update_flags));
    case libMesh::ElemType::HEX8:
        return std::unique_ptr<FEMapping<3, 3> >(
            new FELagrangeMapping<3, 3, 8>(key, libMesh::ElemType::HEX8, update_flags));
    case libMesh::ElemType::HEX27:
        return std::unique_ptr<FEMapping<3, 3> >(new Hex27Mapping(key, update_flags));
    default:
        return std::unique_ptr<FEMapping<3, 3> >(new FELagrangeMapping<3, 3>(key, std::get<0>(key), update_flags));
    }

    return {};
}

template <int dim, int spacedim>
std::unique_ptr<FEMapping<dim, spacedim> >
FEMapping<dim, spacedim>::build(const key_type key, const FEUpdateFlags update_flags)
{
    return std::unique_ptr<FEMapping<dim, spacedim> >(
        new FELagrangeMapping<dim, spacedim>(key, std::get<0>(key), update_flags));
}

//
// FENodalMapping
//

template <int dim, int spacedim, int n_nodes>
FENodalMapping<dim, spacedim, n_nodes>::FENodalMapping(
    const typename FENodalMapping<dim, spacedim, n_nodes>::key_type quad_key,
    const libMesh::ElemType element_mapping_type,
    const FEUpdateFlags update_flags)
    : d_quadrature_data(quad_key), d_point_map(element_mapping_type, d_quadrature_data.d_points)
{
    d_update_flags = update_flags;

    // make sure dependencies are satisfied. These dependencies are only true
    // for Lagrange-type mappings.
    {
        if (d_update_flags | FEUpdateFlags::update_JxW) d_update_flags |= update_jacobians;

        if (d_update_flags | FEUpdateFlags::update_jacobians) d_update_flags |= update_contravariants;

        if (d_update_flags | FEUpdateFlags::update_covariants) d_update_flags |= update_contravariants;
    }

    if (d_update_flags | FEUpdateFlags::update_contravariants) d_contravariants.resize(this->d_quadrature_data.size());
    if (d_update_flags | FEUpdateFlags::update_covariants) d_covariants.resize(this->d_quadrature_data.size());

    if (d_update_flags | FEUpdateFlags::update_jacobians) d_Jacobians.resize(this->d_quadrature_data.size());

    if (d_update_flags | FEUpdateFlags::update_JxW) d_JxW.resize(this->d_quadrature_data.size());

    if (d_update_flags | FEUpdateFlags::update_quadrature_points)
        d_quadrature_points.resize(this->d_quadrature_data.size());
}

template <int dim, int spacedim, int n_nodes>
void
FENodalMapping<dim, spacedim, n_nodes>::reinit(const libMesh::Elem* elem)
{
    if (d_update_flags & FEUpdateFlags::update_contravariants || d_update_flags & FEUpdateFlags::update_covariants)
        this->fillTransforms(elem);
    if (d_update_flags & FEUpdateFlags::update_jacobians) this->fillJacobians();
    if (d_update_flags & FEUpdateFlags::update_JxW) this->fillJxW();
    if (d_update_flags & FEUpdateFlags::update_quadrature_points) this->fillQuadraturePoints(elem);
}

template <int dim, int spacedim, int n_nodes>
bool
FENodalMapping<dim, spacedim, n_nodes>::isAffine() const
{
    return false;
}

template <int dim, int spacedim, int n_nodes>
void
FENodalMapping<dim, spacedim, n_nodes>::fillJacobians()
{
    for (unsigned int q = 0; q < d_contravariants.size(); ++q)
    {
        if (dim == spacedim)
        {
            d_Jacobians[q] = d_contravariants[q].determinant();
        }
        else
        {
            Eigen::Matrix<double, dim, dim> Jac = d_contravariants[q].transpose() * d_contravariants[q];
            d_Jacobians[q] = std::sqrt(Jac.determinant());
        }
        TBOX_ASSERT(d_Jacobians[q] > 0.0);

        if (isAffine()) break;
    }

    if (isAffine()) std::fill(d_Jacobians.begin() + 1, d_Jacobians.end(), d_Jacobians[0]);

    return;
}

template <int dim, int spacedim, int n_nodes>
void
FENodalMapping<dim, spacedim, n_nodes>::fillJxW()
{
    for (unsigned int q = 0; q < d_Jacobians.size(); ++q) d_JxW[q] = d_quadrature_data.d_weights[q] * d_Jacobians[q];
}

template <int dim, int spacedim, int n_nodes>
void
FENodalMapping<dim, spacedim, n_nodes>::fillQuadraturePoints(const libMesh::Elem* elem)
{
    libMesh::Point nodes[27];

    // We occasionally (e.g., TET10 and TET4) want to call the lower-order
    // mapping from the higher-order mapping, so permit elements with more
    // nodes in that code
    if (n_nodes != -1) TBOX_ASSERT(n_nodes <= static_cast<int>(elem->n_nodes()));
    const int n_nodes_ = n_nodes == -1 ? elem->n_nodes() : n_nodes;
    TBOX_ASSERT(n_nodes_ <= 27);
    for (int node_n = 0; node_n < n_nodes_; ++node_n)
    {
        nodes[node_n] = static_cast<const libMesh::Point&>(*elem->node_ptr(node_n));
    }

    d_point_map.getMappedQuadraturePoints(std::begin(nodes), std::begin(nodes) + n_nodes_, d_quadrature_points);
}

//
// FELagrangeMapping
//

template <int dim, int spacedim, int n_nodes>
FELagrangeMapping<dim, spacedim, n_nodes>::FELagrangeMapping(
    const typename FELagrangeMapping<dim, spacedim, n_nodes>::key_type quad_key,
    const libMesh::ElemType element_mapping_type,
    const FEUpdateFlags update_flags)
    : FENodalMapping<dim, spacedim, n_nodes>(quad_key, element_mapping_type, update_flags),
      d_n_nodes(n_nodes == -1 ? get_n_nodes(std::get<0>(quad_key)) : n_nodes)
{
    if (n_nodes != -1) TBOX_ASSERT(d_n_nodes == n_nodes);
#if LIBMESH_VERSION_LESS_THAN(1, 4, 0)
    TBOX_ASSERT(d_n_nodes <= 27);
#else
    TBOX_ASSERT(d_n_nodes <= static_cast<int>(libMesh::Elem::max_n_nodes));
#endif
    typename decltype(d_dphi)::extent_gen extents;
    d_dphi.resize(extents[d_n_nodes][this->d_quadrature_data.size()]);

    for (int node_n = 0; node_n < d_n_nodes; ++node_n)
    {
        for (unsigned int q = 0; q < this->d_quadrature_data.size(); ++q)
        {
            for (unsigned int d = 0; d < dim; ++d)
                d_dphi[node_n][q][d] =
                    libMesh::FE<dim, libMesh::LAGRANGE>::shape_deriv(element_mapping_type,
                                                                     get_default_order(element_mapping_type),
                                                                     node_n,
                                                                     d,
                                                                     this->d_quadrature_data.d_points[q]);
        }
    }
}

template <int dim, int spacedim, int n_nodes>
void
FELagrangeMapping<dim, spacedim, n_nodes>::fillTransforms(const libMesh::Elem* elem)
{
    TBOX_ASSERT(this->d_update_flags & FEUpdateFlags::update_contravariants);
    TBOX_ASSERT(d_n_nodes <= static_cast<int>(elem->n_nodes()));

    // max_n_nodes is a constant defined by libMesh - currently 27
#if LIBMESH_VERSION_LESS_THAN(1, 4, 0)
    double xs[27][spacedim];
#else
    double xs[libMesh::Elem::max_n_nodes][spacedim];
#endif

    const int n_nodes_ = n_nodes == -1 ? d_n_nodes : n_nodes;
    for (int i = 0; i < n_nodes_; ++i)
    {
        const libMesh::Point p = elem->point(i);
        for (unsigned int j = 0; j < spacedim; ++j) xs[i][j] = p(j);
    }

    for (unsigned int q = 0; q < this->d_JxW.size(); ++q)
    {
        auto& contravariant = this->d_contravariants[q];
        contravariant.setZero();
        for (int node_n = 0; node_n < n_nodes_; ++node_n)
        {
            for (unsigned int i = 0; i < spacedim; ++i)
            {
                for (unsigned int j = 0; j < dim; ++j)
                {
                    contravariant(i, j) += xs[node_n][i] * d_dphi[node_n][q][j];
                }
            }
        }
    }

    if (this->d_update_flags & FEUpdateFlags::update_covariants)
    {
        for (unsigned int q = 0; q < this->d_JxW.size(); ++q)
        {
            this->d_covariants[q] = getCovariant(this->d_contravariants[q]);
        }
    }

    return;
}

//
// Tri3Mapping
//

Tri3Mapping::Tri3Mapping(const key_type quad_key, const FEUpdateFlags update_flags)

    : FENodalMapping<2, 2, 3>(quad_key, libMesh::TRI3, update_flags)
{
}

void
Tri3Mapping::fillTransforms(const libMesh::Elem* elem)
{
    TBOX_ASSERT(this->d_update_flags & FEUpdateFlags::update_contravariants);
    // also permit TRI6
    const auto type = elem->type();
    TBOX_ASSERT(type == libMesh::TRI3 || type == libMesh::TRI6);

    const libMesh::Point p0 = elem->point(0);
    const libMesh::Point p1 = elem->point(1);
    const libMesh::Point p2 = elem->point(2);

    Eigen::Matrix<double, 2, 2> contravariant;
    contravariant(0, 0) = p1(0) - p0(0);
    contravariant(0, 1) = p2(0) - p0(0);
    contravariant(1, 0) = p1(1) - p0(1);
    contravariant(1, 1) = p2(1) - p0(1);
    std::fill(this->d_contravariants.begin(), this->d_contravariants.end(), contravariant);

    if (this->d_update_flags & FEUpdateFlags::update_covariants)
    {
        const Eigen::Matrix<double, 2, 2> covariant = getCovariant(contravariant);
        std::fill(this->d_covariants.begin(), this->d_covariants.end(), covariant);
    }

    return;
}

bool
Tri3Mapping::isAffine() const
{
    return true;
}

//
// Tri6Mapping
//

Tri6Mapping::Tri6Mapping(const key_type quad_key, const FEUpdateFlags update_flags)
    : FELagrangeMapping<2, 2, 6>(quad_key, libMesh::ElemType::TRI6, update_flags), tri3_mapping(quad_key, update_flags)
{
}

void
Tri6Mapping::reinit(const libMesh::Elem* elem)
{
    if (elem_is_affine(elem))
    {
        tri3_mapping.reinit(elem);
        // If we ever add more fields to the mapping classes we will need to
        // duplicate them here
        std::swap(d_contravariants, tri3_mapping.d_contravariants);
        std::swap(d_covariants, tri3_mapping.d_covariants);
        std::swap(d_Jacobians, tri3_mapping.d_Jacobians);
        std::swap(d_JxW, tri3_mapping.d_JxW);
        std::swap(d_quadrature_points, tri3_mapping.d_quadrature_points);
    }
    else
        FELagrangeMapping<2, 2, 6>::reinit(elem);
}

bool
Tri6Mapping::elem_is_affine(const libMesh::Elem* elem)
{
    std::array<libMesh::Point, 6> nodes;
    for (unsigned int n = 0; n < nodes.size(); ++n) nodes[n] = elem->node_ref(n);

    // try to determine the size of the coordinates to use as the tolerance.
    double characteristic_point_size = 0.0;
    for (int d = 0; d < LIBMESH_DIM; ++d)
    {
        characteristic_point_size += std::abs(nodes[0](d));
        characteristic_point_size += std::abs(nodes[4](d));
    }
    const double tol = 1e-16 * characteristic_point_size;

    return nodes[3].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[1]), tol) &&
           nodes[4].absolute_fuzzy_equals(0.5 * (nodes[1] + nodes[2]), tol) &&
           nodes[5].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[2]), tol);
}

//
// Quad4Mapping
//

Quad4Mapping::Quad4Mapping(const key_type quad_key, const FEUpdateFlags update_flags)

    : FENodalMapping<2, 2, 4>(quad_key, libMesh::QUAD4, update_flags)
{
}

void
Quad4Mapping::fillTransforms(const libMesh::Elem* elem)
{
    TBOX_ASSERT(this->d_update_flags & FEUpdateFlags::update_contravariants);
    TBOX_ASSERT(elem->type() == std::get<0>(this->d_quadrature_data.d_key));

    // calculate constants in Jacobians here
    const libMesh::Point p0 = elem->point(0);
    const libMesh::Point p1 = elem->point(1);
    const libMesh::Point p2 = elem->point(2);
    const libMesh::Point p3 = elem->point(3);

    const double a_1 = 0.25 * (-p0(0) + p1(0) + p2(0) - p3(0));
    const double b_1 = 0.25 * (-p0(0) - p1(0) + p2(0) + p3(0));
    const double c_1 = 0.25 * (p0(0) - p1(0) + p2(0) - p3(0));
    const double a_2 = 0.25 * (-p0(1) + p1(1) + p2(1) - p3(1));
    const double b_2 = 0.25 * (-p0(1) - p1(1) + p2(1) + p3(1));
    const double c_2 = 0.25 * (p0(1) - p1(1) + p2(1) - p3(1));

    for (unsigned int i = 0; i < this->d_JxW.size(); i++)
    {
        // calculate Jacobians here
        const double x = d_quadrature_data.d_points[i](0);
        const double y = d_quadrature_data.d_points[i](1);

        Eigen::Matrix<double, 2, 2>& contravariant = d_contravariants[i];
        contravariant(0, 0) = a_1 + c_1 * y;
        contravariant(0, 1) = b_1 + c_1 * x;
        contravariant(1, 0) = a_2 + c_2 * y;
        contravariant(1, 1) = b_2 + c_2 * x;
    }

    if (this->d_update_flags & FEUpdateFlags::update_covariants)
    {
        for (unsigned int q = 0; q < this->d_JxW.size(); ++q)
        {
            const auto& contravariant = this->d_contravariants[q];
            d_covariants[q] = getCovariant(contravariant);
        }
    }

    return;
}

//
// Quad9Mapping
//

Quad9Mapping::Quad9Mapping(const Quad9Mapping::key_type quad_key, FEUpdateFlags update_flags)
    : FENodalMapping<2, 2, 9>(quad_key, std::get<0>(quad_key), update_flags)
{
    // This code utilizes an implementation detail of
    // QBase::tensor_product_quad where the x coordinate increases fastest to
    // reconstruct the 1D quadrature rule
    d_n_oned_q_points = static_cast<std::size_t>(std::round(std::sqrt(d_quadrature_data.d_points.size())));
    TBOX_ASSERT(d_n_oned_q_points * d_n_oned_q_points == d_quadrature_data.d_points.size());
    std::vector<libMesh::Point> oned_points(d_n_oned_q_points);
    for (unsigned int q = 0; q < d_n_oned_q_points; ++q)
    {
        oned_points[q] = d_quadrature_data.d_points[q](0);
    }

    // verify that we really do have a tensor product rule
    unsigned int q = 0;
    for (unsigned int j = 0; j < d_n_oned_q_points; ++j)
    {
        for (unsigned int i = 0; i < d_n_oned_q_points; ++i)
        {
            TBOX_ASSERT(d_quadrature_data.d_points[q] == libMesh::Point(oned_points[i](0), oned_points[j](0)));
            ++q;
        }
    }

    d_phi.resize(3, d_n_oned_q_points);
    d_dphi.resize(3, d_n_oned_q_points);
    for (unsigned int i = 0; i < 3u; ++i)
    {
        for (unsigned int q = 0; q < oned_points.size(); ++q)
        {
            // This class orders the vertices in a different way to make
            // writing tensor products easier: we do a left-to-right ordering
            // 0 - 1 - 2 instead of 0 - 2 - 1.
            constexpr int ibamr_to_libmesh_ordering[3] = { 0, 2, 1 };
            using FE = libMesh::FE<1, libMesh::LAGRANGE>;
            d_phi(i, q) = FE::shape(libMesh::EDGE3, libMesh::SECOND, ibamr_to_libmesh_ordering[i], oned_points[q]);
            d_dphi(i, q) =
                FE::shape_deriv(libMesh::EDGE3, libMesh::SECOND, ibamr_to_libmesh_ordering[i], 0, oned_points[q]);
        }
    }
}

void
Quad9Mapping::fillTransforms(const libMesh::Elem* elem)
{
    TBOX_ASSERT(this->d_update_flags & FEUpdateFlags::update_contravariants);
    TBOX_ASSERT(elem->type() == std::get<0>(this->d_quadrature_data.d_key));

    constexpr std::size_t n_oned_shape_functions = 3;

    // We index points in the following way:
    //
    // i = 2 +--+--+
    //       |     |
    // i = 1 +  +  +
    //       |     |
    // i = 0 +--+--+
    //      j=0 1  2
    //
    // All 2D arrays created here are in row-major order.

    double xs[n_oned_shape_functions][n_oned_shape_functions];
    double ys[n_oned_shape_functions][n_oned_shape_functions];

    libMesh::Point points[n_oned_shape_functions][n_oned_shape_functions];
    points[0][0] = elem->point(0);
    points[0][1] = elem->point(4);
    points[0][2] = elem->point(1);
    points[1][0] = elem->point(7);
    points[1][1] = elem->point(8);
    points[1][2] = elem->point(5);
    points[2][0] = elem->point(3);
    points[2][1] = elem->point(6);
    points[2][2] = elem->point(2);

    // j is the x index, i is the y index
    for (unsigned int i = 0; i < n_oned_shape_functions; ++i)
    {
        for (unsigned int j = 0; j < n_oned_shape_functions; ++j)
        {
            xs[i][j] = points[i][j](0);
            ys[i][j] = points[i][j](1);
        }
    }

    for (unsigned int q = 0; q < this->d_JxW.size(); q++)
    {
        Eigen::Matrix<double, 2, 2>& contravariant = d_contravariants[q];
        contravariant.setZero();
        // Exploit the fact that Quad9 is a tensor product element by indexing
        // the x component of each tensor product shape function with j and
        // the y component with i
        const unsigned int q_point_x = q % d_n_oned_q_points;
        const unsigned int q_point_y = q / d_n_oned_q_points;
        for (unsigned int i = 0; i < n_oned_shape_functions; ++i)
        {
            for (unsigned int j = 0; j < n_oned_shape_functions; ++j)
            {
                contravariant(0, 0) += xs[i][j] * d_dphi(j, q_point_x) * d_phi(i, q_point_y);
                contravariant(0, 1) += xs[i][j] * d_phi(j, q_point_x) * d_dphi(i, q_point_y);
                contravariant(1, 0) += ys[i][j] * d_dphi(j, q_point_x) * d_phi(i, q_point_y);
                contravariant(1, 1) += ys[i][j] * d_phi(j, q_point_x) * d_dphi(i, q_point_y);
            }
        }
    }

    if (this->d_update_flags & FEUpdateFlags::update_covariants)
    {
        for (unsigned int q = 0; q < this->d_JxW.size(); ++q)
        {
            const auto& contravariant = this->d_contravariants[q];
            d_covariants[q] = getCovariant(contravariant);
        }
    }

    return;
}

//
// Tet4Mapping
//

Tet4Mapping::Tet4Mapping(const key_type quad_key, const FEUpdateFlags update_flags)

    : FENodalMapping<3, 3, 4>(quad_key, libMesh::TET4, update_flags)
{
}

void
Tet4Mapping::fillTransforms(const libMesh::Elem* elem)
{
    TBOX_ASSERT(this->d_update_flags & FEUpdateFlags::update_contravariants);
    // also permit TET10
    const auto type = elem->type();
    TBOX_ASSERT(type == libMesh::TET4 || type == libMesh::TET10);

    // calculate Jacobians here
    const libMesh::Point p0 = elem->point(0);
    const libMesh::Point p1 = elem->point(1);
    const libMesh::Point p2 = elem->point(2);
    const libMesh::Point p3 = elem->point(3);

    Eigen::Matrix<double, 3, 3> contravariant;
    contravariant(0, 0) = p1(0) - p0(0);
    contravariant(0, 1) = p2(0) - p0(0);
    contravariant(0, 2) = p3(0) - p0(0);
    contravariant(1, 0) = p1(1) - p0(1);
    contravariant(1, 1) = p2(1) - p0(1);
    contravariant(1, 2) = p3(1) - p0(1);
    contravariant(2, 0) = p1(2) - p0(2);
    contravariant(2, 1) = p2(2) - p0(2);
    contravariant(2, 2) = p3(2) - p0(2);
    std::fill(d_contravariants.begin(), d_contravariants.end(), contravariant);

    if (this->d_update_flags & FEUpdateFlags::update_covariants)
    {
        const Eigen::Matrix<double, 3, 3> covariant = getCovariant(contravariant);
        std::fill(this->d_covariants.begin(), this->d_covariants.end(), covariant);
    }

    return;
}

bool
Tet4Mapping::isAffine() const
{
    return true;
}

//
// Tet10Mapping
//

Tet10Mapping::Tet10Mapping(const key_type quad_key, const FEUpdateFlags update_flags)
    : FELagrangeMapping<3, 3, 10>(quad_key, libMesh::ElemType::TET10, update_flags),
      tet4_mapping(quad_key, update_flags)
{
}

void
Tet10Mapping::reinit(const libMesh::Elem* elem)
{
    if (elem_is_affine(elem))
    {
        tet4_mapping.reinit(elem);
        // If we ever add more fields to the mapping classes we will need to
        // duplicate them here
        std::swap(d_contravariants, tet4_mapping.d_contravariants);
        std::swap(d_covariants, tet4_mapping.d_covariants);
        std::swap(d_Jacobians, tet4_mapping.d_Jacobians);
        std::swap(d_JxW, tet4_mapping.d_JxW);
        std::swap(d_quadrature_points, tet4_mapping.d_quadrature_points);
    }
    else
        FELagrangeMapping<3, 3, 10>::reinit(elem);
}

bool
Tet10Mapping::elem_is_affine(const libMesh::Elem* elem)
{
    std::array<libMesh::Point, 10> nodes;
    for (unsigned int n = 0; n < nodes.size(); ++n) nodes[n] = elem->node_ref(n);

    // try to determine the size of the coordinates to use as the tolerance.
    double characteristic_point_size = 0.0;
    for (int d = 0; d < LIBMESH_DIM; ++d)
    {
        characteristic_point_size += std::abs(nodes[0](d));
        characteristic_point_size += std::abs(nodes[3](d));
    }
    const double tol = 1e-16 * characteristic_point_size;

    return nodes[4].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[1]), tol) &&
           nodes[5].absolute_fuzzy_equals(0.5 * (nodes[1] + nodes[2]), tol) &&
           nodes[6].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[2]), tol) &&
           nodes[7].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[3]), tol) &&
           nodes[8].absolute_fuzzy_equals(0.5 * (nodes[1] + nodes[3]), tol) &&
           nodes[9].absolute_fuzzy_equals(0.5 * (nodes[2] + nodes[3]), tol);
}

//
// Hex27Mapping
//

Hex27Mapping::Hex27Mapping(const key_type quad_key, const FEUpdateFlags update_flags)
    : FELagrangeMapping<3, 3, 27>(quad_key, libMesh::HEX27, update_flags),
      hex8_mapping(quad_key, libMesh::HEX8, update_flags)
{
}

void
Hex27Mapping::reinit(const libMesh::Elem* elem)
{
    if (elem_is_trilinear(elem))
    {
        hex8_mapping.reinit(elem);
        // If we ever add more fields to the mapping classes we will need to
        // duplicate them here
        std::swap(d_contravariants, hex8_mapping.d_contravariants);
        std::swap(d_covariants, hex8_mapping.d_covariants);
        std::swap(d_Jacobians, hex8_mapping.d_Jacobians);
        std::swap(d_JxW, hex8_mapping.d_JxW);
        std::swap(d_quadrature_points, hex8_mapping.d_quadrature_points);
    }
    else
        FELagrangeMapping<3, 3, 27>::reinit(elem);
}

bool
Hex27Mapping::elem_is_trilinear(const libMesh::Elem* elem)
{
    std::array<libMesh::Point, 27> nodes;
    for (unsigned int n = 0; n < nodes.size(); ++n) nodes[n] = elem->node_ref(n);

    // try to determine the size of the coordinates to use as the tolerance.
    double characteristic_point_size = 0.0;
    for (int d = 0; d < LIBMESH_DIM; ++d)
    {
        characteristic_point_size += std::abs(nodes[0](d));
        characteristic_point_size += std::abs(nodes[6](d));
    }
    const double tol = 1e-16 * characteristic_point_size;

    return
        // line midpoints
        nodes[8].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[1]), tol) &&
        nodes[9].absolute_fuzzy_equals(0.5 * (nodes[1] + nodes[2]), tol) &&
        nodes[10].absolute_fuzzy_equals(0.5 * (nodes[2] + nodes[3]), tol) &&
        nodes[11].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[3]), tol) &&
        nodes[12].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[4]), tol) &&
        nodes[13].absolute_fuzzy_equals(0.5 * (nodes[1] + nodes[5]), tol) &&
        nodes[14].absolute_fuzzy_equals(0.5 * (nodes[2] + nodes[6]), tol) &&
        nodes[15].absolute_fuzzy_equals(0.5 * (nodes[3] + nodes[7]), tol) &&
        nodes[16].absolute_fuzzy_equals(0.5 * (nodes[4] + nodes[5]), tol) &&
        nodes[17].absolute_fuzzy_equals(0.5 * (nodes[5] + nodes[6]), tol) &&
        nodes[18].absolute_fuzzy_equals(0.5 * (nodes[6] + nodes[7]), tol) &&
        nodes[19].absolute_fuzzy_equals(0.5 * (nodes[4] + nodes[7]), tol) &&
        // face midpoints
        nodes[20].absolute_fuzzy_equals(0.5 * (nodes[8] + nodes[10]), tol) &&
        nodes[21].absolute_fuzzy_equals(0.5 * (nodes[8] + nodes[16]), tol) &&
        nodes[22].absolute_fuzzy_equals(0.5 * (nodes[9] + nodes[17]), tol) &&
        nodes[23].absolute_fuzzy_equals(0.5 * (nodes[10] + nodes[18]), tol) &&
        nodes[24].absolute_fuzzy_equals(0.5 * (nodes[11] + nodes[19]), tol) &&
        nodes[25].absolute_fuzzy_equals(0.5 * (nodes[16] + nodes[18]), tol) &&
        // cell center
        nodes[26].absolute_fuzzy_equals(0.5 * (nodes[20] + nodes[25]), tol);
}

//
// Instantiations
//

template class FEMapping<1, 1>;
template class FEMapping<1, 2>;
template class FEMapping<1, 3>;
template class FEMapping<2, 2>;
template class FEMapping<2, 3>;
template class FEMapping<3, 3>;

template class FENodalMapping<1, 1>;
template class FENodalMapping<1, 2>;
template class FENodalMapping<1, 3>;
template class FENodalMapping<2, 2>;
template class FENodalMapping<2, 3>;
template class FENodalMapping<3, 3>;

template class FELagrangeMapping<1, 1>;
template class FELagrangeMapping<1, 2>;
template class FELagrangeMapping<1, 3>;
template class FELagrangeMapping<2, 2>;
template class FELagrangeMapping<2, 3>;
template class FELagrangeMapping<3, 3>;

template class FELagrangeMapping<2, 2, 6>;
template class FELagrangeMapping<3, 3, 10>;

template class FENodalMapping<2, 2, 3>;
template class FENodalMapping<2, 2, 4>;
template class FENodalMapping<2, 2, 6>;
template class FENodalMapping<2, 2, 9>;
template class FENodalMapping<3, 3, 4>;

} // namespace IBTK