// standard includes and external libraries
#include <iostream>
#include <random>
#include <Eigen/Dense>
#include <cmath>
#include <fstream>
#include <nlohmann/json.hpp>
#include <ctime>
// project includes
#include "abstractclasses.h"
#include "bandit.h"
#include "oful.h"
#include "leader.h"
#include "leaderelim.h"
#include "regbalancing.h"
#include "adversarial_master.h"
#include "finitelinrep.h"
#include "utils.h"
#include "gzip.h"

using json = nlohmann::json;
using namespace std;
using namespace Eigen;

size_t PREC = 4;   // for saving numbers are rounded to PREC decimals
size_t EVERY = 1;  // save EVERY round

int main()
{

    int REF_REPR = 0;
    std::vector<std::string> files{
        "../../problem_data/lastfm/repr/lastfm_post_13_d10_span10_gt.npz",
        "../../problem_data/lastfm/repr/lastfm_post_0_d43_span43.npz",
        "../../problem_data/lastfm/repr/lastfm_post_1_d44_span43.npz",
        "../../problem_data/lastfm/repr/lastfm_post_2_d20_span19.npz",
        "../../problem_data/lastfm/repr/lastfm_post_3_d28_span27.npz",
        "../../problem_data/lastfm/repr/lastfm_post_4_d39_span39.npz",
        "../../problem_data/lastfm/repr/lastfm_post_5_d13_span13.npz",
        "../../problem_data/lastfm/repr/lastfm_post_6_d23_span21.npz",
        "../../problem_data/lastfm/repr/lastfm_post_7_d31_span31.npz",
        "../../problem_data/lastfm/repr/lastfm_post_8_d24_span24.npz",
        "../../problem_data/lastfm/repr/lastfm_post_9_d20_span20.npz",
        "../../problem_data/lastfm/repr/lastfm_post_10_d44_span44.npz",
        "../../problem_data/lastfm/repr/lastfm_post_11_d9_span8.npz",
        "../../problem_data/lastfm/repr/lastfm_post_12_d28_span27.npz",
        "../../problem_data/lastfm/repr/lastfm_post_14_d40_span40.npz",
        "../../problem_data/lastfm/repr/lastfm_post_15_d7_span6.npz",
        "../../problem_data/lastfm/repr/lastfm_post_16_d14_span14.npz",
        "../../problem_data/lastfm/repr/lastfm_post_17_d15_span15.npz",
        "../../problem_data/lastfm/repr/lastfm_post_18_d37_span35.npz",
        "../../problem_data/lastfm/repr/lastfm_post_19_d37_span37.npz"
    };
    std::vector<std::string> names{
        "0_d43_span43",
        "1_d44_span43",
        "2_d20_span19",
        "3_d28_span27",
        "4_d39_span39",
        "5_d13_span13",
        "6_d23_span21",
        "7_d31_span31",
        "8_d24_span24",
        "9_d20_span20",
        "10_d44_span44",
        "11_d9_span8",
        "12_d28_span27",
        "13_d10_span10_gt",
        "14_d40_span40",
        "15_d7_span6",
        "16_d14_span14",
        "17_d15_span15",
        "18_d37_span35",
        "19_d37_span37",
    };

    std::time_t t = std::time(nullptr);
    char MY_TIME[100];
    std::strftime(MY_TIME, sizeof(MY_TIME), "%Y%m%d%H%M%S", std::localtime(&t));
    std::cout << MY_TIME << '\n';

    typedef std::vector<std::vector<double>> vec2double;

    int seed = time(NULL);
    srand (seed);
    cout << "seed: " << seed << endl;
    int n_runs = 10, T = 100000;
    double delta = 0.01;
    double reg_val = 1.;
    double noise_std = 0.3;
    double bonus_scale = 1.;
    bool adaptive_ci = true;
    bool update_all = true;
    std::string path;
    std::string name;

    std::vector<long> seeds(n_runs);
    std::generate(seeds.begin(), seeds.end(), [] ()
    {
        return rand();
    });

    // load reference representation
    // FiniteLinearRepresentation reference_rep=flr_loadjson("linrep3.json", noise_std, seed);
    FiniteLinearRepresentation reference_rep = flr_loadnpz(files[REF_REPR], noise_std, seed, "features", "theta");
    cout << "Ref_rep.dim: " << reference_rep.features_dim() << endl;
    cout << "Ref_rep.feat_bound=" << reference_rep.features_bound() << endl;

    // other representations
    std::vector<FiniteLinearRepresentation> reps;
    for (int i = 0; i < files.size(); ++i)
    {

        FiniteLinearRepresentation rr = flr_loadnpz(files[i], noise_std, seed, "features", "theta");

        cout << "phi_" << i << ".dim=" << rr.features_dim() << endl;
        cout << "phi_" << i << ".feat_bound=" << rr.features_bound() << endl;
        bool flag = reference_rep.is_equal(rr, 0.05);
        cout << "phi_" << i << ".equal_ref=" << flag << endl;
        if (!flag)
        {
            std::cout << "Error: " << i << "is a non realizable representation" << std::endl;
            exit(1);
        }
        reps.push_back(rr);
    }

    //LEADER
    name = "ELEADER";
    std::cout << name << std::endl;
    vec2double regrets(n_runs), pseudo_regrets(n_runs);
    #pragma omp parallel for
    for (int i = 0; i < n_runs; ++i)
    {
        std::vector<std::shared_ptr<ContRepresentation<int>>> lreps;
        for(auto& ll : reps)
        {
            auto tmp = std::make_shared<FiniteLinearRepresentation>(ll.copy(seeds[i]));
            lreps.push_back(tmp);
        }
        ElimLEADER<int> localg(lreps, reg_val, noise_std, bonus_scale, delta/lreps.size(), adaptive_ci);
        // create same representation but witth different seed
        FiniteLinearRepresentation cpRefRep = reference_rep.copy(seeds[i]);
        ContBanditProblem<int> prb(cpRefRep, localg);
        prb.reset();
        auto start = TIC();
        prb.run(T);
        auto tottime = TOC(start);
        cout << "time(" << i << "): " << tottime << endl;
        regrets[i] = prb.instant_regret;
        pseudo_regrets[i] = prb.exp_instant_regret;
    }
    save_vector_csv_gzip(regrets, name +"_regrets.csv.gz", EVERY, PREC);
    save_vector_csv_gzip(pseudo_regrets, name+"_pseudoregrets.csv.gz", EVERY, PREC);

    //return 0;
    //LEADER VS MODEL SELECTION BASELINES

    //EXP3.P
    name = "EXP3dotP";
    std::cout << name << std::endl;
    #pragma omp parallel for
    for (int i = 0; i < n_runs; ++i)
    {
        std::vector<std::shared_ptr<ContRepresentation<int>>> lreps;
        std::vector<std::shared_ptr<SmoothedAlgo<int>>> base_algs;
        for(auto& ll : reps)
        {
            auto tmp = std::make_shared<FiniteLinearRepresentation>(ll.copy(seeds[i]));
            lreps.push_back(tmp);
            base_algs.push_back(
                std::make_shared<SmoothedAlgo<int>>(
                    SmoothedAlgo<int>(
                        std::make_shared<OFUL<int>>(
                            OFUL<int>(*tmp, reg_val,noise_std,bonus_scale,delta,adaptive_ci)
                        ), seeds[i]
                    )
                )
            );
        }
        EXP3dotP<int> localg(base_algs, base_algs[0]->base()->exp3_rate(T, base_algs.size()), seeds[i], update_all);
        FiniteLinearRepresentation cpRefRep = reference_rep.copy(seeds[i]);
        ContBanditProblem<int> prb(cpRefRep, localg);
        prb.reset();
        auto start = TIC();
        prb.run(T);
        auto tottime = TOC(start);
        cout << "time(" << i << "): " << tottime << endl;
        regrets[i] = prb.instant_regret;
        pseudo_regrets[i] = prb.exp_instant_regret;
    }
    save_vector_csv_gzip(regrets, name +"_regrets.csv.gz", EVERY, PREC);
    save_vector_csv_gzip(pseudo_regrets, name+"_pseudoregrets.csv.gz", EVERY, PREC);

    //Corral
    name = "Corral";
    std::cout << name << std::endl;
    #pragma omp parallel for
    for (int i = 0; i < n_runs; ++i)
    {
        std::vector<std::shared_ptr<ContRepresentation<int>>> lreps;
        std::vector<std::shared_ptr<SmoothedAlgo<int>>> base_algs;
        for(auto& ll : reps)
        {
            auto tmp = std::make_shared<FiniteLinearRepresentation>(ll.copy(seeds[i]));
            lreps.push_back(tmp);
            base_algs.push_back(
                std::make_shared<SmoothedAlgo<int>>(
                    SmoothedAlgo<int>(
                        std::make_shared<OFUL<int>>(
                            OFUL<int>(*tmp, reg_val,noise_std,bonus_scale,delta,adaptive_ci)
                        ), seeds[i]
                    )
                )
            );
        }
        Corral<int> localg(base_algs, base_algs[0]->base()->corral_lr(T, base_algs.size()), seeds[i], update_all);
        FiniteLinearRepresentation cpRefRep = reference_rep.copy(seeds[i]);

        ContBanditProblem<int> prb(cpRefRep, localg);
        prb.reset();
        auto start = TIC();
        prb.run(T);
        auto tottime = TOC(start);
        cout << "time(" << i << "): " << tottime << endl;
        regrets[i] = prb.instant_regret;
        pseudo_regrets[i] = prb.exp_instant_regret;
    }
    save_vector_csv_gzip(regrets, name +"_regrets.csv.gz", EVERY, PREC);
    save_vector_csv_gzip(pseudo_regrets, name+"_pseudoregrets.csv.gz", EVERY, PREC);

    //EXP4.IX
    name = "EXP4dotIX";
    std::cout << name << std::endl;
    #pragma omp parallel for
    for (int i = 0; i < n_runs; ++i)
    {
        std::vector<std::shared_ptr<ContRepresentation<int>>> lreps;
        std::vector<std::shared_ptr<Algo<int>>> base_algs;
        for(auto& ll : reps)
        {
            auto tmp = std::make_shared<FiniteLinearRepresentation>(ll.copy(seeds[i]));
            lreps.push_back(tmp);
            base_algs.push_back(
                std::make_shared<OFUL<int>>(
                    OFUL<int>(*tmp, reg_val,noise_std,bonus_scale,delta,adaptive_ci)
                )
            );
        }
        double exp4_gamma = sqrt(2*log(base_algs.size())/(reference_rep.n_arms()*T));
        double exp4_lr = 2*exp4_gamma;
        EXP4dotIX<int> localg(base_algs, exp4_lr, exp4_gamma, seeds[i]);
        FiniteLinearRepresentation cpRefRep = reference_rep.copy(seeds[i]);

        ContBanditProblem<int> prb(cpRefRep, localg);
        prb.reset();
        auto start = TIC();
        prb.run(T);
        auto tottime = TOC(start);
        cout << "time(" << i << "): " << tottime << endl;
        regrets[i] = prb.instant_regret;
        pseudo_regrets[i] = prb.exp_instant_regret;
    }
    save_vector_csv_gzip(regrets, name +"_regrets.csv.gz", EVERY, PREC);
    save_vector_csv_gzip(pseudo_regrets, name+"_pseudoregrets.csv.gz", EVERY, PREC);

    //REGRET Balancing
    name = "OFULBAL";
    std::cout << name << std::endl;
    #pragma omp parallel for
    for (int i = 0; i < n_runs; ++i)
    {
        std::vector<std::shared_ptr<ContRepresentation<int>>> lreps;
        std::vector<std::shared_ptr<Algo<int>>> base_algs;
        for(auto& ll : reps)
        {
            auto tmp = std::make_shared<FiniteLinearRepresentation>(ll.copy(seeds[i]));
            lreps.push_back(tmp);
            base_algs.push_back(
                std::make_shared<OFUL<int>>(
                    OFUL<int>(*tmp, reg_val,noise_std,bonus_scale,delta,adaptive_ci)
                )
            );
        }
        RegretBalance<int> localg(base_algs, update_all);
        FiniteLinearRepresentation cpRefRep = reference_rep.copy(seeds[i]);

        ContBanditProblem<int> prb(cpRefRep, localg);
        prb.reset();
        auto start = TIC();
        prb.run(T);
        auto tottime = TOC(start);
        cout << "time(" << i << "): " << tottime << endl;
        regrets[i] = prb.instant_regret;
        pseudo_regrets[i] = prb.exp_instant_regret;
        // delete localg;
    }
    save_vector_csv_gzip(regrets, name +"_regrets.csv.gz", EVERY, PREC);
    save_vector_csv_gzip(pseudo_regrets, name+"_pseudoregrets.csv.gz", EVERY, PREC);

    //Regret balancing with elimination
    name = "OFULBALELIM";
    std::cout << name << std::endl;
    #pragma omp parallel for
    for (int i = 0; i < n_runs; ++i)
    {
        std::vector<std::shared_ptr<ContRepresentation<int>>> lreps;
        std::vector<std::shared_ptr<Algo<int>>> base_algs;
        for(auto& ll : reps)
        {
            auto tmp = std::make_shared<FiniteLinearRepresentation>(ll.copy(seeds[i]));
            lreps.push_back(tmp);
            base_algs.push_back(
                std::make_shared<OFUL<int>>(
                    OFUL<int>(*tmp, reg_val,noise_std,bonus_scale,delta,adaptive_ci)
                )
            );
        }
        RegretBalanceAndEliminate<int> localg(base_algs, delta, update_all);
        FiniteLinearRepresentation cpRefRep = reference_rep.copy(seeds[i]);
        ContBanditProblem<int> prb(cpRefRep, localg);
        prb.reset();
        auto start = TIC();
        prb.run(T);
        auto tottime = TOC(start);
        cout << "time(" << i << "): " << tottime << endl;
        regrets[i] = prb.instant_regret;
        pseudo_regrets[i] = prb.exp_instant_regret;
        // delete localg;
    }
    save_vector_csv_gzip(regrets, name +"_regrets.csv.gz", EVERY, PREC);
    save_vector_csv_gzip(pseudo_regrets, name+"_pseudoregrets.csv.gz", EVERY, PREC);

    return 0;
}


///////////////////////////////////////////////////////////////////////////////
// distribution::survival::models::common::importance_sampling::sample.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_SURVIVAL_MODELS_IMPORTANCE_SAMPLING_IMPORTANCE_SAMPLE_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_DISTRIBUTION_SURVIVAL_MODELS_IMPORTANCE_SAMPLING_IMPORTANCE_SAMPLE_HPP_ER_2009
#include <algorithm>
#include <boost/range.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/statistics/detail/importance_sampling/random/sampler.hpp>
#include <boost/statistics/detail/importance_sampling/weights/prepare_weights.hpp>
#include <boost/statistics/detail/fusion/at_key/range.hpp>

namespace boost {
namespace statistics{
namespace detail{
namespace distribution{
namespace survival {

	// each elem of [b_p,e_p) is a fusion map with a pair tagged by TagPar
	template<
    	typename TagPar,
        typename It_t,typename N_t,typename It_p,typename V,typename U
    >
	It_t importance_sample(
    	It_t it_targets,
        N_t n_targets,
        It_p b_p, 		// proposals
        It_p e_p,
        const V& vec_w, // importance_weights
        U& urng
    )
    {
        typedef typename boost::range_value<V>::type val_;
        typedef
        	statistics::detail::fusion::at_key::meta_range<It_p,TagPar> meta_;
        typedef typename meta_::type r_;
        typedef importance_sampling::sampler<r_,val_> sampler_;
        sampler_ sampler(vec_w,meta_::call(b_p,e_p));
        typedef boost::variate_generator<U&,sampler_> vg_;
        vg_ vg(urng,sampler);
        return std::generate_n(it_targets, n_targets, vg );
	}

}// survival
}// distribution
}// detail
}// statistics
}// boost

#endif


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

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

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

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

#include "equitycrossassetmodel.hpp"

#include <qle/methods/multipathgeneratorbase.hpp>
#include <qle/models/all.hpp>
#include <qle/processes/all.hpp>
#include <qle/pricingengines/all.hpp>

#include <ql/currencies/europe.hpp>
#include <ql/currencies/america.hpp>
#include <ql/indexes/ibor/euribor.hpp>
#include <ql/indexes/ibor/gbplibor.hpp>
#include <ql/indexes/ibor/usdlibor.hpp>
#include <ql/instruments/vanillaoption.hpp>
#include <ql/math/statistics/incrementalstatistics.hpp>
#include <ql/math/optimization/levenbergmarquardt.hpp>
#include <ql/math/randomnumbers/rngtraits.hpp>
#include <ql/methods/montecarlo/multipathgenerator.hpp>
#include <ql/methods/montecarlo/pathgenerator.hpp>
#include <ql/models/shortrate/onefactormodels/gsr.hpp>
#include <ql/models/shortrate/calibrationhelpers/swaptionhelper.hpp>
#include <ql/pricingengines/swaption/gaussian1dswaptionengine.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/time/calendars/target.hpp>
#include <ql/time/daycounters/actual360.hpp>
#include <ql/time/daycounters/thirty360.hpp>
#include <ql/quotes/simplequote.hpp>

#include <test-suite/utilities.hpp>

#include <boost/make_shared.hpp>
// fix for boost 1.64, see https://lists.boost.org/Archives/boost/2016/11/231756.php
#if BOOST_VERSION >= 106400
#include <boost/serialization/array_wrapper.hpp>
#endif
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/covariance.hpp>
#include <boost/accumulators/statistics/error_of_mean.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/variates/covariate.hpp>

using namespace QuantLib;
using namespace QuantExt;

using boost::unit_test_framework::test_suite;
using namespace boost::accumulators;

namespace {

struct CrossAssetData {
    CrossAssetData()
        : referenceDate(30, July, 2015), eurYts(boost::make_shared<FlatForward>(referenceDate, 0.02, Actual365Fixed())),
          usdYts(boost::make_shared<FlatForward>(referenceDate, 0.05, Actual365Fixed())),
          eqDivSp(boost::make_shared<FlatForward>(referenceDate, 0.01, Actual365Fixed())),
          eqDivLh(boost::make_shared<FlatForward>(referenceDate, 0.0075, Actual365Fixed())),
          usdEurSpotToday(boost::make_shared<SimpleQuote>(0.90)), eurEurSpotToday(boost::make_shared<SimpleQuote>(1.0)),
          spSpotToday(boost::make_shared<SimpleQuote>(2100)), lhSpotToday(boost::make_shared<SimpleQuote>(12.50)) {

        SavedSettings backup;

        Settings::instance().evaluationDate() = referenceDate;

        // use different grids for each of the individual processes
        // to test the piecewise numerical integration ...

        std::vector<Date> volstepdatesEur, volstepdatesUsd, volstepdatesFx;
        volstepdatesEqSp.resize(0);
        volstepdatesEqLh.resize(0);

        volstepdatesEur.push_back(Date(15, July, 2016));
        volstepdatesEur.push_back(Date(15, July, 2017));
        volstepdatesEur.push_back(Date(15, July, 2018));
        volstepdatesEur.push_back(Date(15, July, 2019));
        volstepdatesEur.push_back(Date(15, July, 2020));

        volstepdatesUsd.push_back(Date(13, April, 2016));
        volstepdatesUsd.push_back(Date(13, September, 2016));
        volstepdatesUsd.push_back(Date(13, April, 2017));
        volstepdatesUsd.push_back(Date(13, September, 2017));
        volstepdatesUsd.push_back(Date(13, April, 2018));
        volstepdatesUsd.push_back(Date(15, July, 2018)); // shared with EUR
        volstepdatesUsd.push_back(Date(13, April, 2019));
        volstepdatesUsd.push_back(Date(13, September, 2019));

        volstepdatesFx.push_back(Date(15, July, 2016)); // shared with EUR
        volstepdatesFx.push_back(Date(15, October, 2016));
        volstepdatesFx.push_back(Date(15, May, 2017));
        volstepdatesFx.push_back(Date(13, September, 2017)); // shared with USD
        volstepdatesFx.push_back(Date(15, July, 2018));      //  shared with EUR and USD

        volstepdatesEqSp.push_back(Date(13, April, 2016));   // shared with USD
        volstepdatesEqSp.push_back(Date(15, October, 2016)); // shared with FX
        volstepdatesEqSp.push_back(Date(15, March, 2017));
        volstepdatesEqSp.push_back(Date(13, October, 2017));
        volstepdatesEqSp.push_back(Date(15, July, 2018)); //  shared with EUR and USD
        volstepdatesEqSp.push_back(Date(13, October, 2018));

        volstepdatesEqLh.push_back(Date(13, June, 2016));
        volstepdatesEqLh.push_back(Date(15, September, 2016));
        volstepdatesEqLh.push_back(Date(15, April, 2017));
        volstepdatesEqLh.push_back(Date(13, October, 2017));
        volstepdatesEqLh.push_back(Date(15, July, 2018)); //  shared with EUR and USD
        volstepdatesEqLh.push_back(Date(13, December, 2018));

        std::vector<Real> eurVols, usdVols, fxVols, eqSpVols, eqLhVols;

        for (Size i = 0; i < volstepdatesEur.size() + 1; ++i) {
            eurVols.push_back(0.0050 + (0.0080 - 0.0050) * std::exp(-0.3 * static_cast<double>(i)));
        }
        for (Size i = 0; i < volstepdatesUsd.size() + 1; ++i) {
            usdVols.push_back(0.0030 + (0.0110 - 0.0030) * std::exp(-0.3 * static_cast<double>(i)));
        }
        for (Size i = 0; i < volstepdatesFx.size() + 1; ++i) {
            fxVols.push_back(0.15 + (0.20 - 0.15) * std::exp(-0.3 * static_cast<double>(i)));
        }
        for (Size i = 0; i < volstepdatesEqSp.size() + 1; ++i) {
            eqSpVols.push_back(0.20 + (0.35 - 0.20) * std::exp(-0.3 * static_cast<double>(i)));
        }
        for (Size i = 0; i < volstepdatesEqLh.size() + 1; ++i) {
            eqLhVols.push_back(0.25 + (0.45 - 0.25) * std::exp(-0.3 * static_cast<double>(i)));
        }

        Array alphaTimesEur(volstepdatesEur.size()), alphaEur(eurVols.begin(), eurVols.end()), kappaTimesEur(0),
            kappaEur(1, 0.02);
        Array alphaTimesUsd(volstepdatesUsd.size()), alphaUsd(usdVols.begin(), usdVols.end()), kappaTimesUsd(0),
            kappaUsd(1, 0.04);
        Array fxTimes(volstepdatesFx.size()), fxSigmas(fxVols.begin(), fxVols.end());
        Array eqSpTimes(volstepdatesEqSp.size()), spSigmas(eqSpVols.begin(), eqSpVols.end());
        Array eqLhTimes(volstepdatesEqLh.size()), lhSigmas(eqLhVols.begin(), eqLhVols.end());

        for (Size i = 0; i < alphaTimesEur.size(); ++i) {
            alphaTimesEur[i] = eurYts->timeFromReference(volstepdatesEur[i]);
        }
        for (Size i = 0; i < alphaTimesUsd.size(); ++i) {
            alphaTimesUsd[i] = eurYts->timeFromReference(volstepdatesUsd[i]);
        }
        for (Size i = 0; i < fxTimes.size(); ++i) {
            fxTimes[i] = eurYts->timeFromReference(volstepdatesFx[i]);
        }
        for (Size i = 0; i < eqSpTimes.size(); ++i) {
            eqSpTimes[i] = eurYts->timeFromReference(volstepdatesEqSp[i]);
        }
        for (Size i = 0; i < eqLhTimes.size(); ++i) {
            eqLhTimes[i] = eurYts->timeFromReference(volstepdatesEqLh[i]);
        }

        boost::shared_ptr<IrLgm1fParametrization> eurLgmParam =
            boost::make_shared<IrLgm1fPiecewiseConstantParametrization>(EURCurrency(), eurYts, alphaTimesEur, alphaEur,
                                                                        kappaTimesEur, kappaEur);

        boost::shared_ptr<IrLgm1fParametrization> usdLgmParam =
            boost::make_shared<IrLgm1fPiecewiseConstantParametrization>(USDCurrency(), usdYts, alphaTimesUsd, alphaUsd,
                                                                        kappaTimesUsd, kappaUsd);

        boost::shared_ptr<FxBsParametrization> fxUsdEurBsParam =
            boost::make_shared<FxBsPiecewiseConstantParametrization>(USDCurrency(), usdEurSpotToday, fxTimes, fxSigmas);

        boost::shared_ptr<EqBsParametrization> eqSpBsParam = boost::make_shared<EqBsPiecewiseConstantParametrization>(
            USDCurrency(), "SP", spSpotToday, usdEurSpotToday, eqSpTimes, spSigmas, usdYts, eqDivSp);

        boost::shared_ptr<EqBsParametrization> eqLhBsParam = boost::make_shared<EqBsPiecewiseConstantParametrization>(
            EURCurrency(), "LH", lhSpotToday, eurEurSpotToday, eqLhTimes, lhSigmas, eurYts, eqDivLh);

        singleModels.resize(0);
        singleModels.push_back(eurLgmParam);
        singleModels.push_back(usdLgmParam);
        singleModels.push_back(fxUsdEurBsParam);
        singleModels.push_back(eqSpBsParam);
        singleModels.push_back(eqLhBsParam);

        ccLgm = boost::make_shared<CrossAssetModel>(singleModels);

        eurIdx = ccLgm->ccyIndex(EURCurrency());
        usdIdx = ccLgm->ccyIndex(USDCurrency());
        eurUsdIdx = usdIdx - 1;
        eqSpIdx = ccLgm->eqIndex("SP");
        eqLhIdx = ccLgm->eqIndex("LH");

        ccLgm->correlation(IR, eurIdx, IR, usdIdx, -0.2);
        ccLgm->correlation(IR, eurIdx, FX, eurUsdIdx, 0.8);
        ccLgm->correlation(IR, usdIdx, FX, eurUsdIdx, -0.5);
        ccLgm->correlation(EQ, eqSpIdx, EQ, eqLhIdx, 0.6);
        ccLgm->correlation(EQ, eqSpIdx, IR, usdIdx, -0.1);
        ccLgm->correlation(EQ, eqLhIdx, IR, eurIdx, -0.05);
        ccLgm->correlation(EQ, eqSpIdx, FX, eurUsdIdx, 0.1);
    }

    SavedSettings backup;
    Date referenceDate;
    Handle<YieldTermStructure> eurYts, usdYts;
    Handle<YieldTermStructure> eqDivSp, eqDivLh;
    Handle<Quote> usdEurSpotToday, eurEurSpotToday, spSpotToday, lhSpotToday;
    std::vector<boost::shared_ptr<Parametrization> > singleModels;
    boost::shared_ptr<CrossAssetModel> ccLgm;
    Size eurIdx, usdIdx, eurUsdIdx, eqSpIdx, eqLhIdx;
    std::vector<Date> volstepdatesEqSp, volstepdatesEqLh;
};

} // anonymous namespace

namespace testsuite {

void EquityCrossAssetModelTest::testEqLgm5fPayouts() {

    BOOST_TEST_MESSAGE("Testing pricing of equity payouts under domestic "
                       "measure in CrossAsset LGM model...");

    CrossAssetData d;
    Settings::instance().evaluationDate() = d.referenceDate;

    boost::shared_ptr<StochasticProcess> process = d.ccLgm->stateProcess(CrossAssetStateProcess::exact);
    boost::shared_ptr<StochasticProcess> process2 = d.ccLgm->stateProcess(CrossAssetStateProcess::euler);

    // path generation

    Size n = 500000; // number of paths
    Size seed = 121; // seed
    // maturity of test payoffs
    Time T = 5.0;
    // take large steps, but not only one (since we are testing)
    Size steps = static_cast<Size>(T * 2.0);
    Size steps_euler = static_cast<Size>(T * 52.0);
    TimeGrid grid(T, steps);
    TimeGrid grid_euler(T, steps_euler);
    PseudoRandom::rsg_type sg2 = PseudoRandom::make_sequence_generator(steps, seed);

    MultiPathGeneratorMersenneTwister pg(process, grid, seed, false);
    MultiPathGeneratorMersenneTwister pg2(process2, grid_euler, seed, false);

    // test
    // 1 LH (EUR) forward under CrossAsset numeraire vs. analytic pricing engine
    // 2 SP (USD) forward (converted to EUR) under CrossAsset numeraire vs. analytic pricing engine
    // 3 LH (EUR) EQ option under CrossAsset numeraire vs. analytic pricing engine
    // 4 SP (USD) EQ option under CrossAsset numeraire vs. analytic pricing engine

    accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > stat1, stat2, stat3a, stat3b, stat4a, stat4b;

    Real strikeLh = 12.7;
    Real strikeSp = 2150.0;

    // same for paths2 since shared time grid
    for (Size j = 0; j < n; ++j) {
        Sample<MultiPath> path = pg.next();
        // Sample<MultiPath> path = pg2.next();
        Size l = path.value[0].length() - 1;
        Real eurusdfx = std::exp(path.value[2][l]);
        Real zeur = path.value[0][l];
        Real eqsp = std::exp(path.value[3][l]);
        Real eqlh = std::exp(path.value[4][l]);
        Real ccnum = d.ccLgm->numeraire(0, T, zeur);

        // 1 LH forward settled at T deflated with numeraire
        Real lhFwd = eqlh - strikeLh;
        stat1(lhFwd / ccnum);

        // 2 SP forward settled at T (converted to base) deflated with numeraire
        Real spFwd = eurusdfx * (eqsp - strikeSp);
        stat2(spFwd / ccnum);

        // 3 LH option exercise at T deflated with numeraire
        Real lhCall = std::max(lhFwd, 0.0);
        Real lhPut = std::max(-1.0 * lhFwd, 0.0);
        stat3a(lhCall / ccnum);
        stat3b(lhPut / ccnum);

        // 4 SP option exercise at T (converted to base) deflated with numeraire
        Real spCall = std::max(spFwd, 0.0);
        Real spPut = std::max(-1.0 * spFwd, 0.0);
        stat4a(spCall / ccnum);
        stat4b(spPut / ccnum);
    }

    Date tradeMaturity = d.referenceDate + 5 * 365;

    boost::shared_ptr<EquityForward> lhFwdTrade =
        boost::make_shared<EquityForward>("LH", EURCurrency(), Position::Long, 1, tradeMaturity, strikeLh);
    boost::shared_ptr<EquityForward> spFwdTrade =
        boost::make_shared<EquityForward>("SP", USDCurrency(), Position::Long, 1, tradeMaturity, strikeSp);

    boost::shared_ptr<VanillaOption> lhCall =
        boost::make_shared<VanillaOption>(boost::make_shared<PlainVanillaPayoff>(Option::Call, strikeLh),
                                          boost::make_shared<EuropeanExercise>(d.referenceDate + 5 * 365));
    boost::shared_ptr<VanillaOption> lhPut =
        boost::make_shared<VanillaOption>(boost::make_shared<PlainVanillaPayoff>(Option::Put, strikeLh),
                                          boost::make_shared<EuropeanExercise>(d.referenceDate + 5 * 365));
    boost::shared_ptr<VanillaOption> spCall =
        boost::make_shared<VanillaOption>(boost::make_shared<PlainVanillaPayoff>(Option::Call, strikeSp),
                                          boost::make_shared<EuropeanExercise>(d.referenceDate + 5 * 365));
    boost::shared_ptr<VanillaOption> spPut =
        boost::make_shared<VanillaOption>(boost::make_shared<PlainVanillaPayoff>(Option::Put, strikeSp),
                                          boost::make_shared<EuropeanExercise>(d.referenceDate + 5 * 365));

    boost::shared_ptr<DiscountingEquityForwardEngine> lhFwdEngine =
        boost::make_shared<DiscountingEquityForwardEngine>(d.eurYts, d.eqDivLh, d.lhSpotToday, d.eurYts);
    boost::shared_ptr<DiscountingEquityForwardEngine> spFwdEngine =
        boost::make_shared<DiscountingEquityForwardEngine>(d.usdYts, d.eqDivSp, d.spSpotToday, d.usdYts);

    lhFwdTrade->setPricingEngine(lhFwdEngine);
    spFwdTrade->setPricingEngine(spFwdEngine);

    boost::shared_ptr<AnalyticXAssetLgmEquityOptionEngine> spEqOptionEngine =
        boost::make_shared<AnalyticXAssetLgmEquityOptionEngine>(
            d.ccLgm, d.eqSpIdx, d.ccLgm->ccyIndex(d.ccLgm->eqbs(d.eqSpIdx)->currency()));
    boost::shared_ptr<AnalyticXAssetLgmEquityOptionEngine> lhEqOptionEngine =
        boost::make_shared<AnalyticXAssetLgmEquityOptionEngine>(
            d.ccLgm, d.eqLhIdx, d.ccLgm->ccyIndex(d.ccLgm->eqbs(d.eqLhIdx)->currency()));

    lhCall->setPricingEngine(lhEqOptionEngine);
    lhPut->setPricingEngine(lhEqOptionEngine);
    spCall->setPricingEngine(spEqOptionEngine);
    spPut->setPricingEngine(spEqOptionEngine);

    Real npv1 = mean(stat1);
    Real error1 = error_of<tag::mean>(stat1);
    Real expected1 = lhFwdTrade->NPV();

    Real npv2 = mean(stat2);
    Real error2 = error_of<tag::mean>(stat2);
    Real expected2 = d.usdEurSpotToday->value() * spFwdTrade->NPV();

    Real npv3a = mean(stat3a);
    Real error3a = error_of<tag::mean>(stat3a);
    Real expected3a = lhCall->NPV();
    Real npv3b = mean(stat3b);
    Real error3b = error_of<tag::mean>(stat3b);
    Real expected3b = lhPut->NPV();

    Real npv4a = mean(stat4a);
    Real error4a = error_of<tag::mean>(stat4a);
    Real expected4a = d.usdEurSpotToday->value() * spCall->NPV();
    Real npv4b = mean(stat4b);
    Real error4b = error_of<tag::mean>(stat4b);
    Real expected4b = d.usdEurSpotToday->value() * spPut->NPV();

    Real tolErrEst = 1.5; // allow absolute diffs to be within 1.5 standard errors
    // TODO : IS THIS STRICT ENOUGH?
    BOOST_CHECK_LE(std::fabs(npv1 - expected1), tolErrEst * error1);
    BOOST_CHECK_LE(std::fabs(npv2 - expected2), tolErrEst * error2);
    BOOST_CHECK_LE(std::fabs(npv3a - expected3a), tolErrEst * error3a);
    BOOST_CHECK_LE(std::fabs(npv3b - expected3b), tolErrEst * error3b);
    BOOST_CHECK_LE(std::fabs(npv4a - expected4a), tolErrEst * error4a);
    BOOST_CHECK_LE(std::fabs(npv4b - expected4b), tolErrEst * error4b);

} // testEqLgm5fPayouts

void EquityCrossAssetModelTest::testLgm5fEqCalibration() {

    BOOST_TEST_MESSAGE("Testing EQ calibration of IR-FX-EQ LGM 5F model...");

    CrossAssetData d;
    Settings::instance().evaluationDate() = d.referenceDate;

    // calibration baskets
    std::vector<boost::shared_ptr<CalibrationHelper> > basketSp, basketLh;

    for (Size i = 0; i < d.volstepdatesEqSp.size(); ++i) {
        Date tmp = i < d.volstepdatesEqSp.size() ? d.volstepdatesEqSp[i] : d.volstepdatesEqSp.back() + 365;
        basketSp.push_back(boost::make_shared<FxEqOptionHelper>(
            tmp, Null<Real>(), d.spSpotToday, Handle<Quote>(boost::make_shared<SimpleQuote>(0.20)), d.usdYts, d.eqDivSp,
            CalibrationHelper::RelativePriceError));
    }
    for (Size i = 0; i < d.volstepdatesEqLh.size(); ++i) {
        Date tmp = i < d.volstepdatesEqLh.size() ? d.volstepdatesEqLh[i] : d.volstepdatesEqLh.back() + 365;
        basketLh.push_back(boost::make_shared<FxEqOptionHelper>(
            tmp, Null<Real>(), d.lhSpotToday, Handle<Quote>(boost::make_shared<SimpleQuote>(0.20)), d.eurYts, d.eqDivLh,
            CalibrationHelper::RelativePriceError));
    }

    // pricing engines
    boost::shared_ptr<AnalyticXAssetLgmEquityOptionEngine> spEqOptionEngine =
        boost::make_shared<AnalyticXAssetLgmEquityOptionEngine>(
            d.ccLgm, d.eqSpIdx, d.ccLgm->ccyIndex(d.ccLgm->eqbs(d.eqSpIdx)->currency()));
    boost::shared_ptr<AnalyticXAssetLgmEquityOptionEngine> lhEqOptionEngine =
        boost::make_shared<AnalyticXAssetLgmEquityOptionEngine>(
            d.ccLgm, d.eqLhIdx, d.ccLgm->ccyIndex(d.ccLgm->eqbs(d.eqLhIdx)->currency()));

    // assign engines to calibration instruments
    for (Size i = 0; i < basketSp.size(); ++i) {
        basketSp[i]->setPricingEngine(spEqOptionEngine);
    }
    for (Size i = 0; i < basketLh.size(); ++i) {
        basketLh[i]->setPricingEngine(lhEqOptionEngine);
    }

    // calibrate the model
    LevenbergMarquardt lm(1E-8, 1E-8, 1E-8);
    EndCriteria ec(1000, 500, 1E-8, 1E-8, 1E-8);

    d.ccLgm->calibrateBsVolatilitiesIterative(CrossAssetModelTypes::EQ, d.eqSpIdx, basketSp, lm, ec);
    d.ccLgm->calibrateBsVolatilitiesIterative(CrossAssetModelTypes::EQ, d.eqLhIdx, basketLh, lm, ec);

    // check the results
    Real tol = 1E-6;

    for (Size i = 0; i < basketSp.size(); ++i) {
        Real model = basketSp[i]->modelValue();
        Real market = basketSp[i]->marketValue();
        if (std::abs((model - market) / market) > tol)
            BOOST_ERROR("calibration failed for instrument #"
                        << i << " in SP basket, model value is " << model << " market value is " << market
                        << " relative error " << std::abs((model - market) / market) << " tolerance " << tol);
    }
    for (Size i = 0; i < basketLh.size(); ++i) {
        Real model = basketLh[i]->modelValue();
        Real market = basketLh[i]->marketValue();
        if (std::abs((model - market) / market) > tol)
            BOOST_ERROR("calibration failed for instrument #"
                        << i << " in LH basket, model value is " << model << " market value is " << market
                        << " relative error " << std::abs((model - market) / market) << " tolerance " << tol);
    }
}

void EquityCrossAssetModelTest::testLgm5fMoments() {

    // TODO : REVIEW TOLERANCES
    BOOST_TEST_MESSAGE("Testing analytic moments vs. Euler and exact discretization "
                       "in IR-FX-EQ LGM 5F model...");

    CrossAssetData d;
    Settings::instance().evaluationDate() = d.referenceDate;

    boost::shared_ptr<StochasticProcess> p_exact = d.ccLgm->stateProcess(CrossAssetStateProcess::exact);
    boost::shared_ptr<StochasticProcess> p_euler = d.ccLgm->stateProcess(CrossAssetStateProcess::euler);

    Real T = 10.0;                                  // horizon at which we compare the moments
    Size steps_euler = static_cast<Size>(T * 50.0); // number of simulation steps
    Size steps_exact = 1;
    Size paths = 25000; // number of paths

    Array e_an = p_exact->expectation(0.0, p_exact->initialValues(), T);
    Matrix v_an = p_exact->covariance(0.0, p_exact->initialValues(), T);
    Matrix v_an_eu = p_euler->covariance(0.0, p_euler->initialValues(), T);

    TimeGrid grid_euler(T, steps_euler);
    TimeGrid grid_exact(T, steps_exact);

    MultiPathGeneratorSobolBrownianBridge pgen(p_euler, grid_euler);
    MultiPathGeneratorSobolBrownianBridge pgen2(p_exact, grid_exact);

    accumulator_set<double, stats<tag::mean, tag::error_of<tag::mean> > > e_eu[5], e_eu2[5];
    accumulator_set<double, stats<tag::covariance<double, tag::covariate1> > > v_eu[5][5], v_eu2[5][5];

    for (Size i = 0; i < paths; ++i) {
        Sample<MultiPath> path = pgen.next();
        Sample<MultiPath> path2 = pgen2.next();
        for (Size ii = 0; ii < 5; ++ii) {
            Real cii = path.value[ii].back();
            Real cii2 = path2.value[ii].back();
            e_eu[ii](cii);
            e_eu2[ii](cii2);
            for (Size jj = 0; jj <= ii; ++jj) {
                Real cjj = path.value[jj].back();
                v_eu[ii][jj](cii, covariate1 = cjj);
                Real cjj2 = path2.value[jj].back();
                v_eu2[ii][jj](cii2, covariate1 = cjj2);
            }
        }
    }

    Real errTol[] = { 0.2E-4, 0.2E-4, 10.0E-4, 10.0E-4, 10.0E-4 };

    for (Size i = 0; i < 5; ++i) {
        // check expectation against analytical calculation (Euler)
        if (std::fabs(mean(e_eu[i]) - e_an[i]) > errTol[i]) {
            BOOST_ERROR("analytical expectation for component #"
                        << i << " (" << e_an[i] << ") is inconsistent with numerical value (Euler "
                                                   "discretization, " << mean(e_eu[i]) << "), error is "
                        << e_an[i] - mean(e_eu[i]) << " tolerance is " << errTol[i]);
        }
        // check expectation against analytical calculation (exact disc)
        if (std::fabs(mean(e_eu2[i]) - e_an[i]) > errTol[i]) {
            BOOST_ERROR("analytical expectation for component #"
                        << i << " (" << e_an[i] << ") is inconsistent with numerical value (Exact "
                                                   "discretization, " << mean(e_eu2[i]) << "), error is "
                        << e_an[i] - mean(e_eu2[i]) << " tolerance is " << errTol[i]);
        }
    }

    // we have to deal with different natures of volatility
    // for ir (normal) and fx (ln) so different error
    // tolerances apply
    Real tollNormal = 0.1E-4; // ir-ir
    Real tolMixed = 0.25E-4;  // ir-fx, ir-eq
    Real tolLn = 8.0E-4;      // fx-fx, fx-eq
    Real tolEq = 12.0E-4;     // eq-eq (to account for higher eq vols)
    Real tol;                 // set below

    for (Size i = 0; i < 5; ++i) {
        for (Size j = 0; j <= i; ++j) {
            if (i < 2) {
                tol = tollNormal;
            } else if ((i >= 3) && (j >= 3)) {
                tol = tolEq;
            } else {
                if (j < 2) {
                    tol = tolMixed;
                } else {
                    tol = tolLn;
                }
            }
            if (std::fabs(covariance(v_eu[i][j]) - v_an[i][j]) > tol) {
                BOOST_ERROR("analytical covariance at (" << i << "," << j << ") (" << v_an[i][j]
                                                         << ") is inconsistent with numerical "
                                                            "value (Euler discretization, " << covariance(v_eu[i][j])
                                                         << "), error is " << v_an[i][j] - covariance(v_eu[i][j])
                                                         << " tolerance is " << tol);
            }
            if (std::fabs(covariance(v_eu2[i][j]) - v_an[i][j]) > tol) {
                BOOST_ERROR("analytical covariance at (" << i << "," << j << ") (" << v_an[i][j]
                                                         << ") is inconsistent with numerical "
                                                            "value (Exact discretization, " << covariance(v_eu2[i][j])
                                                         << "), error is " << v_an[i][j] - covariance(v_eu2[i][j])
                                                         << " tolerance is " << tol);
            }
        }
    }

    BOOST_TEST_MESSAGE("Testing correlation matrix recovery in presence of equity simulation");
    Matrix corr_input = d.ccLgm->correlation();
    BOOST_CHECK(corr_input.rows() == corr_input.columns());
    Size dim = corr_input.rows();
    BOOST_CHECK(corr_input.rows() == 5);
    Matrix r1(dim, dim), r2(dim, dim);
    Real dt = 1.0E-6;
    Real tol_corr = 1.0E-7;
    Matrix v_an_dt = p_exact->covariance(0.0, p_exact->initialValues(), dt);
    Matrix v_an_eu_dt = p_euler->covariance(0.0, p_euler->initialValues(), dt);
    BOOST_CHECK(v_an_dt.rows() == v_an_eu_dt.rows());
    BOOST_CHECK(v_an_dt.columns() == v_an_eu_dt.columns());
    BOOST_CHECK(corr_input.rows() == v_an_dt.rows());
    BOOST_CHECK(corr_input.columns() == corr_input.columns());

    for (Size i = 0; i < dim; ++i) {
        for (Size j = 0; j <= i; ++j) {
            r1[i][j] = r1[j][i] = v_an_dt[i][j] / std::sqrt(v_an_dt[i][i] * v_an_dt[j][j]);
            r2[i][j] = r2[j][i] = v_an_eu_dt[i][j] / std::sqrt(v_an_eu_dt[i][i] * v_an_eu_dt[j][j]);
            BOOST_CHECK_MESSAGE(std::fabs(r1[i][j] - corr_input[i][j]) < tol_corr,
                                "failed to recover correlation matrix from "
                                "exact state process (i,j)=("
                                    << i << "," << j << "), input correlation is " << corr_input[i][j] << ", output is "
                                    << r1[i][j] << ", difference " << (corr_input[i][j] - r1[i][j]) << ", tolerance "
                                    << tol_corr);
            BOOST_CHECK_MESSAGE(std::fabs(r2[i][j] - corr_input[i][j]) < tol_corr,
                                "failed to recover correlation matrix from "
                                "Euler state process (i,j)=("
                                    << i << "," << j << "), input correlation is " << corr_input[i][j] << ", output is "
                                    << r2[i][j] << ", difference " << (corr_input[i][j] - r2[i][j]) << ", tolerance "
                                    << tol_corr);
        }
    }
    /*
    for (Size i = 0; i < 5; ++i) {
        Real meu = mean(e_eu[i]);
        Real s_meu = error_of<tag::mean>(e_eu[i]);
        BOOST_TEST_MESSAGE(i << ";EULER;" << e_an[i] << ";" << meu << ";" << s_meu);
        Real meu2 = mean(e_eu2[i]);
        Real s_meu2 = error_of<tag::mean>(e_eu2[i]);
        BOOST_TEST_MESSAGE(i << ";EXACT;" << e_an[i] << ";" << meu2 << ";" << s_meu2);
    }
    for (Size i = 0; i < 5; ++i) {
        for (Size j = 0; j <= i; ++j) {
            Real cov = covariance(v_eu[i][j]);
            BOOST_TEST_MESSAGE(i << ";" << j << ";" << "EULER;" << v_an[i][j] << ";" << cov);
            Real cov2 = covariance(v_eu2[i][j]);
            BOOST_TEST_MESSAGE(i << ";" << j << ";" << "EXACT;" << v_an[i][j] << ";" << cov2);
        }
    }
    */
} // testLgm5fMoments

test_suite* EquityCrossAssetModelTest::suite() {
    test_suite* suite = BOOST_TEST_SUITE("EquityCrossAssetModelTests");
    suite->add(QUANTLIB_TEST_CASE(&EquityCrossAssetModelTest::testEqLgm5fPayouts));
    suite->add(QUANTLIB_TEST_CASE(&EquityCrossAssetModelTest::testLgm5fEqCalibration));
    suite->add(QUANTLIB_TEST_CASE(&EquityCrossAssetModelTest::testLgm5fMoments));
    return suite;
}
}


#ifndef PYTHONIC_INCLUDE_NUMPY_FMAX_HPP
#define PYTHONIC_INCLUDE_NUMPY_FMAX_HPP

#include "pythonic/include/utils/functor.hpp"
#include "pythonic/include/types/ndarray.hpp"
#include "pythonic/include/types/numpy_broadcast.hpp"
#include "pythonic/include/utils/numpy_traits.hpp"

#include <boost/simd/function/max.hpp>

namespace pythonic
{

  namespace numpy
  {
#define NUMPY_NARY_FUNC_NAME fmax
#define NUMPY_NARY_FUNC_SYM boost::simd::max
#include "pythonic/include/types/numpy_nary_expr.hpp"
  }
}

#endif


#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <boost/ref.hpp>
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
#include <opencv2/opencv.hpp>
#include <glog/logging.h>
using namespace boost::python;
namespace np = boost::python::numpy;

namespace {
    using std::istringstream;
    using std::ostringstream;
    using std::string;
    using std::runtime_error;
    using std::cerr;
    using std::endl;
    using std::vector;

    float box_area (float const *b) {
        return (b[2] - b[0]) * (b[3] - b[1]);
    }

#if 0
    float box_iarea (float const *b1, float const *b2) {
        float ibox[] = {std::max(b1[0], b2[0]),
                        std::max(b1[1], b2[1]),
                        std::min(b1[2], b2[2]),
                        std::min(b1[3], b2[3])};
        std::cerr << "AAA " << ibox[0] << " " << ibox[1] << " " << ibox[2] << " " << ibox[3] << std::endl;
        return box_area(ibox);
    }
#endif

    float iou_score (float const *b1, float const *b2) {
        float ibox[] = {std::max(b1[0], b2[0]),
                        std::max(b1[1], b2[1]),
                        std::min(b1[2], b2[2]),
                        std::min(b1[3], b2[3])};
        if (ibox[0] >= ibox[2]) return 0;
        if (ibox[1] >= ibox[3]) return 0;
        float ia = box_area(ibox);
        float ua = box_area(b1) + box_area(b2) - ia;
        return ia / (ua + 1.0);
    }

    class GTMatcher {
        float iou_th;
        int max;
        float min_size;
        bool best_only;
    public:
        GTMatcher (float th_, int max_, float min_size_, bool best_only_): iou_th(th_), max(max_), min_size(min_size_), best_only(best_only_) {
        }

        list apply (np::ndarray boxes,
                    np::ndarray box_ind_,
                    np::ndarray gt_boxes) {
            vector<std::pair<int, int>> match;

            CHECK(boxes.get_nd() == 2);
            CHECK(boxes.shape(0) == 0 || boxes.shape(1) == 4);
            CHECK(gt_boxes.get_nd() == 2);
            CHECK(gt_boxes.shape(0) == 0 || gt_boxes.shape(1) >= 7);
            // assign prediction to gt_boxes
            // algorithm:
            //      for each gt box pick the best match
            int nb = boxes.shape(0);
            int ng = gt_boxes.shape(0);
            CHECK(nb == box_ind_.shape(0));
            vector<bool> used(nb, false);

            int32_t const *box_ind = (int32_t const *)(box_ind_.get_data());
            int hit = 0;
            for (int i = 0; i < ng; ++i) {
                // i-th gt box
                float const *gt = (float const *)(gt_boxes.get_data() + gt_boxes.strides(0) * i);
                int ind = gt[0];
                gt = gt + 3;    // the box parameters
                float iou = iou_th;
                int best = -1;
                int this_hit = 0;
                for (int j = 0; j < nb; ++j) {
                    if (box_ind[j] != ind) continue; // not the same image
                    if (used[j]) continue;
                    float const *b = (float const *)(boxes.get_data() + boxes.strides(0) * j);
                    if (b[2] - b[0] < min_size) continue;
                    if (b[3] - b[1] < min_size) continue;
                    float s = iou_score(gt, b);
                    if (s > iou) {
                        if (best_only) {
                            iou = s;
                            best = j;
                        }
                        else {
                            match.emplace_back(j, i);
                            ++this_hit;
                        }
                    }
                }
                if (best >= 0) {
                    /*
                    float const *b = (float const *)(boxes.get_data() + boxes.strides(0) * best);
                    std::cerr << "YYY " << iou << ' ' << b[0] << ' ' << b[1] << ' ' << b[2] << ' ' << b[3] << std::endl;
                    float ia = box_iarea(gt, b);
                    std::cerr << iou_score(gt, b) << "  " << ia << "    " << box_area(gt) << "    " << box_area(b) << std::endl;
                    */
                    match.emplace_back(best, i);
                    used[best] = true;
                    ++this_hit;
                }
                if (this_hit > 0) {
                    ++hit;
                }
            }

            list r;
            np::ndarray cnt = np::zeros(make_tuple(), np::dtype::get_builtin<float>());
            if (best_only) {
                *(float *)cnt.get_data() = match.size();
            }
            else {
                *(float *)cnt.get_data() = hit;
            }

            if (match.size() > max) {
                std::random_shuffle(match.begin(), match.end());
                match.resize(max);
            }

            np::ndarray idx1 = np::zeros(make_tuple(match.size()), np::dtype::get_builtin<int32_t>());
            np::ndarray idx2 = np::zeros(make_tuple(match.size()), np::dtype::get_builtin<int32_t>());

            int32_t *p1 = (int32_t *)idx1.get_data();
            int32_t *p2 = (int32_t *)idx2.get_data();
            for (auto const &p: match) {
                *p1 = p.first;
                *p2 = p.second;
                ++p1;
                ++p2;
            }
            r.append(cnt);
            r.append(idx1);
            r.append(idx2);
            return r;
        }
    };

    class MaskExtractor {
        cv::Size sz;
    public:
        MaskExtractor (int width, int height): sz(width, height) {
        }

        np::ndarray apply (np::ndarray images,
                    np::ndarray gt_boxes,
                    np::ndarray boxes) {
            int n = 0;
            int H = images.shape(1);
            int W = images.shape(2);
            int C = images.shape(3);
            CHECK(C == 1);

            do {
                CHECK(images.get_nd() == 4);
                CHECK(gt_boxes.get_nd() == 2);
                CHECK(boxes.get_nd() == 2);
                CHECK(gt_boxes.shape(0) == boxes.shape(0));
                if (gt_boxes.shape(0) == 0) break;
                CHECK(gt_boxes.shape(1) >= 3);
                if (boxes.shape(0) == 0) break;
                CHECK(boxes.shape(1) == 4);
                n = gt_boxes.shape(0);
            } while(false);

            np::ndarray masks = np::zeros(make_tuple(n, sz.height, sz.width, 1), np::dtype::get_builtin<float>());

#pragma omp parallel for
            for (int i = 0; i < n; ++i) {
                float *gt_box = (float *)(gt_boxes.get_data() + i * gt_boxes.strides(0));
                float *box = (float *)(boxes.get_data() + i * boxes.strides(0));
                int index(gt_box[0]);
                int tag(gt_box[2]);
                cv::Mat image(H, W, CV_32F, images.get_data() + index * images.strides(0));
                float *mask_begin =  (float *)(masks.get_data() + i * masks.strides(0));
                cv::Mat mask(sz, CV_32F, mask_begin);

                int x1 = int(round(box[0]));
                int y1 = int(round(box[1]));
                int x2 = int(round(box[2]));
                int y2 = int(round(box[3]));
                CHECK(x1 >= 0);
                CHECK(y1 >= 0);
                CHECK(x2 < W);
                CHECK(y2 < H);
                cv::Rect roi(x1, y1, x2-x1+1, y2-y1+1);
                cv::Mat from = image(roi).clone();
                for (float *p = from.ptr<float>(0); p < from.ptr<float>(from.rows); ++p) {
                    if (p[0] == tag) { p[0] = 1.0;}
                    else p[0] = 0.0;
                }
                cv::resize(from, mask, sz, 0, 0);
            }
            return masks;
        }
    };

    list predict_basic_keypoints (np::ndarray prob, np::ndarray offsets, int stride, float th) {
        //
        CHECK(prob.get_nd() == 3);
        CHECK(offsets.get_nd() == 3);
        int H = prob.shape(0);
        int W = prob.shape(1);
        int C = prob.shape(2);
        CHECK(offsets.shape(0) == H);
        CHECK(offsets.shape(1) == W);
        CHECK(offsets.shape(2) == C * 2);

        // for each class
        list kp;
        for (int c = 0; c < C; ++c) {
            cv::Mat mass(H * stride, W * stride, CV_32F, cv::Scalar(0));
            for (int y = 0; y < H; ++y) {
                float const *pp = (float const *)(prob.get_data() + prob.strides(0) * y) + c;
                float const *po = (float const *)(offsets.get_data() + offsets.strides(0) * y) + c * 2;
                for (int x = 0; x < W; ++x, pp += C, po += C * 2) {
                    int tx = int(roundf(x * stride + po[0]));
                    int ty = int(roundf(y * stride + po[1]));
                    if (tx < 0) continue;
                    if (tx >= mass.cols) continue;
                    if (ty < 0) continue;
                    if (ty >= mass.rows) continue;
                    if (pp[0] >= th) {
                        mass.ptr<float>(ty)[tx] += pp[0];
                    }
                }
            }
            cv::boxFilter(mass, mass, -1, cv::Size(3,3));
            // find argmax
            double min, max;
            cv::Point min_loc, max_loc;
            cv::minMaxLoc(mass, &min, &max, &min_loc, &max_loc);
            kp.append(make_tuple(max_loc.x, max_loc.y, c, float(max)));
        }
        return kp;
    }
}

BOOST_PYTHON_MODULE(cpp)
{
    np::initialize();
    class_<GTMatcher>("GTMatcher", init<float, int, float, bool>())
        .def("apply", &GTMatcher::apply)
    ;
    class_<MaskExtractor>("MaskExtractor", init<int, int>())
        .def("apply", &MaskExtractor::apply)
    ;
    def("predict_basic_keypoints", ::predict_basic_keypoints);
}


///////////////////////////////////////////////////////////////////////////////
/// \file partial_sum.hpp
/// Like std::accumulate for the whole series, multiplied by the discretization
//
//  Copyright 2006 Eric Niebler. 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_TIME_SERIES_NUMERIC_PARTIAL_SUM_EAN_06_14_2006
#define BOOST_TIME_SERIES_NUMERIC_PARTIAL_SUM_EAN_06_14_2006

#include <boost/implicit_cast.hpp>
#include <boost/concept/requires.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <boost/range_run_storage/traits/is_unit_run.hpp>
#include <boost/range_run_storage/algorithm/copy.hpp>
#include <boost/time_series/time_series_fwd.hpp>
#include <boost/time_series/concepts.hpp>
#include <boost/time_series/piecewise_constant_series.hpp>
#include <boost/time_series/ordered_inserter.hpp>

namespace boost { namespace time_series
{

    namespace detail
    {
        namespace rrs = range_run_storage;

        // partial_sum_inserter
        template<typename Out, typename Series>
        struct partial_sum_inserter
        {
            typedef concepts::TimeSeries<Series> series_concept;
            typedef typename series_concept::value_type value_type;
            typedef typename series_concept::offset_type offset_type;

            partial_sum_inserter(Out const &out, Series const &series)
              : out_(out)
              , sum_(implicit_cast<value_type const &>(rrs::zero(series)))
              , offset_(-inf)
            {}

            template<typename Run>
            void set_at(Run const &run, value_type const &value)
            {
                if(this->offset_ != range_run_storage::offset(run))
                {
                    this->out_(this->sum_, this->offset_, rrs::offset(run));
                }

                this->partial_sum_run(value, run, rrs::traits::is_unit_run<Run>());
                this->offset_ = rrs::end_offset(run);
            }

            Out const &finalize()
            {
                if(inf != this->offset_)
                {
                    this->out_(this->sum_, this->offset_, inf);
                }
                return this->out_;
            }
        private:
            template<typename Run>
            void partial_sum_run(value_type const &value, Run const &run, mpl::false_)
            {
                BOOST_MPL_ASSERT((is_integral<offset_type>)); // TODO
                offset_type offset = rrs::offset(run);
                offset_type endoff = rrs::end_offset(run);

                for(; offset != endoff; ++offset)
                {
                    this->out_(this->sum_ += value, offset);
                }
            }

            template<typename Run>
            void partial_sum_run(value_type const &value, Run const &run, mpl::true_)
            {
                this->out_(this->sum_ += value, rrs::offset(run));
            }

            Out out_;
            value_type sum_;
            offset_type offset_;
        };
    }

    /// \brief Like \c std::partial_sum(), \c time_series::partial_sum() calculates
    /// a series of sums such that the result at offset \em i is the sum of the input
    /// from offsets [-inf,i].
    ///
    /// Like \c std::partial_sum(), \c time_series::partial_sum() calculates
    /// a series of sums such that the result at offset \em i is the sum of the input
    /// from offsets [0,i].
    ///
    /// This function assumes that zero value of the series does not contribute to the
    /// sums, and that the series does not have non-zero positive or negative infinite
    /// runs.
    ///
    /// \param series The input series.
    /// \param out The ordered inserter to receive the adjacent difference result.
    ///
    /// \pre \c Series is a model of the \c TimeSeries concept.
    /// \pre \c Out is a model of the \c OrderedInserter concept.
    /// \pre The \c offset_type of the \c Series is integral.
    /// \pre For variaibles \c x and \c y of the series' \c value_type, the expression
    ///     \c (x+=y) must be convertible to \c value_type.
    /// \pre For a variable \c x of the series' value_type,
    ///     <tt>x == (x + range_run_storage::zero(series))</tt>.
    /// \pre <tt>series[-inf] == range_run_storage::zero(series)</tt>
    /// \pre <tt>series[inf] == range_run_storage::zero(series)</tt>
    /// \return A \c piecewise_constant_series\<\> if no ordered inserter is specified;
    ///     otherwise, the ordered inserter.
    /// \attention If using the version that takes an \c OrderedInserter, you must call
    ///     <tt>.commit()</tt> on the returned \c OrderedInserter when you are done with it.
    template<typename Series, typename Out>
    BOOST_CONCEPT_REQUIRES(
        ((concepts::TimeSeries<Series const>)),
    (ordered_inserter<Out>))
    partial_sum(Series const &series, ordered_inserter<Out> out)
    {
        detail::partial_sum_inserter<ordered_inserter<Out>, Series> o(out, series);
        return range_run_storage::copy(series, o).finalize();
    }

    /// \overload
    ///
    template<typename Series>
    BOOST_CONCEPT_REQUIRES(
        ((concepts::TimeSeries<Series const>)),
    (piecewise_constant_series<
        typename concepts::TimeSeries<Series const>::value_type
      , typename concepts::TimeSeries<Series const>::discretization_type
    >))
    partial_sum(Series const &series)
    {
        typedef typename concepts::TimeSeries<Series const>::value_type value_type;
        typedef typename concepts::TimeSeries<Series const>::discretization_type discretization_type;

        // The partial differences are held in a piecewise constant array.
        piecewise_constant_series<value_type, discretization_type> result(
            time_series::discretization = series.discretization()
        );

        time_series::partial_sum(
            series
          , time_series::make_ordered_inserter(result)
        ).commit();

        return result;
    }

}}

#endif // BOOST_TIME_SERIES_NUMERIC_PARTIAL_SUM_EAN_06_14_2006


/**
 * @file    format_test.cc
 * @brief   unit testing for format related functions.
 * @author  [Yi-Mu "Enoch" Chen](https://github.com/yimuchen)
 * @details Detailed descriptions
 */
#ifdef CMSSW_GIT_HASH
#include "UserUtils/Common/interface/Format.hpp"
#include "UserUtils/Common/interface/STLUtils.hpp"
#else
#include "UserUtils/Common/Format.hpp"
#include "UserUtils/Common/STLUtils.hpp"
#endif

#include <boost/format.hpp>
#include <iostream>

using namespace std;

void
testdecimal( double x )
{
  cout << "Default: " << usr::fmt::base::decimal( x ) << endl
       << usr::separator() << endl;

  for( int i = 0; i < 30; ++i ){
    usr::fout( "Precision: %3d | %40s | %40s | %40s",
      i,
      usr::fmt::base::decimal( x, i ),
      usr::fmt::base::decimal( x, -i ),
      usr::fmt::base::decimal( x, i ).spacestr( " " ).str() );
  }

  cout << usr::separator() << endl;
}

void
testscientific( double x )
{
  for( int i = 0; i < 30; ++i ){
    usr::fout( "Precision %3d | %40s | %40s\n",
      i,
      usr::fmt::base::scientific( x, i ).str(),
      usr::fmt::base::scientific( x, i ).spacestr( "  " ).str() );
  }

  cout << usr::separator() << endl;
}

int
main( int argc, char* argv[] )
{
  cout << usr::separator() << ">>> General decimal point testing" << endl;

  testdecimal( 123456789 );
  testdecimal( 12345679.123456 );

  testscientific( 123456789 );
  testscientific( 12345679.123456 );

  return 0;
}


//
//  Copyright (C) Toon Knapen 2003
//
// 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_NUMERIC_BINDINGS_BLAS_BLAS2_OVERLOADS_HPP
#define BOOST_NUMERIC_BINDINGS_BLAS_BLAS2_OVERLOADS_HPP

#include <boost/numeric/bindings/blas/blas.h>
#include <boost/numeric/bindings/traits/type_traits.hpp>

namespace boost { namespace numeric { namespace bindings { namespace blas { namespace detail {

  using namespace boost::numeric::bindings::traits ;

  inline
  void gemv( char TRANS, const int& m, const int& n, const float    & alpha, const float    * a_ptr, const int& lda, const float    * x_ptr, const int& incx, const float    & beta, float    * y_ptr, const int& incy ) { BLAS_SGEMV( &TRANS, &m, &n,          ( &alpha ),          ( a_ptr ), &lda,          ( x_ptr ), &incx,          ( &beta ),          ( y_ptr ), &incy ) ; }
  inline
  void gemv( char TRANS, const int& m, const int& n, const double   & alpha, const double   * a_ptr, const int& lda, const double   * x_ptr, const int& incx, const double   & beta, double   * y_ptr, const int& incy ) { BLAS_DGEMV( &TRANS, &m, &n,          ( &alpha ),          ( a_ptr ), &lda,          ( x_ptr ), &incx,          ( &beta ),          ( y_ptr ), &incy ) ; }
  inline
  void gemv( char TRANS, const int& m, const int& n, const complex_f& alpha, const complex_f* a_ptr, const int& lda, const complex_f* x_ptr, const int& incx, const complex_f& beta, complex_f* y_ptr, const int& incy ) { BLAS_CGEMV( &TRANS, &m, &n, complex_ptr( &alpha ), complex_ptr( a_ptr ), &lda, complex_ptr( x_ptr ), &incx, complex_ptr( &beta ), complex_ptr( y_ptr ), &incy ) ; }
  inline
  void gemv( char TRANS, const int& m, const int& n, const complex_d& alpha, const complex_d* a_ptr, const int& lda, const complex_d* x_ptr, const int& incx, const complex_d& beta, complex_d* y_ptr, const int& incy ) { BLAS_ZGEMV( &TRANS, &m, &n, complex_ptr( &alpha ), complex_ptr( a_ptr ), &lda, complex_ptr( x_ptr ), &incx, complex_ptr( &beta ), complex_ptr( y_ptr ), &incy ) ; }

  inline
  void ger( const int& m, const int& n, const float  & alpha, const float  * x_ptr, const int& incx, const float  * y_ptr, const int& incy, float  * a_ptr, const int& lda ) { BLAS_SGER( &m, &n, &alpha, x_ptr, &incx, y_ptr, &incy, a_ptr, &lda ) ; }
  inline
  void ger( const int& m, const int& n, const double & alpha, const double * x_ptr, const int& incx, const double * y_ptr, const int& incy, double * a_ptr, const int& lda ) { BLAS_DGER( &m, &n, &alpha, x_ptr, &incx, y_ptr, &incy, a_ptr, &lda ) ; }
/*
  inline
  void geru( const int& m, const int& n, const complex_f & alpha, const complex_f * x_ptr, const int& incx, complex_f * y_ptr, const int& incy, complex_f * a_ptr, const int& lda ) { BLAS_CGERU( &m, &n, complex_ptr( &alpha ), complex_ptr( x_ptr ), &incx, complex_ptr( y_ptr ), &incy, complex_ptr( a_ptr ), &lda ) ; }
  inline
  void geru( const int& m, const int& n, const complex_d & alpha, const complex_d * x_ptr, const int& incx, complex_d * y_ptr, const int& incy, complex_d * a_ptr, const int& lda ) { BLAS_ZGERU( &m, &n, complex_ptr( &alpha ), complex_ptr( x_ptr ), &incx, complex_ptr( y_ptr ), &incy, complex_ptr( a_ptr ), &lda ) ; }
*/
}}}}}

#endif // BOOST_NUMERIC_BINDINGS_BLAS_BLAS2_OVERLOADS_HPP


/** \file MatVecExpr.hpp */

#pragma once

// #include <boost/numeric/linear_algebra/identity.hpp>	// mtl::math::zero

#include <traits/basic.hpp>
#include <traits/traits_fwd.hpp>

#include "matrix_vector/expr/base_expr.hpp"
#include <operations/meta.hpp>
#include <matrix_vector/Vector.hpp>
#include <Math.hpp>

namespace AMDiS
{
  // helper class for matrix element access
  template <class MatE>
  struct ColumnIndex
  {
    using value_type = Value_t<MatE>;
    using size_type  = Size_t<MatE>;

    ColumnIndex(MatE const& matrix_, size_type row_)
      : matrix(matrix_), row(row_) { }

    value_type const& operator()(size_type col) const
    {
      return matrix(row,col);
    }

  private:
    MatE const& matrix;
    size_type row;
  };

  // if necessary assign vector_expr to buffer-vector, otherwise store an expr.
  template <class VecE, bool use_buffer>
  using BufferType
    = if_then_else< use_buffer,
	if_then_else< (VecE::_SIZE> 0),
	  VectorBase<MemoryBaseStatic<Value_t<VecE>, math::max(0,VecE::_SIZE), 1>,
		     StaticSizePolicy<math::max(0,VecE::_SIZE)> >,
	  VectorBase<MemoryBaseDynamic<Value_t<VecE>, false>> >,
	VecE const& >;

  /// Expression with two arguments, that multiplied a matrix_expr with a vector_expr
  template <class MatE, class VecE, bool use_buffer>
  struct MatVecExpr
    : public VectorExpr<MatVecExpr<MatE, VecE, use_buffer>>
  {
    using Self      = MatVecExpr;
    using expr_base = VectorExpr<Self>;

    using value_type = Value_t<VecE>;
    using size_type  = traits::MaxSizeType<MatE, VecE>;

    using matrix_type = MatE;
    using vector_type = BufferType<VecE, use_buffer>;

    // sizes of the resulting expr.
    static constexpr int _SIZE = MatE::_ROWS;
    static constexpr int _ROWS = MatE::_ROWS;
    static constexpr int _COLS = math::max(VecE::_COLS, 1);

  private:
    static constexpr int ARG_COLS = math::max(VecE::_ROWS, MatE::_COLS);

  public:
    /// constructor takes a matrix expression \p mat and a
    /// vector expression \p vec for the matrix-vector product.
    MatVecExpr(matrix_type const& mat, vector_type const& vec)
      : matrix(mat), vector(vec)
    {
      TEST_EXIT_DBG( num_cols(mat) == num_rows(vec), "Sizes do not match!\n");
    }

    MatVecExpr(Self const&) = delete;
    MatVecExpr(Self&&)      = default;

    /// access the elements of an expr.
    value_type operator()(size_type i) const
    {
      return reduce(i, int_<ARG_COLS>());
    }

    matrix_type const& get_matrix() const
    {
      return matrix;
    }
    vector_type const& get_vector() const
    {
      return vector;
    }

  protected:
    // reduce the expression, if length is known at compiletime
    template <int N>
    value_type reduce(size_type row, int_<N>) const
    {
      using meta::FOR;
      value_type erg; nullify(erg);
      FOR<0,N>::inner_product(ColumnIndex<MatE>(matrix,row), vector, erg,
                              functors::dot_functor<value_type, value_type>());
      return erg;
    }

    // reduce the expression, if length is known only at runtime
    value_type reduce(size_type r, int_<-1>) const
    {
      value_type erg; nullify(erg);
      for (size_type c = 0; c < num_cols(matrix); ++c)
        erg += matrix(r,c) * vector(c);
      return erg;
    }

  private:
    matrix_type const&  matrix;
    vector_type vector;
  };

  // TODO: generalize MatVec expression to allow a functor passed to the expr.
  //       Example: diagonal(matrix) -> vector_expr

  /// Size of MatVecExpr
  template <class E1, class E2, bool b>
  inline size_t size(MatVecExpr<E1,E2,b> const& expr)
  {
    return num_rows(expr.get_matrix());
  }

  /// number of rows of MatVecExpr
  template <class E1, class E2, bool b>
  inline size_t num_rows(MatVecExpr<E1,E2,b> const& expr)
  {
    return num_rows(expr.get_matrix());
  }

  /// number of columns of MatVecExpr
  template <class E1, class E2, bool b>
  inline size_t num_cols(MatVecExpr<E1,E2,b> const& /*expr*/)
  {
    return 1;
  }

  namespace traits
  {
    /// \cond HIDDEN_SYMBOLS
    template <class E1, class E2, bool b>
    struct category<MatVecExpr<E1,E2,b>>
    {
      using tag = typename category<E2>::tag;
      using value_type = Value_t<MatVecExpr<E1,E2,b>>;
      using size_type  = Size_t<MatVecExpr<E1,E2,b>>;
    };
    /// \endcond

  } // end namespace traits

} // end namespace AMDiS


#include <iostream>                    // std::flush
#include <fstream>                     // std::ofstream
#include <string>                      // std::string
#include <tuple>                       // std::tuple
#include <vector>                      // std::vector
// For External Library
#include <torch/torch.h>               // torch
#include <boost/program_options.hpp>   // boost::program_options
// For Original Header
#include "loss.hpp"                    // Loss, MMDLoss
#include "networks.hpp"                // WAE_Encoder, WAE_Decoder
#include "dataloader.hpp"              // DataLoader::ImageFolderWithPaths
#include "visualizer.hpp"              // visualizer::graph

// Define Namespace
namespace po = boost::program_options;

// -------------------
// Validation Function
// -------------------
void valid(po::variables_map &vm, DataLoader::ImageFolderWithPaths &valid_dataloader, torch::Device &device, Loss &criterion, MMDLoss &criterion_MMD, WAE_Encoder &enc, WAE_Decoder &dec, const size_t epoch, visualizer::graph &writer){

    // (0) Initialization and Declaration
    size_t iteration;
    size_t mini_batch_size;
    float ave_rec_loss, total_rec_loss;
    float ave_mmd_loss, total_mmd_loss;
    std::ofstream ofs;
    std::tuple<torch::Tensor, std::vector<std::string>> mini_batch;
    torch::Tensor image, output, z_real, z_fake;
    torch::Tensor rec_loss, mmd_loss;

    // (1) Tensor Forward per Mini Batch
    torch::NoGradGuard no_grad;
    enc->eval();
    dec->eval();
    iteration = 0;
    total_rec_loss = 0.0; total_mmd_loss = 0.0;
    while (valid_dataloader(mini_batch)){

        image = std::get<0>(mini_batch).to(device);
        mini_batch_size = image.size(0);

        // (1.1) Calculation of Reconstruction Loss
        z_real = torch::randn({(long int)mini_batch_size, (long int)vm["nz"].as<size_t>()}).to(device);
        z_fake = enc->forward(image);
        output = dec->forward(z_fake);
        rec_loss = criterion(output, image);

        // (1.2) Calculation of Maximum Mean Discrepancy (MMD) Loss
        mmd_loss = criterion_MMD(z_fake, z_real) * vm["Lambda"].as<float>();

        // (1.3) Update Loss
        total_rec_loss += rec_loss.item<float>();
        total_mmd_loss += mmd_loss.item<float>();

        iteration++;
    }

    // (2) Calculate Average Loss
    ave_rec_loss = total_rec_loss / (float)iteration;
    ave_mmd_loss = total_mmd_loss / (float)iteration;

    // (3.1) Record Loss (Log)
    ofs.open("checkpoints/" + vm["dataset"].as<std::string>() + "/log/valid.txt", std::ios::app);
    ofs << "epoch:" << epoch << '/' << vm["epochs"].as<size_t>() << ' ' << std::flush;
    ofs << "rec:" << ave_rec_loss << ' ' << std::flush;
    ofs << "mmd:" << ave_mmd_loss << std::endl;
    ofs.close();

    // (3.2) Record Loss (Graph)
    writer.plot(/*base=*/epoch, /*value=*/{ave_rec_loss, ave_mmd_loss});

    // End Processing
    return;

}