/*

Copyright (c) 2005-2022, University of Oxford.
All rights reserved.

University of Oxford means the Chancellor, Masters and Scholars of the
University of Oxford, having an administrative office at Wellington
Square, Oxford OX1 2JD, UK.

This file is part of Chaste.

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 University of Oxford 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.

*/

#ifndef ABSTRACTBACKWARDEULERCARDIACCELL_HPP_
#define ABSTRACTBACKWARDEULERCARDIACCELL_HPP_

#include <cassert>
#include <cmath>

#include "ChasteSerialization.hpp"
#include <boost/serialization/base_object.hpp>
#include "ClassIsAbstract.hpp"

#include "AbstractCardiacCell.hpp"
#include "Exception.hpp"
#include "PetscTools.hpp"
#include "TimeStepper.hpp"

/**
 * This is the base class for cardiac cells solved using a (decoupled) backward
 * Euler approach (see http://dx.doi.org/10.1109/TBME.2006.879425).
 *
 * The basic approach to solving such models is:
 *  \li Update the transmembrane potential, either from solving an external PDE,
 *      or using a forward Euler step.
 *  \li Update any gating variables (or similar) using a backward euler step.
 *      Suitable ODEs can be written in the form  \f$du/dt = g(V) + h(V)*u\f$.  The update
 *      expression is then \f$u_n = ( u_{n-1} + g(V_n)*dt ) / ( 1 - h(V_n)*dt )\f$.
 *  \li Update the remaining state variables using Newton's method to solve the
 *      nonlinear system \f$U_n - U_{n-1} = dt*F(U_n, V_n)\f$.
 *      The template parameter to the class specifies the size of this nonlinear system.
 */
template<unsigned SIZE>
class AbstractBackwardEulerCardiacCell : public AbstractCardiacCell
{
    private:
    /** Needed for serialization. */
    friend class boost::serialization::access;
    /**
     * Archive the member variables.
     *
     * @param archive
     * @param version
     */
    template<class Archive>
    void serialize(Archive & archive, const unsigned int version)
    {
        // This calls serialize on the base class.
        archive & boost::serialization::base_object<AbstractCardiacCell>(*this);
    }
public:

    /**
     * Standard constructor for a cell.
     *
     * @param numberOfStateVariables  the size of the ODE system
     * @param voltageIndex  the index of the variable representing the transmembrane
     *     potential within the state variable vector
     * @param pIntracellularStimulus  the intracellular stimulus function
     *
     * Some notes for future reference:
     *  \li We may want to remove the timestep from this class, and instead pass it to
     *      the Compute* methods, especially if variable timestepping is to be used.
     *  \li It's a pity that inheriting from AbstractCardiacCell forces us to store a
     *      null pointer (for the unused ODE solver) in every instance.  We may want
     *      to revisit this design decision at a later date.
     */
    AbstractBackwardEulerCardiacCell(
        unsigned numberOfStateVariables,
        unsigned voltageIndex,
        boost::shared_ptr<AbstractStimulusFunction> pIntracellularStimulus);

    /** Virtual destructor */
    virtual ~AbstractBackwardEulerCardiacCell();

    /**
     * Compute the residual of the nonlinear system portion of the cell model.
     *
     * @param time  the current time
     * @param rCurrentGuess  the current guess for \f$U_n\f$
     * @param rResidual  to be filled in with the residual vector
     */
    virtual void ComputeResidual(double time, const double rCurrentGuess[SIZE], double rResidual[SIZE])=0;

    /**
     * Compute the Jacobian matrix for the nonlinear system portion of the cell model.
     *
     * @param time  the current time
     * @param rCurrentGuess  the current guess for \f$U_n\f$
     * @param rJacobian  to be filled in with the Jacobian matrix
     */
    virtual void ComputeJacobian(double time, const double rCurrentGuess[SIZE], double rJacobian[SIZE][SIZE])=0;

    /**
     * Simulates this cell's behaviour between the time interval [tStart, tEnd],
     * with timestep #mDt.  Uses a forward Euler step to update the transmembrane
     * potential at each timestep.
     *
     * The length of the time interval must be a multiple of the timestep.
     *
     * @param tStart  beginning of the time interval to simulate
     * @param tEnd  end of the time interval to simulate
     * @param tSamp  sampling interval for returned results (defaults to #mDt)
     * @return  the values of each state variable, at intervals of tSamp.
     */
    OdeSolution Compute(double tStart, double tEnd, double tSamp=0.0);

    /**
     * Simulates this cell's behaviour between the time interval [tStart, tEnd],
     * with timestep #mDt.  The transmembrane potential is kept fixed throughout.
     *
     * The length of the time interval must be a multiple of the timestep.
     *
     * @param tStart  beginning of the time interval to simulate
     * @param tEnd  end of the time interval to simulate
     */
    void ComputeExceptVoltage(double tStart, double tEnd);

    /**
     * Simulate this cell's behaviour between the time interval [tStart, tEnd],
     * with timestemp #mDt, updating the internal state variable values.
     *
     * @param tStart  beginning of the time interval to simulate
     * @param tEnd  end of the time interval to simulate
     */
    void SolveAndUpdateState(double tStart, double tEnd);

private:
// LCOV_EXCL_START
    /**
     * This function should never be called - the cell class incorporates its own solver.
     *
     * @param time
     * @param rY
     * @param rDY
     */
    void EvaluateYDerivatives(double time, const std::vector<double> &rY, std::vector<double> &rDY)
    {
        NEVER_REACHED;
    }
// LCOV_EXCL_STOP

protected:
    /**
     * Compute the values of all state variables, except the voltage, using backward Euler,
     * for one timestep from tStart.
     *
     * \note This method must be provided by subclasses.
     *
     * @param tStart  start of this timestep
     */
    virtual void ComputeOneStepExceptVoltage(double tStart)=0;

    /**
     * Perform a forward Euler step to update the transmembrane potential.
     *
     * \note This method must be provided by subclasses.
     *
     * @param time  start of this timestep
     */
    virtual void UpdateTransmembranePotential(double time)=0;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/*
 * NOTE: Explicit instantiation is not used for this class, because the SIZE
 * template parameter could take arbitrary values.
 */

template <unsigned SIZE>
AbstractBackwardEulerCardiacCell<SIZE>::AbstractBackwardEulerCardiacCell(
    unsigned numberOfStateVariables,
    unsigned voltageIndex,
    boost::shared_ptr<AbstractStimulusFunction> pIntracellularStimulus)
        : AbstractCardiacCell(boost::shared_ptr<AbstractIvpOdeSolver>(),
                              numberOfStateVariables,
                              voltageIndex,
                              pIntracellularStimulus)
{}

template <unsigned SIZE>
AbstractBackwardEulerCardiacCell<SIZE>::~AbstractBackwardEulerCardiacCell()
{}

template <unsigned SIZE>
OdeSolution AbstractBackwardEulerCardiacCell<SIZE>::Compute(double tStart, double tEnd, double tSamp)
{
    // In this method, we iterate over timesteps, doing the following for each:
    //   - update V using a forward Euler step
    //   - call ComputeExceptVoltage(t) to update the remaining state variables
    //     using backward Euler

    // Check length of time interval
    if (tSamp < mDt)
    {
        tSamp = mDt;
    }
    double _n_steps = (tEnd - tStart) / tSamp;
    const unsigned n_steps = (unsigned) floor(_n_steps+0.5);
    assert(fabs(tStart+n_steps*tSamp - tEnd) < 1e-12);
    const unsigned n_small_steps = (unsigned) floor(tSamp/mDt+0.5);
    assert(fabs(mDt*n_small_steps - tSamp) < 1e-12);

    // Initialise solution store
    OdeSolution solutions;
    solutions.SetNumberOfTimeSteps(n_steps);
    solutions.rGetSolutions().push_back(rGetStateVariables());
    solutions.rGetTimes().push_back(tStart);
    solutions.SetOdeSystemInformation(this->mpSystemInfo);

    // Loop over time
    double curr_time = tStart;
    for (unsigned i=0; i<n_steps; i++)
    {
        for (unsigned j=0; j<n_small_steps; j++)
        {
            curr_time = tStart + i*tSamp + j*mDt;

            // Compute next value of V
            UpdateTransmembranePotential(curr_time);

            // Compute other state variables
            ComputeOneStepExceptVoltage(curr_time);

            // check gating variables are still in range
            VerifyStateVariables();
        }

        // Update solutions
        solutions.rGetSolutions().push_back(rGetStateVariables());
        solutions.rGetTimes().push_back(curr_time+mDt);
    }

    return solutions;
}

template <unsigned SIZE>
void AbstractBackwardEulerCardiacCell<SIZE>::ComputeExceptVoltage(double tStart, double tEnd)
{
    // This method iterates over timesteps, calling ComputeExceptVoltage(t) at
    // each one, to update all state variables except for V, using backward Euler.

    // Check length of time interval
    unsigned n_steps = (unsigned)((tEnd - tStart) / mDt + 0.5);
    assert(fabs(tStart + n_steps*mDt - tEnd) < 1e-12);

    // Loop over time
    double curr_time;
    for (unsigned i=0; i<n_steps; i++)
    {
        curr_time = tStart + i*mDt;

        // Compute other state variables
        ComputeOneStepExceptVoltage(curr_time);

#ifndef NDEBUG
        // Check gating variables are still in range
        VerifyStateVariables();
#endif // NDEBUG
    }
}

template<unsigned SIZE>
void AbstractBackwardEulerCardiacCell<SIZE>::SolveAndUpdateState(double tStart, double tEnd)
{
    TimeStepper stepper(tStart, tEnd, mDt);

    while (!stepper.IsTimeAtEnd())
    {
        double time = stepper.GetTime();

        // Compute next value of V
        UpdateTransmembranePotential(time);

        // Compute other state variables
        ComputeOneStepExceptVoltage(time);

        // check gating variables are still in range
        VerifyStateVariables();

        stepper.AdvanceOneTimeStep();
    }
}

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

/**
 * Specialization for the case where there are no non-linear ODEs in the model.
 */
template<>
class AbstractBackwardEulerCardiacCell<0u> : public AbstractCardiacCell
{
private:
    /** Needed for serialization. */
    friend class boost::serialization::access;
    /**
     * Archive the member variables.
     *
     * @param archive
     * @param version
     */
    template<class Archive>
    void serialize(Archive & archive, const unsigned int version)
    {
        // This calls serialize on the base class.
        archive & boost::serialization::base_object<AbstractCardiacCell>(*this);
    }

public:
    /**
     * Standard constructor for a cell.
     *
     * @param numberOfStateVariables  the size of the ODE system
     * @param voltageIndex  the index of the variable representing the transmembrane
     *     potential within the state variable vector
     * @param pIntracellularStimulus  the intracellular stimulus function
     */
    AbstractBackwardEulerCardiacCell(unsigned numberOfStateVariables,
                                     unsigned voltageIndex,
                                     boost::shared_ptr<AbstractStimulusFunction> pIntracellularStimulus)
        : AbstractCardiacCell(boost::shared_ptr<AbstractIvpOdeSolver>(),
                              numberOfStateVariables,
                              voltageIndex,
                              pIntracellularStimulus)
    {}

    /** Virtual destructor */
    virtual ~AbstractBackwardEulerCardiacCell()
    {}

    /**
     * Simulates this cell's behaviour between the time interval [tStart, tEnd],
     * with timestep #mDt.  Uses a forward Euler step to update the transmembrane
     * potential at each timestep.
     *
     * The length of the time interval must be a multiple of the timestep.
     *
     * @param tStart  beginning of the time interval to simulate
     * @param tEnd  end of the time interval to simulate
     * @param tSamp  sampling interval for returned results (defaults to #mDt)
     * @return  the values of each state variable, at intervals of tSamp.
     */
    OdeSolution Compute(double tStart, double tEnd, double tSamp=0.0)
    {
        // In this method, we iterate over timesteps, doing the following for each:
        //   - update V using a forward Euler step
        //   - call ComputeExceptVoltage(t) to update the remaining state variables
        //     using backward Euler

        // Check length of time interval
        if (tSamp < mDt)
        {
            tSamp = mDt;
        }
        double _n_steps = (tEnd - tStart) / tSamp;
        const unsigned n_steps = (unsigned) floor(_n_steps+0.5);
        assert(fabs(tStart+n_steps*tSamp - tEnd) < 1e-12);
        const unsigned n_small_steps = (unsigned) floor(tSamp/mDt+0.5);
        assert(fabs(mDt*n_small_steps - tSamp) < 1e-12);

        // Initialise solution store
        OdeSolution solutions;
        solutions.SetNumberOfTimeSteps(n_steps);
        solutions.rGetSolutions().push_back(rGetStateVariables());
        solutions.rGetTimes().push_back(tStart);
        solutions.SetOdeSystemInformation(this->mpSystemInfo);

        // Loop over time
        double curr_time = tStart;
        for (unsigned i=0; i<n_steps; i++)
        {
            for (unsigned j=0; j<n_small_steps; j++)
            {
                curr_time = tStart + i*tSamp + j*mDt;

                // Compute next value of V
                UpdateTransmembranePotential(curr_time);

                // Compute other state variables
                ComputeOneStepExceptVoltage(curr_time);

                // check gating variables are still in range
                VerifyStateVariables();
            }

            // Update solutions
            solutions.rGetSolutions().push_back(rGetStateVariables());
            solutions.rGetTimes().push_back(curr_time+mDt);
        }

        return solutions;
    }

    /**
     * Simulates this cell's behaviour between the time interval [tStart, tEnd],
     * with timestep #mDt.  The transmembrane potential is kept fixed throughout.
     *
     * The length of the time interval must be a multiple of the timestep.
     *
     * @param tStart  beginning of the time interval to simulate
     * @param tEnd  end of the time interval to simulate
     */
    void ComputeExceptVoltage(double tStart, double tEnd)
    {
        // This method iterates over timesteps, calling ComputeExceptVoltage(t) at
        // each one, to update all state variables except for V, using backward Euler.

        // Check length of time interval
        unsigned n_steps = (unsigned)((tEnd - tStart) / mDt + 0.5);
        assert(fabs(tStart + n_steps*mDt - tEnd) < 1e-12);

        // Loop over time
        double curr_time;
        for (unsigned i=0; i<n_steps; i++)
        {
            curr_time = tStart + i*mDt;

            // Compute other state variables
            ComputeOneStepExceptVoltage(curr_time);

#ifndef NDEBUG
            // Check gating variables are still in range
            VerifyStateVariables();
#endif // NDEBUG
        }
    }

    /**
     * Simulate this cell's behaviour between the time interval [tStart, tEnd],
     * with timestemp #mDt, updating the internal state variable values.
     *
     * @param tStart  beginning of the time interval to simulate
     * @param tEnd  end of the time interval to simulate
     */
    void SolveAndUpdateState(double tStart, double tEnd)
    {
        TimeStepper stepper(tStart, tEnd, mDt);

        while (!stepper.IsTimeAtEnd())
        {
            double time = stepper.GetTime();

            // Compute next value of V
            UpdateTransmembranePotential(time);

            // Compute other state variables
            ComputeOneStepExceptVoltage(time);

            // Check gating variables are still in range
            VerifyStateVariables();

            stepper.AdvanceOneTimeStep();
        }
    }

private:
// LCOV_EXCL_START
    /**
     * This function should never be called - the cell class incorporates its own solver.
     *
     * @param time
     * @param rY
     * @param rDY
     */
    void EvaluateYDerivatives(double time, const std::vector<double> &rY, std::vector<double> &rDY)
    {
        NEVER_REACHED;
    }
// LCOV_EXCL_STOP

protected:
    /**
     * Compute the values of all state variables, except the voltage, using backward Euler,
     * for one timestep from tStart.
     *
     * \note This method must be provided by subclasses.
     *
     * @param tStart  start of this timestep
     */
    virtual void ComputeOneStepExceptVoltage(double tStart)=0;

    /**
     * Perform a forward Euler step to update the transmembrane potential.
     *
     * \note This method must be provided by subclasses.
     *
     * @param time  start of this timestep
     */
    virtual void UpdateTransmembranePotential(double time)=0;
};

// Debugging
#ifndef NDEBUG
#include "OutputFileHandler.hpp"
#include <boost/foreach.hpp>
template<unsigned SIZE>
void DumpJacobianToFile(double time, const double rCurrentGuess[SIZE], double rJacobian[SIZE][SIZE],
                        const std::vector<double>& rY)
{
    OutputFileHandler handler("DumpJacs", false);
    out_stream p_file = handler.OpenOutputFile("J.txt", std::ios::app);
    (*p_file) << "At " << time << " " << SIZE << std::endl;
    (*p_file) << "rY";
    BOOST_FOREACH(double y, rY)
    {
        (*p_file) << " " << y;
    }
    (*p_file) << std::endl;
    (*p_file) << "rCurrentGuess";
    for (unsigned i=0; i<SIZE; i++)
    {
        (*p_file) << " " << rCurrentGuess[i];
    }
    (*p_file) << std::endl;
    (*p_file) << "rJacobian";
    for (unsigned i=0; i<SIZE; i++)
    {
        for (unsigned j=0; j<SIZE; j++)
        {
            (*p_file) << " " << rJacobian[i][j];
        }
    }
    (*p_file) << std::endl;
}
#endif

TEMPLATED_CLASS_IS_ABSTRACT_1_UNSIGNED(AbstractBackwardEulerCardiacCell)

#endif /*ABSTRACTBACKWARDEULERCARDIACCELL_HPP_*/


/**
 * @file cf_test.cpp
 * @author Mudit Raj Gupta
 *
 * Test file for CF class.
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */

#include <mlpack/core.hpp>
#include <mlpack/methods/cf/cf.hpp>
#include <iostream>

#include <boost/test/unit_test.hpp>
#include "test_tools.hpp"
#include "serialization.hpp"

BOOST_AUTO_TEST_SUITE(CFTest);

using namespace mlpack;
using namespace mlpack::cf;
using namespace std;

/**
 * Make sure that correct number of recommendations are generated when query
 * set. Default case.
 */
BOOST_AUTO_TEST_CASE(CFGetRecommendationsAllUsersTest)
{
  // Dummy number of recommendations.
  size_t numRecs = 3;
  // GroupLensSmall.csv dataset has 200 users.
  size_t numUsers = 200;

  // Matrix to save recommendations into.
  arma::Mat<size_t> recommendations;

  // Load GroupLens data.
  arma::mat dataset;
  data::Load("GroupLensSmall.csv", dataset);

  // Make data into sparse matrix.
  arma::sp_mat cleanedData;
  CF::CleanData(dataset, cleanedData);

  // Create a CF object.
  CF c(cleanedData);

  // Generate recommendations when query set is not specified.
  c.GetRecommendations(numRecs, recommendations);

  // Check if correct number of recommendations are generated.
  BOOST_REQUIRE_EQUAL(recommendations.n_rows, numRecs);

  // Check if recommendations are generated for all users.
  BOOST_REQUIRE_EQUAL(recommendations.n_cols, numUsers);
}

/**
 * Make sure that the recommendations are generated for queried users only.
 */
BOOST_AUTO_TEST_CASE(CFGetRecommendationsQueriedUserTest)
{
  // Number of users that we will search for recommendations for.
  size_t numUsers = 10;

  // Default number of recommendations.
  size_t numRecsDefault = 5;

  // Create dummy query set.
  arma::Col<size_t> users = arma::zeros<arma::Col<size_t> >(numUsers, 1);
  for (size_t i = 0; i < numUsers; i++)
    users(i) = i;

  // Matrix to save recommendations into.
  arma::Mat<size_t> recommendations;

  // Load GroupLens data.
  arma::mat dataset;
  data::Load("GroupLensSmall.csv", dataset);

  // Make data into sparse matrix.
  arma::sp_mat cleanedData;
  CF::CleanData(dataset, cleanedData);

  CF c(cleanedData);

  // Generate recommendations when query set is specified.
  c.GetRecommendations(numRecsDefault, recommendations, users);

  // Check if correct number of recommendations are generated.
  BOOST_REQUIRE_EQUAL(recommendations.n_rows, numRecsDefault);

  // Check if recommendations are generated for the right number of users.
  BOOST_REQUIRE_EQUAL(recommendations.n_cols, numUsers);
}

/**
 * Make sure recommendations that are generated are reasonably accurate.
 */
BOOST_AUTO_TEST_CASE(RecommendationAccuracyTest)
{
  // Load the GroupLens dataset; then, we will remove some values from it.
  arma::mat dataset;
  data::Load("GroupLensSmall.csv", dataset);

  // Save the columns we've removed.
  arma::mat savedCols(3, 50); // Remove 50 5-star ratings.
  savedCols.fill(/* random very large value */ 10000000);
  size_t currentCol = 0;
  for (size_t i = 0; i < dataset.n_cols; ++i)
  {
    if (currentCol == 50)
      break;

    if (dataset(2, i) > 4.5) // 5-star rating.
    {
      // Make sure we don't have this user yet.  This is a slow way to do this
      // but I don't particularly care here because it's in the tests.
      bool found = false;
      for (size_t j = 0; j < currentCol; ++j)
      {
        if (savedCols(0, j) == dataset(0, i))
        {
          found = true;
          break;
        }
      }

      // If this user doesn't already exist in savedCols, add them.  Otherwise
      // ignore this point.
      if (!found)
      {
        savedCols.col(currentCol) = dataset.col(i);
        dataset.shed_col(i);
        ++currentCol;
      }
    }
  }

  // Make data into sparse matrix.
  arma::sp_mat cleanedData;
  CF::CleanData(dataset, cleanedData);

  // Now create the CF object.
  CF c(cleanedData);

  // Obtain 150 recommendations for the users in savedCols, and make sure the
  // missing item shows up in most of them.  First, create the list of users,
  // which requires casting from doubles...
  arma::Col<size_t> users(50);
  for (size_t i = 0; i < 50; ++i)
    users(i) = (size_t) savedCols(0, i);
  arma::Mat<size_t> recommendations;
  size_t numRecs = 150;
  c.GetRecommendations(numRecs, recommendations, users);

  BOOST_REQUIRE_EQUAL(recommendations.n_rows, numRecs);
  BOOST_REQUIRE_EQUAL(recommendations.n_cols, 50);

  size_t failures = 0;
  for (size_t i = 0; i < 50; ++i)
  {
    size_t targetItem = (size_t) savedCols(1, i);
    bool found = false;
    // Make sure the target item shows up in the recommendations.
    for (size_t j = 0; j < numRecs; ++j)
    {
      const size_t user = users(i);
      const size_t item = recommendations(j, i);
      if (item == targetItem)
      {
        found = true;
      }
      else
      {
        // Make sure we aren't being recommended an item that the user already
        // rated.
        BOOST_REQUIRE_EQUAL((double) c.CleanedData()(item, user), 0.0);
      }
    }

    if (!found)
      ++failures;
  }

  // Make sure the right item showed up in at least 1/3 of the recommendations.
  // GroupLens dataset would give somewhere around a 10% success rate (failures
  // would be closer to 270).  The failure rate is allowed to be so high because
  // the dataset used here is pretty small and it is hard to generalize.
  BOOST_REQUIRE_LT(failures, 17);
}

// Make sure that Predict() is returning reasonable results.
BOOST_AUTO_TEST_CASE(CFPredictTest)
{
  // Load the GroupLens dataset; then, we will remove some values from it.
  arma::mat dataset;
  data::Load("GroupLensSmall.csv", dataset);

  // Save the columns we've removed.
  arma::mat savedCols(3, 50); // Remove 50 5-star ratings.
  savedCols.fill(/* random very large value */ 10000000);
  size_t currentCol = 0;
  for (size_t i = 0; i < dataset.n_cols; ++i)
  {
    if (currentCol == 50)
      break;

    if (dataset(2, i) > 4.5) // 5-star rating.
    {
      // Make sure we don't have this user yet.  This is a slow way to do this
      // but I don't particularly care here because it's in the tests.
      bool found = false;
      for (size_t j = 0; j < currentCol; ++j)
      {
        if (savedCols(0, j) == dataset(0, i))
        {
          found = true;
          break;
        }
      }

      // If this user doesn't already exist in savedCols, add them.  Otherwise
      // ignore this point.
      if (!found)
      {
        savedCols.col(currentCol) = dataset.col(i);
        dataset.shed_col(i);
        ++currentCol;
      }
    }
  }

  // Make data into sparse matrix.
  arma::sp_mat cleanedData;
  CF::CleanData(dataset, cleanedData);

  // Now create the CF object.
  CF c(cleanedData);

  // Now, for each removed rating, make sure the prediction is... reasonably
  // accurate.
  double totalError = 0.0;
  for (size_t i = 0; i < savedCols.n_cols; ++i)
  {
    const double prediction = c.Predict(savedCols(0, i), savedCols(1, i));

    const double error = std::pow(prediction - savedCols(2, i), 2.0);
    totalError += error;
  }

  totalError = std::sqrt(totalError) / savedCols.n_cols;

  // The mean squared error should be less than one.
  BOOST_REQUIRE_LT(totalError, 0.5);
}

// Do the same thing as the previous test, but ensure that the ratings we
// predict with the batch Predict() are the same as the individual Predict()
// calls.
BOOST_AUTO_TEST_CASE(CFBatchPredictTest)
{
  // Load the GroupLens dataset; then, we will remove some values from it.
  arma::mat dataset;
  data::Load("GroupLensSmall.csv", dataset);

  // Save the columns we've removed.
  arma::mat savedCols(3, 50); // Remove 50 5-star ratings.
  savedCols.fill(/* random very large value */ 10000000);
  size_t currentCol = 0;
  for (size_t i = 0; i < dataset.n_cols; ++i)
  {
    if (currentCol == 50)
      break;

    if (dataset(2, i) > 4.5) // 5-star rating.
    {
      // Make sure we don't have this user yet.  This is a slow way to do this
      // but I don't particularly care here because it's in the tests.
      bool found = false;
      for (size_t j = 0; j < currentCol; ++j)
      {
        if (savedCols(0, j) == dataset(0, i))
        {
          found = true;
          break;
        }
      }

      // If this user doesn't already exist in savedCols, add them.  Otherwise
      // ignore this point.
      if (!found)
      {
        savedCols.col(currentCol) = dataset.col(i);
        dataset.shed_col(i);
        ++currentCol;
      }
    }
  }

  // Make data into sparse matrix.
  arma::sp_mat cleanedData;
  CF::CleanData(dataset, cleanedData);

  // Now create the CF object.
  CF c(cleanedData);

  // Get predictions for all user/item pairs we held back.
  arma::Mat<size_t> combinations(2, savedCols.n_cols);
  for (size_t i = 0; i < savedCols.n_cols; ++i)
  {
    combinations(0, i) = size_t(savedCols(0, i));
    combinations(1, i) = size_t(savedCols(1, i));
  }

  arma::vec predictions;
  c.Predict(combinations, predictions);

  for (size_t i = 0; i < combinations.n_cols; ++i)
  {
    const double prediction = c.Predict(combinations(0, i), combinations(1, i));
    BOOST_REQUIRE_CLOSE(prediction, predictions[i], 1e-8);
  }
}

/**
 * Make sure we can train an already-trained model and it works okay.
 */
BOOST_AUTO_TEST_CASE(TrainTest)
{
  // Generate random data.
  arma::sp_mat randomData;
  randomData.sprandu(100, 100, 0.3);
  CF c(randomData);

  // Now retrain with data we know about.
  arma::mat dataset;
  data::Load("GroupLensSmall.csv", dataset);

  // Save the columns we've removed.
  arma::mat savedCols(3, 50); // Remove 50 5-star ratings.
  savedCols.fill(/* random very large value */ 10000000);
  size_t currentCol = 0;
  for (size_t i = 0; i < dataset.n_cols; ++i)
  {
    if (currentCol == 50)
      break;

    if (dataset(2, i) > 4.5) // 5-star rating.
    {
      // Make sure we don't have this user yet.  This is a slow way to do this
      // but I don't particularly care here because it's in the tests.
      bool found = false;
      for (size_t j = 0; j < currentCol; ++j)
      {
        if (savedCols(0, j) == dataset(0, i))
        {
          found = true;
          break;
        }
      }

      // If this user doesn't already exist in savedCols, add them.  Otherwise
      // ignore this point.
      if (!found)
      {
        savedCols.col(currentCol) = dataset.col(i);
        dataset.shed_col(i);
        ++currentCol;
      }
    }
  }

  // Make data into sparse matrix.
  arma::sp_mat cleanedData;
  CF::CleanData(dataset, cleanedData);

  // Now retrain.
  c.Train(dataset);

  // Get predictions for all user/item pairs we held back.
  arma::Mat<size_t> combinations(2, savedCols.n_cols);
  for (size_t i = 0; i < savedCols.n_cols; ++i)
  {
    combinations(0, i) = size_t(savedCols(0, i));
    combinations(1, i) = size_t(savedCols(1, i));
  }

  arma::vec predictions;
  c.Predict(combinations, predictions);

  for (size_t i = 0; i < combinations.n_cols; ++i)
  {
    const double prediction = c.Predict(combinations(0, i), combinations(1, i));
    BOOST_REQUIRE_CLOSE(prediction, predictions[i], 1e-8);
  }
}

/**
 * Make sure we can train a model after using the empty constructor.
 */
BOOST_AUTO_TEST_CASE(EmptyConstructorTrainTest)
{
  // Use default constructor.
  CF c;

  // Now retrain with data we know about.
  arma::mat dataset;
  data::Load("GroupLensSmall.csv", dataset);

  // Save the columns we've removed.
  arma::mat savedCols(3, 50); // Remove 300 5-star ratings.
  savedCols.fill(/* random very large value */ 10000000);
  size_t currentCol = 0;
  for (size_t i = 0; i < dataset.n_cols; ++i)
  {
    if (currentCol == 50)
      break;

    if (dataset(2, i) > 4.5) // 5-star rating.
    {
      // Make sure we don't have this user yet.  This is a slow way to do this
      // but I don't particularly care here because it's in the tests.
      bool found = false;
      for (size_t j = 0; j < currentCol; ++j)
      {
        if (savedCols(0, j) == dataset(0, i))
        {
          found = true;
          break;
        }
      }

      // If this user doesn't already exist in savedCols, add them.  Otherwise
      // ignore this point.
      if (!found)
      {
        savedCols.col(currentCol) = dataset.col(i);
        dataset.shed_col(i);
        ++currentCol;
      }
    }
  }

  // Make data into sparse matrix.
  arma::sp_mat cleanedData;
  CF::CleanData(dataset, cleanedData);

  // Now retrain.
  c.Train(cleanedData);

  // Get predictions for all user/item pairs we held back.
  arma::Mat<size_t> combinations(2, savedCols.n_cols);
  for (size_t i = 0; i < savedCols.n_cols; ++i)
  {
    combinations(0, i) = size_t(savedCols(0, i));
    combinations(1, i) = size_t(savedCols(1, i));
  }

  arma::vec predictions;
  c.Predict(combinations, predictions);

  for (size_t i = 0; i < combinations.n_cols; ++i)
  {
    const double prediction = c.Predict(combinations(0, i), combinations(1, i));
    BOOST_REQUIRE_CLOSE(prediction, predictions[i], 1e-8);
  }
}

/**
 * Ensure we can load and save the CF model.
 */
BOOST_AUTO_TEST_CASE(SerializationTest)
{
  // Load a dataset to train on.
  arma::mat dataset;
  data::Load("GroupLensSmall.csv", dataset);

  arma::sp_mat cleanedData;
  CF::CleanData(dataset, cleanedData);

  CF c(cleanedData);

  arma::sp_mat randomData;
  randomData.sprandu(100, 100, 0.3);

  CF cXml(randomData);
  CF cBinary;
  CF cText(cleanedData, amf::NMFALSFactorizer(), 5, 5);

  SerializeObjectAll(c, cXml, cText, cBinary);

  // Check the internals.
  BOOST_REQUIRE_EQUAL(c.NumUsersForSimilarity(), cXml.NumUsersForSimilarity());
  BOOST_REQUIRE_EQUAL(c.NumUsersForSimilarity(),
      cBinary.NumUsersForSimilarity());
  BOOST_REQUIRE_EQUAL(c.NumUsersForSimilarity(), cText.NumUsersForSimilarity());

  BOOST_REQUIRE_EQUAL(c.Rank(), cXml.Rank());
  BOOST_REQUIRE_EQUAL(c.Rank(), cBinary.Rank());
  BOOST_REQUIRE_EQUAL(c.Rank(), cText.Rank());

  CheckMatrices(c.W(), cXml.W(), cBinary.W(), cText.W());
  CheckMatrices(c.H(), cXml.H(), cBinary.H(), cText.H());

  BOOST_REQUIRE_EQUAL(c.CleanedData().n_rows, cXml.CleanedData().n_rows);
  BOOST_REQUIRE_EQUAL(c.CleanedData().n_rows, cBinary.CleanedData().n_rows);
  BOOST_REQUIRE_EQUAL(c.CleanedData().n_rows, cText.CleanedData().n_rows);

  BOOST_REQUIRE_EQUAL(c.CleanedData().n_cols, cXml.CleanedData().n_cols);
  BOOST_REQUIRE_EQUAL(c.CleanedData().n_cols, cBinary.CleanedData().n_cols);
  BOOST_REQUIRE_EQUAL(c.CleanedData().n_cols, cText.CleanedData().n_cols);

  BOOST_REQUIRE_EQUAL(c.CleanedData().n_nonzero, cXml.CleanedData().n_nonzero);
  BOOST_REQUIRE_EQUAL(c.CleanedData().n_nonzero,
      cBinary.CleanedData().n_nonzero);
  BOOST_REQUIRE_EQUAL(c.CleanedData().n_nonzero, cText.CleanedData().n_nonzero);

  for (size_t i = 0; i <= c.CleanedData().n_cols; ++i)
  {
    BOOST_REQUIRE_EQUAL(c.CleanedData().col_ptrs[i],
        cXml.CleanedData().col_ptrs[i]);
    BOOST_REQUIRE_EQUAL(c.CleanedData().col_ptrs[i],
        cBinary.CleanedData().col_ptrs[i]);
    BOOST_REQUIRE_EQUAL(c.CleanedData().col_ptrs[i],
        cText.CleanedData().col_ptrs[i]);
  }

  for (size_t i = 0; i <= c.CleanedData().n_nonzero; ++i)
  {
    BOOST_REQUIRE_EQUAL(c.CleanedData().row_indices[i],
        cXml.CleanedData().row_indices[i]);
    BOOST_REQUIRE_EQUAL(c.CleanedData().row_indices[i],
        cBinary.CleanedData().row_indices[i]);
    BOOST_REQUIRE_EQUAL(c.CleanedData().row_indices[i],
        cText.CleanedData().row_indices[i]);

    BOOST_REQUIRE_CLOSE(c.CleanedData().values[i], cXml.CleanedData().values[i],
        1e-5);
    BOOST_REQUIRE_CLOSE(c.CleanedData().values[i],
        cBinary.CleanedData().values[i], 1e-5);
    BOOST_REQUIRE_CLOSE(c.CleanedData().values[i],
        cText.CleanedData().values[i], 1e-5);
  }
}

BOOST_AUTO_TEST_SUITE_END();


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Nikita Kaskov <nbering@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#define BOOST_TEST_MODULE qap_test

#include <boost/test/unit_test.hpp>

#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <vector>
#include <chrono>

#include <nil/crypto3/zk/snark/reductions/r1cs_to_qap.hpp>
#include <nil/crypto3/zk/snark/relations/constraint_satisfaction_problems/r1cs.hpp>

#include <nil/crypto3/algebra/random_element.hpp>
#include <nil/crypto3/algebra/curves/mnt4.hpp>
#include <nil/crypto3/algebra/fields/mnt4/base_field.hpp>
#include <nil/crypto3/algebra/fields/mnt4/scalar_field.hpp>
#include <nil/crypto3/algebra/fields/arithmetic_params/mnt4.hpp>
#include <nil/crypto3/algebra/curves/params/multiexp/mnt4.hpp>
#include <nil/crypto3/algebra/curves/params/wnaf/mnt4.hpp>
#include <nil/crypto3/algebra/curves/mnt6.hpp>
#include <nil/crypto3/algebra/fields/mnt6/base_field.hpp>
#include <nil/crypto3/algebra/fields/mnt6/scalar_field.hpp>
#include <nil/crypto3/algebra/fields/arithmetic_params/mnt6.hpp>
#include <nil/crypto3/algebra/curves/params/multiexp/mnt6.hpp>
#include <nil/crypto3/algebra/curves/params/wnaf/mnt6.hpp>

#include "../../schemes/ppzksnark/r1cs_examples.hpp"

using namespace nil::crypto3::zk::snark;
using namespace nil::crypto3::algebra;

template<typename FieldType>
void test_qap(const std::size_t qap_degree, const std::size_t num_inputs, const bool binary_input) {
    /*
      We construct an instance where the QAP degree is qap_degree.
      So we generate an instance of R1CS where the number of constraints qap_degree - num_inputs - 1.
      See the transformation from R1CS to QAP for why this is the case.
      So we need that qap_degree >= num_inputs + 1.
    */
    BOOST_CHECK(num_inputs + 1 <= qap_degree);

    const std::size_t num_constraints = qap_degree - num_inputs - 1;

    std::cout << "Num constraints " << num_constraints << std::endl;
    std::cout << "Binary input " << bool(binary_input) << std::endl;

    auto begin = std::chrono::high_resolution_clock::now();

    r1cs_example<FieldType> example;
    if (binary_input) {
        example = generate_r1cs_example_with_binary_input<FieldType>(num_constraints, num_inputs);
    } else {
        example = generate_r1cs_example_with_field_input<FieldType>(num_constraints, num_inputs);
    }

    auto end = std::chrono::high_resolution_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);

    std::cout << "Example generated, time: " << elapsed.count() * 1e-9 << std::endl;

    BOOST_CHECK(example.constraint_system.is_satisfied(example.primary_input, example.auxiliary_input));

    std::cout << "Constraint system satisfied" << std::endl;

    const typename FieldType::value_type t = random_element<FieldType>(),
                                         d1 = random_element<FieldType>(),
                                         d2 = random_element<FieldType>(),
                                         d3 = random_element<FieldType>();
    begin = std::chrono::high_resolution_clock::now();

    qap_instance<FieldType> qap_inst_1 = reductions::r1cs_to_qap<FieldType>::instance_map(example.constraint_system);

    end = std::chrono::high_resolution_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);

    std::cout << "Instance 1 evaluated, time: " << elapsed.count() * 1e-9 << std::endl;

    begin = std::chrono::high_resolution_clock::now();

    qap_instance_evaluation<FieldType> qap_inst_2 =
        reductions::r1cs_to_qap<FieldType>::instance_map_with_evaluation(example.constraint_system, t);

    end = std::chrono::high_resolution_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);

    std::cout << "Instance 2 evaluated, time: " << elapsed.count() * 1e-9 << std::endl;

    begin = std::chrono::high_resolution_clock::now();

    qap_witness<FieldType> qap_wit =
        reductions::r1cs_to_qap<FieldType>::witness_map(example.constraint_system, example.primary_input, example.auxiliary_input, d1, d2, d3);

    end = std::chrono::high_resolution_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);

    std::cout << "Witness evaluated, time: " << elapsed.count() * 1e-9 << std::endl;

    BOOST_CHECK(qap_inst_1.is_satisfied(qap_wit));
    BOOST_CHECK(qap_inst_2.is_satisfied(qap_wit));
}

BOOST_AUTO_TEST_SUITE(qap_test_suite)

BOOST_AUTO_TEST_CASE(qap_test_case) {
    const std::size_t num_inputs = 10;

    using basic_curve_type = curves::mnt6<298>;

    const std::size_t basic_domain_size = 1ul << fields::arithmetic_params<basic_curve_type::scalar_field_type>::s;
    const std::size_t step_domain_size = (1ul << 10) + (1ul << 8);
    const std::size_t extended_domain_size = 1ul << (fields::arithmetic_params<basic_curve_type::scalar_field_type>::s + 1);
    const std::size_t extended_domain_size_special = extended_domain_size - 1;

    test_qap<typename basic_curve_type::scalar_field_type>(basic_domain_size, num_inputs, true);
    test_qap<typename basic_curve_type::scalar_field_type>(step_domain_size, num_inputs, true);
    test_qap<typename basic_curve_type::scalar_field_type>(extended_domain_size, num_inputs, true);
    test_qap<typename basic_curve_type::scalar_field_type>(extended_domain_size_special, num_inputs, true);

    test_qap<typename basic_curve_type::scalar_field_type>(basic_domain_size, num_inputs, false);
    test_qap<typename basic_curve_type::scalar_field_type>(step_domain_size, num_inputs, false);
    test_qap<typename basic_curve_type::scalar_field_type>(extended_domain_size, num_inputs, false);
    test_qap<typename basic_curve_type::scalar_field_type>(extended_domain_size_special, num_inputs, false);
}

BOOST_AUTO_TEST_SUITE_END()


#include <scitbx/array_family/boost_python/flex_fwd.h>
#include <scitbx/random.h>
#include <scitbx/random_legacy_boost_1_63.h>

#include <boost/python/class.hpp>
#include <boost/python/args.hpp>

namespace scitbx { namespace af { namespace boost_python {

namespace {

  struct mersenne_twister_wrappers
  {
    typedef random::mersenne_twister w_t;
    typedef random_legacy_boost_1_63::mersenne_twister wl_t;

    static void
    wrap()
    {
      using namespace boost::python;
      using boost::python::arg;
      class_<wl_t>("mersenne_twister_legacy_boost_1_63",
        "Results for seeded random r3 rotation matrices changed as of Boost 1.70.\n"
        "This class restores the legacy behavior.",
        no_init)
        .def(init<unsigned>((arg("seed")=0)))
        .def(init<boost_random::mt19937 &>())
        .def("random_double_unit_quaternion",
          &wl_t::random_double_unit_quaternion)
        .def("random_double_r3_rotation_matrix",
          &wl_t::random_double_r3_rotation_matrix)
      ;
      class_<w_t>("mersenne_twister", no_init)
        .def(init<unsigned>((arg("seed")=0)))
        .def(init<boost_random::mt19937 &>())
        .def("random_size_t_min", &w_t::random_size_t_min)
        .def("random_size_t_max", &w_t::random_size_t_max)
        .def("seed", &w_t::seed, (arg("value")=0))
        .def("random_size_t", (std::size_t(w_t::*)()) &w_t::random_size_t)
        .def("random_size_t",
          (af::shared<std::size_t>(w_t::*)(std::size_t))
            &w_t::random_size_t, (arg("size")))
        .def("random_size_t",
          (af::shared<std::size_t>(w_t::*)(std::size_t, std::size_t))
            &w_t::random_size_t, (arg("size"), arg("modulus")))
        .def("random_double", (double(w_t::*)()) &w_t::random_double)
        .def("random_double", (af::shared<double>(w_t::*)(std::size_t))
          &w_t::random_double, (arg("size")))
        .def("random_double",
          (af::shared<double>(w_t::*)(std::size_t, double))
            &w_t::random_double, (arg("size"), arg("factor")))
        .def("random_bool", &w_t::random_bool, (
          arg("size"), arg("threshold")))
        .def("random_permutation", &w_t::random_permutation, (arg("size")))
        .def("random_double_point_on_sphere",
          &w_t::random_double_point_on_sphere)
        .def("random_double_unit_quaternion",
          &w_t::random_double_unit_quaternion)
        .def("random_double_r3_rotation_matrix",
          &w_t::random_double_r3_rotation_matrix)
        .def("random_double_r3_rotation_matrix_arvo_1992",
          &w_t::random_double_r3_rotation_matrix_arvo_1992)
        .def("random_int_gaussian_distribution",
          (af::shared<int>(w_t::*)(std::size_t,const double&,const double&))
          &w_t::random_int_gaussian_distribution,
          (arg("size"), arg("mu"), arg("sigma"))
          )
        .def("getstate", &w_t::getstate)
        .def("setstate", &w_t::setstate, (arg("state")))
      ;
    }
  };

} // namespace <anonymous>

  void wrap_flex_random()
  {
    mersenne_twister_wrappers::wrap();
  }

}}} // namespace scitbx::af::boost_python


#include <boost/test/unit_test.hpp>

#include "../../helpers/cycleDetectorVisitor.h"

BOOST_AUTO_TEST_SUITE(cycle_detection_tests)

BOOST_AUTO_TEST_CASE(cycle_should_be_found)
{
	CategoryHirachyGraph graph(10);

	std::vector<std::size_t> edges = {
		0, 1,
		1, 2,
		2, 3,
		2, 4,
		0, 5,
		5, 6,
		6, 7,
		7, 5,
		5, 8,
		8, 9
	};

	for(std::size_t i = 0; i < edges.size(); i += 2)
		boost::add_edge(edges[i], edges[i+1], graph);

	std::vector<std::vector<CategoryHirachyGraph::vertex_descriptor>> cycles;
	CycleDetectorDfsVisitor vis(cycles);

	boost::depth_first_search(graph, boost::visitor(vis));

	BOOST_CHECK_EQUAL(cycles.size(), 1);

	std::set<CategoryHirachyGraph::vertex_descriptor> tmp(cycles[0].begin(), cycles[0].end());
	BOOST_CHECK(tmp.find(5) != tmp.end());
	BOOST_CHECK(tmp.find(6) != tmp.end());
	BOOST_CHECK(tmp.find(7) != tmp.end());
}

BOOST_AUTO_TEST_SUITE_END()


#include <unordered_set>
#include <unordered_map>
#include <boost/dynamic_bitset.hpp>
#include "graph.h++"

PeersGraph::PeersGraph(void)
{
    return;
}

PeersGraph::PeersGraph(const PeersGraph& t)
{
    W = t.W;
}

PeersGraph::PeersGraph(HierarchicalGossipConnector tab[], size_t num)
{
    W = boost::numeric::ublas::matrix<double>(num,num);
    for(size_t i = 0; i < num; ++i)
    {
        for(size_t j = 0; j < num; ++j)
        {
            W.insert_element(i,j,0.0);
        }
    }
    std::unordered_map<boost::dynamic_bitset<>,size_t> index(num);
    for(size_t i = 0; i < num; ++i)
    {
        index.emplace(tab[i].getAddress(),i);
    }
    for(size_t i = 0; i < num; ++i)
    {
        std::unordered_map<boost::dynamic_bitset<>,double> peers = tab[i].outEdges();
        for(auto& p : peers)
        {
            W.insert_element(i,index[p.first],p.second);
        }
    }
}

PeersGraph::~PeersGraph(void)
{
    return;
}

PeersGraph& PeersGraph::operator=(const PeersGraph& t)
{
    W = t.W;
    return *this;
}

std::valarray<unsigned> PeersGraph::inDegs(void) const
{
    std::valarray<unsigned> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(j,i) > 0.0)
            {
                ++ans[i];
            }
        }
    }
    return ans;
}

std::valarray<unsigned> PeersGraph::outDegs(void) const
{
    std::valarray<unsigned> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(i,j) > 0.0)
            {
                ++ans[i];
            }
        }
    }
    return ans;
}

std::valarray<unsigned> PeersGraph::totDegs(void) const
{
    std::valarray<unsigned> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(i,j) > 0.0)
            {
                ++ans[i];
            }
            if(W(j,i) > 0.0)
            {
                ++ans[i];
            }
        }
    }
    return ans;
}

std::valarray<unsigned> PeersGraph::biDegs(void) const
{
    std::valarray<unsigned> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if((W(i,j) > 0.0) && (W(j,i) > 0.0))
            {
                ++ans[i];
            }
        }
    }
    return ans;
}

std::valarray<unsigned> PeersGraph::degs(void) const
{
    std::valarray<unsigned> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if((W(i,j) > 0.0) || (W(j,i) > 0.0))
            {
                ++ans[i];
            }
        }
    }
    return ans;
}

std::valarray<double> PeersGraph::inWeights(void) const
{
    std::valarray<double> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            ans[i]+= W(j,i);
        }
    }
    return ans;
}

std::valarray<double> PeersGraph::outWeights(void) const
{
    std::valarray<double> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            ans[i]+= W(i,j);
        }
    }
    return ans;
}

std::valarray<double> PeersGraph::totWeights(void) const
{
    std::valarray<double> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            ans[i]+= W(i,j);
            ans[i]+= W(j,i);
        }
    }
    return ans;
}

std::valarray<double> PeersGraph::biWeights(void) const
{
    std::valarray<double> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if((W(i,j) > 0.0) && (W(j,i) > 0.0))
            {
                ans[i]+= (W(i,j)+W(j,i))/2;
            }
        }
    }
    return ans;
}

std::valarray<double> PeersGraph::weights(void) const
{
    std::valarray<double> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if((W(i,j) > 0.0) || (W(j,i) > 0.0))
            {
                ans[i]+= (W(i,j)+W(j,i))/2;
            }
        }
    }
    return ans;
}

double PeersGraph::avgIODeg(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(i,j) > 0.0)
            {
                ans+= 1.0;
            }
        }
    }
    ans/= W.size1();
    return ans;
}

double PeersGraph::avgTotDeg(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(i,j) > 0.0)
            {
                ans+= 2.0;
            }
        }
    }
    ans/= W.size1();
    return ans;
}

double PeersGraph::avgBiDeg(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if((W(i,j) > 0.0) && (W(j,i) > 0.0))
            {
                ans+= 1.0;
            }
        }
    }
    ans/= W.size1();
    return ans;
}

double PeersGraph::avgDeg(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if((W(i,j) > 0.0) || (W(j,i) > 0.0))
            {
                ans+= 1.0;
            }
        }
    }
    ans/= W.size1();
    return ans;
}

double PeersGraph::avgIOWeight(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            ans+= W(i,j);
        }
    }
    ans/= W.size1();
    return ans;
}

double PeersGraph::avgTotWeight(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            ans+= W(i,j);
        }
    }
    ans*= 2;
    ans/= W.size1();
    return ans;
}

double PeersGraph::avgBiWeight(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if((W(i,j) > 0.0) && (W(j,i) > 0.0))
            {
                ans+= (W(i,j)+W(j,i))/2;
            }
        }
    }
    ans*= 2;
    ans/= W.size1();
    return ans;
}

double PeersGraph::avgWeight(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if((W(i,j) > 0.0) || (W(j,i) > 0.0))
            {
                ans+= (W(i,j)+W(j,i))/2;
            }
        }
    }
    ans/= W.size1();
    return ans;
}

std::valarray<double> PeersGraph::bidirShares(void) const
{
    std::valarray<double> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        unsigned l = 0;
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if((W(i,j) > 0.0) && (W(j,i) > 0.0))
            {
                ++l;
                ans[i]+= 1.0;
            }
            else if((W(i,j) > 0.0) || (W(j,i) > 0.0))
            {
                ++l;
            }
        }
        if(l != 0)
        {
            ans[i]/= l;
        }
    }
    return ans;
}

double PeersGraph::bidirShare(void) const
{
    double ans = 0.0;
    unsigned l = 0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if((W(i,j) > 0.0) && (W(j,i) > 0.0))
            {
                ++l;
                ans+= 1.0;
            }
            else if((W(i,j) > 0.0) || (W(j,i) > 0.0))
            {
                ++l;
            }
        }
    }
    if(l != 0)
    {
        ans/= l;
    }
    return ans;
}

std::valarray<int> PeersGraph::difDegs(void) const
{
    std::valarray<int> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(i,j) > 0.0)
            {
                ++ans[i];
            }
            if(W(j,i) > 0.0)
            {
                --ans[i];
            }
        }
    }
    return ans;
}

std::valarray<double> PeersGraph::difWeights(void) const
{
    std::valarray<double> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        for(size_t j = 0; j < W.size2(); ++j)
        {
            ans[i]+= W(i,j);
            ans[i]-= W(j,i);
        }
    }
    return ans;
}

double PeersGraph::avgAbsDifDeg(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        int di = 0;
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(i,j) > 0.0)
            {
                ++di;
            }
            if(W(j,i) > 0.0)
            {
                --di;
            }
        }
        ans+= abs(di);
    }
    ans/= W.size1();
    return ans;
}

double PeersGraph::avgAbsDifWeight(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        double wi = 0;
        for(size_t j = 0; j < W.size2(); ++j)
        {
            wi+= W(i,j);
            wi-= W(j,i);
        }
        ans+= abs(wi);
    }
    ans/= W.size1();
    return ans;
}

std::valarray<double> PeersGraph::locUwClustCoefs(void) const
{
    std::valarray<double> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        unsigned div = 0;
        std::unordered_set<size_t> neibi, neibo;
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(i,j) > 0.0)
            {
                neibo.insert(j);
            }
            if(W(j,i) > 0.0)
            {
                neibi.insert(j);
            }
        }
        for(auto& j : neibo)
        {
            for(auto& k : neibi)
            {
                if(k != j)
                {
                    if(W(j,k) > 0.0)
                    {
                        ans+= 1.0;
                    }
                    div++;
                }
            }
        }
        if(div != 0)
        {
            ans/= div;
        }
    }
    return ans;
}

std::valarray<double> PeersGraph::locClustCoefs(void) const
{
    std::valarray<double> ans(W.size1());
    for(size_t i = 0; i < W.size1(); ++i)
    {
        double div = 0;
        std::unordered_set<size_t> neibi, neibo;
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(i,j) > 0.0)
            {
                neibo.insert(j);
            }
            if(W(j,i) > 0.0)
            {
                neibi.insert(j);
            }
        }
        for(auto& j : neibo)
        {
            for(auto& k : neibi)
            {
                if(k != j)
                {
                    if(W(j,k) > 0.0)
                    {
                        ans+= (W(i,j)+W(k,i));
                    }
                    div+= (W(i,j)+W(k,i));
                }
            }
        }
        if(div != 0)
        {
            ans/= div;
        }
    }
    return ans;
}

double PeersGraph::avgUwClustCoef(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        unsigned num = 0, den = 0;
        std::unordered_set<size_t> neibi, neibo;
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(i,j) > 0.0)
            {
                neibo.insert(j);
            }
            if(W(j,i) > 0.0)
            {
                neibi.insert(j);
            }
        }
        for(auto& j : neibo)
        {
            for(auto& k : neibi)
            {
                if(k != j)
                {
                    if(W(j,k) > 0.0)
                    {
                        num++;
                    }
                    den++;
                }
            }
        }
        if(den != 0)
        {
            ans+= (num/den);
        }
    }
    ans/= W.size1();
    return ans;
}

double PeersGraph::avgClustCoef(void) const
{
    double ans = 0.0;
    for(size_t i = 0; i < W.size1(); ++i)
    {
        double num = 0.0, den = 0.0;
        std::unordered_set<size_t> neibi, neibo;
        for(size_t j = 0; j < W.size2(); ++j)
        {
            if(W(i,j) > 0.0)
            {
                neibo.insert(j);
            }
            if(W(j,i) > 0.0)
            {
                neibi.insert(j);
            }
        }
        for(auto& j : neibo)
        {
            for(auto& k : neibi)
            {
                if(k != j)
                {
                    if(W(j,k) > 0.0)
                    {
                        num+= (W(i,j)+W(k,i));
                    }
                    den+= (W(i,j)+W(k,i));
                }
            }
        }
        if(den != 0.0)
        {
            ans+= (num/den);
        }
    }
    ans/= W.size1();
    return ans;
}

unsigned PeersGraph::diameter(void) const
{
    boost::numeric::ublas::matrix<double> B = W+boost::numeric::ublas::identity_matrix<double>(W.size1()), M = boost::numeric::ublas::identity_matrix<double>(W.size1());
    unsigned pn, c = W.size1(), k = 0;
    do
    {
        pn = c;
        c = 0;
        M = prod(M,B);
        for(size_t i = 0; i < W.size1(); ++i)
        {
            for(size_t j = 0; j < W.size2(); ++j)
            {
                if(M(i,j) > 0.0)
                {
                    c++;
                }
            }
        }
        k++;
    }
    while(c>pn);
    return k-1;
}

double PeersGraph::avgShortPathLength(void) const
{
    boost::numeric::ublas::matrix<double> B = W+boost::numeric::ublas::identity_matrix<double>(W.size1()), M = boost::numeric::ublas::identity_matrix<double>(W.size1());
    double ans = 0;
    unsigned pn, c = W.size1(), k = 1;
    do
    {
        pn = c;
        c = 0;
        M = prod(M,B);
        for(size_t i = 0; i < W.size1(); ++i)
        {
            for(size_t j = 0; j < W.size2(); ++j)
            {
                if(M(i,j) > 0.0)
                {
                    c++;
                }
            }
        }
        ans+= k*(c-pn);
        k++;
    }
    while(c>pn);
    ans/= (W.size1()*(W.size2()-1));
    return ans;
}


#include "drake/solvers/fbstab/components/mpc_feasibility.h"

#include <algorithm>
#include <stdexcept>

#include <Eigen/Dense>

#include "drake/solvers/fbstab/components/mpc_data.h"
#include "drake/solvers/fbstab/components/mpc_variable.h"

namespace drake {
namespace solvers {
namespace fbstab {

MpcFeasibility::MpcFeasibility(int N, int nx, int nu, int nc) {
  if (N <= 0 || nx <= 0 || nu <= 0 || nc <= 0) {
    throw std::runtime_error(
        "All size inputs to MpcFeasibility::MpcFeasibility must be >= 1.");
  }
  nx_ = nx;
  nu_ = nu;
  nc_ = nc;
  N_ = N;

  nz_ = (nx_ + nu_) * (N_ + 1);
  nl_ = nx_ * (N_ + 1);
  nv_ = nc_ * (N_ + 1);

  tz_.resize(nz_);
  tl_.resize(nl_);
  tv_.resize(nv_);
}

void MpcFeasibility::ComputeFeasibility(const MpcVariable& x, double tol) {
  const MpcData* const data = x.data();
  if (x.N_ != N_ || x.nx_ != nx_ || x.nu_ != nu_ || x.nc_ != nc_) {
    throw std::runtime_error(
        "In MpcFeasibility::ComputeFeasibility: size mismatch between *this "
        "and x.");
  }
  // The conditions for dual-infeasibility are:
  // max(Az) <= 0 and f'*z < 0 and |Hz| <= tol * |z| and |Gz| <= tol*|z|

  // Compute d1 = max(Az).
  data->gemvA(x.z(), 1.0, 0.0, &tv_);
  const double d1 = tv_.maxCoeff();

  // Compute d2 = infnorm(Gz).
  data->gemvG(x.z(), 1.0, 0.0, &tl_);
  const double d2 = tl_.lpNorm<Eigen::Infinity>();

  // Compute d3 = infnorm(Hz).
  data->gemvH(x.z(), 1.0, 0.0, &tz_);
  const double d3 = tz_.lpNorm<Eigen::Infinity>();

  // Compute d4 = f'*z
  tz_.setConstant(0.0);
  data->axpyf(1.0, &tz_);
  const double d4 = tz_.dot(x.z());

  double w = x.z().lpNorm<Eigen::Infinity>();
  if ((d1 <= w * tol) && (d2 <= tol * w) && (d3 <= tol * w) && (d4 < 0) &&
      (w > 1e-14)) {
    dual_feasible_ = false;
  } else {
    dual_feasible_ = true;
  }

  // The conditions for primal infeasibility are:
  // v'*b + l'*h < 0 and |A'*v + G'*l| \leq tol * |(v,l)|

  // Compute p1 = infnorm(G'*l + A'*v).
  tz_.fill(0.0);
  data->gemvAT(x.v(), 1.0, 1.0, &tz_);
  data->gemvGT(x.l(), 1.0, 1.0, &tz_);
  const double p1 = tz_.lpNorm<Eigen::Infinity>();

  // Compute p2 = v'*b + l'*h.
  tv_.setConstant(0.0);
  data->axpyb(1.0, &tv_);
  tl_.setConstant(0.0);
  data->axpyh(1.0, &tl_);
  const double p2 = tl_.dot(x.l()) + tv_.dot(x.v());

  const double u = std::max(x.v().lpNorm<Eigen::Infinity>(),
                            x.l().lpNorm<Eigen::Infinity>());
  if ((p1 <= tol * u) && (p2 < 0)) {
    primal_feasible_ = false;
  } else {
    primal_feasible_ = true;
  }
}

}  // namespace fbstab
}  // namespace solvers
}  // namespace drake


/*
 * Copyright 2017 Will Mitchell
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

#include <algorithm>
using std::remove_if;
#include <cctype>
using std::isalpha;
#include <cstdlib>
#include <iostream>
using std::cerr;
using std::cout;
using std::endl;
#include <fstream>
using std::ifstream;
using std::ofstream;
#include <random>
using std::mt19937;
using std::uniform_int_distribution;
#include <string>
using std::string;

#include <boost/algorithm/string.hpp>

#include "bloomfilter/BloomFilter.h"
using bloomfilter::BloomFilterStandard;
#include "bloomfilter/HashSet.h"
using bloomfilter::HashSetPair;
#include "util/ByteVector.h"
using util::ByteVector::toByteVector;
#include "util/Hexadecimal.h"
#include "util/random/String.h"
using util::random::uniformString;

string encode(const string& in, const string& key1, const string& key2);
string normalize(const string& in);

string encode(const string& in, const string& skey1, const string& skey2) {
  auto key1 = toByteVector(skey1);
  auto key2 = toByteVector(skey2);
  const unsigned m = 1000;
  const unsigned k = 30;

  // HashSetPair generates k hashes from just two hashes. They are of the form
  // h_i(x) = hash_1(x) + i * hash_2(b), where i ranges from 0 to k-1.
  HashSetPair hs(k);
  // These HMAC hashes follow the standard HMAC nested hashing using ipad and opad
  // from FIPS-180
  hs.addHMAC(hash::SHA_256, key1).addHMAC(hash::SHA_256, key2);

  // BloomFilterStandard encoded the inserted words using bigrams, the start
  // sentenel of ^ and the stop sentinel of $.
  BloomFilterStandard filter(m, hs);

  filter.insert(in);

  string binary;
  // Store into out the binary string in msb to lsb order
  boost::to_string(filter.raw(), binary);

  // Hex now contains encoding in lsb to msb order
  string hex = util::binaryStringToHexString(binary.rbegin(), binary.rend());

  return hex;
}

string normalize(const string& in) {
  // No filtering
  return in;
  /*
  string out{in};
  // Convert to all lower case
  boost::algorithm::to_lower(out);

  // Remove characters that fail isalpha
  out.erase(remove_if(out.begin(), out.end(), [](char a) { return !isalpha(a); }),
	    out.end());
  return out;
  */
}

int main(int argc, const char* argv[]) {
  // Open file on argv[1] as input, and derive names for output
  if (argc != 2) {
    cerr << "Invalid usage. First parameter should be source filename" << endl;
    return EXIT_FAILURE;
  }

  string inputFilename{argv[1]};
  string filteredFilename{inputFilename + ".filtered"};
  string encodedFilename{inputFilename + ".encoded"};
  string keysFilename{inputFilename + ".keys"};

  cout << "Reading from: " << inputFilename << "\n"
       << "Writing filtered input to: " << filteredFilename << "\n"
       << "Writing encoded to: " << encodedFilename << "\n"
       << "With keys to: " << keysFilename << endl;

  ifstream input(inputFilename, std::ios::in);
  if (!input) {
    cerr << "Failed to open " << inputFilename << " for reading" << endl;
    return EXIT_FAILURE;
  }

  ofstream filtered(filteredFilename, std::ios::out);
  if (!filtered) {
    cerr << "Failed to open " << filteredFilename << " for writing" << endl;
    return EXIT_FAILURE;
  }

  ofstream encoded(encodedFilename, std::ios::out);
  if (!encoded) {
    cerr << "Failed to open " << encodedFilename << " for writing" << endl;
    return EXIT_FAILURE;
  }

  ofstream keys(keysFilename, std::ios::out);
  if (!keys) {
    cerr << "Failed to open " << keysFilename << " for writing" << endl;
    return EXIT_FAILURE;
  }

  // Generate random keys
  // Set up Mersenne Twister RNG
  // Use a set seed
  //mt19937 mt(1);

  // Use a good seed, if possible
  std::random_device seed;
  mt19937 mt(seed());

  const string hexAlpha = "0123456789ABCDEF";

  uniform_int_distribution<string::size_type> uniform_dist(0, hexAlpha.size() - 1);

  string key1 = uniformString(hexAlpha, 64, uniform_dist, mt);
  string key2 = uniformString(hexAlpha, 64, uniform_dist, mt);

  keys << "key1: " << key1 << "\n" << "key2: " << key2 << endl;
  keys.close();
  cout << "Keys (" << key1 << ", " << key2 << ") written." << endl;

  // For each line of input, shove it in a Bloom Filter and write it as output
  for(string line; std::getline(input, line);) {
    auto normed = normalize(line);
    filtered << normed << "\n";
    encoded << encode(normed, key1, key2) << "\n";
  }

  encoded.close();

  return 0;
}


#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/reverse_graph.hpp>

#include "policy.hpp"

using namespace mstar;

Policy::Policy(const Graph &g, const RobCoord goal){
  g_ = g;
  costs_ = std::vector<double>(num_vertices(g_));
  predecessors_.resize(boost::num_vertices(g_));

  boost::dijkstra_shortest_paths(
    boost::make_reverse_graph(g_), goal,
    boost::predecessor_map(&predecessors_[0]).distance_map(&costs_[0]));
  edge_weight_map_ = boost::get(boost::edge_weight_t(), g_);
}

double Policy::get_cost(RobCoord coord){
  return costs_[coord];
}

double Policy::get_edge_cost(RobCoord u, RobCoord v){
  // boost::edge returns pair<edge_descriptor, bool>
  return boost::get(edge_weight_map_, boost::edge(u, v, g_).first);
}

std::vector<RobCoord> Policy::get_out_neighbors(RobCoord coord){
  std::vector<RobCoord> out;
  for (auto adj_verts = boost::adjacent_vertices(coord, g_);
       adj_verts.first != adj_verts.second; adj_verts.first++){
    out.push_back(*(adj_verts.first));
  }
  return out;
}

RobCoord Policy::get_step(RobCoord coord){
  return predecessors_[coord];
}


// Copyright (C) 2011 by the BEM++ Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "../check_arrays_are_close.hpp"
#include "../type_template.hpp"
#include "../random_arrays.hpp"

#include "create_regular_grid.hpp"

#include "assembly/assembly_options.hpp"
#include "assembly/discrete_boundary_operator.hpp"
#include "assembly/boundary_operator.hpp"
#include "assembly/context.hpp"
#include "assembly/identity_operator.hpp"
#include "assembly/numerical_quadrature_strategy.hpp"

#include "assembly/modified_helmholtz_3d_single_layer_boundary_operator.hpp"

#include "common/boost_make_shared_fwd.hpp"

#include "grid/grid_factory.hpp"
#include "grid/grid.hpp"

#include "space/piecewise_linear_continuous_scalar_space.hpp"
#include "space/piecewise_constant_scalar_space.hpp"

#include <algorithm>
#include "common/armadillo_fwd.hpp"
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/version.hpp>
#include <complex>

// Tests

using namespace Bempp;

namespace
{

template <typename T> T initWaveNumber();
template <> float initWaveNumber() { return 1.2f; }
template <> double initWaveNumber(){ return 1.2; }
template <> std::complex<float> initWaveNumber()
{ return std::complex<float>(1.2f, 0.7f); }
template <> std::complex<double> initWaveNumber()
{ return std::complex<double>(1.2, 0.7); }

template <typename BFT, typename RT>
struct DiscreteBoundaryOperatorSumFixture
{
    DiscreteBoundaryOperatorSumFixture()
    {
        grid = createRegularTriangularGrid();

        shared_ptr<Space<BFT> > pwiseConstants(
            new PiecewiseConstantScalarSpace<BFT>(grid));
        shared_ptr<Space<BFT> > pwiseLinears(
            new PiecewiseLinearContinuousScalarSpace<BFT>(grid));

        AssemblyOptions assemblyOptions;
        assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
        shared_ptr<NumericalQuadratureStrategy<BFT, RT> > quadStrategy(
            new NumericalQuadratureStrategy<BFT, RT>);
        shared_ptr<Context<BFT, RT> > context(
            new Context<BFT, RT>(quadStrategy, assemblyOptions));

        const RT waveNumber = initWaveNumber<RT>();

        BoundaryOperator<BFT, RT> slp =
            modifiedHelmholtz3dSingleLayerBoundaryOperator<BFT, RT, RT>(
            context, pwiseConstants, pwiseConstants, pwiseLinears, waveNumber);
        BoundaryOperator<BFT, RT> id =
            identityOperator<BFT, RT>(
            context, pwiseConstants, pwiseConstants, pwiseLinears);
        op = slp + id;
    }

    shared_ptr<Grid> grid;
    BoundaryOperator<BFT, RT> op;
};

} // namespace

BOOST_AUTO_TEST_SUITE(DiscreteBoundaryOperatorSum)

BOOST_AUTO_TEST_CASE_TEMPLATE(builtin_apply_works_correctly_for_alpha_equal_to_2_and_beta_equal_to_0_and_y_initialized_to_nans, ResultType, result_types)
{
    std::srand(1);

    typedef ResultType RT;
    typedef typename Fiber::ScalarTraits<RT>::RealType BFT;
    typedef typename Fiber::ScalarTraits<RT>::RealType CT;

    DiscreteBoundaryOperatorSumFixture<BFT, RT> fixture;
    shared_ptr<const DiscreteBoundaryOperator<RT> > dop = fixture.op.weakForm();

    RT alpha(2.);
    RT beta(0.);

    arma::Col<RT> x = generateRandomVector<RT>(dop->columnCount());
    arma::Col<RT> y(dop->rowCount());
    y.fill(std::numeric_limits<CT>::quiet_NaN());

    arma::Col<RT> expected = alpha * dop->asMatrix() * x;

    dop->apply(NO_TRANSPOSE, x, y, alpha, beta);

    BOOST_CHECK(y.is_finite());
    BOOST_CHECK(check_arrays_are_close<RT>(y, expected,
                                           10. * std::numeric_limits<CT>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(builtin_apply_works_correctly_for_alpha_equal_to_2_plus_3j_and_beta_equal_to_0_and_y_initialized_to_nans, ResultType, complex_result_types)
{
    std::srand(1);

    typedef ResultType RT;
    typedef typename Fiber::ScalarTraits<RT>::RealType BFT;
    typedef typename Fiber::ScalarTraits<RT>::RealType CT;

    DiscreteBoundaryOperatorSumFixture<BFT, RT> fixture;
    shared_ptr<const DiscreteBoundaryOperator<RT> > dop = fixture.op.weakForm();

    RT alpha(2., 3.);
    RT beta(0.);

    arma::Col<RT> x = generateRandomVector<RT>(dop->columnCount());
    arma::Col<RT> y(dop->rowCount());
    y.fill(std::numeric_limits<CT>::quiet_NaN());

    arma::Col<RT> expected = alpha * dop->asMatrix() * x;

    dop->apply(NO_TRANSPOSE, x, y, alpha, beta);

    BOOST_CHECK(y.is_finite());
    BOOST_CHECK(check_arrays_are_close<RT>(y, expected,
                                           10. * std::numeric_limits<CT>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(builtin_apply_works_correctly_for_alpha_equal_to_2_and_beta_equal_to_3, ResultType, result_types)
{
    std::srand(1);

    typedef ResultType RT;
    typedef typename Fiber::ScalarTraits<RT>::RealType BFT;
    typedef typename Fiber::ScalarTraits<RT>::RealType CT;

    DiscreteBoundaryOperatorSumFixture<BFT, RT> fixture;
    shared_ptr<const DiscreteBoundaryOperator<RT> > dop = fixture.op.weakForm();

    RT alpha(2.);
    RT beta(3.);

    arma::Col<RT> x = generateRandomVector<RT>(dop->columnCount());
    arma::Col<RT> y = generateRandomVector<RT>(dop->rowCount());

    arma::Col<RT> expected = alpha * dop->asMatrix() * x + beta * y;

    dop->apply(NO_TRANSPOSE, x, y, alpha, beta);

    BOOST_CHECK(check_arrays_are_close<RT>(y, expected,
                                           10. * std::numeric_limits<CT>::epsilon()));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(builtin_apply_works_correctly_for_alpha_equal_to_2_plus_3j_and_beta_equal_to_4_minus_5j, ResultType, complex_result_types)
{
    std::srand(1);

    typedef ResultType RT;
    typedef typename Fiber::ScalarTraits<RT>::RealType BFT;
    typedef typename Fiber::ScalarTraits<RT>::RealType CT;

    DiscreteBoundaryOperatorSumFixture<BFT, RT> fixture;
    shared_ptr<const DiscreteBoundaryOperator<RT> > dop = fixture.op.weakForm();

    RT alpha(2., 3.);
    RT beta(4., -5.);

    arma::Col<RT> x = generateRandomVector<RT>(dop->columnCount());
    arma::Col<RT> y = generateRandomVector<RT>(dop->rowCount());

    arma::Col<RT> expected = alpha * dop->asMatrix() * x + beta * y;

    dop->apply(NO_TRANSPOSE, x, y, alpha, beta);

    BOOST_CHECK(check_arrays_are_close<RT>(y, expected,
                                           10. * std::numeric_limits<CT>::epsilon()));
}

BOOST_AUTO_TEST_SUITE_END()


#include <boost/numeric/interval/compare/lexicographic.hpp>


#ifndef TMC_UNIT_TESTS
#define BOOST_TEST_MODULE lib_test_feature_transform_subset
#endif
#include <boost/test/included/unit_test.hpp>
#include <boost/property_tree/json_parser.hpp>

#include "feature_transform_subset.h"

BOOST_AUTO_TEST_SUITE( feature_transform_subset )

BOOST_AUTO_TEST_CASE( basics )
{
    std::vector<double> v_in = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<double> v_out = {1, -4, -7, 9}, v_out1, v_out2, v_out3;
    std::string index_mask_str = "01010010010";
    std::string sign_mask_str = "00010010000";
    boost::dynamic_bitset<> index_mask(index_mask_str);
    boost::dynamic_bitset<> sign_mask(sign_mask_str);
    size_t dim_in=index_mask.size();
    size_t dim_out=index_mask.count();
    BOOST_CHECK_EQUAL(v_in.size(),dim_in);
    BOOST_CHECK_EQUAL(v_out.size(),dim_out);
    BOOST_CHECK_EQUAL(index_mask.size(),sign_mask.size());

    auto pFT1 = std::make_shared<FeatureTransformSubset>(std::make_shared<FeatureMask>(index_mask,sign_mask));
    auto pFT2 = std::make_shared<FeatureTransformSubset>(std::make_shared<FeatureMask>(index_mask,sign_mask));
    auto pFT3 = std::make_shared<FeatureTransformSubset>(std::make_shared<FeatureMask>(index_mask));

    BOOST_CHECK_EQUAL(pFT1->dim_in(),dim_in);
    BOOST_CHECK_EQUAL(pFT2->dim_in(),dim_in);
    BOOST_CHECK_EQUAL(pFT3->dim_in(),dim_in);
    BOOST_CHECK_EQUAL(pFT1->dim_out(),dim_out);
    BOOST_CHECK_EQUAL(pFT2->dim_out(),dim_out);
    BOOST_CHECK_EQUAL(pFT3->dim_out(),dim_out);

    pFT1->transform_std_vector(v_in,v_out1);BOOST_CHECK(v_out1==v_out);BOOST_CHECK_EQUAL(v_out1.size(), dim_out);
    pFT2->transform_std_vector(v_in,v_out2);BOOST_CHECK(v_out2==v_out);BOOST_CHECK_EQUAL(v_out2.size(), dim_out);
    pFT3->transform_std_vector(v_in,v_out3);BOOST_CHECK_EQUAL(v_out3.size(), dim_out);

    auto pFV_in = std::make_shared<FeatureVector>(v_in);
    pFV_in->inc_weight_negatives(22);
    pFV_in->inc_weight_positives(33);
    auto pFV_out_truth = std::make_shared<FeatureVector>(v_out);
    const auto pFV_out_real1 = pFT1->transform_feature_vector(pFV_in);
    const auto pFV_out_real2 = pFT2->transform_feature_vector(pFV_in);
    const auto pFV_out_real3 = pFT3->transform_feature_vector(pFV_in);
    BOOST_CHECK_EQUAL(pFV_out_real1->dim(),dim_out);
    BOOST_CHECK_EQUAL(pFV_out_real2->dim(),dim_out);
    BOOST_CHECK_EQUAL(pFV_out_real3->dim(),dim_out);
    BOOST_CHECK_EQUAL(*pFV_out_real1,*pFV_out_truth);
    BOOST_CHECK_EQUAL(*pFV_out_real2,*pFV_out_truth);
    BOOST_CHECK_EQUAL(*pFV_out_real3,FeatureVector({1, 4, 7, 9}));
    BOOST_CHECK_EQUAL(pFV_out_real1->get_weight_negatives(),pFV_in->get_weight_negatives());
    BOOST_CHECK_EQUAL(pFV_out_real2->get_weight_negatives(),pFV_in->get_weight_negatives());
    BOOST_CHECK_EQUAL(pFV_out_real3->get_weight_negatives(),pFV_in->get_weight_negatives());
    BOOST_CHECK_EQUAL(pFV_out_real1->get_weight_positives(),pFV_in->get_weight_positives());
    BOOST_CHECK_EQUAL(pFV_out_real2->get_weight_positives(),pFV_in->get_weight_positives());
    BOOST_CHECK_EQUAL(pFV_out_real3->get_weight_positives(),pFV_in->get_weight_positives());
}

BOOST_AUTO_TEST_CASE( ptree ) {
    std::vector<double> v_in = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<double> v_out = {1, -4, -7, 9}, v_out1, v_out2, v_out3;
    std::string index_mask_str = "01010010010";
    std::string sign_mask_str = "00010010000";
    boost::dynamic_bitset<> index_mask(index_mask_str);
    boost::dynamic_bitset<> sign_mask(sign_mask_str);
    size_t dim_in=index_mask.size();
    size_t dim_out=index_mask.count();

    BOOST_CHECK_EQUAL(v_in.size(),dim_in);
    BOOST_CHECK_EQUAL(v_out.size(),dim_out);
    BOOST_CHECK_EQUAL(index_mask.size(),sign_mask.size());
    auto pFT1_aux = std::make_shared<FeatureTransformSubset>(std::make_shared<FeatureMask>(index_mask,sign_mask));
    boost::property_tree::ptree pt;
    pFT1_aux->dump_to_ptree(pt);
    BOOST_CHECK_EQUAL(pt.size(), 4);
    std::stringstream ss;
    boost::property_tree::json_parser::write_json(ss, pt);
    BOOST_TEST_MESSAGE("Property tree as json:\n" << ss.str());

    auto pFT1 = std::make_shared<FeatureTransformSubset>(pt);
    BOOST_CHECK_EQUAL(pFT1->dim_in(),dim_in);
    BOOST_CHECK_EQUAL(pFT1->dim_out(),dim_out);

    pFT1->transform_std_vector(v_in,v_out1);BOOST_CHECK(v_out1==v_out);BOOST_CHECK_EQUAL(v_out1.size(), dim_out);

    auto pFV_in = std::make_shared<FeatureVector>(v_in);
    pFV_in->inc_weight_negatives(22);
    pFV_in->inc_weight_positives(33);
    auto pFV_out_truth = std::make_shared<FeatureVector>(v_out);
    const auto pFV_out_real1 = pFT1->transform_feature_vector(pFV_in);
    BOOST_CHECK_EQUAL(pFV_out_real1->dim(),dim_out);
    BOOST_CHECK_EQUAL(*pFV_out_real1,*pFV_out_truth);
    BOOST_CHECK_EQUAL(pFV_out_real1->get_weight_negatives(),pFV_in->get_weight_negatives());
    BOOST_CHECK_EQUAL(pFV_out_real1->get_weight_positives(),pFV_in->get_weight_positives());
}

BOOST_AUTO_TEST_SUITE_END()


/*
==============================================================================
Kratos
A General Purpose Software for Multi-Physics Finite Element Analysis
Version 1.0 (Released on march 05, 2007).

Copyright 2007
Pooyan Dadvand, Riccardo Rossi
pooyan@cimne.upc.edu
rrossi@cimne.upc.edu
CIMNE (International Center for Numerical Methods in Engineering),
Gran Capita' s/n, 08034 Barcelona, Spain

Permission is hereby granted, free  of charge, to any person obtaining
a  copy  of this  software  and  associated  documentation files  (the
"Software"), to  deal in  the Software without  restriction, including
without limitation  the rights to  use, copy, modify,  merge, publish,
distribute,  sublicense and/or  sell copies  of the  Software,  and to
permit persons to whom the Software  is furnished to do so, subject to
the following condition:

Distribution of this code for  any  commercial purpose  is permissible
ONLY BY DIRECT ARRANGEMENT WITH THE COPYRIGHT OWNER.

The  above  copyright  notice  and  this permission  notice  shall  be
included in all copies or substantial portions of the Software.

THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT  SHALL THE AUTHORS OR COPYRIGHT HOLDERS  BE LIABLE FOR ANY
CLAIM, DAMAGES OR  OTHER LIABILITY, WHETHER IN AN  ACTION OF CONTRACT,
TORT  OR OTHERWISE, ARISING  FROM, OUT  OF OR  IN CONNECTION  WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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

//
//   Project Name:        Kratos
//   Last modified by:    $Author: mossaiby $
//   Date:                $Date: 2008-12-22 14:49:02 $
//   Revision:            $Revision: 1.5 $
//
//

// System includes

// External includes
#include <boost/python.hpp>

// Project includes
#include "includes/define.h"
#include "python/add_equation_systems_to_python.h"
#include "linear_solvers/cg_solver.h"
#include "linear_solvers/deflated_cg_solver.h"
#include "linear_solvers/bicgstab_solver.h"
#include "linear_solvers/tfqmr_solver.h"
#include "includes/dof.h"
#include "spaces/ublas_space.h"

#include "linear_solvers/reorderer.h"
#include "linear_solvers/direct_solver.h"
#include "linear_solvers/skyline_lu_factorization_solver.h"

#include "linear_solvers/preconditioner.h"
#include "linear_solvers/diagonal_preconditioner.h"
#include "linear_solvers/ilu0_preconditioner.h"
#include "linear_solvers/ilu_preconditioner.h"
//#include "linear_solvers/superlu_solver.h"
#include "linear_solvers/power_iteration_eigenvalue_solver.h"

namespace Kratos
{

namespace Python
{
  void  AddLinearSolversToPython()
  {
typedef UblasSpace<double, CompressedMatrix, Vector> SpaceType;
typedef UblasSpace<double, Matrix, Vector> LocalSpaceType;
typedef LinearSolver<SpaceType,  LocalSpaceType> LinearSolverType;
typedef IterativeSolver<SpaceType,  LocalSpaceType> IterativeSolverType;
typedef CGSolver<SpaceType,  LocalSpaceType> CGSolverType;
typedef DeflatedCGSolver<SpaceType,  LocalSpaceType> DeflatedCGSolverType;
typedef BICGSTABSolver<SpaceType,  LocalSpaceType> BICGSTABSolverType;
typedef TFQMRSolver<SpaceType,  LocalSpaceType> TFQMRSolverType;
typedef PowerIterationEigenvalueSolver<SpaceType, LocalSpaceType, LinearSolverType> PowerIterationEigenvalueSolverType;

	bool (LinearSolverType::*pointer_to_solve)(LinearSolverType::SparseMatrixType& rA, LinearSolverType::VectorType& rX, LinearSolverType::VectorType& rB) = &LinearSolverType::Solve;

  using namespace boost::python;

  //****************************************************************************************************
  //preconditioners
  //****************************************************************************************************
	typedef Preconditioner<SpaceType,  LocalSpaceType> PreconditionerType;

    class_<PreconditionerType, PreconditionerType::Pointer>("Preconditioner")
		  .def(self_ns::str(self))
		  ;

	typedef DiagonalPreconditioner<SpaceType,  LocalSpaceType> DiagonalPreconditionerType;
	class_<DiagonalPreconditionerType, DiagonalPreconditionerType::Pointer, bases<PreconditionerType> >("DiagonalPreconditioner")
		  .def(self_ns::str(self))
		  ;

	typedef ILUPreconditioner<SpaceType,  LocalSpaceType> ILUPreconditionerType;
	class_<ILUPreconditionerType, ILUPreconditionerType::Pointer, bases<PreconditionerType> >("ILUPreconditioner")
		  .def(self_ns::str(self))
		  ;

	typedef ILU0Preconditioner<SpaceType,  LocalSpaceType> ILU0PreconditionerType;
	class_<ILU0PreconditionerType, ILU0PreconditionerType::Pointer, bases<PreconditionerType> >("ILU0Preconditioner")
		  .def(self_ns::str(self))
		  ;

  //****************************************************************************************************
  //linear solvers
  //****************************************************************************************************
  class_<LinearSolverType, LinearSolverType::Pointer>("LinearSolver")
		  .def("Initialize",&LinearSolverType::Initialize)
		  .def("Solve",pointer_to_solve)
		  //.def("",&LinearSolverType::)
		  .def(self_ns::str(self))
		  ;

  class_<IterativeSolverType, IterativeSolverType::Pointer, bases<LinearSolverType> >("IterativeSolver")
		  //.def("",&LinearSolverType::)
		  .def(self_ns::str(self))
		  ;

  class_<CGSolverType, CGSolverType::Pointer, bases<IterativeSolverType> >("CGSolver")
		  .def(init<double>())
		  .def(init<double, unsigned int>())
		  .def(init<double, unsigned int,  PreconditionerType::Pointer>())
		  //.def("",&LinearSolverType::)
		  .def(self_ns::str(self))
		  ;

  class_<BICGSTABSolverType, BICGSTABSolverType::Pointer, bases<IterativeSolverType> >("BICGSTABSolver")
		  .def(init<double>())
		  .def(init<double, unsigned int>())
		  .def(self_ns::str(self))
		  .def(init<double, unsigned int,  PreconditionerType::Pointer>())
		  ;

  class_<TFQMRSolverType, TFQMRSolverType::Pointer, bases<IterativeSolverType> >("TFQMRSolver")
		  .def(init<double>())
		  .def(init<double, unsigned int>())
		  .def(self_ns::str(self))
		  .def(init<double, unsigned int,  PreconditionerType::Pointer>())
		  ;

          class_<PowerIterationEigenvalueSolverType, PowerIterationEigenvalueSolverType::Pointer, bases<LinearSolverType> >("PowerIterationEigenvalueSolver")
                  .def(init<double, unsigned int, unsigned int, LinearSolverType::Pointer>())
                          ;

	typedef Reorderer<SpaceType,  LocalSpaceType > ReordererType;
	typedef DirectSolver<SpaceType,  LocalSpaceType, ReordererType > DirectSolverType;
	typedef SkylineLUFactorizationSolver<SpaceType,  LocalSpaceType, ReordererType > SkylineLUFactorizationSolverType;

 	class_<ReordererType, ReordererType::Pointer >("Reorderer")
		  .def( init< >() )
		  .def(self_ns::str(self))
		  .def( "Initialize",&ReordererType::Initialize)
		  .def( "Reorder",&ReordererType::Reorder)
		  .def( "InverseReorder",&ReordererType::InverseReorder)
		  ;

 	class_<DirectSolverType, DirectSolverType::Pointer, bases<LinearSolverType> >("DirectSolver")
		  .def( init< >() )
		  .def(self_ns::str(self))
		  ;

	class_<SkylineLUFactorizationSolverType, SkylineLUFactorizationSolverType::Pointer, bases<DirectSolverType> >("SkylineLUFactorizationSolver")
		  .def(init< >())
		  .def(self_ns::str(self))
		  ;

  class_<DeflatedCGSolverType, DeflatedCGSolverType::Pointer, bases<IterativeSolverType> >("DeflatedCGSolver")
		  .def(init<double, int>())
		  .def(init<double, unsigned int, int>())
		  .def(init<double, unsigned int,  PreconditionerType::Pointer, int>())
// 		  .def(init<double, unsigned int,  PreconditionerType::Pointer, ModelPart::Pointer>())
		  //.def("",&LinearSolverType::)
		  .def(self_ns::str(self))
		  ;

//	typedef SuperLUSolver<SparseSpaceType, LocalSpaceType> SuperLUSolverType;
//	class_<SuperLUSolverType, bases<DirectSolverType>, boost::noncopyable >
//		( "SuperLUSolver",
//			init<>() )
  }

}  // namespace Python.

} // Namespace Kratos


// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE RecomputeTestIrNormOnlyAnnotation0

#include <boost/test/unit_test.hpp>
#include <memory>
#include <vector>
#include <popart/testdevice.hpp>

#include <filereader.hpp>
#include <popart/builder.hpp>
#include <popart/dataflow.hpp>
#include <popart/ir.hpp>
#include <popart/names.hpp>
#include <popart/op/identity.hpp>
#include <popart/op/l1.hpp>
#include <popart/sessionoptions.hpp>
#include <popart/sgd.hpp>
#include <popart/tensordata.hpp>

using namespace popart;

TensorId conv(Builder *b, TensorId act, ConstVoidData wdata) {
  auto aiOnnx  = b->aiOnnxOpset9();
  auto weights = b->addInitializedInputTensor(wdata);
  act = aiOnnx.conv({act, weights}, {1, 1}, 1, {}, {1, 1, 1, 1}, {1, 1});
  return act;
}

TensorId batchnormalization(Builder *b, TensorId act, ConstVoidData bndata) {
  auto aiOnnx = b->aiOnnxOpset9();
  auto scale  = b->addInitializedInputTensor(bndata);
  auto bias   = b->addInitializedInputTensor(bndata);
  auto mean   = b->addInitializedInputTensor(bndata);
  auto var    = b->addInitializedInputTensor(bndata);
  auto bn_out = aiOnnx.batchnormalization({act, scale, bias, mean, var}, 5);
  act         = bn_out.at(0);
  return act;
}

BOOST_AUTO_TEST_CASE(NormOnlyRecomputeTest) {

  // Test that norms are Recompute

  // The model:
  //
  // In -> Conv -> BN -> Relu -> Conv -> Relu -> Conv -> BN -> Out
  //
  auto test = [](bool explicitRecomputation) {
    // Build an onnx model
    auto builder = Builder::create();
    auto aiOnnx  = builder->aiOnnxOpset9();

    TensorInfo input_shape{"FLOAT", std::vector<int64_t>{1, 4, 32, 32}};

    TensorInfo weights_shape{"FLOAT", std::vector<int64_t>{4, 4, 3, 3}};
    float weight_vals[4 * 4 * 3 * 3] = {0};
    ConstVoidData weight_data        = {weight_vals, weights_shape};

    TensorInfo bn_shape{"FLOAT", std::vector<int64_t>{4}};
    float bn_vals[4]      = {0};
    ConstVoidData bn_data = {bn_vals, bn_shape};

    auto act = builder->addInputTensor(input_shape);

    act     = conv(builder.get(), act, weight_data);
    act     = batchnormalization(builder.get(), act, bn_data);
    act     = aiOnnx.relu({act});
    act     = conv(builder.get(), act, weight_data);
    act     = aiOnnx.relu({act});
    act     = conv(builder.get(), act, weight_data);
    act     = batchnormalization(builder.get(), act, bn_data);
    auto l1 = builder->aiGraphcoreOpset1().l1loss({act}, 0.1);

    int nBNs = 2;

    auto proto      = builder->getModelProto();
    auto modelProto = io::getModelFromString(proto);

    // Add the last tensor, and the 3rd tensor as anchors
    auto dataFlow  = DataFlow(1, {{act, AnchorReturnType("All")}});
    auto optimizer = ConstSGD(0.01);
    auto device    = createTestDevice(TEST_TARGET);

    SessionOptions opts;
    if (explicitRecomputation) {
      opts.explicitRecomputation = true;
    } else {
      opts.explicitRecomputation = false;
    }
    opts.autoRecomputation = RecomputationType::NormOnly;
    opts.enableOutlining   = false;
    opts.mergeVarUpdate    = MergeVarUpdateType::None;

    Ir ir;
    ir.prepare({modelProto,
                InputShapeInfo(),
                dataFlow,
                l1,
                &optimizer,
                *device,
                opts,
                Patterns::create({"OpToIdentity", "PostNRepl"})
                    .enableRuntimeAsserts(false)});

    int nRecompute = 0;
    for (auto op : ir.getOpSchedule({}, RequireOptimalSchedule::Yes)) {
      if (explicitRecomputation) {
        if ((op->settings.recomputeType == RecomputeType::Recomputed) &&
            (!op->opid.type.compare("BatchNormalization"))) {
          nRecompute++;
        } else {
          BOOST_CHECK(op->settings.recomputeType == RecomputeType::Checkpoint);
        }
      } else {
        // When explicit recomputation is switched OFF, only the BNs should
        // have their flags set to Recompute
        if (op->isNorm()) {
          BOOST_CHECK(op->settings.recomputeType == RecomputeType::Recompute);
        } else {
          BOOST_CHECK(op->settings.recomputeType == RecomputeType::Checkpoint);
        }
      }
    }

    // Verify that the number of recomputation equals to ops
    if (explicitRecomputation) {
      BOOST_CHECK(nRecompute == nBNs);
    }
  };

  test(false);
  test(true);
}


#include <cdrm/cdrm.h>

#include <cdrm/cdrm_serialisation.h>
#include <cdrm/utils.h>

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/filesystem.hpp>

#include <ros/console.h>

#include <fstream>

namespace cdrm
{
Cdrm::Cdrm(double resolution) : resolution_(resolution)
{
}

Key Cdrm::pointToKey(const Eigen::Vector3d &p) const
{
  Key k;
  k[0] = static_cast<std::int16_t>(std::round(p(0) / resolution_));
  k[1] = static_cast<std::int16_t>(std::round(p(1) / resolution_));
  k[2] = static_cast<std::int16_t>(std::round(p(2) / resolution_));
  return k;
}

Eigen::Vector3d Cdrm::keyToPoint(const Key &k) const
{
  Eigen::Vector3d p;
  p(0) = resolution_ * k[0];
  p(1) = resolution_ * k[1];
  p(2) = resolution_ * k[2];
  return p;
}

const Eigen::VectorXd &Cdrm::getVertexConfig(const VertexDescriptor &v) const
{
  return roadmap_[v].q_;
}

bool Cdrm::save(const std::string &filename) const
{
  try
  {
    std::ofstream ofs(filename);
    boost::archive::binary_oarchive oa(ofs);
    oa << *this;
  }
  catch (const boost::archive::archive_exception &ex)
  {
    ROS_ERROR("%s", ex.what());
    return false;
  }

  return true;
}

bool Cdrm::load(const std::string &filename)
{
  // If it's relative, then default to reading from ROS home.
  boost::filesystem::path path(filename);

  if (path.is_relative())
    path = boost::filesystem::path(getRosHome() + filename);

  try
  {
    std::ifstream ifs(path.c_str());
    boost::archive::binary_iarchive ia(ifs);
    ia >> *this;
  }
  catch (const boost::archive::archive_exception &ex)
  {
    ROS_ERROR("%s", ex.what());
    return false;
  }

  return true;
}
}