#include <iostream>
#include <exception>
#include <vector>
#include <math.h>
#include <unistd.h> //~ UNIX standard w glibc
#include <boost/timer/timer.hpp>

#include <poplar/DeviceManager.hpp>
#include <poplar/Graph.hpp>
#include <poplar/Tensor.hpp>

#include <popops/codelets.hpp>

#include <poplin/codelets.hpp>

//~ the -using- lines have moved to graveyard
//~ resurrect once you are clear on what is being abbreviated

int N = 5;               // Number of planes
float d = 1.0;           // Distance between planes
float sigma = 10E-2;     // Resolution of planes
float z = 0.1;           // Thickness of absorber
float x0 = 0.01;         // Radiation length of absorber
float theta0 = 10E-3;    // Multiple scatter uncertainty

poplar::Device connectToIPU()
{
    bool success = false;
    poplar::DeviceManager manager = poplar::DeviceManager::createDeviceManager();
    poplar::Device dev;
    auto devices = manager.getDevices(poplar::TargetType::IPU, 1);

    for (auto &device : devices)
    {
        dev = std::move(device);
        std::cout << "Trying to attach";
        success = dev.attach();
        if (success)
        {
            std::cout << " - ok, attached to IPU " << dev.getId() << "\n";
            break;
        }
        else
        {
            std::cout << " - hmm, issue attaching to IPU " << dev.getId() << "\n";
        }
    }

    if (!success)
    {
        throw std::runtime_error("Cannot connect to IPU.");
    }

    return dev;
}

void mapInputsToTiles(poplar::Graph &graph,
                      unsigned int n_inputs,
                      unsigned int batch_size,
                      std::vector<poplar::Tensor> &inputs,
                      std::vector<poplar::Tensor> &inputs_batch)
{
    inputs.resize(n_inputs);       //~ this reduces the input dependencies
    inputs_batch.resize(n_inputs); //~ to being JUST n_inputs

    for (int i = 0; i < n_inputs; i++)
    {                             //? why do strings go in to these tensors?
        inputs[i] = graph.addVariable(poplar::FLOAT,
                                      {5, 2},
                                      "x_in" + std::to_string(i));
        inputs_batch[i] = graph.addVariable(poplar::FLOAT,
                                            {batch_size, 5 * 2},
                                            "x_in_batch" + std::to_string(i));
        graph.setTileMapping(inputs[i], i);
        graph.setTileMapping(inputs_batch[i], i);
    }

    std::cout << n_inputs << " inputs of "
              << batch_size << " batches mapped to tiles." << "\n";
}

void addProgramCodelets(poplar::Graph &graph)
{
    popops::addCodelets(graph);
    poplin::addCodelets(graph);
    graph.addCodelets("matrixInverseVertex.cpp");
    graph.addCodelets("matrixProductVertex.cpp");
    graph.addCodelets("scaledAddVertex.cpp");
    graph.addCodelets("packHitsVertex.cpp");

    std::cout << "Codelets added." << "\n";
}

void sendInStreamsToDevice(poplar::Graph &graph,
                          unsigned int batch_size,
                          std::vector<poplar::Tensor> &inputs_batch,
                          std::vector<poplar::DataStream> &inStreams,
                          poplar::program::Sequence &preProg)
{
    unsigned int n_inputs = inputs_batch.size();
    inStreams.resize(n_inputs);
    for (unsigned int i = 0; i < n_inputs; i++)
        {
            inStreams[i] = graph.addHostToDeviceFIFO("inStream" + std::to_string(i),
                                                    poplar::FLOAT,
                                                    5 * 2 * batch_size);
            preProg.add(poplar::program::Copy(inStreams[i], inputs_batch[i]));
        }
    std::cout << "Datastreams sent to IPU." << "\n";
}

//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~//~

int main()
{

    //~ 53
    poplar::Device dev = connectToIPU();

    //~ 61-72
    //~ 59-60
    poplar::Graph graph(dev);
    unsigned int n_inputs = 1;
    unsigned int batch_size = 1;
    std::vector<poplar::Tensor> inputs,
                                inputs_batch;
    mapInputsToTiles(graph,
                     n_inputs,
                     batch_size,
                     inputs,
                     inputs_batch);

    //~ 75-80
    addProgramCodelets(graph);

    //§ preProg §//§//§//§//§//§//§//§//§//§//§//§//§
    //~ 111-117
    std::vector<poplar::DataStream> inStreams(n_inputs);
    poplar::program::Sequence preProg;
    sendInStreamsToDevice(graph,
                          batch_size,
                          inputs_batch,
                          inStreams,
                          preProg);

    //§ prog declarations §//§//§//§//§//§//§//§//§//§
    std::vector<poplar::Tensor> qs(n_inputs),
                                hs(n_inputs),
                                gs(n_inputs),
                                fs(n_inputs),
                                d(n_inputs),
                                dInit(n_inputs),
                                dSkip(n_inputs),
                                scatterInto(n_inputs),
                                loop(n_inputs),
                                zero(n_inputs),
                                one(n_inputs),
                                loop_batch(n_inputs),
                                hitThisLoop(n_inputs),
                                //~ projection tensors
                                p_proj_all(n_inputs),
                                C_proj_all(n_inputs),
                                //~ kalman filter tensors
                                p_filt_all(n_inputs),
                                C_filt_all(n_inputs),
                                //~ backward smoothing tensors
                                p_smooth(n_inputs),
                                C_smooth(n_inputs),
                                //~ flattened covariance tensor
                                covs(n_inputs),
                                covsFlat(n_inputs);

    //~ these xFlat tensors used to be declared in loop
    //~ F is the transfer matrix
    poplar::Tensor fFlat =
        graph.addConstant<float>(poplar::FLOAT, {16, 1},
                                {1., 1., 0., 0.,
                                 0., 1., 0., 0.,
                                 0., 0., 1., 1.,
                                 0., 0., 0., 1.});

    //~ G is the noise matrix
    poplar::Tensor gFlat =
        graph.addConstant<float>(poplar::FLOAT, {16, 1},
                                {float(1.0)/(sigma * sigma), 0., 0., 0.,
                                 0, 0., 0., 0.,
                                 0, 0., float(1.0)/(sigma * sigma), 0.,
                                 0, 0., 0., 0.});

    //~ H the relation between the measurement m and the state p
    poplar::Tensor hFlat =
        graph.addConstant<float>(poplar::FLOAT, {16, 1},
                                {1., 0., 0., 0.,
                                 0., 0., 0., 0.,
                                 0., 0., 1., 0.,
                                 0., 0., 0., 0.});

    //~ Q is the random error matrix, ie the scatter (?why)
    poplar::Tensor qFlat =
        graph.addConstant<float>(poplar::FLOAT,
                                {16, 1},
                                {0.});

    //~ 120-217
    //§ prog §//§//§//§//§//§//§//§//§//§//§//§//§
    poplar::program::Sequence prog;
    for (unsigned int i = 0; i < n_inputs; i++)
    {
        std::string iStr = std::to_string(i);
        //~ what plane of KF I think
        //? why do strings go into these tensors?
        loop[i] = graph.addVariable(poplar::INT, {1}, "loop");
        graph.setTileMapping(loop[i], i);

        //~ btw nothing to do with particle scattering
        //? why do we have <int> and poplar::INT?
        scatterInto[i] = graph.addConstant<int>(poplar::INT, {1}, {0});
        graph.setTileMapping(scatterInto[i], i);

        //? a tensor of zeros?
        zero[i] = graph.addConstant<int>(poplar::INT, {1}, {0});
        graph.setTileMapping(zero[i], i);

        one[i] = graph.addConstant<int>(poplar::INT, {1}, {1});
        graph.setTileMapping(one[i], i);

        prog.add(poplar::program::Copy(inputs_batch[i].slice(0, 1, 0).reshape({5, 2}),
                      inputs[i]));

        d[i] =
            graph.addVariable(poplar::FLOAT, {1, 1}, "d" + iStr);
        dInit[i] =
            graph.addConstant<float>(poplar::FLOAT, {1, 1}, {1.});
        dSkip[i] =
            graph.addConstant<float>(poplar::FLOAT, {1, 1}, {2.});

        prog.add(poplar::program::Copy(dInit[i], d[i]));

        covs[i] =
            graph.addVariable(poplar::FLOAT, {4, 4}, "cov" + iStr);

        //~ cov is the initial parameters (?) is it? why do I think that?
        //~ also - what do you mean by "parameters"?
        covsFlat[i] =
            graph.addConstant<float>(poplar::FLOAT, {16, 1},
                                    {sigma * sigma, 0., 0., 0.,
                                     0., M_PI, 0., 0.,
                                     0., 0., sigma * sigma, 0.,
                                     0., 0., 0., M_PI});

        //~ prog.adds can go to function?
        prog.add(poplar::program::Copy(covsFlat[i].reshape({4, 4}), covs[i]));

        //~ graph add are just building the graph (to func?/class)
        p_proj_all[i] = graph.addVariable(poplar::FLOAT,
                                         {5, 4, 1},
                                         "p_proj_all" + iStr);

        C_proj_all[i] = graph.addVariable(poplar::FLOAT,
                                         {5, 4, 4},
                                         "C_proj_all" + iStr);

        p_filt_all[i] = graph.addVariable(poplar::FLOAT,
                                         {5, 4, 1},
                                         "p_filt_all" + iStr);

        C_filt_all[i] = graph.addVariable(poplar::FLOAT,
                                         {5, 4, 4},
                                         "C_filt_all" + iStr);

        p_smooth[i] = graph.addVariable(poplar::FLOAT,
                                       {4, 1},
                                       "p_smooth" + iStr);

        C_smooth[i] = graph.addVariable(poplar::FLOAT,
                                       {4, 4},
                                       "C_smooth" + iStr);

        graph.setTileMapping(p_proj_all[i], i);
        graph.setTileMapping(C_proj_all[i], i);
        graph.setTileMapping(p_filt_all[i], i);
        graph.setTileMapping(C_filt_all[i], i);

        graph.setTileMapping(p_smooth[i], i);
        graph.setTileMapping(C_smooth[i], i);

        graph.setTileMapping(covs[i], i);
        graph.setTileMapping(covsFlat[i], i);

        graph.setTileMapping(d[i], i);
        graph.setTileMapping(dInit[i], i);
        graph.setTileMapping(dSkip[i], i);

        fs[i] = fFlat.reshape({4, 4});
        graph.setTileMapping(fs[i], i);
        graph.setTileMapping(fFlat, i);
        gs[i] = gFlat.reshape({4, 4});
        graph.setTileMapping(gs[i], i);
        graph.setTileMapping(gFlat, i);
        hs[i] = hFlat.reshape({4, 4});
        graph.setTileMapping(hs[i], i);
        graph.setTileMapping(hFlat, i);
        qs[i] = qFlat.reshape({4, 4});
        graph.setTileMapping(qs[i], i);
        graph.setTileMapping(qFlat, i);

    } //~ end for (unsigned int i = 0; i < n_inputs; i++)

    return 0;
}


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2019 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Alexander Sokolov <asokolov@nil.foundation>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_HASH_SPONGE_CONSTRUCTION_HPP
#define CRYPTO3_HASH_SPONGE_CONSTRUCTION_HPP

#include <boost/crypto3/detail/static_digest.hpp>
#include <boost/crypto3/detail/pack.hpp>

#include <boost/crypto3/hash/detail/nop_finalizer.hpp>

namespace boost {
    namespace crypto3 {
        namespace hashes {
            /*!
             * @brief
             * @tparam DigestEndian
             * @tparam DigestBits
             * @tparam IV
             * @tparam Compressor
             * @tparam Finalizer
             *
             * The Sponge construction builds a block hashes from a
             * one-way compressor.  As this version operated on the block
             * level, it doesn't contain any padding or other strengthening.
             * For a Wide Pipe construction, use a digest that will
             * truncate the internal state.
             */
            template<typename Params,
                     typename IV,
                     typename Compressor,
                     typename Padding,
                     typename Finalizer = detail::nop_finalizer>
            class sponge_construction {
            public:
                typedef IV iv_generator;
                typedef Compressor compressor_functor;
                typedef Padding padding_functor;
                typedef Finalizer finalizer_functor;

                typedef typename Params::digest_endian endian_type;

                constexpr static const std::size_t word_bits = compressor_functor::word_bits;
                typedef typename compressor_functor::word_type word_type;

                constexpr static const std::size_t state_bits = compressor_functor::state_bits;
                constexpr static const std::size_t state_words = compressor_functor::state_words;
                typedef typename compressor_functor::state_type state_type;

                constexpr static const std::size_t block_bits = compressor_functor::block_bits;
                constexpr static const std::size_t block_words = compressor_functor::block_words;
                typedef typename compressor_functor::block_type block_type;

                constexpr static const std::size_t digest_bits = Params::digest_bits;
                constexpr static const std::size_t digest_bytes = digest_bits / octet_bits;
                constexpr static const std::size_t digest_words = digest_bits / word_bits;
                typedef static_digest<digest_bits> digest_type;

                template<typename Integer = std::size_t>
                inline sponge_construction &process_block(const block_type &block, Integer seen = Integer()) {
                    compressor_functor::process_block(state_, block);
                    return *this;
                }

                inline digest_type digest(const block_type &block = block_type(),
                                          std::size_t total_seen = std::size_t()) {
                    using namespace boost::crypto3::detail;

                    block_type b = block;
                    std::size_t block_seen = total_seen % block_bits;
                    // Process block if it is full
                    if (total_seen && !block_seen)
                        process_block(b);

                    std::size_t copy_seen = block_seen;
                    // Pad last message block
                    padding_functor padding;
                    padding(b, block_seen);
                    process_block(b);

                    // Process additional block if not all bits were padded
                    if (!padding.is_last_block()) {
                        std::fill(b.begin(), b.end(), 0);
                        padding.process_last(b, copy_seen);
                        process_block(b);
                    }

                    // Apply finalizer
                    finalizer_functor()(state_);

                    // Convert digest to byte representation
                    std::array<octet_type, state_bits / octet_bits> d_full;
                    pack_from<endian_type, word_bits, octet_bits>(state_.begin(), state_.end(), d_full.begin());

                    digest_type d;
                    std::copy(d_full.begin(), d_full.begin() + digest_bytes, d.begin());

                    return d;
                }

                sponge_construction() {
                    reset();
                }

                void reset(state_type const &s) {
                    state_ = s;
                }

                void reset() {
                    iv_generator iv;
                    reset(iv());
                }

                state_type const &state() const {
                    return state_;
                }

            private:
                state_type state_;
            };

        }    // namespace hashes
    }        // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_HASH_SPONGE_CONSTRUCTION_HPP


#ifndef OFFPOLSETACFITTED_HPP
#define OFFPOLSETACFITTED_HPP

#include <vector>
#include <string>
#include <boost/serialization/list.hpp>
#include <boost/serialization/set.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/filesystem.hpp>
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>

#include "arch/AACAgent.hpp"
#include "bib/Seed.hpp"
#include "bib/Utils.hpp"
#include <bib/MetropolisHasting.hpp>
#include <bib/XMLEngine.hpp>
#include "MLP.hpp"
#include "LinMLP.hpp"
#include "kde.hpp"
#include "kdtree++/kdtree.hpp"

#define DOUBLE_COMPARE_PRECISION 1e-9

typedef struct _sample {
  std::vector<double> s;
  std::vector<double> pure_a;
  std::vector<double> a;
  std::vector<double> next_s;
  double r;
  bool goal_reached;
  double p0;

  friend class boost::serialization::access;
  template <typename Archive>
  void serialize(Archive& ar, const unsigned int) {
    ar& BOOST_SERIALIZATION_NVP(s);
    ar& BOOST_SERIALIZATION_NVP(pure_a);
    ar& BOOST_SERIALIZATION_NVP(a);
    ar& BOOST_SERIALIZATION_NVP(next_s);
    ar& BOOST_SERIALIZATION_NVP(r);
    ar& BOOST_SERIALIZATION_NVP(goal_reached);
  }

  //Used to store all sample into a tree, might be stochastic
  //only pure_a is negligate
  bool operator< (const _sample& b) const {
    for (uint i = 0; i < s.size(); i++) {
      if(fabs(s[i] - b.s[i])>=DOUBLE_COMPARE_PRECISION)
        return s[i] < b.s[i];
    }

    for (uint i = 0; i < a.size(); i++) {
      if(fabs(a[i] - b.a[i])>=DOUBLE_COMPARE_PRECISION)
        return a[i] < b.a[i];
    }

    for (uint i = 0; i < next_s.size(); i++) {
      if(fabs(next_s[i] - b.next_s[i])>=DOUBLE_COMPARE_PRECISION)
        return next_s[i] < b.next_s[i];
    }

    if(fabs(r - b.r)>=DOUBLE_COMPARE_PRECISION)
        return r < b.r;

    return goal_reached < b.goal_reached;
  }

  typedef double value_type;

  inline double operator[](size_t const N) const{
        return s[N];
  }

  bool same_state(const _sample& b) const{
    for (uint i = 0; i < s.size(); i++) {
      if(fabs(s[i] - b.s[i])>=DOUBLE_COMPARE_PRECISION)
        return false;
    }

    return true;
  }

} sample;

class OffPolSetACFitted : public arch::AACAgent<MLP, arch::AgentProgOptions> {
 public:
  typedef MLP PolicyImpl;

  OffPolSetACFitted(unsigned int _nb_motors, unsigned int _nb_sensors)
    : arch::AACAgent<MLP, arch::AgentProgOptions>(_nb_motors), nb_sensors(_nb_sensors) {

  }

  virtual ~OffPolSetACFitted() {
    delete kdtree_s;

    delete vnn;
    delete ann;
  }

  const std::vector<double>& _run(double reward, const std::vector<double>& sensors,
                                 bool learning, bool goal_reached, bool) {

    vector<double>* next_action = ann->computeOut(sensors);

    if (last_action.get() != nullptr && learning){
      double p0 = 1.f;
      for(uint i=0;i < nb_motors;i++)
        p0 *= exp(-(last_pure_action->at(i)-last_action->at(i))*(last_pure_action->at(i)-last_action->at(i))/(2.f*noise*noise));

      trajectory.insert({last_state, *last_pure_action, *last_action, sensors, reward, goal_reached, p0});
      last_trajectory.insert( {last_state, *last_pure_action, *last_action, sensors, reward, goal_reached, p0});
      proba_s.add_data(last_state);

      sample sa = {last_state, *last_pure_action, *last_action, sensors, reward, goal_reached, p0};

      kdtree_s->insert(sa);

      std::vector<double> psa;
      std::vector<double> psas;
      mergeSA(psa, last_state, *last_action);
      mergeSAS(psas, last_state, *last_action, sensors);

      proba_sa.add_data(psa);
      proba_sas.add_data(psas);
    }

    last_pure_action.reset(new vector<double>(*next_action));
    if(learning) {
      if(gaussian_policy){
        vector<double>* randomized_action = bib::Proba<double>::multidimentionnalGaussianWReject(*next_action, noise);
        delete next_action;
        next_action = randomized_action;
      } else if(bib::Utils::rand01() < noise){ //e-greedy
        for (uint i = 0; i < next_action->size(); i++)
          next_action->at(i) = bib::Utils::randin(-1.f, 1.f);
      }
    }
    last_action.reset(next_action);

    last_state.clear();
    for (uint i = 0; i < sensors.size(); i++)
      last_state.push_back(sensors[i]);

    return *next_action;
  }

  void _unique_invoke(boost::property_tree::ptree* pt, boost::program_options::variables_map*) override {
    hidden_unit_v           = pt->get<int>("agent.hidden_unit_v");
    hidden_unit_a           = pt->get<int>("agent.hidden_unit_a");
    noise                   = pt->get<double>("agent.noise");
    gaussian_policy         = pt->get<bool>("agent.gaussian_policy");
    lecun_activation        = pt->get<bool>("agent.lecun_activation");
    determinist_vnn_update  = pt->get<bool>("agent.determinist_vnn_update");
    vnn_from_scratch        = pt->get<bool>("agent.vnn_from_scratch");
    change_policy_each      = pt->get<uint>("agent.change_policy_each");
    strategy_w              = pt->get<uint>("agent.strategy_w");
    current_loaded_policy   = pt->get<uint>("agent.index_starting_loaded_policy");
    converge_precision      = pt->get<double>("agent.converge_precision");
    number_fitted_iteration = pt->get<uint>("agent.number_fitted_iteration");

    if(hidden_unit_v == 0)
      vnn = new LinMLP(nb_sensors + nb_motors , 1, 0.0, lecun_activation);
    else
      vnn = new MLP(nb_sensors + nb_motors, hidden_unit_v, nb_sensors, 0.0, lecun_activation);

    if(hidden_unit_a == 0)
      ann = new LinMLP(nb_sensors , nb_motors, 0.0, lecun_activation);
    else
      ann = new MLP(nb_sensors, hidden_unit_a, nb_motors, lecun_activation);

    kdtree_s = new kdtree_sample(nb_sensors);
  }

  bool learning;
  void _start_episode(const std::vector<double>& sensors, bool _learning) override {
    learning = _learning;
    last_state.clear();
    for (uint i = 0; i < sensors.size(); i++)
      last_state.push_back(sensors[i]);

    last_action = nullptr;
    last_pure_action = nullptr;

    //trajectory.clear();
    last_trajectory.clear();

    fann_reset_MSE(vnn->getNeuralNet());

    if(episode == 0 || episode % change_policy_each == 0){
      if (! boost::filesystem::exists( "polset."+std::to_string(current_loaded_policy) ) ){
        LOG_ERROR("file doesn't exists "<< "polset."+std::to_string(current_loaded_policy));
        exit(1);
      }

      ann->load("polset."+std::to_string(current_loaded_policy));
      current_loaded_policy++;
      end_episode();
    }
  }

  void computePTheta(vector< sample >& vtraj, double *ptheta){
    uint i=0;
    for(auto it = vtraj.begin(); it != vtraj.end() ; ++it) {
      sample sm = *it;
      vector<double>* next_action = ann->computeOut(sm.s);

      double p0 = 1.f;
      for(uint i=0;i < nb_motors;i++)
        p0 *= exp(-(next_action->at(i)-sm.a[i])*(next_action->at(i)-sm.a[i])/(2.f*noise*noise));

      ptheta[i] = p0;
      i++;
      delete next_action;
    }
  }

  struct ParraVtoVNext {
    ParraVtoVNext(const std::vector<sample>& _vtraj, const OffPolSetACFitted* _ptr) : vtraj(_vtraj), ptr(_ptr) {
      data = fann_create_train(vtraj.size(), ptr->nb_sensors + ptr->nb_motors, 1);
      for (uint n = 0; n < vtraj.size(); n++) {
        sample sm = vtraj[n];
        for (uint i = 0; i < ptr->nb_sensors ; i++)
          data->input[n][i] = sm.s[i];
        for (uint i= ptr->nb_sensors ; i < ptr->nb_sensors + ptr->nb_motors; i++)
          data->input[n][i] = sm.a[i - ptr->nb_sensors];
      }
    }

    ~ParraVtoVNext() { //must be empty cause of tbb

    }

    void free() {
      fann_destroy_train(data);
    }

    void operator()(const tbb::blocked_range<size_t>& range) const {

      struct fann* local_nn = fann_copy(ptr->vnn->getNeuralNet());
      struct fann* local_pol = fann_copy(ptr->ann->getNeuralNet());

      for (size_t n = range.begin(); n < range.end(); n++) {
        sample sm = vtraj[n];

        double delta = sm.r;
        if (!sm.goal_reached) {
          std::vector<double> * next_action = MLP::computeOut(local_pol, sm.next_s);
          double nextQA = MLP::computeOutVF(local_nn, sm.next_s, *next_action);
          delta += ptr->gamma * nextQA;
          delete next_action;
        }

        data->output[n][0] = delta;
      }

      fann_destroy(local_nn);
      fann_destroy(local_pol);
    }

    struct fann_train_data* data;
    const std::vector<sample>& vtraj;
    const OffPolSetACFitted* ptr;
  };

    void update_critic(){
      if (trajectory.size() > 2) {

        std::vector<sample> *vtraj;
        double *importance_sample = nullptr;
        if(strategy_w == 0){//only on the last data
          vtraj = new std::vector<sample>(last_trajectory.size());
          std::copy(last_trajectory.begin(), last_trajectory.end(), vtraj->begin());
        } else if(strategy_w >= 1 && strategy_w <= 5){
          vtraj = new std::vector<sample>(trajectory.size());
          std::copy(trajectory.begin(), trajectory.end(), vtraj->begin());

          if(strategy_w > 1)//on every data
            importance_sample = new double [trajectory.size()];

          if(strategy_w == 2) {//on every data 1/p(s)
            uint i=0;
            for(auto it = vtraj->begin(); it != vtraj->end() ; ++it) {
              importance_sample[i] = 1.f / proba_s.pdf(it->s);
              i++;
            }
          } else if(strategy_w == 3) {//on every data 1/p(s) normalized
            uint i=0;
            double sum_ps = 0.00f;
            for(auto it = vtraj->begin(); it != vtraj->end() ; ++it) {
              sum_ps += 1.f / proba_s.pdf(it->s);
              i++;
            }

            i=0;
            for(auto it = vtraj->begin(); it != vtraj->end() ; ++it) {
              importance_sample[i] =  (1.f / proba_s.pdf(it->s)) / sum_ps;
              i++;
            }
          } else if(strategy_w >= 4 && strategy_w <= 5) {

            double * ptheta = new double [trajectory.size()];
            computePTheta(*vtraj, ptheta);

            if(strategy_w == 4){
              uint i=0;
              for(auto it = vtraj->begin(); it != vtraj->end() ; ++it) {
                importance_sample[i] = ptheta[i];
                i++;
              }
            } else {
              uint i=0;
              for(auto it = vtraj->begin(); it != vtraj->end() ; ++it) {
                importance_sample[i] = ptheta[i] / it->p0;
                i++;
              }
            }

            delete[] ptheta;

          } else if(strategy_w != 1) {
              LOG_ERROR("to be implemented");
              exit(1);
          }

        } else if(strategy_w != 0) {
          LOG_ERROR("to be implemented");
          exit(1);
        }

        ParraVtoVNext dq(*vtraj, this);

        auto iter = [&]() {
          tbb::parallel_for(tbb::blocked_range<size_t>(0, vtraj->size()), dq);

          if(vnn_from_scratch)
            fann_randomize_weights(vnn->getNeuralNet(), -0.025, 0.025);

          if(strategy_w <= 1)
            vnn->learn_stoch(dq.data, 30000, 0, converge_precision);
          else
            vnn->learn_stoch_lw(dq.data, importance_sample, 30000, 0, converge_precision);
        };

        auto eval = [&]() {
          return fann_get_MSE(vnn->getNeuralNet());
        };

        if(determinist_vnn_update)
              bib::Converger::determinist<>(iter, eval, number_fitted_iteration, converge_precision, 0);
        else {
          NN best_nn = nullptr;
          auto save_best = [&]() {
            if(best_nn != nullptr)
              fann_destroy(best_nn);
            best_nn = fann_copy(vnn->getNeuralNet());
          };

          bib::Converger::min_stochastic<>(iter, eval, save_best, 30, converge_precision, 0, 10);
          vnn->copy(best_nn);
          fann_destroy(best_nn);
        }

        dq.free();
        if(strategy_w != 0)
          delete[] importance_sample;
        delete vtraj;
      }
  }

  inline void mergeSA(std::vector<double>& AB, const std::vector<double>& A, const std::vector<double>& B) {
    AB.reserve( A.size() + B.size() ); // preallocate memory
    AB.insert( AB.end(), A.begin(), A.end() );
    AB.insert( AB.end(), B.begin(), B.end() );
  }

  inline void mergeSAS(std::vector<double>& AB, const std::vector<double>& A, const std::vector<double>& B, const std::vector<double>& C) {
    AB.reserve( A.size() + B.size() + C.size() ); // preallocate memory
    AB.insert( AB.end(), A.begin(), A.end() );
    AB.insert( AB.end(), B.begin(), B.end() );
    AB.insert( AB.end(), C.begin(), C.end() );
  }

  void end_episode() override {
    if(learning)
      update_critic();
  }

  void learn_V(std::map<std::vector<double>, double>& bvf) override {
    if((episode+1) % change_policy_each == 0){
      trajectory.clear();
      last_trajectory.clear();

      struct fann_train_data* data = fann_create_train(bvf.size(), nb_sensors + nb_motors, 1);

      auto it = bvf.cbegin();
      for (uint n = 0; n < bvf.size(); n++) {

        for (uint i = 0; i < nb_sensors + nb_motors ; i++){
            data->input[n][i] = it->first[i];
            LOG_FILE_NNL("vset.data."+std::to_string(current_loaded_policy-1), it->first[i] << " ");
        }

        data->output[n][0] = it->second;
        LOG_FILE_NNL("vset.data."+std::to_string(current_loaded_policy-1), it->second << "\n");

        ++it;
      }
      //clear all; close all ;X=load('vset.data.904');[xx,yy] = meshgrid (linspace (-pi,pi,300));griddata(X(:,1),X(:,3),X(:,end),xx,yy);
      bib::Logger::getInstance()->closeFile("vset.data."+std::to_string(current_loaded_policy-1));

      fann_randomize_weights(vnn->getNeuralNet(), -0.025, 0.025);
      vnn->learn_stoch(data, 10000, 200, 0.0000001, 200);

      fann_destroy_train(data);

      vnn->save("vset."+std::to_string(current_loaded_policy-1));
      LOG_DEBUG("vset."+std::to_string(current_loaded_policy-1) << " saved " << bvf.size() );
      bvf.clear();
    }
  }

  void end_instance(bool) override {
    episode++;
  }

  double criticEval(const std::vector<double>& perceptions, const std::vector<double>& actions) override {
    return vnn->computeOutVF(perceptions, actions);
  }

  arch::Policy<MLP>* getCopyCurrentPolicy() override {
    return new arch::Policy<MLP>(new MLP(*ann) , gaussian_policy ? arch::policy_type::GAUSSIAN : arch::policy_type::GREEDY, noise, decision_each);
  }

  void save(const std::string& path) override {
    ann->save(path+".actor");
    vnn->save(path+".critic");
    bib::XMLEngine::save<>(trajectory, "trajectory", "trajectory.data");
  }

  void load(const std::string& path) override {
    ann->load(path+".actor");
    vnn->load(path+".critic");
  }

 protected:
  void _display(std::ostream& out) const override {
    out << std::setw(12) << std::fixed << std::setprecision(10) << sum_weighted_reward << " " << std::setw(
          8) << std::fixed << std::setprecision(8) << vnn->error() << " " << noise << " " << trajectory.size() ;
  }

  void _dump(std::ostream& out) const override {
    out <<" " << std::setw(25) << std::fixed << std::setprecision(22) <<
        sum_weighted_reward << " " << std::setw(8) << std::fixed <<
        std::setprecision(8) << vnn->error() << " " << trajectory.size() ;
  }

 private:
  uint nb_sensors;

  uint episode = 0;
  uint current_loaded_policy = 0;
  uint change_policy_each;
  uint strategy_w;

  double noise, converge_precision;
  bool gaussian_policy, vnn_from_scratch, lecun_activation,
        determinist_vnn_update;
  uint hidden_unit_v;
  uint hidden_unit_a;
  uint number_fitted_iteration;

  std::shared_ptr<std::vector<double>> last_action;
  std::shared_ptr<std::vector<double>> last_pure_action;
  std::vector<double> last_state;

  std::set<sample> trajectory;
  std::set<sample> last_trajectory;
//     std::list<sample> trajectory;
  KDE proba_s;
  KDE proba_sa, proba_sas;

  struct L1_distance
  {
    typedef double distance_type;

    double operator() (const double& __a, const double& __b, const size_t) const
    {
      double d = fabs(__a - __b);
      return d;
    }
  };
  typedef KDTree::KDTree<sample, KDTree::_Bracket_accessor<sample>, L1_distance> kdtree_sample;
  kdtree_sample* kdtree_s;

  MLP* ann;
  MLP* vnn;
};

#endif


#include <iostream>
#include <vector>
#include <cassert>
#include <cstring>
#include <limits>
#include <mpi.h>
//#include <Eigen/Eigenvalues>

enum KernelType {CubicSpline = 0, WendlandC2 = 1, WendlandC4 = 2};

#include "particle_simulator.hpp"
#include "hdr_time.hpp"
#include "hdr_run.hpp"
#include "vector_x86.hpp"
#include "hdr_dimension.hpp"
#include "hdr_kernel.hpp"
#include "hdr_sph.hpp"
#include "hdr_hgas.hpp"
#include "hdr_bhns.hpp"

class SPHAnalysis : public HelmholtzGas {
public:
    SPHAnalysis() {
        this->id    = 0;
        this->istar = 0;
        this->mass  = 0.;
        this->pos   = 0.;
        this->vel   = 0.;
        this->uene  = 0.;
        this->alph  = 0.;
        this->alphu = 0.;
        this->ksr   = 0.;
        for(PS::S32 k = 0; k < NR::NumberOfNucleon; k++) {
            this->cmps[k] = 0.;
        }
    }

    void readAscii(FILE * fp) {
        fscanf(fp, "%lld%lld%lf", &this->id, &this->istar, &this->mass);      //  3
        fscanf(fp, "%lf%lf%lf", &this->pos[0], &this->pos[1], &this->pos[2]); //  6
        fscanf(fp, "%lf%lf%lf", &this->vel[0], &this->vel[1], &this->vel[2]); //  9
        fscanf(fp, "%lf%lf%lf", &this->acc[0], &this->acc[1], &this->acc[2]); // 12
        fscanf(fp, "%lf%lf%lf", &this->uene, &this->alph, &this->alphu);      // 15
        fscanf(fp, "%lf%lf%6d", &this->dens, &this->ksr,  &this->np);         // 18
        fscanf(fp, "%lf%lf%lf", &this->vsnd, &this->pres, &this->temp);       // 21
        fscanf(fp, "%lf%lf%lf", &this->divv, &this->rotv, &this->bswt);       // 24
        fscanf(fp, "%lf%lf%lf", &this->pot,  &this->abar, &this->zbar);       // 27
        fscanf(fp, "%lf",       &this->enuc);                                 // 28
        fscanf(fp, "%lf%lf%lf", &this->vsmx, &this->udot, &this->dnuc);       // 31
        for(PS::S32 k = 0; k < NuclearReaction::NumberOfNucleon; k++) {       // 32 -- 44
            fscanf(fp, "%lf", &this->cmps[k]);
        }
        fscanf(fp, "%lf", &this->pot3);
        fscanf(fp, "%lf%lf%lf", &this->tempmax[0], &this->tempmax[1], &this->tempmax[2]);
        fscanf(fp, "%lf", &this->entr);
    }

    /*
    void write(FILE * fp = stdout) {
        fprintf(fp, "%8d %2d %+e",  this->id, this->istar, this->mass);
        fprintf(fp, " %+e %+e %+e", this->pos[0], this->pos[1], this->pos[2]);
        fprintf(fp, " %+e %+e %+e", this->vel[0], this->vel[1], this->vel[2]);
        fprintf(fp, " %+e %+e %+e", this->uene, this->alph, this->alphu);
        fprintf(fp, " %+e", this->ksr);
        for(PS::S32 k = 0; k < NR::NumberOfNucleon; k++) {
            fprintf(fp, " %+.3e", this->cmps[k]);
        }
        fprintf(fp, "\n");
    }
    */

    inline PS::F64mat calcMomentOfInertia(PS::F64vec xc) {
        PS::F64mat qq = 0.;
        PS::F64vec dx = this->pos - xc;
        PS::F64 r2 = dx * dx;
        qq.xx = this->mass * (r2 - dx[0] * dx[0]);
        qq.xy = this->mass * (   - dx[0] * dx[1]);
        qq.xz = this->mass * (   - dx[0] * dx[2]);
        qq.yy = this->mass * (r2 - dx[1] * dx[1]);
        qq.yz = this->mass * (   - dx[1] * dx[2]);
        qq.zz = this->mass * (r2 - dx[2] * dx[2]);
        return qq;
    }
};

class BlackHoleNeutronStarAnalysis : public BlackHoleNeutronStar {
public:
    BlackHoleNeutronStarAnalysis() {
        this->id    = 0;
        this->istar = 0;
        this->mass  = 0.;
        this->pos   = 0.;
        this->vel   = 0.;
        this->acc   = 0.;
        this->eps   = 0.;
        this->pot   = 0.;
    }

    void readAscii(FILE * fp) {
        fscanf(fp, "%lld%lld%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf",
               &this->id, &this->istar, &this->mass,
               &this->pos[0], &this->pos[1], &this->pos[2],
               &this->vel[0], &this->vel[1], &this->vel[2],
               &this->acc[0], &this->acc[1], &this->acc[2],
               &this->eps, &this->pot);
    }

    inline PS::F64mat calcMomentOfInertia(PS::F64vec xc) {
        PS::F64mat qq = 0.;
        PS::F64vec dx = this->pos - xc;
        PS::F64 r2 = dx * dx;
        qq.xx = this->mass * (r2 - dx[0] * dx[0]);
        qq.xy = this->mass * (   - dx[0] * dx[1]);
        qq.xz = this->mass * (   - dx[0] * dx[2]);
        qq.yy = this->mass * (r2 - dx[1] * dx[1]);
        qq.yz = this->mass * (   - dx[1] * dx[2]);
        qq.zz = this->mass * (r2 - dx[2] * dx[2]);
        return qq;
    }
};

template <class Tsph,
          class Tbhns>
void calcDiskParameter(char * ofile,
                       Tsph  & sph,
                       Tbhns & bhns) {

    for(PS::S64 i = 0; i < sph.getNumberOfParticleLocal(); i++) {
        sph[i].pos -= bhns[0].pos;
    }

    PS::F64 dz      = 1e6;
    PS::F64 rbinmin = 1e7;
    PS::F64 rbinmax = 1e10;
    PS::S64 npd     = 10;
    PS::F64 drbin   = pow(10., 1 / (PS::F64)npd);

    FILE * fp = fopen(ofile, "w");
    for(PS::F64 rbin = rbinmin; rbin < rbinmax; rbin *= drbin) {
        PS::F64 rbin02 = rbin * rbin;
        PS::F64 rbin12 = (rbin * drbin) * (rbin * drbin);
        PS::F64 mdisk  = 0.;
        PS::F64 mcol   = 0.;
        PS::F64 udisk  = 0.;
        PS::S64 ncol   = 0;
        PS::S64 ndisk  = 0;
        for(PS::S64 i  = 0; i < sph.getNumberOfParticleLocal(); i++) {
            PS::F64 r2 = sph[i].pos[0] * sph[i].pos[0] + sph[i].pos[1] * sph[i].pos[1];
            if(r2 < rbin02 || rbin12 <= r2) {
                continue;
            }
            mcol  += sph[i].mass;
            ncol  += 1;
            if(fabs(sph[i].pos[2]) > dz) {
                continue;
            }
            mdisk += sph[i].mass;
            udisk += sph[i].mass * sph[i].uene;
            ndisk += 1;
        }
        PS::F64 surface = M_PI * (rbin12 - rbin02);
        PS::F64 volume  = surface * (2. * dz);
        PS::F64 sigma   = mcol / surface;
        PS::F64 dens    = mdisk / volume;
        PS::F64 uene    = udisk / mdisk;
        PS::F64 tout    = 0.;
        if(dens != 0.) {
            NR::Nucleon cmps;
            cmps[1] = cmps[2] = 0.5;
            PS::F64 temp = 1e9;
            PS::F64 pout, cout, sout;
            flash_helmholtz_(&dens, &uene, &temp, cmps.getPointer(),
                             &pout, &cout, &tout, &sout);
        }
        fprintf(fp, "%+e %+e %+e %+e %6d %6d\n", rbin, sigma, dens, tout, ncol, ndisk);
    }
    fclose(fp);

}

int main(int argc, char ** argv) {
    MPI_Init(&argc, &argv);

    init_flash_helmholtz_(&CodeUnit::FractionOfCoulombCorrection);

    PS::ParticleSystem<SPHAnalysis> sph;
    sph.initialize();
    sph.createParticle(0);
    sph.setNumberOfParticleLocal(0);
    PS::ParticleSystem<BlackHoleNeutronStarAnalysis> bhns;
    bhns.initialize();
    bhns.createParticle(0);
    bhns.setNumberOfParticleLocal(0);

    char idir[1024], otype[1024];
    PS::S64 ibgn, iend;
    FILE * fp = fopen(argv[1], "r");
    fscanf(fp, "%s", idir);
    fscanf(fp, "%s", otype);
    fscanf(fp, "%lld%lld", &ibgn, &iend);
    fclose(fp);

    for(PS::S64 itime = ibgn; itime <= iend; itime++) {
        char sfile[1024], bfile[1024];
        sprintf(sfile, "%s/sph_t%04d.dat",  idir, itime);
        sprintf(bfile, "%s/bhns_t%04d.dat", idir, itime);
        fp = fopen(sfile, "r");
        if(fp == NULL) {
            continue;
        }
        sph.readParticleAscii(sfile);
        fclose(fp);
        fp = fopen(bfile, "r");
        if(fp == NULL) {
            continue;
        }
        bhns.readParticleAscii(bfile);
        fclose(fp);

        char ofile[1024];
        sprintf(ofile, "%s_t%04d.dat", otype, itime);
        printf("Output: %s\n", ofile);
        calcDiskParameter(ofile, sph, bhns);

    }

    MPI_Finalize();

    return 0;
}


//
// Copyright (c) Thomas Klimpel 2008
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef BOOST_NUMERIC_BINDINGS_LAPACK_SYEVX_HPP
#define BOOST_NUMERIC_BINDINGS_LAPACK_SYEVX_HPP

#include <boost/numeric/bindings/lapack/heevx.hpp>

namespace boost { namespace numeric { namespace bindings {
  namespace lapack {

    template <typename A, typename T, typename W, typename Z, typename IFail, typename Work>
    inline
    int syevx (
      char jobz, char range, A& a, T vl, T vu, integer_t il, integer_t iu, T abstol, integer_t& m,
      W& w, Z& z, IFail& ifail, Work work = optimal_workspace() ) {

#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      typedef typename A::value_type                               value_type ;
      typedef typename traits::type_traits< value_type >::real_type real_type ;
      BOOST_STATIC_ASSERT((boost::is_same<value_type, real_type>::value));
#endif

      return heevx (jobz, range, a, vl, vu, il, iu, abstol, m, w, z, ifail, work);
    }
  }
}}}

#endif


//
// Copyright (c) 2021 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/CPPAlliance/http_proto
//

#ifndef BOOST_HTTP_PROTO_RFC_IMPL_STATUS_CODE_RULE_PP
#define BOOST_HTTP_PROTO_RFC_IMPL_STATUS_CODE_RULE_PP

#include <boost/http_proto/rfc/status_line_rule.hpp>
#include <boost/http_proto/rfc/crlf_rule.hpp>
#include <boost/http_proto/rfc/version_rule.hpp>
#include <boost/url/grammar/error.hpp>

namespace boost {
namespace http_proto {

/*
    status-code     = 3DIGIT
*/
void
status_code_rule::
parse(
    char const*& it0,
    char const* end,
    error_code& ec,
    status_code_rule& t) noexcept
{
    auto const dig =
        [](char c) -> int
        {
            unsigned char uc(c - '0');
            if(uc > 9)
                return -1;
            return uc;
        };

    auto it = it0;
    int v;

    if(it == end)
    {
        ec = grammar::error::incomplete;
        return;
    }
    v = dig(*it);
    if(v == -1)
    {
        ec = grammar::error::syntax;
        return;
    }
    t.v = 100 * v;
    ++it;

    if(it == end)
    {
        ec = grammar::error::incomplete;
        return;
    }
    v = dig(*it);
    if(v == -1)
    {
        ec = grammar::error::syntax;
        return;
    }
    t.v = t.v + (10 * v);
    ++it;

    if(it == end)
    {
        ec = grammar::error::incomplete;
        return;
    }
    v = dig(*it);
    if(v == -1)
    {
        ec = error::bad_status_code;
        return;
    }
    t.v = t.v + v;
    ++it;

    t.s = string_view(it0, it - it0);
    t.st = int_to_status(t.v);
}

} // http_proto
} // boost

#endif


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

#ifndef CRYPTO3_R1CS_GG_PPZKSNARK_IPP2_VERIFY_HPP
#define CRYPTO3_R1CS_GG_PPZKSNARK_IPP2_VERIFY_HPP

#include <memory>
#include <vector>
#include <tuple>

#include <boost/iterator/zip_iterator.hpp>

#include <nil/crypto3/detail/pack_numeric.hpp>

#include <nil/crypto3/hash/algorithm/hash.hpp>
#include <nil/crypto3/hash/sha2.hpp>

#include <nil/crypto3/zk/snark/schemes/ppzksnark/r1cs_gg_ppzksnark/verification_key.hpp>
#include <nil/crypto3/zk/snark/schemes/ppzksnark/r1cs_gg_ppzksnark/ipp2/proof.hpp>
#include <nil/crypto3/zk/snark/schemes/ppzksnark/r1cs_gg_ppzksnark/ipp2/srs.hpp>

namespace nil {
    namespace crypto3 {
        namespace zk {
            namespace snark {
                template<typename CurveType>
                struct Op {
                    typedef CurveType curve_type;

                    typedef typename curve_type::pairing::fqk_type::value_type TAB;
                    typedef typename curve_type::pairing::fqk_type::value_type UAB;
                    typedef typename curve_type::pairing::fqk_type::value_type ZAB;
                    typedef typename curve_type::pairing::fqk_type::value_type TC;
                    typedef typename curve_type::pairing::fqk_type::value_type UC;
                    typedef typename curve_type::pairing::g1_type::value_type ZC;
                };

                /// Keeps track of the variables that have been sent by the prover and must
                /// be multiplied together by the verifier. Both MIPP and TIPP are merged
                /// together.
                template<typename CurveType>
                struct gipa_tuz {
                    typedef CurveType curve_type;
                    typedef typename curve_type::scalar_field_type::value_type fr_type;

                    std::pair<typename curve_type::pairing::fqk_type::value_type, fr_type::value_type> tab;
                    std::pair<typename curve_type::pairing::fqk_type::value_type, fr_type::value_type> uab;
                    std::pair<typename curve_type::pairing::fqk_type::value_type, fr_type::value_type> zab;
                    std::pair<typename curve_type::pairing::fqk_type::value_type, fr_type::value_type> tc;
                    std::pair<typename curve_type::pairing::fqk_type::value_type, fr_type::value_type> uc;
                    std::pair<typename curve_type::pairing::g1_type, fr_type> zc;
                };

                /// gipa_verify_tipp_mipp recurse on the proof and statement and produces the final
                /// values to be checked by TIPP and MIPP verifier, namely, for TIPP for example:
                /// * T,U: the final commitment values of A and B
                /// * Z the final product between A and B.
                /// * Challenges are returned in inverse order as well to avoid
                /// repeating the operation multiple times later on.
                /// * There are T,U,Z vectors as well for the MIPP relationship. Both TIPP and
                /// MIPP share the same challenges however, enabling to re-use common operations
                /// between them, such as the KZG proof for commitment keys.
                template<typename CurveType, typename Hash = hashes::sha2<256>>
                std::tuple<gipa_tuz<CurveType>, std::vector<typename CurveType::scalar_field_type::value_type>,
                           std::vector<typename CurveType::scalar_field_type::value_type>>
                    gipa_verify_tipp_mipp(const r1cs_gg_ppzksnark_aggregate_proof<CurveType> &proof) {
                    std::vector<typename CurveType::scalar_field_type::value_type> challenges, challenges_inv;

                    typename CurveType::scalar_field_type::value_type default_transcript =
                        typename CurveType::scalar_field_type::value_type::zero();

                    // We first generate all challenges as this is the only consecutive process
                    // that can not be parallelized then we scale the commitments in a
                    // parallelized way
                    std::for_each(
                        boost::make_zip_iterator(
                            std::make_tuple(proof.tmipp.gipa.comms_ab.begin(), proof.tmipp.gipa.z_ab.begin(),
                                            proof.tmipp.gipa.comms_c.begin(), proof.tmipp.gipa.z_c.begin())),
                        boost::make_zip_iterator(
                            std::make_tuple(proof.tmipp.gipa.comms_ab.end(), proof.tmipp.gipa.z_ab.end(),
                                            proof.tmipp.gipa.comms_c.end(), proof.tmipp.gipa.z_c.end())),
                        [&](const std::tuple<const std::pair<r1cs_gg_ppzksnark_ipp2_commitment_output<CurveType>,
                                                             r1cs_gg_ppzksnark_ipp2_commitment_output<CurveType>> &,
                                             const std::pair<typename CurveType::pairing::fqk_type::value_type,
                                                             typename CurveType::pairing::fqk_type::value_type> &,
                                             const std::pair<r1cs_gg_ppzksnark_ipp2_commitment_output<CurveType>,
                                                             r1cs_gg_ppzksnark_ipp2_commitment_output<CurveType>> &,
                                             const std::pair<typename CurveType::g1_type::value_type,
                                                             typename CurveType::g1_type::value_type> &> &t) {
                            auto tab_l = std::get<0>(std::get<0>(t));
                            auto tab_r = std::get<1>(std::get<0>(t));

                            auto zab_l = std::get<0>(std::get<1>(t));
                            auto zab_r = std::get<1>(std::get<1>(t));

                            auto tc_l = std::get<0>(std::get<2>(t));
                            auto tc_r = std::get<1>(std::get<2>(t));

                            auto zc_l = std::get<0>(std::get<3>(t));
                            auto zc_r = std::get<1>(std::get<3>(t));

                            // Fiat-Shamir challenge
                            auto transcript = challenges.empty() ? *(challenges.end() - 1) : default_transcript;

                            std::size_t counter_nonce = 1;
                            std::array<std::uint8_t, sizeof(std::size_t)> counter_nonce_bytes;
                            crypto3::detail::pack<stream_endian::big_byte_big_bit>({counter_nonce},
                                                                                counter_nonce_bytes);
                            accumulator_set<Hash> acc;

                            hash<Hash>(counter_nonce_bytes, acc);
                            hash<Hash>(transcript, acc);
                            hash<Hash>(std::get<0>(tab_l), acc);
                            hash<Hash>(std::get<1>(tab_l), acc);
                            hash<Hash>(std::get<0>(tab_r), acc);
                            hash<Hash>(std::get<1>(tab_r), acc);
                            hash<Hash>(zab_l, acc);
                            hash<Hash>(zab_r, acc);
                            hash<Hash>(zc_l, acc);
                            hash<Hash>(zc_r, acc);
                            hash<Hash>(std::get<0>(tc_l), acc);
                            hash<Hash>(std::get<1>(tc_l), acc);
                            hash<Hash>(std::get<0>(tc_r), acc);
                            hash<Hash>(std::get<1>(tc_r), acc);

                            typename Hash::digest_type d = accumulators::extract::hash<Hash>(acc);
                            typename CurveType::scalar_field_type::value_type c;
                            multiprecision::import_bits(c.data, d);

                            challenges.emplace_back(c);
                            challenges_inv.emplace_back(c.inversed());
                        });

                    gipa_tuz<CurveType> final_res = {std::get<0>(proof.com_ab), std::get<1>(proof.com_ab), proof.ip_ab,
                                                     std::get<0>(proof.com_c),  std::get<1>(proof.com_c),  proof.agg_c};

                    // we first multiply each entry of the Z U and L vectors by the respective
                    // challenges independently
                    // Since at the end we want to multiple all "t" values together, we do
                    // multiply all of them in parrallel and then merge then back at the end.
                    // same for u and z.
                    std::for_each(
                        boost::make_zip_iterator(
                            std::make_tuple(proof.tmipp.gipa.comms_ab.begin(), proof.tmipp.gipa.z_ab.begin(),
                                            proof.tmipp.gipa.comms_c.begin(), proof.tmipp.gipa.z_c.begin(),
                                            challenges.begin(), challenges_inv.begin())),
                        boost::make_zip_iterator(
                            std::make_tuple(proof.tmipp.gipa.comms_ab.end(), proof.tmipp.gipa.z_ab.end(),
                                            proof.tmipp.gipa.comms_c.end(), proof.tmipp.gipa.z_c.end(),
                                            challenges.end(), challenges_inv.end())),
                        [&](const std::tuple<const std::pair<r1cs_gg_ppzksnark_ipp2_commitment_output<CurveType>,
                                                             r1cs_gg_ppzksnark_ipp2_commitment_output<CurveType>> &,
                                             const std::pair<typename CurveType::pairing::fqk_type::value_type,
                                                             typename CurveType::pairing::fqk_type::value_type> &,
                                             const std::pair<r1cs_gg_ppzksnark_ipp2_commitment_output<CurveType>,
                                                             r1cs_gg_ppzksnark_ipp2_commitment_output<CurveType>> &,
                                             const std::pair<typename CurveType::g1_type::value_type,
                                                             typename CurveType::g1_type::value_type> &,
                                             const typename CurveType::scalar_field_type::value_type &,
                                             const typename CurveType::scalar_field_type::value_type &> &t) {
                            // T and U values for right and left for AB part
                            auto tab_l = std::get<0>(std::get<0>(std::get<0>(t)));
                            auto uab_l = std::get<1>(std::get<0>(std::get<0>(t)));
                            auto tab_r = std::get<0>(std::get<1>(std::get<0>(t)));
                            auto uab_r = std::get<1>(std::get<1>(std::get<0>(t)));

                            auto zab_l = std::get<0>(std::get<1>(t));
                            auto zab_r = std::get<1>(std::get<1>(t));

                            // T and U values for right and left for C part
                            auto tc_l = std::get<0>(std::get<0>(std::get<2>(t)));
                            auto uc_l = std::get<1>(std::get<0>(std::get<2>(t)));
                            auto tc_r = std::get<0>(std::get<1>(std::get<2>(t)));
                            auto uc_r = std::get<1>(std::get<1>(std::get<2>(t)));

                            auto zc_l = std::get<0>(std::get<3>(t));
                            auto zc_r = std::get<1>(std::get<3>(t));

                            // we multiple left side by x and right side by x^-1
                            vec ![
                                Op<CurveType>::TAB(tab_l, std::get<4>(t)),
                                Op<CurveType>::tab(tab_r, c_inv_repr),
                                Op::UAB(uab_l, c_repr),
                                Op::UAB(uab_r, c_inv_repr),
                                Op::ZAB(zab_l, c_repr),
                                Op::ZAB(zab_r, c_inv_repr),
                                Op::TC::<E>(tc_l, c_repr),
                                Op::TC(tc_r, c_inv_repr),
                                Op::UC(uc_l, c_repr),
                                Op::UC(uc_r, c_inv_repr),
                                Op::ZC(zc_l, c_repr),
                                Op::ZC(zc_r, c_inv_repr),
                            ]
                        });
                }

                /// verify_tipp_mipp returns a pairing equation to check the tipp proof.  $r$ is
                /// the randomness used to produce a random linear combination of A and B and
                /// used in the MIPP part with C
                template<typename CurveType>
                PairingCheck<CurveType> verify_tipp_mipp(const r1cs_gg_ppzksnark_verifying_srs<CurveType> &v_srs,
                                                         const r1cs_gg_ppzksnark_aggregate_proof<CurveType> &proof,
                                                         const typename CurveType::scalar_field_type::value_type &r_shift) {
                    // (T,U), Z for TIPP and MIPP  and all challenges
                    std::tuple<gipa_tuz<CurveType>, std::vector<typename CurveType::scalar_field_type::value_type>,
                               std::vector<typename CurveType::scalar_field_type::value_type>>
                        gtmp = gipa_verify_tipp_mipp(proof);
                    auto &final_res = std::get<0>(gtmp);
                    auto &challenges = std::get<1>(gtmp);
                    auto &challenges_inv = std::get<2>(gtmp);

                    // we reverse the order so the KZG polynomial have them in the expected
                    // order to construct them in logn time.
                    std::reverse(challenges.begin(), challenges.end());
                    std::reverse(challenges_inv.begin(), challenges_inv.end());

                    // Verify commitment keys wellformed
                    auto fvkey = proof.tmipp.gipa.final_vkey;
                    auto fwkey = proof.tmipp.gipa.final_wkey;
                }

                template<typename CurveType, typename InputPublicInputsIterator>
                bool verify_aggregate_proof(const r1cs_gg_ppzksnark_verifying_srs<CurveType> &ip_verifier_srs,
                                            const r1cs_gg_ppzksnark_processed_verification_key<CurveType> &pvk,
                                            InputPublicInputsIterator public_inputs_first,
                                            InputPublicInputsIterator public_inputs_last,
                                            const r1cs_gg_ppzksnark_aggregate_proof<CurveType> &proof) {

                    // Random linear combination of proofs
                    std::size_t counter_nonce = 1;
                    std::array<std::uint8_t, sizeof(std::size_t)> counter_nonce_bytes;
                    crypto3::detail::pack<stream_endian::big_byte_big_bit>({counter_nonce}, counter_nonce_bytes);
                    accumulator_set<hashes::sha2<256>> acc;

                    hash<hashes::sha2<256>>(counter_nonce_bytes, acc);
                    hash<hashes::sha2<256>>(std::get<0>(proof.com_ab), acc);
                    hash<hashes::sha2<256>>(std::get<1>(proof.com_ab), acc);
                    hash<hashes::sha2<256>>(std::get<0>(proof.com_c), acc);
                    hash<hashes::sha2<256>>(std::get<1>(proof.com_c), acc);

                    typename hashes::sha2<256>::digest_type d = accumulators::extract::hash<hashes::sha2<256>>(acc);
                    typename CurveType::scalar_field_type::value_type r;
                    crypto3::detail::pack(d, r.data);
                    r = r.inversed();

                    InputPublicInputsIterator vpitr = public_inputs_first;

                    while (vpitr != public_inputs_last) {
                        BOOST_ASSERT_MSG(vpitr->size() + 1 == pvk.ic.size(), "malformed verification key!");
                        ++vpitr;
                    }

                    // 1.Check TIPA proof ab
                    // 2.Check TIPA proof c
                    auto tipa_ab =
                        verify_tipp_mipp<CurveType>(ip_verifier_srs,
                                                    proof,
                                                    r    // we give the extra r as it's not part of the proof itself
                                                         // - it is simply used on top for the groth16 aggregation
                        );
                }

                /// verify_kzg_opening_g2 takes a KZG opening, the final commitment key, SRS and
                /// any shift (in TIPP we shift the v commitment by r^-1) and returns a pairing
                /// tuple to check if the opening is correct or not.
                template<typename CurveType, typename InputScalarIterator>
                PairingCheck<CurveType> verify_kzg_opening_g2(
                    const r1cs_gg_ppzksnark_verifying_srs<CurveType> &v_srs,
                    const r1cs_gg_ppzksnark_ipp2_vkey<CurveType> &final_vkey,
                    const kzg_opening<typename CurveType::g2_type> &vkey_opening,
                    InputScalarIterator challenges_first,
                    InputScalarIterator challenges_last,
                    const typename std::iterator_traits<InputScalarIterator>::value_type &r_shift,
                    const typename std::iterator_traits<InputScalarIterator>::value_type &kzg_challenge) {
                }

                /// Similar to verify_kzg_opening_g2 but for g1.
                template<typename CurveType, typename InputScalarIterator>
                PairingCheck<CurveType> verify_kzg_opening_g1(
                    const r1cs_gg_ppzksnark_verifier_srs<CurveType> &v_srs,
                    const r1cs_gg_ppzksnark_ipp2_wkey<CurveType> &final_wkey,
                    const kzg_opening<typename CurveType::g1_type> &wkey_opening, InputScalarIterator challenges_first,
                    InputScalarIterator challenges_last, ,
                    const typename std::iterator_traits<InputScalarIterator>::value_type &r_shift,
                    const typename std::iterator_traits<InputScalarIterator>::value_type &kzg_challenge) {
                }
            }    // namespace snark
        }        // namespace zk
    }            // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_R1CS_GG_PPZKSNARK_TYPES_POLICY_HPP


//////////////////////////////////////////////////////////////////
// example91.cpp
//
// Copyright (c) 2015 Robert Ramey
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <iostream>
#include <limits>

#include <boost/safe_numerics/cpp.hpp>
#include <boost/safe_numerics/safe_integer.hpp>
#include <boost/safe_numerics//safe_integer_range.hpp>

// use same type promotion as used by the pic compiler
// see the following comment in motor.c
// Types: int8,int16,int32=8,16,32bit integers

using pic16_promotion = boost::safe_numerics::cpp<
    8,  // char
    8,  // short
    8,  // int
    16, // long
    32  // long long
>;

// define safe types used desktop version of the program.  In conjunction
// with the promotion policy above, this will permit us to guarantee that
// the resulting program will be free of arithmetic errors introduced by
// C expression syntax and type promotion with no runtime penalty
template <typename T> // T is char, int, etc data type
using safe_t = boost::safe_numerics::safe<
    T,
    pic16_promotion,
    boost::safe_numerics::default_exception_policy // use for compiling and running tests
>;
using safe_bool_t = boost::safe_numerics::safe_unsigned_range<
    0,
    1,
    pic16_promotion,
    boost::safe_numerics::default_exception_policy // use for compiling and running tests
>;

#define DESKTOP
#include "motor1.c"

#include <chrono>
#include <thread>

void sleep(int16){
    std::this_thread::sleep_for(std::chrono::microseconds(ccpr));
}

int main(){
    std::cout << "start test\n";
    try{
        initialize();
        motor_run(100);
        do{
            isr_motor_step();
        }while (run_flg);

        // move motor to position 1000
        motor_run(1000);
        do{
            sleep(ccpr);
            isr_motor_step();
        }while (run_flg);

        // move back to position 0
        motor_run(0);
        do{
            sleep(ccpr);
            isr_motor_step();
        }while (run_flg);
    }
    catch(std::exception & e){
        std::cout << e.what() << '\n';
        // we expect to trap an exception
        return 0;
    }
    catch(...){
        std::cout << "test interrupted\n";
        return 1;
    }
    std::cout << "end test\n";
    return 1;
}


// Copyright (C) 2015 The Regents of the University of California (Regents).
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//
//     * Neither the name of The Regents or University of California nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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.
//
// Please contact the author of this library if you have any questions.
// Author: Aleksander Holynski (holynski@cs.washington.edu)

#include "theia/io/write_colmap_files.h"

#include <Eigen/Core>
#include <Eigen/Geometry>
#include <fstream>  // NOLINT
#include <glog/logging.h>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include "theia/sfm/camera/camera.h"
#include "theia/sfm/camera/camera_intrinsics_model.h"
#include "theia/sfm/camera/pinhole_camera_model.h"
#include "theia/sfm/camera_intrinsics_prior.h"
#include "theia/sfm/reconstruction.h"
#include "theia/sfm/reconstruction_estimator_utils.h"
#include "theia/sfm/track.h"
#include "theia/sfm/types.h"
#include "theia/sfm/view.h"
#include "theia/util/map_util.h"

namespace theia {
namespace {

bool WriteCamerasFile(const Reconstruction& reconstruction,
                      const std::string& cameras_file) {
  std::ofstream ofs_cameras(cameras_file);
  if (!ofs_cameras.is_open()) {
    LOG(ERROR) << "Cannot open the file: " << cameras_file << " for writing.";
    return false;
  }

  const auto& group_ids = reconstruction.CameraIntrinsicsGroupIds();

  for (auto group_id : group_ids) {
    const View* view = reconstruction.View(
        *reconstruction.GetViewsInCameraIntrinsicGroup(group_id).begin());
    const Camera& camera = view->Camera();
    if (camera.GetCameraIntrinsicsModelType() !=
        CameraIntrinsicsModelType::PINHOLE) {
      LOG(ERROR) << "Could not add camera " << view->Name()
                 << " to the COLMAP output file because COLMAP doesn't support "
                    "FOV or fisheye cameras.";
      return false;
    }

    const CameraIntrinsicsModel& intrinsics = *camera.CameraIntrinsics();
    ofs_cameras
        << group_id << " RADIAL " << camera.ImageWidth() << " "
        << camera.ImageHeight() << " " << camera.FocalLength() << " "
        << camera.PrincipalPointX() << " " << camera.PrincipalPointY() << " "
        << intrinsics.GetParameter(PinholeCameraModel::RADIAL_DISTORTION_1)
        << " "
        << intrinsics.GetParameter(PinholeCameraModel::RADIAL_DISTORTION_1)
        << std::endl;
  }
  ofs_cameras.close();

  return true;
}

bool WriteImagesFile(const Reconstruction& reconstruction,
                     const std::string& images_file) {
  std::ofstream ofs_images(images_file);

  if (!ofs_images.is_open()) {
    LOG(ERROR) << "Cannot open the file: " << images_file << " for writing.";
    return false;
  }

  for (auto view_id : reconstruction.ViewIds()) {
    const View* view = reconstruction.View(view_id);
    const Camera& camera = view->Camera();
    const Eigen::Vector3d translation =
        -camera.GetOrientationAsRotationMatrix() * camera.GetPosition();
    const Eigen::Quaterniond orientation(
        camera.GetOrientationAsRotationMatrix());
    ofs_images << view_id << " " << orientation.w() << " " << orientation.x()
               << " " << orientation.y() << " " << orientation.z() << " "
               << translation.x() << " " << translation.y() << " "
               << translation.z() << " "
               << reconstruction.CameraIntrinsicsGroupIdFromViewId(view_id)
               << " " << view->Name() << std::endl;
    const auto& track_ids = view->TrackIds();
    for (auto track_id : track_ids) {
      auto feature = view->GetFeature(track_id);
      ofs_images << feature->point_.x() << " " << feature->point_.y() << " "
                 << track_id << " ";
    }
    ofs_images << std::endl;
  }
  ofs_images.close();
  return true;
}

bool WritePointsFile(const Reconstruction& reconstruction,
                     const std::string& points_file) {
  std::ofstream ofs_points(points_file);
  if (!ofs_points.is_open()) {
    LOG(ERROR) << "Cannot open the file: " << points_file << " for writing.";
    return false;
  }

  for (auto track_id : reconstruction.TrackIds()) {
    const Track* track = reconstruction.Track(track_id);
    const Eigen::Vector3d point = track->Point().hnormalized();
    const auto& color = track->Color();
    ofs_points << track_id << " " << point.x() << " " << point.y() << " "
               << point.z() << " " << static_cast<int>(color[0]) << " "
               << static_cast<int>(color[1]) << " "
               << static_cast<int>(color[2]) << " " << 0.0 << " ";
    for (auto view_id : track->ViewIds()) {
      const View* view = reconstruction.View(view_id);
      const auto& track_ids = view->TrackIds();
      const int point_index =
          std::find(track_ids.begin(), track_ids.end(), track_id) -
          track_ids.begin();
      ofs_points << view_id << " " << point_index << " ";
    }
    ofs_points << std::endl;
  }

  ofs_points.close();
  return true;
}

}  // namespace

bool WriteColmapFiles(const Reconstruction& reconstruction,
                      const std::string& output_directory) {
  Reconstruction estimated_reconstruction;
  CreateEstimatedSubreconstruction(reconstruction, &estimated_reconstruction);

  const std::string& cameras_file = output_directory + "/cameras.txt";
  const std::string& images_file = output_directory + "/images.txt";
  const std::string& points_file = output_directory + "/points3D.txt";

  if (!WriteCamerasFile(estimated_reconstruction, cameras_file)) {
    return false;
  }
  if (!WriteImagesFile(estimated_reconstruction, images_file)) {
    return false;
  }
  if (!WritePointsFile(estimated_reconstruction, points_file)) {
    return false;
  }
  return true;
}

}  // namespace theia


// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#include "crc32.h"

#include <boost/crc.hpp>

namespace mira
{
    int32_t crc32(const void* data, size_t size)
    {
        boost::crc_32_type computer;
        computer.process_bytes(data, size);
        return computer.checksum();
    }

    struct continuous_crc32::impl
    {
        boost::crc_32_type computer;
    };

    continuous_crc32::continuous_crc32()
        : m_impl{ std::make_unique<impl>() }
    {}

    continuous_crc32::~continuous_crc32() = default;
    continuous_crc32::continuous_crc32(continuous_crc32&&) = default;
    continuous_crc32& continuous_crc32::operator=(continuous_crc32&&) = default;

    void continuous_crc32::process(const void* data, size_t size)
    {
        m_impl->computer.process_bytes(data, size);
    }

    int32_t continuous_crc32::checksum() const
    {
        return m_impl->computer.checksum();
    }
}


#ifndef PYTHONIC_TYPES_NUMPY_BROADCAST_HPP
#define PYTHONIC_TYPES_NUMPY_BROADCAST_HPP

#include <array>

#ifdef USE_BOOST_SIMD
#include <boost/simd/sdk/simd/native.hpp>
#include <boost/simd/include/functions/load.hpp>
#include <boost/simd/include/functions/store.hpp>
#endif

//#include "pythonic/types/tuple.hpp"
#include "pythonic/types/vectorizable_type.hpp"

namespace pythonic {

    namespace types {

        /* Type adaptor for broadcasted array values
         *
         * Used when the args of a binary operator do not have the same dimensions:
         * in that case their first dimension always yields a copy
         */
        template<class T>
            struct broadcasted {
                static const bool is_vectorizable = false;
                static const bool is_strided = false;
                typedef typename T::dtype dtype;
                typedef typename T::value_type value_type;
                static constexpr size_t value = T::value + 1;

                T const & ref;
                array<long, value> shape;

                broadcasted(T const& ref) : ref(ref), shape() {
                    shape[0] = 1;
                    std::copy(ref.shape.begin(), ref.shape.end(), shape.begin() + 1);
                }

                T const & operator[](long i) const { return ref;}
                T const & fast(long i) const { return ref;}
#ifdef USE_BOOST_SIMD
                template<class I> // template to prevent automatic instantiation, but the declaration is still needed
                void load(I) const {
                  static_assert(I::this_should_never_happen, "this is *not* vectorizable");
                }
#endif

                long flat_size() const { return 0;}

            };

        /* Type adaptor for scalar values
         *
         * Have them behave like infinite arrays of that value
         *
         * B is the original type of the broadcast value, and T is the type of the expression it is combined with
         * if both B and T are integer types, we choose T instead of B to prevent automatic conversion into larger types
         *
         * That way, np.ones(10, dtype=np.uint8) + 1 yields an array of np.uint8, although 1 is of type long
         */
        template<class dtype, bool is_vectorizable> struct broadcast_base {
          dtype _value;
          template<class V> broadcast_base(V v) : _value(v) {}
          template<class I> void load(I) const { static_assert(sizeof(I) != sizeof(I), "this method should never be instantiated");}
        };
#ifdef USE_BOOST_SIMD
        template<class dtype> struct broadcast_base<dtype, true> {
          dtype _value;
          boost::simd::native<dtype, BOOST_SIMD_DEFAULT_EXTENSION> _splated ;
          template<class V> broadcast_base(V v) : _value(v), _splated(boost::simd::splat<boost::simd::native<dtype, BOOST_SIMD_DEFAULT_EXTENSION>>(_value))
          {}
          template<class I> auto load(I) const -> decltype(this -> _splated) { return _splated; }
        };
#endif
        template<class T, class B>
            struct broadcast {
                // Perform the type conversion here if it seems valid (although it is not always)
                typedef typename std::conditional<std::is_integral<T>::value and std::is_integral<B>::value,
                                          T,
                                          typename __combined<T, B>::type>::type dtype;
                static const bool is_vectorizable = types::is_vectorizable<dtype>::value;
                static const bool is_strided = false;
                typedef dtype value_type;
                static constexpr size_t value = 0;

                broadcast_base<dtype, is_vectorizable> _base;

                broadcast() = default;
                template<class V>
                broadcast(V v) : _base(v) {}

                dtype operator[](long ) const {
                    return _base._value;
                }
                dtype fast(long ) const {
                    return _base._value;
                }
                template<class I>
                auto load(I i) const -> decltype(this -> _base.load(i)) { return _base.load(i); }
                long flat_size() const { return 0; }
            };
    }
}

#endif