/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*  This file is part of the library KASKADE 7                               */
/*    see http://www.zib.de/projects/kaskade7-finite-element-toolbox         */
/*                                                                           */
/*  Copyright (C) 2002-2012 Zuse Institute Berlin                            */
/*                                                                           */
/*  KASKADE 7 is distributed under the terms of the ZIB Academic License.    */
/*    see $KASKADE/academic.txt                                              */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifndef ALGORITHM_HIERARCHIC_ERROR_ESTIMATOR_HH
#define ALGORITHM_HIERARCHIC_ERROR_ESTIMATOR_HH

#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>

#include <boost/timer/timer.hpp>
#include <boost/fusion/include/at_c.hpp>

#include "algorithm/newton_bridge.hh"
#include "linalg/jacobiPreconditioner.hh"
#include "fem/hierarchicErrorEstimator.hh"
#include "fem/variables.hh"
#include "linalg/direct.hh"
#include "linalg/tcg.hh"
#include "utilities/enums.hh"

// forward declarations
namespace Dune
{
  struct InverseOperatorResult;
  template <class,int> class FieldVector;
}

namespace Kaskade
{
  namespace HierarchicErrorEstimator_Detail{
    bool biggerThanAbs(const std::pair<double,int>& x1, const std::pair<double,int>&  x2)
    {
      return fabs(x1.first)>fabs(x2.first);
    }

    double add(const double& x1, const std::pair<double,int>&  x2)
    {
      return x1+x2.first;
    }
  }

  template <class CorrectionVector>
  struct LeaveRHS
  {
    explicit LeaveRHS(CorrectionVector const&) {}

    template <class RhsVector>
    void operator()(RhsVector& rhs)
    {}
  };

  template <class CorrectionVector>
  struct SubstractCorrection
  {
    explicit SubstractCorrection(CorrectionVector const& cor_)
    : cor(cor_)
    {}

    template <class RhsVector>
    void operator()(RhsVector& rhs)
    {
      rhs -= cor;
    }

  private:
    CorrectionVector const& cor;
  };

  template <class Functional, /*class LF,*/ class ExtensionVariableSetDescription, class ExtensionSpace, class NormFunctional=Functional, template <class> class AdjustRHS=LeaveRHS>
  class HierarchicalBasisErrorEstimator : public AbstractHierarchicalErrorEstimator
  {
    static constexpr int yId = 1, uId = 0, pId = 2;
    static constexpr int noOfVariables = ExtensionVariableSetDescription::noOfVariables;
    //    typedef typename LF::AnsatzVars LVars;
  public:
    typedef typename Functional::Scalar Scalar;
    typedef typename Functional::AnsatzVars::VariableSet VariableSet;
    static constexpr int dim = VariableSet::Descriptions::Grid::dimension;
    typedef HierarchicErrorEstimator<LinearizationAt<Functional>,ExtensionVariableSetDescription,ExtensionVariableSetDescription,HierarchicErrorEstimatorDetail::TakeAllD2<LinearizationAt<Functional> > > ErrorEstimator;
    typedef VariationalFunctionalAssembler<ErrorEstimator> Assembler;
    typedef typename ExtensionVariableSetDescription::template CoefficientVectorRepresentation<0,2>::type CoefficientVector02;
    typedef typename ExtensionVariableSetDescription::template CoefficientVectorRepresentation<0,noOfVariables>::type CoefficientVector;
    typedef typename ExtensionVariableSetDescription::GridView::template Codim<0>::Iterator CellIterator;

    //    HierarchicalBasisErrorEstimator(Functional& f_, LF& lf_, LVars& lvars, ExtensionVariableSetDescription& extensionVariableSetDescription_, ExtensionSpace& extensionSpace_, Scalar fraction=0.7, bool verbose_=false)
    //    : f(f_), lf(lf_), normFunctional(f_), extensionVariableSetDescription(extensionVariableSetDescription_), extensionSpace(extensionSpace_), assembler(extensionVariableSetDescription.spaces),
    //    squaredFraction(fraction*fraction), verbose(verbose_)
    //    {}

    HierarchicalBasisErrorEstimator(Functional& f_, /*LF& lf_,*/ /*LVars& lvars_,*/ NormFunctional& normFunctional_, ExtensionVariableSetDescription& extensionVariableSetDescription_,
        ExtensionSpace& extensionSpace_, Scalar fraction=0.7, bool verbose_=false)
    : f(f_), /*lf(lf_), lvars(lvars_),*/ normFunctional(normFunctional_), extensionVariableSetDescription(extensionVariableSetDescription_), extensionSpace(extensionSpace_), assembler(extensionVariableSetDescription.spaces),
      squaredFraction(fraction*fraction), verbose(verbose_)
    {}

    virtual ~HierarchicalBasisErrorEstimator(){}

    void operator()(AbstractVector const& x_, AbstractVector const& dx_, int step, AbstractVector const& lowerOrderRhs)
    {
      if(verbose) std::cout << "ERROR ESTIMATOR: Start." << std::endl;
      Bridge::Vector<VariableSet> const& x = dynamic_cast<const Bridge::Vector<VariableSet>&>(x_);
      Bridge::Vector<VariableSet> const& dx = dynamic_cast<const Bridge::Vector<VariableSet>&>(dx_);
      //    Bridge::Vector<VariableSet> const& loRhs = dynamic_cast<const Bridge::Vector<VariableSet>&>(lowerOrderRhs);

      boost::timer::cpu_timer atimer;
      assembler.assemble(ErrorEstimator(LinearizationAt<Functional>(f,x.get()),dx.get()));
      if(verbose) std::cout << "ERROR ESTIMATOR: assembly time: " << boost::timer::format(atimer.elapsed(), 2, "%t") << "s" << std::endl;

      typedef AssembledGalerkinOperator<Assembler> Operator;
      typedef AssembledGalerkinOperator<Assembler,0,2,0,2> CorrectionOperator;
      typedef typename Functional::AnsatzVars::template CoefficientVectorRepresentation<0,2>::type CorrectionVector;

      Operator op(assembler);
      //      CorrectionOperator(assembler);

      CoefficientVector estRhs(assembler.rhs());
      std::vector<Scalar> tmpRepVec;
      IstlInterfaceDetail::toVector(estRhs,tmpRepVec);
      //estRhs = Bridge::getImpl<VariableSet>(lowerOrderRhs);
      std::cout << "rhs entries:" << std::endl;
      Scalar maxRhs=std::numeric_limits<Scalar>::min(), minRhs=std::numeric_limits<Scalar>::max(), rhsSum=0;
      for(size_t i=0; i<tmpRepVec.size(); ++i){
        if(std::isnan(tmpRepVec[i])) std::cout << "nan at " << i << std::endl;

        Scalar val = tmpRepVec[i];
        if(val < minRhs) minRhs = val;
        if(val > maxRhs) maxRhs = val;
        rhsSum += val*val;
      }
      std::cout << "max rhs: " << maxRhs << std::endl;
      std::cout << "min rhs: " << minRhs << std::endl;
      std::cout << "sum: " << rhsSum << std::endl;
      // possible adjustment of rhs
      //adjustRHS(estRhs);

      boost::timer::cpu_timer timer;
      CoefficientVector estSol(ExtensionVariableSetDescription::template CoefficientVectorRepresentation<0,noOfVariables>::init(extensionVariableSetDescription));

      directInverseOperator(op,DirectType::UMFPACK,MatrixProperties::GENERAL).apply(estRhs,estSol);

      if(verbose) std::cout << "ERROR ESTIMATOR: computation time: " << boost::timer::format(timer.elapsed(), 2, "%t") << "s" << std::endl;
      typename ExtensionVariableSetDescription::VariableSet mySol(extensionVariableSetDescription);
      mySol = estSol;
      using namespace boost::fusion;

      Scalar maxVal = std::numeric_limits<Scalar>::min(),
          minVal = std::numeric_limits<Scalar>::max(),
          sum = 0;

      if(verbose)
      {
        for(int i=0; i<at_c<yId>(mySol.data).size();  ++i)
        {
          for(int j=0; j<at_c<yId>(mySol.data).coefficients()[i].size; ++j)
          {
            Scalar val = at_c<yId>(mySol.data).coefficients()[i][j];
            sum += val*val;
            if(val > maxVal) maxVal = val;
            if(val < minVal) minVal = val;
          }
        }
        std::cout << "y: minimal value: " << minVal << std::endl;
        std::cout << "y: maximal value: " << maxVal << std::endl;
        std::cout << "y: l2-sum: " << sum << std::endl;
        Scalar tmpMax = maxVal, tmpMin = minVal, tmpSum = sum;
        maxVal = std::numeric_limits<Scalar>::min();
        minVal = std::numeric_limits<Scalar>::max();
        sum = 0;

        for(int i=0; i<at_c<uId>(mySol.data).size();  ++i)
        {
          for(int j=0; j<at_c<uId>(mySol.data).coefficients()[i].size; ++j)
          {
            Scalar val = at_c<uId>(mySol.data).coefficients()[i][0];
            sum += val*val;
            if(val > maxVal) maxVal = val;
            if(val < minVal) minVal = val;
          }
        }
        std::cout << "u: minimal value: " << minVal << std::endl;
        std::cout << "u: maximal value: " << maxVal << std::endl;
        std::cout << "u: l2-sum: " << sum << std::endl;
        tmpMax = std::max(tmpMax,maxVal), tmpMin = std::min(tmpMin,minVal), tmpSum += sum;
        maxVal = std::numeric_limits<Scalar>::min();
        minVal = std::numeric_limits<Scalar>::max();
        sum = 0;

        for(int i=0; i<at_c<pId>(mySol.data).size();  ++i)
        {
          for(int j=0; j<at_c<pId>(mySol.data).coefficients()[i].size; ++j)
          {
            Scalar val = at_c<pId>(mySol.data).coefficients()[i][j];
            sum += val*val;
            if(val > maxVal) maxVal = val;
            if(val < minVal) minVal = val;
          }
        }
        std::cout << "p: minimal value: " << minVal << std::endl;
        std::cout << "p: maximal value: " << maxVal << std::endl;
        std::cout << "p: l2-sum: " << sum << std::endl;

        tmpSum += sum;
        std::cout << "minimal value: " << std::min(tmpMin,minVal) << std::endl;
        std::cout << "maximal value: " << std::max(tmpMax,maxVal) << std::endl;
        std::cout << "l2-sum: " << tmpSum << std::endl;
      }
      // Transfer error indicators to cells.
      auto const& is = extensionSpace.gridManager().grid().leafIndexSet();
      errorDistribution.clear();
      errorDistribution.resize(is.size(0),std::make_pair(0.0,0));
      Scalar errLevel(0.0), minRefine(0.2);

      typedef ErrorDistribution<NormFunctional,ExtensionVariableSetDescription> EnergyError;
      EnergyError energyError(normFunctional,x.get(),mySol);
      typedef VariationalFunctionalAssembler<LinearizationAt<EnergyError> > EnergyErrorAssembler;
      EnergyErrorAssembler eeAssembler(extensionSpace.gridManager(), energyError.getSpaces());

      eeAssembler.assemble(linearization(energyError,x.get()), EnergyErrorAssembler::RHS);
      typename EnergyError::ErrorVector distError( eeAssembler.rhs() );
      typename EnergyError::AnsatzVars::VariableSet mde( energyError.getVariableSetDescription() );
      mde = distError;

      if(verbose)
      {
        std::string name = "errorDistribution_";
        name += boost::lexical_cast<std::string>(step);
        writeVTKFile(mde.descriptions.gridView,mde.descriptions,mde,name);
      }
      // Fehler bzgl L_xx : ( sum_i estSol_i * (L_xx * estSol)_i )^{1/2}  (nur die x - Komponente, nicht p)
      // Lokalisierung: einfachste Idee: v_i = estSol_i * (L_xx * estSol)_i
      // Aufteilen von v_i auf die einzelnen Zellen
      CellIterator cend = extensionVariableSetDescription.gridView.template end<0>();
      for (CellIterator ci=extensionVariableSetDescription.gridView.template begin<0>(); ci!=cend; ++ci)
        errorDistribution[is.index(*ci)] = std::make_pair( fabs(boost::fusion::at_c<0>(mde.data).value(*ci,Dune::FieldVector<Scalar,dim>(0.3))) , is.index(*ci));

      totalErrorSquared = std::accumulate(errorDistribution.begin(), errorDistribution.end(), 0.0, HierarchicErrorEstimator_Detail::add);
    }

    void refineGrid()
    {
      //      int lastIndexForDoubleRefinement = -1;
      std::sort(errorDistribution.begin(), errorDistribution.end(), HierarchicErrorEstimator_Detail::biggerThanAbs);

      Scalar bulkCriterionTolerance = squaredFraction*totalErrorSquared;
      if(verbose)
      {
        std::cout << "ERROR ESTIMATOR: totalErrorSquared: " << totalErrorSquared << std::endl;
        std::cout << "ERROR ESTIMATOR: bulkCriterionTolerance: " << bulkCriterionTolerance << std::endl;
      }
      Scalar bulkErrorSquared = 0;

      std::vector<std::pair<Scalar,size_t> > bulkErrorDistribution;
      auto const& is = extensionSpace.gridManager().grid().leafIndexSet();
      CellIterator cend = extensionVariableSetDescription.gridView.template end<0>();

      while(bulkErrorSquared < bulkCriterionTolerance)
      {
        bulkErrorDistribution.push_back(errorDistribution[bulkErrorDistribution.size()]);
        bulkErrorSquared += bulkErrorDistribution.back().first;
      }

      if(verbose) std::cout << "ERROR ESTIMATOR: number of candidates for refinement: " << bulkErrorDistribution.size() << std::endl;

      //      size_t lastIndex = bulkErrorDistribution.size()-1;
      //      size_t firstIndex = 0;

      /*while(firstIndex+3 < lastIndex)
      {
        if(bulkErrorDistribution.size() > 4)
        {
          Scalar firstContribution = bulkErrorDistribution[firstIndex].first;
          firstContribution *= 15.0/256.0;

          Scalar lastContributions = bulkErrorDistribution[lastIndex--].first;
          lastContributions += bulkErrorDistribution[lastIndex--].first;
          lastContributions += bulkErrorDistribution[lastIndex].first;
          lastContributions *= 15.0/16.0;

          if(lastContributions < firstContribution)
          {
            ++lastIndexForDoubleRefinement;
            ++firstIndex;
            --lastIndex;
            bulkErrorDistribution.erase(bulkErrorDistribution.end()-3, bulkErrorDistribution.end());
          }
          else break;
        }
      }*/

      // Refine mesh.
      for (CellIterator ci=extensionVariableSetDescription.gridView.template begin<0>(); ci!=cend; ++ci) // iterate over cells
      {
        for(int i=0; i<bulkErrorDistribution.size(); ++i) // iterate over chosen part of the error distribution
          if(is.index(*ci) == bulkErrorDistribution[i].second)
          {
            //            if(i <= lastIndexForDoubleRefinement) {
            //              extensionSpace.gridManager().mark(2,*ci);
            //            }
            //            else{
            extensionSpace.gridManager().mark(1,*ci);
            //            }
          }
      }

      extensionSpace.gridManager().adaptAtOnce();
    }

    double estimatedAbsoluteError() const final
        {
      return sqrt(fabs(totalErrorSquared));
        }

    size_t gridSize() const final
        {
      return extensionSpace.gridManager().grid().size(0);
        }

  private:
    Functional& f;
    //    LF& lf;
    //    LVars& lvars;
    NormFunctional& normFunctional;
    ExtensionVariableSetDescription& extensionVariableSetDescription;
    ExtensionSpace& extensionSpace;
    Assembler assembler;
    Scalar squaredFraction;
    Scalar totalErrorSquared;
    std::vector<std::pair<double,int> > errorDistribution;
    bool verbose;
    // AdjustRHS<CoefficientVector> adjustRHS;
  };
}

#endif


#include <Eigen/Dense>
#include <Utils.hpp>

#include "PauliOperator.hpp"
#include "cppmicroservices/BundleActivator.h"
#include "cppmicroservices/BundleContext.h"
#include "cppmicroservices/ServiceProperties.h"
#include "qrt.hpp"
#include "xacc.hpp"
#include "xacc_internal_compiler.hpp"
#include "xacc_service.hpp"
using namespace cppmicroservices;

namespace qcor {
class FTQC : public quantum::QuantumRuntime {
 public:
  virtual void initialize(const std::string kernel_name) override {
    provider = xacc::getIRProvider("quantum");
    qpu = xacc::internal_compiler::qpu;
  }

  void __begin_mark_segment_as_compute() override { mark_as_compute = true; }
  void __end_mark_segment_as_compute() override { mark_as_compute = false; }
  bool isComputeSection() override { return mark_as_compute; }
  const std::string name() const override { return "ftqc"; }
  const std::string description() const override { return ""; }

  virtual void h(const qubit &qidx) override { applyGate("H", {qidx.second}); }
  virtual void x(const qubit &qidx) override { applyGate("X", {qidx.second}); }
  virtual void y(const qubit &qidx) override { applyGate("Y", {qidx.second}); }
  virtual void z(const qubit &qidx) override { applyGate("Z", {qidx.second}); }
  virtual void t(const qubit &qidx) override { applyGate("T", {qidx.second}); }
  virtual void tdg(const qubit &qidx) override {
    applyGate("Tdg", {qidx.second});
  }
  virtual void s(const qubit &qidx) override { applyGate("S", {qidx.second}); }
  virtual void sdg(const qubit &qidx) override {
    applyGate("Sdg", {qidx.second});
  }

  // Common single-qubit, parameterized instructions
  virtual void rx(const qubit &qidx, const double theta) override {
    applyGate("Rx", {qidx.second}, {theta});
  }
  virtual void ry(const qubit &qidx, const double theta) override {
    applyGate("Ry", {qidx.second}, {theta});
  }
  virtual void rz(const qubit &qidx, const double theta) override {
    applyGate("Rz", {qidx.second}, {theta});
  }
  // U1(theta) gate
  virtual void u1(const qubit &qidx, const double theta) override {
    applyGate("U1", {qidx.second}, {theta});
  }
  virtual void u3(const qubit &qidx, const double theta, const double phi,
                  const double lambda) override {
    applyGate("U", {qidx.second}, {theta, phi, lambda});
  }

  virtual void reset(const qubit &qidx) override {
    applyGate("Reset", {qidx.second});
  }

  // Measure-Z
  virtual bool mz(const qubit &qidx) override {
    applyGate("Measure", {qidx.second});
    // Return the measure result stored in the q reg.
    return (*qReg)[qidx.second];
  }

  // Common two-qubit gates.
  virtual void cnot(const qubit &src_idx, const qubit &tgt_idx) override {
    applyGate("CNOT", {src_idx.second, tgt_idx.second});
  }
  virtual void cy(const qubit &src_idx, const qubit &tgt_idx) override {
    applyGate("CY", {src_idx.second, tgt_idx.second});
  }
  virtual void cz(const qubit &src_idx, const qubit &tgt_idx) override {
    applyGate("CZ", {src_idx.second, tgt_idx.second});
  }
  virtual void ch(const qubit &src_idx, const qubit &tgt_idx) override {
    applyGate("CH", {src_idx.second, tgt_idx.second});
  }
  virtual void swap(const qubit &src_idx, const qubit &tgt_idx) override {
    applyGate("Swap", {src_idx.second, tgt_idx.second});
  }

  // Common parameterized 2 qubit gates.
  virtual void cphase(const qubit &src_idx, const qubit &tgt_idx,
                      const double theta) override {
    applyGate("CPhase", {src_idx.second, tgt_idx.second}, {theta});
  }
  virtual void crz(const qubit &src_idx, const qubit &tgt_idx,
                   const double theta) override {
    applyGate("CRZ", {src_idx.second, tgt_idx.second}, {theta});
  }

  // exponential of i * theta * H, where H is an Observable pointer
  virtual void exp(qreg q, const double theta,
                   xacc::Observable &H) override { /* TODO */
  }
  virtual void exp(qreg q, const double theta,
                   xacc::Observable *H) override { /* TODO */
  }
  virtual void exp(qreg q, const double theta,
                   std::shared_ptr<xacc::Observable> H) override { /* TODO */
  }

  // Submission API: sanity check that we don't call these API's.
  // e.g. catch high-level code gen errors.
  virtual void submit(xacc::AcceleratorBuffer *buffer) override {
    throw std::runtime_error("FTQC runtime doesn't support submit API.");
  }
  virtual void submit(xacc::AcceleratorBuffer **buffers,
                      const int nBuffers) override {
    throw std::runtime_error("FTQC runtime doesn't support submit API.");
  }

  void general_instruction(std::shared_ptr<xacc::Instruction> inst) override {
    std::vector<double> params;
    for (auto p : inst->getParameters()) {
      params.push_back(p.as<double>());
    }
    applyGate(inst->name(), inst->bits(), params);
  }

  // Some getters for the qcor runtime library.
  virtual void set_current_program(
      std::shared_ptr<xacc::CompositeInstruction> p) override {
    // Nothing to do
  }
  virtual std::shared_ptr<xacc::CompositeInstruction> get_current_program()
      override {
    return nullptr;
  }

  void set_current_buffer(xacc::AcceleratorBuffer *buffer) override {
    qReg = xacc::as_shared_ptr(buffer);
  }

 private:
  // Notes: all gate parameters must be resolved (to double) for FT-QRT
  // execution.
  void applyGate(const std::string &gateName, const std::vector<size_t> &bits,
                 const std::vector<double> &params = {}) {
    std::vector<xacc::InstructionParameter> instParams;
    for (const auto &val : params) {
      instParams.emplace_back(val);
    }
    auto gateInst = provider->createInstruction(gateName, bits, instParams);
    if (mark_as_compute) {
      gateInst->attachMetadata({{"__qcor__compute__segment__", true}});
    }
    qpu->apply(qReg, gateInst);
  }

 private:
  bool mark_as_compute = false;
  std::shared_ptr<xacc::IRProvider> provider;
  std::shared_ptr<xacc::Accelerator> qpu;
  // TODO: eventually, we may want to support an arbitrary number of qubit
  // registers when the FTQC backend can support it.
  std::shared_ptr<xacc::AcceleratorBuffer> qReg;
};
}  // namespace qcor

namespace {
class US_ABI_LOCAL FtqcQRTActivator : public BundleActivator {
 public:
  FtqcQRTActivator() {}
  void Start(BundleContext context) {
    auto xt = std::make_shared<qcor::FTQC>();
    context.RegisterService<quantum::QuantumRuntime>(xt);
  }
  void Stop(BundleContext /*context*/) {}
};
}  // namespace

CPPMICROSERVICES_EXPORT_BUNDLE_ACTIVATOR(FtqcQRTActivator)


/*
  Copyright (c) 2019 Matthew H. Reilly (kb1vc)
  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.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Path.hxx"
#include <boost/format.hpp>
#include <regex>
#include <stdexcept>
#include <iostream>
#include <cmath>
#include <math.h>
#include <limits>

namespace GeoProf {
  void Path::createPath(const Point & from, const Point & to, double step) {
    if(this->size() > 0) this->clear();

    // now estimate the total run.
    double bearing, dmy, distance;
    from.bearingDistanceTo(to, bearing, dmy, distance);

    if(distance > 20.0) {
      from.correctBearingDistanceTo(to, bearing, distance, bearing, distance);
    }

    // size this vector to be pretty close to what we need.
    unsigned long len = static_cast<unsigned long>(100 + distance / step);

    // allocate enough space to get us most of the way there.
    this->reserve(len);

    // register the first point
    this->push_back(from);

    // now iterate from the start to the end.
    // We know to stop when our distance to the "to" point is less
    // than step.
    Point current = from;
    Point next;
    int count = 0;

    for(double path_len = step; path_len < distance; path_len += step) {
      from.stepTo(bearing, path_len, next);
      this->push_back(next);
    }

    // register the last point
    this->push_back(to);
  }
}


//==================================================================================================
/*!
  @file

  @copyright 2015 NumScale SAS
  @copyright 2015 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_INBTRUE_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_INBTRUE_HPP_INCLUDED

#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/zero.hpp>
#include <boost/simd/function/scalar/is_nez.hpp>
#include <boost/dispatch/function/overload.hpp>
#include <boost/config.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  BOOST_DISPATCH_OVERLOAD ( inbtrue_
                          , (typename A0)
                          , bd::cpu_
                          , bd::scalar_< bd::unspecified_<A0> >
                          )
  {
    BOOST_FORCEINLINE std::size_t operator() ( A0 const & a0) const BOOST_NOEXCEPT
    {
      return is_nez(a0) ? One<std::size_t>() : Zero< std::size_t>();
    }
  };
} } }

#endif


//
//  meanie3D-detect
//  cf-algorithms
//
//  Created by Jürgen Lorenz Simon on 5/3/12.
//  Copyright (c) 2012 Jürgen Lorenz Simon. All rights reserved.
//

#if WITH_SHAPELIB

#include <meanie3D/meanie3D.h>

#include <boost/algorithm/string.hpp>
#include <boost/exception/info.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/program_options.hpp>
#include <boost/tokenizer.hpp>

#include <fstream>
#include <exception>
#include <locale>
#include <limits>
#include <map>
#include <netcdf>
#include <set>
#include <stdlib.h>
#include <string>
#include <sstream>
#include <vector>
#include <shapefil.h>

/* ******************************************************** */
/* Namespace includes                                       */
/* ******************************************************** */

using namespace std;
using namespace boost;
using namespace netCDF;
using namespace m3D;
using namespace m3D::utils;

/* ******************************************************** */
/* Defintiions and constants                                */
/* ******************************************************** */

#define DEBUG 0
#define DEBUG_LINES 0
#define DEBUG_LOCAL_SHAPEFILE 0
#define DEBUG_NATIONAL_SHAPEFILE 0

// Feature-space data type
typedef double T;

// Collection for shapefile lines
typedef vector<CoordinateSystem<double>::GridPoint> gp_vec_t;

// Constants

// # of vertical grid vertices
static const size_t NZ = 60;

// Vertical coordinate bounds
float z_min = 0.0f;
float z_max = 14.75f;

// # of horizontal grid vertices (national)
static const size_t NATIONAL_NX = 900;
static const size_t NATIONAL_NY = 900;

// Horizontal bounds (national)
static const float national_x_min = -523.4622f;
static const float national_x_max = 367.5378f;
static const float national_y_min = -4658.645f;
static const float national_y_max = -3759.645f;

// # of horizontal grid vertices (local)
static const size_t LOCAL_NX = 510;
static const size_t LOCAL_NY = 472;

// Horizontal bounds (local)
static const float local_x_min = -366.462f;
static const float local_x_max = -111.962f;
static const float local_y_min = -4340.645f;
static const float local_y_max = -4105.145f;

// shift in grid points (local within national)
int shift_x = (int) ceil((local_x_min - national_x_min));
int shift_y = (int) ceil((local_y_min - national_y_min));

// Marker values for shapefile
static double const x_marks_the_spot = 1.0;
static const double z_fillValue = -9999.0f;

// make compiler happy with this definition
static const double NO_SCALE = numeric_limits<double>::min();

/* ******************************************************** */
/* Global variables                                         */
/* ******************************************************** */

// This is the main source of information
// Note: this data is in [m].
static float gTopography[NATIONAL_NY][NATIONAL_NX];

/* ******************************************************** */
/* Command line parsing                                     */

/* ******************************************************** */

void parse_commmandline(program_options::variables_map vm,
                        NcFile **filePtr,
                        string &source_filename,
                        string &root_directory,
                        vector<NcDim> &dimensions,
                        vector<NcVar> &dimension_variables) {
    if (vm.count("source") == 0) {
        cerr << "Missing 'source' argument" << endl;
        exit(1);
    }

    source_filename = vm["source"].as<string>();
    try {
        root_directory = vm["root"].as<string>();
    } catch (const boost::exception &e) {
        cerr << "Missing 'root' argument" << endl;

        exit(EXIT_FAILURE);;
    }

    // Open NetCDF file
    NcFile *file = NULL;
    try {
        file = new NcFile(source_filename, NcFile::read);
        *filePtr = file;
    } catch (const netCDF::exceptions::NcException &e) {
        cerr << "error opening file '" << source_filename << "' : " << e.what() << endl;
        exit(EXIT_FAILURE);;
    }

    // Extract dimensions
    if (vm.count("dimensions") == 0) {
        cerr << "Missing parameter --dimensions" << endl;
        exit(1);
    }

    // parse dimension list
    typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
    boost::char_separator<char> sep(",");
    tokenizer dim_tokens(vm["dimensions"].as<string>(), sep);
    for (tokenizer::iterator tok_iter = dim_tokens.begin(); tok_iter != dim_tokens.end(); ++tok_iter) {
        const char *name = (*tok_iter).c_str();
        dimensions.push_back(file->getDim(name));
        dimension_variables.push_back(file->getVar(name));
    }
}

/* ******************************************************** */
/* Some helper methods                                      */

/* ******************************************************** */

template<typename T, std::size_t N, std::size_t M>
void
initialize_array(T(&data)[N][M], T initial_value) {
    for (size_t iy = 0; iy < N; iy++)
        for (size_t ix = 0; ix < M; ix++)
            data[iy][ix] = initial_value;
}

template<typename T, std::size_t K, std::size_t N, std::size_t M>
void
initialize_array(T(&data)[K][N][M], T initial_value) {
    for (size_t iz = 0; iz < K; iz++)
        for (size_t iy = 0; iy < N; iy++)
            for (size_t ix = 0; ix < M; ix++)
                data[iz][iy][ix] = initial_value;
}

template<typename T, std::size_t K, std::size_t N, std::size_t M>
void
fill_topography_data_at(T(&data)[K][N][M], int ix, int iy, float z_in_meters) {
    double z_km = z_in_meters / 1000.0f; // [m]->[km]

    int index_z = (int) ceil(z_km / 0.25f);

    if (index_z > NZ) {
        index_z = NZ;
    } else if (index_z < 0) {
        index_z = 0;
    }
    for (size_t iz = 0; iz < NZ; iz++) {
        data[iz][iy][ix] = (iz <= index_z) ? z_in_meters : z_fillValue;
    }
}

template<typename T, std::size_t K, std::size_t N, std::size_t M>
void
mark_topography_data_at(T(&data)[K][N][M], int ix, int iy, float z_in_meters) {
    double z_km = z_in_meters / 1000.0f; // [m]->[km]

    int index_z = (int) ceil(z_km / 0.25f);

    if (index_z > NZ) {
        index_z = NZ;
    } else if (index_z < 0) {
        index_z = 0;
    }

    data[index_z][iy][ix] = 1.0;
}

/* ******************************************************** */
/* Topography                                               */

/* ******************************************************** */

bool read_topography(NcFile &topography_file) {
    bool result = false;

    initialize_array(gTopography, 0.0f);

    if (topography_file.isNull()) {
        cerr << "ERROR: could not open topography file " << topography_file.getName() << endl;
    }
    else {
        NcVar topo = topography_file.getVar("topography_srtm");

        if (topo.isNull()) {
            cerr << "ERROR: topography file " << topography_file.getName() << " has no variable 'topography_srtm'" <<
            endl;
        } else {
            topo.getVar(&gTopography[0][0]);
            result = true;
        }
    }

    return result;
}

void add_local_dimensions(NcFile &topography_file, NcFile &mapfile) {
    nc_redef(mapfile.getId());

    // Local dimensions / variables

    NcDim dx = mapfile.addDim("local_x", LOCAL_NX);
    NcDim dy = mapfile.addDim("local_y", LOCAL_NY);
    NcDim dz = mapfile.addDim("local_z", NZ);

    float delta_x = round(100.0 * (local_x_max - local_x_min) / (float) LOCAL_NX) / 100.0f;
    float delta_y = round(100.0 * (local_y_max - local_y_min) / (float) LOCAL_NY) / 100.0f;
    float delta_z = round(100.0 * (z_max - z_min) / (float) NZ) / 100.0f;

    NcVar x = mapfile.addVar("local_x", "float", "local_x");
    x.putAtt("units", "km");
    x.putAtt("long_name", "projection_x_coordinate, local composite");
    x.putAtt("valid_min", ncFloat, local_x_min);
    x.putAtt("valid_max", ncFloat, local_x_max);

    // y

    NcVar y = mapfile.addVar("local_y", "float", "local_y");
    y.putAtt("units", "km");
    y.putAtt("long_name", "projection_y_coordinate, local composite");
    y.putAtt("valid_min", ncFloat, local_y_min);
    y.putAtt("valid_max", ncFloat, local_y_max);

    // z

    NcVar z = mapfile.addVar("local_z", "float", "local_z");
    z.putAtt("units", "km");
    z.putAtt("long_name", "height above sea level, local composite");
    z.putAtt("valid_min", ncFloat, z_min);
    z.putAtt("valid_max", ncFloat, z_max);

    nc_enddef(mapfile.getId());

    // dim-vars

    float *x_data = (float *) malloc(sizeof(float) * LOCAL_NX);
    for (size_t i = 0; i < LOCAL_NX; i++) {
        x_data[i] = local_x_min + i * delta_x;
    }

    float *y_data = (float *) malloc(sizeof(float) * LOCAL_NY);
    for (size_t i = 0; i < LOCAL_NY; i++) {
        y_data[i] = local_y_min + i * delta_y;
    }

    float *z_data = (float *) malloc(sizeof(float) * NZ);
    for (size_t i = 0; i < NZ; i++) {
        z_data[i] = z_min + i * delta_z;
    }

    try {
        x.putVar(&x_data[0]);
        y.putVar(&y_data[0]);
        z.putVar(&z_data[0]);
    } catch (std::exception &e) {
        cerr << "ERROR:exception " << e.what() << endl;
    }

    delete x_data;
    delete y_data;
    delete z_data;
}

void add_national_dimensions(NcFile &topography_file, NcFile &mapfile) {
    nc_redef(mapfile.getId());

    NcDim dx = mapfile.addDim("national_x", NATIONAL_NX);
    NcDim dy = mapfile.addDim("national_y", NATIONAL_NY);
    NcDim dz = mapfile.addDim("national_z", NZ);

    // x

    NcVar x = mapfile.addVar("national_x", "float", "national_x");
    x.putAtt("units", "km");
    x.putAtt("long_name", "projection_x_coordinate, national composite");
    x.putAtt("valid_min", ncFloat, national_x_min);
    x.putAtt("valid_max", ncFloat, national_x_max);

    // y

    NcVar y = mapfile.addVar("national_y", "float", "national_y");
    y.putAtt("units", "km");
    y.putAtt("long_name", "projection_y_coordinate, national composite");
    y.putAtt("valid_min", ncFloat, national_y_min);
    y.putAtt("valid_max", ncFloat, national_y_max);

    // z

    NcVar z = mapfile.addVar("national_z", "float", "national_z");
    z.putAtt("units", "km");
    z.putAtt("long_name", "height above sea level, national composite");
    z.putAtt("valid_min", ncFloat, z_min);
    z.putAtt("valid_max", ncFloat, z_max);

    nc_enddef(mapfile.getId());

    // read the x/y - data from topography file

    NcVar topo_y = topography_file.getVar("y");
    float *y_data = (float *) malloc(sizeof(float) * netcdf::num_vals(topo_y));
    topo_y.getVar(y_data);

    NcVar topo_x = topography_file.getVar("x");
    float *x_data = (float *) malloc(sizeof(float) * netcdf::num_vals(topo_x));
    topo_x.getVar(x_data);

    // Topo file has no z data, therefore z must be created
    //
    float delta_z = (z_max - z_min) / boost::numeric_cast<float>(NZ);
    float *z_data = (float *) malloc(sizeof(float) * NZ);
    for (size_t iz = 0; iz < NZ; iz++) {
        z_data[iz] = delta_z * boost::numeric_cast<float>(iz);
    }

    try {
        x.putVar(&x_data[0]);
        y.putVar(&y_data[0]);
        z.putVar(&z_data[0]);
    } catch (std::exception &e) {
        cerr << "ERROR:exception " << e.what() << endl;
    }

    free(y_data);
    free(x_data);
    free(z_data);
}

void add_dimensions(NcFile &topography_file, NcFile &mapfile) {
    add_local_dimensions(topography_file, mapfile);
    add_national_dimensions(topography_file, mapfile);
}

void add_local_topography(NcFile &mapfile) {
    nc_redef(mapfile.getId());

    // Create 2D topo variable

    vector<NcDim> dims_2D;
    dims_2D.push_back(mapfile.getDim("local_y"));
    dims_2D.push_back(mapfile.getDim("local_x"));

    NcVar topo_2D = mapfile.addVar("local_topo_2D", ncDouble, dims_2D);
    topo_2D.putAtt("_FillValue", ncDouble, z_fillValue);
    topo_2D.putAtt("units", "m");
    topo_2D.putAtt("long_name", "height above sea level");
    topo_2D.putAtt("valid_min", ncDouble, -163.0);
    topo_2D.putAtt("valid_max", ncDouble, 14750.0f);
    topo_2D.putAtt("scale_factor", ncDouble, 1.0);
    topo_2D.putAtt("add_offset", ncDouble, 0.0);

    // Create 3D topo variable

    vector<NcDim> dims_3D;
    dims_3D.push_back(mapfile.getDim("local_z"));
    dims_3D.push_back(mapfile.getDim("local_y"));
    dims_3D.push_back(mapfile.getDim("local_x"));

    NcVar topo_3D = mapfile.addVar("local_topo_3D", ncDouble, dims_3D);
    topo_3D.putAtt("_FillValue", ncDouble, z_fillValue);
    topo_3D.putAtt("units", "m");
    topo_3D.putAtt("long_name", "height above sea level");
    topo_3D.putAtt("valid_min", ncDouble, -163.0f);
    topo_3D.putAtt("valid_max", ncDouble, 14750.0f);
    topo_3D.putAtt("scale_factor", ncDouble, 1.0);
    topo_3D.putAtt("add_offset", ncDouble, 0.0);

    nc_enddef(mapfile.getId());

    // Write variables

    static double topo_data_2D[LOCAL_NY][LOCAL_NX];
    static double topo_data_3D[NZ][LOCAL_NY][LOCAL_NX];

    // figure out the shift between national and local

    for (size_t iy = 0; iy < LOCAL_NY; iy++) {
        for (size_t ix = 0; ix < LOCAL_NX; ix++) {
            // Read the z value from the topography
            // national composite is offset by dy=318 and dx=157
            // and has twice the resolution

            float z_m = 0.0;
            int count = 0;

            // average the 9 pixels from the national composite
            // around the point

            int ny = shift_y + iy / 2;
            int nx = shift_x + ix / 2;

            for (int national_y = (ny - 1); national_y < (ny + 1); national_y++) {
                for (int national_x = (nx - 1); national_x < (nx + 1); national_x++) {
                    if ((national_x >= shift_x && national_x < NATIONAL_NX) &&
                        (national_y >= shift_y && national_y < NATIONAL_NY)) {
                        z_m += gTopography[national_y][national_x];
                        count++;
                    }
                }
            }

            z_m = z_m / boost::numeric_cast<float>(count);

            // 2D data set
            topo_data_2D[iy][ix] = z_m;

            // 3D data set
            fill_topography_data_at(topo_data_3D, ix, iy, z_m);
        }
    }

    // Write data off

    topo_2D.putVar(&topo_data_2D[0][0]);
    topo_3D.putVar(&topo_data_3D[0][0][0]);

}

void add_national_topography(NcFile &mapfile) {
    nc_redef(mapfile.getId());

    // Create 2D topo variable

    vector<NcDim> dims_2D;
    dims_2D.push_back(mapfile.getDim("national_y"));
    dims_2D.push_back(mapfile.getDim("national_x"));

    vector<NcDim> dims_3D;
    dims_3D.push_back(mapfile.getDim("national_z"));
    dims_3D.push_back(mapfile.getDim("national_y"));
    dims_3D.push_back(mapfile.getDim("national_x"));

    NcVar topo_2D = mapfile.addVar("national_topo_2D", ncDouble, dims_2D);
    topo_2D.putAtt("_FillValue", ncDouble, z_fillValue);
    topo_2D.putAtt("units", "m");
    topo_2D.putAtt("long_name", "height above sea level");
    topo_2D.putAtt("valid_min", ncDouble, -163.0);
    topo_2D.putAtt("valid_max", ncDouble, 14750.0f);
    topo_2D.putAtt("scale_factor", ncDouble, 1.0);
    topo_2D.putAtt("add_offset", ncDouble, 0.0);

    // Create 3D topo variable

    NcVar topo_3D = mapfile.addVar("national_topo_3D", ncDouble, dims_3D);
    topo_3D.putAtt("_FillValue", ncDouble, z_fillValue);
    topo_3D.putAtt("units", "m");
    topo_3D.putAtt("long_name", "height above sea level");
    topo_3D.putAtt("valid_min", ncDouble, -163.0f);
    topo_3D.putAtt("valid_max", ncDouble, 14750.0f);
    topo_3D.putAtt("scale_factor", ncDouble, 1.0);
    topo_3D.putAtt("add_offset", ncDouble, 0.0);

    nc_enddef(mapfile.getId());

    // Write variables

    static double topo_data_2D[NATIONAL_NY][NATIONAL_NX];
    static double topo_data_3D[NZ][NATIONAL_NY][NATIONAL_NX];

    for (size_t iy = 0; iy < NATIONAL_NY; iy++) {
        for (size_t ix = 0; ix < NATIONAL_NX; ix++) {
            // Read the z value from the topography

            double z_m = gTopography[iy][ix];

            topo_data_2D[iy][ix] = z_m;

            double z_km = z_m / 1000.0f; // [m]->[km]

            int index_z = (int) ceil(z_km / 0.25f);

            if (index_z > NZ)
                index_z = NZ;
            else if (index_z < 0)
                index_z = 0;

            for (size_t iz = 0; iz < NZ; iz++) {
                topo_data_3D[iz][iy][ix] = (iz <= index_z) ? z_m : z_fillValue;
            }
        }
    }

    // Write data off

    topo_2D.putVar(&topo_data_2D[0][0]);
    topo_3D.putVar(&topo_data_3D[0][0][0]);
}

/* ******************************************************** */
/* Shapefile data                                           */

/* ******************************************************** */

std::string shape_type(int shapeTypeID) {
    std::string result = "UNKNOWN";

    switch (shapeTypeID) {
        case SHPT_ARC:
            result = "SHPT_ARC";
            break;
        case SHPT_ARCM:
            result = "SHPT_SHPT_ARCM";
            break;
        case SHPT_ARCZ:
            result = "SHPT_ARCZ";
            break;
        case SHPT_MULTIPATCH:
            result = "SHPT_MULTIPATCH";
            break;
        case SHPT_MULTIPOINT:
            result = "SHPT_MULTIPOINT";
            break;
        case SHPT_MULTIPOINTM:
            result = "SHPT_SHPT_MULTIPOINTM";
            break;
        case SHPT_MULTIPOINTZ:
            result = "SHPT_MULTIPOINTZ";
            break;
        case SHPT_POINT:
            result = "SHPT_POINT";
            break;
        case SHPT_POINTM:
            result = "SHPT_POINTM";
            break;
        case SHPT_POINTZ:
            result = "SHPT_POINTZ";
            break;
        case SHPT_POLYGON:
            result = "SHPT_POLYGON";
            break;
        case SHPT_POLYGONM:
            result = "SHPT_POLYGONM";
            break;
        case SHPT_POLYGONZ:
            result = "SHPT_POLYGONZ";
            break;
        case SHPT_NULL:
            result = "SHPT_NULL";
            break;
    }

    return result;
}

template<std::size_t N, std::size_t M>
void
draw_line_in_grid_2D(double (&data)[N][M], gp_vec_t &line_points, NcVar &var) {
    using namespace m3D::utils::vectors;

#if DEBUG_LINES
    cout << "\tdrawing line (" << line_points.size() << " vertices)" << endl;
#endif

    if (line_points.size() < 2) {
#if DEBUG_LINES
        cout << "\tLine consists of only one point. Skipping." << endl;
#endif
        return;
    }

    CoordinateSystem<double>::GridPoint lp;
    bool have_last_point = false;
    gp_vec_t::iterator li;

    size_t j = 0;

    for (li = line_points.begin(); li != line_points.end(); li++) {
        CoordinateSystem<double>::GridPoint p = *li;

        // cout << p << endl;

        if (have_last_point) {
#if DEBUG_LINES
            cout << "\t\tdrawing segment #" << j++ << " from (" << lp[1] << "," << lp[0] << ") "
                    << "to (" << p[1] << "," << p[0] << ")" << endl;
#endif
            // which direction is x going?

            int dx = 0;

            if (lp[1] < p[1])
                dx = 1;
            else if (lp[1] > p[1])
                dx = -1;

            // which direction is y going?

            int dy = 0;

            if (lp[0] < p[0])
                dy = 1;
            else if (lp[0] > p[0])
                dy = -1;

            // on the starting block

            size_t x = lp[1];
            size_t y = lp[0];

#if DEBUG_LINES
            cout << "\t\t\t(" << x << "," << y << ")" << endl;
#endif
            data[y][x] = x_marks_the_spot;

            // go

            while (x != p[1] && y != p[0]) {
                // propose walking x
                vector<int> x_next(2);
                x_next[1] = x + dx;
                x_next[0] = y;

                // propose walking y
                vector<int> y_next(2);
                y_next[1] = x;
                y_next[0] = y + dy;

                // which one gets closer to end?
                double dist_x = vector_norm(p - x_next);
                double dist_y = vector_norm(p - y_next);

                if (dist_x < dist_y) {
                    // walking x gets us closer
                    x = x + dx;
                } else if (dist_x > dist_y) {
                    // walking y gets us closer
                    y = y + dy;
                } else {
                    // same difference, so walk both
                    x = x + dx;
                    y = y + dy;
                }

                // X marks the spot
                data[y][x] = x_marks_the_spot;
#if DEBUG_LINES
                cout << "\t\t\t(" << x << "," << y << ")" << endl;
#endif
            }
        }

        lp = p;
        have_last_point = true;
    }
}

/** Add arcs from a file provided by the following website:
 * http://www.naturalearthdata.com
 *
 * This method expects the dimensions and dimension vars
 * to be in place
 */
void add_shapefile_data(NcFile &mapfile, const char *shapefile, const char *variable_name) {
    SHPHandle file = SHPOpen(shapefile, "rb");

    if (!file) {
        cerr << "ERROR:can't open " << shapefile << endl;
        return;
    }

    // Obtain NetCDF data and create river variable

    vector<NcDim> dimensions_2D;
    dimensions_2D.push_back(mapfile.getDim("national_y"));
    dimensions_2D.push_back(mapfile.getDim("national_x"));

    vector<NcDim> dimensions_3D;
    dimensions_3D.push_back(mapfile.getDim("national_z"));
    dimensions_3D.push_back(mapfile.getDim("national_y"));
    dimensions_3D.push_back(mapfile.getDim("national_x"));

    vector<NcDim> local_dimensions_2D;
    local_dimensions_2D.push_back(mapfile.getDim("local_y"));
    local_dimensions_2D.push_back(mapfile.getDim("local_x"));

    vector<NcDim> local_dimensions_3D;
    local_dimensions_3D.push_back(mapfile.getDim("local_z"));
    local_dimensions_3D.push_back(mapfile.getDim("local_y"));
    local_dimensions_3D.push_back(mapfile.getDim("local_x"));

    vector<NcVar> variables_2D;
    variables_2D.push_back(mapfile.getVar("national_y"));
    variables_2D.push_back(mapfile.getVar("national_x"));
    CoordinateSystem<double> *cs_2D = new CoordinateSystem<double>(dimensions_2D, variables_2D);

    // Need to go into definition mode because of a
    // bug in the c++ NetCDF interface

    nc_redef(mapfile.getId());

    // Create river variable

    std::string varname_2D = "national_" + std::string(variable_name) + "_2D";
    std::string varname_3D = "national_" + std::string(variable_name) + "_3D";

    NcVar shapevar_2D = mapfile.addVar(varname_2D, ncFloat, dimensions_2D);
    shapevar_2D.putAtt("_FillValue", ncFloat, z_fillValue);
    shapevar_2D.putAtt("units", "km");
    shapevar_2D.putAtt("long_name", varname_2D);
    shapevar_2D.putAtt("valid_min", ncFloat, 0.0);
    shapevar_2D.putAtt("valid_max", ncFloat, x_marks_the_spot);
    shapevar_2D.putAtt("scale_factor", ncFloat, 1.0);
    shapevar_2D.putAtt("add_offset", ncFloat, 0.0);

    NcVar shapevar_3D = mapfile.addVar(varname_3D, ncFloat, dimensions_3D);
    shapevar_3D.putAtt("_FillValue", ncFloat, z_fillValue);
    shapevar_3D.putAtt("units", "km");
    shapevar_3D.putAtt("long_name", varname_3D);
    shapevar_3D.putAtt("valid_min", ncFloat, 0.0);
    shapevar_3D.putAtt("valid_max", ncFloat, x_marks_the_spot);
    shapevar_3D.putAtt("scale_factor", ncFloat, 1.0);
    shapevar_3D.putAtt("add_offset", ncFloat, 0.0);

    std::string local_varname_2D = "local_" + std::string(variable_name) + "_2D";
    std::string local_varname_3D = "local_" + std::string(variable_name) + "_3D";

    NcVar local_shapevar_2D = mapfile.addVar(local_varname_2D, ncFloat, local_dimensions_2D);
    local_shapevar_2D.putAtt("_FillValue", ncFloat, z_fillValue);
    local_shapevar_2D.putAtt("units", "km");
    local_shapevar_2D.putAtt("long_name", local_varname_2D);
    local_shapevar_2D.putAtt("valid_min", ncFloat, 0.0);
    local_shapevar_2D.putAtt("valid_max", ncFloat, x_marks_the_spot);
    local_shapevar_2D.putAtt("scale_factor", ncFloat, 1.0);
    local_shapevar_2D.putAtt("add_offset", ncFloat, 0.0);

    NcVar local_shapevar_3D = mapfile.addVar(local_varname_3D, ncFloat, local_dimensions_3D);
    local_shapevar_3D.putAtt("_FillValue", ncFloat, z_fillValue);
    local_shapevar_3D.putAtt("units", "km");
    local_shapevar_3D.putAtt("long_name", local_varname_3D);
    local_shapevar_3D.putAtt("valid_min", ncFloat, 0.0);
    local_shapevar_3D.putAtt("valid_max", ncFloat, x_marks_the_spot);
    local_shapevar_3D.putAtt("scale_factor", ncFloat, 1.0);
    local_shapevar_3D.putAtt("add_offset", ncFloat, 0.0);

    nc_enddef(mapfile.getId());

    vector<NcVar> local_variables_2D;
    local_variables_2D.push_back(mapfile.getVar("local_y"));
    local_variables_2D.push_back(mapfile.getVar("local_x"));
    CoordinateSystem<double> *local_cs_2D = new CoordinateSystem<double>(local_dimensions_2D, local_variables_2D);

    // allocate data and initialize

    static double data_2D[NATIONAL_NY][NATIONAL_NX];
    initialize_array(data_2D, z_fillValue);

    static double local_data_2D[NATIONAL_NY][NATIONAL_NX];
    initialize_array(local_data_2D, z_fillValue);

    // Iterate over the content of the shapefile

    // Radolan coordinate system
    RDCoordinateSystem rcs(RD_RX);

    for (int i = 0; i < file->nRecords; i++) {
        SHPObject *obj = SHPReadObject(file, i);

#if DEBUG
        cout << "Object #" << i << endl;
        cout << "\tnumber of parts: " << obj->nParts << endl;
        cout << "\tpart indexes: ";
        cfa::utils::array::print_array(obj->panPartStart, obj->nParts);
        cout << endl;
        cout << "\tnumber of vertices: " << obj->nVertices << endl;
        cout << "\tshape type:" << shape_type(obj->nSHPType) << endl;
#endif

        CoordinateSystem<double>::Coordinate coord = cs_2D->newCoordinate();

        CoordinateSystem<double>::GridPoint gp = cs_2D->newGridPoint();
        CoordinateSystem<double>::GridPoint local_gp = local_cs_2D->newGridPoint();

        for (int pi = 0; pi < obj->nParts; pi++) {
            gp_vec_t line_points;
            gp_vec_t local_line_points;

            int start_vertex = obj->panPartStart[pi];
            int end_vertex = (pi < (obj->nParts - 1)) ? (obj->panPartStart[pi + 1] - 1) : (obj->nVertices - 1);

#if DEBUG
            cout << "\tpart #" << pi << "vertices [" << start_vertex << " ... " << end_vertex << "]" << endl;
#endif

            for (int vi = start_vertex; vi <= end_vertex; vi++) {
                // Assume at this point that the original
                // projection/coordinate system is WGS84

                RDGeographicalPoint geographical_coordinate = rdGeographicalPoint(obj->padfX[vi], obj->padfY[vi]);

                // project to cartesian system
                RDCartesianPoint cartesian = rcs.cartesianCoordinate(geographical_coordinate);

                coord[0] = cartesian.y;
                coord[1] = cartesian.x;

                // national coordinate system

                try {
                    // look the grid point up from the file's coordinate system
                    cs_2D->reverse_lookup(coord, gp);

#if DEBUG_NATIONAL_SHAPEFILE
                    cout << "\t\tvertice #" << vi << " "
                            << " (lon,lat): (" << obj->padfX[vi] << "," << obj->padfY[vi] << ")"
                            << " (x,y): (" << cartesian.x << "," << cartesian.y << ")"
                            << " (i,j): (" << gp[1] << "," << gp[0] << ")"
                            << endl;
#endif

                    // if it's within bounds, add it
                    // line_points.insert(gp);
                    line_points.push_back(gp);
                } catch (std::out_of_range &e) {
                    // not within bounds.

                    if (!line_points.empty()) {
                        // dropped out of the grid in the middle of a segment, eh?
                        // Draw what we got and clear the line
                        draw_line_in_grid_2D(data_2D, line_points, shapevar_2D);

                        line_points.clear();
#if DEBUG
                        shapevar_2D.putVar(&data_2D[0][0]);
#endif
                    }
                }

                // local coordinate system

                try {
                    // look the grid point up from the file's coordinate system

                    local_cs_2D->reverse_lookup(coord, local_gp);

#if DEBUG_LOCAL_SHAPEFILE
                    cout << "\t\tvertice #" << vi << " "
                            << " (lon,lat): (" << obj->padfX[vi] << "," << obj->padfY[vi] << ")"
                            << " (x,y): (" << cartesian.x << "," << cartesian.y << ")"
                            << " (i,j): (" << local_gp[1] << "," << local_gp[0] << ")"
                            << endl;
#endif

                    // if it's within bounds, add it
                    // line_points.insert(gp);
                    local_line_points.push_back(local_gp);
                } catch (std::out_of_range &e) {
                    // not within bounds.

                    if (!local_line_points.empty()) {
                        // dropped out of the grid in the middle of a segment, eh?
                        // Draw what we got and clear the line
                        draw_line_in_grid_2D(local_data_2D, local_line_points, local_shapevar_2D);

                        local_line_points.clear();
#if DEBUG
                        local_shapevar_2D.putVar(&local_data_2D[0][0]);
#endif
                    }
                }
            }

            if (!line_points.empty()) {
                draw_line_in_grid_2D(data_2D, line_points, shapevar_2D);
#if DEBUG
                shapevar_2D.putVar(&data_2D[0][0]);
#endif
            }

            // Draw the line ...

            if (!local_line_points.empty()) {
                draw_line_in_grid_2D(local_data_2D, local_line_points, local_shapevar_2D);
#if DEBUG
                local_shapevar_2D.putVar(&local_data_2D[0][0]);
#endif
            }
        }

        SHPDestroyObject(obj);
    }

    // The 3D version is basically the same as the 2D version, only
    // the value is only set at the evelation of the topography file

    static double data_3D[NZ][NATIONAL_NY][NATIONAL_NX];
    initialize_array(data_3D, z_fillValue);

    for (size_t iy = 0; iy < NATIONAL_NY; iy++) {
        for (size_t ix = 0; ix < NATIONAL_NY; ix++) {
            if (data_2D[iy][ix] != z_fillValue) {
                // 3D data set
                //fill_topography_data_at(data_3D, ix, iy, data_2D[iy][ix]);
                mark_topography_data_at(data_3D, ix, iy, data_2D[iy][ix]);
            }
        }
    }

    // local

    static double local_data_3D[NZ][LOCAL_NY][LOCAL_NX];
    initialize_array(local_data_3D, z_fillValue);

    for (size_t iy = 0; iy < LOCAL_NY; iy++) {
        for (size_t ix = 0; ix < LOCAL_NY; ix++) {
            if (local_data_2D[iy][ix] != z_fillValue) {
                // 3D data set
                //fill_topography_data_at(local_data_3D, ix, iy, local_data_2D[iy][ix]);
                mark_topography_data_at(local_data_3D, ix, iy, local_data_2D[iy][ix] + 500.0);
            }
        }
    }

    // Write data off

    shapevar_2D.putVar(&data_2D[0][0]);
    shapevar_3D.putVar(&data_3D[0][0][0]);

    local_shapevar_2D.putVar(&local_data_2D[0][0]);
    local_shapevar_3D.putVar(&local_data_3D[0][0][0]);
}

void do_it() {
    // home
    const char *topo_file = "/Users/simon/Projects/Meteo/Ertel/data/maps/mapstuff/oase-georef-1km-germany-2d-v01b.nc";
    const char *river_shapefile = "/Users/simon/Projects/Meteo/Ertel/data/maps/www.naturalearthdata.com/ne_10m_rivers_lake_centerlines/ne_10m_rivers_lake_centerlines.shp";
    const char *boundaries_shapefile = "/Users/simon/Projects/Meteo/Ertel/data/maps/www.naturalearthdata.com/ne_10m_admin_0_countries/ne_10m_admin_0_countries.shp";

    // institute
    //    const char *topo_file =             "/home/Projects/Ertel/data/maps/mapstuff/oase-georef-1km-germany-2d-v01b.nc";
    //    const char *river_shapefile =       "/home/Projects/Ertel/data/maps/www.naturalearthdata.com/ne_10m_rivers_lake_centerlines/ne_10m_rivers_lake_centerlines.shp";
    //    const char *boundaries_shapefile =  "/home/Projects/Ertel/data/maps/www.naturalearthdata.com/ne_10m_admin_0_countries/ne_10m_admin_0_countries.shp";

    cout << "Reading topography data ... ";
    NcFile topography_file(topo_file, NcFile::read);
    if (topography_file.isNull()) {
        cerr << "ERROR: could not read topography file " << topo_file << endl;
        return;
    }
    read_topography(topography_file);
    cout << "done." << endl;

    cout << "Creating mapfile ...";
    NcFile mapfile("/Users/simon/Projects/Meteo/Ertel/data/maps/mapstuff/oase-mapdata.nc", NcFile::replace,
                   NcFile::classic);
    mapfile.putAtt("conventions", "CF-1.6");
    mapfile.putAtt("authors", "Jürgen Simon, Malte Diederich");
    mapfile.putAtt("created", "Oct 5th 2013 18:25:00 CET");
    mapfile.putAtt("version", "1.0");
    cout << "done." << endl;

    cout << "Adding dimensions and dimension variables to mapfile ... ";
    add_dimensions(topography_file, mapfile);
    cout << "done." << endl;

    cout << "Adding local topography data ... ";
    add_local_topography(mapfile);
    cout << "done." << endl;

    cout << "Adding national topography data ... ";
    add_national_topography(mapfile);
    cout << "done." << endl;

    cout << "Adding national shapefile data ... ";
    cout << " (rivers";
    add_shapefile_data(mapfile, river_shapefile, "rivers");
    cout << ",boundaries";
    add_shapefile_data(mapfile, boundaries_shapefile, "boundaries");
    cout << ") done." << endl;
}

int main(int argc, char **argv) {
    try {
        do_it();
    } catch (std::exception &e) {
        cerr << "ERROR:" << e.what() << endl;
    }

    return 0;
};

#else

#include <iostream>
int main(int argc, char **argv) {
    std::cerr << "Component disabled due to missing shapelib" << std::endl;
    return 0;
};

#endif


//==================================================================================================
/**
  Copyright 2015 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_BITSET_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_BITSET_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/detail/assert_utils.hpp>
#include <boost/simd/meta/cardinal_of.hpp>
#include <boost/simd/function/shift_left.hpp>
#include <boost/simd/function/dec.hpp>
#include <boost/simd/constant/one.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  namespace bs = boost::simd;

  BOOST_DISPATCH_OVERLOAD( bitset_
                            , (typename A0, typename A1)
                            , bd::cpu_
                            , bd::scalar_< bd::arithmetic_<A0> >
                            , bd::scalar_< bd::integer_<A1> >
                            )
 {
    BOOST_FORCEINLINE A0 operator()(A0 a0, A1 a1) const BOOST_NOEXCEPT
    {
      using i_t = bd::as_integer_t<A0, unsigned>;
      BOOST_ASSERT_MSG(assert_good_shift<A0>(dec(a1)), "bitset :index is out of range");

      return bitwise_or(a0, shift_left(One<i_t>(), dec(a1)));
    }
  };
} } }

#endif


#define BOOST_TEST_MODULE sparse_distribued_representation_test_module
#include <boost/test/included/unit_test.hpp>
#include "SparseDistributedRepresentation.hpp"

BOOST_AUTO_TEST_SUITE( sdr )

BOOST_AUTO_TEST_CASE( andop_simple ) {
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3} & SDR<>{2, 3, 4}), (SDR<>{2, 3}));
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3} && SDR<>{2, 3, 4}), 2);
}

BOOST_AUTO_TEST_CASE( andop_range_simple ) {
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3, 5, 20}.andb(2, 7)), (SDR<>{2, 3, 5}));
}

BOOST_AUTO_TEST_CASE( andop_positions_simple ) {
    BOOST_REQUIRE((SDR<>{1, 2, 3, 99}.andp(SDR<>{2, 99})) == (std::vector<SDR<>::size_type>{1, 3}));
}

BOOST_AUTO_TEST_CASE( andop_inplace_simple ) {
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3, 99} &= SDR<>{0, 1, 2, 99, 100}), (SDR<>{1, 2, 99}));
}

BOOST_AUTO_TEST_CASE( orop_simple ) {
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3} | SDR<>{2, 3, 4}), (SDR<>{1, 2, 3, 4}));
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3} || SDR<>{2, 3, 4}), 4);
}

BOOST_AUTO_TEST_CASE( orop_inplace_simple ) {
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3, 99} |= SDR<>{0, 1, 2, 99, 100}), (SDR<>{0, 1, 2, 3, 99, 100}));
}

BOOST_AUTO_TEST_CASE( xorop_simple ) {
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3} ^ SDR<>{2, 3, 4}), (SDR<>{1, 4}));
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3} / SDR<>{2, 3, 4}), 2);
}

BOOST_AUTO_TEST_CASE( xorop_inplace_simple ) {
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3, 99} ^= SDR<>{0, 1, 2, 99, 100}), (SDR<>{0, 3, 100}));
}

BOOST_AUTO_TEST_CASE( rm_simple ) {
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3, 99} - SDR<>{0, 1, 2, 99, 100}), (SDR<>{3}));
}

BOOST_AUTO_TEST_CASE( separate_simple ) {
    SDR<> a{1, 2, 3, 4};
    SDR<> b{3, 4, 5, 6};
    SDR<>::separate(a, b);
    BOOST_REQUIRE_EQUAL(a, (SDR<>{1, 2}));
    BOOST_REQUIRE_EQUAL(b, (SDR<>{5, 6}));
}

BOOST_AUTO_TEST_CASE( test_encode ) {
    BOOST_REQUIRE_EQUAL(SDR<>(0, 3, 100), (SDR<>{0, 1, 2}));
    BOOST_REQUIRE_EQUAL(SDR<>(0.5, 3, 100), (SDR<>{49, 50, 51}));
    BOOST_REQUIRE_EQUAL(SDR<>(1, 3, 100), (SDR<>{97, 98, 99}));
}

BOOST_AUTO_TEST_CASE( test_encode_periodic ) {
    std::mt19937 random_number(time(NULL) * getpid());
    static constexpr int DENSE_LENGTH = 100;
    static constexpr int SPARSE_LENGTH = 3;
    float rand_input_0 = (double)(random_number() % 10) / 10;
    int rand_period = random_number() % 9 + 1;
    float rand_input_1 = rand_input_0 + rand_period * (random_number() % 7);

    // there's a case where the input is directly between two representations.
    float progress = rand_input_1 / rand_period * DENSE_LENGTH;
    progress -= (int)progress;
    if (std::abs(progress - 0.5) < 0.0001) {
        rand_input_0 += 0.0001;
        rand_input_1 += 0.0001;
    }

    SDR<> a(rand_input_0, rand_period, SPARSE_LENGTH, DENSE_LENGTH);
    SDR<> b(rand_input_1, rand_period, SPARSE_LENGTH, DENSE_LENGTH);
    BOOST_REQUIRE_EQUAL(a, b);
}

struct LoadedType {
    int index;
    int data;
    friend std::ostream& operator<<(std::ostream&, const LoadedType&);
    bool operator<(const LoadedType& l) const { return index < l.index; }
    bool operator==(const LoadedType& l) const { return index == l.index; }
    operator decltype(index)() const { return index; }
};

std::ostream& operator<<(std::ostream& os, const LoadedType& l) {
    os << l.index;
    return os;
}

BOOST_AUTO_TEST_CASE( test_loaded_types ) {
    SDR<LoadedType> loaded;
    loaded.push_back(LoadedType{0, 2});
    loaded.push_back(LoadedType{1, 1});
    loaded.push_back(LoadedType{2, 0});
    SDR<decltype(LoadedType::index)> selection{0};

    SDR<LoadedType> and_result = loaded.andb(selection);
    BOOST_REQUIRE_EQUAL(and_result[0].data, 2);

    SDR<LoadedType> rm_result = loaded.rmb(selection);
    BOOST_REQUIRE_EQUAL(rm_result[0].data, 1);
    BOOST_REQUIRE_EQUAL(rm_result[1].data, 0);

    SDR<LoadedType> rmi_result = SDR<LoadedType>(loaded).rmi(selection);
    BOOST_REQUIRE_EQUAL(rmi_result[0].data, 1);
    BOOST_REQUIRE_EQUAL(rmi_result[1].data, 0);
}

BOOST_AUTO_TEST_CASE( test_comparison ) {
    BOOST_REQUIRE_EQUAL((SDR<>{1, 2, 3}), (SDR<>{1, 2, 3}));
    BOOST_REQUIRE_NE((SDR<>{0, 2, 3}), (SDR<>{1, 2, 3}));
    BOOST_REQUIRE_LT((SDR<>{0, 2, 3}), (SDR<>{1, 2, 3}));
    BOOST_REQUIRE_GT((SDR<>{4}), (SDR<>{0, 1, 2}));
}

BOOST_AUTO_TEST_SUITE_END()


// Copyright (c) 2018-2020 The PIVX developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "test_pivx.h"

#include "budget/budgetmanager.h"
#include "masternode-payments.h"
#include "masternode-sync.h"
#include "spork.h"
#include "tinyformat.h"
#include "utilmoneystr.h"
#include "validation.h"

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(budget_tests, TestingSetup)

void CheckBudgetValue(int nHeight, std::string strNetwork, CAmount nExpectedValue)
{
    CBudgetManager budget;
    CAmount nBudget = g_budgetman.GetTotalBudget(nHeight);
    std::string strError = strprintf("Budget is not as expected for %s. Result: %s, Expected: %s", strNetwork, FormatMoney(nBudget), FormatMoney(nExpectedValue));
    BOOST_CHECK_MESSAGE(nBudget == nExpectedValue, strError);
}

BOOST_AUTO_TEST_CASE(budget_value)
{
    SelectParams(CBaseChainParams::TESTNET);
    int nHeightTest = Params().GetConsensus().vUpgrades[Consensus::UPGRADE_ZC_V2].nActivationHeight + 1;
    CheckBudgetValue(nHeightTest-1, "testnet", 7200*COIN);
    CheckBudgetValue(nHeightTest, "testnet", 144*COIN);

    SelectParams(CBaseChainParams::MAIN);
    nHeightTest = Params().GetConsensus().vUpgrades[Consensus::UPGRADE_ZC_V2].nActivationHeight + 1;
    CheckBudgetValue(nHeightTest, "mainnet", 43200*COIN);
}

BOOST_AUTO_TEST_CASE(block_value)
{
    SelectParams(CBaseChainParams::TESTNET);
    std::string strError;

    // force mnsync complete
    masternodeSync.RequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED;

    // enable SPORK_13
    int64_t nTime = GetTime() - 10;
    const CSporkMessage& spork = CSporkMessage(SPORK_13_ENABLE_SUPERBLOCKS, nTime + 1, nTime);
    sporkManager.AddOrUpdateSporkMessage(spork);
    BOOST_CHECK(sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS));

    // regular block
    int nHeight = 100;
    const CAmount nBlockReward = GetBlockValue(nHeight);
    CAmount nExpectedRet = nBlockReward;
    CAmount nBudgetAmtRet = 0;
    BOOST_CHECK(IsBlockValueValid(nHeight, nExpectedRet, nBlockReward-1, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, 0);
    BOOST_CHECK(IsBlockValueValid(nHeight, nExpectedRet, nBlockReward, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, 0);
    BOOST_CHECK(!IsBlockValueValid(nHeight, nExpectedRet, nBlockReward+1, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, 0);

    // superblock - create the finalized budget with a proposal, and vote on it
    nHeight = 144;
    const CTxIn mnVin(GetRandHash(), 0);
    const CScript payee = GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))));
    const CAmount propAmt = 100 * COIN;
    const uint256& propHash = GetRandHash(), finTxId = GetRandHash();
    const CTxBudgetPayment txBudgetPayment(propHash, payee, propAmt);
    CFinalizedBudget fin("main (test)", 144, {txBudgetPayment}, finTxId);
    const CFinalizedBudgetVote fvote(mnVin, fin.GetHash());
    BOOST_CHECK(fin.AddOrUpdateVote(fvote, strError));
    g_budgetman.ForceAddFinalizedBudget(fin.GetHash(), fin.GetFeeTXHash(), fin);

    // check superblock's block-value
    nExpectedRet = nBlockReward;
    nBudgetAmtRet = 0;
    BOOST_CHECK(IsBlockValueValid(nHeight, nExpectedRet, nBlockReward, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward + propAmt);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, propAmt);
    nExpectedRet = nBlockReward;
    nBudgetAmtRet = 0;
    BOOST_CHECK(IsBlockValueValid(nHeight, nExpectedRet, nBlockReward+propAmt-1, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward + propAmt);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, propAmt);
    nExpectedRet = nBlockReward;
    nBudgetAmtRet = 0;
    BOOST_CHECK(IsBlockValueValid(nHeight, nExpectedRet, nBlockReward+propAmt, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward + propAmt);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, propAmt);
    nExpectedRet = nBlockReward;
    nBudgetAmtRet = 0;
    BOOST_CHECK(!IsBlockValueValid(nHeight, nExpectedRet, nBlockReward+propAmt+1, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward + propAmt);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, propAmt);

    // disable SPORK_13
    const CSporkMessage& spork2 = CSporkMessage(SPORK_13_ENABLE_SUPERBLOCKS, 4070908800ULL, nTime + 1);
    sporkManager.AddOrUpdateSporkMessage(spork2);
    BOOST_CHECK(!sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS));

    // check with spork disabled
    nExpectedRet = nBlockReward;
    nBudgetAmtRet = 0;
    BOOST_CHECK(IsBlockValueValid(nHeight, nExpectedRet, nBlockReward, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, 0);
    BOOST_CHECK(!IsBlockValueValid(nHeight, nExpectedRet, nBlockReward+propAmt-1, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, 0);
    BOOST_CHECK(!IsBlockValueValid(nHeight, nExpectedRet, nBlockReward+propAmt, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, 0);
    BOOST_CHECK(!IsBlockValueValid(nHeight, nExpectedRet, nBlockReward+propAmt+1, nBudgetAmtRet));
    BOOST_CHECK_EQUAL(nExpectedRet, nBlockReward);
    BOOST_CHECK_EQUAL(nBudgetAmtRet, 0);
}

static CScript GetRandomP2PKH()
{
    CKey key;
    key.MakeNewKey(false);
    return GetScriptForDestination(key.GetPubKey().GetID());
}

static CMutableTransaction NewCoinBase(int nHeight, CAmount cbaseAmt, const CScript& cbaseScript)
{
    CMutableTransaction tx;
    tx.vout.emplace_back(cbaseAmt, cbaseScript);
    tx.vin.emplace_back();
    tx.vin[0].scriptSig = CScript() << nHeight << OP_0;
    return tx;
}

BOOST_AUTO_TEST_CASE(IsCoinbaseValueValid_test)
{
    const CAmount mnAmt = GetMasternodePayment();
    const CScript& cbaseScript = GetRandomP2PKH();
    CValidationState state;

    // force mnsync complete
    masternodeSync.RequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED;

    // -- Regular blocks

    // Exact
    CMutableTransaction cbase = NewCoinBase(1, mnAmt, cbaseScript);
    BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state));
    cbase.vout[0].nValue /= 2;
    cbase.vout.emplace_back(cbase.vout[0]);
    BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state));

    // Underpaying with SPORK_8 disabled (good)
    cbase.vout.clear();
    cbase.vout.emplace_back(mnAmt - 1, cbaseScript);
    BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state));
    cbase.vout[0].nValue = mnAmt/2;
    cbase.vout.emplace_back(cbase.vout[0]);
    cbase.vout[1].nValue = mnAmt/2 - 1;
    BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state));

    // Overpaying with SPORK_8 disabled
    cbase.vout.clear();
    cbase.vout.emplace_back(mnAmt + 1, cbaseScript);
    BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state));
    BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt-spork8-disabled");
    state = CValidationState();
    cbase.vout[0].nValue = mnAmt/2;
    cbase.vout.emplace_back(cbase.vout[0]);
    cbase.vout[1].nValue = mnAmt/2 + 1;
    BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state));
    BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt-spork8-disabled");
    state = CValidationState();

    // enable SPORK_8
    int64_t nTime = GetTime() - 10;
    const CSporkMessage& spork = CSporkMessage(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT, nTime + 1, nTime);
    sporkManager.AddOrUpdateSporkMessage(spork);
    BOOST_CHECK(sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT));

    // Underpaying with SPORK_8 enabled
    cbase.vout.clear();
    cbase.vout.emplace_back(mnAmt - 1, cbaseScript);
    BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state));
    BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt");
    state = CValidationState();
    cbase.vout[0].nValue = mnAmt/2;
    cbase.vout.emplace_back(cbase.vout[0]);
    cbase.vout[1].nValue = mnAmt/2 - 1;
    BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state));
    BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt");
    state = CValidationState();

    // Overpaying with SPORK_8 enabled
    cbase.vout.clear();
    cbase.vout.emplace_back(mnAmt + 1, cbaseScript);
    BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state));
    BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt");
    state = CValidationState();
    cbase.vout[0].nValue = mnAmt/2;
    cbase.vout.emplace_back(cbase.vout[0]);
    cbase.vout[1].nValue = mnAmt/2 + 1;
    BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), 0, state));
    BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-cb-amt");
    state = CValidationState();

    const CAmount budgAmt = 200 * COIN;

    // -- Superblocks

    // Exact
    cbase.vout.clear();
    cbase.vout.emplace_back(budgAmt, cbaseScript);
    BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state));
    cbase.vout[0].nValue /= 2;
    cbase.vout.emplace_back(cbase.vout[0]);
    BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state));

    // Underpaying
    cbase.vout.clear();
    cbase.vout.emplace_back(budgAmt - 1, cbaseScript);
    BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state));
    BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt");
    state = CValidationState();
    cbase.vout[0].nValue = budgAmt/2;
    cbase.vout.emplace_back(cbase.vout[0]);
    cbase.vout[1].nValue = budgAmt/2 - 1;
    BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state));
    BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt");
    state = CValidationState();

    // Overpaying
    cbase.vout.clear();
    cbase.vout.emplace_back(budgAmt + 1, cbaseScript);
    BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state));
    BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt");
    state = CValidationState();
    cbase.vout[0].nValue = budgAmt/2;
    cbase.vout.emplace_back(cbase.vout[0]);
    cbase.vout[1].nValue = budgAmt/2 + 1;
    BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state));
    BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt");

}

BOOST_AUTO_TEST_SUITE_END()


#include "WFCDungeonGenInterface.h"
#include "WaveFunctionCollapse.h"
#include "Map.h"
#include "DRandom.h"
#include <boost/filesystem.hpp>

extern Map m;

namespace WFC
{

WFCDungeonGenInterface::WFCDungeonGenInterface(std::string_view load_disk_path)
{
	std::string patterns_path = std::string(load_disk_path) + "\\Patterns";
	boost::filesystem::directory_iterator end_iter;
	for (boost::filesystem::directory_iterator itr(patterns_path); itr != end_iter; itr++)
	{
		if (boost::filesystem::is_regular_file(itr->path()) && boost::filesystem::extension(itr->path()) == ".json")
		{
			pattern_paths.push_back(itr->path().string());
		}
	}
}

int WFCDungeonGenInterface::Place(int x, int y, int width, int height)
{
	const std::string& path = pattern_paths[DRandom::instance->GetInt(0, pattern_paths.size() - 1)];

	int placed = 0;
	//Could alternatively use the Map to fill up WFC output with pre-collapsed values
	WaveFunctionCollapse wfc(path, width, height);
	auto output = wfc.Run();
	for (int j = y; j < y + height; j++)
	{
		for (int i = x; i < x + width; i++)
		{
			if (m.Get(i, j).type == TileType::Nothing)
			{
				auto outputTile = output[j - y][i - x];
				if (outputTile == WFCTileType::Wall || outputTile == WFCTileType::Asteroid)
				{
					m.Set(i, j, Tile(TileType::WFCWall));
				}
				else if (outputTile == WFCTileType::Empty || outputTile == WFCTileType::Floor || outputTile == WFCTileType::NonRoomFloor)
				{
					m.Set(i, j, Tile(TileType::WFCFloor));
				}

				placed++;
			}
		}
	}

	return placed;
}

int WFCDungeonGenInterface::PlaceConstantVisualisation(int x, int y, int width, int height)
{
	const std::string& path = pattern_paths[DRandom::instance->GetInt(0, pattern_paths.size() - 1)];

	//Could alternatively use the Map to fill up WFC output with pre-collapsed values
	WaveFunctionCollapse wfc(path, width, height);

	int drawStep = 0;

	auto apply_to_map =
		[&](const boost::multi_array<WFCTileType::WFCTile, 2>& wfc)
	{
		int placed = 0;
		for (int j = y; j < y + height; j++)
		{
			for (int i = x; i < x + width; i++)
			{
				if (m.Get(i, j).type == TileType::Nothing || m.Get(i, j).type == TileType::WFCWall || m.Get(i, j).type == TileType::WFCFloor)
				{
					auto outputTile = wfc[j - y][i - x];
					if (outputTile == WFCTileType::Wall || outputTile == WFCTileType::Asteroid)
					{
						m.Set(i, j, Tile(TileType::WFCWall));
					}
					else if (outputTile == WFCTileType::Empty || outputTile == WFCTileType::Floor || outputTile == WFCTileType::NonRoomFloor)
					{
						m.Set(i, j, Tile(TileType::WFCFloor));
					}

					placed++;
				}
			}
		}
		if (drawStep++ % 20 == 0)
		{
			m.Display(false);
		}
		return placed;
	};

	auto output = wfc.RunVisualiseContinuous(apply_to_map);

	return apply_to_map(output);
}

}


/* lazy-cartesian-product.hpp
 * (c) Tyler Burdsall - 2018
 *
 * Licensed under the MIT license
 */

#ifndef _LAZY_CARTESIAN_PRODUCT
#define _LAZY_CARTESIAN_PRODUCT

#define LCP_MAJOR_VERSION 1
#define LCP_MINOR_VERSION 6

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
#define _HAS_ITERATOR_DEBUGGING 0
#define _SECURE_SCL 0
#endif

#include <string>
#include <fstream>
#include <stdexcept>
#include <cmath>
#ifdef USE_BOOST
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/random.hpp>
#include <boost/random/random_device.hpp>
#include <boost/container/vector.hpp>
using namespace boost::random;
using namespace boost::multiprecision;
using boost::container::vector;
using boost::random::random_device;
#else
#include <random>
#include <vector>
using std::mt19937_64;
using std::uniform_int_distribution;
using std::random_device;
using std::vector;
#endif

using std::fstream;
using std::ios;
using std::string;
using std::out_of_range;
using std::runtime_error;
using std::floor;

namespace lazycp
{
    namespace errors
    {
        struct index_error: public runtime_error
        {
            index_error(): runtime_error("The given index cannot be out of range") {}
        };
        struct empty_list_error: public runtime_error
        {
            empty_list_error(): runtime_error("The given list of combinations cannot be empty") {}
        };
        struct empty_answers_error: public runtime_error
        {
            empty_answers_error(): runtime_error("The given list of answers cannot be empty") {}
        };
        struct invalid_sample_size_error: public runtime_error
        {
            invalid_sample_size_error(): runtime_error("The given sample size cannot be out of range") {}
        };
    }
}

namespace lazycp
{
#ifdef USE_BOOST
    struct precomputed_stats
    {
        vector<uint1024_t> divs;
        vector<uint1024_t> mods;
        uint1024_t max_size;
    };
#else
    struct precomputed_stats
    {
        vector<unsigned long long> divs;
        vector<unsigned long long> mods;
        unsigned long long max_size;
    };
#endif
    class RandomIterator
    {
        public:
#ifdef USE_BOOST
            RandomIterator(const string &amount, const string &max): gen((random_device())())
            {
                num_left.assign(amount);
                last_k.assign(0);
                n.assign(max);
            }

            RandomIterator(const uint1024_t &amount, const uint1024_t &max): gen((random_device())())
            {
                num_left.assign(amount);
                last_k.assign(0);
                n.assign(max);
            }
            const uint1024_t next(void)
            {
                if (num_left > 0)
                {
                    uint1024_t range_size((n - last_k) / num_left);
                    uniform_int_distribution<uint1024_t> rnd(0, range_size);
                    uint1024_t r(rnd(gen) + last_k + 1);
                    last_k.assign(r);
                    num_left--;
                    return r;
                }
                else
                {
                    throw out_of_range("Exceeded amount of random numbers to generate.");
                }
            }

#else
            RandomIterator(const string &amount, const string &max): gen((random_device())())
            {
                num_left = stoull(amount, 0, 10);
                last_k = 0;
                n = stoull(max, 0, 0);
            }

            RandomIterator(const unsigned long long &amount, const unsigned long long &max): gen((random_device())())
            {
                num_left = amount;
                last_k = 0;
                n = max;
            }
            const unsigned long long next(void)
            {
                if (num_left > 0)
                {
                    unsigned long long range_size = (n - last_k) / num_left;
                    uniform_int_distribution<> rnd(0, range_size);
                    unsigned long long r = rnd(gen) + last_k + 1;
                    last_k = r;
                    num_left--;
                    return r;
                }
                else
                {
                    throw out_of_range("Exceeded amount of random numbers to generate.");
                }
            }
#endif
            const bool has_next(void)
            {
                return num_left > 0;
            }

        private:
#ifdef USE_BOOST
            uint1024_t         n;
            uint1024_t         last_k;
            uint1024_t         num_left;
#else
            unsigned long long n;
            unsigned long long last_k;
            unsigned long long num_left;
#endif
            mt19937_64         gen;
    };

    class lazy_cartesian_product
    {
        public:
#ifdef USE_BOOST
            static const vector<string> boost_entry_at(const vector<vector<string>> &combinations, const string &index)
            {
                const precomputed_stats pc = boost_precompute(combinations);
                uint1024_t parsed_index(index);
                if (parsed_index  < 0 || parsed_index >= pc.max_size)
                {
                    throw errors::index_error();
                }

                const vector<string> combination = boost_entry_at(combinations, parsed_index, pc);
                return combination;
            }
            static const vector<vector<string>> boost_generate_samples(const vector<vector<string>> combinations, const string &sample_size)
            {
                const uint1024_t parsed_sample_size(sample_size);
                if (combinations.size() == 0)
                {
                    throw errors::empty_list_error();
                }
                precomputed_stats ps = boost_precompute(combinations);

                vector<vector<string>> subset;
                if (parsed_sample_size != ps.max_size)
                {
                    RandomIterator iter(parsed_sample_size, ps.max_size);
                    while(iter.has_next())
                    {
                        subset.push_back(boost_entry_at(combinations, iter.next(), ps));
                    }
                }
                else
                {
                    for (uint1024_t i = 0; i < parsed_sample_size; ++i)
                    {
                        subset.push_back(boost_entry_at(combinations, i, ps));
                    }
                }

                return subset;
            }
            static const uint1024_t boost_compute_max_size(const vector<vector<string>> &combinations)
            {
                uint1024_t size(1);
                for (vector<vector<string>>::const_iterator it = combinations.begin(); it != combinations.end(); ++it)
                {
                    size *= (*it).size();
                }

                return size;
            }

#else
            static const vector<string> entry_at(const vector<vector<string>> &combinations, const unsigned long long &index)
            {
                const precomputed_stats pc = precompute(combinations);
                if (index  < 0 || index >= pc.max_size)
                {
                    throw errors::index_error();
                }

                const vector<string> combination = entry_at(combinations, index, pc);
                return combination;
            }
            static const vector<vector<string>> generate_samples(const vector<vector<string>> combinations, const unsigned long long &sample_size)
            {
                if (combinations.size() == 0)
                {
                    throw errors::empty_list_error();
                }
                precomputed_stats ps = precompute(combinations);

                vector<vector<string>> subset;
                if (sample_size != ps.max_size)
                {
		    subset.reserve(sample_size);
                    RandomIterator iter(sample_size, ps.max_size);
                    while(iter.has_next())
                    {
                        subset.push_back(entry_at(combinations, iter.next(), ps));
                    }
                }
                else
                {
		    subset.reserve(sample_size);
                    for (unsigned long long i = 0; i < sample_size; ++i)
                    {
                        subset.push_back(entry_at(combinations, i, ps));
                    }
                }

                return subset;
            }
            static const unsigned long long compute_max_size(const vector<vector<string>> &combinations)
            {
                unsigned long long size = 1;
                for (vector<vector<string>>::const_iterator it = combinations.begin(); it != combinations.end(); ++it)
                {
                    size *= (*it).size();
                }

                return size;
            }
#endif
        private:
#ifdef USE_BOOST
            static const precomputed_stats boost_precompute(const vector<vector<string>> &combinations)
            {
                precomputed_stats ps;
                if (combinations.size() == 0)
                {
                    throw errors::empty_answers_error();
                }

                long long size = combinations.size();
                ps.divs.resize(size);
                ps.mods.resize(size);
                uint1024_t factor = 1;

                for (long long i = size - 1; i >= 0; --i)
                {
                    uint1024_t items(combinations[i].size());
                    ps.divs[i] = factor;
                    ps.mods[i] = items;
                    factor *= items;
                }

                ps.max_size = boost_compute_max_size(combinations);
                return ps;
            }
            static const bool boost_sample_size_valid(const uint1024_t &sample, const uint1024_t &max_size)
            {
                return sample <= max_size;
            }

            static const vector<string> boost_entry_at(const vector<vector<string>> &combinations, const uint1024_t &n, const precomputed_stats &ps)
            {
                unsigned long long length(combinations.size());
                vector<string> combination(length);

                for (unsigned long long i = 0; i < length; ++i)
                {
                    combination[i] = combinations[i][(unsigned long long)((uint1024_t)(n / ps.divs[i]) % ps.mods[i])];
                }

                return combination;
            }
#else
            static const precomputed_stats precompute(const vector<vector<string>> &combinations)
            {
                precomputed_stats ps;
                if (combinations.size() == 0)
                {
                    throw errors::empty_answers_error();
                }

                unsigned long long size = combinations.size();
                ps.divs.resize(size);
                ps.mods.resize(size);
                unsigned long factor = 1;

                for (long long i = size - 1; i >= 0; --i)
                {
                    unsigned long long items = combinations[i].size();
                    ps.divs[i] = factor;
                    ps.mods[i] = items;
                    factor *= items;
                }

                ps.max_size = compute_max_size(combinations);
                return ps;
            }
            static const bool sample_size_valid(const unsigned long long &sample, const unsigned long &max_size)
            {
                return sample <= max_size;
            }
            static const vector<string> entry_at(const vector<vector<string>> &combinations, const unsigned long long &n, const precomputed_stats &ps)
            {
                unsigned long long length = combinations.size();
                vector<string> combination(length);

                for (unsigned long long i = 0; i < length; ++i)
                {
                    combination[i] = combinations[i][(unsigned long long)(floor(n / ps.divs[i])) % ps.mods[i]];
                }

                return combination;
            }
#endif
            lazy_cartesian_product() {}
    };
}
#endif


// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2011 Gael Guennebaud <g.gael@free.fr>
// Copyright (C) 2012 Kolja Brix <brix@igpm.rwth-aaachen.de>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include "../../test/sparse_solver.h"
#include <Eigen/IterativeSolvers>

template<typename T> void test_gmres_T()
{
  GMRES<SparseMatrix<T>, DiagonalPreconditioner<T> > gmres_colmajor_diag;
  GMRES<SparseMatrix<T>, IdentityPreconditioner    > gmres_colmajor_I;
  GMRES<SparseMatrix<T>, IncompleteLUT<T> >           gmres_colmajor_ilut;
  //GMRES<SparseMatrix<T>, SSORPreconditioner<T> >     gmres_colmajor_ssor;

  CALL_SUBTEST( check_sparse_square_solving(gmres_colmajor_diag)  );
//   CALL_SUBTEST( check_sparse_square_solving(gmres_colmajor_I)     );
  CALL_SUBTEST( check_sparse_square_solving(gmres_colmajor_ilut)     );
  //CALL_SUBTEST( check_sparse_square_solving(gmres_colmajor_ssor)     );
}

void test_gmres()
{
  CALL_SUBTEST_1(test_gmres_T<double>());
  CALL_SUBTEST_2(test_gmres_T<std::complex<double> >());
}


/*
@copyright Louis Dionne 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#include <boost/hana/either.hpp>

#include <boost/hana/assert.hpp>
#include <boost/hana/functional/compose.hpp>
#include <boost/hana/tuple.hpp>

#include <laws/base.hpp>
#include <laws/foldable.hpp>
#include <laws/traversable.hpp>
#include <test/identity.hpp>
using namespace boost::hana;

int main() {
    using test::ct_eq;

    auto eqs = make<Tuple>(
        right(ct_eq<0>{}), left(ct_eq<0>{}),
        right(ct_eq<1>{}), left(ct_eq<1>{}),
        right(ct_eq<2>{}), left(ct_eq<2>{}),
        right(ct_eq<3>{}), left(ct_eq<3>{})
    );

    //////////////////////////////////////////////////////////////////////////
    // Foldable
    //////////////////////////////////////////////////////////////////////////
    {
        // unpack
        {
            test::_injection<0> f{};

            BOOST_HANA_CONSTANT_CHECK(equal(
                unpack(left(ct_eq<0>{}), f),
                f()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                unpack(right(ct_eq<0>{}), f),
                f(ct_eq<0>{})
            ));
        }

        // laws
        test::TestFoldable<Either>{eqs};
    }

    //////////////////////////////////////////////////////////////////////////
    // Traversable
    //////////////////////////////////////////////////////////////////////////
    {
        test::_injection<0> f{};
        auto f_ = compose(lift<test::Identity>, f);

        BOOST_HANA_CONSTANT_CHECK(equal(
            traverse<test::Identity>(left(ct_eq<0>{}), f_),
            test::identity(left(ct_eq<0>{}))
        ));

        BOOST_HANA_CONSTANT_CHECK(equal(
            traverse<test::Identity>(right(ct_eq<0>{}), f_),
            test::identity(right(f(ct_eq<0>{})))
        ));

        // laws
        test::TestTraversable<Either>{};
    }
}


/*

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

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

This file is part of Chaste.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 * Neither the name of the University of Oxford nor the names of its
   contributors may be used to endorse or promote products derived from this
   software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#ifndef GOLDBETER1991SRNMODEL_HPP_
#define GOLDBETER1991SRNMODEL_HPP_

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

#include "Goldbeter1991OdeSystem.hpp"
#include "AbstractOdeSrnModel.hpp"

/**
 * A Subcellular Reaction Network Model
 * that includes a Goldbeter1991 ODE system in the sub-cellular reaction network.
 *
 * \todo #2752 give reference for this ODE system
 */
class Goldbeter1991SrnModel : public AbstractOdeSrnModel
{
private:

    /** Needed for serialization. */
    friend class boost::serialization::access;
    /**
     * Archive the SRN model and member variables.
     *
     * @param archive the archive
     * @param version the current version of this class
     */
    template<class Archive>
    void serialize(Archive & archive, const unsigned int version)
    {
        archive & boost::serialization::base_object<AbstractOdeSrnModel>(*this);
    }

protected:
    /**
     * Protected copy-constructor for use by CreateSrnModel().  The only way for external code to create a copy of a SRN model
     * is by calling that method, to ensure that a model of the correct subclass is created.
     * This copy-constructor helps subclasses to ensure that all member variables are correctly copied when this happens.
     *
     * This method is called by child classes to set member variables for a daughter cell upon cell division.
     * Note that the parent SRN model will have had ResetForDivision() called just before CreateSrnModel() is called,
     * so performing an exact copy of the parent is suitable behaviour. Any daughter-cell-specific initialisation
     * can be done in InitialiseDaughterCell().
     *
     * @param rModel  the SRN model to copy.
     */
    Goldbeter1991SrnModel(const Goldbeter1991SrnModel& rModel);

public:

    /**
     * Default constructor calls base class.
     *
     * @param pOdeSolver An optional pointer to a cell-cycle model ODE solver object (allows the use of different ODE solvers)
     */
    Goldbeter1991SrnModel(boost::shared_ptr<AbstractCellCycleModelOdeSolver> pOdeSolver = boost::shared_ptr<AbstractCellCycleModelOdeSolver>());

    /**
     * Overridden builder method to create new copies of
     * this SRN model.
     *
     * @return Returns a copy of the current SRN model.
     */
    AbstractSrnModel* CreateSrnModel();

    /**
     * Initialise the SRN model at the start of a simulation.
     *
     * This overridden method sets up a new Delta-Notch ODE system.
     */
    void Initialise();

    /**
     * Overridden SimulateToTime() method for custom behaviour.
     *
     * \todo #2752 say what it does in this class
     */
    void SimulateToCurrentTime();

    /**
     * Output SRN model parameters to file.
     *
     * @param rParamsFile the file stream to which the parameters are output
     */
    void OutputSrnModelParameters(out_stream& rParamsFile);

    /**
     * @return the value of the state variable C in the ODE system.
     */
    double GetC();

    /**
     * @return the value of the state variable M in the ODE system.
     */
    double GetM();

    /**
     * @return the value of the state variable X in the ODE system.
     */
    double GetX();
};

// Declare identifier for the serializer
#include "SerializationExportWrapper.hpp"
CHASTE_CLASS_EXPORT(Goldbeter1991SrnModel)
#include "CellCycleModelOdeSolverExportWrapper.hpp"
EXPORT_CELL_CYCLE_MODEL_ODE_SOLVER(Goldbeter1991SrnModel)

#endif /* GOLDBETER1991SRNMODEL_HPP_ */