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

/*
 Copyright (C) 2004, 2005, 2006, 2007 Ferdinando Ametrano
 Copyright (C) 2004, 2005, 2006, 2007, 2008 StatPro Italia srl

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

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

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

#include <ql/types.hpp>
#include <ql/settings.hpp>
#include <ql/utilities/dataparsers.hpp>
#include <ql/version.hpp>

#ifdef QL_ENABLE_PARALLEL_UNIT_TEST_RUNNER
#include "paralleltestrunner.hpp"
#else
#include <boost/test/unit_test.hpp>
#endif

#include <boost/timer/timer.hpp>

/* Use BOOST_MSVC instead of _MSC_VER since some other vendors (Metrowerks,
   for example) also #define _MSC_VER
*/
#ifdef BOOST_MSVC
#  include <ql/auto_link.hpp>

#ifndef QL_ENABLE_PARALLEL_UNIT_TEST_RUNNER
#  define BOOST_LIB_NAME boost_unit_test_framework
#  include <boost/config/auto_link.hpp>
#  undef BOOST_LIB_NAME
#  define BOOST_LIB_NAME boost_timer
#  include <boost/config/auto_link.hpp>
#  undef BOOST_LIB_NAME
#  define BOOST_LIB_NAME boost_chrono
#  include <boost/config/auto_link.hpp>
#  undef BOOST_LIB_NAME
#  define BOOST_LIB_NAME boost_system
#  include <boost/config/auto_link.hpp>
#  undef BOOST_LIB_NAME
#endif

/* uncomment the following lines to unmask floating-point exceptions.
   See http://www.wilmott.com/messageview.cfm?catid=10&threadid=9481
*/
//#  include <float.h>
//   namespace { unsigned int u = _controlfp(_EM_INEXACT, _MCW_EM); }

#endif
#include "utilities.hpp"
#include "speedlevel.hpp"

#include "americanoption.hpp"
#include "andreasenhugevolatilityinterpl.hpp"
#include "amortizingbond.hpp"
#include "array.hpp"
#include "asianoptions.hpp"
#include "assetswap.hpp"
#include "autocovariances.hpp"
#include "barrieroption.hpp"
#include "basismodels.hpp"
#include "basketoption.hpp"
#include "batesmodel.hpp"
#include "bermudanswaption.hpp"
#include "binaryoption.hpp"
#include "blackdeltacalculator.hpp"
#include "blackformula.hpp"
#include "bonds.hpp"
#include "brownianbridge.hpp"
#include "businessdayconventions.hpp"
#include "calendars.hpp"
#include "callablebonds.hpp"
#include "capfloor.hpp"
#include "capflooredcoupon.hpp"
#include "cashflows.hpp"
#include "catbonds.hpp"
#include "cdo.hpp"
#include "cdsoption.hpp"
#include "chooseroption.hpp"
#include "cliquetoption.hpp"
#include "cms.hpp"
#include "cmsspread.hpp"
#include "commodityunitofmeasure.hpp"
#include "compiledboostversion.hpp"
#include "compoundoption.hpp"
#include "convertiblebonds.hpp"
#include "covariance.hpp"
#include "creditdefaultswap.hpp"
#include "creditriskplus.hpp"
#include "crosscurrencyratehelpers.hpp"
#include "curvestates.hpp"
#include "dates.hpp"
#include "daycounters.hpp"
#include "defaultprobabilitycurves.hpp"
#include "digitalcoupon.hpp"
#include "digitaloption.hpp"
#include "distributions.hpp"
#include "dividendoption.hpp"
#include "doublebarrieroption.hpp"
#include "doublebinaryoption.hpp"
#include "europeanoption.hpp"
#include "everestoption.hpp"
#include "exchangerate.hpp"
#include "extendedtrees.hpp"
#include "extensibleoptions.hpp"
#include "fastfouriertransform.hpp"
#include "fdheston.hpp"
#include "fdcir.hpp"
#include "fdmlinearop.hpp"
#include "fdcev.hpp"
#include "fdsabr.hpp"
#include "fittedbonddiscountcurve.hpp"
#include "forwardoption.hpp"
#include "forwardrateagreement.hpp"
#include "functions.hpp"
#include "gaussianquadratures.hpp"
#include "garch.hpp"
#include "gjrgarchmodel.hpp"
#include "gsr.hpp"
#include "hestonmodel.hpp"
#include "hestonslvmodel.hpp"
#include "himalayaoption.hpp"
#include "hybridhestonhullwhiteprocess.hpp"
#include "indexes.hpp"
#include "inflation.hpp"
#include "inflationcapfloor.hpp"
#include "inflationcapflooredcoupon.hpp"
#include "inflationcpibond.hpp"
#include "inflationcpicapfloor.hpp"
#include "inflationcpiswap.hpp"
#include "inflationvolatility.hpp"
#include "instruments.hpp"
#include "integrals.hpp"
#include "interestrates.hpp"
#include "interpolations.hpp"
#include "jumpdiffusion.hpp"
#include "lazyobject.hpp"
#include "libormarketmodel.hpp"
#include "libormarketmodelprocess.hpp"
#include "linearleastsquaresregression.hpp"
#include "lookbackoptions.hpp"
#include "lowdiscrepancysequences.hpp"
#include "margrabeoption.hpp"
#include "marketmodel.hpp"
#include "marketmodel_smmcapletalphacalibration.hpp"
#include "marketmodel_smmcapletcalibration.hpp"
#include "marketmodel_smmcaplethomocalibration.hpp"
#include "marketmodel_smm.hpp"
#include "marketmodel_cms.hpp"
#include "markovfunctional.hpp"
#include "matrices.hpp"
#include "mclongstaffschwartzengine.hpp"
#include "mersennetwister.hpp"
#include "money.hpp"
#include "noarbsabr.hpp"
#include "normalclvmodel.hpp"
#include "nthorderderivativeop.hpp"
#include "nthtodefault.hpp"
#include "numericaldifferentiation.hpp"
#include "observable.hpp"
#include "ode.hpp"
#include "operators.hpp"
#include "optimizers.hpp"
#include "optionletstripper.hpp"
#include "overnightindexedswap.hpp"
#include "pagodaoption.hpp"
#include "partialtimebarrieroption.hpp"
#include "pathgenerator.hpp"
#include "period.hpp"
#include "piecewiseyieldcurve.hpp"
#include "piecewisezerospreadedtermstructure.hpp"
#include "quantooption.hpp"
#include "quotes.hpp"
#include "rangeaccrual.hpp"
#include "riskneutraldensitycalculator.hpp"
#include "riskstats.hpp"
#include "rngtraits.hpp"
#include "rounding.hpp"
#include "sampledcurve.hpp"
#include "schedule.hpp"
#include "shortratemodels.hpp"
#include "sofrfutures.hpp"
#include "solvers.hpp"
#include "spreadoption.hpp"
#include "squarerootclvmodel.hpp"
#include "swingoption.hpp"
#include "stats.hpp"
#include "swap.hpp"
#include "swapforwardmappings.hpp"
#include "swaption.hpp"
#include "swaptionvolatilitycube.hpp"
#include "swaptionvolatilitymatrix.hpp"
#include "termstructures.hpp"
#include "timegrid.hpp"
#include "timeseries.hpp"
#include "tqreigendecomposition.hpp"
#include "tracing.hpp"
#include "transformedgrid.hpp"
#include "twoassetbarrieroption.hpp"
#include "twoassetcorrelationoption.hpp"
#include "ultimateforwardtermstructure.hpp"
#include "variancegamma.hpp"
#include "varianceoption.hpp"
#include "varianceswaps.hpp"
#include "volatilitymodels.hpp"
#include "vpp.hpp"
#include "zabr.hpp"

#include <iostream>
#include <iomanip>

using namespace boost::unit_test_framework;

namespace {

    boost::timer::cpu_timer t;

    void startTimer() { t.start(); }
    void stopTimer() {
        t.stop();
        std::cout << "\nTests completed in ";
        double seconds = t.elapsed().wall * 1e-9;
        int hours = int(seconds/3600);
        seconds -= hours * 3600;
        int minutes = int(seconds/60);
        seconds -= minutes * 60;
        if (hours > 0)
            std::cout << hours << " h ";
        if (hours > 0 || minutes > 0)
            std::cout << minutes << " m ";
        std::cout << std::fixed << std::setprecision(0)
                  << seconds << " s\n" << std::endl;
    }

    void configure(QuantLib::Date evaluationDate) {
        /* if needed, a subset of the lines below can be
           uncommented and/or changed to run the test suite with a
           different configuration. In the future, we'll need a
           mechanism that doesn't force us to recompile (possibly a
           couple of command-line flags for the test suite?)
        */

        // QuantLib::Settings::instance().includeReferenceDateCashFlows() = true;
        // QuantLib::Settings::instance().includeTodaysCashFlows() = boost::none;

        QuantLib::Settings::instance().evaluationDate() = evaluationDate;
    }

}

#if defined(QL_ENABLE_SESSIONS)
namespace QuantLib {

    ThreadKey sessionId() { return 0; }

}
#endif

QuantLib::Date evaluation_date(int argc, char** argv) {
    /*! Dead simple parser:
        - passing --date=YYYY-MM-DD causes the test suite to run on
          that date;
        - passing --date=today causes it to run on today's date;
        - passing nothing causes it to run on a known date for which
          there should be no date-dependent errors as far as we know.

        Dates that should eventually be checked include:
        - 2015-08-29 causes three tests to fail;
        - 2016-02-29 causes two tests to fail.
    */

    QuantLib::Date knownGoodDefault =
        QuantLib::Date(16, QuantLib::September, 2015);

    for (int i=1; i<argc; ++i) {
        std::string arg = argv[i];
        if (arg == "--date=today")
            return QuantLib::Date::todaysDate();
        else if (arg.substr(0, 7) == "--date=")
            return QuantLib::DateParser::parseISO(arg.substr(7));
    }
    return knownGoodDefault;
}

SpeedLevel speed_level(int argc, char** argv) {
    /*! Again, dead simple parser:
        - passing --slow causes all tests to be run;
        - passing --fast causes most tests to be run, except the slowest;
        - passing --faster causes only the faster tests to be run;
        - passing nothing is the same as --slow
    */

    for (int i=1; i<argc; ++i) {
        std::string arg = argv[i];
        if (arg == "--slow")
            return Slow;
        else if (arg == "--fast")
            return Fast;
        else if (arg == "--faster")
            return Faster;
    }
    return Slow;
}

test_suite* init_unit_test_suite(int, char* []) {

    int argc = boost::unit_test::framework::master_test_suite().argc;
    char **argv = boost::unit_test::framework::master_test_suite().argv;
    configure(evaluation_date(argc, argv));
    SpeedLevel speed = speed_level(argc, argv);

    const QuantLib::Settings& settings = QuantLib::Settings::instance();
    std::ostringstream header;
    header <<
        " Testing "
        #ifdef BOOST_MSVC
        QL_LIB_NAME
        #else
        "QuantLib " QL_VERSION
        #endif
        "\n  QL_EXTRA_SAFETY_CHECKS "
        #ifdef QL_EXTRA_SAFETY_CHECKS
        "  defined"
        #else
        "undefined"
        #endif
        "\n  QL_USE_INDEXED_COUPON "
        #ifdef QL_USE_INDEXED_COUPON
        "   defined"
        #else
        " undefined"
        #endif
        "\n"
           << "evaluation date is " << settings.evaluationDate() << ",\n"
           << (settings.includeReferenceDateEvents()
               ? "reference date events are included,\n"
               : "reference date events are excluded,\n")
           << (settings.includeTodaysCashFlows() == boost::none ?
               "" : (*settings.includeTodaysCashFlows() ?
                     "today's cashflows are included,\n"
                     : "today's cashflows are excluded,\n"))
           << (settings.enforcesTodaysHistoricFixings()
               ? "today's historic fixings are enforced."
               : "today's historic fixings are not enforced.")
           << "\nRunning "
           << (speed == Faster ? "faster" :
               (speed == Fast ?   "fast" : "all"))
           << " tests.";

    std::string rule = std::string(41, '=');

    BOOST_TEST_MESSAGE(rule);
    BOOST_TEST_MESSAGE(header.str());
    BOOST_TEST_MESSAGE(rule);
    test_suite* test = BOOST_TEST_SUITE("QuantLib test suite");

    test->add(QUANTLIB_TEST_CASE(startTimer));

    test->add(AmericanOptionTest::suite());
    test->add(AndreasenHugeVolatilityInterplTest::suite(speed));
    test->add(ArrayTest::suite());
    test->add(AsianOptionTest::suite());
    test->add(AssetSwapTest::suite()); // fails with QL_USE_INDEXED_COUPON
    test->add(AutocovariancesTest::suite());
    test->add(BarrierOptionTest::suite());
    test->add(BasketOptionTest::suite(speed));
    test->add(BatesModelTest::suite());
    test->add(BermudanSwaptionTest::suite(speed));
    test->add(BinaryOptionTest::suite());
    test->add(BlackFormulaTest::suite());
    test->add(BondTest::suite());
    test->add(BrownianBridgeTest::suite());
    test->add(BusinessDayConventionTest::suite());
    test->add(CalendarTest::suite());
    test->add(CapFloorTest::suite());
    test->add(CapFlooredCouponTest::suite());
    test->add(CashFlowsTest::suite());
    test->add(CliquetOptionTest::suite());
    test->add(CmsTest::suite());
    test->add(CovarianceTest::suite());
    test->add(CPISwapTest::suite());
    test->add(CreditDefaultSwapTest::suite());
    test->add(CrossCurrencyRateHelpersTest::suite());
    test->add(CurveStatesTest::suite());
    test->add(DateTest::suite(speed));
    test->add(DayCounterTest::suite());
    test->add(DefaultProbabilityCurveTest::suite());
    test->add(DigitalCouponTest::suite()); // might fail with QL_USE_INDEXED_COUPON
    test->add(DigitalOptionTest::suite());
    test->add(DistributionTest::suite(speed));
    test->add(DividendOptionTest::suite());
    test->add(EuropeanOptionTest::suite());
    test->add(ExchangeRateTest::suite());
    test->add(FastFourierTransformTest::suite());
    test->add(FdHestonTest::suite(speed));
    test->add(FdmLinearOpTest::suite());
    test->add(FdCevTest::suite(speed));
    test->add(FdCIRTest::suite(speed));
    test->add(FdSabrTest::suite(speed));
    test->add(FittedBondDiscountCurveTest::suite());
    test->add(ForwardOptionTest::suite());
    test->add(ForwardRateAgreementTest::suite());
    test->add(FunctionsTest::suite());
    test->add(GARCHTest::suite());
    test->add(GaussianQuadraturesTest::suite());
    test->add(GJRGARCHModelTest::suite(speed));
    test->add(GsrTest::suite());
    test->add(HestonModelTest::suite(speed));
    test->add(HybridHestonHullWhiteProcessTest::suite(speed));
    test->add(IndexTest::suite());
    test->add(InflationTest::suite());
    test->add(InflationCapFloorTest::suite());
    test->add(InflationCapFlooredCouponTest::suite());
    test->add(InflationCPIBondTest::suite());
    test->add(InstrumentTest::suite());
    test->add(IntegralTest::suite());
    test->add(InterestRateTest::suite());
    test->add(InterpolationTest::suite());
    test->add(JumpDiffusionTest::suite());
    test->add(LazyObjectTest::suite());
    test->add(LinearLeastSquaresRegressionTest::suite());
    test->add(LookbackOptionTest::suite());
    test->add(LowDiscrepancyTest::suite());
    test->add(MarketModelTest::suite(speed));
    test->add(MarketModelCmsTest::suite(speed));
    test->add(MarketModelSmmTest::suite(speed));
    test->add(MarketModelSmmCapletAlphaCalibrationTest::suite());
    test->add(MarketModelSmmCapletCalibrationTest::suite());
    test->add(MarketModelSmmCapletHomoCalibrationTest::suite());
    test->add(MarkovFunctionalTest::suite(speed));
    test->add(MatricesTest::suite());
    test->add(MCLongstaffSchwartzEngineTest::suite());
    test->add(MersenneTwisterTest::suite());
    test->add(MoneyTest::suite());
    test->add(NumericalDifferentiationTest::suite());
    test->add(NthOrderDerivativeOpTest::suite());
    test->add(ObservableTest::suite());
    test->add(OdeTest::suite());
    test->add(OperatorTest::suite());
    test->add(OptimizersTest::suite(speed));
    test->add(OptionletStripperTest::suite());
    test->add(OvernightIndexedSwapTest::suite());
    test->add(PathGeneratorTest::suite());
    test->add(PeriodTest::suite());
    test->add(PiecewiseYieldCurveTest::suite());
    test->add(PiecewiseZeroSpreadedTermStructureTest::suite());
    test->add(QuantoOptionTest::suite());
    test->add(QuoteTest::suite());
    test->add(RangeAccrualTest::suite());
    test->add(RiskStatisticsTest::suite());
    test->add(RngTraitsTest::suite());
    test->add(RoundingTest::suite());
    test->add(SampledCurveTest::suite());
    test->add(ScheduleTest::suite());
    test->add(ShortRateModelTest::suite(speed)); // fails with QL_USE_INDEXED_COUPON
    test->add(Solver1DTest::suite());
    test->add(StatisticsTest::suite());
    test->add(SwapTest::suite());
    test->add(SwapForwardMappingsTest::suite());
    test->add(SwaptionTest::suite());
    test->add(SwaptionVolatilityCubeTest::suite());
    test->add(SwaptionVolatilityMatrixTest::suite());
    test->add(TermStructureTest::suite());
    test->add(TimeGridTest::suite());
    test->add(TimeSeriesTest::suite());
    test->add(TqrEigenDecompositionTest::suite());
    test->add(TracingTest::suite());
    test->add(TransformedGridTest::suite());
    test->add(UltimateForwardTermStructureTest::suite());
    test->add(VarianceSwapTest::suite());
    test->add(VolatilityModelsTest::suite());

    // tests for experimental classes
    test->add(AmortizingBondTest::suite());
    test->add(AsianOptionTest::experimental());
    test->add(BasismodelsTest::suite());
    test->add(BarrierOptionTest::experimental());
    test->add(DoubleBarrierOptionTest::experimental());
    test->add(BlackDeltaCalculatorTest::suite());
    test->add(CallableBondTest::suite());
    test->add(CatBondTest::suite());
    test->add(CdoTest::suite(speed));
    test->add(CdsOptionTest::suite());
    test->add(ChooserOptionTest::suite());
    test->add(CmsSpreadTest::suite());
    test->add(CommodityUnitOfMeasureTest::suite());
    test->add(CompiledBoostVersionTest::suite());
    test->add(CompoundOptionTest::suite());
    test->add(ConvertibleBondTest::suite());
    test->add(CreditRiskPlusTest::suite());
    test->add(DoubleBarrierOptionTest::suite(speed));
    test->add(DoubleBinaryOptionTest::suite());
    test->add(EuropeanOptionTest::experimental());
    test->add(EverestOptionTest::suite());
    test->add(ExtendedTreesTest::suite());
    test->add(ExtensibleOptionsTest::suite());
    test->add(GaussianQuadraturesTest::experimental());
    test->add(HestonModelTest::experimental());
    test->add(HestonSLVModelTest::experimental(speed));
    test->add(HimalayaOptionTest::suite());
    test->add(InflationCPICapFloorTest::suite());
    test->add(InflationVolTest::suite());
    test->add(MargrabeOptionTest::suite());
    test->add(NoArbSabrTest::suite());
    test->add(NormalCLVModelTest::experimental(speed));
    test->add(NthToDefaultTest::suite(speed));
    test->add(PagodaOptionTest::suite());
    test->add(PartialTimeBarrierOptionTest::suite());
    test->add(QuantoOptionTest::experimental());
    test->add(RiskNeutralDensityCalculatorTest::experimental(speed));
    test->add(SofrFuturesTest::suite());
    test->add(SpreadOptionTest::suite());
    test->add(SquareRootCLVModelTest::experimental());
    test->add(SwingOptionTest::suite(speed));
    test->add(TwoAssetBarrierOptionTest::suite());
    test->add(TwoAssetCorrelationOptionTest::suite());
    test->add(VarianceGammaTest::suite());
    test->add(VarianceOptionTest::suite());
    test->add(VPPTest::suite(speed));
    test->add(ZabrTest::suite(speed));

    // tests for deprecated classes
    test->add(LiborMarketModelTest::suite(speed));
    test->add(LiborMarketModelProcessTest::suite(speed));

    test->add(QUANTLIB_TEST_CASE(stopTimer));

    return test;
}


#include <jni.h>
#include <android/log.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <ctime>

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/scoped_ptr.hpp>

using namespace std;

#define  LOG_TAG	"Random"
#define  LOGI(...)	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define  LOGE(...)	__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

extern "C"
{
	JNIEXPORT void JNICALL Java_it_unibo_slam_random_RandomNative_initRandomSeed(JNIEnv *env, jobject obj);
	JNIEXPORT void JNICALL Java_it_unibo_slam_random_RandomNative_initSeed(JNIEnv *env, jobject obj,
			jlong seed);
	JNIEXPORT void JNICALL Java_it_unibo_slam_random_RandomNative_initUniform(JNIEnv *env, jobject obj,
			jint start, jint end);
	JNIEXPORT jint JNICALL Java_it_unibo_slam_random_RandomNative_next(JNIEnv *env, jobject obj);
}

// Global variables accessible only within this source file
namespace
{
	boost::mt19937 randomEngine;
	boost::scoped_ptr< boost::variate_generator< boost::mt19937&, boost::uniform_int<unsigned> > > randomGeneratorPtr;
}

JNIEXPORT void JNICALL Java_it_unibo_slam_random_RandomNative_initRandomSeed(JNIEnv *env, jobject obj)
{
	randomEngine.seed(static_cast<unsigned>(std::time(NULL)));
}

JNIEXPORT void JNICALL Java_it_unibo_slam_random_RandomNative_initSeed(JNIEnv *env, jobject obj,
		jlong seed)
{
	randomEngine.seed(seed);
}

JNIEXPORT void JNICALL Java_it_unibo_slam_random_RandomNative_initUniform(JNIEnv *env, jobject obj,
		jint start, jint end)
{
	randomGeneratorPtr.reset(new boost::variate_generator< boost::mt19937&, boost::uniform_int<unsigned> >
			(randomEngine, boost::uniform_int<unsigned>(start, end)));
}

JNIEXPORT jint JNICALL Java_it_unibo_slam_random_RandomNative_next(JNIEnv *env, jobject obj)
{
	return (*randomGeneratorPtr)();
}


//=======================================================================
// Copyright (c) 2013 Piotr Wygocki
//               2014 Piotr Godlewski
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================
/**
 * @file steiner_network_oracle.hpp
 * @brief
 * @author Piotr Wygocki, Piotr Godlewski
 * @version 1.0
 * @date 2013-06-24
 */
#ifndef PAAL_STEINER_NETWORK_ORACLE_HPP
#define PAAL_STEINER_NETWORK_ORACLE_HPP

#include "paal/iterative_rounding/min_cut.hpp"

#include <boost/range/as_array.hpp>

namespace paal {
namespace ir {

/**
 * @class steiner_network_violation_checker
 * @brief Violations checker for the separation oracle
 *      in the steiner network problem.
 */
class steiner_network_violation_checker {
    using AuxVertex = min_cut_finder::Vertex;
    using Violation = double;

  public:
    using Candidate = std::pair<AuxVertex, AuxVertex>;

    /**
     * Checks if any solution to the problem exists.
     */
    template <typename Problem>
    bool check_if_solution_exists(Problem &problem) {
        auto const &g = problem.get_graph();
        auto const &index = problem.get_index();
        m_min_cut.init(num_vertices(g));

        for (auto e : boost::as_array(edges(g))) {
            auto u = get(index, source(e, g));
            auto v = get(index, target(e, g));
            m_min_cut.add_edge_to_graph(u, v, 1, 1);
        }

        for (auto res : problem.get_restrictions_vec()) {
            if (check_violation(res, problem)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Returns an iterator range of violated constraint candidates.
     */
    template <typename Problem, typename LP>
    auto get_violation_candidates(const Problem &problem, const LP &lp)
        ->decltype(problem.get_restrictions_vec()) {

        fill_auxiliary_digraph(problem, lp);
        return problem.get_restrictions_vec();
    }

    /**
     * Checks if the given constraint candidate is violated an if it is,
     * returns the violation value and violated constraint ID.
     */
    template <typename Problem>
    Violation check_violation(Candidate candidate, const Problem &problem) {
        double violation =
            find_violation(candidate.first, candidate.second, problem);
        if (problem.get_compare().g(violation, 0)) {
            return violation;
        } else {
            return Violation{};
        }
    }

    /**
     * Adds a violated constraint to the LP.
     */
    template <typename Problem, typename LP>
    void add_violated_constraint(Candidate violation, const Problem &problem,
                                 LP &lp) {
        if (violation != m_min_cut.get_last_cut()) {
            find_violation(violation.first, violation.second, problem);
        }

        auto const &g = problem.get_graph();
        auto const &index = problem.get_index();
        auto restriction =
            problem.get_max_restriction(violation.first, violation.second);

        for (auto const &e : problem.get_edges_in_solution()) {
            if (is_edge_in_violating_cut(e, g, index)) {
                --restriction;
            }
        }

        lp::linear_expression expr;
        for (auto const &e : problem.get_edge_map()) {
            if (is_edge_in_violating_cut(e.second, g, index)) {
                expr += e.first;
            }
        }

        lp.add_row(std::move(expr) >= restriction);
    }

  private:

    /**
     * Checks if a given edge belongs to the cut given by the current violating
     * set.
     */
    template <typename Edge, typename Graph, typename Index>
    bool is_edge_in_violating_cut(Edge edge, const Graph &g,
                                  const Index &index) {
        auto u = get(index, source(edge, g));
        auto v = get(index, target(edge, g));
        return m_min_cut.is_in_source_set(u) != m_min_cut.is_in_source_set(v);
    }

    /**
     * Creates the auxiliary directed graph used for feasibility testing.
     */
    template <typename Problem, typename LP>
    void fill_auxiliary_digraph(Problem &problem, const LP &lp) {
        auto const &g = problem.get_graph();
        auto const &index = problem.get_index();
        m_min_cut.init(num_vertices(g));

        for (auto const &e : problem.get_edge_map()) {
            lp::col_id col_idx = e.first;
            double col_val = lp.get_col_value(col_idx);

            if (problem.get_compare().g(col_val, 0)) {
                auto u = get(index, source(e.second, g));
                auto v = get(index, target(e.second, g));
                m_min_cut.add_edge_to_graph(u, v, col_val, col_val);
            }
        }

        for (auto const &e : problem.get_edges_in_solution()) {
            auto u = get(index, source(e, g));
            auto v = get(index, target(e, g));
            m_min_cut.add_edge_to_graph(u, v, 1, 1);
        }
    }

    /**
     * Finds the most violated set of vertices containing \c src and not
     * containing \c trg and returns its violation value.
     * @param src vertex to be contained in the violating set
     * @param trg vertex not to be contained in the violating set
     * @param problem problem object
     * @return violation of the found set
     */
    template <typename Problem>
    double find_violation(AuxVertex src, AuxVertex trg,
                          const Problem &problem) {
        double min_cut_weight = m_min_cut.find_min_cut(src, trg);
        double restriction = problem.get_max_restriction(src, trg);
        return restriction - min_cut_weight;
    }

    min_cut_finder m_min_cut;
};

} //! ir
} //! paal
#endif // PAAL_STEINER_NETWORK_ORACLE_HPP


/**----------------------------------------------------------------------------
 Copyright (c) 2019-, UT-Battelle, LLC
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
 list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
 this list of conditions and the following disclaimer in the documentation
 and/or other materials provided with the distribution.

 * Neither the name of the copyright holder nor the names of its
 contributors may be used to endorse or promote products derived from
 this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 Author(s): Jay Jay Billings (billingsjj <at> ornl <dot> gov)
 -----------------------------------------------------------------------------*/
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE kelvin

#include <boost/test/included/unit_test.hpp>
#include <mfem.hpp>
#include <vector>
#include <Grid.h>
#include <MeshContainer.h>
#include <H1FESpaceFactory.h>
#include <INIPropertyParser.h>
#include <HydrostaticCR.h>
#include <MFEMData.h>
#include <string>
#include <iostream>

using namespace std;
using namespace mfem;
using namespace Kelvin;
using namespace fire;

// Test file names
static std::string inputFile = "2SquaresInput-smallerMesh.ini";

namespace Kelvin {

/**
 * This operation checks that the strain rate can be computed correctly from
 * the MFEMOlevskyLVCR class.
 */
BOOST_AUTO_TEST_CASE(checkStrain) {

	// Create the space factory
	H1FESpaceFactory spaceFactory;

	// Create the data holder
	MFEMData data;
	data.load(inputFile);

	// Load the input file
	INIPropertyParser propertyParser;
	propertyParser.setSource(inputFile);
    propertyParser.parse();

	// Load the mesh
    MeshContainer mc(propertyParser.getPropertyBlock("mesh"),spaceFactory);
    int dim = mc.dimension();

    // Create the grid
    Grid grid(mc);
    // Just use the quadrature points as the particles
    auto points = mc.getQuadraturePoints();
    // FIXME! Convert to material points
    std::vector<MaterialPoint> mPoints;
    for (int i = 0; i < points.size(); i++) {
    	MaterialPoint point(points[i]);
    	mPoints.push_back(point);
    }

    // Set the particle properties to 1 to get a value for the internal forces
    // that is equal to the gradient/sum of gradients.
    for (int i = 0; i < mPoints.size(); i++) {
    	mPoints[i].stress[0] = {1.0,1.0};
    	mPoints[i].stress[1] = {1.0,1.0};
    	mPoints[i].mass = 1.0;
    	mPoints[i].bodyForce[0] = 1.0;
    	mPoints[i].bodyForce[1] = 1.0;
    	mPoints[i].vel[0] = 0.0;
    	mPoints[i].vel[1] = 0.0;
    }

    // Assemble it to create the arrays of nodes, shapes, masses, etc.
    grid.assemble(mPoints);

	// Compute the accelerations at the grid points
	grid.updateNodalAccelerations(1.0,mPoints);

	// Compute the velocities at the grid points from the momenta
	grid.updateNodalVelocitiesFromMomenta(mPoints);

	// Compute the velocity at the grid point for time dt = 1.0
	grid.updateNodalVelocities(1.0,mPoints);

	// Create the constitutive equation
	HydrostaticCR conRel;

	// Compute the strain
	for (int i = 0; i < mPoints.size(); i++) {
		conRel.updateStrainRate(grid,mPoints[i]);
	}
	// Setup reference values
	std::vector<double> p1Strain{1.0,0.0,0.0,1.0};
	std::vector<double> p2Strain{1.0,0.0,0.0,1.0};
	cout << "----- Strains" << endl;
	for (int i = 0; i < mPoints.size(); i++) {
		for (int j = 0; j < dim; j++) {
			for (int k = 0; k < dim; k++) {
				cout << mPoints[i].strain[j][k] << " ";
			}
			cout << endl;
		}
		cout << "-----" << endl;
	}
	// Check point 1
	BOOST_REQUIRE_CLOSE(p1Strain[0],mPoints[0].strain[0][0],1.0e-15);
	BOOST_REQUIRE_CLOSE(p1Strain[1],mPoints[0].strain[0][1],1.0e-15);
	BOOST_REQUIRE_CLOSE(p1Strain[2],mPoints[0].strain[1][0],1.0e-15);
	BOOST_REQUIRE_CLOSE(p1Strain[3],mPoints[0].strain[1][1],1.0e-15);
	// Check point 2
	BOOST_REQUIRE_CLOSE(p2Strain[0],mPoints[1].strain[0][0],1.0e-15);
	BOOST_REQUIRE_CLOSE(p2Strain[1],mPoints[1].strain[0][1],1.0e-15);
	BOOST_REQUIRE_CLOSE(p2Strain[2],mPoints[1].strain[1][0],1.0e-15);
	BOOST_REQUIRE_CLOSE(p2Strain[3],mPoints[1].strain[1][1],1.0e-15);

	return;
}

/**
 * This operation checks that the stress can be computed correctly from the
 * MFEMOlevskyLVCR class.
 */
BOOST_AUTO_TEST_CASE(checkStress) {

	// Create the space factory
	H1FESpaceFactory spaceFactory;

	// Load the input file
	INIPropertyParser propertyParser;
	propertyParser.setSource(inputFile);
    propertyParser.parse();

	// Create the data holder
	MFEMData data;
	data.load(inputFile);

	// Load the mesh
    MeshContainer mc(propertyParser.getPropertyBlock("mesh"),spaceFactory);
    int dim = mc.dimension();

    // Create the grid
    Grid grid(mc);
    // Just use the quadrature points as the particles
    auto points = mc.getQuadraturePoints();
    // FIXME! Convert to material points
    std::vector<MaterialPoint> mPoints;
    for (int i = 0; i < points.size(); i++) {
    	MaterialPoint point(points[i]);
    	mPoints.push_back(point);
    }

    // Set the particle properties to 1 to get a value for the internal forces
    // that is equal to the gradient/sum of gradients.
    for (int i = 0; i < mPoints.size(); i++) {
    	mPoints[i].stress[0] = {1.0,1.0};
    	mPoints[i].stress[1] = {1.0,1.0};
    	mPoints[i].mass = 1.0;
    	mPoints[i].bodyForce[0] = 1.0;
    	mPoints[i].bodyForce[1] = 1.0;
    	mPoints[i].vel[0] = 1.0;
    	mPoints[i].vel[1] = 1.0;
    }

    // Assemble it to create the arrays of nodes, shapes, masses, etc.
    grid.assemble(mPoints);

	// Compute the accelerations at the grid points
	grid.updateNodalAccelerations(1.0,mPoints);

	// Compute the velocities at the grid points from the momenta
	grid.updateNodalVelocitiesFromMomenta(mPoints);

	// Compute the velocity at the grid point for time dt = 1.0
	grid.updateNodalVelocities(1.0,mPoints);

	// Create the constitutive equation
	HydrostaticCR conRel;

	for (int i = 0; i < mPoints.size(); i++) {
		// Compute the strain
		conRel.updateStrainRate(grid,mPoints[i]);
		// Compute the stress
		conRel.updateStress(grid,mPoints[i]);
	}

	// Setup reference values
	std::vector<double> p1Stress{1.0,0.0,0.0,1.0};
	std::vector<double> p2Stress{1.0,0.0,0.0,1.0};

	cout << "----- Stress" << endl;
	for (int i = 0; i < mPoints.size(); i++) {
		for (int j = 0; j < dim; j++) {
			for (int k = 0; k < dim; k++) {
				cout << mPoints[i].stress[j][k] << " ";
			}
			cout << endl;
		}
		cout << "-----" << endl;
	}

	// Check point 1
	BOOST_REQUIRE_CLOSE(p1Stress[0],mPoints[0].stress[0][0],1.0e-3);
	BOOST_REQUIRE_CLOSE(p1Stress[1],mPoints[0].stress[0][1],1.0e-3);
	BOOST_REQUIRE_CLOSE(p1Stress[2],mPoints[0].stress[1][0],1.0e-3);
	BOOST_REQUIRE_CLOSE(p1Stress[3],mPoints[0].stress[1][1],1.0e-3);
	// Check point 2
	BOOST_REQUIRE_CLOSE(p2Stress[0],mPoints[1].stress[0][0],1.0e-3);
	BOOST_REQUIRE_CLOSE(p2Stress[1],mPoints[1].stress[0][1],1.0e-3);
	BOOST_REQUIRE_CLOSE(p2Stress[2],mPoints[1].stress[1][0],1.0e-3);
	BOOST_REQUIRE_CLOSE(p2Stress[3],mPoints[1].stress[1][1],1.0e-3);

	return;
}

} /* namespace Kelvin */


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   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 NT2_CORE_FUNCTIONS_EXPR_GLOBALNORMP_HPP_INCLUDED
#define NT2_CORE_FUNCTIONS_EXPR_GLOBALNORMP_HPP_INCLUDED

#include <nt2/include/functions/globalnormp.hpp>
#include <nt2/include/functions/normp.hpp>
#include <nt2/include/functions/global.hpp>
#include <nt2/core/container/dsl.hpp>
#include <boost/dispatch/functor/meta/make_functor.hpp>
#include <boost/dispatch/attributes.hpp>

namespace nt2 { namespace ext
{

  BOOST_DISPATCH_IMPLEMENT  ( globalnormp_, tag::cpu_
                            , (A0)(A1)
                            , ((ast_<A0, nt2::container::domain>))
                              (scalar_<arithmetic_<A1> > )
                            )
  {
    typedef typename meta::call<tag::global_( nt2::functor<tag::normp_>
                                            , const A0&
                                            , const A1&
                                            )>::type              result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A1 const& a1) const
    {
       return global(nt2::functor<tag::normp_>(), a0, a1);
    }
  };
} }

#endif


/* ============================================================================
 * Copyright (c) 2009-2019 BlueQuartz Software, LLC
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice, this
 * list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.
 *
 * Neither the name of BlueQuartz Software, the US Air Force, nor the names of its
 * contributors may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The code contained herein was partially funded by the followig contracts:
 *    United States Air Force Prime Contract FA8650-07-D-5800
 *    United States Air Force Prime Contract FA8650-10-D-5210
 *    United States Air Force Prime Contract FA8650-15-D-5231
 *    United States Prime Contract Navy N00173-07-C-2068
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

#include "RotateSampleRefFrame.h"

#include <cmath>

#ifdef SIMPL_USE_PARALLEL_ALGORITHMS
#include <tbb/blocked_range3d.h>
#include <tbb/parallel_for.h>
#include <tbb/partitioner.h>
#endif

#include <QtCore/QTextStream>

#include <Eigen/Dense>

#include "SIMPLib/SIMPLibVersion.h"
#include "SIMPLib/Common/Constants.h"
#include "SIMPLib/DataContainers/DataContainer.h"
#include "SIMPLib/DataContainers/DataContainerArray.h"
#include "SIMPLib/FilterParameters/AbstractFilterParametersReader.h"
#include "SIMPLib/FilterParameters/AttributeMatrixSelectionFilterParameter.h"
#include "SIMPLib/FilterParameters/DynamicTableFilterParameter.h"
#include "SIMPLib/FilterParameters/FloatFilterParameter.h"
#include "SIMPLib/FilterParameters/FloatVec3FilterParameter.h"
#include "SIMPLib/FilterParameters/LinkedChoicesFilterParameter.h"
#include "SIMPLib/FilterParameters/SeparatorFilterParameter.h"
#include "SIMPLib/Geometry/ImageGeom.h"
#include "SIMPLib/Math/MatrixMath.h"

namespace
{
struct RotateArgs
{
  int64_t xp = 0;
  int64_t yp = 0;
  int64_t zp = 0;
  float xRes = 0.0f;
  float yRes = 0.0f;
  float zRes = 0.0f;
  int64_t xpNew = 0;
  int64_t ypNew = 0;
  int64_t zpNew = 0;
  float xResNew = 0.0f;
  float yResNew = 0.0f;
  float zResNew = 0.0f;
  float xMinNew = 0.0f;
  float yMinNew = 0.0f;
  float zMinNew = 0.0f;
};

using Matrix3fR = Eigen::Matrix<float, 3, 3, Eigen::RowMajor>;

constexpr float k_Threshold = 0.0001f;

const Eigen::Vector3f k_XAxis = Eigen::Vector3f::UnitX();
const Eigen::Vector3f k_YAxis = Eigen::Vector3f::UnitY();
const Eigen::Vector3f k_ZAxis = Eigen::Vector3f::UnitZ();

// Requires table to be 3 x 3
Matrix3fR tableToMatrix(const std::vector<std::vector<double>>& table)
{
  Matrix3fR matrix;

  for(size_t i = 0; i < table.size(); i++)
  {
    const auto& row = table[i];
    for(size_t j = 0; j < row.size(); j++)
    {
      matrix(i, j) = row[j];
    }
  }

  return matrix;
}

void determineMinMax(const Matrix3fR& rotationMatrix, const FloatVec3Type& spacing, size_t col, size_t row, size_t plane, float& xMin, float& xMax, float& yMin, float& yMax, float& zMin, float& zMax)
{
  Eigen::Vector3f coords(static_cast<float>(col) * spacing[0], static_cast<float>(row) * spacing[1], static_cast<float>(plane) * spacing[2]);

  Eigen::Vector3f newCoords = rotationMatrix * coords;

  xMin = std::min(newCoords[0], xMin);
  xMax = std::max(newCoords[0], xMax);

  yMin = std::min(newCoords[1], yMin);
  yMax = std::max(newCoords[1], yMax);

  zMin = std::min(newCoords[2], zMin);
  zMax = std::max(newCoords[2], zMax);
}

float cosBetweenVectors(const Eigen::Vector3f& a, const Eigen::Vector3f& b)
{
  float normA = a.norm();
  float normB = b.norm();

  if(normA == 0.0f || normB == 0.0f)
  {
    return 1.0f;
  }

  return a.dot(b) / (normA * normB);
}

float determineSpacing(const FloatVec3Type& spacing, const Eigen::Vector3f& axisNew)
{
  float xAngle = std::abs(cosBetweenVectors(k_XAxis, axisNew));
  float yAngle = std::abs(cosBetweenVectors(k_YAxis, axisNew));
  float zAngle = std::abs(cosBetweenVectors(k_ZAxis, axisNew));

  std::array<float, 3> axes = {xAngle, yAngle, zAngle};

  auto iter = std::max_element(axes.cbegin(), axes.cend());

  size_t index = std::distance(axes.cbegin(), iter);

  return spacing[index];
}

RotateArgs createRotateParams(const ImageGeom& imageGeom, const Matrix3fR& rotationMatrix)
{
  const SizeVec3Type origDims = imageGeom.getDimensions();
  const FloatVec3Type spacing = imageGeom.getSpacing();
  // const FloatVec3Type origin = imageGeom.getOrigin();

  float xMin = std::numeric_limits<float>::max();
  float xMax = std::numeric_limits<float>::min();
  float yMin = std::numeric_limits<float>::max();
  float yMax = std::numeric_limits<float>::min();
  float zMin = std::numeric_limits<float>::max();
  float zMax = std::numeric_limits<float>::min();

  const std::vector<std::vector<size_t>> coords{{0, 0, 0},
                                                {origDims[0] - 1, 0, 0},
                                                {0, origDims[1] - 1, 0},
                                                {origDims[0] - 1, origDims[1] - 1, 0},
                                                {0, 0, origDims[2] - 1},
                                                {origDims[0] - 1, 0, origDims[2] - 1},
                                                {0, origDims[1] - 1, origDims[2] - 1},
                                                {origDims[0] - 1, origDims[1] - 1, origDims[2] - 1}};

  for(const auto& item : coords)
  {
    determineMinMax(rotationMatrix, spacing, item[0], item[1], item[2], xMin, xMax, yMin, yMax, zMin, zMax);
  }

  Eigen::Vector3f xAxisNew = rotationMatrix * k_XAxis;
  Eigen::Vector3f yAxisNew = rotationMatrix * k_YAxis;
  Eigen::Vector3f zAxisNew = rotationMatrix * k_ZAxis;

  float xResNew = determineSpacing(spacing, xAxisNew);
  float yResNew = determineSpacing(spacing, yAxisNew);
  float zResNew = determineSpacing(spacing, zAxisNew);

  MeshIndexType xpNew = static_cast<int64_t>(std::nearbyint((xMax - xMin) / xResNew) + 1);
  MeshIndexType ypNew = static_cast<int64_t>(std::nearbyint((yMax - yMin) / yResNew) + 1);
  MeshIndexType zpNew = static_cast<int64_t>(std::nearbyint((zMax - zMin) / zResNew) + 1);

  RotateArgs params;

  params.xp = origDims[0];
  params.xRes = spacing[0];
  params.yp = origDims[1];
  params.yRes = spacing[1];
  params.zp = origDims[2];
  params.zRes = spacing[2];

  params.xpNew = xpNew;
  params.xResNew = xResNew;
  params.xMinNew = xMin;
  params.ypNew = ypNew;
  params.yResNew = yResNew;
  params.yMinNew = yMin;
  params.zpNew = zpNew;
  params.zResNew = zResNew;
  params.zMinNew = zMin;

  return params;
}

void updateGeometry(ImageGeom& imageGeom, const RotateArgs& params)
{
  FloatVec3Type origin = imageGeom.getOrigin();

  imageGeom.setSpacing(params.xResNew, params.yResNew, params.zResNew);
  imageGeom.setDimensions(params.xpNew, params.ypNew, params.zpNew);
  origin[0] += params.xMinNew;
  origin[1] += params.yMinNew;
  origin[2] += params.zMinNew;
  imageGeom.setOrigin(origin);
}

/**
 * @brief The RotateSampleRefFrameImpl class implements a threaded algorithm to do the
 * actual computation of the rotation by applying the rotation to each Euler angle
 */
class SampleRefFrameRotator
{
  DataArray<int64_t>::Pointer m_NewIndicesPtr;
  float m_RotMatrixInv[3][3] = {{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}};
  bool m_SliceBySlice = false;
  RotateArgs m_Params;

public:
  SampleRefFrameRotator(DataArray<int64_t>::Pointer newindices, const RotateArgs& args, const Matrix3fR& rotationMatrix, bool sliceBySlice)
  : m_NewIndicesPtr(newindices)
  , m_SliceBySlice(sliceBySlice)
  , m_Params(args)
  {
    // We have to inline the 3x3 Maxtrix transpose here because of the "const" nature of the 'convert' function
    Matrix3fR transpose = rotationMatrix.transpose();
    // Need to use row based Eigen matrix so that the values get mapped to the right place in the raw array
    // Raw array is faster than Eigen
    Eigen::Map<Matrix3fR>(&m_RotMatrixInv[0][0], transpose.rows(), transpose.cols()) = transpose;
  }

  ~SampleRefFrameRotator() = default;

  void convert(int64_t zStart, int64_t zEnd, int64_t yStart, int64_t yEnd, int64_t xStart, int64_t xEnd) const
  {
    int64_t* newindicies = m_NewIndicesPtr->getPointer(0);

    for(int64_t k = zStart; k < zEnd; k++)
    {
      int64_t ktot = (m_Params.xpNew * m_Params.ypNew) * k;
      for(int64_t j = yStart; j < yEnd; j++)
      {
        int64_t jtot = (m_Params.xpNew) * j;
        for(int64_t i = xStart; i < xEnd; i++)
        {
          int64_t index = ktot + jtot + i;
          newindicies[index] = -1;

          float coords[3] = {0.0f, 0.0f, 0.0f};
          float coordsNew[3] = {0.0f, 0.0f, 0.0f};

          coords[0] = (static_cast<float>(i) * m_Params.xResNew) + m_Params.xMinNew;
          coords[1] = (static_cast<float>(j) * m_Params.yResNew) + m_Params.yMinNew;
          coords[2] = (static_cast<float>(k) * m_Params.zResNew) + m_Params.zMinNew;

          MatrixMath::Multiply3x3with3x1(m_RotMatrixInv, coords, coordsNew);

          int64_t colOld = static_cast<int64_t>(std::nearbyint(coordsNew[0] / m_Params.xRes));
          int64_t rowOld = static_cast<int64_t>(std::nearbyint(coordsNew[1] / m_Params.yRes));
          int64_t planeOld = static_cast<int64_t>(std::nearbyint(coordsNew[2] / m_Params.zRes));

          if(m_SliceBySlice)
          {
            planeOld = k;
          }

          if(colOld >= 0 && colOld < m_Params.xp && rowOld >= 0 && rowOld < m_Params.yp && planeOld >= 0 && planeOld < m_Params.zp)
          {
            newindicies[index] = (m_Params.xp * m_Params.yp * planeOld) + (m_Params.xp * rowOld) + colOld;
          }
        }
      }
    }
  }

#ifdef SIMPL_USE_PARALLEL_ALGORITHMS
  void operator()(const tbb::blocked_range3d<int64_t, int64_t, int64_t>& r) const
  {
    convert(r.pages().begin(), r.pages().end(), r.rows().begin(), r.rows().end(), r.cols().begin(), r.cols().end());
  }
#endif
};

} // namespace

struct RotateSampleRefFrame::Impl
{
  Matrix3fR m_RotationMatrix = Matrix3fR::Zero();
  RotateArgs m_Params;

  void reset()
  {
    m_RotationMatrix.setZero();

    m_Params = RotateArgs();
  }
};

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
RotateSampleRefFrame::RotateSampleRefFrame()
: p_Impl(std::make_unique<Impl>())
{
  std::vector<std::vector<double>> defaultTable{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}};

  m_RotationTable.setTableData(defaultTable);
  m_RotationTable.setDynamicRows(false);
  m_RotationTable.setDynamicCols(false);
  m_RotationTable.setDefaultColCount(3);
  m_RotationTable.setDefaultRowCount(3);
  m_RotationTable.setMinCols(3);
  m_RotationTable.setMinRows(3);
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
RotateSampleRefFrame::~RotateSampleRefFrame() = default;

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void RotateSampleRefFrame::setupFilterParameters()
{
  FilterParameterVectorType parameters;

  {
    LinkedChoicesFilterParameter::Pointer parameter = LinkedChoicesFilterParameter::New();
    parameter->setHumanLabel("Rotation Representation");
    parameter->setPropertyName("RotationRepresentationChoice");
    parameter->setSetterCallback(SIMPL_BIND_SETTER(RotateSampleRefFrame, this, RotationRepresentationChoice));
    parameter->setGetterCallback(SIMPL_BIND_GETTER(RotateSampleRefFrame, this, RotationRepresentationChoice));
    std::vector<QString> choices{"Axis Angle", "Rotation Matrix"};
    parameter->setChoices(choices);
    std::vector<QString> linkedProps{"RotationAngle", "RotationAxis", "RotationTable"};
    parameter->setLinkedProperties(linkedProps);
    parameter->setEditable(false);
    parameter->setCategory(FilterParameter::Category::Parameter);
    parameters.push_back(parameter);
  }

  // Axis Angle Parameters

  parameters.push_back(SIMPL_NEW_FLOAT_FP("Rotation Angle (Degrees)", RotationAngle, FilterParameter::Category::Parameter, RotateSampleRefFrame, 0));
  parameters.push_back(SIMPL_NEW_FLOAT_VEC3_FP("Rotation Axis (ijk)", RotationAxis, FilterParameter::Category::Parameter, RotateSampleRefFrame, 0));

  // Rotation Matrix Parameters

  parameters.push_back(SIMPL_NEW_DYN_TABLE_FP("Rotation Matrix", RotationTable, FilterParameter::Category::Parameter, RotateSampleRefFrame, 1));

  // Required Arrays

  parameters.push_back(SeparatorFilterParameter::Create("Cell Data", FilterParameter::Category::RequiredArray));
  {
    AttributeMatrixSelectionFilterParameter::RequirementType req = AttributeMatrixSelectionFilterParameter::CreateRequirement(AttributeMatrix::Type::Cell, IGeometry::Type::Image);
    parameters.push_back(SIMPL_NEW_AM_SELECTION_FP("Cell Attribute Matrix", CellAttributeMatrixPath, FilterParameter::Category::RequiredArray, RotateSampleRefFrame, req));
  }

  setFilterParameters(parameters);
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void RotateSampleRefFrame::readFilterParameters(AbstractFilterParametersReader* reader, int index)
{
  reader->openFilterGroup(this, index);
  setCellAttributeMatrixPath(reader->readDataArrayPath("CellAttributeMatrixPath", getCellAttributeMatrixPath()));
  setRotationAxis(reader->readFloatVec3("RotationAxis", getRotationAxis()));
  setRotationAngle(reader->readValue("RotationAngle", getRotationAngle()));
  reader->closeFilterGroup();
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void RotateSampleRefFrame::initialize()
{
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void RotateSampleRefFrame::dataCheck()
{
  clearErrorCode();
  clearWarningCode();

  p_Impl->reset();

  if(getErrorCode() < 0)
  {
    return;
  }

  if(!isRotationRepresentationValid(m_RotationRepresentationChoice))
  {
    QString ss = QObject::tr("Invalid rotation representation");
    setErrorCondition(-45001, ss);
    return;
  }

  getDataContainerArray()->getPrereqGeometryFromDataContainer<ImageGeom>(this, getCellAttributeMatrixPath().getDataContainerName());
  getDataContainerArray()->getPrereqAttributeMatrixFromPath(this, getCellAttributeMatrixPath(), -301);
  if(getErrorCode() < 0)
  {
    return;
  }

  DataContainer::Pointer m = getDataContainerArray()->getDataContainer(getCellAttributeMatrixPath().getDataContainerName());

  if(m == nullptr)
  {
    QString ss = QObject::tr("Failed to get DataContainer '%1'").arg(getCellAttributeMatrixPath().getDataContainerName());
    setErrorCondition(-45002, ss);
    return;
  }

  ImageGeom::Pointer imageGeom = m->getGeometryAs<ImageGeom>();

  if(imageGeom == nullptr)
  {
    QString ss = QObject::tr("Failed to get Image Geometry from '%1'").arg(getCellAttributeMatrixPath().getDataContainerName());
    setErrorCondition(-45002, ss);
    return;
  }

  const RotationRepresentation representation = getRotationRepresentation();

  switch(representation)
  {
  case RotationRepresentation::AxisAngle: {
    const Eigen::Vector3f rotationAxis(m_RotationAxis.data());
    float norm = rotationAxis.norm();
    if(!SIMPLibMath::closeEnough(rotationAxis.norm(), 1.0f, k_Threshold))
    {
      QString ss = QObject::tr("Axis angle is not normalized (norm is %1). Filter will automatically normalize the value.").arg(norm);
      setWarningCondition(-45003, ss);
    }

    float rotationAngleRadians = m_RotationAngle * SIMPLib::Constants::k_DegToRadD;

    Eigen::AngleAxisf axisAngle(rotationAngleRadians, rotationAxis.normalized());

    p_Impl->m_RotationMatrix = axisAngle.toRotationMatrix();
  }
  break;
  case RotationRepresentation::RotationMatrix: {
    auto rotationMatrixTable = m_RotationTable.getTableData();

    if(rotationMatrixTable.size() != 3)
    {
      QString ss = QObject::tr("Rotation Matrix must be 3 x 3");
      setErrorCondition(-45004, ss);
      return;
    }

    for(const auto& row : rotationMatrixTable)
    {
      if(row.size() != 3)
      {
        QString ss = QObject::tr("Rotation Matrix must be 3 x 3");
        setErrorCondition(-45005, ss);
        return;
      }
    }

    Matrix3fR rotationMatrix = tableToMatrix(rotationMatrixTable);

    float determinant = rotationMatrix.determinant();

    if(!SIMPLibMath::closeEnough(determinant, 1.0f, k_Threshold))
    {
      QString ss = QObject::tr("Rotation Matrix must have a determinant of 1 (is %1)").arg(determinant);
      setErrorCondition(-45006, ss);
      return;
    }

    Matrix3fR transpose = rotationMatrix.transpose();
    Matrix3fR inverse = rotationMatrix.inverse();

    if(!transpose.isApprox(inverse, k_Threshold))
    {
      QString ss = QObject::tr("Rotation Matrix's inverse and transpose must be equal");
      setErrorCondition(-45007, ss);
      return;
    }

    p_Impl->m_RotationMatrix = rotationMatrix;
  }
  break;
  default: {
    QString ss = QObject::tr("Invalid rotation representation");
    setErrorCondition(-45008, ss);
    return;
  }
  }

  p_Impl->m_Params = createRotateParams(*imageGeom, p_Impl->m_RotationMatrix);

  updateGeometry(*imageGeom, p_Impl->m_Params);

  // Resize attribute matrix

  std::vector<size_t> tDims(3);
  tDims[0] = p_Impl->m_Params.xpNew;
  tDims[1] = p_Impl->m_Params.ypNew;
  tDims[2] = p_Impl->m_Params.zpNew;
  QString attrMatName = getCellAttributeMatrixPath().getAttributeMatrixName();
  m->getAttributeMatrix(attrMatName)->resizeAttributeArrays(tDims);
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void RotateSampleRefFrame::execute()
{
  dataCheck();
  if(getErrorCode() < 0)
  {
    return;
  }

  DataContainer::Pointer m = getDataContainerArray()->getDataContainer(getCellAttributeMatrixPath().getDataContainerName());

  if(m == nullptr)
  {
    QString ss = QObject::tr("Failed to get DataContainer '%1'").arg(getCellAttributeMatrixPath().getDataContainerName());
    setErrorCondition(-45101, ss);
    return;
  }

  int64_t newNumCellTuples = p_Impl->m_Params.xpNew * p_Impl->m_Params.ypNew * p_Impl->m_Params.zpNew;

  DataArray<int64_t>::Pointer newIndiciesPtr = DataArray<int64_t>::CreateArray(newNumCellTuples, std::string("_INTERNAL_USE_ONLY_RotateSampleRef_NewIndicies"), true);
  newIndiciesPtr->initializeWithValue(-1);
  int64_t* newindicies = newIndiciesPtr->getPointer(0);

#ifdef SIMPL_USE_PARALLEL_ALGORITHMS
  tbb::parallel_for(tbb::blocked_range3d<int64_t, int64_t, int64_t>(0, p_Impl->m_Params.zpNew, 0, p_Impl->m_Params.ypNew, 0, p_Impl->m_Params.xpNew),
                    SampleRefFrameRotator(newIndiciesPtr, p_Impl->m_Params, p_Impl->m_RotationMatrix, m_SliceBySlice), tbb::auto_partitioner());
#else
  {
    SampleRefFrameRotator serial(newIndiciesPtr, p_Impl->m_Params, p_Impl->m_RotationMatrix, m_SliceBySlice);
    serial.convert(0, p_Impl->m_Params.zpNew, 0, p_Impl->m_Params.ypNew, 0, p_Impl->m_Params.xpNew);
  }
#endif

  // This could technically be parallelized also where each thread takes an array to adjust. Except
  // that the DataContainer is NOT thread safe or re-entrant so that would actually be a BAD idea.

  QString attrMatName = getCellAttributeMatrixPath().getAttributeMatrixName();
  QList<QString> voxelArrayNames = m->getAttributeMatrix(attrMatName)->getAttributeArrayNames();

  for(const auto& attrArrayName : voxelArrayNames)
  {
    IDataArray::Pointer p = m->getAttributeMatrix(attrMatName)->getAttributeArray(attrArrayName);

    // Make a copy of the 'p' array that has the same name. When placed into
    // the data container this will over write the current array with
    // the same name.

    IDataArray::Pointer data = p->createNewArray(newNumCellTuples, p->getComponentDimensions(), p->getName());
    int64_t newIndicies_I = 0;
    for(size_t i = 0; i < static_cast<size_t>(newNumCellTuples); i++)
    {
      newIndicies_I = newindicies[i];
      if(newIndicies_I >= 0)
      {
        if(!data->copyFromArray(i, p, newIndicies_I, 1))
        {
          QString ss = QObject::tr("copyFromArray Failed: ");
          QTextStream out(&ss);
          out << "Source Array Name: " << p->getName() << " Source Tuple Index: " << newIndicies_I << "\n";
          out << "Dest Array Name: " << data->getName() << "  Dest. Tuple Index: " << i << "\n";
          setErrorCondition(-45102, ss);
          return;
        }
      }
      else
      {
        int var = 0;
        data->initializeTuple(i, &var);
      }
    }
    m->getAttributeMatrix(attrMatName)->insertOrAssign(data);
  }
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
AbstractFilter::Pointer RotateSampleRefFrame::newFilterInstance(bool copyFilterParameters) const
{
  RotateSampleRefFrame::Pointer filter = RotateSampleRefFrame::New();
  if(copyFilterParameters)
  {
    copyFilterParameterInstanceVariables(filter.get());
  }
  return filter;
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
QString RotateSampleRefFrame::getCompiledLibraryName() const
{
  return Core::CoreBaseName;
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
QString RotateSampleRefFrame::getBrandingString() const
{
  return "SIMPLib Core Filter";
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
QString RotateSampleRefFrame::getFilterVersion() const
{
  QString version;
  QTextStream vStream(&version);
  vStream << SIMPLib::Version::Major() << "." << SIMPLib::Version::Minor() << "." << SIMPLib::Version::Patch();
  return version;
}
// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
QString RotateSampleRefFrame::getGroupName() const
{
  return SIMPL::FilterGroups::SamplingFilters;
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
QUuid RotateSampleRefFrame::getUuid() const
{
  return QUuid("{e25d9b4c-2b37-578c-b1de-cf7032b5ef19}");
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
QString RotateSampleRefFrame::getSubGroupName() const
{
  return SIMPL::FilterSubGroups::RotationTransformationFilters;
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
QString RotateSampleRefFrame::getHumanLabel() const
{
  return "Rotate Sample Reference Frame";
}

// -----------------------------------------------------------------------------
RotateSampleRefFrame::Pointer RotateSampleRefFrame::NullPointer()
{
  return Pointer(static_cast<Self*>(nullptr));
}

// -----------------------------------------------------------------------------
std::shared_ptr<RotateSampleRefFrame> RotateSampleRefFrame::New()
{
  struct make_shared_enabler : public RotateSampleRefFrame
  {
  };
  std::shared_ptr<make_shared_enabler> val = std::make_shared<make_shared_enabler>();
  val->setupFilterParameters();
  return val;
}

// -----------------------------------------------------------------------------
QString RotateSampleRefFrame::getNameOfClass() const
{
  return QString("RotateSampleRefFrame");
}

// -----------------------------------------------------------------------------
QString RotateSampleRefFrame::ClassName()
{
  return QString("RotateSampleRefFrame");
}

// -----------------------------------------------------------------------------
void RotateSampleRefFrame::setCellAttributeMatrixPath(const DataArrayPath& value)
{
  m_CellAttributeMatrixPath = value;
}

// -----------------------------------------------------------------------------
DataArrayPath RotateSampleRefFrame::getCellAttributeMatrixPath() const
{
  return m_CellAttributeMatrixPath;
}

// -----------------------------------------------------------------------------
void RotateSampleRefFrame::setRotationAxis(const FloatVec3Type& value)
{
  m_RotationAxis = value;
}

// -----------------------------------------------------------------------------
FloatVec3Type RotateSampleRefFrame::getRotationAxis() const
{
  return m_RotationAxis;
}

// -----------------------------------------------------------------------------
void RotateSampleRefFrame::setRotationAngle(float value)
{
  m_RotationAngle = value;
}

// -----------------------------------------------------------------------------
float RotateSampleRefFrame::getRotationAngle() const
{
  return m_RotationAngle;
}

// -----------------------------------------------------------------------------
void RotateSampleRefFrame::setSliceBySlice(bool value)
{
  m_SliceBySlice = value;
}

// -----------------------------------------------------------------------------
bool RotateSampleRefFrame::getSliceBySlice() const
{
  return m_SliceBySlice;
}

// -----------------------------------------------------------------------------
void RotateSampleRefFrame::setRotationTable(const DynamicTableData& value)
{
  m_RotationTable = value;
}

// -----------------------------------------------------------------------------
DynamicTableData RotateSampleRefFrame::getRotationTable() const
{
  return m_RotationTable;
}

// -----------------------------------------------------------------------------
void RotateSampleRefFrame::setRotationRepresentationChoice(int value)
{
  m_RotationRepresentationChoice = value;
}

// -----------------------------------------------------------------------------
int RotateSampleRefFrame::getRotationRepresentationChoice() const
{
  return m_RotationRepresentationChoice;
}

// -----------------------------------------------------------------------------
RotateSampleRefFrame::RotationRepresentation RotateSampleRefFrame::getRotationRepresentation() const
{
  return static_cast<RotationRepresentation>(m_RotationRepresentationChoice);
}

// -----------------------------------------------------------------------------
void RotateSampleRefFrame::setRotationRepresentation(RotationRepresentation value)
{
  m_RotationRepresentationChoice = static_cast<int>(value);
}

// -----------------------------------------------------------------------------
bool RotateSampleRefFrame::isRotationRepresentationValid(int value) const
{
  return (value >= 0) && (value <= 1);
}


/**
 * \ file LinkwitzRileyFilter.cpp
 */

#include <ATK/EQ/LinkwitzRileyFilter.h>
#include <ATK/EQ/IIRFilter.h>

#include <ATK/Mock/FFTCheckerFilter.h>
#include <ATK/Mock/SimpleSinusGeneratorFilter.h>

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>

#define PROCESSSIZE (1024*64)

BOOST_AUTO_TEST_CASE( IIRFilter_LinkwitzRileyLowPassCoefficients_1k_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(1000);

  ATK::IIRFilter<ATK::LinkwitzRileyLowPassCoefficients<double> > filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_cut_frequency(100);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(1000, 0.09942899724462938));
  frequency_checks.push_back(std::make_pair(10000, 0));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( IIRFilter_LinkwitzRileyLowPassCoefficients_100_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(100);

  ATK::IIRFilter<ATK::LinkwitzRileyLowPassCoefficients<double> > filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_cut_frequency(100);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(10, 0));
  frequency_checks.push_back(std::make_pair(100, 0.7071067811865476));
  frequency_checks.push_back(std::make_pair(1000, 0));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( IIRFilter_LinkwitzRileyLowPassCoefficients_2k_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(2000);

  ATK::IIRFilter<ATK::LinkwitzRileyLowPassCoefficients<double> > filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_cut_frequency(100);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(1000, 0));
  frequency_checks.push_back(std::make_pair(2000, 0.04978527826193893));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( IIRFilter_LinkwitzRileyLowPassCoefficients_200_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(200);

  ATK::IIRFilter<ATK::LinkwitzRileyLowPassCoefficients<double> > filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_cut_frequency(100);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(200, 0.44720537393460674));
  frequency_checks.push_back(std::make_pair(1000, 0));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( IIRFilter_LinkwitzRileyHighPassCoefficients_1k_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(1000);

  ATK::IIRFilter<ATK::LinkwitzRileyHighPassCoefficients<double> > filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_cut_frequency(100);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(1000, 0.9950446595540061));
  frequency_checks.push_back(std::make_pair(10000, 0));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( IIRFilter_LinkwitzRileyHighPassCoefficients_100_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(100);

  ATK::IIRFilter<ATK::LinkwitzRileyHighPassCoefficients<double> > filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_cut_frequency(100);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(10, 0));
  frequency_checks.push_back(std::make_pair(100, 0.7071067811865476));
  frequency_checks.push_back(std::make_pair(1000, 0));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( IIRFilter_LinkwitzRileyHighPassCoefficients_2k_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(2000);

  ATK::IIRFilter<ATK::LinkwitzRileyHighPassCoefficients<double> > filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_cut_frequency(100);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(1000, 0));
  frequency_checks.push_back(std::make_pair(2000, 0.9987599441649563));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}

BOOST_AUTO_TEST_CASE( IIRFilter_LinkwitzRileyHighPassCoefficients_200_test )
{
  ATK::SimpleSinusGeneratorFilter<double> generator;
  generator.set_output_sampling_rate(1024*64);
  generator.set_amplitude(1);
  generator.set_frequency(200);

  ATK::IIRFilter<ATK::LinkwitzRileyHighPassCoefficients<double> > filter;
  filter.set_input_sampling_rate(1024*64);
  filter.set_output_sampling_rate(1024*64);
  filter.set_cut_frequency(100);

  ATK::FFTCheckerFilter<double> checker;
  checker.set_input_sampling_rate(1024*64);
  std::vector<std::pair<int, double> > frequency_checks;
  frequency_checks.push_back(std::make_pair(100, 0));
  frequency_checks.push_back(std::make_pair(200, 0.8944313017355006));
  frequency_checks.push_back(std::make_pair(1000, 0));
  checker.set_checks(frequency_checks);

  checker.set_input_port(0, &filter, 0);
  filter.set_input_port(0, &generator, 0);

  filter.process(1024*64);

  checker.process(PROCESSSIZE);
}


#ifndef KRATOS_AMGCL_MPI_SOLVE_FUNCTIONS_H
#define KRATOS_AMGCL_MPI_SOLVE_FUNCTIONS_H

#include <boost/range/iterator_range.hpp>
#include <boost/property_tree/ptree.hpp>

#include <amgcl/adapter/crs_tuple.hpp>
#include <amgcl/adapter/epetra.hpp>
#include <amgcl/adapter/ublas.hpp>
#include <amgcl/adapter/zero_copy.hpp>
#include <amgcl/adapter/block_matrix.hpp>
#include <amgcl/backend/builtin.hpp>
#include <amgcl/value_type/static_matrix.hpp>
#include <amgcl/solver/runtime.hpp>

#include <amgcl/mpi/util.hpp>
#include <amgcl/mpi/make_solver.hpp>
#include <amgcl/mpi/preconditioner.hpp>

#ifdef AMGCL_GPGPU
#  include <amgcl/backend/vexcl.hpp>
#  include <amgcl/backend/vexcl_static_matrix.hpp>
#endif

#include "Epetra_FECrsMatrix.h"
#include "Epetra_FEVector.h"
#include "trilinos_space.h"

#include "external_includes/amgcl_mpi_solve_functions.h"

namespace Kratos
{

#ifdef AMGCL_GPGPU
vex::Context& vexcl_context();

template <int TBlockSize>
void register_vexcl_static_matrix_type();
#endif

// Spacialization of AMGCLScalarSolve for distribued systems.
void AMGCLScalarSolve(
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::MatrixType& rA,
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::VectorType& rX,
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::VectorType& rB,
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::IndexType& rIterationNumber,
    double& rResidual,
    const boost::property_tree::ptree &amgclParams,
    int verbosity_level,
    bool use_gpgpu
    )
{
#ifdef AMGCL_GPGPU
    if (use_gpgpu && vexcl_context()) {
        auto &ctx = vexcl_context();

        typedef amgcl::backend::vexcl<double> Backend;

        typedef
            amgcl::mpi::make_solver<
                amgcl::runtime::mpi::preconditioner<Backend>,
                amgcl::runtime::solver::wrapper
                >
            Solver;

        Backend::params bprm;
        bprm.q = ctx;

        Solver solve(MPI_COMM_WORLD, amgcl::adapter::map(rA), amgclParams, bprm);

        std::size_t n = rA.NumMyRows();

        vex::vector<double> b(ctx, n, rB.Values());
        vex::vector<double> x(ctx, n, rX.Values());

        std::tie(rIterationNumber, rResidual) = solve(b, x);

        vex::copy(x.begin(), x.end(), rX.Values());
    } else
#endif
    {
        typedef amgcl::backend::builtin<double> Backend;

        typedef
            amgcl::mpi::make_solver<
                amgcl::runtime::mpi::preconditioner<Backend>,
                amgcl::runtime::solver::wrapper
                >
            Solver;

        Solver solve(MPI_COMM_WORLD, amgcl::adapter::map(rA), amgclParams);

        std::size_t n = rA.NumMyRows();

        auto b_range = boost::make_iterator_range(rB.Values(), rB.Values() + n);
        auto x_range = boost::make_iterator_range(rX.Values(), rX.Values() + n);

        std::tie(rIterationNumber, rResidual) = solve(b_range, x_range);
    }
}

// Spacialization of AMGCLBlockSolve for distribued systems.
template <int TBlockSize>
void AMGCLBlockSolve(
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::MatrixType & rA,
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::VectorType& rX,
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::VectorType& rB,
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::IndexType& rIterationNumber,
    double& rResidual,
    boost::property_tree::ptree amgclParams,
    int verbosity_level,
    bool use_gpgpu
    )
{
    if(amgclParams.get<std::string>("precond.class") != "amg")
        amgclParams.erase("precond.coarsening");
    else
        amgclParams.put("precond.coarsening.aggr.block_size",1);

    typedef amgcl::static_matrix<double, TBlockSize, TBlockSize> val_type;
    typedef amgcl::static_matrix<double, TBlockSize, 1> rhs_type;

    std::size_t n = rA.RowMap().NumMyElements();
    std::size_t nb = n / TBlockSize;

#ifdef AMGCL_GPGPU
    if (use_gpgpu && vexcl_context()) {
        auto &ctx = vexcl_context();
        register_vexcl_static_matrix_type<TBlockSize>();

        typedef amgcl::backend::vexcl<val_type> Backend;

        typedef
            amgcl::mpi::make_solver<
                amgcl::runtime::mpi::preconditioner<Backend>,
                amgcl::runtime::solver::wrapper
                >
            Solver;

        typename Backend::params bprm;
        bprm.q = ctx;

        Solver solve(
                MPI_COMM_WORLD,
                amgcl::adapter::block_matrix<val_type>(amgcl::adapter::map(rA)),
                amgclParams, bprm
                );

        auto b_begin = reinterpret_cast<const rhs_type*>(rB.Values());
        auto x_begin = reinterpret_cast<rhs_type*>(rX.Values());

        vex::vector<rhs_type> x(ctx, nb, x_begin);
        vex::vector<rhs_type> b(ctx, nb, b_begin);

        std::tie(rIterationNumber, rResidual) = solve(b, x);

        vex::copy(x.begin(), x.end(), x_begin);
    } else
#endif
    {
        typedef amgcl::backend::builtin<val_type> Backend;

        typedef
            amgcl::mpi::make_solver<
                amgcl::runtime::mpi::preconditioner<Backend>,
                amgcl::runtime::solver::wrapper
                >
            Solver;

        Solver solve(
                MPI_COMM_WORLD,
                amgcl::adapter::block_matrix<val_type>(amgcl::adapter::map(rA)),
                amgclParams
                );

        auto b_begin = reinterpret_cast<const rhs_type*>(rB.Values());
        auto x_begin = reinterpret_cast<rhs_type*>(rX.Values());

        auto b_range = boost::make_iterator_range(b_begin, b_begin + nb);
        auto x_range = boost::make_iterator_range(x_begin, x_begin + nb);

        std::tie(rIterationNumber, rResidual) = solve(b_range, x_range);
    }
}

void AMGCLSolve(
    int block_size,
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::MatrixType& rA,
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::VectorType& rX,
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::VectorType& rB,
    TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector>::IndexType& rIterationNumber,
    double& rResidual,
    const boost::property_tree::ptree &amgclParams,
    int verbosity_level,
    bool use_gpgpu
    )
{
    switch (block_size) {
        case 2:
            AMGCLBlockSolve<2>(rA, rX, rB, rIterationNumber, rResidual, amgclParams, verbosity_level, use_gpgpu);
            return;
        case 3:
            AMGCLBlockSolve<3>(rA, rX, rB, rIterationNumber, rResidual, amgclParams, verbosity_level, use_gpgpu);
            return;
        case 4:
            AMGCLBlockSolve<4>(rA, rX, rB, rIterationNumber, rResidual, amgclParams, verbosity_level, use_gpgpu);
            return;
        default:
            AMGCLScalarSolve(rA, rX, rB, rIterationNumber, rResidual, amgclParams, verbosity_level, use_gpgpu);
            return;
    }
}

} // namespace Kratos

#endif


// Copyright (c) 2017-2020 The Merit Foundation
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "pog/reward.h"

#include <algorithm>
#include <numeric>

#include <boost/multiprecision/cpp_int.hpp>

namespace pog
{
    using int128_t = boost::multiprecision::int128_t;

    AmbassadorLottery RewardAmbassadors(
            int height,
            const referral::AddressANVs& winners,
            CAmount total_reward_0)
    {
        /**
         * Increase ANV precision on block 16000
         */
        int128_t fixed_precision = height < 16000 ? 100 : 1000;
        int128_t total_reward = total_reward_0;

        CAmount total_anv =
            std::accumulate(std::begin(winners), std::end(winners), CAmount{0},
                    [](CAmount acc, const referral::AddressANV& v)
                    {
                        return acc + v.anv;
                    });

        Rewards rewards(winners.size());
        std::transform(std::begin(winners), std::end(winners), std::begin(rewards),
                [total_reward, total_anv, fixed_precision](const referral::AddressANV& v)
                {
                    int128_t percent = (v.anv*fixed_precision) / total_anv;
                    int128_t reward = (total_reward * percent) / fixed_precision;
                    assert(reward <= total_reward);
                    return AmbassadorReward{
                        v.address_type, v.address, static_cast<CAmount>(reward)};
                });

        Rewards filtered_rewards;
        filtered_rewards.reserve(rewards.size());
        std::copy_if(std::begin(rewards), std::end(rewards),
                std::back_inserter(filtered_rewards),
                [](const AmbassadorReward& reward) {
                    return reward.amount > 0;
                });

        CAmount total_rewarded =
            std::accumulate(std::begin(filtered_rewards), std::end(filtered_rewards), CAmount{0},
                    [](CAmount acc, const AmbassadorReward& reward)
                    {
                        return acc + reward.amount;
                    });

        assert(total_rewarded >= 0);
        assert(total_rewarded <= total_reward_0);

        auto remainder = total_reward_0 - total_rewarded;

        assert(remainder >= 0);
        assert(remainder <= total_reward_0);

        return {filtered_rewards, remainder};
    }

    int OldComputeTotalInviteLotteryWinners(
            int height,
            const InviteLotteryParams& lottery,
            const Consensus::Params& params)
    {
        LogPrint(BCLog::POG, "Invites used: %d created: %d period: %d used per block: %d\n",
                lottery.invites_used,
                lottery.invites_created,
                params.daedalus_block_window,
                lottery.mean_used);

        const auto period = (height - params.vDeployments[Consensus::DEPLOYMENT_DAEDALUS].start_block) /
            params.daedalus_block_window;

        /**
         * Distribute out invites at the maximum rate for the very first period
         * to kickstart daedalus.
         */
        if(period < 1) {
            return params.daedalus_max_invites_per_block;
        }

        assert(lottery.invites_used >= 0);
        assert(params.daedalus_min_one_invite_for_every_x_blocks > 0);
        assert(params.daedalus_min_one_invite_for_every_x_blocks <= params.daedalus_block_window);

        /**
         * If no invites are generated that means that the amount used fell
         * under 1 per block during that period. Therefore replace at least
         * the invites used during the period in this block plus at least 1
         * ever ten minutes of that period under the assumption that some invites
         * will leak to users who forget about them or abandon merit. To prevent
         * starvation we need to be able to always generate some merit over the period.
         */
        if(lottery.invites_created == 0) {
            return lottery.invites_used +
                (params.daedalus_block_window / params.daedalus_min_one_invite_for_every_x_blocks);
        }

        const auto invites_used_per_block = lottery.invites_used / params.daedalus_block_window;
        const auto total_winners =
            std::max(0, std::min(invites_used_per_block, params.daedalus_max_invites_per_block));

        assert(total_winners >= 0 && total_winners <= params.daedalus_max_invites_per_block);
        return total_winners;
    }

    int ImpComputeTotalInviteLotteryWinners(
            int height,
            const InviteLotteryParamsVec& lottery_points,
            const Consensus::Params& params)
    {
        assert(lottery_points.size() == 2);

        const auto& block1 = lottery_points[0];
        const auto& block2 = lottery_points[1];

        LogPrint(BCLog::POG, "Invites used: %d created: %d period: %d used per block: %d\n",
                block1.invites_used,
                block1.invites_created,
                params.daedalus_block_window,
                block1.mean_used);

        int min_total_winners = 0;
        if(block1.invites_created <= (block1.blocks / params.imp_miner_reward_for_every_x_blocks)) {
            min_total_winners =
                (block1.blocks / params.imp_min_one_invite_for_every_x_blocks);
        }

        const double mean_diff = block1.mean_used - block2.mean_used;

        //Assume we need more or less than what was used before.
        //This allows invites to grow or shrink exponentially.
        const int change = mean_diff >= 0 ?
            std::ceil(mean_diff) :
            std::floor(mean_diff);

        const int total_winners = std::max(
                min_total_winners,
                static_cast<int>(std::floor(block1.mean_used) + change));

        assert(total_winners >= 0);
        return total_winners;
    }

    double ComputeUsedInviteMean(const InviteLotteryParams& lottery)
    {
        if(lottery.blocks <= 0) {
            return 0.0;
        }

        auto mean = static_cast<double>(lottery.invites_used) /
            static_cast<double>(lottery.blocks);
        return mean;
    }

    double ComputeUsedInviteMeanFixed(const InviteLotteryParams& lottery)
    {
        if(lottery.blocks <= 0) {
            return 0.0;
        }

        auto mean = static_cast<double>(lottery.invites_used_fixed) /
            static_cast<double>(lottery.blocks);
        return mean;
    }

    int ComputeTotalInviteLotteryWinners(
            int height,
            const InviteLotteryParamsVec& lottery,
            const Consensus::Params& params)
    {
        assert(lottery.size() > 0 && lottery.size() <=2);
        if(height >= params.imp_invites_blockheight) {
            return ImpComputeTotalInviteLotteryWinners(height, lottery, params);
        }
        return OldComputeTotalInviteLotteryWinners(height, lottery[0], params);
    }

    InviteRewards RewardInvites(const referral::ConfirmedAddresses& winners)
    {
        assert(winners.size() >= 0);

        const auto INVITES_PER_WINNER = 1;

        InviteRewards rewards(winners.size());
        std::transform(winners.begin(), winners.end(), rewards.begin(),
                [INVITES_PER_WINNER](const referral::ConfirmedAddress& winner) -> InviteReward {
                    return {
                        winner.address_type,
                        winner.address,
                        INVITES_PER_WINNER
                    };
                });

        assert(rewards.size() == winners.size());
        return rewards;
    }

} // namespace pog


#ifndef DFG_PHYSICS_DIDNITUL
#define DFG_PHYSICS_DIDNITUL

#include "dfgDefs.hpp"
#include "physics/constants.hpp"
#include "math/constants.hpp"
#include "math/pow.hpp"
#include "build/utils.hpp"
#include <boost/units/quantity.hpp>
#include <boost/units/systems/si/temperature.hpp>
#include <boost/units/systems/si/frequency.hpp>
#include <boost/units/systems/si/energy.hpp>
#include <boost/units/systems/si/temperature.hpp>
#include <boost/units/systems/temperature/celsius.hpp>
#include <boost/units/systems/temperature/fahrenheit.hpp>

DFG_ROOT_NS_BEGIN { DFG_SUB_NS(physics) {

class CelsiusType
{
public:
	explicit CelsiusType(double val) :
	  m_val(val)
	{}

	double value() const {return m_val;}

	double m_val;
};

const auto kelvin		= boost::units::si::kelvin;
const auto celsius		= boost::units::celsius::degrees;
const auto fahrenheit	= boost::units::fahrenheit::degrees;

class InvalidType {};

inline double photonEnergy_RawSi(const double& fInHz)
{
	return const_hPlanck_rawSiValue * fInHz;
}

inline double photonEnergyVacuumWavelength_RawSi(const double& waveLengthInM)
{
	return photonEnergy_RawSi(const_speedOfLight_rawSiValue / waveLengthInM);
}

template <class FromQuantity_T, class ToUnit_T> inline InvalidType convertTo(const FromQuantity_T&, const ToUnit_T&)
{
	DFG_BUILD_GENERATE_FAILURE_IF_INSTANTIATED(FromQuantity_T,
												DFG_CURRENT_FUNCTION_NAME ": not implemented. Note that e.g. for temperatures "
												"either convertToAbsolute or convertToDifference should be used.");
	return InvalidType();
}

template <class FromQuantity_T, class ToUnit_T> inline InvalidType convertToAbsolute(const FromQuantity_T&, const ToUnit_T&)
{
	DFG_BUILD_GENERATE_FAILURE_IF_INSTANTIATED(FromQuantity_T, DFG_CURRENT_FUNCTION_NAME ": not implemented.");
	return InvalidType();
}

template <class FromQuantity_T, class ToUnit_T> inline InvalidType convertToDifference(const FromQuantity_T&, const ToUnit_T&)
{
	DFG_BUILD_GENERATE_FAILURE_IF_INSTANTIATED(FromQuantity_T, DFG_CURRENT_FUNCTION_NAME ": not implemented.");
	return InvalidType();
}

// Celsius -> Kelvin
auto inline convertToAbsolute(const decltype(1.0 * celsius)& from, const decltype(kelvin)&) -> decltype(from.value() * kelvin)
{
	return (from.value() + 273.15) * kelvin;
}

// Kelvin -> Celsius
auto inline convertToAbsolute(const decltype(1.0 * kelvin)& from, const decltype(celsius)&) -> decltype(from.value() * celsius)
{
	return (from.value() - 273.15) * celsius;
}

auto inline convertToDifference(const decltype(1.0 * celsius)& from, const decltype(kelvin)&) -> decltype(from.value() * kelvin)
{
	return from.value() * kelvin;
}

auto inline convertToDifference(const decltype(1.0 * kelvin)& from, const decltype(celsius)&) -> decltype(from.value() * celsius)
{
	return from.value() * celsius;
}

// Wikipedia http://en.wikipedia.org/wiki/Planck%27s_law
// TODO: test
inline double planckBlackBodySpectralEnergyDensityByFrequency_RawSi(const double TinK, const double fInHz)
{
	const double factor = 8.0 * DFG_SUB_NS_NAME(math)::const_pi * const_hPlanck_rawSiValue / DFG_SUB_NS_NAME(math)::pow3(const_speedOfLight_rawSiValue);
	const double beta = 1.0 / (const_k_B_rawSiValue * TinK);
	return factor * DFG_SUB_NS_NAME(math)::pow3(fInHz) / (exp(photonEnergy_RawSi(fInHz) * beta) - 1);
}

// Wikipedia http://en.wikipedia.org/wiki/Planck%27s_law
// TODO: test
inline double planckBlackBodySpectralEnergyDensityByWaveLength_RawSi(const double TinK, const double wavelengthInM)
{
	const double factor = 8.0 * DFG_SUB_NS_NAME(math)::const_pi * const_hPlanck_rawSiValue * const_speedOfLight_rawSiValue;
	const double beta = 1.0 / (const_k_B_rawSiValue * TinK);
	return factor * (1.0 / DFG_SUB_NS_NAME(math)::pow5(wavelengthInM)) * (1.0 / (exp(photonEnergyVacuumWavelength_RawSi(wavelengthInM) * beta) - 1));
}

}} // module physics

#endif // include guard