#define BOOST_TEST_MODULE
#include <boost/test/unit_test.hpp>
#include <util/test_macros.hpp>
#include <stdlib.h>
#include <vector>
#include <string>
#include <functional>

#include <random>

// ML-Data Utils
#include <ml_data/ml_data.hpp>

// Models
#include <unity/toolkits/supervised_learning/supervised_learning.hpp>
#include <unity/toolkits/supervised_learning/boosted_trees.hpp>

#include <sframe/testing_utils.hpp>
#include <util/testing_utils.hpp>

#include <cfenv>

using namespace turi;
using namespace turi::supervised;
using namespace turi::supervised::xgboost;

void run_boosted_trees_classifier_test(
    std::map<std::string, flexible_type> test_opts,
    std::map<std::string, flexible_type> model_opts = std::map<std::string, flexible_type>()) {

  size_t examples = test_opts.at("examples");
  size_t features = test_opts.at("features");
  std::string target_column_name = "target";
  std::vector<std::string> column_names = {"user", "item"};

  // Answers
  // -----------------------------------------------------------------------
  DenseVector coefs(features+1);
  coefs.setRandom();

  // Feature names
  std::vector<std::string> feature_names;
  std::vector<flex_type_enum> feature_types;
  for(size_t i=0; i < features; i++){
    feature_names.push_back(std::to_string(i));
    feature_types.push_back(flex_type_enum::FLOAT);
  }

  // Data
  std::vector<std::vector<flexible_type>> y_data;
  std::vector<std::vector<flexible_type>> X_data;
  for(size_t i=0; i < examples; i++){
    DenseVector x(features);
    x.setRandom();
    std::vector<flexible_type> x_tmp;
    for(size_t k=0; k < features; k++){
      x_tmp.push_back(x(k));
    }

    // Compute the prediction for this
    double t = x.dot(coefs.segment(0, features)) + coefs(features);
    t = 1.0/(1.0+exp(-1.0*t));
    int c = turi::random::bernoulli(t);
    std::vector<flexible_type> y_tmp;
    y_tmp.push_back(c);

    X_data.push_back(x_tmp);
    y_data.push_back(y_tmp);
  }

  // Options
  std::map<std::string, flexible_type> options = model_opts;

  // Make the data
  sframe X = make_testing_sframe(feature_names, feature_types, X_data);
  sframe y = make_testing_sframe({"target"}, {flex_type_enum::INTEGER}, y_data);

  // Train model
  std::shared_ptr<boosted_trees_classifier> model;
  model.reset(new boosted_trees_classifier);
  model->init(X,y);
  model->init_options(options);
  if (test_opts.count("external_memory")) {
    model->_set_storage_mode(storage_mode_enum::EXT_MEMORY);
    model->_set_num_batches(8);
  }
  model->train();

  // Check options
  // ----------------------------------------------------------------------
  std::map<std::string, flexible_type> _options;
  _options = model->get_current_options();
  for (auto& kvp: options){
    TS_ASSERT(_options[kvp.first] == kvp.second);
  }
  TS_ASSERT(model->is_trained() == true);

  // Check predictions
  // ----------------------------------------------------------------------

  // Construct the ml_data
  ml_data data = model->construct_ml_data_using_current_metadata(X, y);

  std::vector<flexible_type> pred_class;
  std::shared_ptr<sarray<flexible_type>> _pred_class
    = model->predict(data, "class");

  // Check that we can train a model when providing a validation set
  std::vector<std::vector<flexible_type>> y_v;
  std::vector<std::vector<flexible_type>> X_v;
  for(size_t i=0; i < 5; i++){
    DenseVector x(features);
    x.setRandom();
    std::vector<flexible_type> x_tmp;
    for(size_t k=0; k < features; k++){
      x_tmp.push_back(x(k));
    }

    // Compute the prediction for this
    double t = x.dot(coefs.segment(0, features)) + coefs(features);
    t = 1.0/(1.0+exp(-1.0*t));
    int c = turi::random::bernoulli(t);
    std::vector<flexible_type> y_tmp;
    y_tmp.push_back(1 + c);

    X_v.push_back(x_tmp);
    y_v.push_back(y_tmp);
  }

  sframe Xv = make_testing_sframe(feature_names, feature_types, X_v);
  sframe yv = make_testing_sframe({"target"}, {flex_type_enum::INTEGER}, y_v);

  model.reset(new boosted_trees_classifier);
  model->init(X, y, Xv, yv);
  model->init_options(options);
  if (test_opts.count("external_memory")) {
    model->_set_storage_mode(storage_mode_enum::EXT_MEMORY);
    model->_set_num_batches(8);
  }

  model->train();

  ml_data valid_data = model->construct_ml_data_using_current_metadata(Xv, yv);
  auto ret = model->evaluate(valid_data, "accuracy");
  for (auto kv : ret) {
    logprogress_stream << kv.first << ": "
                       << variant_get_value<flexible_type>(kv.second)
                       << std::endl;
  }
  ret = model->evaluate(valid_data, "rmse");
  for (auto kv : ret) {
    logprogress_stream << kv.first << ": "
                       << variant_get_value<flexible_type>(kv.second)
                       << std::endl;
  }
}

struct boosted_trees_classifier_test  {
  public:
  void test_boosted_trees_classifier_basic_2d() {
    std::map<std::string, flexible_type> opts = {
      {"examples", 100},
      {"features", 1}};
    run_boosted_trees_classifier_test(opts);
  }

  void test_boosted_trees_classifier_small() {
    std::map<std::string, flexible_type> opts = {
      {"examples", 1000},
      {"features", 10}};
    run_boosted_trees_classifier_test(opts);
  }

  void test_boosted_trees_classifier_with_insufficient_column_subsample() {
    std::map<std::string, flexible_type> opts = {
      {"examples", 1000},
      {"features", 10}
    };
    std::map<std::string, flexible_type> model_opts = {
      {"column_subsample", 0.01}
    };
    run_boosted_trees_classifier_test(opts, model_opts);
  }

  void test_boosted_trees_classifier_external_memory() {
    std::map<std::string, flexible_type> opts = {
      {"examples", 1000},
      {"features", 10},
      {"external_memory", 1},
      {"row_subsample", .5},
      {"column_subsample", .5},
    };
    run_boosted_trees_classifier_test(opts);
  }
};

struct boosted_trees_stress_test  {

  void run_stress(size_t n, const std::string& run_string,
                  const std::string& target_column_str) {

    random::seed(0);

    size_t ntest = 10;

    sframe X = make_random_sframe(n, run_string, false);
    sframe X2 = make_random_sframe(ntest, run_string, false);
    sframe y = make_random_sframe(n, target_column_str, false);
    sframe y2 = make_random_sframe(ntest, target_column_str, false);
    y.set_column_name(0, "target");
    y2.set_column_name(0, "target");

    std::shared_ptr<xgboost_model> model;

    for (auto storage_mode : {storage_mode_enum::IN_MEMORY, storage_mode_enum::EXT_MEMORY}) {
      // Skip external memory stress test for large target columns
      if (storage_mode == storage_mode_enum::EXT_MEMORY &&
          (target_column_str[0] == 'S' || target_column_str[0] == 's'))
        continue;

      if(target_column_str == "n")
        model.reset(new boosted_trees_regression);
      else
        model.reset(new boosted_trees_classifier);

      // Options
      std::map<std::string, flexible_type> options = {
        {"max_iterations", 3},
      };

      model->init(X,y);
      model->init_options(options);
      model->_set_storage_mode(storage_mode);
      if (storage_mode == storage_mode_enum::EXT_MEMORY)
        model->_set_num_batches(8);
      model->train();

      // Construct the ml_data
      ml_data data = model->construct_ml_data_using_current_metadata(X2, y2);
      size_t num_classes = model->num_classes();

      // Check prediction API
      // ----------------------------------------------------------------------
      if (model->is_classifier()) {

        std::cout << "Check predict class" << std::endl;
        // predict
        auto preds = model->predict(data, "class");
        TS_ASSERT_EQUALS(preds->size(), data.num_rows());

        std::cout << "Check predict prob vector" << std::endl;

        // predict all
        auto preds_vec = model->predict(data, "probability_vector");
        TS_ASSERT_EQUALS(preds->size(), data.num_rows());

        std::cout << "Check classify " << std::endl;
        // classify
        auto classify_vec = model->classify(data);
        TS_ASSERT_EQUALS(preds->size(), data.num_rows());

        // predict topk
        std::vector<size_t> topk{1, 2, num_classes};
        for (size_t k : topk) {
          std::cout << "Check predict topk=" <<  k << std::endl;
          auto topk_preds = model->predict_topk(data, "probability", k);
          TS_ASSERT_EQUALS(topk_preds.size(), data.num_rows() * k);
          std::vector<flexible_type> probs;
          topk_preds.select_column("probability")->get_reader()->read_rows(0, topk_preds.size(), probs);
          for (auto& p : probs) {
            TS_ASSERT(p <= 1.0 && p >= 0.0);
          }
        }
      } else {
        auto preds = model->predict(data);
        TS_ASSERT_EQUALS(preds->size(), data.num_rows());
      }
    }
  }

 public:

  ////////////////////////////////////////////////////////////////////////////////
  // All the ones with targets

  void test_boosted_tree_stress000_tn() {
    // All unique
    run_stress(1, "n", "n");
  }

  void test_boosted_tree_stress0n_tn() {
    // All unique
    run_stress(5, "n", "n");
  }

  void test_boosted_tree_stress0S_tn() {
    // All unique
    run_stress(5, "s", "n");
  }

  void test_boosted_tree_stress1_unsorted_tn() {
    run_stress(5, "b", "n");
  }

  void test_boosted_tree_stress0b_tn() {
    // All unique
    run_stress(13, "S", "n");
  }

  void test_boosted_tree_stress1b_unsorted_tn() {
    run_stress(13, "b", "n");
  }

  void test_boosted_tree_stress1_tn() {
    run_stress(13, "bs", "n");
  }

  void test_boosted_tree_stress2_tn() {
    run_stress(13, "zs", "n");
  }

  void test_boosted_tree_stress3_tn() {
    run_stress(100, "Zs", "n");
  }

  void test_boosted_tree_stress4_tn() {
    // Pretty mush gonna be unique
    run_stress(100, "Ss", "n");
  }

  void test_boosted_tree_stress5_tn() {
    // 10 blocks of values.
    run_stress(1000, "Zs", "n");
  }

  void test_boosted_tree_stress6_tn() {
    // two large blocks of values
    run_stress(1000, "bs", "n");
  }

  void test_boosted_tree_stress10_tn() {
    // Yeah, a corner sase
    run_stress(1, "bc", "n");
  }

  void test_boosted_tree_stress11_tn() {
    // One with just a lot of stuff
    run_stress(200, "u", "n");
  }

  void test_boosted_tree_stress12_tn() {
    // One with just a lot of stuff
    run_stress(200, "d", "n");
  }

  void test_boosted_tree_stress13_tn() {
    // One with just a lot of stuff
    run_stress(1000, "snv", "n");
  }

  void test_boosted_tree_stress14_tn() {
    // One with just a lot of stuff
    run_stress(1000, "du", "n");
  }

  void test_boosted_tree_stress15_tn() {
    // One with just a lot of stuff
    run_stress(3, "UDssssV", "n");
  }

  void test_boosted_tree_stress15b_tn() {
    // One with just a lot of stuff
    run_stress(35, "UDssssV", "n");
  }

  void test_boosted_tree_stress15c_tn() {
    // One with just a lot of stuff
    run_stress(500, "UDsssV", "n");
  }

  void test_boosted_tree_stress100_tn() {
    // One with just a lot of stuff
    run_stress(10, "Zsuvd", "n");
  }

  void test_boosted_tree_stress16_null_tn() {
    // two large blocks of values
    run_stress(1000, "S", "n");
  }

  void test_boosted_tree_stress000_tc() {
    // All unique
    run_stress(2, "n", "s");
  }

  void test_boosted_tree_stress0n_tc() {
    // All unique
    run_stress(5, "n", "s");
  }

  void test_boosted_tree_stress0S_tc() {
    // All unique
    run_stress(5, "s", "s");
  }

  void test_boosted_tree_stress1_unsorted_tc() {
    run_stress(5, "b", "s");
  }

  void test_boosted_tree_stress0b_tc() {
    // All unique
    run_stress(13, "S", "s");
  }

  void test_boosted_tree_stress1b_unsorted_tc() {
    run_stress(13, "b", "s");
  }

  void test_boosted_tree_stress1_tc() {
    run_stress(13, "bs", "s");
  }

  void test_boosted_tree_stress2_tc() {
    run_stress(13, "zs", "s");
  }

  void test_boosted_tree_stress3_tc() {
    run_stress(100, "Zs", "s");
  }

  void test_boosted_tree_stress4_tc() {
    // Pretty mush gonna be unique
    run_stress(100, "Ss", "s");
  }

  void test_boosted_tree_stress5_tc() {
    // 10 blocks of values.
    run_stress(1000, "Zs", "s");
  }

  void test_boosted_tree_stress6_tc() {
    // two large blocks of values
    run_stress(1000, "bs", "s");
  }

  void test_boosted_tree_stress10_tc() {
    // Yeah, a corner sase
    run_stress(2, "bc", "s");
  }

  void test_boosted_tree_stress11_tc() {
    // One with just a lot of stuff
    run_stress(200, "u", "s");
  }

  void test_boosted_tree_stress12_tc() {
    // One with just a lot of stuff
    run_stress(200, "d", "s");
  }

  void test_boosted_tree_stress13_tc() {
    // One with just a lot of stuff
    run_stress(1000, "snv", "s");
  }

  void test_boosted_tree_stress14_tc() {
    // One with just a lot of stuff
    run_stress(1000, "du", "s");
  }

  void test_boosted_tree_stress15_tc() {
    // One with just a lot of stuff
    run_stress(3, "UDssssV", "s");
  }

  void test_boosted_tree_stress15b_tc() {
    // One with just a lot of stuff
    run_stress(35, "UDssssV", "s");
  }

  void test_boosted_tree_stress15c_tc() {
    // One with just a lot of stuff
    run_stress(500, "UDsssV", "s");
  }

  void test_boosted_tree_stress100_tc() {
    // One with just a lot of stuff
    run_stress(10, "Zsuvd", "s");
  }

  void test_boosted_tree_stress16_null_tc() {
    // two large blocks of values
    run_stress(1000, "S", "s");
  }

  //////////////////////////////////////////////////////////////////////////////

  void test_boosted_tree_stress000_tC() {
    // All unique
    run_stress(2, "n", "S");
  }

  void test_boosted_tree_stress0n_tC() {
    // All unique
    run_stress(5, "n", "S");
  }

  void test_boosted_tree_stress0S_tC() {
    // All unique
    run_stress(5, "s", "S");
  }

  void test_boosted_tree_stress1_unsorted_tC() {
    run_stress(5, "b", "S");
  }

  void test_boosted_tree_stress0b_tC() {
    // All unique
    run_stress(13, "S", "S");
  }

  void test_boosted_tree_stress1b_unsorted_tC() {
    run_stress(13, "b", "S");
  }

  void test_boosted_tree_stress1_tC() {
    run_stress(13, "bs", "S");
  }

  void test_boosted_tree_stress2_tC() {
    run_stress(13, "zs", "S");
  }

  void test_boosted_tree_stress3_tC() {
    run_stress(100, "Zs", "S");
  }

  void test_boosted_tree_stress4_tC() {
    // Pretty mush gonna be unique
    run_stress(100, "Ss", "S");
  }

  void test_boosted_tree_stress5_tC() {
    // 10 blocks of values.
    run_stress(1000, "Zs", "S");
  }

  void test_boosted_tree_stress6_tC() {
    // two large blocks of values
    run_stress(1000, "bs", "S");
  }

  void test_boosted_tree_stress10_tC() {
    // Yeah, a corner sase
    run_stress(2, "bc", "S");
  }

  void test_boosted_tree_stress11_tC() {
    // One with just a lot of stuff
    run_stress(200, "u", "S");
  }

  void test_boosted_tree_stress12_tC() {
    // One with just a lot of stuff
    run_stress(200, "d", "S");
  }

  void test_boosted_tree_stress13_tC() {
    // One with just a lot of stuff
    run_stress(1000, "snv", "S");
  }

  void test_boosted_tree_stress14_tC() {
    // One with just a lot of stuff
    run_stress(1000, "du", "S");
  }

  void test_boosted_tree_stress15_tC() {
    // One with just a lot of stuff
    run_stress(3, "UDssssV", "S");
  }

  void test_boosted_tree_stress15b_tC() {
    // One with just a lot of stuff
    run_stress(35, "UDssssV", "S");
  }

  void test_boosted_tree_stress15c_tC() {
    // One with just a lot of stuff
    run_stress(500, "UDsssV", "S");
  }

  void test_boosted_tree_stress100_tC() {
    // One with just a lot of stuff
    run_stress(10, "Zsuvd", "S");
  }

  void test_boosted_tree_stress16_null_tC() {
    // two large blocks of values
    run_stress(1000, "S", "S");
  }

};

BOOST_FIXTURE_TEST_SUITE(_boosted_trees_classifier_test, boosted_trees_classifier_test)
BOOST_AUTO_TEST_CASE(test_boosted_trees_classifier_basic_2d) {
  boosted_trees_classifier_test::test_boosted_trees_classifier_basic_2d();
}
BOOST_AUTO_TEST_CASE(test_boosted_trees_classifier_small) {
  boosted_trees_classifier_test::test_boosted_trees_classifier_small();
}
BOOST_AUTO_TEST_CASE(test_boosted_trees_classifier_with_insufficient_column_subsample) {
  boosted_trees_classifier_test::test_boosted_trees_classifier_with_insufficient_column_subsample();
}
BOOST_AUTO_TEST_CASE(test_boosted_trees_classifier_external_memory) {
  boosted_trees_classifier_test::test_boosted_trees_classifier_external_memory();
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_FIXTURE_TEST_SUITE(_boosted_trees_stress_test, boosted_trees_stress_test)
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress000_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress000_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress0n_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress0n_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress0S_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress0S_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress1_unsorted_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress1_unsorted_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress0b_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress0b_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress1b_unsorted_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress1b_unsorted_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress1_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress1_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress2_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress2_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress3_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress3_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress4_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress4_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress5_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress5_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress6_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress6_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress10_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress10_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress11_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress11_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress12_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress12_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress13_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress13_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress14_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress14_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress15_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress15_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress15b_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress15b_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress15c_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress15c_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress100_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress100_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress16_null_tn) {
  boosted_trees_stress_test::test_boosted_tree_stress16_null_tn();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress000_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress000_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress0n_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress0n_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress0S_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress0S_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress1_unsorted_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress1_unsorted_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress0b_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress0b_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress1b_unsorted_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress1b_unsorted_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress1_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress1_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress2_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress2_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress3_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress3_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress4_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress4_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress5_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress5_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress6_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress6_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress10_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress10_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress11_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress11_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress12_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress12_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress13_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress13_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress14_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress14_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress15_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress15_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress15b_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress15b_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress15c_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress15c_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress100_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress100_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress16_null_tc) {
  boosted_trees_stress_test::test_boosted_tree_stress16_null_tc();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress000_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress000_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress0n_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress0n_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress0S_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress0S_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress1_unsorted_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress1_unsorted_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress0b_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress0b_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress1b_unsorted_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress1b_unsorted_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress1_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress1_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress2_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress2_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress3_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress3_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress4_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress4_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress5_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress5_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress6_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress6_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress10_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress10_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress11_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress11_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress12_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress12_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress13_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress13_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress14_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress14_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress15_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress15_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress15b_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress15b_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress15c_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress15c_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress100_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress100_tC();
}
BOOST_AUTO_TEST_CASE(test_boosted_tree_stress16_null_tC) {
  boosted_trees_stress_test::test_boosted_tree_stress16_null_tC();
}
BOOST_AUTO_TEST_SUITE_END()


/*****************************************************************************
* Dwarf Mine - The 13-11 Benchmark
*
* Copyright (c) 2013 Bünger, Thomas; Kieschnick, Christian; Kusber,
* Michael; Lohse, Henning; Wuttke, Nikolai; Xylander, Oliver; Yao, Gary;
* Zimmermann, Florian
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*****************************************************************************/

#include <iostream>
#include <fstream>
#include <vector>
#include <stdexcept>
#include <ctime>
#include <random>
#include <map>
#include <matrix/Matrix.h>
#include <matrix/MatrixHelper.h>
#include <boost/lexical_cast.hpp>

using namespace std;

void printUsage();
void printUsage(const string&);

void verifyMatrices(const vector<string>&)
{
    throw runtime_error("Not implemented yet!");
}

function<float()> makeGenerator()
{
    auto distribution = uniform_real_distribution<float> (-100, +100);
    auto engine = mt19937(time(nullptr));
    return bind(distribution, engine);
}

void generateMatrix(const vector<string>& args)
{
    using namespace boost;

    if (args.size() < 3)
        printUsage("<rows> <cols> <out_file>");

    auto rows = lexical_cast<size_t>(args[0]);
    auto cols = lexical_cast<size_t>(args[1]);
    auto outFile = args[2];

    Matrix<float> result(rows, cols);
    auto generator = makeGenerator();
    MatrixHelper::fill(result, generator);
    MatrixHelper::writeMatrixTo(outFile, result);

    cout << "Matrix generated." << endl;
}

void generateMatrixPair(const vector<string>& args)
{
    using namespace boost;

    if (args.size() < 4)
        printUsage("<rows> <middle> <cols> <out_file>");

    auto rows = lexical_cast<size_t>(args[0]);
    auto middle = lexical_cast<size_t>(args[1]);
    auto cols = lexical_cast<size_t>(args[2]);
    auto outFile = args[3];

    Matrix<float> left(rows, middle);
    Matrix<float> right(middle, cols);
    auto generator = makeGenerator();
    MatrixHelper::fill(left, generator);
    MatrixHelper::fill(right, generator);

    ofstream output(outFile, ios_base::binary);

    MatrixHelper::writeMatrixPairTo(output, { left, right });

    cout << "Matrix pair generated." << endl;
}

void convert(const vector<string>& args, bool toBinary)
{
    if (args.size() < 2)
        printUsage("<in_file> <out_file>");

    auto inFlags = ios_base::in;
    auto outFlags = ios_base::out;

    if (toBinary)
        outFlags |= ios_base::binary;
    else
        inFlags |= ios_base::binary;

    ifstream inFile(args[0], inFlags);
    ofstream outFile(args[1], outFlags);

    size_t count(0);

    while (inFile.peek() != EOF && inFile.good() && !inFile.eof())
    {
        if (toBinary)
        {
            Matrix<float> input(MatrixHelper::readMatrixTextFrom(inFile));
            MatrixHelper::writeMatrixTo(outFile, input);
        }
        else
        {
            Matrix<float> input(MatrixHelper::readMatrixFrom(inFile));
            MatrixHelper::writeMatrixTextTo(outFile, input);
        }
        ++count;
    }

    cout << "Converted " << count << " matrices." << endl;
}

map<string, function<void(const vector<string>&)>> subCommands =
{
    { "verify", [](const vector<string>& args)
        {
            verifyMatrices(args);
        }
    },
    { "generate", [](const vector<string>& args)
        {
            generateMatrix(args);
        }
    },
    { "generate_pair", [](const vector<string>& args)
        {
            generateMatrixPair(args);
        }
    },
    {
        "text2bin", [](const vector<string>& args)
        {
            convert(args, true);
        }
    },
    {
        "bin2text", [](const vector<string>& args)
        {
            convert(args, false);
        }
    }
};

void printUsage()
{
    cout
        << "Usage: matrixtool <command> [<args>]" << endl
        << "with <command> being one of:" << endl;

    for (const auto& sc : subCommands)
    {
        cout << "\t" << sc.first << endl;
    }

    exit(1);
}

void printUsage(const string& msg)
{
    cerr << "Usage: " << msg << endl;
    exit(1);
}

int main(int argc, char** argv)
{
    if (argc < 2)
    {
        printUsage();
    }

    vector<string> arguments(argv+2, argv+argc);

    subCommands[argv[1]](arguments);

    return 0;
}


#include <cstdio>
#include <climits>
#include <cfloat>
#include <cstdlib>
#include <cmath>
#include <limits>

#include <NTL/version.h>

using namespace std;

#if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)))

#define GNUC_INTEL (1)

#else

#define GNUC_INTEL (0)

#endif

int val_int(int x);
unsigned int val_uint(unsigned int x);

long val_long(long x);
unsigned long val_ulong(unsigned long x);

size_t val_size_t(size_t x);

double val_double(double x);
long double val_ldouble(double x);

void touch_int(int* x);
void touch_uint(unsigned int* x);

void touch_long(long* x);
void touch_ulong(unsigned long* x);

void touch_size_t(size_t* x);

void touch_double(double* x);
void touch_ldouble(long double* x);

double sum_double(double *x, long n);

double fma_test(double a, double b, double c);
double reassoc_test(double a, double b, double c, double d);

double power2(long k);

long FMADetected(long dp)
{
   double x = power2(0) + power2(dp-1);
   double y = power2(0) + power2(dp-1);

   touch_double(&x);
   touch_double(&y);

   double z = x*y;
   touch_double(&z);
   z = -z;
   touch_double(&z);

   double lo = fma_test(x, y, z);
   return lo != 0;
}

long ReassocDetected(long dp)
{
   double a =  power2(0) + power2(dp-1);
   double b =  power2(0) - power2(dp-1);
   double c =  power2(0) + power2(dp-1);
   double d =  power2(0) + power2(dp-1);

   touch_double(&a);
   touch_double(&b);
   touch_double(&c);
   touch_double(&d);

   double e = reassoc_test(a, b, c, d);
   touch_double(&e);

   double f = power2(dp+1);
   touch_double(&f);

   return e != f;
}

long DoubleRounding(long dp)
{
   double a = power2(dp-1) + 1;
   double b = (power2(dp)-1)/power2(dp+1);

   double vec[2];
   vec[0] = a;
   vec[1] = b;

   double sum = sum_double(vec, 2);

   touch_double(&sum);

   if (sum != a)
      return 1;
   else
      return 0;
}

long DoublePrecision()
{
   double eps, one, res;
   long k;

   one = val_double(1.0);
   eps = val_double(1.0);

   k = 0;

   do {
      double tmp;

      k++;
      eps *= 1.0/2.0;
      tmp = 1.0 + eps;
      touch_double(&tmp);
      res = tmp - one;
   } while (res == eps);

   return k;
}

long LongDoublePrecision()
{
   long double eps, one, res;
   long k;

   one = val_ldouble(1.0);
   eps = val_ldouble(1.0);

   k = 0;

   do {
      long double tmp;

      k++;
      eps *= 1.0/2.0;
      tmp = 1.0 + eps;
      touch_ldouble(&tmp);
      res = tmp - one;
   } while (res == eps && k < 500);

   // if k >= 500, then most likely this is some
   // weird double/double implementation.
   // We also check what numeric_limits says about long doubles.

   if (k >= 500 || !numeric_limits<long double>::is_iec559) k = 0;
   return k;
}

void print2k(FILE *f, long k, long bpl)
{
   long m, l;
   long first;

   if (k <= 0) {
      fprintf(f, "((double) 1.0)");
      return;
   }

   m = bpl - 2;
   first = 1;

   fprintf(f, "(");

   while (k > 0) {
      if (k > m)
         l = m;
      else
         l = k;

      k = k - l;

      if (first)
         first = 0;
      else
         fprintf(f, "*");

      fprintf(f, "((double)(1L<<%ld))", l);
   }

   fprintf(f, ")");
}

void print2k_WD(FILE *f, long k, long bpl)
{
   long m, l;
   long first;

   if (k <= 0) {
      fprintf(f, "(wide_double(1L))");
      return;
   }

   m = bpl - 2;
   first = 1;

   fprintf(f, "(");

   while (k > 0) {
      if (k > m)
         l = m;
      else
         l = k;

      k = k - l;

      if (first)
         first = 0;
      else
         fprintf(f, "*");

      fprintf(f, "(wide_double(1L<<%ld))", l);
   }

   fprintf(f, ")");
}

void print_mul_body(FILE *f, long n1, long k, long fn,
                    long half_flag, long short_flag)
{

  long n, i, chop, r;
  unsigned long mask, mask2;

  if (half_flag)
    n = n1/2;
  else
    n = n1;

  chop = n % k; /* first block */
  if (chop == 0)
    chop = k;
  r = n - k;
  mask = (1UL << k) - 1UL;

  fprintf(f, "\n\n#define NTL_");
  if (half_flag) fprintf(f, "HALF_");
  if (short_flag) fprintf(f, "SHORT_");

  fprintf(f, "BB_MUL_CODE%ld \\\n", fn);

  if (fn > 0) /* Mul1/AddMul1 */
    {
      fprintf(f, "   long i;\\\n");
      fprintf(f, "   _ntl_ulong carry = 0, b;\\\n");
    }
  fprintf(f, "   _ntl_ulong hi, lo, t;\\\n");
  fprintf(f, "   _ntl_ulong A[%ld];\\\n", 1L << k);
  fprintf(f, "   A[0] = 0;\\\n");

  fprintf(f, "   A[1] = a;\\\n");

  for (i = 2; i < (1L << k); i++)
    {
      if (i % 2 == 0)
        fprintf(f, "   A[%ld] = A[%ld] << 1;\\\n", i, i / 2);
      else
        fprintf(f, "   A[%ld] = A[%ld] ^ A[1];\\\n", i, i - 1);
    }

  if (fn > 0)
    {
      fprintf(f, "   for (i = 0; i < sb; i++) {\\\n");
      fprintf(f, "      b = bp[i];\\\n");
      fprintf(f, "   ");
    }

  fprintf(f, "   lo = A[b & %lu]; ", mask);
  fprintf(f, "t = A[(b >> %ld) & %lu]; ", k, mask);
  fprintf(f, "hi = t >> %ld; lo ^= t << %ld;\\\n", n1-k, k);

  for (i = 2*k; i < n - chop; i += k) {
    if (fn > 0) fprintf(f, "   ");
    fprintf(f, "   t = A[(b >> %ld) & %lu]; ", i, mask);
    fprintf(f, "hi ^= t >> %ld; lo ^= t << %ld;\\\n", n1-i, i);
  }

  if (fn > 0) fprintf(f, "   ");
  fprintf(f, "   t = A[b >> %ld]; ", n-chop);
  fprintf(f, "hi ^= t >> %ld; lo ^= t << %ld;\\\n", n1-i, i);

  mask = 0;
  for (i = 0; i < n; i += k)
    mask |= 1UL << i;
  mask = ~mask;
  if (half_flag) mask &= (1UL << n) - 1UL;
  mask2 = mask;

  if (!short_flag) {
    for (i = 1; i < k; i++)
      {
	if (fn > 0) fprintf(f, "   ");

        if (i == 1)
	   fprintf(f, "   if (a >> %ld) ", n1-i);
        else
           fprintf(f, "   if ((a >> %ld) & 1) ", n1-i);

	/* bit n1-i from a was not considered in blocks of
           k bits from b for index j >= i */
	fprintf(f, "hi ^= ((b & 0x%lxUL) >> %ld);\\\n", mask2, i);
	mask2 = (mask2 << 1) & mask;
      }
   }

  if (fn > 0) fprintf(f, "   ");

  if (fn == 0)
    {
      fprintf(f, "   c[0] = lo; ");
      fprintf(f, "   c[1] = hi;\\\n");
    }
  else if (fn == 1 || fn == 3)
    {
      fprintf(f, "   cp[i] = carry ^ lo; ");
      fprintf(f, "   carry = hi;\\\n");
    }
  else if (fn == 2)
    {
      fprintf(f, "   cp[i] ^= (carry ^ lo); ");
      fprintf(f, "   carry = hi;\\\n");
    }
  if (fn > 0)
    {

      fprintf(f, "   }\\\n");

      if (fn == 1 || fn == 3)
        fprintf(f, "   cp[sb] = carry;\\\n");
      else
        fprintf(f, "   cp[sb] ^= carry;\\\n");

    }
  fprintf(f, "\n\n\n");

}

/*
 * This generates anternative code that runs significantly faster
 * on some machines, like a PowerPC (and probably other RISC machines).
 * It makes it easier for the compiler to schedule instrucyions better,
 * and it avoids branches.  It seems like this does not help
 * on x86 machines (and can even make things worse).
 */

void print_alt_mul_body(FILE *f, long n1, long k, long fn,
                        long half_flag, long short_flag)
{

  long n, i, chop, r;
  unsigned long mask, mask2;

  if (half_flag)
    n = n1/2;
  else
    n = n1;

  chop = n % k; /* first block */
  if (chop == 0)
    chop = k;
  r = n - k;
  mask = (1UL << k) - 1UL;

  fprintf(f, "\n\n#define NTL_ALT_");
  if (half_flag) fprintf(f, "HALF_");
  if (short_flag) fprintf(f, "SHORT_");

  fprintf(f, "BB_MUL_CODE%ld \\\n", fn);

  if (fn > 0) /* Mul1/AddMul1 */
    {
      fprintf(f, "   long i;\\\n");
      fprintf(f, "   _ntl_ulong carry = 0;\\\n");
    }
  fprintf(f, "   _ntl_ulong A[%ld];\\\n", 1L << k);
  fprintf(f, "   A[0] = 0;\\\n");

  fprintf(f, "   A[1] = a;\\\n");

  for (i = 2; i < (1L << k); i++)
    {
      if (i % 2 == 0)
        fprintf(f, "   A[%ld] = A[%ld] << 1;\\\n", i, i / 2);
      else
        fprintf(f, "   A[%ld] = A[%ld] ^ A[1];\\\n", i, i - 1);
    }

  if (fn > 0)
    {
      fprintf(f, "   for (i = 0; i < sb; i++) {\\\n");
      fprintf(f, "      const _ntl_ulong b = bp[i];\\\n");
    }

  for (i = k; i < n - chop; i += k) {
    if (fn > 0) fprintf(f, "   ");
    fprintf(f, "   const _ntl_ulong t%ld = A[(b >> %ld) & %lu]; \\\n", i, i, mask);

  }
  if (fn > 0) fprintf(f, "   ");
  fprintf(f, "   const _ntl_ulong t%ld = A[b >> %ld]; \\\n", n-chop, n-chop);

  if (fn > 0) fprintf(f, "   ");
  fprintf(f, "   const _ntl_ulong lo = A[b & %lu] \\\n", mask);

  for (i = k; i < n; i += k) {
    if (fn > 0) fprintf(f, "   ");
    fprintf(f, "      ^ (t%ld << %ld)", i, i);
    if (i == n - chop)
      fprintf(f, ";\\\n");
    else
      fprintf(f, "\\\n");
  }

  for (i = k; i < n; i += k) {
    if (fn > 0) fprintf(f, "   ");
    if (i == k)
      fprintf(f, "   const _ntl_ulong hi = ");
    else
      fprintf(f, "      ^ ");
    fprintf(f, "(t%ld >> %ld)", i, n1-i);
    if (i == n - chop && short_flag)
      fprintf(f, ";\\\n");
    else
      fprintf(f, "\\\n");

  }

  mask = 0;
  for (i = 0; i < n; i += k)
    mask |= 1UL << i;
  mask = ~mask;
  if (half_flag) mask &= (1UL << n) - 1UL;
  mask2 = mask;

  if (!short_flag) {
    for (i = 1; i < k; i++)
      {

        /* bit n1-i from a was not considered in blocks of
           k bits from b for index j >= i */

        if (fn > 0) fprintf(f, "   ");

        if (i == 1)
          fprintf(f,
             "      ^ (((b & 0x%lxUL) >> %ld) & (-(a >> %ld)))",
        	mask2, i, n1-1);
        else {
          fprintf(f,
             "      ^ (((b & 0x%lxUL) >> %ld) & (-((a >> %ld) & 1UL)))",
                  mask2, i, n1-i);
        }
        if (i == k-1)
           fprintf(f, ";\\\n");
        else
            fprintf(f, "\\\n");

	mask2 = (mask2 << 1) & mask;
      }
   }

  if (fn > 0) fprintf(f, "   ");

  if (fn == 0)
    {
      fprintf(f, "   c[0] = lo; ");
      fprintf(f, "   c[1] = hi;\\\n");
    }
  else if (fn == 1)
    {
      fprintf(f, "   cp[i] = carry ^ lo; ");
      fprintf(f, "   carry = hi;\\\n");
    }
  else if (fn == 2)
    {
      fprintf(f, "   cp[i] ^= (carry ^ lo); ");
      fprintf(f, "   carry = hi;\\\n");
    }
  if (fn > 0)
    {

      fprintf(f, "   }\\\n");

      if (fn == 1 || fn == 3)
        fprintf(f, "   cp[sb] = carry;\\\n");
      else
        fprintf(f, "   cp[sb] ^= carry;\\\n");

    }
  fprintf(f, "\n\n\n");

}

void print_alt1_mul_body(FILE *f, long n1, long k, long fn,
                    long half_flag, long short_flag)
{

  long n, i, chop, r;
  unsigned long mask, mask2;

  if (half_flag)
    n = n1/2;
  else
    n = n1;

  chop = n % k; /* first block */
  if (chop == 0)
    chop = k;
  r = n - k;
  mask = (1UL << k) - 1UL;

  fprintf(f, "\n\n#define NTL_ALT1_");
  if (half_flag) fprintf(f, "HALF_");
  if (short_flag) fprintf(f, "SHORT_");

  fprintf(f, "BB_MUL_CODE%ld \\\n", fn);

  if (fn > 0) /* Mul1/AddMul1 */
    {
      fprintf(f, "   long i;\\\n");
      fprintf(f, "   _ntl_ulong carry = 0, b;\\\n");
    }
  fprintf(f, "   _ntl_ulong hi, lo, t;\\\n");
  fprintf(f, "   _ntl_ulong A[%ld];\\\n", 1L << k);
  fprintf(f, "   A[0] = 0;\\\n");

  fprintf(f, "   A[1] = a;\\\n");

  for (i = 2; i < (1L << k); i++)
    {
      if (i % 2 == 0)
        fprintf(f, "   A[%ld] = A[%ld] << 1;\\\n", i, i / 2);
      else
        fprintf(f, "   A[%ld] = A[%ld] ^ A[1];\\\n", i, i - 1);
    }

  if (fn > 0)
    {
      fprintf(f, "   for (i = 0; i < sb; i++) {\\\n");
      fprintf(f, "      b = bp[i];\\\n");
      fprintf(f, "   ");
    }

  fprintf(f, "   lo = A[b & %lu]; ", mask);
  fprintf(f, "t = A[(b >> %ld) & %lu]; ", k, mask);
  fprintf(f, "hi = t >> %ld; lo ^= t << %ld;\\\n", n1-k, k);

  for (i = 2*k; i < n - chop; i += k) {
    if (fn > 0) fprintf(f, "   ");
    fprintf(f, "   t = A[(b >> %ld) & %lu]; ", i, mask);
    fprintf(f, "hi ^= t >> %ld; lo ^= t << %ld;\\\n", n1-i, i);
  }

  if (fn > 0) fprintf(f, "   ");
  fprintf(f, "   t = A[b >> %ld]; ", n-chop);
  fprintf(f, "hi ^= t >> %ld; lo ^= t << %ld;\\\n", n1-i, i);

  mask = 0;
  for (i = 0; i < n; i += k)
    mask |= 1UL << i;
  mask = ~mask;
  if (half_flag) mask &= (1UL << n) - 1UL;
  mask2 = mask;

  if (!short_flag) {
    for (i = 1; i < k; i++)
      {

        /* bit n1-i from a was not considered in blocks of
           k bits from b for index j >= i */

        if (fn > 0) fprintf(f, "   ");

        if (i == 1)
          fprintf(f,
             "   hi ^= (((b & 0x%lxUL) >> %ld) & (-(a >> %ld)))",
        	mask2, i, n1-1);
        else {
          fprintf(f,
             "      ^ (((b & 0x%lxUL) >> %ld) & (-((a >> %ld) & 1UL)))",
                  mask2, i, n1-i);
        }
        if (i == k-1)
           fprintf(f, ";\\\n");
        else
            fprintf(f, "\\\n");

	mask2 = (mask2 << 1) & mask;
      }
   }

  if (fn > 0) fprintf(f, "   ");

  if (fn == 0)
    {
      fprintf(f, "   c[0] = lo; ");
      fprintf(f, "   c[1] = hi;\\\n");
    }
  else if (fn == 1 || fn == 3)
    {
      fprintf(f, "   cp[i] = carry ^ lo; ");
      fprintf(f, "   carry = hi;\\\n");
    }
  else if (fn == 2)
    {
      fprintf(f, "   cp[i] ^= (carry ^ lo); ");
      fprintf(f, "   carry = hi;\\\n");
    }
  if (fn > 0)
    {

      fprintf(f, "   }\\\n");

      if (fn == 1 || fn == 3)
        fprintf(f, "   cp[sb] = carry;\\\n");
      else
        fprintf(f, "   cp[sb] ^= carry;\\\n");

    }
  fprintf(f, "\n\n\n");

}

void print_BB_mul_code(FILE *f, long n)
{
   long k;

   if (n >= 64)
      k = 4;
   else
      k = 3;

   print_mul_body(f, n, k, 0, 0, 0);
   print_mul_body(f, n, 4, 1, 0, 0);
   print_mul_body(f, n, 4, 2, 0, 0);
   print_mul_body(f, n, 4, 1, 0, 1);
   print_mul_body(f, n, 2, 0, 1, 0);

   print_alt_mul_body(f, n, k, 0, 0, 0);
   print_alt_mul_body(f, n, 4, 1, 0, 0);
   print_alt_mul_body(f, n, 4, 2, 0, 0);
   print_alt_mul_body(f, n, 4, 1, 0, 1);
   print_alt_mul_body(f, n, 2, 0, 1, 0);

   print_alt1_mul_body(f, n, k, 0, 0, 0);
   print_alt1_mul_body(f, n, 4, 1, 0, 0);
   print_alt1_mul_body(f, n, 4, 2, 0, 0);
   print_alt1_mul_body(f, n, 4, 1, 0, 1);
   print_alt1_mul_body(f, n, 2, 0, 1, 0);

   fprintf(f, "#define NTL_BB_MUL1_BITS (4)\n\n");
}

void print_BB_sqr_code(FILE *f, long n)
{
   long i, pos;

   fprintf(f, "\n\n");
   fprintf(f, "#define NTL_BB_SQR_CODE \\\n");
   fprintf(f, "lo=sqrtab[a&255];\\\n");
   pos = 16;

   for (i = 8; i < n; i += 8) {
      if (2*(i+8) <= n) {
         fprintf(f, "lo=lo|(sqrtab[(a>>%ld)&255]<<%ld);\\\n", i, pos);
         pos += 16;
      }
      else if (2*i == n) {
         fprintf(f, "hi=sqrtab[(a>>%ld)&255];\\\n", i);
         pos = 16;
      }
      else if (2*i > n) {
         fprintf(f, "hi=hi|(sqrtab[(a>>%ld)&255]<<%ld);\\\n", i, pos);
         pos += 16;
      }
      else { /* only applies if word size is not a multiple of 16 */
         fprintf(f, "_ntl_ulong t=sqrtab[(a>>%ld)&255];\\\n", i);
         fprintf(f, "lo=lo|(t<<%ld);\\\n", pos);
         fprintf(f, "hi=t>>%ld;\\\n", n-8);
         pos = 8;
      }
   }

   fprintf(f, "\n\n");
}

void print_BB_rev_code(FILE *f, long n)
{
   long i;

   fprintf(f, "\n\n");
   fprintf(f, "#define NTL_BB_REV_CODE ");

   for (i = 0; i < n; i += 8) {
      if (i != 0) fprintf(f, "\\\n|");
      fprintf(f, "(revtab[(a>>%ld)&255]<<%ld)", i, n-8-i);
   }

   fprintf(f, "\n\n");
}

const char *yn_vec[2] = { "no", "yes" };

int main()
{
   long bpl, bpi, bpt, rs_arith, nbits, wnbits;
   long nb_bpl;
   long dp, dr;
   long fma_detected;
   long reassoc_detected;
   long big_pointers;
   long ldp;
   FILE *f;
   long warnings = 0;

   unsigned long ulval;
   unsigned int uival;
   size_t tval;
   long slval;

   fprintf(stderr, "This is NTL version %s\n", NTL_VERSION);

   /*
    * compute bpl =  bits per long
    */

   ulval = val_ulong(1);
   bpl = 0;

   while (ulval) {
      ulval <<= 1;
      touch_ulong(&ulval);
      bpl++;
   }

   /*
    * compute nb_bpl = NumBits(bpl)
    */

   ulval = bpl;
   nb_bpl = 0;
   while (ulval) {
      ulval = ulval >> 1;
      nb_bpl++;
   }

   /*
    * compute  bpi = bits per int
    */

   uival = val_uint(1);
   bpi = 0;

   while (uival) {
      uival <<= 1;
      touch_uint(&uival);
      bpi++;
   }

   /*
    * compute bpt = bits per size_t
    */

   tval = val_size_t(1);
   bpt = 0;

   while (tval) {
      tval <<= 1;
      touch_size_t(&tval);
      bpt++;
   }

   /*
    * check if bpl and bpi are not too small --- any standard conforming
    * platform should pass this test.
    */

   if (bpi < 16) {
      fprintf(stderr, "BAD NEWS: int type too short.\n");
      return 1;
   }

   if (bpl < 32) {
      fprintf(stderr, "BAD NEWS: long type too short.\n");
      return 1;
   }

   /*
    * check that there are 8 bits in a char. This is a POSIX requirement.
    */

   if (CHAR_BIT != 8) {
      fprintf(stderr, "BAD NEWS: char type must have 8 bits.\n");
      return 1;
   }

   /*
    * check that bpi is a multiple of 8.
    */

   if (bpi % 8 != 0) {
      fprintf(stderr, "BAD NEWS: int type must be multiple of 8 bits.\n");
      return 1;
   }

   /*
    * check that bpl is a multiple of 8.
    */

   if (bpl % 8 != 0) {
      fprintf(stderr, "BAD NEWS: long type must be multiple of 8 bits.\n");
      return 1;
   }

   /*
    * check if width of signed versions of int and long agree with that of
    * the unsigned versions, and that negative numbers are represented
    * using 2's compliment.
    *
    * The C99 standard, at least, is very precise about the possible
    * representations of unsigned and signed integer types, and so if
    * the following tests pass, we can be sure that the desired
    * properties hold.  NTL relies implicitly and crucially on
    * these properties.
    *
    * I know of no machines for which these properties do not hold.
    */

   if (((unsigned int) val_int(INT_MIN)) != val_uint(1U << (bpi-1))) {
      fprintf(stderr, "BAD NEWS: machine must be 2's compliment.\n");
      return 1;
   }

   if (((unsigned int) val_int(INT_MAX)) != val_uint((1U << (bpi-1)) - 1U)) {
      fprintf(stderr, "BAD NEWS: machine must be 2's compliment.\n");
      return 1;
   }

   if (((unsigned long) val_long(LONG_MIN)) != val_ulong(1UL << (bpl-1))) {
      fprintf(stderr, "BAD NEWS: machine must be 2's compliment.\n");
      return 1;
   }

   if (((unsigned long) val_long(LONG_MAX)) != val_ulong((1UL<<(bpl-1))-1UL)) {
      fprintf(stderr, "BAD NEWS: machine must be 2's compliment.\n");
      return 1;
   }

   /*
    * check that floating point to integer conversions truncates toward zero
    * --- any standard conforming platform should pass this test.
    */

   if (((long) val_double(1.75)) != 1L) {
      fprintf(stderr,
         "BAD NEWS: machine must truncate floating point toward zero.\n");
      return 1;
   }

   if (((long) val_double(-1.75)) != -1L) {
      fprintf(stderr,
         "BAD NEWS: machine must truncate floating point toward zero.\n");
      return 1;
   }

   /*
    * Test if right shift is arithemtic or not.  According to the
    * standards, the result of right-shifting a negative number is
    * "implementation defined", which almost surely means the right shift
    * is *always* arithmetic or *always* logical.  However, this cannot
    * be guaranteed, and so this test is *not* 100% portable --- but I
    * know of no machine for which this test does not correctly
    * predict the general behavior.  One should set the NTL_CLEAN_INT
    * flag if one wants to avoid such machine dependencies.
    */

   slval = val_long(-1);
   if ((slval >> 1) == slval)
      rs_arith = 1;
   else
      rs_arith = 0;

   /*
    * Next, we check some properties of floating point arithmetic.
    * An implementation should conform to the IEEE floating
    * point standard --- essentially all modern platforms do,
    * except for a few very old Cray's.  There is no easy way
    * to check this, so we simply make a few simple sanity checks,
    * calculate the precision, and if the platform performs
    * double precision arithemtic in extended double precision registers.
    * The last property is one that the IEE standard allows, and which
    * some important platforms (like x86) have --- this is quite
    * unfortunate, as it really makes many of the other properties
    * of the IEEE standard unusable.
    */

   /*
    * First, we simply check that we are using a machine with radix 2.
    */

   if (FLT_RADIX != 2) {
      fprintf(stderr, "BAD NEWS: machine must use IEEE floating point.\n");
      return 1;
   }

   /*
    * Next, we calculate the precision of "in memory" doubles,
    * and check that it is at least 53.
    */

   dp = DoublePrecision();

   if (dp < 53) {
      fprintf(stderr, "BAD NEWS: machine must use IEEE floating point (*).\n");
      return 1;
   }

   /*
    * Next, we check that the *range* of doubles is sufficiently large.
    * Specifically, we require that DBL_MAX > 2^{7*max(bpl, dp)}
    * and 1/DBL_MIN > 2^{7*max(bpl, dp)}.
    * On IEEE floating point compliant machines, this
    * will hold, and the following test will pass, if bpl is at most 128, which
    * should be true for the foreseeable future.
    */

   if (log(DBL_MAX)/log(2.0) < 7.01*bpl ||  log(DBL_MAX)/log(2.0) < 7.01*dp ||
      -log(DBL_MIN)/log(2.0) < 7.01*bpl || -log(DBL_MIN)/log(2.0) < 7.01*dp) {
      fprintf(stderr, "BAD NEWS: range of doubles too small.\n");
      return 1;
   }

   /*
    * Next, we check if the machine has wider "in-register" doubles or not.
    * This test almost always yields the correct result --- if not,
    * you will have to set the NTL_EXT_DOUBLE in "mach_desc.h"
    * by hand.
    */

   dr = DoubleRounding(dp);

   /*
    * Next, we check if the platform uses FMA (fused multiply add),
    * even across statement boundaries.
    */

   fma_detected = FMADetected(dp);

   /*
    * Next, we check if the platform may reassociate FP operations.
    */

   reassoc_detected = ReassocDetected(dp);

   /*
    * Next, we test the precision of long doubles.
    * If long doubles don't look good or useful, ldp == 0.
    * Right now, we ony enable long double usage on Intel/gcc
    * platforms.
    */

   ldp = LongDoublePrecision();
   if (ldp <= dp || !GNUC_INTEL) ldp = 0;

   // Disable if it looks like rounding doesn't work right
   if (((long) val_ldouble(1.75)) != 1L) ldp = 0;
   if (((long) val_ldouble(-1.75)) != -1L) ldp = 0;

   if (reassoc_detected) {
      fprintf(stderr, "BAD NEWS: Floating point reassociation detected.\n");
      fprintf(stderr, "Do not use -Ofast, -ffast-math.\n");
      return 1;
   }

   /*
    * Set nbits = min(bpl-2, dp-3) [and even]

    *  Given the minimum size of blp and dp, the smallest possible
    *  value of nbits is 30.
    */

   if (bpl-2 < dp-3)
      nbits = bpl-2;
   else
      nbits = dp-3;

   if (nbits % 2 != 0) nbits--;

   /*
    * Set wnbits = min(bpl-2, ldp-2) [and even]
    */

   if (ldp) {
      if (bpl-2 < ldp-2)
         wnbits = bpl-2;
      else
         wnbits = ldp-2;

      if (wnbits % 2 != 0) wnbits--;
   }
   else {
      wnbits = nbits;
   }

   if (wnbits <= nbits) ldp = 0;
   // disable long doubles if it doesn't increase nbits...
   // (for example, on 32-bit machines)

   big_pointers = 0;
   if (sizeof(char*) > sizeof(long)) big_pointers = 1;

   /*
    * That's it!  All tests have passed.
    */

   fprintf(stderr, "\n*** GOOD NEWS: compatible machine.\n");
   fprintf(stderr, "summary of machine characteristics:\n");
   fprintf(stderr, "bits per long = %ld\n", bpl);
   fprintf(stderr, "bits per int = %ld\n", bpi);
   fprintf(stderr, "bits per size_t = %ld\n", bpt);
   fprintf(stderr, "arith right shift = %s\n", yn_vec[rs_arith]);
   fprintf(stderr, "double precision = %ld\n", dp);
   fprintf(stderr, "long double precision = %ld\n", ldp);
   fprintf(stderr, "NBITS (maximum) = %ld\n", nbits);
   fprintf(stderr, "WNBITS (maximum) = %ld\n", wnbits);
   fprintf(stderr, "double rounding detected = %s\n", yn_vec[dr]);
   fprintf(stderr, "FMA detected = %s\n", yn_vec[fma_detected]);
   fprintf(stderr, "big pointers = %s\n", yn_vec[big_pointers]);

   if (dr && GNUC_INTEL)
      fprintf(stderr, "-- auto x86 fix\n");

   if (dp != 53) {
      warnings = 1;

      fprintf(stderr, "\n*** WARNING :\n");
      fprintf(stderr, "Nonstandard floating point precision.\n");
      fprintf(stderr, "IEEE standard is 53 bits.\n");
   }

#if (defined(__sparc__) && !defined(__sparc_v8__) && \
    !defined(__sparcv8) && !defined(__sparc_v9__) && !defined(__sparcv9))

   warnings = 1;

   fprintf(stderr, "\n*** WARNING :\n");
   fprintf(stderr, "If this Sparc is a Sparc-10 or later (so it has\n");
   fprintf(stderr, "a hardware integer multiply instruction) you\n");
   fprintf(stderr, "should specify the -mv8 option in the makefile\n");
   fprintf(stderr, "to obtain more efficient code.\n");

#endif

   if (dr && !GNUC_INTEL) {
      warnings = 1;
      fprintf(stderr, "\n*** WARNING :\n");
      fprintf(stderr, "This platform has extended double precision registers.\n");
      fprintf(stderr, "While that may sound like a good thing, it actually is not.\n");
      fprintf(stderr, "If this is a Pentium or other x86 and your compiler\n");
      fprintf(stderr, "is g++ or supports GNU 'asm' constructs, it is recommended\n");
      fprintf(stderr, "to compile NTL with the NTL_X86_FIX flag to get true IEEE floating point.\n");
      fprintf(stderr, "Set this flag by editing the file config.h.\n");
      fprintf(stderr, "The code should still work even if you don't set\n");
      fprintf(stderr, "this flag.  See quad_float.txt for details.\n\n");
   }

#if 0

   /* better not to be interactive */

   if (warnings) {
      int c;

      fprintf(stderr, "Do you want to continue anyway[y/n]? ");
      c = getchar();
      if (c == 'n' || c == 'N') {
         fprintf(stderr, "Make the necessary changes to the makefile and/or config.h,\n");
         fprintf(stderr, "then type 'make clobber' and then 'make'.\n\n\n");
         return 1;
      }
   }

#else

   if (warnings) {
      fprintf(stderr, "\n\n");
      fprintf(stderr, "********************************************************\n");
      fprintf(stderr, "********************************************************\n");
      fprintf(stderr, "****         !!! SEE WARNINGS ABOVE !!!             ****\n");
      fprintf(stderr, "********************************************************\n");
      fprintf(stderr, "********************************************************\n");
      fprintf(stderr, "\n\n");
   }

#endif

   f = fopen("mach_desc.h", "w");
   if (!f) {
      fprintf(stderr, "can't open mach_desc.h for writing\n");
      return 1;
   }

   fprintf(f, "#ifndef NTL_mach_desc__H\n");
   fprintf(f, "#define NTL_mach_desc__H\n\n\n");
   fprintf(f, "#define NTL_BITS_PER_LONG (%ld)\n", bpl);
   fprintf(f, "#define NTL_NUMBITS_BPL (%ld)\n", nb_bpl);
   fprintf(f, "#define NTL_MAX_LONG (%ldL)\n", ((long) ((1UL<<(bpl-1))-1UL)));
   fprintf(f, "#define NTL_MAX_INT (%ld)\n", ((long) ((1UL<<(bpi-1))-1UL)));
   fprintf(f, "#define NTL_BITS_PER_INT (%ld)\n", bpi);
   fprintf(f, "#define NTL_BITS_PER_SIZE_T (%ld)\n", bpt);
   fprintf(f, "#define NTL_ARITH_RIGHT_SHIFT (%ld)\n", rs_arith);
   fprintf(f, "#define NTL_NBITS_MAX (%ld)\n", nbits);
   fprintf(f, "#define NTL_WNBITS_MAX (%ld)\n", wnbits);
   fprintf(f, "#define NTL_DOUBLE_PRECISION (%ld)\n", dp);
   fprintf(f, "#define NTL_FDOUBLE_PRECISION ");
   print2k(f, dp-1, bpl);
   fprintf(f, "\n");

   if (ldp) {
      fprintf(f, "#define NTL_LONGDOUBLE_OK (1)\n");
      fprintf(f, "#define NTL_LONGDOUBLE_PRECISION (%ld)\n", ldp);
      fprintf(f, "#define NTL_WIDE_DOUBLE_LDP ");
      print2k_WD(f, ldp-1, bpl);
      fprintf(f, "\n");
      fprintf(f, "#define NTL_WIDE_DOUBLE_DP ");
      print2k_WD(f, dp-1, bpl);
      fprintf(f, "\n");
   }
   else {
      fprintf(f, "#define NTL_LONGDOUBLE_OK (0)\n");
      fprintf(f, "#define NTL_WIDE_DOUBLE_DP ");
      print2k_WD(f, dp-1, bpl);
      fprintf(f, "\n");
   }

   fprintf(f, "#define NTL_QUAD_FLOAT_SPLIT (");
   print2k(f, dp - (dp/2), bpl);
   fprintf(f, "+1.0)\n");
   fprintf(f, "#define NTL_EXT_DOUBLE (%ld)\n", dr);

   fprintf(f, "#define NTL_FMA_DETECTED (%ld)\n", fma_detected);
   fprintf(f, "#define NTL_BIG_POINTERS (%ld)\n", big_pointers);

   fprintf(f, "#define NTL_MIN_LONG (-NTL_MAX_LONG - 1L)\n");
   fprintf(f, "#define NTL_MIN_INT  (-NTL_MAX_INT - 1)\n");

   print_BB_mul_code(f, bpl);
   print_BB_sqr_code(f, bpl);
   print_BB_rev_code(f, bpl);

   fprintf(f, "#endif\n\n");

   fclose(f);

   fprintf(stderr, "\n\n");

   return 0;
}


#ifndef _WIN32
#define BOOST_TEST_DYN_LINK
#endif
#define BOOST_TEST_MODULE generator
#include <boost/test/unit_test.hpp>

#include "../bf/generator.h"
#include "../bf/interpreter.h"

template <typename memory_type = unsigned char>
void bfg_check(const std::string &program, const std::string &description,
        const std::vector<memory_type> &input, const std::vector<memory_type> &expected_output)
{
    bf::interpreter<memory_type> test(program);
    test.send_input(input);
    test.run();
    const auto received_output = test.recv_output();

    BOOST_CHECK_MESSAGE(std::equal(expected_output.begin(), expected_output.end(),
                                   received_output.begin(), received_output.end()),
                        "Unexpected result after processing '" + description + "'!");

    // Ensure correct SP movement
    BOOST_CHECK(test.get_memory().at(0) == 1);
    BOOST_CHECK(test.get_stack_pointer() == 0);

    BOOST_TEST_MESSAGE("----- Results for '" + description + "' -----");
    std::string output_int;
    for (auto v : received_output)
        output_int += " " + std::to_string(v);
    BOOST_TEST_MESSAGE("Received output (as int):" + output_int);
    BOOST_TEST_MESSAGE("Memory used: " + std::to_string(test.get_memory().size()));
}

// ----- bf::var::add(unsigned) ------------------------------------------------
BOOST_AUTO_TEST_CASE(var__add_unsigned) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->add(3);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "2 + 3 == 5", {2}, {5});
    bfg_check(program, "7 + 3 == 10", {7}, {10});
}

// ----- bf::var::subtract(unsigned) -------------------------------------------
BOOST_AUTO_TEST_CASE(var__subtract_unsigned) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->subtract(2);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "5 - 2 == 3", {5}, {3});
    bfg_check(program, "10 - 2 == 8", {10}, {8});
}

// ----- bf::var::multiply(unsigned) -------------------------------------------
BOOST_AUTO_TEST_CASE(var__multiply_unsigned) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->multiply(3);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "5 * 3 == 15", {5}, {15});
    bfg_check(program, "8 * 3 == 24", {8}, {24});
}

// ----- bf::var::copy(const var&) ---------------------------------------------
BOOST_AUTO_TEST_CASE(var__copy) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->copy(*a);
        b->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "Simple copy instruction", {5}, {5});
}

// ----- bf::var::add(const var&) ----------------------------------------------
BOOST_AUTO_TEST_CASE(var__add) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->add(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "3 + 4 == 7", {3, 4}, {7});
    bfg_check(program, "5 + 8 == 13", {5, 8}, {13});
}

// ----- bf::var::add(const var&) ----------------------------------------------
BOOST_AUTO_TEST_CASE(var__add_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->add(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "3 + 3 == 6", {3}, {6});
    bfg_check(program, "5 + 5 == 10", {5}, {10});
}

// ----- bf::var::subtract(const var&) -----------------------------------------
BOOST_AUTO_TEST_CASE(var__subtract) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->subtract(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "5 - 3 == 2", {5, 3}, {2});
    bfg_check(program, "20 - 7 == 13", {20, 7}, {13});
}

// ----- bf::var::subtract(const var&) -----------------------------------------
BOOST_AUTO_TEST_CASE(var__subtract_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->subtract(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "3 - 3 == 0", {3}, {0});
    bfg_check(program, "10 - 10 == 0", {10}, {0});
}

// ----- bf::var::multiply(const var&) -----------------------------------------
BOOST_AUTO_TEST_CASE(var__multiply) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->multiply(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "5 * 3 == 15", {5, 3}, {15});
    bfg_check(program, "4 * 8 == 32", {4, 8}, {32});
}

// ----- bf::var::multiply(const var&) -----------------------------------------
BOOST_AUTO_TEST_CASE(var__multiply_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->multiply(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "5 * 5 == 25", {5}, {25});
    bfg_check(program, "7 * 7 == 49", {7}, {49});
}

// ----- bf::var::bool_not(const var&) -----------------------------------------
BOOST_AUTO_TEST_CASE(var__bool_not) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->bool_not(*a);
        b->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "!0 == 1", {0}, {1});
    bfg_check(program, "!1 == 0", {1}, {0});
    bfg_check(program, "!7 == 0", {7}, {0});
}

// ----- bf::var::bool_not(const var&) -----------------------------------------
BOOST_AUTO_TEST_CASE(var__bool_not_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->bool_not(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "!0 == 1", {0}, {1});
    bfg_check(program, "!1 == 0", {1}, {0});
    bfg_check(program, "!7 == 0", {7}, {0});
}

// ----- bf::var::bool_and(const var&) -----------------------------------------
BOOST_AUTO_TEST_CASE(var__bool_and) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->bool_and(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "0 && 0 == 0", {0, 0}, {0});
    bfg_check(program, "0 && 1 == 0", {0, 1}, {0});
    bfg_check(program, "1 && 0 == 0", {1, 0}, {0});
    bfg_check(program, "1 && 1 == 1", {1, 1}, {1});
    bfg_check(program, "3 && 5 == 1", {3, 5}, {1});
}

// ----- bf::var::bool_and(const var&) -----------------------------------------
BOOST_AUTO_TEST_CASE(var__bool_and_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->bool_and(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "0 && 0 == 0", {0}, {0});
    bfg_check(program, "1 && 1 == 1", {1}, {1});
}

// ----- bf::var::bool_or(const var&) ------------------------------------------
BOOST_AUTO_TEST_CASE(var__bool_or) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->bool_or(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "0 || 0 == 0", {0, 0}, {0});
    bfg_check(program, "0 || 1 == 1", {0, 1}, {1});
    bfg_check(program, "1 || 0 == 1", {1, 0}, {1});
    bfg_check(program, "1 || 1 == 1", {1, 1}, {1});
    bfg_check(program, "3 || 0 == 1", {3, 0}, {1});
}

// ----- bf::var::bool_or(const var&) ------------------------------------------
BOOST_AUTO_TEST_CASE(var__bool_or_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->bool_or(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "0 || 0 == 0", {0}, {0});
    bfg_check(program, "1 || 1 == 1", {1}, {1});
}

// ----- bf::var::lower_than(const var&) ---------------------------------------
BOOST_AUTO_TEST_CASE(var__lower_than) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->lower_than(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(4 < 5) == 1", {4, 5}, {1});
    bfg_check(program, "(5 < 5) == 0", {5, 5}, {0});
    bfg_check(program, "(5 < 4) == 0", {5, 4}, {0});
}

// ----- bf::var::lower_than(const var&) ---------------------------------------
BOOST_AUTO_TEST_CASE(var__lower_than_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->lower_than(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(0 < 0) == 0", {0, 0}, {0});
    bfg_check(program, "(3 < 3) == 0", {3, 3}, {0});
}

// ----- bf::var::lower_equal(const var&) --------------------------------------
BOOST_AUTO_TEST_CASE(var__lower_equal) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->lower_equal(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(4 <= 5) == 1", {4, 5}, {1});
    bfg_check(program, "(5 <= 5) == 1", {5, 5}, {1});
    bfg_check(program, "(5 <= 4) == 0", {5, 4}, {0});
}

// ----- bf::var::lower_equal(const var&) --------------------------------------
BOOST_AUTO_TEST_CASE(var__lower_equal_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->lower_equal(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(0 <= 0) == 1", {0, 0}, {1});
    bfg_check(program, "(3 <= 3) == 1", {3, 3}, {1});
}

// ----- bf::var::greater_than(const var&) -------------------------------------
BOOST_AUTO_TEST_CASE(var__greater_than) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->greater_than(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(4 > 5) == 0", {4, 5}, {0});
    bfg_check(program, "(5 > 5) == 0", {5, 5}, {0});
    bfg_check(program, "(5 > 4) == 1", {5, 4}, {1});
}

// ----- bf::var::greater_than(const var&) -------------------------------------
BOOST_AUTO_TEST_CASE(var__greater_than_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->greater_than(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(0 > 0) == 0", {0, 0}, {0});
    bfg_check(program, "(3 > 3) == 0", {3, 3}, {0});
}

// ----- bf::var::greater_equal(const var&) ------------------------------------
BOOST_AUTO_TEST_CASE(var__greater_equal) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->greater_equal(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(4 >= 5) == 0", {4, 5}, {0});
    bfg_check(program, "(5 >= 5) == 1", {5, 5}, {1});
    bfg_check(program, "(5 >= 4) == 1", {5, 4}, {1});
}

// ----- bf::var::greater_equal(const var&) ------------------------------------
BOOST_AUTO_TEST_CASE(var__greater_equal_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->greater_equal(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(0 >= 0) == 1", {0, 0}, {1});
    bfg_check(program, "(3 >= 3) == 1", {3, 3}, {1});
}

// ----- bf::var::equal(const var&) --------------------------------------------
BOOST_AUTO_TEST_CASE(var__equal) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->equal(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(2 == 2) == 1", {2, 2}, {1});
    bfg_check(program, "(3 == 4) == 0", {3, 4}, {0});
    bfg_check(program, "(5 == 4) == 0", {5, 4}, {0});
    bfg_check(program, "(0 == 0) == 1", {0, 0}, {1});
}

// ----- bf::var::equal(const var&) --------------------------------------------
BOOST_AUTO_TEST_CASE(var__equal_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->equal(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(0 == 0) == 1", {0, 0}, {1});
    bfg_check(program, "(3 == 3) == 1", {3, 3}, {1});
}

// ----- bf::var::not_equal(const var&) ----------------------------------------
BOOST_AUTO_TEST_CASE(var__not_equal) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();
        a->not_equal(*b);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(2 != 2) == 0", {2, 2}, {0});
    bfg_check(program, "(3 != 4) == 1", {3, 4}, {1});
    bfg_check(program, "(5 != 4) == 1", {5, 4}, {1});
    bfg_check(program, "(0 != 0) == 0", {0, 0}, {0});
}

// ----- bf::var::not_equal(const var&) ----------------------------------------
BOOST_AUTO_TEST_CASE(var__not_equal_self) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        a->read_input();
        a->not_equal(*a);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "(0 != 0) == 0", {0, 0}, {0});
    bfg_check(program, "(3 != 3) == 0", {3, 3}, {0});
}

// ----- bf::generator::if_begin(const var&) -----------------------------------
BOOST_AUTO_TEST_CASE(generator__if) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        // Simple "bool()" function
        auto a = bfg.new_var("a");
        a->read_input();
        bfg.if_begin(*a);
        {
            a->set(1);
        }
        bfg.if_end();
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "bool(0) == 0", {0}, {0});
    bfg_check(program, "bool(1) == 1", {1}, {1});
    bfg_check(program, "bool(5) == 1", {5}, {1});
}

// ----- bf::generator::else_begin(const var&) ---------------------------------
BOOST_AUTO_TEST_CASE(generator__else) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        // Simple "not()" function
        auto a = bfg.new_var("a");
        a->read_input();
        bfg.if_begin(*a);
        {
            a->set(0);
        }
        bfg.else_begin();
        {
            a->set(1);
        }
        bfg.if_end();
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "not(0) == 1", {0}, {1});
    bfg_check(program, "not(1) == 0", {1}, {0});
    bfg_check(program, "not(5) == 0", {5}, {0});
}

// ----- bf::generator::print(const std::string &text) -------------------------
BOOST_AUTO_TEST_CASE(generator__print) {
    const std::string test_str = "Test_123";
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        bfg.print(test_str);

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check(program, "print('" + test_str + "')", {}, {test_str.begin(), test_str.end()});
}

// ----- Example code: Greatest common divisor ---------------------------------
BOOST_AUTO_TEST_CASE(example_ggt) {
    std::string program;
    {
        bf::generator bfg;
        auto begin = bfg.new_var();

        auto a = bfg.new_var("a");
        auto b = bfg.new_var("b");
        a->read_input();
        b->read_input();

        auto neq = bfg.new_var("neq");
        neq->copy(*a);
        neq->not_equal(*b);
        bfg.while_begin(*neq); // a != b
        {
            auto lt = bfg.new_var("lt");
            lt->copy(*a);
            lt->lower_than(*b);
            bfg.if_begin(*lt); // a < b
            {
                b->subtract(*a);
            }
            bfg.else_begin();
            {
                a->subtract(*b);
            }
            bfg.if_end();

            neq->copy(*a);
            neq->not_equal(*b);
        }
        bfg.while_end(*neq);
        a->write_output();

        // Ensure correct SP movement
        begin->add(1);
        program = bfg.get_code();
    }

    bfg_check     (program, "ggt(24,   16)   == 8",   {24,   16},   {8});
    bfg_check     (program, "ggt(128,  42)   == 2",   {128,  42},   {2});
    bfg_check     (program, "ggt(9,    13)   == 1",   {9,    13},   {1});
    bfg_check<int>(program, "ggt(3528, 3780) == 252", {3528, 3780}, {252});
}


//---------------------------------------------------------------------------//
//!
//! \file   tstGridGenerator.cpp
//! \author Alex Robinson
//! \brief  Grid generator class unit tests
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <string>
#include <iostream>
#include <list>

// Boost Includes
#include <boost/bind.hpp>

// Trilinos Includes
#include <Teuchos_UnitTestHarness.hpp>
#include <Teuchos_VerboseObject.hpp>
#include <Teuchos_RCP.hpp>
#include <Teuchos_Array.hpp>

// FRENSIE Includes
#include "Utility_GridGenerator.hpp"
#include "Utility_SortAlgorithms.hpp"
#include "Utility_InterpolationPolicy.hpp"

//---------------------------------------------------------------------------//
// Testing Functions
//---------------------------------------------------------------------------//
double x2( const double x )
{
  return x*x;
}

struct x3
{
  x3( const double a )
    : d_a( a )
  { /* ... */ }

  double operator()( const double x )
  {
    return (x-d_a)*(x-d_a)*(x-d_a);
  }

private:

  double d_a;
};

struct xcosxAB
{
  xcosxAB( const double a,
	   const double b )
    : d_a( a ),
      d_b( b )
  { /* ... */ }

  double operator()( const double x )
  {
    if( x < d_a )
      return 0.0;
    else if( x > d_b )
      return 0.0;
    else
      return x*cos(x);
  }

private:

  double d_a;
  double d_b;
};

//---------------------------------------------------------------------------//
// Tests
//---------------------------------------------------------------------------//
// Check that a grid can be generated for the functions
TEUCHOS_UNIT_TEST( GridGenerator, generate )
{
  // Create the different grid generators
  Utility::GridGenerator<Utility::LinLin> linlin_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LogLin> loglin_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LinLog> linlog_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LogLog> loglog_generator( 0.001, 1e-12 );

  // Create the initial grid
  Teuchos::Array<double> initial_grid( 2 );
  initial_grid[0] = 0.0;
  initial_grid[1] = 10.0;

  // Create a lin-lin grid for x^2
  boost::function<double (double x)> function = &x2;

  Teuchos::Array<double> grid;

  linlin_generator.generate( grid, initial_grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 321 );

  grid.clear();

  // Create a log-lin grid for x^2
  initial_grid[0] = 1e-3;
  loglin_generator.generate( grid, initial_grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 214 );

  // Create a lin-lin grid for cos(x)
  function = &cos;

  linlin_generator.generate( grid, initial_grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 129 );

  grid.clear();

  // Create a lin-lin grid for (x-2)^3
  initial_grid[0] = 0.0;
  x3 x_cubed( 2 );
  function = boost::bind<double>(x_cubed, _1);

  linlin_generator.generate( grid, initial_grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 708 );

  grid.clear();

  // Create a log-lin grid for (x-2)^3
  initial_grid[0] = 2.0 + 1e-6;

  loglin_generator.generate( grid, initial_grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 266 );

  // Create a lin-lin grid for x*cos(x) in [-1, 1]
  xcosxAB x_cos_x( -1, 1 );
  function = boost::bind<double>(x_cos_x, _1);

  initial_grid.resize( 7 );
  initial_grid[0] = -2.0;
  initial_grid[1] = -1.0 - 1e-15;
  initial_grid[2] = -1.0;
  initial_grid[3] = 0.0;
  initial_grid[4] = 1.0;
  initial_grid[5] = 1.0 + 1e-15;
  initial_grid[6] = 2.0;

  linlin_generator.generate( grid, initial_grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 69 );
}

//---------------------------------------------------------------------------//
// Check that a grid can be generated for the functions
TEUCHOS_UNIT_TEST( GridGenerator, generateAndEvaluate )
{
  // Create the different grid generators
  Utility::GridGenerator<Utility::LinLin> linlin_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LogLin> loglin_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LinLog> linlog_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LogLog> loglog_generator( 0.001, 1e-12 );

  // Create the initial grid
  Teuchos::Array<double> initial_grid( 2 );
  initial_grid[0] = 0.0;
  initial_grid[1] = 10.0;

  // Create a lin-lin grid for x^2
  boost::function<double (double x)> function = &x2;

  Teuchos::Array<double> grid, evaluated_function;

  linlin_generator.generateAndEvaluate( grid,
					evaluated_function,
					initial_grid,
					function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 321 );
  TEST_EQUALITY_CONST( evaluated_function.size(), 321 );

  grid.clear();
  evaluated_function.clear();

  // Create a log-lin grid for x^2
  initial_grid[0] = 1e-3;

  loglin_generator.generateAndEvaluate( grid,
					evaluated_function,
					initial_grid,
					function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 214 );
  TEST_EQUALITY_CONST( evaluated_function.size(), 214 );

  // Create a lin-lin grid for cos(x)
  initial_grid[0] = 0.0;
  function = &cos;

  linlin_generator.generateAndEvaluate( grid,
					evaluated_function,
					initial_grid,
					function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 129 );
  TEST_EQUALITY_CONST( evaluated_function.size(), 129 );

  grid.clear();
  evaluated_function.clear();

  // Create a lin-lin grid for (x-2)^3
  x3 x_cubed( 2 );
  function = boost::bind<double>(x_cubed, _1);

  linlin_generator.generateAndEvaluate( grid,
					evaluated_function,
					initial_grid,
					function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 708 );
  TEST_EQUALITY_CONST( evaluated_function.size(), 708 );

  // Create a log-lin grid for (x-2)^3
  initial_grid[0] = 2.0 + 1e-6;

  loglin_generator.generateAndEvaluate( grid,
					evaluated_function,
					initial_grid,
					function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 266 );

  // Create a lin-lin grid for x*cos(x) in [-1, 1]
  xcosxAB x_cos_x( -1, 1 );
  function = boost::bind<double>(x_cos_x, _1);

  initial_grid.resize( 7 );
  initial_grid[0] = -2.0;
  initial_grid[1] = -1.0 - 1e-15;
  initial_grid[2] = -1.0;
  initial_grid[3] = 0.0;
  initial_grid[4] = 1.0;
  initial_grid[5] = 1.0 + 1e-15;
  initial_grid[6] = 2.0;

  std::list<double> grid_list, evaluated_function_list;

  linlin_generator.generateAndEvaluate( grid_list,
					evaluated_function_list,
					initial_grid,
					function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid_list.begin(),
						 grid_list.end() ));
  TEST_EQUALITY_CONST( grid_list.size(), 69 );
  TEST_EQUALITY_CONST( evaluated_function_list.size(), 69 );
}

//---------------------------------------------------------------------------//
// Check that a grid can be generated for the functions
TEUCHOS_UNIT_TEST( GridGenerator, generateInPlace )
{
  // Create the different grid generators
  Utility::GridGenerator<Utility::LinLin> linlin_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LogLin> loglin_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LinLog> linlog_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LogLog> loglog_generator( 0.001, 1e-12 );

  // Create the initial grid
  Teuchos::Array<double> initial_grid( 2 );
  initial_grid[0] = 0.0;
  initial_grid[1] = 10.0;

  // Create a lin-lin grid for x^2
  boost::function<double (double x)> function = &x2;

  Teuchos::Array<double> grid = initial_grid;

  linlin_generator.generateInPlace( grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 321 );

  // Create a log-lin grid for x^2
  initial_grid[0] = 1e-3;
  grid = initial_grid;

  loglin_generator.generateInPlace( grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 214 );

  // Create a lin-lin grid for cos(x)
  function = &cos;

  initial_grid[0] = 0.0;
  grid = initial_grid;

  linlin_generator.generateInPlace( grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 129 );

  grid = initial_grid;

  // Create a lin-lin grid for (x-2)^3
  x3 x_cubed( 2 );
  function = boost::bind<double>(x_cubed, _1);

  linlin_generator.generateInPlace( grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 708 );

  // Create a log-lin grid for (x-2)^3
  initial_grid[0] = 2 + 1e-6;
  grid = initial_grid;

  loglin_generator.generateInPlace( grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 266 );

  // Create a lin-lin grid for x*cos(x) in [-1, 1]
  xcosxAB x_cos_x( -1, 1 );
  function = boost::bind<double>(x_cos_x, _1);

  initial_grid.resize( 7 );
  initial_grid[0] = -2.0;
  initial_grid[1] = -1.0 - 1e-15;
  initial_grid[2] = -1.0;
  initial_grid[3] = 0.0;
  initial_grid[4] = 1.0;
  initial_grid[5] = 1.0 + 1e-15;
  initial_grid[6] = 2.0;

  grid = initial_grid;

  linlin_generator.generateInPlace( grid, function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 69 );
}

//---------------------------------------------------------------------------//
// Check that a grid can be generated for the functions
TEUCHOS_UNIT_TEST( GridGenerator, generateAndEvaluateInPlace )
{
  // Create the different grid generators
  Utility::GridGenerator<Utility::LinLin> linlin_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LogLin> loglin_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LinLog> linlog_generator( 0.001, 1e-12 );
  Utility::GridGenerator<Utility::LogLog> loglog_generator( 0.001, 1e-12 );

  // Create the initial grid
  Teuchos::Array<double> initial_grid( 2 );
  initial_grid[0] = 0.0;
  initial_grid[1] = 10.0;

  // Create a lin-lin grid for x^2
  boost::function<double (double x)> function = &x2;

  Teuchos::Array<double> grid = initial_grid, evaluated_function;

  linlin_generator.generateAndEvaluateInPlace( grid,
					       evaluated_function,
					       function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 321 );
  TEST_EQUALITY_CONST( evaluated_function.size(), 321 );

  evaluated_function.clear();

  // Create a log-lin grid for x^2
  initial_grid[0] = 1e-3;
  grid = initial_grid;

  loglin_generator.generateAndEvaluateInPlace( grid,
					       evaluated_function,
					       function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 214 );
  TEST_EQUALITY_CONST( evaluated_function.size(), 214 );

  evaluated_function.clear();

  // Create a lin-lin grid for cos(x)
  initial_grid[0] = 0.0;
  grid = initial_grid;

  function = &cos;

  linlin_generator.generateAndEvaluateInPlace( grid,
					       evaluated_function,
					       function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 129 );
  TEST_EQUALITY_CONST( evaluated_function.size(), 129 );

  grid = initial_grid;
  evaluated_function.clear();

  // Create a lin-lin grid for (x-2)^3
  x3 x_cubed( 2 );
  function = boost::bind<double>(x_cubed, _1);

  linlin_generator.generateAndEvaluateInPlace( grid,
					       evaluated_function,
					       function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 708 );
  TEST_EQUALITY_CONST( evaluated_function.size(), 708 );

  evaluated_function.clear();

  // Create a log-lin grid for (x-2)^3
  initial_grid[0] = 2.0 + 1e-6;
  grid = initial_grid;

  loglin_generator.generateAndEvaluateInPlace( grid,
					       evaluated_function,
					       function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 266 );
  TEST_EQUALITY_CONST( evaluated_function.size(), 266 );

  evaluated_function.clear();

  // Create a lin-lin grid for x*cos(x) in [-1, 1]
  xcosxAB x_cos_x( -1, 1 );
  function = boost::bind<double>(x_cos_x, _1);

  initial_grid.resize( 7 );
  initial_grid[0] = -2.0;
  initial_grid[1] = -1.0 - 1e-15;
  initial_grid[2] = -1.0;
  initial_grid[3] = 0.0;
  initial_grid[4] = 1.0;
  initial_grid[5] = 1.0 + 1e-15;
  initial_grid[6] = 2.0;

  grid = initial_grid;
  evaluated_function.clear();

  linlin_generator.generateAndEvaluateInPlace( grid,
					       evaluated_function,
					       function );

  TEST_ASSERT( Utility::Sort::isSortedAscending( grid.begin(), grid.end() ) );
  TEST_EQUALITY_CONST( grid.size(), 69 );
  TEST_EQUALITY_CONST( evaluated_function.size(), 69 );
}

//---------------------------------------------------------------------------//
// end tstGridGenerator.cpp
//---------------------------------------------------------------------------//


#pragma once

#include <armadillo>

namespace utils
{
    arma::vec tridag(const arma::vec& a, const arma::vec& b, const arma::vec& c,
                     const arma::vec& r, const arma::uword n);
}


///////////////////////////////////
#include <boost/test/unit_test.hpp>
///////////////////////////
#include "distancefixture.h"
/////////////////////////
#include <indivcluster.h>
//////////////////////
using namespace std;
using namespace info;
/////////////////////////////
BOOST_FIXTURE_TEST_SUITE(IndivClusterTestSuite, DistanceFixture)
;
BOOST_AUTO_TEST_CASE(testIndivCluster) {
	IIndivProvider *pProvider = m_provider.get();
	BOOST_REQUIRE(pProvider != nullptr);
	IndivDistanceMap *pMap = m_odistances.get();
	BOOST_REQUIRE(pMap != nullptr);
	IntType aIndex1 = 100, aIndex2 = 200;
	IndivCluster oCluster1(pProvider, aIndex1);
	IndivCluster oCluster2(pProvider, aIndex2);
	size_t nc = 0;
	bool bRet = pProvider->indivs_count(nc);
	BOOST_REQUIRE(bRet);
	BOOST_REQUIRE(nc > 1);
	for (size_t i = 0; i < nc; i += 2) {
		Indiv oInd1;
		if (!pProvider->find_indiv_at(i, oInd1)) {
			break;
		}
		oCluster1.add(oInd1);
		Indiv oInd2;
		if (!pProvider->find_indiv_at(i + 1, oInd2)) {
			break;
		}
		oCluster2.add(oInd2);
	}// i
	oCluster1.update_center();
	oCluster2.update_center();
	//
	double fintra = 0, finter = 0;
	bRet = oCluster1.intra_inertia(fintra);
	BOOST_CHECK(bRet);
	BOOST_CHECK(fintra > 0);
	bRet = oCluster1.inter_inertia(oCluster2, finter);
	BOOST_CHECK(bRet);
	BOOST_CHECK(finter > 0);
	//
	double dd = 0;
	ClusterAppendMode mode = ClusterAppendMode::modeInvalid;
	bRet = oCluster1.min_distance(oCluster2,dd, mode);
	BOOST_CHECK(bRet);
	BOOST_CHECK(mode != ClusterAppendMode::modeInvalid);
	BOOST_CHECK(dd > 0);
	oCluster1.add(oCluster2, mode);
	oCluster1.update_center();
	//
	for (size_t i = 0; i < nc; i += 2) {
		Indiv oInd;
		if (!pProvider->find_indiv_at(i, oInd)) {
			break;
		}
		double d1 = oCluster1.distance(oInd);
		BOOST_CHECK(d1 > 0);
		double d2 = oCluster2.distance(oInd);
		BOOST_CHECK(d2 > 0);
	}// i
} //testStatInfo

BOOST_AUTO_TEST_SUITE_END();