/*
 Copyright (C) 2017 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

/*! \file qle/termstructures/swaptionvolcubewithatm.hpp
    \brief Wrapper class for a SwaptionVolatilityCube that easily and efficiently exposes ATM vols.
    \ingroup termstructures
*/

#ifndef quantext_swaptionvolcubewithatm_hpp
#define quantext_swaptionvolcubewithatm_hpp

#include <ql/termstructures/volatility/swaption/swaptionvolcube.hpp>

#include <boost/shared_ptr.hpp>

namespace QuantExt {
using namespace QuantLib;

//! Wrapper class for a SwaptionVolatilityCube that easily and efficiently exposes ATM vols.
/*! This class implements SwaptionVolatilityStructure and takes a cube as an input. If asked for
    a volatility with strike=Null<Real>() it will return the ATM vol by asking the ATM surface directly.
    If asked for any other strike it will pass it on to the cube.

    There is no calculation of ATM in this class.

    \ingroup termstructures
 */
class SwaptionVolCubeWithATM : public SwaptionVolatilityStructure {
public:
    //! Constructor. This is a floating term structure (settlement days is zero) to match
    //! QuantLib::SwaptionVolatilityCube
    SwaptionVolCubeWithATM(const boost::shared_ptr<SwaptionVolatilityCube>& cube)
        : SwaptionVolatilityStructure(0, cube->calendar(), cube->businessDayConvention(), cube->dayCounter()),
          cube_(cube) {
        enableExtrapolation(cube_->allowsExtrapolation());
        registerWith(cube);
    }

    //! \name TermStructure interface
    //@{
    DayCounter dayCounter() const { return cube_->dayCounter(); }
    Date maxDate() const { return cube_->maxDate(); }
    Time maxTime() const { return cube_->maxTime(); }
    const Date& referenceDate() const { return cube_->referenceDate(); }
    Calendar calendar() const { return cube_->calendar(); }
    Natural settlementDays() const { return cube_->settlementDays(); }
    //! \name VolatilityTermStructure interface
    //@{
    Rate minStrike() const { return cube_->minStrike(); }
    Rate maxStrike() const { return cube_->maxStrike(); }
    //@}
    //! \name SwaptionVolatilityStructure interface
    //@{
    const Period& maxSwapTenor() const { return cube_->maxSwapTenor(); }
    VolatilityType volatilityType() const { return cube_->volatilityType(); }
    //@}

    boost::shared_ptr<SwaptionVolatilityCube> cube() const { return cube_; }

protected:
    // Nothing to do here, just ask the cube
    boost::shared_ptr<SmileSection> smileSectionImpl(Time optionTime, Time swapLength) const {
        return cube_->smileSection(optionTime, swapLength);
    }

    // Here we check if strike is Null<Real>() and ask the ATM surface if so.
    Volatility volatilityImpl(Time optionTime, Time swapLength, Rate strike) const {
        if (strike == Null<Real>()) {
            return cube_->atmVol()->volatility(optionTime, swapLength, 0.0);
        } else {
            return cube_->volatility(optionTime, swapLength, strike);
        }
    }

private:
    boost::shared_ptr<SwaptionVolatilityCube> cube_;
};

} // namespace QuantExt

#endif


// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE slice_train_0

#include <algorithm>
#include <boost/test/unit_test.hpp>
#include <map>
#include <random>
#include <vector>
#include <popart/builder.hpp>
#include <popart/error.hpp>
#include <popart/ndarraywrapper.hpp>
#include <popart/session.hpp>
#include <popart/sgd.hpp>
#include <popart/testdevice.hpp>

BOOST_AUTO_TEST_CASE(SliceTrain0) {

  // model to train:
  //
  // weights
  //   |
  //   |---- slice ---=| == add ( weight slice 0, data) --------- l1
  //   |               |                                             \.
  //   |---- slice ---=| == add (weight slice 1, data)   ------- l1 -- final
  //                   |                                              /.
  //                   | == add (weight slice 1, weight slice 2)  -- l1
  // data ------------=|
  //
  //
  // data has shape   10 x 4.
  // weight has shape 25 x 4 if offset is false, and
  //                  25 x 5 if offset is true.
  //
  // The slices taken of weights have the data shape, i.e. 10 x 4.
  //
  // Weights when offset = true are,  ( 0 and 1 are the  2 sliced regions)
  //
  // . . . .
  // . . . .
  // . . . .
  // . . . .
  // . . . .
  // 0 0 0 0
  // 0 0 0 0
  // 0 0 0 0
  // 0 0 0 0
  //   |||
  // 1 1 1 1
  // 1 1 1 1
  // 1 1 1 1
  // 1 1 1 1
  //
  //
  // and when offset = false:
  //
  // . . . . .
  // . . . . .
  // . . . . .
  // . . . . .
  // . . . . .
  // . 0 0 0 0
  // . 0 0 0 0
  //   |||
  //   0 0 0 0
  //   0 0 0 0
  // 1 1 1 1 .
  // 1 1 1 1 .
  //   |||
  // 1 1 1 1 .
  // 1 1 1 1 .
  //
  // When offset is false, we expect the PadSum transform to be inapplicable.
  //

  /**
   * \param inplace Is the Inplacing Pattern enabled?
   *
   * \param padsum Is the PadSum Pattern, which converts sums of Pad outputs
   *               into concats where possible, enabled?
   *
   * \param offset See diagram above.
   * */
  auto getFinalWeights = [](bool inplace, bool padsum, bool offset) {
    using namespace popart;

    std::cout << "\n\n\nwith inplace = " << inplace << " padsum = " << padsum
              << " offset = " << offset << std::endl;

    std::map<std::string, std::pair<float, bool>> optParams;
    optParams.insert({"defaultLearningRate", {1.0f, true}});
    auto opt0 = SGD(optParams);

    // names of  weights used in model
    std::string w0name = "__w0__";

    auto builder = Builder::create();
    auto aiOnnx  = builder->aiOnnxOpset9();

    // sample shape: [sampleDim0, sampleDim1]
    int64_t sampleDim0 = 10;
    int64_t sampleDim1 = 4;
    std::vector<int64_t> sampleShape{sampleDim0, sampleDim1};
    TensorInfo sampleInfo{"FLOAT", sampleShape};

    // weigh shape:  [weightDim0, weightDim1]
    int64_t weightDim0 = 2 * sampleDim0 + 5;
    int64_t weightDim1 = sampleDim1 + 1 * (offset == true);
    auto nWeightVals   = weightDim0 * weightDim1;
    std::vector<int64_t> weightShape{weightDim0, weightDim1};
    TensorInfo weightInfo("FLOAT", weightShape);

    // a step of samples: [stepSize, batchSize, sampleDim0, sampleDim1]
    int64_t batchSize = 3;
    int64_t stepSize  = 1;

    std::vector<int64_t> batchShape{batchSize, sampleDim0, sampleDim1};
    TensorInfo batchInfo{"FLOAT", batchShape};

    std::vector<int64_t> stepDataShape{
        stepSize, batchSize, sampleDim0, sampleDim1};
    TensorInfo stepDataInfo{"FLOAT", stepDataShape};

    // Insert input Tensor into ONNX model
    auto input0 = builder->addInputTensor(batchInfo, "0tupni");

    // Note on randomness : generators are guaranteed to be platform
    // independent, so this will generate the same random values everywhere.
    // [0,1,2,3,4,5] - 2.5
    std::mt19937 gen(1011);
    auto getVal = [&gen]() { return static_cast<float>(gen() % 6) - 2.5; };

    // the initial weight values
    std::vector<float> weight0(nWeightVals);

    for (auto &x : weight0) {
      x = getVal();
    }

    // for reading back buffer for weight0
    std::vector<float> rb0(nWeightVals, -777.0f);

    // insert initialized weight Tensor into the ONNX model
    ConstVoidData cvd0({weight0.data(), weightInfo});
    auto w0Id = builder->addInitializedInputTensor(cvd0, w0name);
    WeightsIO weightsRead;
    weightsRead.insert(w0Id, {rb0.data(), weightInfo});

    // slice the weights into 2 parts, each the shape of a sample:
    auto slice0 = aiOnnx.slice(
        {w0Id}, {2 * sampleDim0, sampleDim1 + offset}, {sampleDim0, offset});
    auto slice1 = aiOnnx.slice({w0Id}, {sampleDim0, sampleDim1}, {0, 0});

    float lamb0 = 6.0;
    float lamb1 = 12.0;
    float lamb2 = 24.0;

    // input + weight slices, and sum of weight slices.
    // all with an l1 loss, them summed again:
    auto add0 = aiOnnx.add({slice0, input0});

    auto add1 = aiOnnx.add({slice1, add0});
    auto l1   = builder->aiGraphcoreOpset1().l1loss({add1}, lamb1);

    auto l0   = builder->aiGraphcoreOpset1().l1loss({add0}, lamb0);
    auto add2 = aiOnnx.add({slice0, slice1});
    auto l2   = builder->aiGraphcoreOpset1().l1loss({add2}, lamb2);

    auto lSum = aiOnnx.sum({l0, l1, l2});
    builder->addOutputTensor(lSum);

    auto proto    = builder->getModelProto();
    auto dataFlow = DataFlow(stepSize);

    SessionOptions userOptions;

    auto device = createTestDevice(TEST_TARGET, 1);

    auto session = popart::TrainingSession::createFromOnnxModel(
        proto,
        dataFlow,
        lSum,
        opt0,
        device,
        InputShapeInfo(),
        SessionOptions(),
        popart::Patterns(PatternsLevel::Default)
            .enableInPlace(inplace)
            .enablePattern("PadSum", true));

    session->prepareDevice();

    /*
    const auto sched = session->getIr().getOpSchedule({},
        RequireOptimalSchedule::Yes);
    for (auto x : sched) {
      std::cout << "\n   " << x->str();
    }
    std::cout << std::endl;
    */
    std::vector<float> v_input_x(stepDataInfo.nelms());
    for (auto &x : v_input_x) {
      x = getVal();
    }

    popart::NDArrayWrapper<float> input_x_wrapper(v_input_x.data(),
                                                  stepDataInfo);

    std::map<popart::TensorId, popart::IArray &> inputs = {
        {input0, input_x_wrapper}};

    popart::StepIO stepio(inputs, {});

    session->weightsFromHost();

    // run 1 with opt0
    session->run(stepio);
    session->run(stepio);

    // read final weights back
    session->weightsToHost();
    session->readWeights(weightsRead);

    return rb0;
  };

  //                                  inplace padsum offset
  const auto offset0 = getFinalWeights(true, true, true);
  const auto offset1 = getFinalWeights(true, false, true);
  const auto offset2 = getFinalWeights(false, false, true);
  const auto offset3 = getFinalWeights(false, true, true);

  if (offset0 != offset1 || offset0 != offset2 || offset0 != offset3) {
    throw popart::error("Offset cases do not all agree");
  }

  const auto nooffset0 = getFinalWeights(true, true, false);
  const auto nooffset1 = getFinalWeights(true, false, false);
  const auto nooffset2 = getFinalWeights(false, true, false);
  const auto nooffset3 = getFinalWeights(false, false, false);

  if (nooffset0 != nooffset1 || nooffset0 != nooffset2 ||
      nooffset0 != nooffset3) {
    throw popart::error("Offset cases do not all agree");
  }
}


// -----------------------------------------------------------------------------
// Fern © Geoneric
//
// This file is part of Geoneric Fern which is available under the terms of
// the GNU General Public License (GPL), version 2. If you do not want to
// be bound by the terms of the GPL, you may purchase a proprietary license
// from Geoneric (http://www.geoneric.eu/contact).
// -----------------------------------------------------------------------------
#define BOOST_TEST_MODULE fern algorithm core offset
#include <boost/test/unit_test.hpp>
#include "fern/feature/core/data_customization_point/array.h"
#include "fern/feature/core/data_customization_point/masked_array.h"
#include "fern/core/data_customization_point/scalar.h"
#include "fern/core/data_customization_point/point.h"
#include "fern/core/data_customization_point/vector.h"
#include "fern/algorithm/core/offset.h"
#include "fern/algorithm/core/test/test_utils.h"

namespace fa = fern::algorithm;

template<
    typename ExecutionPolicy>
void test_array_1d(
    ExecutionPolicy& execution_policy)
{
    size_t const nr_threads{fern::hardware_concurrency()};
    size_t const nr_elements{10 * nr_threads};
    std::vector<int> values(nr_elements);
    std::vector<int> result_we_want(nr_elements);
    std::vector<int> result_we_got(nr_elements);

    std::iota(values.begin(), values.end(), 0);

    {
        fern::Point<int, 1> offset{0};

        // 0, 1, ..., n - 1
        std::iota(result_we_want.begin(), result_we_want.end(), 0);
        std::fill(result_we_got.begin(), result_we_got.end(), -9);

        fa::core::offset(execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{2};

        // -9, -9, 0, 1, ..., n - 3
        std::iota(result_we_want.begin() + fern::get<0>(offset),
            result_we_want.end(), 0);
        std::fill(result_we_want.begin(), result_we_want.begin() +
            fern::get<0>(offset), -9);
        std::fill(result_we_got.begin(), result_we_got.end(), -9);

        fa::core::offset(execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{static_cast<int>(nr_elements) + 10};

        // -9, -9, ..., -9, -9
        std::fill(result_we_want.begin(), result_we_want.end(), -9);
        std::fill(result_we_got.begin(), result_we_got.end(), -9);

        fa::core::offset(execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{-2};

        // 2, 3, ..., -9, -9
        std::iota(result_we_want.begin(),
            result_we_want.end() + fern::get<0>(offset),
            std::abs(fern::get<0>(offset)));
        std::fill(result_we_want.end() + fern::get<0>(offset),
            result_we_want.end(), -9);
        std::fill(result_we_got.begin(), result_we_got.end(), -9);

        fa::core::offset(execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{-static_cast<int>(nr_elements + 10)};

        // -9, -9, ..., -9, -9
        std::fill(result_we_want.begin(), result_we_want.end(), -9);
        std::fill(result_we_got.begin(), result_we_got.end(), -9);

        fa::core::offset(execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }
}

BOOST_AUTO_TEST_CASE(array_1d_sequential)
{
    fa::SequentialExecutionPolicy sequential;

    test_array_1d(sequential);
    fa::ExecutionPolicy execution_policy{sequential};
    test_array_1d(execution_policy);
}

BOOST_AUTO_TEST_CASE(array_1d_parallel)
{
    fa::ParallelExecutionPolicy parallel;

    test_array_1d(parallel);
    fa::ExecutionPolicy execution_policy{parallel};
    test_array_1d(execution_policy);
}

template<
    typename ExecutionPolicy>
void test_array_1d_masked(
    ExecutionPolicy& execution_policy)
{
    size_t const nr_threads{fern::hardware_concurrency()};
    size_t const nr_elements{10 * nr_threads};
    fern::MaskedArray<int, 1> values(nr_elements);
    fern::MaskedArray<int, 1> result_we_want(nr_elements);
    fern::MaskedArray<int, 1> result_we_got(nr_elements);

    fa::InputNoDataPolicies<fa::DetectNoDataByValue<fern::Mask<1>>>
        input_no_data_policy{{values.mask(), true}};
    fa::MarkNoDataByValue<fern::Mask<1>> output_no_data_policy(
        result_we_got.mask(), true);

    std::iota(values.data(), values.data() + nr_elements, 0);
    values.mask()[1] = true;

    {
        fern::Point<int, 1> offset{0};

        // 0, -9, 2, ..., n - 1
        std::iota(result_we_want.data(), result_we_want.data() + nr_elements,
            0);
        result_we_want[1] = -9;
        result_we_want.mask()[1] = true;
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{2};

        // -9, -9, 0, -9, 1, ..., n - 3
        std::iota(result_we_want.data() + fern::get<0>(offset),
            result_we_want.data() + nr_elements, 0);
        std::fill(result_we_want.data(), result_we_want.data() +
            fern::get<0>(offset), -9);
        result_we_want.data()[fern::get<0>(offset) + 1] = -9;
        std::fill(result_we_want.mask().data(), result_we_want.mask().data() +
            fern::get<0>(offset), true);
        result_we_want.mask()[fern::get<0>(offset) + 1] = true;
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{static_cast<int>(nr_elements + 10)};

        // -9, -9, ..., -9, -9
        result_we_want.fill(-9);
        result_we_want.mask().fill(true);
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{-2};

        // 2, 3, ..., n - 1, -9, -9
        std::iota(result_we_want.data(), result_we_want.data() + nr_elements +
            fern::get<0>(offset), std::abs(fern::get<0>(offset)));
        std::fill(result_we_want.data() + nr_elements + fern::get<0>(offset),
            result_we_want.data() + nr_elements, -9);
        // false, false, ..., false, true, true
        std::fill(result_we_want.mask().data(), result_we_want.mask().data() +
            nr_elements + fern::get<0>(offset), false);
        std::fill(result_we_want.mask().data() + nr_elements +
            fern::get<0>(offset), result_we_want.mask().data() + nr_elements,
            true);
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{-static_cast<int>(nr_elements + 10)};

        // -9, -9, ..., -9, -9
        result_we_want.fill(-9);
        // true, true, ..., true, true
        result_we_want.mask().fill(true);
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }
}

BOOST_AUTO_TEST_CASE(array_1d_masked_sequential)
{
    fa::SequentialExecutionPolicy sequential;

    test_array_1d_masked(sequential);
    fa::ExecutionPolicy execution_policy{sequential};
    test_array_1d_masked(execution_policy);
}

BOOST_AUTO_TEST_CASE(array_1d_masked_parallel)
{
    fa::ParallelExecutionPolicy parallel;

    test_array_1d_masked(parallel);
    fa::ExecutionPolicy execution_policy{parallel};
    test_array_1d_masked(execution_policy);
}

template<
    typename ExecutionPolicy>
void test_array_1d_fill_value(
    ExecutionPolicy& execution_policy)
{
    size_t const nr_threads{fern::hardware_concurrency()};
    size_t const nr_elements{10 * nr_threads};
    std::vector<int> values(nr_elements);
    std::vector<int> result_we_want(nr_elements);
    std::vector<int> result_we_got(nr_elements);
    int const fill_value{5};

    std::iota(values.begin(), values.end(), 0);

    {
        fern::Point<int, 1> offset{0};

        // 0, 1, ..., n - 1
        std::iota(result_we_want.begin(), result_we_want.end(), 0);
        std::fill(result_we_got.begin(), result_we_got.end(), -9);

        fa::core::offset(execution_policy, values, offset, fill_value,
            result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{2};

        // 5, 5, 0, 1, ..., n - 3
        std::iota(result_we_want.begin() + fern::get<0>(offset),
            result_we_want.end(), 0);
        std::fill(result_we_want.begin(), result_we_want.begin() +
            fern::get<0>(offset), fill_value);
        std::fill(result_we_got.begin(), result_we_got.end(), -9);

        fa::core::offset(execution_policy, values, offset, fill_value,
            result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{static_cast<int>(nr_elements) + 10};

        // 5, 5, ..., 5, 5
        std::fill(result_we_want.begin(), result_we_want.end(), fill_value);
        std::fill(result_we_got.begin(), result_we_got.end(), -9);

        fa::core::offset(execution_policy, values, offset, fill_value,
            result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{-2};

        // 2, 3, ..., 5, 5
        std::iota(result_we_want.begin(),
            result_we_want.end() + fern::get<0>(offset),
            std::abs(fern::get<0>(offset)));
        std::fill(result_we_want.end() + fern::get<0>(offset),
            result_we_want.end(), fill_value);
        std::fill(result_we_got.begin(), result_we_got.end(), -9);

        fa::core::offset(execution_policy, values, offset, fill_value,
            result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{-static_cast<int>(nr_elements + 10)};

        // 5, 5, ..., 5, 5
        std::fill(result_we_want.begin(), result_we_want.end(), fill_value);
        std::fill(result_we_got.begin(), result_we_got.end(), -9);

        fa::core::offset(execution_policy, values, offset, fill_value,
            result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }
}

BOOST_AUTO_TEST_CASE(array_1d_fill_value_sequential)
{
    fa::SequentialExecutionPolicy sequential;

    test_array_1d_fill_value(sequential);
    fa::ExecutionPolicy execution_policy{sequential};
    test_array_1d_fill_value(execution_policy);
}

BOOST_AUTO_TEST_CASE(array_1d_fill_value_parallel)
{
    fa::ParallelExecutionPolicy parallel;

    test_array_1d_fill_value(parallel);
    fa::ExecutionPolicy execution_policy{parallel};
    test_array_1d_fill_value(execution_policy);
}

template<
    typename ExecutionPolicy>
void test_array_1d_fill_value_masked(
    ExecutionPolicy& execution_policy)
{
    size_t const nr_threads{fern::hardware_concurrency()};
    size_t const nr_elements{10 * nr_threads};
    fern::MaskedArray<int, 1> values(nr_elements);
    fern::MaskedArray<int, 1> result_we_want(nr_elements);
    fern::MaskedArray<int, 1> result_we_got(nr_elements);
    int const fill_value{5};

    fa::InputNoDataPolicies<fa::DetectNoDataByValue<fern::Mask<1>>>
        input_no_data_policy{{values.mask(), true}};
    fa::MarkNoDataByValue<fern::Mask<1>> output_no_data_policy(
        result_we_got.mask(), true);

    std::iota(values.data(), values.data() + nr_elements, 0);
    values.mask()[1] = true;

    {
        fern::Point<int, 1> offset{0};

        // 0, -9, 2, ..., n - 1
        std::iota(result_we_want.data(), result_we_want.data() + nr_elements,
            0);
        result_we_want.data()[1] = -9;
        result_we_want.mask()[1] = true;
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, fill_value, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{2};

        // 5, 5, 0, -9, 1, ..., n - 3
        std::iota(result_we_want.data() + fern::get<0>(offset),
            result_we_want.data() + nr_elements, 0);
        std::fill(result_we_want.data(), result_we_want.data() +
            fern::get<0>(offset), fill_value);
        result_we_want.data()[fern::get<0>(offset) + 1] = -9;
        // false, false, false, true, false, ..., false
        std::fill(result_we_want.mask().data(), result_we_want.mask().data() +
            fern::get<0>(offset), false);
        result_we_want.mask()[fern::get<0>(offset) + 1] = true;
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, fill_value, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{static_cast<int>(nr_elements + 10)};

        // 5, 5, ..., 5, 5
        result_we_want.fill(fill_value);
        // false, ..., false
        result_we_want.mask().fill(false);
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, fill_value, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{-2};

        // 2, 3, ..., n - 1, 5, 5
        std::iota(result_we_want.data(), result_we_want.data() + nr_elements +
            fern::get<0>(offset), std::abs(fern::get<0>(offset)));
        std::fill(result_we_want.data() + nr_elements + fern::get<0>(offset),
            result_we_want.data() + nr_elements, fill_value);
        // false, false, ..., false, false, false
        std::fill(result_we_want.mask().data(), result_we_want.mask().data() +
            nr_elements, false);
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, fill_value, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    {
        fern::Point<int, 1> offset{-static_cast<int>(nr_elements + 10)};

        // 5, 5, ..., 5, 5
        result_we_want.fill(fill_value);
        // false, false, ..., false, false
        result_we_want.mask().fill(false);
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, fill_value, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }
}

BOOST_AUTO_TEST_CASE(array_1d_fill_value_masked_sequential)
{
    fa::SequentialExecutionPolicy sequential;

    test_array_1d_fill_value_masked(sequential);
    fa::ExecutionPolicy execution_policy{sequential};
    test_array_1d_fill_value_masked(execution_policy);
}

BOOST_AUTO_TEST_CASE(array_1d_fill_value_masked_parallel)
{
    fa::ParallelExecutionPolicy parallel;

    test_array_1d_fill_value_masked(parallel);
    fa::ExecutionPolicy execution_policy{parallel};
    test_array_1d_fill_value_masked(execution_policy);
}

template<
    typename ExecutionPolicy>
void test_array_2d(
    ExecutionPolicy& execution_policy)
{
    size_t const nr_threads{fern::hardware_concurrency()};
    size_t const nr_rows{30 * nr_threads};
    size_t const nr_cols{20 * nr_threads};
    size_t const nr_elements{nr_rows * nr_cols};

    fern::Array<int, 2> values(fern::extents[nr_rows][nr_cols]);
    fern::Array<int, 2> result_we_want(fern::extents[nr_rows][nr_cols]);
    fern::Array<int, 2> result_we_got(fern::extents[nr_rows][nr_cols]);

    std::iota(values.data(), values.data() + nr_elements, 0);

    {
        fern::Point<int, 2> offset(0, 0);

        // 0, 1, ..., n - 1
        std::iota(result_we_want.data(), result_we_want.data() + nr_elements,
            0);
        std::fill(result_we_got.data(), result_we_got.data() + nr_elements, -9);

        fa::core::offset(execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    // TODO Add tests.
}

BOOST_AUTO_TEST_CASE(array_2d_sequential)
{
    fa::SequentialExecutionPolicy sequential;

    test_array_2d(sequential);
    fa::ExecutionPolicy execution_policy{sequential};
    test_array_2d(execution_policy);
}

BOOST_AUTO_TEST_CASE(array_2d_parallel)
{
    fa::ParallelExecutionPolicy parallel;

    test_array_2d(parallel);
    fa::ExecutionPolicy execution_policy{parallel};
    test_array_2d(execution_policy);
}

template<
    typename ExecutionPolicy>
void test_array_2d_masked(
    ExecutionPolicy& execution_policy)
{
    size_t const nr_threads{fern::hardware_concurrency()};
    size_t const nr_rows{30 * nr_threads};
    size_t const nr_cols{20 * nr_threads};
    size_t const nr_elements{nr_rows * nr_cols};

    fern::MaskedArray<int, 2> values(fern::extents[nr_rows][nr_cols]);
    fern::MaskedArray<int, 2> result_we_want(fern::extents[nr_rows][nr_cols]);
    fern::MaskedArray<int, 2> result_we_got(fern::extents[nr_rows][nr_cols]);

    fa::InputNoDataPolicies<fa::DetectNoDataByValue<fern::Mask<2>>>
        input_no_data_policy{{values.mask(), true}};
    fa::MarkNoDataByValue<fern::Mask<2>> output_no_data_policy(
        result_we_got.mask(), true);

    std::iota(values.data(), values.data() + nr_elements, 0);
    values.mask()[1][2] = true;

    {
        fern::Point<int, 2> offset(0, 0);

        // 0, -9, 2, ..., n - 1
        std::iota(result_we_want.data(), result_we_want.data() + nr_elements,
            0);
        result_we_want[1][2] = -9;
        result_we_want.mask()[1][2] = true;
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    // TODO Add tests.
}

BOOST_AUTO_TEST_CASE(array_2d_masked_sequential)
{
    fa::SequentialExecutionPolicy sequential;

    test_array_2d_masked(sequential);
    fa::ExecutionPolicy execution_policy{sequential};
    test_array_2d_masked(execution_policy);
}

BOOST_AUTO_TEST_CASE(array_2d_masked_parallel)
{
    fa::ParallelExecutionPolicy parallel;

    test_array_2d_masked(parallel);
    fa::ExecutionPolicy execution_policy{parallel};
    test_array_2d_masked(execution_policy);
}

template<
    typename ExecutionPolicy>
void test_array_2d_fill_value(
    ExecutionPolicy& execution_policy)
{
    size_t const nr_threads{fern::hardware_concurrency()};
    size_t const nr_rows{30 * nr_threads};
    size_t const nr_cols{20 * nr_threads};
    size_t const nr_elements{nr_rows * nr_cols};

    fern::Array<int, 2> values(fern::extents[nr_rows][nr_cols]);
    fern::Array<int, 2> result_we_want(fern::extents[nr_rows][nr_cols]);
    fern::Array<int, 2> result_we_got(fern::extents[nr_rows][nr_cols]);

    int const fill_value{5};

    std::iota(values.data(), values.data() + nr_elements, 0);

    {
        fern::Point<int, 2> offset(0, 0);

        // 0, 1, ..., n - 1
        std::iota(result_we_want.data(), result_we_want.data() + nr_elements,
            0);
        std::fill(result_we_got.data(), result_we_got.data() + nr_elements, -9);

        fa::core::offset(execution_policy, values, offset, fill_value,
            result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    // TODO Add tests.
}

BOOST_AUTO_TEST_CASE(array_2d_fill_value_sequential)
{
    fa::SequentialExecutionPolicy sequential;

    test_array_2d_fill_value(sequential);
    fa::ExecutionPolicy execution_policy{sequential};
    test_array_2d_fill_value(execution_policy);
}

BOOST_AUTO_TEST_CASE(array_2d_fill_value_parallel)
{
    fa::ParallelExecutionPolicy parallel;

    test_array_2d_fill_value(parallel);
    fa::ExecutionPolicy execution_policy{parallel};
    test_array_2d_fill_value(execution_policy);
}

template<
    typename ExecutionPolicy>
void test_array_2d_fill_value_masked(
    ExecutionPolicy& execution_policy)
{
    size_t const nr_threads{fern::hardware_concurrency()};
    size_t const nr_rows{30 * nr_threads};
    size_t const nr_cols{20 * nr_threads};
    size_t const nr_elements{nr_rows * nr_cols};

    fern::MaskedArray<int, 2> values(fern::extents[nr_rows][nr_cols]);
    fern::MaskedArray<int, 2> result_we_want(fern::extents[nr_rows][nr_cols]);
    fern::MaskedArray<int, 2> result_we_got(fern::extents[nr_rows][nr_cols]);

    int const fill_value{5};

    fa::InputNoDataPolicies<fa::DetectNoDataByValue<fern::Mask<2>>>
        input_no_data_policy{{values.mask(), true}};
    fa::MarkNoDataByValue<fern::Mask<2>> output_no_data_policy(
        result_we_got.mask(), true);

    std::iota(values.data(), values.data() + nr_elements, 0);
    values.mask()[1][2] = true;

    {
        fern::Point<int, 2> offset(0, 0);

        // 0, -9, 2, ..., n - 1
        std::iota(result_we_want.data(), result_we_want.data() + nr_elements,
            0);
        result_we_want[1][2] = -9;
        result_we_want.mask()[1][2] = true;
        result_we_got.fill(-9);
        result_we_got.mask().fill(false);

        fa::core::offset(input_no_data_policy, output_no_data_policy,
            execution_policy, values, offset, fill_value, result_we_got);

        BOOST_CHECK(fern::compare(execution_policy, result_we_got,
            result_we_want));
    }

    // TODO Add tests.
}

BOOST_AUTO_TEST_CASE(array_2d_fill_value_masked_sequential)
{
    fa::SequentialExecutionPolicy sequential;

    test_array_2d_fill_value_masked(sequential);
    fa::ExecutionPolicy execution_policy{sequential};
    test_array_2d_fill_value_masked(execution_policy);
}

BOOST_AUTO_TEST_CASE(array_2d_fill_value_masked_parallel)
{
    fa::ParallelExecutionPolicy parallel;

    test_array_2d_fill_value_masked(parallel);
    fa::ExecutionPolicy execution_policy{parallel};
    test_array_2d_fill_value_masked(execution_policy);
}

BOOST_AUTO_TEST_CASE(pcraster_example_1)
{
    // First example from the PCRaster manual page for shift/shift0.
    size_t const nr_rows{5};
    size_t const nr_cols{5};

    fern::MaskedArray<int, 2> values({
        {0,  -1, 1, -30,  0},
        {2, 999, 1,   2, -3},
        {3,   2, 3,   4,  2},
        {0,   0, 2,  40,  2},
        {1,  -2, 4,   7,  1}
    });
    values.mask()[1][1] = true;

    fern::MaskedArray<int, 2> result_we_want({
        {999,   1,   2,   -3, 999},
        {  2,   3,   4,    2, 999},
        {  0,   2,  40,    2, 999},
        { -2,   4,   7,    1, 999},
        {999, 999, 999,  999, 999}
    });
    result_we_want.mask()[0][0] = true;
    for(size_t row = 0; row < nr_rows; ++row) {
        result_we_want.mask()[row][nr_cols-1] = true;
    }
    for(size_t col = 0; col < nr_cols; ++col) {
        result_we_want.mask()[nr_rows-1][col] = true;
    }

    fern::MaskedArray<int, 2> result_we_got(fern::extents[nr_rows][nr_cols],
        999);

    fern::Point<int, 2> offset(-1, -1);

    fa::InputNoDataPolicies<fa::DetectNoDataByValue<fern::Mask<2>>>
        input_no_data_policy{{values.mask(), true}};
    fa::MarkNoDataByValue<fern::Mask<2>> output_no_data_policy(
        result_we_got.mask(), true);

    fa::SequentialExecutionPolicy sequential;

    fa::core::offset(input_no_data_policy, output_no_data_policy,
        sequential, values, offset, result_we_got);

    BOOST_CHECK(fern::compare(sequential, result_we_got,
        result_we_want));
}

BOOST_AUTO_TEST_CASE(pcraster_example_2)
{
    // First example from the PCRaster manual page for shift/shift0.
    size_t const nr_rows{5};
    size_t const nr_cols{5};

    fern::MaskedArray<int, 2> values({
        {0,  -1, 1, -30,  0},
        {2, 999, 1,   2, -3},
        {3,   2, 3,   4,  2},
        {0,   0, 2,  40,  2},
        {1,  -2, 4,   7,  1}
    });
    values.mask()[1][1] = true;

    fern::MaskedArray<int, 2> result_we_want({
        {999, 999, 999, 999,  999},
        {999,   0,   -1,  1,  -30},
        {999,   2,  999,  1,    2},
        {999,   3,    2,  3,    4},
        {999,   0,    0,  2,   40}
    });
    result_we_want.mask()[2][2] = true;
    for(size_t row = 0; row < nr_rows; ++row) {
        result_we_want.mask()[row][0] = true;
    }
    for(size_t col = 0; col < nr_cols; ++col) {
        result_we_want.mask()[0][col] = true;
    }

    fern::MaskedArray<int, 2> result_we_got(fern::extents[nr_rows][nr_cols],
        999);

    fern::Point<int, 2> offset(1, 1);

    fa::InputNoDataPolicies<fa::DetectNoDataByValue<fern::Mask<2>>>
        input_no_data_policy{{values.mask(), true}};
    fa::MarkNoDataByValue<fern::Mask<2>> output_no_data_policy(
        result_we_got.mask(), true);

    fa::SequentialExecutionPolicy sequential;

    fa::core::offset(input_no_data_policy, output_no_data_policy,
        sequential, values, offset, result_we_got);

    BOOST_CHECK(fern::compare(sequential, result_we_got,
        result_we_want));
}

BOOST_AUTO_TEST_CASE(pcraster_example_3)
{
    // First example from the PCRaster manual page for shift/shift0.
    size_t const nr_rows{5};
    size_t const nr_cols{5};

    fern::MaskedArray<int, 2> values({
        {0,  -1, 1, -30,  0},
        {2, 999, 1,   2, -3},
        {3,   2, 3,   4,  2},
        {0,   0, 2,  40,  2},
        {1,  -2, 4,   7,  1}
    });
    values.mask()[1][1] = true;

    fern::MaskedArray<int, 2> result_we_want({
        {0, 0,   0, 0,   0},
        {0, 0,  -1, 1, -30},
        {0, 2, 999, 1,   2},
        {0, 3,   2, 3,   4},
        {0, 0,   0, 2,  40}
    });
    result_we_want.mask()[2][2] = true;

    fern::MaskedArray<int, 2> result_we_got(fern::extents[nr_rows][nr_cols],
        999);

    fern::Point<int, 2> offset(1, 1);

    fa::InputNoDataPolicies<fa::DetectNoDataByValue<fern::Mask<2>>>
        input_no_data_policy{{values.mask(), true}};
    fa::MarkNoDataByValue<fern::Mask<2>> output_no_data_policy(
        result_we_got.mask(), true);

    fa::SequentialExecutionPolicy sequential;

    int const fill_value{0};

    fa::core::offset(input_no_data_policy, output_no_data_policy,
        sequential, values, offset, fill_value, result_we_got);

    BOOST_CHECK(fern::compare(sequential, result_we_got,
        result_we_want));
}


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

  @copyright 2016 NumScale SAS

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

#include <boost/simd/config.hpp>
#include <boost/simd/detail/brigand.hpp>
#include <boost/simd/detail/dispatch.hpp>
#include <boost/simd/detail/constant_traits.hpp>
#include <boost/simd/detail/dispatch/function/make_callable.hpp>
#include <boost/simd/detail/dispatch/hierarchy/functions.hpp>
#include <boost/simd/detail/dispatch/as.hpp>

/*

    @ingroup group-constant

    Generates constant Minlog used in logarithm/exponential computations
    boost::simd::log(x) return  0 if x is less than Minlog (underflow)

    @par Semantic:

    @code
    T r = Minlog<T>();
    @endcode

    is similar to:

    @code
    if T is double
      r = -708.3964185322641;
    else if T is float
      r = -88.3762626647949
    @endcode

    @see Maxlog

*/

namespace boost { namespace simd
{
  namespace tag
  {
    struct minlog_ : boost::dispatch::constant_value_<minlog_>
    {
      BOOST_DISPATCH_MAKE_CALLABLE(ext,minlog_,boost::dispatch::constant_value_<minlog_>);
      BOOST_SIMD_REGISTER_CONSTANT(0, 0xc2b0c0a5UL, 0xc086232bdd7abcd2ULL);
    };
  }

  namespace ext
  {
    BOOST_DISPATCH_FUNCTION_DECLARATION(tag, minlog_)
  }

  namespace detail
  {
    BOOST_DISPATCH_CALLABLE_DEFINITION(tag::minlog_,minlog);
  }

  template<typename T> BOOST_FORCEINLINE auto Minlog()
  BOOST_NOEXCEPT_DECLTYPE(detail::minlog( boost::dispatch::as_<T>{}))
  {
    return detail::minlog( boost::dispatch::as_<T>{} );
  }
} }

#include <boost/simd/arch/common/scalar/constant/constant_value.hpp>
#include <boost/simd/arch/common/simd/constant/constant_value.hpp>

#endif


//  Copyright John Maddock 2006.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_STATS_LOGNORMAL_HPP
#define BOOST_STATS_LOGNORMAL_HPP

// http://www.itl.nist.gov/div898/handbook/eda/section3/eda3669.htm
// http://mathworld.wolfram.com/LogNormalDistribution.html
// http://en.wikipedia.org/wiki/Lognormal_distribution

#include <boost/math/distributions/fwd.hpp>
#include <boost/math/distributions/normal.hpp>
#include <boost/math/special_functions/expm1.hpp>
#include <boost/math/distributions/detail/common_error_handling.hpp>

#include <utility>

namespace boost{ namespace math
{
namespace detail
{

  template <class RealType, class Policy>
  inline bool check_lognormal_x(
        const char* function,
        RealType const& x,
        RealType* result, const Policy& pol)
  {
     if((x < 0) || !(boost::math::isfinite)(x))
     {
        *result = policies::raise_domain_error<RealType>(
           function,
           "Random variate is %1% but must be >= 0 !", x, pol);
        return false;
     }
     return true;
  }

} // namespace detail

template <class RealType = double, class Policy = policies::policy<> >
class lognormal_distribution
{
public:
   typedef RealType value_type;
   typedef Policy policy_type;

   lognormal_distribution(RealType location = 0, RealType scale = 1)
      : m_location(location), m_scale(scale)
   {
      RealType result;
      detail::check_scale("boost::math::lognormal_distribution<%1%>::lognormal_distribution", scale, &result, Policy());
      detail::check_location("boost::math::lognormal_distribution<%1%>::lognormal_distribution", location, &result, Policy());
   }

   RealType location()const
   {
      return m_location;
   }

   RealType scale()const
   {
      return m_scale;
   }
private:
   //
   // Data members:
   //
   RealType m_location;  // distribution location.
   RealType m_scale;     // distribution scale.
};

typedef lognormal_distribution<double> lognormal;

template <class RealType, class Policy>
inline const std::pair<RealType, RealType> range(const lognormal_distribution<RealType, Policy>& /*dist*/)
{ // Range of permissible values for random variable x is >0 to +infinity.
   using boost::math::tools::max_value;
   return std::pair<RealType, RealType>(static_cast<RealType>(0), max_value<RealType>());
}

template <class RealType, class Policy>
inline const std::pair<RealType, RealType> support(const lognormal_distribution<RealType, Policy>& /*dist*/)
{ // Range of supported values for random variable x.
   // This is range where cdf rises from 0 to 1, and outside it, the pdf is zero.
   using boost::math::tools::max_value;
   return std::pair<RealType, RealType>(static_cast<RealType>(0),  max_value<RealType>());
}

template <class RealType, class Policy>
RealType pdf(const lognormal_distribution<RealType, Policy>& dist, const RealType& x)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   RealType mu = dist.location();
   RealType sigma = dist.scale();

   static const char* function = "boost::math::pdf(const lognormal_distribution<%1%>&, %1%)";

   RealType result = 0;
   if(0 == detail::check_scale(function, sigma, &result, Policy()))
      return result;
   if(0 == detail::check_location(function, mu, &result, Policy()))
      return result;
   if(0 == detail::check_lognormal_x(function, x, &result, Policy()))
      return result;

   if(x == 0)
      return 0;

   RealType exponent = log(x) - mu;
   exponent *= -exponent;
   exponent /= 2 * sigma * sigma;

   result = exp(exponent);
   result /= sigma * sqrt(2 * constants::pi<RealType>()) * x;

   return result;
}

template <class RealType, class Policy>
inline RealType cdf(const lognormal_distribution<RealType, Policy>& dist, const RealType& x)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::cdf(const lognormal_distribution<%1%>&, %1%)";

   RealType result = 0;
   if(0 == detail::check_scale(function, dist.scale(), &result, Policy()))
      return result;
   if(0 == detail::check_location(function, dist.location(), &result, Policy()))
      return result;
   if(0 == detail::check_lognormal_x(function, x, &result, Policy()))
      return result;

   if(x == 0)
      return 0;

   normal_distribution<RealType, Policy> norm(dist.location(), dist.scale());
   return cdf(norm, log(x));
}

template <class RealType, class Policy>
inline RealType quantile(const lognormal_distribution<RealType, Policy>& dist, const RealType& p)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::quantile(const lognormal_distribution<%1%>&, %1%)";

   RealType result = 0;
   if(0 == detail::check_scale(function, dist.scale(), &result, Policy()))
      return result;
   if(0 == detail::check_location(function, dist.location(), &result, Policy()))
      return result;
   if(0 == detail::check_probability(function, p, &result, Policy()))
      return result;

   if(p == 0)
      return 0;
   if(p == 1)
      return policies::raise_overflow_error<RealType>(function, 0, Policy());

   normal_distribution<RealType, Policy> norm(dist.location(), dist.scale());
   return exp(quantile(norm, p));
}

template <class RealType, class Policy>
inline RealType cdf(const complemented2_type<lognormal_distribution<RealType, Policy>, RealType>& c)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::cdf(const lognormal_distribution<%1%>&, %1%)";

   RealType result = 0;
   if(0 == detail::check_scale(function, c.dist.scale(), &result, Policy()))
      return result;
   if(0 == detail::check_location(function, c.dist.location(), &result, Policy()))
      return result;
   if(0 == detail::check_lognormal_x(function, c.param, &result, Policy()))
      return result;

   if(c.param == 0)
      return 1;

   normal_distribution<RealType, Policy> norm(c.dist.location(), c.dist.scale());
   return cdf(complement(norm, log(c.param)));
}

template <class RealType, class Policy>
inline RealType quantile(const complemented2_type<lognormal_distribution<RealType, Policy>, RealType>& c)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   static const char* function = "boost::math::quantile(const lognormal_distribution<%1%>&, %1%)";

   RealType result = 0;
   if(0 == detail::check_scale(function, c.dist.scale(), &result, Policy()))
      return result;
   if(0 == detail::check_location(function, c.dist.location(), &result, Policy()))
      return result;
   if(0 == detail::check_probability(function, c.param, &result, Policy()))
      return result;

   if(c.param == 1)
      return 0;
   if(c.param == 0)
      return policies::raise_overflow_error<RealType>(function, 0, Policy());

   normal_distribution<RealType, Policy> norm(c.dist.location(), c.dist.scale());
   return exp(quantile(complement(norm, c.param)));
}

template <class RealType, class Policy>
inline RealType mean(const lognormal_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   RealType mu = dist.location();
   RealType sigma = dist.scale();

   RealType result = 0;
   if(0 == detail::check_scale("boost::math::mean(const lognormal_distribution<%1%>&)", sigma, &result, Policy()))
      return result;
   if(0 == detail::check_location("boost::math::mean(const lognormal_distribution<%1%>&)", mu, &result, Policy()))
      return result;

   return exp(mu + sigma * sigma / 2);
}

template <class RealType, class Policy>
inline RealType variance(const lognormal_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   RealType mu = dist.location();
   RealType sigma = dist.scale();

   RealType result = 0;
   if(0 == detail::check_scale("boost::math::variance(const lognormal_distribution<%1%>&)", sigma, &result, Policy()))
      return result;
   if(0 == detail::check_location("boost::math::variance(const lognormal_distribution<%1%>&)", mu, &result, Policy()))
      return result;

   return boost::math::expm1(sigma * sigma, Policy()) * exp(2 * mu + sigma * sigma);
}

template <class RealType, class Policy>
inline RealType mode(const lognormal_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   RealType mu = dist.location();
   RealType sigma = dist.scale();

   RealType result = 0;
   if(0 == detail::check_scale("boost::math::mode(const lognormal_distribution<%1%>&)", sigma, &result, Policy()))
      return result;
   if(0 == detail::check_location("boost::math::mode(const lognormal_distribution<%1%>&)", mu, &result, Policy()))
      return result;

   return exp(mu - sigma * sigma);
}

template <class RealType, class Policy>
inline RealType median(const lognormal_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std functions
   RealType mu = dist.location();
   return exp(mu); // e^mu
}

template <class RealType, class Policy>
inline RealType skewness(const lognormal_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   //RealType mu = dist.location();
   RealType sigma = dist.scale();

   RealType ss = sigma * sigma;
   RealType ess = exp(ss);

   RealType result = 0;
   if(0 == detail::check_scale("boost::math::skewness(const lognormal_distribution<%1%>&)", sigma, &result, Policy()))
      return result;
   if(0 == detail::check_location("boost::math::skewness(const lognormal_distribution<%1%>&)", dist.location(), &result, Policy()))
      return result;

   return (ess + 2) * sqrt(boost::math::expm1(ss, Policy()));
}

template <class RealType, class Policy>
inline RealType kurtosis(const lognormal_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   //RealType mu = dist.location();
   RealType sigma = dist.scale();
   RealType ss = sigma * sigma;

   RealType result = 0;
   if(0 == detail::check_scale("boost::math::kurtosis(const lognormal_distribution<%1%>&)", sigma, &result, Policy()))
      return result;
   if(0 == detail::check_location("boost::math::kurtosis(const lognormal_distribution<%1%>&)", dist.location(), &result, Policy()))
      return result;

   return exp(4 * ss) + 2 * exp(3 * ss) + 3 * exp(2 * ss) - 3;
}

template <class RealType, class Policy>
inline RealType kurtosis_excess(const lognormal_distribution<RealType, Policy>& dist)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   // RealType mu = dist.location();
   RealType sigma = dist.scale();
   RealType ss = sigma * sigma;

   RealType result = 0;
   if(0 == detail::check_scale("boost::math::kurtosis_excess(const lognormal_distribution<%1%>&)", sigma, &result, Policy()))
      return result;
   if(0 == detail::check_location("boost::math::kurtosis_excess(const lognormal_distribution<%1%>&)", dist.location(), &result, Policy()))
      return result;

   return exp(4 * ss) + 2 * exp(3 * ss) + 3 * exp(2 * ss) - 6;
}

} // namespace math
} // namespace boost

// This include must be at the end, *after* the accessors
// for this distribution have been defined, in order to
// keep compilers that support two-phase lookup happy.
#include <boost/math/distributions/detail/derived_accessors.hpp>

#endif // BOOST_STATS_STUDENTS_T_HPP


/******************************************************************************
Copyright (c) 2021 Dmitriy Korchemkin

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

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

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

#ifndef POLYNOMIALS_DENSE_OPERATORS_HPP
#define POLYNOMIALS_DENSE_OPERATORS_HPP

#include <Eigen/Dense>
#include <iosfwd>

#include "polynomials/assert.hpp"
#include "polynomials/types.hpp"

namespace polynomials {

template <typename Lhs, typename Rhs> struct polynomial_mul_trait;

template <typename DerivedLhs, typename DerivedRhs>
struct polynomial_mul_trait<DensePolyBase<DerivedLhs>,
                            DensePolyBase<DerivedRhs>> {
  using Lhs = DensePolyBase<DerivedLhs>;
  using Rhs = DensePolyBase<DerivedRhs>;
  using Scalar =
      typename Eigen::ScalarBinaryOpTraits<typename Lhs::Scalar,
                                           typename Rhs::Scalar>::ReturnType;
  static constexpr Index DegreeAtCompileTime =
      (Lhs::DegreeAtCompileTime == Dynamic ||
       Rhs::DegreeAtCompileTime == Dynamic)
          ? Dynamic
          : Lhs::DegreeAtCompileTime + Rhs::DegreeAtCompileTime;

  using ResultType = DensePoly<Scalar, DegreeAtCompileTime>;
};

template <typename Lhs, typename Rhs> struct polynomial_sum_trait;

template <typename DerivedLhs, typename DerivedRhs>
struct polynomial_sum_trait<DensePolyBase<DerivedLhs>,
                            DensePolyBase<DerivedRhs>> {
  using Lhs = DensePolyBase<DerivedLhs>;
  using Rhs = DensePolyBase<DerivedRhs>;
  using Scalar =
      typename Eigen::ScalarBinaryOpTraits<typename Lhs::Scalar,
                                           typename Rhs::Scalar>::ReturnType;
  static constexpr Index DegreeAtCompileTime =
      max_or_dynamic(Lhs::DegreeAtCompileTime, Rhs::DegreeAtCompileTime);

  using ResultType = DensePoly<Scalar, DegreeAtCompileTime>;

  static constexpr Index CoeffsCompileTime = ResultType::CoeffsCompileTime;
};

struct AddOp {
  template <typename Lhs, typename Rhs>
  auto operator()(const Lhs &lhs, const Rhs &rhs) const -> decltype(lhs + rhs) {
    return lhs + rhs;
  }
  template <typename D> const D &left_op(const D &d) const { return d; }
  template <typename D> const D &right_op(const D &d) const { return d; }
};

struct SubOp {
  template <typename Lhs, typename Rhs>
  auto operator()(const Lhs &lhs, const Rhs &rhs) const -> decltype(lhs - rhs) {
    return lhs - rhs;
  }
  template <typename D> const D &left_op(const D &d) const { return d; }
  template <typename D> auto right_op(const D &d) const -> decltype(-d) {
    return -d;
  }
};

template <typename Lhs, typename Rhs, typename Op,
          bool constsize =
              polynomial_sum_trait<Lhs, Rhs>::CoeffsCompileTime != Dynamic>
struct SumLikeOpImpl;

// static-sized version
template <typename Result, typename Lhs, typename Rhs, typename Op,
          bool from_lhs>
struct TailOp;

template <typename Result, typename Lhs, typename Rhs, typename Op,
          bool has_intersection>
struct MidOp;

template <typename Result, typename Lhs, typename Rhs, typename Op>
struct TailOp<Result, Lhs, Rhs, Op, false> {
  using ResultScalar = typename Result::Scalar;
  void operator()(Result &res, const Lhs &lhs, const Rhs &rhs,
                  const Op &op) const {
    (void)lhs;
    static_assert(Rhs::DegreeAtCompileTime == Result::DegreeAtCompileTime);
    constexpr Index max_degree = Rhs::DegreeAtCompileTime;
    constexpr Index min_degree = Lhs::DegreeAtCompileTime;
    constexpr Index tail_len = max_degree - min_degree;
    constexpr Index tail_available = std::min(Rhs::CoeffsCompileTime, tail_len);
    static_assert(tail_available != Dynamic);
    if (tail_available == 0)
      return;

    res.coeffs().template tail<tail_available>() =
        op.right_op(rhs.coeffs())
            .template tail<tail_available>()
            .template cast<ResultScalar>();
  }
};
template <typename Result, typename Lhs, typename Rhs, typename Op>
struct TailOp<Result, Lhs, Rhs, Op, true> {
  using ResultScalar = typename Result::Scalar;
  void operator()(Result &res, const Lhs &lhs, const Rhs &rhs,
                  const Op &op) const {
    (void)rhs;
    static_assert(Lhs::DegreeAtCompileTime == Result::DegreeAtCompileTime);
    constexpr Index max_degree = Lhs::DegreeAtCompileTime;
    constexpr Index min_degree = Rhs::DegreeAtCompileTime;
    constexpr Index tail_len = max_degree - min_degree;
    constexpr Index tail_available = std::min(Lhs::CoeffsCompileTime, tail_len);
    static_assert(tail_available != Dynamic);
    if (tail_available == 0)
      return;

    res.coeffs().template tail<tail_available>() =
        op.left_op(lhs.coeffs())
            .template tail<tail_available>()
            .template cast<ResultScalar>();
  }
};

template <typename Result, typename Lhs, typename Rhs, typename Op>
struct MidOp<Result, Lhs, Rhs, Op, true> {
  static constexpr Index min_degree =
      std::min(Lhs::DegreeAtCompileTime, Rhs::DegreeAtCompileTime);
  void operator()(Result &res, const Lhs &lhs, const Rhs &rhs,
                  const Op &op) const {
    static_assert(min_degree != Dynamic);

    constexpr Index len = min_degree + 1;
    static_assert(len >= 0);
    if constexpr (len == 0)
      return;

    res.coeffs().template head<len>() = op(lhs.coeffs().template head<len>(),
                                           rhs.coeffs().template head<len>());
  }
};

template <typename DerivedLhs, typename DerivedRhs, typename Op>
struct SumLikeOpImpl<DensePolyBase<DerivedLhs>, DensePolyBase<DerivedRhs>, Op,
                     true> : private Op {
  using Lhs = DensePolyBase<DerivedLhs>;
  using Rhs = DensePolyBase<DerivedRhs>;
  using SumTraits = polynomial_sum_trait<Lhs, Rhs>;
  using Result = typename SumTraits::ResultType;
  using ResultScalar = typename Result::Scalar;
  static constexpr Index min_degree =
      std::min(Lhs::DegreeAtCompileTime, Rhs::DegreeAtCompileTime);

  SumLikeOpImpl(const Lhs &lhs, const Rhs &rhs) : lhs(lhs), rhs(rhs) {}

  Result operator()() const {
    Result res;
    static_assert(Result::DegreeAtCompileTime != Dynamic);
    MidOp<Result, Lhs, Rhs, Op, true>()(res, lhs, rhs, *this);
    TailOp<Result, Lhs, Rhs, Op, min_degree == Rhs::DegreeAtCompileTime>()(
        res, lhs, rhs, *this);
    return res;
  }

  const Lhs &lhs;
  const Rhs &rhs;
};

// dynamic-sized version
template <typename Result, typename Lhs, typename Rhs, typename Op>
struct TailOpDynamic {
  using ResultScalar = typename Result::Scalar;
  void operator()(Result &res, const Lhs &lhs, const Rhs &rhs,
                  const Op &op) const {
    const auto rhs_degree = rhs.degree();
    const auto lhs_degree = lhs.degree();
    const auto res_degree = res.degree();
    if (rhs_degree == res_degree) {
      const auto max_degree = rhs_degree;
      const auto min_degree = lhs_degree;
      const auto tail_len = max_degree - min_degree;
      const auto tail_available = std::min(rhs.total_coeffs(), tail_len);
      if (tail_available == 0)
        return;

      res.coeffs().tail(tail_available) = op.right_op(rhs.coeffs())
                                              .tail(tail_available)
                                              .template cast<ResultScalar>();
    } else {
      POLYNOMIALS_ASSERT(lhs_degree == res_degree,
                         "Invalid degree: " << lhs_degree << ' ' << rhs_degree
                                            << ' ' << res_degree);
      const auto max_degree = lhs_degree;
      const auto min_degree = rhs_degree;
      const auto tail_len = max_degree - min_degree;
      const auto tail_available = std::min(lhs.total_coeffs(), tail_len);
      if (tail_available == 0)
        return;

      res.coeffs().tail(tail_available) = op.left_op(lhs.coeffs())
                                              .tail(tail_available)
                                              .template cast<ResultScalar>();
    }
  }
};

template <typename Result, typename Lhs, typename Rhs, typename Op>
struct MidOpDynamic {

  using ResultScalar = typename Result::Scalar;
  void operator()(Result &res, const Lhs &lhs, const Rhs &rhs,
                  const Op &op) const {
    const auto rhs_degree = rhs.degree();
    const auto lhs_degree = lhs.degree();
    const auto min_degree = std::min(lhs_degree, rhs_degree);

    const auto len = min_degree + 1;
    POLYNOMIALS_ASSERT(len >= 0, "Invalid mid length");
    if (len == 0)
      return;

    res.coeffs().head(len) = op(lhs.coeffs().head(len), rhs.coeffs().head(len));
  }
};

// dynamic-sized version
template <typename DerivedLhs, typename DerivedRhs, typename Op>
struct SumLikeOpImpl<DensePolyBase<DerivedLhs>, DensePolyBase<DerivedRhs>, Op,
                     false> : private Op {
  using Lhs = DensePolyBase<DerivedLhs>;
  using Rhs = DensePolyBase<DerivedRhs>;
  using SumTraits = polynomial_sum_trait<Lhs, Rhs>;
  using Result = typename SumTraits::ResultType;
  using ResultScalar = typename Result::Scalar;

  SumLikeOpImpl(const Lhs &lhs, const Rhs &rhs) : lhs(lhs), rhs(rhs) {}

  Result operator()() const {
    Result res(std::max(lhs.degree(), rhs.degree()));
    MidOpDynamic<Result, Lhs, Rhs, Op>()(res, lhs, rhs, *this);
    TailOpDynamic<Result, Lhs, Rhs, Op>()(res, lhs, rhs, *this);
    return res;
  }

  const Lhs &lhs;
  const Rhs &rhs;
};

template <typename DerivedLhs, typename DerivedRhs, typename Op>
auto sum_like_op(const DensePolyBase<DerivedLhs> &lhs,
                 const DensePolyBase<DerivedRhs> &rhs) ->
    typename polynomial_sum_trait<DensePolyBase<DerivedLhs>,
                                  DensePolyBase<DerivedRhs>>::ResultType {
  using SumTraits = polynomial_sum_trait<DensePolyBase<DerivedLhs>,
                                         DensePolyBase<DerivedRhs>>;
  using Impl =
      SumLikeOpImpl<typename SumTraits::Lhs, typename SumTraits::Rhs, Op>;
  return Impl(lhs, rhs)();
}

template <typename DerivedLhs, typename DerivedRhs>
auto operator*(const DensePolyBase<DerivedLhs> &lhs,
               const DensePolyBase<DerivedRhs> &rhs) ->
    typename polynomial_mul_trait<DensePolyBase<DerivedLhs>,
                                  DensePolyBase<DerivedRhs>>::ResultType {
  using MulTraits = polynomial_mul_trait<DensePolyBase<DerivedLhs>,
                                         DensePolyBase<DerivedRhs>>;
  using Result = typename MulTraits::ResultType;
  Result res(lhs.degree() + rhs.degree());

  if constexpr (MulTraits::DegreeAtCompileTime != Dynamic) {
    using Lhs = typename MulTraits::Lhs;
    using Rhs = typename MulTraits::Rhs;
    // using Scalar = typename MulTraits::Scalar;
    constexpr Index tail = Rhs::CoeffsCompileTime;
    constexpr Index head = Lhs::CoeffsCompileTime - 1;
    static_assert(tail != Dynamic && tail > 0);
    static_assert(head != Dynamic && head >= 0);

    auto &c = res.coeffs();
    c.template tail<tail>() = rhs.coeffs() * lhs[Lhs::DegreeAtCompileTime];
    c.template head<head>() = lhs.coeffs().template head<head>() * rhs[0];

    constexpr Index mul_rhs_len = Rhs::CoeffsCompileTime - 1;
    for (Index lhs_degree = 0; lhs_degree < Lhs::DegreeAtCompileTime;
         ++lhs_degree) {
      c.template segment<mul_rhs_len>(lhs_degree + 1) +=
          rhs.coeffs().template tail<mul_rhs_len>() * lhs[lhs_degree];
    }
  } else {
    const Index tail = rhs.total_coeffs();
    const Index head = lhs.total_coeffs() - 1;
    POLYNOMIALS_ASSERT(tail != Dynamic && tail > 0,
                       "Invalid multiplication size");
    POLYNOMIALS_ASSERT(head != Dynamic && head >= 0,
                       "Invalid multiplication size");

    auto &c = res.coeffs();
    c.tail(tail) = rhs.coeffs() * lhs[lhs.degree()];
    c.head(head) = lhs.coeffs().head(head) * rhs[0];

    const Index mul_rhs_len = rhs.total_coeffs() - 1;
    for (Index lhs_degree = 0; lhs_degree < lhs.degree(); ++lhs_degree) {
      c.segment(lhs_degree + 1, mul_rhs_len) +=
          rhs.coeffs().tail(mul_rhs_len) * lhs[lhs_degree];
    }
  }
  return res;
}

template <typename DerivedLhs, typename DerivedRhs>
auto operator+(const DensePolyBase<DerivedLhs> &lhs,
               const DensePolyBase<DerivedRhs> &rhs) ->
    typename polynomial_sum_trait<DensePolyBase<DerivedLhs>,
                                  DensePolyBase<DerivedRhs>>::ResultType {
  return sum_like_op<DerivedLhs, DerivedRhs, AddOp>(lhs, rhs);
}

template <typename DerivedLhs, typename DerivedRhs>
auto operator-(const DensePolyBase<DerivedLhs> &lhs,
               const DensePolyBase<DerivedRhs> &rhs) ->
    typename polynomial_sum_trait<DensePolyBase<DerivedLhs>,
                                  DensePolyBase<DerivedRhs>>::ResultType {
  return sum_like_op<DerivedLhs, DerivedRhs, SubOp>(lhs, rhs);
}

template <typename T> struct is_map : std::false_type {};

template <typename T> struct is_map<Eigen::Map<T>> : std::true_type {};

template <typename T> constexpr bool is_map_v = is_map<T>::value;

template <typename T> struct is_const_map : std::false_type {};

template <typename T>
struct is_const_map<Eigen::Map<const T>> : std::true_type {};

template <typename T> constexpr bool is_const_map_v = is_const_map<T>::value;

template <typename Lhs, typename Rhs> constexpr bool potentially_assignable() {
  if (!std::is_same_v<typename Lhs::Scalar, typename Rhs::Scalar>)
    return false;
  if (Lhs::MaxDegreeAtCompileTime != Dynamic &&
      Lhs::MaxDegreeAtCompileTime < Rhs::MaxDegreeAtCompileTime)
    return false;
  if (is_const_map_v<Lhs>)
    return false;
  return true;
}

template <typename Lhs, typename Rhs>
constexpr bool potentially_assignable_v = potentially_assignable<Lhs, Rhs>();

template <typename Lhs, typename Rhs,
          bool Static = Lhs::DegreeAtCompileTime !=
                        Dynamic &&Rhs::DegreeAtCompileTime != Dynamic>
struct AssignOp;

template <typename Lhs, typename Rhs> struct AssignOp<Lhs, Rhs, true> {
  Lhs &operator()(Lhs &lhs, const Rhs &rhs) const {
    static_assert(Lhs::DegreeAtCompileTime >= Rhs::DegreeAtCompileTime,
                  "Cannot assign polynomial with larger degree to polynomial "
                  "with smaller");
    constexpr Index head = Rhs::CoeffsCompileTime;
    constexpr Index tail = Lhs::CoeffsCompileTime - Rhs::CoeffsCompileTime;
    if constexpr (head >= 0) {
      lhs.coeffs().template head<head>() = rhs.coeffs();
    }
    if constexpr (tail >= 0) {
      lhs.coeffs().template tail<tail>().setZero();
    }
    return lhs;
  }
};

template <typename Lhs, typename Rhs> struct AssignOp<Lhs, Rhs, false> {
  Lhs &operator()(Lhs &lhs, const Rhs &rhs) const {
    static_assert(Lhs::DegreeAtCompileTime == Dynamic ||
                  Rhs::DegreeAtCompileTime <= Lhs::DegreeAtCompileTime);
    if constexpr (Lhs::MaxDegreeAtCompileTime != Dynamic &&
                  Rhs::DegreeAtCompileTime == Dynamic) {
      POLYNOMIALS_ASSERT(Lhs::MaxDegreeAtCompileTime >= rhs.degree(),
                         "Not enough storage to assign "
                             << rhs.degree() << "-degree polynomial");
    }
    if constexpr (is_map_v<typename Lhs::Derived>) {
      POLYNOMIALS_ASSERT(lhs.degree() >= rhs.degree(),
                         "Eigen::Map is not resizeable, but need to assign "
                             << rhs.degree() << "-degree polynomial to "
                             << lhs.degree() << " map");

    } else {

      lhs.resize(Lhs::DegreeAtCompileTime == Dynamic
                     ? rhs.degree()
                     : Lhs::DegreeAtCompileTime);
    }
    const Index head = rhs.total_coeffs();
    const Index tail = lhs.total_coeffs() - head;
    if (head) {
      lhs.coeffs().head(head) = rhs.coeffs();
    }
    if (tail) {
      lhs.coeffs().tail(tail).setZero();
    }
    return lhs;
  }
};

template <typename Derived1>
template <typename Derived2>
Derived1 &
DensePolyBase<Derived1>::operator=(const DensePolyBase<Derived2> &rhs) {
  using Lhs = DensePolyBase<Derived1>;
  using Rhs = DensePolyBase<Derived2>;
  static_assert(potentially_assignable_v<Lhs, Rhs>,
                "Incompatible types to assign");

  return AssignOp<Lhs, Rhs>()(*this, rhs).derived();
}

template <typename Derived, char var = 'x'>
std::ostream &operator<<(std::ostream &os, const DensePolyBase<Derived> &poly) {
  using Poly = DensePolyBase<Derived>;
  using Scalar = typename Poly::Scalar;
  const Scalar zero(0);
  const Scalar one(1);
  const Scalar neg_one(-1);

  bool not_first = false;
  bool showpos = os.flags() & std::ios_base::showpos;
  for (Index i = 0; i <= poly.degree(); ++i) {
    const auto &C = poly[i];
    if (C == zero)
      continue;

    bool need_plus = not_first && !showpos && C > zero;
    if (need_plus)
      os << "+";
    not_first = true;
    if ((C != one && C != neg_one) || i == 0) {
      os << C;
      if (i == 0)
        continue;
      os << '*';
    } else if (C == neg_one) {
      os << '-';
    }
    os << var;

    if (i == 1)
      continue;
    os << '^' << i;
  }
  if (!not_first)
    os << '0';
  return os;
}

} // namespace polynomials

#endif


#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
#include <boost/multiprecision/cpp_int.hpp>
namespace np = boost::multiprecision;
using Booint = np::cpp_int;
using namespace std;
int main() {
    Booint a, b, c, d;
    cin >> a >> b >> c >> d;
    if (a >= 0 && b >= 0) {
        if (c >= 0 && d >= 0) cout << b * d << endl;
        else if (c <= 0 && d >= 0) cout << b * d << endl;
        else cout << a * d << endl;
    }
    else if (a <= 0 && b >= 0) {
        if (c >= 0 && d >= 0) cout << b * d << endl;
        else if (c <= 0 && d >= 0) cout << max(b * d, a * c) << endl;
        else cout << a * c << endl;
    }
    else {
        if (c >= 0 && d >= 0) cout << b * c << endl;
        else if (c <= 0 && d >= 0) cout << a * c << endl;
        else cout << a * c << endl;
    }
}


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

/*
 Copyright (C) 2013 Yue Tian

 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.
*/

#include <ql/experimental/barrieroption/vannavolgabarrierengine.hpp>
#include <ql/experimental/barrieroption/vannavolgainterpolation.hpp>
#include <ql/experimental/fx/blackdeltacalculator.hpp>
#include <ql/pricingengines/barrier/fdblackscholesbarrierengine.hpp>
#include <ql/pricingengines/barrier/analyticbarrierengine.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
#include <ql/pricingengines/blackformula.hpp>
#include <ql/math/matrix.hpp>
#include <ql/time/calendars/nullcalendar.hpp>
#include <boost/make_shared.hpp>

namespace QuantLib {

    VannaVolgaBarrierEngine::VannaVolgaBarrierEngine(
            const Handle<DeltaVolQuote>& atmVol,
            const Handle<DeltaVolQuote>& vol25Put,
            const Handle<DeltaVolQuote>& vol25Call,
            const Handle<Quote>& spotFX,
            const Handle<YieldTermStructure>& domesticTS,
            const Handle<YieldTermStructure>& foreignTS,
            const bool adaptVanDelta,
            const Real bsPriceWithSmile
            )
    : GenericEngine<DividendBarrierOption::arguments,
                    DividendBarrierOption::results>(),
      atmVol_(atmVol), vol25Put_(vol25Put), vol25Call_(vol25Call), T_(atmVol_->maturity()),
      spotFX_(spotFX), domesticTS_(domesticTS), foreignTS_(foreignTS),
      adaptVanDelta_(adaptVanDelta), bsPriceWithSmile_(bsPriceWithSmile)
      {
          QL_REQUIRE(vol25Put_->delta() == -0.25, "25 delta put is required by vanna volga method");
          QL_REQUIRE(vol25Call_->delta() == 0.25, "25 delta call is required by vanna volga method");

          QL_REQUIRE(vol25Put_->maturity() == vol25Call_->maturity() && vol25Put_->maturity() == atmVol_->maturity(),
              "Maturity of 3 vols are not the same");

          QL_REQUIRE(!domesticTS_.empty(), "domestic yield curve is not defined");
          QL_REQUIRE(!foreignTS_.empty(), "foreign yield curve is not defined");

          registerWith(atmVol_);
          registerWith(vol25Put_);
          registerWith(vol25Call_);
          registerWith(spotFX_);
          registerWith(domesticTS_);
          registerWith(foreignTS_);

    }

    void VannaVolgaBarrierEngine::calculate() const {

        const Real sigmaShift_vega = 0.0001;
        const Real sigmaShift_volga = 0.0001;
        const Real spotShift_delta = 0.0001 * spotFX_->value();
        const Real sigmaShift_vanna = 0.0001;

        Handle<Quote> x0Quote(
            boost::make_shared<SimpleQuote>(spotFX_->value())); //used for shift
        Handle<Quote> atmVolQuote(
            boost::make_shared<SimpleQuote>(atmVol_->value())); //used for shift

        boost::shared_ptr<BlackVolTermStructure> blackVolTS =
            boost::make_shared<BlackConstantVol>(
                Settings::instance().evaluationDate(),
                NullCalendar(), atmVolQuote, Actual365Fixed());
        boost::shared_ptr<BlackScholesMertonProcess> stochProcess =
            boost::make_shared<BlackScholesMertonProcess>(
                                 x0Quote,
                                 foreignTS_,
                                 domesticTS_,
                                 Handle<BlackVolTermStructure>(blackVolTS));

        boost::shared_ptr<PricingEngine> engineBS =
            boost::make_shared<AnalyticBarrierEngine>(stochProcess);

        BlackDeltaCalculator blackDeltaCalculatorAtm(
                        Option::Call, atmVol_->deltaType(), x0Quote->value(),
                        domesticTS_->discount(T_), foreignTS_->discount(T_),
                        atmVol_->value() * sqrt(T_));
        Real atmStrike = blackDeltaCalculatorAtm.atmStrike(atmVol_->atmType());

        Real call25Vol = vol25Call_->value();
        Real put25Vol = vol25Put_->value();

        BlackDeltaCalculator blackDeltaCalculatorPut25(Option::Put, vol25Put_->deltaType(), x0Quote->value(),
                                                      domesticTS_->discount(T_), foreignTS_->discount(T_),
                                                      put25Vol * sqrt(T_));
        Real put25Strike = blackDeltaCalculatorPut25.strikeFromDelta(-0.25);
        BlackDeltaCalculator blackDeltaCalculatorCall25(Option::Call, vol25Call_->deltaType(), x0Quote->value(),
                                                      domesticTS_->discount(T_), foreignTS_->discount(T_),
                                                      call25Vol * sqrt(T_));
        Real call25Strike = blackDeltaCalculatorCall25.strikeFromDelta(0.25);

        //here use vanna volga interpolated smile to price vanilla
        std::vector<Real> strikes;
        std::vector<Real> vols;
        strikes.push_back(put25Strike);
        vols.push_back(put25Vol);
        strikes.push_back(atmStrike);
        vols.push_back(atmVol_->value());
        strikes.push_back(call25Strike);
        vols.push_back(call25Vol);
        VannaVolga vannaVolga(x0Quote->value(), domesticTS_->discount(T_), foreignTS_->discount(T_), T_);
        Interpolation interpolation = vannaVolga.interpolate(strikes.begin(), strikes.end(), vols.begin());
        interpolation.enableExtrapolation();
        const boost::shared_ptr<StrikedTypePayoff> payoff =
                                        boost::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
        Real strikeVol = interpolation(payoff->strike());

        //vannila option price
        Real vanillaOption = blackFormula(payoff->optionType(), payoff->strike(),
                                      x0Quote->value()* foreignTS_->discount(T_)/ domesticTS_->discount(T_),
                                      strikeVol * sqrt(T_),
                                      domesticTS_->discount(T_));

        //spot > barrier up&out 0
        if(x0Quote->value() >= arguments_.barrier && arguments_.barrierType == Barrier::UpOut){
            results_.value = 0.0;
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
            results_.additionalResults["BarrierOutPrice"] = 0.0;
        }
        //spot > barrier up&in vanilla
        else if(x0Quote->value() >= arguments_.barrier && arguments_.barrierType == Barrier::UpIn){
            results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
            results_.additionalResults["BarrierOutPrice"] = 0.0;
        }
        //spot < barrier down&out 0
        else if(x0Quote->value() <= arguments_.barrier && arguments_.barrierType == Barrier::DownOut){
            results_.value = 0.0;
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
            results_.additionalResults["BarrierOutPrice"] = 0.0;
        }
        //spot < barrier down&in vanilla
        else if(x0Quote->value() <= arguments_.barrier && arguments_.barrierType == Barrier::DownIn){
            results_.value = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
            results_.additionalResults["VanillaPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
            results_.additionalResults["BarrierInPrice"] = adaptVanDelta_? bsPriceWithSmile_ : vanillaOption;
            results_.additionalResults["BarrierOutPrice"] = 0.0;
        }
        else{

            //set up BS barrier option pricing
            //only calculate out barrier option price
            // in barrier price = vanilla - out barrier
            Barrier::Type barrierTyp;
            if(arguments_.barrierType == Barrier::UpOut)
                barrierTyp = arguments_.barrierType;
            else if(arguments_.barrierType == Barrier::UpIn)
                barrierTyp = Barrier::UpOut;
            else if(arguments_.barrierType == Barrier::DownOut)
                barrierTyp = arguments_.barrierType;
            else
                barrierTyp = Barrier::DownOut;

            BarrierOption barrierOption(barrierTyp,
                                        arguments_.barrier,
                                        arguments_.rebate,
                                        boost::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff),
                                        arguments_.exercise);

            barrierOption.setPricingEngine(engineBS);

            //BS price with atm vol
            Real priceBS = barrierOption.NPV();

            Real priceAtmCallBS = blackFormula(Option::Call,atmStrike,
                                              x0Quote->value()* foreignTS_->discount(T_)/ domesticTS_->discount(T_),
                                              atmVol_->value() * sqrt(T_),
                                              domesticTS_->discount(T_));
            Real price25CallBS = blackFormula(Option::Call,call25Strike,
                                              x0Quote->value()* foreignTS_->discount(T_)/ domesticTS_->discount(T_),
                                              atmVol_->value() * sqrt(T_),
                                              domesticTS_->discount(T_));
            Real price25PutBS = blackFormula(Option::Put,put25Strike,
                                              x0Quote->value()* foreignTS_->discount(T_)/ domesticTS_->discount(T_),
                                              atmVol_->value() * sqrt(T_),
                                              domesticTS_->discount(T_));

            //market price
            Real priceAtmCallMkt = blackFormula(Option::Call,atmStrike,
                                              x0Quote->value()* foreignTS_->discount(T_)/ domesticTS_->discount(T_),
                                              atmVol_->value() * sqrt(T_),
                                              domesticTS_->discount(T_));

            Real price25CallMkt = blackFormula(Option::Call,call25Strike,
                                              x0Quote->value()* foreignTS_->discount(T_)/ domesticTS_->discount(T_),
                                              call25Vol * sqrt(T_),
                                              domesticTS_->discount(T_));
            Real price25PutMkt = blackFormula(Option::Put,put25Strike,
                                              x0Quote->value()* foreignTS_->discount(T_)/ domesticTS_->discount(T_),
                                              put25Vol * sqrt(T_),
                                              domesticTS_->discount(T_));

            //Analytical Black Scholes formula for vanilla option
            NormalDistribution norm;
            Real d1atm = (std::log(x0Quote->value()* foreignTS_->discount(T_)/ domesticTS_->discount(T_)/atmStrike)
                           + 0.5*std::pow(atmVolQuote->value(),2.0) * T_)/(atmVolQuote->value() * sqrt(T_));
            Real vegaAtm_Analytical = x0Quote->value() * norm(d1atm) * sqrt(T_) * foreignTS_->discount(T_);
            Real vannaAtm_Analytical = vegaAtm_Analytical/x0Quote->value() *(1.0 - d1atm/(atmVolQuote->value()*sqrt(T_)));
            Real volgaAtm_Analytical = vegaAtm_Analytical * d1atm * (d1atm - atmVolQuote->value() * sqrt(T_))/atmVolQuote->value();

            Real d125call = (std::log(x0Quote->value()* foreignTS_->discount(T_)/ domesticTS_->discount(T_)/call25Strike)
                           + 0.5*std::pow(atmVolQuote->value(),2.0) * T_)/(atmVolQuote->value() * sqrt(T_));
            Real vega25Call_Analytical = x0Quote->value() * norm(d125call) * sqrt(T_) * foreignTS_->discount(T_);
            Real vanna25Call_Analytical = vega25Call_Analytical/x0Quote->value() *(1.0 - d125call/(atmVolQuote->value()*sqrt(T_)));
            Real volga25Call_Analytical = vega25Call_Analytical * d125call * (d125call - atmVolQuote->value() * sqrt(T_))/atmVolQuote->value();

            Real d125Put = (std::log(x0Quote->value()* foreignTS_->discount(T_)/ domesticTS_->discount(T_)/put25Strike)
                           + 0.5*std::pow(atmVolQuote->value(),2.0) * T_)/(atmVolQuote->value() * sqrt(T_));
            Real vega25Put_Analytical = x0Quote->value() * norm(d125Put) * sqrt(T_) * foreignTS_->discount(T_);
            Real vanna25Put_Analytical = vega25Put_Analytical/x0Quote->value() *(1.0 - d125Put/(atmVolQuote->value()*sqrt(T_)));
            Real volga25Put_Analytical = vega25Put_Analytical * d125Put * (d125Put - atmVolQuote->value() * sqrt(T_))/atmVolQuote->value();

            //BS vega
            boost::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_vega);
            barrierOption.recalculate();
            Real vegaBarBS = (barrierOption.NPV() - priceBS)/sigmaShift_vega;

            boost::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() - sigmaShift_vega);//setback

            //BS volga

            //vegaBar2
            //base NPV
            boost::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_volga);
            barrierOption.recalculate();
            Real priceBS2 = barrierOption.NPV();

            //shifted npv
            boost::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_vega);
            barrierOption.recalculate();
            Real vegaBarBS2 = (barrierOption.NPV() - priceBS2)/sigmaShift_vega;
            Real volgaBarBS = (vegaBarBS2 - vegaBarBS)/sigmaShift_volga;

            boost::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value()
                                                                                - sigmaShift_volga
                                                                                - sigmaShift_vega);//setback

            //BS Delta
            //base delta
            boost::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() + spotShift_delta);//shift forth
            barrierOption.recalculate();
            Real priceBS_delta1 = barrierOption.NPV();

            boost::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() - 2 * spotShift_delta);//shift back
            barrierOption.recalculate();
            Real priceBS_delta2 = barrierOption.NPV();

            boost::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() +  spotShift_delta);//set back
            Real deltaBar1 = (priceBS_delta1 - priceBS_delta2)/(2.0*spotShift_delta);

            //shifted delta
            boost::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() + sigmaShift_vanna);//shift sigma
            boost::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() + spotShift_delta);//shift forth
            barrierOption.recalculate();
            priceBS_delta1 = barrierOption.NPV();

            boost::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() - 2 * spotShift_delta);//shift back
            barrierOption.recalculate();
            priceBS_delta2 = barrierOption.NPV();

            boost::static_pointer_cast<SimpleQuote> (x0Quote.currentLink())->setValue(x0Quote->value() +  spotShift_delta);//set back
            Real deltaBar2 = (priceBS_delta1 - priceBS_delta2)/(2.0*spotShift_delta);

            Real vannaBarBS = (deltaBar2 - deltaBar1)/sigmaShift_vanna;

            boost::static_pointer_cast<SimpleQuote> (atmVolQuote.currentLink())->setValue(atmVolQuote->value() - sigmaShift_vanna);//set back

            //Matrix
            Matrix A(3,3,0.0);

            //analytical
            A[0][0] = vegaAtm_Analytical;
            A[0][1] = vega25Call_Analytical;
            A[0][2] = vega25Put_Analytical;
            A[1][0] = vannaAtm_Analytical;
            A[1][1] = vanna25Call_Analytical;
            A[1][2] = vanna25Put_Analytical;
            A[2][0] = volgaAtm_Analytical;
            A[2][1] = volga25Call_Analytical;
            A[2][2] = volga25Put_Analytical;

            Array b(3,0.0);
            b[0] = vegaBarBS;
            b[1] = vannaBarBS;
            b[2] = volgaBarBS;

            Array q = inverse(A) * b;

            //touch probability
            CumulativeNormalDistribution cnd;
            Real mu = domesticTS_->zeroRate(T_, Continuous) - foreignTS_->zeroRate(T_, Continuous) - pow(atmVol_->value(), 2.0)/2.0;
            Real h2 = (log(arguments_.barrier/x0Quote->value()) + mu*T_)/(atmVol_->value()*sqrt(T_));
            Real h2Prime = (log(x0Quote->value()/arguments_.barrier) + mu*T_)/(atmVol_->value()*sqrt(T_));
            Real probTouch = 0.0;
            if(arguments_.barrierType == Barrier::UpIn || arguments_.barrierType == Barrier::UpOut)
                probTouch = cnd(h2Prime) + pow(arguments_.barrier/x0Quote->value(), 2.0*mu/pow(atmVol_->value(), 2.0))*cnd(-h2);
            else
                probTouch = cnd(-h2Prime) + pow(arguments_.barrier/x0Quote->value(), 2.0*mu/pow(atmVol_->value(), 2.0))*cnd(h2);
            Real p_survival = 1.0 - probTouch;

            Real lambda = p_survival ;
            Real adjust = q[0]*(priceAtmCallMkt - priceAtmCallBS)
                        + q[1]*(price25CallMkt - price25CallBS)
                        + q[2]*(price25PutMkt - price25PutBS);
            Real outPrice = priceBS + lambda*adjust;//
            Real inPrice;

            //adapt Vanilla delta
            if(adaptVanDelta_ == true){
                outPrice += lambda*(bsPriceWithSmile_ - vanillaOption);
                //capfloored by (0, vanilla)
                outPrice = std::max(0.0, std::min(bsPriceWithSmile_, outPrice));
                inPrice = bsPriceWithSmile_ - outPrice;
            }
            else{
                //capfloored by (0, vanilla)
                outPrice = std::max(0.0, std::min(vanillaOption , outPrice));
                inPrice = vanillaOption - outPrice;
            }

            if(arguments_.barrierType == Barrier::DownOut || arguments_.barrierType == Barrier::UpOut)
                results_.value = outPrice;
            else
                results_.value = inPrice;
            results_.additionalResults["VanillaPrice"] = vanillaOption;
            results_.additionalResults["BarrierInPrice"] = inPrice;
            results_.additionalResults["BarrierOutPrice"] = outPrice;
            results_.additionalResults["lambda"] = lambda;
         }
    }
}


#include <boost/math/distributions.hpp>


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

    @copyright 2016 NumScale SAS

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

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-trigonometric
    This function object returns the inverse cosine in degree.

    @par Header <boost/simd/function/acosd.hpp>

    @par Decorators

       - pedantic_     is similar to indeg(pedantic_)(acos(x));

    @see acos, acospi, cosd, indeg

    @par Example:

      @snippet acosd.cpp acosd

    @par Possible output:

      @snippet acosd.txt acosd

  **/
  IEEEValue acosd(IEEEValue const & x);
} }
#endif

#include <boost/simd/function/scalar/acosd.hpp>
#include <boost/simd/function/simd/acosd.hpp>

#endif


#include "Engines/Internals/iTransform.h"
#include "Engines/Internals/InternalsUtilityFunctions.h"

#define BOOST_TEST_DYN_LINK

#include <boost/test/unit_test.hpp>
#include <iostream>
#include <boost/lexical_cast.hpp>

#include <Utilities/IntermediateConformerCollector.h>

SmolDock::IntermediateConformerCollector* conformerCollector;

#include <random>

#include <Eigen/Eigen>
#include <Vc/Vc>
#include <Engines/ScoringFunctions/VinaLikeCommon.h>

namespace sd = SmolDock;

BOOST_AUTO_TEST_SUITE(Engines_ts)

    BOOST_AUTO_TEST_SUITE(geometry_ts)

    BOOST_AUTO_TEST_SUITE_END();

    BOOST_AUTO_TEST_SUITE(scoreComponents_ts)

        struct ExampleDistanceFixture {
            ExampleDistanceFixture() :
            mt(452),
            dist(0.0, 12.0){
                //static_assert(Vc::Vector<double>::Size >= 3);

                for(unsigned int i = 0; i < numTestPoint; i++) {
                    double random_distance = dist(mt);
                    distances_array[i] = random_distance;
                    distances_memoryblock.scalar(i) = random_distance;
                    BOOST_TEST_MESSAGE("ExampleDistanceFixture distance[" + boost::lexical_cast<std::string>(i) + "] = " + boost::lexical_cast<std::string>(random_distance));
                }
            }

            ~ExampleDistanceFixture() = default;

            std::mt19937 mt;
            std::uniform_real_distribution<double> dist;

            static constexpr const unsigned int numTestPoint = Vc::Vector<double>::Size * 4; // Multiple of vect size makes code easier
            std::array<double, numTestPoint> distances_array;
            Vc::Memory<Vc::Vector<double>,numTestPoint> distances_memoryblock;
        };

        BOOST_FIXTURE_TEST_CASE(gaussVectorized_equivalency, ExampleDistanceFixture,
                                *boost::unit_test::tolerance(0.001)) {

            std::array<double, numTestPoint> result_nonvectorized_gauss1;
            std::array<double, numTestPoint> result_nonvectorized_gauss2;
            for(unsigned int i = 0; i < numTestPoint; i++) {
                result_nonvectorized_gauss1[i] = sd::Score::vinaGaussComponent(distances_array[i], 0.0, 0.5);
                result_nonvectorized_gauss2[i] = sd::Score::vinaGaussComponent(distances_array[i], 3.0, 2.0);
            }

            Vc::Memory<Vc::Vector<double>,numTestPoint> result_vectorized_gauss1;
            Vc::Memory<Vc::Vector<double>,numTestPoint> result_vectorized_gauss2;
            for (unsigned int i = 0; i < result_vectorized_gauss1.vectorsCount(); ++i) {
                Vc::Vector<double> distance_simdvector = distances_memoryblock.vector(i);

                result_vectorized_gauss1.vector(i) = sd::Score::vinaGaussComponent(distance_simdvector, 0.0, 0.5);
                result_vectorized_gauss2.vector(i) = sd::Score::vinaGaussComponent(distance_simdvector, 3.0, 2.0);
            }

            for(unsigned int i = 0; i < numTestPoint; i++) {
                BOOST_TEST(result_nonvectorized_gauss1[i] == result_vectorized_gauss1.scalar(i));
            }

            for(unsigned int i = 0; i < numTestPoint; i++) {
                BOOST_TEST(result_nonvectorized_gauss2[i] == result_vectorized_gauss2.scalar(i));
            }

        }

        BOOST_FIXTURE_TEST_CASE(repulsionVectorized_equivalency, ExampleDistanceFixture,
                                *boost::unit_test::tolerance(0.001)) {

            std::array<double, numTestPoint> result_nonvectorized_repulsion;
            for(unsigned int i = 0; i < numTestPoint; i++) {
                result_nonvectorized_repulsion[i] = sd::Score::vinaRepulsionComponent(distances_array[i], 0.0);
            }

            Vc::Memory<Vc::Vector<double>,numTestPoint> result_vectorized_repulsion;
            for (unsigned int i = 0; i < result_vectorized_repulsion.vectorsCount(); ++i) {
                Vc::Vector<double> distance_simdvector = distances_memoryblock.vector(i);

                result_vectorized_repulsion.vector(i) = sd::Score::vinaRepulsionComponent(distance_simdvector, 0.0);
            }

            for(unsigned int i = 0; i < numTestPoint; i++) {
                BOOST_TEST(result_nonvectorized_repulsion[i] == result_vectorized_repulsion.scalar(i));
            }

        }

    BOOST_AUTO_TEST_SUITE_END();

    BOOST_AUTO_TEST_CASE(internals_TypeProperties) {
        BOOST_TEST(std::is_polymorphic<SmolDock::iConformer>::value == false);

    }

BOOST_AUTO_TEST_SUITE_END();


// Copyright (c) 2021 Graphcore Ltd. All rights reserved.

#include <popsolver/Model.hpp>
#define BOOST_TEST_MODULE ReifiedEqual
#include <boost/test/unit_test.hpp>

using namespace popsolver;

BOOST_AUTO_TEST_CASE(ReifiedEqualCase1) {
  Model m;

  auto a = m.addConstant(0);
  auto b = m.addConstant(0);
  auto c = m.reifiedEqual(a, b);
  auto s = m.minimize(c);

  BOOST_CHECK(s.validSolution());
  BOOST_CHECK_EQUAL(s[c], DataType{1});
}

BOOST_AUTO_TEST_CASE(ReifiedEqualCase2) {
  Model m;

  auto a = m.addConstant(0);
  auto b = m.addConstant(1);
  auto c = m.reifiedEqual(a, b);
  auto s = m.minimize(c);

  BOOST_CHECK(s.validSolution());
  BOOST_CHECK_EQUAL(s[c], DataType{0});
}

BOOST_AUTO_TEST_CASE(ReifiedEqualCase3) {
  Model m;

  auto a = m.addConstant(50);
  auto b = m.addConstant(150);
  auto c = m.reifiedEqual(a, b);
  auto s = m.minimize(c);

  BOOST_CHECK(s.validSolution());
  BOOST_CHECK_EQUAL(s[c], DataType{0});
}

BOOST_AUTO_TEST_CASE(ReifiedEqualCase4) {
  Model m;

  auto a = m.addConstant(DataType::max());
  auto b = m.addConstant(DataType::max());
  auto c = m.reifiedEqual(a, b);
  auto s = m.minimize(c);

  BOOST_CHECK(s.validSolution());
  BOOST_CHECK_EQUAL(s[c], DataType{1});
}

BOOST_AUTO_TEST_CASE(ReifiedEqualCase5) {
  Model m;

  auto a = m.addConstant(DataType::min());
  auto b = m.addConstant(DataType::max());
  auto c = m.reifiedEqual(a, b);
  auto s = m.minimize(c);

  BOOST_CHECK(s.validSolution());
  BOOST_CHECK_EQUAL(s[c], DataType{0});
}


/*
 * perlinNoise.hxx
 *
 *  Created on: Apr 24, 2015
 *      Author: cgg
 */

#ifndef PERLINNOISE_HXX_
#define PERLINNOISE_HXX_

#ifndef __BOOST__
	#define __BOOST__
	#include <boost/random.hpp>
	#include <boost/math/distributions/beta.hpp>
	#include <boost/program_options.hpp>
#endif

class perlinNoise{

	private:
		double frequency;
		double lacunarity;
		double persistence;
		int numOctaves;
		int32_t seed;
		int32_t xNoiseGen,yNoiseGen,zNoiseGen,seedNoiseGen,shiftNoiseGen;
		double makeInt32Range(double x);
		double pInterp(double x);
		double linInterp(double lbound, double rbound, double x);
		double coherentNoise(double x, double y, double z, int32_t mySeed);
		double gradientNoise(double x, double y, double z,
			int32_t ia, int32_t ib, int32_t ic, int32_t mySeed);

	public:
		double getNoise(double* r);
		void setSeed(int32_t inSeed);
		perlinNoise(boost::program_options::variables_map vm, int32_t inSeed, const char* type);
		perlinNoise(boost::program_options::variables_map vm, int32_t inSeed, double freq, double lac, double pers, int oct);
		perlinNoise(boost::program_options::variables_map vm, const char* type);

};

#endif /* PERLINNOISE_HXX_ */