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

#ifndef poplibs_test_FullyConnected_hpp
#define poplibs_test_FullyConnected_hpp

#include <boost/multi_array.hpp>

namespace poplibs_test {
namespace fc {

void fullyConnected(const boost::multi_array<double, 3> &in,
                    const boost::multi_array<double, 3> &weights,
                    const boost::multi_array<double, 2> &biases,
                    boost::multi_array<double, 3> &out);

void fullyConnectedBackward(const boost::multi_array<double, 3> &in,
                            const boost::multi_array<double, 3> &weights,
                            boost::multi_array<double, 3> &out);

void fullyConnectedWeightUpdate(
    double learningRate, const boost::multi_array<double, 3> &activations,
    const boost::multi_array<double, 3> &deltas,
    boost::multi_array<double, 3> &weights,
    boost::multi_array<double, 2> &biases);

// Compute estimates of mean and standard deviation for a batch of activations
// and return
// 1) mean
// 2) 1.0 / sqrt(stdDev * stdDev + eps)
void batchNormEstimates(const boost::multi_array_ref<double, 2> actsIn,
                        double eps, bool unbiasedVarEstimate,
                        boost::multi_array_ref<double, 1> mean,
                        boost::multi_array_ref<double, 1> invStdDev);

// Batch normalise activations given input activations and parameters
// gamma and beta and estimates mean and inverse of standard deviation
void batchNormalise(const boost::multi_array_ref<double, 2> acts,
                    const boost::multi_array_ref<double, 1> gamma,
                    const boost::multi_array_ref<double, 1> beta,
                    const boost::multi_array_ref<double, 1> mean,
                    const boost::multi_array_ref<double, 1> invStdDev,
                    boost::multi_array_ref<double, 2> actsOut,
                    boost::multi_array_ref<double, 2> actsWhitened);

// Compute gradients for batch normalisation given whitened activations,
// input gradients, inverse of standard deviation and gamma
void batchNormGradients(const boost::multi_array_ref<double, 2> actsWhitened,
                        const boost::multi_array_ref<double, 2> gradsIn,
                        const boost::multi_array_ref<double, 1> invStdDev,
                        const boost::multi_array_ref<double, 1> gamma,
                        boost::multi_array_ref<double, 2> gradsOut);

// Update parameters gamma and beta given whitened activations
void batchNormParamUpdate(const boost::multi_array_ref<double, 2> actsWhitened,
                          const boost::multi_array_ref<double, 2> gradsIn,
                          double learningRate,
                          boost::multi_array_ref<double, 1> gamma,
                          boost::multi_array_ref<double, 1> beta);

} // namespace fc
} // namespace poplibs_test

#endif // poplibs_test_FullyConnected_hpp


///////////////////////////////////////////////////////////////////////////////
// distribution::model::models::posterior::include.hpp                       //
//                                                                           //
//  Copyright 2009 Erwann Rogard. 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)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_DISTRIBUTION_MODEL_MODELS_POSTERIOR_INCLUDE_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_DISTRIBUTION_MODEL_MODELS_POSTERIOR_INCLUDE_HPP_ER_2009

#include <boost/statistics/detail/distribution/model/models/posterior/posterior.hpp>
#include <boost/statistics/detail/distribution/model/models/posterior/log_unnormalized_pdf.hpp>

#endif


#include "config/load.h"
#include "exceptions.h"
#include <boost/ut.hpp>

using namespace boost::ut;

suite parsing_durations = [] {
    using angonoka::detail::parse_duration;
    using namespace std::chrono;
    using namespace std::literals::chrono_literals;

    expect(parse_duration("123 s") == 123s);
    expect(parse_duration("123    s") == 123s);
    expect(parse_duration("123 seconds") == 123s);
    expect(parse_duration("123 h") == 123h);
    expect(parse_duration("1 h 15 minutes") == 1h + 15min);
    expect(parse_duration("1 h 1 s") == 1h + 1s);
    expect(parse_duration("01h15m") == 1h + 15min);
    expect(parse_duration("1 h 1 m 1 s") == 1h + 1min + 1s);
    expect(parse_duration("1h 1m 1s") == 1h + 1min + 1s);
    expect(parse_duration("1 min") == 1min);
    expect(parse_duration("5 mins") == 5min);
    expect(parse_duration("12 days 15s") == days{12} + 15s);
    expect(throws<angonoka::DurationParseError>(
        [&] { parse_duration(""); }));

    expect(throws<angonoka::DurationParseError>(
        [&] { parse_duration("asdf"); }));

    expect(parse_duration("5 min 1h") == 5min + 1h);
    expect(parse_duration("1h 5 min") == 1h + 5min);
    expect(parse_duration("1h and 5 min") == 1h + 5min);
    expect(throws<angonoka::DurationParseError>(
        [&] { parse_duration("-5 sec"); }));
    expect(throws<angonoka::DurationParseError>(
        [&] { parse_duration("s"); }));
};


// Copyright (c) 2012-2015 The Bitcoin Core developers
// Copyright (c) 2021 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <script/script.h>

#include <test/scriptnum10.h>
#include <test/setup_common.h>

#include <boost/test/unit_test.hpp>

#include <climits>
#include <cstdint>

BOOST_FIXTURE_TEST_SUITE(scriptnum_tests, BasicTestingSetup)

static constexpr int64_t int64_t_min = std::numeric_limits<int64_t>::min();
static constexpr int64_t int64_t_max = std::numeric_limits<int64_t>::max();
static constexpr int64_t int64_t_min_8_bytes = int64_t_min + 1;

static const int64_t values[] = {0,
                                 1,
                                 -1,
                                 -2,
                                 127,
                                 128,
                                 -255,
                                 256,
                                 (1LL << 15) - 1,
                                 -(1LL << 16),
                                 (1LL << 24) - 1,
                                 (1LL << 31),
                                 1 - (1LL << 32),
                                 1LL << 40,
                                 int64_t_min_8_bytes,
                                 int64_t_min,
                                 int64_t_max};

static const int64_t offsets[] = {1, 0x79, 0x80, 0x81, 0xFF, 0x7FFF, 0x8000, 0xFFFF, 0x10000};

static
bool verify(const CScriptNum10 &bignum, const CScriptNum &scriptnum) {
    return bignum.getvch() == scriptnum.getvch() &&
           bignum.getint() == scriptnum.getint32();
}

static
void CheckCreateVchOldRules(int64_t x) {
    size_t const maxIntegerSize = CScriptNum::MAXIMUM_ELEMENT_SIZE_32_BIT;

    CScriptNum10 bigx(x);
    auto const scriptx = CScriptNum::fromIntUnchecked(x);
    BOOST_CHECK(verify(bigx, scriptx));

    CScriptNum10 bigb(bigx.getvch(), false);
    CScriptNum scriptb(scriptx.getvch(), false, maxIntegerSize);
    BOOST_CHECK(verify(bigb, scriptb));

    CScriptNum10 bigx3(scriptb.getvch(), false);
    CScriptNum scriptx3(bigb.getvch(), false, maxIntegerSize);
    BOOST_CHECK(verify(bigx3, scriptx3));
}

static
void CheckCreateVchNewRules(int64_t x) {
    size_t const maxIntegerSize = CScriptNum::MAXIMUM_ELEMENT_SIZE_64_BIT;

    auto res = CScriptNum::fromInt(x);
    if ( ! res) {
        BOOST_CHECK(x == int64_t_min);
        return;
    }
    auto const scriptx = *res;

    CScriptNum10 bigx(x);
    BOOST_CHECK(verify(bigx, scriptx));

    CScriptNum10 bigb(bigx.getvch(), false, maxIntegerSize);
    CScriptNum scriptb(scriptx.getvch(), false, maxIntegerSize);
    BOOST_CHECK(verify(bigb, scriptb));

    CScriptNum10 bigx3(scriptb.getvch(), false, maxIntegerSize);
    CScriptNum scriptx3(bigb.getvch(), false, maxIntegerSize);
    BOOST_CHECK(verify(bigx3, scriptx3));
}

static
void CheckCreateIntOldRules(int64_t x) {
    auto const scriptx = CScriptNum::fromIntUnchecked(x);
    CScriptNum10 const bigx(x);
    BOOST_CHECK(verify(bigx, scriptx));
    BOOST_CHECK(verify(CScriptNum10(bigx.getint()), CScriptNum::fromIntUnchecked(scriptx.getint32())));
    BOOST_CHECK(verify(CScriptNum10(scriptx.getint32()), CScriptNum::fromIntUnchecked(bigx.getint())));
    BOOST_CHECK(verify(CScriptNum10(CScriptNum10(scriptx.getint32()).getint()),
                       CScriptNum::fromIntUnchecked(CScriptNum::fromIntUnchecked(bigx.getint()).getint32())));
}

static
void CheckCreateIntNewRules(int64_t x) {
    auto res = CScriptNum::fromInt(x);
    if ( ! res) {
        BOOST_CHECK(x == int64_t_min);
        return;
    }
    auto const scriptx = *res;

    CScriptNum10 const bigx(x);
    BOOST_CHECK(verify(bigx, scriptx));
    BOOST_CHECK(verify(CScriptNum10(bigx.getint()), CScriptNum::fromIntUnchecked(scriptx.getint32())));
    BOOST_CHECK(verify(CScriptNum10(scriptx.getint32()), CScriptNum::fromIntUnchecked(bigx.getint())));
    BOOST_CHECK(verify(CScriptNum10(CScriptNum10(scriptx.getint32()).getint()),
                       CScriptNum::fromIntUnchecked(CScriptNum::fromIntUnchecked(bigx.getint()).getint32())));
}

static
void CheckAddOldRules(int64_t a, int64_t b) {
    if (a == int64_t_min || b == int64_t_min) {
        return;
    }

    CScriptNum10 const biga(a);
    CScriptNum10 const bigb(b);
    auto const scripta = CScriptNum::fromIntUnchecked(a);
    auto const scriptb = CScriptNum::fromIntUnchecked(b);

    // int64_t overflow is undefined.
    bool overflowing = (b > 0 && a > int64_t_max - b) ||
                       (b < 0 && a < int64_t_min_8_bytes - b);

    if ( ! overflowing) {
        auto res = scripta.safeAdd(scriptb);
        BOOST_CHECK(res);
        BOOST_CHECK(verify(biga + bigb, *res));
        res = scripta.safeAdd(b);
        BOOST_CHECK(res);
        BOOST_CHECK(verify(biga + bigb, *res));
        res = scriptb.safeAdd(scripta);
        BOOST_CHECK(res);
        BOOST_CHECK(verify(biga + bigb, *res));
        res = scriptb.safeAdd(a);
        BOOST_CHECK(res);
        BOOST_CHECK(verify(biga + bigb, *res));
    } else {
        BOOST_CHECK(!scripta.safeAdd(scriptb));
        BOOST_CHECK(!scripta.safeAdd(b));
        BOOST_CHECK(!scriptb.safeAdd(a));
    }
}

static
void CheckAddNewRules(int64_t a, int64_t b) {
    auto res = CScriptNum::fromInt(a);
    if ( ! res) {
        BOOST_CHECK(a == int64_t_min);
        return;
    }
    auto const scripta = *res;

    res = CScriptNum::fromInt(b);
    if ( ! res) {
        BOOST_CHECK(b == int64_t_min);
        return;
    }
    auto const scriptb = *res;

    bool overflowing = (b > 0 && a > int64_t_max - b) ||
                       (b < 0 && a < int64_t_min_8_bytes - b);

    res = scripta.safeAdd(scriptb);
    BOOST_CHECK(bool(res) != overflowing);
    BOOST_CHECK( ! res || a + b == res->getint64());

    res = scripta.safeAdd(b);
    BOOST_CHECK(bool(res) != overflowing);
    BOOST_CHECK( ! res || a + b == res->getint64());

    res = scriptb.safeAdd(scripta);
    BOOST_CHECK(bool(res) != overflowing);
    BOOST_CHECK( ! res || b + a == res->getint64());

    res = scriptb.safeAdd(a);
    BOOST_CHECK(bool(res) != overflowing);
    BOOST_CHECK( ! res || b + a == res->getint64());
}

static
void CheckSubtractOldRules(int64_t a, int64_t b) {
    if (a == int64_t_min || b == int64_t_min) {
        return;
    }

    CScriptNum10 const biga(a);
    CScriptNum10 const bigb(b);
    auto const scripta = CScriptNum::fromIntUnchecked(a);
    auto const scriptb = CScriptNum::fromIntUnchecked(b);

    // int64_t overflow is undefined.
    bool overflowing = (b > 0 && a < int64_t_min_8_bytes + b) ||
                       (b < 0 && a > int64_t_max + b);

    if ( ! overflowing) {
        auto res = scripta.safeSub(scriptb);
        BOOST_CHECK(res);
        BOOST_CHECK(verify(biga - bigb, *res));
        res = scripta.safeSub(b);
        BOOST_CHECK(res);
        BOOST_CHECK(verify(biga - bigb, *res));
    } else {
        BOOST_CHECK(!scripta.safeSub(scriptb));
        BOOST_CHECK(!scripta.safeSub(b));
    }

    overflowing = (a > 0 && b < int64_t_min_8_bytes + a) ||
                  (a < 0 && b > int64_t_max + a);

    if ( ! overflowing) {
        auto res = scriptb.safeSub(scripta);
        BOOST_CHECK(res);
        BOOST_CHECK(verify(bigb - biga, *res));
        res = scriptb.safeSub(a);
        BOOST_CHECK(res);
        BOOST_CHECK(verify(bigb - biga, *res));
    } else {
        BOOST_CHECK(!scriptb.safeSub(scripta));
        BOOST_CHECK(!scriptb.safeSub(a));
    }
}

static
void CheckSubtractNewRules(int64_t a, int64_t b) {
    auto res = CScriptNum::fromInt(a);
    if ( ! res) {
        BOOST_CHECK(a == int64_t_min);
        return;
    }
    auto const scripta = *res;

    res = CScriptNum::fromInt(b);
    if ( ! res) {
        BOOST_CHECK(b == int64_t_min);
        return;
    }
    auto const scriptb = *res;

    bool overflowing = (b > 0 && a < int64_t_min_8_bytes + b) ||
                       (b < 0 && a > int64_t_max + b);

    res = scripta.safeSub(scriptb);
    BOOST_CHECK(bool(res) != overflowing);
    BOOST_CHECK( ! res || a - b == res->getint64());

    res = scripta.safeSub(b);
    BOOST_CHECK(bool(res) != overflowing);
    BOOST_CHECK( ! res || a - b == res->getint64());

    overflowing = (a > 0 && b < int64_t_min_8_bytes + a) ||
                  (a < 0 && b > int64_t_max + a);

    res = scriptb.safeSub(scripta);
    BOOST_CHECK(bool(res) != overflowing);
    BOOST_CHECK( ! res || b - a == res->getint64());

    res = scriptb.safeSub(a);
    BOOST_CHECK(bool(res) != overflowing);
    BOOST_CHECK( ! res || b - a == res->getint64());
}

static
void CheckMultiply(int64_t a, int64_t b) {
    auto res = CScriptNum::fromInt(a);
    if ( ! res) {
        BOOST_CHECK(a == int64_t_min);
        return;
    }
    auto const scripta = *res;

    res = CScriptNum::fromInt(b);
    if ( ! res) {
        BOOST_CHECK(b == int64_t_min);
        return;
    }
    auto const scriptb = *res;

    res = scripta.safeMul(scriptb);
    BOOST_CHECK( ! res || a * b == res->getint64());
    res = scripta.safeMul(b);
    BOOST_CHECK( ! res || a * b == res->getint64());
    res = scriptb.safeMul(scripta);
    BOOST_CHECK( ! res || b * a == res->getint64());
    res = scriptb.safeMul(a);
    BOOST_CHECK( ! res || b * a == res->getint64());
}

static
void CheckDivideOldRules(int64_t a, int64_t b) {
    CScriptNum10 const biga(a);
    CScriptNum10 const bigb(b);
    auto const scripta = CScriptNum::fromIntUnchecked(a);
    auto const scriptb = CScriptNum::fromIntUnchecked(b);

    // int64_t overflow is undefined.
    bool overflowing = a == int64_t_min && b == -1;

    if (b != 0) {
        if ( ! overflowing) {
            auto res = scripta / scriptb;
            BOOST_CHECK(verify(CScriptNum10(a / b), res));
            res = scripta / b;
            BOOST_CHECK(verify(CScriptNum10(a / b), res));
        } else {
            BOOST_CHECK(scripta / scriptb == scripta);
            BOOST_CHECK(verify(biga, scripta / b));
        }
    }

    overflowing = b == int64_t_min && a == -1;

    if (a != 0) {
        if ( ! overflowing) {
            auto res = scriptb / scripta;
            BOOST_CHECK(verify(CScriptNum10(b / a), res));
            res = scriptb / a;
            BOOST_CHECK(verify(CScriptNum10(b / a), res));
        } else {
            BOOST_CHECK(scriptb / scripta == scriptb);
            BOOST_CHECK(verify(bigb, scriptb / a));
        }
    }
}

static
void CheckDivideNewRules(int64_t a, int64_t b) {
    auto res = CScriptNum::fromInt(a);
    if ( ! res) {
        BOOST_CHECK(a == int64_t_min);
        return;
    }
    auto const scripta = *res;

    res = CScriptNum::fromInt(b);
    if ( ! res) {
        BOOST_CHECK(b == int64_t_min);
        return;
    }
    auto const scriptb = *res;

    if (b != 0) { // Prevent divide by 0
        auto val = scripta / scriptb;
        BOOST_CHECK(a / b == val.getint64());
        val = scripta / b;
        BOOST_CHECK(a / b == val.getint64());
    }
    if (a != 0) { // Prevent divide by 0
        auto val = scriptb / scripta;
        BOOST_CHECK(b / a == val.getint64());
        val = scriptb / a;
        BOOST_CHECK(b / a == val.getint64());
    }
}

static
void CheckNegateOldRules(int64_t x) {
    const CScriptNum10 bigx(x);
    auto const scriptx = CScriptNum::fromIntUnchecked(x);

    // -INT64_MIN is undefined
    if (x != int64_t_min) {
        BOOST_CHECK(verify(-bigx, -scriptx));
    }
}

static
void CheckNegateNewRules(int64_t x) {
    auto res = CScriptNum::fromInt(x);
    if ( ! res) {
        BOOST_CHECK(x == int64_t_min);
        return;
    }
    auto const scriptx = *res;
    CScriptNum10 const bigx(x);
    BOOST_CHECK(verify(-bigx, -scriptx));
    BOOST_CHECK(verify(-(-bigx), -(-scriptx)));
}

static
void CheckCompare(int64_t a, int64_t b) {
    const CScriptNum10 biga(a);
    const CScriptNum10 bigb(b);
    auto const scripta = CScriptNum::fromIntUnchecked(a);
    auto const scriptb = CScriptNum::fromIntUnchecked(b);

    BOOST_CHECK((biga == biga) == (scripta == scripta));
    BOOST_CHECK((biga != biga) == (scripta != scripta));
    BOOST_CHECK((biga < biga) == (scripta < scripta));
    BOOST_CHECK((biga > biga) == (scripta > scripta));
    BOOST_CHECK((biga >= biga) == (scripta >= scripta));
    BOOST_CHECK((biga <= biga) == (scripta <= scripta));

    BOOST_CHECK((biga == biga) == (scripta == a));
    BOOST_CHECK((biga != biga) == (scripta != a));
    BOOST_CHECK((biga < biga) == (scripta < a));
    BOOST_CHECK((biga > biga) == (scripta > a));
    BOOST_CHECK((biga >= biga) == (scripta >= a));
    BOOST_CHECK((biga <= biga) == (scripta <= a));

    BOOST_CHECK((biga == bigb) == (scripta == scriptb));
    BOOST_CHECK((biga != bigb) == (scripta != scriptb));
    BOOST_CHECK((biga < bigb) == (scripta < scriptb));
    BOOST_CHECK((biga > bigb) == (scripta > scriptb));
    BOOST_CHECK((biga >= bigb) == (scripta >= scriptb));
    BOOST_CHECK((biga <= bigb) == (scripta <= scriptb));

    BOOST_CHECK((biga == bigb) == (scripta == b));
    BOOST_CHECK((biga != bigb) == (scripta != b));
    BOOST_CHECK((biga < bigb) == (scripta < b));
    BOOST_CHECK((biga > bigb) == (scripta > b));
    BOOST_CHECK((biga >= bigb) == (scripta >= b));
    BOOST_CHECK((biga <= bigb) == (scripta <= b));
}

static
void RunCreateOldRules(CScriptNum const& scriptx) {
    size_t const maxIntegerSize = CScriptNum::MAXIMUM_ELEMENT_SIZE_32_BIT;
    int64_t const x = scriptx.getint64();
    CheckCreateIntOldRules(x);
    if (scriptx.getvch().size() <= maxIntegerSize) {
        CheckCreateVchOldRules(x);
    } else {
        BOOST_CHECK_THROW(CheckCreateVchOldRules(x), scriptnum10_error);
    }
}

static
void RunCreateOldRulesSet(int64_t v, int64_t o) {
    auto const value = CScriptNum::fromIntUnchecked(v);
    auto const offset = CScriptNum::fromIntUnchecked(o);

    RunCreateOldRules(value);

    auto res = value.safeAdd(offset);
    if (res) {
        RunCreateOldRules(*res);
    }

    res = value.safeSub(offset);
    if (res) {
        RunCreateOldRules(*res);
    }
}

static
void RunCreateNewRules(CScriptNum const& scriptx) {
    size_t const maxIntegerSize = CScriptNum::MAXIMUM_ELEMENT_SIZE_64_BIT;
    int64_t const x = scriptx.getint64();
    CheckCreateIntNewRules(x);
    if (scriptx.getvch().size() <= maxIntegerSize) {
        CheckCreateVchNewRules(x);
    } else {
        BOOST_CHECK_THROW(CheckCreateVchNewRules(x), scriptnum10_error);
    }
}

static
void RunCreateNewRulesSet(int64_t v, int64_t o) {
    auto res = CScriptNum::fromInt(v);
    if ( ! res) {
        BOOST_CHECK(v == int64_t_min);
        return;
    }
    auto const value = *res;

    res = CScriptNum::fromInt(o);
    if ( ! res) {
        BOOST_CHECK(o == int64_t_min);
        return;
    }
    auto const offset = *res;

    RunCreateNewRules(value);

    res = value.safeAdd(offset);
    if (res) {
        RunCreateNewRules(*res);
    }

    res = value.safeSub(offset);
    if (res) {
        RunCreateNewRules(*res);
    }
}

static
void RunOperators(int64_t a, int64_t b) {
    CheckAddOldRules(a, b);
    CheckAddNewRules(a, b);
    CheckSubtractOldRules(a, b);
    CheckSubtractNewRules(a, b);
    CheckMultiply(a, b);
    CheckDivideOldRules(a, b);
    CheckDivideNewRules(a, b);
    CheckNegateOldRules(a);
    CheckNegateNewRules(a);
    CheckCompare(a, b);
}

BOOST_AUTO_TEST_CASE(creation) {
    for (auto value : values) {
        for (auto offset : offsets) {
            RunCreateOldRulesSet(value, offset);
            RunCreateNewRulesSet(value, offset);
        }
    }
}

BOOST_AUTO_TEST_CASE(operators) {
    // Prevent potential UB below
    auto negate = [](int64_t x) { return x != int64_t_min ? -x : int64_t_min; };

    for (auto a : values) {
        RunOperators(a, a);
        RunOperators(a, negate(a));
        for (auto b : values) {
            RunOperators(a, b);
            RunOperators(a, negate(b));
            if (a != int64_t_max && a != int64_t_min && a != int64_t_min_8_bytes &&
                b != int64_t_max && b != int64_t_min && b != int64_t_min_8_bytes) {
                RunOperators(a + b, a);
                RunOperators(a + b, b);
                RunOperators(a - b, a);
                RunOperators(a - b, b);
                RunOperators(a + b, a + b);
                RunOperators(a + b, a - b);
                RunOperators(a - b, a + b);
                RunOperators(a - b, a - b);
                RunOperators(a + b, negate(a));
                RunOperators(a + b, negate(b));
                RunOperators(a - b, negate(a));
                RunOperators(a - b, negate(b));
            }
        }
    }
}

static
void CheckMinimalyEncode(std::vector<uint8_t> data, const std::vector<uint8_t> &expected) {
    bool alreadyEncoded = CScriptNum::IsMinimallyEncoded(data, data.size());
    bool hasEncoded = CScriptNum::MinimallyEncode(data);
    BOOST_CHECK_EQUAL(hasEncoded, !alreadyEncoded);
    BOOST_CHECK(data == expected);
}

BOOST_AUTO_TEST_CASE(minimize_encoding_test) {
    CheckMinimalyEncode({}, {});

    // Check that positive and negative zeros encode to nothing.
    std::vector<uint8_t> zero, negZero;
    for (size_t i = 0; i < MAX_SCRIPT_ELEMENT_SIZE; i++) {
        zero.push_back(0x00);
        CheckMinimalyEncode(zero, {});

        negZero.push_back(0x80);
        CheckMinimalyEncode(negZero, {});

        // prepare for next round.
        negZero[negZero.size() - 1] = 0x00;
    }

    // Keep one leading zero when sign bit is used.
    std::vector<uint8_t> n{0x80, 0x00}, negn{0x80, 0x80};
    std::vector<uint8_t> npadded = n, negnpadded = negn;
    for (size_t i = 0; i < MAX_SCRIPT_ELEMENT_SIZE; i++) {
        CheckMinimalyEncode(npadded, n);
        npadded.push_back(0x00);

        CheckMinimalyEncode(negnpadded, negn);
        negnpadded[negnpadded.size() - 1] = 0x00;
        negnpadded.push_back(0x80);
    }

    // Mege leading byte when sign bit isn't used.
    std::vector<uint8_t> k{0x7f}, negk{0xff};
    std::vector<uint8_t> kpadded = k, negkpadded = negk;
    for (size_t i = 0; i < MAX_SCRIPT_ELEMENT_SIZE; i++) {
        CheckMinimalyEncode(kpadded, k);
        kpadded.push_back(0x00);

        CheckMinimalyEncode(negkpadded, negk);
        negkpadded[negkpadded.size() - 1] &= 0x7f;
        negkpadded.push_back(0x80);
    }
}

BOOST_AUTO_TEST_SUITE_END()


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_TOOLBOX_ARITHMETIC_FUNCTIONS_SIMD_COMMON_ABSS_HPP_INCLUDED
#define BOOST_SIMD_TOOLBOX_ARITHMETIC_FUNCTIONS_SIMD_COMMON_ABSS_HPP_INCLUDED
#include <boost/simd/toolbox/arithmetic/functions/abss.hpp>
#include <boost/simd/include/functions/simd/if_else.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::abss_, tag::cpu_
                            , (A0)(X)
                            , ((simd_<unsigned_<A0>,X>))
                            )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL(1) { return a0; }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::abss_, tag::cpu_
                            , (A0)(X)
                            , ((simd_<floating_<A0>,X>))
                            )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL(1) { return boost::simd::abs(a0); }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::abss_, tag::cpu_
                            , (A0)(X)
                            , ((simd_<signed_<A0>,X>))
                            )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL(1)
    {
      A0 a =  boost::simd::abs(a0);
      return if_else(eq(a0,boost::simd::Valmin<A0>()), boost::simd::Valmax<A0>(), a);
    }
  };
} } }

#endif


//  ratio.hpp  ---------------------------------------------------------------//

//  Copyright 2008 Howard Hinnant
//  Copyright 2008 Beman Dawes
//  Copyright 2009 Vicente J. Botet Escriba

//  Distributed under the Boost Software License, Version 1.0.
//  See http://www.boost.org/LICENSE_1_0.txt

/*

This code was derived by Beman Dawes from Howard Hinnant's time2_demo prototype.
Many thanks to Howard for making his code available under the Boost license.
The original code was modified to conform to Boost conventions and to section
20.4 Compile-time rational arithmetic [ratio], of the C++ committee working
paper N2798.
See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2798.pdf.

time2_demo contained this comment:

    Much thanks to Andrei Alexandrescu,
                   Walter Brown,
                   Peter Dimov,
                   Jeff Garland,
                   Terry Golubiewski,
                   Daniel Krugler,
                   Anthony Williams.
*/

// The way overflow is managed for ratio_less is taken from llvm/libcxx/include/ratio

#ifndef BOOST_RATIO_RATIO_HPP
#define BOOST_RATIO_RATIO_HPP

#include <boost/ratio/config.hpp>
#include <boost/ratio/detail/mpl/abs.hpp>
#include <boost/ratio/detail/mpl/sign.hpp>
#include <boost/ratio/detail/mpl/gcd.hpp>
#include <boost/ratio/detail/mpl/lcm.hpp>
#include <cstdlib>
#include <climits>
#include <limits>
#include <boost/cstdint.hpp>
#include <boost/type_traits/integral_constant.hpp>
#include <boost/core/enable_if.hpp>
#include <boost/integer_traits.hpp>
#include <boost/ratio/ratio_fwd.hpp>
#include <boost/ratio/detail/overflow_helpers.hpp>
#ifdef BOOST_RATIO_EXTENSIONS
#include <boost/rational.hpp>
#include <boost/ratio/mpl/rational_c_tag.hpp>
#endif

//
// We simply cannot include this header on gcc without getting copious warnings of the kind:
//
// boost/integer.hpp:77:30: warning: use of C99 long long integer constant
//
// And yet there is no other reasonable implementation, so we declare this a system header
// to suppress these warnings.
//
#if defined(__GNUC__) && (__GNUC__ >= 4)
#pragma GCC system_header
#endif

namespace boost
{

//----------------------------------------------------------------------------//
//                                                                            //
//                20.6.1 Class template ratio [ratio.ratio]                   //
//                                                                            //
//----------------------------------------------------------------------------//

template <boost::intmax_t N, boost::intmax_t D>
class ratio
{
    static const boost::intmax_t ABS_N = mpl::abs_c<boost::intmax_t, N>::value;
    static const boost::intmax_t ABS_D = mpl::abs_c<boost::intmax_t, D>::value;
    BOOST_RATIO_STATIC_ASSERT(ABS_N >= 0, BOOST_RATIO_NUMERATOR_IS_OUT_OF_RANGE, ());
    BOOST_RATIO_STATIC_ASSERT(ABS_D > 0, BOOST_RATIO_DENOMINATOR_IS_OUT_OF_RANGE, ());
    BOOST_RATIO_STATIC_ASSERT(D != 0, BOOST_RATIO_DIVIDE_BY_0 , ());
    static const boost::intmax_t SIGN_N = mpl::sign_c<boost::intmax_t,N>::value
      * mpl::sign_c<boost::intmax_t,D>::value;
    static const boost::intmax_t GCD = mpl::gcd_c<boost::intmax_t, ABS_N, ABS_D>::value;
public:
    BOOST_STATIC_CONSTEXPR boost::intmax_t num = SIGN_N * ABS_N / GCD;
    BOOST_STATIC_CONSTEXPR boost::intmax_t den = ABS_D / GCD;

#ifdef BOOST_RATIO_EXTENSIONS
    typedef mpl::rational_c_tag tag;
    typedef boost::rational<boost::intmax_t> value_type;
    typedef boost::intmax_t num_type;
    typedef boost::intmax_t den_type;
    ratio()
    {}
    template <boost::intmax_t _N2, boost::intmax_t _D2>
    ratio(const ratio<_N2, _D2>&,
        typename enable_if_c
            <
                (ratio<_N2, _D2>::num == num &&
                ratio<_N2, _D2>::den == den)
            >::type* = 0)
    {}

    template <boost::intmax_t _N2, boost::intmax_t _D2>
        typename enable_if_c
        <
            (ratio<_N2, _D2>::num == num &&
            ratio<_N2, _D2>::den == den),
            ratio&
        >::type
    operator=(const ratio<_N2, _D2>&) {return *this;}

    static value_type value() {return value_type(num,den);}
    value_type operator()() const {return value();}
#endif
    typedef ratio<num, den> type;
};

#if defined(BOOST_NO_CXX11_CONSTEXPR)
template <boost::intmax_t N, boost::intmax_t D>
const    boost::intmax_t ratio<N, D>::num;
template <boost::intmax_t N, boost::intmax_t D>
const    boost::intmax_t ratio<N, D>::den;
#endif

//----------------------------------------------------------------------------//
//                                                                            //
//                20.6.2 Arithmetic on ratio types [ratio.arithmetic]         //
//                                                                            //
//----------------------------------------------------------------------------//

template <class R1, class R2>
struct ratio_add
: boost::ratio_detail::ratio_add<R1, R2>::type
{
};

template <class R1, class R2>
struct ratio_subtract
: boost::ratio_detail::ratio_subtract<R1, R2>::type
{
};

template <class R1, class R2>
struct ratio_multiply
: boost::ratio_detail::ratio_multiply<R1, R2>::type
{
};

template <class R1, class R2>
struct ratio_divide
: boost::ratio_detail::ratio_divide<R1, R2>::type
{
};

//----------------------------------------------------------------------------//
//                                                                            //
//                20.6.3 Comparision of ratio types [ratio.comparison]        //
//                                                                            //
//----------------------------------------------------------------------------//

// ratio_equal

template <class R1, class R2>
struct ratio_equal
    : public boost::integral_constant<bool,
                               (R1::num == R2::num && R1::den == R2::den)>
{};

template <class R1, class R2>
struct ratio_not_equal
    : public boost::integral_constant<bool, !ratio_equal<R1, R2>::value>
{};

// ratio_less

template <class R1, class R2>
struct ratio_less
    : boost::integral_constant<bool, boost::ratio_detail::ratio_less<R1, R2>::value>
{};

template <class R1, class R2>
struct ratio_less_equal
    : boost::integral_constant<bool, !ratio_less<R2, R1>::value>
{};

template <class R1, class R2>
struct ratio_greater
    : boost::integral_constant<bool, ratio_less<R2, R1>::value>
{};

template <class R1, class R2>
struct ratio_greater_equal
    : boost::integral_constant<bool, !ratio_less<R1, R2>::value>
{};

template <class R1, class R2>
struct ratio_gcd :
    ratio<mpl::gcd_c<boost::intmax_t, R1::num, R2::num>::value,
        mpl::lcm_c<boost::intmax_t, R1::den, R2::den>::value>::type
{
};

    //----------------------------------------------------------------------------//
    //                                                                            //
    //                More arithmetic on ratio types [ratio.arithmetic]           //
    //                                                                            //
    //----------------------------------------------------------------------------//

#ifdef BOOST_RATIO_EXTENSIONS
template <class R>
struct ratio_negate
    : ratio<-R::num, R::den>::type
{
};
template <class R>
struct ratio_abs
    : ratio<mpl::abs_c<boost::intmax_t, R::num>::value, R::den>::type
{
};
template <class R>
struct ratio_sign
    : mpl::sign_c<boost::intmax_t, R::num>
{
};

template <class R>
struct ratio_inverse
    : ratio<R::den, R::num>::type
{
};

template <class R1, class R2>
struct ratio_lcm :
    ratio<mpl::lcm_c<boost::intmax_t, R1::num, R2::num>::value,
        mpl::gcd_c<boost::intmax_t, R1::den, R2::den>::value>::type
{
};

template <class R1, class R2>
struct ratio_modulo :
    ratio<(R1::num * R2::den) % (R2::num * R1::den), R1::den * R2::den>::type
{
};

namespace detail {
  template <class R1, class R2, bool r1ltr2>
  struct ratio_min : R1 {};
  template <class R1, class R2>
  struct ratio_min<R1,R2,false> : R2 {};

  template <class R1, class R2, bool r1ltr2>
  struct ratio_max : R2 {};
  template <class R1, class R2>
  struct ratio_max<R1,R2,false> : R1 {};
}

template <class R1, class R2>
struct ratio_min : detail::ratio_min<R1, R2, ratio_less<R1,R2>::value>::type
{
};

template <class R1, class R2>
struct ratio_max : detail::ratio_max<R1, R2, ratio_less<R1,R2>::value>::type
{
};

template<typename R, int p>
struct ratio_power :
  ratio_multiply<
    typename ratio_power<R, p%2>::type,
    typename ratio_power<typename ratio_multiply<R, R>::type, p/2>::type
  >::type
{};

template<typename R>
struct ratio_power<R, 0> : ratio<1>::type {};

template<typename R>
struct ratio_power<R, 1> : R {};

template<typename R>
struct ratio_power<R, -1> : ratio_divide<ratio<1>, R>::type {};

#endif
}  // namespace boost

#endif  // BOOST_RATIO_RATIO_HPP


//---------------------------------------------------------------------------//
//!
//! \file   tstSampleMomentHistogram.cpp
//! \author Alex Robinson
//! \brief  Sample moment histogram unit tests
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <iostream>
#include <sstream>

// Boost Includes
#include <boost/units/quantity.hpp>
#include <boost/units/systems/cgs/length.hpp>
#include <boost/units/systems/si/length.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>

// FRENSIE Includes
#include "Utility_SampleMomentHistogram.hpp"
#include "Utility_UnitTestHarnessWithMain.hpp"

//---------------------------------------------------------------------------//
// Template Typedefs
//---------------------------------------------------------------------------//
typedef std::tuple<double,
                   boost::units::quantity<boost::units::cgs::length>,
                   boost::units::quantity<boost::units::si::length> > TestingTypes;

//---------------------------------------------------------------------------//
// Check that the size of a histogram can be returned
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram, size, TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );

  Utility::SampleMomentHistogram<T> default_histogram;

  FRENSIE_CHECK_EQUAL( default_histogram.size(), 1 );

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*2.0}) ) );

  FRENSIE_CHECK_EQUAL( histogram.size(), 2 );
}

//---------------------------------------------------------------------------//
// Check that the bin boundaries can be returned
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram,
                            getBinBoundaries,
                            TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );

  Utility::SampleMomentHistogram<T> default_histogram;

  {
    const std::vector<T>& bin_boundaries =
      default_histogram.getBinBoundaries();

    FRENSIE_REQUIRE_EQUAL( bin_boundaries.size(), 2 );
    FRENSIE_CHECK_EQUAL( bin_boundaries.front(),
                         Utility::QuantityTraits<T>::lowest() );
    FRENSIE_CHECK_EQUAL( bin_boundaries.back(),
                         Utility::QuantityTraits<T>::max() );
  }

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*2.0}) ) );

  {
    const std::vector<T>& bin_boundaries =
      histogram.getBinBoundaries();

    FRENSIE_REQUIRE_EQUAL( bin_boundaries.size(), 3 );
    FRENSIE_CHECK_EQUAL( bin_boundaries[0],
                         Utility::QuantityTraits<T>::zero() );
    FRENSIE_CHECK_EQUAL( bin_boundaries[1],
                         Utility::QuantityTraits<T>::one() );
    FRENSIE_CHECK_EQUAL( bin_boundaries[2],
                         Utility::QuantityTraits<T>::one()*2.0 );
  }
}

//---------------------------------------------------------------------------//
// Check that raw scores can be added to the histogram
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram, addRawScore, TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );
  typedef typename Utility::QuantityTraits<T>::RawType RawType;

  Utility::SampleMomentHistogram<T> default_histogram;

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*2.0}) ) );

  default_histogram.addRawScore( -Utility::QuantityTraits<T>::one() );
  histogram.addRawScore( -Utility::QuantityTraits<T>::one() );

  FRENSIE_CHECK_EQUAL( default_histogram.getNumberOfScores(), 1 );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 0 );

  default_histogram.addRawScore( Utility::QuantityTraits<T>::zero() );
  histogram.addRawScore( Utility::QuantityTraits<T>::zero() );

  FRENSIE_CHECK_EQUAL( default_histogram.getNumberOfScores(), 2 );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 1 );

  default_histogram.addRawScore( Utility::QuantityTraits<T>::one() );
  histogram.addRawScore( Utility::QuantityTraits<T>::one() );

  FRENSIE_CHECK_EQUAL( default_histogram.getNumberOfScores(), 3 );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 2 );

  default_histogram.addRawScore( Utility::QuantityTraits<T>::one()*2.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*2.0 );

  FRENSIE_CHECK_EQUAL( default_histogram.getNumberOfScores(), 4 );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 3 );

  default_histogram.addRawScore( Utility::QuantityTraits<T>::one()*3.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*3.0 );

  FRENSIE_CHECK_EQUAL( default_histogram.getNumberOfScores(), 5 );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 3 );

  {
    const std::vector<RawType>& histogram_values =
      default_histogram.getHistogramValues();

    FRENSIE_REQUIRE_EQUAL( histogram_values.size(), 1 );
    FRENSIE_CHECK_EQUAL( histogram_values[0], 5.0 );
  }

  {
    const std::vector<RawType>& histogram_values =
      histogram.getHistogramValues();

    FRENSIE_REQUIRE_EQUAL( histogram_values.size(), 2 );
    FRENSIE_CHECK_EQUAL( histogram_values[0], 1.0 );
    FRENSIE_CHECK_EQUAL( histogram_values[1], 2.0 );
  }
}

//---------------------------------------------------------------------------//
// Check that a histogram can be cleared
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram, clear, TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*2.0}) ) );

  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.5 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.5 );

  FRENSIE_CHECK_EQUAL( histogram.size(), 2 );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 2 );

  histogram.clear();

  FRENSIE_CHECK_EQUAL( histogram.size(), 1 );
  FRENSIE_CHECK_EQUAL( histogram.getBinBoundaries()[0],
                       Utility::QuantityTraits<T>::lowest() );
  FRENSIE_CHECK_EQUAL( histogram.getBinBoundaries()[1],
                       Utility::QuantityTraits<T>::max() );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 0 );
}

//---------------------------------------------------------------------------//
// Check that a histogram can be reset
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram, reset, TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*2.0}) ) );

  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.5 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.5 );

  FRENSIE_CHECK_EQUAL( histogram.size(), 2 );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 2 );

  histogram.reset();

  FRENSIE_CHECK_EQUAL( histogram.size(), 2 );
  FRENSIE_CHECK_EQUAL( histogram.getBinBoundaries()[0],
                       Utility::QuantityTraits<T>::zero() );
  FRENSIE_CHECK_EQUAL( histogram.getBinBoundaries()[1],
                       Utility::QuantityTraits<T>::one() );
  FRENSIE_CHECK_EQUAL( histogram.getBinBoundaries()[2],
                       Utility::QuantityTraits<T>::one()*2.0 );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 0 );
}

//---------------------------------------------------------------------------//
// Check that the bin boundaries of a histogram can be set
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram,
                            setBinBoundaries,
                            TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );

  Utility::SampleMomentHistogram<T> histogram;

  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.5 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.5 );

  FRENSIE_CHECK_EQUAL( histogram.size(), 1 );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 2 );

  histogram.setBinBoundaries( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*2.0}) ) );

  histogram.reset();

  FRENSIE_CHECK_EQUAL( histogram.size(), 2 );
  FRENSIE_CHECK_EQUAL( histogram.getBinBoundaries()[0],
                       Utility::QuantityTraits<T>::zero() );
  FRENSIE_CHECK_EQUAL( histogram.getBinBoundaries()[1],
                       Utility::QuantityTraits<T>::one() );
  FRENSIE_CHECK_EQUAL( histogram.getBinBoundaries()[2],
                       Utility::QuantityTraits<T>::one()*2.0 );
  FRENSIE_CHECK_EQUAL( histogram.getHistogramValues()[0], 0.0 );
  FRENSIE_CHECK_EQUAL( histogram.getHistogramValues()[1], 0.0 );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 0 );
}

//---------------------------------------------------------------------------//
// Check that the normalized histogram values can be returned
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram,
                            getNormalizedHistogramValues,
                            TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );
  typedef typename Utility::QuantityTraits<T>::RawType RawType;

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*2.0}) ) );

  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*2.0 );

  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 11 );

  std::vector<RawType> normalized_values;

  histogram.getNormalizedHistogramValues( normalized_values );

  FRENSIE_REQUIRE_EQUAL( normalized_values.size(), 2 );
  FRENSIE_CHECK_EQUAL( normalized_values[0], 5.0/11 );
  FRENSIE_CHECK_EQUAL( normalized_values[1], 6.0/11 );
}

//---------------------------------------------------------------------------//
// Check that the density values can be returned
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram,
                            getDensityValues,
                            TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );
  typedef typename Utility::SampleMomentHistogram<T>::DensityValueType DensityValueType;

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one()*0.5, Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*1.5, Utility::QuantityTraits<T>::one()*2.0}) ) );

  histogram.addRawScore( -Utility::QuantityTraits<T>::one() );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*2.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*3.0 );

  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 11 );

  std::vector<DensityValueType> density_values;

  histogram.getDensityValues( density_values );

  FRENSIE_REQUIRE_EQUAL( density_values.size(), 4 );
  FRENSIE_CHECK_EQUAL( density_values[0],
                       (3.0/(11*0.5)/Utility::QuantityTraits<T>::one()) );
  FRENSIE_CHECK_EQUAL( density_values[1],
                       (2.0/(11*0.5))/Utility::QuantityTraits<T>::one() );
  FRENSIE_CHECK_EQUAL( density_values[2],
                       (3.0/(11*0.5))/Utility::QuantityTraits<T>::one() );
  FRENSIE_CHECK_EQUAL( density_values[3],
                       (3.0/(11*0.5))/Utility::QuantityTraits<T>::one() );

  histogram.setBinBoundaries( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::lowest(), Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one()*0.5, Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*1.5, Utility::QuantityTraits<T>::one()*2.0, Utility::QuantityTraits<T>::max()}) ) );

  histogram.addRawScore( -Utility::QuantityTraits<T>::one() );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*2.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*3.0 );

  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 13 );

  histogram.getDensityValues( density_values );

  FRENSIE_REQUIRE_EQUAL( density_values.size(), 6 );
  FRENSIE_CHECK_EQUAL( density_values[0],
                       Utility::QuantityTraits<DensityValueType>::zero() );
  FRENSIE_CHECK_EQUAL( density_values[1],
                       (3.0/(13*0.5)/Utility::QuantityTraits<T>::one()) );
  FRENSIE_CHECK_EQUAL( density_values[2],
                       (2.0/(13*0.5))/Utility::QuantityTraits<T>::one() );
  FRENSIE_CHECK_EQUAL( density_values[3],
                       (3.0/(13*0.5))/Utility::QuantityTraits<T>::one() );
  FRENSIE_CHECK_EQUAL( density_values[4],
                       (2.0/(13*0.5))/Utility::QuantityTraits<T>::one() );
  FRENSIE_CHECK_EQUAL( density_values[5],
                       Utility::QuantityTraits<DensityValueType>::zero() );
}

//---------------------------------------------------------------------------//
// Check that a histogram can be copied
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram,
                            copy_constructor,
                            TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one()*0.5, Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*1.5, Utility::QuantityTraits<T>::one()*2.0}) ) );

  histogram.addRawScore( -Utility::QuantityTraits<T>::one() );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*2.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*3.0 );

  Utility::SampleMomentHistogram<T> histogram_copy( histogram );

  FRENSIE_CHECK_EQUAL( histogram_copy.size(), histogram.size() );
  FRENSIE_CHECK_EQUAL( histogram_copy.getBinBoundaries(),
                       histogram.getBinBoundaries() );
  FRENSIE_CHECK_EQUAL( histogram_copy.getHistogramValues(),
                       histogram.getHistogramValues() );
  FRENSIE_CHECK_EQUAL( histogram_copy.getNumberOfScores(),
                       histogram.getNumberOfScores() );
}

//---------------------------------------------------------------------------//
// Check that a histogram can be copied
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram,
                            assignment_operator,
                            TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one()*0.5, Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*1.5, Utility::QuantityTraits<T>::one()*2.0}) ) );

  histogram.addRawScore( -Utility::QuantityTraits<T>::one() );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*2.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*3.0 );

  Utility::SampleMomentHistogram<T> histogram_copy = histogram;

  FRENSIE_CHECK_EQUAL( histogram_copy.size(), histogram.size() );
  FRENSIE_CHECK_EQUAL( histogram_copy.getBinBoundaries(),
                       histogram.getBinBoundaries() );
  FRENSIE_CHECK_EQUAL( histogram_copy.getHistogramValues(),
                       histogram.getHistogramValues() );
  FRENSIE_CHECK_EQUAL( histogram_copy.getNumberOfScores(),
                       histogram.getNumberOfScores() );
}

//---------------------------------------------------------------------------//
// Check that two histograms can be merged
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram,
                            mergeHistograms,
                            TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );
  typedef typename Utility::SampleMomentHistogram<T>::HistogramValueType HistogramValueType;

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::lowest(), Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one()*0.5, Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*1.5, Utility::QuantityTraits<T>::one()*2.0, Utility::QuantityTraits<T>::max()}) ) );

  histogram.addRawScore( -Utility::QuantityTraits<T>::one() );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*2.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*3.0 );

  {
    Utility::SampleMomentHistogram<T> other_histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::lowest(), Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one()*0.5, Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*1.5, Utility::QuantityTraits<T>::one()*2.0, Utility::QuantityTraits<T>::max()}) ) );

    histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.6 );
    histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.8 );
    histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.0 );
    histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.2 );
    histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.4 );
    histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.6 );

    histogram.mergeHistograms( other_histogram );
  }

  FRENSIE_CHECK_EQUAL( histogram.size(), 6 );
  FRENSIE_CHECK_EQUAL( histogram.getBinBoundaries(),
                       std::vector<T>({Utility::QuantityTraits<T>::lowest(), Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one()*0.5, Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*1.5, Utility::QuantityTraits<T>::one()*2.0, Utility::QuantityTraits<T>::max()}) );
  FRENSIE_CHECK_EQUAL( histogram.getNumberOfScores(), 19 );

  const std::vector<HistogramValueType>& histogram_values =
    histogram.getHistogramValues();

  FRENSIE_REQUIRE_EQUAL( histogram_values.size(), 6 );
  FRENSIE_CHECK_EQUAL( histogram_values[0],
                       Utility::QuantityTraits<HistogramValueType>::one() );
  FRENSIE_CHECK_EQUAL( histogram_values[1],
                       3.0*Utility::QuantityTraits<HistogramValueType>::one() );
  FRENSIE_CHECK_EQUAL( histogram_values[2],
                       4.0*Utility::QuantityTraits<HistogramValueType>::one() );
  FRENSIE_CHECK_EQUAL( histogram_values[3],
                       6.0*Utility::QuantityTraits<HistogramValueType>::one() );
  FRENSIE_CHECK_EQUAL( histogram_values[4],
                       3.0*Utility::QuantityTraits<HistogramValueType>::one() );
  FRENSIE_CHECK_EQUAL( histogram_values[5],
                       2.0*Utility::QuantityTraits<HistogramValueType>::one() );
}

//---------------------------------------------------------------------------//
// Check that a histogram can be archived
FRENSIE_UNIT_TEST_TEMPLATE( SampleMomentHistogram, archive, TestingTypes )
{
  FETCH_TEMPLATE_PARAM( 0, T );

  Utility::SampleMomentHistogram<T> histogram( std::make_shared<std::vector<T> >( std::vector<T>({Utility::QuantityTraits<T>::lowest(), Utility::QuantityTraits<T>::zero(), Utility::QuantityTraits<T>::one()*0.5, Utility::QuantityTraits<T>::one(), Utility::QuantityTraits<T>::one()*1.5, Utility::QuantityTraits<T>::one()*2.0, Utility::QuantityTraits<T>::max()}) ) );

  histogram.addRawScore( -Utility::QuantityTraits<T>::one() );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*0.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.2 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.4 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.6 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*1.8 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*2.0 );
  histogram.addRawScore( Utility::QuantityTraits<T>::one()*3.0 );

  std::ostringstream archive_ostream;

  {
    boost::archive::xml_oarchive archive( archive_ostream );

    FRENSIE_REQUIRE_NO_THROW( archive << BOOST_SERIALIZATION_NVP( histogram ) );
  }

  Utility::SampleMomentHistogram<T> extracted_histogram;

  {
    std::istringstream iss( archive_ostream.str() );

    boost::archive::xml_iarchive archive( iss );

    FRENSIE_REQUIRE_NO_THROW( archive >> boost::serialization::make_nvp( "histogram", extracted_histogram ) );
  }

  FRENSIE_CHECK_EQUAL( extracted_histogram.size(), histogram.size() );
  FRENSIE_CHECK_EQUAL( extracted_histogram.getBinBoundaries(),
                       histogram.getBinBoundaries() );
  FRENSIE_CHECK_EQUAL( extracted_histogram.getHistogramValues(),
                       histogram.getHistogramValues() );
  FRENSIE_CHECK_EQUAL( extracted_histogram.getNumberOfScores(),
                       histogram.getNumberOfScores() );
}

//---------------------------------------------------------------------------//
// end tstSampleMomentHistogram.cpp
//---------------------------------------------------------------------------//


/*
 * converter_tests.cpp
 *
 *  Created on: Feb 6, 2019
 *      Author: Gregory Kramida
 *   Copyright: 2019 Gregory Kramida
 *
 *   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.
 */

//stdlib
#include <vector>

//librarires
#include <boost/python.hpp>

//local
#include "conversion_tests.hpp"
#include <unsupported/Eigen/CXX11/Tensor>
namespace eig = Eigen;

namespace bp = boost::python;

namespace python_export{

template<typename T>
std::vector<T> arange(T start, T stop, T step = 1) {
    std::vector<T> values;
    for (T value = start; value < stop; value += step)
        values.push_back(value);
    return values;
}

eig::Tensor<float,3> return_input_F3(eig::Tensor<float,3> input){
	return input;
}

eig::Tensor<float,4> return_input_F4(eig::Tensor<float,4> input){
	return input;
}

eig::Tensor<float,3, eig::RowMajor> return_tensor_F3RM(){
	std::vector<float> v = arange(1.0f,25.0f);
	Eigen::Tensor<float, 3, Eigen::RowMajor> tensor = Eigen::TensorMap<Eigen::Tensor<float, 3, Eigen::RowMajor>>(&v[0], 2, 4, 3);
	return tensor;
}

eig::Tensor<float,4, eig::RowMajor> return_tensor_F4RM(){
	std::vector<float> v = arange(1.0f,49.0f);
	return Eigen::TensorMap<Eigen::Tensor<float, 4, Eigen::RowMajor>>(&v[0], 2, 4, 2, 3);
}

eig::Tensor<float,3> scale(eig::Tensor<float,3> a, float factor){
	return a * factor;
}

eig::Tensor<float,3> add_constant(eig::Tensor<float,3> a, float constant){
	return a + constant;
}

eig::Tensor<float,3> add_tensors(eig::Tensor<float,3> a, eig::Tensor<float,3> b){
	return a + b;
}

void export_conversion_tests(){
	bp::def("return_input_f3", &return_input_F3,"Returns the input 3-dimensional tensor", bp::args("input"));
	bp::def("return_input_f4", &return_input_F4,"Returns the input 4-dimensional tensor", bp::args("input"));
	bp::def("return_tensor_f3rm", &return_tensor_F3RM,"Returns a 3-dimensional tensor of dimensions (2, 4, 3) "
				"containing in range [1,24].");
	bp::def("return_tensor_f4rm", &return_tensor_F4RM,"Returns a 4-dimensional tensor of dimensions (2, 4, 2, 3) "
			"containing in range [1,48].");
	bp::def("scale", &scale,"Scales the input 3-dimensional tensor by the given factor",
			bp::args("a","factor"));
	bp::def("add_constant", &add_constant,
			"Adds the given constant to each element of the provided 3-dimensional tensor", bp::args("a"),"constant");
	bp::def("add_tensors", &add_tensors,"Adds the two provided 3-dimensional tensors "
			"and returns the result.", bp::args("a","b"));
}

}//namespace python_export


#ifndef GEM_DIMENSIONS_TUPLE_HPP_INCLUDED
#define GEM_DIMENSIONS_TUPLE_HPP_INCLUDED

#define BOOST_HANA_CONFIG_ENABLE_STRING_UDL

#include <type_traits>

#include <boost/hana/assert.hpp>
#include <boost/hana/integral_constant.hpp>
#include <boost/hana/minus.hpp>
#include <boost/hana/tuple.hpp>
#include <boost/hana/transform.hpp>
#include <boost/hana/fold.hpp>
#include <boost/hana/string.hpp>

#include <cereal/cereal.hpp>

#include <gem/litterals.hpp>
#include <gem/fwd/dimensions_tuple.hpp>

namespace gem {

gem::concepts::DimensionTuple {T_flag, n_con, n_cov, ...dims}
class DimensionTuple :
    public boost::hana::tuple<dims...>
{
private:

    template <gem::concepts::Dimension d1, gem::concepts::Dimension... rd>
    struct gem_dim_common_type {
        using type =
            std::common_type_t<typename d1::dim_t,
                               typename gem_dim_common_type<rd...>::type>;
    };

    // template <gem::concepts::Dimension d1>
    gem::concepts::Dimension {d1}
    struct gem_dim_common_type<d1> {
        using type = typename d1::dim_t;
    };

public:
    using type_t = DimensionTuple<T_flag, n_con, n_cov, dims...>;
    using base_t = boost::hana::tuple<dims...>;
    using common_t = typename gem_dim_common_type<dims...>::type;

    friend class cereal::access;

public:

    static constexpr BOOST_HANA_CONSTANT_CHECK_MSG(
        (boost::hana::ullong_c<0> <
         boost::hana::ullong_c<sizeof...(dims)>),
        "At least one dimension must be specified.");

    using base_t::tuple;

    constexpr inline auto
    value(void) const noexcept
    {
        constexpr auto _value = [](auto && d) {
            return d.value();
        };
        return boost::hana::transform(*this, _value);
    }

    static constexpr inline auto
    get_max(void) noexcept
    {

        constexpr auto _max = [](auto && d) {
            return decltype(+d)::type::get_max();
        };
        return boost::hana::transform(boost::hana::tuple_t<dims...>, _max);
    }

    static constexpr inline auto
    get_min(void) noexcept
    {
        constexpr auto _min = [](auto && d) {
            return decltype(+d)::type::get_min();
        };
        return boost::hana::transform(boost::hana::tuple_t<dims...>, _min);
    }

    constexpr inline auto size(void) const noexcept {
        constexpr auto _mult = [](auto && acc, auto && d) {
            return acc * d;
        };
        return boost::hana::fold(*this, _mult);
    }

    constexpr inline auto operator [](const auto & idx) const noexcept
        -> const std::enable_if_t<T_flag,
            decltype(base_t::operator[] (
                boost::hana::ullong_c<sizeof...(dims)> -
                                      idx + GEM_START_IDX -
                                      boost::hana::ullong_c<1>))> &
    {
        return base_t::operator[] (
            boost::hana::ullong_c<sizeof...(dims)> -
                                  idx + GEM_START_IDX -
                                  boost::hana::ullong_c<1>);
    }

    constexpr inline auto operator [](const auto & idx) const noexcept
        -> const std::enable_if_t<!T_flag,
            decltype(base_t::operator[] (idx - GEM_START_IDX))> &
    {
        return base_t::operator[] (idx - GEM_START_IDX);
    }

private:
    template<class Archive>
    auto save(Archive & archive) const -> void
    {
        // archive(cereal::make_size_tag(n_con));
        boost::hana::ullong_c<n_con>.times.with_index(
            [this, &archive](auto index) {
                // TODO: make the string concatenation compile time.
                archive(cereal::make_nvp("contravariant",
                                         this->operator[] (GEM_START_IDX + index)));
        });
        boost::hana::ullong_c<n_cov>.times.with_index(
            [this, &archive](auto index) {
                // TODO: make the string concatenation compile time.
                archive(cereal::make_nvp("covariant",
                                         this->operator[] (GEM_START_IDX + boost::hana::ullong_c<n_con> + index)));
        });
    }

    template<class Archive>
    auto load(Archive & archive) -> void
    {

    }
};

}  // namespage gem

#endif  // !GEM_DIMENSIONS_TUPLE_HPP_INCLUDED


/* Copyright (C) 2012-2017 IBM Corp.
 * This program is 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. See accompanying LICENSE file.
 */
#include <NTL/ZZ.h>
#include <algorithm>
#include <complex>

#include "norms.h"
#include "EncryptedArray.h"
#include "FHE.h"
#include "debugging.h"

#include "gtest/gtest.h"
#include "test_common.h"

namespace {
struct Parameters {
    Parameters(long m, long r, long L, double epsilon) :
        m(m),
        r(r),
        L(L),
        epsilon(epsilon)
    {};

    const long m;
    const long r;
    const long L;
    const double epsilon;

    friend std::ostream& operator<<(std::ostream& os, const Parameters& params)
    {
        return os << "{" <<
            "m=" << params.m << "," <<
            "r=" << params.r << "," <<
            "L=" << params.L << "," <<
            "epsilon=" << params.epsilon <<
            "}";
    }
};

// Utility functions for the tests

  // Compute the L-infinity distance between two vectors
  double calcMaxDiff(const std::vector<cx_double>& v1,
                     const std::vector<cx_double>& v2){
    if(lsize(v1) != lsize(v2)) {
      throw std::runtime_error("Vector sizes differ.");
    }

    double maxDiff = 0.0;
    for (long i=0; i<lsize(v1); i++) {
      double diffAbs = std::abs(v1[i]-v2[i]);
      if (diffAbs > maxDiff)
        maxDiff = diffAbs;
    }
    return maxDiff;
  }
  // Compute the max relative difference between two vectors
  double calcMaxRelDiff(const std::vector<cx_double>& v1,
                        const std::vector<cx_double>& v2)
  {
    if(lsize(v1)!=lsize(v2)) {
      throw std::runtime_error("Vector sizes differ.");

    }

    // Compute the largest-magnitude value in the vector
    double maxAbs = 0.0;
    for (auto& x : v1) {
      if (std::abs(x) > maxAbs)
        maxAbs = std::abs(x);
    }
    if (maxAbs<1e-10)
      maxAbs = 1e-10;

    double maxDiff = 0.0;
    for (long i=0; i<lsize(v1); i++) {
      double relDiff = std::abs(v1[i]-v2[i]) / maxAbs;
      if (relDiff > maxDiff)
        maxDiff = relDiff;
    }

    return maxDiff;
  }

  inline bool cx_equals(const std::vector<cx_double>& v1,
                        const std::vector<cx_double>& v2,
                        double epsilon)
  {
    return (calcMaxDiff(v1,v2) < epsilon);
  }

  ::testing::AssertionResult ciphertextMatches(const EncryptedArrayCx &ea, const FHESecKey &sk,
                                               const std::vector<cx_double> &p, const Ctxt &c, double epsilon)
  {
    std::vector<cx_double> pp;
    ea.decrypt(c, sk, pp);
    if (helib_test::verbose) {
      std::cout << "    relative-error=" << calcMaxRelDiff(p, pp)
                << ", absolute-error=" << calcMaxRelDiff(p, pp) << std::endl;
    }

    if(cx_equals(pp, p, epsilon)) {
      return ::testing::AssertionSuccess();
    } else {
      return ::testing::AssertionFailure()
          << "Ciphertext does not match plaintext:" << std::endl
          << "p = " << p << std::endl
          << "pp = " << pp << std::endl;
    }
  }

  void negateVec(std::vector<cx_double>& p1)
  {
    for (auto& x: p1) x = -x;
  }
  void conjVec(std::vector<cx_double>& p1)
  {
    for (auto& x: p1) x = conj(x);
  }
  void add(std::vector<cx_double>& to, const std::vector<cx_double>& from)
  {
    if (to.size() < from.size())
      to.resize(from.size(), 0);
    for (long i=0; i<from.size(); i++) to[i] += from[i];
  }
  void add(std::vector<cx_double>& to, double from)
  {
    for (long i=0; i<to.size(); i++) to[i] += from;
  }
  void sub(std::vector<cx_double>& to, const std::vector<cx_double>& from)
  {
    if (to.size() < from.size())
      to.resize(from.size(), 0);
    for (long i=0; i<from.size(); i++) to[i] -= from[i];
  }
  void mul(std::vector<cx_double>& to, const std::vector<cx_double>& from)
  {
    if (to.size() < from.size())
      to.resize(from.size(), 0);
    for (long i=0; i<from.size(); i++) to[i] *= from[i];
  }
  void mul(std::vector<cx_double>& to, double from)
  {
    for (long i=0; i<to.size(); i++) to[i] *= from;
  }
  void rotate(std::vector<cx_double>& p, long amt)
  {
    long sz = p.size();
    std::vector<cx_double> tmp(sz);
    for (long i=0; i<sz; i++)
      tmp[((i+amt)%sz +sz)%sz] = p[i];
    p = tmp;
  }

class Test_CKKS : public ::testing::TestWithParam<Parameters> {
    protected:
        const long m;         // Zm*
        const long r;         // bit precision
        const long L;         // Number of bits
        const double epsilon; // error threshold

        FHEcontext context;
        FHESecKey secretKey;
        const FHEPubKey publicKey;
        const EncryptedArrayCx& ea;

        Test_CKKS () :
            m(GetParam().m),
            r(GetParam().r),
            L(GetParam().L),
            epsilon(GetParam().epsilon),
            context(m, /*p=*/-1, r),
            secretKey((context.scale=4, buildModChain(context, L, /*c=*/2), context)),
            publicKey((secretKey.GenSecKey(), addSome1DMatrices(secretKey), secretKey)),
            ea(context.ea->getCx())
        {}

        virtual void SetUp() override
        {
            if (helib_test::verbose) {
                ea.getPAlgebra().printout();
                std::cout << "r = " << context.alMod.getR() << std::endl;
                std::cout << "ctxtPrimes="<<context.ctxtPrimes
                    << ", specialPrimes="<<context.specialPrimes<<std::endl<<std::endl;
            }

#ifdef DEBUG_PRINTOUT
            dbgKey = &secretKey;
            dbgEa = const_cast<EncryptedArray*>(context.ea);
#endif // DEBUG_PRINTOUT
        }

        virtual void TearDown() override
        {
            cleanupGlobals();
        }

};

TEST_P(Test_CKKS, negating_ciphertext_works)
{
  Ctxt c1(publicKey);
  std::vector<cx_double> vd1, vd2;
  NTL::ZZX poly;
  NTL::xdouble rf, pm;

  ea.random(vd1);
  ea.encrypt(c1, publicKey, vd1);
  rf = c1.getRatFactor();
  pm = c1.getPtxtMag();
  ea.encode(poly, vd1, /*size=*/ 1.0);
  c1.negate();
  ea.decrypt(c1, secretKey, vd2);

  negateVec(vd1);

  EXPECT_TRUE(cx_equals(vd2, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd2)=" << largestCoeff(vd2)
                  << ", maxDiff=" << calcMaxDiff(vd1,vd2) << std::endl << std::endl;
  EXPECT_EQ(rf, c1.getRatFactor());
  EXPECT_EQ(pm, c1.getPtxtMag());
}

TEST_P(Test_CKKS, adding_poly_constant_to_ciphertext_works)
{
  Ctxt c1(publicKey);
  std::vector<cx_double> vd1, vd2, vd3;
  NTL::ZZX poly;
  NTL::xdouble rf, pm;

  ea.random(vd1);
  ea.random(vd2);
  ea.encrypt(c1, publicKey, vd1);
  rf = c1.getRatFactor();
  pm = c1.getPtxtMag();
  ea.encode(poly, vd2, /*size=*/ 1.0);
  c1.addConstantCKKS(poly);
  ea.decrypt(c1, secretKey, vd3);

  add(vd1, vd2);

  EXPECT_TRUE(cx_equals(vd3, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd3)=" << largestCoeff(vd3)
                  << ", maxDiff=" << calcMaxDiff(vd1,vd3) << std::endl << std::endl;
}

TEST_P(Test_CKKS, adding_negated_poly_constant_to_ciphertext_works)
{
  Ctxt c1(publicKey);
  std::vector<cx_double> vd1, vd2, vd3;
  NTL::ZZX poly;
  NTL::xdouble rf, pm;

  ea.random(vd1);
  ea.random(vd2);
  negateVec(vd2);
  ea.encrypt(c1, publicKey, vd1);
  rf = c1.getRatFactor();
  pm = c1.getPtxtMag();
  ea.encode(poly, vd2, /*size=*/ 1.0);
  c1.addConstantCKKS(poly);
  ea.decrypt(c1, secretKey, vd3);

  add(vd1, vd2);

  EXPECT_TRUE(cx_equals(vd3, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd3)=" << largestCoeff(vd3) << std::endl
                  << ", maxDiff=" << calcMaxDiff(vd1,vd3)
                  << ", maxRelDiff=" << calcMaxRelDiff(vd1,vd3) << std::endl << std::endl;
}

TEST_P(Test_CKKS, multiplying_poly_constant_to_ciphertext_works)
{
  Ctxt c1(publicKey);
  std::vector<cx_double> vd1, vd2, vd3;
  NTL::ZZX poly;
  NTL::xdouble rf, pm;

  ea.random(vd1);
  ea.random(vd2);
  ea.encrypt(c1, publicKey, vd1);
  rf = c1.getRatFactor();
  pm = c1.getPtxtMag();
  ea.encode(poly, vd2, /*size=*/ 1.0);
  c1.multByConstantCKKS(poly);
  ea.decrypt(c1, secretKey, vd3);

  mul(vd1, vd2);
  rf *= ea.encodeScalingFactor();

  EXPECT_TRUE(cx_equals(vd3, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd3)=" << largestCoeff(vd3)
                  << ", maxDiff=" << calcMaxDiff(vd1,vd3) << std::endl << std::endl;
  EXPECT_EQ(rf, c1.getRatFactor());
  EXPECT_EQ(pm, c1.getPtxtMag());
}

TEST_P(Test_CKKS, adding_double_to_ciphertext_works)
{
  Ctxt c1(publicKey);
  std::vector<cx_double> vd1, vd2;
  NTL::xdouble rf, pm;
  std::vector<double> vd(1);
  ea.random(vd);

  ea.random(vd1);
  ea.encrypt(c1, publicKey, vd1);
  rf = c1.getRatFactor();
  pm = c1.getPtxtMag();
  c1.addConstantCKKS(vd[0]);
  ea.decrypt(c1, secretKey, vd2);

  add(vd1, vd[0]);

  EXPECT_TRUE(cx_equals(vd2, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd2)=" << largestCoeff(vd2)
                  << ", maxDiff=" << calcMaxDiff(vd1,vd2) << std::endl << std::endl;
}

TEST_P(Test_CKKS, multiplying_double_to_ciphertext_works)
{
  Ctxt c1(publicKey);
  std::vector<cx_double> vd1, vd2, vd0;
  NTL::xdouble rf, pm;
  std::vector<double> vd(1);
  ea.random(vd);

  ea.random(vd1);
  vd0 = vd1;
  ea.encrypt(c1, publicKey, vd1);
  rf = c1.getRatFactor();
  pm = c1.getPtxtMag();
  c1.multByConstantCKKS(vd[0]);
  ea.decrypt(c1, secretKey, vd2);

  mul(vd1, vd[0]);
  rf /= vd[0];
  pm *= std::abs(vd[0]);

  EXPECT_TRUE(cx_equals(vd2, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd2)=" << largestCoeff(vd2)
                  << ", maxDiff=" << calcMaxDiff(vd1,vd2) << std::endl
		  << ", ptxtMag=" << c1.getPtxtMag() << std::endl
		  << ", vd[0]=" << vd[0] << std::endl;
  EXPECT_EQ(rf, c1.getRatFactor());
  EXPECT_EQ(pm, c1.getPtxtMag());
}

TEST_P(Test_CKKS, getting_the_complex_conjugate_works)
{
  Ctxt c1(publicKey);
  std::vector<cx_double> vd1, vd2;
  NTL::xdouble rf, pm;
  NTL::ZZX poly;

  ea.random(vd1);
  ea.encrypt(c1, publicKey, vd1);
  rf = c1.getRatFactor();
  pm = c1.getPtxtMag();
  ea.encode(poly, vd1, /*size=*/ 1.0);
  c1.complexConj();
  ea.decrypt(c1, secretKey, vd2);

  conjVec(vd1);

  EXPECT_TRUE(cx_equals(vd2, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd2)=" << largestCoeff(vd2)
                  << ", maxDiff=" << calcMaxDiff(vd1,vd2) << std::endl << std::endl;
  EXPECT_EQ(rf, c1.getRatFactor());
  EXPECT_EQ(pm, c1.getPtxtMag());
}

TEST_P(Test_CKKS, adding_ciphertexts_works)
{
  Ctxt c1(publicKey), c2(publicKey);
  std::vector<cx_double> vd1, vd2, vd3;

  ea.random(vd1);
  ea.random(vd2);
  ea.encrypt(c1, publicKey, vd1);
  ea.encrypt(c2, publicKey, vd2);
  c1 += c2;
  ea.decrypt(c1, secretKey, vd3);

  add(vd1, vd2);

  EXPECT_TRUE(cx_equals(vd3, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd3)=" << largestCoeff(vd3)
                  << ", maxDiff=" << calcMaxDiff(vd1,vd3) << std::endl << std::endl;
}

TEST_P(Test_CKKS, subtracting_ciphertexts_works)
{
  Ctxt c1(publicKey), c2(publicKey);
  std::vector<cx_double> vd1, vd2, vd3;

  ea.random(vd1);
  ea.random(vd2);
  ea.encrypt(c1, publicKey, vd1);
  ea.encrypt(c2, publicKey, vd2);
  c1 -= c2;
  ea.decrypt(c1, secretKey, vd3);

  sub(vd1, vd2);

  EXPECT_TRUE(cx_equals(vd3, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd3)=" << largestCoeff(vd3)
                  << ", maxDiff=" << calcMaxDiff(vd1,vd3) << std::endl << std::endl;
}

TEST_P(Test_CKKS, raw_multiplication_of_ciphertexts_works)
{
  Ctxt c1(publicKey), c2(publicKey);
  std::vector<cx_double> vd1, vd2, vd3;

  ea.random(vd1);
  ea.random(vd2);
  ea.encrypt(c1, publicKey, vd1);
  ea.encrypt(c2, publicKey, vd2);
  NTL::xdouble expectedPtxtMag = c1.getPtxtMag() * c2.getPtxtMag();
  c1 *= c2;
  ea.decrypt(c1, secretKey, vd3);

  mul(vd1, vd2);

  EXPECT_TRUE(cx_equals(vd3, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd3)=" << largestCoeff(vd3)
                  << ", maxDiff=" << calcMaxDiff(vd1,vd3) << std::endl << std::endl;
  EXPECT_EQ(expectedPtxtMag, c1.getPtxtMag());
}

TEST_P(Test_CKKS, high_level_multiplication_of_ciphertexts_works)
{
  Ctxt c1(publicKey), c2(publicKey);
  std::vector<cx_double> vd1, vd2, vd3;

  ea.random(vd1);
  ea.random(vd2);
  ea.encrypt(c1, publicKey, vd1);
  ea.encrypt(c2, publicKey, vd2);
  NTL::xdouble expectedPtxtMag = c1.getPtxtMag() * c2.getPtxtMag();
  c1.multiplyBy(c2);
  ea.decrypt(c1, secretKey, vd3);

  mul(vd1, vd2);

  EXPECT_TRUE(cx_equals(vd3, vd1, NTL::conv<double>(epsilon)))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd3)=" << largestCoeff(vd3)
                  << ", maxDiff=" << calcMaxDiff(vd1,vd3) << std::endl << std::endl;
  EXPECT_EQ(expectedPtxtMag, c1.getPtxtMag());
}

TEST_P(Test_CKKS, multiplying_ciphertext_by_negative_constant_and_then_adding_to_other_ciphertext_works)
{
  Ctxt c1(publicKey), c2(publicKey);
  std::vector<cx_double> vd1, vd2, vd3, vd4;
  NTL::ZZX poly;

  ea.random(vd1);
  ea.random(vd2);
  ea.encrypt(c1, publicKey, vd1);
  ea.encrypt(c2, publicKey, vd2);
  c1.multByConstantCKKS(std::make_pair<long, long>(-5, 2));
  c1 += c2;
  ea.decrypt(c1, secretKey, vd4);

  mul(vd1,std::vector<cx_double>(ea.size(),-5/2.0));
  add(vd1, vd2);

  EXPECT_TRUE(cx_equals(vd4, vd1, epsilon))
                  << "  max(vd1)=" << largestCoeff(vd1)
                  << ", max(vd4)=" << largestCoeff(vd4) << std::endl
                  << ", maxDiff=" << calcMaxDiff(vd1,vd4) << std::endl << std::endl;
}

INSTANTIATE_TEST_SUITE_P(typical_parameters, Test_CKKS, ::testing::Values(
            //SLOW
            Parameters(1024, 20, 150, 0.01)
            //FAST
            //Parameters(128, 20, 150, 0.01)
            ));

} // namespace


/*******************************************************************************
 *
 * MIT License
 *
 * Copyright (c) 2021 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *******************************************************************************/

#include <miopen/handle.hpp>
#include <miopen/miopen.h>
#include <miopen/util_sol.hpp>
#include <miopen/tensor.hpp>
#include <miopen/tensor_layout.hpp>
#include <miopen/batched_transpose_sol.hpp>
#include <miopen/invoker.hpp>
#include <miopen/invoke_params.hpp>
#include <boost/optional.hpp>
#include <vector>
#include <cstdlib>
#include <ctime>
#include "test.hpp"
#include "driver.hpp"
#include "random.hpp"

template <>
struct miopen_type<uint8_t> : std::integral_constant<miopenDataType_t, miopenInt8>
{
};

template <>
struct miopen_type<uint16_t> : std::integral_constant<miopenDataType_t, miopenHalf>
{
};

template <typename T>
void cpu_nchw2nhwc(T* dst, T* src, uint64_t N, uint64_t C, uint64_t H, uint64_t W)
{
    for(uint64_t i_n = 0; i_n < N; i_n++)
    {
        for(uint64_t i_h = 0; i_h < H; i_h++)
        {
            for(uint64_t i_w = 0; i_w < W; i_w++)
            {
                for(uint64_t i_c = 0; i_c < C; i_c++)
                {
                    uint64_t idx_nhwc = i_n * H * W * C + i_h * W * C + i_w * C + i_c;
                    uint64_t idx_nchw = i_n * C * H * W + i_c * H * W + i_h * W + i_w;
                    dst[idx_nhwc]     = src[idx_nchw];
                }
            }
        }
    }
}

template <typename T>
void cpu_nhwc2nchw(T* dst, T* src, uint64_t N, uint64_t C, uint64_t H, uint64_t W)
{
    for(uint64_t i_n = 0; i_n < N; i_n++)
    {
        for(uint64_t i_c = 0; i_c < C; i_c++)
        {
            for(uint64_t i_h = 0; i_h < H; i_h++)
            {
                for(uint64_t i_w = 0; i_w < W; i_w++)
                {
                    uint64_t idx_nhwc = i_n * H * W * C + i_h * W * C + i_w * C + i_c;
                    uint64_t idx_nchw = i_n * C * H * W + i_c * H * W + i_h * W + i_w;
                    dst[idx_nchw]     = src[idx_nhwc];
                }
            }
        }
    }
}

template <typename T, typename TRANSPOSE_SOL>
struct cpu_transpose
{
};

template <typename T>
struct cpu_transpose<T, miopen::TransposeSolutionDefault2Nhwc>
{
    static void run(T* dst, T* src, uint64_t N, uint64_t C, uint64_t H, uint64_t W)
    {
        cpu_nchw2nhwc<T>(dst, src, N, C, H, W);
    }
};

template <typename T>
struct cpu_transpose<T, miopen::TransposeSolutionNhwc2Default>
{
    static void run(T* dst, T* src, uint64_t N, uint64_t C, uint64_t H, uint64_t W)
    {
        cpu_nhwc2nchw<T>(dst, src, N, C, H, W);
    }
};

template <typename TRANSPOSE_SOL>
struct transpose_str
{
};

template <>
struct transpose_str<miopen::TransposeSolutionDefault2Nhwc>
{
    static std::string get() { return "nchw2nhwc"; }
};

template <>
struct transpose_str<miopen::TransposeSolutionNhwc2Default>
{
    static std::string get() { return "nhwc2nchw"; }
};

enum tensor_layout_t
{
    miopen_tensor_layout_nchw,
    miopen_tensor_layout_ncdhw,
    miopen_tensor_layout_nhwc,
    miopen_tensor_layout_ndhwc,
};

std::string tensor_layout_to_string(tensor_layout_t layout)
{
    std::string layout_string("N/A");
    if(layout == miopen_tensor_layout_nchw)
        layout_string = "NCHW";
    else if(layout == miopen_tensor_layout_ncdhw)
        layout_string = "NCDHW";
    else if(layout == miopen_tensor_layout_nhwc)
        layout_string = "NHWC";
    else if(layout == miopen_tensor_layout_ndhwc)
        layout_string = "NDHWC";
    else
        MIOPEN_THROW("Unsupported tensor layout");
    return layout_string;
}

template <typename T>
struct to_miopen_data_type
{
};

template <>
struct to_miopen_data_type<float>
{
    static miopenDataType_t get() { return miopenFloat; }
};

template <>
struct to_miopen_data_type<uint16_t>
{
    static miopenDataType_t get() { return miopenHalf; } // we actually didn't calculate 16bit float
};

template <>
struct to_miopen_data_type<uint8_t>
{
    static miopenDataType_t get() { return miopenInt8; }
};

#define RAND_INTEGER_MAX 120
#define RAND_INTEGER_MIN -88

static int gen_rand_integer()
{
    // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
    static int inited = 0;
    if(inited == 0)
    {
        std::srand(std::time(nullptr));
        inited = 1;
    }
    return GET_RAND();
}

template <typename T>
void rand_tensor_integer(tensor<T>& t, int max = RAND_INTEGER_MAX, int min = RAND_INTEGER_MIN)
{
    // use integer to random.
    for(int i = 0; i < t.data.size(); i++)
        t[i] = static_cast<T>(gen_rand_integer() % (max - min) + min);
}

template <typename T>
bool compare_equal(T r1, T r2)
{
    return r1 == r2;
}

template <>
bool compare_equal<float>(float r1, float r2)
{
    return miopen::float_equal(r1, r2);
}

template <typename T>
bool verify_tensor(tensor<T>& t_gpu, tensor<T>& t_cpu)
{
    if(t_gpu.data.size() != t_cpu.data.size())
    {
        MIOPEN_LOG_E("size not equal, should not happen");
        return false;
    }
    auto idx          = miopen::mismatch_idx(t_gpu.data, t_cpu.data, compare_equal<T>);
    bool valid_result = idx >= miopen::range_distance(t_cpu);

    if(!valid_result)
    {
        std::cout << "diff at:" << idx << ", gpu:" << t_gpu[idx] << ", cpu:" << t_cpu[idx]
                  << std::endl;
    }
    return valid_result;
}

struct transpose_base
{
    miopenHandle_t handle{};
#if MIOPEN_BACKEND_OPENCL
    cl_command_queue q{};
#endif

    transpose_base()
    {
        miopenCreate(&handle);
#if MIOPEN_BACKEND_OPENCL
        miopenGetStream(handle, &q);
#endif
    }
    ~transpose_base() { miopenDestroy(handle); }

    static std::vector<uint32_t> get_image_size() { return {1, 9, 14}; }

    static std::vector<uint32_t> get_channel_size() { return {3, 8, 14}; }

    static std::vector<uint32_t> get_batch_size() { return {1, 2}; }

    template <typename F>
    void iterate_transpose(F f)
    {
        std::vector<uint32_t> channel_list = get_channel_size();
        std::vector<uint32_t> image_list   = get_image_size();
        std::vector<uint32_t> batch_list   = get_batch_size();
        channel_list.push_back(gen_rand_integer() % 13 + 29);
        image_list.push_back(gen_rand_integer() % 13 + 15);
        batch_list.push_back(gen_rand_integer() % 4 + 3);

        for(uint32_t c : channel_list)
        {
            for(uint32_t h : image_list)
            {
                for(uint32_t w : image_list)
                {
                    for(uint32_t n : batch_list)
                    {
                        f(n, c, h, w);
                    }
                }
            }
        }
    }
};

struct transpose_invoke_param : public miopen::InvokeParams
{
    ConstData_t src = nullptr;
    Data_t dst      = nullptr;

    transpose_invoke_param(ConstData_t src_, Data_t dst_) : src(src_), dst(dst_) {}
    transpose_invoke_param(miopen::InvokeType type_, ConstData_t src_, Data_t dst_)
        : InvokeParams{type_}, src(src_), dst(dst_)
    {
    }
};

template <typename T, typename TRANSPOSE_SOL>
struct transpose_test : transpose_base
{
    void run()
    {
        auto run_transpose = [this](uint32_t n, uint32_t c, uint32_t h, uint32_t w) {
            int tensor_sz = n * c * h * w;
            std::vector<int> tensor_len({static_cast<int>(n),
                                         static_cast<int>(c),
                                         static_cast<int>(h),
                                         static_cast<int>(w)});

            std::vector<int> tensor_strides;

            std::string layout_default = miopen::tensor_layout_get_default(4);
            std::string layout_string  = tensor_layout_to_string(miopen_tensor_layout_nchw);

            miopen::tensor_layout_to_strides(
                tensor_len, layout_default, layout_string, tensor_strides);

            tensor<T> t_src(tensor_len, tensor_strides);
            tensor<T> t_dst(tensor_len, tensor_strides);
            tensor<T> t_dst_gpu(tensor_len, tensor_strides);
            rand_tensor_integer(t_src);
#if MIOPEN_BACKEND_OPENCL
            cl_context cl_ctx;
            clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &cl_ctx, nullptr);
            cl_int status = CL_SUCCESS;
            cl_mem src_dev =
                clCreateBuffer(cl_ctx, CL_MEM_READ_WRITE, sizeof(T) * tensor_sz, nullptr, &status);
            cl_mem dst_dev =
                clCreateBuffer(cl_ctx, CL_MEM_READ_WRITE, sizeof(T) * tensor_sz, nullptr, nullptr);
            status |= clEnqueueWriteBuffer(q,
                                           src_dev,
                                           CL_TRUE,
                                           0,
                                           sizeof(T) * tensor_sz,
                                           t_src.data.data(),
                                           0,
                                           nullptr,
                                           nullptr);
            EXPECT(status == CL_SUCCESS);
#elif MIOPEN_BACKEND_HIP
            void* src_dev;
            void* dst_dev;
            EXPECT(hipMalloc(&src_dev, sizeof(T) * tensor_sz) == hipSuccess);
            EXPECT(hipMalloc(&dst_dev, sizeof(T) * tensor_sz) == hipSuccess);
            EXPECT(hipMemcpy(
                       src_dev, t_src.data.data(), sizeof(T) * tensor_sz, hipMemcpyHostToDevice) ==
                   hipSuccess);
#endif

            const auto invoke_param = transpose_invoke_param{
                DataCast(static_cast<const void*>(src_dev)), DataCast(dst_dev)};

            miopen::ExecutionContext ctx;
            ctx.SetStream(&miopen::deref(this->handle));
            ctx.DetectRocm();
            // ctx.SetupFloats();

            TRANSPOSE_SOL transpose_sol(ctx, to_miopen_data_type<T>::get(), n, c, h, w);

            std::vector<OpKernelArg> opArgs = transpose_sol.GetKernelArg();

            boost::optional<miopen::InvokerFactory> invoker_factory(
                [=](const std::vector<miopen::Kernel>& kernels) mutable {
                    return [=](const miopen::Handle& handle,
                               const miopen::AnyInvokeParams& primitive_param) mutable {
                        decltype(auto) invoke_params =
                            primitive_param.CastTo<transpose_invoke_param>();

                        const auto k = handle.Run(kernels[0]);

                        opArgs[0] = OpKernelArg(invoke_params.dst);
                        opArgs[1] = OpKernelArg(invoke_params.src);

                        k(opArgs);
                    };
                });

            std::vector<miopen::solver::KernelInfo> construction_params{transpose_sol.GetKernel()};

            const auto invoker =
                miopen::deref(this->handle).PrepareInvoker(*invoker_factory, construction_params);

            // run gpu
            invoker(miopen::deref(this->handle), invoke_param);

            // run cpu
            cpu_transpose<T, TRANSPOSE_SOL>::run(t_dst.data.data(), t_src.data.data(), n, c, h, w);

#if MIOPEN_BACKEND_OPENCL
            status = clEnqueueReadBuffer(q,
                                         dst_dev,
                                         CL_TRUE,
                                         0,
                                         sizeof(T) * tensor_sz,
                                         t_dst_gpu.data.data(),
                                         0,
                                         nullptr,
                                         nullptr);
            EXPECT(status == CL_SUCCESS);
#elif MIOPEN_BACKEND_HIP
            EXPECT(hipMemcpy(t_dst_gpu.data.data(),
                             dst_dev,
                             sizeof(T) * tensor_sz,
                             hipMemcpyDeviceToHost) == hipSuccess);
#endif

            // we expect excact match, since use integer
            bool valid_result = verify_tensor(t_dst_gpu, t_dst);

            std::cout << "[" << transpose_str<TRANSPOSE_SOL>::get() << ", b" << (sizeof(T) * 8)
                      << " ] "
                      << "n:" << n << ", c:" << c << ", h:" << h << ", w:" << w
                      << ", valid:" << valid_result << std::endl;

            EXPECT(valid_result == true);

#if MIOPEN_BACKEND_OPENCL
            clReleaseMemObject(src_dev);
            clReleaseMemObject(dst_dev);
#elif MIOPEN_BACKEND_HIP
            hipFree(src_dev);
            hipFree(dst_dev);
#endif
        };

        iterate_transpose(run_transpose);
    }
};

int main()
{
    run_test<transpose_test<float, miopen::TransposeSolutionDefault2Nhwc>>();
    run_test<transpose_test<uint16_t, miopen::TransposeSolutionDefault2Nhwc>>();
    run_test<transpose_test<uint8_t, miopen::TransposeSolutionDefault2Nhwc>>();

    run_test<transpose_test<float, miopen::TransposeSolutionNhwc2Default>>();
    run_test<transpose_test<uint16_t, miopen::TransposeSolutionNhwc2Default>>();
    run_test<transpose_test<uint8_t, miopen::TransposeSolutionNhwc2Default>>();
}


//
// Created by huangkun on 2020/1/11.
//

#ifndef OPENGV2_NORMALBUNDLEADJUSTMENT_HPP
#define OPENGV2_NORMALBUNDLEADJUSTMENT_HPP

#include <Eigen/Eigen>

#include <opengv2/bundle_adjustment/BundleAdjustmentBase.hpp>

namespace opengv2 {
    class NormalBundleAdjustment : public BundleAdjustmentBase {
    public:
        NormalBundleAdjustment(bool fixTsb, bool robust, bool planarConstrain);

        void
        run(const std::map<double, Bodyframe::Ptr> &keyframes,
            const std::map<int, LandmarkBase::Ptr> &landmarks) override;

        struct ReprojectionError {
            EIGEN_MAKE_ALIGNED_OPERATOR_NEW

            explicit ReprojectionError(const Eigen::Ref<const Eigen::Vector3d> &bearingVector, bool isMono)
                    : isMono_(isMono), bearingVector_(bearingVector) {}

            template<typename T>
            bool operator()(const T *const Qbw_twb, const T *const lm, const T *const Tcb, T *residuals) const {
                Eigen::Quaternion<T> Qbw(Qbw_twb);
                Qbw.normalize();
                Eigen::Map<Eigen::Matrix<T, 3, 1> const> const twb(Qbw_twb + 4);
                Eigen::Map<Eigen::Matrix<T, 3, 1> const> const Xw(lm);
                Eigen::Quaternion<T> Qcb(Tcb);
                Qcb.normalize();
                Eigen::Map<Eigen::Matrix<T, 3, 1> const> const tcb(Tcb + 4);
                Eigen::Map<Eigen::Matrix<T, 3, 1>> res(residuals);

                Eigen::Matrix<T, 3, 1> Xb = Qbw * (Xw - twb);
                Eigen::Matrix<T, 3, 1> Xc = Qcb * Xb + tcb;
                if (isMono_)
                    Xc.normalize();

                res = bearingVector_ - Xc;
                return true;
            }

            static ceres::CostFunction *
            Create(const Eigen::Ref<const Eigen::Vector3d> &bearingVector, bool isMono) {
                return (new ceres::AutoDiffCostFunction<ReprojectionError, 3, 7, 3, 7>(
                        new ReprojectionError(bearingVector, isMono)));
            }

            bool isMono_;
            Eigen::Vector3d bearingVector_;
        };

    protected:
        void optimize(ceres::Problem &problem,
                      const std::map<double, Bodyframe::Ptr> &keyframes,
                      const std::map<int, LandmarkBase::Ptr> &landmarks) override;

        bool fixTsb_;
        bool robust_;
        bool planarConstrain_;
    };
}

#endif //OPENGV2_NORMALBUNDLEADJUSTMENT_HPP


#include <boost/math/special_functions/round.hpp>