/******************************************************************************
 * Copyright (C) 2013 by Jerome Maye                                          *
 * jerome.maye@gmail.com                                                      *
 ******************************************************************************/

/** \file OptimizationProblemTest.cpp
    \brief This file tests the OptimizationProblem class.
  */

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

#include <gtest/gtest.h>

#include <aslam/backend/ErrorTerm.hpp>
#include <aslam/backend/JacobianContainer.hpp>

#include "aslam/calibration/core/OptimizationProblem.h"
#include "aslam/calibration/data-structures/VectorDesignVariable.h"
#include "aslam/calibration/exceptions/OutOfBoundException.h"
#include "aslam/calibration/exceptions/InvalidOperationException.h"

class DummyErrorTerm :
  public aslam::backend::ErrorTermFs<3> {
public:
  DummyErrorTerm() = default;
  DummyErrorTerm(const DummyErrorTerm& other) = delete;
  DummyErrorTerm& operator = (const DummyErrorTerm& other) = delete;
  virtual ~DummyErrorTerm() {};
protected:
  virtual double evaluateErrorImplementation() {
    return 0;
  };
  virtual void evaluateJacobiansImplementation(
    aslam::backend::JacobianContainer& J) {};
};

using namespace aslam::calibration;

TEST(AslamCalibrationTestSuite, testOptimizationProblem) {
  OptimizationProblem problem;
  auto dv1 = boost::make_shared<VectorDesignVariable<2> >();
  dv1->setActive(true);
  auto dv2 = boost::make_shared<VectorDesignVariable<3> >();
  dv2->setActive(true);
  auto dv3 = boost::make_shared<VectorDesignVariable<4> >();
  dv3->setActive(true);
  auto dv4 = boost::make_shared<VectorDesignVariable<4> >();
  dv4->setActive(true);
  auto et1 = boost::make_shared<DummyErrorTerm>();
  auto et2 = boost::make_shared<DummyErrorTerm>();
  auto et3 = boost::make_shared<DummyErrorTerm>();
  auto et4 = boost::make_shared<DummyErrorTerm>();
  problem.addDesignVariable(dv1, 0);
  problem.addDesignVariable(dv2, 1);
  problem.addDesignVariable(dv3, 1);
  problem.addErrorTerm(et1);
  problem.addErrorTerm(et2);
  problem.addErrorTerm(et3);
  ASSERT_TRUE(problem.isDesignVariableInProblem(dv1.get()));
  ASSERT_TRUE(problem.isDesignVariableInProblem(dv2.get()));
  ASSERT_TRUE(problem.isDesignVariableInProblem(dv3.get()));
  ASSERT_FALSE(problem.isDesignVariableInProblem(dv4.get()));
  ASSERT_THROW(problem.addDesignVariable(dv1), InvalidOperationException);
  ASSERT_EQ(problem.getNumGroups(), 2);
  ASSERT_EQ(problem.getDesignVariablesGroup(1),
    OptimizationProblem::DesignVariablesSP({dv2, dv3}));
  ASSERT_EQ(problem.getDesignVariablesGroup(0),
    OptimizationProblem::DesignVariablesSP({dv1}));
  ASSERT_THROW(problem.getDesignVariablesGroup(2), OutOfBoundException<size_t>);
  problem.setGroupsOrdering({1, 0});
  ASSERT_EQ(problem.getGroupsOrdering(), std::vector<size_t>({1, 0}));
  ASSERT_THROW(problem.setGroupsOrdering({2, 0}), OutOfBoundException<size_t>);
  ASSERT_THROW(problem.setGroupsOrdering({2, 0, 3}),
    OutOfBoundException<size_t>);
  ASSERT_THROW(problem.setGroupsOrdering({0, 0}), OutOfBoundException<size_t>);
  ASSERT_EQ(problem.getGroupId(dv1.get()), 0);
  ASSERT_EQ(problem.getGroupId(dv2.get()), 1);
  ASSERT_EQ(problem.getGroupId(dv3.get()), 1);
  ASSERT_THROW(problem.getGroupId(dv4.get()), InvalidOperationException);
  ASSERT_EQ(problem.numDesignVariables(), 3);
  ASSERT_EQ(problem.designVariable(0), dv2.get());
  ASSERT_EQ(problem.designVariable(1), dv3.get());
  ASSERT_EQ(problem.designVariable(2), dv1.get());
//  ASSERT_THROW(problem.designVariable(3), OutOfBoundException<size_t>);
  problem.permuteDesignVariables({1, 0}, 1);
  ASSERT_EQ(problem.designVariable(0), dv3.get());
  ASSERT_THROW(problem.permuteDesignVariables({1, 0}, 2),
    OutOfBoundException<size_t>);
  ASSERT_EQ(problem.getGroupDim(0), 2);
  ASSERT_EQ(problem.getGroupDim(1), 7);
  ASSERT_TRUE(problem.isGroupInProblem(0));
  ASSERT_TRUE(problem.isGroupInProblem(1));
  ASSERT_FALSE(problem.isGroupInProblem(2));
  ASSERT_EQ(problem.numErrorTerms(), 3);
  ASSERT_TRUE(problem.isErrorTermInProblem(et1.get()));
  ASSERT_TRUE(problem.isErrorTermInProblem(et2.get()));
  ASSERT_TRUE(problem.isErrorTermInProblem(et3.get()));
  ASSERT_FALSE(problem.isErrorTermInProblem(et4.get()));
  auto ets = problem.getErrorTerms();
  ASSERT_EQ(ets.size(), 3);
  ASSERT_EQ(ets, OptimizationProblem::ErrorTermsSP({et1, et2, et3}));
  ASSERT_EQ(problem.errorTerm(0), et1.get());
  ASSERT_EQ(problem.errorTerm(1), et2.get());
  ASSERT_EQ(problem.errorTerm(2), et3.get());
//  ASSERT_THROW(problem.errorTerm(3), OutOfBoundException<size_t>);
  ASSERT_THROW(problem.addErrorTerm(et1), InvalidOperationException);
  problem.permuteErrorTerms({2, 1, 0});
  auto etsp = problem.getErrorTerms();
  ASSERT_EQ(etsp, OptimizationProblem::ErrorTermsSP({et3, et2, et1}));
  ASSERT_EQ(problem.errorTerm(0), et3.get());
  ASSERT_EQ(problem.errorTerm(1), et2.get());
  ASSERT_EQ(problem.errorTerm(2), et1.get());
  Eigen::MatrixXd dv1Param;
  problem.saveDesignVariables();
  dv1->setParameters(Eigen::Vector2d::Ones());
  problem.restoreDesignVariables();
  dv1->getParameters(dv1Param);
  ASSERT_EQ(dv1Param, Eigen::Vector2d::Zero());
}


/*
 * Vertex.cpp
 *
 *  Created on: 2012-02-19
 *      Author: leo
 */

#include <fruitpunch/Graphics/Vertex.h>
#include <fruitpunch/Graphics/Renderer.h>
#include <fruitpunch/Graphics/Matrices.h>
#include <math.h>
#include <armadillo>

#ifndef PI
#define PI 3.141592
#endif

using namespace std;

namespace fp_core {

Vertex::Vertex(point p) {
	setPosition(p);
}

Vertex::~Vertex() {
	// TODO Auto-generated destructor stub
}

point Vertex::getPosition() const {
	return mPosition;
}

point Vertex::getUvmap() const {
	return mUvmap;
}

void Vertex::setPosition(point position) {
	mPosition.x = position.x;
	mPosition.y = position.y;
}

Vertex::Vertex(const Vertex & v) {
	copy_from(v);
}

void Vertex::setUvmap(point uvmap) {
	mUvmap = uvmap;
}

Vertex & Vertex::operator =(const Vertex & v) {
	copy_from(v);
	return *this;
}

void Vertex::copy_from(const Vertex & v) {
	mPosition.x = v.getPosition().x;
	mPosition.y = v.getPosition().y;
	mUvmap.x = v.getUvmap().x;
	mUvmap.y = v.getUvmap().y;
}

bool point::comesBeforeCC(point a, point b, point center, Renderer& r) {

  // convert to local space
  arma::fmat33 trans = to_local_space_matrix(center);
  a = r.transform_point(a, trans);
  b = r.transform_point(b, trans);

  // convert to polar coordinate
  float aPolar = atan(a.y / a.x);
  float bPolar = atan(b.y / b.x);

  // If a or b falls in quadrants II, III, or IV, we will have to compensate
  // see: http://www.mathsisfun.com/polar-cartesian-coordinates.html
  float aAdd = 0;
  float bAdd = 0;

  // Quadrant II or III
  if (a.x < 0) aAdd = PI;
  if (b.x < 0) bAdd = PI;

  // Quadrant IV
  if (a.x > 0 && a.y < 0) aAdd = 2 * PI;
  if (b.x > 0 && b.y < 0) bAdd = 2 * PI;

  aPolar += aAdd;
  bPolar += bAdd;

  // compares the angles
  // Here, we consider we are using a flipped Y (standard screen coordinates)
  // if not, change the > to a < to have counter clockwise
  return aPolar > bPolar;
}

std::vector<point> point::boundaries(const std::vector<point> & points) {
  vector<point> result;
  point max;
  point min;

  if (points.size() == 0)
    return result;
  max = points[0];
  min = max;

  vector<point>::const_iterator it = points.begin();

  for (; it != points.end(); it++) {
    point p = *it;
    if (p.x < min.x)
      min.x = p.x;
    if (p.y < min.y)
      min.y = p.y;
    if (p.x > max.x)
      max.x = p.x;
    if (p.y > max.y)
      max.y = p.y;
  }
  result.push_back(min);
  result.push_back(max);

  return result;
}

std::vector<point> Vertex::positionPointVector(const std::vector<Vertex> vertices) {
  vector<point> result;

  vector<Vertex>::const_iterator it;
  for (it = vertices.begin(); it != vertices.end(); ++it) {
    result.push_back(it->getPosition());
  }

  return result;
}

}
 /* namespace fp_core */


/*
# Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#  * Neither the name of NVIDIA CORPORATION nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef NVX_VSTAB_NODES_HPP
#define NVX_VSTAB_NODES_HPP

#include <NVX/nvx.h>

#include <algorithm>
#include <Eigen/Dense>

// row-major storage order
typedef Eigen::Matrix<vx_float32, 3, 3, Eigen::RowMajor> Matrix3x3f_rm;
typedef Eigen::Matrix<vx_float32, 3, 4, Eigen::RowMajor> Matrix3x4f_rm;

// Register homographyFilter kernel in OpenVX context
vx_status registerHomographyFilterKernel(vx_context context);

// Create homographyFilter node
vx_node homographyFilterNode(vx_graph graph, vx_matrix input,
                             vx_matrix homography, vx_image image,
                             vx_array mask);

// Register matrixSmoother kernel in OpenVX context
vx_status registerMatrixSmootherKernel(vx_context context);

// Create matrixSmoother node
vx_node matrixSmootherNode(vx_graph graph,
                      vx_delay matrices, vx_matrix smoothed);

// Register truncateStabTransform kernel in OpenVX context
vx_status registerTruncateStabTransformKernel(vx_context context);

/* Create truncateStabTransform node.
 * cropMargin - proportion of the width(height) of the frame
 * that is allowed to be cropped for stabilizing of the frames. The value should be less than 0.5.
 * If cropMargin is negative then the truncation procedure is turned off.
 */
vx_node truncateStabTransformNode(vx_graph graph, vx_matrix stabTransform, vx_matrix truncatedTransform,
                                  vx_image image, vx_scalar cropMargin);

#endif


/* Copyright Ramakrishnan Kannan 2017 */

#ifndef NTF_AUNTF_HPP_
#define NTF_AUNTF_HPP_

#define MPITIC tic();
#define MPITOC toc();

#include <cblas.h>
#include <armadillo>
#include <vector>
#include "common/ncpfactors.hpp"
#include "common/ntf_utils.hpp"
#include "common/tensor.hpp"
#include "dimtree/ddt.hpp"

namespace planc {

#define TENSOR_DIM (m_input_tensor.dimensions())
#define TENSOR_NUMEL (m_input_tensor.numel())

// #ifndef NTF_VERBOSE
// #define NTF_VERBOSE 1
// #endif

// extern "C" void cblas_dgemm_(const CBLAS_LAYOUT Layout,
//                              const CBLAS_TRANSPOSE transa,
//                              const CBLAS_TRANSPOSE transb,
//                              const int m, const int n,
//                              const int k, const double alpha,
//                              const double *a, const int lda,
//                              const double *b, const int ldb,
//                              const double beta, double *c,
//                              const int ldc);
class AUNTF {
 protected:
  planc::NCPFactors m_ncp_factors;
  MAT *ncp_mttkrp_t;
  MAT gram_without_one;
  virtual MAT update(const int mode) = 0;

 private:
  const planc::Tensor &m_input_tensor;
  int m_num_it;
  int m_current_it;
  bool m_compute_error;

  const int m_low_rank_k;
  MAT *ncp_krp;
  const algotype m_updalgo;
  planc::Tensor *lowranktensor;
  DenseDimensionTree *kdt;
  bool m_enable_dim_tree;
  // needed for acceleration algorithms.
  bool m_accelerated;
  double m_rel_error;
  double m_normA;
  std::vector<bool> m_stale_mttkrp;

  // Ensure factor is unnormalised when calling this function
  void update_factor_mode(const int &current_mode, const MAT &factor) {
    m_ncp_factors.set(current_mode, factor);
    m_ncp_factors.normalize(current_mode);

    if (m_enable_dim_tree) {
      MAT temp = m_ncp_factors.factor(current_mode).t();
      kdt->set_factor(temp.memptr(), current_mode);
    }

    int num_modes = this->m_input_tensor.modes();
    for (int mode = 0; mode < num_modes; mode++) {
      if (mode != current_mode) this->m_stale_mttkrp[current_mode] = true;
    }
  }
  virtual void accelerate() {}

 public:
  AUNTF(const planc::Tensor &i_tensor, const int i_k, algotype i_algo)
      : m_ncp_factors(i_tensor.dimensions(), i_k, false),
        m_input_tensor(i_tensor),
        m_low_rank_k(i_k),
        m_updalgo(i_algo) {
    m_ncp_factors.normalize();
    gram_without_one.zeros(i_k, i_k);
    ncp_mttkrp_t = new MAT[i_tensor.modes()];
    ncp_krp = new MAT[i_tensor.modes()];
    for (int i = 0; i < i_tensor.modes(); i++) {
      UWORD current_size = TENSOR_NUMEL / TENSOR_DIM[i];
      ncp_krp[i].zeros(current_size, i_k);
      ncp_mttkrp_t[i].zeros(i_k, TENSOR_DIM[i]);
      this->m_stale_mttkrp.push_back(true);
    }
    lowranktensor = new planc::Tensor(i_tensor.dimensions());
    m_compute_error = false;
    m_num_it = 20;
    m_normA = i_tensor.norm();
    // INFO << "Init factors for NCP" << std::endl << "======================";
    // m_ncp_factors.print();
    this->m_enable_dim_tree = false;
  }
  ~AUNTF() {
    for (int i = 0; i < m_input_tensor.modes(); i++) {
      ncp_krp[i].clear();
      ncp_mttkrp_t[i].clear();
    }
    delete[] ncp_krp;
    delete[] ncp_mttkrp_t;
    if (this->m_enable_dim_tree) {
      delete kdt;
    }
    delete lowranktensor;
  }
  NCPFactors &ncp_factors() { return m_ncp_factors; }
  void dim_tree(bool i_dim_tree) {
    this->m_enable_dim_tree = i_dim_tree;
    if (i_dim_tree) {
      this->kdt = new DenseDimensionTree(m_input_tensor, m_ncp_factors,
                                         m_input_tensor.modes() / 2);
    }
  }
  double current_error() const { return this->m_rel_error; }
  void num_it(const int i_n) { this->m_num_it = i_n; }
  void computeNTF() {
    for (m_current_it = 0; m_current_it < m_num_it; m_current_it++) {
      INFO << "iter::" << this->m_current_it << std::endl;
      for (int j = 0; j < this->m_input_tensor.modes(); j++) {
        m_ncp_factors.gram_leave_out_one(j, &gram_without_one);
#ifdef NTF_VERBOSE
        INFO << "gram_without_" << j << "::" << arma::cond(gram_without_one)
             << std::endl
             << gram_without_one << std::endl;
#endif
        if (this->m_stale_mttkrp[j]) {
          m_ncp_factors.krp_leave_out_one(j, &ncp_krp[j]);
#ifdef NTF_VERBOSE
          INFO << "krp_leave_out_" << j << std::endl << ncp_krp[j] << std::endl;
#endif
          if (this->m_enable_dim_tree) {
            double multittv_time = 0;
            double mttkrp_time = 0;
            kdt->in_order_reuse_MTTKRP(j, ncp_mttkrp_t[j].memptr(), false,
                                       multittv_time, mttkrp_time);
          } else {
            m_input_tensor.mttkrp(j, ncp_krp[j], &ncp_mttkrp_t[j]);
          }
          this->m_stale_mttkrp[j] = false;
#ifdef NTF_VERBOSE
          INFO << "mttkrp for factor" << j << std::endl
               << ncp_mttkrp_t[j] << std::endl;
#endif
        }
        // MAT factor = update(m_updalgo, gram_without_one, ncp_mttkrp_t[j], j);
        MAT factor = update(j);
#ifdef NTF_VERBOSE
        INFO << "iter::" << i << "::factor:: " << j << std::endl
             << factor << std::endl;
#endif
        update_factor_mode(j, factor.t());
      }
      if (m_compute_error) {
        double temp_err = computeObjectiveError();
        this->m_rel_error = temp_err;
        INFO << "relative_error at it::" << this->m_current_it
             << "::" << temp_err << std::endl;
      }
      if (this->m_accelerated) accelerate();
#ifdef NTF_VERBOSE
      INFO << "ncp factors" << std::endl;
      m_ncp_factors.print();
#endif
    }
  }
  void accelerated(const bool &set_acceleration) {
    this->m_accelerated = set_acceleration;
    this->m_compute_error = true;
  }
  bool is_stale_mttkrp(const int &current_mode) const {
    return this->m_stale_mttkrp[current_mode];
  }
  void compute_error(bool i_error) { this->m_compute_error = i_error; }
  void reset(const NCPFactors &new_factors, bool trans = false) {
    int m_modes = this->m_input_tensor.modes();
    if (!trans) {
      for (int i = 0; i < m_modes; i++) {
        update_factor_mode(i, new_factors.factor(i));
      }
    } else {
      for (int i = 0; i < m_modes; i++) {
        update_factor_mode(i, new_factors.factor(i).t());
      }
    }
    m_ncp_factors.set_lambda(new_factors.lambda());
  }
  int current_it() const { return m_current_it; }
  double computeObjectiveError() {
    // current low rank tensor
    // UWORD krpsize = arma::prod(this->m_dimensions);
    // krpsize /= this->m_dimensions[0];
    // MAT krpleavingzero.zeros(krpsize, this->m_k);
    // krp_leave_out_one(0, &krpleavingzero);
    // MAT lowranktensor(this->m_dimensions[0], krpsize);
    // lowranktensor = this->ncp_factors[0] * trans(krpleavingzero);

    // compute current low rank tensor as above.
    m_ncp_factors.krp_leave_out_one(0, &ncp_krp[0]);
    // cblas_dgemm_(const CBLAS_LAYOUT Layout,
    //              const CBLAS_TRANSPOSE transa,
    //              const CBLAS_TRANSPOSE transb,
    //              const MKL_INT m, const MKL_INT n,
    //              const MKL_INT k, const double alpha,
    //              const double * a, const MKL_INT lda,
    //              const double * b, const MKL_INT ldb,
    //              const double beta, double * c,
    //              const MKL_INT ldc);
    // char transa = 'T';
    // char transb = 'N';
    int m = m_ncp_factors.factor(0).n_rows;
    int n = ncp_krp[0].n_rows;
    int k = m_ncp_factors.factor(0).n_cols;
    int lda = m;
    int ldb = n;
    int ldc = m;
    double alpha = 1;
    double beta = 0;
    char nt = 'N';
    char t = 'T';

    MAT unnorm_fac =
        m_ncp_factors.factor(0) * arma::diagmat(m_ncp_factors.lambda());
    // double *output_tensor = new double[ldc * n];
    dgemm_(&nt, &t, &m, &n, &k, &alpha, unnorm_fac.memptr(), &lda,
           ncp_krp[0].memptr(), &ldb, &beta, &lowranktensor->m_data[0], &ldc);
    // INFO << "lowrank tensor::" << std::endl;
    // lowranktensor->print();
    // for (int i=0; i < ldc*n; i++){
    //     INFO << i << ":" << output_tensor[i] << std::endl;
    // }
    double err = m_input_tensor.err(*lowranktensor);
    err = std::sqrt(err / this->m_normA);
    return err;
  }
  double computeObjectiveError(const NCPFactors &new_factors_t) {
    reset(new_factors_t, true);
    return computeObjectiveError();
  }
};  // class AUNTF
}  // namespace planc

#endif  // NTF_AUNTF_HPP_


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

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

#include <boost/simd/config.hpp>
#include <boost/simd/detail/nsm.hpp>
#include <boost/simd/detail/dispatch.hpp>
#include <boost/simd/detail/constant_traits.hpp>
#include <boost/simd/detail/dispatch/function/make_callable.hpp>
#include <boost/simd/detail/dispatch/hierarchy/functions.hpp>
#include <boost/simd/detail/dispatch/as.hpp>

namespace boost { namespace simd
{
  namespace tag
  {
    struct invlog_10hi_ : boost::dispatch::constant_value_<invlog_10hi_>
    {
      BOOST_DISPATCH_MAKE_CALLABLE(ext,invlog_10hi_,boost::dispatch::constant_value_<invlog_10hi_>);
      BOOST_SIMD_REGISTER_CONSTANT(0,0x3ede6000UL, 0x3fdbcb7b15200000ULL);
    };
  }

  namespace ext
  {
    BOOST_DISPATCH_FUNCTION_DECLARATION(tag, invlog_10hi_)
  }

  namespace detail
  {
    BOOST_DISPATCH_CALLABLE_DEFINITION(tag::invlog_10hi_,invlog_10hi);
  }

  template<typename T> BOOST_FORCEINLINE auto Invlog_10hi()
  BOOST_NOEXCEPT_DECLTYPE(detail::invlog_10hi( boost::dispatch::as_<T>{}))
  {
    return detail::invlog_10hi( boost::dispatch::as_<T>{} );
  }
} }

#include <boost/simd/arch/common/scalar/constant/constant_value.hpp>
#include <boost/simd/arch/common/simd/constant/constant_value.hpp>

#endif


// Copyright (C) 2011 by the BEM++ Authors
//
// 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 "../type_template.hpp"
#include "../check_arrays_are_close.hpp"

#include "assembly/laplace_3d_single_layer_boundary_operator.hpp"
#include "assembly/numerical_quadrature_strategy.hpp"
#include "common/scalar_traits.hpp"
#include "fiber/geometrical_data.hpp"
#include "fiber/local_assembler_for_operators.hpp"
#include "grid/grid.hpp"
#include "grid/grid_factory.hpp"
#include "grid/grid_view.hpp"
#include "space/piecewise_constant_scalar_space.hpp"
#include "space/piecewise_linear_continuous_scalar_space.hpp"

#include "grid/entity.hpp"
#include "grid/mapper.hpp"

#include <algorithm>
#include "common/armadillo_fwd.hpp"
#include <boost/test/unit_test.hpp>
#include <boost/test/test_case_template.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/version.hpp>
#include <complex>

using namespace Bempp;

const int N_ELEMENTS_X = 2, N_ELEMENTS_Y = 3;

/** \brief Fixture class. */
template <typename BFT, typename RT>
class DefaultLocalAssemblerForIntegralOperatorsOnSurfacesManager
{
public:
    typedef typename ScalarTraits<RT>::RealType CT;
    typedef PiecewiseConstantScalarSpace<BFT> PiecewiseConstantSpace;
    typedef PiecewiseLinearContinuousScalarSpace<BFT> PiecewiseLinearSpace;
    typedef Laplace3dSingleLayerBoundaryOperator<BFT, RT> Operator;
    typedef NumericalQuadratureStrategy<BFT, RT> QuadratureStrategy;
    typedef Fiber::RawGridGeometry<CT> RawGridGeometry;

    DefaultLocalAssemblerForIntegralOperatorsOnSurfacesManager(
            bool cacheSingularIntegrals)
    {
        // Create a Bempp grid
        shared_ptr<Grid> grid = createGrid();

        // These important thing is that the domain and dualToRange spaces are
        // different
        piecewiseConstantSpace = std::auto_ptr<PiecewiseConstantSpace>(
                    new PiecewiseConstantSpace(grid));
        piecewiseLinearSpace = std::auto_ptr<PiecewiseLinearSpace>(
                    new PiecewiseLinearSpace(grid));

        op = std::auto_ptr<Operator>(new Operator(
                                         make_shared_from_ref(*piecewiseConstantSpace),
                                         make_shared_from_ref(*piecewiseLinearSpace),
                                         make_shared_from_ref(*piecewiseLinearSpace),
                                         "SLP"));

        // Construct local assembler

        Fiber::AccuracyOptions options;
        options.doubleRegular.setRelativeQuadratureOrder(1);
        quadStrategy = std::auto_ptr<QuadratureStrategy>(new QuadratureStrategy);

        AssemblyOptions assemblyOptions;
        assemblyOptions.setVerbosityLevel(VerbosityLevel::LOW);
        assemblyOptions.enableSingularIntegralCaching(cacheSingularIntegrals);
        assembler = op->makeAssembler(*quadStrategy, assemblyOptions);
    }

private:
    shared_ptr<Grid> createGrid() {
        GridParameters params;
        params.topology = GridParameters::TRIANGULAR;

        const int dimGrid = 2;
        typedef double ctype;
        arma::Col<double> lowerLeft(dimGrid);
        arma::Col<double> upperRight(dimGrid);
        arma::Col<unsigned int> nElements(dimGrid);
        lowerLeft.fill(0);
        upperRight.fill(1);
        nElements(0) = N_ELEMENTS_X;
        nElements(1) = N_ELEMENTS_Y;

        return GridFactory::createStructuredGrid(
                    params, lowerLeft, upperRight, nElements);
    }

public:
    std::auto_ptr<PiecewiseConstantSpace> piecewiseConstantSpace;
    std::auto_ptr<PiecewiseLinearSpace> piecewiseLinearSpace;
    std::auto_ptr<Operator> op;

    std::auto_ptr<QuadratureStrategy> quadStrategy;
    std::auto_ptr<typename Operator::LocalAssembler> assembler;
};

// Tests

BOOST_AUTO_TEST_SUITE(DefaultLocalAssemblerForIntegralOperatorsOnSurfaces)

template <typename ResultType>
void
both_variants_of_evaluateLocalWeakForms_agree_for_callVariant_TEST_TRIAL_and_cacheSingularIntegrals(
        bool cacheSingularIntegrals)
{
    DefaultLocalAssemblerForIntegralOperatorsOnSurfacesManager<
            typename ScalarTraits<ResultType>::RealType, ResultType> mgr(
                cacheSingularIntegrals);

    const int elementCount = N_ELEMENTS_X * N_ELEMENTS_Y * 2;
    const int testIndexCount = elementCount, trialIndexCount = elementCount;
    std::vector<int> testIndices(testIndexCount);
    for (int i = 0; i < testIndexCount; ++i)
        testIndices[i] = i;
    std::vector<int> trialIndices(trialIndexCount);
    for (int i = 0; i < trialIndexCount; ++i)
        trialIndices[i] = i;

    Fiber::_2dArray<arma::Mat<ResultType> > resultVariant2;
    mgr.assembler->evaluateLocalWeakForms(testIndices, trialIndices, resultVariant2);

    // Gather successive columns
    Fiber::_2dArray<arma::Mat<ResultType> > resultVariant1(
                testIndexCount, trialIndexCount);
    std::vector<arma::Mat<ResultType> > colResult;
    for (int trialI = 0; trialI < trialIndexCount; ++trialI)
    {
        mgr.assembler->evaluateLocalWeakForms(Fiber::TEST_TRIAL, testIndices,
                                              trialIndices[trialI],
                                              Fiber::ALL_DOFS, colResult);
        for (int testI = 0; testI < testIndexCount; ++testI)
            resultVariant1(testI, trialI) = colResult[testI];
    }

    BOOST_CHECK(check_arrays_are_close<ResultType>(
                    resultVariant1, resultVariant2, 1e-6));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(
        both_variants_of_evaluateLocalWeakForms_agree_for_callVariant_TEST_TRIAL_and_cacheSingularIntegrals_false,
        ResultType, result_types)
{
    both_variants_of_evaluateLocalWeakForms_agree_for_callVariant_TEST_TRIAL_and_cacheSingularIntegrals<
            ResultType>(false);
}

BOOST_AUTO_TEST_CASE_TEMPLATE(
        both_variants_of_evaluateLocalWeakForms_agree_for_callVariant_TEST_TRIAL_and_cacheSingularIntegrals_true,
        ResultType, result_types)
{
    both_variants_of_evaluateLocalWeakForms_agree_for_callVariant_TEST_TRIAL_and_cacheSingularIntegrals<
            ResultType>(true);
}

template <typename ResultType>
void
both_variants_of_evaluateLocalWeakForms_agree_for_callVariant_TRIAL_TEST_and_cacheSingularIntegrals(
        bool cacheSingularIntegrals)
{
    DefaultLocalAssemblerForIntegralOperatorsOnSurfacesManager<
            typename ScalarTraits<ResultType>::RealType, ResultType> mgr(
                cacheSingularIntegrals);

    const int elementCount = N_ELEMENTS_X * N_ELEMENTS_Y * 2;
    const int testIndexCount = elementCount, trialIndexCount = elementCount;
    std::vector<int> testIndices(testIndexCount);
    for (int i = 0; i < testIndexCount; ++i)
        testIndices[i] = i;
    std::vector<int> trialIndices(trialIndexCount);
    for (int i = 0; i < trialIndexCount; ++i)
        trialIndices[i] = i;

    Fiber::_2dArray<arma::Mat<ResultType> > resultVariant2;
    mgr.assembler->evaluateLocalWeakForms(testIndices, trialIndices, resultVariant2);

    // Gather successive rows
    Fiber::_2dArray<arma::Mat<ResultType> > resultVariant1(
                testIndexCount, trialIndexCount);
    std::vector<arma::Mat<ResultType> > rowResult;
    for (int testI = 0; testI < testIndexCount; ++testI)
    {
        mgr.assembler->evaluateLocalWeakForms(Fiber::TRIAL_TEST, trialIndices,
                                              testIndices[testI],
                                              Fiber::ALL_DOFS, rowResult);
        for (int trialI = 0; trialI < trialIndexCount; ++trialI)
            resultVariant1(testI, trialI) = rowResult[trialI];
    }

    BOOST_CHECK(check_arrays_are_close<ResultType>(
                    resultVariant1, resultVariant2, 1e-6));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(
        both_variants_of_evaluateLocalWeakForms_agree_for_callVariant_TRIAL_TEST_and_cacheSingularIntegrals_false,
        ResultType, result_types)
{
    both_variants_of_evaluateLocalWeakForms_agree_for_callVariant_TRIAL_TEST_and_cacheSingularIntegrals<
            ResultType>(false);
}

BOOST_AUTO_TEST_CASE_TEMPLATE(
        both_variants_of_evaluateLocalWeakForms_agree_for_callVariant_TRIAL_TEST_and_cacheSingularIntegrals_true,
        ResultType, result_types)
{
    both_variants_of_evaluateLocalWeakForms_agree_for_callVariant_TRIAL_TEST_and_cacheSingularIntegrals<
            ResultType>(true);
}

template <typename ResultType>
void
evaluateLocalWeakForms_for_ALL_DOFS_and_single_dof_agree_for_callVariant_TEST_TRIAL_and_cacheSingularIntegrals(
        bool cacheSingularIntegrals)
{
    DefaultLocalAssemblerForIntegralOperatorsOnSurfacesManager<
            typename ScalarTraits<ResultType>::RealType, ResultType> mgr(
                cacheSingularIntegrals);

    const int elementCount = N_ELEMENTS_X * N_ELEMENTS_Y * 2;
    std::vector<int> elementIndicesA(elementCount);
    for (int i = 0; i < elementCount; ++i)
        elementIndicesA[i] = i;

    int elementIndexB = 2;

    std::vector<arma::Mat<ResultType> > completeResult;
    mgr.assembler->evaluateLocalWeakForms(Fiber::TEST_TRIAL, elementIndicesA,
                                          elementIndexB,
                                          Fiber::ALL_DOFS, completeResult);
    int elementBDofCount = completeResult[0].n_cols;

    // Gather successive rows
    std::vector<arma::Mat<ResultType> > resultForSingleDof;
    std::vector<arma::Mat<ResultType> > expected(elementCount);
    for (int i = 0; i < elementCount; ++i)
        expected[i].set_size(completeResult[i].n_rows, completeResult[i].n_cols);

    for (int dof = 0; dof < elementBDofCount; ++dof)
    {
        mgr.assembler->evaluateLocalWeakForms(Fiber::TEST_TRIAL, elementIndicesA,
                                              elementIndexB,
                                              dof, resultForSingleDof);
        for (int i = 0; i < elementCount; ++i)
            expected[i].col(dof) = resultForSingleDof[i];
    }

    BOOST_CHECK(check_arrays_are_close<ResultType>(
                    completeResult, expected, 1e-6));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(
        evaluateLocalWeakForms_for_ALL_DOFS_and_single_dof_agree_for_callVariant_TEST_TRIAL_and_cacheSingularIntegrals_false,
        ResultType, result_types)
{
    evaluateLocalWeakForms_for_ALL_DOFS_and_single_dof_agree_for_callVariant_TEST_TRIAL_and_cacheSingularIntegrals<
            ResultType>(false);
}

BOOST_AUTO_TEST_CASE_TEMPLATE(
        evaluateLocalWeakForms_for_ALL_DOFS_and_single_dof_agree_for_callVariant_TEST_TRIAL_and_cacheSingularIntegrals_true,
        ResultType, result_types)
{
    evaluateLocalWeakForms_for_ALL_DOFS_and_single_dof_agree_for_callVariant_TEST_TRIAL_and_cacheSingularIntegrals<
            ResultType>(true);
}

template <typename ResultType>
void
evaluateLocalWeakForms_for_ALL_DOFS_and_single_dof_agree_for_callVariant_TRIAL_TEST_and_cacheSingularIntegrals(
        bool cacheSingularIntegrals)
{
    DefaultLocalAssemblerForIntegralOperatorsOnSurfacesManager<
            typename ScalarTraits<ResultType>::RealType, ResultType> mgr(
                cacheSingularIntegrals);

    const int elementCount = N_ELEMENTS_X * N_ELEMENTS_Y * 2;
    std::vector<int> elementIndicesA(elementCount);
    for (int i = 0; i < elementCount; ++i)
        elementIndicesA[i] = i;

    int elementIndexB = 2;

    std::vector<arma::Mat<ResultType> > completeResult;
    mgr.assembler->evaluateLocalWeakForms(Fiber::TRIAL_TEST, elementIndicesA,
                                          elementIndexB,
                                          Fiber::ALL_DOFS, completeResult);
    int elementBDofCount = completeResult[0].n_rows;

    // Gather successive rows
    std::vector<arma::Mat<ResultType> > resultForSingleDof;
    std::vector<arma::Mat<ResultType> > expected(elementCount);
    for (int i = 0; i < elementCount; ++i)
        expected[i].set_size(completeResult[i].n_rows, completeResult[i].n_cols);

    for (int dof = 0; dof < elementBDofCount; ++dof)
    {
        mgr.assembler->evaluateLocalWeakForms(Fiber::TRIAL_TEST, elementIndicesA,
                                              elementIndexB,
                                              dof, resultForSingleDof);
        for (int i = 0; i < elementCount; ++i)
            expected[i].row(dof) = resultForSingleDof[i];
    }

    BOOST_CHECK(check_arrays_are_close<ResultType>(
                    completeResult, expected, 1e-6));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(
        evaluateLocalWeakForms_for_ALL_DOFS_and_single_dof_agree_for_callVariant_TRIAL_TEST_and_cacheSingularIntegrals_false,
        ResultType, result_types)
{
    evaluateLocalWeakForms_for_ALL_DOFS_and_single_dof_agree_for_callVariant_TRIAL_TEST_and_cacheSingularIntegrals<
            ResultType>(false);
}

BOOST_AUTO_TEST_CASE_TEMPLATE(
        evaluateLocalWeakForms_for_ALL_DOFS_and_single_dof_agree_for_callVariant_TRIAL_TEST_and_cacheSingularIntegrals_true,
        ResultType, result_types)
{
    evaluateLocalWeakForms_for_ALL_DOFS_and_single_dof_agree_for_callVariant_TRIAL_TEST_and_cacheSingularIntegrals<
            ResultType>(true);
}

template <typename ResultType>
void
evaluateLocalWeakForms_with_and_without_singular_integral_caching_gives_same_results(
        bool cacheSingularIntegrals)
{
    const int elementCount = N_ELEMENTS_X * N_ELEMENTS_Y * 2;
    const int testIndexCount = elementCount, trialIndexCount = elementCount;
    std::vector<int> testIndices(testIndexCount);
    for (int i = 0; i < testIndexCount; ++i)
        testIndices[i] = i;
    std::vector<int> trialIndices(trialIndexCount);
    for (int i = 0; i < trialIndexCount; ++i)
        trialIndices[i] = i;

    Fiber::_2dArray<arma::Mat<ResultType> > resultWithCaching;
    Fiber::_2dArray<arma::Mat<ResultType> > resultWithoutCaching;

    {
        DefaultLocalAssemblerForIntegralOperatorsOnSurfacesManager<
                typename ScalarTraits<ResultType>::RealType, ResultType> mgr(
                    true);
        mgr.assembler->evaluateLocalWeakForms(testIndices, trialIndices,
                                              resultWithCaching);
    }
    {
        DefaultLocalAssemblerForIntegralOperatorsOnSurfacesManager<
            typename ScalarTraits<ResultType>::RealType, ResultType> mgr(
                false);
        mgr.assembler->evaluateLocalWeakForms(testIndices, trialIndices,
                                              resultWithoutCaching);
    }

        BOOST_CHECK(check_arrays_are_close<ResultType>(
                    resultWithCaching, resultWithoutCaching, 1e-6));
}

BOOST_AUTO_TEST_SUITE_END()


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2012 - 2014 MetaScale SAS
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_SIMD_COMMON_IROUND2EVEN_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_SIMD_COMMON_IROUND2EVEN_HPP_INCLUDED

#include <boost/simd/arithmetic/functions/iround2even.hpp>
#include <boost/simd/include/functions/simd/fast_iround2even.hpp>
#include <boost/simd/include/functions/simd/is_greater.hpp>
#include <boost/simd/include/functions/simd/is_less.hpp>
#include <boost/simd/include/functions/simd/if_else.hpp>
#include <boost/simd/include/functions/simd/splat.hpp>
#include <boost/simd/include/constants/valmax.hpp>
#include <boost/simd/include/constants/valmin.hpp>
#include <boost/simd/sdk/meta/scalar_of.hpp>
#include <boost/simd/sdk/config.hpp>
#include <boost/dispatch/meta/as_integer.hpp>

#ifndef BOOST_SIMD_NO_NANS
#include <boost/simd/include/functions/simd/is_nan.hpp>
#include <boost/simd/include/functions/simd/if_zero_else.hpp>
#endif

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( iround2even_, tag::cpu_
                                    , (A0)(X)
                                    , ((simd_<single_<A0>, X>))
                                    )
  {
    typedef typename dispatch::meta::as_integer<A0>::type result_type;
    BOOST_SIMD_FUNCTOR_CALL(1)
    {
      typedef typename meta::scalar_of<result_type>::type sr_t;
      static const A0 Vax =  splat<A0>(boost::simd::Valmax<sr_t>());
      static const A0 Vix =  splat<A0>(boost::simd::Valmin<sr_t>());
    #ifndef BOOST_SIMD_NO_NANS
      A0 aa0 = if_zero_else(is_nan(a0), a0);
      return if_else(boost::simd::lt(aa0, Vix), Valmin<result_type>(),
                     if_else(boost::simd::gt(aa0, Vax), Valmax<result_type>(),
                             fast_iround2even(aa0)
                            )
                    );
    #else
      return if_else(boost::simd::lt(a0, Vix), Valmin<result_type>(),
                     if_else(boost::simd::gt(a0, Vax), Valmax<result_type>(),
                             fast_iround2even(a0)
                            )
                    );
    #endif
    }
  };
} } }

#endif


#ifndef DISTRIBUTED_TRAINING_OF_RECURRENT_NEURAL_NETWORKS_BY_FGM_PROTOCOL_FGM_NETWORK_HH
#define DISTRIBUTED_TRAINING_OF_RECURRENT_NEURAL_NETWORKS_BY_FGM_PROTOCOL_FGM_NETWORK_HH

#include <boost/range/adaptors.hpp>
#include <boost/shared_ptr.hpp>
#include <random>
#include "protocols.hh"
#include "cpp/models/rnn.hh"

namespace algorithms {

    namespace fgm {

        using namespace rnn;
        using namespace dds;
        using namespace protocols;

        struct Coordinator;
        struct CoordinatorProxy;
        struct LearningNode;
        struct LearningNodeProxy;

        // This is the FGM Network implementation.
        struct FgmNet : LearningNetwork<FgmNet, Coordinator, LearningNode> {
            typedef LearningNetwork<network_t, coordinator_t, node_t> fgmLearningNetwork;

            FgmNet(const set<source_id> &_hids, const string &_name, Query *Q);
        };

        // This is the hub/coordinator implementation for the Functional Geometric Method protocol.
        struct Coordinator : process {
            typedef Coordinator coordinator_t;
            typedef LearningNode node_t;
            typedef LearningNodeProxy node_proxy_t;
            typedef FgmNet network_t;

            proxy_map<node_proxy_t, node_t> proxy;

            // Protocol Stuff
            RnnLearner *globalLearner;          // ML model
            arma::cube trainX, trainY;          // Trainset data points and labels for warmup
            arma::cube testX, testY;            // Testset data points and labels
            size_t trainPoints;
            Query *Q;                           // query
            QueryState *queryState;             // current query state
            SafeFunction *safeFunction;         // the safe zone wrapper
            size_t k;                           // number of sites

            // Nodes indexing
            map<node_t *, size_t> nodeIndex;
            vector<node_t *> nodePtr;

            double psi;                         // The psi value of the functional geometric protocol.
            double theta;                       // The quantum of the functional geometric protocol.
            size_t counter;                     // A counter used by the functional geometric protocol.
            double barrier;                     // The smallest number the zeta function can reach.
            arma::mat params;                   // A placeholder for the parameters send by the nodes.

            // Statistics
            size_t nRounds;                     // Total number of rounds
            size_t nSubrounds;                  // Total number of subrounds
            size_t nSzSent;                     // Total safe zones sent
            size_t nUpdates;                    // Number of stream updates received

            // Constructor and Destructor
            Coordinator(network_t *nw, Query *Q);

            ~Coordinator() override;

            network_t *Net();

            const ProtocolConfig &Cfg() const;

            // Initialize the learner model and its' variables.
            void InitializeGlobalLearner();
            // Pre-train global model with a small batch to override the initial unstable situation of the distributed algorithm.
            void WarmupGlobalLearner();

            // Method used by the hub to establish the connections with the nodes of the star network.
            void SetupConnections();

            // Start a new round.
            void StartRound();

            // Getting the (drift) model of a node.
            void FetchUpdates(node_t *n);

            // Remote call on host violation.
            oneway ReceiveIncrement(IntValue inc);

            // Stuff for finalizing a round and start a new one.
            void FinishRound();

            // Printing overall stats of the training process.
            void ShowOverallStats();

            // Printing and saving the accuracy.
            void ShowProgress();

            // Getting the accuracy of the global learner.
            double Accuracy();
        };

        struct CoordinatorProxy : remote_proxy<Coordinator> {
            using coordinator_t = Coordinator;

            REMOTE_METHOD(coordinator_t, ReceiveIncrement);

            explicit CoordinatorProxy(process *c) : remote_proxy<coordinator_t>(c) {}
        };

        // This is the site/learning node implementation for the Functional Geometric Method protocol.
        struct LearningNode : local_site {

            typedef Coordinator coordinator_t;
            typedef LearningNode node_t;
            typedef LearningNodeProxy node_proxy_t;
            typedef FgmNet network_t;
            typedef CoordinatorProxy coord_proxy_t;
            typedef Query continuous_query_t;

            Query *Q;                           // The query management object.
            Safezone szone;                     // The safezone object.
            RnnLearner *learner;                // The learning algorithm.
            coord_proxy_t coord;                // The proxy of the coordinator/hub.
            arma::mat drift;
            arma::mat currentEstimate;          // The global estimate. It has a fixed value during a round.
            size_t localCounter;                // The counter used by the FGM protocol.
            float theta;                        // The quantum provided by the hub.
            float zeta;                         // The value of the safezone function.
            float phi;

            size_t datapointsPassed;

            LearningNode(network_t *net, source_id hid, continuous_query_t *Q);

            const ProtocolConfig &Cfg() const;

            // Initialize the learner model and its' variables.
            void InitializeLearner();

            // Train the local model with a batch and update protocol variables to check for violations.
            void UpdateState(arma::cube &x, arma::cube &y);

            // Called at the start of each round.
            oneway ResetForNewRound(const Safezone &newsz, DoubleValue qntm);

            // Refreshing the quantum for a new subround.
            oneway ReceiveQuantum(DoubleValue qntm);

            // Transfer (drift) model to the coordinator.
            ModelState SendDrift();

            // Transfer the value of Z(Xi,E) to the coordinator
            DoubleValue SendZeta();

            // Get the parameters from the hub at the start of a new round.
            oneway ReceiveGlobalModel(const ModelState &params);
        };

        struct LearningNodeProxy : remote_proxy<LearningNode> {
            typedef LearningNode node_t;

            REMOTE_METHOD(node_t, ResetForNewRound);
            REMOTE_METHOD(node_t, ReceiveQuantum);
            REMOTE_METHOD(node_t, SendDrift);
            REMOTE_METHOD(node_t, SendZeta);
            REMOTE_METHOD(node_t, ReceiveGlobalModel);

            explicit LearningNodeProxy(process *p) : remote_proxy<node_t>(p) {}
        };

    } // end namespace fgm
} // end namespace algorithms

namespace dds {
    template<>
    inline size_t byte_size<algorithms::fgm::LearningNode *>(
            algorithms::fgm::LearningNode *const &) { return 4; }
}
#endif


// tensorrtinputconns.cc ---

// Copyright (C) 2019 Jolibrain http://www.jolibrain.com

// Author: Guillaume Infantes <guillaume.infantes@jolibrain.com>

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.

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

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "tensorrtinputconns.h"
#include "half.hpp"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <boost/math/cstdfloat/cstdfloat_types.hpp>

namespace dd
{

  void ImgTensorRTInputFileConn::CVMatToRTBuffer(cv::Mat &img, int i)
  {
    cv::Mat converted;
    int channels = img.channels();
    int offset = channels * _height * _width * i;
    img.convertTo(converted, CV_32F);
    boost::float32_t *fbuf = (boost::float32_t *)(_buf.data());
    boost::float32_t *cvbuf = (boost::float32_t *)converted.data;
    for (int c = 0; c < channels; ++c)
      for (int h = 0; h < _height; ++h)
        for (int w = 0; w < _width; ++w)
          fbuf[offset++]
              = _scale * cvbuf[(converted.cols * h + w) * channels + c];
  }

  void ImgTensorRTInputFileConn::applyMeanToRTBuf(int channels, int i)
  {
    int offset = channels * _height * _width * i;
    for (int c = 0; c < channels; ++c)
      for (int h = 0; h < _height; ++h)
        for (int w = 0; w < _width; ++w)
          {
            int data_index = (c * _height + h) * _width + w;
            boost::float32_t *fbuf
                = static_cast<boost::float32_t *>(_buf.data());
            fbuf[offset + data_index] -= _mean[c];
          }
  }

  void ImgTensorRTInputFileConn::transform(const APIData &ad)
  {

    if (ad.has("has_mean_file"))
      _logger->warn("mean file cannot be used with tensorRT backend");

    try
      {
        ImgInputFileConn::transform(ad);
      }
    catch (const std::exception &e)
      {
        throw;
      }

    // ids
    bool set_ids = false;
    if (this->_ids.empty())
      set_ids = true;

    for (int i = 0; i < (int)this->_images.size(); i++)
      {
        if (set_ids)
          this->_ids.push_back(this->_uris.at(i));
        _imgs_size.insert(std::pair<std::string, std::pair<int, int>>(
            this->_ids.at(i), this->_images_size.at(i)));
      }
    _batch_size = this->_images.size();
    _batch_index = 0;
  }

  int ImgTensorRTInputFileConn::process_batch(const unsigned int batch_size)
  {
    if (batch_size < this->_images.size())
      this->_logger->warn("you are giving  more images than max_batch_size, "
                          "please double check");
    unsigned int i;
    if (_bw)
      _buf.resize(batch_size * height() * width());
    else
      _buf.resize(batch_size * 3 * height() * width());
    for (i = 0; i < batch_size && _batch_index < (int)this->_images.size();
         i++, _batch_index++)
      {
        cv::Mat img = this->_images.at(_batch_index);
        CVMatToRTBuffer(img, i);
        if (_has_mean_scalar)
          {
            applyMeanToRTBuf(img.channels(), i);
          }
      }
    return i;
  }

}


#pragma once

#include <mutex>
#include <shared_mutex>

#include <boost/heap/fibonacci_heap.hpp>

#include "abstract_cache.hpp"
#include "utils/assert.hpp"

namespace opossum {

/**
 * Generic cache implementation using the GDFS policy.
 * To iterate over the cache in a thread-safe manner, use the copy provided by snapshot().
 * Different cache implementations existed in the past, but were retired with PR 2129.
 */
template <typename Key, typename Value>
class GDFSCache : public AbstractCache<Key, Value> {
 public:
  // Entries within the GDFS cache.
  struct GDFSCacheEntry {
    Key key;
    Value value;
    size_t frequency;
    double size;
    double priority;

    // The underlying heap is a max-heap.
    // To have the item with lowest priority at the top, we invert the comparison.
    bool operator<(const GDFSCacheEntry& other) const {
      return priority > other.priority;
    }
  };

  using Handle = typename boost::heap::fibonacci_heap<GDFSCacheEntry>::handle_type;
  using CacheMap = typename std::unordered_map<Key, Handle>;
  using SnapshotEntry = typename AbstractCache<Key, Value>::SnapshotEntry;

  explicit GDFSCache(size_t capacity = DEFAULT_CACHE_CAPACITY) : AbstractCache<Key, Value>(capacity), _inflation(0.0) {}

  void set(const Key& key, const Value& value, double cost = 1.0, double size = 1.0) final {
    std::unique_lock<std::shared_mutex> lock(_mutex);
    if (this->_capacity == 0) {
      return;
    }

    auto it = _map.find(key);
    if (it != _map.end()) {
      // Update priority.
      Handle handle = it->second;

      GDFSCacheEntry& entry = (*handle);
      entry.value = value;
      entry.size = size;
      entry.frequency++;
      entry.priority = _inflation + static_cast<double>(entry.frequency) / entry.size;
      _queue.update(handle);

      return;
    }

    // If the cache is full, erase the item at the top of the heap
    // so that we can insert the new item.
    if (_queue.size() >= this->_capacity) {
      _evict();
    }

    // Insert new item in cache.
    GDFSCacheEntry entry{key, value, 1, size, 0.0};
    entry.priority = _inflation + static_cast<double>(entry.frequency) / entry.size;
    Handle handle = _queue.push(entry);
    _map[key] = handle;
  }

  std::optional<Value> try_get(const Key& query) final {
    std::unique_lock<std::shared_mutex> lock(_mutex);
    auto it = _map.find(query);
    if (it == _map.end()) {
      return std::nullopt;
    }

    Handle handle = it->second;
    GDFSCacheEntry& entry = (*handle);
    entry.frequency++;
    entry.priority = _inflation + static_cast<double>(entry.frequency) / entry.size;
    _queue.update(handle);
    return entry.value;
  }

  bool has(const Key& key) const final {
    std::shared_lock<std::shared_mutex> lock(_mutex);
    return _map.contains(key);
  }

  size_t size() const final {
    std::shared_lock<std::shared_mutex> lock(_mutex);
    return _map.size();
  }

  void clear() final {
    std::unique_lock<std::shared_mutex> lock(_mutex);
    _map.clear();
    _queue.clear();
  }

  void resize(size_t capacity) final {
    std::unique_lock<std::shared_mutex> lock(_mutex);
    while (_queue.size() > capacity) {
      _evict();
    }
    this->_capacity = capacity;
  }

  std::unordered_map<Key, SnapshotEntry> snapshot() const final {
    std::shared_lock<std::shared_mutex> lock(_mutex);
    std::unordered_map<Key, SnapshotEntry> map_copy(_map.size());
    for (const auto& [key, entry] : _map) {
      map_copy[key] = SnapshotEntry{(*entry).value, (*entry).frequency};
    }
    return map_copy;
  }

 protected:
  friend class CachePolicyTest;
  friend class QueryPlanCacheTest;

  // Priority queue to hold all elements. Implemented as max-heap.
  boost::heap::fibonacci_heap<GDFSCacheEntry> _queue;

  // Map to point towards element in the list.
  CacheMap _map;

  /**
   * Locking this data structure is easier than using a concurrent data structure (e.g. TBB) as
   * (1) both the queue and the map would have to be concurrent data structures,
   * (2) their modifications would have to be synchronized, and
   * (3) TBB's concurrent_unordered_map does not provide safe deletion and concurrent_hash_map is difficult to use.
   */
  mutable std::shared_mutex _mutex;

  // Inflation value that will be updated whenever an item is evicted.
  double _inflation;

  void _evict() final {
    auto top = _queue.top();

    _inflation = top.priority;
    _map.erase(top.key);
    _queue.pop();
  }
};

}  // namespace opossum


/* ----------------------------------------------------------------------------

 * GTSAM Copyright 2010, Georgia Tech Research Corporation,
 * Atlanta, Georgia 30332-0415
 * All Rights Reserved
 * Authors: Frank Dellaert, et al. (see THANKS for the full author list)

 * See LICENSE for the license information

 * -------------------------------------------------------------------------- */

/**
 *  @file DiscreteConditional.cpp
 *  @date Feb 14, 2011
 *  @author Duy-Nguyen Ta
 *  @author Frank Dellaert
 */

#include <gtsam/discrete/DiscreteConditional.h>
#include <gtsam/discrete/Signature.h>
#include <gtsam/inference/Conditional-inst.h>
#include <gtsam/base/Testable.h>
#include <gtsam/base/debug.h>

#include <boost/make_shared.hpp>

#include <algorithm>
#include <random>
#include <stdexcept>
#include <string>
#include <vector>

using namespace std;

namespace gtsam {

// Instantiate base class
template class GTSAM_EXPORT Conditional<DecisionTreeFactor, DiscreteConditional> ;

/* ******************************************************************************** */
DiscreteConditional::DiscreteConditional(const size_t nrFrontals,
    const DecisionTreeFactor& f) :
    BaseFactor(f / (*f.sum(nrFrontals))), BaseConditional(nrFrontals) {
}

/* ******************************************************************************** */
DiscreteConditional::DiscreteConditional(const DecisionTreeFactor& joint,
    const DecisionTreeFactor& marginal) :
    BaseFactor(
        ISDEBUG("DiscreteConditional::COUNT") ? joint : joint / marginal), BaseConditional(
            joint.size()-marginal.size()) {
  if (ISDEBUG("DiscreteConditional::DiscreteConditional"))
    cout << (firstFrontalKey()) << endl; //TODO Print all keys
}

/* ******************************************************************************** */
DiscreteConditional::DiscreteConditional(const DecisionTreeFactor& joint,
    const DecisionTreeFactor& marginal, const Ordering& orderedKeys) :
    DiscreteConditional(joint, marginal) {
  keys_.clear();
  keys_.insert(keys_.end(), orderedKeys.begin(), orderedKeys.end());
}

/* ******************************************************************************** */
DiscreteConditional::DiscreteConditional(const Signature& signature)
    : BaseFactor(signature.discreteKeys(), signature.cpt()),
      BaseConditional(1) {}

/* ******************************************************************************** */
void DiscreteConditional::print(const string& s,
                                const KeyFormatter& formatter) const {
  cout << s << " P( ";
  for (const_iterator it = beginFrontals(); it != endFrontals(); ++it) {
    cout << formatter(*it) << " ";
  }
  if (nrParents()) {
    cout << "| ";
    for (const_iterator it = beginParents(); it != endParents(); ++it) {
      cout << formatter(*it) << " ";
    }
  }
  cout << ")";
  Potentials::print("");
  cout << endl;
}

/* ******************************************************************************** */
bool DiscreteConditional::equals(const DiscreteFactor& other,
    double tol) const {
  if (!dynamic_cast<const DecisionTreeFactor*>(&other))
    return false;
  else {
    const DecisionTreeFactor& f(
        static_cast<const DecisionTreeFactor&>(other));
    return DecisionTreeFactor::equals(f, tol);
  }
}

/* ******************************************************************************** */
static DiscreteConditional::ADT Choose(const DiscreteConditional& conditional,
                                       const DiscreteValues& parentsValues) {
  // Get the big decision tree with all the levels, and then go down the
  // branches based on the value of the parent variables.
  DiscreteConditional::ADT adt(conditional);
  size_t value;
  for (Key j : conditional.parents()) {
    try {
      value = parentsValues.at(j);
      adt = adt.choose(j, value);  // ADT keeps getting smaller.
    } catch (std::out_of_range&) {
      parentsValues.print("parentsValues: ");
      throw runtime_error("DiscreteConditional::choose: parent value missing");
    };
  }
  return adt;
}

/* ******************************************************************************** */
DecisionTreeFactor::shared_ptr DiscreteConditional::choose(
    const DiscreteValues& parentsValues) const {
  // Get the big decision tree with all the levels, and then go down the
  // branches based on the value of the parent variables.
  ADT adt(*this);
  size_t value;
  for (Key j : parents()) {
    try {
      value = parentsValues.at(j);
      adt = adt.choose(j, value);  // ADT keeps getting smaller.
    } catch (exception&) {
      parentsValues.print("parentsValues: ");
      throw runtime_error("DiscreteConditional::choose: parent value missing");
    };
  }

  // Convert ADT to factor.
  DiscreteKeys discreteKeys;
  for (Key j : frontals()) {
    discreteKeys.emplace_back(j, this->cardinality(j));
  }
  return boost::make_shared<DecisionTreeFactor>(discreteKeys, adt);
}

/* ******************************************************************************** */
DecisionTreeFactor::shared_ptr DiscreteConditional::likelihood(
    const DiscreteValues& frontalValues) const {
  // Get the big decision tree with all the levels, and then go down the
  // branches based on the value of the frontal variables.
  ADT adt(*this);
  size_t value;
  for (Key j : frontals()) {
    try {
      value = frontalValues.at(j);
      adt = adt.choose(j, value);  // ADT keeps getting smaller.
    } catch (exception&) {
      frontalValues.print("frontalValues: ");
      throw runtime_error("DiscreteConditional::choose: frontal value missing");
    };
  }

  // Convert ADT to factor.
  DiscreteKeys discreteKeys;
  for (Key j : parents()) {
    discreteKeys.emplace_back(j, this->cardinality(j));
  }
  return boost::make_shared<DecisionTreeFactor>(discreteKeys, adt);
}

/* ******************************************************************************** */
DecisionTreeFactor::shared_ptr DiscreteConditional::likelihood(
    size_t parent_value) const {
  if (nrFrontals() != 1)
    throw std::invalid_argument(
        "Single value likelihood can only be invoked on single-variable "
        "conditional");
  DiscreteValues values;
  values.emplace(keys_[0], parent_value);
  return likelihood(values);
}

/* ******************************************************************************** */
void DiscreteConditional::solveInPlace(DiscreteValues* values) const {
  // TODO: Abhijit asks: is this really the fastest way? He thinks it is.
  ADT pFS = Choose(*this, *values); // P(F|S=parentsValues)

  // Initialize
  DiscreteValues mpe;
  double maxP = 0;

  DiscreteKeys keys;
  for(Key idx: frontals()) {
    DiscreteKey dk(idx, cardinality(idx));
    keys & dk;
  }
  // Get all Possible Configurations
  const auto allPosbValues = cartesianProduct(keys);

  // Find the MPE
  for(const auto& frontalVals: allPosbValues) {
    double pValueS = pFS(frontalVals); // P(F=value|S=parentsValues)
    // Update MPE solution if better
    if (pValueS > maxP) {
      maxP = pValueS;
      mpe = frontalVals;
    }
  }

  //set values (inPlace) to mpe
  for(Key j: frontals()) {
    (*values)[j] = mpe[j];
  }
}

/* ******************************************************************************** */
void DiscreteConditional::sampleInPlace(DiscreteValues* values) const {
  assert(nrFrontals() == 1);
  Key j = (firstFrontalKey());
  size_t sampled = sample(*values); // Sample variable given parents
  (*values)[j] = sampled; // store result in partial solution
}

/* ******************************************************************************** */
size_t DiscreteConditional::solve(const DiscreteValues& parentsValues) const {

  // TODO: is this really the fastest way? I think it is.
  ADT pFS = Choose(*this, parentsValues); // P(F|S=parentsValues)

  // Then, find the max over all remaining
  // TODO, only works for one key now, seems horribly slow this way
  size_t mpe = 0;
  DiscreteValues frontals;
  double maxP = 0;
  assert(nrFrontals() == 1);
  Key j = (firstFrontalKey());
  for (size_t value = 0; value < cardinality(j); value++) {
    frontals[j] = value;
    double pValueS = pFS(frontals); // P(F=value|S=parentsValues)
    // Update MPE solution if better
    if (pValueS > maxP) {
      maxP = pValueS;
      mpe = value;
    }
  }
  return mpe;
}

/* ******************************************************************************** */
size_t DiscreteConditional::sample(const DiscreteValues& parentsValues) const {
  static mt19937 rng(2);  // random number generator

  // Get the correct conditional density
  ADT pFS = Choose(*this, parentsValues);  // P(F|S=parentsValues)

  // TODO(Duy): only works for one key now, seems horribly slow this way
  if (nrFrontals() != 1) {
    throw std::invalid_argument(
        "DiscreteConditional::sample can only be called on single variable "
        "conditionals");
  }
  Key key = firstFrontalKey();
  size_t nj = cardinality(key);
  vector<double> p(nj);
  DiscreteValues frontals;
  for (size_t value = 0; value < nj; value++) {
    frontals[key] = value;
    p[value] = pFS(frontals);  // P(F=value|S=parentsValues)
    if (p[value] == 1.0) {
      return value;  // shortcut exit
    }
  }
  std::discrete_distribution<size_t> distribution(p.begin(), p.end());
  return distribution(rng);
}

/* ******************************************************************************** */
size_t DiscreteConditional::sample(size_t parent_value) const {
  if (nrParents() != 1)
    throw std::invalid_argument(
        "Single value sample() can only be invoked on single-parent "
        "conditional");
  DiscreteValues values;
  values.emplace(keys_.back(), parent_value);
  return sample(values);
}

/* ************************************************************************* */
std::string DiscreteConditional::markdown(
    const KeyFormatter& keyFormatter) const {
  std::stringstream ss;

  // Print out signature.
  ss << " *P(";
  bool first = true;
  for (Key key : frontals()) {
    if (!first) ss << ",";
    ss << keyFormatter(key);
    first = false;
  }
  if (nrParents() == 0) {
   // We have no parents, call factor method.
    ss << ")*:\n" << std::endl;
    ss << DecisionTreeFactor::markdown(keyFormatter);
    return ss.str();
  }

  // We have parents, continue signature and do custom print.
  ss << "|";
  first = true;
  for (Key parent : parents()) {
    if (!first) ss << ",";
    ss << keyFormatter(parent);
    first = false;
  }
  ss << ")*:\n" << std::endl;

  // Print out header and construct argument for `cartesianProduct`.
  std::vector<std::pair<Key, size_t>> pairs;
  ss << "|";
  const_iterator it;
  for(Key parent: parents()) {
    ss << keyFormatter(parent) << "|";
    pairs.emplace_back(parent, cardinalities_.at(parent));
  }

  size_t n = 1;
  for(Key key: frontals()) {
    size_t k = cardinalities_.at(key);
    pairs.emplace_back(key, k);
    n *= k;
  }
  std::vector<std::pair<Key, size_t>> slatnorf(pairs.rbegin(),
                                               pairs.rend() - nrParents());
  const auto frontal_assignments = cartesianProduct(slatnorf);
  for (const auto& a : frontal_assignments) {
    for (it = beginFrontals(); it != endFrontals(); ++it) ss << a.at(*it);
    ss << "|";
  }
  ss << "\n";

  // Print out separator with alignment hints.
  ss << "|";
  for (size_t j = 0; j < nrParents() + n; j++) ss << ":-:|";
  ss << "\n";

  // Print out all rows.
  std::vector<std::pair<Key, size_t>> rpairs(pairs.rbegin(), pairs.rend());
  const auto assignments = cartesianProduct(rpairs);
  size_t count = 0;
  for (const auto& a : assignments) {
    if (count == 0) {
      ss << "|";
      for (it = beginParents(); it != endParents(); ++it)
        ss << a.at(*it) << "|";
    }
    ss << operator()(a) << "|";
    count = (count + 1) % n;
    if (count == 0) ss << "\n";
  }
  return ss.str();
}
/* ************************************************************************* */

}  // namespace gtsam


//////////////////////////////////////////////////////////////////////////////
//
// Released under MIT License
// Copyright (c) 2020 Hernan Perrone (hernan.perrone@gmail.com)
//////////////////////////////////////////////////////////////////////////////

#ifndef FT_PROTOCOL_MSG_H
#define FT_PROTOCOL_MSG_H

#include <string>
#include <vector>

#include <boost/uuid/uuid.hpp>

#include "ft_utils.hpp"

namespace ft { namespace proto {

FT_DECLARE_CLASS(Message)

class MessageBuilder;

// The protocol is being designed to be also compatible with UDP, where the
// maximum datagram size is 4096B.
static const uint32_t MAX_MSG_SIZE = 4096U;

// In this case, 128 bytes are being reserved for protocol head, and using all
// the remaining bytes for delivering file's chunks payloads.
static const size_t MAX_MSG_PAYLOAD_SIZE = MAX_MSG_SIZE - 128U;

// Using BLAKE2 for hashing, digest is 64 bytes long
static const size_t HASH_SIZE = 64U;

// Using BLAKE2 for hashing chunks, but only 1 half of the digest
static const size_t CHUNK_HASH_SIZE = 32U;

// MAGIC number used to tag the beginning of each message
static const uint32_t MAGIC = 0x87FE7700;

/// Message type, works also as a magic number
typedef enum {
	MSGTYPE_FILE_OFFER      = MAGIC | 0x01,
	MSGTYPE_FILE_CHUNK_REQ  = MAGIC | 0x02,
	MSGTYPE_FILE_CHUNK_DATA = MAGIC | 0x03,
	MSGTYPE_FILE_COMPLETE   = MAGIC | 0x04,
	MSGTYPE_MAX
} MessageType;

/// @brief Message of the protocol
///
/// Parses, serializes and holds the information of the messages that are used
/// in the protocol for transferring files.
class Message {
public:
	MessageType          msg_type;    ///! Message type
	uint16_t             msg_len;     ///! Message length (from seq_number)
	uint16_t             seq_number;  ///! Message sequence number
	boost::uuids::uuid   client_uuid; ///! Client UUID
	std::string          file_name;   ///! File name

	/// Fields in FILE OFFER messages
	struct {
		uint32_t             file_size;     ///! Total file size
		uint32_t             file_n_chunks; ///! Number of chunks
		std::vector<uint8_t> file_hash;     ///! File hash [FILE_HASH]
	} offer;

	/// Fields in FILE CHUNK REQUEST messages
	struct {
		uint32_t             chunk_idx_first; ///! Chunk index being requested
		uint32_t             chunk_idx_last;  ///! (Not used)
	} chunk_req;

	/// Fields in FILE CHUNK DATA messages
	struct {
		uint32_t             idx;  ///! Chunk index
		std::vector<uint8_t> data; ///! Chunk data [up to MAX_MSG_PAYLOAD_SIZE]
		std::vector<uint8_t> hash; ///! Chunk hash [CHUNK_HASH_SIZE] (Not used)
	} chunk_data;

public:
	Message() {}
	Message(const std::vector<uint8_t>& buf);

	virtual ~Message() {}

	void serialize(std::vector<uint8_t>& out);

	friend class MessageFactory;
};

} // proto
} // ft

#endif // FT_PROTOCOL_MSG_H


/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2020 Scientific Computing and Imaging Institute,
   University of Utah.

   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 <Core/Algorithms/Base/AlgorithmPreconditions.h>
#include <Core/Algorithms/BrainStimulator/BiotSavartSolverAlgorithm.h>
#include <Core/Datatypes/DenseMatrix.h>
#include <Core/Datatypes/Legacy/Field/Field.h>
#include <Core/Datatypes/Legacy/Field/VField.h>
#include <Core/Datatypes/Legacy/Field/VMesh.h>
#include <Core/Math/MiscMath.h>
#include <Core/Datatypes/Legacy/Field/FieldInformation.h>
#include <Core/Thread/Barrier.h>
#include <Core/Thread/Parallel.h>
#include <string>
#include <cassert>
#include <memory>
#include <iomanip>
#include <locale>
#include <Core/Logging/Log.h>
#include <boost/lexical_cast.hpp>

using namespace SCIRun::Core::Algorithms;
using namespace SCIRun::Core::Algorithms::BrainStimulator;
using namespace SCIRun::Core::Datatypes;
using namespace SCIRun::Core::Thread;
using namespace SCIRun::Core::Geometry;
using namespace SCIRun::Core::Logging;
using namespace SCIRun;

ALGORITHM_PARAMETER_DEF(BrainStimulator, Mesh);
ALGORITHM_PARAMETER_DEF(BrainStimulator, Coil);
ALGORITHM_PARAMETER_DEF(BrainStimulator, VectorBField);
ALGORITHM_PARAMETER_DEF(BrainStimulator, VectorAField);
ALGORITHM_PARAMETER_DEF(BrainStimulator, OutType);

class KernelBase
{

public:
	KernelBase(const AlgorithmBase* algo, int t) :
	  algo_(algo),
	  numprocessors_(Parallel::NumCores()),
	  barrier_("BSV KernelBase Barrier", numprocessors_),
	  typeOut_(t)
	{
	}

	virtual ~KernelBase()
	{
	}

	//! Local entry function, must be implemented by each specific kernel
	virtual bool integrate(FieldHandle& mesh, FieldHandle& coil, DenseMatrixHandle& outdata) = 0;

protected:

	//! ref to the executing algorithm context
	const AlgorithmBase* algo_;
	unsigned int numprocessors_;

	//! model miscs.
	VMesh* vmesh_ {nullptr};
	VField* vfield_ {nullptr};
	size_type modelSize_ {0};

	//! coil miscs.
	VMesh* vcoil_ {nullptr};
	VField* vcoilField_ {nullptr};
	size_type coilSize_ {0};

	//! parallel essential primitives
	Barrier barrier_;
	std::vector<bool> success_;

	//! output Field
	int typeOut_;
	DenseMatrixHandle matOut_;

	bool preIntegration( FieldHandle& mesh, FieldHandle& coil )
	{
		vmesh_ = mesh->vmesh();
		vcoil_ = coil->vmesh();
		vfield_ = mesh->vfield();
		vcoilField_ = coil->vfield();

		numprocessors_ = Parallel::NumCores();

		#ifdef _DEBUG
			//! DEBUG when we want to test with one CPU only
			numprocessors_ = 1;
		#endif

		success_.resize(numprocessors_, true);

		//! get number of nodes for the model
		modelSize_ = vmesh_->num_nodes();
		assert(modelSize_ > 0);

		matOut_.reset(new DenseMatrix(static_cast<int>(modelSize_), 3));

    algo_->remark("Output matrix size: " + std::to_string(modelSize_) + "x3");
    algo_->remark("Number of processors:  " + boost::lexical_cast<std::string>(numprocessors_));
    algo_->remark("[Important] CPU usage will be very high while running this module. \r\nTo limit the number of cores used, adjust the maximum core setting in Preferences->Advanced");

		return true;
	}

	bool postIntegration(DenseMatrixHandle& outdata)
	{
		//! check for error
		for (size_t j=0; j < success_.size(); j++)
		{
			if (!success_[j]) return (false);
		}
		outdata = matOut_;
		return true;
	}
};

namespace
{
  template<class T>
  std::string formatWithCommas(const T& value)
  {
    std::stringstream ss;
    ss.imbue(std::locale(""));
    ss << std::fixed << value;
    return ss.str();
  }
}

class PieceWiseKernel : public KernelBase
{
public:
	PieceWiseKernel(const AlgorithmBase* algo, int t ) : KernelBase(algo,t)
	{
		//we keep last calculated step
		//however if segments lenght varies,
		//it makes more sense to keep a look-up table of previous steps for given lenght
		autostep_ = 0.1;
		extstep_ = -1.0;
	}

	//! Complexity O(M*N) ,where M is the number of nodes of the model and N is the numbder of nodes of the coil
	bool integrate(FieldHandle& mesh, FieldHandle& coil, DenseMatrixHandle& outdata) override
	{
		if (!preIntegration(mesh,coil))
		{
			return (false);
		}

    algo_->remark("Launching PieceWiseKernel. Size of computation remark coming soon..");

		vmesh_->synchronize(Mesh::NODES_E | Mesh::EDGES_E);

		VMesh::Node::array_type enodes;
		Point enode1, enode2;

		//! get numbder of nodes for the coil
		coilSize_ = vcoil_->num_nodes();

		//! basic assumption
		assert(modelSize_ > 0 && coilSize_ > 1);

		coilNodes_.clear();
		coilNodes_.reserve(coilSize_);

		for (VMesh::Edge::index_type i = 0; i < vcoil_->num_edges(); i++)
		{
			vcoil_->get_nodes(enodes,i);
			vcoil_->get_point(enode1,enodes[0]);
			vcoil_->get_point(enode2,enodes[1]);
			coilNodes_.push_back(Vector(enode1));
			coilNodes_.push_back(Vector(enode2));
		}

		//! Start the multi threaded
		Parallel::RunTasks([this](int i) { ParallelKernel(i); }, numprocessors_);

		return postIntegration(outdata);
	}

	void setIntegrationStep(double step)
	{
		assert(step >= 0.0);
		extstep_ = step;
	}

	double getIntegrationStep() const
	{
		return extstep_;
	}

private:
	//! integration step, will auto adapt
	double autostep_;
	//! integration step, externally provided
	double extstep_;
	//! keep nodes on the coil cached
	std::vector<Vector> coilNodes_;

	//! execute in parallel
	void ParallelKernel(int proc_num)
	{
		assert(proc_num >= 0);

		int cnt = 0;

		const index_type begins = (modelSize_ * proc_num) / numprocessors_;
		const index_type ends  = (modelSize_ * (proc_num+1)) / numprocessors_;

		assert( begins <= ends );

		//! buffer of points used for integration
		std::vector<Vector> integrPoints;
		integrPoints.reserve(256);

		//! keep previous step length
		//! used for optimization purpose
		double prevSegLen = 123456789.12345678;

		//! number of integration points
		int nips = 0;

    bool remarkedOnProblemSize = false;

		try
		{
			for (index_type iM = begins; iM < ends; iM++)
			{
        Point modelNodeP;
				vmesh_->get_node(modelNodeP, iM);
        const Vector modelNodeV(modelNodeP);

				// result
				Vector F;

				for (size_t iC0 = 0, iC1 =1, iCV = 0; iC0 < coilNodes_.size(); iC0+=2, iC1+=2, iCV++)
				{
          double currentFromField;
					vcoilField_->get_value(currentFromField,iCV);

					const double current = currentFromField == 0.0 ? 1.0 : currentFromField;
          auto absCurrent = std::fabs(current);

					Vector coilNodeThis;
					Vector coilNodeNext;

					if (current >= 0.0)
					{
						coilNodeThis = coilNodes_[iC0];
						coilNodeNext = coilNodes_[iC1];
					}
					else
					{
						coilNodeThis = coilNodes_[iC1];
						coilNodeNext = coilNodes_[iC0];
					}

					//! Length of the curve element
					Vector diffNodes = coilNodeNext - coilNodeThis;
					double newSegLen = diffNodes.length();

					//first check if externally suplied integration step is available and use it
					if (extstep_ > 0)
					{
						nips = newSegLen / extstep_;
					}
					else
					{
						//! optimization
						//! only recompute integration step only if segment length changes
						if (Abs(prevSegLen - newSegLen ) > 0.00000001)
						{
							prevSegLen = newSegLen;

							//auto adaptive integration step calculation
							nips = adjustNumberOfIntegrationPoints(newSegLen);
						}
					}

					if (nips < 3)
					{
						algo_->warning("integration step too big");
					}

					integrPoints.clear();

          if (!remarkedOnProblemSize && proc_num == 0)
          {
            auto problemSize = (ends - begins) * coilNodes_.size() * nips;
            algo_->remark("Per core load: " + formatWithCommas(problemSize) + " field computations.");
            algo_->remark("To speed up this module, reduce the number of nodes in either the input mesh or the coil, or pick a simpler algorithm.");
            remarkedOnProblemSize = true;
          }

					//! curve segment discretization
					for (int iip = 0; iip < nips; iip++)
					{

						double interpolant = static_cast<double>(iip) / static_cast<double>(nips);
						Vector v = Interpolate( coilNodeThis, coilNodeNext, interpolant );
						integrPoints.push_back( v );
					}

					//! integration step over line segment
					for (int iip = 0; iip < nips -1; iip++)
					{
            const auto piip = integrPoints[iip];
            const auto piip1 = integrPoints[iip+1];
						//! Vector connecting the infinitesimal curve-element
						Vector Rxyz = (piip + piip1) / 2  - modelNodeV;

						//! Infinitesimal curve-element components
						Vector dLxyz = piip1 - piip;

						double Rn = Rxyz.length();

						if (typeOut_ == 1)
						{
              //! check for distance between coil and model close to zero
              //! it might cause numerical stability issues with respect to the cross-product
              if (Rn < 0.00001)
              {
                algo_->warning("coil<->model distance approaching zero!");
              }
							//! Biot-Savart Magnetic Field
							F += 1.0e-7 * Cross( Rxyz, dLxyz ) * (absCurrent / (Rn*Rn*Rn) );
						}
						else if (typeOut_ == 2)
						{
							//! Biot-Savart Magnetic Vector Potential Field
							F += 1.0e-7 * dLxyz * (absCurrent / (Rn) );
						}
					}
				}

				matOut_->put(iM,0, F[0]);
				matOut_->put(iM,1, F[1]);
				matOut_->put(iM,2, F[2]);

				//! progress reporter
				if (proc_num == 0)
				{
					cnt++;
					if (cnt == 200)
					{
						cnt = 0;
						algo_->update_progress_max(iM, ends-begins);
					}
				}
			}
			success_[proc_num] = true;
		}
		catch (...)
		{
			algo_->error("PieceWiseKernel crashed while integrating");
			success_[proc_num] = false;
		}

		//! check point
		barrier_.wait();

		// Bail out if one of the processes failed
		for (size_t q = 0; q < numprocessors_; q++)
			if (!success_[q]) return;
	}

	//! Auto adjust accuracy of integration
	int adjustNumberOfIntegrationPoints(double len)
	{
		int minNP = 100;//more than 1 for sure
		int maxNP = 200;//no more than 1000
		int NP = 0;
		bool over = false;
		bool under = false;

		do
		{
			NP = ceil( len / autostep_ );

			under = NP < minNP;
			over = NP > maxNP;

			if (under) autostep_ *= 0.5;
			if (over) autostep_ *= 1.5;

		} while ( under || over );

		return NP;
	}
};

//! TODO
class VolumetricKernel : public KernelBase
{
public:
	using KernelBase::KernelBase;

	bool integrate(FieldHandle& mesh, FieldHandle& coil, DenseMatrixHandle& outdata) override
	{
		if (!preIntegration(mesh,coil))
		{
			return (false);
		}

    algo_->remark("Launching VolumetricKernel");

		//! get numbder of nodes for the coil
		coilSize_ = vcoil_->num_elems();

		//! basic assumption
		assert(modelSize_ > 0 && coilSize_ > 1);

		vmesh_->synchronize(Mesh::NODES_E | Mesh::EDGES_E);

		//! Start the multi threaded
		Parallel::RunTasks([this](int i) { ParallelKernel(i); }, numprocessors_);

		return postIntegration(outdata);
	}
private:
	void ParallelKernel(int proc_num)
	{
		assert(proc_num >= 0);

		int cnt = 0;
		Point modelNode;
		Point coilCenter;
		Vector current;

		const VMesh::Node::index_type begins = (modelSize_ * proc_num) / numprocessors_;
		const VMesh::Node::index_type ends  = (modelSize_ * (proc_num+1)) / numprocessors_;

		assert( begins <= ends );

		try
		{
			for (VMesh::Node::index_type iM = begins; iM < ends; iM++)
			{
				vmesh_->get_node(modelNode,iM);

				//! accumulatedresult
				Vector F, R;
				double evol = 0.0;
				double Rl;

				for (VMesh::Elem::index_type iC = 0; iC < coilSize_; iC++)
				{
					vcoilField_->get_value(current,iC);

					vcoilField_->get_center(coilCenter, iC);//auto resolve based on basis_order

					evol = vcoil_->get_volume(iC);

					R = coilCenter - modelNode;

					Rl = R.length();

					if (typeOut_ == 1)
					{
						//! Biot-Savart Magnetic Field
						F += Cross ( current , R ) * ( evol / (4.0 * M_PI * Rl) );
					}
					else if (typeOut_ == 2)
					{
						//! Biot-Savart Magnetic Vector Potential Field
						F += current * ( evol / (4.0 * M_PI * Rl) );
					}
				}

				matOut_->put(iM, 0, F[0]);
				matOut_->put(iM, 1, F[1]);
				matOut_->put(iM, 2, F[2]);

				//! progress reporter
				if (proc_num == 0)
				{
					cnt++;
					if (cnt == 200)
					{
						cnt = 0;
						algo_->update_progress_max(iM, ends - begins);
					}
				}
			}

			success_[proc_num] = true;
		}
		catch (...)
		{
			algo_->error("VolumetricKernel crashed while integrating");
			success_[proc_num] = false;
		}

		//! check point
		barrier_.wait();

		// Bail out if one of the processes failed
		for (size_t q = 0; q < numprocessors_; q++)
			if (!success_[q])
				return;
	}
};

//! Magnetic Dipoles solver
class DipolesKernel : public KernelBase
{
public:
	using KernelBase::KernelBase;

	bool integrate(FieldHandle& mesh, FieldHandle& coil, DenseMatrixHandle& outdata) override
	{
		if (!preIntegration(mesh,coil))
		{
			return false;
		}

    algo_->remark("Launching DipolesKernel");

		//! get number of nodes for the coil
		coilSize_ = vcoil_->num_elems();

		//! basic assumption
		assert(modelSize_ > 0 && coilSize_ > 1);

		//needed?
		vmesh_->synchronize(Mesh::NODES_E | Mesh::EDGES_E);

		//! Start the multi threaded
		Parallel::RunTasks([this](int i) { ParallelKernel(i); }, numprocessors_);

		return postIntegration(outdata);
	}

private:
	void ParallelKernel(int proc_num)
	{
		assert(proc_num >= 0);

		int cnt = 0;
		Point modelNode;
		Point dipoleLocation;
		Vector dipoleMoment;

		const VMesh::Node::index_type begins = (modelSize_ * proc_num) / numprocessors_;
		const VMesh::Node::index_type ends  = (modelSize_ * (proc_num+1)) / numprocessors_;

		assert( begins <= ends );

		try
		{
			for (VMesh::Node::index_type iM = begins; iM < ends; iM++)
			{
				vmesh_->get_node(modelNode,iM);

				//! accumulated result
				Vector F, R;
				double Rl;

				for (VMesh::Elem::index_type iC = 0; iC < coilSize_; iC++)
				{
					vcoilField_->get_value(dipoleMoment, iC);
					vcoilField_->get_center(dipoleLocation, iC);//auto resolve based on basis_order

					R = dipoleLocation - modelNode;
					Rl = R.length();

					if (typeOut_ == 1)
					{
						//! Biot-Savart Magnetic Field
						F += 1.0e-7 * ( 3 * R * Dot ( dipoleMoment, R ) / (Rl*Rl*Rl*Rl*Rl) - dipoleMoment / (Rl*Rl*Rl) ) ;
					}
					if (typeOut_ == 2)
					{
						//! Biot-Savart Magnetic Vector Potential Field
						F += 1.0e-7 * Cross ( dipoleMoment , R ) / (Rl*Rl*Rl) ;
					}
				}

				matOut_->put(iM, 0, F[0]);
				matOut_->put(iM, 1, F[1]);
				matOut_->put(iM, 2, F[2]);

				//! progress reporter
				if (proc_num == 0)
				{
					cnt++;
					if (cnt == 200)
					{
						cnt = 0;
						algo_->update_progress_max(iM, ends - begins);;
					}
				}
			}

			success_[proc_num] = true;
		}
		catch (...)
		{
			algo_->error(std::string("DipoleKernel crashed while integrating"));
			success_[proc_num] = false;
		}

		//! check point
		barrier_.wait();

		// Bail out if one of the processes failed
		for (size_t q = 0; q < numprocessors_; q++)
			if (!success_[q])
				return;
	}
};

bool BiotSavartSolverAlgorithm::run(FieldHandle mesh, FieldHandle coil, DenseMatrixHandle& outdata, int outtype) const
{
  if (!mesh)
  {
    error("No input domain field");
    return (false);
  }

  if (!coil)
  {
    error("No input coil source field");
    return (false);
  }

  if (coil->vfield()->basis_order() == -1)
  {
		error("Need data on coil mesh.");
		return (false);
  }

  if (coil->vmesh()->is_curvemesh())
  {
    if (coil->vfield()->is_constantdata() && coil->vfield()->is_scalar())
    {
      PieceWiseKernel pwk(this, outtype);
      if (!pwk.integrate(mesh,coil,outdata))
      {
				error("Aborted during integration");
				return (false);
      }
    }
    else
    {
      error("Curve mesh expected with constant scalar data.");
      return (false);
    }
  }
  else if (coil->vmesh()->is_pointcloudmesh())
  {
		if ((coil->vfield()->is_lineardata() || coil->vfield()->is_constantdata() ) && coil->vfield()->is_vector())
		{
			DipolesKernel dp(this, outtype);
			if (!dp.integrate(mesh,coil,outdata))
			{
				error("Aborted during integration");
				return (false);
			}
		}
		else
		{
			error("Pointcloud expected with linear vector data.");
			return (false);
		}
	}
  else if (coil->vmesh()->is_volume())
  {
		if (coil->vfield()->is_constantdata() && coil->vfield()->is_vector())
		{
			VolumetricKernel vp(this, outtype);
			if (!vp.integrate(mesh,coil,outdata))
			{
				error("Aborted during integration");
				return (false);
			}
		}
		else
		{
			error("Volumetric mesh expected with constant vector data.");
			return (false);
		}
	}
	else
	{
		error("Unsupported mesh type! Only curve or volumetric.");
		return (false);
	}

  return (true);
}

AlgorithmOutput BiotSavartSolverAlgorithm::run(const AlgorithmInput& input) const
{
	AlgorithmOutput output;

	auto mesh = input.get<Field>(Parameters::Mesh);
	auto coil = input.get<Field>(Parameters::Coil);

	auto oports = get(Parameters::OutType).toInt();
	std::map<int, DenseMatrixHandle> cases;
	if (oports == 3)
		cases = {{1, nullptr}, {2, nullptr}};
	else
		cases[oports] = nullptr;

  for (auto& c : cases)
  {
    if (!run(mesh, coil, c.second, c.first))
    {
      error("Error: Algorithm of BiotSavartSolver failed.");
    }
  }
	output[Parameters::VectorBField] = cases[1];
	output[Parameters::VectorAField] = cases[2];

	return output;
}


#ifndef TVMTL_TVMINIMIZER_HPP
#define TVMTL_TVMINIMIZER_HPP

//System includes
#include <iostream>
#include <map>
#include <vector>
#include <chrono>

#ifdef TVMTL_TVMIN_DEBUG
    #include <string>
#endif

//Eigen includes
#include <Eigen/Sparse>
#include <Eigen/Core>

//CGAL includes For linear Interpolation
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Interpolation_traits_2.h>
#include <CGAL/natural_neighbor_coordinates_2.h>
#include <CGAL/interpolation_functions.h>

//vpp includes
#include <vpp/vpp.hh>

//own inlcudes
#include "manifold.hpp"
#include "data.hpp"
#include "functional.hpp"

namespace tvmtl {

// Primary Template
template < enum ALGORITHM AL, class FUNCTIONAL, class MANIFOLD, class DATA, enum PARALLEL PAR=OMP, int DIM=2 >
    class TV_Minimizer {

    };

}

#include "tvmin_irls.hpp"
#include "tvmin_irls3d.hpp"
#include "tvmin_prpt.hpp"
#include "tvmin_prpt3d.hpp"

#endif


#include "bio_test_data.h"

#include <bio/biobase_match.h>
#include <bio/matrix.h>
#include <bio/score_sequence.h>
USING_BIO_NS;

#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/io/ios_state.hpp>
#include <boost/progress.hpp>
#include <boost/test/parameterized_test.hpp>
using namespace boost;
using boost::unit_test::test_suite;

#include <string>
using namespace std;

/**
Results from TRANSFAC for the test sequence:

matrix                    position  core   matrix sequence (always the               factor name
identifier                (strand)  match  match  (+)-strand is shown)

V$DEAF1_01                  86 (+)  1.000  0.857  gcgcctcagtgtacTTCCGaacgaa          DEAF1
V$IPF1_Q4_01               111 (+)  1.000  0.975  tgagtCATTAataga                    IPF1
V$EFC_Q6                   336 (-)  0.910  0.853  ttacccgaGTGACt                     RFX1
                                                                                (EF-C)
V$AP1_Q4                   343 (+)  1.000  0.990  agTGACTgact                        AP-1
V$CREBP1_01                384 (+)  1.000  1.000  TTACGtaa                           CRE-BP1
V$CREBP1_01                384 (-)  1.000  1.000  ttaCGTAA                           CRE-BP1
V$HNF3ALPHA_Q6             446 (+)  1.000  0.973  TGTTTgctctc                        HNF-3alpha
V$FOXP3_Q4                 589 (+)  0.859  0.831  tatgaGCTGTttcagat                  FOXP3
V$HNF1_Q6                  694 (-)  0.952  0.873  cttctgaaggAGTAActc                 HNF-1
V$COMP1_01                 888 (+)  1.000  0.859  taggaaGATTGgccacatcagctt           COMP1
V$CDPCR1_01                979 (-)  0.896  0.914  acgaTCTATg                         CDP CR1
*/

void check_biobase_matches(BiobaseParams const & params)
{
	ensure_biobase_params_built();

	cout << "******* check_biobase_matches(): Testing with " << params.name.c_str() << " parameters" << endl;
	//progress_timer timer;

	boost::io::ios_precision_saver saver(cout);
	cout.precision(3);

	//build the pssms
	const Pssm matrix_pssm = *params.pssm;
	const Pssm iupac_pssm = make_pssm_from_iupac(params.iupac->begin(), params.iupac->end());

#ifdef VERBOSE_CHECKING
	cout
		<< "Matrix pssm:" << endl
		<< matrix_pssm
		<< endl;
	cout
		<< "IUPAC pssm:" << endl
		<< iupac_pssm
		<< endl;
#endif

	//match to compute the score
	Hit pssm_match (-1.0, 0);
	Hit iupac_match (-1.0, 0);
	if (params.run_over_range)
	{
		pssm_match =
			score_sequence(
				matrix_pssm,
				params.seq_begin,
				params.seq_end,
				params.match_complement);
		iupac_match =
			score_sequence(
				iupac_pssm,
				params.seq_begin,
				params.seq_end,
				params.match_complement);
	}
	else
	{
		assert(params.seq_end - params.seq_begin == int( matrix_pssm.size() ) );
		pssm_match.score = matrix_pssm.score(params.seq_begin, params.match_complement);
		iupac_match.score = iupac_pssm.score(params.seq_begin, params.match_complement);
	}

	//compare against expected
    BOOST_CHECK_CLOSE(params.target_match.score, pssm_match.score, 1.0); //only compare to 1% (we have 3 figure accuracy)
	BOOST_CHECK_CLOSE(iupac_match.score, pssm_match.score, params.iupac_tolerance); //IUPAC should be close to PSSM match
    BOOST_CHECK_EQUAL(params.target_match.position, pssm_match.position); //position of the pssm match
    BOOST_CHECK_EQUAL(params.target_match.complement, pssm_match.complement); //was it on the complementary strand
}

void register_biobase_matches_tests(boost::unit_test::test_suite * test)
{
	ensure_biobase_params_built();

	test->add(BOOST_PARAM_TEST_CASE(&check_biobase_matches, biobase_params.begin(), biobase_params.end()), 0);

	//superceded
	//test->add(BOOST_TEST_CASE(&check_ott_normaliser_cache), 0);
	//test->add(BOOST_TEST_CASE(&check_ott_normalisation), 0);
	//test->add(BOOST_TEST_CASE(&check_weak_pssms), 0);
}