/*
Copyright (c) 2014 Mohamed Elsabagh <melsabag@gmu.edu>

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

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

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

#ifndef SAXQUANTIZER_HPP
#define SAXQUANTIZER_HPP

#include <deque>
#include <vector>
#include <cassert>
#include <boost/math/distributions/normal.hpp>

using std::deque;
using std::vector;

namespace SaxQuantizer {

  /**
  * Helper routine to generate the SAX codebook (list of cutpoints) of a given size
  * @param <alphabet_size>: number of desired codewords
  * @param <cutpoints> (out): resulting codebook
  */
  inline void fill_cutpoints(size_t alphabet_size, vector<double> *cutpoints) {
    assert(alphabet_size > 0);
    static boost::math::normal dist(0.0, 1.0);
    std::cout << "alphabet: " << alphabet_size << std::endl;
    cutpoints->reserve(alphabet_size);
    cutpoints->push_back(-DBL_MAX);
    for (size_t i = 1; i < alphabet_size; ++i) {
      double cdf = ((double) i) / alphabet_size;
      cutpoints->push_back(quantile(dist, cdf));
    }
  }

  /**
   * Symbolic Aggregate Approximation with fractional sliding window, numerosity reduction, and scaling
   */
  class Sax {
  private:
    size_t m_window_size;
    size_t m_string_size;
    size_t m_alphabet_size;

    double m_baseline_mean;
    double m_baseline_stdev;
    vector<double> m_cutpoints;

    bool m_trained;

    /**
     * SAX with fractional sliding window and automatic scaling.
     * @param <it>: start iterator
     * @param <end>: end iterator
     * @param <syms> (out): quantized range
     */
    template<class Iter>
    void saxify(Iter it, const Iter end, vector<int> *syms) {
      // perform PAA using a fractional sliding window
      double paa[m_string_size];
      double paa_window = ((double) m_window_size) / m_string_size;

      double p = 0; // p for progress
      double w = 1; // w for weight
      size_t available = 0;

      for (size_t i = 0; i < m_string_size && it != end; ++i, ++available) {
        // normalize around baseline
        double normalized = (*it - m_baseline_mean) / m_baseline_stdev;

        paa[i] = 0;
        double j = 0;
        while (j < paa_window && it != end) {
          paa[i] += w * normalized; // sum of (partial) elements inside the window
          j += w;
          p += w;

          // window full
          if (paa_window == p) {
            if (fabs(w - 1.0) <= 0.01) {   // if last element fully consumed,
              ++it;                        // then just move next.
            } else {                       // o.w.,
              w = 1.0 - w;                 // set remaining portion.
            }

            p = 0;                         // reset progress

          // window not full, but next must be split
          } else if (paa_window - p < 1.0) {
            w = paa_window - p;            // set needed portion
            ++it;                          // move to next

          // window not full, next can be fully consumed
          } else {
            ++it;                          // move to next
          }
        }

        paa[i] /= j; // averaging
      }

      // map to symbols. 0-based.
      for (size_t i = 0; i < available; ++i) {
        int cnt = -1;
        for (const auto & cp : m_cutpoints) {
          if (paa[i] >= cp) ++cnt;
        }
        syms->push_back(cnt);
      }
    }

  public:
    /**
    * Constructs a SAX quantizer of a given window size, string size and alphabet size.
    * @param <window_size>: sliding window size
    * @param <string_size>: output string size for each sliding window (can be greater than window_size)
    * @param <alphabet_size>: number of codewords
    */
    Sax(size_t window_size, size_t string_size, size_t alphabet_size)
     : m_window_size(window_size), m_string_size(string_size), m_alphabet_size(alphabet_size),
       m_baseline_mean(0), m_baseline_stdev(1), m_trained(false) {

      assert(window_size > 0);
      assert(string_size > 0);
      assert(alphabet_size > 0);

      fill_cutpoints(alphabet_size, &m_cutpoints);
    }

    virtual ~Sax() {
      m_cutpoints.clear();
    }

    /**
     * Trains the quantizer from a given sample. This sets the baseline mean and stdevs, which are used in
     * normalizing the input.
     *
     * @param <samples>: list of training values
     */
    template<typename Container>
    void train(const Container & samples) {
      double mean = 0;
      double stdev = DBL_MIN;

      assert(!samples.empty());

      if (samples.size() < 2) {
        mean = samples[0];
        stdev = DBL_MIN;

      } else {
        size_t n = 0;
        double M2 = 0;
        for (const auto & val : samples) {
          ++n;
          double delta = val - mean;
          mean += delta / n;
          M2 += delta * (val - mean);
        }
        stdev = sqrt(M2 / (n-1));
      }

      if (stdev == 0) stdev = DBL_MIN;

      m_baseline_mean = mean;
      m_baseline_stdev = stdev;

      m_trained = true;
    }

    /**
     * Quantizes the given input sequence into a discrete alphabet using SAX.
     * Calling this method will also train the quantizer using the input sequence, if
     * not already trained.
     *
     * @param <seq>: the input sequence to be quantized
     * @param <qseq> (out): quantized output sequence
     * @param reduce (default true): if true, applies run-length numerosity reduction
     *
     * Returns the number of consumed symbols from the input sequence.
     */
    template<typename Container>
    size_t quantize(const Container & seq, vector<int> *qseq, bool reduce=true) {
      if (!m_trained) train(seq);

      vector<int> buf1, buf2;
      auto *syms_buf = &buf1;
      auto *old_syms_buf = &buf2;

      size_t consumed = 0;
      for (consumed = 0; consumed < seq.size(); ++consumed) {

        if (reduce) { // run-length numerosity reduction
          syms_buf->clear();
          saxify(seq.begin() +consumed, seq.end(), syms_buf);

          // skip window if same as previous
          if (*syms_buf != *old_syms_buf) {
            qseq->insert(qseq->end(), syms_buf->begin(), syms_buf->end());
            std::swap(syms_buf, old_syms_buf);
          }

        } else { // no reduction
          saxify(seq.begin() +consumed, seq.end(), qseq);
        }

        // ignore excess elements, if sequence size isn't a multiple of window size
        //if (seq.size() - consumed <= m_window_size) break;
      }

      return consumed;
    }

    /**
     * Returns the order of the quantizer (here, the window size)
     */
    inline size_t order() const {
      return m_window_size;
    }

    /**
     * Returns the compression ratio of the quantizer (input size / output size)
     */
    inline double ratio() const {
      return ((double) m_window_size) / m_string_size;
    }
  };

}

#endif


/**
 * @file helper.hpp
 * @author francois.hamonic@gmail.com
 * @brief
 * @version 0.1
 * @date 2021-07-19
 */
#ifndef HELPER_HPP
#define HELPER_HPP

#include <math.h>
#include <filesystem>
#include <iterator>

#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>

#include "landscape/decored_landscape.hpp"
#include "landscape/mutable_landscape.hpp"

#include "Eigen/Dense"
#include "algorithms/multiplicative_dijkstra.hpp"
#include "lemon/dijkstra.h"

#include "fast-cpp-csv-parser/csv.h"
#include "solvers/concept/solver.hpp"

#include "lemon/dim2.h"
#include "lemon/graph_to_eps.h"

#include "indices/eca.hpp"

namespace Helper {
/**
 * @brief Compute the distance matrix of a graph.
 *
 * Generic implementation of computing the distance matrix of a graph by running
 * dijkstra for each node. The length map values must support numeric
 * operations. Complexities are $O((m+n) \cdot \log n)$ time and $O(n^2)$ space,
 * where $n$ is the number of nodes and $m$ the number of arcs. Diagonal entries
 * are $0$ and unreachable nodes entries are
 * $std::numeric_limits<Value>::max()$.
 *
 * @tparam GR : type of graph
 * @tparam DM : type of length map
 * @param g : graph
 * @param l : length map
 * @return Eigen::Matrix<typename DM::Value, Eigen::Dynamic, Eigen::Dynamic>*
 */
template <typename GR, typename DM>
static Eigen::Matrix<typename DM::Value, Eigen::Dynamic, Eigen::Dynamic> *
distanceMatrix(const GR & g, DM & l) {
    using Value = typename DM::Value;

    const int n = lemon::countNodes(g);
    Eigen::Matrix<Value, Eigen::Dynamic, Eigen::Dynamic> * distances =
        new Eigen::Matrix<Value, Eigen::Dynamic, Eigen::Dynamic>(n, n);
    distances->fill(std::numeric_limits<Value>::max());

    lemon::SimplerDijkstra<GR, DM> dijkstra(g, l);

    for(typename GR::NodeIt s(g); s != lemon::INVALID; ++s) {
        const int id_s = g.id(s);
        dijkstra.run(s);

        for(typename GR::NodeIt t(g); t != lemon::INVALID; ++t) {
            const int id_t = g.id(t);
            Value & d_st = (*distances)(id_s, id_t);
            if(!dijkstra.reached(t)) {
                d_st = std::numeric_limits<Value>::max();
                continue;
            }
            d_st = dijkstra.dist(t);
        }
    }
    return distances;
}

/**
 * @brief Compute the distance matrix of a graph.
 *
 * Generic implementation of computing the distance matrix of a graph by running
 * dijkstra for each node. The probability map values must support numeric
 * operations. Complexities are $O((m+n) \cdot \log n)$ time and $O(n^2)$ space,
 * where $n$ is the number of nodes and $m$ the number of arcs. Diagonal entries
 * are $1$ and unreachable nodes entries are $0$.
 *
 * @tparam GR : type of graph
 * @tparam DM : type of length map
 * @param g : graph
 * @param p : probability map
 * @return Eigen::Matrix<typename DM::Value, Eigen::Dynamic, Eigen::Dynamic>*
 */
template <typename GR, typename DM>
static Eigen::Matrix<typename DM::Value, Eigen::Dynamic, Eigen::Dynamic> *
multDistanceMatrix(const GR & g, DM & p) {
    using Value = typename DM::Value;

    const int n = lemon::countNodes(g);
    Eigen::Matrix<Value, Eigen::Dynamic, Eigen::Dynamic> * distances =
        new Eigen::Matrix<Value, Eigen::Dynamic, Eigen::Dynamic>(n, n);
    distances->fill(0);

    lemon::MultiplicativeSimplerDijkstra<GR, DM> dijkstra(g, p);

    for(typename GR::NodeIt s(g); s != lemon::INVALID; ++s) {
        const int id_s = g.id(s);
        dijkstra.run(s);

        for(typename GR::NodeIt t(g); t != lemon::INVALID; ++t) {
            const int id_t = g.id(t);
            Value & d_st = (*distances)(id_s, id_t);
            if(!dijkstra.reached(t)) {
                d_st = 0;
                continue;
            }
            d_st = dijkstra.dist(t);
        }
    }
    return distances;
}

template <typename LS>
double minNonZeroQuality(const LS & landscape) {
    using Graph = typename LS::Graph;
    const Graph & graph = landscape.getNetwork();
    double min = std::numeric_limits<double>::max();
    for(typename Graph::NodeIt v(graph); v != lemon::INVALID; ++v) {
        if(landscape.getQuality(v) == 0) continue;
        min = std::min(min, landscape.getQuality(v));
    }
    return min;
}

/**
 * @brief Compute the centrality of each arc, i.e the number of shortest paths
 * that contain it
 * @tparam GR
 * @tparam LM
 * @param graph
 * @param lengthMap
 * @return GR::ArcMap<int>*
 */
template <typename Graph, typename LengthMap>
std::unique_ptr<typename Graph::template ArcMap<int>> arcCentralityMap(
    const Graph & graph, const LengthMap & lengthMap) {
    using PredMap = typename Graph::template NodeMap<typename Graph::Arc>;

    std::unique_ptr<typename Graph::template ArcMap<int>> centralityMap =
        std::make_unique<typename Graph::template ArcMap<int>>(graph, 0);

    lemon::Dijkstra<Graph, LengthMap> dijkstra(graph, lengthMap);

    for(typename Graph::NodeIt s(graph); s != lemon::INVALID; ++s) {
        dijkstra.run(s);
        const PredMap & predMap = dijkstra.predMap();
        for(typename Graph::NodeIt t(graph); t != lemon::INVALID; ++t) {
            if(!dijkstra.reached(t)) continue;
            typename Graph::Node u = t;
            while(u != s) {
                (*centralityMap)[predMap[u]] += 1;
                u = graph.source(predMap[u]);
            }
        }
    }
    return centralityMap;
}

/**
 * @brief Compute the centrality of each arc, i.e the number of shortest paths
 * that contain it
 * @tparam GR
 * @tparam LM
 * @param graph
 * @param lengthMap
 * @return GR::ArcMap<int>*
 */
template <typename LS>
std::unique_ptr<typename LS::Graph::template ArcMap<double>>
corridorCentralityMap(const LS & landscape) {
    using Graph = typename LS::Graph;
    using PredMap = typename Graph::template NodeMap<typename Graph::Arc>;
    const Graph & graph = landscape.getNetwork();

    std::unique_ptr<typename Graph::template ArcMap<double>> centralityMap =
        std::make_unique<typename Graph::template ArcMap<double>>(graph, 0);

    lemon::Dijkstra<Graph, typename LS::ProbabilityMap> dijkstra(
        graph, landscape.getProbabilityMap());

    for(typename Graph::NodeIt s(graph); s != lemon::INVALID; ++s) {
        if(landscape.getQuality(s) == 0) continue;
        dijkstra.run(s);
        const PredMap & predMap = dijkstra.predMap();
        for(typename Graph::NodeIt t(graph); t != lemon::INVALID; ++t) {
            if(landscape.getQuality(t) == 0) continue;
            if(!dijkstra.reached(t)) continue;
            typename Graph::Node u = t;
            while(u != s) {
                (*centralityMap)[predMap[u]] += 1;
                u = graph.source(predMap[u]);
            }
        }
    }
    return centralityMap;
}

/**
 * @brief Computes the value of the ECA index of the specified landscape
 * graph.
 *
 * @time \f$O(n \cdot (m + n) \log n)\f$ where \f$n\f$ is the number of
 * nodes and \f$m\f$ the number of arcs
 * @space \f$O(m)\f$ where \f$m\f$ is the number of arcs
 */
template <typename GR, typename QM, typename PM>
std::vector<std::pair<typename GR::Node, double>> computeDistancePairs(
    const GR & graph, const QM & qualityMap, const PM & probabilityMap,
    const typename GR::Node s) {
    using Node = typename GR::Node;

    std::vector<std::pair<Node, double>> result;
    result.reserve(lemon::countNodes(graph));
    lemon::MultiplicativeSimplerDijkstra<GR, PM> dijkstra(graph,
                                                          probabilityMap);
    if(qualityMap[s] != 0) {
        dijkstra.init(s);
        while(!dijkstra.emptyQueue()) {
            result.emplace_back(dijkstra.processNextNode());
            if(result.back().second == 0.0) {
                result.pop_back();
                break;
            }
        }
    }
    return result;
}

template <typename LS>
std::vector<std::pair<typename LS::Node, double>> computeDistancePairs(
    const LS & landscape, const typename LS::Node s) {
    auto result =
        computeDistancePairs(landscape.getNetwork(), landscape.getQualityMap(),
                             landscape.getProbabilityMap(), s);
    return result;
}

template <typename LS>
double averageRatioOfNodesInECARealization(double ratio_of_eca,
                                           LS && landscape) {
    using Graph = typename std::remove_reference<LS>::type::Graph;
    using NodeIt = typename std::remove_reference<LS>::type::NodeIt;

    const Graph & graph = landscape.getNetwork();

    double average_sum = 0.0;
    int cpt = 0;
    for(NodeIt s(graph); s != lemon::INVALID; ++s) {
        const auto sorted_pairs = computeDistancePairs(landscape, s);

        if(sorted_pairs.empty()) continue;

        const double contribution_max = std::transform_reduce(
            sorted_pairs.begin(), sorted_pairs.end(), 0.0, std::plus<double>(),
            [&](const auto & p) {
                return landscape.getQuality(s) * landscape.getQuality(p.first) *
                       p.second;
            });

        int node_count = 0;
        double contribution_sum = 0.0;
        for(const auto & [t, p_st] : sorted_pairs) {
            if(contribution_sum / contribution_max >= ratio_of_eca) break;
            ++node_count;
            contribution_sum +=
                landscape.getQuality(s) * landscape.getQuality(t) * p_st;
        }

        average_sum += node_count / static_cast<double>(sorted_pairs.size());
        ++cpt;
    }
    return average_sum / cpt;
}

template <typename LS>
DecoredLandscape<LS> decore_landscape(const LS & landscape,
                                      const RestorationPlan<LS> & plan,
                                      const Solution & solution) {
    using Graph = typename LS::Graph;
    const Graph & graph = landscape.getNetwork();
    DecoredLandscape<LS> decored_landscape(landscape);

    for(typename Graph::NodeIt u(graph); u != lemon::INVALID; ++u)
        for(const auto & e : plan[u])
            decored_landscape.getQualityRef(u) +=
                solution[e.option] * e.quality_gain;
    for(typename Graph::ArcIt a(graph); a != lemon::INVALID; ++a)
        for(const auto & e : plan[a])
            decored_landscape.getProbabilityRef(a) = std::max(
                decored_landscape.getProbability(a),
                landscape.getProbability(a) +
                    solution[e.option] *
                        (e.restored_probability - landscape.getProbability(a)));

    return decored_landscape;
}

template <typename LS>
DecoredLandscape<LS> decore_landscape(const LS & landscape,
                                      const RestorationPlan<LS> & plan) {
    using Graph = typename LS::Graph;
    const Graph & graph = landscape.getNetwork();
    DecoredLandscape<LS> decored_landscape(landscape);

    for(typename Graph::NodeIt u(graph); u != lemon::INVALID; ++u)
        for(const auto & e : plan[u])
            decored_landscape.getQualityRef(u) += e.quality_gain;
    for(typename Graph::ArcIt a(graph); a != lemon::INVALID; ++a)
        for(const auto & e : plan[a])
            decored_landscape.getProbabilityRef(a) = std::max(
                decored_landscape.getProbability(a), e.restored_probability);

    return decored_landscape;
}

void printSolution(const MutableLandscape & landscape,
                   const RestorationPlan<MutableLandscape> & plan,
                   std::string name, concepts::Solver & solver, double B,
                   const Solution & solution);

template <typename LS_From, typename LS_To>
void copyPlan(
    RestorationPlan<LS_To> & contracted_plan,
    const RestorationPlan<LS_From> & plan,
    const typename LS_From::Graph::template NodeMap<typename LS_To::Node> &
        nodesRef,
    const typename LS_From::Graph::template ArcMap<typename LS_To::Arc> &
        arcsRef) {
    assert(contracted_plan.getNbOptions() == 0);
    for(const RestorationPlan<MutableLandscape>::Option i : plan.options())
        contracted_plan.addOption(plan.getCost(i));

    const typename LS_From::Graph & from_graph =
        plan.getLandscape().getNetwork();

    for(typename LS_From::Graph::NodeIt u(from_graph); u != lemon::INVALID; ++u)
        for(const auto & e : plan[u])
            contracted_plan.addNode(e.option, nodesRef[u], e.quality_gain);
    for(typename LS_From::Graph::ArcIt a(from_graph); a != lemon::INVALID; ++a)
        for(const auto & e : plan[a])
            contracted_plan.addArc(e.option, arcsRef[a],
                                   e.restored_probability);
}

// need to include the binary search tree for y-h , y+h search
std::pair<MutableLandscape::Node, MutableLandscape::Node> neerestNodes(
    const MutableLandscape & landscape);

void assert_well_formed(const MutableLandscape & landscape,
                        const RestorationPlan<MutableLandscape> & plan);

// template <typename GR>
// class NodeIterator {
// public:
//     using Node = typename GR::Node;
//     using NodeIt = typename GR::NodeIt;

//     using difference_type = std::ptrdiff_t;
//     using value_type = Node;
//     using pointer = void;
//     using reference = const Node&;
//     using iterator_category = std::input_iterator_tag;

// private:
//     NodeIt it;
// public:
//     NodeIterator() : it(lemon::INVALID) {}
//     NodeIterator(const GR & graph) : it(graph) {}
//     NodeIterator(const NodeIterator<GR> & o) : it(o.it) {}

//     NodeIterator& operator=(const NodeIterator<GR> & o) { it = o.it; return
//     *this; }

//     value_type operator*() const { return static_cast<value_type>(it); }
//     NodeIterator operator++(int) const { return ++NodeIterator<GR>(*this); }
//     NodeIterator& operator++() { ++it; return *this; }

//     bool operator==(const NodeIterator<GR> & o) const { return o.it == it; }
//     bool operator!=(const NodeIterator<GR> & o) const { return o.it != it; }
// };

// template <typename GR>
// std::pair<NodeIterator<GR>, NodeIterator<GR>> nodesRange(const GR & graph) {
//     return std::make_pair(NodeIterator(graph), NodeIterator<GR>());
// }
}  // namespace Helper

template <typename LS>
double max_flow_in(const LS & landscape, const RestorationPlan<LS> & plan,
                   typename LS::Node t) {
    using Graph = typename LS::Graph;
    using ProbabilityMap = typename LS::ProbabilityMap;
    using Reversed = lemon::ReverseDigraph<const Graph>;

    const Graph & original_g = landscape.getNetwork();
    Reversed reversed_g(original_g);
    ProbabilityMap probabilities(original_g);

    for(typename Graph::ArcIt b(original_g); b != lemon::INVALID; ++b) {
        probabilities[b] = landscape.getProbability(b);
        for(auto const & e : plan[b])
            probabilities[b] =
                std::max(probabilities[b], e.restored_probability);
    }

    lemon::MultiplicativeSimplerDijkstra<Reversed, ProbabilityMap> dijkstra(
        reversed_g, probabilities);
    double sum = 0;
    dijkstra.init(t);
    while(!dijkstra.emptyQueue()) {
        std::pair<typename Graph::Node, double> pair =
            dijkstra.processNextNode();
        typename Graph::Node v = pair.first;
        const double p_tv = pair.second;
        sum += landscape.getQuality(v) * p_tv;
        for(auto const & e : plan[v]) sum += e.quality_gain * p_tv;
    }
    return sum;
}

#endif  // HELPER


// Copyright (c) Dietmar Wolz.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory.

// Eigen based implementation of differential evolution using on the DE/best/1 strategy.
// Uses two deviations from the standard DE algorithm:
// a) temporal locality introduced in
// https://www.researchgate.net/publication/309179699_Differential_evolution_for_protein_folding_optimization_based_on_a_three-dimensional_AB_off-lattice_model
// b) reinitialization of individuals based on their age.
// requires https://github.com/imneme/pcg-cpp

#include <Eigen/Core>
#include <iostream>
#include <float.h>
#include <stdint.h>
#include <ctime>
#include <random>
#include <queue>
#include <tuple>
#include "pcg_random.hpp"
#include "evaluator.h"

using namespace std;

namespace differential_evolution {

class DeOptimizer {

public:

    DeOptimizer(long runid_, Fitness *fitfun_, int dim_, int seed_,
            int popsize_, int maxEvaluations_, double keep_,
            double stopfitness_, double F_, double CR_) {
        // runid used to identify a specific run
        runid = runid_;
        // fitness function to minimize
        fitfun = fitfun_;
        // Number of objective variables/problem dimension
        dim = dim_;
        // Population size
        popsize = popsize_ > 0 ? popsize_ : 15 * dim;
        // maximal number of evaluations allowed.
        maxEvaluations = maxEvaluations_ > 0 ? maxEvaluations_ : 50000;
        // keep best young after each iteration.
        keep = keep_ > 0 ? keep_ : 30;
        // Limit for fitness value.
        stopfitness = stopfitness_;
        F = F0 = F_ > 0 ? F_ : 0.5;
        CR = CR0 = CR_ > 0 ? CR_ : 0.9;
        // Number of iterations already performed.
        iterations = 0;
        bestY = DBL_MAX;
        // stop criteria
        stop = 0;
        pos = 0;
        //std::random_device rd;
        rs = new pcg64(seed_);
        init();
    }

    ~DeOptimizer() {
        delete rs;
    }

    double rnd01() {
        return distr_01(*rs);
    }

    int rndInt(int max) {
        return (int) (max * distr_01(*rs));
    }

    vec nextX(int p, const vec &xp, const vec &xb) {
        if (p == 0) {
            iterations++;
            CR = iterations % 2 == 0 ? 0.5 * CR0 : CR0;
            F = iterations % 2 == 0 ? 0.5 * F0 : F0;
        }
        int r1, r2;
        do {
            r1 = rndInt(popsize);
        } while (r1 == p || r1 == bestI);
        do {
            r2 = rndInt(popsize);
        } while (r2 == p || r2 == bestI || r2 == r1);
        vec x1 = popX.col(r1);
        vec x2 = popX.col(r2);
        vec x = xb + (x1 - x2) * F;
        int r = rndInt(dim);
        for (int j = 0; j < dim; j++)
            if (j != r && rnd01() > CR)
                x[j] = xp[j];
        return fitfun->getClosestFeasible(x);
    }

    vec next_improve(const vec &xb, const vec &x, const vec &xi) {
        return fitfun->getClosestFeasible(xb + ((x - xi) * 0.5));
    }

    vec ask(int &p) {
        // ask for one new argument vector.
        if (improvesX.empty()) {
            p = pos;
            vec x = nextX(p, popX.col(p), popX.col(bestI));
            pos = (pos + 1) % popsize;
            return x;
        } else {
            p = improvesP.front();
            vec x = improvesX.front();
            improvesP.pop();
            improvesX.pop();
            return x;
        }
    }

    int tell(double y, const vec &x, int p) {
        //tell function value for a argument list retrieved by ask_one().
        if (isfinite(y) && y < popY[p]) {
            if (iterations > 1) {
                // temporal locality
                improvesP.push(p);
                improvesX.push(next_improve(popX.col(bestI), x, popX0.col(p)));
            }
            popX0.col(p) = popX.col(p);
            popX.col(p) = x;
            popY[p] = y;
            popIter[p] = iterations;
            if (y < popY[bestI]) {
                bestI = p;
                if (y < bestY) {
                    bestY = y;
                    bestX = x;
                    if (isfinite(stopfitness) && bestY < stopfitness)
                        stop = 1;
                }
            }
        } else {
            // reinitialize individual
            if (keep * rnd01() < iterations - popIter[p]) {
                popX.col(p) = fitfun->sample(*rs);
                popY[p] = DBL_MAX;
            }
        }
        return stop;
    }

    void doOptimize() {

        // -------------------- Generation Loop --------------------------------
        for (iterations = 1; fitfun->evaluations() < maxEvaluations;
                iterations++) {

            CR = iterations % 2 == 0 ? 0.5 * CR0 : CR0;
            F = iterations % 2 == 0 ? 0.5 * F0 : F0;

            for (int p = 0; p < popsize; p++) {
                vec xp = popX.col(p);
                vec xb = popX.col(bestI);
                int r1, r2;
                do {
                    r1 = rndInt(popsize);
                } while (r1 == p || r1 == bestI);
                do {
                    r2 = rndInt(popsize);
                } while (r2 == p || r2 == bestI || r2 == r1);
                vec x1 = popX.col(r1);
                vec x2 = popX.col(r2);
                int r = rndInt(dim);
                vec x = vec(xp);
                for (int j = 0; j < dim; j++) {
                    if (j == r || rnd01() < CR) {
                        x[j] = xb[j] + F * (x1[j] - x2[j]);
                        if (!fitfun->feasible(j, x[j]))
                            x[j] = fitfun->sample_i(j, *rs);
                    }
                }

                double y = fitfun->eval(x);
                if (isfinite(y) && y < popY[p]) {
                    // temporal locality
                    vec x2 = next_improve(xb, x, xp);
                    double y2 = fitfun->eval(x2);
                    if (isfinite(y2) && y2 < y) {
                        y = y2;
                        x = x2;
                    }
                    popX.col(p) = x;
                    popY(p) = y;
                    popIter[p] = iterations;
                    if (y < popY[bestI]) {
                        bestI = p;
                        if (y < bestY) {
                            bestY = y;
                            bestX = x;
                            if (isfinite(stopfitness) && bestY < stopfitness) {
                                stop = 1;
                                return;
                            }
                        }
                    }
                } else {
                    // reinitialize individual
                    if (keep * rnd01() < iterations - popIter[p]) {
                        popX.col(p) = fitfun->sample(*rs);
                        popY[p] = DBL_MAX;
                    }
                }
            }
        }
    }

    void do_optimize_delayed_update(int workers) {
    	 iterations = 0;
    	 fitfun->resetEvaluations();
         workers = std::min(workers, popsize); // workers <= popsize
    	 evaluator eval(fitfun, 1, workers);
         int evals_size = popsize*10;
    	 vec evals_x[evals_size];
   	     int evals_p[evals_size];
         int cp = 0;

	     // fill eval queue with initial population
    	 for (int i = 0; i < workers; i++) {
    		 int p;
    		 vec x = ask(p);
    		 eval.evaluate(x, cp);
    		 evals_x[cp] = x;
    		 evals_p[cp] = p;
             cp = (cp + 1) % evals_size;
    	 }
    	 while (fitfun->evaluations() < maxEvaluations) {
    		 vec_id* vid = eval.result();
    		 vec y = vec(vid->_v);
    		 int id = vid->_id;
    		 delete vid;
    		 vec x = evals_x[id];
             int p = evals_p[id];
    		 tell(y(0), x, p); // tell evaluated x
    		 if (fitfun->evaluations() >= maxEvaluations)
    			 break;
    		 x = ask(p);
    		 eval.evaluate(x, cp);
    		 evals_x[cp] = x;
    		 evals_p[cp] = p;
             cp = (cp + 1) % evals_size;
    	 }
	}

    void init() {
        popX = mat(dim, popsize);
        popX0 = mat(dim, popsize);
        popY = vec(popsize);
        for (int p = 0; p < popsize; p++) {
            popX0.col(p) = popX.col(p) = fitfun->sample(*rs);
            popY[p] = DBL_MAX; // compute fitness
        }
        bestI = 0;
        bestX = popX.col(bestI);
        popIter = zeros(popsize);
    }

    vec getBestX() {
        return bestX;
    }

    double getBestValue() {
        return bestY;
    }

    mat getX() {
        return popX;
    }

    mat getY() {
        return popY;
    }

    double getIterations() {
        return iterations;
    }

    double getStop() {
        return stop;
    }

    Fitness* getFitfun() {
        return fitfun;
    }

    int getDim() {
        return dim;
    }

private:
    long runid;
    Fitness *fitfun;
    int popsize; // population size
    int dim;
    int maxEvaluations;
    double keep;
    double stopfitness;
    int iterations;
    double bestY;
    vec bestX;
    int bestI;
    int stop;
    double F0;
    double CR0;
    double F;
    double CR;
    pcg64 *rs;
    mat popX;
    mat popX0;
    vec popY;
    vec popIter;
    queue<vec> improvesX;
    queue<int> improvesP;
    int pos;
};

// see https://cvstuff.wordpress.com/2014/11/27/wraping-c-code-with-python-ctypes-memory-and-pointers/

}

using namespace differential_evolution;

/*
 * Class:     fcmaes_core_Jni
 * Method:    optimizeDE
 * Signature: (Lfcmaes/core/Fitness;[D[D[DIDIDDDJII)I
 */
JNIEXPORT jint JNICALL Java_fcmaes_core_Jni_optimizeDE(JNIEnv *env, jclass cls,
        jobject func, jdoubleArray jlower, jdoubleArray jupper,
        jdoubleArray jresult, jint maxEvals, jdouble stopfitness, jint popsize,
        jdouble keep, jdouble F, jdouble CR, jlong seed, jint runid, jint workers) {

    double *result = env->GetDoubleArrayElements(jresult, JNI_FALSE);
    double *lower = env->GetDoubleArrayElements(jlower, JNI_FALSE);
    double *upper = env->GetDoubleArrayElements(jupper, JNI_FALSE);
    int dim = env->GetArrayLength(jlower);
    vec lower_limit(dim), upper_limit(dim);
    for (int i = 0; i < dim; i++) {
        lower_limit[i] = lower[i];
        upper_limit[i] = upper[i];
    }
    CallJava callJava(func, env);
    Fitness fitfun(&callJava, dim, 1, lower_limit, upper_limit);

    DeOptimizer opt(runid, &fitfun, dim, seed, popsize, maxEvals, keep,
            stopfitness, F, CR);
    try {
        if (workers <= 1)
            opt.doOptimize();
        else
            opt.do_optimize_delayed_update(workers);
        vec bestX = opt.getBestX();
        double bestY = opt.getBestValue();

        for (int i = 0; i < dim; i++)
            result[i] = bestX[i];

        env->SetDoubleArrayRegion(jresult, 0, dim, (jdouble*) result);
        env->ReleaseDoubleArrayElements(jresult, result, 0);
        env->ReleaseDoubleArrayElements(jupper, upper, 0);
        env->ReleaseDoubleArrayElements(jlower, lower, 0);
        return fitfun.evaluations();

    } catch (std::exception &e) {
        cout << e.what() << endl;
        return fitfun.evaluations();
    }
    return 0;
}

/*
 * Class:     fcmaes_core_Jni
 * Method:    initDE
 * Signature: (Lfcmaes/core/Fitness;[D[DIDDDJI)J
 */
JNIEXPORT jlong JNICALL Java_fcmaes_core_Jni_initDE(JNIEnv *env, jclass cls,
        jobject func, jdoubleArray jlower, jdoubleArray jupper,
        jint popsize, jdouble keep, jdouble F, jdouble CR, jlong seed,
        jint runid) {
    double *lower = env->GetDoubleArrayElements(jlower, JNI_FALSE);
    double *upper = env->GetDoubleArrayElements(jupper, JNI_FALSE);
    int dim = env->GetArrayLength(jlower);
    vec lower_limit(dim), upper_limit(dim);
    for (int i = 0; i < dim; i++) {
        lower_limit[i] = lower[i];
        upper_limit[i] = upper[i];
    }
    CallJava* callJava = new CallJava(func, env);
    Fitness* fitfun = new Fitness(callJava, dim, 1, lower_limit, upper_limit);
    DeOptimizer *opt = new DeOptimizer(runid, fitfun, dim, seed, popsize,
            INT_MAX, keep, -DBL_MAX, F, CR);
    env->ReleaseDoubleArrayElements(jupper, upper, 0);
    env->ReleaseDoubleArrayElements(jlower, lower, 0);
    return (intptr_t) opt;
}

/*
 * Class:     fcmaes_core_Jni
 * Method:    destroyDE
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_fcmaes_core_Jni_destroyDE(JNIEnv *env, jclass cls, intptr_t ptr) {
    DeOptimizer* opt = (DeOptimizer*)ptr;
    Fitness* fitfun = opt->getFitfun();
    delete fitfun->getFunc();
    delete fitfun;
    delete opt;
}

/*
 * Class:     fcmaes_core_Jni
 * Method:    askDE
 * Signature: (J)[D
 */
JNIEXPORT jdoubleArray JNICALL Java_fcmaes_core_Jni_askDE(JNIEnv *env,
        jclass cls, intptr_t ptr) {
    DeOptimizer *opt = (DeOptimizer*) ptr;
    int dim = opt->getDim();
    jdoubleArray jx = env->NewDoubleArray(dim + 1);
    double x[dim + 1];
    int p;
    vec args = opt->ask(p);
    for (int i = 0; i < dim; i++)
        x[i] = args[i];
    x[dim] = p;
    env->SetDoubleArrayRegion(jx, 0, dim + 1, x);
    return jx;
}

/*
 * Class:     fcmaes_core_Jni
 * Method:    tellDE
 * Signature: (J[DDI)I
 */
JNIEXPORT jint JNICALL Java_fcmaes_core_Jni_tellDE(JNIEnv *env, jclass cls,
		intptr_t ptr, jdoubleArray jx, jdouble y, jint p) {
    DeOptimizer *opt = (DeOptimizer*) ptr;
    int dim = opt->getDim();
    double *x = env->GetDoubleArrayElements(jx, JNI_FALSE);
    vec args(dim);
    for (int i = 0; i < dim; i++)
        args[i] = x[i];
    opt->tell(y, args, p);
    env->ReleaseDoubleArrayElements(jx, x, 0);
    return opt->getStop();
}

/*
 * Class:     fcmaes_core_Jni
 * Method:    populationDE
 * Signature: (J)[D
 */
JNIEXPORT jdoubleArray JNICALL Java_fcmaes_core_Jni_populationDE(JNIEnv *env,
        jclass cls, intptr_t ptr) {
    DeOptimizer *opt = (DeOptimizer*) ptr;
    int size = opt->getX().size();
    double* xdata = opt->getX().data();
    jdoubleArray jres = env->NewDoubleArray(size);
    env->SetDoubleArrayRegion(jres, 0, size, (jdouble*) xdata);
    return jres;
}


#include "NTtimescales.h"
#include "write.h"
#include "messages.h"
#include "globalVariables.h"
#include "adafFunctions.h"

#include <flosses/nonThermalLosses.h>
#include <flosses/lossesSyn.h>
#include <flosses/lossesIC.h>
#include <flosses/lossesBrem.h>
#include <flosses/lossesHadronics.h>
#include <flosses/lossesPhotoHadronic.h>
#include <fparameters/SpaceIterator.h>
#include <fparameters/Dimension.h>
#include <fparameters/parameters.h>

#include <boost/property_tree/ptree.hpp>

void nonThermalTimescales(Particle& p, State& st, const std::string& filename)
{
	if (p.id == "ntElectron")
		show_message(msgStart, Module_electronRadLosses);
	else if (p.id == "ntProton")
		show_message(msgStart, Module_protonRadLosses);

	std::ofstream file,file2;
	file.open(filename.c_str(), std::ios::out);
	if (p.id == "ntProton") file2.open("diffusion_adv.dat", std::ios::out);

	file << "r [2M]"
		<< "\t" << "Log(gamma)"
		<< "\t" << "Acc"
		<< "\t" << "Acc_SDA"
		<< "\t" << "Adv"
		<< "\t" << "Diff_K"
		<< "\t" << "Diff_B"
		<< "\t" << "Diff_|_"
		<< "\t" << "Diff_||"
        << "\t" << "EmaxHillas"
		<< "\t" << "Sy"
		<< "\t" << "IC/pp"
		<< "\t" << "pg/Bremss"
		<< "\t" << "Relax"

		<< std::endl;

	if (p.id == "ntProton") {
		file2   << "r [2M]"
				<< "\t" << "Log(gamma)"
				<< "\t" << "Diff_length"
				<< "\t" << "dR"
				<< "\t" << "Dlenght/dR"
				<< std::endl;
	}

	int flag1, flag2, flag3, flag4, flag5, flag6, flag7;
	flag1 = flag2 = flag3 = flag4 = flag5 = flag6 = flag7 = 0;
	double logr1,logr2,logr3,logr4,logr5,logr6,logr7;
	logr1 = log10(1.5);
	double aux = log10(st.denf_e.ps[DIM_R].last()/schwRadius)/7.0;
	logr2 = logr1+aux;
	logr3 = logr2+aux;
	logr4 = logr3+aux;
	logr5 = logr4+aux;
	logr6 = logr5+aux;
	logr7 = logr6+aux;
	p.ps.iterate([&](const SpaceIterator& iR) {
		double r = iR.val(DIM_R);
		double logr = log10(r/schwRadius);
		if (logr > logr1) flag1++;
		if (logr > logr2) flag2++;
		if (logr > logr3) flag3++;
		if (logr > logr4) flag4++;
		if (logr > logr5) flag5++;
		if (logr > logr6) flag6++;
		if (logr > 0.9*logr7) flag7++;

		if (flag1 == 1 || flag2 == 1 || flag3 == 1 || flag4 == 1 || flag5 == 1
				|| flag6 == 1 || flag7 == 1) {
			double tAdv = accretionTime(r);
			double vR = radialVel(r);
			double dR = r * (sqrt(paso_r)-1.0/sqrt(paso_r));
			double tCell = dR / abs(vR);
			double B = st.magf.get(iR);
			double height = height_fun(r);
			double rho = massDensityADAF(r);
			double eMaxHillas = electronCharge*B*height;
			p.ps.iterate([&](const SpaceIterator& iRE) {
				double E = iRE.val(DIM_E);
				double tAcc = 1.0/accelerationRate(E,B);
				double tAccSDA = accelerationTimeSDA(E,p,B,height,rho);
				double tDiffKol = diffusionTimeTurbulence(E,height,p,B);
				double tDiffBohm = height*height/BohmDiffusionCoeff(E,B);
				double tDiffParallel = diffusionTimeParallel(E,height,B);
				double tDiffPerpend = diffusionTimePerpendicular(E,height,B);

				file << (int)(r/schwRadius)												// 0
					 << "\t" << safeLog10(E/(p.mass*cLight2))							// 1
					 << "\t" << safeLog10(tAcc)											// 2
					 << "\t" << safeLog10(tAccSDA)										// 3
					 << "\t" << safeLog10(tAdv)											// 4
					 << "\t" << safeLog10(tDiffKol)										// 5
					 << "\t" << safeLog10(tDiffBohm)									// 6
					 << "\t" << safeLog10(tDiffParallel)								// 7
					 << "\t" << safeLog10(tDiffPerpend)									// 8
					 << "\t" << safeLog10(eMaxHillas/1.602e-12/(p.mass*cLight2));		// 9
				if (p.id == "ntProton") {
					double diff_length = diffLength(E/(p.mass*cLight2),p,r,height,B,vR);
					file2   << (int)(r/schwRadius)
							<< "\t" << safeLog10(E/(p.mass*cLight2))
							<< "\t" << safeLog10(diff_length/schwRadius)
							<< "\t" << safeLog10(dR/schwRadius)
							<< "\t" << diff_length/dR << std::endl;
				}

				if (p.id == "ntElectron") {
					double tSyn = E/lossesSyn(E,B,p);
					double tIC = E/lossesIC(E,p,st.photon.distribution,iR.coord,st.photon.emin(),
											st.photon.emax());
					double tBrem = E/lossesBremss(E,st.denf_e.get(iR)+st.denf_i.get(iR),p);
					file << "\t" << safeLog10(tSyn)		// 10
						 << "\t" << safeLog10(tIC)		// 11
						 << "\t" << safeLog10(tBrem)	// 12
						 << "\t" << safeLog10(relaxTime_e(E,st.tempElectrons.get(iR),st.denf_e.get(iR)))	// 13
						 << std::endl;
				} else if(p.id == "ntProton") {
					double tAdi = 2.0*r/(-radialVel(r)) / (E/p.mass/cLight2);
					double tSyn = E/lossesSyn(E,B,p);
					double tIC_Th = E/lossesIC_Th(E,p,st.photon.distribution,iR.coord,st.photon.emin(),
											st.photon.emax());
					double tPP = E/lossesHadronics(E,st.denf_i.get(iR),p);
					double tPG = E/lossesPhotoMeson(E,p,st.photon.distribution,iR,st.photon.emin(),
									st.photon.emax());
					double tBH = E/lossesPhotoPair(E,p,st.photon.distribution,iR,st.photon.emin(),
									st.photon.emax());
					file << "\t" << safeLog10(tSyn)				// 10
						 << "\t" << safeLog10(tIC_Th)			// 11
						 << "\t" << safeLog10(tPP)				// 12
						 << "\t" << safeLog10(tPG)				// 13
						 << "\t" << safeLog10(tBH)				// 14
						 << "\t" << safeLog10(tAdi)				// 15
						 << "\t" << safeLog10(relaxTime_p(E, st.tempIons.get(iR), st.denf_i.get(iR)))		// 16
						 << std::endl;
				}
			},{-1, iR.coord[DIM_R], 0});
		}
	},{0,-1,0});
	file.close();

	if (p.id == "ntElectron") {
		show_message(msgEnd, Module_electronRadLosses);
	}
	else if (p.id == "ntProton") {
		file2.close();
		show_message(msgEnd, Module_protonRadLosses);
	}
}

using namespace std;

void secondariesTimescales(Particle& p, State& st, const std::string& filename)
{
	ofstream file;
	file.open(filename.c_str(), ios::out);

	file << "r [M]"
		<< "\t" << "Log(gamma)"
		<< "\t" << "Adv"
		<< "\t" << "Diff"
		<< "\t" << "Decay"
		<< "\t" << "Sy"
		<< "\t" << "IC/pp"
		<< "\t" << "pg/Bremss"
		<< endl;

	int flag1,flag2,flag3,flag4,flag5;
	flag1 = flag2 = flag3 = flag4 = flag5 = 0;
	double logr1,logr2,logr3,logr4,logr5;
	logr1 = log10(1.5);
	double aux = log10(st.denf_e.ps[DIM_R].last()/schwRadius)/4.0;
	logr2 = logr1+aux;
	logr3 = logr2+aux;
	logr4 = logr3+aux;
	logr5 = logr4+aux;
	p.ps.iterate([&](const SpaceIterator& iR) {
		double r = iR.val(DIM_R);
		double logr = log10(r/schwRadius);
		if (logr > logr1) flag1++;
		if (logr > logr2) flag2++;
		if (logr > logr3) flag3++;
		if (logr > logr4) flag4++;
		if (logr > 0.9*logr5) flag5++;

		if (flag1 == 1 || flag2 == 1 || flag3 == 1 || flag4 == 1 || flag5 == 1) {
			double tAdv = accretionTime(r);
			double B = st.magf.get(iR);
			double height = height_fun(r);
			p.ps.iterate([&](const SpaceIterator& iRE) {
				double E = iRE.val(DIM_E);
				double tAcc = 1.0/accelerationRate(E,B);
				double tDiff = diffusionTimeTurbulence(E,height,p,B);
				double tSyn = E/lossesSyn(E,B,p);

				file << "\t" << (int)(r/schwRadius)
					 << "\t" << safeLog10(E/(p.mass*cLight2))
					 << "\t" << safeLog10(tAdv)
					 << "\t" << safeLog10(tDiff)
					 << "\t" << safeLog10(tSyn);

				double tDecay(0.0),tPP(1.0e30),tPG(0.0),tBH(0.0);
				if (p.id == "ntMuon") {
					tDecay = muonMeanLife*(E/(p.mass*cLight2));
					file << "\t" << safeLog10(tDecay) << endl;
				} else if (p.id == "ntChargedPion") {
					tDecay = chargedPionMeanLife*(E/(p.mass*cLight2));
					tPP = E/lossesHadronics(E,st.denf_i.get(iR),p);
					tPG = E/lossesPhotoMeson(E,p,st.photon.distribution,iR,st.photon.emin(),
									st.photon.emax());
					tBH = E/lossesPhotoPair(E,p,st.photon.distribution,iR,st.photon.emin(),
									st.photon.emax());
					file << "\t" << safeLog10(tDecay)
						 << "\t" << safeLog10(tPP)
						 << "\t" << safeLog10(tPG)
						 << "\t" << safeLog10(tBH) << endl;
				}
			},{-1,iR.coord[DIM_R],0});
		}
	},{0,-1,0});
	file.close();
}

void radiativeLossesNeutron(Particle& n, State& st, const std::string& filename)
{
	std::ofstream file;
	file.open(filename.c_str(), std::ios::out);

	file << "r [Rs]"
		<< "\t" << "E [GeV]"
		<< "\t" << "Escape"
		<< "\t" << "Decay"
		<< "\t" << "pp"
		<< "\t" << "pg"
		<< std::endl;

	double phEmin = st.photon.emin();
	double phEmax = st.photon.emax();

	int flag1,flag2,flag3,flag4,flag5;
	flag1 = flag2 = flag3 = flag4 = flag5 = 0;
	double logr1,logr2,logr3,logr4,logr5;
	logr1 = log10(1.5);
	double aux = log10(st.denf_e.ps[DIM_R].last()/schwRadius)/4.0;
	logr2 = logr1+aux;
	logr3 = logr2+aux;
	logr4 = logr3+aux;
	logr5 = logr4+aux;
	n.ps.iterate([&](const SpaceIterator& iR) {
		double r = iR.val(DIM_R);
		double logr = log10(r/schwRadius);
		if (logr > logr1) flag1++;
		if (logr > logr2) flag2++;
		if (logr > logr3) flag3++;
		if (logr > logr4) flag4++;
		if (logr > 0.9*logr5) flag5++;

		if (flag1 == 1 || flag2 == 1 || flag3 == 1 || flag4 == 1 || flag5 == 1) {
			n.ps.iterate([&](const SpaceIterator& iRE) {
				double En = iRE.val(DIM_E);
				double gamma_n = En / (n.mass*cLight2);
				double tEscape = height_fun(r) / cLight;
				double tDecay = gamma_n*neutronMeanLife;
				double loss_np = lossesHadronics(En,st.denf_i.get(iR),n);
				double loss_ng = lossesPhotoMeson(En,n,st.photon.distribution,iR,phEmin,phEmax);
				double tNP = (loss_np > 0.0) ? En / loss_np : 1e30;
				double tNG = (loss_ng > 0.0) ? En / loss_ng : 1e30;

				file << r/schwRadius << "\t" << En / (EV_TO_ERG*1e9)
						 << "\t" << tEscape
						 << "\t" << tDecay
						 << "\t" << tNP
						 << "\t" << tNG << endl;
			},{-1,iR.coord[DIM_R],0});
		}
	},{0,-1,0});
	file.close();
}


#ifndef FIBER_HPP
#define FIBER_HPP

#include <skelly_sim.hpp>

#include <Eigen/LU>
#include <list>
#include <unordered_map>

#include <kernels.hpp>
#include <params.hpp>

class Periphery;

/// @brief Class to represent a single flexible filament
///
/// Actions on the fiber class are typically handled via the container object, which will
/// distribute calls appropriately across all fibers in the container.
class Fiber {
  public:
    enum BC { Force, Torque, Velocity, AngularVelocity, Position, Angle };
    static const std::string BC_name[];

    // Input parameters
    int n_nodes_;                  ///< number of nodes representing the fiber
    double length_;                ///< Desired 'constraint' length of fiber
    double length_prev_;           ///< Last accepted length_
    double bending_rigidity_;      ///< bending rigidity 'E' of fiber
    double penalty_param_ = 500.0; ///< @brief Tension penalty parameter for linear operator @see update_linear_operator
    /// @brief scale of external force on node @see generate_external_force
    /// \f[{\bf f} = f_s * {\bf x}_s\f]
    double force_scale_ = 0.0;
    // FIXME: Magic numbers in linear operator calculation
    double beta_tstep_ = 1.0; ///< penalty parameter to ensure inextensibility
    double epsilon_ = 1E-3;   ///< slenderness parameter

    /// (body, site) pair for minus end binding. -1 implies unbound
    std::pair<int, int> binding_site_{-1, -1};

    double v_growth_ = 0.0;      ///< instantaneous fiber growth velocity
    bool near_periphery = false; ///< flag if interacting with periphery

    /// @brief Coefficient for SBT @see Fiber::init
    /// \f[ c_0 = -\frac{log(e \epsilon^\ell)}{8 \pi \eta}\f]
    double c_0_;

    /// @brief Coefficient for SBT @see Fiber::init
    /// \f[ c_1 = \frac{1}{4\pi\eta} \f]
    double c_1_;

    /// Boundary condition pair for minus end of fiber
    std::pair<BC, BC> bc_minus_ = {BC::Velocity, BC::AngularVelocity};
    /// Boundary condition pair for plus end of fiber
    std::pair<BC, BC> bc_plus_ = {BC::Force, BC::Torque};

    Eigen::MatrixXd x_;     ///< [ 3 x n_nodes_ ] matrix representing coordinates of fiber nodes
    Eigen::MatrixXd xs_;    ///< [ 3 x n_nodes_ ] matrix representing first derivative of fiber nodes
    Eigen::MatrixXd xss_;   ///< [ 3 x n_nodes_ ] matrix representing second derivative of fiber nodes
    Eigen::MatrixXd xsss_;  ///< [ 3 x n_nodes_ ] matrix representing third derivative of fiber nodes
    Eigen::MatrixXd xssss_; ///< [ 3 x n_nodes_ ] matrix representing fourth derivative of fiber nodes

    /// [ 3*n_nodes_ x 3*n_nodes_] Oseen tensor for fiber @see Fiber::update_stokeslet
    Eigen::MatrixXd stokeslet_;

    Eigen::MatrixXd A_;                         ///< Fiber's linear operator for matrix solver
    Eigen::FullPivLU<Eigen::MatrixXd> A_LU_; ///< Fiber preconditioner, LU decomposition of Fiber::A_
    /// Fiber force operator, @see Fiber::update_force_operator, FiberContainer::apply_fiber_force
    Eigen::MatrixXd force_operator_;
    Eigen::VectorXd RHS_; ///< Current 'right-hand-side' for matrix formulation of solver

    /// Structure that caches arrays useful for calculating various fiber values
    typedef struct {
        Eigen::ArrayXd alpha;
        Eigen::ArrayXd alpha_roots;
        Eigen::ArrayXd alpha_tension;
        Eigen::ArrayXd weights_0;
        Eigen::MatrixXd D_1_0;
        Eigen::MatrixXd D_2_0;
        Eigen::MatrixXd D_3_0;
        Eigen::MatrixXd D_4_0;
        Eigen::MatrixXd P_X;
        Eigen::MatrixXd P_T;
        Eigen::MatrixXd P_downsample_bc;
    } fib_mat_t;

    /// Map of cached matrices for different values of n_nodes_. Calculated automagically at program start. @see
    /// compute_matrices
    const static std::unordered_map<int, fib_mat_t> matrices_;

    Fiber(toml::value &fiber_table, double eta);
    Fiber() = default;

    /// @brief initialize empty fiber
    /// @param[in] n_nodes fiber 'resolution'
    /// @param[in] bending_rigidity bending rigidity of fiber
    /// @param[in] eta fluid viscosity
    ///
    /// @deprecated Initializing with a toml::table structure is the preferred initialization. This is only around for
    /// testing.
    Fiber(int n_nodes, double bending_rigidity, double eta) : n_nodes_(n_nodes), bending_rigidity_(bending_rigidity) {
        init(eta);
    };

    ///< @brief Set some default values and resize arrays
    ///
    ///< _MUST_ be called from constructors.
    ///
    /// Initializes: Fiber::x_, Fiber::xs_, Fiber::xss_, Fiber::xsss_, Fiber::xssss_, Fiber::c_0_, Fiber::c_1_
    void init(double eta) {
        x_ = Eigen::MatrixXd::Zero(3, n_nodes_);
        x_.row(0) = Eigen::ArrayXd::LinSpaced(n_nodes_, 0, 1.0).transpose();
        xs_.resize(3, n_nodes_);
        xss_.resize(3, n_nodes_);
        xsss_.resize(3, n_nodes_);
        xssss_.resize(3, n_nodes_);
        length_prev_ = length_;

        c_0_ = -log(M_E * std::pow(epsilon_, 2)) / (8 * M_PI * eta);
        c_1_ = 2.0 / (8.0 * M_PI * eta);
    };

    void update_preconditioner();
    void update_force_operator();
    void update_RHS(double dt, MatrixRef &flow, MatrixRef &f_external);
    void update_linear_operator(double dt, double eta);
    void apply_bc_rectangular(double dt, MatrixRef &v_on_fiber, MatrixRef &f_on_fiber);
    void translate(const Eigen::Vector3d &r) { x_.colwise() += r; };
    void update_derivatives();
    void update_stokeslet(double);
    bool attached_to_body() { return binding_site_.first >= 0; };
    MSGPACK_DEFINE_MAP(n_nodes_, length_, bending_rigidity_, penalty_param_, force_scale_, beta_tstep_, epsilon_,
                       binding_site_, x_);
};

/// Class to hold the fiber objects.
///
/// The container object is designed to work on fibers local to that MPI rank. Each MPI rank
/// should have its own container, with its own unique fibers. The container object does not
/// have any knowledge of the MPI world state, which, for example, is passed in externally to
/// the FiberContainer::flow method and potentially others.
///
/// Developer note: ideally all interactions with the fiber objects should be through this
/// container, except for testing purposes. Operating on fibers outside of the container class
/// is ill-advised.
class FiberContainer {
  public:
    std::list<Fiber> fibers; ///< Array of fibers local to this MPI rank
    /// pointer to FMM object (pointer to avoid constructing stokeslet_kernel_ with default FiberContainer)
    std::shared_ptr<kernels::FMM<stkfmm::Stk3DFMM>> stokeslet_kernel_;

    /// Empty container constructor to avoid initialization list complications. No way to
    /// initialize after using this constructor, so overwrite objects with full constructor.
    FiberContainer() = default;
    FiberContainer(toml::array &fiber_tables, Params &params);

    void update_derivatives();
    void update_stokeslets(double eta);
    void update_linear_operators(double dt, double eta);
    void update_cache_variables(double dt, double eta);
    void update_RHS(double dt, MatrixRef &v_on_fibers, MatrixRef &f_on_fibers);
    void apply_bc_rectangular(double dt, MatrixRef &v_on_fibers, MatrixRef &f_on_fibers);

    /// @brief get total number of nodes across fibers in the container
    /// Usually you need this to form arrays used as input later
    /// @returns total number of nodes across fibers in the container :)
    int get_local_node_count() const {
        // FIXME: This could certainly be cached
        int tot = 0;
        for (auto &fib : fibers)
            tot += fib.n_nodes_;
        return tot;
    };

    int get_global_total_fib_nodes() const;

    /// @brief Get the size of all local fibers contribution to the matrix problem solution
    int get_local_solution_size() const { return get_local_node_count() * 4; }

    /// @brief Get number of local fibers
    int get_local_count() const { return fibers.size(); };

    /// @brief Get number of local fibers
    int get_global_count() const;

    Eigen::MatrixXd generate_constant_force() const;
    Eigen::MatrixXd get_local_node_positions() const;
    Eigen::VectorXd get_RHS() const;
    Eigen::MatrixXd flow(MatrixRef &forces, MatrixRef &r_trg_external, double eta) const;
    Eigen::VectorXd matvec(VectorRef &x_all, MatrixRef &v_fib, MatrixRef &v_fib_boundary) const;
    Eigen::MatrixXd apply_fiber_force(VectorRef &x_all) const;
    Eigen::VectorXd apply_preconditioner(VectorRef &x_all) const;

    void update_boundary_conditions(Periphery &shell, bool periphery_binding_flag);

  private:
    int world_size_ = -1;
    int world_rank_;

  public:
    MSGPACK_DEFINE(fibers);
};

#endif


#pragma once
#include <Eigen/Sparse>
#include <tuple>

#include "grid.h"
#include "mtao/algebra/combinatorial.hpp"
#include "mtao/iterator/enumerate.hpp"
#include "staggered_grid_utils.hpp"

namespace mtao::geometry::grid {

// A not-so-simple staggered grid class
template <typename T, int Dim>
struct StaggeredGrid {
   public:
    constexpr static int D = Dim;
    using GridType = GridD<T, Dim>;
    using Scalar = T;
    using BBox = typename GridType::BBox;
    using Indexer = typename GridType::Indexer;
    using coord_type = typename GridType::coord_type;
    using Vec = typename GridType::Vec;
    using VecMap = typename GridType::VecMap;
    using CVecMap = typename GridType::CVecMap;
    using IVec = typename GridType::IVec;
    using IVecMap = typename GridType::IVecMap;
    using CIVecMap = typename GridType::CIVecMap;
    using StaggeredGrids =
        decltype(staggered_grid::make_grids(std::declval<GridType>()));

    const Vec& dx() const { return vertex_grid().dx(); }

    template <int N>
    constexpr static size_t form_grid_size() {
        return std::tuple_size<std::tuple_element_t<N, StaggeredGrids>>();
    }
    /*
    StaggeredGrid(const coord_type& shape): Base(shape) {
        resize_grids();
    }
    */
    template <typename U>
    StaggeredGrid(const StaggeredGrid<U, D>& o)
        : StaggeredGrid(o.vertex_grid().template cast<T>()) {}
    // the vertex grid is the "defining" type
    StaggeredGrid(const GridType& g) {
        std::get<0>(std::get<0>(m_grids)) = g;
        resize_grids();
    }
    StaggeredGrid(const coord_type& g) {
        std::get<0>(std::get<0>(m_grids)) = g;
        resize_grids();
    }
    /*
    template <typename... Args>
        StaggeredGrid(Args&&... args) {

            static_assert((std::is_integral_v<Args> && ... ));
            std::get<0>(std::get<0>(m_grids)) =
    GridType(std::forward<Args>(args)...); resize_grids();
        }
        */
    // template <typename... Args>
    //    StaggeredGrid(const coord_type& shape, Args&&... args): Base(shape,
    //    std::forward<Args>(args)...) {
    //        resize_grids();
    //    }
    StaggeredGrid() {}
    StaggeredGrid(const StaggeredGrid& other) = default;
    StaggeredGrid(StaggeredGrid&& other) = default;
    StaggeredGrid& operator=(const StaggeredGrid& other) = default;
    StaggeredGrid& operator=(StaggeredGrid&& other) = default;

    auto bbox() const { return vertex_grid().bbox(); }
    static StaggeredGrid from_bbox(BBox bb, const coord_type& shape,
                                   bool cubes = false) {
        return GridType::from_bbox(bb, shape, cubes);
    }
    auto&& origin() const { return vertex_grid().origin(); }
    template <int N, int K>
    const GridType& grid() const {
        return std::get<K>(std::get<N>(m_grids));
    }
    template <int N>
    auto&& grids() const {
        return std::get<N>(m_grids);
    }
    template <int N>
    const GridType& grid(int K) const {
        return std::get<N>(m_grids)[K];
    }
    auto&& vertex_grid() const { return grid<0, 0>(); }
    auto&& cell_grid() const { return grid<D, 0>(); }
    template <int N>
    auto offsets() const {
        return std::get<N>(m_offsets);
    }
    template <int N, int K>
    int offset() const {
        return offsets<N>()[K];
    }
    template <int N>
    int offset(int K) const {
        return offsets<N>()[K];
    }

    template <int N, int K>
    size_t staggered_index(const coord_type& idx) const {
        return offset<N, K>() + grid<N, K>().index(idx);
    }
    template <int N>
    size_t staggered_index(const coord_type& idx, int K) const {
        return offset<N>(K) + grid<N>(K).index(idx);
    }
    template <int N, int K>
    auto staggered_unindex(int idx) const {
        return grid<N, K>().unindex(idx - offset<N, K>());
    }
    template <int N>
    auto staggered_unindex(int idx, int K) const {
        return grid<N>(K).unindex(idx - offset<N>(K));
    }
    template <int N, int K>
    auto staggered_vertices() const {
        return grid<N, K>().vertices();
    }
    template <int N, int K>
    auto staggered_vertex(const coord_type& idx) const {
        return grid<N, K>().vertex(idx);
    }
    template <int N, int K>
    const coord_type& staggered_shape() const {
        return grid<N, K>().shape();
    }
    template <int N, int K>
    size_t staggered_size() const {
        return grid<N, K>().size();
    }
    template <int N>
    auto staggered_vertices(int K) const {
        return grid<N>(K).vertices();
    }
    template <int N>
    auto staggered_vertex(const coord_type& idx, int K) const {
        return grid<N>(K).vertex(idx);
    }
    template <int N>
    const coord_type& staggered_shape(int K) const {
        return grid<N>(K).shape();
    }
    template <int N>
    size_t staggered_size(int K) const {
        return grid<N>(K).size();
    }
    template <int N, int K = 0>
    auto staggered_vertex(int idx) const {
        return staggered_vertex<N, K>(staggered_unindex<N, K>(idx));
    }
    template <int N>
    auto staggered_vertex(int idx, int K) const {
        return staggered_vertex<N>(staggered_unindex<N>(idx, K), K);
    }
    template <int N, int K>
    bool staggered_valid_index(const coord_type& idx) const {
        return grid<N, K>().valid_index(idx);
    }
    template <int N>
    bool staggered_valid_index(const coord_type& idx, int K) const {
        return grid<N>(K).valid_index(idx);
    }

    auto vertex(const coord_type& idx) const {
        return staggered_vertex<0, 0>(idx);
    }
    auto vertex(int idx) const { return vertex(staggered_unindex<0, 0>(idx)); }
    auto vertices() const { return staggered_vertices<0, 0>(); }
    auto cell_vertex(const coord_type& idx) const {
        return staggered_vertex<D>(idx);
    }
    auto cell_vertex(int idx) const {
        return vertex(staggered_unindex<D, 0>(idx));
    }
    auto cell_vertices() const { return staggered_vertices<D, 0>(); }

    size_t u_index(const coord_type& idx) const {
        return staggered_index<1, 0>(idx);
    }
    size_t v_index(const coord_type& idx) const {
        return staggered_index<1, 1>(idx);
    }
    size_t w_index(const coord_type& idx) const {
        return staggered_index<1, 2>(idx);
    }
    size_t vw_index(const coord_type& idx) const {
        return staggered_index<2, 0>(idx);
    }
    size_t uw_index(const coord_type& idx) const {
        return staggered_index<2, 1>(idx);
    }
    size_t uv_index(const coord_type& idx) const {
        return staggered_index<2, 2>(idx);
    }
    size_t vertex_index(const coord_type& idx) const {
        return staggered_index<0, 0>(idx);
    }
    size_t cell_index(const coord_type& idx) const {
        return staggered_index<D, 0>(idx);
    }
    coord_type u_unindex(size_t idx) const {
        return staggered_unindex<1, 0>(idx);
    }
    coord_type v_unindex(size_t idx) const {
        return staggered_unindex<1, 1>(idx);
    }
    coord_type w_unindex(size_t idx) const {
        return staggered_unindex<1, 2>(idx);
    }
    coord_type vw_unindex(size_t idx) const {
        return staggered_unindex<2, 0>(idx);
    }
    coord_type uw_unindex(size_t idx) const {
        return staggered_unindex<2, 1>(idx);
    }
    coord_type uv_unindex(size_t idx) const {
        return staggered_unindex<2, 2>(idx);
    }
    coord_type vertex_unindex(size_t idx) const {
        return staggered_unindex<0, 0>(idx);
    }
    coord_type cell_unindex(size_t idx) const {
        return staggered_unindex<D, 0>(idx);
    }

    const coord_type& u_shape() const { return staggered_shape<1, 0>(); }
    const coord_type& v_shape() const { return staggered_shape<1, 1>(); }
    const coord_type& w_shape() const { return staggered_shape<1, 2>(); }
    const coord_type& vw_shape() const { return staggered_shape<2, 0>(); }
    const coord_type& uw_shape() const { return staggered_shape<2, 1>(); }
    const coord_type& uv_shape() const { return staggered_shape<2, 2>(); }
    const coord_type& vertex_shape() const { return staggered_shape<0, 0>(); }
    const coord_type& cell_shape() const { return staggered_shape<D, 0>(); }

    size_t u_size() const { return staggered_size<1, 0>(); }
    size_t v_size() const { return staggered_size<1, 1>(); }
    size_t w_size() const { return staggered_size<1, 2>(); }
    size_t vw_size() const { return staggered_size<2, 0>(); }
    size_t uw_size() const { return staggered_size<2, 1>(); }
    size_t uv_size() const { return staggered_size<2, 2>(); }
    size_t vertex_size() const { return staggered_size<0, 0>(); }
    size_t cell_size() const { return staggered_size<D, 0>(); }

    bool u_valid_index(const coord_type& idx) const {
        return staggered_valid_index<1, 0>(idx);
    }
    bool v_valid_index(const coord_type& idx) const {
        return staggered_valid_index<1, 1>(idx);
    }
    bool w_valid_index(const coord_type& idx) const {
        return staggered_valid_index<1, 2>(idx);
    }
    bool vw_valid_index(const coord_type& idx) const {
        return staggered_valid_index<2, 0>(idx);
    }
    bool uw_valid_index(const coord_type& idx) const {
        return staggered_valid_index<2, 1>(idx);
    }
    bool uv_valid_index(const coord_type& idx) const {
        return staggered_valid_index<2, 2>(idx);
    }
    bool vertex_valid_index(const coord_type& idx) const {
        return staggered_valid_index<0, 0>(idx);
    }
    bool cell_valid_index(const coord_type& idx) const {
        return staggered_valid_index<D, 0>(idx);
    }

    template <int D>
    std::array<T, combinatorial::nCr(Dim, D)> form_volumes() const {
        std::array<T, combinatorial::nCr(Dim, D)> R;
        if constexpr (D == 0) {
            return {{T(1)}};
        } else if constexpr (D == 1) {
            std::copy(dx().data(), dx().data() + dx().size(), R.begin());
        } else if constexpr (D == Dim - 1) {
            std::fill(R.begin(), R.end(), 1);
            for (int i = 0; i < Dim; ++i) {
                for (int j = 1; j < Dim; ++j) {
                    R[i] *= dx()((i + j) % Dim);
                }
            }
        } else if constexpr (D == Dim) {
            return {{dx().prod()}};
        }
        return R;
    }
    std::vector<T> form_volumes(int D) const {
        int size = combinatorial::nCr(Dim, D);
        std::vector<T> R(size, 1);
        if (D == 0) {
            return {T(1)};
        } else if (D == 1) {
            std::copy(dx().data(), dx().data() + dx().size(), R.begin());
        } else if (D == Dim - 1) {
            std::fill(R.begin(), R.end(), 1);
            for (int i = 0; i < Dim; ++i) {
                for (int j = 1; j < Dim; ++j) {
                    R[i] *= dx()((i + j) % Dim);
                }
            }
        } else if (D == Dim) {
            return {dx().prod()};
        }
        return R;
    }

    template <int D>
    size_t form_size() const {
        auto&& gs = std::get<D>(m_offsets);
        using U = types::remove_cvref_t<decltype(gs)>;
        return gs[std::tuple_size<U>() - 1];
    }
    size_t edge_size() const { return form_size<1>(); }
    size_t flux_size() const { return form_size<D - 1>(); }

    template <int D>
    int form_type(int index) const {
        if (index < 0) {
            return -1;
        }
        using namespace iterator;
        auto&& ofs = offsets<D>();
        size_t result = 0;
        using U = types::remove_cvref_t<decltype(ofs)>;
        for (auto&& [i, v] : enumerate(reverse(ofs))) {
            if (index >= v) {
                result = std::tuple_size<U>() - i - 1;
                break;
            }
        }
        if (result >= ofs.size()) {
            return -1;
        }
        return result;
    }

    size_t edge_type(int index) const { return form_type<1>(index); }
    template <int D>
    auto form_unindex(int idx) const {
        int ft = form_type<D>(idx);
        return std::make_tuple(staggered_unindex<D>(idx, ft), ft);
    }
    template <typename Derived>
    auto coord(const Eigen::MatrixBase<Derived>& v) const {
        return vertex_grid().coord(v);
    }
    template <int N>
    Eigen::SparseMatrix<T> boundary() const {
        int rows = form_size<N - 1>();
        int cols = form_size<N>();
        std::vector<Eigen::Triplet<T>> trips;
        for (auto&& [K, grids] : iterator::enumerate(std::get<N>(m_grids))) {
            std::bitset<D> difference_mask = combinatorial::nCr_mask<D>(N, K);
            grid<N>(K).loop([&, K = K](const coord_type& c) {
                int col = staggered_index<N>(c, K);
                // auto s = staggered_shape<N>(K);
                masked_difference_looper(
                    difference_mask, c,
                    [&](const coord_type& l, const coord_type& u, int d) {
                        std::bitset mybs = difference_mask;
                        mybs[d] = 0;
                        int dim = combinatorial::nCr_unmask<D>(N - 1, mybs);

                        // auto s = staggered_shape<N-1>(dim);
                        int lrow =
                            static_cast<int>(staggered_index<N - 1>(l, dim));
                        int urow =
                            static_cast<int>(staggered_index<N - 1>(u, dim));
                        trips.emplace_back(lrow, col, T{-1});
                        trips.emplace_back(urow, col, T{1});
                    });
            });
        }

        Eigen::SparseMatrix<T> A(rows, cols);
        A.setFromTriplets(trips.begin(), trips.end());
        return A;
    }
    template <int N, typename Func>
    static void cell_vertex_looper(int K, const coord_type& c, Func&& f) {
        staggered_grid::internal::bitmask_looper(
            combinatorial::nCr_mask<D>(N, K), [&](const std::bitset<D>& bs) {
                coord_type cc = c;
                for (int j = 0; j < D; ++j) {
                    cc[j] += bs[j];
                }
                f(cc);
            });
    }
    template <int N, typename Func>
    static void cell_dual_vertex_looper(int K, const coord_type& c, Func&& f) {
        staggered_grid::internal::bitmask_looper(
            !combinatorial::nCr_mask<D>(N, K), [&](const std::bitset<D>& bs) {
                coord_type cc = c;
                for (int j = 0; j < D; ++j) {
                    cc[j] -= bs[j];
                }
                f(cc);
            });
    }

    template <typename Func>
    static void masked_difference_looper(const std::bitset<D>& mask,
                                         const coord_type& c, Func&& f) {
        coord_type cc = c;
        for (int j = 0; j < D; ++j) {
            if (mask[j]) {
                cc[j]++;
                const coord_type& ccc = cc;
                f(c, ccc, j);
                cc[j]--;
            }
        }
    }
    // f( lower_index_boundary, higher_index_boundary, index) as a centered
    // difference stencil for <1>::instance boundary_cell_looper<1>(0,{N+.5},f)
    // calls
    //          f({N},{N+1},0)
    // for <2>::instance boundary_cell_looper<2>(1,{N+.5,M+.5},f) calls
    //          f({N,M+.5},{N+1,M+.5},0)
    //          f({N+.5,M},{N+.5,M+1},1)
    template <int N, typename Func>
    static void cell_boundary_looper(int K, const coord_type& c, Func&& f) {
        auto comb = combinatorial::nCr_mask<D>(N, K);
        masked_difference_looper(comb, c, f);
    }

    // opposite of the above
    template <int N, typename Func>
    static void dual_cell_boundary_looper(int K, const coord_type& c,
                                          Func&& f) {
        coord_type cc = c;
        auto comb = !combinatorial::nCr_mask<D>(N, K);
        for (int j = 0; j < D; ++j) {
            if (comb[j]) {
                cc[j]--;
                const coord_type& ccc = cc;
                f(ccc, c, j);
                cc[j]++;
            }
        }
    }

    template <int N>
    auto form_vertices(int K, const coord_type& c) const {
        std::array<int, 1 << N> ret;
        auto it = ret.begin();
        cell_vertex_looper<N>(
            K, c, [&](const coord_type& c) { *(it++) = vertex_index(c); });
        return ret;
    }
    template <int D>
    auto form_vertices(int idx) const {
        int K = form_type<D>(idx);
        auto coord = staggered_unindex<D>(idx, K);
        return form_vertices<D>(K, coord);
    }
    auto edge(int K, const coord_type& c) const -> std::array<int, 2> {
        return form_vertices<1>(K, c);
    }
    auto edge(int idx) const { return form_vertices<1>(idx); }

   private:
    void resize_grids() {
        m_grids = staggered_grid::make_grids(vertex_grid());
        m_offsets = staggered_grid::staggered_grid_offsets(vertex_shape());
    }
    StaggeredGrids m_grids;
    decltype(staggered_grid::staggered_grid_offsets(
        std::declval<coord_type>())) m_offsets;
};

using StaggeredGrid2f = StaggeredGrid<float, 2>;
using StaggeredGrid3f = StaggeredGrid<float, 3>;
using StaggeredGrid2d = StaggeredGrid<double, 2>;
using StaggeredGrid3d = StaggeredGrid<double, 3>;
}  // namespace mtao::geometry::grid


#include <iostream>
#include <list>
#include <experimental/filesystem>
#include <fstream>
#include <map>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <armadillo>
#include <omp.h>
#include <cassert>

#include "../../lib/json.hpp"
#include "../exciton_transfer/cnt.h"
#include "../helper/prepare_directory.hpp"
#include "../helper/progress.hpp"
#include "monte_carlo.h"

namespace mc
{

  // high level method to calculate proper scattering table
  std::vector<std::vector<scattering_struct>> monte_carlo::create_scattering_table(nlohmann::json j) {
    assert(j.count("rate type")>0);

    std::string rate_type = j["rate type"].get<std::string>();

    std::cout << "\ninitializing scattering table with " << rate_type << "..." << std::endl;

    if (rate_type == "davoody") {

      // get the parent directory for cnts
      std::string parent_directory = j["cnts"]["directory"];
      j["cnts"].erase("directory");
      j["cnts"].erase("comment");

      // create excitons and calculate exciton dispersions
      std::vector<cnt> cnts;
      cnts.reserve(j["cnts"].size()); // this is reservation of space is crucial to ensure we do not move
                                      // cnts, since the move constructor is not implemented yet

      for (const auto& j_cnt : j["cnts"]) {
        cnts.emplace_back(cnt(j_cnt, parent_directory));
        cnts.back().calculate_exciton_dispersion();
      };

	  std::vector<std::vector<scattering_struct>> all_tables(size(cnts));
	  for (int i = 0; i < size(cnts); i++) {
		  all_tables[i] = std::vector<scattering_struct>(size(cnts));
		  for (int j = 0; j < size(cnts); j++) {
			  all_tables[i][j] = create_davoody_scatt_table(cnts[i], cnts[j]);
		  }
	  }

	  return all_tables;
    }

    /*if (rate_type == "forster") {
      return create_forster_scatt_table(1.e15, 1.4e9);
    }

    if (rate_type == "wong") {
      return create_forster_scatt_table(1.e13, 1.4e9);
    }*/

    throw std::invalid_argument("rate type must be one of the following: \"davoody\", \"forster\", \"wong\"");

  };

  // method to calculate scattering rate via davoody et al. method
  scattering_struct monte_carlo::create_davoody_scatt_table(const cnt& d_cnt, const cnt& a_cnt) {
    auto zshift_prop = _json_prop["zshift [m]"];
    arma::vec z_shift = arma::linspace<arma::vec>(zshift_prop[0], zshift_prop[1], zshift_prop[2]);

    auto axis_shift_prop_1 = _json_prop["axis shift 1 [m]"];
    arma::vec axis_shift_1 = arma::linspace<arma::vec>(axis_shift_prop_1[0], axis_shift_prop_1[1], axis_shift_prop_1[2]);

    auto axis_shift_prop_2 = _json_prop["axis shift 2 [m]"];
    arma::vec axis_shift_2 = arma::linspace<arma::vec>(axis_shift_prop_2[0], axis_shift_prop_2[1], axis_shift_prop_2[2]);

    auto theta_prop = _json_prop["theta [degrees]"];
    arma::vec theta = arma::linspace<arma::vec>(theta_prop[0], theta_prop[1], theta_prop[2])*(constants::pi/180);

    arma::field<arma::cube> rate(theta.n_elem);
    rate.for_each([&](arma::cube& c){c.zeros(z_shift.n_elem, axis_shift_1.n_elem, axis_shift_2.n_elem);});

    exciton_transfer ex_transfer(d_cnt, a_cnt);

    ex_transfer.save_atom_locations(_output_directory.path(), {0, 0}, 1.5e-9, 0, ".0_angle");
    ex_transfer.save_atom_locations(_output_directory.path(), {0, 0}, 1.5e-9, constants::pi / 2, ".90_angle");
    ex_transfer.save_atom_locations(_output_directory.path(), {0, 0}, 1.5e-9, constants::pi, ".180_angle");

    #ifdef DEBUG_CHECK_RATES_SYMMETRY
    {
      double zsh = 1.5e-9;
      double ash1 = 0;
      double ash2 = 0;

      double th = 0;
      double r = ex_transfer.first_order(zsh, {ash1, ash2}, th, false);
      std::cout << "rate(" << th << ") = " << r << std::endl;

      th = constants::pi;
      r = ex_transfer.first_order(zsh, {ash1, ash2}, th, false);
      std::cout << "rate(" << th << ") = " << r << std::endl;

      std::exit(0);
    }
    #endif

    // progress_bar prog(theta.n_elem*z_shift.n_elem*axis_shift_1.n_elem*axis_shift_2.n_elem,"create davoody scattering table");
    progress_bar prog(theta.n_elem * z_shift.n_elem * axis_shift_1.n_elem * axis_shift_2.n_elem, "create davoody scattering table");

    #pragma omp parallel
    {
      double th, zsh, ash1, ash2;

      #pragma omp for
      for (unsigned i_th = 0; i_th<theta.n_elem; ++i_th) {

        th = theta(i_th);
        for (unsigned i_zsh = 0; i_zsh < z_shift.n_elem; ++i_zsh) {
          zsh = z_shift(i_zsh);
          for (unsigned i_ash1 = 0; i_ash1 < axis_shift_1.n_elem; ++i_ash1) {
            ash1 = axis_shift_1(i_ash1);
            for (unsigned i_ash2 = 0; i_ash2 < axis_shift_2.n_elem; ++i_ash2) {
              ash2 = axis_shift_2(i_ash2);
              // prog.step();
              rate(i_th)(i_zsh, i_ash1, i_ash2) = ex_transfer.first_order(zsh, {ash1, ash2}, th, false);

              #pragma omp critical
              {
                prog.step();
              }
            }
          }
        }

      }
    }

    scattering_struct scat_table(rate,theta,z_shift,axis_shift_1,axis_shift_2);

    double max_rate = 0;
    double min_rate = 10e15;
    rate.for_each([&min_rate](arma::cube& c) { min_rate = min_rate < c.min() ? min_rate : c.min(); });
    rate.for_each([&max_rate](arma::cube& c) { max_rate = max_rate > c.max() ? max_rate : c.max(); });

    std::cout << std::endl
              << "max rate in davoody scattering table: " << max_rate << " [1/s]" << std::endl
              << "min rate in davoody scattering table: " << min_rate << " [1/s]"
              << std::endl
              << std::endl;

    // std::string filename(_output_directory.path() / "davoody_scat_rates.dat");
    scat_table.save(_output_directory.path());

    return scat_table;
  };

  // method to calculate scattering rate via forster method
  scattering_struct monte_carlo::create_forster_scatt_table(double gamma_0, double r_0) {
    auto zshift_prop = _json_prop["zshift [m]"];
    arma::vec z_shift = arma::linspace<arma::vec>(zshift_prop[0], zshift_prop[1], zshift_prop[2]);

    auto axis_shift_prop_1 = _json_prop["axis shift 1 [m]"];
    arma::vec axis_shift_1 = arma::linspace<arma::vec>(axis_shift_prop_1[0], axis_shift_prop_1[1], axis_shift_prop_1[2]);

    auto axis_shift_prop_2 = _json_prop["axis shift 2 [m]"];
    arma::vec axis_shift_2 = arma::linspace<arma::vec>(axis_shift_prop_2[0], axis_shift_prop_2[1], axis_shift_prop_2[2]);

    auto theta_prop = _json_prop["theta [degrees]"];
    arma::vec theta = arma::linspace<arma::vec>(theta_prop[0], theta_prop[1], theta_prop[2])*(constants::pi/180);

    arma::field<arma::cube> rate(theta.n_elem);
    rate.for_each([&](arma::cube& c){c.zeros(z_shift.n_elem, axis_shift_1.n_elem, axis_shift_2.n_elem);});

    progress_bar prog(theta.n_elem*z_shift.n_elem*axis_shift_1.n_elem*axis_shift_2.n_elem,"create forster scattering table");

    unsigned i_th=0;
    for (const auto& th: theta) {
      unsigned i_zsh=0;
      for (const auto& zsh: z_shift) {
        unsigned i_ash1=0;
        for (const auto& ash1: axis_shift_1) {
          unsigned i_ash2=0;
          for (const auto& ash2: axis_shift_2) {
            prog.step();
            arma::vec r1 = {ash1, 0, 0};
            arma::vec r2 = {ash2*std::cos(th), ash2*std::sin(th), zsh};
            arma::vec dR = r1-r2;
            double angle_factor = std::cos(th)-3*arma::dot(arma::normalise(r1),arma::normalise(dR))*arma::dot(arma::normalise(r2),arma::normalise(dR));
            rate(i_th)(i_zsh,i_ash1,i_ash2) = gamma_0*std::pow(angle_factor,2)*std::pow(1.e-9/arma::norm(dR),6);
            i_ash2++;
          }
          i_ash1++;
        }
        i_zsh++;
      }
      i_th++;
    }

    scattering_struct scat_table(rate,theta,z_shift,axis_shift_1,axis_shift_2);

    return scat_table;
  };

  // slice the domain into n sections in each direction, and return a list of scatterers in the center region as the injection region
  std::vector<const scatterer *> monte_carlo::injection_region(const std::vector<scatterer> &all_scat, const domain_t domain, const int n) {
    assert((n > 0) && (n % 2 == 1));

    double xmin = domain.first(0), ymin = domain.first(1), zmin = domain.first(2);
    double xmax = domain.second(0), ymax = domain.second(1), zmax = domain.second(2);
    double dx = (xmax - xmin) / double(n), dy = (ymax - ymin) / double(n), dz = (zmax - zmin) / double(n);

    std::vector<double> x, y, z;

    for (int i = 0; i <= n; ++i) {
      x.push_back(double(i) * dx + xmin);
      y.push_back(double(i) * dy + ymin);
      z.push_back(double(i) * dz + zmin);
    }

    std::vector<const scatterer *> inject_list;

    for (const auto& s : all_scat) {
      if (x[n / 2] <= s.pos(0) && s.pos(0) <= x[n / 2 + 1] &&
          y[n / 2] <= s.pos(1) && s.pos(1) <= y[n / 2 + 1] &&
          z[n / 2] <= s.pos(2) && s.pos(2) <= z[n / 2 + 1])
        inject_list.push_back(&s);
    }

    return inject_list;
  }

  // slice the domain into n sections in each direction, and return the domain that leaves only 1 section from each side
  monte_carlo::domain_t monte_carlo::get_removal_domain(const monte_carlo::domain_t domain, const int n) {
    assert((n > 1));

    double xmin = domain.first(0), ymin = domain.first(1), zmin = domain.first(2);
    double xmax = domain.second(0), ymax = domain.second(1), zmax = domain.second(2);
    double dx = (xmax - xmin) / double(n), dy = (ymax - ymin) / double(n), dz = (zmax - zmin) / double(n);

    std::vector<double> x, y, z;

    for (int i = 0; i <= n; ++i) {
      x.push_back(double(i) * dx + xmin);
      y.push_back(double(i) * dy + ymin);
      z.push_back(double(i) * dz + zmin);
    }

    domain_t removal_domain;
    removal_domain.first = {x[1], y[1], z[1]};
    removal_domain.second = {x[n-1], y[n-1], z[n-1]};

    return removal_domain;
  }

  // initialize the simulation condition to calculate diffusion coefficient using green-kubo approach
  void monte_carlo::kubo_init() {
    // set maximum hopping radius
    _max_hopping_radius = double(_json_prop["max hopping radius [m]"]);
    std::cout << "maximum hopping radius: " << _max_hopping_radius * 1.e9 << " [nm]\n";

    _particle_velocity = _json_prop["exciton velocity [m/s]"];
    std::cout << "exciton velocity [m/s]: " << _particle_velocity << std::endl;

    //_scat_tables = create_scattering_table(_json_prop);
    _all_scat_list = create_scatterers(_input_directory.path());

    domain_t d = find_simulation_domain();
    std::ios::fmtflags f(std::cout.flags()); // save cout flags to be reset after printing
    std::cout << std::fixed << std::showpos;
    std::cout << "\n"
              << "simulation domain BEFORE trimming:\n"
              << "    x (" << d.first(0) * 1e9 << " , " << d.second(0) * 1e9 << ") [nm]\n"
              << "    y (" << d.first(1) * 1e9 << " , " << d.second(1) * 1e9 << ") [nm]\n"
              << "    z (" << d.first(2) * 1e9 << " , " << d.second(2) * 1e9 << ") [nm]\n"
              << std::endl;
    std::cout.flags(f); // reset the cout flags

    limit_t xlim = _json_prop["trim limits"]["xlim"];
    limit_t ylim = _json_prop["trim limits"]["ylim"];
    limit_t zlim = _json_prop["trim limits"]["zlim"];

    trim_scats(xlim, ylim, zlim, _all_scat_list);

    _domain = find_simulation_domain();
    f = std::cout.flags(); // save cout flags to be reset after printing
    std::cout << std::fixed << std::showpos;
    std::cout << "\n"
              << "simulation domain AFTER trimming:\n"
              << "    x (" << _domain.first(0) * 1e9 << " , " << _domain.second(0) * 1e9 << ") [nm]\n"
              << "    y (" << _domain.first(1) * 1e9 << " , " << _domain.second(1) * 1e9 << ") [nm]\n"
              << "    z (" << _domain.first(2) * 1e9 << " , " << _domain.second(2) * 1e9 << ") [nm]\n"
              << std::endl;
    std::cout.flags(f); // reset the cout flags

    std::cout << "total number of scatterers: " << _all_scat_list.size() << std::endl;

    _quenching_list = create_quenching_sites(_all_scat_list, 10000);
    //set_scat_table(_scat_tables[0][0], _all_scat_list);

    create_scatterer_buckets(_domain, _max_hopping_radius, _all_scat_list, _scat_buckets, _quenching_list, _q_buckets);
    _scat_tables = create_scattering_table(_json_prop);
    set_scat_table(_scat_tables[0][0], _all_scat_list);
    set_max_rate(_max_hopping_radius, _all_scat_list);

    int n = _json_prop["number of sections for injection region"];
    _inject_scats = injection_region(_all_scat_list, _domain, n);
    _removal_domain = get_removal_domain(_domain, n);

    _max_time = _json_prop["maximum time for kubo simulation [seconds]"];
  };

  // create particles for kubo simulation
  void monte_carlo::kubo_create_particles() {
    int n_particle = _json_prop["number of particles for kubo simulation"];
    for (int i=0; i<n_particle; ++i) {
      int dice = std::rand() % _inject_scats.size();
      const scatterer *s = _inject_scats[dice];
      arma::vec pos = s->pos();
      _particle_list.push_back(particle(pos, s, _particle_velocity));
      _particle_list.back().set_init_pos(pos);
    }
  }

  // step the simulation in time
  void monte_carlo::kubo_step(double dt) {
    #pragma omp parallel
    {
      #pragma omp for
      for (unsigned i = 0; i < _particle_list.size(); ++i) {
        particle& p = _particle_list[i];

        p.step(dt, _all_scat_list, _max_hopping_radius);

        p.update_delta_pos();

        if (arma::any(p.pos()<_removal_domain.first) || arma::any(_removal_domain.second < p.pos())){
          int dice = std::rand() % _inject_scats.size();
          const scatterer* s = _inject_scats[dice];
          arma::vec pos = s->pos();
          p.set_pos(pos);
          p.set_scatterer(s);
        }
      }
    }

    // increase simulation time
    _time += dt;
  };

  // save the displacement of individual particles in kubo simulation
  void monte_carlo::kubo_save_individual_particle_dispalcements() {
    if (! _displacement_file_x.is_open()) {
      _displacement_file_x.open(_output_directory.path() / "particle_dispalcement.x.dat", std::ios::out);
      _displacement_file_y.open(_output_directory.path() / "particle_dispalcement.y.dat", std::ios::out);
      _displacement_file_z.open(_output_directory.path() / "particle_dispalcement.z.dat", std::ios::out);

      _displacement_file_x << std::showpos << std::scientific;
      _displacement_file_y << std::showpos << std::scientific;
      _displacement_file_z << std::showpos << std::scientific;

      _displacement_file_x << "time";
      _displacement_file_y << "time";
      _displacement_file_z << "time";
      for (int i=0; i<int(_particle_list.size()); ++i){
        _displacement_file_x << "," << i;
        _displacement_file_y << "," << i;
        _displacement_file_z << "," << i;
      }
      _displacement_file_x << std::endl;
      _displacement_file_y << std::endl;
      _displacement_file_z << std::endl;
    }

    _displacement_file_x << time();
    _displacement_file_y << time();
    _displacement_file_z << time();

    for (const auto& p: _particle_list) {
      _displacement_file_x << "," << p.delta_pos(0);
      _displacement_file_y << "," << p.delta_pos(1);
      _displacement_file_z << "," << p.delta_pos(2);
    }
    _displacement_file_x << std::endl;
    _displacement_file_y << std::endl;
    _displacement_file_z << std::endl;
  };

  void monte_carlo::kubo_save_avg_dispalcement_squared() {
    if (!_displacement_squard_file.is_open()) {
      _displacement_squard_file.open(_output_directory.path() / "particle_dispalcement.avg.squared.dat", std::ios::out);

      _displacement_squard_file << std::showpos << std::scientific;

      _displacement_squard_file << "# this file contains the average of dx^2, dy^2, and dz^2 of the particle ensemble over time" << std::endl
                                << "# number of particles: " << _particle_list.size() << std::endl
                                << std::endl;

      _displacement_squard_file << "time,x,y,z" << std::endl;
    }

    double avg_x2=0, avg_y2=0, avg_z2=0;

    for (const auto& p : _particle_list) {
      avg_x2 += std::pow(p.delta_pos(0), 2);
      avg_y2 += std::pow(p.delta_pos(1), 2);
      avg_z2 += std::pow(p.delta_pos(2), 2);
    }

    avg_x2 /= double(_particle_list.size());
    avg_y2 /= double(_particle_list.size());
    avg_z2 /= double(_particle_list.size());

    _displacement_squard_file << time() << "," << avg_x2 << "," << avg_y2 << "," << avg_z2 << std::endl;
  }

  void monte_carlo::kubo_save_diffusion_tensor(){
    if (!_diffusion_tensor_file.is_open()) {
      _diffusion_tensor_file.open(_output_directory.path() / "particle_diffusion_tensor.dat", std::ios::out);

     _diffusion_tensor_file << std::showpos << std::scientific;

      _diffusion_tensor_file << "# this file contains the diffusion tensor Dij of the particle ensemble over time" << std::endl
                                << "# number of particles: " << _particle_list.size() << std::endl
                                << std::endl;

      _diffusion_tensor_file << "time,Dxx,Dxy,Dxz,Dyy,Dyz,Dzz" << std::endl;
    }

    _diffusion_tensor_file << time();

    double Dij, D1, D2, D3;
    for(int i = 0; i < 3; i++){
      for(int j = i; j < 3; j++){
        for(const auto& p : _particle_list){
          D1 += p.pos(i)*p.pos(j);
          D2 += p.pos(i);
          D3 += p.pos(j);
        }
        Dij = (D1 + D2 * D3)/(double)(_particle_list.size());
        Dij /= double(2 * double(time()));
        _diffusion_tensor_file << "," << Dij;
      }
    }
    _diffusion_tensor_file << std::endl;
  }

  void monte_carlo::kubo_save_diffusion_length() {
    if (!_diffusion_length_file.is_open()) {
      _diffusion_length_file.open(_output_directory.path() / "particle_diffusion_length.dat", std::ios::out);

      _diffusion_length_file << std::showpos << std::scientific;

      _diffusion_length_file << "# this file contains the diffusion length in x,y,z of the particle ensemble over time" << std::endl
                                << "# number of particles: " << _particle_list.size() << std::endl
                                << std::endl;

      _diffusion_length_file << "x,y,z" << std::endl;
    }

    for (const auto& p : _particle_list) {
      _diffusion_length_file << p.diff_len(0) << "," << p.diff_len(1) << "," << p.diff_len(2) << std::endl;
    }

  }

} // end of namespace mc


#ifndef STAN_MATH_PRIM_FUN_GAMMA_Q_HPP
#define STAN_MATH_PRIM_FUN_GAMMA_Q_HPP

#include <stan/math/prim/meta.hpp>
#include <boost/math/special_functions/gamma.hpp>

namespace stan {
namespace math {

/**
 *
   \f[
   \mbox{gamma\_q}(a, z) =
   \begin{cases}
     \textrm{error} & \mbox{if } a\leq 0 \textrm{ or } z < 0\\
     Q(a, z) & \mbox{if } a > 0, z \geq 0 \\[6pt]
     \textrm{NaN} & \mbox{if } a = \textrm{NaN or } z = \textrm{NaN}
   \end{cases}
   \f]

   \f[
   \frac{\partial\, \mbox{gamma\_q}(a, z)}{\partial a} =
   \begin{cases}
     \textrm{error} & \mbox{if } a\leq 0 \textrm{ or } z < 0\\
     \frac{\partial\, Q(a, z)}{\partial a} & \mbox{if } a > 0, z \geq 0 \\[6pt]
     \textrm{NaN} & \mbox{if } a = \textrm{NaN or } z = \textrm{NaN}
   \end{cases}
   \f]

   \f[
   \frac{\partial\, \mbox{gamma\_q}(a, z)}{\partial z} =
   \begin{cases}
     \textrm{error} & \mbox{if } a\leq 0 \textrm{ or } z < 0\\
     \frac{\partial\, Q(a, z)}{\partial z} & \mbox{if } a > 0, z \geq 0 \\[6pt]
     \textrm{NaN} & \mbox{if } a = \textrm{NaN or } z = \textrm{NaN}
   \end{cases}
   \f]

   \f[
   Q(a, z)=\frac{1}{\Gamma(a)}\int_z^\infty t^{a-1}e^{-t}dt
   \f]

   \f[
   \frac{\partial \, Q(a, z)}{\partial a} =
 -\frac{\Psi(a)}{\Gamma^2(a)}\int_z^\infty t^{a-1}e^{-t}dt
   + \frac{1}{\Gamma(a)}\int_z^\infty (a-1)t^{a-2}e^{-t}dt
   \f]

   \f[
   \frac{\partial \, Q(a, z)}{\partial z} = -\frac{z^{a-1}e^{-z}}{\Gamma(a)}
   \f]
   * @throws domain_error if x is at pole
 */
inline double gamma_q(double x, double a) { return boost::math::gamma_q(x, a); }

}  // namespace math
}  // namespace stan
#endif