#include <chrono>
#include <cmath>
#include <exception>
#include <thread>
#include <vector>

#include <boost/log/trivial.hpp>

#include "mimir/algorithm/Leadline.hpp"
#include "mimir/StateMachineFwd.hpp"
#include "mimir/FkinDds.hpp"

#include <casadi/casadi.hpp>

namespace mimir
{
  namespace algorithm
  {
    class Leadline::Impl
    {
    public:
      Impl(
          const YAML::Node& config,
          dds::pub::Publisher publisher,
          dds::sub::Subscriber subscriber) :
        inputParameters(dds::core::null),
        outputWriter(dds::core::null)
      {

        // Setup input reader for parameters
        auto input_config = config["inputs"]["parameters"];
        auto readerQos = subscriber.default_datareader_qos();
        auto topicName = input_config["topic"].as<std::string>();
        auto id = input_config["id"].as<std::string>();
        auto topic = dds::topic::Topic<fkin::IdVec2d>(
            subscriber.participant(),
            topicName);
        auto filter = dds::topic::Filter("id = %0", {id});

        inputParameters = dds::sub::DataReader<fkin::IdVec2d>(
            subscriber,
            dds::topic::ContentFilteredTopic<fkin::IdVec2d>(
                topic,
                topicName + id,
                filter),
            readerQos);

        parameters = casadi::DM(config["inputs"]["parameters"]["default"].as<std::vector<double>>());

        // Setup output writer
        auto writerQos = publisher.default_datawriter_qos();

        outputWriter = dds::pub::DataWriter<fkin::BatchIdVec1d>(
            publisher,
            dds::topic::Topic<fkin::BatchIdVec1d>(
                publisher.participant(),
                config["outputs"]["depth"]["topic"].as<std::string>()),
            writerQos);

        outputId = config["outputs"]["depth"]["id"].as<std::string>();

        // Setup model description
        using namespace casadi;
        SX x = SX::sym("x", 1);
        SX tau = SX::sym("tau", 1);
        SX x_d = SX::sym("set_point", 1);
        SX param = SX::vertcat({tau,x_d});
        SX rhs = SX::zeros(1,1);
        rhs = -(x - x_d)/tau;

        SXDict ode_int = {{"x", x}, {"p", param}, {"ode", rhs}};

        double predHorizonSec = config["prediction_horizon_sec"].as<double>();
        double timeStepSec = config["time_step_ms"].as<double>()/1000.;

        int32_t steps = static_cast<int32_t>(std::ceil(predHorizonSec/timeStepSec));
        timeGrid = std::vector<double>(steps+1);
        prediction = std::vector<fkin::IdVec1d>(timeGrid.size());
        predictionTime = std::vector<fkin::Timestamp>(timeGrid.size());

        for (size_t i = 0; i < timeGrid.size(); ++i)
          timeGrid[i] = 0. + i*timeStepSec;

        ode = casadi::integrator(
            "leadline", "cvodes", ode_int,
            {{"tf", predHorizonSec}, {"grid", timeGrid}});

        xGrid = casadi::DM::zeros(1,timeGrid.size()-1);

      }
      ~Impl() {}
      dds::sub::DataReader<fkin::IdVec2d> inputParameters;
      dds::pub::DataWriter<fkin::BatchIdVec1d> outputWriter;
      std::chrono::steady_clock::time_point t0;
      std::vector<double> timeGrid;
      std::vector<fkin::IdVec1d> prediction;
      std::vector<fkin::Timestamp> predictionTime;
      int32_t waitMs;
      std::string outputId;
      casadi::Function ode;
      casadi::DM xGrid;
      casadi::DM parameters;
    };

    Leadline::Leadline(
        const YAML::Node& config,
        boost::statechart::fifo_scheduler<>& scheduler,
        boost::statechart::fifo_scheduler<>::processor_handle machine,
        dds::pub::Publisher publisher,
        dds::sub::Subscriber subscriber) :
      m_impl( new Leadline::Impl(config, publisher, subscriber)),
      m_scheduler(scheduler),
      m_stateMachine(machine),
      m_time_step(std::chrono::milliseconds(config["time_step_ms"].as<std::int32_t>())),
      m_next_step(std::chrono::steady_clock::now()),
      m_config(config)
    {}

    Leadline::~Leadline() = default;
    void Leadline::solve(const std::atomic<bool>& cancel_token)
    {
      try
      {
        BOOST_LOG_TRIVIAL(trace) << "Leadline solve..";

        // Fetch current input, sample and hold
        auto params = m_impl->inputParameters.read();

        // Set new desired value
        if(params.length() > 0)
        {
          auto sample = (--params.end());
          if(sample->info().valid())
          {
            auto sD = sample->data();
            BOOST_LOG_TRIVIAL(debug) << "Got new params: "
                                    << sD.vec().x() << ", " << sD.vec().y();
            m_impl->parameters(0) = sD.vec().x();
            m_impl->parameters(1) = sD.vec().y();
          }
        }

        if(cancel_token)
        {
          BOOST_LOG_TRIVIAL(info) << name() << " interrupted";
          event(new mimir::EvInterrupt());
          return;
        }

        BOOST_LOG_TRIVIAL(debug) << "Parameters are : " << m_impl->parameters;

        // Integrate
        auto result = m_impl->ode(
            casadi::DMDict( {{"x0", casadi::DM::zeros(1,1)}, {"p", m_impl->parameters}} ));

        if(cancel_token)
        {
          BOOST_LOG_TRIVIAL(info) << name() << " interrupted";
          event(new mimir::EvInterrupt());
          return;
        }

        using namespace casadi;
        m_impl->xGrid = result["xf"];

        const auto x_k = std::vector<double>{0};
        namespace sc = std::chrono;

        auto t0 = m_now; // system clock of time at simulation start

        m_impl->prediction[0] = fkin::IdVec1d(m_impl->outputId, fkin::Vector1d(x_k[0]));
        m_impl->predictionTime[0] = fkin::Timestamp(
            sc::duration_cast<sc::milliseconds>(t0.time_since_epoch()).count());

        for(int i = 0; i < static_cast<int>(m_impl->timeGrid.size()-1); ++i)
        {
          m_impl->prediction[i+1] = fkin::IdVec1d(
              m_impl->outputId,
              fkin::Vector1d(std::vector<double>(m_impl->xGrid(Slice(),Slice(i,i+1)))[0]));
          m_impl->predictionTime[i+1] = fkin::Timestamp(
              sc::duration_cast<sc::milliseconds>(
                  (t0 + (i+1)*m_time_step).time_since_epoch()).count());
        }

        BOOST_LOG_TRIVIAL(trace) << "Response: " << m_impl->xGrid;

        m_impl->outputWriter << fkin::BatchIdVec1d(
            m_impl->outputId,
            m_impl->prediction,
            m_impl->predictionTime);

        step_time();
        event(new mimir::EvReady());

      }
      catch(casadi::CasadiException &e)
      {
        BOOST_LOG_TRIVIAL(fatal) << name() << " Casadi Exception: " << e.what();
        event(new mimir::EvError());
        throw;
      }
      catch (...)
      {
        // Need to post event in case of exception so that the state machine
        // knows that the job has finished.
        BOOST_LOG_TRIVIAL(fatal) << name() << " exception thrown";
        event(new mimir::EvError());
        throw;
      }
    }
    void Leadline::initialize(const std::atomic<bool>&)
    {
      try
      {
        BOOST_LOG_TRIVIAL(debug) << name() << " is initializing";
        m_next_step = std::chrono::steady_clock::now();
        m_now = std::chrono::system_clock::now() - m_time_step;
        event(new mimir::EvReady());
      }
      catch (...)
      {
        BOOST_LOG_TRIVIAL(fatal) << name() << " initializing threw exception";
        event(new mimir::EvError());
        throw;
      }
    }

    void Leadline::timer(const std::atomic<bool>& cancel_token)
    {
      // probably want external time point to wait until.
      typedef std::chrono::milliseconds scm;
      auto now = std::chrono::steady_clock::now();
      auto time_p = m_next_step;
      auto timeleft = std::chrono::duration_cast<scm>(time_p - now).count();
      BOOST_LOG_TRIVIAL(trace) << "Time left: " << timeleft;

      auto fraction = (m_time_step/100 > scm(0) ? m_time_step/100 :
       (m_time_step/50 > scm(0) ? m_time_step/50 :
        (m_time_step/25 > scm(0) ? m_time_step/25 :
         (m_time_step/10 > scm(0) ? m_time_step/10 :
          (m_time_step/2 > scm(0) ?  m_time_step/2  : scm(1))))));

      while(now < time_p)
      {
        auto diff = time_p - now;
        std::this_thread::sleep_for(diff < fraction ? diff : fraction);
        if(cancel_token)
          break;
        now = std::chrono::steady_clock::now();
      }

      if(cancel_token)
      {
        BOOST_LOG_TRIVIAL(trace) << "Timer Canceled";
        event(new mimir::EvInterrupt());
      }
      else
      {
        BOOST_LOG_TRIVIAL(trace) << "Timeout";
        event(new mimir::EvTimeout());
      }
    }

    void Leadline::event(boost::statechart::event_base * const event)
    {
      // who is responsible for the pointer?
      m_scheduler.queue_event(
          m_stateMachine,
          make_intrusive(event));
    }
  }
}


//| Copyright Inria May 2015
//| This project has received funding from the European Research Council (ERC) under
//| the European Union's Horizon 2020 research and innovation programme (grant
//| agreement No 637972) - see http://www.resibots.eu
//|
//| Contributor(s):
//|   - Jean-Baptiste Mouret (jean-baptiste.mouret@inria.fr)
//|   - Antoine Cully (antoinecully@gmail.com)
//|   - Konstantinos Chatzilygeroudis (konstantinos.chatzilygeroudis@inria.fr)
//|   - Federico Allocati (fede.allocati@gmail.com)
//|   - Vaios Papaspyros (b.papaspyros@gmail.com)
//|   - Roberto Rama (bertoski@gmail.com)
//|
//| This software is a computer library whose purpose is to optimize continuous,
//| black-box functions. It mainly implements Gaussian processes and Bayesian
//| optimization.
//| Main repository: http://github.com/resibots/limbo
//| Documentation: http://www.resibots.eu/limbo
//|
//| This software is governed by the CeCILL-C license under French law and
//| abiding by the rules of distribution of free software.  You can  use,
//| modify and/ or redistribute the software under the terms of the CeCILL-C
//| license as circulated by CEA, CNRS and INRIA at the following URL
//| "http://www.cecill.info".
//|
//| As a counterpart to the access to the source code and  rights to copy,
//| modify and redistribute granted by the license, users are provided only
//| with a limited warranty  and the software's author,  the holder of the
//| economic rights,  and the successive licensors  have only  limited
//| liability.
//|
//| In this respect, the user's attention is drawn to the risks associated
//| with loading,  using,  modifying and/or developing or reproducing the
//| software by the user in light of its specific status of free software,
//| that may mean  that it is complicated to manipulate,  and  that  also
//| therefore means  that it is reserved for developers  and  experienced
//| professionals having in-depth computer knowledge. Users are therefore
//| encouraged to load and test the software's suitability as regards their
//| requirements in conditions enabling the security of their systems and/or
//| data to be ensured and,  more generally, to use and operate it in the
//| same conditions as regards security.
//|
//| The fact that you are presently reading this means that you have had
//| knowledge of the CeCILL-C license and that you accept its terms.
//|

#ifndef LIMBO_TOOLS_RANDOM_GENERATOR_HPP
#define LIMBO_TOOLS_RANDOM_GENERATOR_HPP

#include <Eigen/Core>

#include <cmath>
#include <cstdlib>
#include <ctime>
#include <external/rand_utils.hpp>
#include <list>
#include <mutex>
#include <random>
#include <stdlib.h>
#include <utility>

namespace limbo {
    namespace tools {
        /// @ingroup tools
        /// a mt19937-based random generator (mutex-protected)
        ///
        /// usage :
        /// - RandomGenerator<dist<double>>(0.0, 1.0);
        /// - double r = rgen.rand();
        template <typename D>
        class RandomGenerator {
        public:
            using result_type = typename D::result_type;
            RandomGenerator(result_type a, result_type b, int seed = -1) : _dist(a, b) { this->seed(seed); }

            result_type rand() { return _dist(_rgen); }

            void seed(int seed = -1)
            {
                if (seed >= 0)
                    _rgen.seed(seed);
                else
                    _rgen.seed(randutils::auto_seed_128{}.base());
            }

            void reset() { _dist.reset(); }

            void param(const typename D::param_type& param) { _dist.param(param); }

        private:
            D _dist;
            std::mt19937 _rgen;
        };

        /// @ingroup tools
        using rdist_double_t = std::uniform_real_distribution<double>;
        /// @ingroup tools
        using rdist_int_t = std::uniform_int_distribution<int>;
        /// @ingroup tools
        using rdist_gauss_t = std::normal_distribution<>;

        /// @ingroup tools
        /// Double random number generator
        using rgen_double_t = RandomGenerator<rdist_double_t>;

        /// @ingroup tools
        /// Double random number generator (gaussian)
        using rgen_gauss_t = RandomGenerator<rdist_gauss_t>;

        /// @ingroup tools
        /// integer random number generator
        using rgen_int_t = RandomGenerator<rdist_int_t>;

        /// @ingroup tools
        /// random vector by providing custom RandomGenerator
        template <typename Rng>
        inline Eigen::VectorXd random_vec(int size, Rng& rng)
        {
            Eigen::VectorXd res(size);
            for (int i = 0; i < size; ++i)
                res[i] = rng.rand();
            return res;
        }

        /// @ingroup tools
        /// random vector in [0, 1]
        ///
        /// - this function is thread safe because we use a random generator for each thread
        /// - we use a C++11 random number generator
        inline Eigen::VectorXd random_vector_bounded(int size)
        {
            static thread_local rgen_double_t rgen(0.0, 1.0);
            return random_vec(size, rgen);
        }

        /// @ingroup tools
        /// random vector generated with a normal distribution centered on 0, with standard deviation of 10
        ///
        /// - this function is thread safe because we use a random generator for each thread
        /// - we use a C++11 random number generator
        inline Eigen::VectorXd random_vector_unbounded(int size)
        {
            static thread_local rgen_gauss_t rgen(0.0, 10.0);
            return random_vec(size, rgen);
        }

        /// @ingroup tools
        /// random vector wrapper for both bounded and unbounded versions
        inline Eigen::VectorXd random_vector(int size, bool bounded = true)
        {
            if (bounded)
                return random_vector_bounded(size);
            return random_vector_unbounded(size);
        }

        /// @ingroup tools
        /// generate n random samples with Latin Hypercube Sampling (LHS) in [0, 1]^dim
        inline Eigen::MatrixXd random_lhs(int dim, int n)
        {
            Eigen::VectorXd cut = Eigen::VectorXd::LinSpaced(n + 1, 0., 1.);
            Eigen::MatrixXd u = Eigen::MatrixXd::Zero(n, dim);

            for (int i = 0; i < n; i++) {
                u.row(i) = tools::random_vector(dim, true);
            }

            Eigen::VectorXd a = cut.head(n);
            Eigen::VectorXd b = cut.tail(n);

            Eigen::MatrixXd rdpoints = Eigen::MatrixXd::Zero(n, dim);
            for (int i = 0; i < dim; i++) {
                rdpoints.col(i) = u.col(i).array() * (b - a).array() + a.array();
            }

            Eigen::MatrixXd H = Eigen::MatrixXd::Zero(n, dim);
            Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> perm(n);
            static thread_local std::mt19937 rgen(randutils::auto_seed_128{}.base());
            for (int i = 0; i < dim; i++) {
                perm.setIdentity();
                std::shuffle(perm.indices().data(), perm.indices().data() + perm.indices().size(), rgen);
                Eigen::MatrixXd tmp = perm * rdpoints;
                H.col(i) = tmp.col(i);
            }

            return H;
        }
    } // namespace tools
} // namespace limbo

#endif


//    Copyright 2019 Jij Inc.

//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at

//        http://www.apache.org/licenses/LICENSE-2.0

//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

#ifndef OPENJIJ_SYSTEM_CLASSICAL_ISING_HPP__
#define OPENJIJ_SYSTEM_CLASSICAL_ISING_HPP__

#include <cassert>
#include <utility>
#include <system/system.hpp>
#include <graph/all.hpp>
#include <utility/eigen.hpp>
#include <type_traits>
#include <Eigen/Dense>
#include <Eigen/Sparse>

namespace openjij {
    namespace system {

        /**
         * @brief naive ClassicalIsing structure (system for classical Ising model)
         *
         * @tparam GraphType type of graph
         * @tparam eigen_impl specify that Eigen implementation is enabled.
         */
        template<typename GraphType, bool eigen_impl=false>
            struct ClassicalIsing {
                static_assert(!eigen_impl, "Eigen implementation is not supported.");

                using system_type = classical_system;

                /**
                 * @brief Constructor to initialize spin and interaction
                 *
                 * @param spin
                 * @param interaction
                 */
                ClassicalIsing(const graph::Spins& init_spin, const GraphType& init_interaction)
                    : spin{init_spin}, interaction{init_interaction}, num_spins{init_spin.size()} {
                        assert(init_spin.size() == init_interaction.get_num_spins());
                    }

                /**
                 * @brief reset spins
                 *
                 * @param init_spin
                 */
                void reset_spins(const graph::Spins& init_spin){
                    this->spin = init_spin;
                }

                graph::Spins spin;
                const GraphType interaction;
                /**
                 * @brief number of real spins (dummy spin excluded)
                 */
                const std::size_t num_spins; //spin.size()
            };

        //TODO: unify Dense and Sparse Eigen-implemented ClassicalIsing struct

        /**
         * @brief ClassicalIsing structure for Dense graph (Eigen-based)
         *
         * @tparam FloatType type of floating-point
         */
        template<typename FloatType>
            struct ClassicalIsing<graph::Dense<FloatType>, true>{
                using system_type = classical_system;

                //matrix (row major)
                using MatrixXx = Eigen::Matrix<FloatType, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
                //vector (col major)
                using VectorXx = Eigen::Matrix<FloatType, Eigen::Dynamic, 1, Eigen::ColMajor>;

                /**
                 * @brief Constructor to initialize spin and interaction
                 *
                 * @param spin
                 * @param interaction
                 */
                ClassicalIsing(const graph::Spins& init_spin, const graph::Dense<FloatType>& init_interaction)
                    : spin(utility::gen_vector_from_std_vector<FloatType, Eigen::ColMajor>(init_spin)),
                    interaction(utility::gen_matrix_from_graph<Eigen::RowMajor>(init_interaction)),
                    num_spins(init_interaction.get_num_spins()){
                        assert(init_spin.size() == init_interaction.get_num_spins());
                    }

                /**
                 * @brief reset spins
                 *
                 * @param init_spin
                 */
                void reset_spins(const graph::Spins& init_spin){
                    this->spin = utility::gen_vector_from_std_vector<FloatType, Eigen::ColMajor>(init_spin);
                }

                VectorXx spin;
                const MatrixXx interaction;

                /**
                 * @brief number of real spins (dummy spin excluded)
                 */
                const std::size_t num_spins; //spin.size()-1
            };

        /**
         * @brief ClassicalIsing structure for Sparse graph (Eigen-based)
         *
         * @tparam FloatType type of floating-point
         */
        template<typename FloatType>
            struct ClassicalIsing<graph::Sparse<FloatType>, true>{
                using system_type = classical_system;

                //matrix (row major)
                using SparseMatrixXx = Eigen::SparseMatrix<FloatType, Eigen::RowMajor>;
                //vector (col major)
                using VectorXx = Eigen::Matrix<FloatType, Eigen::Dynamic, 1, Eigen::ColMajor>;

                /**
                 * @brief Constructor to initialize spin and interaction
                 *
                 * @param spin
                 * @param interaction
                 */
                ClassicalIsing(const graph::Spins& init_spin, const graph::Sparse<FloatType>& init_interaction)
                    : spin(utility::gen_vector_from_std_vector<FloatType, Eigen::ColMajor>(init_spin)),
                    interaction(utility::gen_matrix_from_graph<Eigen::RowMajor>(init_interaction)),
                    num_spins(init_interaction.get_num_spins()){
                        assert(init_spin.size() == init_interaction.get_num_spins());
                    }

                /**
                 * @brief reset spins
                 *
                 * @param init_spin
                 */
                void reset_spins(const graph::Spins& init_spin){
                    this->spin = utility::gen_vector_from_std_vector<FloatType, Eigen::ColMajor>(init_spin);
                }

                VectorXx spin;
                const SparseMatrixXx interaction;

                /**
                 * @brief number of real spins (dummy spin excluded)
                 */
                const std::size_t num_spins; //spin.size()-1
            };

        /**
         * @brief helper function for ClassicalIsing constructor
         *
         * @tparam eigen_impl
         * @tparam GraphType
         * @param init_spin initial spin
         -        * @param init_interaction initial interaction
         *
         * @return generated object
         */
        template<bool eigen_impl=false,typename GraphType>
            ClassicalIsing<GraphType, eigen_impl> make_classical_ising(const graph::Spins& init_spin, const GraphType& init_interaction){
                return ClassicalIsing<GraphType, eigen_impl>(init_spin, init_interaction);
            }

    } // namespace system
} // namespace openjij

#endif


#ifndef SKYLARK_COMBBLAS_SLAB_VIEW_HPP
#define SKYLARK_COMBBLAS_SLAB_VIEW_HPP

#if SKYLARK_HAVE_COMBBLAS
#include <CombBLAS.h>
#include <CommGrid.h>
#endif

#include <map>
#include <boost/mpi.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>

#include "combblas_comm_grid.hpp"

namespace skylark {
namespace utility {

#if SKYLARK_HAVE_COMBBLAS

/**
 * Slab view for CombBLAS matrices.
 * This are helpers for the panel gemms, where we communicate parts of
 * the CombBLAS matrix and then perform a local Elemental gemm.
 * The communication patterns are dictated by the "output" Elemental
 * distribution, e.g. if we want an STAR / MR view, CombBLAS values are
 * redistributed accordingly.
 * Note that due to the CombBLAS distribution we generate slabs in parallel:
 *
 *     [x....|x....]
 *     [x....|x....]
 *     [-----------]
 *     [x....|x....]
 *     [x....|x....]
 *
 * FIXME: - see how we can integrate into base::view
 *        - different comm patterns for _set_view
 *        - compare performance against one-sided value getter for cb matrices
 *        - flag to generate in-order
 *        - transpose case should invert row/col slab view
 */
template <typename index_type, typename value_type>
struct combblas_slab_view_t {

    typedef SpDCCols<index_type, value_type> cb_col_t;
    typedef SpParMat<index_type, value_type, cb_col_t> sp_par_mat_t;
    typedef typename cb_col_t::SpColIter cb_col_itr_t;
    typedef typename cb_col_t::SpColIter::NzIter cb_nz_itr_t;

    /**
     *  Create a slab view of the CombBLAS matrix B that may be transposed.
     */
    combblas_slab_view_t(const sp_par_mat_t &B, bool transp_b = false)
        : _world(B.getcommgrid()->GetWorld(), boost::mpi::comm_duplicate)
        , _data((const_cast<sp_par_mat_t &>(B)).seq())
        , _col_itr(_data.begcol())
        , _row_itr(_data.begnz(_col_itr))
        , _transp_b(transp_b)
        , _offset_row(cb_my_row_offset(B))
        , _offset_col(cb_my_col_offset(B))
        , _global_row(0)
        , _global_col(0) {

        _ncol = B.getncol();
        _nrow = B.getnrow();
        if(_transp_b) {
            _ncol = B.getnrow();
            _nrow = B.getncol();
        }

        for(cb_col_itr_t col = _data.begcol(); col != _data.endcol(); col++)
            _row_itrs.push_back(_data.begnz(col));
    }

    /**
     * Extract the value of a global index from the CombBLAS matrix.
     * TODO: Use this to define a sparse-dense local GEMM that avoids building
     *       a dense part of the CombBLAS matrix.
     */
    value_type operator()(index_type g_row, index_type g_col) {

        index_type map_idx = idx(g_row, g_col);
        if(_values.count(map_idx) > 0)
            return _values[map_idx];
        else
            return static_cast<value_type>(0);
    }

    /**
     *  Extract a Elemental view of CombBLAS COLUMNS. The distribution of the
     *  Elemental view is determined by the distribution of the input matrix
     *  A.
     *  The column_idxs store the global column indices that contain non-zeros.
     */
    template<typename dist_elem_matrix_t>
    void extract_elemental_column_slab_view(
            const dist_elem_matrix_t &A,
            std::set<index_type> &column_idxs,
            size_t width = 1) {

        _values.clear();

        // structure per proc data
        std::vector< std::map<index_type, value_type> >
            per_proc_data(_world.size());

        // accumulate locale values
        for(size_t i = 0; i < width && _col_itr != _data.endcol();
            _col_itr++, i++) {

            // first accumulate column values, then distribute
            for(cb_nz_itr_t nz = _data.begnz(_col_itr);
                nz != _data.endnz(_col_itr); nz++) {

                index_type g_cb_col_idx = _col_itr.colid() + _offset_col;
                index_type g_cb_row_idx = nz.rowid()  + _offset_row;
                if(_transp_b) std::swap(g_cb_row_idx, g_cb_col_idx);

                size_t target_proc = A.Owner(g_cb_row_idx, g_cb_col_idx);
                index_type coords = idx(g_cb_row_idx, g_cb_col_idx);
                per_proc_data[target_proc].insert(
                    std::make_pair(coords, nz.value()));
            }
        }

        std::set<index_type> indices;
        _set_view(per_proc_data, indices);
        typename std::set<index_type>::iterator itr;
        for(itr = indices.begin(); itr != indices.end(); itr++)
            column_idxs.insert(col(*itr));
    }

    /**
     *  Extract a Elemental view of CombBLAS COLUMNS in sequence. The
     *  distribution of the Elemental view is determined by the distribution
     *  of the input matrix A.
     */
    template<typename dist_elem_matrix_t>
    void extract_elemental_column_slab_view(
            const dist_elem_matrix_t &A,
            size_t width = 1) {

        _values.clear();

        // structure per proc data
        std::vector< std::map<index_type, value_type> >
            per_proc_data(_world.size());

        for(size_t i = 0; i < width && _global_col < _ncol;
            _global_col++, i++) {

            // if I don't own anything in this column, continue
            if(_col_itr.colid() + _offset_col != _global_col)
                    continue;

            // first accumulate column values, then distribute
            for(cb_nz_itr_t nz = _data.begnz(_col_itr);
                nz != _data.endnz(_col_itr); nz++) {

                index_type g_cb_col_idx = _col_itr.colid() + _offset_col;
                index_type g_cb_row_idx = nz.rowid()  + _offset_row;
                if(_transp_b) std::swap(g_cb_row_idx, g_cb_col_idx);

                size_t target_proc = A.Owner(g_cb_row_idx, g_cb_col_idx);
                index_type coords = idx(g_cb_row_idx, g_cb_col_idx);
                per_proc_data[target_proc].insert(
                    std::make_pair(coords, nz.value()));
            }

            // this proc can advance to next column
            _col_itr++;
        }

        std::set<index_type> indices;
        _set_view(per_proc_data, indices);
    }

    /**
     *  Extract a Elemental view of CombBLAS ROWS. The distribution of the
     *  Elemental view is determined by the distribution of the input matrix
     *  A.
     *  The row_idxs store the global row indices that contain non-zeros.
     */
    template<typename dist_elem_matrix_t>
    void extract_elemental_row_slab_view(
            const dist_elem_matrix_t &A,
            std::set<index_type> &row_idxs,
            size_t height = 1) {

        _values.clear();

        // structure per proc data
        std::vector< std::map<index_type, value_type> >
            per_proc_data(_world.size());

        // accumulate locale values
        cb_col_itr_t col_itr;
        for(col_itr = _data.begcol(); col_itr != _data.endcol(); col_itr++) {

            // first accumulate column values, then distribute
            size_t r = 0;
            for(; r < height && _row_itr != _data.endnz(col_itr); _row_itr++, r++) {

                index_type g_cb_col_idx = col_itr.colid()  + _offset_col;
                index_type g_cb_row_idx = _row_itr.rowid() + _offset_row;
                if(_transp_b) std::swap(g_cb_row_idx, g_cb_col_idx);

                size_t target_proc = A.Owner(g_cb_row_idx, g_cb_col_idx);
                index_type coords = idx(g_cb_row_idx, g_cb_col_idx);
                per_proc_data[target_proc].insert(
                    std::make_pair(coords, _row_itr.value()));
            }
        }

        std::set<index_type> indices;
        _set_view(per_proc_data, indices);
        typename std::set<index_type>::iterator itr;
        for(itr = indices.begin(); itr != indices.end(); itr++)
            row_idxs.insert(row(*itr));
    }

    /**
     *  Extract a Elemental view of CombBLAS ROWS in sequence. The
     *  distribution of the Elemental view is determined by the distribution
     *  of the input matrix A.
     *  @Note CombBLAS is not well suited for this kind of traversals..
     */
    template<typename dist_elem_matrix_t>
    void extract_elemental_row_slab_view(
            const dist_elem_matrix_t &A,
            size_t height = 1) {

        _values.clear();

        // structure per proc data
        std::vector< std::map<index_type, value_type> >
            per_proc_data(_world.size());

        for(size_t col = 0; col < _row_itrs.size(); col++) {

            cb_nz_itr_t tmp_row_itr = _row_itrs[col];

            for(size_t i = 0; i < height && _global_row + i < _nrow; i++) {

                if(tmp_row_itr.rowid() + _offset_row != _global_row + i)
                    continue;

                index_type g_cb_col_idx = col + _offset_col;
                index_type g_cb_row_idx = tmp_row_itr.rowid() + _offset_row;
                if(_transp_b) std::swap(g_cb_row_idx, g_cb_col_idx);

                size_t target_proc = A.Owner(g_cb_row_idx, g_cb_col_idx);
                index_type coords = idx(g_cb_row_idx, g_cb_col_idx);
                per_proc_data[target_proc].insert(
                    std::make_pair(coords, tmp_row_itr.value()));

                tmp_row_itr++;
                _row_itrs[col]++;
            }
        }

        _global_row += height;

        std::set<index_type> indices;
        _set_view(per_proc_data, indices);
    }

    /**
     *  Extract the same full (redundantly stored) column view of the CombBLAS
     *  matrix on all processors.
     *  @caveat This method uses collective communication.
     */
    void extract_full_slab_view(std::set<index_type> &column_idxs,
                                const size_t slab_size = 1) {

        std::map<index_type, value_type> column_values;
        _values.clear();

        for(size_t i = 0; i < slab_size && _col_itr != _data.endcol();
            _col_itr++, i++) {

            // first accumulate column values, then distribute
            for(cb_nz_itr_t nz = _data.begnz(_col_itr);
                nz != _data.endnz(_col_itr); nz++) {

                index_type g_cb_col_idx = _col_itr.colid() + _offset_col;
                index_type g_cb_row_idx = nz.rowid()  + _offset_row;
                if(_transp_b) std::swap(g_cb_row_idx, g_cb_col_idx);

                index_type coords = idx(g_cb_row_idx, g_cb_col_idx);
                column_values.insert(std::make_pair(coords, nz.value()));
            }
        }

        std::vector< std::map< index_type, value_type> > vector_of_maps;
        boost::mpi::all_gather< std::map<index_type, value_type> > (
            _world, column_values, vector_of_maps);

        // gather vectors in one map
        typename std::map<index_type, value_type>::iterator itr;
        for(size_t i = 0; i < vector_of_maps.size(); ++i) {
            for(itr = vector_of_maps[i].begin();
                itr != vector_of_maps[i].end(); itr++) {

                column_idxs.insert(col(itr->first));

                if(_values.count(itr->first) != 0)
                    _values[itr->first] += itr->second;
                else
                    _values.insert(std::make_pair(itr->first, itr->second));
            }
        }
    }

    void extract_full_slab_view(const size_t width = 1) {

        _values.clear();

        std::map<index_type, value_type> column_values;

        for(size_t i = 0; i < width && _global_col < _ncol;
            _global_col++, i++) {

            // if I don't own anything in this column, continue
            if(_col_itr.colid() + _offset_col != _global_col)
                    continue;

            // first accumulate column values, then distribute
            for(cb_nz_itr_t nz = _data.begnz(_col_itr);
                nz != _data.endnz(_col_itr); nz++) {

                index_type g_cb_col_idx = _col_itr.colid() + _offset_col;
                index_type g_cb_row_idx = nz.rowid()  + _offset_row;
                if(_transp_b) std::swap(g_cb_row_idx, g_cb_col_idx);

                index_type coords = idx(g_cb_row_idx, g_cb_col_idx);
                column_values.insert(std::make_pair(coords, nz.value()));
            }

            // this proc can advance to next column
            _col_itr++;
        }

        std::vector< std::map< index_type, value_type> > vector_of_maps;
        boost::mpi::all_gather< std::map<index_type, value_type> > (
            _world, column_values, vector_of_maps);

        // gather vectors in one map
        typename std::map<index_type, value_type>::iterator itr;
        for(size_t i = 0; i < vector_of_maps.size(); ++i) {
            for(itr = vector_of_maps[i].begin();
                itr != vector_of_maps[i].end(); itr++) {

                if(_values.count(itr->first) != 0)
                    _values[itr->first] += itr->second;
                else
                    _values.insert(std::make_pair(itr->first, itr->second));
            }
        }
    }

    /// get the number of columns
    index_type ncols() { return _ncol; }
    index_type nrows() { return _nrow; }

private:

    /// world communicator of the view
    boost::mpi::communicator _world;

    /// the CombBLAS data
    cb_col_t &_data;

    /// the iterator holding the current position in iterating the CombBLAS
    /// matrix. This is used when generating slabs column wise.
    cb_col_itr_t _col_itr;

    /// the iterator holding the current position in iterating the CombBLAS
    /// matrix. This is used when generating slabs row wise.
    cb_nz_itr_t _row_itr;
    std::vector<cb_nz_itr_t> _row_itrs;

    /// transpose flag
    const bool _transp_b;

    /// this processors row offset in the CombBLAS matrix
    const size_t _offset_row;

    /// this processors column offset in the CombBLAS matrix
    const size_t _offset_col;

    size_t _global_row;
    size_t _global_col;

    /// number of columns
    size_t _ncol;
    /// number of rows
    size_t _nrow;

    /// holds the values of the current slab view
    std::map<index_type, value_type> _values;

    /// convert an 1D index to the global row index
    index_type row(index_type value) { return  value / _ncol; }

    /// convert an 1D index to the global column index
    index_type col(index_type value) { return  value % _ncol; }

    /// convert a (row, col) pair to a 1D index
    index_type idx(index_type row, index_type col) {
        return row * _ncol + col;
    }

    //FIXME: here we use collectives, add existing code for other comm schemes
    /// This methods takes a mapping from indices to processors and
    /// communicates the values to the target processor.
    void _set_view(
            std::vector< std::map<index_type, value_type> > &per_proc_data,
            std::set<index_type> &indices) {

        //// first we need to know how many elements we receive from other
        //// processors:
        //std::vector<size_t> n_values(_world.size());
        //for(size_t idx = 0; idx < _world.size(); ++idx)
            //n_values[idx] = per_proc_data[idx].size();

        //// resulting vector holds expected values: vector_of_sizes[from][to]
        //std::vector< std::vector<size_t> > vector_of_sizes;
        //boost::mpi::all_gather< std::vector<size_t> > (
            //_world, n_values, vector_of_sizes);

        // pre-post receives for all values then
        //FIXME: this is not working because Boost sends serialized data in
        // two steps: first the size and then the data, so waiting for one
        // irecv is not correct.
        //std::vector<boost::mpi::request> reqs;
        //std::vector< std::map<index_type, value_type> > vector_of_maps(_world.size());
        //for(size_t from = 0; from < _world.size(); ++from) {
            //size_t recv_size = vector_of_sizes[from][_world.rank()];
            //std::cout << "from " << from << " receive " << recv_size << std::endl;
            //if(recv_size > 0)
                //reqs.push_back(
                    //_world.irecv(from, 0, &vector_of_maps[from], recv_size));
        //}

        //// then perform all sends
        //for(size_t to = 0; to < _world.size(); ++to) {
            //if(per_proc_data[to].size() > 0)
                //_world.send(to, 0, per_proc_data[to]);
        //}

        //// wait for completion
        //boost::mpi::wait_all(reqs.begin(), reqs.end());

        std::vector<std::map<index_type, value_type> > vector_of_maps;
        for(int i = 0; i < _world.size(); ++i)
            boost::mpi::gather(_world, per_proc_data[i], vector_of_maps, i);

        typename std::map<index_type, value_type>::iterator itr;
        for(size_t i = 0; i < vector_of_maps.size(); ++i) {
            for(itr = vector_of_maps[i].begin();
                itr != vector_of_maps[i].end(); itr++) {

                indices.insert(itr->first);

                if(_values.count(itr->first) != 0)
                    _values[itr->first] += itr->second;
                else
                    _values.insert(std::make_pair(itr->first, itr->second));
            }
        }
    }
};

#endif

} } // namespace skylark::utility

#endif //SKYLARK_COMBBLAS_SLAB_VIEW


#include <iostream>
#include <armadillo>
#include <vector>
#include <cstdlib>
#include <fstream>
#include "libsnnlfisrm/snn.h"

/**
 * Calculates initial delays
*/
arma::Col<double> get_initial_delays();

/**
 * Calculates initial weights for lateral synapses
*/
arma::Col<double> get_initial_weights();

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

    // Test individual LIF/SRM neuron
    int snn_id = 2;
    int n_inputs = 2;
    int n_lateral = 1;
    std::vector<double> init_d({2, 4});
    std::vector<double> init_w({4});
    double tau_m = 1;
    double u_rest = 3;
    double init_v = 20;
    unsigned char t_rest = 2;
    double kappa_naugh = 3;
    double round_zero = 0.1;
    double u_max = 10;

    SpikeResponseModelNeuron neuron(snn_id, n_inputs, init_d, tau_m,
    u_rest, init_v, t_rest, kappa_naugh, round_zero, u_max);

    printf("delay vector: [");
    for(unsigned int i = 0; i < neuron.d_j.size(); i++)
    {
        printf(" %f ", neuron.d_j.at(i));
    }
    printf("]\n");

    // test t_pulse is working and making kappas...
    std::vector<double> u;
    printf("number of kappas: %d\n", (int)neuron.k_filter_list.at(K_LIST_INPUT_SYNAPSES).size());
    neuron.dendrite = std::vector<DelayedSpike>({DelayedSpike(0, true), DelayedSpike(0, 0)});
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.dendrite = std::vector<DelayedSpike>({DelayedSpike(0, false), DelayedSpike(0, 0)});
    neuron.t_pulse();
    u.push_back(neuron.membrane_potential());
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.dendrite = std::vector<DelayedSpike>({DelayedSpike(0, true), DelayedSpike(0, 0)});
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("membrane potential: %f\n", neuron.membrane_potential());
    u.push_back(neuron.membrane_potential());
    neuron.t_pulse();
    printf("number of kappas: %d\n", (int)neuron.k_filter_list.at(K_LIST_INPUT_SYNAPSES).size());

    printf("u plot: [ %f", u.at(0));
    for(unsigned int v = 1; v < u.size(); v++)
        printf(", %f", u.at(v));
    printf("]\n");

    // Testing FSTN
    printf("Testing Fisrt spike time neuron\n");
    double alpha = 1;
    FirstSpikeTimeNeuron fstn(0, alpha);
    fstn.dendrite = (double)2;
    fstn.t_pulse();
    fstn.dendrite = (double)2;
    fstn.t_pulse();
    fstn.dendrite = (double)2;
    fstn.t_pulse();
    fstn.dendrite = (double)2;
    fstn.t_pulse();
    fstn.dendrite = (double)2;
    fstn.t_pulse();
    fstn.dendrite = (double)2;
    fstn.t_pulse();
    fstn.dendrite = (double)2;
    fstn.t_pulse();
    fstn.dendrite = (double)2;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();
    fstn.dendrite = (double)5;
    fstn.t_pulse();

    // Testing neural network
    // parameters:
    unsigned int i_layer_size = 2;
    unsigned int h_layer_size = 16;
    std::vector<std::vector<double>> d_init({
        {4, 3, 2, 3, 4, 3, 3, 2, 1, 4, 3, 3, 3, 2, 5, 2},
        {1, 4, 2, 4, 6, 2, 1, 3, 5, 3, 3, 3, 3, 2, 5, 2}
    });
    std::vector<std::vector<double>> w_init({
        {1, 3, 2, -1, 3, 1, -2, 1, -2, 1, 3, 3, 3, 2, 5, 2},
        {3, 4, 2, 3, 4, 12, 1, 1, 2, 1, 3, 3, 3, 2, 5, 2},
        {2, 3, 2, 1, 2, 1, 2, 1, 1, 2, 3, 3, 3, 2, 5, 2},
        {-1, -2, -1, -2, -3, -4, -9, -1, 2, -2, 3, 3, 3, 2, 5, 2},
        {3, 4, 2, 1, 3, 6, 3, 1, 2, 3, 3, 3, 3, 2, 5, 2},
        {1, 3, 1, 3, 5, 3, 1, 0, 3, 1, 3, 3, 3, 2, 5, 2},
        {-2, -3, -1, -3, -5, 3, 2, 4, 5, 4, 3, 3, 3, 2, 5, 2},
        {1, 6, 2, 2, 4, 6, 3, 2, 4, 5, 3, 3, 3, 2, 5, 2},
        {-2, -5, -1, 4, -1, -3, 2, -7, -1, 3, 3, 3, 3, 2, 5, 2},
        {1, 5, 7, 2, 4, 6, 4, 1, 3, 5, 3, 3, 3, 2, 5, 2},
        {1, 5, 7, 2, 4, 6, 4, 1, 3, 5, 3, 3, 3, 2, 5, 2},
        {1, 5, 7, 2, 4, 6, 4, 1, 3, 5, 3, 3, 3, 2, 5, 2},
        {1, 5, 7, 2, 4, 6, 4, 1, 3, 5, 3, 3, 3, 2, 5, 2},
        {1, 5, 7, 2, 4, 6, 4, 1, 3, 5, 3, 3, 3, 2, 5, 2},
        {1, 5, 7, 2, 4, 6, 4, 1, 3, 5, 3, 3, 3, 2, 5, 2},
        {1, 5, 7, 2, 4, 6, 4, 1, 3, 5, 3, 3, 3, 2, 5, 2},
    });
    tau_m = 4;
    u_rest = 0;
    init_v = 20;
    double t_reset = 3;
    double k_nought = 10;
    round_zero = 0.1;
    alpha = 2;

    BaseSNN snn(i_layer_size, d_init, tau_m, u_rest, init_v, t_reset, k_nought,
    round_zero, alpha, u_max);

    snn.re_process({0, 0});
    for(int p = 0; p < 20; p++)
    {
        snn.process();
        for(unsigned int i = 0; i < h_layer_size; i++)
        {
            double u_i = snn.hidden_layer.at(i).membrane_potential();
            printf("U_%u = %f\n", i, u_i);
            if(snn.hidden_layer.at(i).axon.signal)
            {
                // this neuron is the "winner"
                printf("Spike %u winner (Reached threshold)\n", i);
                goto next_test;
            }
        }
    }
    next_test:
    // Test euclidean_distance_matrix function
    double distance_unit = 2;

    printf("Testing matrices.\n");
    arma::Mat<double> e_mat = euclidean_distance_matrix (&snn, distance_unit);
    printf("Matrix Content\n\n");
    for(unsigned int i = 0; i < snn.h_layer_size; i++)
    {
        for(unsigned int j = 0; j < snn.h_layer_size; j++)
        {
            printf("%f\t", e_mat(i, j));
        }
        printf("\n");
    }

    // Test euclidean initial weight function
    double sigma_1 = .5;
    double sigma_2 = 2;
    std::vector<arma::Col<double>> w_mat = initial_weight_euclidean(e_mat, sigma_1, sigma_2);
    printf("\nPrinting weight matrix's contents:\n\n");
    printf("Weight Matrix Content\n\n");
    for(unsigned int i = 0; i < snn.h_layer_size; i++)
    {
        for(unsigned int j = 0; j < snn.h_layer_size; j++)
        {
            printf("%f\t", w_mat.at(i).at(j));
        }
        printf("\n");
    }

    // Testing euclidean_distance_matrix without snn
    std::vector<std::vector<double>> point_list({
        {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3},
        {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}
    });

    printf("Testing matrices.\n");
    arma::Mat<double> e_mat_init = euclidean_distance_matrix(&point_list, distance_unit);
    printf("Matrix Content\n\n");
    for(unsigned int i = 0; i < e_mat_init.n_rows; i++)
    {
        for(unsigned int j = 0; j < e_mat_init.n_cols; j++)
        {
            printf("%f\t", e_mat_init(i, j));
        }
        printf("\n");
    }

    // Testing Random delay generator
    printf("\nTesting Random Delay generator\n");
    std::vector<std::vector<double>> initial_delays;

    unsigned int n_x = 10;
    unsigned int n_y = 10;
    unsigned int delays_per_row = 10;
    unsigned int delays_per_column = 10;

    initial_delays = initial_delay_vector_2d_map(n_x, n_y,
    delays_per_row, delays_per_column);

    printf("Delay Matrix Content\n\n");
    for(unsigned int i = 0; i < initial_delays.size(); i++)
    {
        for(unsigned int j = 0; j < initial_delays.at(i).size(); j++)
        {
            printf("%f\t", initial_delays.at(i).at(j));
        }
        printf("\n");
    }

    // Test point map creator
    printf("\nTesting point map creator.\n");
    unsigned int x_size = 4;
    unsigned int y_size = 7;
    std::vector<std::vector<double>> map_ = euclidean_point_map(x_size, y_size);
    printf("Point Map Matrix Content\n\n");

    for(unsigned int j = 0; j < x_size*y_size; j++)
    {
        printf("(%f, %f)\t", map_.at(0).at(j), map_.at(1).at(j));
    }

    // Testing training algorthm
    unsigned int n_data = 2;
    tau_m = 1.5;
    u_rest = 2;
    init_v = 4.5;
    t_reset = 3;
    k_nought = 2.5;
    round_zero = 0.05;
    alpha = 4;
    // note that n_x * n_y = h_layer_size
    n_x = 10;
    n_y = 10;
    double delay_distance = 1;
    distance_unit = 1;
    sigma_1 = 0.7;
    sigma_2 = 1.6;

    double sigma_neighbor = 1;

    double eta_d = .5;
    unsigned int t_max = 25;
    unsigned int t_delta = 3;
    double ltd_max = -0.45;
    SNN model(n_data, tau_m, u_rest, init_v,
    t_reset, k_nought, round_zero, alpha, n_x, n_y, delay_distance,
    distance_unit, sigma_neighbor, eta_d, t_max, u_max);

    std::vector<std::vector<double>> data = {
        {0, 0, 0, 1, 2, 2, 3, 3, 3, 3, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 1, 2, 2, 2, 3, 3, 4, 5, 6, 7, 7, 7, 7, 6, 5, 4, 3, 2, 1, 0}
    };
    std::vector<std::vector<double>> data_2 = {
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1},
    };

    // save delays into file:
    std::ofstream delay_file;
    delay_file.open("snn_data_delays.txt");

    // print out delays
    delay_file << "Delays before training:" << std::endl;
    for(unsigned int i = 0; i < model.snn->d_ji.size(); i++)
    {
        delay_file << "[ ";
        for(unsigned int j = 0; j < model.snn->d_ji.at(i).size(); j++)
        {
            delay_file << model.snn->d_ji.at(i).at(j) << ", ";
        }
        delay_file << "]" << std::endl;
    }
    // print out weights

    for(unsigned int p = 0; p < 1; p++)
        model.train(data);

    delay_file << std::endl << "Delays after training:"<< std::endl;
     for(unsigned int i = 0; i < model.snn->d_ji.size(); i++)
    {
        delay_file << "[ ";
        for(unsigned int j = 0; j < model.snn->d_ji.at(i).size(); j++)
        {
            delay_file << model.snn->d_ji.at(i).at(j) << ", ";
        }
        delay_file << "]" << std::endl;
    }
    delay_file.close();

    // Armadillo version printout
    arma::arma_version ver;
    printf("\nArmadillo Version: %s\n", ver.as_string().c_str());
    return 0;
}


/* Copyright 2019 The Spin-Scenario Authors. All Rights Reserved.
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 "grape.h"
#include <kernel/utilities/ssl_plot.h>
using namespace ssl::utility;
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
#include <chrono>
namespace ssl {
namespace oc {
int g_oc_iteration_save = 0;
void set_oc_iteration_save(const sol::table &t) {
	  for (auto &kv : t) {
    int val = kv.second.as<int>();
    g_oc_iteration_save = val;
    break;
  }
}
typedef struct {
  size_t i; // step index.
  size_t j; // channel index.
  size_t chs; // channel number.
  double amp;
} amp_constraint_data;

grape::grape(spin_system &sys)
    : rf_(nullptr) {
  sys_ = &sys;
  superop_.rf_ctrl = sys.rf_hamiltonian();
  superop_.L0 = sys.free_hamiltonian();
  superop_.R = sys.relaxation();
  superop_.L0s = sys.free_hamiltonians();
  int bb_size = superop_.L0s.size();
  //std::cout << bb_size << "\n";
  if (bb_size) {
    // superop_.profile = vec::Ones(bb_size);
    superop_.nominal_offset = sys.nominal_broadband();
    superop_.grad_bb = std::vector<std::vector<double>>(bb_size);
  } else {  // no offset case.
    superop_.L0s.push_back(superop_.L0);
    superop_.grad_bb = std::vector<std::vector<double>>(1);
  }
  opt_model_ = _rho2rho;
  axis_ = uxuy_;
}

grape::~grape() {
}

double grape::maxf() const {
  return max_val_;
}

seq_block &grape::optimize(const sol::table &t) {
  assign_pulse(t);
  assign_x();
  assign_nlopt(t);
  assign_state(t);
  assign_aux_var();

  if (opt_model_ == _rho2rho)
      optimizer_->set_max_objective(objfunc_broadband, this);
  if (opt_model_ == _propagator)
    optimizer_->set_max_objective(objfunc_propagator, this);

  print();
  double max_f;
  nlopt::result result = optimizer_->optimize(x_, max_f);
  if (result == nlopt::SUCCESS)
    ssl_color_text("info", "pulse optimization succeed.\n");
  if (result == nlopt::FAILURE)
    ssl_color_text("err", "pulse optimization failed.\n");
  if (result == nlopt::MAXEVAL_REACHED)
    ssl_color_text("warn", "pulse optimization terminated due to maximum iterations limitation.\n");

  max_val_ = max_f;
  rf_->update_raw_data(axis_, x_.data());

  grape::h5write();
  return *rf_;
}

double grape::objfunc_broadband(const std::vector<double> &x, std::vector<double> &grad,
                                void *func) {
  return ((grape *)func)->objfunc_broadband(x, grad);
}
double grape::objfunc_propagator(const std::vector<double> &x, std::vector<double> &grad,
                                 void *func) {
  return ((grape *)func)->objfunc_propagator(x, grad);
}

void grape::assign_state(const sol::table &t) {
  if (is_retrievable("targ_op", t)) {
    opt_model_ = _propagator;
    targ_op_ = sys_->smart_op(retrieve_table_str("targ_op", t));
    targ_op_ = ssl::spinsys::propagator(targ_op_, _pi);
    int n = targ_op_.rows();
    init_op_ = sp_cx_mat(n, n);
    init_op_.setIdentity();

    // in case of unified targ for diff freq offsets.
    if (superop_.L0s.size())
      targ_op_list_ = std::vector<sp_cx_mat>(superop_.L0s.size(), targ_op_);
    return;
  }

  opt_model_ = _rho2rho;
  init_state_ = sys_->smart_state(retrieve_table_str("init_state", t));
  init_state_ = norm_state(init_state_);

  sol::object obj = retrieve_table("targ_state", t);
  if (obj.get_type() == sol::type::string) {
    targ_state_ = sys_->smart_state(retrieve_table_str("targ_state", t));
    targ_state_ = norm_state(targ_state_);

    // in case of unified targ for diff freq offsets.
    if (superop_.L0s.size())
      targ_list_ = std::vector<sp_cx_vec>(superop_.L0s.size(), targ_state_);
  }

  if (obj.get_type() == sol::type::table) {
    targ_list_.clear();
    sol::table targ_table = obj.as<sol::table>();
    for (size_t i = 0; i < targ_table.size(); i++) {
      sol::object val = targ_table[i + 1];
      sp_cx_vec targ = val.as<sp_cx_vec>();
      targ=norm_state(targ);
      targ_list_.push_back(targ);
    }
  }
}

void grape::assign_nlopt(const sol::table &t) {
  // TO BE REMOVE.
  epsilon_ = 0;      // default val.
  epsilon_num_ = 1;  // default val.
  if (is_retrievable("epsilon", t)) {
    epsilon_ = retrieve_table_double("epsilon", t);
  }

  if (is_retrievable("epsilon_num", t)) {
    epsilon_num_ = retrieve_table_int("epsilon_num", t);
  }
  nlopt::algorithm alg = nlopt::LD_LBFGS;

  if (is_retrievable("algorithm", t)) {
    std::string oc = retrieve_table_str("algorithm", t);
    boost::to_upper(oc);
    if (oc == "MNA") alg = nlopt::LD_MMA;
  }
  optimizer_ = new nlopt::opt(alg, x_dim_);

  if (is_retrievable("max_eval", t)) {
    int val = retrieve_table_int("max_eval", t);
    if (val > 0)
      optimizer_->set_maxeval(val);
    else
      throw std::runtime_error("max_eval must > 0.");
  }

  if (is_retrievable("xtol_rel", t)) {
    double val = retrieve_table_double("xtol_rel", t);
    optimizer_->set_xtol_rel(val);
  } else
    optimizer_->set_xtol_rel(1e-8);

  assign_constraint(t);
}
void grape::print() const {
  if (optimizer_->get_algorithm() == nlopt::LD_MMA)
    ssl_color_text("oc", "nlopt algorithm used: LD_MMA.\n");
  if (optimizer_->get_algorithm() == nlopt::LD_LBFGS)
    ssl_color_text("oc", "nlopt algorithm used: LD_LBFGS.\n");

  ssl_color_text("oc", "x dim: " + boost::lexical_cast<std::string>(x_dim_) +  ".\n");
  ssl_color_text("oc", "nlopt xtol_rel: " + boost::lexical_cast<std::string>(optimizer_->get_xtol_rel()) +  ".\n");

}
void grape::assign_x() {
  x_.clear();
  rf_->convert2(_ux_uy);  // must do it.

  if (axis_ == uxuy_)
    x_ = rf_->clone_raw_data();
  else if (axis_ == ux_)
    x_ = rf_->clone_raw_data_ux();
  else if (axis_ == uy_)
    x_ = rf_->clone_raw_data_uy();

  x_dim_ = x_.size();
}

void grape::assign_pulse(const sol::table &t) {
  double width = retrieve_table_double("width", t); // unit in ms.
  size_t nsteps = (size_t) (retrieve_table_int("step", t));
  std::string pattern = "rand_spline";
  if (is_retrievable("init_pattern", t))
    pattern = retrieve_table_str("init_pattern", t);

  double max_init_amp = 1;
  if (is_retrievable("max_init_amp", t))
    max_init_amp = retrieve_table_double("max_init_amp", t);

  double dt = width * 1e-3 / double(nsteps); // into s.

  std::string str_chs;
  if (is_retrievable("limit_channel", t))
      str_chs = retrieve_table_str("limit_channel", t);
  else
  str_chs = boost::algorithm::join(superop_.rf_ctrl.chs, " ");

  std::string code = "user_rf = shapedRF{name = 'opt-rf', width = " +
                boost::lexical_cast<std::string>(width) +
                ",  step = " + boost::lexical_cast<std::string>(nsteps) +
                ",  max_amp = " + boost::lexical_cast<std::string>(max_init_amp) +
                ",  channel = '" + str_chs + "'," + "pattern = '" + pattern +
                "'}";

  g_lua->script(code);

   std::string s = str(boost::format("pulse width - [%.3f] ms, steps - [%d], step width - [%.3f] us.\n") % width % nsteps
                     % (dt * 1e6));
  ssl_color_text("info", s);

  sol::object val = (*g_lua)["user_rf"];
  seq_block &sb = val.as<seq_block &>(); // note the original type should be 'seq_block'.
  shaped_rf &user_rf = (shaped_rf &) sb; // transfrom into 'shaped_rf'.

  rf_ = &user_rf;

  if (is_retrievable("limit_axis", t)) {
    std::string s = retrieve_table_str("limit_axis", t);
    if (s == "x")
      axis_ = ux_;
    else if (s == "y")
      axis_ = uy_;
  }

  rf_->convert2(_ux_uy);  // must do it.

  if(axis_==ux_) {
   std::vector<double>zeros = std::vector<double>(rf_->get_dims() / 2, 0);
    rf_->update_raw_data_uy(zeros.data());
  }
  if (axis_ == uy_) {
    std::vector<double> zeros = std::vector<double>(rf_->get_dims() / 2, 0);
    rf_->update_raw_data_ux(zeros.data());
  }
}

void grape::h5write(std::string file_name) const {
  if (file_name.empty()) {
    std::string time_s = sys_time();
    file_name = "oc_" + time_s + ".h5";
  }
  H5File file(file_name, H5F_ACC_TRUNC);

  rf_->switch_rf_mode("amp/phase");
  rf_->h5write(file, "opt");

  ssl::utility::h5write(file, nullptr, "obj", stl2vec(obj_val_));
  file.close();
}

void grape::assign_aux_var() {
  // traj_ = state_traj(rf_->get_steps());
  int nbb = superop_.L0s.size();
  if (nbb >= 1) {
    omp_set_num_threads(omp_core_num);
    for (size_t i = 0; i < superop_.grad_bb.size(); i++)
      superop_.grad_bb[i] = std::vector<double>(rf_->get_dims(), 0);

    if (opt_model_ == _rho2rho) {
      traj_omp_ = std::vector<state_traj>(omp_core_num);
      for (int i = 0; i < omp_core_num; i++) {
        traj_omp_[i] = state_traj(rf_->get_steps());
      }
    }
    if (opt_model_ == _propagator) {
      op_traj_omp_ = std::vector<op_traj>(omp_core_num);
      for (int i = 0; i < omp_core_num; i++) {
        op_traj_omp_[i] = op_traj(rf_->get_steps());
      }
    }
  }
}

void grape::assign_constraint(const sol::table &t) {
  if (is_retrievable("max_amp", t)) {
    double val = retrieve_table_double("max_amp", t);  // unit in hz.
    val *= 2 * _pi;

    size_t dim = rf_->get_dims();
    size_t nsteps = rf_->get_steps();
    size_t nchannels = rf_->get_channels();
    std::vector<double> up_bound(dim);
    std::vector<double> low_bound(dim);

    // only for ux/uy mode.
    if (rf_->mode() == _ux_uy)
      for (size_t i = 0; i < nsteps; i++) {
        for (size_t j = 0; j < nchannels; j++) {
          // ux.
          up_bound[2 * nchannels * i + 2 * j] = val;
          low_bound[2 * nchannels * i + 2 * j] = -val;

          // uy.
          up_bound[2 * nchannels * i + 2 * j + 1] = val;
          low_bound[2 * nchannels * i + 2 * j + 1] = -val;
        }
      }

    optimizer_->set_upper_bounds(up_bound);
    optimizer_->set_lower_bounds(low_bound);
  }
}

double grape::objfunc_broadband(const std::vector<double> &x, std::vector<double> &grad) {
  auto start = std::chrono::system_clock::now();
  rf_->update_raw_data(axis_, x.data());
  int N = superop_.L0s.size();
  vec phi = vec::Zero(N);

  size_t nsteps = rf_->get_steps();
  size_t nchannels = rf_->get_channels();
  double dt = rf_->get_dt() * 1e-6;  // into s.
  std::vector<std::string> chs = rf_->get_channels_str();

  // double alpha = alpha0;
  vec rf_scaling =
      vec::LinSpaced(epsilon_num_, -epsilon_, epsilon_);  // e.g. -10% - 10%

#pragma omp parallel for
  for (int p = 0; p < N; p++) {
    int id = omp_get_thread_num();
    traj_omp_[id].forward[0] = init_state_;
    traj_omp_[id].backward[nsteps] = targ_list_[p];
    sp_cx_mat L;
    sp_cx_mat L0 = superop_.L0s[p] + ci * superop_.R;

    int dim = rf_->get_dims();
    if (axis_ != uxuy_) dim /= 2;
    superop_.grad_bb[p] = std::vector<double>(dim, 0);
    // start rf inhom.

    for (int q = 0; q < rf_scaling.size();
         q++) {  // for each rf scaling factor, do

      double kx = 1 + rf_scaling[q], ky = 1 + rf_scaling[q];

      sp_cx_vec rho = traj_omp_[id].forward[0];
      for (size_t i = 0; i < nsteps; i++) {
        L = L0;
        for (size_t j = 0; j < nchannels; j++)
          L += update_rf_ham(x.data(), i, j, chs[j], nchannels, kx, ky);
        traj_omp_[id].forward[i + 1] = ssl::spinsys::step(rho, L, dt);
        rho = traj_omp_[id].forward[i + 1];
      }
      rho = traj_omp_[id].backward[nsteps];
      for (int i = nsteps - 1; i >= 0; i--) {
        L = L0.adjoint();
        for (size_t j = 0; j < nchannels; j++)
          L += update_rf_ham(x.data(), i, j, chs[j], nchannels, kx, ky);
        traj_omp_[id].backward[i] = ssl::spinsys::step(rho, L, -dt);
        rho = traj_omp_[id].backward[i];
      }
      sp_cx_mat Gx, Gy, tmp;
      int k = 0;
      for (size_t i = 0; i < nsteps; i++) {
        L = L0;
        for (size_t j = 0; j < nchannels; j++)
          L += update_rf_ham(x.data(), i, j, chs[j], nchannels, kx, ky);
        tmp = traj_omp_[id].backward[i + 1].adjoint() *
              ssl::spinsys::propagator(L, dt);
        for (size_t j = 0; j < nchannels; j++) {
          if (axis_ == uxuy_) {
            Gx = propagator_derivative(L, superop_.rf_ctrl.Lx[j], dt);
            Gy = propagator_derivative(L, superop_.rf_ctrl.Ly[j], dt);

            superop_.grad_bb[p][k] +=
                traced(tmp * Gx * traj_omp_[id].forward[i]).real();
            superop_.grad_bb[p][k + 1] +=
                traced(tmp * Gy * traj_omp_[id].forward[i]).real();
            k += 2;
          } else if (axis_ == ux_) {
            Gx = propagator_derivative(L, superop_.rf_ctrl.Lx[j], dt);

            superop_.grad_bb[p][k] +=
                traced(tmp * Gx * traj_omp_[id].forward[i]).real();
            k += 1;
          } else if (axis_ == uy_) {
            Gy = propagator_derivative(L, superop_.rf_ctrl.Ly[j], dt);

            superop_.grad_bb[p][k] +=
                traced(tmp * Gy * traj_omp_[id].forward[i]).real();
            k += 1;
          }
        }
      }
      phi[p] += transfer_fidelity(traj_omp_[id].forward[nsteps], targ_list_[p]);
    }

    /// transfer_fidelity(targ_list_[p], targ_list_[p]);
  }  // end parallel for.

  double val = phi.sum() / (double)N / (double)rf_scaling.size();

  int k = 0;
  for (size_t i = 0; i < nsteps; i++) {
    for (size_t j = 0; j < nchannels; j++) {
      double gx = 0;
      double gy = 0;
      if (axis_ == uxuy_) {
        for (int p = 0; p < N; p++) {
          gx += superop_.grad_bb[p][k];
          gy += superop_.grad_bb[p][k + 1];
        }
        grad[k] = gx / (double)N / (double)rf_scaling.size();
        grad[k + 1] = gy / (double)N / (double)rf_scaling.size();

        // rf power reduction.
        /*if (alpha != 0) {
          double ux, uy;
          ux = x[2 * nchannels * i + 2 * j];
          uy = x[2 * nchannels * i + 2 * j + 1];
          grad[k] -= 2.0 * alpha * ux * dt;
          grad[k + 1] -= 2.0 * alpha * uy * dt;
        }*/
        k += 2;
      } else if (axis_ == ux_) {
        for (int p = 0; p < N; p++) {
          gx += superop_.grad_bb[p][k];
        }
        grad[k] = gx / (double)N / (double)rf_scaling.size();

        k += 1;
      } else if (axis_ == uy_) {
        for (int p = 0; p < N; p++) {
          gy += superop_.grad_bb[p][k];
        }
        grad[k] = gy / (double)N / (double)rf_scaling.size();

        k += 1;
      }
    }
  }
  auto end = std::chrono::system_clock::now();
  auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
  if(double(duration.count()) * std::chrono::microseconds::period::num / std::chrono::microseconds::period::den>2){
    g_oc_iteration_save = 1;
  }
  iteration_shape(x, optimizer_->get_numevals());
  //double PHI1 = val;
  //double PHI2 = 0;
  // double PHI2 = alpha * rf_->rf_power();
  // std::cout << boost::format("==> %04d  [%.8f] [%.8f]\n") %
  // (++opt_.iter_count) % PHI1 % PHI2;
  std::cout << boost::format("==> %04d  [%.8f]\n") % (optimizer_->get_numevals()) % val;
 obj_val_.push_back(val);
  //std::cout << "Use Time:" << double(duration.count()) * microseconds::period::num / microseconds::period::den << " s.\n";
  return val;
}
void grape::iteration_shape(const std::vector<double> &x, int iter) {
  if (!g_oc_iteration_save) return;
  rf_->update_raw_data(axis_, x_.data());
  std::string file = "iteration_" + std::to_string(iter) + "_rf_shape.RF";
  std::ofstream ofstr(file.c_str());
  ofstr << "# " << sys_time()<<"\n";
   rf_->write(ofstr);
  ofstr.close();
}
double grape::objfunc_propagator(const std::vector<double> &x,
                                 std::vector<double> &grad) {
  auto start = std::chrono::system_clock::now();
  rf_->update_raw_data(x.data());
  int N = superop_.L0s.size();
  vec phi = vec::Zero(N);

  size_t nsteps = rf_->get_steps();
  size_t nchannels = rf_->get_channels();
  double dt = rf_->get_dt() * 1e-6;  // into s.
  std::vector<std::string> chs = rf_->get_channels_str();

  // double alpha = alpha0;
  vec rf_scaling =
      vec::LinSpaced(epsilon_num_, -epsilon_, epsilon_);  // e.g. -10% - 10%

#pragma omp parallel for
  for (int p = 0; p < N; p++) {
    int id = omp_get_thread_num();
    op_traj_omp_[id].forward[0] = init_op_;
    op_traj_omp_[id].backward[nsteps] = targ_op_list_[p];
    sp_cx_mat L;
    sp_cx_mat L0 = superop_.L0s[p] + ci * superop_.R;

     int dim = rf_->get_dims();
    if (axis_ != uxuy_) dim /= 2;
    superop_.grad_bb[p] = std::vector<double>(dim, 0);
    // start rf inhom.

    for (int q = 0; q < rf_scaling.size();
         q++) {  // for each rf scaling factor, do

      double kx = 1 + rf_scaling[q], ky = 1 + rf_scaling[q];

      sp_cx_mat rho = op_traj_omp_[id].forward[0];
      for (size_t i = 0; i < nsteps; i++) {
        L = L0;
        for (size_t j = 0; j < nchannels; j++)
          L += update_rf_ham(x.data(), i, j, chs[j], nchannels, kx, ky);
        op_traj_omp_[id].forward[i + 1] = ssl::spinsys::propagator(L, dt) * rho;
        rho = op_traj_omp_[id].forward[i + 1];
      }
      rho = op_traj_omp_[id].backward[nsteps];
      for (int i = nsteps - 1; i >= 0; i--) {
        L = L0;
        for (size_t j = 0; j < nchannels; j++)
          L += update_rf_ham(x.data(), i, j, chs[j], nchannels, kx, ky);
        op_traj_omp_[id].backward[i] =
            ssl::spinsys::propagator(L, dt).adjoint() * rho;
        rho = op_traj_omp_[id].backward[i];
      }
      sp_cx_mat Gx, Gy, tmp;
      int k = 0;
      for (size_t i = 0; i < nsteps; i++) {
        L = L0;
        for (size_t j = 0; j < nchannels; j++)
          L += update_rf_ham(x.data(), i, j, chs[j], nchannels, kx, ky);
        tmp = op_traj_omp_[id].backward[i + 1].adjoint() *
              ssl::spinsys::propagator(L, dt);
        for (size_t j = 0; j < nchannels; j++) {
          if (axis_ == uxuy_) {
            Gx = propagator_derivative(L, superop_.rf_ctrl.Lx[j], dt);
            Gy = propagator_derivative(L, superop_.rf_ctrl.Ly[j], dt);

            superop_.grad_bb[p][k] +=
                traced(tmp * Gx * op_traj_omp_[id].forward[i]).real();
            superop_.grad_bb[p][k + 1] +=
                traced(tmp * Gy * op_traj_omp_[id].forward[i]).real();
            k += 2;
          } else if (axis_ == ux_) {
            Gx = propagator_derivative(L, superop_.rf_ctrl.Lx[j], dt);

            superop_.grad_bb[p][k] +=
                traced(tmp * Gx * op_traj_omp_[id].forward[i]).real();
            k += 1;
          } else if (axis_ == uy_) {
            Gy = propagator_derivative(L, superop_.rf_ctrl.Ly[j], dt);
            superop_.grad_bb[p][k] +=
                traced(tmp * Gy * op_traj_omp_[id].forward[i]).real();
            k += 2;
          }
        }
      }
      phi[p] += transfer_fidelity(op_traj_omp_[id].forward[nsteps],
                                  targ_op_list_[p]) /
                transfer_fidelity(targ_op_list_[p], targ_op_list_[p]);
    }

    /// transfer_fidelity(targ_list_[p], targ_list_[p]);
  }  // end parallel for.

  double val = phi.sum() / (double)N / (double)rf_scaling.size();

  int k = 0;
  for (size_t i = 0; i < nsteps; i++) {
    for (size_t j = 0; j < nchannels; j++) {
      double gx = 0;
      double gy = 0;
     if (axis_ == uxuy_) {
        for (int p = 0; p < N; p++) {
          gx += superop_.grad_bb[p][k];
          gy += superop_.grad_bb[p][k + 1];
        }
        grad[k] = gx / (double)N / (double)rf_scaling.size();
        grad[k + 1] = gy / (double)N / (double)rf_scaling.size();

        // rf power reduction.
        /*if (alpha != 0) {
          double ux, uy;
          ux = x[2 * nchannels * i + 2 * j];
          uy = x[2 * nchannels * i + 2 * j + 1];
          grad[k] -= 2.0 * alpha * ux * dt;
          grad[k + 1] -= 2.0 * alpha * uy * dt;
        }*/
        k += 2;
      } else if (axis_ == ux_) {
        for (int p = 0; p < N; p++) {
          gx += superop_.grad_bb[p][k];
        }
        grad[k] = gx / (double)N / (double)rf_scaling.size();

        k += 1;
      } else if (axis_ == uy_) {
        for (int p = 0; p < N; p++) {
          gy += superop_.grad_bb[p][k];
        }
        grad[k] = gy / (double)N / (double)rf_scaling.size();

        k += 1;
      }
    }
  }

  //double PHI1 = val;
  //double PHI2 = 0;
  // double PHI2 = alpha * rf_->rf_power();
  // std::cout << boost::format("==> %04d  [%.8f] [%.8f]\n") %
  // (++opt_.iter_count) % PHI1 % PHI2;

  auto end = std::chrono::system_clock::now();
  auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
  if(double(duration.count()) * std::chrono::microseconds::period::num / std::chrono::microseconds::period::den>2){
    g_oc_iteration_save = 1;
  }
  iteration_shape(x, optimizer_->get_numevals());

  std::cout << boost::format("==> %04d  [%.8f]\n") % (optimizer_->get_numevals()) % val;
  obj_val_.push_back(val);
  return (val);
}
// void grape::cartesian2polar(double amp, double phase, double gx, double gy,
//                            double &g_amp, double &g_phase) {
//  double ux = amp * cos(phase);
//  double uy = amp * sin(phase);
//  g_amp = gx * ux + gy * uy;
//  g_amp /= amp;
//  g_phase = -gx * uy + gy * ux;
//}

double grape::amplitude_constraint(unsigned n, const double *x, double *grad,
                                   void *data) {
  amp_constraint_data *d = reinterpret_cast<amp_constraint_data *>(data);

  double ux = x[2 * d->chs * d->i + 2 * d->j];
  double uy = x[2 * d->chs * d->i + 2 * d->j + 1];

  return ux * ux + uy * uy - d->amp * d->amp;
}

sp_cx_mat grape::update_rf_ham(const double *x, size_t step, size_t channel, std::string ch_str,
                               size_t nchannels, double kx, double ky) {
  int ch = superop_.rf_ctrl.channel_index(ch_str);
  if (axis_ == uxuy_) {
    double ux = x[2 * nchannels * step + 2 * channel];
    double uy = x[2 * nchannels * step + 2 * channel + 1];
    // Rf inhom
    ux *= kx;
    uy *= ky;
    return ux * superop_.rf_ctrl.Lx[ch] + uy * superop_.rf_ctrl.Ly[ch];
  } else if (axis_ == ux_) {
    double ux = x[nchannels * step + channel];
    // Rf inhom
    ux *= kx;
    return ux * superop_.rf_ctrl.Lx[ch];
  } else if (axis_ == uy_) {
    double uy = x[nchannels * step + channel];
    // Rf inhom
    uy *= ky;
    return uy * superop_.rf_ctrl.Ly[ch];
  }
}
sp_cx_mat grape::propagator_derivative(const sp_cx_mat &L, const sp_cx_mat &Lrf, double dt) {
  sp_cx_mat commu1, commu2, commu3;
  commu1 = commutator(L, Lrf);
  commu2 = commutator(L, commu1);
  commu3 = commutator(L, commu2);
  return -ci * dt * Lrf + 1 / 2.0 * dt * dt * commu1 + 1 / 6.0 * dt * dt * dt * commu2
      - 1 / 24.0 * dt * dt * dt * dt * commu3;
}

void grape::projection(const sol::table &t) {
  epsilon_ = 0;
  epsilon_num_ = 1;  // default val.
  if (is_retrievable("epsilon", t)) {
    epsilon_ = retrieve_table_double("epsilon", t);
  }
  if (is_retrievable("epsilon_num", t)) {
    epsilon_num_ = retrieve_table_int("epsilon_num", t);
  }
  sol::object val = retrieve_table("init_state", t);
  sp_cx_vec init_state = sys_->smart_state(val.as<std::string>());
  init_state = norm_state(init_state);
  //init_state = levante_ernst(init_state);

  val = retrieve_table("rf", t);
  seq_block &sb = val.as<seq_block &>();
 shaped_rf &user_rf = (shaped_rf &) sb; // transfrom into 'shaped_rf'.
  user_rf.convert2(_ux_uy);

  size_t nsteps = user_rf.get_steps();
  size_t nchannels = user_rf.get_channels();
  double dt = user_rf.width_in_ms() * 1e-3 / double(nsteps); // into s.

  std::map<std::string, sp_cx_vec> obsrv_state_map;
  std::vector<std::string> expr;
  std::vector<sp_cx_vec> obsrv_state;
  if (is_retrievable("observ_states", t)) {
    val = retrieve_table("observ_states", t);
    sol::table expr_table = val.as<sol::table>();
    if (expr_table.size() == 0) {
      obsrv_state_map = sys_->cartesian_basis_states();
    } else {
      for (size_t i = 0; i < expr_table.size(); i++) {
        sol::object val = expr_table[i + 1];
        std::string exp = val.as<std::string>();
        sp_cx_vec rho = sys_->smart_state(exp);
        obsrv_state_map.insert(std::pair<std::string, sp_cx_vec>(exp, rho));
      }
    }

  } else if (is_retrievable("ignore_states", t)) {
    val = retrieve_table("ignore_states", t);
    sol::table expr_table = val.as<sol::table>();
    if (expr_table.size() == 0) {
      obsrv_state_map = sys_->cartesian_basis_states();
    } else {
      obsrv_state_map = sys_->cartesian_basis_states();

      for (size_t i = 0; i < expr_table.size(); i++) {
        sol::object val = expr_table[i + 1];
        std::string exp = val.as<std::string>();
        std::map<std::string, sp_cx_vec>::iterator key = obsrv_state_map.find(exp);
        if (key != obsrv_state_map.end()) obsrv_state_map.erase(key);
      }
    }
  } else {
    obsrv_state_map = sys_->cartesian_basis_states();
  }

  std::map<std::string, sp_cx_vec>::iterator iter;
  for (iter = obsrv_state_map.begin(); iter != obsrv_state_map.end(); iter++) {
    obsrv_state.push_back(norm_state(iter->second));
    // obsrv_state.push_back(levante_ernst(iter->second));
    expr.push_back(iter->first);
  }
  std::string dim1 = "observed states\n";
  std::string dim2 = "";
  std::string dim3 = "rf scaling\n";
  std::string s;
  for (size_t p = 0; p < expr.size(); p++)
    s += std::to_string(p + 1) + "-" + expr[p] + "\n";
  dim1 += s;

  std::string str_opt = "step";
  if (is_retrievable("option", t)) str_opt = retrieve_table_str("option", t);

  if (str_opt != "step" && str_opt != "broadband") {
    std::string s = "unknown projection option ** " + str_opt +
               " ** using 'step' or 'broadband' instead.";
    throw std::runtime_error(s.c_str());
  }

  std::vector<double> x = user_rf.clone_raw_data();
  vec rf_scaling =
      vec::LinSpaced(epsilon_num_, -epsilon_, epsilon_);  // e.g. -10% - 10%

  size_t n = rf_scaling.size();
  if (n == 1)
    dim3 += "no rf inhomogeneity";
  else
    dim3 += boost::lexical_cast<std::string>(rf_scaling[0] * 100) + ":" +
            boost::lexical_cast<std::string>(rf_scaling[n - 1] * 100) + " " +
            boost::lexical_cast<std::string>(n) + " %\n";

  cube comp_dist;
  // for BROADBAND case: states, freq offsets, rf scalings
  // for STEP case: states, steps, rf scalings
  std::vector<std::string> chs = rf_->get_channels_str();

  if (str_opt == "step") {
    dim2 = "pulse steps\n";
    dim2 += "interval: " + boost::lexical_cast<std::string>(dt) + " s\n";
    comp_dist = cube(obsrv_state.size(), nsteps,
                     rf_scaling.size());  // states, steps, rf scalings
    sp_cx_mat L;
    sp_cx_mat L0 = superop_.L0 + ci * superop_.R;
    sp_cx_vec *forward = new sp_cx_vec[nsteps + 1];
    forward[0] = init_state;

    for (int q = 0; q < rf_scaling.size();
         q++) {  // for each rf scaling factor, do
      double kx = 1 + rf_scaling[q], ky = 1 + rf_scaling[q];
      sp_cx_vec rho = forward[0];

      for (size_t i = 0; i < nsteps; i++) {
        L = L0;
        for (size_t j = 0; j < nchannels; j++)
          L += update_rf_ham(x.data(), i, j, chs[j], nchannels, kx, ky);
        forward[i + 1] = ssl::spinsys::step(rho, L, dt);
        rho = forward[i + 1];

        for (size_t k = 0; k < obsrv_state.size(); k++) {
          sp_cx_vec compo = obsrv_state[k];
          comp_dist(k, i, q) = transfer_fidelity(rho, compo);
        }
      }
    }
  } else if (str_opt == "broadband") {
    dim2 = "freq offsets\n";
    int n = superop_.nominal_offset.size();
    dim2 += boost::lexical_cast<std::string>(superop_.nominal_offset[0]) + ":" +
            boost::lexical_cast<std::string>(superop_.nominal_offset[n - 1]) + " " +
            boost::lexical_cast<std::string>(n) + " Hz\n";

    comp_dist = cube(obsrv_state.size(), superop_.L0s.size(),
                     rf_scaling.size());  // states, freq offsets, rf scalings

    omp_set_num_threads(omp_core_num);

    std::vector<sp_cx_vec *> forward_trajs_parfor =
        std::vector<sp_cx_vec *>(omp_core_num);
    for (int i = 0; i < omp_core_num; i++) {
      sp_cx_vec *forward = new sp_cx_vec[nsteps + 1];
      forward[0] = init_state;
      forward_trajs_parfor[i] = forward;
    }

#pragma omp parallel for
    for (int p = 0; p < superop_.L0s.size(); p++) {
      int id = omp_get_thread_num();

      for (int q = 0; q < rf_scaling.size();
           q++) {  // for each rf scaling factor, do
        double kx = 1 + rf_scaling[q], ky = 1 + rf_scaling[q];
        sp_cx_mat L;
        sp_cx_mat L0 = superop_.L0s[p] + ci * superop_.R;
        sp_cx_vec rho = forward_trajs_parfor[id][0];

        for (size_t i = 0; i < nsteps; i++) {
          L = L0;
          for (size_t j = 0; j < nchannels; j++)
            L += update_rf_ham(x.data(), i, j, chs[j], nchannels, kx, ky);
          forward_trajs_parfor[id][i + 1] = ssl::spinsys::step(rho, L, dt);
          rho = forward_trajs_parfor[id][i + 1];
        }

        for (size_t k = 0; k < obsrv_state.size(); k++) {
          sp_cx_vec compo = obsrv_state[k];
          // transfer_wave(p, k) = transfer_fidelity(rho, compo);  // /
          // transfer_fidelity(compo, compo);
          comp_dist(k, p, q) = transfer_fidelity(rho, compo);
        }
      }
    }
  }

  std::string time_s = sys_time();
  H5File file("proj_" + time_s + ".h5", H5F_ACC_TRUNC);
  ssl::utility::h5write(file, nullptr, "projection", comp_dist);
  ssl::utility::h5write(file, nullptr, "dim1", dim1);
  ssl::utility::h5write(file, nullptr, "dim2", dim2);
  ssl::utility::h5write(file, nullptr, "dim3", dim3);
  file.close();

  if (rf_scaling.size() == 1) {  // no rf inhomogeneity.
    Eigen::Tensor<double, 2> sub =
        comp_dist.chip(0, 2);  // rf scaling at dim 2.
    Eigen::Map<mat> m(sub.data(), sub.dimension(0), sub.dimension(1));
    mat grid = m.matrix();

    sol::table lines;

    lines = g_lua->create_table();
    for (int i = 0; i < grid.rows(); i++) {  // each state.
      vec line = grid.row(i).transpose();
      lines.add(line);
    }

    std::string fig_spec;
    vec xval;
    if (str_opt == "step") {
      xval = vec::LinSpaced(grid.cols(), 0, user_rf.width_in_ms()); // transfer trajectories of basis operators
	  std::string s = retrieve_table_str("init_state", t);
      fig_spec = "title<initial state: " + s + "> ";
      fig_spec +=
          "xlabel<pulse " //initial state I_{1y}
          "duration "
          "/ ms> ylabel<magnetization>";
      fig_spec +=
          "xrange<0:" + boost::lexical_cast<std::string>(user_rf.width_in_ms()) +
          "> ";
    } else if (str_opt == "broadband") {
      int n = superop_.nominal_offset.size();
      xval = vec::LinSpaced(grid.cols(), superop_.nominal_offset[0],
                            superop_.nominal_offset[n - 1]);
      xval *= 1e-3;
      fig_spec = "xlabel<frequency offset / kHz> ylabel<magnetization>"; // title<scan 1>
      fig_spec += "xrange<" + boost::lexical_cast<std::string>(xval[0]) + ":" +
                  boost::lexical_cast<std::string>(xval[xval.size() - 1]) + "> ";
    }
    if (expr.size() > 5)
        fig_spec += " gnuplot<set key outside>";
      //fig_spec += " gnuplot<set label 'Scan 2' at graph 0.5,0.5 center font 'Arial,26'\n set ytics 0.2\n set key horizontal above>";
	  //fig_spec += " gnuplot<set label 'Scan 2' at graph 0.5,0.5 center font 'Arial,26'\n set ytics 0.2\n unset key>";
	  //fig_spec += " gnuplot<set ytics 0.2\n unset key>";

    fig_spec += " lw<7>";
    fig_spec += " color<YiZhang16,16>";
    std::string lege;
    for (size_t i = 0; i < expr.size(); i++) lege += expr[i] + ";";
    fig_spec += " legend<" + lege + ">";
    plot(fig_spec, line_series(xval, lines));
    return;
  }

  // rf scaling case.

  Eigen::Tensor<double, 2> sub =
      comp_dist.chip(0, 0);  // observed states at dim 0, by default, plot the
                             // map for first state.
  std::string state_name = expr[0];
  Eigen::Map<mat> m(sub.data(), sub.dimension(0), sub.dimension(1));
  mat grid = m.matrix();

  vec2 xrange, yrange;
  yrange[0] = superop_.nominal_offset[0];
  yrange[1] = superop_.nominal_offset[superop_.nominal_offset.size() - 1];
  yrange *= 1e-3;  // into kHz.
  xrange[0] = rf_scaling[0];
  xrange[1] = rf_scaling[rf_scaling.size() - 1];
  xrange *= 100;  // into %

  utility::map gnu(grid, "style<3d>");
  gnu.xrange = xrange;
  gnu.yrange = yrange;
  std::string fig_spec =
      "'ylabel<freq offset / kHz> xlabel<rf inhomogeneity / %> color<Spectral> "
      "gnuplot<set xlabel offset -1,-1; set ylabel offset -2,-2; set palette "
      "negative; set zrange [0.8:1]; set cbrange [0.8:1]> ";

  (*g_lua)["_comp_dist"] = gnu;
  g_lua->script("plot(" + fig_spec + " title<magnetization map [" + state_name +
                "]>', _comp_dist)");
}

} /* namespace oc */
} /* namespace ssl */


/* Software License Agreement (BSD License)
*
* Copyright (c) 2014, Ross Linscott (rossklin@gmail.com)
*
* 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.
*
*     The names of its contributors may not 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.
*/

#if defined(NDEBUG)
#undef NDEBUG
#endif

#include <cstdlib>
#include <cmath>
#include <cstring>

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#ifdef STANDALONE
#include <RInside.h>
#endif

#include "../inst/include/SimpleSDESampler.h"

lpoly_evaluator::lpoly_evaluator(NumericMatrix cm, NumericMatrix trm) : terms(trm), coef_matrix(as_ublas_matrix(cm)){}

void lpoly_evaluator::set_h(double v){
  h = v;
}

NumericMatrix lpoly_evaluator::build(NumericMatrix data){
  double value;
  NumericMatrix result(data.nrow(), terms.nrow());
  int i,j,k;

  for (i = 0; i < data.nrow(); i++){
    for (j = 0; j < terms.nrow(); j++){
      value = 1;
      for (k = 0; k < terms.ncol(); k++){
	value *= pow(data(i, k), terms(j,k));
      }
      result(i,j) = value;
    }

  }
  return result;
}

// compute evaluation function
void lpoly_evaluator::operator()(uvector &query, uvector &out, double t) {
  if (query.size() != terms.ncol()){
    Rcpp::Rcout << "lpoly_evaluator::(): dimension mismatch: " << query.size() << " != " << terms.ncol() << endl;
    exit(-1);
  }

  NumericMatrix state(1, query.size(), query.begin());
  NumericMatrix state_model = build(state);
  uvector state_vector = as_ublas_vector(NumericVector(state_model.begin(), state_model.end()));

  uvector dpart = prod(coef_matrix, state_vector);
  out = dpart + (1 / sqrt(h)) * noise(t);
}

uvector lpoly_evaluator::noise(double t){
  int idx = t / h + 0.5;
  int dim = noise_precache.size2();
  uvector x(dim);

  if (idx >= noise_precache.size1()){
    Rcpp::Rcout << "Invalid time index: " << idx << endl;
    exit(1);
  }

  memcpy(&x[0], &noise_precache(idx,0), dim * sizeof(double));
  return x;
}

void lpoly_evaluator::set_noise(umatrix m){
  noise_precache = m;
}

void lpoly_evaluator::build_noise(int n, int d, double sigma){
  int i;
  int size = n * d;
  double *p;

  boost::mt19937 gener;
  boost::normal_distribution<> normal(0,sigma*sigma);
  boost::variate_generator<boost::mt19937&,boost::normal_distribution<> > rng(gener, normal);

  boost::posix_time::time_duration diff_time = boost::posix_time::microsec_clock::local_time() - boost::posix_time::second_clock::local_time();
  unsigned int the_seed = diff_time.total_microseconds();

  rng.engine().seed(the_seed);
  rng.distribution().reset();

  noise_precache.resize(n, d);
  p = &noise_precache(0,0);

  for (i = 0; i < size; i++){
    p[i] = rng();
  }
}

uvector lpoly_evaluator::evalfun(uvector query){
  NumericMatrix state(1, query.size(), query.begin());
  NumericMatrix state_model = build(state);
  uvector state_vector = as_ublas_vector(NumericVector(state_model.begin(), state_model.end()));
  return prod(coef_matrix, state_vector);
}

lpoly_jacobian::lpoly_jacobian(NumericMatrix cm, NumericMatrix trm) : terms(trm), coef_matrix(as_ublas_matrix(cm)){}

// compute jacobian
void lpoly_jacobian::operator()(uvector &q, umatrix &out, double t){
  int i,j,k;
  umatrix term_derivs = umatrix(terms.nrow(), terms.ncol());
  double x;

  for (i = 0; i < terms.nrow(); i++){
    for (j = 0; j < terms.ncol(); j++){
      // compute derivative of term i with respect to variable j
      term_derivs(i,j) = 1;
      for (k = 0; k < terms.ncol(); k++){
	// do something about corny case: q = 0, terms(i,k) = 0, j = k
	// which causes NAN but should actually be zero
	term_derivs(i,j) *= pow(terms(i, k), j == k) * pow(q(k), terms(i,k) - (j == k));
      }
    }
  }

  out = prod(coef_matrix, term_derivs);
}


#include <iostream>
#include <fstream>

#include <boost/algorithm/hex.hpp>
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>

#include <nil/crypto3/zk/snark/schemes/ppzksnark/r1cs_gg_ppzksnark.hpp>
#include <nil/crypto3/zk/snark/schemes/ppzksnark/r1cs_gg_ppzksnark/marshalling.hpp>

#include "./types.h"

std::string convert_byteblob_to_hex_string(std::vector<std::uint8_t> blob) {
    // convert byte_blob to hex string and print it to output
    std::string hex;
    hex.reserve(blob.size() * 2);
    boost::algorithm::hex(blob.begin(), blob.end(), back_inserter(hex));
    return hex;
}

void save_byteblob(std::vector<std::uint8_t> byteblob, boost::filesystem::path fname) {
    boost::filesystem::ofstream out(fname);
    for (const auto &v : byteblob) {
        out << v;
    }
    out.close();
}

std::vector<std::uint8_t> load_byteblob(boost::filesystem::path fname) {
    boost::filesystem::ifstream stream(fname, std::ios::in | std::ios::binary);
    std::vector<std::uint8_t> contents((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
    if (contents.size() == 0) {
        throw std::ios_base::failure("Empty file");
    }
    return contents;
}

// proving key

void save_proving_key(scheme_type::proving_key_type pk, boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = nil::marshalling::verifier_input_serializer_tvm<scheme_type>::process(pk);
    save_byteblob(byteblob, fname);
}

scheme_type::proving_key_type load_proving_key(boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = load_byteblob(fname);
    nil::marshalling::status_type processingStatus = nil::marshalling::status_type::success;
    return nil::marshalling::verifier_input_deserializer_tvm<scheme_type>::proving_key_process(
        byteblob.cbegin(),
        byteblob.cend(),
        processingStatus);
}

// verification key

void save_verification_key(scheme_type::verification_key_type vk, boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = nil::marshalling::verifier_input_serializer_tvm<scheme_type>::process(vk);
    save_byteblob(byteblob, fname);
}

scheme_type::verification_key_type load_verification_key(boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = load_byteblob(fname);
    nil::marshalling::status_type processingStatus = nil::marshalling::status_type::success;
    return nil::marshalling::verifier_input_deserializer_tvm<scheme_type>::verification_key_process(
        byteblob.cbegin(),
        byteblob.cend(),
        processingStatus);
}

// proof

void save_proof(scheme_type::proof_type proof, boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = nil::marshalling::verifier_input_serializer_tvm<scheme_type>::process(proof);
    save_byteblob(byteblob, fname);
}

scheme_type::proof_type load_proof(boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = load_byteblob(fname);
    nil::marshalling::status_type processingStatus = nil::marshalling::status_type::success;
    return nil::marshalling::verifier_input_deserializer_tvm<scheme_type>::proof_process(
        byteblob.cbegin(),
        byteblob.cend(),
        processingStatus);
}

//  primary input

void save_primary_input(zk::snark::r1cs_primary_input<field_type> primary_input, boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = nil::marshalling::verifier_input_serializer_tvm<scheme_type>::process(primary_input);
    save_byteblob(byteblob, fname);
}

zk::snark::r1cs_primary_input<field_type> load_primary_input(boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = load_byteblob(fname);
    nil::marshalling::status_type processingStatus = nil::marshalling::status_type::success;
    return nil::marshalling::verifier_input_deserializer_tvm<scheme_type>::primary_input_process(
        byteblob.cbegin(),
        byteblob.cend(),
        processingStatus);
}