/*
 Copyright (C) 2016 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.
*/

#include "capfloormarketdata.hpp"
#include "yieldcurvemarketdata.hpp"

#include "toplevelfixture.hpp"
#include <boost/test/unit_test.hpp>
#include <qle/termstructures/optionletstripper1.hpp>
#include <qle/termstructures/optionletstripper2.hpp>

#include <ql/instruments/makecapfloor.hpp>
#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
#include <ql/termstructures/volatility/capfloor/capfloortermvolcurve.hpp>
#include <ql/termstructures/volatility/optionlet/strippedoptionletadapter.hpp>
#include <ql/termstructures/yield/zerospreadedtermstructure.hpp>

using namespace QuantExt;
using namespace QuantLib;
using namespace boost::unit_test_framework;

namespace {
// Variables to be used in the test
struct CommonVars {
    // Constructor
    CommonVars()
        : referenceDate(5, Feb, 2016), settlementDays(0), calendar(TARGET()), bdc(Following),
          dayCounter(Actual365Fixed()), accuracy(1.0e-6), maxIter(100) {
        // Reference date
        Settings::instance().evaluationDate() = referenceDate;
        // Cap floor ibor index
        iborIndex = boost::make_shared<Euribor6M>(yieldCurves.forward6M);
    }

    // Members
    Date referenceDate;

    // Some common conventions for our cap floor curve and surface construction
    Natural settlementDays;
    Calendar calendar;
    BusinessDayConvention bdc;
    DayCounter dayCounter;

    // Accuracy and max iterations for optionlet stripping
    Real accuracy;
    Natural maxIter;

    boost::shared_ptr<IborIndex> iborIndex;
    CapFloorVolatilityEUR vols;
    YieldCurveEUR yieldCurves;

    SavedSettings backup;
};
} // namespace

BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)

BOOST_AUTO_TEST_SUITE(OptionletStripperTest)

BOOST_AUTO_TEST_CASE(testUsualNormalStripping) {
    BOOST_TEST_MESSAGE("Testing standard stripping of normal capfloor vols...");

    CommonVars vars;

    // EUR cap floor normal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.nVols,
                                                             vars.dayCounter);

    // Create Normal stripped optionlet surface and Normal engine
    boost::shared_ptr<QuantExt::OptionletStripper> stripper = boost::shared_ptr<OptionletStripper1>(
        new OptionletStripper1(volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter,
                               vars.yieldCurves.discountEonia, Normal));
    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();
    boost::shared_ptr<BachelierCapFloorEngine> engine =
        boost::make_shared<BachelierCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BachelierCapFloorEngine> flatEngine =
        boost::make_shared<BachelierCapFloorEngine>(vars.yieldCurves.discountEonia, quote, vars.dayCounter);

    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < vars.vols.strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], vars.iborIndex, vars.vols.strikes[j],
                               vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.nVols[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(vars.vols.strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }
}

BOOST_AUTO_TEST_CASE(testUsualShiftedLognormalStripping) {
    BOOST_TEST_MESSAGE("Testing standard stripping of shifted lognormal capfloor vols...");

    CommonVars vars;

    // EUR cap floor shifted lognormal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.slnVols_1,
                                                             vars.dayCounter);

    // Create shifted lognormal stripped optionlet surface and shifted lognormal engine
    boost::shared_ptr<QuantExt::OptionletStripper> stripper = boost::shared_ptr<OptionletStripper1>(
        new QuantExt::OptionletStripper1(volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter,
                                         vars.yieldCurves.discountEonia, ShiftedLognormal, vars.vols.shift_1));
    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();
    boost::shared_ptr<BlackCapFloorEngine> engine =
        boost::make_shared<BlackCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BlackCapFloorEngine> flatEngine = boost::make_shared<BlackCapFloorEngine>(
        vars.yieldCurves.discountEonia, quote, vars.dayCounter, vars.vols.shift_1);

    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < vars.vols.strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], vars.iborIndex, vars.vols.strikes[j],
                               vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.slnVols_1[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(vars.vols.strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }
}

BOOST_AUTO_TEST_CASE(testNormalToShiftedLognormalStripping) {
    BOOST_TEST_MESSAGE("Testing stripping of normal capfloor vols to give shifted lognormal optionlet vols...");

    CommonVars vars;

    // EUR cap floor normal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.nVols,
                                                             vars.dayCounter);

    // Create shifted lognormal stripped optionlet surface and Black engine
    boost::shared_ptr<QuantExt::OptionletStripper> stripper =
        boost::shared_ptr<OptionletStripper1>(new OptionletStripper1(
            volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter, vars.yieldCurves.discountEonia,
            Normal, 0.0, false, ShiftedLognormal, vars.vols.shift_1));
    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();
    boost::shared_ptr<BlackCapFloorEngine> engine =
        boost::make_shared<BlackCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BachelierCapFloorEngine> flatEngine =
        boost::make_shared<BachelierCapFloorEngine>(vars.yieldCurves.discountEonia, quote, vars.dayCounter);

    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < vars.vols.strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], vars.iborIndex, vars.vols.strikes[j],
                               vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.nVols[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(vars.vols.strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }
}

BOOST_AUTO_TEST_CASE(testShiftedLognormalToNormalStripping) {
    BOOST_TEST_MESSAGE("Testing stripping of shifted lognormal capfloor vols to give normal optionlet vols...");

    CommonVars vars;

    // EUR cap floor shifted lognormal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.slnVols_2,
                                                             vars.dayCounter);

    // Create normal stripped optionlet surface and Bachelier engine
    boost::shared_ptr<QuantExt::OptionletStripper> stripper =
        boost::shared_ptr<OptionletStripper1>(new OptionletStripper1(
            volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter, vars.yieldCurves.discountEonia,
            ShiftedLognormal, vars.vols.shift_2, false, Normal, 0.0));
    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();
    boost::shared_ptr<BachelierCapFloorEngine> engine =
        boost::make_shared<BachelierCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BlackCapFloorEngine> flatEngine = boost::make_shared<BlackCapFloorEngine>(
        vars.yieldCurves.discountEonia, quote, vars.dayCounter, vars.vols.shift_2);

    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < vars.vols.strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], vars.iborIndex, vars.vols.strikes[j],
                               vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.slnVols_2[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(vars.vols.strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }
}

BOOST_AUTO_TEST_CASE(testShiftedLognormalToShiftedLognormalStripping) {
    BOOST_TEST_MESSAGE("Testing stripping with shifted lognormal vols with different shifts...");

    CommonVars vars;

    // EUR cap floor shifted lognormal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.slnVols_2,
                                                             vars.dayCounter);

    // Create shifted lognormal stripped optionlet surface and shifted lognormal engine with different shift
    boost::shared_ptr<QuantExt::OptionletStripper> stripper =
        boost::shared_ptr<OptionletStripper1>(new OptionletStripper1(
            volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter, vars.yieldCurves.discountEonia,
            ShiftedLognormal, vars.vols.shift_2, false, ShiftedLognormal, vars.vols.shift_1));
    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();
    boost::shared_ptr<BlackCapFloorEngine> engine =
        boost::make_shared<BlackCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BlackCapFloorEngine> flatEngine = boost::make_shared<BlackCapFloorEngine>(
        vars.yieldCurves.discountEonia, quote, vars.dayCounter, vars.vols.shift_2);

    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < vars.vols.strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], vars.iborIndex, vars.vols.strikes[j],
                               vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.slnVols_2[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(vars.vols.strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }
}

BOOST_AUTO_TEST_CASE(testUsualNormalStrippingWithAtm) {
    BOOST_TEST_MESSAGE("Testing standard stripping of normal capfloor vols with overlayed ATM curve...");

    CommonVars vars;

    // EUR cap floor normal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.nVols,
                                                             vars.dayCounter);

    // EUR cap floor normal ATM curve
    Handle<CapFloorTermVolCurve> atmVolCurve(boost::make_shared<CapFloorTermVolCurve>(
        vars.settlementDays, vars.calendar, vars.bdc, vars.vols.atmTenors, vars.vols.nAtmVols, vars.dayCounter));

    // Create Normal stripped optionlet surface
    boost::shared_ptr<QuantExt::OptionletStripper1> tempStripper = boost::shared_ptr<OptionletStripper1>(
        new OptionletStripper1(volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter,
                               vars.yieldCurves.discountEonia, Normal));

    // Overlay normal ATM curve
    boost::shared_ptr<QuantExt::OptionletStripper> stripper =
        boost::make_shared<QuantExt::OptionletStripper2>(tempStripper, atmVolCurve, Normal);

    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();

    // Create engine
    boost::shared_ptr<BachelierCapFloorEngine> engine =
        boost::make_shared<BachelierCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BachelierCapFloorEngine> flatEngine =
        boost::make_shared<BachelierCapFloorEngine>(vars.yieldCurves.discountEonia, quote, vars.dayCounter);

    // Non-ATM pillar points: check flat cap/foor surface price = stripped optionlet surface price
    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < vars.vols.strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], vars.iborIndex, vars.vols.strikes[j],
                               vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.nVols[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(vars.vols.strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }

    // ATM pillar points: check flat cap/foor surface price = stripped optionlet surface price
    Volatility dummyVol = 0.10;
    boost::shared_ptr<BlackCapFloorEngine> tempEngine =
        boost::make_shared<BlackCapFloorEngine>(vars.yieldCurves.discountEonia, dummyVol);

    for (Size i = 0; i < vars.vols.atmTenors.size(); ++i) {
        // Null strike => cap is set up with ATM strike
        // Need a temp BlackCapFloorEngine for ATM strike to be calculated (bad - should be fixed in QL)
        cap = MakeCapFloor(CapFloor::Cap, vars.vols.atmTenors[i], vars.iborIndex, Null<Rate>(),
                           vars.settlementDays * Days)
                  .withPricingEngine(tempEngine);

        cap->setPricingEngine(engine);
        Real strippedPrice = cap->NPV();

        quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.nAtmVols[i]));
        cap->setPricingEngine(flatEngine);
        Real flatPrice = cap->NPV();

        Real error = std::fabs(strippedPrice - flatPrice);
        BOOST_CHECK_MESSAGE(error < vars.accuracy,
                            "\noption tenor:       " << vars.vols.atmTenors[i] << "\natm strike:         "
                                                     << io::rate(cap->atmRate(**vars.yieldCurves.discountEonia))
                                                     << "\nstripped vol price: " << io::rate(strippedPrice)
                                                     << "\nconstant vol price: " << io::rate(flatPrice)
                                                     << "\nerror:              " << io::rate(error)
                                                     << "\ntolerance:          " << io::rate(vars.accuracy));
    }
}

BOOST_AUTO_TEST_CASE(testUsualShiftedLognormalStrippingWithAtm) {
    BOOST_TEST_MESSAGE("Testing standard stripping of shifted lognormal capfloor vols with overlayed ATM curve...");

    CommonVars vars;

    // EUR cap floor shifted lognormal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.slnVols_2,
                                                             vars.dayCounter);

    // EUR cap floor shifted lognormal ATM curve
    Handle<CapFloorTermVolCurve> atmVolCurve(boost::make_shared<CapFloorTermVolCurve>(
        vars.settlementDays, vars.calendar, vars.bdc, vars.vols.atmTenors, vars.vols.slnAtmVols_2, vars.dayCounter));

    // Create shifted lognormal stripped optionlet surface
    boost::shared_ptr<QuantExt::OptionletStripper1> tempStripper = boost::shared_ptr<OptionletStripper1>(
        new QuantExt::OptionletStripper1(volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter,
                                         vars.yieldCurves.discountEonia, ShiftedLognormal, vars.vols.shift_2));

    // Overlay shifted lognormal ATM curve
    boost::shared_ptr<QuantExt::OptionletStripper> stripper = boost::make_shared<QuantExt::OptionletStripper2>(
        tempStripper, atmVolCurve, ShiftedLognormal, vars.vols.shift_2);

    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();

    // Create engine
    boost::shared_ptr<BlackCapFloorEngine> engine =
        boost::make_shared<BlackCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BlackCapFloorEngine> flatEngine = boost::make_shared<BlackCapFloorEngine>(
        vars.yieldCurves.discountEonia, quote, vars.dayCounter, vars.vols.shift_2);

    // Non-ATM pillar points: check flat cap/foor surface price = stripped optionlet surface price
    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < vars.vols.strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], vars.iborIndex, vars.vols.strikes[j],
                               vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.slnVols_2[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(vars.vols.strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }

    // ATM pillar points: check flat cap/foor surface price = stripped optionlet surface price
    for (Size i = 0; i < vars.vols.atmTenors.size(); ++i) {
        // Null strike => cap is set up with ATM strike
        cap = MakeCapFloor(CapFloor::Cap, vars.vols.atmTenors[i], vars.iborIndex, Null<Rate>(),
                           vars.settlementDays * Days)
                  .withPricingEngine(engine);
        Real strippedPrice = cap->NPV();

        quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.slnAtmVols_2[i]));
        cap->setPricingEngine(flatEngine);
        Real flatPrice = cap->NPV();

        Real error = std::fabs(strippedPrice - flatPrice);
        BOOST_CHECK_MESSAGE(error < vars.accuracy,
                            "\noption tenor:       " << vars.vols.atmTenors[i] << "\natm strike:         "
                                                     << io::rate(cap->atmRate(**vars.yieldCurves.discountEonia))
                                                     << "\nstripped vol price: " << io::rate(strippedPrice)
                                                     << "\nconstant vol price: " << io::rate(flatPrice)
                                                     << "\nerror:              " << io::rate(error)
                                                     << "\ntolerance:          " << io::rate(vars.accuracy));
    }
}

BOOST_AUTO_TEST_CASE(testNormalToShiftedLognormalStrippingWithAt) {
    BOOST_TEST_MESSAGE("Testing stripping of normal capfloor vols with ATM to give shifted lognormal...");

    CommonVars vars;

    // EUR cap floor normal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.nVols,
                                                             vars.dayCounter);

    // EUR cap floor normal ATM curve
    Handle<CapFloorTermVolCurve> atmVolCurve(boost::make_shared<CapFloorTermVolCurve>(
        vars.settlementDays, vars.calendar, vars.bdc, vars.vols.atmTenors, vars.vols.nAtmVols, vars.dayCounter));

    // Create shifted lognormal stripped optionlet surface
    boost::shared_ptr<QuantExt::OptionletStripper1> tempStripper =
        boost::shared_ptr<OptionletStripper1>(new OptionletStripper1(
            volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter, vars.yieldCurves.discountEonia,
            Normal, 0.0, false, ShiftedLognormal, vars.vols.shift_1));

    // Overlay shifted lognormal ATM curve
    boost::shared_ptr<QuantExt::OptionletStripper> stripper =
        boost::make_shared<QuantExt::OptionletStripper2>(tempStripper, atmVolCurve, Normal);

    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();

    // Create engine
    boost::shared_ptr<BlackCapFloorEngine> engine =
        boost::make_shared<BlackCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BachelierCapFloorEngine> flatEngine =
        boost::make_shared<BachelierCapFloorEngine>(vars.yieldCurves.discountEonia, quote, vars.dayCounter);

    // Non-ATM pillar points: check flat cap/foor surface price = stripped optionlet surface price
    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < vars.vols.strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], vars.iborIndex, vars.vols.strikes[j],
                               vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.nVols[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(vars.vols.strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }

    // ATM pillar points: check flat cap/foor surface price = stripped optionlet surface price
    for (Size i = 0; i < vars.vols.atmTenors.size(); ++i) {
        // Null strike => cap is set up with ATM strike
        cap = MakeCapFloor(CapFloor::Cap, vars.vols.atmTenors[i], vars.iborIndex, Null<Rate>(),
                           vars.settlementDays * Days)
                  .withPricingEngine(engine);
        Real strippedPrice = cap->NPV();

        quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.nAtmVols[i]));
        cap->setPricingEngine(flatEngine);
        Real flatPrice = cap->NPV();

        Real error = std::fabs(strippedPrice - flatPrice);
        BOOST_CHECK_MESSAGE(error < vars.accuracy,
                            "\noption tenor:       " << vars.vols.atmTenors[i] << "\natm strike:         "
                                                     << io::rate(cap->atmRate(**vars.yieldCurves.discountEonia))
                                                     << "\nstripped vol price: " << io::rate(strippedPrice)
                                                     << "\nconstant vol price: " << io::rate(flatPrice)
                                                     << "\nerror:              " << io::rate(error)
                                                     << "\ntolerance:          " << io::rate(vars.accuracy));
    }
}

BOOST_AUTO_TEST_CASE(testShiftedLognormalToNormalStrippingWithAtm) {
    BOOST_TEST_MESSAGE("Testing stripping of shifted lognormal capfloor vols with ATM to give normal...");

    CommonVars vars;

    // EUR cap floor shifted lognormal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.slnVols_1,
                                                             vars.dayCounter);

    // EUR cap floor shifted lognormal ATM curve
    Handle<CapFloorTermVolCurve> atmVolCurve(boost::make_shared<CapFloorTermVolCurve>(
        vars.settlementDays, vars.calendar, vars.bdc, vars.vols.atmTenors, vars.vols.slnAtmVols_1, vars.dayCounter));

    // Create normal stripped optionlet surface
    boost::shared_ptr<QuantExt::OptionletStripper1> tempStripper =
        boost::shared_ptr<OptionletStripper1>(new OptionletStripper1(
            volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter, vars.yieldCurves.discountEonia,
            ShiftedLognormal, vars.vols.shift_1, false, Normal, 0.0));

    // Overlay shifted lognormal ATM curve
    boost::shared_ptr<QuantExt::OptionletStripper> stripper = boost::make_shared<QuantExt::OptionletStripper2>(
        tempStripper, atmVolCurve, ShiftedLognormal, vars.vols.shift_1);

    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();

    // Create engine
    boost::shared_ptr<BachelierCapFloorEngine> engine =
        boost::make_shared<BachelierCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BlackCapFloorEngine> flatEngine = boost::make_shared<BlackCapFloorEngine>(
        vars.yieldCurves.discountEonia, quote, vars.dayCounter, vars.vols.shift_1);

    // Non-ATM pillar points: check flat cap/foor surface price = stripped optionlet surface price
    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < vars.vols.strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], vars.iborIndex, vars.vols.strikes[j],
                               vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.slnVols_1[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(vars.vols.strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }

    // ATM pillar points: check flat cap/foor surface price = stripped optionlet surface price
    for (Size i = 0; i < vars.vols.atmTenors.size(); ++i) {
        quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.slnAtmVols_1[i]));
        // Null strike => cap is set up with ATM strike
        cap = MakeCapFloor(CapFloor::Cap, vars.vols.atmTenors[i], vars.iborIndex, Null<Rate>(),
                           vars.settlementDays * Days)
                  .withPricingEngine(flatEngine);
        Real flatPrice = cap->NPV();

        cap->setPricingEngine(engine);
        Real strippedPrice = cap->NPV();

        Real error = std::fabs(strippedPrice - flatPrice);
        BOOST_CHECK_MESSAGE(error < vars.accuracy,
                            "\noption tenor:       " << vars.vols.atmTenors[i] << "\natm strike:         "
                                                     << io::rate(cap->atmRate(**vars.yieldCurves.discountEonia))
                                                     << "\nstripped vol price: " << io::rate(strippedPrice)
                                                     << "\nconstant vol price: " << io::rate(flatPrice)
                                                     << "\nerror:              " << io::rate(error)
                                                     << "\ntolerance:          " << io::rate(vars.accuracy));
    }
}

BOOST_AUTO_TEST_CASE(testShiftedLognormalToShiftedLognormalStrippingWithAtm) {
    BOOST_TEST_MESSAGE("Testing stripping with shifted lognormal vols with ATM with different shifts...");

    CommonVars vars;

    // EUR cap floor shifted lognormal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.slnVols_1,
                                                             vars.dayCounter);

    // EUR cap floor shifted lognormal ATM curve
    Handle<CapFloorTermVolCurve> atmVolCurve(boost::make_shared<CapFloorTermVolCurve>(
        vars.settlementDays, vars.calendar, vars.bdc, vars.vols.atmTenors, vars.vols.slnAtmVols_1, vars.dayCounter));

    // Create shifted lognormal stripped optionlet surface
    boost::shared_ptr<QuantExt::OptionletStripper1> tempStripper =
        boost::shared_ptr<OptionletStripper1>(new OptionletStripper1(
            volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter, vars.yieldCurves.discountEonia,
            ShiftedLognormal, vars.vols.shift_1, false, ShiftedLognormal, vars.vols.shift_2));

    // Overlay shifted lognormal ATM curve
    boost::shared_ptr<QuantExt::OptionletStripper> stripper = boost::make_shared<QuantExt::OptionletStripper2>(
        tempStripper, atmVolCurve, ShiftedLognormal, vars.vols.shift_1);

    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();

    // Create engine
    boost::shared_ptr<BlackCapFloorEngine> engine =
        boost::make_shared<BlackCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BlackCapFloorEngine> flatEngine = boost::make_shared<BlackCapFloorEngine>(
        vars.yieldCurves.discountEonia, quote, vars.dayCounter, vars.vols.shift_1);

    // Non-ATM pillar points: check flat cap/foor surface price = stripped optionlet surface price
    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < vars.vols.strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], vars.iborIndex, vars.vols.strikes[j],
                               vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.slnVols_1[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(vars.vols.strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }

    // ATM pillar points: check flat cap/foor surface price = stripped optionlet surface price
    for (Size i = 0; i < vars.vols.atmTenors.size(); ++i) {
        quote.linkTo(boost::make_shared<SimpleQuote>(vars.vols.slnAtmVols_1[i]));
        // Null strike => cap is set up with ATM strike
        cap = MakeCapFloor(CapFloor::Cap, vars.vols.atmTenors[i], vars.iborIndex, Null<Rate>(),
                           vars.settlementDays * Days)
                  .withPricingEngine(flatEngine);
        Real flatPrice = cap->NPV();

        cap->setPricingEngine(engine);
        Real strippedPrice = cap->NPV();

        Real error = std::fabs(strippedPrice - flatPrice);
        BOOST_CHECK_MESSAGE(error < vars.accuracy,
                            "\noption tenor:       " << vars.vols.atmTenors[i] << "\natm strike:         "
                                                     << io::rate(cap->atmRate(**vars.yieldCurves.discountEonia))
                                                     << "\nstripped vol price: " << io::rate(strippedPrice)
                                                     << "\nconstant vol price: " << io::rate(flatPrice)
                                                     << "\nerror:              " << io::rate(error)
                                                     << "\ntolerance:          " << io::rate(vars.accuracy));
    }
}

BOOST_AUTO_TEST_CASE(testNormalToLognormalGivesError) {
    BOOST_TEST_MESSAGE("Testing stripping of normal to give lognormal gives error (due to negative strike)...");

    CommonVars vars;

    // EUR cap floor normal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, vars.vols.strikes, vars.vols.nVols,
                                                             vars.dayCounter);

    // Create shifted lognormal stripped optionlet surface and Black engine
    boost::shared_ptr<QuantExt::OptionletStripper> stripper = boost::shared_ptr<OptionletStripper1>(
        new QuantExt::OptionletStripper1(volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter,
                                         vars.yieldCurves.discountEonia, Normal, 0.0, false, ShiftedLognormal, 0.0));

    // Error due to negative strike in input matrix
    BOOST_CHECK_THROW(stripper->recalculate(), QuantLib::Error);
}

BOOST_AUTO_TEST_CASE(testNormalToLognormalModifiedGivesError) {
    BOOST_TEST_MESSAGE("Testing stripping of normal to give lognormal gives error (due to negative forward)...");

    CommonVars vars;

    // Create a reduced capfloor matrix with only positive strikes
    vector<Volatility> strikes(vars.vols.strikes.begin() + 2, vars.vols.strikes.end());
    Matrix vols(vars.vols.tenors.size(), strikes.size());
    for (Size i = 0; i < vols.rows(); ++i)
        for (Size j = 0; j < vols.columns(); ++j)
            vols[i][j] = vars.vols.nVols[i][j + 2];

    // EUR cap floor normal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, strikes, vols, vars.dayCounter);

    // Create shifted lognormal stripped optionlet surface and Black engine
    boost::shared_ptr<QuantExt::OptionletStripper> stripper = boost::shared_ptr<OptionletStripper1>(
        new QuantExt::OptionletStripper1(volSurface, vars.iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter,
                                         vars.yieldCurves.discountEonia, Normal, 0.0, false, ShiftedLognormal, 0.0));

    // Error due to forward rates being negative
    BOOST_CHECK_THROW(stripper->recalculate(), QuantLib::Error);
}

BOOST_AUTO_TEST_CASE(testNormalToLognormalWithPositiveForwards) {
    BOOST_TEST_MESSAGE("Testing stripping of normal to give lognormal when forwards are positive...");

    CommonVars vars;

    // Create a reduced capfloor matrix with only positive strikes
    vector<Volatility> strikes(vars.vols.strikes.begin() + 2, vars.vols.strikes.end());
    Matrix vols(vars.vols.tenors.size(), strikes.size());
    for (Size i = 0; i < vols.rows(); ++i)
        for (Size j = 0; j < vols.columns(); ++j)
            vols[i][j] = vars.vols.nVols[i][j + 2];

    // Link ibor index to shifted forward curve
    Handle<Quote> spread(boost::make_shared<SimpleQuote>(0.015));
    Handle<YieldTermStructure> shiftedForward(
        boost::make_shared<ZeroSpreadedTermStructure>(vars.yieldCurves.forward6M, spread));
    boost::shared_ptr<IborIndex> iborIndex = vars.iborIndex->clone(shiftedForward);

    // EUR cap floor normal volatility surface
    boost::shared_ptr<QuantExt::CapFloorTermVolSurface> volSurface =
        boost::make_shared<QuantExt::CapFloorTermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc,
                                                             vars.vols.tenors, strikes, vols, vars.dayCounter);

    // Create shifted lognormal stripped optionlet surface and Black engine
    boost::shared_ptr<QuantExt::OptionletStripper> stripper = boost::shared_ptr<OptionletStripper1>(
        new QuantExt::OptionletStripper1(volSurface, iborIndex, Null<Rate>(), vars.accuracy, vars.maxIter,
                                         vars.yieldCurves.discountEonia, Normal, 0.0, false, ShiftedLognormal, 0.0));
    boost::shared_ptr<StrippedOptionletAdapter> adapter = boost::make_shared<StrippedOptionletAdapter>(stripper);
    Handle<OptionletVolatilityStructure> ovs(adapter);
    ovs->enableExtrapolation();
    boost::shared_ptr<BlackCapFloorEngine> engine =
        boost::make_shared<BlackCapFloorEngine>(vars.yieldCurves.discountEonia, ovs);

    // Price a cap at each pillar point with flat cap/foor surface and stripped optionlet surface and compare price
    boost::shared_ptr<CapFloor> cap;

    RelinkableHandle<Quote> quote(boost::make_shared<SimpleQuote>(0.0));
    boost::shared_ptr<BachelierCapFloorEngine> flatEngine =
        boost::make_shared<BachelierCapFloorEngine>(vars.yieldCurves.discountEonia, quote, vars.dayCounter);

    for (Size i = 0; i < vars.vols.tenors.size(); ++i) {
        for (Size j = 0; j < strikes.size(); ++j) {
            cap = MakeCapFloor(CapFloor::Cap, vars.vols.tenors[i], iborIndex, strikes[j], vars.settlementDays * Days);

            cap->setPricingEngine(engine);
            Real strippedPrice = cap->NPV();

            quote.linkTo(boost::make_shared<SimpleQuote>(vols[i][j]));
            cap->setPricingEngine(flatEngine);
            Real flatPrice = cap->NPV();

            Real error = std::fabs(strippedPrice - flatPrice);
            BOOST_CHECK_MESSAGE(error < vars.accuracy,
                                "\noption tenor:       " << vars.vols.tenors[i]
                                                         << "\nstrike:             " << io::rate(strikes[j])
                                                         << "\nstripped vol price: " << io::rate(strippedPrice)
                                                         << "\nconstant vol price: " << io::rate(flatPrice)
                                                         << "\nerror:              " << io::rate(error)
                                                         << "\ntolerance:          " << io::rate(vars.accuracy));
        }
    }
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


#include <boost/mpl/pair.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/vector_c.hpp>

typedef boost::mpl::vector<
        boost::mpl::pair<
        boost::mpl::vector_c<int, 0, 0>, // inputs
        boost::mpl::vector_c<int, 0>     // target output
        >,
        boost::mpl::pair<
        boost::mpl::vector_c<int, 0, 1>, // inputs
        boost::mpl::vector_c<int, 1>     // target output
        >,
        boost::mpl::pair<
        boost::mpl::vector_c<int, 1, 0>, // inputs
        boost::mpl::vector_c<int, 1>     // target output
        >,
        boost::mpl::pair<
        boost::mpl::vector_c<int, 1, 1>, // inputs
        boost::mpl::vector_c<int, 0>     // target output
        >
    > samples_t;

//    // test ignored
//#include<omnn/omnn.h>
//using namespace omnn::constexp;
//typedef TrainBySamples<samples_t> LearnedXorPerceptron; // test


#ifndef VIGRA_EXPORT_GRAPH_ALGORITHM_VISITOR_HXX
#define VIGRA_EXPORT_GRAPH_ALGORITHM_VISITOR_HXX
//#define NO_IMPORT_ARRAY

/*boost python before anything else*/
#include <boost/python.hpp>

/*std*/
#include <sstream>
#include <string>

/*vigra*/
#include <vigra/numpy_array.hxx>
#include <vigra/numpy_array_converters.hxx>
#include <vigra/graphs.hxx>
#include <vigra/graph_maps.hxx>
#include <vigra/python_graph.hxx>
#include <vigra/graph_algorithms.hxx>
#include <vigra/metrics.hxx>
#include <vigra/multi_gridgraph.hxx>
#include <vigra/error.hxx>
#include <vigra/multi_watersheds.hxx>
namespace python = boost::python;

namespace vigra{

template<class GRAPH>
class LemonGraphAlgorithmVisitor
:   public boost::python::def_visitor<LemonGraphAlgorithmVisitor<GRAPH> >
{
public:

    friend class def_visitor_access;

    typedef GRAPH Graph;

    typedef LemonGraphAlgorithmVisitor<GRAPH> VisitorType;
    // Lemon Graph Typedefs

    typedef typename Graph::index_type       index_type;
    typedef typename Graph::Edge             Edge;
    typedef typename Graph::Node             Node;
    typedef typename Graph::Arc              Arc;

    typedef typename Graph::NodeIt              NodeIt;
    typedef typename Graph::EdgeIt              EdgeIt;
    typedef typename Graph::ArcIt               ArcIt;

    typedef EdgeHolder<Graph> PyEdge;
    typedef NodeHolder<Graph> PyNode;
    typedef  ArcHolder<Graph> PyArc;

    // predefined array (for map usage)
    const static unsigned int EdgeMapDim = IntrinsicGraphShape<Graph>::IntrinsicEdgeMapDimension;
    const static unsigned int NodeMapDim = IntrinsicGraphShape<Graph>::IntrinsicNodeMapDimension;

    typedef NumpyArray<EdgeMapDim,   Singleband<float > > FloatEdgeArray;
    typedef NumpyArray<EdgeMapDim,   Singleband<UInt32> > UInt32EdgeArray;
    typedef NumpyArray<EdgeMapDim,   Singleband<Int32 > > Int32EdgeArray;
    typedef NumpyArray<NodeMapDim,   Singleband<float > > FloatNodeArray;
    typedef NumpyArray<NodeMapDim,   Singleband<UInt32> > UInt32NodeArray;
    typedef NumpyArray<NodeMapDim,   Singleband<Int32 > > Int32NodeArray;
    typedef NumpyArray<NodeMapDim +1,Multiband <float > > MultiFloatNodeArray;
    typedef NumpyArray<EdgeMapDim +1,Multiband <float > > MultiFloatEdgeArray;

    typedef NumpyScalarEdgeMap<Graph,FloatEdgeArray>         FloatEdgeArrayMap;
    typedef NumpyScalarEdgeMap<Graph,UInt32EdgeArray>        UInt32EdgeArrayMap;
    typedef NumpyScalarEdgeMap<Graph,Int32EdgeArray>         Int32EdgeArrayMap;
    typedef NumpyScalarNodeMap<Graph,FloatNodeArray>         FloatNodeArrayMap;
    typedef NumpyScalarNodeMap<Graph,UInt32NodeArray>        UInt32NodeArrayMap;
    typedef NumpyScalarNodeMap<Graph,Int32NodeArray>         Int32NodeArrayMap;
    typedef NumpyMultibandNodeMap<Graph,MultiFloatNodeArray> MultiFloatNodeArrayMap;

    typedef ShortestPathDijkstra<Graph,float> ShortestPathDijkstraType;

    typedef typename GraphDescriptorToMultiArrayIndex<Graph>::IntrinsicNodeMapShape NodeCoordinate;
    typedef NumpyArray<1,NodeCoordinate>  NodeCoorinateArray;

    LemonGraphAlgorithmVisitor(const std::string clsName)
    :clsName_(clsName){

    }

    void exportSegmentationAlgorithms()const{
        python::def("_edgeWeightedWatershedsSegmentation",registerConverters(&pyEdgeWeightedWatershedsSegmentation),
            (
                python::arg("graph"),
                python::arg("edgeWeights"),
                python::arg("seeds"),
                python::arg("out")=python::object()
            ),
            "Seeded watersheds on a edge weighted graph"
        );

        python::def("_nodeWeightedWatershedsSegmentation",registerConverters(&pyNodeWeightedWatershedsSegmentation),
            (
                python::arg("graph"),
                python::arg("nodeWeights"),
                python::arg("seeds"),
                python::arg("method")=std::string("regionGrowing"),
                python::arg("out")=python::object()
            ),
            "Seeded watersheds on a node weighted graph"
        );
        python::def("_nodeWeightedWatershedsSeeds",registerConverters(&pyNodeWeightedWatershedsSeeds),
            (
                python::arg("graph"),
                python::arg("nodeWeights"),
                python::arg("out")=python::object()
            ),
            "Generate seeds for node weighted watersheds"
        );

        python::def("_carvingSegmentation",registerConverters(&pyCarvingSegmentation),
            (
                python::arg("graph"),
                python::arg("edgeWeights"),
                python::arg("seeds"),
                python::arg("backgroundLabel"),
                python::arg("backgroundBias"),
                python::arg("noBiasBelow") = 0.0,
                python::arg("out")=python::object()
            ),
            "Seeded watersheds on a edge weighted graph"
        );

        python::def("_shortestPathSegmentation",registerConverters(&pyShortestPathSegmentation),
            (
                python::arg("graph"),
                python::arg("edgeWeights"),
                python::arg("nodeWeights"),
                python::arg("seeds"),
                python::arg("out")=python::object()
            ),
            "Seeded shorted path segmentation on a edge and node weighted graph"
        );

        python::def("_felzenszwalbSegmentation",registerConverters(&pyFelzenszwalbSegmentation),
            (
                python::arg("graph"),
                python::arg("edgeWeights"),
                python::arg("nodeSizes"),
                python::arg("k")=300.0f,
                python::arg("nodeNumStop")=-1,
                python::arg("out")=python::object()
            ),
            "Felzenwalb graph based segmentation"
        );
    }

    void exportMiscAlgorithms()const{

        python::def("_nodeFeatureDistToEdgeWeight",registerConverters(&pyNodeFeatureDistToEdgeWeight),
            (
                python::arg("graph"),
                python::arg("nodeFeatures"),
                python::arg("metric"),
                python::arg("out")=python::object()
            ),
            "convert node features to edge weights with the given metric"
        );
        python::def("_nodeFeatureSumToEdgeWeight",registerConverters(&pyNodeFeatureSumToEdgeWeight),
            (
                python::arg("graph"),
                python::arg("nodeFeatures"),
                python::arg("out")=python::object()
            ),
            "convert node features to edge weights"
        );

        python::def("_opengmMulticutDataStructure",registerConverters(&pyMulticutDataStructure),
            (
                python::arg("graph"),
                python::arg("edgeWeights")
            )
        );

        python::def("nodeGtToEdgeGt",registerConverters(&pyNodeGtToEdgeGt),
            (
                python::arg("graph"),
                python::arg("nodeGt"),
                python::arg("ignoreLabel"),
                python::arg("out")=python::object()
            )
        );

        python::def("_opengmArgToLabeling",registerConverters(&pyMulticutArgToLabeling),
            (
                python::arg("graph"),
                python::arg("arg"),
                python::arg("out")=python::object()
            )
        );
        python::def("_wardCorrection",registerConverters(&pyWardCorrection),
            (
                python::arg("graph"),
                python::arg("edgeIndicator"),
                python::arg("nodeSize"),
                python::arg("out")=python::object()
            ),
            "apply wards method to an edgeIndicator"
        );
    }

    void exportSmoothingAlgorithms()const{

        python::def("_recursiveGraphSmoothing",registerConverters(&pyRecursiveGraphSmoothing),
            (
                python::arg("graph"),
                python::arg("nodeFeatures"),
                python::arg("edgeIndicator"),
                python::arg("gamma"),
                python::arg("edgeThreshold"),
                python::arg("scale"),
                python::arg("iterations")=1,
                python::arg("outBuffer")=python::object(),
                python::arg("out")=python::object()
            ),
            "recursive edge weighted guided graph smoothing"
        );

    }

    std::string clsName_;
    template <class classT>
    void visit(classT& c) const
    {
        // - watersheds-segmentation
        // - carving-segmentation
        // - felzenwalb-segmentation
        // - labeling
        exportSegmentationAlgorithms();

        // - node Labels (usefull to make grid graph labels from rag labels)
        // - node feature distance to edge weights
        exportMiscAlgorithms();

        // - recursiveGraphSmoothing
        exportSmoothingAlgorithms();
    }

    static NumpyAnyArray pyWardCorrection(
        const Graph &           g,
        const FloatEdgeArray    edgeWeightsArray,
        const FloatNodeArray    nodeSizeArray,
        const float             wardness,
        FloatEdgeArray    outArray
    ){
        outArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(g));

        // numpy arrays => lemon maps
        FloatEdgeArrayMap  edgeWeightsArrayMap(g,edgeWeightsArray);
        FloatNodeArrayMap  nodeSizeArrayMap(g,nodeSizeArray);
        FloatEdgeArrayMap  outArrayMap(g,outArray);

        for(EdgeIt iter(g);iter!=lemon::INVALID;++iter){
            const float uSize=nodeSizeArrayMap[g.u(*iter)];
            const float vSize=nodeSizeArrayMap[g.v(*iter)];
            const float w = edgeWeightsArrayMap[*iter];
            const float ward  = 1.0f/(1.0f/std::log(uSize) + 1.0f/std::log(vSize)  );
            const float wardF = wardness*ward + (1.0-wardness);
            outArrayMap[*iter]=w*wardF;
        }
        return outArray;

    }

    static python::tuple pyMulticutDataStructure(
        const Graph &           g,
        const FloatEdgeArray    edgeWeightsArray
    ){
        UInt32NodeArray toDenseArray( IntrinsicGraphShape<Graph>::intrinsicNodeMapShape(g));

        // numpy arrays => lemon maps
        UInt32NodeArrayMap toDenseArrayMap(g,toDenseArray);
        FloatEdgeArrayMap  edgeWeightsArrayMap(g,edgeWeightsArray);

        NumpyArray<2,UInt32> vis      ((    typename NumpyArray<2,UInt64>::difference_type(g.edgeNum(),2)));
        NumpyArray<1,float > weights  ((    typename NumpyArray<1,double>::difference_type(g.edgeNum()  )));

        size_t denseIndex = 0 ;
        for(NodeIt iter(g);iter!=lemon::INVALID;++iter){
            toDenseArrayMap[*iter]=denseIndex;
            ++denseIndex;
        }
        denseIndex=0;
        for(EdgeIt iter(g);iter!=lemon::INVALID;++iter){
            const size_t dU=toDenseArrayMap[g.u(*iter)];
            const size_t dV=toDenseArrayMap[g.v(*iter)];
            vis(denseIndex,0)=std::min(dU,dV);
            vis(denseIndex,1)=std::max(dU,dV);
            weights(denseIndex)=edgeWeightsArrayMap[*iter];
            ++denseIndex;
        }
        return python::make_tuple(vis,weights);

    }

    static NumpyAnyArray pyNodeGtToEdgeGt(
        const Graph &           g,
        const UInt32NodeArray & nodeGt,
        const Int64 ignoreLabel,
        UInt32EdgeArray edgeGt
    ){
        edgeGt.reshapeIfEmpty(IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(g));

        // numpy arrays => lemon maps
        UInt32NodeArrayMap nodeGtMap(g,nodeGt);
        UInt32EdgeArrayMap  edgeGtMap(g,edgeGt);
        nodeGtToEdgeGt(g, nodeGtMap, ignoreLabel, edgeGtMap);
        return edgeGt;
    }

    static NumpyAnyArray pyMulticutArgToLabeling(
        const Graph &              g,
        const NumpyArray<1,UInt32> arg,
        UInt32NodeArray            labelsArray
    ){
        labelsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicNodeMapShape(g));
        UInt32NodeArrayMap labelsArrayMap(g,labelsArray);
        size_t denseIndex = 0 ;
        for(NodeIt iter(g);iter!=lemon::INVALID;++iter){
            labelsArrayMap[*iter]=arg(denseIndex);
            ++denseIndex;
        }
        return labelsArray;
    }
    /*
    static NumpyAnyArray pyNodeIdsLabels(
        const GRAPH & g,
        NumpyArray<1,Singleband<UInt32> >  nodeIds,
        UInt32NodeArray                    nodeLabelArray,
        NumpyArray<1,Singleband<UInt32> >  out
    ){
        // reshape out
        out.reshapeIfEmpty(nodeIds.shape());

        // numpy arrays => lemon maps
        UInt32NodeArrayMap nodeLabelArrayMap(g,nodeLabelArray);

        for(size_t i=0;i<nodeIds.shape(0);++i)
            out(i)=nodeLabelArrayMap[g.nodeFromId(nodeIds(i))];
        return out;
    }

    static NumpyAnyArray pyNodeIdsFeatures(
        const GRAPH & g,
        NumpyArray<1,Singleband<UInt32> >  nodeIds,
        MultiFloatNodeArray                nodeFeaturesArray,
        NumpyArray<2,Multiband<float >  >  out
    ){
        //  reshape out ?
        typename NumpyArray<2,Multiband<float> >::difference_type outShape(nodeIds.shape(0),nodeFeaturesArray.shape(NodeMapDim));
        out.reshapeIfEmpty(  NumpyArray<2,Multiband<float >  >::ArrayTraits::taggedShape(outShape,"xc"));

        // numpy arrays => lemon maps
        MultiFloatNodeArrayMap nodeFeaturesArrayMap(g,nodeFeaturesArray);

        typedef typename  NumpyArray<1,int>::difference_type Coord1;
        for(size_t i=0;i<nodeIds.shape(0);++i)
            out[Coord1(i)]=nodeFeaturesArrayMap[g.nodeFromId(nodeIds(i))];
        return out;
    }
    */
    static NumpyAnyArray pyNodeFeatureDistToEdgeWeight(
        const GRAPH & g,
        const MultiFloatNodeArray & nodeFeaturesArray,
        const std::string & functor,
        FloatEdgeArray edgeWeightsArray
    ){
        edgeWeightsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(g) );

        if(functor==std::string("euclidean") || functor==std::string("norm") || functor==std::string("l2")){
            typedef  metrics::Norm<float> DistFunctor;
            DistFunctor f;
            return pyNodeFeatureDistToEdgeWeightT<DistFunctor>(g,nodeFeaturesArray,f,edgeWeightsArray);
        }
        if(functor==std::string("squaredNorm")){
            typedef  metrics::SquaredNorm<float> DistFunctor;
            DistFunctor f;
            return pyNodeFeatureDistToEdgeWeightT<DistFunctor>(g,nodeFeaturesArray,f,edgeWeightsArray);
        }
        else if (functor==std::string("manhattan") || functor==std::string("l1")){
            typedef  metrics::Manhattan<float> DistFunctor;
            DistFunctor f;
            return pyNodeFeatureDistToEdgeWeightT<DistFunctor>(g,nodeFeaturesArray,f,edgeWeightsArray);
        }
        else if (functor==std::string("chiSquared")){
            typedef  metrics::ChiSquared<float> DistFunctor;
            DistFunctor f;
            return pyNodeFeatureDistToEdgeWeightT<DistFunctor>(g,nodeFeaturesArray,f,edgeWeightsArray);
        }
        else{
            throw std::runtime_error(
                "distance not supported\n"
                "supported distance types:\n"
                "- euclidean/norm/l2\n"
                "- squaredNorm\n"
                "- manhattan/l1\n"
                "- chiSquared\n"
            );
        }
    }

    static NumpyAnyArray pyNodeFeatureSumToEdgeWeight(
        const GRAPH & g,
        const FloatNodeArray & nodeFeaturesArray,
        FloatEdgeArray edgeWeightsArray
    ){
        // reshape out?
        edgeWeightsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(g) );

        // numpy arrays => lemon maps
        FloatNodeArrayMap  nodeFeatureArrayMap(g,nodeFeaturesArray);
        FloatEdgeArrayMap  edgeWeightsArrayMap(g,edgeWeightsArray);

        for(EdgeIt e(g);e!=lemon::INVALID;++e){
            const Edge edge(*e);
            const Node u=g.u(edge);
            const Node v=g.v(edge);
            edgeWeightsArrayMap[edge]=nodeFeatureArrayMap[u]+nodeFeatureArrayMap[v];
        }
        return edgeWeightsArray;
    }

    template<class FUNCTOR>
    static NumpyAnyArray pyNodeFeatureDistToEdgeWeightT(
        const GRAPH & g,
        const MultiFloatNodeArray & nodeFeaturesArray,
        FUNCTOR & functor,
        FloatEdgeArray edgeWeightsArray
    ){
        // reshape out?
        edgeWeightsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(g) );

        // numpy arrays => lemon maps
        MultiFloatNodeArrayMap nodeFeatureArrayMap(g,nodeFeaturesArray);
        FloatEdgeArrayMap      edgeWeightsArrayMap(g,edgeWeightsArray);

        for(EdgeIt e(g);e!=lemon::INVALID;++e){
            const Edge edge(*e);
            const Node u=g.u(edge);
            const Node v=g.v(edge);
            edgeWeightsArrayMap[edge]=functor(nodeFeatureArrayMap[u],nodeFeatureArrayMap[v]);
        }
        return edgeWeightsArray;
    }

    static NumpyAnyArray pyEdgeWeightedWatershedsSegmentation(
        const GRAPH & g,
        FloatEdgeArray edgeWeightsArray,
        UInt32NodeArray seedsArray,
        UInt32NodeArray labelsArray
    ){
        // resize output ?
        labelsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicNodeMapShape(g) );

        // numpy arrays => lemon maps
        FloatEdgeArrayMap edgeWeightsArrayMap(g,edgeWeightsArray);
        UInt32NodeArrayMap seedsArrayMap(g,seedsArray);
        UInt32NodeArrayMap labelsArrayMap(g,labelsArray);

        // call algorithm itself
        edgeWeightedWatershedsSegmentation(g,edgeWeightsArrayMap,seedsArrayMap,labelsArrayMap);

        // retun labels
        return labelsArray;
    }

    static NumpyAnyArray pyNodeWeightedWatershedsSegmentation(
        const Graph &       g,
        FloatNodeArray      nodeWeightsArray,
        UInt32NodeArray     seedsArray,
        const std::string & method,
        UInt32NodeArray     labelsArray
    ){

        // resize output ?
        labelsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicNodeMapShape(g) );

        WatershedOptions watershedsOption;
        if(method==std::string("regionGrowing"))
            watershedsOption.regionGrowing();
        else
            watershedsOption.unionFind();

        // numpy arrays => lemon maps
        FloatNodeArrayMap  nodeWeightsArrayMap(g,nodeWeightsArray);
        UInt32NodeArrayMap labelsArrayMap(g,labelsArray);

        std::copy(seedsArray.begin(),seedsArray.end(),labelsArray.begin());

        //lemon_graph::graph_detail::generateWatershedSeeds(g, nodeWeightsArrayMap, labelsArrayMap, watershedsOption.seed_options);
        lemon_graph::watershedsGraph(g, nodeWeightsArrayMap, labelsArrayMap, watershedsOption);
        //lemon_graph::graph_detail::seededWatersheds(g, nodeWeightsArrayMap, seedsArrayMap, watershedsOption);

        return labelsArray;
    }

    static NumpyAnyArray pyNodeWeightedWatershedsSeeds(
        const Graph &       g,
        FloatNodeArray      nodeWeightsArray,
        UInt32NodeArray     seedsArray
    ){
        const std::string method="regionGrowing";
        // resize output ?
        seedsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicNodeMapShape(g) );

        WatershedOptions watershedsOption;
        if(method==std::string("regionGrowing"))
            watershedsOption.regionGrowing();

        // numpy arrays => lemon maps
        FloatNodeArrayMap  nodeWeightsArrayMap(g,nodeWeightsArray);
        UInt32NodeArrayMap seedsArrayMap(g,seedsArray);

        lemon_graph::graph_detail::generateWatershedSeeds(g, nodeWeightsArrayMap, seedsArrayMap, watershedsOption.seed_options);

        return seedsArray;
    }

    static NumpyAnyArray pyCarvingSegmentation(
        const GRAPH & g,
        FloatEdgeArray edgeWeightsArray,
        UInt32NodeArray seedsArray,
        const UInt32    backgroundLabel,
        const float     backgroundBias,
        const float     noBiasBelow,
        UInt32NodeArray labelsArray
    ){
        // resize output ?
        labelsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicNodeMapShape(g) );

        // numpy arrays => lemon maps
        FloatEdgeArrayMap edgeWeightsArrayMap(g,edgeWeightsArray);
        UInt32NodeArrayMap seedsArrayMap(g,seedsArray);
        UInt32NodeArrayMap labelsArrayMap(g,labelsArray);

        // call algorithm itself
        carvingSegmentation(g,edgeWeightsArrayMap,seedsArrayMap,backgroundLabel,backgroundBias,noBiasBelow,labelsArrayMap);

        // retun labels
        return labelsArray;
    }

    static NumpyAnyArray pyShortestPathSegmentation(
        const Graph &       g,
        FloatEdgeArray      edgeWeightsArray,
        FloatNodeArray      nodeWeightsArray,
        UInt32NodeArray     seedsArray,
        UInt32NodeArray     labelsArray
    ){

        // resize output ?
        labelsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicNodeMapShape(g) );

        // numpy arrays => lemon maps
        FloatEdgeArrayMap  edgeWeightsArrayMap(g,edgeWeightsArray);
        FloatNodeArrayMap  nodeWeightsArrayMap(g,nodeWeightsArray);
        UInt32NodeArrayMap labelsArrayMap(g,labelsArray);

        std::copy(seedsArray.begin(),seedsArray.end(),labelsArray.begin());

        shortestPathSegmentation<
            Graph,FloatEdgeArrayMap, FloatNodeArrayMap, UInt32NodeArrayMap, float
        >(g, edgeWeightsArrayMap, nodeWeightsArrayMap, labelsArrayMap);

        return labelsArray;
    }

    static NumpyAnyArray pyFelzenszwalbSegmentation(
        const GRAPH & g,
        FloatEdgeArray edgeWeightsArray,
        FloatNodeArray nodeSizesArray,
        const float k,
        const int nodeNumStop,
        UInt32NodeArray labelsArray
    ){
        // resize output ?
        labelsArray.reshapeIfEmpty(  IntrinsicGraphShape<Graph>::intrinsicNodeMapShape(g) );

        // numpy arrays => lemon maps
        FloatEdgeArrayMap  edgeWeightsArrayMap(g,edgeWeightsArray);
        FloatNodeArrayMap  nodeSizesArrayMap(g,nodeSizesArray);
        UInt32NodeArrayMap labelsArrayMap(g,labelsArray);

        // call algorithm itself
        felzenszwalbSegmentation(g,edgeWeightsArrayMap,nodeSizesArrayMap,k,labelsArrayMap,nodeNumStop);

        // retun labels
        return labelsArray;
    }

    static NumpyAnyArray pyRecursiveGraphSmoothing(
        const GRAPH & g,
        MultiFloatNodeArray nodeFeaturesArray,
        FloatEdgeArray      edgeIndicatorArray,
        const float         lambda,
        const float         edgeThreshold,
        const float         scale,
        const size_t        iterations,
        MultiFloatNodeArray nodeFeaturesBufferArray,
        MultiFloatNodeArray nodeFeaturesOutArray
    ){
        TaggedShape inShape  = nodeFeaturesArray.taggedShape();
        TaggedShape outShape = TaggedGraphShape<Graph>::taggedNodeMapShape(g);
        if(inShape.hasChannelAxis()){
            outShape.setChannelCount(inShape.channelCount());
        }
        nodeFeaturesBufferArray.reshapeIfEmpty(outShape);
        nodeFeaturesOutArray.reshapeIfEmpty(outShape);

        // numpy arrays => lemon maps
        MultiFloatNodeArrayMap nodeFeaturesArrayMap(g,nodeFeaturesArray);
        FloatEdgeArrayMap edgeIndicatorArrayMap(g,edgeIndicatorArray);
        MultiFloatNodeArrayMap nodeFeaturesBufferArrayMap(g,nodeFeaturesBufferArray);
        MultiFloatNodeArrayMap nodeFeaturesOutArrayMap(g,nodeFeaturesOutArray);

        // call algorithm itself
        recursiveGraphSmoothing(g,nodeFeaturesArrayMap,edgeIndicatorArrayMap,lambda,edgeThreshold,scale,iterations,nodeFeaturesBufferArrayMap,nodeFeaturesOutArrayMap);

        // retun smoothed features
        return nodeFeaturesOutArray;
    }

};

template<class GRAPH>
class LemonGridGraphAlgorithmAddonVisitor
:   public boost::python::def_visitor<LemonGridGraphAlgorithmAddonVisitor<GRAPH> >
{
public:

    friend class def_visitor_access;

    typedef GRAPH Graph;

    typedef LemonGraphAlgorithmVisitor<GRAPH> VisitorType;
    // Lemon Graph Typedefs

    typedef typename Graph::index_type       index_type;
    typedef typename Graph::Edge             Edge;
    typedef typename Graph::Node             Node;
    typedef typename Graph::Arc              Arc;

    typedef typename Graph::NodeIt              NodeIt;
    typedef typename Graph::EdgeIt              EdgeIt;
    typedef typename Graph::ArcIt               ArcIt;

    typedef EdgeHolder<Graph> PyEdge;
    typedef NodeHolder<Graph> PyNode;
    typedef  ArcHolder<Graph> PyArc;

    // predefined array (for map usage)
    const static unsigned int EdgeMapDim = IntrinsicGraphShape<Graph>::IntrinsicEdgeMapDimension;
    const static unsigned int NodeMapDim = IntrinsicGraphShape<Graph>::IntrinsicNodeMapDimension;

    typedef NumpyArray<EdgeMapDim,   Singleband<float > > FloatEdgeArray;
    typedef NumpyArray<NodeMapDim,   Singleband<float > > FloatNodeArray;
    typedef NumpyArray<NodeMapDim,   Singleband<UInt32> > UInt32NodeArray;
    typedef NumpyArray<NodeMapDim,   Singleband<Int32 > > Int32NodeArray;
    typedef NumpyArray<NodeMapDim +1,Multiband <float > > MultiFloatNodeArray;
    typedef NumpyArray<EdgeMapDim +1,Multiband <float > > MultiFloatEdgeArray;

    typedef NumpyScalarEdgeMap<Graph,FloatEdgeArray>         FloatEdgeArrayMap;
    typedef NumpyScalarNodeMap<Graph,FloatNodeArray>         FloatNodeArrayMap;
    typedef NumpyScalarNodeMap<Graph,UInt32NodeArray>        UInt32NodeArrayMap;
    typedef NumpyScalarNodeMap<Graph,Int32NodeArray>         Int32NodeArrayMap;
    typedef NumpyMultibandNodeMap<Graph,MultiFloatNodeArray> MultiFloatNodeArrayMap;
    typedef NumpyMultibandEdgeMap<Graph,MultiFloatEdgeArray> MultiFloatEdgeArrayMap;

    typedef ShortestPathDijkstra<Graph,float> ShortestPathDijkstraType;

    typedef typename GraphDescriptorToMultiArrayIndex<Graph>::IntrinsicNodeMapShape NodeCoordinate;
    typedef NumpyArray<1,NodeCoordinate>  NodeCoorinateArray;

    LemonGridGraphAlgorithmAddonVisitor(const std::string & clsName){}

    template <class classT>
    void visit(classT& c) const
    {

        // - edge weights from interpolated image
        exportMiscAlgorithms(c);

    }

    template <class classT>
    void exportMiscAlgorithms(classT & c)const{

        python::def("edgeFeaturesFromInterpolatedImage",registerConverters(&pyEdgeWeightsFromInterpolatedImage),
            (
                python::arg("graph"),
                python::arg("image"),
                python::arg("out")=python::object()
            ),
            "convert an image with with ``shape = graph.shape*2 - 1`` to an edge weight array"
        );

        python::def("edgeFeaturesFromImage",registerConverters(&pyEdgeWeightsFromImage),
            (
                python::arg("graph"),
                python::arg("image"),
                python::arg("out")=python::object()
            ),
            "convert an image with with shape = graph.shape OR shape = graph.shape *2 -1 to an edge weight array"
        );

        python::def("edgeFeaturesFromImage",registerConverters(&pyEdgeWeightsFromImageMb),
            (
                python::arg("graph"),
                python::arg("image"),
                python::arg("out")=python::object()
            ),
            "convert an image with with shape = graph.shape OR shape = graph.shape *2 -1 to an edge weight array"
        );

        c
        .def("affiliatedEdgesSerializationSize",&pyAffiliatedEdgesSerializationSize,
            (
                python::arg("rag"),
                python::arg("affiliatedEdges")
            )
        );

        //'python::def("edgeFeaturesFromInterpolatedImageCorrected",registerConverters(&pyEdgeWeightsFromInterpolatedImageCorrected),
        //'    (
        //'        python::arg("graph"),
        //'        python::arg("image"),
        //'        python::arg("out")=python::object()
        //'    ),
        //'    "convert an image with with shape = graph.shape *2 -1 to an edge weight array"
        //'    ""
        //');
    }

    static size_t pyAffiliatedEdgesSerializationSize(
        const GRAPH & gridGraph,
        const AdjacencyListGraph & rag,
        const typename AdjacencyListGraph:: template EdgeMap< std::vector<Edge> > & affiliatedEdges
    ){
        return affiliatedEdgesSerializationSize(gridGraph, rag, affiliatedEdges);
    }

    static NumpyAnyArray pyEdgeWeightsFromImage(
        const GRAPH & g,
        const FloatNodeArray & image,
        FloatEdgeArray edgeWeightsArray
    ){

        bool regularShape=true;
        bool topologicalShape=true;

        for(size_t d=0;d<NodeMapDim;++d){
            if(image.shape(d)!=g.shape()[d]){
                regularShape=false;
            }
            if(image.shape(d)!=2*g.shape()[d]-1){
                topologicalShape=false;
            }
        }

        if(regularShape)
            return pyEdgeWeightsFromOrginalSizeImage(g,image,edgeWeightsArray);
        else if(topologicalShape)
            return pyEdgeWeightsFromInterpolatedImage(g,image,edgeWeightsArray);
        else{
            vigra_precondition(false, "shape of edge image does not match graph shape");
            // to avid no return warnings
            return pyEdgeWeightsFromOrginalSizeImage(g,image,edgeWeightsArray);
        }
    }

    static NumpyAnyArray pyEdgeWeightsFromImageMb(
        const GRAPH & g,
        const MultiFloatNodeArray & image,
        MultiFloatEdgeArray edgeWeightsArray
    ){

        bool regularShape=true;
        bool topologicalShape=true;

        for(size_t d=0;d<NodeMapDim;++d){
            if(image.shape(d)!=g.shape()[d]){
                regularShape=false;
            }
            if(image.shape(d)!=2*g.shape()[d]-1){
                topologicalShape=false;
            }
        }

        if(regularShape)
            return pyEdgeWeightsFromOrginalSizeImageMb(g,image,edgeWeightsArray);
        else if(topologicalShape)
            return pyEdgeWeightsFromInterpolatedImageMb(g,image,edgeWeightsArray);
        else{
            vigra_precondition(false, "shape of edge image does not match graph shape");
            // to avid no return warnings
            return pyEdgeWeightsFromOrginalSizeImageMb(g,image,edgeWeightsArray);
        }
    }

    static NumpyAnyArray pyEdgeWeightsFromInterpolatedImage(
        const GRAPH & g,
        const FloatNodeArray & interpolatedImage,
        FloatEdgeArray edgeWeightsArray
    ){

        for(size_t d=0;d<NodeMapDim;++d){
            //std::cout<<"is "<<interpolatedImage.shape(d)<<"gs "<<2*g.shape()[d]-1<<"\n";
            vigra_precondition(interpolatedImage.shape(d)==2*g.shape()[d]-1, "interpolated shape must be shape*2 -1");
        }
        edgeWeightsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(g) );

        // numpy arrays => lemon maps
        FloatEdgeArrayMap edgeWeightsArrayMap(g,edgeWeightsArray);
        typedef typename FloatNodeArray::difference_type CoordType;
        for(EdgeIt iter(g); iter!=lemon::INVALID; ++ iter){

            const Edge edge(*iter);
            const CoordType uCoord(g.u(edge));
            const CoordType vCoord(g.v(edge));
            const CoordType tCoord = uCoord+vCoord;
            edgeWeightsArrayMap[edge]=interpolatedImage[tCoord];
        }
        return edgeWeightsArray;
    }

    static NumpyAnyArray pyEdgeWeightsFromOrginalSizeImage(
        const GRAPH & g,
        const FloatNodeArray & image,
        FloatEdgeArray edgeWeightsArray
    ){

        for(size_t d=0;d<NodeMapDim;++d){
            //std::cout<<"is "<<image.shape(d)<<"gs "<<2*g.shape()[d]-1<<"\n";
            vigra_precondition(image.shape(d)==g.shape()[d], "interpolated shape must be shape*2 -1");
        }
        edgeWeightsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(g) );

        // numpy arrays => lemon maps
        FloatEdgeArrayMap edgeWeightsArrayMap(g,edgeWeightsArray);
        typedef typename FloatNodeArray::difference_type CoordType;
        for(EdgeIt iter(g); iter!=lemon::INVALID; ++ iter){

            const Edge edge(*iter);
            const CoordType uCoord(g.u(edge));
            const CoordType vCoord(g.v(edge));
            edgeWeightsArrayMap[edge]=(image[uCoord]+image[vCoord])/2.0;
        }
        return edgeWeightsArray;
    }

    static NumpyAnyArray pyEdgeWeightsFromInterpolatedImageMb(
        const GRAPH & g,
        const MultiFloatNodeArray & interpolatedImage,
        MultiFloatEdgeArray edgeWeightsArray
    ){

        for(size_t d=0;d<NodeMapDim;++d){
            //std::cout<<"is "<<interpolatedImage.shape(d)<<"gs "<<2*g.shape()[d]-1<<"\n";
            vigra_precondition(interpolatedImage.shape(d)==2*g.shape()[d]-1, "interpolated shape must be shape*2 -1");
        }

        // resize out
        typename MultiArray<EdgeMapDim+1,int>::difference_type outShape;
        for(size_t d=0;d<EdgeMapDim;++d){
            outShape[d]=IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(g)[d];
        }
        outShape[EdgeMapDim] = interpolatedImage.shape(NodeMapDim);

        edgeWeightsArray.reshapeIfEmpty(   MultiFloatEdgeArray::ArrayTraits::taggedShape(outShape,"nc") );

        // numpy arrays => lemon maps
        MultiFloatEdgeArrayMap edgeWeightsArrayMap(g,edgeWeightsArray);
        typedef typename FloatNodeArray::difference_type CoordType;
        for(EdgeIt iter(g); iter!=lemon::INVALID; ++ iter){

            const Edge edge(*iter);
            const CoordType uCoord(g.u(edge));
            const CoordType vCoord(g.v(edge));
            const CoordType tCoord = uCoord+vCoord;
            edgeWeightsArrayMap[edge]=interpolatedImage[tCoord];
        }
        return edgeWeightsArray;
    }

    static NumpyAnyArray pyEdgeWeightsFromOrginalSizeImageMb(
        const GRAPH & g,
        const MultiFloatNodeArray & image,
        MultiFloatEdgeArray edgeWeightsArray
    ){

        for(size_t d=0;d<NodeMapDim;++d){
            //std::cout<<"is "<<image.shape(d)<<"gs "<<2*g.shape()[d]-1<<"\n";
            vigra_precondition(image.shape(d)==g.shape()[d], "interpolated shape must be shape*2 -1");
        }

        // resize out
        typename MultiArray<EdgeMapDim+1,int>::difference_type outShape;
        for(size_t d=0;d<EdgeMapDim;++d){
            outShape[d]=IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(g)[d];
        }
        outShape[EdgeMapDim] = image.shape(NodeMapDim);

        //edgeWeightsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(outShape),"ec" );

        edgeWeightsArray.reshapeIfEmpty(   MultiFloatEdgeArray::ArrayTraits::taggedShape(outShape,"nc") );

        // numpy arrays => lemon maps
        MultiFloatEdgeArrayMap edgeWeightsArrayMap(g,edgeWeightsArray);
        typedef typename FloatNodeArray::difference_type CoordType;
        for(EdgeIt iter(g); iter!=lemon::INVALID; ++ iter){

            const Edge edge(*iter);
            const CoordType uCoord(g.u(edge));
            const CoordType vCoord(g.v(edge));
            MultiArray<1, float>  val = image[uCoord];
            val+=image[vCoord];
            val/=2.0;
            edgeWeightsArrayMap[edge]=val;
        }
        return edgeWeightsArray;
    }

    /*
    static NumpyAnyArray pyEdgeWeightsFromInterpolatedImageCorrected(
        const GRAPH & g,
        const FloatNodeArray & interpolatedImage,
        FloatEdgeArray edgeWeightsArray
    ){

        for(size_t d=0;d<NodeMapDim;++d){
            //std::cout<<"is "<<interpolatedImage.shape(d)<<"gs "<<2*g.shape()[d]-1<<"\n";
            vigra_precondition(interpolatedImage.shape(d)==2*g.shape()[d]-1, "interpolated shape must be shape*2 -1");
        }

        edgeWeightsArray.reshapeIfEmpty( IntrinsicGraphShape<Graph>::intrinsicEdgeMapShape(g) );

        // numpy arrays => lemon maps
        FloatEdgeArrayMap edgeWeightsArrayMap(g,edgeWeightsArray);
        typedef typename FloatNodeArray::difference_type CoordType;
        for(EdgeIt iter(g); iter!=lemon::INVALID; ++ iter){

            const Edge edge(*iter);
            const CoordType uCoord(g.u(edge));
            const CoordType vCoord(g.v(edge));
            const CoordType tCoord = uCoord+vCoord;
            int diffCounter = 0;
            for(int i=0; i<NodeMapDim; ++i) {
                if (uCoord[i] != vCoord[i]) {
                    diffCounter++;
                }
            }
            edgeWeightsArrayMap[edge]=sqrt(diffCounter)*interpolatedImage[tCoord];
        }
        return edgeWeightsArray;
    }
    */
};

} // end namespace vigra

#endif // VIGRA_EXPORT_GRAPH_ALGORITHM_VISITOR_HXX


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG, www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also tools/license/license.mtl.txt in the distribution.

#ifndef ITL_DEFAULT_ITERATION_INCLUDE
#define ITL_DEFAULT_ITERATION_INCLUDE

#include <boost/numeric/itl/iteration/cyclic_iteration.hpp>

namespace itl {

/// Default cyclic iteration
/** max. 1000 iterations, relative tolerance 1e-10, absolute tolerance 0, set to quite. **/
template <class Real>
inline cyclic_iteration<Real> default_iteration()
{
    cyclic_iteration<Real> i(Real(0), 100, Real(1e-10), Real(0));
    i.set_quite(true);
    return i;
}

} // namespace itl

#endif // ITL_DEFAULT_ITERATION_INCLUDE


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

/*
 * test-mask-values.c
 *
 *  Created on: Dec 4, 2013
 *      Author: David A. Boyuka II
 */

#include <cstdio>
#include <vector>
#include <boost/smart_ptr.hpp>

#include "pique/region/cblq/cblq.hpp"
#include "pique/region/cblq/cblq-encode.hpp"
#include "pique/setops/setops.hpp"
#include "pique/setops/cblq/cblq-setops.hpp"

template<typename SetOps>
void do_test() {
    CBLQRegionEncoder<2> builder(CBLQRegionEncoderConfig(true), 64);

    builder.push_bits(3, true);
    builder.push_bits(4, false);
    builder.push_bits(2, true);
    builder.push_bits(7, false);

    builder.push_bits(24, true);
    builder.push_bits(7, false);
    builder.push_bits(1, true);

    // Try consecutive 1-pushes
    builder.push_bits(16, true);
    builder.finalize();

    boost::shared_ptr< CBLQRegionEncoding<2> > region = builder.to_region_encoding();

    region->dump();
    printf("\n\n");
    // Expected:
    // Level 0: 2121
    // Level 1: 2220 1102
    // Level 2:
    // Dense suffix: 1110 0001 1000 0001

    CBLQRegionEncoder<2> builder2(CBLQRegionEncoderConfig(true), 64);

    builder2.push_bits(7, true);
    builder2.push_bits(2, false);
    builder2.push_bits(7, true);

    builder2.push_bits(16, false);

    builder2.push_bits(32, false);
    builder2.finalize();

    boost::shared_ptr< CBLQRegionEncoding<2> > region2 = builder2.to_region_encoding();

    region2->dump();
    printf("\n\n");
    // Expected:
    // Level 0: 2000
    // Level 1: 1221
    // Level 2:
    // Dense suffix: 1110 0111

    std::vector< boost::shared_ptr< CBLQRegionEncoding<2> > > regions;
    regions.push_back(region);
    regions.push_back(region2);

    SetOps setops(CBLQSetOperationsConfig(false));
    SetOps setops_compact(CBLQSetOperationsConfig(true));

    boost::shared_ptr< const CBLQRegionEncoding<2> > iregion = setops.nary_set_op(regions.begin(), regions.end(), NArySetOperation::INTERSECTION);
    printf("Intersect:\n");
    iregion->dump();
    printf("\n\n");
    // Expected:
    // Level 0: 2000
    // Level 1: 2220
    // Level 2:
    // Dense suffix: 1110 0000 0000

    boost::shared_ptr< const CBLQRegionEncoding<2> > icregion = setops_compact.nary_set_op(regions.begin(), regions.end(), NArySetOperation::INTERSECTION);
    printf("Intersect-compact:\n");
    icregion->dump();
    printf("\n\n");
    // Expected:
    // Level 0: 2000
    // Level 1: 2000
    // Level 2:
    // Dense suffix: 1110
}

int main(int argc, char **argv) {
	//do_test< CBLQSetOperationsNAry2Dense<2> >();
	do_test< CBLQSetOperationsNAry3Dense<2> >();
}


// Copyright 2019 Peter Dimov
//
// Distributed under the Boost Software License, Version 1.0.
//
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt

#if defined(ONLY_V2)
# define NO_BV
# define NO_SV
#endif

#if defined(ONLY_BV)
# define NO_V2
# define NO_SV
#endif

#if defined(ONLY_SV)
# define NO_V2
# define NO_BV
#endif

#if !defined(NO_V2)
#include <boost/variant2/variant.hpp>
#endif

#if !defined(NO_BV)
#include <boost/variant.hpp>
#endif

#if !defined(NO_SV)
#include <variant>
#endif

#include <type_traits>
#include <chrono>
#include <iostream>
#include <iomanip>
#include <vector>
#include <map>
#include <string>
#include <stdexcept>

template<class T> struct is_numeric: std::integral_constant<bool, std::is_integral<T>::value || std::is_floating_point<T>::value>
{
};

template<class T, class U> struct have_addition: std::integral_constant<bool, is_numeric<T>::value && is_numeric<U>::value>
{
};

template<class T, class U, class E = std::enable_if_t<have_addition<T, U>::value>> auto add( T const& t, U const& u )
{
    return t + u;
}

template<class T, class U, class E = std::enable_if_t<!have_addition<T, U>::value>> double add( T const& /*t*/, U const& /*u*/ )
{
    throw std::logic_error( "Invalid addition" );
}

inline double to_double( double const& v )
{
    return v;
}

#if !defined(NO_V2)

template<class... T> boost::variant2::variant<T...> operator+( boost::variant2::variant<T...> const& v1, boost::variant2::variant<T...> const& v2 )
{
    return visit( [&]( auto const& x1, auto const & x2 ) -> boost::variant2::variant<T...> { return add( x1, x2 ); }, v1, v2 );
}

template<class... T> double to_double( boost::variant2::variant<T...> const& v )
{
    return boost::variant2::get<double>( v );
}

#endif

#if !defined(NO_BV)

template<class... T> boost::variant<T...> operator+( boost::variant<T...> const& v1, boost::variant<T...> const& v2 )
{
    return boost::apply_visitor( [&]( auto const& x1, auto const & x2 ) -> boost::variant<T...> { return add( x1, x2 ); }, v1, v2 );
}

template<class... T> double to_double( boost::variant<T...> const& v )
{
    return boost::get<double>( v );
}

#endif

#if !defined(NO_SV)

template<class... T> std::variant<T...> operator+( std::variant<T...> const& v1, std::variant<T...> const& v2 )
{
    return visit( [&]( auto const& x1, auto const & x2 ) -> std::variant<T...> { return add( x1, x2 ); }, v1, v2 );
}

template<class... T> double to_double( std::variant<T...> const& v )
{
    return std::get<double>( v );
}

#endif

template<class V> void test_( long long N )
{
    std::vector<V> w;
    // lack of reserve is deliberate

    auto tp1 = std::chrono::high_resolution_clock::now();

    for( long long i = 0; i < N; ++i )
    {
        V v;

        if( i % 7 == 0 )
        {
            v = i / 7;
        }
        else
        {
            v = i / 7.0;
        }

        w.push_back( v );
    }

    V s = 0.0;

    for( long long i = 0; i < N; ++i )
    {
        s = s + w[ i ];
    }

    auto tp2 = std::chrono::high_resolution_clock::now();

    std::cout << std::setw( 6 ) << std::chrono::duration_cast<std::chrono::milliseconds>( tp2 - tp1 ).count() << " ms; S=" << to_double( s ) << "\n";
}

template<class... T> void test( long long N )
{
    std::cout << "N=" << N << ":\n";

    std::cout << "        double: "; test_<double>( N );
#if !defined(NO_V2)
    std::cout << "      variant2: "; test_<boost::variant2::variant<T...>>( N );
#endif
#if !defined(NO_BV)
    std::cout << "boost::variant: "; test_<boost::variant<T...>>( N );
#endif
#if !defined(NO_SV)
    std::cout << "  std::variant: "; test_<std::variant<T...>>( N );
#endif

    std::cout << '\n';
}

int main()
{
    long long const N = 100'000'000LL;

    test<long long, double>( N );
    test<std::nullptr_t, long long, double, std::string, std::vector<std::string>, std::map<std::string, std::string>>( N );
}