#include <assert.h>
#include <iostream>
#include <vector>
#include <memory>
#include <math.h>
#include <Eigen/Dense>
#include <iostream>
#include <sstream>
#include <fstream>
#include <set>

#include "multidim.h"
#include "pymulti.h"
#include "extras.h"

#include "pstring.h"
#include "clstm.h"

using namespace Eigen;
using namespace ocropus;
using namespace pymulti;
using std::vector;
using std::map;
using std::make_pair;
using std::shared_ptr;
using std::unique_ptr;
using std::cout;
using std::ifstream;
using std::set;
using std::to_string;
using std_string = std::string;
using std_wstring = std::wstring;
#define string std_string
#define wstring std_wstring

struct Sample {
  wstring in, out;
};

void read_samples(vector<Sample> &samples, const string &fname) {
  ifstream stream(fname);
  string line;
  wstring in, out;
  ;
  samples.clear();
  while (getline(stream, line)) {
    // skip blank lines and lines starting with a comment
    if (line.substr(0, 2) == "# ") continue;
    if (line.size() == 0) continue;
    int where = line.find("\t");
    if (where < 0) THROW("no tab found in input line");
    in = utf8_to_utf32(line.substr(0, where));
    out = utf8_to_utf32(line.substr(where + 1));
    if (in.size() == 0) continue;
    if (out.size() == 0) continue;
    samples.push_back(Sample{in, out});
  }
}

void read_samples2(vector<Sample> &samples, const string &fname,
                   const string &fname2) {
  string line;
  samples.clear();
  wstring empty;
  {
    ifstream stream(fname);
    while (getline(stream, line)) {
      // skip blank lines and lines starting with a comment
      if (line.substr(0, 2) == "# ") continue;
      if (line.size() == 0) continue;
      wstring in = utf8_to_utf32(line);
      samples.push_back(Sample{in, empty});
    }
  }

  {
    ifstream stream(fname);
    int n = 0;
    while (getline(stream, line)) {
      // skip blank lines and lines starting with a comment
      if (line.substr(0, 2) == "# ") continue;
      if (line.size() == 0) continue;
      if (n >= samples.size()) THROW("too many lines in output file");
      wstring out = utf8_to_utf32(line);
      samples[n].out = out;
      n++;
    }
    if (n < samples.size()) THROW("too few lines in output file");
  }
}

void get_codec(vector<int> &codec, vector<Sample> &samples,
               wstring Sample::*p) {
  set<int> codes;
  codes.insert(0);
  for (auto e : samples) {
    for (auto c : e.*p) codes.insert(int(c));
  }
  for (auto c : codes) codec.push_back(c);
  for (int i = 1; i < codec.size(); i++) assert(codec[i] > codec[i - 1]);
}

void set_inputs_with_eps(INetwork *net, wstring &s, int neps) {
  Classes cs;
  net->iencode(cs, s);
  Sequence &seq = net->inputs;
  int d = net->ninput();
  seq.clear();
  seq.resize(cs.size() * (neps + 1) + neps);
  for (int i = 0; i < neps; i++) seq[i].setZero(d, 1);
  for (int pos = 0; pos < cs.size(); pos++) {
    seq[pos].setZero(d, 1);
    seq[pos](cs[pos], 0) = 1.0;
    for (int i = 0; i < neps; i++) seq[pos + 1 + i].setZero(d, 1);
  }
}

double error_rate(Network net, const string &testset, int nclasses, int neps) {
  int maxeval = getienv("maxeval", 1000000000);
  vector<Sample> samples;
  read_samples(samples, testset);

  int N = fmin(samples.size(), maxeval);
  double errs = 0.0;
  double total = 0;

  for (int sample = 0; sample < N; sample++) {
    Classes cs;
    set_inputs_with_eps(net.get(), samples[sample].in, neps);
    mdarray<float> image;
    assign(image, net->inputs);
    net->forward();
    mdarray<float> outputs;
    assign(outputs, net->outputs);
    Classes output_classes;
    trivial_decode(output_classes, net->outputs);
    wstring out = net->decode(output_classes);
    wstring gt = samples[sample].out;
    double err = levenshtein(gt, out);
    errs += err;
    total += gt.size();
  }
  return errs / total;
}

int main_train(int argc, char **argv) {
  srandomize();

  vector<Sample> samples;
  if (argc == 2) {
    read_samples(samples, argv[1]);
  } else if (argc == 3) {
    read_samples2(samples, argv[1], argv[2]);
  } else {
    THROW("args: training.txt [output.txt]");
  }
  print("got", samples.size(), "lines");
  int nsamples = samples.size();

  string load_name = getsenv("load", "");
  int save_every = getienv("save_every", 0);
  string save_name = getsenv("save_name", "");
  if (save_every >= 0 && save_name == "") THROW("must give save_name=");
  if (save_every > 0 && save_name.find('%') == string::npos)
    save_name += "-%08d.clstm";
  else
    save_name += ".clstm";
  string after_save = getsenv("after_save", "");

  int ntrain = getienv("ntrain", 1000000);
  double lrate = getrenv("lrate", 1e-4);
  int nhidden = getrenv("nhidden", getrenv("hidden", 100));
  int nhidden2 = getrenv("nhidden2", getrenv("hidden2", -1));
  int batch = getrenv("batch", 1);
  double momentum = getuenv("momentum", 0.9);
  int display_every = getienv("display_every", 0);
  int report_every = getienv("report_every", 1);
  bool randomize = getienv("randomize", 1);
  string lrnorm = getsenv("lrnorm", "batch");
  int neps = int(getuenv("neps", 3));
  string net_type = getsenv("lstm", "bidi");
  string lstm_type = getsenv("lstm_type", "NPLSTM");
  string output_type = getsenv("output_type", "SoftmaxLayer");

  string testset = getsenv("testset", "");
  int test_every = getienv("test_every", -1);
  string after_test = getsenv("after_test", "");

  print("params", "hg_version", HGVERSION, "lrate", lrate, "nhidden", nhidden,
        "nhidden2", nhidden2, "batch", batch, "momentum", momentum);

  unique_ptr<PyServer> py;
  if (display_every > 0) {
    py.reset(new PyServer());
    if (display_every > 0) py->open();
    py->eval("ion()");
    py->eval("matplotlib.rc('xtick',labelsize=7)");
    py->eval("matplotlib.rc('ytick',labelsize=7)");
    py->eval("matplotlib.rcParams.update({'font.size':7})");
  }

  Network net;
  int nclasses = -1, iclasses = -1;
  if (load_name != "") {
    net = load_net(load_name);
    nclasses = net->codec.size();
    iclasses = net->icodec.size();
    neps = net->attr.get("neps");
  } else {
    vector<int> icodec, codec;
    get_codec(icodec, samples, &Sample::in);
    get_codec(codec, samples, &Sample::out);
    iclasses = icodec.size();
    nclasses = codec.size();
    net = make_net(net_type, {{"ninput", iclasses},
                              {"noutput", nclasses},
                              {"nhidden", nhidden},
                              {"nhidden2", nhidden2},
                              {"lstm_type", lstm_type},
                              {"output_type", output_type},
                              {"neps", neps}});
    net->icodec = icodec;
    net->codec = codec;
  }
  net->setLearningRate(lrate, momentum);
  net->makeEncoders();
  net->info("");
  print("codec", net->codec.size(), "icodec", net->icodec.size());
  INetwork::Normalization norm = INetwork::NORM_DFLT;
  if (lrnorm == "len") norm = INetwork::NORM_LEN;
  if (lrnorm == "none") norm = INetwork::NORM_NONE;
  if (norm != INetwork::NORM_DFLT) print("nonstandard lrnorm: ", lrnorm);
  net->networks("",
                [norm](string s, INetwork *net) { net->normalization = norm; });

  Sequence targets;
  Sequence saligned;
  Classes classes;

  double start_time = now();
  double best_erate = 1e38;

  int start = net->attr.get("trial", getienv("start", -1)) + 1;
  if (start > 0) print("start", start);
  for (int trial = start; trial < ntrain; trial++) {
    bool report = (report_every > 0) && (trial % report_every == 0);
    int sample = trial % nsamples;
    if (randomize) sample = irandom() % nsamples;
    if (trial > 0 && save_every > 0 && trial % save_every == 0) {
      char fname[4096];
      sprintf(fname, save_name.c_str(), trial);
      print("saving", fname);
      net->attr.set("trial", trial);
      save_net(fname, net);
      if (after_save != "") system(after_save.c_str());
      cout.flush();
    }
    if (trial > 0 && test_every > 0 && trial % test_every == 0 &&
        testset != "") {
      double erate = error_rate(net, testset, nclasses, neps);
      print("TESTERR", now() - start_time, save_name, trial, erate, "lrate",
            lrate, "hidden", nhidden, nhidden2, "batch", batch, "momentum",
            momentum);
      if (save_every == 0 && erate < best_erate) {
        best_erate = erate;
        print("saving", save_name, "at", erate);
        net->attr.set("trial", trial);
        net->attr.set("last_err", best_erate);
        save_net(save_name, net);
        if (after_save != "") system(after_save.c_str());
      }
      if (after_test != "") system(after_test.c_str());
      cout.flush();
    }
    set_inputs_with_eps(net.get(), samples[sample].in, neps);
    mdarray<float> image;
    assign(image, net->inputs);
    Classes transcript;
    net->encode(transcript, samples[sample].out);
    net->forward();
    classes = transcript;
    mdarray<float> outputs;
    assign(outputs, net->outputs);
    mktargets(targets, classes, nclasses);
    ctc_align_targets(saligned, net->outputs, targets);
    assert(saligned.size() == net->outputs.size());
    for (int t = 0; t < saligned.size(); t++)
      net->outputs[t].d = saligned[t] - net->outputs[t];
    net->backward();
    if (trial % batch == 0) net->update();
    mdarray<float> aligned;
    assign(aligned, saligned);
    if (anynan(outputs) || anynan(aligned)) {
      print("got nan");
      break;
    }
    Classes output_classes, aligned_classes;
    trivial_decode(output_classes, net->outputs);
    trivial_decode(aligned_classes, saligned);
    wstring gt = net->decode(transcript);
    wstring out = net->decode(output_classes);
    wstring aln = net->decode(aligned_classes);
    if (report) {
      wstring s = samples[sample].in;
      print("trial", trial);
      print("INP:", "'" + utf32_to_utf8(s) + "'");
      print("TRU:", "'" + utf32_to_utf8(gt) + "'");
      print("OUT:", "'" + utf32_to_utf8(out) + "'");
      print("ALN:", "'" + utf32_to_utf8(aln) + "'");
      print(levenshtein(gt, out));
      cout.flush();
    }

    if (display_every > 0 && trial % display_every == 0) {
      py->eval("clf()");
      py->subplot(4, 1, 1);
      py->evalf("title('%s')", gt.c_str());
      py->imshowT(image, "cmap=cm.gray,interpolation='bilinear'");
      py->subplot(4, 1, 2);
      py->evalf("title('%s')", out.c_str());
      py->imshowT(outputs, "cmap=cm.hot,interpolation='bilinear'");
      py->subplot(4, 1, 3);
      py->evalf("title('%s')", aln.c_str());
      py->imshowT(aligned, "cmap=cm.hot,interpolation='bilinear'");
      py->subplot(4, 1, 4);
      mdarray<float> v;
      v.resize(outputs.dim(0));
      for (int t = 0; t < outputs.dim(0); t++) v(t) = outputs(t, 0);
      py->plot(v, "color='b'");
      int sp = 1;
      for (int t = 0; t < outputs.dim(0); t++) v(t) = outputs(t, sp);
      py->plot(v, "color='g'");
      int nclass = net->outputs[0].size();
      for (int t = 0; t < outputs.dim(0); t++)
        v(t) = net->outputs[t].col(0).segment(2, nclass - 2).maxCoeff();
      py->evalf("xlim(0,%d)", outputs.dim(0));
      py->plot(v, "color='r'");
      py->eval("ginput(1,1e-3)");
      cout.flush();
    }
  }
  return 0;
}

int main_filter(int argc, char **argv) {
  if (argc != 2) THROW("give text file as an argument");
  int display_every = getienv("display_every", -1);
  double display_delay = getdenv("display_delay", 1e-3);
  const char *fname = argv[1];
  string load_name = getsenv("load", "");
  if (load_name == "") THROW("must give load= parameter");
  Network net;
  net = load_net(load_name);
  int neps = net->attr.get("neps");
  dprint("codec", net->codec.size(), "icodec", net->icodec.size(), "neps",
         neps);

  unique_ptr<PyServer> py;
  if (display_every > 0) {
    py.reset(new PyServer());
    if (display_every > 0) py->open();
    py->eval("ion()");
    py->eval("matplotlib.rc('xtick',labelsize=7)");
    py->eval("matplotlib.rc('ytick',labelsize=7)");
    py->eval("matplotlib.rcParams.update({'font.size':7})");
  }

  string line;
  wstring in, out;

  ifstream stream(fname);
  int trial = 0;
  while (getline(stream, line)) {
    int where = line.find("\t");
    if (where >= 0) line = line.substr(0, where);
    in = utf8_to_utf32(line);
    set_inputs_with_eps(net.get(), in, neps);
    net->forward();
    Classes output_classes;
    trivial_decode(output_classes, net->outputs);
    wstring out = net->decode(output_classes);
    string out8 = utf32_to_utf8(out);
    print(out8);
    if (display_every > 0 && trial % display_every == 0) {
      mdarray<float> inputs, outputs;
      assign(inputs, net->inputs);
      assign(outputs, net->outputs);
      py->eval("clf()");
      py->subplot(2, 1, 1);
      py->eval("set_aspect('auto')");
      py->evalf("title(unicode('%s','utf-8'))", line.c_str());
      py->imshowT(inputs, "cmap=cm.gray,interpolation='bilinear'");
      py->subplot(2, 1, 2);
      py->eval("set_aspect('auto')");
      py->evalf("title(unicode('%s','utf-8'))", out.c_str());
      py->imshowT(outputs, "cmap=cm.gray,interpolation='bilinear'");
      py->evalf("ginput(1,%g)", display_delay);
    }
    trial++;
  }
  return 0;
}

const char *usage = /*program+*/
    "training.txt\n\n"
    "training.txt is a text file consisting of lines of the form:\n\n"
    "input\toutput\n\n"
    "UTF-8 encoding is assumed.\n";

int main(int argc, char **argv) {
  if (argc < 2) {
    print(string(argv[0]) + " " + usage);
    exit(1);
  }
  TRY {
    string mode = getsenv("mode", "train");
    if (mode == "train") {
      return main_train(argc, argv);
    } else if (mode == "filter") {
      return main_filter(argc, argv);
    }
  } CATCH (const char *msg) {
    print("EXCEPTION", msg);
  } CATCH (...) {
    print("UNKNOWN EXCEPTION");
  }
}


// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2012 Désiré Nuentsa-Wakam <desire.nuentsa_wakam@inria.fr>
//
// Eigen is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// Alternatively, you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// Eigen 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 GNU Lesser General Public License or the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License and a copy of the GNU General Public License along with
// Eigen. If not, see <http://www.gnu.org/licenses/>.
#include "sparse_solver.h"
#include <Eigen/SparseLU>
#include <Eigen/MetisSupport>
#include <unsupported/Eigen/SparseExtra>

template<typename T> void test_metis_T()
{
  SparseLU<SparseMatrix<T, ColMajor>, MetisOrdering<int> > sparselu_metis;

  check_sparse_square_solving(sparselu_metis);
}

void test_metis_support()
{
  CALL_SUBTEST_1(test_metis_T<double>());
}


// Distributed under the MIT License.
// See LICENSE.txt for details.

#include "Framework/TestingFramework.hpp"

#include <array>
#include <boost/functional/hash.hpp>
#include <cstddef>
#include <random>
#include <string>
#include <unordered_map>
#include <utility>

#include "DataStructures/DataBox/Tag.hpp"
#include "DataStructures/DataVector.hpp"
#include "DataStructures/Tags.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "DataStructures/Variables.hpp"
#include "Domain/LogicalCoordinates.hpp"
#include "Domain/Structure/Direction.hpp"
#include "Domain/Structure/Element.hpp"  // IWYU pragma: keep
#include "Domain/Structure/ElementId.hpp"
#include "Domain/Structure/OrientationMap.hpp"
#include "Domain/Structure/OrientationMapHelpers.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/HwenoImpl.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/SimpleWenoImpl.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/Weno.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/WenoType.hpp"
#include "Framework/TestCreation.hpp"
#include "Framework/TestHelpers.hpp"
#include "Helpers/DataStructures/MakeWithRandomValues.hpp"
#include "Helpers/Evolution/DiscontinuousGalerkin/Limiters/TestHelpers.hpp"
#include "NumericalAlgorithms/Interpolation/RegularGridInterpolant.hpp"
#include "NumericalAlgorithms/LinearOperators/MeanValue.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "NumericalAlgorithms/Spectral/Spectral.hpp"
#include "Utilities/ConstantExpressions.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/MakeArray.hpp"
#include "Utilities/TMPL.hpp"
#include "Utilities/TaggedTuple.hpp"

// IWYU pragma: no_include "DataStructures/DataBox/Prefixes.hpp"
// IWYU pragma: no_include "DataStructures/VariablesHelpers.hpp"
// IWYU pragma: no_forward_declare Limiters::Weno
// IWYU pragma: no_forward_declare Tags::Mean
// IWYU pragma: no_forward_declare Tensor
// IWYU pragma: no_forward_declare Variables
// IWYU pragma: no_forward_declare intrp::RegularGrid

namespace {

struct ScalarTag : db::SimpleTag {
  using type = Scalar<DataVector>;
  static std::string name() noexcept { return "Scalar"; }
};

template <size_t VolumeDim>
struct VectorTag : db::SimpleTag {
  using type = tnsr::I<DataVector, VolumeDim>;
  static std::string name() noexcept { return "Vector"; }
};

void test_weno_option_parsing() noexcept {
  INFO("Test WENO option parsing");

  const auto hweno_1d =
      TestHelpers::test_creation<Limiters::Weno<1, tmpl::list<ScalarTag>>>(
          "Type: Hweno\n"
          "NeighborWeight: 0.001\n"
          "TvbConstant: 0.0\n"
          "DisableForDebugging: False");
  const auto hweno_1d_larger_weight =
      TestHelpers::test_creation<Limiters::Weno<1, tmpl::list<ScalarTag>>>(
          "Type: Hweno\n"
          "NeighborWeight: 0.01\n"
          "TvbConstant: 0.0\n"
          "DisableForDebugging: False");
  const auto hweno_1d_tvb =
      TestHelpers::test_creation<Limiters::Weno<1, tmpl::list<ScalarTag>>>(
          "Type: Hweno\n"
          "NeighborWeight: 0.001\n"
          "TvbConstant: 1.0\n"
          "DisableForDebugging: False");
  const auto hweno_1d_disabled =
      TestHelpers::test_creation<Limiters::Weno<1, tmpl::list<ScalarTag>>>(
          "Type: Hweno\n"
          "NeighborWeight: 0.001\n"
          "TvbConstant: 0.0\n"
          "DisableForDebugging: True");
  const auto simple_weno_1d =
      TestHelpers::test_creation<Limiters::Weno<1, tmpl::list<ScalarTag>>>(
          "Type: SimpleWeno\n"
          "NeighborWeight: 0.001\n"
          "TvbConstant: 0.0\n"
          "DisableForDebugging: False");

  // Check operators == and !=
  CHECK(hweno_1d == hweno_1d);
  CHECK(hweno_1d != hweno_1d_larger_weight);
  CHECK(hweno_1d != hweno_1d_tvb);
  CHECK(hweno_1d != hweno_1d_disabled);
  CHECK(hweno_1d != simple_weno_1d);

  const auto hweno_2d =
      TestHelpers::test_creation<Limiters::Weno<2, tmpl::list<ScalarTag>>>(
          "Type: Hweno\n"
          "NeighborWeight: 0.001\n"
          "TvbConstant: 0.0\n"
          "DisableForDebugging: False");
  const auto hweno_3d_larger_weight = TestHelpers::test_creation<
      Limiters::Weno<3, tmpl::list<ScalarTag, VectorTag<3>>>>(
      "Type: Hweno\n"
      "NeighborWeight: 0.01\n"
      "TvbConstant: 0.0\n"
      "DisableForDebugging: True");

  // Check that creation from options gives correct object
  const Limiters::Weno<1, tmpl::list<ScalarTag>> expected_hweno_1d(
      Limiters::WenoType::Hweno, 0.001, 0.0);
  const Limiters::Weno<1, tmpl::list<ScalarTag>>
      expected_hweno_1d_larger_weight(Limiters::WenoType::Hweno, 0.01, 0.0);
  const Limiters::Weno<1, tmpl::list<ScalarTag>> expected_hweno_1d_tvb(
      Limiters::WenoType::Hweno, 0.001, 1.0);
  const Limiters::Weno<1, tmpl::list<ScalarTag>> expected_hweno_1d_disabled(
      Limiters::WenoType::Hweno, 0.001, 0.0, true);
  const Limiters::Weno<1, tmpl::list<ScalarTag>> expected_simple_weno_1d(
      Limiters::WenoType::SimpleWeno, 0.001, 0.0);
  const Limiters::Weno<2, tmpl::list<ScalarTag>> expected_hweno_2d(
      Limiters::WenoType::Hweno, 0.001, 0.0);
  const Limiters::Weno<3, tmpl::list<ScalarTag, VectorTag<3>>>
      expected_hweno_3d_larger_weight(Limiters::WenoType::Hweno, 0.01, 0.0,
                                      true);
  CHECK(hweno_1d == expected_hweno_1d);
  CHECK(hweno_1d_larger_weight == expected_hweno_1d_larger_weight);
  CHECK(hweno_1d_tvb == expected_hweno_1d_tvb);
  CHECK(hweno_1d_disabled == expected_hweno_1d_disabled);
  CHECK(simple_weno_1d == expected_simple_weno_1d);
  CHECK(hweno_2d == expected_hweno_2d);
  CHECK(hweno_3d_larger_weight == expected_hweno_3d_larger_weight);
}

void test_weno_serialization() noexcept {
  INFO("Test WENO serialization");
  const Limiters::Weno<1, tmpl::list<ScalarTag>> weno(Limiters::WenoType::Hweno,
                                                      0.01, 1.0, true);
  test_serialization(weno);
}

template <size_t VolumeDim>
void test_package_data_work(
    const Mesh<VolumeDim>& mesh,
    const OrientationMap<VolumeDim>& orientation_map) noexcept {
  const DataVector used_for_size(mesh.number_of_grid_points());
  MAKE_GENERATOR(generator);
  std::uniform_real_distribution<> dist(-1., 1.);

  const auto input_scalar = make_with_random_values<ScalarTag::type>(
      make_not_null(&generator), make_not_null(&dist), used_for_size);
  const auto input_vector =
      make_with_random_values<typename VectorTag<VolumeDim>::type>(
          make_not_null(&generator), make_not_null(&dist), used_for_size);
  const auto element_size =
      make_with_random_values<std::array<double, VolumeDim>>(
          make_not_null(&generator), make_not_null(&dist), 0.0);

  using TagList = tmpl::list<ScalarTag, VectorTag<VolumeDim>>;
  const double neighbor_linear_weight = 0.001;
  const double tvb_constant = 0.0;
  const Limiters::Weno<VolumeDim, TagList> weno(
      Limiters::WenoType::Hweno, neighbor_linear_weight, tvb_constant);
  typename Limiters::Weno<VolumeDim, TagList>::PackagedData packaged_data{};

  weno.package_data(make_not_null(&packaged_data), input_scalar, input_vector,
                    mesh, element_size, orientation_map);

  const Variables<TagList> oriented_vars = [&mesh, &input_scalar, &input_vector,
                                            &orientation_map]() noexcept {
    Variables<TagList> input_vars(mesh.number_of_grid_points());
    get<ScalarTag>(input_vars) = input_scalar;
    get<VectorTag<VolumeDim>>(input_vars) = input_vector;
    return orient_variables(input_vars, mesh.extents(), orientation_map);
  }();
  CHECK(packaged_data.volume_data == oriented_vars);
  CHECK(packaged_data.mesh == orientation_map(mesh));
  CHECK(packaged_data.element_size ==
        orientation_map.permute_from_neighbor(element_size));

  // Means are just numbers and don't care about orientations
  CHECK(get(get<::Tags::Mean<ScalarTag>>(packaged_data.means)) ==
        mean_value(get(input_scalar), mesh));
  for (size_t i = 0; i < VolumeDim; ++i) {
    CHECK(get<::Tags::Mean<VectorTag<VolumeDim>>>(packaged_data.means).get(i) ==
          mean_value(input_vector.get(i), mesh));
  }
}

void test_package_data_1d() noexcept {
  INFO("Test WENO package_data in 1D");
  const Mesh<1> mesh(4, Spectral::Basis::Legendre,
                     Spectral::Quadrature::GaussLobatto);

  const OrientationMap<1> orientation_aligned{};
  test_package_data_work(mesh, orientation_aligned);

  const OrientationMap<1> orientation_flipped(
      std::array<Direction<1>, 1>{{Direction<1>::lower_xi()}});
  test_package_data_work(mesh, orientation_flipped);
}

void test_package_data_2d() noexcept {
  INFO("Test WENO package_data in 2D");
  const Mesh<2> mesh({{4, 6}}, Spectral::Basis::Legendre,
                     Spectral::Quadrature::GaussLobatto);

  const OrientationMap<2> orientation_aligned{};
  test_package_data_work(mesh, orientation_aligned);

  const OrientationMap<2> orientation_rotated(std::array<Direction<2>, 2>{
      {Direction<2>::lower_eta(), Direction<2>::upper_xi()}});
  test_package_data_work(mesh, orientation_rotated);
}

void test_package_data_3d() noexcept {
  INFO("Test WENO package_data in 3D");
  const Mesh<3> mesh({{4, 6, 3}}, Spectral::Basis::Legendre,
                     Spectral::Quadrature::GaussLobatto);

  const OrientationMap<3> orientation_aligned{};
  test_package_data_work(mesh, orientation_aligned);

  const OrientationMap<3> orientation_rotated(std::array<Direction<3>, 3>{
      {Direction<3>::lower_zeta(), Direction<3>::upper_xi(),
       Direction<3>::lower_eta()}});
  test_package_data_work(mesh, orientation_rotated);
}

template <size_t VolumeDim>
Variables<tmpl::list<ScalarTag, VectorTag<VolumeDim>>> make_local_vars(
    const Mesh<VolumeDim>& mesh) noexcept;

template <>
Variables<tmpl::list<ScalarTag, VectorTag<1>>> make_local_vars(
    const Mesh<1>& mesh) noexcept {
  const auto logical_coords = logical_coordinates(mesh);
  const auto& x = get<0>(logical_coords);
  Variables<tmpl::list<ScalarTag, VectorTag<1>>> vars(
      mesh.number_of_grid_points());
  get(get<ScalarTag>(vars)) = 1.0 - 2.0 * x + square(x);
  get<0>(get<VectorTag<1>>(vars)) = 0.4 * x - 0.1 * square(x);
  return vars;
}

template <>
Variables<tmpl::list<ScalarTag, VectorTag<2>>> make_local_vars(
    const Mesh<2>& mesh) noexcept {
  const auto logical_coords = logical_coordinates(mesh);
  const auto& x = get<0>(logical_coords);
  const auto& y = get<1>(logical_coords);
  Variables<tmpl::list<ScalarTag, VectorTag<2>>> vars(
      mesh.number_of_grid_points());
  get(get<ScalarTag>(vars)) = x + y - 0.5 * square(x) + 0.5 * square(y);
  get<0>(get<VectorTag<2>>(vars)) = x + 2.5 * y;
  get<1>(get<VectorTag<2>>(vars)) =
      0.1 + 0.2 * x - 0.4 * y + 0.3 * square(x) * square(y);
  return vars;
}

template <>
Variables<tmpl::list<ScalarTag, VectorTag<3>>> make_local_vars(
    const Mesh<3>& mesh) noexcept {
  const auto logical_coords = logical_coordinates(mesh);
  const auto& x = get<0>(logical_coords);
  const auto& y = get<1>(logical_coords);
  const auto& z = get<2>(logical_coords);
  Variables<tmpl::list<ScalarTag, VectorTag<3>>> vars(
      mesh.number_of_grid_points());
  get(get<ScalarTag>(vars)) = x + y - 0.2 * z - y * z + x * y * square(z);
  get<0>(get<VectorTag<3>>(vars)) = 0.4 * x * y * z + square(z);
  get<1>(get<VectorTag<3>>(vars)) = 0.01 * x - 0.01 * y + z;
  get<2>(get<VectorTag<3>>(vars)) = x + 0.2 * y + cube(z);
  return vars;
}

template <size_t VolumeDim>
using VariablesMap = std::unordered_map<
    std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>,
    Variables<tmpl::list<ScalarTag, VectorTag<VolumeDim>>>,
    boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>;

template <size_t VolumeDim>
std::unordered_map<
    std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>,
    typename Limiters::Weno<
        VolumeDim, tmpl::list<ScalarTag, VectorTag<VolumeDim>>>::PackagedData,
    boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>
make_neighbor_data_from_neighbor_vars(
    const Mesh<VolumeDim>& mesh, const Element<VolumeDim>& element,
    const std::array<double, VolumeDim>& element_size,
    const VariablesMap<VolumeDim>& neighbor_vars) noexcept {
  const auto make_tuple_of_means =
      [&mesh](const Variables<tmpl::list<ScalarTag, VectorTag<VolumeDim>>>&
                  vars_to_average) noexcept {
        tuples::TaggedTuple<::Tags::Mean<ScalarTag>,
                            ::Tags::Mean<VectorTag<VolumeDim>>>
            result;
        get(get<::Tags::Mean<ScalarTag>>(result)) =
            mean_value(get(get<ScalarTag>(vars_to_average)), mesh);
        for (size_t d = 0; d < VolumeDim; ++d) {
          get<::Tags::Mean<VectorTag<VolumeDim>>>(result).get(d) = mean_value(
              get<VectorTag<VolumeDim>>(vars_to_average).get(d), mesh);
        }
        return result;
      };

  std::unordered_map<
      std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>,
      typename Limiters::Weno<
          VolumeDim, tmpl::list<ScalarTag, VectorTag<VolumeDim>>>::PackagedData,
      boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>
      neighbor_data{};

  for (const auto& neighbor : element.neighbors()) {
    const auto dir = neighbor.first;
    const auto id = *(neighbor.second.cbegin());
    const auto dir_and_id = std::make_pair(dir, id);
    neighbor_data[dir_and_id].volume_data = neighbor_vars.at(dir_and_id);
    neighbor_data[dir_and_id].means =
        make_tuple_of_means(neighbor_vars.at(dir_and_id));
    neighbor_data[dir_and_id].mesh = mesh;
    neighbor_data[dir_and_id].element_size = element_size;
  }

  return neighbor_data;
}

template <size_t VolumeDim>
std::unordered_map<
    std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>,
    typename Limiters::Weno<
        VolumeDim, tmpl::list<ScalarTag, VectorTag<VolumeDim>>>::PackagedData,
    boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>
make_neighbor_data(const Mesh<VolumeDim>& mesh,
                   const Element<VolumeDim>& element,
                   const std::array<double, VolumeDim>& element_size) noexcept;

template <>
std::unordered_map<std::pair<Direction<1>, ElementId<1>>,
                   typename Limiters::Weno<
                       1, tmpl::list<ScalarTag, VectorTag<1>>>::PackagedData,
                   boost::hash<std::pair<Direction<1>, ElementId<1>>>>
make_neighbor_data(const Mesh<1>& mesh, const Element<1>& element,
                   const std::array<double, 1>& element_size) noexcept {
  const auto make_lower_xi_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<1>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = 16.4;
    get<0>(get<VectorTag<1>>(vars)) = 1.2;
    return vars;
  };
  const auto make_upper_xi_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<1>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = -9.5;
    get<0>(get<VectorTag<1>>(vars)) = 4.2;
    return vars;
  };

  VariablesMap<1> neighbor_vars{};
  const auto lower_xi =
      std::make_pair(Direction<1>::lower_xi(), ElementId<1>(1));
  const auto upper_xi =
      std::make_pair(Direction<1>::upper_xi(), ElementId<1>(2));
  neighbor_vars[lower_xi] = make_lower_xi_vars();
  neighbor_vars[upper_xi] = make_upper_xi_vars();

  return make_neighbor_data_from_neighbor_vars(mesh, element, element_size,
                                               neighbor_vars);
}

template <>
std::unordered_map<std::pair<Direction<2>, ElementId<2>>,
                   typename Limiters::Weno<
                       2, tmpl::list<ScalarTag, VectorTag<2>>>::PackagedData,
                   boost::hash<std::pair<Direction<2>, ElementId<2>>>>
make_neighbor_data(const Mesh<2>& mesh, const Element<2>& element,
                   const std::array<double, 2>& element_size) noexcept {
  const auto make_lower_xi_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<2>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = -3.2;
    get<0>(get<VectorTag<2>>(vars)) = -4.2;
    get<1>(get<VectorTag<2>>(vars)) = -1.7;
    return vars;
  };
  const auto make_upper_xi_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<2>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = 2.8;
    get<0>(get<VectorTag<2>>(vars)) = 3.1;
    get<1>(get<VectorTag<2>>(vars)) = 2.3;
    return vars;
  };
  const auto make_lower_eta_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<2>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = -2.7;
    get<0>(get<VectorTag<2>>(vars)) = 1.4;
    get<1>(get<VectorTag<2>>(vars)) = 4.5;
    return vars;
  };
  const auto make_upper_eta_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<2>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = 3.1;
    get<0>(get<VectorTag<2>>(vars)) = 2.4;
    get<1>(get<VectorTag<2>>(vars)) = -1.2;
    return vars;
  };

  VariablesMap<2> neighbor_vars{};
  const auto lower_xi =
      std::make_pair(Direction<2>::lower_xi(), ElementId<2>(1));
  const auto upper_xi =
      std::make_pair(Direction<2>::upper_xi(), ElementId<2>(2));
  const auto lower_eta =
      std::make_pair(Direction<2>::lower_eta(), ElementId<2>(3));
  const auto upper_eta =
      std::make_pair(Direction<2>::upper_eta(), ElementId<2>(4));
  neighbor_vars[lower_xi] = make_lower_xi_vars();
  neighbor_vars[upper_xi] = make_upper_xi_vars();
  neighbor_vars[lower_eta] = make_lower_eta_vars();
  neighbor_vars[upper_eta] = make_upper_eta_vars();

  return make_neighbor_data_from_neighbor_vars(mesh, element, element_size,
                                               neighbor_vars);
}

template <>
std::unordered_map<std::pair<Direction<3>, ElementId<3>>,
                   typename Limiters::Weno<
                       3, tmpl::list<ScalarTag, VectorTag<3>>>::PackagedData,
                   boost::hash<std::pair<Direction<3>, ElementId<3>>>>
make_neighbor_data(const Mesh<3>& mesh, const Element<3>& element,
                   const std::array<double, 3>& element_size) noexcept {
  const auto make_lower_xi_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<3>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = -3.2;
    get<0>(get<VectorTag<3>>(vars)) = 0.;
    get<1>(get<VectorTag<3>>(vars)) = -0.1;
    get<2>(get<VectorTag<3>>(vars)) = -2.1;
    return vars;
  };
  const auto make_upper_xi_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<3>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = 3.8;
    get<0>(get<VectorTag<3>>(vars)) = 0.;
    get<1>(get<VectorTag<3>>(vars)) = 0.1;
    get<2>(get<VectorTag<3>>(vars)) = 2.1;
    return vars;
  };
  const auto make_lower_eta_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<3>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = -2.5;
    get<0>(get<VectorTag<3>>(vars)) = 0.;
    get<1>(get<VectorTag<3>>(vars)) = 0.1;
    get<2>(get<VectorTag<3>>(vars)) = -0.4;
    return vars;
  };
  const auto make_upper_eta_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<3>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = 2.3;
    get<0>(get<VectorTag<3>>(vars)) = 0.;
    get<1>(get<VectorTag<3>>(vars)) = -0.1;
    get<2>(get<VectorTag<3>>(vars)) = 1.1;
    return vars;
  };
  const auto make_lower_zeta_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<3>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = 0.41;
    get<0>(get<VectorTag<3>>(vars)) = 0.;
    get<1>(get<VectorTag<3>>(vars)) = -2.3;
    ;
    get<2>(get<VectorTag<3>>(vars)) = -8.2;
    return vars;
  };
  const auto make_upper_zeta_vars = [&mesh]() noexcept {
    Variables<tmpl::list<ScalarTag, VectorTag<3>>> vars(
        mesh.number_of_grid_points());
    get(get<ScalarTag>(vars)) = -0.42;
    get<0>(get<VectorTag<3>>(vars)) = 0.;
    get<1>(get<VectorTag<3>>(vars)) = 2.3;
    get<2>(get<VectorTag<3>>(vars)) = 9.;
    return vars;
  };

  VariablesMap<3> neighbor_vars{};
  const auto lower_xi =
      std::make_pair(Direction<3>::lower_xi(), ElementId<3>(1));
  const auto upper_xi =
      std::make_pair(Direction<3>::upper_xi(), ElementId<3>(2));
  const auto lower_eta =
      std::make_pair(Direction<3>::lower_eta(), ElementId<3>(3));
  const auto upper_eta =
      std::make_pair(Direction<3>::upper_eta(), ElementId<3>(4));
  const auto lower_zeta =
      std::make_pair(Direction<3>::lower_zeta(), ElementId<3>(5));
  const auto upper_zeta =
      std::make_pair(Direction<3>::upper_zeta(), ElementId<3>(6));
  neighbor_vars[lower_xi] = make_lower_xi_vars();
  neighbor_vars[upper_xi] = make_upper_xi_vars();
  neighbor_vars[lower_eta] = make_lower_eta_vars();
  neighbor_vars[upper_eta] = make_upper_eta_vars();
  neighbor_vars[lower_zeta] = make_lower_zeta_vars();
  neighbor_vars[upper_zeta] = make_upper_zeta_vars();

  return make_neighbor_data_from_neighbor_vars(mesh, element, element_size,
                                               neighbor_vars);
}

template <size_t VolumeDim>
void test_simple_weno(const std::array<size_t, VolumeDim>& extents) noexcept {
  INFO("Test simple WENO limiter");
  CAPTURE(VolumeDim);
  const auto mesh = Mesh<VolumeDim>(extents, Spectral::Basis::Legendre,
                                    Spectral::Quadrature::Gauss);
  const auto element = TestHelpers::Limiters::make_element<VolumeDim>();
  const auto element_size = make_array<VolumeDim>(1.2);

  // Make local + neighbor data where the vector x-component triggers limiting
  const auto local_vars = make_local_vars(mesh);
  const auto neighbor_data = make_neighbor_data(mesh, element, element_size);

  const double neighbor_linear_weight = 0.001;
  const double tvb_constant = 0.0;
  using Weno =
      Limiters::Weno<VolumeDim, tmpl::list<ScalarTag, VectorTag<VolumeDim>>>;
  const Weno simple_weno(Limiters::WenoType::SimpleWeno, neighbor_linear_weight,
                         tvb_constant);

  auto scalar = get<ScalarTag>(local_vars);
  auto vector = get<VectorTag<VolumeDim>>(local_vars);
  const bool activated =
      simple_weno(make_not_null(&scalar), make_not_null(&vector), mesh, element,
                  element_size, neighbor_data);

  // Because simple WENO acts on each tensor component independently, only the
  // vector x-component should be modified by the limiter
  const auto& expected_scalar = get<ScalarTag>(local_vars);
  auto expected_vector = get<VectorTag<VolumeDim>>(local_vars);
  std::unordered_map<
      std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>,
      intrp::RegularGrid<VolumeDim>,
      boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>
      interpolator_buffer{};
  std::unordered_map<
      std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>, DataVector,
      boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>
      modified_neighbor_solution_buffer{};
  for (const auto& neighbor_and_data : neighbor_data) {
    const auto& neighbor = neighbor_and_data.first;
    modified_neighbor_solution_buffer.insert(
        make_pair(neighbor, DataVector(mesh.number_of_grid_points())));
  }
  Limiters::Weno_detail::simple_weno_impl<VectorTag<VolumeDim>>(
      make_not_null(&interpolator_buffer),
      make_not_null(&modified_neighbor_solution_buffer),
      make_not_null(&expected_vector), neighbor_linear_weight,
      0,  // the x-component
      mesh, element, neighbor_data);

  CHECK(activated);
  CHECK_ITERABLE_CUSTOM_APPROX(expected_scalar, scalar, approx);
  CHECK_ITERABLE_CUSTOM_APPROX(expected_vector, vector, approx);

  // Now call the limiter again, but this time use a non-zero TVB constant such
  // that the TCI says limiting isn't needed
  const double tvb_constant_larger = 2.0;
  const Weno simple_weno_tvb(Limiters::WenoType::SimpleWeno,
                             neighbor_linear_weight, tvb_constant_larger);

  scalar = get<ScalarTag>(local_vars);
  vector = get<VectorTag<VolumeDim>>(local_vars);
  const bool activated_tvb =
      simple_weno_tvb(make_not_null(&scalar), make_not_null(&vector), mesh,
                      element, element_size, neighbor_data);

  // expected_scalar is already set to local_vars
  expected_vector = get<VectorTag<VolumeDim>>(local_vars);

  CHECK_FALSE(activated_tvb);
  CHECK_ITERABLE_APPROX(expected_scalar, scalar);
  CHECK_ITERABLE_APPROX(expected_vector, vector);
}

template <size_t VolumeDim>
void test_hweno(const std::array<size_t, VolumeDim>& extents) noexcept {
  INFO("Test HWENO limiter");
  CAPTURE(VolumeDim);
  const auto mesh = Mesh<VolumeDim>(extents, Spectral::Basis::Legendre,
                                    Spectral::Quadrature::GaussLobatto);
  const auto element = TestHelpers::Limiters::make_element<VolumeDim>();
  const auto element_size = make_array<VolumeDim>(1.2);

  // Make local + neighbor data where the vector x-component triggers limiting
  const auto local_vars = make_local_vars(mesh);
  const auto neighbor_data = make_neighbor_data(mesh, element, element_size);

  const double neighbor_linear_weight = 0.001;
  const double tvb_constant = 0.0;
  using Weno =
      Limiters::Weno<VolumeDim, tmpl::list<ScalarTag, VectorTag<VolumeDim>>>;
  const Weno hweno(Limiters::WenoType::Hweno, neighbor_linear_weight,
                   tvb_constant);

  auto scalar = get<ScalarTag>(local_vars);
  auto vector = get<VectorTag<VolumeDim>>(local_vars);
  const bool activated = hweno(make_not_null(&scalar), make_not_null(&vector),
                               mesh, element, element_size, neighbor_data);

  // Because HWENO acts on the whole solution, the scalar and all vector
  // components should be modified by the limiter
  auto expected_scalar = get<ScalarTag>(local_vars);
  auto expected_vector = get<VectorTag<VolumeDim>>(local_vars);
  std::unordered_map<
      std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>, DataVector,
      boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>
      modified_neighbor_solution_buffer{};
  for (const auto& neighbor_and_data : neighbor_data) {
    const auto& neighbor = neighbor_and_data.first;
    modified_neighbor_solution_buffer.insert(
        make_pair(neighbor, DataVector(mesh.number_of_grid_points())));
  }
  Limiters::Weno_detail::hweno_impl<ScalarTag>(
      make_not_null(&modified_neighbor_solution_buffer),
      make_not_null(&expected_scalar), neighbor_linear_weight, mesh, element,
      neighbor_data);
  Limiters::Weno_detail::hweno_impl<VectorTag<VolumeDim>>(
      make_not_null(&modified_neighbor_solution_buffer),
      make_not_null(&expected_vector), neighbor_linear_weight, mesh, element,
      neighbor_data);

  CHECK(activated);
  CHECK_ITERABLE_CUSTOM_APPROX(expected_scalar, scalar, approx);
  CHECK_ITERABLE_CUSTOM_APPROX(expected_vector, vector, approx);

  // Now call the limiter again, but this time use a non-zero TVB constant such
  // that the TCI says limiting isn't needed
  const double tvb_constant_larger = 2.0;
  const Weno hweno_tvb(Limiters::WenoType::Hweno, neighbor_linear_weight,
                       tvb_constant_larger);

  scalar = get<ScalarTag>(local_vars);
  vector = get<VectorTag<VolumeDim>>(local_vars);
  const bool activated_tvb =
      hweno_tvb(make_not_null(&scalar), make_not_null(&vector), mesh, element,
                element_size, neighbor_data);

  expected_scalar = get<ScalarTag>(local_vars);
  expected_vector = get<VectorTag<VolumeDim>>(local_vars);

  CHECK_FALSE(activated_tvb);
  CHECK_ITERABLE_APPROX(expected_scalar, scalar);
  CHECK_ITERABLE_APPROX(expected_vector, vector);
}

}  // namespace

SPECTRE_TEST_CASE("Unit.Evolution.DG.Limiters.Weno", "[Limiters][Unit]") {
  test_weno_option_parsing();
  test_weno_serialization();

  test_package_data_1d();
  test_package_data_2d();
  test_package_data_3d();

  // The simple WENO reconstruction is tested in Test_SimpleWenoImpl.cpp.
  // There, both LGL and LG points are used. Here we only use Gauss points.
  // Here we test that
  // - the TCI correctly acts component-by-component
  // - the limiter is indeed calling `simple_weno_impl`
  test_simple_weno<1>({{3}});
  test_simple_weno<2>({{3, 4}});
  test_simple_weno<3>({{3, 4, 5}});

  // The HWENO reconstruction is tested in Test_HwenoImpl.cpp.
  // There, both LGL and LG points are used. Here we only use LGL points.
  // Here we test that
  // - the TCI correctly triggers limiting on all tensors at once
  // - the limiter is indeed calling `hweno_impl`
  test_hweno<1>({{3}});
  test_hweno<2>({{3, 4}});
  test_hweno<3>({{3, 4, 5}});
}


#include <sstream>
#include <boost/timer.hpp>
#include "WaveTable.hpp"

int main(int argc, char **argv)
{
    if(argc != 2) {
        std::cout << "usage: a.out <Table Entry Size (which should be power of 2)>" << std::endl;
        return 1;
    }

    int tmp_num_entry = 0;
    std::stringstream ss;
    ss << argv[1];
    ss >> tmp_num_entry;

    hwm::WaveTable table;
    try {
        table = hwm::WaveTable(tmp_num_entry);
    } catch(std::exception &e) {
        std::cout << e.what() << std::endl;
        return 1;
    }

    //! Output WaveTable Information.
    table.CheckWaveTableInfo(std::cout);

    //! Measure calculation speed.
    boost::timer tm;
    int kMaxRepeatCount = 100;
    int kAnglePrecision = 100000;
    double sec1 = 0;
    double sec2 = 0;

    double tmp1 = 0;
    double omega = M_PI / 2 / (double)kAnglePrecision;

    std::stringstream dummy_output;

    tm.restart();
    for(int repeat = 0; repeat < kMaxRepeatCount; ++repeat) {
        for(int i = 0; i < kAnglePrecision; ++i) {
            tmp1 = tmp1 + sin(i * omega);
        }
    }
    sec1 = tm.elapsed();
    dummy_output << tmp1;

    tmp1 = 0;

    tm.restart();
    for(int repeat = 0; repeat < kMaxRepeatCount; ++repeat) {
        for(int i = 0; i < kAnglePrecision; ++i) {
            tmp1 = tmp1 + table.fast_sin(i * omega);
        }
    }
    sec2 = tm.elapsed();

    dummy_output << tmp1;

    std::string volatile dummy_string(dummy_output.str());

    std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
    std::cout << "elapsed sin(x):      " << std::setw(16) << std::setprecision(10) << sec1 << " sec" << std::endl;
    std::cout << "elapsed fast_sin(x): " << std::setw(16) << std::setprecision(10) << sec2 << " sec" << std::endl;
    std::cout << "average time of sin(x):      " << std::setw(10) << std::setprecision(5) << (sec1 / (kMaxRepeatCount * kAnglePrecision) * 1000000000.0) << " nanosec" << std::endl;
    std::cout << "average time of fast_sin(x): " << std::setw(10) << std::setprecision(5) << (sec2 / (kMaxRepeatCount * kAnglePrecision) * 1000000000.0) << " nanosec" << std::endl;
    std::cout << "time of fast_sin(x) / time of sin(x): " << std::setw(6) << std::setprecision(4) << (sec2 / sec1 * 100) << "%" << std::endl;
}


////////////////////////////////////////////////////////////////////////////////
// cursor.cc
////////////////////////////////////////////////////////////////////////////////
/*! @file
//      Writes an ASCII MSH field to stdout with "cursor" geometry at the
//      specified points.
*/
//  Author:  Julian Panetta (jpanetta), julian.panetta@gmail.com
//  Company:  New York University
//  Created:  06/01/2016 11:54:42
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <cstdlib>

#include <boost/algorithm/string.hpp>
#include <MeshFEM/filters/gen_cursor.hh>

#include <MeshFEM/MeshIO.hh>

double CURSOR_RADIUS = 1;

using namespace std;
using namespace MeshIO;

[[noreturn]] void usage() {
    cerr << "usage: cursor \"x1 y1 z1\" ..." << endl;
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
    if (argc < 2) usage();
    std::vector<IOVertex> pts;

    for (int i = 1; i < argc; ++i) {
        string ptString(argv[i]);
        boost::trim(ptString);
        std::vector<string> componentStrings;
        boost::split(componentStrings, ptString, boost::is_any_of("\t "),
                     boost::token_compress_on);
        if (componentStrings.size() == 2)
            pts.emplace_back(stod(componentStrings[0]), stod(componentStrings[1]));
        else if (componentStrings.size() == 3)
            pts.emplace_back(stod(componentStrings[0]), stod(componentStrings[1]), stod(componentStrings[2]));
        else throw std::runtime_error("Invalid point specifier: " + ptString);
    }

    // Create crosshair cursor geometry
    std::vector<IOVertex> vertices;
    std::vector<IOElement> elements;
    for (const auto &p : pts)
        gen_cursor(CURSOR_RADIUS, p, vertices, elements);

    MeshIO_MSH io;
    io.setBinary(false);
    io.save(std::cout, vertices, elements, MESH_LINE);

    return 0;
}


/**
 * @file main.cpp
 * @date 2020-06-10
 *
 * Defines a command line program that can be used
 * to run the BanditPAM KMedoids algorithm.
 *
 * Usage (from home repo directory):
 * ./src/build/BanditPAM -f [path/to/input] -k [number of clusters]
 */

#include "kmedoids_algorithm.hpp"
#include "log_helper.hpp"
#include "kmedoids_pywrapper.hpp"

#include <armadillo>
#include <chrono>
#include <fstream>
#include <unistd.h>
#include <exception>
#include <regex>
#include <filesystem>

int main(int argc, char* argv[]) {
    std::string input_name;
    std::string log_file_name = "KMedoidsLogfile";
    size_t k;
    int opt;
    int prev_ind;
    size_t verbosity = 0;
    size_t max_iter = 1000;
    size_t buildConfidence = 1000;
    size_t swapConfidence = 10000;
    std::string loss = "2";
    bool f_flag = false;
    bool k_flag = false;
    const int ARGUMENT_ERROR_CODE = 1;

    while (prev_ind = optind, (opt = getopt(argc, argv, "f:l:k:v:s:")) != -1) {

        if ( optind == prev_ind + 2 && *optarg == '-' ) {
        opt = ':';
        --optind;
        }

        switch (opt) {
            // path to the data file to be read in
            case 'f':
                input_name = optarg;
                f_flag = true;
                break;
            // name of the output log file
            case 's':
                log_file_name = optarg;
                break;
            // number of clusters to create
            case 'k':
                k = std::stoi(optarg);
                k_flag = true;
                break;
            // type of loss/distance function to use
            case 'l':
                loss = optarg;
                break;
            // set the verbosity of the algorithm
            case 'v':
                verbosity = std::stoi(optarg);
                break;
            case ':':
                printf("option needs a value\n");
                return ARGUMENT_ERROR_CODE;
            case '?':
                printf("unknown option: %c\n", optopt);
                return ARGUMENT_ERROR_CODE;
        }
    }

    try {
      if (!f_flag) {
        throw std::invalid_argument("error: Must specify input file via -f flag");
      } else if (!k_flag) {
        throw std::invalid_argument("error: Must specify number of clusters via -k flag");
      } else if (!std::filesystem::exists(input_name)) {
        throw std::invalid_argument("error: The file does not exist");
      }
    } catch (std::invalid_argument& e) {
      std::cout << e.what() << std::endl;
      return ARGUMENT_ERROR_CODE;
    }

    arma::mat data;
    data.load(input_name);
    arma::uword n = data.n_cols;
    arma::uword d = data.n_rows;

    km::KMedoids kmed(k, "BanditPAM", verbosity, max_iter, buildConfidence, swapConfidence, log_file_name);
    kmed.fit(data, loss);

    if (verbosity > 0) {
      arma::rowvec meds = kmed.getMedoidsFinal();
      std::cout << "Medoids: ";
      for (size_t i = 0; i < meds.n_cols; i++) {
        if (i == (meds.n_cols - 1)) {
          std::cout << meds(i) << std::endl;
        } else {
          std::cout << meds(i) << ',';
        }
      }
    }
}


// __BEGIN_LICENSE__
// Copyright (C) 2006-2011 United States Government as represented by
// the Administrator of the National Aeronautics and Space Administration.
// All Rights Reserved.
// __END_LICENSE__

// std header
#include <stdlib.h>
#include <iostream>
#include <vector>

// Boost
#include <boost/program_options.hpp>
#include <boost/filesystem/operations.hpp>
namespace po = boost::program_options;
namespace fs = boost::filesystem;

// Vision Workbench
#include <vw/Math.h>
#include <vw/InterestPoint/InterestData.h>
#include <vw/InterestPoint/Matcher.h>
using namespace vw;

// Stereo Pipeline
#include <asp/ControlNetTK/Equalization.h>
#include <asp/Core/Macros.h>
#include <asp/Core/Common.h>
using namespace asp;

struct Options : public asp::BaseOptions {
  std::vector<std::string> match_files;
  size_t max_points, min_points;
};

void handle_arguments( int argc, char *argv[], Options& opt ) {
  po::options_description general_options("");
  general_options.add_options()
    ("max-pts,m",po::value(&opt.max_points)->default_value(100),"Max points a pair can have. If it execeeds we trim.")
    ("min-pts,n",po::value(&opt.min_points)->default_value(10),"Minimum points a pair is required to have. Delete if fails this.");
  general_options.add( asp::BaseOptionsDescription(opt) );

  po::options_description positional("");
  positional.add_options()
    ("input-files", po::value(&opt.match_files));

  po::positional_options_description positional_desc;
  positional_desc.add("input-files", -1);

  std::string usage("[options] <match-files> ...");
  po::variables_map vm =
    asp::check_command_line( argc, argv, opt, general_options,
                             positional, positional_desc, usage );

  if ( opt.match_files.empty() )
    vw_throw( ArgumentErr() << "Must specify at least one input file!\n\n" << usage );
}

int main( int argc, char* argv[] ) {

  Options opt;
  try {
    handle_arguments( argc, argv, opt );
    BOOST_FOREACH( std::string const& matchfile, opt.match_files ) {
      vw_out() << "Loading: " << matchfile << "\n";

      std::vector<ip::InterestPoint> ip1, ip2;
      ip::read_binary_match_file( matchfile, ip1, ip2 );
      vw_out() << "\t> Found " << ip1.size() << " matches.\n";

      if ( ip1.size() < opt.min_points ) {
        vw_out() << "Match failed to have enough pairs.\n";
        fs::remove( matchfile );
        continue;
      }

      vw_out() << "Performing equalization\n";
      cnettk::equalization( ip1, ip2, opt.max_points );

      // Finally write back out the reduced match file
      ip::write_binary_match_file( matchfile, ip1, ip2 );
      vw_out() << "Wrote back: " << matchfile << "\n";
    }
  } ASP_STANDARD_CATCHES;

  return 0;
}


//Link to Boost
#define BOOST_TEST_DYN_LINK

//VERY IMPORTANT - include this last
//#include <boost/test/included/unit_test.hpp>
#include <boost/test/unit_test.hpp>

#include "test.h"
#include "../StateGenerator.h"
#include "../PowerSeries.h"
#include "../HamOperator.h"
#include "../ScriptGenerator.h"
#include "../Spectrum.h"

// test suite
BOOST_FIXTURE_TEST_SUITE(Demo_suite, SimpleTestFixture, * utf::label("Demo"))

BOOST_AUTO_TEST_CASE(GenerateStatesDemo, * utf::disabled())
{
    StateGenerator generator;
	for (int i = 1; i <= MAX_BIT_TO_COUNT; i++)
	{
	    cout << i << "\t" << generator.SingleStateNumber(i) << "\t" << generator.StateNumber(i) << endl;
	}

	generator.GenerateAllStates();
	generator.InitStateCollection(StateCollection::Inst());
	for (int n = 4; n < 5; n++) {
	    cout << "n=" << n << endl;
	    for (int i = 0; i < generator.SingleStateNumber(n); i++) {
	        cout << i << ' ' << generator.SingleTraceState(n, i) << endl;
	    }
	    cout << "=======" << endl;
	    for (int i = 0; i < generator.StateNumber(n); i++) {
	        cout << i << ' ' << generator.State(n, i) << endl;
	    }
	}
}

BOOST_AUTO_TEST_CASE(HamOperatorDemo, * utf::disabled())
{
    DilatationOperator ham;
    int L = 10;
    int M = 4;
    const TraceState& state = *StateCollection::Inst()->Begin(L, M);
//    TraceState state;
//    state.AddTrace(SingleTrace("ZX"));
//    state.AddTrace(SingleTrace("ZX"));
    MixState ms;
    cout << "state=" << state << endl;
    ham.ApplyOn(state, ms);
    cout << "ms=" << ms << endl;
}

BOOST_AUTO_TEST_CASE(HamMatrixDemo, * utf::disabled())
{
    DilatationOperator ham;
    int input[1][2] = {{4,2}};

    for (int n = 0; n < 1; n++) {
        int L = input[n][0];
        int M = input[n][1];
        vector<vector<NExpansionSeries> > mat;
        ham.Matrix(L, M, mat);
        for (int i = 0; i < mat.size(); i++) {
            for (int j = 0; j < mat.size(); j++) {
                if (j) cout << '\t';
                cout << mat[i][j];
            }
            cout << endl;
        }
    }
}

BOOST_AUTO_TEST_CASE(ScriptGeneratorDemo, * utf::disabled())
{
    string rootFolder = "/home/gaolichen/gitroot/mywork/chaos/build";
    ScriptGenerator gen(rootFolder);
//    int input[9][2] = {{4,2}, {6,2}, {8,2}, {8,4}, {10,2}, {10,4}, {12,2}, {12, 4}, {12, 6}};
    int input[3][2] = {{16, 2}, {16, 3}, {16, 4}};

    for (int n = 0; n < 3; n++) {
        int L = input[n][0];
        int M = input[n][1];
        gen.SaveHamMatrix(L, M);
        gen.SaveStates(L, M);
    }
}

BOOST_AUTO_TEST_CASE(SingleTraceDemo, * utf::disabled())
{
    SingleTrace single("XXZZXZ");
    SingleTrace a, b, c;
    single.Split(3, 1, a, b, c);
//    single.Split(2, a, b);
    cout << "single=" << single << endl;
    cout << "a=" << a <<", b=" << b << ", c=" << c << endl;
}

BOOST_AUTO_TEST_CASE(SpectrumDemo, * utf::disabled())
{
    Spectrum spectrum;
    int N = 10;
    f_type beta = 0.9;
    int L = 4;
    int M = 2;
    std::vector<var_t> eigens;
    spectrum.Eigenvalues(L, M, 10, 0.9, eigens);
    std::cout << "eigens=" << eigens << std::endl;

    int zeros = 0;
    for (int i = 0; i < eigens.size(); i++) {
        if (std::abs(eigens[i]) < EPS) zeros++;
    }
    std::cout << "number of eigenvalues = " << eigens.size() << ", # of zero eigenvalues = " << zeros << std::endl;

}

BOOST_AUTO_TEST_SUITE_END()


#define BOOST_TEST_MODULE pcraster geo raster_space
#include <boost/test/unit_test.hpp>
#include <sstream>
#include "com_exception.h"
#include "geo_rasterspace.h"

struct Fixture
{

    Fixture()
    {
      using namespace geo;

      d_rs1 = new RasterSpace(11, 12, 13.0, 14.0, 15.0);
    }

    ~Fixture()
    {
      delete d_rs1;
    }

    geo::RasterSpace* d_rs1;

};

BOOST_FIXTURE_TEST_SUITE(raster_space, Fixture)

BOOST_AUTO_TEST_CASE(equality)
{
  using namespace geo;

  BOOST_CHECK(RasterSpace(1, 2, 3.0, 4.0, 5.0, geo::YIncrB2T, 6.0) ==
            RasterSpace(1, 2, 3.0, 4.0, 5.0, geo::YIncrB2T, 6.0));
}

BOOST_AUTO_TEST_CASE(nr_rows)
{
  using namespace geo;

  BOOST_CHECK(d_rs1->nrRows() == 11);
}

BOOST_AUTO_TEST_CASE(nr_cols)
{
  using namespace geo;

  BOOST_CHECK(d_rs1->nrCols() == 12);
}

BOOST_AUTO_TEST_CASE(nr_cells)
{
  using namespace geo;

  BOOST_CHECK(d_rs1->nrCells() == 11 * 12);
}

BOOST_AUTO_TEST_CASE(quadrant)
{
  using namespace geo;

  {
    RasterSpace space(3, 3, 1.0, 0.0, 0.0, geo::YIncrT2B);
    BOOST_CHECK(space.quadrant(0.0, 0.0) == NorthWest);
    BOOST_CHECK(space.quadrant(3.0, 0.0) == NorthWest);
    BOOST_CHECK(space.quadrant(3.0, 3.0) == NorthWest);
    BOOST_CHECK(space.quadrant(0.0, 3.0) == NorthWest);

    BOOST_CHECK(space.quadrant(1.1, 1.1) == NorthWest);
    BOOST_CHECK(space.quadrant(1.9, 1.1) == NorthEast);
    BOOST_CHECK(space.quadrant(1.9, 1.9) == SouthEast);
    BOOST_CHECK(space.quadrant(1.1, 1.9) == SouthWest);

    BOOST_CHECK(space.quadrant(1.5, 1.5) == SouthEast);
  }

  {
    RasterSpace space(3, 3, 1.0, 0.0, 0.0, geo::YIncrB2T);
    BOOST_CHECK(space.quadrant(0.0,  0.0) == NorthWest);
    BOOST_CHECK(space.quadrant(3.0,  0.0) == NorthWest);
    BOOST_CHECK(space.quadrant(3.0, -3.0) == NorthWest);
    BOOST_CHECK(space.quadrant(0.0, -3.0) == NorthWest);

    BOOST_CHECK(space.quadrant(1.1, -1.1) == NorthWest);
    BOOST_CHECK(space.quadrant(1.9, -1.1) == NorthEast);
    BOOST_CHECK(space.quadrant(1.9, -1.9) == SouthEast);
    BOOST_CHECK(space.quadrant(1.1, -1.9) == SouthWest);

    BOOST_CHECK(space.quadrant(1.5, -1.5) == SouthEast);
  }
}

BOOST_AUTO_TEST_CASE(io)
{
  using namespace geo;

  bool stuffReadDoesntEqualStuffWritten;

  try {

    stuffReadDoesntEqualStuffWritten = false;

    std::stringstream s;

    RasterSpace rs1(11, 12, 13.0, 14.0, 15.0, geo::YIncrB2T);
    s << rs1;

    RasterSpace rs2;
    s >> rs2;

    BOOST_CHECK(rs1 == rs2);

  }
  catch(const com::BadStreamFormat& ) {
    stuffReadDoesntEqualStuffWritten = true;
  }

  BOOST_CHECK(!stuffReadDoesntEqualStuffWritten);

  try {

    stuffReadDoesntEqualStuffWritten = false;

    std::stringstream s;

    RasterSpace rs1(11, 12, 13.0, 14.0, 15.0, geo::YIncrB2T);
    s << "blabla" << rs1;

    RasterSpace rs2;
    s >> rs2;

  }
  catch(const com::BadStreamFormat& ) {
    stuffReadDoesntEqualStuffWritten = true;
  }

  BOOST_CHECK(stuffReadDoesntEqualStuffWritten);
}

BOOST_AUTO_TEST_SUITE_END()


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

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

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

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

 /*!

    @ingroup group-ieee
    Function object implementing maxnum capabilities

    Returns the maximum value, ignoring nans.

    @par Semantic:

    @code
    T r = maxnum(x,y);
    @endcode

    is similar to:

    @code
    T r = (is_nan(x) ? y : (isnan(y) ? x : max(x, y));
    @endcode

    @par Alias:
    @c fmax

    With the std_ decorator std_(maxnum)(x, y) calls the stdlibc++ function std::fmax.

    @par Decorators

    std_ for floating entries

    @see max, maxnummag,  maxmag

  **/
  const boost::dispatch::functor<tag::maxnum_> maxnum = {};
} }
#endif

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

#endif


// ysoftman
// boost chrono 사용하기
// 빌드시 boost_system boost_chrono 링크
// g++ -lboost_system -lboost_chrono chrono.cpp
#include <iostream>
#include <boost/chrono.hpp>

using namespace std;
using namespace boost::chrono;

int main()
{
    cout << "boost chrono test" << endl;

	// 경과시간 측정
	system_clock::time_point start = system_clock::now();
	float ff = 12.12;
	for (int i=0; i<1000000; i++)
	{
		ff *= ff;
	}
	system_clock::time_point end = system_clock::now();
	// 초 단위 측정
	seconds sdiff = duration_cast<seconds>(end - start);
	// 마이크로 초 단위 측정
	microseconds msdiff = duration_cast<milliseconds>(end - start);
	// 나노 초 단위 측정
	nanoseconds nsdiff = duration_cast<microseconds>(end - start);
	cout << "elapsed : " << sdiff << endl;
	cout << "elapsed : " << msdiff << endl;
	cout << "elapsed : " << nsdiff << endl;

    return 0;
}


#pragma once

#include <data_model.hpp>
#include <graph_model.hpp>

#include <vector>
#include <map>
#include <iostream>
#include <deque>
#include <sstream>
#include <stdexcept>
#include <iterator>

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/topological_sort.hpp>

namespace verilog {
  namespace graph {

    LogicValue propagate(LogicValue v, NegP np) {
      checkValid(v);

      return np == NegP::Positive ? v : !v;
    }

    void simulate(
        std::map<std::string, bool> & inputs,
        std::map<std::string, bool> & outputs, G_builder & b) {

      G & g = b.g;

      using namespace boost;

      std::deque<int> topo_order;
      boost::topological_sort(g.graph, std::front_inserter(topo_order));
      //

      // Initialize the constants
      g.graph[g.zero].value = LogicValue::Zero;
      g.graph[g.one].value = LogicValue::One;

      //std::cout << "// initialized constants\n";
      //write_graph(std::cout, b);

      // Initialize the inputs
      for (auto it : inputs) {
        g.graph[b.get_vertex(it.first)].value = fromBool(it.second);
      }

      //std::cout << "// initialized inputs\n";
      //write_graph(std::cout, b);

      // Propagate the values to the outputs
      for (int node : topo_order) {
        if(in_degree(node, g.graph) > 0) {
          auto p = in_edges(node, g.graph);

          LogicValue sum = LogicValue::One;

          for (auto e = p.first; e != p.second; ++e) {

            int s = source(*e, g.graph);
            LogicValue value = propagate(g.graph[s].value, g.graph[*e]);

            sum = sum && value;
          }
          g.graph[node].value = sum;

        }

        //std::cout << "// executed " << node << "\n";
        //write_graph(std::cout, b);
      }

      // output the outputs!
      for(std::string n : b.outputs) {
        outputs[n] = toBool(g.graph[b.get_vertex(n)].value);
      }
    }
  }
}


/**
 * @file
 * @copyright This code is licensed under the 3-clause BSD license.\n
 *            Copyright ETH Zurich, Laboratory of Physical Chemistry, Reiher Group.\n
 *            See LICENSE.txt for details.
 */

#include <Sparrow/Implementations/Nddo/Utils/IntegralsEvaluationUtils/GTOOverlapMatrixBlock.h>
#include <Utils/DataStructures/GtoExpansion.h>
#include <Utils/DataStructures/MatrixWithDerivatives.h>
#include <Utils/DataStructures/SlaterToGaussian.h>
#include <gmock/gmock.h>
#include <Eigen/Core>

namespace Scine {
namespace Sparrow {

using namespace testing;
using namespace nddo;

class AGTOOverlapMatrixBlock : public Test {
 public:
  GTOOverlapMatrixBlock<Utils::DerivativeOrder::One> overlapBlock;
  Utils::GtoExpansion gs1, gs2, gp1, gp2, gd1, gd2;
  Eigen::Vector3d arbitraryVector;

  void SetUp() override {
    arbitraryVector = Eigen::Vector3d(1.1, 2.0, 0.23);
    gs1 = Utils::SlaterToGaussian::getGTOExpansion(6, 1, 0);
    gs2 = Utils::SlaterToGaussian::getGTOExpansion(6, 4, 0, 1.42);
    gp1 = Utils::SlaterToGaussian::getGTOExpansion(6, 2, 1, 2.2);
    gp2 = Utils::SlaterToGaussian::getGTOExpansion(6, 3, 1);
    gd1 = Utils::SlaterToGaussian::getGTOExpansion(6, 5, 2, 0.92);
    gd2 = Utils::SlaterToGaussian::getGTOExpansion(6, 4, 2);
  }
};

TEST_F(AGTOOverlapMatrixBlock, ReturnsBlockOfCorrectSize) {
  ASSERT_THAT(overlapBlock.getMatrixBlock(gs1, gs2, arbitraryVector).size(), Eq(1));
  ASSERT_THAT(overlapBlock.getMatrixBlock(gd1, gd2, arbitraryVector).size(), Eq(25));
  ASSERT_THAT(overlapBlock.getMatrixBlock(gp1, gd2, arbitraryVector).rows(), Eq(3));
  ASSERT_THAT(overlapBlock.getMatrixBlock(gp1, gd2, arbitraryVector).cols(), Eq(5));
}

TEST_F(AGTOOverlapMatrixBlock, ReturnsIdentityForOrbitalsOnSameAtom) {
  Utils::MatrixWithDerivatives m;
  m.setOrder(Utils::DerivativeOrder::One);
  m.setDimension(gs1.nAOs(), gs1.nAOs());
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 1, 1) = overlapBlock.getMatrixBlock(gs1, gs1, Eigen::Vector3d(0, 0, 0));
  Eigen::MatrixXd blockSS = m.getMatrixXd();
  m.setDimension(gp1.nAOs(), gp1.nAOs());
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 3, 3) = overlapBlock.getMatrixBlock(gp1, gp1, Eigen::Vector3d(0, 0, 0));
  Eigen::MatrixXd blockPP = m.getMatrixXd();
  m.setDimension(gd1.nAOs(), gd1.nAOs());
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 5, 5) = overlapBlock.getMatrixBlock(gd1, gd1, Eigen::Vector3d(0, 0, 0));
  Eigen::MatrixXd blockDD = m.getMatrixXd();
  m.setDimension(gs1.nAOs(), gp1.nAOs());
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 1, 3) = overlapBlock.getMatrixBlock(gs1, gp1, Eigen::Vector3d(0, 0, 0));
  Eigen::MatrixXd blockSP = m.getMatrixXd();
  m.setDimension(gs1.nAOs(), gd1.nAOs());
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 1, 5) = overlapBlock.getMatrixBlock(gs1, gd1, Eigen::Vector3d(0, 0, 0));
  Eigen::MatrixXd blockSD = m.getMatrixXd();
  m.setDimension(gp1.nAOs(), gd1.nAOs());
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 3, 5) = overlapBlock.getMatrixBlock(gp1, gd1, Eigen::Vector3d(0, 0, 0));
  Eigen::MatrixXd blockPD = m.getMatrixXd();

  ASSERT_TRUE(blockSS.isApprox(Eigen::Matrix<double, 1, 1>::Identity(), 1e-7));
  ASSERT_TRUE(blockPP.isApprox(Eigen::Matrix<double, 3, 3>::Identity(), 1e-7));
  ASSERT_TRUE(blockDD.isApprox(Eigen::Matrix<double, 5, 5>::Identity(), 1e-7));
  ASSERT_TRUE(blockSP.isZero(1e-7));
  ASSERT_TRUE(blockSD.isZero(1e-7));
  ASSERT_TRUE(blockPD.isZero(1e-7));
}

TEST_F(AGTOOverlapMatrixBlock, ReturnsCorrectDerivativesForSOrbitals) {
  Utils::MatrixWithDerivatives m;
  m.setOrder(Utils::DerivativeOrder::One);
  m.setDimension(gs1.nAOs(), gs2.nAOs());
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 1, 1) = overlapBlock.getMatrixBlock(gs1, gs2, arbitraryVector);
  Eigen::MatrixXd S1 = m.getMatrixXd();
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 1, 1) =
      overlapBlock.getMatrixBlock(gs1, gs2, arbitraryVector + Eigen::Vector3d(0.01, 0, 0));
  Eigen::MatrixXd S2 = m.getMatrixXd();
  ASSERT_THAT(((S2 - S1)(0, 0)), DoubleNear(0.01 * m.v1(0, 0).derivatives().x(), 1e-5));
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 1, 1) =
      overlapBlock.getMatrixBlock(gs1, gs2, arbitraryVector + Eigen::Vector3d(0, 0.01, 0));
  S2 = m.getMatrixXd();
  ASSERT_THAT(((S2 - S1)(0, 0)), DoubleNear(0.01 * m.v1(0, 0).derivatives().y(), 1e-5));
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 1, 1) =
      overlapBlock.getMatrixBlock(gs1, gs2, arbitraryVector + Eigen::Vector3d(0, 0, 0.01));
  S2 = m.getMatrixXd();
  ASSERT_THAT(((S2 - S1)(0, 0)), DoubleNear(0.01 * m.v1(0, 0).derivatives().z(), 1e-5));
}

TEST_F(AGTOOverlapMatrixBlock, ReturnsCorrectDerivativesForPOrbitals) {
  Utils::MatrixWithDerivatives m;
  m.setOrder(Utils::DerivativeOrder::One);
  m.setDimension(gp1.nAOs(), gp2.nAOs());
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 3, 3) = overlapBlock.getMatrixBlock(gp1, gp2, arbitraryVector);
  Eigen::MatrixXd S1 = m.getMatrixXd();

  m.get<Utils::DerivativeOrder::One>().block(0, 0, 3, 3) =
      overlapBlock.getMatrixBlock(gp1, gp2, arbitraryVector + Eigen::Vector3d(0.01, 0, 0));
  Eigen::MatrixXd S2 = m.getMatrixXd();
  for (int i = 0; i < gp1.nAOs(); i++)
    for (int j = 0; j < gp2.nAOs(); j++)
      ASSERT_THAT(((S2 - S1)(i, j)), DoubleNear(0.01 * m.v1(i, j).derivatives().x(), 1e-5));

  m.get<Utils::DerivativeOrder::One>().block(0, 0, 3, 3) =
      overlapBlock.getMatrixBlock(gp1, gp2, arbitraryVector + Eigen::Vector3d(0, 0.01, 0));
  S2 = m.getMatrixXd();
  for (int i = 0; i < gp1.nAOs(); i++)
    for (int j = 0; j < gp2.nAOs(); j++)
      ASSERT_THAT(((S2 - S1)(i, j)), DoubleNear(0.01 * m.v1(i, j).derivatives().y(), 1e-5));

  m.get<Utils::DerivativeOrder::One>().block(0, 0, 3, 3) =
      overlapBlock.getMatrixBlock(gp1, gp2, arbitraryVector + Eigen::Vector3d(0, 0, 0.01));
  S2 = m.getMatrixXd();
  for (int i = 0; i < gp1.nAOs(); i++)
    for (int j = 0; j < gp2.nAOs(); j++)
      ASSERT_THAT(((S2 - S1)(i, j)), DoubleNear(0.01 * m.v1(i, j).derivatives().z(), 1e-5));
}

TEST_F(AGTOOverlapMatrixBlock, ReturnsCorrectDerivativesForDOrbitals) {
  Utils::MatrixWithDerivatives m;
  m.setOrder(Utils::DerivativeOrder::One);
  m.setDimension(gd1.nAOs(), gd2.nAOs());
  m.get<Utils::DerivativeOrder::One>().block(0, 0, 5, 5) = overlapBlock.getMatrixBlock(gd1, gd2, arbitraryVector);
  Eigen::MatrixXd S1 = m.getMatrixXd();

  m.get<Utils::DerivativeOrder::One>().block(0, 0, 5, 5) =
      overlapBlock.getMatrixBlock(gd1, gd2, arbitraryVector + Eigen::Vector3d(0.01, 0, 0));
  Eigen::MatrixXd S2 = m.getMatrixXd();
  for (int i = 0; i < gd1.nAOs(); i++)
    for (int j = 0; j < gd2.nAOs(); j++)
      ASSERT_THAT(((S2 - S1)(i, j)), DoubleNear(0.01 * m.v1(i, j).derivatives().x(), 1e-5));

  m.get<Utils::DerivativeOrder::One>().block(0, 0, 5, 5) =
      overlapBlock.getMatrixBlock(gd1, gd2, arbitraryVector + Eigen::Vector3d(0, 0.01, 0));
  S2 = m.getMatrixXd();
  for (int i = 0; i < gd1.nAOs(); i++)
    for (int j = 0; j < gd2.nAOs(); j++)
      ASSERT_THAT(((S2 - S1)(i, j)), DoubleNear(0.01 * m.v1(i, j).derivatives().y(), 1e-5));

  m.get<Utils::DerivativeOrder::One>().block(0, 0, 5, 5) =
      overlapBlock.getMatrixBlock(gd1, gd2, arbitraryVector + Eigen::Vector3d(0, 0, 0.01));
  S2 = m.getMatrixXd();
  for (int i = 0; i < gd1.nAOs(); i++)
    for (int j = 0; j < gd2.nAOs(); j++)
      ASSERT_THAT(((S2 - S1)(i, j)), DoubleNear(0.01 * m.v1(i, j).derivatives().z(), 1e-5));
}
} // namespace Sparrow
} // namespace Scine


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

#ifndef poplibs_test_ctc_inference_hpp
#define poplibs_test_ctc_inference_hpp

#include "CTCUtil.hpp"
#include <poplibs_support/CTCInferenceDefs.hpp>
#include <poplibs_support/LogArithmetic.hpp>

#include <boost/multi_array.hpp>
#include <boost/optional.hpp>

#include <vector>

namespace poplibs_test {
namespace ctc {

// Probabilities representing the final symbol of a beam
template <typename FPType> struct BeamProbability {
  FPType pnb; // non blank
  FPType pb;  // blank
};

template <typename FPType> struct Candidate {
  unsigned beam;   // Parent output sequence
  unsigned addend; // Symbol to append onto beam

  FPType pnb;    // beam and non blank
  FPType pb;     // beam and blank
  FPType pTotal; // pTotal = pnb + pb
};

struct BeamHistory {
  // symbols array contains the output sequences for each beam, with an entry at
  // every timestep (so potentially "oversampled"). Traversing through this
  // array builds up the actual output sequence by either:
  //  - voidSymbol -> no change in output sequence
  //  - otherwise -> appended to output sequence
  // so traversing `a` -> `a` in the table represents the output sequence `aa`,
  // not `a`!
  boost::multi_array<unsigned, 2> symbols;
  boost::multi_array<boost::optional<unsigned>, 2>
      parents; // If unset, no parent for convenience

  // This represents the position which is one beyond the end of the assigned
  // beams (assessed with candidates)
  unsigned nextIndexToAssign = 0;

  BeamHistory(unsigned beamwidth, unsigned t);

  bool compareReversedOutputSequence(
      unsigned beamIndex, unsigned addend,
      const std::vector<unsigned> &reversedSequence) const;

  template <typename FPType>
  std::vector<unsigned>
  getOutputSequence(const Candidate<FPType> &candidate) const {
    auto reversedSequence =
        getReversedOutputSequence(candidate.beam, candidate.addend);

    std::reverse(reversedSequence.begin(), reversedSequence.end());
    return reversedSequence;
  }

  std::vector<unsigned> getOutputSequence(unsigned beamIndex) const {
    Candidate<double> candidate = {beamIndex, popnn::ctc_infer::voidSymbol};
    return getOutputSequence(candidate);
  }

  template <typename FPType>
  std::vector<unsigned>
  getReversedOutputSequence(const Candidate<FPType> &candidate) const {
    return getReversedOutputSequence(candidate.beam, candidate.addend);
  }

  std::vector<unsigned> getReversedOutputSequence(unsigned beamIndex,
                                                  unsigned addend) const;

  // Find the last symbol in the beam output sequence
  unsigned getLastOutput(unsigned beamIndex) const;
  // Returning tuple<symbol, beamIndex, timeIndex>
  std::tuple<unsigned, unsigned, int> getNextSymbol(unsigned beamIndex,
                                                    int timeIndex) const;
  void assignParent(unsigned beamIndex, unsigned parentBeamIndex);
  void assignSymbol(unsigned beamIndex, unsigned addend);
  void incrementIndex();
};

void print(const BeamHistory &beamHistory);

template <typename FPType>
std::vector<Candidate<FPType>> generateCandidates(
    const boost::multi_array<FPType, 2> &input, unsigned t,
    const std::vector<BeamProbability<FPType>> &beamProbabilities,
    const BeamHistory &beamHistory, unsigned blankSymbol, bool useLog);

template <typename FPType>
std::vector<Candidate<FPType>>
mergeEquivalentCandidates(const std::vector<Candidate<FPType>> &candidates,
                          const BeamHistory &beamHistory, bool useLog);

template <typename FPType>
std::vector<std::pair<unsigned, unsigned>>
listMergeableCandidates(const std::vector<Candidate<FPType>> &candidates,
                        const BeamHistory &beamHistory);
template <typename FPType>
std::vector<Candidate<FPType>>
sortCandidates(const std::vector<Candidate<FPType>> &candidates, bool useLog);

template <typename FPType>
std::vector<Candidate<FPType>>
pruneCandidates(const std::vector<Candidate<FPType>> &candidates, size_t max,
                bool useLog);

template <typename FPType>
void applyCandidates(BeamHistory &beamHistory,
                     std::vector<BeamProbability<FPType>> &beamProbabilities,
                     const std::vector<Candidate<FPType>> &candidates,
                     bool useLog);

template <typename FPType>
std::vector<std::pair<std::vector<unsigned>, FPType>>
infer(const boost::multi_array<FPType, 2> &input, unsigned blankSymbol,
      unsigned beamwidth, unsigned topBeams, bool useLog, bool verbose = false);

//------------------------------------------------------------------------------
// Exhaustive path inference functions. Coded to look simple and be divided
// into lots of individually verifiable steps and help with debug.
boost::multi_array<unsigned, 2> findAllInputPaths(unsigned timeSteps,
                                                  unsigned sequenceLength);

template <typename FPType>
boost::multi_array<FPType, 1>
findAllInputPathProbabilities(const boost::multi_array<FPType, 2> &sequence,
                              const boost::multi_array<unsigned, 2> &inputPaths,
                              bool isLog);

boost::multi_array<unsigned, 2>
inputToOutputPath(const boost::multi_array<unsigned, 2> &inputPath,
                  unsigned blankSymbol);

template <typename FPType>
std::tuple<std::vector<unsigned>, std::vector<FPType>,
           std::vector<std::vector<unsigned>>>
mergePaths(boost::multi_array<unsigned, 2> &outPaths,
           boost::multi_array<FPType, 1> &probabilities, bool isLog);

} // namespace ctc
} // namespace poplibs_test

#endif // poplibs_test_ctc_inference_hpp


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

/// @version 0.1
/// @author  Xinyan DAI
/// @contact xinyan.dai@outlook.com
//////////////////////////////////////////////////////////////////////////////

#pragma once

#include <utility>
#include <vector>
#include <random>
#include <unordered_map>
#include <boost/progress.hpp>

#include "rptree.hpp"
#include "rptree.hpp"
#include "joint_inverted.hpp"
#include "../index.hpp"

namespace ss {

    using std::vector;

    template<class DataType>
    class MultiPQIndex : public Index<DataType > {
    protected:
        vector<KMeansIndex<DataType > * >   _left_joint_index;
        vector<KMeansIndex<DataType > * >   _right_joint_index;
        int                                 _left_dimension;
        int                                 _right_dimension;
    public:
        explicit MultiPQIndex(const parameter& para) :
            Index<DataType >(para), _left_joint_index(para.forest_size), _right_joint_index(para.forest_size)  {
        }
        ~MultiPQIndex() {
            for (KMeansIndex<DataType> * pointer : _left_joint_index) { if (pointer) delete pointer;}
            for (KMeansIndex<DataType> * pointer : _right_joint_index) { if (pointer) delete pointer; }
        }

        void Train(const Matrix<DataType > & data) override {
            _left_dimension = data.getDim() / 2;
            _right_dimension = data.getDim() - data.getDim() / 2;
            auto left = ss::GroupCenters(
                    Visitor<DataType >(data, 0, _left_dimension), this->_para);
            auto right = ss::GroupCenters(
                    Visitor<DataType >(data, _left_dimension, _right_dimension), this->_para);

            for (int l = 0; l < this->_para.forest_size; ++l) {
                _left_joint_index[l] = new KMeansIndex<DataType >(this->_para);
                _left_joint_index[l]->set_centers(left[l]);

                _right_joint_index[l] = new KMeansIndex<DataType >(this->_para);
                _right_joint_index[l]->set_centers(right[l]);
            }
        }

        void Add(const Matrix<DataType > & data) override {

            for (int l = 0; l < this->_para.forest_size; ++l) {
                _left_joint_index[l]->Assign(Visitor<DataType >(data, 0, _left_dimension));
                _right_joint_index[l]->Assign(Visitor<DataType >(data, _left_dimension, _right_dimension));
            }
        }

        void Search(const DataType *query, const std::function<void (int)>& prober) override {
            for (int l = 0; l < this->_para.forest_size; ++l) {
                const vector<int>& left_idx = _left_joint_index[l]->Search(query);

                for (int i = 0; i < this->_para.forest_size; ++i) {
                    const vector<int>& right_idx = _right_joint_index[i]->Search(query + _left_dimension);
                    std::vector<int> indices;
                    indices.reserve(std::min(left_idx.size(), right_idx.size()));

                    std::set_intersection(
                            left_idx.begin(),left_idx.end(),
                            right_idx.begin(),right_idx.end(),
                            std::back_inserter(indices));
                    for (int id : indices) {
                        prober(id);
                    }
                }
            }
        }

    };

} // namespace ss


/*
 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 <boost/test/unit_test.hpp>
#include <oret/datapaths.hpp>
#include <oret/fileutilities.hpp>
#include <oret/toplevelfixture.hpp>

#include <ored/model/calibrationinstruments/cpicapfloor.hpp>
#include <ored/model/crossassetmodeldata.hpp>
#include <ored/model/inflation/infdkdata.hpp>
#include <ored/utilities/correlationmatrix.hpp>

using namespace QuantLib;
using namespace QuantExt;
using namespace boost::unit_test_framework;
using namespace std;
using namespace ore;
using namespace ore::data;

using ore::test::TopLevelFixture;

namespace {

boost::shared_ptr<vector<boost::shared_ptr<IrLgmData>>> irConfigsData() {

    // Create three instances
    boost::shared_ptr<IrLgmData> lgmData1(new data::IrLgmData());
    boost::shared_ptr<IrLgmData> lgmData2(new data::IrLgmData());
    boost::shared_ptr<IrLgmData> lgmData3(new data::IrLgmData());

    vector<std::string> expiries = {"1Y", "2Y", "36M"};
    vector<std::string> terms = {"5Y", "2Y", "6M"};
    vector<std::string> strikes = {"ATM", "ATM", "ATM"};

    // First instance
    lgmData1->ccy() = "EUR";

    lgmData1->calibrationType() = parseCalibrationType("BOOTSTRAP");
    lgmData1->reversionType() = parseReversionType("HULLWHITE");
    lgmData1->volatilityType() = parseVolatilityType("HAGAN");
    std::vector<Time> hTimes = {1.0, 2.0, 3.0, 4.0};
    std::vector<Real> hValues = {1.0, 2.0, 3.0, 4.0};
    std::vector<Time> aTimes = {1.0, 2.0, 3.0, 4.0};
    std::vector<Real> aValues = {1.0, 2.0, 3.0, 4.0};

    lgmData1->calibrateH() = false;
    lgmData1->hParamType() = parseParamType("PIECEWISE");

    lgmData1->hTimes() = hTimes;

    lgmData1->hValues() = hValues;

    lgmData1->calibrateA() = false;
    lgmData1->aParamType() = parseParamType("PIECEWISE");
    lgmData1->aTimes() = aTimes;
    lgmData1->aValues() = aValues;
    lgmData1->shiftHorizon() = 1.0;

    lgmData1->optionExpiries() = expiries;
    lgmData1->optionTerms() = terms;
    lgmData1->optionStrikes() = strikes;

    lgmData1->scaling() = 1.0;

    // Second instance
    lgmData2->ccy() = "USD";

    lgmData2->calibrationType() = parseCalibrationType("BOOTSTRAP");
    lgmData2->reversionType() = parseReversionType("HULLWHITE");
    lgmData2->volatilityType() = parseVolatilityType("HAGAN");

    lgmData2->calibrateH() = false;
    lgmData2->hParamType() = parseParamType("PIECEWISE");
    lgmData2->hTimes() = hTimes;
    lgmData2->hValues() = hValues;

    lgmData2->calibrateA() = false;
    lgmData2->aParamType() = parseParamType("PIECEWISE");

    lgmData2->aTimes() = aTimes;
    lgmData2->aValues() = aValues;
    lgmData2->shiftHorizon() = 1.0;

    lgmData2->optionExpiries() = expiries;
    lgmData2->optionTerms() = terms;
    lgmData2->optionStrikes() = strikes;

    lgmData2->scaling() = 1.0;

    // Third instance
    lgmData3->ccy() = "JPY";

    lgmData3->calibrationType() = parseCalibrationType("BOOTSTRAP");
    lgmData3->reversionType() = parseReversionType("HULLWHITE");
    lgmData3->volatilityType() = parseVolatilityType("HAGAN");

    lgmData3->calibrateH() = false;
    lgmData3->hParamType() = parseParamType("PIECEWISE");
    lgmData3->hTimes() = hTimes;
    lgmData3->hValues() = hValues;

    lgmData3->calibrateA() = false;
    lgmData3->aParamType() = parseParamType("PIECEWISE");

    lgmData3->aTimes() = aTimes;
    lgmData3->aValues() = aValues;
    lgmData3->shiftHorizon() = 1.0;

    lgmData3->optionExpiries() = expiries;
    lgmData3->optionTerms() = terms;
    lgmData3->optionStrikes() = strikes;

    lgmData3->scaling() = 1.0;

    boost::shared_ptr<vector<boost::shared_ptr<IrLgmData>>> lgmDataVector(new vector<boost::shared_ptr<IrLgmData>>);
    *lgmDataVector = {lgmData1, lgmData2, lgmData3};
    return lgmDataVector;
}

vector<boost::shared_ptr<InflationModelData>> infConfigsData() {

    // TODO: Replacing the data that was here for now. It doesn't make sense.

    vector<boost::shared_ptr<CalibrationInstrument>> instruments;
    vector<Period> expiries = { 1 * Years, 2 * Years, 36 * Months };
    auto strike = boost::make_shared<AbsoluteStrike>(0.03);
    for (const Period& expiry : expiries) {
        instruments.push_back(boost::make_shared<CpiCapFloor>(CapFloor::Floor, expiry, strike));
    }
    CalibrationBasket cb(instruments);
    vector<CalibrationBasket> calibrationBaskets = { cb };

    ReversionParameter reversion(LgmData::ReversionType::HullWhite, false, ParamType::Piecewise,
        { 1.0, 2.0, 3.0, 4.0 }, { 1.0, 2.0, 3.0, 4.0, 4.0 });

    VolatilityParameter volatility(LgmData::VolatilityType::Hagan, false, ParamType::Piecewise,
        { 1.0, 2.0, 3.0, 4.0 }, { 1.0, 2.0, 3.0, 4.0, 4.0 });

    LgmReversionTransformation rt(1.0, 1.0);

    boost::shared_ptr<InfDkData> data = boost::make_shared<InfDkData>(CalibrationType::Bootstrap, calibrationBaskets,
        "EUR", "EUHICPXT", reversion, volatility, rt);

    return {data};
}

boost::shared_ptr<vector<boost::shared_ptr<FxBsData>>> fxConfigsData() {

    // Create two instances
    boost::shared_ptr<FxBsData> fxBsData1(new data::FxBsData());
    boost::shared_ptr<FxBsData> fxBsData2(new data::FxBsData());

    vector<std::string> expiries = {"1Y", "2Y", "36M"};
    vector<std::string> strikes = {"ATMF", "ATMF", "ATMF"};
    std::vector<Time> times = {1.0, 2.0, 3.0, 4.0};

    // First instance
    fxBsData1->foreignCcy() = "USD";
    fxBsData1->domesticCcy() = "EUR";
    fxBsData1->calibrationType() = parseCalibrationType("BOOTSTRAP");
    fxBsData1->calibrateSigma() = true;
    fxBsData1->sigmaParamType() = parseParamType("CONSTANT");
    fxBsData1->sigmaTimes() = times;
    fxBsData1->optionExpiries() = expiries;
    fxBsData1->optionStrikes() = strikes;

    // Second instance
    fxBsData2->foreignCcy() = "JPY";
    fxBsData2->domesticCcy() = "EUR";
    fxBsData2->calibrationType() = parseCalibrationType("BOOTSTRAP");
    fxBsData2->calibrateSigma() = true;
    fxBsData2->sigmaParamType() = parseParamType("CONSTANT");
    fxBsData2->sigmaTimes() = times;
    fxBsData2->optionExpiries() = expiries;
    fxBsData2->optionStrikes() = strikes;

    boost::shared_ptr<vector<boost::shared_ptr<FxBsData>>> fxBsDataVector(new vector<boost::shared_ptr<FxBsData>>);
    *fxBsDataVector = {fxBsData1, fxBsData2};
    return fxBsDataVector;
}

boost::shared_ptr<vector<boost::shared_ptr<EqBsData>>> eqConfigsData() {

    // Create two instances
    boost::shared_ptr<EqBsData> eqBsData1(new data::EqBsData());

    vector<std::string> expiries = {"1Y", "2Y", "36M"};
    vector<std::string> strikes = {"ATMF", "ATMF", "ATMF"};
    std::vector<Time> times = {1.0, 2.0, 3.0, 4.0};

    // First instance
    eqBsData1->eqName() = "SP5";
    eqBsData1->currency() = "EUR";
    eqBsData1->calibrationType() = parseCalibrationType("BOOTSTRAP");
    eqBsData1->calibrateSigma() = true;
    eqBsData1->sigmaParamType() = parseParamType("CONSTANT");
    eqBsData1->sigmaTimes() = times;
    eqBsData1->optionExpiries() = expiries;
    eqBsData1->optionStrikes() = strikes;

    boost::shared_ptr<vector<boost::shared_ptr<EqBsData>>> eqBsDataVector(new vector<boost::shared_ptr<EqBsData>>);
    *eqBsDataVector = {eqBsData1};
    return eqBsDataVector;
}

boost::shared_ptr<vector<boost::shared_ptr<CrLgmData>>> crLgmConfigsData() {

    // Create three instances
    boost::shared_ptr<CrLgmData> lgmData(new data::CrLgmData());

    lgmData->name() = "ItraxxEuropeS9V1";

    lgmData->calibrationType() = parseCalibrationType("BOOTSTRAP");
    lgmData->reversionType() = parseReversionType("HULLWHITE");
    lgmData->volatilityType() = parseVolatilityType("HAGAN");
    std::vector<Time> hTimes = {1.0, 2.0, 3.0, 4.0};
    std::vector<Real> hValues = {1.0, 2.0, 3.0, 4.0};
    std::vector<Time> aTimes = {1.0, 2.0, 3.0, 4.0};
    std::vector<Real> aValues = {1.0, 2.0, 3.0, 4.0};

    lgmData->calibrateH() = false;
    lgmData->hParamType() = parseParamType("CONSTANT");

    lgmData->hTimes() = hTimes;

    lgmData->hValues() = hValues;

    lgmData->calibrateA() = false;
    lgmData->aParamType() = parseParamType("CONSTANT");
    lgmData->aTimes() = aTimes;
    lgmData->aValues() = aValues;
    lgmData->shiftHorizon() = 1.0;

    lgmData->scaling() = 1.0;

    boost::shared_ptr<vector<boost::shared_ptr<CrLgmData>>> lgmDataVector(new vector<boost::shared_ptr<CrLgmData>>);
    *lgmDataVector = {lgmData};
    return lgmDataVector;
}

boost::shared_ptr<vector<boost::shared_ptr<CrCirData>>> crCirConfigsData() {

    // Create three instances
    boost::shared_ptr<CrCirData> cirData(new data::CrCirData());

    cirData->name() = "CDX.NA.S33v1";

    cirData->currency() = "USD";
    cirData->calibrationType() = parseCalibrationType("None");
    cirData->calibrationStrategy() = parseCirCalibrationStrategy("None");
    cirData->startValue() = 0.1;
    cirData->reversionValue() = 0.1;
    cirData->longTermValue() = 0.1;
    cirData->volatility() = 0.1;
    cirData->relaxedFeller() = true;
    cirData->fellerFactor() = 1.1;
    cirData->tolerance() = 1e-8;

    boost::shared_ptr<vector<boost::shared_ptr<CrCirData>>> cirDataVector(new vector<boost::shared_ptr<CrCirData>>);
    *cirDataVector = {cirData};
    return cirDataVector;
}

boost::shared_ptr<data::CrossAssetModelData> crossAssetData() {

    boost::shared_ptr<data::CrossAssetModelData> crossAssetData(new data::CrossAssetModelData());

    crossAssetData->domesticCurrency() = "EUR";
    crossAssetData->currencies() = {"EUR", "USD", "JPY"}; // need to check how to set this up
    crossAssetData->equities() = {"SP5"};
    crossAssetData->infIndices() = {"EUHICPXT"};
    crossAssetData->creditNames() = {"ItraxxEuropeS9V1", "CDX.NA.S33v1"};
    crossAssetData->irConfigs() = *irConfigsData();
    crossAssetData->fxConfigs() = *fxConfigsData();
    crossAssetData->eqConfigs() = *eqConfigsData();
    crossAssetData->infConfigs() = infConfigsData();
    crossAssetData->crLgmConfigs() = *crLgmConfigsData();
    crossAssetData->crCirConfigs() = *crCirConfigsData();

    CorrelationMatrixBuilder cmb;
    cmb.addCorrelation("IR:EUR", "IR:USD", 1.0);
    cmb.addCorrelation("IR:EUR", "IR:JPY", 1.0);
    cmb.addCorrelation("IR:USD", "IR:JPY", 1.0);
    cmb.addCorrelation("INF:EUHICPXT", "IR:EUR", 1.0);
    cmb.addCorrelation("IR:EUR", "CR:ItraxxEuropeS9V1", 1.0);
    cmb.addCorrelation("IR:USD", "CR:CDX.NA.S33v1", 1.0);
    cmb.addCorrelation("CR:ItraxxEuropeS9V1", "CR:CDX.NA.S33v1", 1.0);

    crossAssetData->setCorrelations(cmb.correlations());

    crossAssetData->bootstrapTolerance() = 0.001;

    return crossAssetData;
}

// Fixture to remove output files
class F : public TopLevelFixture {
public:
    F() {}
    ~F() { clearOutput(TEST_OUTPUT_PATH); }
};
} // namespace

BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, TopLevelFixture)

BOOST_FIXTURE_TEST_SUITE(CrossAssetModelDataTests, F)

BOOST_AUTO_TEST_CASE(testToXMLFromXML) {

    BOOST_TEST_MESSAGE("Testing toXML/fromXML...");

    data::CrossAssetModelData data = *crossAssetData();
    XMLDocument OutDoc;

    XMLNode* simulationNode = OutDoc.allocNode("Simulation");
    OutDoc.appendNode(simulationNode);

    XMLNode* crossAssetModelNode = data.toXML(OutDoc);
    XMLUtils::appendNode(simulationNode, crossAssetModelNode);

    std::string filename = TEST_OUTPUT_FILE("simulationtest.xml");
    OutDoc.toFile(filename);

    data::CrossAssetModelData newData;
    newData.fromFile(filename);

    BOOST_CHECK(data == newData);

    newData.irConfigs() = {};
    BOOST_CHECK(data != newData);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()