#ifndef GEODB_INTERVAL_SET_HPP
#define GEODB_INTERVAL_SET_HPP

#include "geodb/algorithm.hpp"
#include "geodb/common.hpp"
#include "geodb/interval.hpp"
#include "geodb/type_traits.hpp"

#include <boost/range/adaptor/indirected.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/equal.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>
#include <boost/range/concepts.hpp>
#include <boost/range/counting_range.hpp>
#include <boost/range/empty.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/sub_range.hpp>
#include <boost/range/metafunctions.hpp>
#include <boost/range/numeric.hpp>
#include <tpie/serialization2.h>

#include <algorithm>
#include <ostream>
#include <queue>
#include <vector>

/// \file
/// Implements a compact set based on sorted, non-overlapping intervals.

namespace geodb {

namespace detail {
    /// Represents the start or end point of some interval.
    template<typename T>
    struct interval_event {
        enum kind_t {
            open = 1, close = 2
        };

        kind_t kind;
        T point;

        // Ordered by point. Open events come before close events on the same point.
        bool operator<(const interval_event& other) const {
            return point < other.point || (point == other.point && kind < other.kind);
        }

        bool operator==(const interval_event& other) const {
            return kind == other.kind && point == other.point;
        }
    };

    /// Takes a range of interval-ranges and invokes the provided callback
    /// for every interval event (open or close) in sorted ascernding order.
    /// Each individual interval range must be in sorted order (ascending)
    /// with no overlapping intervals.
    /// Intervals from different ranges may overlap.
    ///
    /// This function serves as a building block for plane-sweep algorithms over
    /// a series of interval sets.
    ///
    /// Runtime complexity: O(N log M) where N is the total number of intervals
    /// and M is the number of nested ranges.
    template<typename Range, typename Callback>
    void interval_events(const Range& rng, Callback&& cb) {
        using nested_range_type = typename boost::range_value<const Range>::type;

        using nested_iterator_type = typename boost::range_iterator<
            typename boost::range_value<const Range>::type
        >::type;

        using interval_type = typename boost::range_value<nested_range_type>::type;

        using point_type = typename interval_type::value_type;

        using event_type = interval_event<point_type>;

        static_assert(is_specialization_of<interval, interval_type>::value,
                      "nested ranges must contain intervals");
        static_assert(std::is_lvalue_reference<typename boost::range_reference<const Range>::type>::value,
                      "nested ranges must be lvalues (i.e. no temporaries).");

        BOOST_CONCEPT_ASSERT(( boost::ForwardRangeConcept<const Range> ));
        BOOST_CONCEPT_ASSERT(( boost::ForwardRangeConcept<const nested_range_type> ));

        // A cursor tracks the iterator position within one of the subranges.
        // Every interval is visited twice, once for the start and once for the end point.
        struct cursor_t {
        private:
            typename event_type::kind_t kind;
            nested_iterator_type pos;
            nested_iterator_type end;

        public:
            cursor_t(nested_iterator_type pos, nested_iterator_type end)
                : kind(event_type::open)
                , pos(pos), end(end)
            {}

            bool at_end() const { return pos == end; }

            event_type event() const {
                geodb_assert(pos != end, "not at end");
                return kind == event_type::open
                          ? event_type{kind, pos->begin()}
                          : event_type{kind, pos->end()};
            }

            void advance() {
                geodb_assert(pos != end, "not at end");
                if (kind == event_type::open) {
                    kind = event_type::close;
                    return;
                }

                ++pos;
                kind = event_type::open;
            }

            bool operator<(const cursor_t& other) const { return event() < other.event(); }
            bool operator>(const cursor_t& other) const { return other < *this; }
        };

        // A min heap that keeps track of the closest cursor.
        std::priority_queue<cursor_t, std::vector<cursor_t>, std::greater<cursor_t>> queue;
        for (auto&& nested_range : rng) {
            cursor_t c(boost::begin(nested_range), boost::end(nested_range));
            if (!c.at_end()) {
                queue.push(std::move(c));
            }
        }

        while (!queue.empty()) {
            // Pop the closest cursor and get one event from it. Reinsert the
            // cursor if there are more elements.
            cursor_t c = std::move(queue.top());
            queue.pop();

            cb(c.event());

            c.advance();
            if (!c.at_end()) {
                queue.push(std::move(c));
            }
        }
    }

    /// Takes a vector of intervals and a range of iterators into that vector.
    /// Every iterator in `gaps` gives the position after which a new merged interval begins.
    /// \pre `gaps` must be sorted.
    /// \warning `gaps` must not contain the last iterator.
    template<typename T, typename IteratorRange>
    void merge_positions(std::vector<interval<T>>& intervals, const IteratorRange& gaps) {
        using iterator = typename std::vector<interval<T>>::iterator;

        auto gaps_pos = boost::begin(gaps);
        auto gaps_end = boost::end(gaps);

        if (intervals.empty()) {
            return;
        }

        auto write_merged = [](iterator out, iterator a, iterator b) {
            geodb_assert(a <= b, "iterator ordering violated");
            T begin = a->begin();
            T end = b->end();
            *out = interval<T>(begin, end);
        };

        // Merge intervals until a new gap is encountered, at which point a new interval begins.
        iterator in = intervals.begin();
        iterator out = in;
        iterator last = intervals.end();
        while (gaps_pos != gaps_end) {
            // Note: in != last because there are valid iterators in gaps remaining.
            iterator gap = *gaps_pos++;
            geodb_assert(gap != last - 1, "the last interval has no successor.");

            write_merged(out, in, gap);
            ++out;
            in = gap + 1;
        }

        // The interval after the last gap.
        write_merged(out, in, last - 1);
        ++out;
        intervals.erase(out, last);
    }

    /// Merges adjacent intervals until there are no more than
    /// `capacity` intervals in total. Chooses the intervals with the
    /// smallest gaps in between.
    /// The interval vector is modified in-place.
    template<typename T>
    void merge_intervals(std::vector<interval<T>>& intervals, size_t capacity) {
        using iterator = typename std::vector<interval<T>>::iterator;

        geodb_assert(capacity > 0, "invalid capacity");

        const size_t size = intervals.size();
        if (size <= capacity) {
            return;
        }

        // Compute the `capacity - 1` largest gaps.
        // We will keep these gaps and merge all other intervals between them.
        // Then, the size of the new set will have the size `capacity`.
        const size_t k = capacity - 1;
        std::vector<iterator> gaps(k);
        k_smallest(boost::counting_range(intervals.begin(), intervals.end() - 1), k, gaps,
            // Compare the distances between the interval pairs.
            [](const iterator& pos1, const iterator& pos2) {
                geodb_assert(pos1[1].begin() > pos1[0].end(), "intervals must be ordered");
                geodb_assert(pos2[1].begin() > pos2[0].end(), "intervals must be ordered");
                // ">": compute largest instead of k_smallest.
                return pos1[1].begin() - pos1[0].end() > pos2[1].begin() - pos2[0].end();
            }
        );

        // Intervals are ordered by merge cost, now sort them in positional order.
        std::sort(gaps.begin(), gaps.end());

        // Close the gaps in place.
        return merge_positions(intervals, gaps);
    }

} // namespace detail

/// Represents a set of integers as intervals.
/// Points can be inserted as intervals of size 1.
/// Using \ref trim(), one can reduce the size of the set
/// by merging neighboring intervals, thus introducing some error.
///
/// Efficient algorithms for computing the union / intersection of an
/// arbitrary number of interval_sets are provided.
///
/// Note: This class is backed by a sorted vector. Insert performance could
/// be improved by using a balanced search tree instead.
/// Intervals are sorted and do not overlap, so a "normal" 1-D tree
/// would be sufficient.
template<typename T>
class interval_set {
    using storage_type = std::vector<interval<T>>;

public:
    using iterator = typename storage_type::const_iterator;
    using const_iterator = iterator;

    using interval_type = interval<T>;
    using point_type = T;

public:
    /// Takes a range of interval_sets and computes their union.
    ///
    /// Runtime complexity: O(N log M) where N is the total number of intervals across all ranges
    /// and M is the number of ranges.
    template<typename Range>
    static interval_set set_union(Range&& rng)
    {
        using detail::interval_events;

        // Sweep over the plane and keep track of open intervals.
        // Whenever the open counter reaches zero, an element of the union has been found.
        std::vector<interval_type> result;
        point_type begin;   // Start of the earliest still active interval.
        size_t open = 0;    // Number of open intervals.
        interval_events(rng, [&](const auto& event) {
            if (event.kind == event.open) {
                if (++open == 1) {
                    begin = event.point;
                }
            } else {
                if (open-- == 1) {
                    result.push_back(interval_type(begin, event.point));
                }
            }
        });
        return interval_set(std::move(result));
    }

    /// Takes a range of interval_sets and return their intersection.
    ///
    /// Runtime complexity: O(N log M) where N is the total number of intervals across all ranges
    /// and M is the number of ranges.
    template<typename Range>
    static interval_set set_intersection(Range&& rng) {
        using detail::interval_events;

        // Sweep over the plane and keep track of open intervals.
        // When a close event is encountered and the count of open intervals
        // equals the number of sets in `rng`, then the closing interval
        // is a member of the intersection.
        // Note: Intervals in individual sets do not overlap, thus the count
        // of open intervals can never be greater than `size`.
        std::vector<interval_type> result;
        size_t size = boost::size(rng);
        point_type begin = 0;   // Start of the intersection interval.
        size_t open = 0;        // Number of open intervals.
        interval_events(rng, [&](const auto& event) {
            geodb_assert(open <= size, "too many active intervals");

            if (event.kind == event.open) {
                if (++open == size) {
                    begin = event.point;
                }
            } else {
                if (open-- == size) {
                    result.push_back(interval_type(begin, event.point));
                }
            }
        });
        return interval_set(std::move(result));
    }

public:
    /// Creates an empty interval set.
    /// \post `size() == 0`.
    interval_set() {}

    /// Creates a new interval set from the given list of intervals.
    /// The list must be sorted (by start coordinate, ascending) and
    /// adjacent intervals must not overlap.
    interval_set(std::initializer_list<interval_type> list):
        interval_set(list.begin(), list.end()) {}

    /// \copydoc interval_set(std::initializer_list<interval_type>)
    template<typename FwdIterator>
    interval_set(FwdIterator first, FwdIterator last)
        : m_intervals(first, last)
    {
        assert_invariant();
    }

private:
    interval_set(storage_type&& vec): m_intervals(std::move(vec)) {}

public:
    /// Returns the iterator to the first element.
    /// The itervals are disjoint and sorted.
    iterator begin() const { return m_intervals.begin(); }

    /// Returns the past-the-end iterator.
    iterator end() const { return m_intervals.end(); }

    /// Returns the interval at the given index.
    /// \pre `index < size()`.
    const interval_type& operator[](size_t index) const {
        geodb_assert(index < size(), "index out of bounds");
        return *(begin() + index);
    }

    /// Returns true iff `size() == 0`.
    bool empty() const { return size() == 0; }

    /// Returns the size (the number of intervals) of this set.
    size_t size() const { return m_intervals.size(); }

    /// Assign a new set of intervals. Reuses existing capacity.
    template<typename FwdIterator>
    void assign(FwdIterator first, FwdIterator last) {
        m_intervals.assign(first, last);
        assert_invariant();
    }

    /// Adds a point to this set.
    ///
    ///     - If some interval already contains this point, nothing needs to be done.
    ///     - Otherwise, insert a new point-like interval at the appropriate position.
    ///
    /// Runtime complexity: O(size()).
    ///
    /// \post `contains(point)`.
    /// \return Returns true iff adding the point caused the set to change, i.e. when
    ///         the point was not already represented by one of the intervals.
    bool add(point_type point) {
        const auto begin = mut_begin();
        const auto end = mut_end();
        const auto pos = interval_before(point);
        geodb_assert(pos == end || point >= pos->begin(), "");

        if (pos != end && pos->contains(point)) {
            // Point already represented.
            return false;
        }

        geodb_assert(pos == end || point > pos->end(),
                     "point must lie to the right of the found interval");
        m_intervals.insert(pos != end ? pos + 1 : begin, interval_type(point));

        geodb_assert(contains(point), "postcondition violated");
        return true;
    }

    /// Returns true if this set contains the given point.
    /// Runtime complexity: O(log(size)).
    bool contains(point_type point) const {
        const auto pos = interval_before(point);
        if (pos == end()) {
            return false;
        }

        geodb_assert(pos->begin() <= point, "interval to the left");
        return pos->contains(point);
    }

    /// Trims this set to fit the new size.
    /// Excess intervals will be merged.
    /// \pre `size > 0`.
    /// \post `this->size() <= size`.
    void trim(size_t size) {
        detail::merge_intervals(m_intervals, size);
        geodb_assert(m_intervals.size() <= size, "postcondition failure.");
    }

    /// Resets this instance.
    void clear() {
        m_intervals.clear();
    }

    /// Returns the union of \p *this and \p other.
    interval_set union_with(const interval_set& other) const {
        std::array<const interval_set*, 2> args{this, &other};
        return interval_set::set_union(args | boost::adaptors::indirected);
    }

    /// Returns the intersection of \p *this and \p other.
    interval_set intersection_with(const interval_set& other) const {
        std::array<const interval_set*, 2> args{this, &other};
        return interval_set::set_intersection(args | boost::adaptors::indirected);
    }

private:
    using mut_iter = typename storage_type::iterator;

    // mutable iterators not exposed to users.
    mut_iter mut_begin() { return m_intervals.begin(); }
    mut_iter mut_end() { return m_intervals.end(); }

    /// Finds the last interval i thats begin before `point`,
    /// i.e. i.begin() <= point.
    mut_iter interval_before(point_type point) {
        const auto compare = [](point_type p, const interval_type& i) {
            return p < i.begin();
        };

        // pos is the first interval with i.begin() > point.
        const auto pos = std::upper_bound(mut_begin(), mut_end(), point, compare);
        if (pos != begin()) {
            // The predecessor is the interval we're looking for.
            return pos - 1;
        }
        // There is no such interval.
        return mut_end();
    }

    /// \copydoc interval_before()
    iterator interval_before(point_type point) const {
        return static_cast<iterator>(
                    const_cast<interval_set*>(this)->interval_before(point));
    }

    /// Iterate over the intervals, but merge adjacent intervals
    /// that do not have a gap between them (for cleaner display functionality).
    template<typename Func>
    void adjacent_merged(Func&& f) const {
        for (auto i = begin(), e = end(); i != e; ) {
            interval_type c = *i;
            while (++i != e && i->begin() == c.end() + 1) {
                c = interval_type(c.begin(), i->end());
            }
            f(c);
        }
    }

    friend std::ostream& operator<<(std::ostream& o, const interval_set& set) {
        o << "{";
        set.adjacent_merged([&](const interval_type& i) {
            o << i;
        });
        o << "}";
        return o;
    }

    void assert_invariant() {
#ifdef GEODB_DEBUG
        if (empty()) {
            return;
        }

        auto last = m_intervals.end();
        auto iter = m_intervals.begin();
        auto prev = iter++;
        for (; iter != last; ++iter, ++prev) {
            geodb_assert(!iter->overlaps(*prev), "Intervals must not overlap");
            geodb_assert(iter->begin() >= prev->end(), "intervals must be sorted");
        }
#endif
    }

    template<typename Dest>
    friend void serialize(Dest& dst, const interval_set& set) {
        using tpie::serialize;
        serialize(dst, set.m_intervals);
    }

    template<typename Src>
    friend void unserialize(Src& src, interval_set& set) {
        using tpie::unserialize;
        unserialize(src, set.m_intervals);
        set.assert_invariant();
    }

private:
    storage_type m_intervals;
};

/// A variant of \ref interval_set that automatically enforces a capacity.
/// The number of elements within a `static_interval_set` will always be
/// lesser than or equal to `Capacity`.
/// Intervals are merged as neccessary in order to keep this invariant.
template<typename T, size_t Capacity>
class static_interval_set {
    using inner_t = interval_set<T>;

    static_assert(Capacity > 0, "Capacity must not be zero");

public:
    using iterator = typename inner_t::iterator;
    using const_iterator = typename inner_t::const_iterator;

    using point_type = typename inner_t::point_type;
    using interval_type = typename inner_t::interval_type;

public:
    template<typename Range>
    static static_interval_set set_union(Range&& r) {
        inner_t set = inner_t::set_union(std::forward<Range>(r));
        return static_interval_set(std::move(set));
    }

    template<typename Range>
    static static_interval_set set_intersection(Range&& r) {
        inner_t set = inner_t::set_intersection(std::forward<Range>(r));
        return static_interval_set(std::move(set));
    }

    static constexpr size_t capacity() { return Capacity; }

public:
    static_interval_set() {}

    static_interval_set(std::initializer_list<interval_type> list)
        : inner(list)
    {
        trim();
    }

    template<typename FwdIter>
    static_interval_set(FwdIter begin, FwdIter end)
        : inner(begin, end)
    {
        trim();
    }

    explicit static_interval_set(interval_set<T> set)
        : inner(std::move(set))
    {
        trim();
    }

    iterator begin() const { return inner.begin(); }
    iterator end() const { return inner.end(); }

    /// \copydoc interval_set<T>::operator[]
    const interval_type& operator[](size_t index) const { return inner[index]; }

    /// \copydoc interval_set<T>::empty
    bool empty() const { return inner.empty(); }

    /// \copydoc interval_set<T>::size
    size_t size() const { return inner.size(); }

    /// Assign a new set of intervals, merging them if neccessary.
    template<typename FwdIter>
    void assign(FwdIter first, FwdIter last) {
        inner.assign(first, last);
        trim();
    }

    /// Adds the point to the set, merging intervals if the set becomes full.
    ///
    /// \sa interval_set::add
    bool add(point_type point) {
        bool changed = inner.add(point);
        trim();
        return changed;
    }

    /// \copydoc interval_set<T>::trim
    bool contains(point_type point) const { return inner.contains(point); }

    /// \copydoc interval_set<T>::trim
    void trim(size_t size) { inner.trim(size); }

    /// Equivalent to `trim(capacity())`.
    void trim() { trim(capacity()); }

    /// \copydoc interval_set<T>::clear
    void clear() { inner.clear(); }

    /// Returns the union of `*this` and `other`, adjusted for capacity.
    static_interval_set union_with(const static_interval_set& other) const {
        return static_interval_set(inner.union_with(other.inner));
    }

    /// Returns the intersection of `*this` and `other`, adjusted for capacity.
    static_interval_set intersection_with(const static_interval_set& other) const {
        return static_interval_set(inner.intersection_with(other.inner));
    }

    /// Returns a const view to the dynamic interval data.
    operator const interval_set<T>& () const { return inner; }

private:
    friend std::ostream& operator<<(std::ostream& o, const static_interval_set& set) {
        return o << set.inner;
    }

    template<typename Dest>
    friend void serialize(Dest& dst, const static_interval_set& set) {
        using tpie::serialize;
        serialize(dst, set.inner);
    }

    template<typename Src>
    friend void unserialize(Src& src, static_interval_set& set) {
        using tpie::unserialize;
        unserialize(src, set.inner);
    }

private:
    inner_t inner;
};

} // namespace geodb

#endif // GEODB_INTERVAL_SET_HPP


#include <iostream>
#include <string>
#include <vector>
#include <initializer_list>

#include <ROOT/RDataFrame.hxx>
#include <TCanvas.h>
#include <TRatioPlot.h>
#include <yaml-cpp/yaml.h>
#include <boost/algorithm/string.hpp>
#include <boost/program_options.hpp>

#include <FileInPath.h>
#include <Options.h>
#include <Utils.h>
using namespace ROOT;
namespace po = boost::program_options;

void drawHist(RDF::RNode df_ll, RDF::RNode df_photon, TString obs, RDF::TH1DModel model){
  auto h_ll = (TH1F*) df_ll.Histo1D(model, obs).GetValue().Clone();
  auto h_photon = (TH1F*) df_photon.Histo1D(model, obs, "pt_weight").GetValue().Clone();
  auto c1 = new TCanvas("c1","c1",1200,1200);
  c1->SetLogy();
  h_ll->SetLineColor(kBlue+1);
  h_photon->SetLineColor(kRed);
  h_ll->Draw();
  h_photon->Draw("Same");
  auto rp_inc = new TRatioPlot(h_ll, h_photon);
  c1->SetTicks(0,1);
  rp_inc->Draw();
  rp_inc->GetLowerRefYaxis()->SetTitle("ratio");
  rp_inc->GetLowerRefGraph()->SetMinimum(0);
  rp_inc->GetLowerRefGraph()->SetMaximum(3);
  c1->Print("mc_closure_" + obs + ".png");
}

TH1F * GetWeightHisto(TH1F * h_ll, TH1F * h_gamma,  TString obs) {

  h_ll->Scale(1/h_ll->Integral());
  h_gamma->Scale(1/h_gamma->Integral());
  auto h_weight = (TH1F*) h_ll->Clone();
  h_weight->Divide(h_gamma);
  h_weight->SetName(obs + "_weight");
  h_weight->SetTitle(obs + "_weight");
  if (obs.EqualTo("nvtx")){
  auto h_nvtx_weight_smooth = utils::SmoothenHisto(h_weight);
  return h_nvtx_weight_smooth;
  }
  else
    return h_weight;
}

float applyWeight(TH1F * h_weight, float boson_eta) {
    //auto h_weight = (TH1F*) gROOT->Get("eta_weight");
    auto const bin = h_weight->FindFixBin(boson_eta);
    return h_weight->GetBinContent(bin);
    //std::cout << h_weight->GetBinContent(bin) <<std::endl;
}

RDF::RNode applyCutsCommon(RDF::RNode df){
  std::function<bool(int)> nGoodJet = [](int ngood_jets) { return ngood_jets >= 2; };
  std::function<bool(int)> nGoodbJet = [](int ngood_bjets) { return ngood_bjets == 0; };
  std::function<bool(int)> nTaus = [](int ntau) { return ntau == 0; };
  std::function<bool(float)> zpt = [](float Z_pt) { return Z_pt > 55.; };
  std::function<bool(float)> met = [](float met) { return met < 60.; };
  std::function<bool(float)> delta_phi_j_met = [](float delta_phi_j_met) { return abs(delta_phi_j_met) > 0.5; };
  std::function<bool(float)> delta_phi_ZMet_bst = [](float delta_phi_ZMet_bst) { return abs(delta_phi_ZMet_bst) > 1.0; };
  std::function<bool(float)> Jet_etas_multiplied = [](float Jet_etas_multiplied) { return Jet_etas_multiplied < 0.; };
  std::function<bool(float)> dijet_Mjj = [](float dijet_Mjj) { return dijet_Mjj > 400.; };
  std::function<bool(float)> dijet_abs_dEta = [](float dijet_abs_dEta) { return dijet_abs_dEta > 2.4; };
  auto tmp = df.
                     Filter(nGoodJet, {"ngood_jets"}, "2-jets").
                     Filter(nGoodbJet, {"ngood_bjets"}, "bveto").
                     Filter(nTaus, {"nhad_taus"}, "tau-veto").
                     Filter(delta_phi_j_met, {"delta_phi_j_met"}, "delta_phi_j_met").
                     //Filter(delta_phi_ZMet_bst, {"delta_phi_ZMet_bst"}).
                     Filter(Jet_etas_multiplied, {"Jet_etas_multiplied"}, "opposite_jet_eta").
                     Filter(dijet_Mjj, {"dijet_Mjj"}, "dijet_Mjj<400").
                     Filter(dijet_abs_dEta, {"dijet_abs_dEta"}, "dijet_abs_eta<2.4").
                     Filter(zpt, {"Z_pt"}, "boson_pt>55");
  return tmp;
}

RDF::RNode applyCutsLL(RDF::RNode df){
  std::function<bool(int)> lep_cat = [](int lep_category) { return lep_category == 1 || lep_category == 3; };
  std::function<bool(float)> deltaR_ll = [](float delta_R_ll) { return delta_R_ll < 2.5; };
  auto tmp = df.Filter(lep_cat, {"lep_category"}).
                Filter(deltaR_ll, {"delta_R_ll"});
  return tmp;
}

RDF::RNode applyCutsMET(RDF::RNode df){
  std::function<bool(float)> met = [](float met) { return met < 60.; };
  auto tmp = df.Filter(met, {"met_pt"}, "MET<60");
  return tmp;
}

RDF::RNode applyCutsPhoton(RDF::RNode df){
  std::function<bool(float)> boson_eta = [](float Z_eta) { return abs(Z_eta) < 2.4; };
  std::function<bool(int)> nextra_leptons = [](int nextra_leptons) { return nextra_leptons == 0; };
  std::function<bool(int)> ngood_leptons = [](int ngood_leptons) { return ngood_leptons == 0; };
  std::function<bool(int)> nextra_photons = [](int nloose_photons) { return nloose_photons == 1; };

  auto tmp = df.
             Filter(nextra_leptons,{"nextra_leptons"}, "No_nextra_leptons").
             Filter(ngood_leptons,{"ngood_leptons"}, "No_good_leptons").
             //Filter(nextra_photons,{"nloose_photons"}, "No_extra_photons").
             Filter(boson_eta, {"Z_eta"});
  return tmp;
}

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

  Options options(argc, argv);
  YAML::Node const config = options.GetConfig();
  std::string tree = Options::NodeAs<std::string>(config, {"tree_name"});
 // std::string dilepton_filename = Options::NodeAs<std::string>(config, {"dilepton_files"});
  std::vector<std::string> dilepton_filenames = Options::GetStrings(config, {"dilepton_files"});
  std::vector<std::string> photon_filenames = Options::GetStrings(config, {"photon_files"});
  //std::string photon_filename = Options::NodeAs<std::string>(config, {"photon_files"});
  bool isMC = Options::NodeAs<bool>(config, {"isMC"});
  std::vector<std::string> dilepton_files;
  std::vector<std::string> photon_files;
  for (auto &dilepton_filename : dilepton_filenames)
    FileInPath::GetFilenames(FileInPath::Resolve(dilepton_filename), dilepton_files);
  for (auto &photon_filename : photon_filenames)
    FileInPath::GetFilenames(FileInPath::Resolve(photon_filename), photon_files);
  //std::string photonPath = FileInPath::Resolve(photon_filename);
  RDataFrame::ColumnNames_t varibles = {"nJet","Muon_pt" "Jet_pt_nom",
      "lead_jet_pt", "lead_jet_phi", "trail_jet_pt", "trail_jet_eta",
      "trail_jet_phi", "lep_category", "ngood_jets", "ngood_bjets",
      "nhad_taus", "met_pt", "met_phi", "delta_R_ll", "delta_phi_j_met",
      "Jet_etas_multiplied", "dijet_Mjj", "dijet_abs_dEta",
      "Z_pt", "Z_eta", "Z_phi", "Z_mass",
      "nPhoton", "nMuon", "nElectron", "Pileup_nPU",
      "ngood_leptons", "nextra_leptons",
      "deltaPhiClosestJetMet", "deltaPhiFarthestJetMet",
      //"delta_phi_ZMet_bst",
      "nloose_photons",
       "delta_phi_ZMet"};
  RDataFrame::ColumnNames_t storeBranches = {"boson_pt", "boson_eta", "Pileup_nPU", "met_pt"};
  if (isMC) {
    //varibles.insert(0, RDataFrame::ColumnNames_t{"weight"});
    for (auto &br : RDataFrame::ColumnNames_t{"weight", "puWeight", "w_muon_SF", "w_electron_SF",
        "PrefireWeight", "nvtxWeight", "TriggerSFWeight", "btagEventWeight", "Jet_partonFlavour",
        "Jet_qgl"})
      varibles.push_back(br);
  }
  // enabling Multi-Thread
  ROOT::EnableImplicitMT();
  // data-frame initializing
  RDataFrame df_dilepton(tree, dilepton_files, varibles);
  RDataFrame df_photon(tree, photon_files, varibles);
  // apply common selections
  auto df_ll_filtered_tmp = applyCutsCommon(df_dilepton);
  auto df_gamma_filtered_tmp = applyCutsCommon(df_photon);
  // apply dilepton & photon selections separately
  auto df_ll_filtered_noMET = applyCutsLL(df_ll_filtered_tmp);
  auto df_gamma_filtered_noMET = applyCutsPhoton(df_gamma_filtered_tmp);
  // apply MET cut, (we will need the ones without MET cut to draw control plots)
  auto df_ll_filtered = applyCutsMET(df_ll_filtered_noMET);
  auto df_gamma_filtered = applyCutsMET(df_gamma_filtered_noMET);

  auto df_ll_aug = df_ll_filtered.
                  Define("boson_pt", "Z_pt").
                  Define("boson_eta", "abs(Z_eta)").
                  Define("nvtx", "Pileup_nPU").
                  Define("ptmiss", "met_pt").
                  Define("corr", "weight");

  auto df_gamma_aug = df_gamma_filtered.
                  Define("boson_pt", "Z_pt").
                  Define("boson_eta", "abs(Z_eta)").
                  Define("nvtx", "Pileup_nPU").
                  Define("ptmiss", "met_pt").
                  Define("corr", "weight");
  // fill histograms in binning of nvtx and abseta
  auto h_nvtx_ll = (TH1F*)df_ll_aug.Histo1D({"nvtx_ll","nvtx_ll",100,0,100},"nvtx", "corr").GetValue().Clone();
  auto h_nvtx_gamma = (TH1F*)df_gamma_aug.Histo1D({"nvtx_gamma","nvtx_gamma",100,0,100},"nvtx", "corr").GetValue().Clone();
  auto h_eta_ll = (TH1F*)df_ll_aug.Histo1D({"eta_ll","eta_ll",24,0,2.4},"boson_eta", "corr").GetValue().Clone();
  auto h_eta_gamma = (TH1F*)df_gamma_aug.Histo1D({"eta_gamma","eta_gamma",24,0,2.4},"boson_eta", "corr").GetValue().Clone();
  auto h_nvtx_weight = GetWeightHisto(h_nvtx_ll, h_nvtx_gamma, "nvtx");
  auto h_eta_weight = GetWeightHisto(h_eta_ll, h_eta_gamma, "eta");
  // define a lambda function for applying weights in the Data-Frame
  auto applyEtaWeight = [] (float boson_eta) {
    TH1F * h_weight = (TH1F *)gROOT->Get("eta_weight");
    auto const bin = h_weight->FindFixBin(boson_eta);
    return h_weight->GetBinContent(bin);
  };
  auto df_gamma_eta_weighted_tmp = df_gamma_aug.Define("eta_weight", applyEtaWeight, {"boson_eta"});
  auto df_gamma_eta_weighted = df_gamma_eta_weighted_tmp.Define("corr_eta", "corr * eta_weight");
  const float  binning[] = {55,75,100,150,200,250,1500};
  RDF::TH1DModel pt_model("pt_ll","pt_ll",6, binning);
  RDF::TH1DModel pt_model_gamma("pt_gamma","pt_gamma",6, binning);
  auto h_pt_ll = (TH1F*)df_ll_aug.Histo1D(pt_model,"boson_pt", "corr").GetValue().Clone();
  auto h_pt_gamma = (TH1F*)df_gamma_eta_weighted.Histo1D(pt_model_gamma,"boson_pt", "corr_eta").GetValue().Clone();
  auto h_pt_weight = GetWeightHisto(h_pt_ll, h_pt_gamma, "pt");

  // define a lambda function for applying weights in the Data-Frame
  auto applyPtWeight = [] (float boson_pt) {
    TH1F * h_weight = (TH1F *)gROOT->Get("pt_weight");
    auto const bin = h_weight->FindFixBin(boson_pt);
    return h_weight->GetBinContent(bin);
  };
  auto df_gamma_eta_pt_weighted = df_gamma_eta_weighted.Define("pt_weight", applyPtWeight, {"boson_pt"});
  auto df_photon_weighted = df_gamma_filtered_noMET.Define("pt_weight", applyPtWeight, {"Z_pt"});
  std::cout << "All stats:" << std::endl;
  auto allCutsReport = df_gamma_filtered.Report();
  std::cout << "Name\tAll\tPass\tEfficiency" << std::endl;
   for (auto &&cutInfo : allCutsReport) {
      std::cout << cutInfo.GetName() << "\t" << cutInfo.GetAll() << "\t" << cutInfo.GetPass() << "\t"
                << cutInfo.GetEff() << " %" << std::endl;
   }
  const float  met_binning[] = {0,55,60,65,70,75,80,85,90,95,100,105,110,115,120,125,130,135,140,145,150,160,170,180,190,200,250,300,350,400,450,500,600,900,1500};
  RDF::TH1DModel met_model("","",sizeof(met_binning)/sizeof(float) - 1, met_binning);
  drawHist(df_ll_filtered_noMET, df_photon_weighted, "met_pt", met_model);

  RDataFrame::ColumnNames_t toBeStored_ll = { "met_pt",
                                          };
  RDataFrame::ColumnNames_t toBeStored_photon = toBeStored_ll;
  toBeStored_photon.push_back("pt_weight");

  df_photon_weighted.Snapshot("Events","photon.root", toBeStored_photon);
  df_ll_filtered_noMET.Snapshot("Events","ll.root", toBeStored_ll);
  auto f1 = new TFile("weights.root", "RECREATE");
  h_nvtx_weight->Write();
  h_eta_weight->Write();
  h_pt_weight->Write();
  h_pt_gamma->Write();
  h_pt_ll->Write();
  f1->Close();
  return 0;
}


/*! \file Peridigm_Material.cpp */

//@HEADER
// ************************************************************************
//
//                             Peridigm
//                 Copyright (2011) Sandia Corporation
//
// Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
// the U.S. Government retains certain rights in this software.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. 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.
//
// 3. Neither the name of the Corporation nor the names of the
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "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 SANDIA CORPORATION OR THE
// 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.
//
// Questions?
// David J. Littlewood   djlittl@sandia.gov
// John A. Mitchell      jamitch@sandia.gov
// Michael L. Parks      mlparks@sandia.gov
// Stewart A. Silling    sasilli@sandia.gov
//
// ************************************************************************
//@HEADER

#include "Peridigm_Material.hpp"
#include "Peridigm_Field.hpp"
#include <Teuchos_Assert.hpp>
#include <Epetra_SerialComm.h>
//#include "material_utilities.h"
#include <boost/math/special_functions/fpclassify.hpp>

using namespace std;

void PeridigmNS::Material::computeJacobian(const double dt,
                                           const int numOwnedPoints,
                                           const int* ownedIDs,
                                           const int* neighborhoodList,
                                           PeridigmNS::DataManager& dataManager,
                                           PeridigmNS::SerialMatrix& jacobian,
                                           PeridigmNS::Material::JacobianType jacobianType) const
{
  // Compute a finite-difference Jacobian using either FORWARD_DIFFERENCE or CENTRAL_DIFFERENCE
  computeFiniteDifferenceJacobian(dt, numOwnedPoints, ownedIDs, neighborhoodList, dataManager, jacobian, CENTRAL_DIFFERENCE, jacobianType);
}

void PeridigmNS::Material::computeFiniteDifferenceJacobian(const double dt,
                                                           const int numOwnedPoints,
                                                           const int* ownedIDs,
                                                           const int* neighborhoodList,
                                                           PeridigmNS::DataManager& dataManager,
                                                           PeridigmNS::SerialMatrix& jacobian,
                                                           FiniteDifferenceScheme finiteDifferenceScheme,
                                                           PeridigmNS::Material::JacobianType jacobianType) const
{
  // The Jacobian is of the form:
  //
  // dF_0x/dx_0  dF_0x/dy_0  dF_0x/dz_0  dF_0x/dx_1  dF_0x/dy_1  dF_0x/dz_1  ...  dF_0x/dx_n  dF_0x/dy_n  dF_0x/dz_n
  // dF_0y/dx_0  dF_0y/dy_0  dF_0y/dz_0  dF_0y/dx_1  dF_0y/dy_1  dF_0y/dz_1  ...  dF_0y/dx_n  dF_0y/dy_n  dF_0y/dz_n
  // dF_0z/dx_0  dF_0z/dy_0  dF_0z/dz_0  dF_0z/dx_1  dF_0z/dy_1  dF_0z/dz_1  ...  dF_0z/dx_n  dF_0z/dy_n  dF_0z/dz_n
  // dF_1x/dx_0  dF_1x/dy_0  dF_1x/dz_0  dF_1x/dx_1  dF_1x/dy_1  dF_1x/dz_1  ...  dF_1x/dx_n  dF_1x/dy_n  dF_1x/dz_n
  // dF_1y/dx_0  dF_1y/dy_0  dF_1y/dz_0  dF_1y/dx_1  dF_1y/dy_1  dF_1y/dz_1  ...  dF_1y/dx_n  dF_1y/dy_n  dF_1y/dz_n
  // dF_1z/dx_0  dF_1z/dy_0  dF_1z/dz_0  dF_1z/dx_1  dF_1z/dy_1  dF_1z/dz_1  ...  dF_1z/dx_n  dF_1z/dy_n  dF_1z/dz_n
  //     .           .           .           .           .           .                .           .           .
  //     .           .           .           .           .           .                .           .           .
  //     .           .           .           .           .           .                .           .           .
  // dF_nx/dx_0  dF_nx/dy_0  dF_nx/dz_0  dF_nx/dx_1  dF_nx/dy_1  dF_nx/dz_1  ...  dF_nx/dx_n  dF_nx/dy_n  dF_nx/dz_n
  // dF_ny/dx_0  dF_ny/dy_0  dF_ny/dz_0  dF_ny/dx_1  dF_ny/dy_1  dF_ny/dz_1  ...  dF_ny/dx_n  dF_ny/dy_n  dF_ny/dz_n
  // dF_nz/dx_0  dF_nz/dy_0  dF_nz/dz_0  dF_nz/dx_1  dF_nz/dy_1  dF_nz/dz_1  ...  dF_nz/dx_n  dF_nz/dy_n  dF_nz/dz_n

  // Each entry is computed by finite difference:
  //
  // Forward difference:
  // dF_0x/dx_0 = ( F_0x(perturbed x_0) - F_0x(unperturbed) ) / epsilon
  //
  // Central difference:
  // dF_0x/dx_0 = ( F_0x(positive perturbed x_0) - F_0x(negative perturbed x_0) ) / ( 2.0*epsilon )

  TEUCHOS_TEST_FOR_EXCEPT_MSG(m_finiteDifferenceProbeLength == DBL_MAX, "**** Finite-difference Jacobian requires that the \"Finite Difference Probe Length\" parameter be set.\n");
  double epsilon = m_finiteDifferenceProbeLength;

  // Get field ids for all relevant data
  PeridigmNS::FieldManager& fieldManager = PeridigmNS::FieldManager::self();
  int volumeFId = fieldManager.getFieldId("Volume");
  int coordinatesFId = fieldManager.getFieldId("Coordinates");
  int velocityFId = fieldManager.getFieldId("Velocity");
  int forceDensityFId = fieldManager.getFieldId("Force_Density");

  // Loop over all points.
  int neighborhoodListIndex = 0;
  for(int iID=0 ; iID<numOwnedPoints ; ++iID){

    // Create a temporary neighborhood consisting of a single point and its neighbors.
    int numNeighbors = neighborhoodList[neighborhoodListIndex++];
    vector<int> tempMyGlobalIDs(numNeighbors+1);
    // Put the node at the center of the neighborhood at the beginning of the list.
    tempMyGlobalIDs[0] = dataManager.getOwnedScalarPointMap()->GID(iID);
    vector<int> tempNeighborhoodList(numNeighbors+1);
    tempNeighborhoodList[0] = numNeighbors;
    for(int iNID=0 ; iNID<numNeighbors ; ++iNID){
      int neighborID = neighborhoodList[neighborhoodListIndex++];
      tempMyGlobalIDs[iNID+1] = dataManager.getOverlapScalarPointMap()->GID(neighborID);
      tempNeighborhoodList[iNID+1] = iNID+1;
    }

    Epetra_SerialComm serialComm;
    Teuchos::RCP<Epetra_BlockMap> tempOneDimensionalMap = Teuchos::rcp(new Epetra_BlockMap(numNeighbors+1, numNeighbors+1, &tempMyGlobalIDs[0], 1, 0, serialComm));
    Teuchos::RCP<Epetra_BlockMap> tempThreeDimensionalMap = Teuchos::rcp(new Epetra_BlockMap(numNeighbors+1, numNeighbors+1, &tempMyGlobalIDs[0], 3, 0, serialComm));
    Teuchos::RCP<Epetra_BlockMap> tempBondMap = Teuchos::rcp(new Epetra_BlockMap(1, 1, &tempMyGlobalIDs[0], numNeighbors, 0, serialComm));

    // Create a temporary DataManager containing data for this point and its neighborhood.
    PeridigmNS::DataManager tempDataManager;
    tempDataManager.setMaps(Teuchos::RCP<const Epetra_BlockMap>(),
                            tempOneDimensionalMap,
                            Teuchos::RCP<const Epetra_BlockMap>(),
                            tempThreeDimensionalMap,
                            tempBondMap);

    // The temporary data manager will have the same fields and data as the real data manager.
    vector<int> fieldIds = dataManager.getFieldIds();
    tempDataManager.allocateData(fieldIds);
    tempDataManager.copyLocallyOwnedDataFromDataManager(dataManager);

    // Set up numOwnedPoints and ownedIDs.
    // There is only one owned ID, and it has local ID zero in the tempDataManager.
    int tempNumOwnedPoints = 1;
    vector<int> tempOwnedIDs(1);
    tempOwnedIDs[0] = 0;

    // Extract pointers to the underlying data in the constitutiveData array.
    double *volume, *y, *v, *force;
    tempDataManager.getData(volumeFId, PeridigmField::STEP_NONE)->ExtractView(&volume);
    tempDataManager.getData(coordinatesFId, PeridigmField::STEP_NP1)->ExtractView(&y);
    tempDataManager.getData(velocityFId, PeridigmField::STEP_NP1)->ExtractView(&v);
    tempDataManager.getData(forceDensityFId, PeridigmField::STEP_NP1)->ExtractView(&force);

    // Create a temporary vector for storing force
    Teuchos::RCP<Epetra_Vector> forceVector = tempDataManager.getData(forceDensityFId, PeridigmField::STEP_NP1);
    Teuchos::RCP<Epetra_Vector> tempForceVector = Teuchos::rcp(new Epetra_Vector(*forceVector));
    double* tempForce;
    tempForceVector->ExtractView(&tempForce);

    // Use the scratchMatrix as sub-matrix for storing tangent values prior to loading them into the global tangent matrix.
    // Resize scratchMatrix if necessary
    if(scratchMatrix.Dimension() < 3*(numNeighbors+1))
      scratchMatrix.Resize(3*(numNeighbors+1));

    // Create a list of global indices for the rows/columns in the scratch matrix.
    vector<int> globalIndices(3*(numNeighbors+1));
    for(int i=0 ; i<numNeighbors+1 ; ++i){
      int globalID = tempOneDimensionalMap->GID(i);
      for(int j=0 ; j<3 ; ++j)
        globalIndices[3*i+j] = 3*globalID+j;
    }

    if(finiteDifferenceScheme == FORWARD_DIFFERENCE){
      // Compute and store the unperturbed force.
      computeForce(dt, tempNumOwnedPoints, &tempOwnedIDs[0], &tempNeighborhoodList[0], tempDataManager);
      for(int i=0 ; i<forceVector->MyLength() ; ++i)
        tempForce[i] = force[i];
    }

    // Perturb one dof in the neighborhood at a time and compute the force.
    // The point itself plus each of its neighbors must be perturbed.
    for(int iNID=0 ; iNID<numNeighbors+1 ; ++iNID){

      int perturbID;
      if(iNID < numNeighbors)
        perturbID = tempNeighborhoodList[iNID+1];
      else
        perturbID = 0;

      for(int dof=0 ; dof<3 ; ++dof){

        // Perturb a dof and compute the forces.
        double oldY = y[3*perturbID+dof];
        double oldV = v[3*perturbID+dof];

        if(finiteDifferenceScheme == CENTRAL_DIFFERENCE){
          // Compute and store the negatively perturbed force.
          y[3*perturbID+dof] -= epsilon;
          v[3*perturbID+dof] -= epsilon/dt;
          computeForce(dt, tempNumOwnedPoints, &tempOwnedIDs[0], &tempNeighborhoodList[0], tempDataManager);
          y[3*perturbID+dof] = oldY;
          v[3*perturbID+dof] = oldV;
          for(int i=0 ; i<forceVector->MyLength() ; ++i)
            tempForce[i] = force[i];
        }

        // Compute the purturbed force
        y[3*perturbID+dof] += epsilon;
        v[3*perturbID+dof] += epsilon/dt;
        computeForce(dt, tempNumOwnedPoints, &tempOwnedIDs[0], &tempNeighborhoodList[0], tempDataManager);
        y[3*perturbID+dof] = oldY;
        v[3*perturbID+dof] = oldV;

        for(int i=0 ; i<numNeighbors+1 ; ++i){
          int forceID;
          if(i < numNeighbors)
            forceID = tempNeighborhoodList[i+1];
          else
            forceID = 0;

          for(int d=0 ; d<3 ; ++d){
            double value = ( force[3*forceID+d] - tempForce[3*forceID+d] ) / epsilon;
            if(finiteDifferenceScheme == CENTRAL_DIFFERENCE)
              value *= 0.5;
            scratchMatrix(3*forceID+d, 3*perturbID+dof) = value;
          }
        }
      }
    }

    // Convert force density to force
    // \todo Create utility function for this in ScratchMatrix
    for(unsigned int row=0 ; row<globalIndices.size() ; ++row){
      for(unsigned int col=0 ; col<globalIndices.size() ; ++col){
        scratchMatrix(row, col) *= volume[row/3];
      }
    }

    // Check for NaNs
    for(unsigned int row=0 ; row<globalIndices.size() ; ++row){
      for(unsigned int col=0 ; col<globalIndices.size() ; ++col){
        TEUCHOS_TEST_FOR_EXCEPT_MSG(!boost::math::isfinite(scratchMatrix(row, col)), "**** NaN detected in finite-difference Jacobian.\n");
      }
    }

    // Sum the values into the global tangent matrix (this is expensive).
    if (jacobianType == PeridigmNS::Material::FULL_MATRIX)
      jacobian.addValues((int)globalIndices.size(), &globalIndices[0], scratchMatrix.Data());
    else if (jacobianType == PeridigmNS::Material::BLOCK_DIAGONAL) {
      jacobian.addBlockDiagonalValues((int)globalIndices.size(), &globalIndices[0], scratchMatrix.Data());
    }
    else // unknown jacobian type
      TEUCHOS_TEST_FOR_EXCEPT_MSG(true, "**** Unknown Jacobian Type\n");
  }
}

double PeridigmNS::Material::calculateBulkModulus(const Teuchos::ParameterList & params) const
{
  bool bulkModulusDefined(false), shearModulusDefined(false), youngsModulusDefined(false), poissonsRatioDefined(false);
  double bulkModulus(0.0), shearModulus(0.0), youngsModulus(0.0), poissonsRatio(0.0);
  double computedValue;

  if( params.isParameter("Bulk Modulus") ){
    bulkModulusDefined = true;
    bulkModulus = params.get<double>("Bulk Modulus");
  }
  if( params.isParameter("Shear Modulus") ){
    shearModulus = params.get<double>("Shear Modulus");
    shearModulusDefined = true;
  }
  if( params.isParameter("Young's Modulus") ){
    youngsModulus = params.get<double>("Young's Modulus");
    youngsModulusDefined = true;
  }
  if( params.isParameter("Poisson's Ratio") ){
    poissonsRatio = params.get<double>("Poisson's Ratio");
    poissonsRatioDefined = true;
  }

  int numDefinedConstants = static_cast<int>(bulkModulusDefined) +
    static_cast<int>(shearModulusDefined) +
    static_cast<int>(youngsModulusDefined) +
    static_cast<int>(poissonsRatioDefined);

  TEUCHOS_TEST_FOR_EXCEPT_MSG(numDefinedConstants != 2, "**** Error:  Exactly two elastic constants must be provided.  Allowable constants are \"Bulk Modulus\", \"Shear Modulus\", \"Young's Modulus\", \"Poisson's Ratio\".\n");

  if(bulkModulusDefined)
    computedValue = bulkModulus;
  else if(youngsModulusDefined && shearModulusDefined)
    computedValue = (youngsModulus * shearModulus) / (3.0*(3.0*shearModulus - youngsModulus));
  else if(youngsModulusDefined && poissonsRatioDefined)
    computedValue = youngsModulus / (3.0*(1.0 - 2.0*poissonsRatio));
  else if(shearModulusDefined && poissonsRatioDefined)
    computedValue = (2.0*shearModulus*(1.0 + poissonsRatio)) / (3.0*(1.0 - 2.0*poissonsRatio));
  else
    TEUCHOS_TEST_FOR_EXCEPT_MSG(true, "**** Error:  Exactly two elastic constants must be provided.  Allowable constants are \"Bulk Modulus\", \"Shear Modulus\", \"Young's Modulus\", \"Poisson's Ratio\".\n");

  return computedValue;
}

double PeridigmNS::Material::calculateShearModulus(const Teuchos::ParameterList & params) const
{
  bool bulkModulusDefined(false), shearModulusDefined(false), youngsModulusDefined(false), poissonsRatioDefined(false);
  double bulkModulus(0.0), shearModulus(0.0), youngsModulus(0.0), poissonsRatio(0.0);
  double computedValue;

  if( params.isParameter("Bulk Modulus") ){
    bulkModulusDefined = true;
    bulkModulus = params.get<double>("Bulk Modulus");
  }
  if( params.isParameter("Shear Modulus") ){
    shearModulus = params.get<double>("Shear Modulus");
    shearModulusDefined = true;
  }
  if( params.isParameter("Young's Modulus") ){
    youngsModulus = params.get<double>("Young's Modulus");
    youngsModulusDefined = true;
  }
  if( params.isParameter("Poisson's Ratio") ){
    poissonsRatio = params.get<double>("Poisson's Ratio");
    poissonsRatioDefined = true;
  }

  int numDefinedConstants = static_cast<int>(bulkModulusDefined) +
    static_cast<int>(shearModulusDefined) +
    static_cast<int>(youngsModulusDefined) +
    static_cast<int>(poissonsRatioDefined);

  TEUCHOS_TEST_FOR_EXCEPT_MSG(numDefinedConstants != 2, "**** Error:  Exactly two elastic constants must be provided.  Allowable constants are \"Bulk Modulus\", \"Shear Modulus\", \"Young's Modulus\", \"Poisson's Ratio\".\n");

  if(shearModulusDefined)
    computedValue = shearModulus;
  else if(bulkModulusDefined && youngsModulusDefined)
    computedValue = (3.0*bulkModulus*youngsModulus) / (9.0*bulkModulus - youngsModulus);
  else if(bulkModulusDefined & poissonsRatioDefined)
    computedValue = (3.0*bulkModulus*(1.0 - 2.0*poissonsRatio)) / (2.0*(1.0 + poissonsRatio));
  else if(youngsModulusDefined && poissonsRatioDefined)
    computedValue = youngsModulus / (2.0*(1.0 + poissonsRatio));
  else
    TEUCHOS_TEST_FOR_EXCEPT_MSG(true, "**** Error:  Exactly two elastic constants must be provided.  Allowable constants are \"Bulk Modulus\", \"Shear Modulus\", \"Young's Modulus\", \"Poisson's Ratio\".\n");

  return computedValue;
}

void
PeridigmNS::Material::computeDilatation(
        const int numOwnedPoints,
        const int* ownedIDs,
        const int* neighborhoodList,
        const double BM,
        const double SM,
        PeridigmNS::DataManager& dataManager) const {

    //////////////////////////////////////////////////////////////////////////////////////
    // Routine developed for Peridigm by DLR.
    // Dilatation has to be updated before the damage routine. Therefore, this routine has been added
    // to the model evaluator. The dilatation is needed to calculate the energy at both bonds (12 and 21)
    // for the correct time step, prior to the calculation of the internal forces. The bonds with critical
    // energy are deleted and the internal loads calculation will be done.
    //////////////////////////////////////////////////////////////////////////////////////
    // Questions?
    // Christian Willberg  christian.willberg@dlr.de
    // Martin Raedel       martin.raedel@dlr.de
    //////////////////////////////////////////////////////////////////////////////////////

    PeridigmNS::FieldManager& fieldManager = PeridigmNS::FieldManager::self();
    //double m_horizon = params.get<double>("Horizon");
    //////////////////////////////////////////////////////////////////////////////////////
    // ALL IDS will be initialized in the material routine "Peridigm_ElasticMaterial.cpp"
    // carefull if changed from Peridigm_ElasticMaterial.cpp to something else
    //////////////////////////////////////////////////////////////////////////////////////

    int m_modelCoordinatesFieldId = fieldManager.getFieldId(PeridigmField::NODE, PeridigmField::VECTOR, PeridigmField::CONSTANT, "Model_Coordinates");
    int m_coordinatesFieldId = fieldManager.getFieldId(PeridigmField::NODE, PeridigmField::VECTOR, PeridigmField::TWO_STEP, "Coordinates");
    int m_volumeFieldId = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::SCALAR, PeridigmField::CONSTANT, "Volume");
    int m_weightedVolumeFieldId = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::SCALAR, PeridigmField::CONSTANT, "Weighted_Volume");
    int m_dilatationFieldId = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::SCALAR, PeridigmField::TWO_STEP, "Dilatation");
	int m_bondDamageFieldId = fieldManager.getFieldId(PeridigmField::BOND, PeridigmField::SCALAR, PeridigmField::TWO_STEP, "Bond_Damage");
    int m_horizonFieldId = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::SCALAR, PeridigmField::CONSTANT, "Horizon");
    int m_damageModelFieldId = fieldManager.getFieldId(PeridigmField::NODE,    PeridigmField::VECTOR, PeridigmField::TWO_STEP, "Damage_Model_Data");

    ///////////////////////////////////////////////////////////////////////////////
    PeridigmNS::InfluenceFunction::functionPointer OMEGA;
    OMEGA = PeridigmNS::InfluenceFunction::self().getInfluenceFunction();
    double *x, *y, *bondDamage, *horizon;
    double *Volume, *weightedVolume, *damageModel, *theta;
    ///////////////////////////////////////////////////////////////////////////////
    dataManager.getData(m_modelCoordinatesFieldId, PeridigmField::STEP_NONE)->ExtractView(&x);
    dataManager.getData(m_weightedVolumeFieldId, PeridigmField::STEP_NONE)->ExtractView(&weightedVolume);
    dataManager.getData(m_volumeFieldId, PeridigmField::STEP_NONE)->ExtractView(&Volume);
	dataManager.getData(m_coordinatesFieldId, PeridigmField::STEP_NP1)->ExtractView(&y);
    dataManager.getData(m_dilatationFieldId, PeridigmField::STEP_NP1)->ExtractView(&theta);
	dataManager.getData(m_horizonFieldId, PeridigmField::STEP_NONE)->ExtractView(&horizon);

    dataManager.getData(m_damageModelFieldId, PeridigmField::STEP_NP1)->ExtractView(&damageModel);

	*(dataManager.getData(m_bondDamageFieldId, PeridigmField::STEP_NP1)) = *(dataManager.getData(m_bondDamageFieldId, PeridigmField::STEP_N));
    dataManager.getData(m_bondDamageFieldId, PeridigmField::STEP_NP1)->ExtractView(&bondDamage);
    double m_alpha = 0.0; // to be adapted for temperature
    double *deltaTemperature = NULL;
	const double *xOwned = x;
	const double *yOwned = y;
	const double *deltaT = deltaTemperature;
	const double *m = weightedVolume;
	const double *v = Volume;
	double cellVolume;

	const int *neighPtr = neighborhoodList;

	for(int p=0; p<numOwnedPoints;p++, xOwned+=3, yOwned+=3, horizon++, deltaT++, m++, theta++){
		int numNeigh = *neighPtr; neighPtr++;
		const double *X = xOwned;
		const double *Y = yOwned;
		*theta = 0.0;
		for(int n=0;n<numNeigh;n++,neighPtr++,bondDamage++){
			int localId = *neighPtr;
			cellVolume = v[localId];
			const double *XP = &x[3*localId];
			const double *YP = &y[3*localId];
			double X_dx = XP[0]-X[0];
			double X_dy = XP[1]-X[1];
			double X_dz = XP[2]-X[2];
			double zetaSquared = X_dx*X_dx+X_dy*X_dy+X_dz*X_dz;
			double Y_dx = YP[0]-Y[0];
			double Y_dy = YP[1]-Y[1];
			double Y_dz = YP[2]-Y[2];
			double dY = Y_dx*Y_dx+Y_dy*Y_dy+Y_dz*Y_dz;
			double d = sqrt(zetaSquared);
			double e = sqrt(dY);
			e -= d;
			//if(deltaTemperature)
			//  e -= thermalExpansionCoefficient*(*deltaT)*d;
			double omega = OMEGA(d,*horizon);
			*theta += 3.0*omega*(1.0-*bondDamage)*d*e*cellVolume/(*m);
		}
        // variable is needed to determine the accurate energy in the energy based damage criterion
		damageModel[3*p] = *theta;
		damageModel[3*p+1] = BM / (*m);
		damageModel[3*p+2] = SM / (*m) ;
		*theta = 0.0;

	}
}


/// \file   price_setter.cpp
///
/// \brief
///
/// \authors    Maarten P. Scholl
/// \date       2018-07-19
/// \copyright  Copyright 2017-2019 The Institute for New Economic Thinking,
///             Oxford Martin School, University of Oxford
///
///             Licensed under the Apache License, Version 2.0 (the "License");
///             you may not use this file except in compliance with the License.
///             You may obtain a copy of the License at
///
///                 http://www.apache.org/licenses/LICENSE-2.0
///
///             Unless required by applicable law or agreed to in writing,
///             software distributed under the License is distributed on an "AS
///             IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
///             express or implied. See the License for the specific language
///             governing permissions and limitations under the License.
///
///             You may obtain instructions to fulfill the attribution
///             requirements in CITATION.cff
///
#include <esl/economics/markets/walras/price_setter.hpp>

#include <algorithm>
#include <type_traits>
#include <map>
using std::map;

#include <esl/economics/markets/walras/quote_message.hpp>
#include <esl/economics/markets/walras/tatonnement.hpp>
using esl::law::property;

#include <esl/economics/finance/securities_lending_contract.hpp>

template <class... T>
struct always_false
: std::false_type
{

};

template <> struct always_false<>
: std::true_type
{

};

namespace esl::economics::markets::walras {
    price_setter::price_setter()
    : price_setter(identity<price_setter>())
    {

    }

    ///
    /// \brief
    ///
    /// \details    Initialises the differentiable variable context to 1.0 times
    ///             the initial quotes. In essence, the
    ///             solver starts at 1.0 times the initial quote
    /// \param i
    /// \param traded_assets
    price_setter::price_setter(const identity<price_setter> &i,
                               law::property_map<quote> traded_properties)

    : agent(i)
    , market(i, traded_properties)
    , state(sending_quotes)
    {
        output_clearing_prices_ =
            create_output<std::vector<price>>("clearing_prices");

        this->register_callback<walras::differentiable_order_message>(
            [this](auto msg, simulation::time_interval ti,
                   std::seed_seq &seed) {
                (void)msg;
                (void)seed;
                return ti.upper;
            });
    }

    simulation::time_point
    price_setter::act(simulation::time_interval step, std::seed_seq &seed)
    {
        (void)seed;
        simulation::time_point next_ = step.upper;
        std::vector<quote> quotes_;

        if(state == sending_quotes) {
            // send out initial quotes and wait for orders,
            // scheduled in the same time step
            next_ = step.lower;
            for(const auto &[k, v] : traded_properties) {
                (void)k;
                quotes_.push_back(v);
            }
        }else{
            std::unordered_map< identity<agent>,
                                std::shared_ptr<walras::differentiable_order_message>>
                orders_;
            for(const auto &[k, message_]: inbox) {
                (void)k;
                if(walras::differentiable_order_message::code == message_->type) {
                    auto order_ = std::dynamic_pointer_cast<
                        walras::differentiable_order_message>(message_);

                    if(message_->sent < step.lower){
                        next_ = step.lower;
                        break;
                    }

                    orders_.insert({order_->sender, order_});

                }
            }

            if(!orders_.empty()) {
                // there is at least one order so we clear the market
                auto scalars_ = clear_market(orders_, step);
                std::vector<price> prices_;
                for(auto &[k, v]: traded_properties){
                    (void)v;
                    //auto price_ =
                    //    cash(currencies::USD).value(scalars_[k->identifier]);
                    //traded_properties.insert(
                    //    std::make_pair(k, quote(price_)));  // overwrite
                    prices_.emplace_back(std::get<price>(v.type));
                    quotes_.emplace_back(quote(v));
                }
                //output_clearing_prices_->put(step.lower, prices_);
                latest = step.lower;
            } else { // restore previous prices
                for(const auto &[k, v]: traded_properties) {
                    (void)k;
                    quotes_.push_back(v);
                }
            }
        }

        law::property_map<quote> quote_map_;
        {
            size_t sequence_ = 0;
            for(const auto &[k, v] : traded_properties) {
                quote_map_.insert({k, quotes_[sequence_]});
                ++sequence_;
            }
        }

        for(const auto &p : participants) {
            auto m = this->template create_message<walras::quote_message>(
                p, step.lower + 1, this->identifier, p, quote_map_);
        }
        state = clearing_market;

        return next_;
    }

    ///
    /// \brief  Clear market using tatonnement. It is assumed the price_setter
    ///         has received at least one excess demand function for each
    ///         property from market participants, otherwise
    ///         it will return the previous price for the property with no
    ///         excess demand curve.
    ///
    ///
    std::map<identity<law::property>, double> price_setter::clear_market(const std::unordered_map<identity<agent>, std::shared_ptr<walras::differentiable_order_message>> &orders, const simulation::time_interval &step)
    {
        law::property_map<quote> old_quotes_ = this->traded_properties;

        std::map<identity<law::property>, quote> quotes_;
        for(const auto &[k, v] : traded_properties) {
            (void)k;
            quotes_.emplace(k->identifier, v);
        }

        tatonnement::excess_demand_model model_(quotes_);
        for(auto [key, function_] : orders) {
            (void)key;
            model_.excess_demand_functions_.push_back(function_);
        }
        auto result1_ = model_.do_compute();

        // if finding a price failed, return previous price vector
        if(!result1_.has_value()) {
            std::map<identity<law::property>, double> previous_;
            for(const auto &[k, v] : traded_properties) {
                (void)k;
                previous_.insert({k->identifier, 1.0 * double(v)});
            }
            return previous_;
        }

        auto result_ = result1_.value();

        ////////////////////////////////////////////////////////////////////////
        // round to the nearest valid price
        std::map<identity<law::property>, std::tuple<quote, double>> solution_;
        for(auto [p, q] : this->traded_properties) {
            double scalar_ = result_.find(p->identifier)->second;
            std::tuple<quote, double> part_ = std::make_tuple(q, scalar_);
            solution_.emplace(p->identifier, part_);

            std::visit([this, result_, p = p](auto &quote) {
                    using type_ = std::decay_t<decltype(quote)>;
                    if constexpr(std::is_same_v<type_, price>) {
                        std::get<price>(traded_properties[p].type).value =
                            int64_t(
                                quote.value
                                * result_.find(p->identifier)->second);

                    }else if constexpr(std::is_same_v<type_, exchange_rate>) {
                        quote =
                            exchange_rate(uint64_t(
                                quote.numerator()
                                * result_.find(p->identifier)->second), quote.denominator());
                        traded_properties[p].type = quote;
                    }else{
                         static_assert(always_false<type_>::value,
                                       "non-exhaustive handling of quotes");
                    }
                }, q.type);
        }

        ////////////////////////////////////////////////////////////////////////
        // recompute demand for solution
        ////////////////////////////////////////////////////////////////////////
        map<identity<property>, double> volumes_;
        map<identity<property>, map<identity<agent>, std::tuple<double, quantity, quantity> >> orders_;
        for(const auto &[participant, order_]: orders) {
            auto demand_ = order_->excess_demand_m(solution_);
            for(const auto &[k, v]: demand_) {
                if(v == 0) {
                    continue;
                }
                auto i = volumes_.find(k);
                if(volumes_.end() == i) {
                    i = volumes_.emplace(k, 0).first;
                    orders_.emplace(k, map<identity<agent>, std::tuple<double, quantity, quantity>>());
                }
                i->second += abs(v);

                auto j = order_->supply.find(k);
                if(order_->supply.end() == j){
                    orders_.find(k)->second.emplace(participant,
                                                    std::make_tuple(v
                                                            ,quantity(0,1)
                                                            , quantity(0,1)));
                }else{
                    orders_.find(k)->second.emplace(participant,
                                                    std::make_tuple(v
                                                            ,std::get<0>(j->second)
                                                            , std::get<1>(j->second)));
                }
            }
        }

        ////////////////////////////////////////////////////////////////////////
        // compute transfers in terms of the primary property
        map<identity<property>, map<identity<agent>, int64_t>> transfers_;
        for(const auto &[property_, data_]: traded_properties){
            auto i = volumes_.find(property_->identifier);
            if(volumes_.end() == i){
                continue;   // no agents expressed interest
            }
            int64_t accumulator_ = 0;
            std::vector<std::tuple<identity<agent>, int64_t>> allocations_;
            for(auto [p,a]: orders_.find(property_->identifier)->second){
                auto alloc_ = int64_t(std::get<0>(a));// / i->second);
                accumulator_ += alloc_;
                allocations_.push_back(std::make_tuple(p, alloc_ ));
            }
            std::sort(allocations_.begin(), allocations_.end(),
                      [](const auto &a, const auto &b) -> bool
                      {
                          return std::abs(std::get<1>(a))
                                 < std::abs(std::get<1>(b));
                      });
            //
             int64_t error_ = accumulator_;
            if(error_ < 0){ // assigned too many

                if(allocations_.size() < -error_){
                    LOG(notice) << "clearing price beyond rounding error" << std::endl;
                }

                for(size_t ii = 0; ii < uint64_t(-error_); ++ii){
                    std::get<1>(allocations_[ii%allocations_.size()]) += 1;
                }
            }else{
                //assert(allocations_.size() >= uint64_t(error_));
                if(allocations_.size() < uint64_t(error_)){
                    LOG(notice) << "clearing price beyond rounding error" << std::endl;
                }

                for(size_t ii = 0; ii < uint64_t(error_); ++ii){
                    std::get<1>(allocations_[ii%allocations_.size()]) -= 1;
                }
            }
            auto pair_ = transfers_.emplace(property_->identifier, map<identity<agent>, int64_t>());

            for(auto [p,a]: allocations_) {
                 pair_.first->second.emplace(p, a);
            }
        }
        LOG(trace) << transfers_ << std::endl;

        //
        //  send_: we, the market maker, send items to participant
        //  receive_: we, the market maker, receive items from participant
        //
        map<identity<agent>,  accounting::inventory_filter<law::property> > send_, receive_;

        auto usd_ = std::make_shared<cash>(currencies::USD);

        for(const auto &[property_, data_]: traded_properties) {
            for(auto &[p, v]: transfers_.find(*property_)->second){
                if(v == 0){
                    continue;
                }

                auto i = orders.find(p)->second->supply.find(*property_);
                if(orders.find(p)->second->supply.end() == i){
                    continue;
                }
                uint64_t &long_amount_ = std::get<0>(i->second).amount;
                uint64_t &short_amount_ = std::get<1>(i->second).amount;

                if(v > 0){ // send properties to this agent, or receive shorts
                    if(long_amount_ > 0){ // they had long pos
                        //  purchase
                        accounting::inventory_filter<law::property> purchased_;
                        purchased_.insert(property_, quantity(uint64_t(v), 1));
                        send_.emplace(p, purchased_);
                        //  payment
                        // they should send cash for the purchase
                        auto j = receive_.find(p);
                        if(receive_.end() == j){
                            auto [jj, succes_] = receive_.emplace(p, accounting::inventory_filter<law::property>());
                            j = jj;
                        }
                        auto q__ = usd_->amount(v * double(std::get<price>(data_.type)) / data_.lot);
                        j->second.insert(usd_, q__);
                    }else if(short_amount_ > 0){// there was a short position
                        if(uint64_t(v) >= short_amount_){// cancel all
                            // 1 cancel short, by transferring shorts back to market agent
                            // cancel the short, by receiving their short contracts
                            std::map<identity<property>, esl::quantity> basket_;
                            basket_.emplace(property_->identifier, quantity(1,1));
                            auto short_contract_ = std::make_shared<finance::securities_lending_contract>(identifier, p, basket_);
                            auto r = receive_.find(p);
                            if(receive_.end() == r){
                                auto [rr, b_] = receive_.emplace(p, accounting::inventory_filter<law::property>());
                                r = rr;
                            }
                            r->second.insert(short_contract_, quantity(short_amount_,1));
                            // we pay back the collateral amount
                            auto s = send_.find(p);
                            if(send_.end() == s){
                                auto [ss, success_] = send_.emplace(p, accounting::inventory_filter<law::property>());
                                s = ss;
                            }

                            auto o = old_quotes_.find(property_);
                            auto collateral_ =
                                usd_->amount((short_amount_ * double(std::get<price>(o->second.type))) / o->second.lot);
                            s->second.insert(usd_, collateral_);

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

                            v -=  short_amount_;

                            if(v > 0){
                                // this is all copy pasted from above
                                accounting::inventory_filter<law::property> purchased_;
                                purchased_.insert(property_, quantity(uint64_t(v), 1));
                                send_.emplace(p, purchased_);

                                ////////////////////////////////////////////////////////
                                //  payment
                                // they should send cash for the purchase
                                auto j = receive_.find(p);
                                if(receive_.end() == j){
                                    auto [jj, succes_] = receive_.emplace(p, accounting::inventory_filter<law::property>());
                                    j=jj;
                                }
                                j->second.insert(usd_, usd_->amount(v * double(std::get<price>(data_.type)) / data_.lot));
                            }

                        }else{// cancel in part

                            auto amount_to_cancel = v;

                            std::map<identity<property>, esl::quantity> basket_;
                            basket_.emplace(property_->identifier, quantity(1,1));
                            auto short_contract_ = std::make_shared<finance::securities_lending_contract>(identifier, p, basket_);
                            auto r = receive_.find(p);
                            if(receive_.end() == r){
                                auto [rr, b_] = receive_.emplace(p, accounting::inventory_filter<law::property>());
                                r=rr;
                            }
                            r->second.insert(short_contract_, quantity(amount_to_cancel,1));
                            // we pay back the collateral amount
                            auto s = send_.find(p);
                            if(send_.end() == s){
                                auto [ss, success_] = send_.emplace(p, accounting::inventory_filter<law::property>());
                                s=ss;
                            }

                            auto o = old_quotes_.find(property_);
                            auto collateral_ =
                                usd_->amount((amount_to_cancel * double(std::get<price>(o->second.type))) / o->second.lot);
                            s->second.insert(usd_, collateral_);
                        }
                    }
                }else{ // participant wants to sell/short///////////////////////////////////////////////////////////////////////////
                    if(long_amount_ > 0 || short_amount_ == 0) {  // they had long pos/had nothing

                        if(-v >= long_amount_){// cancel all
                            // 1 cancel long, by transferring stocks back to market agent
                            auto r = receive_.find(p);
                            if(receive_.end() == r){
                                auto [rr, b_] = receive_.emplace(p, accounting::inventory_filter<law::property>());
                                r = rr;
                            }
                            r->second.insert(property_, quantity(long_amount_,1));

                            auto s = send_.find(p);
                            if(send_.end() == s){
                                auto [ss, success_] = send_.emplace(p, accounting::inventory_filter<law::property>());
                                s = ss;
                            }
                            auto proceeds_ =
                                usd_->amount((long_amount_ * double(std::get<price>(data_.type))) / data_.lot);
                            s->second.insert(usd_, proceeds_);

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

                            v +=  long_amount_;
                            if(v < 0){
                                std::map<identity<property>, esl::quantity> basket_;
                                basket_.emplace(property_->identifier, quantity(1,1));
                                auto short_contract_ = std::make_shared<finance::securities_lending_contract>(identifier, p, basket_);

                                auto j = send_.find(p);
                                if(send_.end() == j){
                                    auto [jj, succes_] = send_.emplace(p, accounting::inventory_filter<law::property>());
                                    j=jj;
                                }

                                j->second.insert(short_contract_, quantity(uint64_t(-v), 1));
                                j->second.insert(usd_, usd_->amount(-v * double(std::get<price>(data_.type)) / data_.lot));
                            }

                        }else{// cancel in part

                            auto amount_to_cancel = -v;
                            auto r = receive_.find(p);
                            if(receive_.end() == r){
                                auto [rr, b_] = receive_.emplace(p, accounting::inventory_filter<law::property>());
                                r = rr;
                            }
                            r->second.insert(property_, quantity(amount_to_cancel,1));

                            // we pay back the collateral amount
                            auto s = send_.find(p);
                            if(send_.end() == s){
                                auto [ss, success_] = send_.emplace(p, accounting::inventory_filter<law::property>());
                                s = ss;
                            }
                            auto proceeds_ =
                                usd_->amount((amount_to_cancel * double(std::get<price>(data_.type))) / data_.lot);
                            s->second.insert(usd_, proceeds_);
                        }
                    }else{
                        //extend the short position
                        auto amount_to_extend = -v;

                        std::map<identity<property>, esl::quantity> basket_;
                        basket_.emplace(property_->identifier, quantity(1,1));
                        auto short_contract_ = std::make_shared<finance::securities_lending_contract>(identifier, p, basket_);
                        auto s = send_.find(p);
                        if(send_.end() == s){
                            auto [ss, b_] = receive_.emplace(p, accounting::inventory_filter<law::property>());
                            s=ss;
                        }

                        auto sale_ =
                            usd_->amount((amount_to_extend * double(std::get<price>(data_.type))) / data_.lot);
                        s->second.insert(usd_, sale_);
                        s->second.insert(short_contract_, quantity(amount_to_extend,1));
                    }
                }
            }
        }

        for(auto [p,i]: send_){
            LOG(trace) << "market sends to " << p << " items " << i << std::endl;
            this->template create_message<interaction::transfer>( p
                                                                , step.lower
                                                                , identifier
                                                                 , p
                                                                 , reinterpret_identity_cast<law::owner<law::property>>(identifier)
                                                                 , reinterpret_identity_cast<law::owner<law::property>>(p)
                                                                 , i
                                                                );
        }

        for(auto [p,i]: receive_){
            LOG(trace) << "market receives from " << p << " items " << i << std::endl;
            this->template create_message<interaction::transfer>( p
                , step.lower
                , p
                , identifier
                , reinterpret_identity_cast<law::owner<law::property>>(p)
                , reinterpret_identity_cast<law::owner<law::property>>(identifier)
                , i
            );
        }

        return result_;
    }

}  // namespace esl::economics::markets::walras

#include <boost/serialization/export.hpp>

#include <esl/data/serialization.hpp>

BOOST_CLASS_EXPORT(std::vector<esl::economics::price>);
typedef std::tuple<esl::simulation::time_point,
                   std::vector<esl::economics::price>>
    tuple_time_point_price_vector;
BOOST_CLASS_EXPORT(tuple_time_point_price_vector);
typedef std::vector<
    std::tuple<esl::simulation::time_point, std::vector<esl::economics::price>>>
    time_series_price_vector;
BOOST_CLASS_EXPORT(time_series_price_vector);
BOOST_CLASS_EXPORT(esl::data::output<std::vector<esl::economics::price>>);