#pragma warning(disable: 4996)

#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <fstream>
// Assimp
#include <assimp/cimport.h>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
// Point Cloud Library
#include <pcl/point_cloud.h>
#include <pcl/io/pcd_io.h>
#include <pcl/io/obj_io.h>
#include <pcl/point_types.h>
#include <pcl/registration/icp.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/filters/filter.h>
#include <pcl/point_representation.h>
#include <pcl/filters/filter.h>
#include <pcl/features/normal_3d.h>
#include <pcl/registration/icp_nl.h>
#include <pcl/registration/transforms.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/voxel_grid.h>
using pcl::visualization::PointCloudColorHandlerGenericField;
using pcl::visualization::PointCloudColorHandlerCustom;
// Eigen
#include <Eigen/Dense>
#include <Eigen/Core>
// Boost
#include <boost/algorithm/string.hpp>
#include <boost/make_shared.hpp>
// Go-ICP
#include "jly_goicp.h"
#include "ConfigMap.hpp"

typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> pclPointCloud;
typedef pcl::PointNormal PointNormalT;
typedef pcl::PointCloud<PointNormalT> PointCloudWithNormals;

double maxCorr = 0.2;
int maxIter = 5, iterTimes = 5;
int modelPair = 0;

void insertBeeModels(std::vector<std::string> &fileNames) {
    fileNames.clear();
    for (int i = 1; i <= 8; i++) {
        std::stringstream s;
        s << "bee" << i << ".obj";
        fileNames.push_back(s.str());
    }
}

void insertBunnyModels(std::vector<std::string> &fileNames) {
    fileNames.clear();
    for (int i = 1; i <= 11; i++) {
        std::stringstream s;
        s << "point_cloudbunny_34k.obj_" << std::setw(3) << std::setfill('0') << i << ".pcd";
        fileNames.push_back(s.str());
    }
}

void insertDiceModels(std::vector<std::string> &fileNames, std::vector<Eigen::Matrix4f> &transformations) {
    fileNames.clear();
    for (int i = 1; i <= 20; i++) {
        std::stringstream s;
        s << "point_cloud" << std::setw(3) << std::setfill('0') << i << ".pcd";
        fileNames.push_back(s.str());
    }
    ifstream trans;
    trans.open("transLog.txt", ios::in);
    string s;
    for (int i = 0; i < 20; i++) {
        Eigen::Matrix4f tempTrans;
        for (int i = 0; i < 3; i++) {
            trans >> s;
        }
        for (int c = 0; c < 4; c++) {
            for (int r = 0; r < 4; r++) {
                trans >> tempTrans(c, r);
            }
        }
        transformations.push_back(tempTrans);
    }
    trans.close();

    /*for (int i = 0; i < 20; i++){
        cout << "Transform Matrix " << i << ":" << endl;
        cout << transformations[i].matrix() << endl;
    }*/
}

void importPointsFromPCD(std::vector<pclPointCloud::Ptr> &models, const std::string filePath) {
    pclPointCloud::Ptr m(new pclPointCloud);
    pcl::io::loadPCDFile(filePath, *m);
    std::cout << "Read " << m->size() << " vertices" << std::endl;
    models.push_back(m);
    return;
}

void importPointsFromOBJ(std::vector<pclPointCloud::Ptr> &models, const std::string filePath) {
    const struct aiScene* scene = aiImportFile(filePath.c_str(), aiProcessPreset_TargetRealtime_Quality);
    if (scene == NULL) {
        std::cerr << "Could not load mesh from file " << filePath << std::endl;
        exit(-1);
    }
    if (scene->mNumMeshes>1) {
        std::cerr << "Object " << filePath << " consists of more than one mesh." << std::endl;
        exit(-1);
    }
    unsigned numVertices = scene->mMeshes[0]->mNumVertices;
    std::cout << "Read " << numVertices << " vertices" << std::endl;

    // normalize
    Eigen::MatrixXd vertices(3, numVertices);
    for (size_t i = 0; i < numVertices; i++) {
        vertices(0, i) = scene->mMeshes[0]->mVertices[i].x;
        vertices(1, i) = scene->mMeshes[0]->mVertices[i].y;
        vertices(2, i) = scene->mMeshes[0]->mVertices[i].z;
    }
    Eigen::VectorXd maxima, minima, means;
    means = vertices.rowwise().mean();
    vertices.colwise() -= means;
    maxima = vertices.rowwise().maxCoeff();
    minima = vertices.rowwise().minCoeff();
    vertices /= (maxima - minima).maxCoeff();

    // assign value
    pclPointCloud::Ptr m(new pclPointCloud);
    m->width = numVertices;
    m->height = 1;
    m->is_dense = false;
    m->points.resize(m->width * m->height);

    for (size_t i = 0; i < numVertices; i++) {
        m->points[i].x = vertices(0, i);
        m->points[i].y = vertices(1, i);
        m->points[i].z = vertices(2, i);
    }

    models.push_back(m);

    return;
}

void readBunnyInfo(
    std::vector<int> &ids,
    std::vector<Eigen::Matrix4f> &tfs,
    std::vector<std::pair<int, int>> &seq) {

    ifstream info;
    info.open("result_bunny_34k.obj.txt", ios::in);
    int size;
    string str;
    info >> size >> str;
    for (int i = 0; i < size; i++) {
        Eigen::Matrix4f tf;
        int id1, id2;
        info >> id1 >> id2 >> str;
        ids.push_back(id1);
        for (int c = 0; c < 4; c++) {
            for (int r = 0; r < 4; r++) {
                info >> tf(c, r);
            }
        }
        tfs.push_back(tf);
        // first scan
        if (id2 == -1)
            continue;
        // following scans
        int cur_id_size = ids.size();
        for (int j = 0; j < cur_id_size; j++) {
            if (ids[j] == id1)
                id1 = j;
            if (ids[j] == id2)
                id2 = j;
        }
        seq.push_back(std::make_pair(id2, id1));
    }

    info.close();
    cout << "Read info finished" << endl;
}

// Align target to source
void pairAlign(const pclPointCloud::Ptr cloud_src, const pclPointCloud::Ptr cloud_tgt,
    pclPointCloud::Ptr output, Eigen::Matrix4f &final_transform)
{
    pclPointCloud::Ptr src(new pcl::PointCloud<pcl::PointXYZ>);
    pclPointCloud::Ptr tgt(new pcl::PointCloud<pcl::PointXYZ>);
    src = cloud_src;
    tgt = cloud_tgt;

    // Compute surface normals and curvature
    PointCloudWithNormals::Ptr points_with_normals_src(new PointCloudWithNormals);
    PointCloudWithNormals::Ptr points_with_normals_tgt(new PointCloudWithNormals);

    pcl::NormalEstimation<PointT, PointNormalT> norm_est;
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
    norm_est.setSearchMethod(tree);
    norm_est.setKSearch(30);

    norm_est.setInputCloud(src);
    norm_est.compute(*points_with_normals_src);
    pcl::copyPointCloud(*src, *points_with_normals_src);

    norm_est.setInputCloud(tgt);
    norm_est.compute(*points_with_normals_tgt);
    pcl::copyPointCloud(*tgt, *points_with_normals_tgt);

    // Align
    pcl::IterativeClosestPointNonLinear<PointNormalT, PointNormalT> reg;
    reg.setTransformationEpsilon(1e-6);
    // Set the maximum distance between two correspondences (src<->tgt) to 10cm
    // Note: adjust this based on the size of your datasets
    reg.setMaxCorrespondenceDistance(maxCorr);
    // Set the point representation
    reg.setInputSource(points_with_normals_src);
    reg.setInputTarget(points_with_normals_tgt);

    // Run the same optimization in a loop and visualize the results
    Eigen::Matrix4f Ti = Eigen::Matrix4f::Identity(), prev, targetToSource;
    PointCloudWithNormals::Ptr reg_result = points_with_normals_src;
    reg.setMaximumIterations(maxIter);
    for (int i = 0; i < iterTimes; ++i)
    {
        // save cloud for visualization purpose
        points_with_normals_src = reg_result;

        // Estimate
        reg.setInputSource(points_with_normals_src);
        reg.align(*reg_result);

        //accumulate transformation between each Iteration
        Ti = reg.getFinalTransformation() * Ti;

        //if the difference between this transformation and the previous one
        //is smaller than the threshold, refine the process by reducing
        //the maximal correspondence distance
        if (fabs((reg.getLastIncrementalTransformation() - prev).sum()) < reg.getTransformationEpsilon())
            reg.setMaxCorrespondenceDistance(reg.getMaxCorrespondenceDistance() - 0.001);

        prev = reg.getLastIncrementalTransformation();
    }

    // Get the transformation from target to source
    targetToSource = Ti.inverse();

    //std::cout << targetToSource << std::endl;
    //
    // Transform target back in source frame
    pcl::transformPointCloud(*cloud_tgt, *output, targetToSource);

    //add the source to the transformed target
    *output += *cloud_src;

    final_transform = targetToSource;
}

void configGoICP(std::string configName, GoICP & goicp) {
    // Open and parse the associated config file
    ConfigMap config(configName.c_str());

    goicp.MSEThresh = config.getF("MSEThresh");
    goicp.initNodeRot.a = config.getF("rotMinX");
    goicp.initNodeRot.b = config.getF("rotMinY");
    goicp.initNodeRot.c = config.getF("rotMinZ");
    goicp.initNodeRot.w = config.getF("rotWidth");
    goicp.initNodeTrans.x = config.getF("transMinX");
    goicp.initNodeTrans.y = config.getF("transMinY");
    goicp.initNodeTrans.z = config.getF("transMinZ");
    goicp.initNodeTrans.w = config.getF("transWidth");
    goicp.trimFraction = config.getF("trimFraction");
    // If < 0.1% trimming specified, do no trimming
    if (goicp.trimFraction < 0.001)
    {
        goicp.doTrim = false;
    }
    goicp.dt.SIZE = config.getI("distTransSize");
    goicp.dt.expandFactor = config.getF("distTransExpandFactor");

    std::cout << "CONFIG:" << std::endl;
    config.print();
    //cout << "(doTrim)->(" << goicp.doTrim << ")" << endl;
    std::cout << std::endl;
}

void registrationGoICP(std::vector<pclPointCloud::Ptr> &models, std::string &configName, int dataDownsampled) {
    clock_t st, ed;
    GoICP goicp;
    int NdDownsampled = dataDownsampled;

    configGoICP(configName, goicp);

    POINT3D *pM, *pD;
    pclPointCloud::Ptr m = models[modelPair], d = models[modelPair + 1];
    int nM = m->size(), nD = d->size();
    pM = new POINT3D[nM];
    pD = new POINT3D[nD];
    for (int i = 0; i < nM; i++) {
        pM[i].x = m->points[i].x;
        pM[i].y = m->points[i].y;
        pM[i].z = m->points[i].z;
    }
    for (int i = 0; i < nD; i++) {
        pD[i].x = d->points[i].x;
        pD[i].y = d->points[i].y;
        pD[i].z = d->points[i].z;
    }

    // randomize pD for further subsampling
    auto swapPoint = [](POINT3D &a, POINT3D &b) {
        POINT3D temp = a;
        a = b;
        b = temp;
    };
    for (int i = nD; i > 0; --i)
        swapPoint(pD[i - 1], pD[rand() % i]);

    goicp.pModel = pM;
    goicp.Nm = nM;
    goicp.pData = pD;
    goicp.Nd = nD;

    // Build Distance Transform
    cout << "Building Distance Transform..." << flush;
    st = clock();
    goicp.BuildDT();
    ed = clock();
    std::cout << (double)(ed - st) / CLOCKS_PER_SEC << "s (CPU)" << std::endl;

    // Run GO-ICP
    if (NdDownsampled > 0)
    {
        goicp.Nd = NdDownsampled; // Only use first NdDownsampled data points (assumes data points are randomly ordered)
    }
    std::cout << "Model ID: " << modelPair << " (" << goicp.Nm << "), Data ID: " << modelPair + 1 << " (" << goicp.Nd << ")" << std::endl;
    std::cout << "Registering..." << std::endl;
    st = clock();
    goicp.Register();
    ed = clock();
    double time = (double)(ed - st) / CLOCKS_PER_SEC;

    Eigen::Matrix4f tf = Eigen::Matrix4f::Identity();
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            tf(i, j) = goicp.optR.val[i][j];
        }
        tf(i, 3) = goicp.optT.val[i][0];
    }
    tf.row(3) = Eigen::Vector4f(0, 0, 0, 1).transpose();

    /*std::cout << "Optimal Rotation Matrix:" << std::endl;
    std::cout << goicp.optR << std::endl;
    std::cout << "Optimal Translation Vector:" << endl;
    std::cout << goicp.optT << std::endl;*/

    std::cout << "Optimal Affine Tranformation Matrix:" << endl;
    std::cout << tf << std::endl;
    std::cout << "Finished in " << time << "s (CPU)" << std::endl;

    delete [] pM, pD;

    pclPointCloud::Ptr result(new pclPointCloud);

    pcl::transformPointCloud(*d, *result, tf);
    *result += *m;

    std::cout << "Result size: " << result->size() << std::endl;
    std::stringstream ss;
    ss << "../tmp/" << modelPair << ".pcd";
    pcl::io::savePCDFile(ss.str(), *result, true);

    pcl::visualization::CloudViewer viewer("Simple Cloud Viewer");
    pclPointCloud::Ptr p_cloud(new pclPointCloud);
    pcl::io::loadPCDFile(ss.str(), *p_cloud);
    viewer.showCloud(p_cloud);

    while (!viewer.wasStopped()) {}

}

void registration(std::vector<pclPointCloud::Ptr> &models,
    std::vector<Eigen::Matrix4f> &transformations,
    std::vector<std::pair<int, int>> &reg_seq) {

    int len = models.size();
    std::vector<Eigen::Matrix4f> global_transform(models.size(), Eigen::Matrix4f::Identity());

    pclPointCloud::Ptr result(new pclPointCloud);

    // add first scan
    *result += *models[0];

    for (auto pair : reg_seq) {
        int src_id = pair.first, tgt_id = pair.second;

        // initial alignment
        Eigen::Matrix4f init_align = transformations[src_id] * transformations[tgt_id].inverse();
        pclPointCloud::Ptr temp_tgt(new pclPointCloud), reg_res(new pclPointCloud);
        pcl::transformPointCloud(*models[tgt_id], *temp_tgt, init_align);

        // registration
        Eigen::Matrix4f pair_transform = Eigen::Matrix4f::Identity();
        pairAlign(models[src_id], temp_tgt, reg_res, pair_transform);
        //pcl::transformPointCloud(*temp_tgt, *reg_res, pair_transform);

        if (src_id == 0) {
            global_transform[tgt_id] = pair_transform * init_align;
            *result += *reg_res;
        }
        else {
            pclPointCloud::Ptr source_res(new pclPointCloud);
            pcl::transformPointCloud(*reg_res, *source_res, global_transform[src_id]);
            global_transform[tgt_id] = global_transform[src_id] * pair_transform * init_align;
            *result += *source_res;
        }

        cout << "Register " << src_id << " and " << tgt_id << std::endl;
        cout << pair_transform.matrix() << std::endl;
    }

    // restore first pose
    pclPointCloud::Ptr result_restored(new pclPointCloud);
    pcl::transformPointCloud(*result, *result_restored, transformations[0].inverse());

    // downsampling
    pclPointCloud::Ptr result_downsampled(new pclPointCloud);
    pcl::VoxelGrid<PointT> sor;
    sor.setInputCloud(result_restored);
    sor.setLeafSize(0.005f, 0.005f, 0.005f);
    sor.filter(*result_downsampled);

    pcl::visualization::CloudViewer viewer("Simple Cloud Viewer");
    viewer.showCloud(result_downsampled);
    while (!viewer.wasStopped()) {}

}

void reconstruction() {}

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

    std::vector<pclPointCloud::Ptr> models;
    std::string filePath = "../obj_models/multi/";
    std::vector<std::string> fileNames;

    int dataDownsampled = 2000;
    std::string configName = "config.txt";

    //cout << "Config file name: ";
    //cin >> configName;
    /*cout << "Data downsampled: ";
    cin >> dataDownsampled;*/
    /*cout << "Model pair: ";
    cin >> modelPair;*/

    //insertBeeModels(fileNames);
    insertBunnyModels(fileNames);
    //insertDiceModels(fileNames, transformations);

    // import models
    for (auto s : fileNames) {
        if (boost::algorithm::ends_with(s, ".obj")) {
            importPointsFromOBJ(models, filePath + s);
        }
        else if (boost::algorithm::ends_with(s, ".pcd")) {
            importPointsFromPCD(models, filePath + s);
        }
        else {
            std::cerr << "Cannot open file, please check the file name and path." << std::endl;
            exit(-1);
        }
    }

    // read scan information
    std::vector<Eigen::Matrix4f> transformations;
    std::vector<int> scan_indices;
    std::vector<std::pair<int, int>> reg_sequence;
    readBunnyInfo(scan_indices, transformations, reg_sequence);

    // registration
    registration(models, transformations, reg_sequence);
    //registrationGoICP(models, configName, dataDownsampled);

    // reconstruction
    reconstruction();

    return 0;
}


// Copyright 2021 the Autoware Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Co-developed by Tier IV, Inc. and Apex.AI, Inc.
/// \file
/// \brief Implementation of the localization evaluator
#ifndef LOCALIZATION_SYSTEM_TESTS__LOCALIZATION_EVALUATION_NODE_HPP_
#define LOCALIZATION_SYSTEM_TESTS__LOCALIZATION_EVALUATION_NODE_HPP_

#include <localization_system_tests/visibility_control.hpp>
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/pose_with_covariance_stamped.hpp>
#include <message_filters/subscriber.h>
#include <message_filters/synchronizer.h>
#include <message_filters/sync_policies/approximate_time.h>
#include <tf2_ros/transform_listener.h>
#include <tf2/buffer_core.h>
#include <time_utils/time_utils.hpp>
#include <common/types.hpp>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <memory>
#include <string>

namespace localization_system_tests
{
/// \brief A node that is ran beside a localization application and a source of ground truth and
// estimates the error with respect to the ground truth.
class LOCALIZATION_SYSTEM_TESTS_PUBLIC LocalizationEvaluationNode
  : public rclcpp::Node
{
public:
  using float64_t = autoware::common::types::float64_t;
  using Pose = geometry_msgs::msg::PoseWithCovarianceStamped;
  using EigTransform = Eigen::Transform<float64_t, 3, Eigen::Affine, Eigen::ColMajor>;
  using EigTranslationPart = decltype(std::declval<const EigTransform>().translation());
  using EigRotationPart = decltype(std::declval<const EigTransform>().rotation());
  using SyncPolicyT = message_filters::sync_policies::ApproximateTime<Pose, Pose>;
  /// \brief Basic node constructor.
  explicit LocalizationEvaluationNode(const rclcpp::NodeOptions & options);

  /// \brief Finalize and report the metrics in the destructor.
  ~LocalizationEvaluationNode() noexcept;

private:
  /// \brief Compute the translation and rotation error metrics for the given estimate - ground
  /// truth pair.
  void evaluation_callback(
    const Pose::ConstSharedPtr & estimate, const Pose::ConstSharedPtr & ground_truth);

  /// \brief Given the current average and the increment of a single sample, compute the moving
  /// average using the number of points stored in the class
  /// \param current_avg Current average
  /// \param addition New sample's contribution
  /// \return The new average
  float64_t get_new_average(float64_t current_avg, float64_t addition);

  /// \brief Compute metrics and update the values
  void compute_and_update_metrics(
    const EigTransform & ground_truth,
    const EigTransform & estimate);

  /// Compute the euclidean distance between two translation values.
  float64_t translation_error(const EigTranslationPart & tf1, const EigTranslationPart & tf2);

  /// Compute the angular distance in radian between two rotation matrices
  float64_t rotation_error(const EigRotationPart & tf1, const EigRotationPart & tf2);

  std::array<std::unique_ptr<message_filters::Subscriber<Pose>>, 2U> m_pose_subscribers;
  std::unique_ptr<message_filters::Synchronizer<SyncPolicyT>> m_pose_synchronizer;
  float64_t m_average_translation_err;
  float64_t m_average_rotation_err;
  std::uint32_t m_num_computed{0U};
  EigTransform m_ground_truth_to_estimate_tf;
};  // class LocalizationEvaluationNode

}  // namespace localization_system_tests

#endif  // LOCALIZATION_SYSTEM_TESTS__LOCALIZATION_EVALUATION_NODE_HPP_


#ifndef PROJECTION_HPP
#define PROJECTION_HPP

#include <Eigen/Core>

class Projection {
  public:
    Eigen::Matrix4d getTransformation();
    double getLeft();
    double getRight();
    double getBottom();
    double getTop();
    double getNear();
    double getFar();

  protected:
    virtual void generateTransformation() = 0;
    Eigen::Matrix4d transformation;

    double left;
    double right;
    double bottom;
    double top;
    double near;
    double far;
};

#endif // PROJECTION_HPP


#include <boost/mpl/fold.hpp>
<%= Benchcc::MPL::List.new(0...depth).includes %>

<%= render('_main.erb') do |f, state, xs|
    "boost::mpl::fold<#{Benchcc::MPL::List.new(xs)}, #{state}, #{f}>::type"
end %>


/*
 * Collector.cpp
 *
 *  Created on: Jan 21, 2016
 *      Author: sieber
 */

#include "Collector.h"
#include "OFLatencyStats.h"
#include "../globals.h"
#include "../HVBenchController.h"
#include <boost/thread/mutex.hpp>
#include <boost/format.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/max.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

Collector::Collector() {
}

Collector::~Collector() {
}

void Collector::addMeta(std::string key, std::string value) {
	this->meta_[key] = value;
}

std::string Collector::toJson() {

	using boost::property_tree::ptree;
	ptree pt;

	for (const auto& e : this->statistics_) {
		pt.add(e.first, e.second);
	}
	for (const auto& e : this->meta_) {
		pt.add(e.first, e.second);
	}

	std::ostringstream oss;

	write_json(oss, pt);

	return oss.str();
}

void Collector::visit(std::shared_ptr<OFLatencyStats> s) {

	boost::mutex::scoped_lock lock(s->mutex_);

	for (auto& e : s->stats_latency_) {

		OFPT::OFPT_T key = e.first;

		const auto key_mean = boost::str(boost::format("latency_%s_mean") % OFPT::STR.at(key));
		const auto key_min = boost::str(boost::format("latency_%s_min") % OFPT::STR.at(key));
		const auto key_max = boost::str(boost::format("latency_%s_max") % OFPT::STR.at(key));

		if (boost::accumulators::count(e.second) != 0) {

			this->statistics_[key_mean] = static_cast<uint64_t>(boost::accumulators::mean(e.second));
			this->statistics_[key_min] = static_cast<uint64_t>(boost::accumulators::min(e.second));
			this->statistics_[key_max] = static_cast<uint64_t>(boost::accumulators::max(e.second));
		} else {
			this->statistics_[key_mean] = static_cast<uint64_t>(0);
			this->statistics_[key_min] = static_cast<uint64_t>(0);
			this->statistics_[key_max] = static_cast<uint64_t>(0);
		}
	}
}

void Collector::visit(std::shared_ptr<HVBenchController> s) {

	auto duration = boost::chrono::duration_cast<boost::chrono::milliseconds>(clock_source::now() - s->start_).count() + 1;

	this->statistics_["duration_ms"] = static_cast<uint64_t>(duration);

	this->statistics_["timestamp"] = (boost::posix_time::microsec_clock::universal_time() - reference_t).total_milliseconds();

	for (auto& e : OFPT::req_types) {

		const auto key = boost::str(boost::format("requests_%s_cnt") % OFPT::STR.at(e));

		this->statistics_[key] = static_cast<uint64_t>(s->counters_[e]);
	}

}

const std::map<std::string, uint64_t> Collector::statistics() {
	return statistics_;
}

void Collector::visit(std::shared_ptr<OFConnWrapper> s) {

	this->statistics_["tx_bytes"] = s->byte_cnt_;
}

void Collector::print() {

	for (auto& e : this->statistics_) {

		std::cout << e.first << ": " << e.second << std::endl;
	}
}


// Copyright 2018 The Simons Foundation, Inc. - All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef NETKET_ABSTRACTOBSERVABLE_HPP
#define NETKET_ABSTRACTOBSERVABLE_HPP

#include <Eigen/Dense>
#include <complex>
#include <string>
#include <vector>

#include "Hamiltonian/abstract_hamiltonian.hpp"

namespace netket {

/**
      Abstract class for Observables.
      This class prototypes the methods needed
      by a class satisfying the Observable concept.
      Users interested in implementing new observables should derive they own
      class from this class.
*/
class AbstractObservable {
 public:
  /**
  Member function finding the connected elements of the Hamiltonian.
  Starting from a given visible state v, it finds all other visible states v'
  such that the observable matrix element O(v,v') is different from zero.
  In general there will be several different connected visible units satisfying
  this condition, and they are denoted here v'(k), for k=0,1...N_connected.
  @param v a constant reference to the visible configuration.
  @param mel(k) is modified to contain matrix elements O(v,v'(k)).
  @param connector(k) for each k contains a list of sites that should be changed
  to obtain v'(k) starting from v.
  @param newconfs(k) is a vector containing the new values of the visible units
  on the affected sites, such that: v'(k,connectors(k,j))=newconfs(k,j). For the
  other sites v'(k)=v, i.e. they are equal to the starting visible
  configuration.
  */
  virtual void FindConn(const Eigen::VectorXd &v,
                        std::vector<std::complex<double>> &mel,
                        std::vector<std::vector<int>> &connectors,
                        std::vector<std::vector<double>> &newconfs) const = 0;

  using ConnCallback = std::function<void(ConnectorRef)>;
  virtual void ForEachConn(const Eigen::VectorXd &v,
                           ConnCallback callback) const;

  /**
  Member function returning the hilbert space associated with this Observable.
  @return Hilbert space specifier for this Observable
  */
  virtual const Hilbert &GetHilbert() const = 0;

  virtual const std::string Name() const = 0;

  virtual ~AbstractObservable() {}
};

void AbstractObservable::ForEachConn(const Eigen::VectorXd &v,
                                     ConnCallback callback) const {
  std::vector<std::complex<double>> weights;
  std::vector<std::vector<int>> connectors;
  std::vector<std::vector<double>> newconfs;

  FindConn(v, weights, connectors, newconfs);

  for (size_t k = 0; k < connectors.size(); k++) {
    const ConnectorRef mel{weights[k], connectors[k], newconfs[k]};
    callback(mel);
  }
}

}  // namespace netket

#endif


//  (C) Copyright Daniel James 2005.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/config.hpp>
#include <cstddef>

namespace test
{
    struct custom
    {
        int value_;

        std::size_t hash() const
        {
            return value_ * 10;
        }

#if !defined(BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP)
        friend std::size_t hash_value(custom const& x )
        {
            return x.hash();
        }
#endif

        custom(int x) : value_(x) {}
    };
}

#if defined(BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP)
namespace boost
{
    std::size_t hash_value(test::custom x)
    {
        return x.hash();
    }
}
#endif

#include "./config.hpp"

#ifdef TEST_EXTENSIONS
#  ifdef TEST_STD_INCLUDES
#    include <functional>
#  else
#    include <boost/functional/hash/hash.hpp>
#  endif
#endif

#define BOOST_AUTO_TEST_MAIN
#include <boost/test/auto_unit_test.hpp>

#ifdef TEST_EXTENSIONS

#include <vector>
#include <string>
#include <cctype>

BOOST_AUTO_UNIT_TEST(custom_tests)
{
    HASH_NAMESPACE::hash<test::custom> custom_hasher;
    BOOST_CHECK(custom_hasher(10) == 100u);
    test::custom x(55);
    BOOST_CHECK(custom_hasher(x) == 550u);

    {
        using namespace HASH_NAMESPACE;
        BOOST_CHECK(custom_hasher(x) == hash_value(x));
    }

    std::vector<test::custom> custom_vector;
    custom_vector.push_back(5);
    custom_vector.push_back(25);
    custom_vector.push_back(35);

    std::size_t seed = 0;
    HASH_NAMESPACE::hash_combine(seed, test::custom(5));
    HASH_NAMESPACE::hash_combine(seed, test::custom(25));
    HASH_NAMESPACE::hash_combine(seed, test::custom(35));

    std::size_t seed2 = 0;
    HASH_NAMESPACE::hash_combine(seed2, 50u);
    HASH_NAMESPACE::hash_combine(seed2, 250u);
    HASH_NAMESPACE::hash_combine(seed2, 350u);

    BOOST_CHECK(seed ==
            HASH_NAMESPACE::hash_range(custom_vector.begin(), custom_vector.end()));
    BOOST_CHECK(seed == seed2);
}

#endif // TEST_EXTENSIONS


///////////////////////////////////////////////////////////////////////////////
// BSD 3-Clause License
//
// Copyright (C) 2018-2020, LAAS-CNRS, University of Edinburgh
// Copyright note valid unless otherwise stated in individual files.
// All rights reserved.
///////////////////////////////////////////////////////////////////////////////

#ifndef CROCODDYL_MULTIBODY_COST_BASE_HPP_
#define CROCODDYL_MULTIBODY_COST_BASE_HPP_

#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

#include "crocoddyl/multibody/fwd.hpp"
#include "crocoddyl/multibody/states/multibody.hpp"
#include "crocoddyl/core/data-collector-base.hpp"
#include "crocoddyl/core/activation-base.hpp"
#include "crocoddyl/core/utils/to-string.hpp"
#include "crocoddyl/core/activations/quadratic.hpp"

namespace crocoddyl {

template <typename _Scalar>
class CostModelAbstractTpl {
 public:
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW

  typedef _Scalar Scalar;
  typedef MathBaseTpl<Scalar> MathBase;
  typedef CostDataAbstractTpl<Scalar> CostDataAbstract;
  typedef StateMultibodyTpl<Scalar> StateMultibody;
  typedef ActivationModelAbstractTpl<Scalar> ActivationModelAbstract;
  typedef ActivationModelQuadTpl<Scalar> ActivationModelQuad;
  typedef DataCollectorAbstractTpl<Scalar> DataCollectorAbstract;
  typedef typename MathBase::VectorXs VectorXs;
  typedef typename MathBase::MatrixXs MatrixXs;

  CostModelAbstractTpl(boost::shared_ptr<StateMultibody> state, boost::shared_ptr<ActivationModelAbstract> activation,
                       const std::size_t& nu);
  CostModelAbstractTpl(boost::shared_ptr<StateMultibody> state, boost::shared_ptr<ActivationModelAbstract> activation);
  CostModelAbstractTpl(boost::shared_ptr<StateMultibody> state, const std::size_t& nr, const std::size_t& nu);
  CostModelAbstractTpl(boost::shared_ptr<StateMultibody> state, const std::size_t& nr);
  ~CostModelAbstractTpl();

  virtual void calc(const boost::shared_ptr<CostDataAbstract>& data, const Eigen::Ref<const VectorXs>& x,
                    const Eigen::Ref<const VectorXs>& u) = 0;
  virtual void calcDiff(const boost::shared_ptr<CostDataAbstract>& data, const Eigen::Ref<const VectorXs>& x,
                        const Eigen::Ref<const VectorXs>& u) = 0;
  virtual boost::shared_ptr<CostDataAbstract> createData(DataCollectorAbstract* const data);

  void calc(const boost::shared_ptr<CostDataAbstract>& data, const Eigen::Ref<const VectorXs>& x);
  void calcDiff(const boost::shared_ptr<CostDataAbstract>& data, const Eigen::Ref<const VectorXs>& x);

  const boost::shared_ptr<StateMultibody>& get_state() const;
  const boost::shared_ptr<ActivationModelAbstract>& get_activation() const;
  const std::size_t& get_nu() const;

  template <class ReferenceType>
  void set_reference(ReferenceType ref);

  template <class ReferenceType>
  ReferenceType get_reference() const;

 protected:
  virtual void set_referenceImpl(const std::type_info&, const void*);
  virtual void get_referenceImpl(const std::type_info&, void*) const;

  boost::shared_ptr<StateMultibody> state_;
  boost::shared_ptr<ActivationModelAbstract> activation_;
  std::size_t nu_;
  VectorXs unone_;
};

template <typename _Scalar>
struct CostDataAbstractTpl {
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW

  typedef _Scalar Scalar;
  typedef MathBaseTpl<Scalar> MathBase;
  typedef ActivationDataAbstractTpl<Scalar> ActivationDataAbstract;
  typedef DataCollectorAbstractTpl<Scalar> DataCollectorAbstract;
  typedef typename MathBase::VectorXs VectorXs;
  typedef typename MathBase::MatrixXs MatrixXs;

  template <template <typename Scalar> class Model>
  CostDataAbstractTpl(Model<Scalar>* const model, DataCollectorAbstract* const data)
      : shared(data),
        activation(model->get_activation()->createData()),
        cost(Scalar(0.)),
        Lx(model->get_state()->get_ndx()),
        Lu(model->get_nu()),
        Lxx(model->get_state()->get_ndx(), model->get_state()->get_ndx()),
        Lxu(model->get_state()->get_ndx(), model->get_nu()),
        Luu(model->get_nu(), model->get_nu()),
        r(model->get_activation()->get_nr()),
        Rx(model->get_activation()->get_nr(), model->get_state()->get_ndx()),
        Ru(model->get_activation()->get_nr(), model->get_nu()) {
    Lx.setZero();
    Lu.setZero();
    Lxx.setZero();
    Lxu.setZero();
    Luu.setZero();
    r.setZero();
    Rx.setZero();
    Ru.setZero();
  }
  virtual ~CostDataAbstractTpl() {}

  DataCollectorAbstract* shared;
  boost::shared_ptr<ActivationDataAbstract> activation;
  Scalar cost;
  VectorXs Lx;
  VectorXs Lu;
  MatrixXs Lxx;
  MatrixXs Lxu;
  MatrixXs Luu;
  VectorXs r;
  MatrixXs Rx;
  MatrixXs Ru;
};

}  // namespace crocoddyl

/* --- Details -------------------------------------------------------------- */
/* --- Details -------------------------------------------------------------- */
/* --- Details -------------------------------------------------------------- */
#include "crocoddyl/multibody/cost-base.hxx"

#endif  // CROCODDYL_MULTIBODY_COST_BASE_HPP_


/**
 * @file    Constants.cc
 * @brief   Defining the constants and extended constants
 * @author  [Yi-Mu "Enoch" Chen](https://github.com/yimuchen)
 */

#ifdef CMSSW_GIT_HASH
#include "UserUtils/Common/interface/STLUtils/StringUtils.hpp"
#include "UserUtils/PlotUtils/interface/Constants.hpp"
#else
#include "UserUtils/Common/STLUtils/StringUtils.hpp"
#include "UserUtils/PlotUtils/Constants.hpp"
#endif

#include <boost/algorithm/string.hpp>
#include <string>

namespace usr  {

namespace plt  {

/**
 * @details Plotting length in ROOT is defined using pixels (usually), with the
 * conversion of pixels into printable formats using the old standard of 72 DPI.
 *
 * Here we provide functions for convert from printing units (inches, mm... etc),
 * to the standard ROOT length units, as well as commonly used dimension in
 * CMS documents.
 */
namespace len {

extern const unsigned ROOT_DPI = 72;
extern const float INCH_TO_CM  = 2.54;

/**
 * @brief Convert inches to standard ROOT size.
 */
extern length_t
inch( const float x ){ return std::round( ROOT_DPI*x ); }

/**
 * @brief Convert centimeters to standard ROOT size.
 */
extern length_t
cm( const float x ){ return inch( x/INCH_TO_CM ); }

/**
 * @brief Conver millimeters to standard ROOT size.
 */
extern length_t
mm( const float x ){ return cm( x/10 ); }

/**
 * @brief A4 Paper is 210mm wide.
 */
extern length_t
a4paperwidth(){ return mm( 210 ); }

/**
 * @brief A4 paper is 297mm tall.
 */
extern length_t
a4paperheight(){ return mm( 297 ); }

/**
 * @brief Width of text area defined in CMS TDR is 160mm.
 */
extern length_t
a4textwidth_default(){ return mm( 160 ); }

/**
 * @brief Height of text area defined in CMS TDR is 235mm.
 */
extern length_t
a4textheight_default(){ return mm( 235 ); }

}/* len */

/**
 * @class usr::plt::font
 * Since font setting are more commonly copied around, implemenation of font
 * conversion functions would be a class to enable storage of variables.
 *
 * Fonts in ROOT (when using precision greater than 3) are defined in pixels,
 * which, when used together with the ROOT DPI of 72, makes it the common
 * unit of pts.
 */

/** @brief default font setting for CMS documents */
float font::default_basesize = 11;

/** @brief Recommended plot font for CMS documents */
font::face font::default_face = font::helvetica;

/** @brief default font line spacing multiplier */
float font::default_lineheight = 1.2;

/**
 * @brief Defining a font setting.
 * @param basesize   The base size of the font
 * @param face       The type-face of the font to use.
 * @param lineheight The lineheight **multiplier** to use.
 */
font::font( float basesize,
            face  face,
            float lineheight ) :
  _basesize( basesize ),
  _face( face ),
  _lineheight( em( lineheight ) )
{
}

/** @brief returning base size */
float
font::size() const { return _basesize; }

/** @brief returning line height (absolute, not relative to basesize) */
float
font::lineheight() const { return _lineheight; }

/** @brief returning the font face as standard ROOT font type */
short
font::fontface() const
{
  return ( _face * 10 )/10 + 3;
}

/** @brief returning scaled font size */
float
font::em( const float x ) const
{
  return x * _basesize;
}

/*-----------------------------------------------------------------------------
 *  Font-size conversion helper variables.
   --------------------------------------------------------------------------*/
static const float scaletable[7][4] = {
  // 10      11          12             others
  {
    5/10,     6/11.,      6/12.,         0.5// tiny
  },{
    8/10,     9/11.,      10/12.,        0.75// footnote
  },{
    9/10,     10/11.,     10.95/12.,     0.9// small
  },{
    12/10,    12/11.,     14.4/12.,      1.2// large
  },{
    14.4/10,  14.4/11.,   17.28/12.,     14.4// Large
  },{
    17.28/10, 17.28/11.,  20.74/12.,     1.72// LARGE
  },{
    20.7/10,  20.7/11.,   24.8/12.,      2   }// huge
};

enum e_size : short
{
  e_tiny,
  e_footnote,
  e_small,
  e_large,
  e_llarge,
  e_lllarge,
  e_huge
};

static unsigned
getcol( const float x )
{
  return x == 10 ? 0 :
         x == 11 ? 1 :
         x == 12 ? 2 :
         3;
}

/** @{
 * @brief conversion to LateX [in-build font sizes](https://en.wikibooks.org/wiki/LaTeX/Fonts#Built-in_sizes)
 */
float
font::tiny() const
{ return _basesize*scaletable[e_tiny][getcol( _basesize )]; }

float
font::footnote() const
{ return _basesize*scaletable[e_footnote][getcol( _basesize )]; }

float
font::small() const
{ return _basesize*scaletable[e_small][getcol( _basesize )]; }

float
font::large() const
{ return _basesize*scaletable[e_large][getcol( _basesize )]; }

float
font::Large() const
{ return _basesize*scaletable[e_llarge][getcol( _basesize )]; }

float
font::LARGE() const
{ return _basesize*scaletable[e_lllarge][getcol( _basesize )]; }

float
font::huge() const
{ return _basesize*scaletable[e_huge][getcol( _basesize )]; }

/** @} */

/**
 * Notice that these strings are to be used in the ROOT plotting functions,
 * so it will be using the ROOT style '#' for latex commands rather than
 * backslashes
 */
namespace unit  {
const std::string GeV   = "GeV";
const std::string GeVc  = "GeV/#it{c}";
const std::string GeVcc = "GeV/#it{c}^{2}";
}/* unit  */

namespace cap  {
const std::string prelim = "Preliminary";
const std::string sim    = "Simulation";
}/* cap  */

/**
 * @details  Probably one of the worst offenders of magic-number usage in @ROOT:
 * that of the styling variables. Unless you have memorised the contents of the
 * documentation, you would probably have no idea what someone else code is doing
 * to stylise the plots.
 */
namespace sty {

/**
 * @{
 * @brief Predefined fill styles for @ROOT{TAttrFill}.
 * */
const short fillnone         = 0;
const short fillsolid        = 1001;
const short filldotdense     = 3001;
const short filldot          = 3002;
const short filldotsparse    = 3003;
const short fillhashdense    = fillhash( 1, angle1( 45 ), angle2( 45 ) );
const short fillhashsparse   = fillhash( 5, angle1( 45 ), angle2( 45 ) );
const short fillhashforward  = fillhash( 2, angle1( 60 ), angle2( -1 ) );
const short fillhashbackward = fillhash( 2, angle1( -1 ), angle2( 120 ) );
/** @} */

/**
 * @brief Translating a fill style string to the corresponding integer code
 *
 * fill( "solid" ) will returng 1001 and such.
 */
const short
fill( const std::string& x )
{
  const std::string op_string = StripToNaming( x );

#define STRINGIFY( x ) \
  op_string == #x ? fill ## x :

  return STRINGIFY( none )
         STRINGIFY( solid )
         STRINGIFY( dotdense )
         STRINGIFY( dot )
         STRINGIFY( dotsparse )
         STRINGIFY( hashdense )
         STRINGIFY( hashsparse )
         STRINGIFY( hashforward )
         STRINGIFY( hashbackward )
         fillnone;
#undef STRINGIFY
}

/**
 * @brief  A human readable interface to hash-fill styles in @ROOT{TAttFill}.
 *
 * To be used with the distmm(), angle1(), and angle2() functions to make what
 * the hash is suppose to look like look more natural. The call to a function
 * would SetFillStyle Function would then be something like:
 * ```
 * myobject.SetFillStyle( fillhash(distmm(0.9), angle1(90), angle2(100)));
 * ```
 *
 * @param  distance distance between hash lines
 * @param  angle1   angle of first hash lines
 * @param  angle2   angle of second hash lines
 * @return          the value corresponding to the angle code.
 */
const short
fillhash( unsigned short distance,
          unsigned short angle1,
          unsigned short angle2 )
{
  distance = std::max( distance, (unsigned short)1 );
  distance = std::min( distance, (unsigned short)9 );
  angle1   = std::min( angle1,  (unsigned short)9 );
  angle2   = std::min( angle2,  (unsigned short)9 );
  return 3000 + 100*distance+10*angle1+1*angle2;
}

/**
 * @brief Converting actual hash distances in millimeters into the unit
 * used by the distance string.
 *
 * @ROOT{TAttFill} only defines 0.5mm converts to 1 , and 6mm converts to 9.
 * There we assume a linear relation, rounding to the closest value.
 */
const short
distmm( const float x )
{
  return x < usr::plt::len::mm( 0.84375 ) ? 1 :
         x < usr::plt::len::mm( 1.53125 ) ? 2 :
         x < usr::plt::len::mm( 2.21875 ) ? 3 :
         x < usr::plt::len::mm( 2.90625 ) ? 4 :
         x < usr::plt::len::mm( 3.59375 ) ? 5 :
         x < usr::plt::len::mm( 4.28125 ) ? 6 :
         x < usr::plt::len::mm( 4.96875 ) ? 7 :
         x < usr::plt::len::mm( 5.65625 ) ? 8 :
         9;
}

/**
 * @brief Converting the first angle of the hash in degrees into defined digits.
 *
 * Angles are defined in 10 degrees increments (with the exception of the 45
 * degree mark), rounding to closest defined value.
 */
const short
angle1( const float x )
{
  return x < 0    ? anglenone :
         x < 5    ? 0 :
         x < 15   ? 1 :
         x < 25   ? 2 :
         x < 37.5 ? 3 :
         x < 52.5 ? 4 :
         x < 65   ? 6 :
         x < 75   ? 7 :
         x < 85   ? 8 :
         x <= 90  ? 9 :
         anglenone;
}

/**
 * @brief Converting the first angle of the hash in degrees into defined digits.
 *
 * Angles are defined in 10 degrees increments (with the exception of the 45
 * degree mark), rounding to closest defined value.
 */
const short
angle2( const float x )
{
  return x > 180   ? anglenone :
         x > 175   ? 0 :
         x > 165   ? 1 :
         x > 155   ? 2 :
         x > 142.5 ? 3 :
         x > 127.5 ? 4 :
         x > 115   ? 6 :
         x > 105   ? 7 :
         x > 95    ? 8 :
         x >= 90   ? 9 :
         anglenone;
}

/** @brief Flag to use if you want to diplay a certain angle display */
const short anglenone = 5;

/**
 * @{
 * @brief common settings used by @ROOT{TAttLine}.
 * */
const short linsolid     = 1;
const short lindotted    = 2;
const short lindensedot  = 3;
const short lindashed    = 9;
const short linshortdash = 7;
/** @} */

/**
 * @brief Converting a string to the corresponding line style integer code.
 *
 * line( "solid" ) will returng linsolid(=1). An so on. By default it will
 * returng linsolid.
 */
const short
line( const std::string& x )
{
  const std::string op = StripToNaming( x );
#define STRINGIFY( x ) \
  op == #x ? lin ## x :

  return STRINGIFY( solid     )
         STRINGIFY( dotted    )
         STRINGIFY( densedot  )
         STRINGIFY( dashed    )
         STRINGIFY( shortdash )
         linsolid;

#undef STRINGIFY
}

/**
 * @{
 * @brief common settings used by @ROOT{TAttMarker}.
 * */
extern const short mkrcircle           = 20;
extern const short mkrsquare           = 21;
extern const short mkrtriangleup       = 22;
extern const short mkrtriangledown     = 23;
extern const short mkrdiamond          = 33;
extern const short mkrcross            = 34;
extern const short mkrstar             = 29;
extern const short mkropencircle       = 24;
extern const short mkropensquare       = 25;
extern const short mkropentriangleup   = 26;
extern const short mkropentriangledown = 32;
extern const short mkropendiamond      = 27;
extern const short mkropencross        = 28;
extern const short mkropenstar         = 30;
/** @} */

/**
 * @brief Given a string return the corresponding marker style code
 *
 * marker("circle") will returng mkrcircle(=20) so so forth. By default, it will
 * returng mkrcircle if the string is not known.
 */
const short
marker( const std::string x )
{
  const std::string op = StripToNaming( x );
#define STRINGIFY( x ) \
  op == #x ? mkr ## x :

  return STRINGIFY( circle           )
         STRINGIFY( square           )
         STRINGIFY( triangleup       )
         STRINGIFY( triangledown     )
         STRINGIFY( diamond          )
         STRINGIFY( cross            )
         STRINGIFY( star             )
         STRINGIFY( opencircle       )
         STRINGIFY( opensquare       )
         STRINGIFY( opentriangleup   )
         STRINGIFY( opentriangledown )
         STRINGIFY( opendiamond      )
         STRINGIFY( opencross        )
         STRINGIFY( openstar         )
         mkrcircle;
#undef STRINGIFY
}

}/* sty */

}/* plt  */

}/* usr  */


//---------------------------------------------------------------------------//
//!
//! \file   tstGamowInterpolator.cpp
//! \author Alex Robinson
//! \brief  The Gamow interpolator unit tests
//!
//---------------------------------------------------------------------------//

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

// Boost Includes
#include <boost/units/systems/si.hpp>
#include <boost/units/systems/cgs.hpp>
#include <boost/units/io.hpp>

// FRENSIE Includes
#include "Utility_GamowInterpolator.hpp"
#include "Utility_QuantityTraits.hpp"
#include "Utility_ElectronVoltUnit.hpp"
#include "Utility_BarnUnit.hpp"
#include "Utility_UnitTestHarnessWithMain.hpp"

//---------------------------------------------------------------------------//
// Testing Types
//---------------------------------------------------------------------------//

using boost::units::quantity;
using Utility::Units::MegaElectronVolt;
using Utility::Units::MeV;
using Utility::Units::Barn;
using Utility::Units::barn;
using Utility::Units::barns;

//---------------------------------------------------------------------------//
// Tests.
//---------------------------------------------------------------------------//
// Check that an instance of the interpolator can be retrieved
FRENSIE_UNIT_TEST( GamowInterpolator, getExothermicInstance )
{
  std::shared_ptr<const Utility::Interpolator<double> > exothermic_instance =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  FRENSIE_CHECK( exothermic_instance.get() != NULL );
}

//---------------------------------------------------------------------------//
// Check that an instance of the interpolator can be retrieved
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, getExothermicInstance )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > exothermic_instance =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  FRENSIE_CHECK( exothermic_instance.get() != NULL );
}

//---------------------------------------------------------------------------//
// Check that the interpolation type can be returned
FRENSIE_UNIT_TEST( GamowInterpolator, getInterpolationType )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  FRENSIE_CHECK_EQUAL( interpolator->getInterpolationType(),
                       Utility::GAMOW_INTERPOLATION );
}

//---------------------------------------------------------------------------//
// Check that the interpolation type can be returned
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, getInterpolationType )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  FRENSIE_CHECK_EQUAL( interpolator->getInterpolationType(),
                       Utility::GAMOW_INTERPOLATION );
}

//---------------------------------------------------------------------------//
// Check that the interpolation type can be returned
FRENSIE_UNIT_TEST( GamowInterpolator, isIndepVarInValidRange )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  FRENSIE_CHECK( !interpolator->isIndepVarInValidRange(
				       -std::numeric_limits<double>::max() ) );
  FRENSIE_CHECK( !interpolator->isIndepVarInValidRange( 0.0 ) );
  FRENSIE_CHECK( interpolator->isIndepVarInValidRange(
					std::numeric_limits<double>::min() ) );
  FRENSIE_CHECK( interpolator->isIndepVarInValidRange(
					std::numeric_limits<double>::max() ) );

  interpolator.reset( new Utility::GamowInterpolator<double>( 1.0 ) );

  FRENSIE_CHECK( !interpolator->isIndepVarInValidRange(
				       -std::numeric_limits<double>::max() ) );
  FRENSIE_CHECK( !interpolator->isIndepVarInValidRange( 1.0 ) );
  FRENSIE_CHECK( interpolator->isIndepVarInValidRange( 1.0+ 1e-12 ) );
  FRENSIE_CHECK( interpolator->isIndepVarInValidRange(
					std::numeric_limits<double>::max() ) );
}

//---------------------------------------------------------------------------//
// Check that the interpolation type can be returned
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, isIndepVarInValidRange )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  FRENSIE_CHECK( !interpolator->isIndepVarInValidRange(
                                   -std::numeric_limits<double>::max()*MeV ) );
  FRENSIE_CHECK( !interpolator->isIndepVarInValidRange( 0.0*MeV ) );
  FRENSIE_CHECK( interpolator->isIndepVarInValidRange(
                                    std::numeric_limits<double>::min()*MeV ) );
  FRENSIE_CHECK( interpolator->isIndepVarInValidRange(
                                    std::numeric_limits<double>::max()*MeV ) );

  interpolator.reset( new Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>( 1.0*MeV ) );

  FRENSIE_CHECK( !interpolator->isIndepVarInValidRange(
                                   -std::numeric_limits<double>::max()*MeV ) );
  FRENSIE_CHECK( !interpolator->isIndepVarInValidRange( 1.0*MeV ) );
  FRENSIE_CHECK( interpolator->isIndepVarInValidRange( (1.0+1e-12)*MeV ) );
  FRENSIE_CHECK( interpolator->isIndepVarInValidRange(
                                    std::numeric_limits<double>::max()*MeV ) );
}

//---------------------------------------------------------------------------//
// Check that the validity of a dependent variable can be tested
FRENSIE_UNIT_TEST( GamowInterpolator, isDepVarInValidRange )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  FRENSIE_CHECK( !interpolator->isDepVarInValidRange(
				       -std::numeric_limits<double>::max() ) );
  FRENSIE_CHECK( !interpolator->isDepVarInValidRange( 0.0 ) );
  FRENSIE_CHECK( interpolator->isDepVarInValidRange(
					std::numeric_limits<double>::min() ) );
  FRENSIE_CHECK( interpolator->isDepVarInValidRange(
					std::numeric_limits<double>::max() ) );
}

//---------------------------------------------------------------------------//
// Check that the validity of a dependent variable can be tested
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, isDepVarInValidRange )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  FRENSIE_CHECK( !interpolator->isDepVarInValidRange(
				 -std::numeric_limits<double>::max()*barns ) );
  FRENSIE_CHECK( !interpolator->isDepVarInValidRange( 0.0*barn ) );
  FRENSIE_CHECK( interpolator->isDepVarInValidRange(
				  std::numeric_limits<double>::min()*barn ) );
  FRENSIE_CHECK( interpolator->isDepVarInValidRange(
				  std::numeric_limits<double>::max()*barns ) );
}

//---------------------------------------------------------------------------//
// Check that an independent variable can be processed
FRENSIE_UNIT_TEST( GamowInterpolator, processIndepVar )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  FRENSIE_CHECK_FLOATING_EQUALITY( 0.1, interpolator->processIndepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( 1.0, interpolator->processIndepVar( 1.0 ), 1e-15 );

  interpolator.reset( new Utility::GamowInterpolator<double>( 1.0 ) );

  FRENSIE_CHECK_FLOATING_EQUALITY( 0.1, interpolator->processIndepVar( 1.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( 1.0, interpolator->processIndepVar( 2.0 ), 1e-15 );
}

//---------------------------------------------------------------------------//
// Check that an independent variable can be processed
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, processIndepVar )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  FRENSIE_CHECK_FLOATING_EQUALITY(
                        0.1, interpolator->processIndepVar( 0.1*MeV ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
                        1.0, interpolator->processIndepVar( 1.0*MeV ), 1e-15 );

  interpolator.reset( new Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>( 1.0*MeV ) );

  FRENSIE_CHECK_FLOATING_EQUALITY(
                        0.1, interpolator->processIndepVar( 1.1*MeV ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
                        1.0, interpolator->processIndepVar( 2.0*MeV ), 1e-15 );
}

//---------------------------------------------------------------------------//
// Check that a dependent variable can be processed
FRENSIE_UNIT_TEST( GamowInterpolator, processDepVar )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  FRENSIE_CHECK_FLOATING_EQUALITY( 0.1, interpolator->processDepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( 1.0, interpolator->processDepVar( 1.0 ), 1e-15 );

  interpolator.reset( new Utility::GamowInterpolator<double>( 1.0 ) );

  FRENSIE_CHECK_FLOATING_EQUALITY( 0.1, interpolator->processDepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( 1.0, interpolator->processDepVar( 1.0 ), 1e-15 );
}

//---------------------------------------------------------------------------//
// Check that a dependent variable can be processed
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, processDepVar )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  FRENSIE_CHECK_FLOATING_EQUALITY( 0.1, interpolator->processDepVar( 0.1*barn ), 1e-15);
  FRENSIE_CHECK_FLOATING_EQUALITY( 1.0, interpolator->processDepVar( 1.0*barn ), 1e-15);

  interpolator.reset( new Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>( 1.0*MeV ) );

  FRENSIE_CHECK_FLOATING_EQUALITY( 0.1, interpolator->processDepVar( 0.1*barn ), 1e-15);
  FRENSIE_CHECK_FLOATING_EQUALITY( 1.0, interpolator->processDepVar( 1.0*barn ), 1e-15);
}

//---------------------------------------------------------------------------//
// Check that a processed independent variable can be recovered
FRENSIE_UNIT_TEST( GamowInterpolator,recoverProcessedIndepVar )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  FRENSIE_CHECK_FLOATING_EQUALITY(
                   0.1, interpolator->recoverProcessedIndepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
                   1.0, interpolator->recoverProcessedIndepVar( 1.0 ), 1e-15 );

  interpolator.reset( new Utility::GamowInterpolator<double>( 1.0 ) );

  FRENSIE_CHECK_FLOATING_EQUALITY(
                   1.1, interpolator->recoverProcessedIndepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
                   2.0, interpolator->recoverProcessedIndepVar( 1.0 ), 1e-15 );
}

//---------------------------------------------------------------------------//
// Check that a processed independent variable can be recovered
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, recoverProcessedIndepVar )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  FRENSIE_CHECK_FLOATING_EQUALITY(
               0.1*MeV, interpolator->recoverProcessedIndepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
               1.0*MeV, interpolator->recoverProcessedIndepVar( 1.0 ), 1e-15 );

  interpolator.reset( new Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>( 1.0*MeV ) );

  FRENSIE_CHECK_FLOATING_EQUALITY(
               1.1*MeV, interpolator->recoverProcessedIndepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
               2.0*MeV, interpolator->recoverProcessedIndepVar( 1.0 ), 1e-15 );
}

//---------------------------------------------------------------------------//
// Check that a processed dependent variable can be recovered
FRENSIE_UNIT_TEST( GamowInterpolator, recoverProcessedDepVar )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  FRENSIE_CHECK_FLOATING_EQUALITY(
                     0.1, interpolator->recoverProcessedDepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
                     1.0, interpolator->recoverProcessedDepVar( 1.0 ), 1e-15 );

  interpolator.reset( new Utility::GamowInterpolator<double>( 1.0 ) );

  FRENSIE_CHECK_FLOATING_EQUALITY(
                     0.1, interpolator->recoverProcessedDepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
                     1.0, interpolator->recoverProcessedDepVar( 1.0 ), 1e-15 );
}

//---------------------------------------------------------------------------//
// Check that a processed dependent variable can be recovered
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, recoverProcessedDepVar )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  FRENSIE_CHECK_FLOATING_EQUALITY(
                0.1*barn, interpolator->recoverProcessedDepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
                1.0*barn, interpolator->recoverProcessedDepVar( 1.0 ), 1e-15 );

  interpolator.reset( new Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>( 1.0*MeV ) );

  FRENSIE_CHECK_FLOATING_EQUALITY(
                0.1*barn, interpolator->recoverProcessedDepVar( 0.1 ), 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
                1.0*barn, interpolator->recoverProcessedDepVar( 1.0 ), 1e-15 );
}

//---------------------------------------------------------------------------//
// Check that interpolation between two points can be done
FRENSIE_UNIT_TEST( GamowInterpolator, interpolate )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  double x0 = 0.1, x1 = 10.0, x = 1.0;
  double y0 = 10.0, y1 = 1000.0;

  double y = interpolator->interpolate( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 1093.9254005049686, 1e-12 );

  x = 0.1;

  y = interpolator->interpolate( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 10.0, 1e-12 );

  x = 10.0;

  y = interpolator->interpolate( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 1000.0, 1e-12 );

  interpolator.reset( new Utility::GamowInterpolator<double>( 0.05 ) );

  x = 1.0;

  y = interpolator->interpolate( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 2077.3469921631245, 1e-12 );

  x = 0.1;

  y = interpolator->interpolate( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 10.0, 1e-12 );

  x = 10.0;

  y = interpolator->interpolate( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 1000.0, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that interpolation between two points can be done
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, interpolate )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  quantity<MegaElectronVolt,double> e0 = 0.1*MeV, e1 = 10.0*MeV, e = 1.0*MeV;
  quantity<Barn,double> cs0 = 10.0*barns, cs1 = 1000.0*barns;

  quantity<Barn,double> cs = interpolator->interpolate( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 1093.9254005049686*barns, 1e-12 );

  e = 0.1*MeV;

  cs = interpolator->interpolate( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 10.0*barn, 1e-12 );

  e = 10.0*MeV;

  cs = interpolator->interpolate( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 1000.0*barn, 1e-12 );

  interpolator.reset( new Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>( 0.05*MeV ) );

  e = 1.0*MeV;

  cs = interpolator->interpolate( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 2077.3469921631245*barn, 1e-12 );

  e = 0.1*MeV;

  cs = interpolator->interpolate( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 10.0*barn, 1e-12 );

  e = 10.0*MeV;

  cs = interpolator->interpolate( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 1000.0*barn, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that interpolation between two processed points can be done
FRENSIE_UNIT_TEST( GamowInterpolator, interpolateProcessed )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  double processed_x0 = interpolator->processIndepVar( 0.1 );
  double processed_x1 = interpolator->processIndepVar( 10.0 );
  double processed_x = interpolator->processIndepVar( 1.0 );
  double processed_y0 = interpolator->processDepVar( 10.0 );
  double processed_y1 = interpolator->processDepVar( 1000.0 );

  double b_value = std::log( (1000.0*10.0)/(10.0*0.1) )/
    ( 1.0/sqrt(processed_x0) - 1.0/sqrt(processed_x1) );

  double y = interpolator->interpolateProcessed( processed_x0,
                                                 processed_x,
                                                 processed_y0,
                                                 b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 1093.9254005049686, 1e-12 );

  processed_x = interpolator->processIndepVar( 0.1 );

  y = interpolator->interpolateProcessed( processed_x0,
                                          processed_x,
                                          processed_y0,
                                          b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 10.0, 1e-12 );

  processed_x = interpolator->processIndepVar( 10.0 );

  y = interpolator->interpolateProcessed( processed_x0,
                                          processed_x,
                                          processed_y0,
                                          b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 1000.0, 1e-12 );

  interpolator.reset( new Utility::GamowInterpolator<double>( 0.05 ) );

  processed_x0 = interpolator->processIndepVar( 0.1 );
  processed_x1 = interpolator->processIndepVar( 10.0 );
  processed_x = interpolator->processIndepVar( 1.0 );

  b_value = std::log( (1000.0*10.0)/(10.0*0.1) )/
    ( 1.0/sqrt(processed_x0) - 1.0/sqrt(processed_x1) );

  y = interpolator->interpolateProcessed( processed_x0,
                                          processed_x,
                                          processed_y0,
                                          b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 2077.3469921631245, 1e-12 );

  processed_x = interpolator->processIndepVar( 0.1 );

  y = interpolator->interpolateProcessed( processed_x0,
                                          processed_x,
                                          processed_y0,
                                          b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 10.0, 1e-12 );

  processed_x = interpolator->processIndepVar( 10.0 );

  y = interpolator->interpolateProcessed( processed_x0,
                                          processed_x,
                                          processed_y0,
                                          b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( y, 1000.0, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that interpolation between two processed points can be done
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, interpolateProcessed )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  double processed_e0 = interpolator->processIndepVar( 0.1*MeV );
  double processed_e1 = interpolator->processIndepVar( 10.0*MeV );
  double processed_e = interpolator->processIndepVar( 1.0*MeV );
  double processed_cs0 = interpolator->processDepVar( 10.0*barns );
  double processed_cs1 = interpolator->processDepVar( 1000.0*barns );

  double b_value = std::log( (1000.0*10.0)/(10.0*0.1) )/
    ( 1.0/sqrt(processed_e0) - 1.0/sqrt(processed_e1) );

  quantity<Barn,double> cs =
    interpolator->interpolateProcessed( processed_e0,
                                        processed_e,
                                        processed_cs0,
                                        b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 1093.9254005049686*barns, 1e-12 );

  processed_e = interpolator->processIndepVar( 0.1*MeV );

  cs = interpolator->interpolateProcessed( processed_e0,
                                           processed_e,
                                           processed_cs0,
                                           b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 10.0*barns, 1e-12 );

  processed_e = interpolator->processIndepVar( 10.0*MeV );

  cs = interpolator->interpolateProcessed( processed_e0,
                                           processed_e,
                                           processed_cs0,
                                           b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 1000.0*barns, 1e-12 );

  interpolator.reset( new Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>( 0.05*MeV ) );

  processed_e0 = interpolator->processIndepVar( 0.1*MeV );
  processed_e1 = interpolator->processIndepVar( 10.0*MeV );
  processed_e = interpolator->processIndepVar( 1.0*MeV );

  b_value = std::log( (1000.0*10.0)/(10.0*0.1) )/
    ( 1.0/sqrt(processed_e0) - 1.0/sqrt(processed_e1) );

  cs = interpolator->interpolateProcessed( processed_e0,
                                           processed_e,
                                           processed_cs0,
                                           b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 2077.3469921631245*barns, 1e-12 );

  processed_e = interpolator->processIndepVar( 0.1*MeV );

  cs = interpolator->interpolateProcessed( processed_e0,
                                           processed_e,
                                           processed_cs0,
                                           b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 10.0*barns, 1e-12 );

  processed_e = interpolator->processIndepVar( 10.0*MeV );

  cs = interpolator->interpolateProcessed( processed_e0,
                                           processed_e,
                                           processed_cs0,
                                           b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( cs, 1000.0*barns, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that interpolation between two points can be done
FRENSIE_UNIT_TEST( GamowInterpolator, interpolateAndProcess )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  double x0 = 0.1, x1 = 10.0, x = 1.0;
  double y0 = 10.0, y1 = 1000.0;

  double processed_y =
    interpolator->interpolateAndProcess( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 1093.9254005049686, 1e-12 );

  x = 0.1;

  processed_y = interpolator->interpolateAndProcess( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 10.0, 1e-12 );

  x = 10.0;

  processed_y = interpolator->interpolateAndProcess( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 1000.0, 1e-12 );

  interpolator.reset( new Utility::GamowInterpolator<double>( 0.05 ) );

  x = 1.0;

  processed_y = interpolator->interpolateAndProcess( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 2077.3469921631245, 1e-12 );

  x = 0.1;

  processed_y = interpolator->interpolateAndProcess( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 10.0, 1e-12 );

  x = 10.0;

  processed_y = interpolator->interpolateAndProcess( x0, x1, x, y0, y1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 1000.0, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that interpolation between two points can be done
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator, interpolateAndProcess )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  quantity<MegaElectronVolt,double> e0 = 0.1*MeV, e1 = 10.0*MeV, e = 1.0*MeV;
  quantity<Barn,double> cs0 = 10.0*barns, cs1 = 1000.0*barns;

  double processed_cs =
    interpolator->interpolateAndProcess( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 1093.9254005049686, 1e-12 );

  e = 0.1*MeV;

  processed_cs = interpolator->interpolateAndProcess( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 10.0, 1e-12 );

  e = 10.0*MeV;

  processed_cs = interpolator->interpolateAndProcess( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 1000.0, 1e-12 );

  interpolator.reset( new Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>( 0.05*MeV ) );

  e = 1.0*MeV;

  processed_cs = interpolator->interpolateAndProcess( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 2077.3469921631245, 1e-12 );

  e = 0.1*MeV;

  processed_cs = interpolator->interpolateAndProcess( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 10.0, 1e-12 );

  e = 10.0*MeV;

  processed_cs = interpolator->interpolateAndProcess( e0, e1, e, cs0, cs1 );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 1000.0, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that interpolation between two processed points can be done
FRENSIE_UNIT_TEST( GamowInterpolator, interpolateProcessedAndProcess )
{
  std::shared_ptr<const Utility::Interpolator<double> > interpolator =
    Utility::GamowInterpolator<double>::getExothermicInstance();

  double processed_x0 = interpolator->processIndepVar( 0.1 );
  double processed_x1 = interpolator->processIndepVar( 10.0 );
  double processed_x = interpolator->processIndepVar( 1.0 );
  double processed_y0 = interpolator->processDepVar( 10.0 );
  double processed_y1 = interpolator->processDepVar( 1000.0 );

  double b_value = std::log( (1000.0*10.0)/(10.0*0.1) )/
    ( 1.0/sqrt(processed_x0) - 1.0/sqrt(processed_x1) );

  double processed_y = interpolator->interpolateProcessedAndProcess(
                                                                  processed_x0,
                                                                  processed_x,
                                                                  processed_y0,
                                                                  b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 1093.9254005049686, 1e-12 );

  processed_x = interpolator->processIndepVar( 0.1 );

  processed_y = interpolator->interpolateProcessedAndProcess( processed_x0,
                                                              processed_x,
                                                              processed_y0,
                                                              b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 10.0, 1e-12 );

  processed_x = interpolator->processIndepVar( 10.0 );

  processed_y = interpolator->interpolateProcessedAndProcess( processed_x0,
                                                              processed_x,
                                                              processed_y0,
                                                              b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 1000.0, 1e-12 );

  interpolator.reset( new Utility::GamowInterpolator<double>( 0.05 ) );

  processed_x0 = interpolator->processIndepVar( 0.1 );
  processed_x1 = interpolator->processIndepVar( 10.0 );
  processed_x = interpolator->processIndepVar( 1.0 );

  b_value = std::log( (1000.0*10.0)/(10.0*0.1) )/
    ( 1.0/sqrt(processed_x0) - 1.0/sqrt(processed_x1) );

  processed_y = interpolator->interpolateProcessedAndProcess( processed_x0,
                                                              processed_x,
                                                              processed_y0,
                                                              b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 2077.3469921631245, 1e-12 );

  processed_x = interpolator->processIndepVar( 0.1 );

  processed_y = interpolator->interpolateProcessedAndProcess( processed_x0,
                                                              processed_x,
                                                              processed_y0,
                                                              b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 10.0, 1e-12 );

  processed_x = interpolator->processIndepVar( 10.0 );

  processed_y = interpolator->interpolateProcessedAndProcess( processed_x0,
                                                              processed_x,
                                                              processed_y0,
                                                              b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_y, 1000.0, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that interpolation between two processed points can be done
FRENSIE_UNIT_TEST( GamowUnitAwareInterpolator,
                   interpolateProcessedAndProcess )
{
  std::shared_ptr<const Utility::UnitAwareInterpolator<MegaElectronVolt,Barn,double> > interpolator =
    Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>::getExothermicInstance();

  double processed_e0 = interpolator->processIndepVar( 0.1*MeV );
  double processed_e1 = interpolator->processIndepVar( 10.0*MeV );
  double processed_e = interpolator->processIndepVar( 1.0*MeV );
  double processed_cs0 = interpolator->processDepVar( 10.0*barns );
  double processed_cs1 = interpolator->processDepVar( 1000.0*barns );

  double b_value = std::log( (1000.0*10.0)/(10.0*0.1) )/
    ( 1.0/sqrt(processed_e0) - 1.0/sqrt(processed_e1) );

  double processed_cs = interpolator->interpolateProcessedAndProcess(
                                                                 processed_e0,
                                                                 processed_e,
                                                                 processed_cs0,
                                                                 b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 1093.9254005049686, 1e-12 );

  processed_e = interpolator->processIndepVar( 0.1*MeV );

  processed_cs = interpolator->interpolateProcessedAndProcess( processed_e0,
                                                               processed_e,
                                                               processed_cs0,
                                                               b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 10.0, 1e-12 );

  processed_e = interpolator->processIndepVar( 10.0*MeV );

  processed_cs = interpolator->interpolateProcessedAndProcess( processed_e0,
                                                               processed_e,
                                                               processed_cs0,
                                                               b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 1000.0, 1e-12 );

  interpolator.reset( new Utility::GamowUnitAwareInterpolator<MegaElectronVolt,Barn,double>( 0.05*MeV ) );

  processed_e0 = interpolator->processIndepVar( 0.1*MeV );
  processed_e1 = interpolator->processIndepVar( 10.0*MeV );
  processed_e = interpolator->processIndepVar( 1.0*MeV );

  b_value = std::log( (1000.0*10.0)/(10.0*0.1) )/
    ( 1.0/sqrt(processed_e0) - 1.0/sqrt(processed_e1) );

  processed_cs = interpolator->interpolateProcessedAndProcess( processed_e0,
                                                               processed_e,
                                                               processed_cs0,
                                                               b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 2077.3469921631245, 1e-12 );

  processed_e = interpolator->processIndepVar( 0.1*MeV );

  processed_cs = interpolator->interpolateProcessedAndProcess( processed_e0,
                                                               processed_e,
                                                               processed_cs0,
                                                               b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 10.0, 1e-12 );

  processed_e = interpolator->processIndepVar( 10.0*MeV );

  processed_cs = interpolator->interpolateProcessedAndProcess( processed_e0,
                                                               processed_e,
                                                               processed_cs0,
                                                               b_value );

  FRENSIE_CHECK_FLOATING_EQUALITY( processed_cs, 1000.0, 1e-12 );
}

//---------------------------------------------------------------------------//
// end tstGamowInterpolator.cpp
//---------------------------------------------------------------------------//


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

#include <cstddef>
#include <chain.h>
#include <chainparams.h>
#include <pow.h>
#include <random.h>
#include <util.h>
#include <test/test_bitcoin.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(pow_tests, BasicTestingSetup)

/* Test calculation of next difficulty target with no constraints applying */
BOOST_AUTO_TEST_CASE(get_next_work)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    // int64_t nLastRetargetTime = 1261130161; // Block #30240
    CBlockIndex pindexLast;
    pindexLast.nHeight = 32255;
    pindexLast.nTime = 1418407764;  // Block #32255
    pindexLast.nDifficulty = 0x1d00ffff; // actually 24.59237286
    CBlock pblock;
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&pindexLast, &pblock, chainParams->GetConsensus()), 0xffff00001d00ffffU /*0x1d00d86aU*/);
}

/* Test the constraint on the upper bound for next work */
BOOST_AUTO_TEST_CASE(get_next_work_pow_limit)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    // int64_t nLastRetargetTime = 1231006505; // Block #0
    CBlockIndex pindexLast;
    pindexLast.nHeight = 2015;
    pindexLast.nTime = 1414163232;  // Block #2015
    pindexLast.nDifficulty = 0x1d00ffff; // actually 20.26221364
    CBlock pblock;
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&pindexLast, &pblock, chainParams->GetConsensus()), 0xffff00001d00ffffU /*0x1d00ffffU*/);
}

/* Test the constraint on the lower bound for actual time taken */
BOOST_AUTO_TEST_CASE(get_next_work_lower_limit_actual)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    // int64_t nLastRetargetTime = 1279008237; // Block #66528
    CBlockIndex pindexLast;
    pindexLast.nHeight = 68543;
    pindexLast.nTime = 1423443655;  // Block #68543
    pindexLast.nDifficulty = 0x1c05a3f4; // actually 23.71308238
    CBlock pblock;
    // BOOST_CHECK_EQUAL(GetNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1c0168fdU);
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&pindexLast, &pblock, chainParams->GetConsensus()), 0xffff00001c05a3f4U /*0x1c0168fdU*/);
}

/* Test the constraint on the upper bound for actual time taken */
BOOST_AUTO_TEST_CASE(get_next_work_upper_limit_actual)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    // int64_t nLastRetargetTime = 1263163443; // NOTE: Not an actual block time
    CBlockIndex pindexLast;
    pindexLast.nHeight = 46367;
    pindexLast.nTime = 1420363245;  // Block #46367
    pindexLast.nDifficulty = 0x1c387f6f; // actually 24.00984862
    CBlock pblock;
    // BOOST_CHECK_EQUAL(GetNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00e1fdU);
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&pindexLast, &pblock, chainParams->GetConsensus()), 0xffff00001c387f6fU /*0x1d00e1fdU*/);
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_negative_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nDifficulty;
    // nDifficulty = UintToArith256(consensus.powLimit).GetCompact(true);
    uint256 hashTarget = uint256S("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
    nDifficulty = UintToArith256(hashTarget).GetCompact(true);
    hash.SetHex("0x1");
    uint16_t nShift = 20;
    std::vector<unsigned char> nAdd;
    nAdd.assign(1, 0);
    BOOST_CHECK(!CheckProofOfWork(hash, nShift, &nAdd, nDifficulty, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_overflow_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nDifficulty = ~0x00800000;
    hash.SetHex("0x1");
    uint16_t nShift = 20;
    std::vector<unsigned char> nAdd;
    nAdd.assign(1, 0);
    BOOST_CHECK(!CheckProofOfWork(hash, nShift, &nAdd, nDifficulty, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_too_easy_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nDifficulty;
    // arith_uint256 nDifficulty_arith = UintToArith256(consensus.powLimit);
    uint256 hashTarget = uint256S("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
    arith_uint256 nDifficulty_arith = UintToArith256(hashTarget);
    nDifficulty_arith *= 2;
    nDifficulty = nDifficulty_arith.GetCompact();
    hash.SetHex("0x1");
    uint16_t nShift = 20;
    std::vector<unsigned char> nAdd;
    nAdd.assign(1, 0);
    BOOST_CHECK(!CheckProofOfWork(hash, nShift, &nAdd, nDifficulty, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_bigger_hash_than_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nDifficulty;
    // arith_uint256 hash_arith = UintToArith256(consensus.powLimit);
    uint256 hashTarget = uint256S("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
    arith_uint256 hash_arith = UintToArith256(hashTarget);
    nDifficulty = hash_arith.GetCompact();
    hash_arith *= 2; // hash > nBits
    hash = ArithToUint256(hash_arith);
    uint16_t nShift = 20;
    std::vector<unsigned char> nAdd;
    nAdd.assign(1, 0);
    BOOST_CHECK(!CheckProofOfWork(hash, nShift, &nAdd, nDifficulty, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_zero_target) // Fails for Bitcoin CheckPoW, passes for Gapcoin CheckPoW
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nDifficulty;
    arith_uint256 hash_arith{0};
    nDifficulty = hash_arith.GetCompact();
    hash = ArithToUint256(hash_arith);
    uint16_t nShift = 20;
    std::vector<unsigned char> nAdd;
    nAdd.assign(1, 0);
    BOOST_CHECK(CheckProofOfWork(hash, nShift, &nAdd, nDifficulty, consensus));
}

BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    std::vector<CBlockIndex> blocks(10000);
    for (int i = 0; i < 10000; i++) {
        blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
        blocks[i].nHeight = i;
        blocks[i].nTime = 1413914801 + i * chainParams->GetConsensus().nPowTargetSpacing;
        blocks[i].nDifficulty = 0x207fffff; /* target 0x7fffff000... */
        blocks[i].nChainWork = i ? blocks[i - 1].nChainWork + GetBlockProof(blocks[i - 1]) : arith_uint256(0);
    }

    for (int j = 0; j < 1000; j++) {
        CBlockIndex *p1 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p2 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p3 = &blocks[InsecureRandRange(10000)];

        int64_t tdiff = GetBlockProofEquivalentTime(*p1, *p2, *p3, chainParams->GetConsensus());
        BOOST_CHECK_EQUAL(tdiff, p1->GetBlockTime() - p2->GetBlockTime());
    }
}

BOOST_AUTO_TEST_SUITE_END()