/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2015 Peter Caspers

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

/*! \file mcfxtarfengine.hpp
    \brief Monte Carlo engine for FX Tarf
*/

#ifndef quantlib_pricingengines_mc_fxtarf_hpp
#define quantlib_pricingengines_mc_fxtarf_hpp

#include <ql/experimental/fx/fxtarfengine.hpp>
#include <ql/event.hpp>
#include <ql/pricingengines/mcsimulation.hpp>
#include <ql/processes/blackscholesprocess.hpp>
#include <ql/math/generallinearleastsquares.hpp>

#include <boost/make_shared.hpp>

namespace QuantLib {

namespace {

// raw data for proxy estimation
// the data vector is organized as follows
// level 0 = openFixings (e.g. 6 5 4 3 2 1 = Indices 5 ... 0)
// level 1 = accumulated amount buckets
//    [ previously accumuated amount = a(0), a(1) ]
//    [ a(1), a(2) ]
//    ...
//    [ a(n-1), a(n) = target ]
// level 2 = vector of pairs (spot, npv), sorted asc by spot values

typedef std::vector<std::vector<std::vector<std::pair<Real, Real> > > >
    ProxyData;

// regression function basis
Real basis0(const double) { return 1; }
Real basis1(const double x) { return x; }
Real basis2(const double x) { return x * x; }

} // empty namespace

template <class RNG = PseudoRandom, class S = Statistics>
class McFxTarfEngine : public FxTarfEngine,
                       public McSimulation<SingleVariate, RNG, S> {
  public:
    /*! proxy function giving a function spot => npv for one segment
        (bucket accumulated amount, number of open fixings)
        the function is given by two quadratic polynomials on intervals
        (-\infty,cutoff] and (cutoff,\infty).
        Only the ascending (long calls) or descending (long puts) branch
        is used and then extrapolated flat.
        For calls the extrapolation below the given lowerCutoff is done
        linear (for puts above this value).
    */
    class QuadraticProxyFunction : public FxTarf::Proxy::ProxyFunction {
      public:
        QuadraticProxyFunction(Option::Type type, const Real cutoff,
                               const Real a1, const Real b1, const Real c1,
                               const Real a2, const Real b2, const Real c2,
                               const Real lowerCutoff, const Real coreRegionMin,
                               const Real coreRegionMax);
        Real operator()(const Real spot) const;
        std::pair<Real, Real> coreRegion() const {
            return std::make_pair(coreRegionMin_, coreRegionMax_);
        }

      private:
        Option::Type type_;
        const Real a1_, b1_, c1_, a2_, b2_, c2_;
        const Real cutoff_, lowerCutoff_;
        const Real coreRegionMin_, coreRegionMax_;
        int flatExtrapolationType1_,
            flatExtrapolationType2_; // +1 = right, -1 = left
        Real extrapolationPoint1_, extrapolationPoint2_;
    };

    /*! typedefs */
    typedef typename McSimulation<SingleVariate, RNG, S>::path_generator_type
        path_generator_type;
    typedef typename McSimulation<SingleVariate, RNG, S>::path_pricer_type
        path_pricer_type;
    typedef typename McSimulation<SingleVariate, RNG, S>::stats_type stats_type;

    //! constructor
    McFxTarfEngine(
        const boost::shared_ptr<GeneralizedBlackScholesProcess> &process,
        Size timeSteps, Size timeStepsPerYears, bool brownianBridge,
        bool antitheticVariate, Size requiredSamples, Real requiredTolerance,
        Size maxSamples, BigNatural seed,
        const Handle<YieldTermStructure> discount, const bool generateProxy);

    void calculate() const;
    void reset();

  protected:
    // McSimulation Interface
    TimeGrid timeGrid() const;
    boost::shared_ptr<path_generator_type> pathGenerator() const;
    boost::shared_ptr<path_pricer_type> pathPricer() const;
    // data members
    boost::shared_ptr<GeneralizedBlackScholesProcess> process_;
    Size timeSteps_, timeStepsPerYear_;
    Size requiredSamples_, maxSamples_;
    Real requiredTolerance_;
    bool brownianBridge_;
    BigNatural seed_;
    bool generateProxy_;
    mutable std::vector<Real> fixingTimes_, discounts_;
    // proxy information generated by the engine
    mutable boost::shared_ptr<FxTarf::Proxy> proxy_;
    // simulation data on which the proxy is estimated
    mutable ProxyData data_;
    // bucket limits for collected data
    mutable std::vector<Real> accBucketLimits_;
};

//! Proxy function
template <class RNG, class S>
McFxTarfEngine<RNG, S>::QuadraticProxyFunction::QuadraticProxyFunction(
    Option::Type type, const Real cutoff, const Real a1, const Real b1,
    const Real c1, const Real a2, const Real b2, const Real c2,
    const Real lowerCutoff, const Real coreRegionMin, const Real coreRegionMax)
    : type_(type), cutoff_(cutoff), a1_(a1), b1_(b1), c1_(c1), a2_(a2), b2_(b2),
      c2_(c2), lowerCutoff_(lowerCutoff), coreRegionMin_(coreRegionMin),
      coreRegionMax_(coreRegionMax) {
    QL_REQUIRE((type == Option::Call && lowerCutoff_ <= cutoff) ||
                   (type == Option::Put && lowerCutoff_ >= cutoff),
               "lowerCutoff (" << lowerCutoff_
                               << ") must be less or equal (call) or greater "
                                  "equal (put) than cutoff ("
                               << cutoff
                               << ") for type "
                               << type_);
    // for calls we want ascending, for puts descending functions
    if (close(a1_, 0.0)) {
        // for minspot = maxspot a constant function with a=b=0
        // is constructed, so this requirement must be dropped
        // QL_REQUIRE(b1_ > 0.0, "for a call and a1=0 b ("
        //                           << b1_ << ") must be positive");
    } else {
        extrapolationPoint1_ = -b1_ / (2.0 * a1_);
        flatExtrapolationType1_ =
            (type_ == Option::Call ? 1 : -1) * (a1_ > 0.0 ? -1 : 1);
    }
    if (close(a2_, 0.0)) {
        // for minspot = maxspot a constant function with a=b=0
        // is constructed, so this requirement must be dropped
        // QL_REQUIRE(b2_ > 0.0, "for a call and a2=0 b ("
        //                           << b2_ << ") must be positive");
    } else {
        extrapolationPoint2_ = -b2_ / (2.0 * a2_);
        flatExtrapolationType2_ =
            (type_ == Option::Call ? 1 : -1) * (a2_ > 0.0 ? -1 : 1);
    }
}

template <class RNG, class S>
Real McFxTarfEngine<RNG, S>::QuadraticProxyFunction::
operator()(const Real spot) const {
    Real x = spot;
    if (spot <= cutoff_) {
        if (spot <= lowerCutoff_ && type_ == Option::Call &&
            flatExtrapolationType1_ == 1) {
            // linear extrapolation instead of quadratic outside lowerCutoff
            // if flat extrapolation is to the right)
            return (2.0 * a1_ * lowerCutoff_ + b1_) * spot + c1_ -
                   a1_ * lowerCutoff_ * lowerCutoff_;
        }
        x = flatExtrapolationType1_ *
            std::min(flatExtrapolationType1_ * extrapolationPoint1_,
                     flatExtrapolationType1_ * x);
        Real tmp = a1_ * x * x + b1_ * x + c1_;
        // ensure global monotonicity
        // if (type_ == Option::Put) {
        //     Real ct = flatExtrapolationType2_ *
        //               std::min(flatExtrapolationType2_ *
        //               extrapolationPoint2_,
        //                        flatExtrapolationType2_ * cutoff_);
        //     tmp = std::max(a2_ * ct * ct + b2_ * ct + c2_, tmp);
        // }
        return tmp;
    } else {
        if (spot >= lowerCutoff_ && type_ == Option::Put &&
            flatExtrapolationType2_ == -1) {
            // linear extrapolation instead of quadratic outside lowerCutoff if
            // flat extrapolation is to the left
            return (2.0 * a2_ * lowerCutoff_ + b2_) * spot + c2_ -
                   a2_ * lowerCutoff_ * lowerCutoff_;
        }
        x = flatExtrapolationType2_ *
            std::min(flatExtrapolationType2_ * extrapolationPoint2_,
                     flatExtrapolationType2_ * x);
        Real tmp = a2_ * x * x + b2_ * x + c2_;
        // ensure global monotonicity
        // if (type_ == Option::Call) {
        //     Real ct = flatExtrapolationType1_ *
        //               std::min(flatExtrapolationType1_ *
        //               extrapolationPoint1_,
        //                        flatExtrapolationType1_ * cutoff_);
        //     tmp = std::max(a1_ * ct * ct + b1_ * ct + c1_, tmp);
        // }
        return tmp;
    }
}

//! Monte Carlo fx-tarf engine factory
template <class RNG = PseudoRandom, class S = Statistics>
class MakeMcFxTarfEngine {
  public:
    MakeMcFxTarfEngine(
        const boost::shared_ptr<GeneralizedBlackScholesProcess> &);
    // named parameters
    MakeMcFxTarfEngine &withSteps(Size steps);
    MakeMcFxTarfEngine &withStepsPerYear(Size steps);
    MakeMcFxTarfEngine &withBrownianBridge(bool b = true);
    MakeMcFxTarfEngine &withAntitheticVariate(bool b = true);
    MakeMcFxTarfEngine &withSamples(Size samples);
    MakeMcFxTarfEngine &withAbsoluteTolerance(Real tolerance);
    MakeMcFxTarfEngine &withMaxSamples(Size samples);
    MakeMcFxTarfEngine &withSeed(BigNatural seed);
    MakeMcFxTarfEngine &
    withDiscount(const Handle<YieldTermStructure> &discount);
    MakeMcFxTarfEngine &withProxy(bool b = true);
    // conversion to pricing engine
    operator boost::shared_ptr<PricingEngine>() const;

  private:
    boost::shared_ptr<GeneralizedBlackScholesProcess> process_;
    bool brownianBridge_, antithetic_;
    Size steps_, stepsPerYear_, samples_, maxSamples_;
    Real tolerance_;
    BigNatural seed_;
    Handle<YieldTermStructure> discount_;
    bool generateProxy_;
};

//! Path Pricer
class FxTarfPathPricer : public PathPricer<Path> {
  public:
    FxTarfPathPricer(const std::vector<Real> &fixingTimes,
                     const std::vector<Real> &discounts,
                     const Real accumulatedAmount, const Real sourceNominal,
                     const Real target, const FxTarf *instrument,
                     ProxyData &data, const std::vector<Real> &accBucketLimits,
                     const Date lastPaymentDate,
                     const Handle<YieldTermStructure> &discount,
                     const bool generateProxy);
    Real operator()(const Path &path) const;

  private:
    const std::vector<Real> &fixingTimes_, &discounts_;
    const Real accumulatedAmount_, sourceNominal_, target_;
    const FxTarf *instrument_;
    mutable std::vector<Size> fixingIndices_;
    ProxyData &data_;
    const std::vector<Real> &accBucketLimits_;
    const Date lastPaymentDate_;
    const Handle<YieldTermStructure> discount_;
    const bool generateProxy_;
};

// Implementation

template <class RNG, class S>
McFxTarfEngine<RNG, S>::McFxTarfEngine(
    const boost::shared_ptr<GeneralizedBlackScholesProcess> &process,
    Size timeSteps, Size timeStepsPerYear, bool brownianBridge,
    bool antitheticVariate, Size requiredSamples, Real requiredTolerance,
    Size maxSamples, BigNatural seed, const Handle<YieldTermStructure> discount,
    const bool generateProxy)
    : FxTarfEngine(discount),
      McSimulation<SingleVariate, RNG, S>(antitheticVariate, false),
      process_(process), timeSteps_(timeSteps),
      timeStepsPerYear_(timeStepsPerYear), requiredSamples_(requiredSamples),
      maxSamples_(maxSamples), requiredTolerance_(requiredTolerance),
      brownianBridge_(brownianBridge), seed_(seed),
      generateProxy_(generateProxy) {
    QL_REQUIRE(timeSteps != Null<Size>() || timeStepsPerYear != Null<Size>(),
               "no time steps provided");
    QL_REQUIRE(timeSteps == Null<Size>() || timeStepsPerYear == Null<Size>(),
               "both time steps and time steps per year were provided");
    QL_REQUIRE(timeSteps != 0, "timeSteps must be positive, "
                                   << timeSteps << " not allowed");
    QL_REQUIRE(timeStepsPerYear != 0, "timeStepsPerYear must be positive, "
                                          << timeStepsPerYear
                                          << " not allowed");
    QL_REQUIRE(!discount_.empty(), "no discount curve given");
    registerWith(process_);
}

template <class RNG, class S> void McFxTarfEngine<RNG, S>::reset() {
    FxTarfEngine::reset();
    fixingTimes_.clear();
    discounts_.clear();
    proxy_ = boost::shared_ptr<FxTarf::Proxy>();
    data_.clear();
    accBucketLimits_.clear();
}

template <class RNG, class S> void McFxTarfEngine<RNG, S>::calculate() const {

    Date today = Settings::instance().evaluationDate();

    // handle the trivial cases
    FxTarfEngine::calculate();

    // are we already done, i.e. has the base engine set the npv ?
    if (results_.value != Null<Real>())
        return;

    // we have at least one fixing left which is tommorow or later
    for (Size i = 0; i < arguments_.openFixingDates.size(); ++i) {
        fixingTimes_.push_back(process_->time(arguments_.openFixingDates[i]));
        discounts_.push_back(
            discount_->discount(arguments_.openPaymentDates[i]));
    }

    // prepare the data container on which the proxy pricing is estimated
    // later

    // we use a number of heuristics in the following
    // number of buckets for accumulated amounts
    // which are merged though if the data in the buckets
    // does not meet the requirement below
    Size nAccBuckets = 5;
    // first the data points per accumulated amount bucket should
    // be more than the total number of data points divided by dFactor
    Size dFactor = 10;
    // then (spot,npv) pairs are divided into two segments (for calls)
    // [spotMin,spotMin+relCutoff*(spotMax-spotMin)) and
    // [spotMin+relCutoff*(spotMax-spotMin,spotMax]
    // for puts we use 1.0-relCutoff to divide the intervall instead
    Real relCutoff = 0.80;
    // we require minCutoffRatio*(1.0-relCutoff)*totalNoDataPoints
    // to be still in the smaller (spot,npv) segment, otherwise
    // the cutoff will be lowered (calls) by a factor of
    // cutoffShrinkFactor until we reach this critical size
    Real minCutoffRatio = 0.25;
    Real cutoffShrinkFactor = 0.99;
    // on the lower bound (for calls) a lowerCutoff is determined
    // such that more than 1-minLowerExtr points are above this
    // cutoff. Below this threshhold, a linear extrapolation is
    // used.
    Real minLowerExtr = 0.05;
    // if the intersection of the two quadratic functions lies
    // within (1-smoothInt)*cutoff, (1+smoothInt)*cutoff
    // the cutoff is moved to the intersection points
    Real smoothInt = 0.05;
    // the "trusted" region (aka core region) is defined by chopping
    // off the lower and upper coreCutoff part of the data
    Real coreCutoff = 0.001;

    // this is the minimum number of points required for regression
    Size minRegPoints = 3;

    if (generateProxy_) {
        // create the buckets
        for (Size i = 0; i < nAccBuckets; ++i) {
            accBucketLimits_.push_back(
                static_cast<Real>(i) / static_cast<Real>(nAccBuckets) *
                    (arguments_.target - arguments_.accumulatedAmount) +
                arguments_.accumulatedAmount);
        }

        // we set the first bucket limit to zero, which does not change
        // anything, but leaves no room that the given accumulated amount
        // is below the first limit
        accBucketLimits_[0] = 0.0;

        // initialize the data container
        for (Size i1 = 0; i1 < fixingTimes_.size(); ++i1) {
            std::vector<std::vector<std::pair<Real, Real> > > level1Tmp;
            for (Size i2 = 0; i2 < nAccBuckets; ++i2) {
                level1Tmp.push_back(std::vector<std::pair<Real, Real> >(0));
            }
            data_.push_back(level1Tmp);
        }
    }

    // do the main calculation using the mc machinery
    McSimulation<SingleVariate, RNG, S>::calculate(
        requiredTolerance_, requiredSamples_, maxSamples_);
    results_.value =
        this->mcModel_->sampleAccumulator().mean() + unsettledAmountNpv_;
    if (RNG::allowsErrorEstimate)
        results_.errorEstimate =
            this->mcModel_->sampleAccumulator().errorEstimate();

    if (!generateProxy_)
        return;

    // create the proxy object and initialize the members
    proxy_ = boost::make_shared<FxTarf::Proxy>();
    proxy_->origEvalDate = today;
    proxy_->openFixingDates = arguments_.openFixingDates;
    proxy_->accBucketLimits = accBucketLimits_;
    proxy_->lastPaymentDate = arguments_.schedule.dates().back();
    for (Size i = 0; i < proxy_->openFixingDates.size(); ++i) {
        std::vector<boost::shared_ptr<FxTarf::Proxy::ProxyFunction> > fctVecTmp(
            accBucketLimits_.size());
        proxy_->functions.push_back(fctVecTmp);
    }

    // do the regression on appropriately merged data sets
    for (Size i = 0; i < arguments_.openFixingDates.size(); ++i) {
        // get the data for the specific number of open fixing times
        std::vector<std::vector<std::pair<Real, Real> > > &tmp = data_[i];

        // how many data points do we have over all
        // accumulated amount buckets ?
        Size numberOfDataPoints = 0;
        for (Size j = 0; j < tmp.size(); ++j)
            numberOfDataPoints += tmp[j].size();

        // merge data if pieces are too small
        Size k0 = 0, k0Before = 0;
        do {
            std::vector<std::pair<Real, Real> > xTmp;
            Real spotMin = QL_MAX_REAL, spotMax = QL_MIN_REAL;
            do {
                std::vector<std::pair<Real, Real> > xTmp2(xTmp.size() +
                                                          tmp[k0].size());
                std::merge(xTmp.begin(), xTmp.end(), tmp[k0].begin(),
                           tmp[k0].end(), xTmp2.begin());
                xTmp.swap(xTmp2);
                if (tmp[k0].size() > 0) {
                    if (tmp[k0].front().first < spotMin)
                        spotMin = tmp[k0].front().first;
                    if (tmp[k0].back().first > spotMax)
                        spotMax = tmp[k0].back().first;
                }
                k0++;
            } while (dFactor * xTmp.size() < numberOfDataPoints);

            // count the number of remaining data points ...
            Size remainingDataPoints = 0;
            for (Size j = k0; j < tmp.size(); ++j)
                remainingDataPoints += tmp[j].size();

            // ... and join the rest of data if they are to few
            if (dFactor * remainingDataPoints < numberOfDataPoints) {
                for (Size j = k0; j < tmp.size(); ++j) {
                    std::vector<std::pair<Real, Real> > xTmp2(xTmp.size() +
                                                              tmp[j].size());
                    std::merge(xTmp.begin(), xTmp.end(), tmp[j].begin(),
                               tmp[j].end(), xTmp2.begin());
                    xTmp.swap(xTmp2);
                    if (tmp[j].size() > 0) {
                        if (tmp[j].front().first < spotMin)
                            spotMin = tmp[j].front().first;
                        if (tmp[j].back().first > spotMax)
                            spotMax = tmp[j].back().first;
                    }
                }
                k0 = tmp.size();
            }

            // we rearrange the data to get two segments for the spot
            bool isCall = arguments_.longPositionType == Option::Call;
            Real relCutoffTmp = isCall ? relCutoff : 1.0 - relCutoff;
            std::vector<Real> xTmp1, xTmp2, yTmp1, yTmp2;
            Real cutoff = spotMin + relCutoffTmp * (spotMax - spotMin);

            // we want a certain percentage of data still in the smaller
            // data set, otherwise we lower the cutoff
            Size minDataSegment =
                static_cast<Size>((1.0 - relCutoffTmp) * minCutoffRatio *
                                  xTmp.size()) +
                1;
            Size sizeA, sizeB, criticalSize;
            do {
                sizeA = std::upper_bound(xTmp.begin(), xTmp.end(),
                                         std::make_pair(cutoff, 0.0)) -
                        xTmp.begin();
                sizeB = xTmp.size() - sizeA;
                criticalSize = isCall ? sizeB : sizeA;
                if (((isCall && relCutoffTmp > 0.5) ||
                     (!isCall && relCutoffTmp < 0.5)) &&
                    (criticalSize < minDataSegment ||
                     criticalSize < minRegPoints)) {
                    if (isCall)
                        relCutoffTmp *= cutoffShrinkFactor;
                    else
                        relCutoffTmp /= std::min(cutoffShrinkFactor, 1.0);
                    cutoff = spotMin + relCutoffTmp * (spotMax - spotMin);
                }
            } while (
                ((isCall && relCutoffTmp > 0.5) ||
                 (!isCall && relCutoffTmp < 0.5)) &&
                (criticalSize < minDataSegment || criticalSize < minRegPoints));

            // copy the data to the final vectors used for the regression
            for (Size ii = 0; ii < xTmp.size(); ++ii) {
                if (xTmp[ii].first <= cutoff) {
                    xTmp1.push_back(xTmp[ii].first);
                    yTmp1.push_back(xTmp[ii].second);
                } else {
                    xTmp2.push_back(xTmp[ii].first);
                    yTmp2.push_back(xTmp[ii].second);
                }
            }

            // determine lower cutoff (in terms of calls), below which we
            // extrapolate linear
            Real lowerCutoff =
                xTmp[static_cast<int>(xTmp.size() * (isCall
                                                         ? minLowerExtr
                                                         : 1.0 - minLowerExtr))]
                    .first;
            // determine the core (trusted) region
            Real coreRegionMin =
                xTmp[static_cast<int>(xTmp.size() * coreCutoff)].first;
            Real coreRegionMax =
                xTmp[static_cast<int>(xTmp.size() * (1.0 - coreCutoff))].first;

            // the function object
            boost::shared_ptr<FxTarf::Proxy::ProxyFunction> fct;

            // if minSpot = cutoff = maxSpot (this may happen at t = 0) we
            // just return a constant function being the average over all
            // data points
            if (std::fabs(spotMax - spotMin) < QL_EPSILON) {
                Real avg = 0.0;
                for (Size i = 0; i < xTmp1.size(); ++i)
                    avg += yTmp1[i];
                for (Size i = 0; i < xTmp2.size(); ++i)
                    avg += yTmp2[i];
                avg /= static_cast<double>(xTmp1.size() + xTmp2.size());
                fct = boost::shared_ptr<QuadraticProxyFunction>(
                    new QuadraticProxyFunction(
                        arguments_.longPositionType, cutoff, 0.0, 0.0, avg, 0.0,
                        0.0, avg, -QL_MAX_REAL, spotMin, spotMax));
            } else {

                // final sanity check before regression
                QL_REQUIRE(xTmp1.size() >= 3,
                           "too few points for regression in set 1 ("
                               << xTmp1.size() << ")");
                QL_REQUIRE(xTmp2.size() >= 3,
                           "too few points for regression in set 2 ("
                               << xTmp2.size() << ")");

                // regression
                std::vector<boost::function<Real(Real)> > v;
                v.push_back(&basis0);
                v.push_back(&basis1);
                v.push_back(&basis2);

                GeneralLinearLeastSquares ls1(xTmp1, yTmp1, v);
                Array result1 = ls1.coefficients();

                GeneralLinearLeastSquares ls2(xTmp2, yTmp2, v);
                Array result2 = ls2.coefficients();

                // check if the cutoff should be moved to the intersection
                // point of the two overlapping functions
                Real a1 = result1[2];
                Real b1 = result1[1];
                Real c1 = result1[0];
                Real a2 = result2[2];
                Real b2 = result2[1];
                Real c2 = result2[0];
                if (close(a1, a2)) {
                    if (!close(b1, b2)) {
                        Real tmp = -(c1 - c2) / (b1 - b2);
                        if (fabs((tmp - cutoff) / cutoff) < smoothInt) {
                            cutoff = tmp;
                        }
                    }
                } else {
                    Real tmp1 =
                        (-(b1 - b2) + std::sqrt((b1 - b2) * (b1 - b2) -
                                                4.0 * (a1 - a2) * (c1 - c2))) /
                        (2.0 * (a1 - a2));
                    Real tmp2 =
                        (-(b1 - b2) - std::sqrt((b1 - b2) * (b1 - b2) -
                                                4.0 * (a1 - a2) * (c1 - c2))) /
                        (2.0 * (a1 - a2));
                    if (fabs((tmp1 - cutoff) / cutoff) < smoothInt)
                        cutoff = tmp1;
                    if (fabs((tmp2 - cutoff) / cutoff) < smoothInt)
                        cutoff = tmp2;
                }

                // make sure that lower cutoff is still left from cutoff
                lowerCutoff = std::min(lowerCutoff, cutoff);

                fct = boost::shared_ptr<QuadraticProxyFunction>(
                    new QuadraticProxyFunction(
                        arguments_.longPositionType, cutoff, a1, b1, c1, a2, b2,
                        c2, lowerCutoff, coreRegionMin, coreRegionMax));
            }

            // store the proxy function, please note that
            // due to merging we may have the same function for several
            // accumulated amount segments
            for (Size kk = k0Before; kk < k0; ++kk) {
                proxy_->functions[i][kk] = fct;
            }
            k0Before = k0;
        } while (k0 < tmp.size()); // do-while over accumulated amount buckets
    }                              // for openFixingTimes

    // set proxy information generated during simulation as result

    results_.proxy = this->proxy_;

    // clear the collected data

    data_.clear();
}

template <class RNG, class S>
TimeGrid McFxTarfEngine<RNG, S>::timeGrid() const {
    if (timeSteps_ != Null<Size>()) {
        return TimeGrid(fixingTimes_.begin(), fixingTimes_.end(), timeSteps_);
    } else if (timeStepsPerYear_ != Null<Size>()) {
        Size steps = static_cast<Size>(timeStepsPerYear_ * fixingTimes_.back());
        return TimeGrid(fixingTimes_.begin(), fixingTimes_.end(),
                        std::max<Size>(steps, 1));
    } else {
        QL_FAIL("time steps not specified");
    }
}

template <class RNG, class S>
boost::shared_ptr<typename McFxTarfEngine<RNG, S>::path_generator_type>
McFxTarfEngine<RNG, S>::pathGenerator() const {
    TimeGrid grid = timeGrid();
    typename RNG::rsg_type gen =
        RNG::make_sequence_generator(grid.size() - 1, seed_);
    return boost::make_shared<path_generator_type>(process_, grid, gen,
                                                   brownianBridge_);
}

template <class RNG, class S>
boost::shared_ptr<typename McFxTarfEngine<RNG, S>::path_pricer_type>
McFxTarfEngine<RNG, S>::pathPricer() const {
    return boost::shared_ptr<FxTarfPathPricer>(new FxTarfPathPricer(
        fixingTimes_, discounts_, arguments_.accumulatedAmount,
        arguments_.sourceNominal, arguments_.target, arguments_.instrument,
        this->data_, this->accBucketLimits_, arguments_.schedule.dates().back(),
        this->discount_, this->generateProxy_));
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S>::MakeMcFxTarfEngine(
    const boost::shared_ptr<GeneralizedBlackScholesProcess> &process)
    : process_(process), brownianBridge_(false), antithetic_(false),
      steps_(Null<Size>()), stepsPerYear_(Null<Size>()), samples_(Null<Size>()),
      maxSamples_(Null<Size>()), tolerance_(Null<Real>()), seed_(0),
      discount_(process->riskFreeRate()), generateProxy_(false) {}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withSteps(Size steps) {
    steps_ = steps;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withStepsPerYear(Size steps) {
    stepsPerYear_ = steps;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withBrownianBridge(bool brownianBridge) {
    brownianBridge_ = brownianBridge;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withAntitheticVariate(bool b) {
    antithetic_ = b;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withSamples(Size samples) {
    QL_REQUIRE(tolerance_ == Null<Real>(), "tolerance already set");
    samples_ = samples;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withAbsoluteTolerance(Real tolerance) {
    QL_REQUIRE(samples_ == Null<Size>(), "number of samples already set");
    QL_REQUIRE(RNG::allowsErrorEstimate, "chosen random generator policy "
                                         "does not allow an error estimate");
    tolerance_ = tolerance;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withMaxSamples(Size samples) {
    maxSamples_ = samples;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withSeed(BigNatural seed) {
    seed_ = seed;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &MakeMcFxTarfEngine<RNG, S>::withDiscount(
    const Handle<YieldTermStructure> &discount) {
    discount_ = discount;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withProxy(bool b) {
    generateProxy_ = b;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S>::
operator boost::shared_ptr<PricingEngine>() const {
    QL_REQUIRE(steps_ != Null<Size>() || stepsPerYear_ != Null<Size>(),
               "number of steps not given");
    QL_REQUIRE(steps_ == Null<Size>() || stepsPerYear_ == Null<Size>(),
               "number of steps overspecified");
    return boost::shared_ptr<PricingEngine>(new McFxTarfEngine<RNG, S>(
        process_, steps_, stepsPerYear_, brownianBridge_, antithetic_, samples_,
        tolerance_, maxSamples_, seed_, discount_, generateProxy_));
}

} // namespace QuantLib
#endif


// Author(s): Jeroen Keiren
// Copyright: see the accompanying file COPYING or copy at
// https://svn.win.tue.nl/trac/MCRL2/browser/trunk/COPYING
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
/// \file bag_test.cpp
/// \brief Basic regression test for bag expressions.

#include <boost/test/minimal.hpp>

#include "mcrl2/data/bag.h"
#include "mcrl2/data/fbag.h"
#include "mcrl2/data/parse.h"
#include "mcrl2/data/rewriter.h"

using namespace mcrl2;
using namespace mcrl2::data;

// test whether parsing s returns an expression matching predicate p.
// Furthermore check whether the expression does not satisfy q.
template <typename Predicate, typename NegativePredicate>
void test_data_expression(const std::string& s, const variable_vector& v, Predicate p, NegativePredicate q)
{
  std::cerr << "testing data expression " << s << std::endl;
  data_expression e = parse_data_expression(s, v);
  std::cerr << "parsed expression " << e << "\n";
  BOOST_CHECK(p(e));
  BOOST_CHECK(!q(e));
}

/* Test case for various bag sort expressions, based
   on the following specification:

proc P(b: Bag(Nat)) = (1 in b) -> tau . P(({:} + b) - {20:1} * {40:5})
                    + ({10:count(20, b)} < b) -> tau . P(b)
                    + (b <= {20:2} + Set2Bag({20,30,40})) -> tau . P(b);

init P({20:4, 30:3, 40:2});

*/
void bag_expression_test()
{
  data::data_specification specification;

  specification.add_context_sort(sort_bag::bag(sort_pos::pos()));

  data::rewriter normaliser(specification);

  variable_vector v;
  v.push_back(parse_variable("b:Bag(Nat)"));

  BOOST_CHECK(sort_bag::is_bag(sort_bag::bag(sort_nat::nat())));
  BOOST_CHECK(!sort_bag::is_bag(sort_nat::nat()));

  test_data_expression("{x : Nat | x}", v, sort_bag::is_constructor_application, sort_bag::is_in_application);
  test_data_expression("1 in b", v, sort_bag::is_in_application, sort_bag::is_union_application);
  test_data_expression("{:} + b", v, sort_bag::is_union_application, sort_bag::is_intersection_application);
  test_data_expression("(({:} + b) - {20:1}) * {40:5}", v, sort_bag::is_intersection_application, is_less_application<data_expression>);
  test_data_expression("{10:count(20,b)} < b", v, is_less_application<data_expression>, sort_bag::is_bag_comprehension_application);
  test_data_expression("b <= {20:2}", v, is_less_equal_application<data_expression>, sort_bag::is_set2bag_application);
  test_data_expression("Set2Bag({20,30,40})", v, sort_bag::is_set2bag_application, sort_bag::is_union_application);
  test_data_expression("{20:2} + Set2Bag({20,30,40})", v, sort_bag::is_union_application, is_less_equal_application<data_expression>);
  test_data_expression("b <= Set2Bag({20,30,40})", v, is_less_equal_application<data_expression>, sort_bag::is_constructor_application);

  test_data_expression("b <= {20:2} + Set2Bag({20,30,40})", v, is_less_equal_application<data_expression>, sort_bag::is_constructor_application);

  data_expression e = parse_data_expression("{20:1}", v);
  BOOST_CHECK(sort_fbag::is_cons_application(normaliser(e)));

  e = parse_data_expression("{20:4, 30:3, 40:2}", v);
  BOOST_CHECK(sort_fbag::is_cons_application(normaliser(e)));

  e = parse_data_expression("{10:count(20,b)}", v);
  BOOST_CHECK(sort_fbag::is_cinsert_application(normaliser(e)));
}

int test_main(int argc, char** argv)
{
  bag_expression_test();

  return EXIT_SUCCESS;
}


#pragma once
#include <Eigen/Core>
#include <boost/optional.hpp>
#include <catch2/catch.hpp>

const Eigen::IOFormat HeavyFmt(Eigen::FullPrecision, 0, ", ", ";\n", "[", "]",
                               "[", "]");

// improve the formatting of Eigen matrices
namespace Catch {
  template <typename T>
  struct StringMaker<Eigen::DenseBase<T>> {
    static std::string convert(Eigen::DenseBase<T> const& value) {
      std::ostringstream ss;
      ss << value.format(HeavyFmt);
      return ss.str();
    }
  };
}  // namespace Catch

template <typename T>
class EigenIsApprox : public Catch::MatcherBase<T> {
  T value;
  boost::optional<typename T::Scalar> precision;

 public:
  EigenIsApprox(T value, boost::optional<typename T::Scalar> precision)
      : value(std::move(value)), precision(precision) {}

  bool match(T const& other) const override {
    if (precision)
      return value.isApprox(other, precision.get());
    else
      return value.isApprox(other);
  }

  virtual std::string describe() const override {
    std::ostringstream ss;
    ss << std::endl
       << "    is not approximately equal to" << std::endl
       << value.format(HeavyFmt);
    if (precision) ss << std::endl << "    with precision " << precision.get();
    return ss.str();
  }
};

/// check that two Eigen Matrices are approximately equal using value.isApprox
template <typename T>
inline EigenIsApprox<T> IsApprox(
    T value, boost::optional<typename T::Scalar> precision = boost::none) {
  return EigenIsApprox<T>(std::move(value), precision);
}


// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008-2010 Gael Guennebaud <g.gael@free.fr>
//
// Eigen is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// Alternatively, you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// Eigen is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License or the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License and a copy of the GNU General Public License along with
// Eigen. If not, see <http://www.gnu.org/licenses/>.

#include "sparse.h"
#include <Eigen/SparseExtra>

#ifdef EIGEN_CHOLMOD_SUPPORT
#include <Eigen/CholmodSupport>
#endif

#ifdef EIGEN_TAUCS_SUPPORT
#include <Eigen/TaucsSupport>
#endif

template<typename Scalar> void sparse_llt(int rows, int cols)
{
  double density = std::max(8./(rows*cols), 0.01);
  typedef Matrix<Scalar,Dynamic,Dynamic> DenseMatrix;
  typedef Matrix<Scalar,Dynamic,1> DenseVector;

    // TODO fix the issue with complex (see SparseLLT::solveInPlace)
    SparseMatrix<Scalar> m2(rows, cols);
    DenseMatrix refMat2(rows, cols);

    DenseVector b = DenseVector::Random(cols);
    DenseVector refX(cols), x(cols);

    initSparse<Scalar>(density, refMat2, m2, ForceNonZeroDiag|MakeLowerTriangular, 0, 0);
    for(int i=0; i<rows; ++i)
      m2.coeffRef(i,i) = refMat2(i,i) = ei_abs(ei_real(refMat2(i,i)));

    refX = refMat2.template selfadjointView<Lower>().llt().solve(b);
    if (!NumTraits<Scalar>::IsComplex)
    {
      x = b;
      SparseLLT<SparseMatrix<Scalar> > (m2).solveInPlace(x);
      VERIFY(refX.isApprox(x,test_precision<Scalar>()) && "LLT: default");
    }
    #ifdef EIGEN_CHOLMOD_SUPPORT
    x = b;
    SparseLLT<SparseMatrix<Scalar> ,Cholmod>(m2).solveInPlace(x);
    VERIFY(refX.isApprox(x,test_precision<Scalar>()) && "LLT: cholmod");
    #endif

    #ifdef EIGEN_TAUCS_SUPPORT
    // TODO fix TAUCS with complexes
    if (!NumTraits<Scalar>::IsComplex)
    {
      x = b;
//       SparseLLT<SparseMatrix<Scalar> ,Taucs>(m2,IncompleteFactorization).solveInPlace(x);
//       VERIFY(refX.isApprox(x,test_precision<Scalar>()) && "LLT: taucs (IncompleteFactorization)");

      x = b;
      SparseLLT<SparseMatrix<Scalar> ,Taucs>(m2,SupernodalMultifrontal).solveInPlace(x);
      VERIFY(refX.isApprox(x,test_precision<Scalar>()) && "LLT: taucs (SupernodalMultifrontal)");
      x = b;
      SparseLLT<SparseMatrix<Scalar> ,Taucs>(m2,SupernodalLeftLooking).solveInPlace(x);
      VERIFY(refX.isApprox(x,test_precision<Scalar>()) && "LLT: taucs (SupernodalLeftLooking)");
    }
    #endif
}

void test_sparse_llt()
{
  for(int i = 0; i < g_repeat; i++) {
    CALL_SUBTEST_1(sparse_llt<double>(8, 8) );
    int s = ei_random<int>(1,300);
    CALL_SUBTEST_2(sparse_llt<std::complex<double> >(s,s) );
    CALL_SUBTEST_1(sparse_llt<double>(s,s) );
  }
}


/*
 *            Copyright 2009-2020 The VOTCA Development Team
 *                       (http://www.votca.org)
 *
 *      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.
 *
 */

// Third party includes
#include <boost/math/constants/constants.hpp>

// VOTCA includes
#include <votca/tools/constants.h>

// Local VOTCA includes
#include <votca/xtp/orbitals.h>

// Local private VOTCA includes
#include "spectrum.h"

namespace votca {
namespace xtp {

void Spectrum::ParseOptions(const tools::Property& options) {

  // orbitals file or pure DFT output
  orbfile_ = options.ifExistsReturnElseReturnDefault<std::string>(
      ".input", job_name_ + ".orb");

  output_file_ = options.ifExistsReturnElseReturnDefault<std::string>(
      ".output", job_name_ + "_spectrum.dat");

  n_pt_ = options.get(".points").as<Index>();
  lower_ = options.get(".lower").as<double>();
  upper_ = options.get(".upper").as<double>();
  fwhm_ = options.get(".fwhm").as<double>();

  spectrum_type_ = options.get(".type").as<std::string>();
  minexc_ = options.get(".minexc").as<Index>();
  maxexc_ = options.get(".maxexc").as<Index>();
  shiftby_ = options.get(".shift").as<double>();
}

bool Spectrum::Run() {
  log_.setReportLevel(Log::current_level);
  log_.setMultithreading(true);

  log_.setCommonPreface("\n... ...");

  XTP_LOG(Log::error, log_)
      << "Calculating absorption spectrum plot " << orbfile_ << std::flush;

  Orbitals orbitals;
  // load the QM data from serialized orbitals object
  XTP_LOG(Log::error, log_)
      << " Loading QM data from " << orbfile_ << std::flush;
  orbitals.ReadFromCpt(orbfile_);

  // check if orbitals contains singlet energies and transition dipoles
  if (!orbitals.hasBSESinglets()) {
    throw std::runtime_error(
        "BSE singlet energies not stored in QM data file!");
  }

  if (!orbitals.hasTransitionDipoles()) {
    throw std::runtime_error(
        "BSE transition dipoles not stored in QM data file!");
  }

  const Eigen::VectorXd BSESingletEnergies =
      orbitals.BSESinglets().eigenvalues() * tools::conv::hrt2ev;
  const std::vector<Eigen::Vector3d>& TransitionDipoles =
      orbitals.TransitionDipoles();
  Eigen::VectorXd osc = orbitals.Oscillatorstrengths();

  if (maxexc_ > Index(TransitionDipoles.size())) {
    maxexc_ = Index(TransitionDipoles.size()) - 1;
  }

  Index n_exc = maxexc_ - minexc_ + 1;
  XTP_LOG(Log::error, log_)
      << " Considering " << n_exc << " excitation with max energy "
      << BSESingletEnergies(maxexc_) << " eV / min wave length "
      << evtonm(BSESingletEnergies[maxexc_ - 1]) << " nm" << std::flush;

  /*
   *
   * For a single excitation, broaden by Lineshape function L(v-W)
   *    eps(v) = f * L(v-W)
   *
   * where
   *       v: energy
   *       f: oscillator strength in dipole-length gauge
   *       W: excitation energy
   *
   * Lineshape function depend on FWHM and can be
   *
   *      Gaussian
   *          L(v-W) = 1/(sqrt(2pi)sigma) * exp(-0.5 (v-W)^2/sigma^2
   *
   *
   *            with sigma: derived from FWHM (FWHM/2.3548)
   *
   *     Lorentzian
   *          L(v-W) = 1/pi * 0.5 FWHM/( (v-w)^2 + 0.25*FWHM^2 )
   *
   * Full spectrum is superposition of individual spectra.
   *
   *  Alternatively, one can calculate the imaginary part of the
   *  frequency-dependent dielectric function
   *
   *   IM(eps(v)) ~ 1/v^2 * W^2 * |td|^2 * L(v-W)
   *              = 1/v^2 * W   * f      * L(v-W)
   *
   *
   */

  std::ofstream ofs(output_file_, std::ofstream::out);

  if (spectrum_type_ == "energy") {
    ofs << "# E(eV)    epsGaussian    IM(eps)Gaussian   epsLorentz    "
           "Im(esp)Lorentz\n";
    for (Index i_pt = 0; i_pt <= n_pt_; i_pt++) {

      double e = (lower_ + double(i_pt) * (upper_ - lower_) / double(n_pt_));

      double eps_Gaussian = 0.0;
      double imeps_Gaussian = 0.0;
      double eps_Lorentzian = 0.0;
      double imeps_Lorentzian = 0.0;

      for (Index i_exc = minexc_; i_exc <= maxexc_; i_exc++) {
        eps_Gaussian +=
            osc[i_exc] *
            Gaussian(e, BSESingletEnergies(i_exc) + shiftby_, fwhm_);
        imeps_Gaussian += osc[i_exc] * BSESingletEnergies(i_exc) *
                          Gaussian(e, BSESingletEnergies(i_exc), fwhm_);
        eps_Lorentzian +=
            osc[i_exc] * Lorentzian(e, BSESingletEnergies(i_exc), fwhm_);
        imeps_Lorentzian += osc[i_exc] * BSESingletEnergies(i_exc) *
                            Lorentzian(e, BSESingletEnergies(i_exc), fwhm_);
      }

      ofs << e << "    " << eps_Gaussian << "   " << imeps_Gaussian << "   "
          << eps_Lorentzian << "   " << imeps_Lorentzian << std::endl;
    }

    XTP_LOG(Log::error, log_)
        << " Spectrum in energy range from  " << lower_ << " to " << upper_
        << " eV and with broadening of FWHM " << fwhm_
        << " eV written to file  " << output_file_ << std::flush;
  }

  if (spectrum_type_ == "wavelength") {

    ofs << "# lambda(nm)    epsGaussian    IM(eps)Gaussian   epsLorentz    "
           "Im(esp)Lorentz\n";
    for (Index i_pt = 0; i_pt <= n_pt_; i_pt++) {

      double lambda =
          (lower_ + double(i_pt) * (upper_ - lower_) / double(n_pt_));
      double eps_Gaussian = 0.0;
      double imeps_Gaussian = 0.0;
      double eps_Lorentzian = 0.0;
      double imeps_Lorentzian = 0.0;

      for (Index i_exc = minexc_; i_exc <= maxexc_; i_exc++) {
        double exc_lambda = nmtoev(BSESingletEnergies(i_exc) + shiftby_);
        eps_Gaussian += osc[i_exc] * Gaussian(lambda, exc_lambda, fwhm_);
        imeps_Gaussian +=
            osc[i_exc] * exc_lambda * Gaussian(lambda, exc_lambda, fwhm_);
        eps_Lorentzian += osc[i_exc] * Lorentzian(lambda, exc_lambda, fwhm_);
        imeps_Lorentzian +=
            osc[i_exc] * exc_lambda * Lorentzian(lambda, exc_lambda, fwhm_);
      }

      ofs << lambda << "    " << eps_Gaussian << "   " << imeps_Gaussian
          << "   " << eps_Lorentzian << "   " << imeps_Lorentzian << std::endl;
    }
    XTP_LOG(Log::error, log_)
        << " Spectrum in wavelength range from  " << lower_ << " to " << upper_
        << " nm and with broadening of FWHM " << fwhm_
        << " nm written to file  " << output_file_ << std::flush;
  }

  ofs.close();
  return true;
}

double Spectrum::Lorentzian(double x, double center, double fwhm) {
  return 0.5 * fwhm / (std::pow(x - center, 2) + 0.25 * fwhm * fwhm) /
         boost::math::constants::pi<double>();
}

double Spectrum::Gaussian(double x, double center, double fwhm) {
  // FWHM = 2*sqrt(2 ln2) sigma = 2.3548 sigma
  double sigma = fwhm / 2.3548;
  return std::exp(-0.5 * std::pow((x - center) / sigma, 2)) / sigma /
         sqrt(2.0 * boost::math::constants::pi<double>());
}

double Spectrum::evtonm(double eV) { return 1241.0 / eV; }

double Spectrum::evtoinvcm(double eV) { return 8065.73 * eV; }

double Spectrum::nmtoinvcm(double nm) { return 1241.0 * 8065.73 / nm; }

double Spectrum::invcmtonm(double invcm) { return 1.0e7 / invcm; }

double Spectrum::nmtoev(double nm) { return 1241.0 / nm; }

}  // namespace xtp
}  // namespace votca


#pragma once

/**************************************************************************
 * Combination of domains:
 *
 * (1) reduced product of two arbitrary domains with only lattice
 *     operations.
 *
 * (2) reduced product of two arbitrary domains with all operations.
 *
 * The reduction in (1) and (2) is simply done by making bottom the
 * abstract state if one of them is bottom.
 *
 * (3) reduced product of two numerical domains. The reduction is done
 *     via a special push operation that must be defined by the
 *     domains. This is often more precise than (2).
 **************************************************************************/

#include <crab/domains/abstract_domain.hpp>
#include <crab/domains/abstract_domain_specialized_traits.hpp>
#include <crab/support/stats.hpp>

#include <boost/optional.hpp>
#include <algorithm>

namespace crab {
namespace domains {

// Reduced product of two arbitrary domains with only lattice
// operations.
template <typename Domain1, typename Domain2> class basic_domain_product2 {

public:
  using basic_domain_product2_t = basic_domain_product2<Domain1, Domain2>;
  using first_type = Domain1;
  using second_type = Domain2;

private:
  bool m_is_bottom;
  Domain1 m_first;
  Domain2 m_second;

  void canonicalize() {
    if (!m_is_bottom) {
      m_is_bottom = m_first.is_bottom() || m_second.is_bottom();
      if (m_is_bottom) {
        m_first.set_to_bottom();
        m_second.set_to_bottom();
      }
    }
  }

public:
  basic_domain_product2() : m_is_bottom(false) {
    m_first.set_to_top();
    m_second.set_to_top();
  }

  basic_domain_product2(Domain1 &&first, Domain2 &&second,
                        bool &&apply_reduction = true)
      : m_is_bottom(false), m_first(std::move(first)),
        m_second(std::move(second)) {
    if (apply_reduction) {
      // we don't apply normalization when widening
      canonicalize();
    }
  }

  basic_domain_product2(const basic_domain_product2_t &other) = default;
  basic_domain_product2(basic_domain_product2_t &&other) = default;
  basic_domain_product2_t &
  operator=(const basic_domain_product2_t &other) = default;
  basic_domain_product2_t &operator=(basic_domain_product2_t &&other) = default;

  basic_domain_product2_t make_top() const {
    Domain1 dom1;
    Domain2 dom2;
    dom1.set_to_top();
    dom2.set_to_top();
    return basic_domain_product2_t(std::move(dom1), std::move(dom2));
  }

  basic_domain_product2_t make_bottom() const {
    Domain1 dom1;
    Domain2 dom2;
    dom1.set_to_bottom();
    dom2.set_to_bottom();
    return basic_domain_product2_t(std::move(dom1), std::move(dom2));
  }

  void set_to_top() {
    m_is_bottom = false;
    m_first.set_to_top();
    m_second.set_to_top();
  }

  void set_to_bottom() {
    m_is_bottom = true;
    m_first.set_to_bottom();
    m_second.set_to_bottom();
  }

  bool is_bottom() const {
    // canonicalize();
    // return m_is_bottom;
    if (m_is_bottom) {
      return true;
    } else {
      return m_first.is_bottom() || m_second.is_bottom();
    }
  }

  bool is_top() const {
    return (m_first.is_top() && m_second.is_top());
  }

  Domain1 &first() {
    canonicalize();
    return m_first;
  }

  Domain2 &second() {
    canonicalize();
    return m_second;
  }

  const Domain1 &first() const { return m_first; }

  const Domain2 &second() const { return m_second; }

  bool operator<=(const basic_domain_product2_t &other) const {
    if (is_bottom()) {
      return true;
    } else if (other.is_bottom()) {
      return false;
    } else {
      return (m_first <= other.m_first) && (m_second <= other.m_second);
    }
  }

  bool operator==(const basic_domain_product2_t &other) const {
    return (operator<=(other) && other.operator<=(*this));
  }

  void operator|=(const basic_domain_product2_t &other) {
    if (is_bottom()) {
      *this = other;
    } else if (other.is_bottom()) {
      return;
    } else {
      m_first |= other.m_first;
      m_second |= other.m_second;
    }
  }

  basic_domain_product2_t
  operator|(const basic_domain_product2_t &other) const {
    if (is_bottom()) {
      return other;
    } else if (other.is_bottom()) {
      return *this;
    } else {
      return basic_domain_product2_t(m_first | other.m_first,
                                     m_second | other.m_second);
    }
  }

  basic_domain_product2_t
  operator||(const basic_domain_product2_t &other) const {
    return basic_domain_product2_t(m_first || other.m_first,
                                   m_second || other.m_second,
                                   false /* do not apply reduction */);
  }

  basic_domain_product2_t
  operator&(const basic_domain_product2_t &other) const {
    if (is_bottom() || other.is_top()) {
      return *this;
    } else if (other.is_bottom() || is_top()) {
      return other;
    } else {
      return basic_domain_product2_t(m_first & other.m_first,
                                     m_second & other.m_second);
    }
  }

  basic_domain_product2_t
  operator&&(const basic_domain_product2_t &other) const {
    if (is_bottom() || other.is_top()) {
      return *this;
    } else if (other.is_bottom() || is_top()) {
      return other;
    } else {
      return basic_domain_product2_t(m_first && other.m_first,
                                     m_second && other.m_second);
    }
  }

  void write(crab::crab_os &o) const {
    if (is_bottom()) {
      o << "_|_";
    } else {
      o << "(" << m_first << ", " << m_second << ")";
    }
  }

  friend crab::crab_os &operator<<(crab::crab_os &o,
                                   basic_domain_product2_t &dom) {
    dom.write(o);
    return o;
  }

  std::string domain_name() const {
    std::string name =
        "Product(" + m_first.domain_name() + "," + m_second.domain_name() + ")";
    return name;
  }

}; // class basic_domain_product2

// Reduced product of two arbitrary domains with all operations.
template <typename Number, typename VariableName, typename Domain1,
          typename Domain2>
class domain_product2 final
    : public abstract_domain_api<
          domain_product2<Number, VariableName, Domain1, Domain2>> {
public:
  using domain_product2_t =
      domain_product2<Number, VariableName, Domain1, Domain2>;
  using abstract_domain_t = abstract_domain_api<domain_product2_t>;
  using first_type = Domain1;
  using second_type = Domain2;

  using typename abstract_domain_t::disjunctive_linear_constraint_system_t;
  using typename abstract_domain_t::interval_t;
  using typename abstract_domain_t::linear_constraint_system_t;
  using typename abstract_domain_t::linear_constraint_t;
  using typename abstract_domain_t::linear_expression_t;
  using typename abstract_domain_t::reference_constraint_t;
  using typename abstract_domain_t::variable_or_constant_t;
  using typename abstract_domain_t::variable_t;
  using typename abstract_domain_t::variable_vector_t;
  using typename abstract_domain_t::variable_or_constant_vector_t;
  using number_t = Number;
  using varname_t = VariableName;

private:
  using basic_domain_product2_t = basic_domain_product2<Domain1, Domain2>;

  basic_domain_product2_t m_product;

  domain_product2(basic_domain_product2_t &&product)
      : m_product(std::move(product)) {}

  // Reduce operation
  void reduce() {
    if (m_product.first().is_bottom() ||
        m_product.second().is_bottom()) {
      m_product.set_to_bottom();
    }
  }

public:
  domain_product2_t make_top() const override {
    basic_domain_product2_t dom_prod;
    return domain_product2_t(dom_prod.make_top());
  }

  domain_product2_t make_bottom() const override {
    basic_domain_product2_t dom_prod;
    return domain_product2_t(dom_prod.make_bottom());
  }

  void set_to_top() override {
    basic_domain_product2_t dom_prod;
    domain_product2_t dom(dom_prod.make_top());
    std::swap(*this, dom);
  }

  void set_to_bottom() override {
    basic_domain_product2_t dom_prod;
    domain_product2_t dom(dom_prod.make_bottom());
    std::swap(*this, dom);
  }

  domain_product2() : m_product() {}

  domain_product2(const domain_product2_t &other) : m_product(other.m_product) {}

  domain_product2(const domain_product2_t &&other)
      : m_product(std::move(other.m_product)) {}

  domain_product2_t &operator=(const domain_product2_t &other) {
    if (this != &other)
      m_product = other.m_product;
    return *this;
  }

  domain_product2_t &operator=(const domain_product2_t &&other) {
    if (this != &other)
      m_product = std::move(other.m_product);
    return *this;
  }

  bool is_bottom() const override { return m_product.is_bottom(); }

  bool is_top() const override { return m_product.is_top(); }

  Domain1 &first() { return m_product.first(); }
  const Domain1 &first() const { return m_product.first(); }

  Domain2 &second() { return m_product.second(); }
  const Domain2 &second() const { return m_product.second(); }

  bool operator<=(const domain_product2_t &other) const override {
    return (m_product <= other.m_product);
  }

  bool operator==(const domain_product2_t &other) const {
    return (m_product == other.m_product);
  }

  void operator|=(const domain_product2_t &other) override {
    m_product |= other.m_product;
  }

  domain_product2_t operator|(const domain_product2_t &other) const override {
    return domain_product2_t(m_product | other.m_product);
  }

  domain_product2_t operator&(const domain_product2_t &other) const override {
    return domain_product2_t(m_product & other.m_product);
  }

  domain_product2_t operator||(const domain_product2_t &other) const override {
    return domain_product2_t(m_product || other.m_product);
  }

  domain_product2_t widening_thresholds(
      const domain_product2_t &other,
      const iterators::thresholds<number_t> &ts) const override {
    bool apply_reduction = false;
    return domain_product2_t(basic_domain_product2_t(
        std::move(m_product.first().widening_thresholds(
            other.m_product.first(), ts)),
        std::move(m_product.second().widening_thresholds(
            other.m_product.second(), ts)),
        std::move(apply_reduction)));
  }

  domain_product2_t operator&&(const domain_product2_t &other) const override {
    return domain_product2_t(m_product && other.m_product);
  }

  void assign(const variable_t &x, const linear_expression_t &e) override {
    m_product.first().assign(x, e);
    m_product.second().assign(x, e);
  }

  void apply(arith_operation_t op, const variable_t &x, const variable_t &y,
             const variable_t &z) override {
    m_product.first().apply(op, x, y, z);
    m_product.second().apply(op, x, y, z);
    reduce();
  }

  void apply(arith_operation_t op, const variable_t &x, const variable_t &y,
             Number k) override {
    m_product.first().apply(op, x, y, k);
    m_product.second().apply(op, x, y, k);
    reduce();
  }

  void select(const variable_t &lhs, const linear_constraint_t &cond,
	      const linear_expression_t &e1,  const linear_expression_t &e2) override {
    m_product.first().select(lhs, cond, e1, e2);
    m_product.second().select(lhs, cond, e1, e2);
    reduce();
  }

  void backward_assign(const variable_t &x, const linear_expression_t &e,
                       const domain_product2_t &invariant) override {
    m_product.first().backward_assign(x, e, invariant.first());
    m_product.second().backward_assign(x, e, invariant.second());
    reduce();
  }

  void backward_apply(arith_operation_t op, const variable_t &x,
                      const variable_t &y, Number k,
                      const domain_product2_t &invariant) override {
    m_product.first().backward_apply(op, x, y, k, invariant.first());
    m_product.second().backward_apply(op, x, y, k, invariant.second());
    reduce();
  }

  void backward_apply(arith_operation_t op, const variable_t &x,
                      const variable_t &y, const variable_t &z,
                      const domain_product2_t &invariant) override {
    m_product.first().backward_apply(op, x, y, z, invariant.first());
    m_product.second().backward_apply(op, x, y, z, invariant.second());
    reduce();
  }

  void operator+=(const linear_constraint_system_t &csts) override {
    m_product.first() += csts;
    m_product.second() += csts;
    reduce();
  }

  void operator-=(const variable_t &v) override {
    m_product.first() -= v;
    m_product.second() -= v;
  }

  // cast operators

  void apply(int_conv_operation_t op, const variable_t &dst,
             const variable_t &src) override {
    m_product.first().apply(op, dst, src);
    m_product.second().apply(op, dst, src);
    reduce();
  }

  // bitwise operators

  void apply(bitwise_operation_t op, const variable_t &x, const variable_t &y,
             const variable_t &z) override {
    m_product.first().apply(op, x, y, z);
    m_product.second().apply(op, x, y, z);
    reduce();
  }

  void apply(bitwise_operation_t op, const variable_t &x, const variable_t &y,
             Number k) override {
    m_product.first().apply(op, x, y, k);
    m_product.second().apply(op, x, y, k);
    reduce();
  }

  // array operators

  virtual void array_init(const variable_t &a,
                          const linear_expression_t &elem_size,
                          const linear_expression_t &lb_idx,
                          const linear_expression_t &ub_idx,
                          const linear_expression_t &val) override {
    m_product.first().array_init(a, elem_size, lb_idx, ub_idx, val);
    m_product.second().array_init(a, elem_size, lb_idx, ub_idx, val);
    reduce();
  }

  virtual void array_load(const variable_t &lhs, const variable_t &a,
                          const linear_expression_t &elem_size,
                          const linear_expression_t &i) override {

    m_product.first().array_load(lhs, a, elem_size, i);
    m_product.second().array_load(lhs, a, elem_size, i);
    reduce();
  }

  virtual void array_store(const variable_t &a,
                           const linear_expression_t &elem_size,
                           const linear_expression_t &i,
                           const linear_expression_t &val,
                           bool is_strong_update) override {
    m_product.first().array_store(a, elem_size, i, val, is_strong_update);
    m_product.second().array_store(a, elem_size, i, val, is_strong_update);
    reduce();
  }

  virtual void array_store_range(const variable_t &a,
                                 const linear_expression_t &elem_size,
                                 const linear_expression_t &i,
                                 const linear_expression_t &j,
                                 const linear_expression_t &val) override {
    m_product.first().array_store_range(a, elem_size, i, j, val);
    m_product.second().array_store_range(a, elem_size, i, j, val);
    reduce();
  }

  virtual void array_assign(const variable_t &lhs,
                            const variable_t &rhs) override {
    m_product.first().array_assign(lhs, rhs);
    m_product.second().array_assign(lhs, rhs);
    reduce();
  }

  // backward array operations

  virtual void
  backward_array_init(const variable_t &a, const linear_expression_t &elem_size,
                      const linear_expression_t &lb_idx,
                      const linear_expression_t &ub_idx,
                      const linear_expression_t &val,
                      const domain_product2_t &invariant) override {
    m_product.first().backward_array_init(a, elem_size, lb_idx, ub_idx,
                                               val, invariant.first());
    m_product.second().backward_array_init(a, elem_size, lb_idx, ub_idx,
                                                val, invariant.second());
    reduce();
  }

  virtual void
  backward_array_load(const variable_t &lhs, const variable_t &a,
                      const linear_expression_t &elem_size,
                      const linear_expression_t &i,
                      const domain_product2_t &invariant) override {

    m_product.first().backward_array_load(lhs, a, elem_size, i,
                                               invariant.first());
    m_product.second().backward_array_load(lhs, a, elem_size, i,
                                                invariant.second());
    reduce();
  }

  virtual void backward_array_store(
      const variable_t &a, const linear_expression_t &elem_size,
      const linear_expression_t &i, const linear_expression_t &val,
      bool is_strong_update, const domain_product2_t &invariant) override {
    m_product.first().backward_array_store(
        a, elem_size, i, val, is_strong_update, invariant.first());
    m_product.second().backward_array_store(
        a, elem_size, i, val, is_strong_update, invariant.second());
    reduce();
  }

  virtual void backward_array_store_range(
      const variable_t &a, const linear_expression_t &elem_size,
      const linear_expression_t &i, const linear_expression_t &j,
      const linear_expression_t &val,
      const domain_product2_t &invariant) override {
    m_product.first().backward_array_store_range(a, elem_size, i, j, val,
                                                      invariant.first());
    m_product.second().backward_array_store_range(a, elem_size, i, j, val,
                                                       invariant.second());
    reduce();
  }

  virtual void
  backward_array_assign(const variable_t &lhs, const variable_t &rhs,
                        const domain_product2_t &invariant) override {
    m_product.first().backward_array_assign(lhs, rhs, invariant.first());
    m_product.second().backward_array_assign(lhs, rhs, invariant.second());
    reduce();
  }

  // region/reference operators
  virtual void region_init(const variable_t &reg) override {
    m_product.first().region_init(reg);
    m_product.second().region_init(reg);
    reduce();
  }

  virtual void region_copy(const variable_t &lhs_reg,
                           const variable_t &rhs_reg) override {
    m_product.first().region_copy(lhs_reg, rhs_reg);
    m_product.second().region_copy(lhs_reg, rhs_reg);
    reduce();
  }

  virtual void region_cast(const variable_t &src_reg,
                           const variable_t &dst_reg) override {
    m_product.first().region_cast(src_reg, dst_reg);
    m_product.second().region_cast(src_reg, dst_reg);
    reduce();
  }

  virtual void ref_make(const variable_t &ref, const variable_t &reg,
			const variable_or_constant_t &size,
			const allocation_site &as) override {
    m_product.first().ref_make(ref, reg, size, as);
    m_product.second().ref_make(ref, reg, size, as);
    reduce();
  }

  virtual void ref_free(const variable_t &reg, const variable_t &ref) override {
    m_product.first().ref_free(reg, ref);
    m_product.second().ref_free(reg, ref);
    reduce();
  }

  virtual void ref_load(const variable_t &ref, const variable_t &reg,
                        const variable_t &res) override {
    m_product.first().ref_load(ref, reg, res);
    m_product.second().ref_load(ref, reg, res);
    reduce();
  }

  virtual void ref_store(const variable_t &ref, const variable_t &reg,
                         const variable_or_constant_t &val) override {
    m_product.first().ref_store(ref, reg, val);
    m_product.second().ref_store(ref, reg, val);
    reduce();
  }

  virtual void ref_gep(const variable_t &ref1, const variable_t &reg1,
                       const variable_t &ref2, const variable_t &reg2,
                       const linear_expression_t &offset) override {
    m_product.first().ref_gep(ref1, reg1, ref2, reg2, offset);
    m_product.second().ref_gep(ref1, reg1, ref2, reg2, offset);
    reduce();
  }

  virtual void
  ref_load_from_array(const variable_t &lhs, const variable_t &ref,
                      const variable_t &region,
                      const linear_expression_t &index,
                      const linear_expression_t &elem_size) override {
    m_product.first().ref_load_from_array(lhs, ref, region, index,
                                               elem_size);
    m_product.second().ref_load_from_array(lhs, ref, region, index,
                                                elem_size);
    reduce();
  }

  virtual void ref_store_to_array(const variable_t &ref,
                                  const variable_t &region,
                                  const linear_expression_t &index,
                                  const linear_expression_t &elem_size,
                                  const linear_expression_t &val) override {
    m_product.first().ref_store_to_array(ref, region, index, elem_size,
                                              val);
    m_product.second().ref_store_to_array(ref, region, index, elem_size,
                                               val);
    reduce();
  }

  virtual void ref_assume(const reference_constraint_t &cst) override {
    m_product.first().ref_assume(cst);
    m_product.second().ref_assume(cst);
    reduce();
  }

  void ref_to_int(const variable_t &reg, const variable_t &ref_var,
                  const variable_t &int_var) override {
    m_product.first().ref_to_int(reg, ref_var, int_var);
    m_product.second().ref_to_int(reg, ref_var, int_var);
    reduce();
  }

  void int_to_ref(const variable_t &int_var, const variable_t &reg,
                  const variable_t &ref_var) override {
    m_product.first().int_to_ref(int_var, reg, ref_var);
    m_product.second().int_to_ref(int_var, reg, ref_var);
    reduce();
  }
  void select_ref(const variable_t &lhs_ref, const variable_t &lhs_rgn,
		  const variable_t &cond,
		  const variable_or_constant_t &ref1,
		  const boost::optional<variable_t> &rgn1,
		  const variable_or_constant_t &ref2,
		  const boost::optional<variable_t> &rgn2) override {
    m_product.first().select_ref(lhs_ref, lhs_rgn, cond, ref1, rgn1, ref2, rgn2);
    m_product.second().select_ref(lhs_ref, lhs_rgn, cond, ref1, rgn1, ref2, rgn2);
    reduce();
  }

  boolean_value is_null_ref(const variable_t &ref) override {
    return m_product.first().is_null_ref(ref) & m_product.second().is_null_ref(ref);
  }

  bool get_allocation_sites(const variable_t &ref,
			    std::vector<allocation_site> &out) override {
    std::vector<allocation_site> s1, s2;
    bool b1 = m_product.first().get_allocation_sites(ref, s1);
    bool b2 = m_product.first().get_allocation_sites(ref, s2);
    if (b1 && b2) {
      std::sort(s1.begin(), s1.end());
      std::sort(s2.begin(), s2.end());
      std::set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(),
			    std::back_inserter(out));
      return true;
    } else if (b1) {
      out.assign(s1.begin(), s1.end());
      return true;
    } else if (b2) {
      out.assign(s2.begin(), s2.end());
      return true;
    }
    return false;
  }

  bool get_tags(const variable_t &rgn, const variable_t &ref,
		std::vector<uint64_t> &out) override {
    std::vector<uint64_t> s1, s2;
    bool b1 = m_product.first().get_tags(rgn, ref, s1);
    bool b2 = m_product.first().get_tags(rgn, ref, s2);
    if (b1 && b2) {
      std::sort(s1.begin(), s1.end());
      std::sort(s2.begin(), s2.end());
      std::set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(),
			    std::back_inserter(out));
      return true;
    } else if (b1) {
      out.assign(s1.begin(), s1.end());
      return true;
    } else if (b2) {
      out.assign(s2.begin(), s2.end());
      return true;
    }
    return false;
  }

  // boolean operators
  virtual void assign_bool_cst(const variable_t &lhs,
                               const linear_constraint_t &rhs) override {
    m_product.first().assign_bool_cst(lhs, rhs);
    m_product.second().assign_bool_cst(lhs, rhs);
    reduce();
  }

  virtual void assign_bool_ref_cst(const variable_t &lhs,
                                   const reference_constraint_t &rhs) override {
    m_product.first().assign_bool_ref_cst(lhs, rhs);
    m_product.second().assign_bool_ref_cst(lhs, rhs);
    reduce();
  }

  virtual void assign_bool_var(const variable_t &lhs, const variable_t &rhs,
                               bool is_not_rhs) override {
    m_product.first().assign_bool_var(lhs, rhs, is_not_rhs);
    m_product.second().assign_bool_var(lhs, rhs, is_not_rhs);
    reduce();
  }

  virtual void apply_binary_bool(bool_operation_t op, const variable_t &x,
                                 const variable_t &y,
                                 const variable_t &z) override {
    m_product.first().apply_binary_bool(op, x, y, z);
    m_product.second().apply_binary_bool(op, x, y, z);
    reduce();
  }

  virtual void assume_bool(const variable_t &v, bool is_negated) override {
    m_product.first().assume_bool(v, is_negated);
    m_product.second().assume_bool(v, is_negated);
    reduce();
  }

  virtual void select_bool(const variable_t &lhs, const variable_t &cond,
			   const variable_t &b1, const variable_t &b2) override {
    m_product.first().select_bool(lhs, cond, b1, b2);
    m_product.second().select_bool(lhs, cond, b1, b2);
    reduce();
  }

  // backward boolean operators
  virtual void backward_assign_bool_cst(const variable_t &lhs,
                                        const linear_constraint_t &rhs,
                                        const domain_product2_t &inv) override {
    m_product.first().backward_assign_bool_cst(lhs, rhs, inv.first());
    m_product.second().backward_assign_bool_cst(lhs, rhs, inv.second());
    reduce();
  }

  virtual void
  backward_assign_bool_ref_cst(const variable_t &lhs,
                               const reference_constraint_t &rhs,
                               const domain_product2_t &inv) override {
    m_product.first().backward_assign_bool_ref_cst(lhs, rhs, inv.first());
    m_product.second().backward_assign_bool_ref_cst(lhs, rhs,
                                                         inv.second());
    reduce();
  }

  virtual void backward_assign_bool_var(const variable_t &lhs,
                                        const variable_t &rhs, bool is_not_rhs,
                                        const domain_product2_t &inv) override {
    m_product.first().backward_assign_bool_var(lhs, rhs, is_not_rhs,
                                                    inv.first());
    m_product.second().backward_assign_bool_var(lhs, rhs, is_not_rhs,
                                                     inv.second());
    reduce();
  }

  virtual void
  backward_apply_binary_bool(bool_operation_t op, const variable_t &x,
                             const variable_t &y, const variable_t &z,
                             const domain_product2_t &inv) override {
    m_product.first().backward_apply_binary_bool(op, x, y, z, inv.first());
    m_product.second().backward_apply_binary_bool(op, x, y, z,
                                                       inv.second());
    reduce();
  }

  virtual void forget(const variable_vector_t &variables) override {
    m_product.first().forget(variables);
    m_product.second().forget(variables);
  }

  virtual void project(const variable_vector_t &variables) override {
    m_product.first().project(variables);
    m_product.second().project(variables);
  }

  virtual void expand(const variable_t &var,
                      const variable_t &new_var) override {
    m_product.first().expand(var, new_var);
    m_product.second().expand(var, new_var);
  }

  virtual void normalize() override {
    m_product.first().normalize();
    m_product.second().normalize();
  }

  virtual void minimize() override {
    m_product.first().minimize();
    m_product.second().minimize();
  }

  virtual interval_t operator[](const variable_t &v) override {
    return m_product.first()[v] & m_product.second()[v];
  }

  virtual linear_constraint_system_t
  to_linear_constraint_system() const override {
    linear_constraint_system_t csts;
    // XXX: We might add redundant constraints.
    csts += m_product.first().to_linear_constraint_system();
    csts += m_product.second().to_linear_constraint_system();
    return csts;
  }

  virtual disjunctive_linear_constraint_system_t
  to_disjunctive_linear_constraint_system() const override {
    disjunctive_linear_constraint_system_t csts;
    // XXX: We might add redundant constraints.
    csts += m_product.first().to_disjunctive_linear_constraint_system();
    csts += m_product.second().to_disjunctive_linear_constraint_system();
    return csts;
  }

  virtual void rename(const variable_vector_t &from,
                      const variable_vector_t &to) override {
    m_product.first().rename(from, to);
    m_product.second().rename(from, to);
  }

  /* begin intrinsics operations */
  void intrinsic(std::string name,
		 const variable_or_constant_vector_t &inputs,
                 const variable_vector_t &outputs) override {
    m_product.first().intrinsic(name, inputs, outputs);
    m_product.second().intrinsic(name, inputs, outputs);
  }

  void backward_intrinsic(std::string name,
			  const variable_or_constant_vector_t &inputs,
                          const variable_vector_t &outputs,
                          const domain_product2_t &invariant) override {
    m_product.first().backward_intrinsic(name, inputs, outputs,
					 invariant.first());
    m_product.second().backward_intrinsic(name, inputs, outputs,
					  invariant.second());
  }
  /* end intrinsics operations */

  void write(crab::crab_os &o) const override { m_product.write(o); }

  std::string domain_name() const override {
    return m_product.domain_name();
  }

}; // class domain_product2

namespace reduced_product_impl {
class default_params {
public:
  enum { left_propagate_equalities = 1 };
  enum { right_propagate_equalities = 1 };
  enum { left_propagate_inequalities = 1 };
  enum { right_propagate_inequalities = 1 };
  enum { left_propagate_intervals = 1 };
  enum { right_propagate_intervals = 1 };
  enum { disable_reduction = 0 };
  enum { apply_reduction_only_add_constraint = 0 };
};

class term_dbm_params {
public:
  enum { left_propagate_equalities = 1 };
  enum { right_propagate_equalities = 1 };
  enum { left_propagate_inequalities = 0 };
  enum { right_propagate_inequalities = 0 };
  enum { left_propagate_intervals = 0 };
  enum { right_propagate_intervals = 0 };
  enum { disable_reduction = 0 };
  enum { apply_reduction_only_add_constraint = 1 };
};
} // namespace reduced_product_impl

// This domain is similar to domain_product2 but it combines two
// numerical domains and it defines a more precise, customizable
// reduction operation.
template <typename Domain1, typename Domain2,
          class Params = reduced_product_impl::default_params>
class reduced_numerical_domain_product2 final
    : public abstract_domain_api<
          reduced_numerical_domain_product2<Domain1, Domain2, Params>> {

public:
  using reduced_numerical_domain_product2_t =
      reduced_numerical_domain_product2<Domain1, Domain2, Params>;
  using abstract_domain_t =
      abstract_domain_api<reduced_numerical_domain_product2_t>;
  using typename abstract_domain_t::disjunctive_linear_constraint_system_t;
  using typename abstract_domain_t::interval_t;
  using typename abstract_domain_t::linear_constraint_system_t;
  using typename abstract_domain_t::linear_constraint_t;
  using typename abstract_domain_t::linear_expression_t;
  using typename abstract_domain_t::reference_constraint_t;
  using typename abstract_domain_t::variable_or_constant_t;
  using typename abstract_domain_t::variable_t;
  using typename abstract_domain_t::variable_vector_t;
  using typename abstract_domain_t::variable_or_constant_vector_t;
  using number_t = typename Domain1::number_t;
  using varname_t = typename Domain1::varname_t;

  static_assert(std::is_same<number_t, typename Domain2::number_t>::value,
                "Domain1 and Domain2 must have same type for number_t");
  static_assert(std::is_same<varname_t, typename Domain2::varname_t>::value,
                "Domain1 and Domain2 must have same type for varname_t");

private:
  using domain_product2_t =
      domain_product2<number_t, varname_t, Domain1, Domain2>;

  domain_product2_t m_product;

  reduced_numerical_domain_product2(const domain_product2_t &product)
      : m_product(product) {}

  linear_constraint_system_t to_linear_constraints(const variable_t &v,
                                                   interval_t i) const {
    linear_constraint_system_t csts;
    if (i.lb().is_finite() && i.ub().is_finite()) {
      auto lb = *(i.lb().number());
      auto ub = *(i.ub().number());
      if (lb == ub) {
        csts += (v == lb);
      } else {
        csts += (v >= lb);
        csts += (v <= ub);
      }
    } else if (i.lb().is_finite()) {
      auto lb = *(i.lb().number());
      csts += (v >= lb);
    } else if (i.ub().is_finite()) {
      auto ub = *(i.ub().number());
      csts += (v <= ub);
    }
    return csts;
  }

  void reduce_variable(const variable_t &v) {
    crab::CrabStats::count(domain_name() + ".count.reduce");
    crab::ScopedCrabStats __st__(domain_name() + ".reduce");

    if (!is_bottom() && !Params::disable_reduction) {

      // We just propagate from one domain to another.  We could
      // propagate in the other direction ... and repeat it
      // computing a fixpoint+narrowing of descending iterations.

      Domain1 &inv1 = m_product.first();
      Domain2 &inv2 = m_product.second();

      //////
      // propagate interval constraints between domains
      //////
      if (Params::left_propagate_intervals) {
        interval_t i1 = inv1[v];
        if (!i1.is_top())
          inv2 += to_linear_constraints(v, i1);
      }
      if (Params::right_propagate_intervals) {
        interval_t i2 = inv2[v];
        if (!i2.is_top())
          inv1 += to_linear_constraints(v, i2);
      }

      //////
      // propagate other constraints expressed by the domains
      //////
      if ((Params::left_propagate_equalities ||
           Params::left_propagate_inequalities) &&
          (Params::right_propagate_equalities ||
           Params::right_propagate_inequalities)) {
        linear_constraint_system_t csts1, csts2, filtered_csts1, filtered_csts2;
        bool propagate_only_equalities;

        propagate_only_equalities = !Params::left_propagate_inequalities;
        crab::domains::reduced_domain_traits<Domain1>::extract(
            inv1, v, csts1, propagate_only_equalities);

        propagate_only_equalities = !Params::right_propagate_inequalities;
        crab::domains::reduced_domain_traits<Domain2>::extract(
            inv2, v, csts2, propagate_only_equalities);

        // filter out those redundant constraints (i.e.,
        // constraints that the other domain already knows about)

        for (auto &c1 : csts1) {
          if (std::find_if(csts2.begin(), csts2.end(),
                           [c1](const linear_constraint_t &c2) {
                             return c2.equal(c1);
                           }) == csts2.end()) {
            filtered_csts1 += c1;
          }
        }

        {
          std::string k(domain_name() + ".count.reduce.equalities_from_" +
                        m_product.first().domain_name());
          crab::CrabStats::uset(k, crab::CrabStats::get(k) +
                                       filtered_csts1.size());
        }
        inv2 += filtered_csts1;

        for (auto &c2 : csts2) {
          if (std::find_if(csts1.begin(), csts1.end(),
                           [c2](const linear_constraint_t &c1) {
                             return c1.equal(c2);
                           }) == csts1.end()) {
            filtered_csts2 += c2;
          }
        }
        {
          std::string k(domain_name() + ".count.reduce.equalities_from_" +
                        m_product.second().domain_name());
          crab::CrabStats::uset(k, crab::CrabStats::get(k) + csts2.size());
        }
        inv1 += filtered_csts2;
      } else if (Params::left_propagate_equalities ||
                 Params::left_propagate_inequalities) {
        linear_constraint_system_t csts1;
        const bool propagate_only_equalities =
            !Params::left_propagate_inequalities;
        crab::domains::reduced_domain_traits<Domain1>::extract(
            inv1, v, csts1, propagate_only_equalities);
        std::string k(domain_name() + ".count.reduce.equalities_from_" +
                      m_product.first().domain_name());
        crab::CrabStats::uset(k, crab::CrabStats::get(k) + csts1.size());
        inv2 += csts1;
      } else if (Params::right_propagate_equalities ||
                 Params::right_propagate_inequalities) {
        linear_constraint_system_t csts2;
        const bool propagate_only_equalities =
            !Params::right_propagate_inequalities;
        crab::domains::reduced_domain_traits<Domain2>::extract(
            inv2, v, csts2, propagate_only_equalities);
        std::string k(domain_name() + ".count.reduce.equalities_from_" +
                      m_product.second().domain_name());
        crab::CrabStats::uset(k, crab::CrabStats::get(k) + csts2.size());
        inv1 += csts2;
      }
    }
  }

public:
  reduced_numerical_domain_product2_t make_top() const override {
    domain_product2_t dom_prod;
    return reduced_numerical_domain_product2_t(dom_prod.make_top());
  }

  reduced_numerical_domain_product2_t make_bottom() const override {
    domain_product2_t dom_prod;
    return reduced_numerical_domain_product2_t(dom_prod.make_bottom());
  }

  void set_to_top() override {
    domain_product2_t dom_prod;
    reduced_numerical_domain_product2_t abs(dom_prod.make_top());
    std::swap(*this, abs);
  }

  void set_to_bottom() override {
    domain_product2_t dom_prod;
    reduced_numerical_domain_product2_t abs(dom_prod.make_bottom());
    std::swap(*this, abs);
  }

  reduced_numerical_domain_product2() : m_product() {}

  reduced_numerical_domain_product2(
      const reduced_numerical_domain_product2_t &other)
    : m_product(other.m_product) {}

  reduced_numerical_domain_product2(
      reduced_numerical_domain_product2_t &&other)
    : m_product(std::move(other.m_product)) {}

  reduced_numerical_domain_product2_t &
  operator=(const reduced_numerical_domain_product2_t &other) {
    if (this != &other) {
      m_product = other.m_product;
    }
    return *this;
  }

  reduced_numerical_domain_product2_t &
  operator=(reduced_numerical_domain_product2_t &&other) {
    if (this != &other) {
      m_product = std::move(other.m_product);
    }
    return *this;
  }

  bool is_bottom() const override { return m_product.is_bottom(); }

  bool is_top() const override { return m_product.is_top(); }

  bool
  operator<=(const reduced_numerical_domain_product2_t &other) const override {
    return m_product <= other.m_product;
  }

  void operator|=(const reduced_numerical_domain_product2_t &other) override {
    CRAB_LOG("combined-domain", crab::outs()
                                    << "============ JOIN ==================";
             crab::outs() << *this << "\n----------------";
             crab::outs() << other << "\n----------------";);
    m_product |= other.m_product;
    CRAB_LOG("reduced-dom", crab::outs() << *this << "\n----------------\n";);
  }

  reduced_numerical_domain_product2_t
  operator|(const reduced_numerical_domain_product2_t &other) const override {
    reduced_numerical_domain_product2_t res(m_product | other.m_product);
    CRAB_LOG("combined-domain", crab::outs()
                                    << "============ JOIN ==================";
             crab::outs() << *this << "\n----------------";
             crab::outs() << other << "\n----------------";
             crab::outs() << res << "\n================\n");
    return res;
  }

  reduced_numerical_domain_product2_t
  operator&(const reduced_numerical_domain_product2_t &other) const override {
    reduced_numerical_domain_product2_t res(m_product & other.m_product);
    CRAB_LOG("combined-domain", crab::outs()
                                    << "============ MEET ==================";
             crab::outs() << *this << "\n----------------";
             crab::outs() << other << "\n----------------\n";);
    return res;
  }

  reduced_numerical_domain_product2_t
  operator||(const reduced_numerical_domain_product2_t &other) const override {
    reduced_numerical_domain_product2_t res(m_product || other.m_product);
    CRAB_LOG("combined-domain",
             crab::outs() << "============ WIDENING ==================";
             crab::outs() << *this << "\n----------------";
             crab::outs() << other << "\n----------------\n";);
    return res;
  }

  reduced_numerical_domain_product2_t widening_thresholds(
      const reduced_numerical_domain_product2_t &other,
      const iterators::thresholds<number_t> &ts) const override {
    reduced_numerical_domain_product2_t res(
        m_product.widening_thresholds(other.m_product, ts));
    CRAB_LOG("combined-domain",
             crab::outs() << "============ WIDENING ==================";
             crab::outs() << *this << "\n----------------";
             crab::outs() << other << "\n----------------\n";);
    return res;
  }

  reduced_numerical_domain_product2_t
  operator&&(const reduced_numerical_domain_product2_t &other) const override {
    reduced_numerical_domain_product2_t res(m_product && other.m_product);
    CRAB_LOG("combined-domain",
             crab::outs() << "============ NARROWING ==================";
             crab::outs() << *this << "\n----------------";
             crab::outs() << other << "\n----------------\n";);
    return res;
  }

  void set(const variable_t &v, interval_t x) {
    m_product.first().set(v, x);
    m_product.second().set(v, x);
  }

  interval_t operator[](const variable_t &v) override {
    return m_product.first()[v] & m_product.second()[v];
  }

  void operator+=(const linear_constraint_system_t &csts) override {
    m_product += csts;
    if (!is_bottom()) {
      for (auto const &cst : csts) {
	for (auto const &v : cst.variables()) {
	  reduce_variable(v);
	  if (is_bottom()) {
	    return;
	  }
	}
      }
    }
    CRAB_LOG("combined-domain", crab::outs() << "Added constraints " << csts
                                             << "=" << *this << "\n");
  }

  void operator-=(const variable_t &v) override { m_product -= v; }

  void assign(const variable_t &x, const linear_expression_t &e) override {
    m_product.assign(x, e);
    if (!Params::apply_reduction_only_add_constraint) {
      reduce_variable(x);
    }
    CRAB_LOG("combined-domain", crab::outs()
                                    << x << ":=" << e << "=" << *this << "\n");
  }

  void apply(arith_operation_t op, const variable_t &x, const variable_t &y,
             const variable_t &z) override {
    m_product.apply(op, x, y, z);
    if (!Params::apply_reduction_only_add_constraint) {
      reduce_variable(x);
    }
    CRAB_LOG("combined-domain",
             crab::outs() << x << ":=" << y << op << z << "=" << *this << "\n");
  }

  void apply(arith_operation_t op, const variable_t &x, const variable_t &y,
             number_t k) override {
    m_product.apply(op, x, y, k);
    if (!Params::apply_reduction_only_add_constraint) {
      reduce_variable(x);
    }
    CRAB_LOG("combined-domain",
             crab::outs() << x << ":=" << y << op << k << "=" << *this << "\n");
  }

  void backward_assign(
      const variable_t &x, const linear_expression_t &e,
      const reduced_numerical_domain_product2_t &invariant) override {
    m_product.backward_assign(x, e, invariant.m_product);
    if (!Params::apply_reduction_only_add_constraint) {
      // reduce the variables in the right-hand side
      for (auto const &v : e.variables())
        reduce_variable(v);
    }
  }

  void backward_apply(
      arith_operation_t op, const variable_t &x, const variable_t &y,
      number_t k,
      const reduced_numerical_domain_product2_t &invariant) override {
    m_product.backward_apply(op, x, y, k, invariant.m_product);
    if (!Params::apply_reduction_only_add_constraint) {
      // reduce the variables in the right-hand side
      reduce_variable(y);
    }
  }

  void backward_apply(
      arith_operation_t op, const variable_t &x, const variable_t &y,
      const variable_t &z,
      const reduced_numerical_domain_product2_t &invariant) override {
    m_product.backward_apply(op, x, y, z, invariant.m_product);
    if (!Params::apply_reduction_only_add_constraint) {
      // reduce the variables in the right-hand side
      reduce_variable(y);
      reduce_variable(z);
    }
  }

  // cast operators

  void apply(int_conv_operation_t op, const variable_t &dst,
             const variable_t &src) override {
    m_product.apply(op, dst, src);
    if (!Params::apply_reduction_only_add_constraint) {
      reduce_variable(dst);
    }
  }

  // bitwise operators

  void apply(bitwise_operation_t op, const variable_t &x, const variable_t &y,
             const variable_t &z) override {
    m_product.apply(op, x, y, z);
    if (!Params::apply_reduction_only_add_constraint) {
      reduce_variable(x);
    }
  }

  void apply(bitwise_operation_t op, const variable_t &x, const variable_t &y,
             number_t k) override {
    m_product.apply(op, x, y, k);
    if (!Params::apply_reduction_only_add_constraint) {
      reduce_variable(x);
    }
  }

  void select(const variable_t &lhs, const linear_constraint_t &cond,
	      const linear_expression_t &e1,  const linear_expression_t &e2) override {
    m_product.select(lhs, cond, e1, e2);
    if (!Params::apply_reduction_only_add_constraint) {
      reduce_variable(lhs);
    }
  }

  /// reduced_numerical_domain_product2 implements only standard
  /// abstract operations of a numerical domain so it is intended to be
  /// used as a leaf domain in the hierarchy of domains.
  BOOL_OPERATIONS_NOT_IMPLEMENTED(reduced_numerical_domain_product2_t)
  ARRAY_OPERATIONS_NOT_IMPLEMENTED(reduced_numerical_domain_product2_t)
  REGION_AND_REFERENCE_OPERATIONS_NOT_IMPLEMENTED(
      reduced_numerical_domain_product2_t)

  void rename(const variable_vector_t &from,
              const variable_vector_t &to) override {
    m_product.rename(from, to);
  }

  /* begin intrinsics operations */
  void intrinsic(std::string name,
		 const variable_or_constant_vector_t &inputs,
                 const variable_vector_t &outputs) override {
    m_product.intrinsic(name, inputs, outputs);
  }

  void backward_intrinsic(
      std::string name,
      const variable_or_constant_vector_t &inputs,
      const variable_vector_t &outputs,
      const reduced_numerical_domain_product2_t &invariant) override {
    m_product.backward_intrinsic(name, inputs, outputs,
				 invariant.m_product);
  }
  /* end intrinsics operations */

  void forget(const variable_vector_t &variables) override {
    m_product.forget(variables);
  }

  void project(const variable_vector_t &variables) override {
    m_product.project(variables);
  }

  void expand(const variable_t &var, const variable_t &new_var) override {
    m_product.expand(var, new_var);
  }

  void normalize() override { m_product.normalize(); }

  void minimize() override { m_product.minimize(); }

  void write(crab_os &o) const override { m_product.write(o); }

  linear_constraint_system_t to_linear_constraint_system() const override {
    return m_product.to_linear_constraint_system();
  }

  disjunctive_linear_constraint_system_t
  to_disjunctive_linear_constraint_system() const override {
    return m_product.to_disjunctive_linear_constraint_system();
  }

  std::string domain_name() const override {
    const Domain1 &dom1 = m_product.first();
    const Domain2 &dom2 = m_product.second();

    std::string name =
        "ReducedProduct(" + dom1.domain_name() + "," + dom2.domain_name() + ")";
    return name;
  }

}; // class reduced_numerical_domain_product2

template <typename Number, typename VariableName, typename Domain1,
          typename Domain2>
struct abstract_domain_traits<
    domain_product2<Number, VariableName, Domain1, Domain2>> {
  using number_t = Number;
  using varname_t = VariableName;
};

template <typename Domain1, typename Domain2, class Params>
struct abstract_domain_traits<
    reduced_numerical_domain_product2<Domain1, Domain2, Params>> {
  using number_t = typename Domain1::number_t;
  using varname_t = typename Domain1::varname_t;
};

} // end namespace domains
} // namespace crab


// SPDX-License-Identifier: Apache-2.0
//
// Copyright 2015 Conrad Sanderson (http://conradsanderson.id.au)
// Copyright 2015 National ICT Australia (NICTA)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

#include <armadillo>
#include "catch.hpp"

using namespace arma;

TEST_CASE("fn_cond_1")
  {
  mat A =
    {
    { -0.78838,  0.69298,  0.41084,  0.90142 },
    {  0.49345, -0.12020,  0.78987,  0.53124 },
    {  0.73573,  0.52104, -0.22263,  0.40163 }
    };

  REQUIRE( cond(A)                      == Approx(1.7455) );
  REQUIRE( cond(A(span::all,span::all)) == Approx(1.7455) );
  }

TEST_CASE("fn_cond_2")
  {
  mat A = zeros<mat>(5,6);

  REQUIRE( is_finite(cond(A)) == false );
  }


/**  \file specialtypes.hpp \brief Boost vector and matrix types */
/*
-------------------------------------------------------------------------
   This file is part of BayesOpt, an efficient C++ library for
   Bayesian optimization.

   Copyright (C) 2011-2015 Ruben Martinez-Cantin <rmcantin@unizar.es>

   BayesOpt is free software: you can redistribute it and/or modify it
   under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   BayesOpt is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with BayesOpt.  If not, see <http://www.gnu.org/licenses/>.
------------------------------------------------------------------------
*/

#ifndef __SPECIALTYPES_HPP__
#define __SPECIALTYPES_HPP__

#include <boost/numeric/ublas/matrix.hpp>
//#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/io.hpp>

typedef boost::numeric::ublas::vector<int>                      vectori;
typedef boost::numeric::ublas::vector<double>                   vectord;
typedef boost::numeric::ublas::zero_vector<double>             zvectord;
typedef boost::numeric::ublas::scalar_vector<double>           svectord;
typedef boost::numeric::ublas::matrix<double>                   matrixd;
typedef boost::numeric::ublas::zero_matrix<double>             zmatrixd;

typedef std::vector<vectord>                                   vecOfvec;

// Surprisingly, this is the most efficient version of a growing
// matrix for uBlas, but I leave here the old experiments because it
// might change in the future.
typedef boost::numeric::ublas::matrix<double>                 covMatrix;

// typedef boost::numeric::ublas::bounded_matrix<double, MAX_ITERATIONS,
//                                            MAX_ITERATIONS> covMatrix;

// typedef boost::numeric::ublas::symmetric_matrix<double,lower,row_major,
//                          bounded_array<double,90000> >       covMatrix;

#endif