/*  _______________________________________________________________________

    DAKOTA: Design Analysis Kit for Optimization and Terascale Applications
    Copyright 2014 Sandia Corporation.
    This software is distributed under the GNU Lesser General Public License.
    For more information, see the README file in the top Dakota directory.
    _______________________________________________________________________ */

//- Class:        SurfpackApproximation
//- Description:  Class implementation of Surfpack response surface
//-
//- Owner:        Brian Adams

#include <stdexcept>
#include <typeinfo>

#include "SurfpackApproximation.hpp"
#include "SharedSurfpackApproxData.hpp"
#include "ProblemDescDB.hpp"
#include "DakotaVariables.hpp"
#include "dakota_data_io.hpp"

// Headers from Surfpack
#include "SurfData.h"
// for Hessian data
#include "SurfpackMatrix.h"
#include "ModelFactory.h"
#include "ModelFitness.h"
#include "surfaces/SurfpackModel.h"
#include "SurfpackInterface.h"

#include <algorithm>
#include <boost/math/special_functions/round.hpp>

namespace Dakota {

using surfpack::toString;
using surfpack::fromVec;

/** Initialize the embedded Surfpack surface object and configure it
    using the specifications from the input file.  Data for the
    surface is created later. */
SurfpackApproximation::
SurfpackApproximation(const ProblemDescDB& problem_db,
		      const SharedApproxData& shared_data,
                      const String& approx_label):
  Approximation(BaseConstructor(), problem_db, shared_data, approx_label), //surface(NULL),
  surfData(NULL), model(NULL), factory(NULL)
  //sharedDataRep((SharedSurfpackApproxData*)shared_data.data_rep())
{
  SharedSurfpackApproxData* shared_surf_data_rep
    = (SharedSurfpackApproxData*)sharedDataRep;

  ParamMap args;

  args["verbosity"] = toString<short>(sharedDataRep->outputLevel);
  args["ndims"] = toString<size_t>(sharedDataRep->numVars);

  // For now, not exposing Surfpack random seed in the DAKOTA UI;
  // instead fixing at an arbitrary value (treated as int in Surfpack)
  args["seed"] = "8147";

  // For Polynomial surface fits
  if (sharedDataRep->approxType == "global_polynomial") {
    args["type"] = "polynomial";
    args["order"] = toString<unsigned short>(shared_surf_data_rep->approxOrder);
    // TO DO: activate derivative-based regression
  }

  // For Kriging surface fits
  else if (sharedDataRep->approxType == "global_kriging") {

    args["type"]  = "kriging";
    args["order"] = toString<unsigned short>(shared_surf_data_rep->approxOrder);
    args["reduced_polynomial"] =
      (problem_db.get_string("model.surrogate.trend_order") == "quadratic") ?
      toString<bool>(false) : toString<bool>(true);

    // activate derivative information if available
    unsigned short surfpack_derivative_order = 0;
    short bdo = sharedDataRep->buildDataOrder;
    if (bdo & 2)
      surfpack_derivative_order = 1;
    if (bdo & 4) {
      if (bdo & 2)
	surfpack_derivative_order = 2;
      else {
	Cerr << "\nError (global_kriging): Hessian information only used "
	     << "if gradients present.\nbuildDataOrder = " << bdo << std::endl;
	abort_handler(-1);
      }
    }
    args["derivative_order"] =
      toString<unsigned short>(surfpack_derivative_order);

    // optimization options are none | sample | local | global (default)
    args["optimization_method"] = "global";
    const String& optimization_method =
      problem_db.get_string("model.surrogate.kriging_opt_method");
    if (!optimization_method.empty()) {
      if (optimization_method == "none" || optimization_method == "sampling"
	  || optimization_method == "local"
	  || optimization_method == "global")
	args["optimization_method"] = optimization_method;
      else {
	Cerr << "Error (global_kriging): invalid optimization method "
	     << optimization_method << "; valid options are "
	     << "none, sampling, local, or global" << std::endl;
	abort_handler(-1);
      }
    }

    short max_trials
      = problem_db.get_short("model.surrogate.kriging_max_trials");
    if (max_trials > 0)
      args["max_trials"] = toString<short>(max_trials);

    // NIDR support for RealArray (aka std::vector) would eliminate xtra copy!
    // old parameters
    Real nugget = problem_db.get_real("model.surrogate.nugget");
    short find_nugget = problem_db.get_short("model.surrogate.find_nugget");
    if (nugget > 0) {
      args["nugget"] = toString<Real>(nugget);
    }
    else {
      if (find_nugget > 0) {
	if (find_nugget == 1)
	  args["find_nugget"] = toString<bool>(false);
	else if (find_nugget == 2)
	  args["find_nugget"] = toString<bool>(true);
	else {
	  Cerr << " find_nugget must be 1 or 2" << '\n';
	  abort_handler(-1);
	}
      }
    }

    const RealVector& correlation_rv
      = problem_db.get_rv("model.surrogate.kriging_correlations");
    if (!correlation_rv.empty()) {
      RealArray correlation_ra; //std::vector<double>
      copy_data(correlation_rv, correlation_ra);
      args["correlation_lengths"] = fromVec<Real>(correlation_ra);
      args["optimization_method"] = "none";
    }

    /*
    const RealVector& max_correlations_rv
      = problem_db.get_rv("model.surrogate.kriging_max_correlations");
    if (!max_correlations_rv.empty()) {
      RealArray max_correlation_ra; //std::vector<double>
      copy_data(max_correlations_rv, max_correlation_ra);
      args["max_correlations"] = fromVec<Real>(max_correlation_ra);
    }

    const RealVector& min_correlations_rv
      = problem_db.get_rv("model.surrogate.kriging_min_correlations");
    if (!min_correlations_rv.empty()) {
      RealArray min_correlation_ra; //std::vector<double>
      copy_data(min_correlations_rv, min_correlation_ra);
      args["min_correlations"] = fromVec<Real>(min_correlation_ra);
    }

    // bounds set at run time within build()
    if (!sharedDataRep->approxCLowerBnds.empty()) {
      RealArray alb_ra;
      copy_data(sharedDataRep->approxCLowerBnds, alb_ra);
      args["lower_bounds"] = fromVec<Real>(alb_ra);
    }
    if (!sharedDataRep->approxCUpperBnds.empty()) {
      RealArray aub_ra;
      copy_data(sharedDataRep->approxCUpperBnds, aub_ra);
      args["upper_bounds"] = fromVec<Real>(aub_ra);
    }
    */

    // unused for now
    IntVector dimension_groups_iv;
    if (!dimension_groups_iv.empty()) {
      IntArray dg_ra;
      copy_data(dimension_groups_iv, dg_ra);
      args["dimension_groups"] = fromVec<int>(dg_ra);
    }

  }

  // For ANN surface fits
  else if (sharedDataRep->approxType == "global_neural_network") {
    args["type"] = "ann";
    short random_weight
      = problem_db.get_short("model.surrogate.neural_network_random_weight");
    if (random_weight > 0) {
      args["random_weight"] = toString<short>(random_weight);
    }
    short nodes
      = problem_db.get_short("model.surrogate.neural_network_nodes");
    if (nodes > 0) {
      args["nodes"] = toString<short>(nodes);
    }
    const Real& range
      = problem_db.get_real("model.surrogate.neural_network_range");
    if (range > 0) {
      args["range"] = toString<Real>(range);
    }
  }

  //// For moving least squares
  else if (sharedDataRep->approxType == "global_moving_least_squares") {
    args["type"] = "mls";
    short weight
      = problem_db.get_short("model.surrogate.mls_weight_function");
    if (weight > 0) {
      args["weight"] = toString<short>(weight);
    }
    short order = problem_db.get_short("model.surrogate.polynomial_order");
    if (order > 0) {
      args["order"] = toString<short>(order);
    }
  }

  //// For radial basis function networks
  else if (sharedDataRep->approxType == "global_radial_basis") {
    args["type"] = "rbf";
    // mapping number bases to number of centers
    short bases = problem_db.get_short("model.surrogate.rbf_bases");
    if (bases > 0) {
      args["centers"] = toString<short>(bases);
    }
    short min_partition
      = problem_db.get_short("model.surrogate.rbf_min_partition");
    if (min_partition > 0) {
      args["min_partition"] = toString<short>(min_partition);
    }
    short max_subsets
      = problem_db.get_short("model.surrogate.rbf_max_subsets");
    if (max_subsets > 0) {
      args["max_iter"] = toString<short>(max_subsets);
    }
    // mapping max_pts to cvt_pts
    short max_pts = problem_db.get_short("model.surrogate.rbf_max_pts");
    if (max_pts > 0) {
      args["cvt_pts"] = toString<short>(max_pts);
    }
  }

  //// For Mars surface fits
  else if (sharedDataRep->approxType == "global_mars") {
    args["type"] = "mars";
    short max_bases = problem_db.get_short("model.surrogate.mars_max_bases");
    if (max_bases > 0) {
      args["max_bases"] = toString<short>(max_bases);
    }
    const String& interpolation
      = problem_db.get_string("model.surrogate.mars_interpolation");
    if (interpolation != "") {
      args["interpolation"] = interpolation;
    }
  }
  //Cout << "PARAMETERS: " << std::endl;
  //for (ParamMap::iterator itr = args.begin(); itr != args.end(); itr++)
  //   Cout << "     " << itr->first << ": " << itr->second << std::endl;

  factory = ModelFactory::createModelFactory(args);

  //catch(...) {
  //  Cout << "Exception caught in attempt to create Surface object"
  //       << std::endl;
  //  abort_handler(-1);
  //}

  // validate diagnostic settings (preliminary); TODO: do more at
  // run time and move to both ctors
  bool err_found = false;
  const StringArray& diag_set = shared_surf_data_rep->diagnosticSet;
  if (!diag_set.empty()) {
    std::set<std::string> valid_metrics;
    valid_metrics.insert("sum_squared");
    valid_metrics.insert("mean_squared");
    valid_metrics.insert("root_mean_squared");
    valid_metrics.insert("sum_abs");
    valid_metrics.insert("mean_abs");
    valid_metrics.insert("max_abs");
    valid_metrics.insert("rsquared");

    int num_diag = diag_set.size();
    for (int j = 0; j < num_diag; ++j)
      if (valid_metrics.find(diag_set[j]) == valid_metrics.end()) {
	Cerr << "Error: surrogate metric '" << diag_set[j]
	     << "' is not available in Dakota.\n";
	err_found = true;
      }
    if (err_found) {
      Cerr << "Valid surrogate metrics include:\n  ";
      std::copy(valid_metrics.begin(), valid_metrics.end(),
		std::ostream_iterator<std::string>(Cerr, " "));
      Cerr << std::endl;
    }
  }
  if (shared_surf_data_rep->crossValidateFlag) {
    if (shared_surf_data_rep->numFolds > 0 &&
	shared_surf_data_rep->numFolds < 2) {
      Cerr << "Error: cross_validation folds must be 2 or greater."
	   << std::endl;
      err_found = true;
    }
    if (shared_surf_data_rep->percentFold < 0.0 ||
	shared_surf_data_rep->percentFold > 0.5) {
      Cerr << "Error: cross_validation percent must be between 0.0 and 0.5"
	   << std::endl;
      err_found = true;
    }
  }
  if (err_found)
    abort_handler(-1);
}

/// On-the-fly constructor which uses mostly Surfpack model defaults
SurfpackApproximation::
SurfpackApproximation(const SharedApproxData& shared_data):
  Approximation(NoDBBaseConstructor(), shared_data),
  surfData(NULL), model(NULL), factory(NULL)
  //sharedDataRep((SharedSurfpackApproxData*)shared_data.data_rep())
{
  SharedSurfpackApproxData* shared_surf_data_rep
    = (SharedSurfpackApproxData*)sharedDataRep;

  ParamMap args;
  args["verbosity"] = toString<short>(sharedDataRep->outputLevel);
  args["ndims"]     = toString<size_t>(sharedDataRep->numVars);
  args["seed"]      = "8147";

  if (sharedDataRep->approxType == "global_polynomial") {
    args["type"] = "polynomial";
    args["order"] = toString<unsigned short>(shared_surf_data_rep->approxOrder);
  }
  else if (sharedDataRep->approxType == "global_kriging") {

    args["type"] = "kriging";
    args["order"] = toString<unsigned short>(shared_surf_data_rep->approxOrder);
    args["reduced_polynomial"] = toString<bool>(true);
    /*
    // bounds set at run time within build():
    if (!sharedDataRep->approxCLowerBnds.empty()) {
      RealArray alb_ra;
      copy_data(sharedDataRep->approxCLowerBnds, alb_ra);
      args["lower_bounds"] = fromVec<Real>(alb_ra);
    }
    if (!sharedDataRep->approxCUpperBnds.empty()) {
      RealArray aub_ra;
      copy_data(sharedDataRep->approxCUpperBnds, aub_ra);
      args["upper_bounds"] = fromVec<Real>(aub_ra);
    }
    */
    //size_t krig_max_trials=(2*num_vars+1)*(num_vars+1)*10;
    //size_t krig_max_trials=20*(2*num_vars+1)*
      //(1+num_vars+((num_vars+1)*num_vars)/2); //#der0 + #der1 + #der2
    //if(krig_max_trials>10000)
      //krig_max_trials=10000;
    //size_t krig_max_trials=1000;
    //args["max_trials"] = toString<size_t>(krig_max_trials);
    args["max_trials"] = toString<size_t>(5000);

    // activate derivative information if available
    unsigned short surfpack_derivative_order = 0;
    short bdo = sharedDataRep->buildDataOrder;
    if (bdo == 1)      surfpack_derivative_order = 0;
    else if (bdo == 3) surfpack_derivative_order = 1;
    else if (bdo == 7) surfpack_derivative_order = 2;
    else {
      Cerr << "\nError (global_kriging): Unsupported buildDataOrder = " << bdo
	   << std::endl;
      abort_handler(-1);
    }
    args["derivative_order"] =
      toString<unsigned short>(surfpack_derivative_order);

  }
  else if (sharedDataRep->approxType == "global_neural_network")
    args["type"] = "ann";
  else if (sharedDataRep->approxType == "global_moving_least_squares") {
    args["type"] = "mls";
    args["order"] = toString<unsigned short>(shared_surf_data_rep->approxOrder);
  }
  else if (sharedDataRep->approxType == "global_radial_basis")
    args["type"] = "rbf";
  else if (sharedDataRep->approxType == "global_mars")
    args["type"] = "mars";

  factory = ModelFactory::createModelFactory(args);
}

// Embedded Surfpack objects will need to be deleted
SurfpackApproximation::~SurfpackApproximation()
{
  delete surfData;
  delete model;
  delete factory;
}

int SurfpackApproximation::min_coefficients() const
{
  assert(factory);
  return factory->minPointsRequired();
}

int SurfpackApproximation::recommended_coefficients() const
{
  assert(factory);
  return factory->recommendedNumPoints();
}

void SurfpackApproximation::build(size_t index)
{
  // base class implementation checks data set against min required
  Approximation::build(index);

  // Surface object should have been created in constructor
  if (!factory) {
    Cerr << "Error: surface is null in SurfpackApproximation::build()."
	 << std::endl;
    abort_handler(-1);
  }

  SharedSurfpackApproxData* shared_surf_data_rep
    = (SharedSurfpackApproxData*)sharedDataRep;

  /// surfData will be deleted in dtor
  /// \todo Right now, we're completely deleting the old data and then
  /// recopying the current data into a SurfData object.  This was just
  /// the easiest way to arrive at a solution that would build and run.
  /// This function is frequently called from addPoint rebuild, however,
  /// and it's not good to go through this whole process every time one
  /// more data point is added.
  try {
    if (surfData) {
      delete surfData;
      surfData = NULL;
    }
    surfData = surrogates_to_surf_data();

    // set bounds at run time since they are updated by some methods (e.g., SBO)
    if (!sharedDataRep->approxCLowerBnds.empty() ||
	!sharedDataRep->approxDILowerBnds.empty() ||
	!sharedDataRep->approxDRLowerBnds.empty()) {
      RealArray lb;
      shared_surf_data_rep->merge_variable_arrays(
	sharedDataRep->approxCLowerBnds, sharedDataRep->approxDILowerBnds,
	sharedDataRep->approxDRLowerBnds, lb);
      factory->add("lower_bounds", fromVec<Real>(lb));
    }
    if (!sharedDataRep->approxCUpperBnds.empty() ||
	!sharedDataRep->approxDIUpperBnds.empty() ||
	!sharedDataRep->approxDRUpperBnds.empty()) {
      RealArray ub;
      shared_surf_data_rep->merge_variable_arrays(
	sharedDataRep->approxCUpperBnds, sharedDataRep->approxDIUpperBnds,
	sharedDataRep->approxDRUpperBnds, ub);
      factory->add("upper_bounds", fromVec<Real>(ub));
    }

    if (model) {
      delete model;
      model = NULL;
    }
    model = factory->Build(*surfData);
    // TO DO: extract coefficients array
  }
  catch (std::runtime_error& e) {
    Cerr << e.what() << std::endl;
    Cerr << typeid(e).name() << std::endl;
    abort_handler(-1);
  }
  catch (std::string& e) {
    Cerr << "Error: exception with no recourse caught trying to build model:\n"
	 << e << std::endl;
    abort_handler(-1);
  }
  catch (...) {
    Cerr << "Error: exception caught trying to build model" << std::endl;
    abort_handler(-1);
  }

/*  if (!shared_surf_data_rep->exportModelName.empty() &&
      SurfpackInterface::HasFeature("model_save")) {
    if (sharedDataRep->outputLevel >= VERBOSE_OUTPUT)
      Cout << "\nSaving surrogate model to file "
	   << shared_surf_data_rep->exportModelName << std::endl;
    SurfpackInterface::Save(model, shared_surf_data_rep->exportModelName);
  }*/
}

void SurfpackApproximation::export_model(const String& fn_label,
 					 const String& export_prefix,
                                         const unsigned short export_format)
{
  String without_extension;
  unsigned short formats;
  if(export_format) {
    without_extension = export_prefix + "." + fn_label;
    formats = export_format;
  } else {
    without_extension = sharedDataRep->modelExportPrefix + "." + approxLabel;
    formats = sharedDataRep->modelExportFormat;
  }
  //unsigned short formats = export_format;
  const bool &can_save = SurfpackInterface::HasFeature("model_save");
  // Saving to text archive
  if(formats & TEXT_ARCHIVE) {
    if(can_save) {
      String filename = without_extension + ".sps";
      SurfpackInterface::Save(model,filename);
    } else
        Cerr << "\nRequested surrogate export to text archive failed: "
		<< "Surfpack lacks support for model saving.\n";
  }
  // Saving to binary archive
  if(formats & BINARY_ARCHIVE) {
    if(can_save) {
      String filename = without_extension + ".bsps";
      SurfpackInterface::Save(model,filename);
    } else
        Cerr << "\nRequested surrogate export to binary archive failed: "
	        << "Surfpack lacks support for model saving.\n";
  }
  // Saving to algebraic file
  if(formats & ALGEBRAIC_FILE) {
    String filename = without_extension + ".alg";
    std::ofstream af(filename.c_str(),std::ofstream::out);
    af << "Model for response " << fn_label << ":\n" << model->asString();
    af.close();
    Cout << "Model saved in algebraic format to file '" << filename << "'.\n";
  }
  // Writing in algebraic format to screen
  if(formats & ALGEBRAIC_CONSOLE) {
    Cout << "\nModel for response " << fn_label << ":\n";
    Cout << model->asString();
  }
}

Real SurfpackApproximation::value(const Variables& vars)
{
  //static int times_called = 0;
  if (!model) {
    Cerr << "Error: surface is null in SurfpackApproximation::value()"
	 << std::endl;
    abort_handler(-1);
  }

  RealArray x_array;
  ((SharedSurfpackApproxData*)sharedDataRep)->vars_to_realarray(vars, x_array);
  return (*model)(x_array);
}

const RealVector& SurfpackApproximation::gradient(const Variables& vars)
{
  approxGradient.sizeUninitialized(vars.cv());
  try {
    RealArray x_array;
    ((SharedSurfpackApproxData*)sharedDataRep)
      ->vars_to_realarray(vars, x_array);
    VecDbl local_grad = model->gradient(x_array);
    for (unsigned i = 0; i < surfData->xSize(); i++)
      approxGradient[i] = local_grad[i];
  }
  catch (...) {
    Cerr << "Error: gradient() not available for this approximation type."
	 << std::endl;
    abort_handler(-1);
  }
  return approxGradient;
}

const RealSymMatrix& SurfpackApproximation::hessian(const Variables& vars)
{
  size_t num_cv = vars.cv();
  approxHessian.reshape(num_cv);
  try {
    if (sharedDataRep->approxType == "global_moving_least_squares") {
      Cerr << "Have not implemented analytical hessians in this surfpack class"
	   << std::endl;
      abort_handler(-1);
    }
    RealArray x_array;
    ((SharedSurfpackApproxData*)sharedDataRep)
      ->vars_to_realarray(vars, x_array);
    MtxDbl sm = model->hessian(x_array);
    ///\todo Make this acceptably efficient
    for (size_t i = 0; i < num_cv; i++)
      for(size_t j = 0; j < num_cv; j++)
        approxHessian(i,j) = sm(i,j);
  }
  catch (...) {
    Cerr << "Error: hessian() not available for this approximation type."
	 << std::endl;
    abort_handler(-1);
  }
  return approxHessian;
}

Real SurfpackApproximation::prediction_variance(const Variables& vars)
{
  try {
    RealArray x_array;
    ((SharedSurfpackApproxData*)sharedDataRep)
      ->vars_to_realarray(vars, x_array);
    return model->variance(x_array);
  }
  catch (...) {
    Cerr << "Error: prediction_variance() not available for this "
	 << "approximation type." << std::endl;
    abort_handler(-1);
  }
}

Real SurfpackApproximation::value(const RealVector& c_vars)
{
    //static int times_called = 0;
    if (!model) {
        Cerr << "Error: surface is null in SurfpackApproximation::value()"
        << std::endl;
        abort_handler(-1);
    }

    RealArray x_array;
    size_t num_vars = c_vars.length();
    for (size_t i = 0; i < num_vars; i++) x_array.push_back(c_vars[i]);
    return (*model)(x_array);
}

const RealVector& SurfpackApproximation::gradient(const RealVector& c_vars)
{
    approxGradient.sizeUninitialized(c_vars.length());
    try {
        RealArray x_array;
        size_t num_vars = c_vars.length();
        for (size_t i = 0; i < num_vars; i++) x_array.push_back(c_vars[i]);

        VecDbl local_grad = model->gradient(x_array);
        for (unsigned i = 0; i < surfData->xSize(); i++)
            approxGradient[i] = local_grad[i];
    }
    catch (...) {
        Cerr << "Error: gradient() not available for this approximation type."
        << std::endl;
        abort_handler(-1);
    }
    return approxGradient;
}

const RealSymMatrix& SurfpackApproximation::hessian(const RealVector& c_vars)
{
    size_t num_cv = c_vars.length();
    approxHessian.reshape(num_cv);
    try {
        if (sharedDataRep->approxType == "global_moving_least_squares") {
            Cerr << "Have not implemented analytical hessians in this surfpack class"
            << std::endl;
            abort_handler(-1);
        }
        RealArray x_array;
        for (size_t i = 0; i < num_cv; i++) x_array.push_back(c_vars[i]);

        MtxDbl sm = model->hessian(x_array);
        ///\todo Make this acceptably efficient
        for (size_t i = 0; i < num_cv; i++)
            for(size_t j = 0; j < num_cv; j++)
                approxHessian(i,j) = sm(i,j);
    }
    catch (...) {
        Cerr << "Error: hessian() not available for this approximation type."
        << std::endl;
        abort_handler(-1);
    }
    return approxHessian;
}

Real SurfpackApproximation::prediction_variance(const RealVector& c_vars)
{
    try {
        RealArray x_array;
        size_t num_vars = c_vars.length();
        for (size_t i = 0; i < num_vars; i++) x_array.push_back(c_vars[i]);
        return model->variance(x_array);
    }
    catch (...) {
        Cerr << "Error: prediction_variance() not available for this "
        << "approximation type." << std::endl;
        abort_handler(-1);
    }
}

Real SurfpackApproximation::diagnostic(const String& metric_type)
{
  if (!model) {
    Cerr << "Error: surface is null in SurfpackApproximation::diagnostic()"
         << std::endl;
    abort_handler(-1);
  }

  return diagnostic(metric_type, *model, *surfData);
}

Real SurfpackApproximation::diagnostic(const String& metric_type,
				       const SurfpackModel& sp_model,
				       const SurfData& s_data)
{
  Real approx_diag;
  try {
    ModelFitness* SS_fitness = ModelFitness::Create(metric_type);
    approx_diag = (*SS_fitness)(sp_model, s_data);
    delete SS_fitness;
  }
  catch (const std::string& msg) {
    Cerr << "Error evaluating surrogate metric: " << msg << std::endl;
    abort_handler(-1);
  }

  Cout << std::setw(20) << metric_type << std::setw(20) << approx_diag << '\n';
  return approx_diag;
}

void SurfpackApproximation::primary_diagnostics(int fn_index)
{
  String func_description = approxLabel.empty() ?
    "function " + boost::lexical_cast<std::string>(fn_index+1) : approxLabel;
  SharedSurfpackApproxData* shared_surf_data_rep
    = (SharedSurfpackApproxData*)sharedDataRep;
  const StringArray& diag_set = shared_surf_data_rep->diagnosticSet;
  if (diag_set.empty()) {
    // conditionally print default diagnostics
    if (sharedDataRep->outputLevel > NORMAL_OUTPUT) {
      Cout << "\nSurrogate quality metrics for " << func_description << ":\n";
      diagnostic("root_mean_squared");
      diagnostic("mean_abs");
      diagnostic("rsquared");
    }
  }
  else {
    Cout << "\nSurrogate quality metrics for " << func_description << ":\n";
    int num_diag = diag_set.size();
    for (int j = 0; j < num_diag; ++j)
      diagnostic(diag_set[j]);

    // BMA TODO: at runtime verify (though Surfpack will too)
    //  * 1/N <= percentFold <= 0.5
    //  * 2 <= numFolds <= N
    if (shared_surf_data_rep->crossValidateFlag) {
      int num_folds = shared_surf_data_rep->numFolds;
      // if no folds, try percent, otherwise set default = 10
      if (num_folds == 0) {
        if (shared_surf_data_rep->percentFold > 0.0) {
          num_folds = boost::math::iround(1./shared_surf_data_rep->percentFold);
          if (sharedDataRep->outputLevel >= DEBUG_OUTPUT)
            Cout << "Info: cross_validate num_folds = " << num_folds
                 << " calculated from specified percent = "
                 << shared_surf_data_rep->percentFold << "." << std::endl;
        }
        else {
          num_folds = 10;
          if (sharedDataRep->outputLevel >= DEBUG_OUTPUT)
            Cout << "Info: default num_folds = " << num_folds << " used."
                 << std::endl;
        }
      }

      Cout << "\nSurrogate quality metrics (" << num_folds << "-fold CV) for "
           << func_description << ":\n";
      RealArray cv_metrics = cv_diagnostic(diag_set, num_folds);
      //CrossValidationFitness CV_fitness(num_folds);
      //VecDbl cv_metrics;
      //CV_fitness.eval_metrics(cv_metrics, *model, *surfData, diag_set);

      for (int j = 0; j < num_diag; ++j) {
        const String& metric_type = diag_set[j];
        if (metric_type == "rsquared")
          Cout << std::setw(20) << metric_type
               << std::setw(20) << std::numeric_limits<Real>::quiet_NaN()
               << "  (n/a for cross-validation)"
               << std::endl;
        else
          Cout << std::setw(20) << metric_type << std::setw(20) << cv_metrics[j]
               << std::endl;
      }

    }
    if (shared_surf_data_rep->pressFlag) {
      Cout << "\nSurrogate quality metrics (PRESS/leave-one-out) for "
           << func_description << ":\n";

      // perform press as CV with N folds
      RealArray cv_metrics = cv_diagnostic(diag_set, surfData->size());
      //CrossValidationFitness CV_fitness(surfData->size());
      //VecDbl cv_metrics;
      //CV_fitness.eval_metrics(cv_metrics, *model, *surfData, diag_set);

      for (int j = 0; j < num_diag; ++j) {
        const String& metric_type = diag_set[j];
        if (metric_type == "rsquared")
          Cout << std::setw(20) << metric_type
               << std::setw(20) << std::numeric_limits<Real>::quiet_NaN()
               << "  (n/a for PRESS)" << std::endl;
        else
          Cout << std::setw(20) << metric_type << std::setw(20) << cv_metrics[j]
               << std::endl;
      }

    }
  }
}

void SurfpackApproximation::
challenge_diagnostics(int fn_index, const RealMatrix& challenge_points,
                      const RealVector& challenge_responses)
{
  if (!model) {
    Cerr << "Error: surface is null in SurfpackApproximation::diagnostic()"
	 << std::endl;
    abort_handler(-1);
  }

  String func_description = approxLabel.empty() ?
    "function " + boost::lexical_cast<std::string>(fn_index+1) : approxLabel;

  // copy
  StringArray diag_set =
    ((SharedSurfpackApproxData*)sharedDataRep)->diagnosticSet;
  if (diag_set.empty()) {
    // conditionally print default diagnostics
    if (sharedDataRep->outputLevel > NORMAL_OUTPUT) {
      Cout << "\nSurrogate quality metrics (challenge data) for "
	   << func_description << ":\n";
      diag_set.push_back("root_mean_squared");
      diag_set.push_back("mean_abs");
      diag_set.push_back("rsquared");
      challenge_diagnostic(diag_set, challenge_points, challenge_responses);
    }
  }
  else {
    Cout << "\nSurrogate quality metrics (challenge data) for "
	 << func_description << ":\n";
    challenge_diagnostic(diag_set, challenge_points, challenge_responses);
  }

}

RealArray SurfpackApproximation::cv_diagnostic(const StringArray& metric_types,
                                               unsigned num_folds) {
  CrossValidationFitness CV_fitness(num_folds);
  VecDbl cv_metrics;
  try {
    CV_fitness.eval_metrics(cv_metrics, *model, *surfData, metric_types);
  } catch(String cv_error) {
    Cerr << "Error: Exception caught while computing CV score:\n" << cv_error << std::endl;
    cv_metrics.resize(metric_types.size());
    std::fill(cv_metrics.begin(), cv_metrics.end(), std::numeric_limits<Real>::quiet_NaN());
  }
  return cv_metrics;
}

RealArray SurfpackApproximation::challenge_diagnostic(const StringArray& metric_types,
			    const RealMatrix& challenge_points,
                            const RealVector& challenge_responses) {
  // JAS: painful but probably unavoidable data copy on every call.
  SurfData chal_data;
  RealArray chal_metrics;
  size_t num_v = sharedDataRep->numVars;
  for (size_t row=0; row<challenge_points.numRows(); ++row) {
    RealArray x(num_v);
    for (size_t col=0; col<num_v; ++col)
      x[col] = challenge_points[col][row];
    Real f = challenge_responses[row];
    chal_data.addPoint(SurfPoint(x, f));
  }
  for (int j = 0; j < metric_types.size(); ++j)
    chal_metrics.push_back(diagnostic(metric_types[j], *model, chal_data));
  return chal_metrics;
}

/** Copy the data stored in Dakota-style SurrogateData into
    Surfpack-style SurfPoint and SurfData objects. */
SurfData* SurfpackApproximation::surrogates_to_surf_data()
{
  SurfData* surf_data = new SurfData();

  // screen approximation data for failures
  approxData.data_checks();

  // some surrogates, e.g., global_polynomials and kriging, treat the anchor
  // point specially as a constraint; other treat as a regular data point
  SharedSurfpackApproxData* shared_surf_data_rep
    = (SharedSurfpackApproxData*)sharedDataRep;
  if (approxData.anchor()) {
    if (factory->supports_constraints())
      add_anchor_to_surfdata(*surf_data);
    else
      shared_surf_data_rep->
	add_sd_to_surfdata(approxData.anchor_variables(),
			   approxData.anchor_response(),
			   approxData.failed_anchor_data(), *surf_data);
  }
  // add the remaining surrogate data points
  if (sharedDataRep->outputLevel >= DEBUG_OUTPUT)
    Cout << "Requested build data order is " << sharedDataRep->buildDataOrder
	 << '\n';
  size_t i, num_data_pts = approxData.points();
  const Pecos::SDVArray& sdv_array = approxData.variables_data();
  const Pecos::SDRArray& sdr_array = approxData.response_data();
  const Pecos::SizetShortMap& failed_resp = approxData.failed_response_data();
  Pecos::SizetShortMap::const_iterator fit = failed_resp.begin();
  for (i=0; i<num_data_pts; ++i) {
    short fail_code = 0;
    if (fit != failed_resp.end() && fit->first == i)
      { fail_code = fit->second; ++fit; }
    shared_surf_data_rep->add_sd_to_surfdata(sdv_array[i], sdr_array[i],
					     fail_code, *surf_data);
  }

  return surf_data;
}

/** If there is an anchor point, add an equality constraint for its response
    value.  Also add constraints for gradient and hessian, if applicable. */
void SurfpackApproximation::add_anchor_to_surfdata(SurfData& surf_data)
{
  // coarse-grained fault tolerance for now: any failure qualifies for omission
  if (approxData.failed_anchor_data())
    return;

  // Surfpack's RealArray is std::vector<double>
  RealArray x;
  Real f;
  RealArray gradient;
  SurfpackMatrix<Real> hessian;

  // Print out the anchor continuous variables
  SharedSurfpackApproxData* shared_surf_data_rep
    = (SharedSurfpackApproxData*)sharedDataRep;
  shared_surf_data_rep->sdv_to_realarray(approxData.anchor_variables(), x);
  if (sharedDataRep->outputLevel > NORMAL_OUTPUT)
    Cout << "Anchor point vars\n" << x;

  // At a minimum, there should be a response value
  unsigned short anchor_data_order = 1;
  f = approxData.anchor_function();
  if (sharedDataRep->outputLevel > NORMAL_OUTPUT)
    Cout << "Anchor response: " << f << '\n';

  // Check for gradient in anchor point
  const RealVector& anchor_grad = approxData.anchor_gradient();
  if (!anchor_grad.empty()) {
    anchor_data_order |= 2;
    copy_data(anchor_grad, gradient);
    if (sharedDataRep->outputLevel > NORMAL_OUTPUT) {
      Cout << "Anchor gradient:\n";
      write_data(Cout, anchor_grad);
    }
  }

  // Check for hessian in anchor point
  const RealSymMatrix& anchor_hess = approxData.anchor_hessian();
  if (!anchor_hess.empty()) {
    anchor_data_order |= 4;
    shared_surf_data_rep->copy_matrix(anchor_hess, hessian);
    if (sharedDataRep->outputLevel > NORMAL_OUTPUT) {
      Cout << "Anchor hessian:\n";
      write_data(Cout, anchor_hess, false, true, true);
    }
  }

  if (sharedDataRep->outputLevel > NORMAL_OUTPUT)
    Cout << "Requested constraint data order is " << anchor_data_order
	 << '\n';

  // for now only allow builds from exactly 1, 3=1+2, or 7=1+2+4; use
  // different set functions so the SurfPoint data remains empty if
  // not present
  switch (anchor_data_order) {

  case 1:
    surf_data.setConstraintPoint(SurfPoint(x, f));
    break;

  case 3:
    surf_data.setConstraintPoint(SurfPoint(x, f, gradient));
    break;

  case 7:
    surf_data.setConstraintPoint(SurfPoint(x, f, gradient, hessian));
    break;

  default:
    Cerr << "\nError (SurfpackApproximation): derivative data may only be used"
	 << "if all\nlower-order information is also present. Specified "
	 << "anchor_data_order is " << anchor_data_order << "."  << std::endl;
    abort_handler(-1);
    break;

  }

}

} // namespace Dakota


// File: finite_grad_check.cc
// Author: Karl Moritz Hermann (mail@karlmoritz.com)
// Created: 01-01-2013
// Last Update: Thu 03 Oct 2013 11:44:36 AM BST

// STL
#include <iostream>
#include <cmath>

// Boost
#include <boost/program_options/variables_map.hpp>
#include <boost/program_options/parsers.hpp>

#include "shared_defs.h"

// L-BFGS
#include <lbfgs.h>

// Local
#include "finite_grad_check.h"
#include "train_update.h"
#include "recursive_autoencoder.h"

using namespace std;
namespace bpo = boost::program_options;

int finite_grad_check(Model &model)
{
  Real* vars = nullptr;
  int number_vars = 0;

  modvars<int> counts;
  model.rae.setIncrementalCounts(counts,vars,number_vars);

  WeightVectorType theta(vars,number_vars);

  Real* data1 = new Real[number_vars]();
  Real* data2 = new Real[number_vars]();
  WeightVectorType grad(data1,number_vars);
  WeightVectorType grad2(data2,number_vars);

  Real error1 = computeCostAndGrad(model,nullptr,data1,number_vars);

  modvars<Real> dists;
  Real dist = 0.0;

  Real delta = 1.0e-7;

  for (int i=0;i<number_vars;++i)
  {
    theta[i] += delta;
    Real error2 = computeCostAndGrad(model,nullptr,data2,number_vars);
    Real xdev = (error2 - error1) / delta;
    theta[i] -= delta;
    if (i < counts.D)   { dists.D += abs(grad2[i] - xdev);   cout << "D   "; }
    else if (i < counts.U)  { dists.U += abs(grad2[i] - xdev);  cout << "U  "; }
    else if (i < counts.V)  { dists.V += abs(grad2[i] - xdev);  cout << "V  "; }
    else if (i < counts.W)  { dists.W += abs(grad2[i] - xdev);  cout << "W  "; }
    else if (i < counts.A)  { dists.A += abs(grad2[i] - xdev);  cout << "A  "; }
    else if (i < counts.Wd)  { dists.Wd += abs(grad2[i] - xdev);  cout << "Wd  "; }
    else if (i < counts.Wdr) { dists.Wdr += abs(grad2[i] - xdev); cout << "Wdr "; }
    else if (i < counts.Bd)  { dists.Bd += abs(grad2[i] - xdev);  cout << "Bd  "; }
    else if (i < counts.Bdr) { dists.Bdr += abs(grad2[i] - xdev); cout << "Bdr "; }
    else if (i < counts.Wf)  { dists.Wf += abs(grad2[i] - xdev);  cout << "Wf  "; }
    else if (i < counts.Wl)  { dists.Wl += abs(grad2[i] - xdev);  cout << "Wl  "; }
    else if (i < counts.Bl)  { dists.Bl += abs(grad2[i] - xdev);  cout << "Bl  "; }

    //template<> void modvars<int>::init() { D = 0; U = 0; V = 0; W = 0; A = 0; Wd = 0; Wdr = 0; Bd = 0; Bdr = 0; Wf = 0; Wl = 0; Bl = 0; alpha_rae = 0; alpha_lbl = 0; }
    cout << i << ": " << grad2[i] << " vs " << (xdev) << "   " << error2 << " - " << error1 << "[" << theta[i] << "]" << endl;

    dist += abs(grad2[i] - xdev);
  }

  cout << "total: " << dist << " D/U/V/W/A/Wd/Wdr/Bd/Bdr/Wf/Wl/Bl " << endl;
  cout << dists.D << " ";
  cout << dists.U << " ";
  cout << dists.V << " ";
  cout << dists.W << " ";
  cout << dists.A << " ";
  cout << dists.Wd << " " << dists.Wdr << " " << dists.Bd << " " << dists.Bdr << " ";
  cout << dists.Wf << " ";
  cout << dists.Wl << " " << dists.Bl << endl;
  // Don't care about what's next
  assert(false);

  return 0;
}


//  Copyright (c) 2011-2013 Thomas Heller
//
//  SPDX-License-Identifier: BSL-1.0
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#if !defined(JACOBI_SMP_NO_pika)
#include <pika/init.hpp>
#endif

#include <pika/modules/program_options.hpp>

#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi_action.hpp>
#include <boost/spirit/include/qi_auxiliary.hpp>
#include <boost/spirit/include/qi_char.hpp>
#include <boost/spirit/include/qi_numeric.hpp>
#include <boost/spirit/include/qi_operator.hpp>
#include <boost/spirit/include/qi_parse.hpp>
#include <boost/spirit/include/qi_string.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>

#include <cstddef>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

using pika::program_options::options_description;
using pika::program_options::parse_command_line;
using pika::program_options::store;
using pika::program_options::value;
using pika::program_options::variables_map;

#include "jacobi_nonuniform.hpp"

namespace jacobi_smp {

    void jacobi_kernel_nonuniform(crs_matrix<double> const& A,
        std::vector<double>& dst, std::vector<double> const& src,
        std::vector<double> const& b, std::size_t row)
    {
        double result = b[row];
        double div = 1.0;
        const std::size_t begin = A.row_begin(row);
        const std::size_t end = A.row_end(row);

        for (std::size_t j = begin; j < end; ++j)
        {
            if (row == j)
                div = div / A.values[j];
            else
                result -= A.values[j] * src[A.indices[j]];
        }
        dst[row] = result * div;
    }
}    // namespace jacobi_smp

namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;

void init(
    jacobi_smp::crs_matrix<double>& M, std::size_t dim, std::size_t non_zeros)
{
    M.values.reserve(non_zeros);
    M.indices.reserve(non_zeros);
    M.rows.reserve(dim + 1);
    M.rows.push_back(0);
}

void add_entry(jacobi_smp::crs_matrix<double>& M, std::size_t& row,
    std::size_t& n, std::size_t j, std::size_t i, double v)
{
    M.values.push_back(v);
    M.indices.push_back(j);
    ++n;
    if (i != row)
    {
        row = i;
        M.rows.push_back(M.rows.back() + n);
        n = 0;
    }
}

int pika_main(variables_map& vm)
{
#if !defined(JACOBI_SMP_NO_pika)
    pika::scoped_finalize f;
#endif

    {
        std::size_t iterations = vm["iterations"].as<std::size_t>();
        std::size_t block_size = vm["block-size"].as<std::size_t>();
        std::string matrix = vm["matrix"].as<std::string>();
        std::string mode = vm["mode"].as<std::string>();

        jacobi_smp::crs_matrix<double> A;
        std::vector<double> x;
        std::vector<double> b;

        {
            std::ifstream file(matrix.c_str());
            file.unsetf(std::ios_base::skipws);
            using iterator = boost::spirit::istream_iterator;
            iterator begin(file);
            iterator end;

            std::size_t current_row = 0;
            std::size_t non_zero_row = 0;
            std::size_t dim = 0;
            std::size_t non_zero_entries = 0;

            qi::phrase_parse(begin, end,
                (qi::int_[phx::ref(dim) = qi::_1] >> qi::int_ >>
                    qi::int_[phx::ref(non_zero_entries) = qi::_1])[phx::bind(
                    init, phx::ref(A), dim, non_zero_entries)] >>
                    *(    // entry
                        (qi::int_ >> qi::int_ >> qi::double_)[phx::bind(
                            add_entry, phx::ref(A), phx::ref(current_row),
                            phx::ref(non_zero_row), qi::_1, qi::_2, qi::_3)]),
                qi::space |
                    (qi::lit("%") >> *(!qi::eol >> qi::char_) >> qi::eol));

            if (dim == 0)
            {
                std::cerr << "Parsed zero non zero values in matrix file "
                          << matrix << "\n";
                return 1;
            }

            std::cout << "A: " << dim << "x" << dim
                      << " number of non zeros: " << non_zero_entries << "\n";
        }
        if (vm.count("vector"))
        {
            std::string vector = vm["vector"].as<std::string>();
            std::ifstream file(vector.c_str());
            file.unsetf(std::ios_base::skipws);
            using iterator = boost::spirit::istream_iterator;
            iterator begin(file);
            iterator end;
            qi::phrase_parse(begin, end, *qi::double_, qi::ascii::space, b);
        }
        else
        {
            b = std::vector<double>(A.rows.size() - 1, 1.0);
        }
        std::cout << "b: " << b.size() << "\n";

        if (mode == "solve")
        {
            jacobi_smp::jacobi(A, b, iterations, block_size);
        }
        else if (mode == "statistics")
        {
            std::size_t min_per_row = (std::numeric_limits<std::size_t>::max)();
            std::size_t max_per_row = 0;
            double mean_per_row = 0.0;
            for (std::size_t r = 0; r < b.size(); ++r)
            {
                const std::size_t begin = A.row_begin(r);
                const std::size_t end = A.row_end(r);
                std::size_t n_row = end - begin;
                mean_per_row += double(n_row);
                if (n_row > max_per_row)
                {
                    max_per_row = n_row;
                }
                if (n_row < min_per_row)
                {
                    min_per_row = n_row;
                }
            }
            std::cout << "Matrix has " << A.values.size()
                      << " non zero entries\n";
            std::cout << "order: " << b.size() << "x" << b.size() << "\n";
            std::cout << "Entries per row:\n";
            std::cout << "\tmax " << max_per_row << "\n";
            std::cout << "\tmin " << min_per_row << "\n";
            std::cout << "\tmean " << mean_per_row / double(b.size()) << "\n";
            std::cout << "Density is: "
                      << double(A.values.size()) / double(b.size() * b.size())
                      << "\n";
        }
        else
        {
            std::cout << "Unknown mode " << mode << "\n";
            return 1;
        }
    }

    return 0;
}

int main(int argc, char** argv)
{
    options_description desc_cmd(
        "usage: " PIKA_APPLICATION_STRING " [options]");

    // clang-format off
    desc_cmd.add_options()
        ("iterations", value<std::size_t>()->default_value(1000),
         "Number of iterations")
        ("block-size", value<std::size_t>()->default_value(256),
         "Block size of the different chunks to calculate in parallel")
        ("matrix", value<std::string>(),
         "Filename of the input matrix (Matrix Market format)")
        ("vector", value<std::string>(), "Filename of the right hand side vector")
        ("mode", value<std::string>()->default_value("solve"),
        "Mode of the program, can be solve or statistics (default: solve)");
    // clang-format on

#if defined(JACOBI_SMP_NO_pika)
    variables_map vm;
    desc_cmd.add_options()("help", "This help message");
    store(parse_command_line(argc, argv, desc_cmd), vm);
    if (vm.count("help"))
    {
        std::cout << desc_cmd;
        return 1;
    }
    return pika_main(vm);
#else
    pika::init_params init_args;
    init_args.desc_cmdline = desc_cmd;

    return pika::init(pika_main, argc, argv, init_args);
#endif
}


// Copyright (C) 2011-2013 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.

#ifndef bempp_modified_aca_hpp
#define bempp_modified_aca_hpp

#include "../common/common.hpp"
#include "bempp/common/config_ahmed.hpp"

#ifdef WITH_AHMED

#ifdef __INTEL_COMPILER
#pragma warning(disable:381)
#endif

#include <apprx.h>

#ifdef __INTEL_COMPILER
#pragma warning(default:381)
#endif

#include <boost/scoped_array.hpp>
#include <limits>

namespace Bempp
{

// returns true if a pivot could be found (there were any nonapproximated
// rows or columns), false otherwise
template <class abs_T>
bool select_pivot_with_min_norm2(unsigned n, const abs_T* norm2,
                                 const int* apprx_times /* S or Z */,
                                 unsigned& pivot)
{
    pivot = n; // invalid value
    abs_T min_norm2 = std::numeric_limits<abs_T>::max();
    for (unsigned i = 0; i < n; ++i)
        if (apprx_times[i] >= 0 && norm2[i] < min_norm2) {
            min_norm2 = norm2[i];
            pivot = i;
        }
    return (pivot != n);
}

template<class T, class MATGEN_T>
bool ACAs(MATGEN_T& MatGen, unsigned b1, unsigned n1, unsigned b2, unsigned n2,
          double eps, unsigned kmax, unsigned i0, unsigned& k, T* &U, T* &V,
          const cluster* c1, const cluster* c2)
{
    typedef typename num_traits<T>::abs_type abs_T;
    unsigned no = 0; // number of crosses calculated so far
    unsigned klast;  // unused
    const unsigned maxit = std::min(n1, n2);

    abs_T scale = MatGen.scale(b1, n1, b2, n2, c1, c2); // set initial scale

    U = new T[(kmax+1)*n1]; // these arrays are expected to be
    V = new T[(kmax+1)*n2]; // deallocated by the caller
    assert(U != NULL && V != NULL);

    // arrays storing number of successful approximations of rows and columns
    boost::scoped_array<int> Z(new int[n1]), S(new int[n2]);
    for (unsigned l=0; l<n1; ++l)
        Z[l] = 0;
    for (unsigned l=0; l<n2; ++l)
        S[l] = 0;

    boost::scoped_array<T> orig_row(new T[n2]);
    boost::scoped_array<T> orig_col(new T[n1]);

    boost::scoped_array<abs_T> orig_row_norm2(new abs_T[n2]);
    boost::scoped_array<abs_T> orig_col_norm2(new abs_T[n1]);

    abs_T nrms2 = 0.; // squared Frobenius norm of U V^H

    const unsigned ROW = 0, COL = 1;
    const unsigned NORMAL = 0, FIRST_SHOT = 1, SECOND_SHOT = 2;
    unsigned mode = ROW;
    unsigned stage = NORMAL;

    k = 0;
    unsigned next_pivot = i0;

    // The relativeScale() function is expected to return an estimate of the
    // magnitude of the largest element in the block, relative to the magnitude
    // of the largest element in the whole matrix.
    // If the result is deemed small enough relative to eps, the block is taken to
    // be zero and its elements are not evaluate.
    // This can be useful in the approximation of strongly decaying kernels.
    // abs_T relscale = MatGen.relativeScale(b1, n1, b2, n2, c1, c2);
    // if (relscale < 1e-2 * eps)
    //     return true;

    do {
        ACA_status status;
        abs_T nrmlsk2; // product of squared norms of new columns of U and V
        bool retry_if_zero = (stage == NORMAL); // don't retry if shooting
        // compute a cross
        if (mode == ROW)
            status = ACA_row_step(
                        MatGen, b1, n1, b2, n2, klast, next_pivot, k, no,
                        Z.get(), S.get(), U, V, nrmlsk2, scale, c1, c2,
                        retry_if_zero, orig_row.get(), orig_col.get());
        else
            status = ACA_col_step(
                        MatGen, b1, n1, b2, n2, next_pivot, k, no,
                        Z.get(), S.get(), U, V, nrmlsk2, scale, c1, c2,
                        retry_if_zero, orig_row.get(), orig_col.get());
        // std::cout << "status = " << status << std::endl;

        bool stpcrit = false;
        if (status == ACA_STATUS_SUCCESS) {
            // check stopping criterion
            T sum = 0.;                            // update nrms2
            for (unsigned l=0; l<k; ++l)
                sum += blas::scpr(n1, U+l*n1, U+k*n1) * blas::scpr(n2, V+k*n2, V+l*n2);
            nrms2 += 2. * Re(sum) + nrmlsk2;

            stpcrit = (nrmlsk2 < eps * eps * nrms2);
            // adjust scale (estimated entry size of the next remainder)
            scale = sqrt(nrmlsk2/(n1*n2));
            // std::cout << "nrmlsk2: " << nrmlsk2 << ", nrms2: " << nrms2 << ", scale = " << scale
            //           << std::endl;

            ++k;
        } else if (status == ACA_STATUS_EARLY_EXIT)
            stpcrit = true;
        else {
            // in the last step no non-zero row/column could be found
            assert(status == ACA_STATUS_REMAINDER_IS_ZERO);
            return true;
        }

        if (stpcrit) {
            if (stage == SECOND_SHOT)
                return true;
            else if (stage == FIRST_SHOT) {
                if (mode == ROW) {
                    // select column pivot
                    bool found = select_pivot_with_min_norm2(
                                n2, orig_row_norm2.get(), S.get(), next_pivot);
                    if (!found) // no nonapproximated column could be found
                        return true;
                    mode = COL;
                } else {
                    // select row pivot
                    bool found = select_pivot_with_min_norm2(
                                n1, orig_col_norm2.get(), Z.get(), next_pivot);
                    if (!found) // no nonapproximated row could be found
                        return true;
                    mode = ROW;
                }
                stage = SECOND_SHOT;
            } else {
                assert(stage == NORMAL);
                for (unsigned i = 0; i < n2; ++i)
                    orig_row_norm2[i] = abs2(orig_row[i]);
                for (unsigned i = 0; i < n1; ++i)
                    orig_col_norm2[i] = abs2(orig_col[i]);
                if (mode == ROW) {
                    // select row pivot
                    bool found = select_pivot_with_min_norm2(
                                n1, orig_col_norm2.get(), Z.get(), next_pivot);
                    if (!found) // no nonapproximated row could be found
                        return true;
                }
                else {
                    // select column pivot
                    bool found = select_pivot_with_min_norm2(
                                n2, orig_row_norm2.get(), S.get(), next_pivot);
                    if (!found) // no nonapproximated column could be found
                        return true;
                }
                stage = FIRST_SHOT;
                // mode stays the same
            }
        } else
            stage = NORMAL;
            // mode stays the same

        // std::cout << "Stage: " << stage << ", mode: " << mode << "\n";
    } while (no < maxit && k < kmax);

    // std::cout << "Giving up" << std::endl;
    return false;
}

template<class T,class T1,class T2, class MATGEN_T>
void apprx_unsym_shooting(
        MATGEN_T& MatGen, mblock<T>* &mbl, bemblcluster<T1,T2>* bl,
        double eps, unsigned rankmax)
{
    apprx_unsym_generic(&ACAs<T, MATGEN_T>, MatGen, mbl, bl, eps, rankmax);
}

} // namespace Bempp

#endif // WITH_AHMED

#endif


#include "eigen_numpy.h"

#include <Eigen/Eigen>
#if EIGEN_VERSION_AT_LEAST(3, 3, 0)
#include <unsupported/Eigen/CXX11/Tensor>
#endif // EIGEN_VERSION_AT_LEAST(3, 3, 0)
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/arrayobject.h>

// These macros were renamed in NumPy 1.7.1.
#if !defined(NPY_ARRAY_C_CONTIGUOUS) && defined(NPY_C_CONTIGUOUS)
#define NPY_ARRAY_C_CONTIGUOUS NPY_C_CONTIGUOUS
#endif

#if !defined(NPY_ARRAY_ALIGNED) && defined(NPY_ALIGNED)
#define NPY_ARRAY_ALIGNED NPY_ALIGNED
#endif

namespace bp = boost::python;

using namespace Eigen;

template <typename SCALAR>
struct NumpyEquivalentType {};

template <> struct NumpyEquivalentType<double> {enum {type_code = NPY_DOUBLE};};
template <> struct NumpyEquivalentType<float> {enum {type_code = NPY_FLOAT};};
template <> struct NumpyEquivalentType<int> {enum {type_code = NPY_INT};};
template <> struct NumpyEquivalentType<unsigned short>{enum {type_code = NPY_USHORT};};
template <> struct NumpyEquivalentType<unsigned char>{enum {type_code = NPY_UBYTE};};
template <> struct NumpyEquivalentType<std::complex<double> > {enum {type_code = NPY_CDOUBLE};};

template <typename SourceType, typename DestType >
static void copy_array(const SourceType* source, DestType* dest,
                       const npy_int &nb_rows, const npy_int &nb_cols,
    const bool &isSourceTypeNumpy = false, const bool &isDestRowMajor = true,
    const bool& isSourceRowMajor = true,
    const npy_int &numpy_row_stride = 1, const npy_int &numpy_col_stride = 1)
{
  // determine source strides
  int row_stride = 1, col_stride = 1;
  if (isSourceTypeNumpy) {
    row_stride = numpy_row_stride;
    col_stride = numpy_col_stride;
  } else {
    if (isSourceRowMajor) {
      row_stride = nb_cols;
    } else {
      col_stride = nb_rows;
    }
  }

  if (isDestRowMajor) {
    for (int r=0; r<nb_rows; r++) {
      for (int c=0; c<nb_cols; c++) {
        *dest = source[r*row_stride + c*col_stride];
        dest++;
      }
    }
  } else {
    for (int c=0; c<nb_cols; c++) {
      for (int r=0; r<nb_rows; r++) {
        *dest = source[r*row_stride + c*col_stride];
        dest++;
      }
    }
  }
}

template<class MatType> // MatrixXf or MatrixXd
struct EigenMatrixToPython {
  static PyObject* convert(const MatType& mat) {
    npy_intp shape[2] = { mat.rows(), mat.cols() };
    PyArrayObject* python_array = (PyArrayObject*)PyArray_SimpleNew(
        2, shape, NumpyEquivalentType<typename MatType::Scalar>::type_code);

    copy_array(mat.data(),
               (typename MatType::Scalar*)PyArray_DATA(python_array),
               mat.rows(),
               mat.cols(),
               false,
               true,
               MatType::Flags & Eigen::RowMajorBit);
    return (PyObject*)python_array;
  }
};

template<typename MatType>
struct EigenMatrixFromPython {
  typedef typename MatType::Scalar T;

  EigenMatrixFromPython() {
    bp::converter::registry::push_back(&convertible,
                                       &construct,
                                       bp::type_id<MatType>());
  }

  static void* convertible(PyObject* obj_ptr) {
    PyArrayObject *array = reinterpret_cast<PyArrayObject*>(obj_ptr);
    if (!PyArray_Check(array)) {
      //LOG(ERROR) << "PyArray_Check failed";
      return 0;
    }
    if (PyArray_NDIM(array) > 2) {
      //LOG(ERROR) << "dim > 2";
      return 0;
    }
    if (PyArray_ObjectType(obj_ptr, 0) != NumpyEquivalentType<typename MatType::Scalar>::type_code) {
      //LOG(ERROR) << "types not compatible";
      return 0;
    }
    int flags = PyArray_FLAGS(array);
    if (!(flags & NPY_ARRAY_C_CONTIGUOUS)) {
      //LOG(ERROR) << "Contiguous C array required";
      return 0;
    }
    if (!(flags & NPY_ARRAY_ALIGNED)) {
      //LOG(ERROR) << "Aligned array required";
      return 0;
    }
    return obj_ptr;
  }

  static void construct(PyObject* obj_ptr,
                        bp::converter::rvalue_from_python_stage1_data* data) {
    const int R = MatType::RowsAtCompileTime;
    const int C = MatType::ColsAtCompileTime;

    using bp::extract;

    PyArrayObject *array = reinterpret_cast<PyArrayObject*>(obj_ptr);
    int ndims = PyArray_NDIM(array);
    npy_intp* dimensions = PyArray_DIMS(array);

    int dtype_size = (PyArray_DESCR(array))->elsize;
    int s1 = PyArray_STRIDE(array, 0);
    //CHECK_EQ(0, s1 % dtype_size);
    int s2 = 0;
    if (ndims > 1) {
      s2 = PyArray_STRIDE(array, 1);
      //CHECK_EQ(0, s2 % dtype_size);
    }

    int nrows = R;
    int ncols = C;
    if (ndims == 2) {
      if (R != Eigen::Dynamic) {
        //CHECK_EQ(R, array->dimensions[0]);
      } else {
        nrows = dimensions[0];
      }

      if (C != Eigen::Dynamic) {
        //CHECK_EQ(C, array->dimensions[1]);
      } else {
        ncols = dimensions[1];
      }
    } else {
      //CHECK_EQ(1, ndims);
      // Vector are a somehow special case because for Eigen, everything is
      // a 2D array with a dimension set to 1, but to numpy, vectors are 1D
      // arrays
      // So we could get a 1x4 array for a Vector4

      // For a vector, at least one of R, C must be 1
      //CHECK(R == 1 || C == 1);

      if (R == 1) {
        if (C != Eigen::Dynamic) {
          //CHECK_EQ(C, array->dimensions[0]);
        } else {
          ncols = dimensions[0];
        }
        // We have received a 1xC array and want to transform to VectorCd,
        // so we need to transpose
        // TODO: An alternative is to add wrappers for RowVector, but maybe
        // implicit transposition is more natural
        std::swap(s1, s2);
      } else {
        if (R != Eigen::Dynamic) {
          //CHECK_EQ(R, array->dimensions[0]);
        } else {
          nrows = dimensions[0];
        }
      }
    }

    T* raw_data = reinterpret_cast<T*>(PyArray_DATA(array));

    typedef Map<Matrix<T, Dynamic, Dynamic, RowMajor>, Aligned, Stride<Dynamic, Dynamic> > MapType;

    void* storage=((bp::converter::rvalue_from_python_storage<MatType>*)
                   (data))->storage.bytes;

    new (storage) MatType;
    MatType* emat = (MatType*)storage;
    // TODO: This is a (potentially) expensive copy operation. There should
    // be a better way
    *emat = MapType(raw_data, nrows, ncols,
                Stride<Dynamic, Dynamic>(s1/dtype_size, s2/dtype_size));
    data->convertible = storage;
  }
};

template<class TransformType> // MatrixXf or MatrixXd
struct EigenTransformToPython {
  static PyObject* convert(const TransformType& transform) {
      return EigenMatrixToPython<typename TransformType::MatrixType>::convert(transform.matrix());
  }
};

template<typename TransformType>
struct EigenTransformFromPython {
  EigenTransformFromPython() {
    bp::converter::registry::push_back(&convertible,
                                       &construct,
                                       bp::type_id<TransformType>());
  }

  static void* convertible(PyObject* obj_ptr) {
    return EigenMatrixFromPython<typename TransformType::MatrixType>::convertible(obj_ptr);
  }

  static void construct(PyObject* obj_ptr,
                        bp::converter::rvalue_from_python_stage1_data* data) {
    return EigenMatrixFromPython<typename TransformType::MatrixType>::construct(obj_ptr, data);
  }
};

#if EIGEN_VERSION_AT_LEAST(3, 3, 0)

//Assumes row-major destination
template<typename SourceType, typename DestType>
static void copy_tensor(
    const SourceType* source, DestType* dest,
    const int& num_dimensions,
    const npy_intp* shape,
    const int& size,
    const bool& is_source_row_major = false) {
  if (is_source_row_major) {
    for(int i_element = 0; i_element < size; i_element++,dest++){
      *dest = source[i_element];
    }
    return;
  }

  int col_stride = shape[0];
  std::vector<int> strides;
  std::vector<int> remaining_dims;
  int cumulative_stride = shape[0] * shape[1];
  int chunk_size = 1;

  for (int ix_dimension = 2; ix_dimension < num_dimensions; ix_dimension++) {
    int dim = shape[ix_dimension];
    strides.push_back(cumulative_stride);
    remaining_dims.push_back(dim);
    cumulative_stride *= dim;
    chunk_size *= dim;
  }

  std::reverse(remaining_dims.begin(), remaining_dims.end());
  std::reverse(strides.begin(), strides.end());

  for (int r = 0; r < shape[0]; r++) {
    for (int c = 0; c < shape[1]; c++) {
      int row_and_col_offset = r + c * col_stride;
      for (int ix_element = 0; ix_element < chunk_size; ix_element++) {
        int ix_subelement = ix_element;
        int remaining_offset = 0;
        for (size_t ix_dim = 0; ix_dim < remaining_dims.size(); ix_dim++) {
          div_t division_result = div(ix_subelement, remaining_dims[ix_dim]);
          ix_subelement = division_result.quot;
          int coord = division_result.rem;
          remaining_offset += coord * strides[ix_dim];
        }
        *dest = source[row_and_col_offset + remaining_offset];
        dest++;
      }
    }
  }
}

template<class TensorType>
struct EigenTensorToPython {
  static PyObject* convert(const TensorType& tensor) {

    const int num_dimensions = TensorType::NumDimensions;
    npy_intp* shape = static_cast<npy_intp*>(malloc(sizeof(npy_intp) * num_dimensions));
    //npy_int shape2[num_dimensions]; //will error, check
    for (int i_dimension = 0; i_dimension < num_dimensions; i_dimension++) {
      shape[i_dimension] = static_cast<npy_intp>(tensor.dimension(i_dimension));
    }

    PyArrayObject* python_array = (PyArrayObject*) PyArray_SimpleNew(
        num_dimensions, shape, NumpyEquivalentType<typename TensorType::Scalar>::type_code);

    copy_tensor(tensor.data(),
        (typename TensorType::Scalar*) PyArray_DATA(python_array),
        num_dimensions,
        shape,
        tensor.size(),
        static_cast<Eigen::StorageOptions>(TensorType::Layout) == Eigen::RowMajor);
    free(shape);
    return (PyObject*) python_array;

  }
};

template<typename TensorType>
struct EigenTensorFromPython {
  typedef typename TensorType::Scalar T;
  EigenTensorFromPython() {
    bp::converter::registry::push_back(&convertible,
        &construct,
        bp::type_id<TensorType>());
  }

  static void* convertible(PyObject* obj_ptr) {
    PyArrayObject* array = reinterpret_cast<PyArrayObject*>(obj_ptr);
    if (!PyArray_Check(array)) {
      //LOG(ERROR) << "PyArray_Check failed";
      return 0;
    }

    int dimension_count = PyArray_NDIM(array);
    if (dimension_count <= 2) {
      //This should be a Matrix or Vector, not Eigen::Tensor (default behavior)
      //LOG(ERROR) << "PyArray_Check failed";
      return 0;
    } else if (dimension_count != TensorType::NumDimensions) {
      //LOG(ERROR) << "PyArray_Check failed";
      return 0;
    }
    if (PyArray_ObjectType(obj_ptr, 0) != NumpyEquivalentType<T>::type_code) {
      //LOG(ERROR) << "types not compatible";
      return 0;
    }
    int flags = PyArray_FLAGS(array);
    if (!(flags & NPY_ARRAY_C_CONTIGUOUS)) {
      //LOG(ERROR) << "Contiguous C array required";
      return 0;
    }
    if (!(flags & NPY_ARRAY_ALIGNED)) {
      //LOG(ERROR) << "Aligned array required";
      return 0;
    }
    return obj_ptr;
  }

  static void construct(PyObject* obj_ptr,
      bp::converter::rvalue_from_python_stage1_data* data) {

    using bp::extract;

    PyArrayObject* array = reinterpret_cast<PyArrayObject*>(obj_ptr);
    npy_intp* numpy_array_dimensions = PyArray_DIMS(array);

    T* raw_data = reinterpret_cast<T*>(PyArray_DATA(array));

    typedef TensorMap<Tensor<T, TensorType::NumDimensions, RowMajor>, Aligned> TensorMapType;
    typedef TensorLayoutSwapOp<Tensor<T, TensorType::NumDimensions, RowMajor>> TensorSwapLayoutType;

    void* storage = ((bp::converter::rvalue_from_python_storage<TensorType>*)
        (data))->storage.bytes;

    std::array<Index, TensorType::NumDimensions> tensor_dimensions;
    std::array<int, TensorType::NumDimensions> inverse_dimensions;
    for (size_t i_dim = 0, inv_dim = TensorType::NumDimensions - 1; i_dim < tensor_dimensions.size();
        i_dim++, inv_dim--) {
      tensor_dimensions[i_dim] = static_cast<Index>(numpy_array_dimensions[i_dim]);
      inverse_dimensions[i_dim] = inv_dim;
    }

    new (storage) TensorType;
    TensorType* etensor = (TensorType*) storage;

    // TODO: This is a (potentially) expensive copy operation. There should be a better way
    auto mapped_t = TensorMapType(raw_data, tensor_dimensions);
    *etensor = TensorSwapLayoutType(mapped_t).shuffle(inverse_dimensions);
    data->convertible = storage;
  }
};

#endif // EIGEN_VERSION_AT_LEAST(3, 3, 0)

#define EIGEN_MATRIX_CONVERTER(Type) \
  EigenMatrixFromPython<Type>();  \
  bp::to_python_converter<Type, EigenMatrixToPython<Type> >();

#define EIGEN_TRANSFORM_CONVERTER(Type) \
  EigenTransformFromPython<Type>();  \
  bp::to_python_converter<Type, EigenTransformToPython<Type> >();

#define MAT_CONV(R, C, T) \
  typedef Matrix<T, R, C> Matrix ## R ## C ## T; \
  EIGEN_MATRIX_CONVERTER(Matrix ## R ## C ## T);

// This require a MAT_CONV for that Matrix type to be registered first
#define MAP_CONV(R, C, T) \
  typedef Map<Matrix ## R ## C ## T> Map ## R ## C ## T; \
  EIGEN_MATRIX_CONVERTER(Map ## R ## C ## T);

#define T_CONV(R, C, T) \
  typedef Transpose<Matrix ## R ## C ## T> Transpose ## R ## C ## T; \
  EIGEN_MATRIX_CONVERTER(Transpose ## R ## C ## T);

#define BLOCK_CONV(R, C, BR, BC, T) \
  typedef Block<Matrix ## R ## C ## T, BR, BC> Block ## R ## C ## BR ## BC ## T; \
  EIGEN_MATRIX_CONVERTER(Block ## R ## C ## BR ## BC ## T);

#if EIGEN_VERSION_AT_LEAST(3, 3, 0)

//For two-way Eigen <--> numpy Tensor converters
#define EIGEN_TENSOR_CONVERTER(Type) \
  EigenTensorFromPython<Type>(); \
  bp::to_python_converter<Type, EigenTensorToPython<Type> >();

//For one-way Eigen --> numpy converters (for row-major Eigen stuff)
#define TENSOR_ROW_MAJOR_CONV(T, D)\
  typedef Tensor<T, D, RowMajor> TensorRm ## T ## D; \
  bp::to_python_converter<TensorRm ## T ## D, EigenTensorToPython<TensorRm ## T ## D> >();

#define TENSOR_CONV(T, D) \
  typedef Tensor<T, D> Tensor ## T ## D; \
  EIGEN_TENSOR_CONVERTER(Tensor ## T ## D);

#endif // EIGEN_VERSION_AT_LEAST(3, 3, 0)

static const int X = Eigen::Dynamic;

#if PY_VERSION_HEX >= 0x03000000
void*
#else
void
#endif
SetupEigenConverters() {
  static bool is_setup = false;
  if (is_setup) return NUMPY_IMPORT_ARRAY_RETVAL;
  is_setup = true;

  import_array();

  EIGEN_MATRIX_CONVERTER(Matrix2f);
  EIGEN_MATRIX_CONVERTER(Matrix2d);
  EIGEN_MATRIX_CONVERTER(Matrix3f);
  EIGEN_MATRIX_CONVERTER(Matrix3d);
  EIGEN_MATRIX_CONVERTER(Matrix4f);
  EIGEN_MATRIX_CONVERTER(Matrix4d);

  EIGEN_MATRIX_CONVERTER(Vector2f);
  EIGEN_MATRIX_CONVERTER(Vector3f);
  EIGEN_MATRIX_CONVERTER(Vector4f);
  EIGEN_MATRIX_CONVERTER(Vector2d);
  EIGEN_MATRIX_CONVERTER(Vector3d);
  EIGEN_MATRIX_CONVERTER(Vector4d);

  EIGEN_TRANSFORM_CONVERTER(Affine2f);
  EIGEN_TRANSFORM_CONVERTER(Affine3f);
  EIGEN_TRANSFORM_CONVERTER(Affine2d);
  EIGEN_TRANSFORM_CONVERTER(Affine3d);

  EIGEN_TRANSFORM_CONVERTER(Isometry2f);
  EIGEN_TRANSFORM_CONVERTER(Isometry3f);
  EIGEN_TRANSFORM_CONVERTER(Isometry2d);
  EIGEN_TRANSFORM_CONVERTER(Isometry3d);

  EIGEN_TRANSFORM_CONVERTER(Projective2f);
  EIGEN_TRANSFORM_CONVERTER(Projective3f);
  EIGEN_TRANSFORM_CONVERTER(Projective2d);
  EIGEN_TRANSFORM_CONVERTER(Projective3d);

  MAT_CONV(2, 3, double);
  MAT_CONV(X, 3, double);
  MAT_CONV(X, X, double);
  MAT_CONV(X, 1, double);
  MAT_CONV(1, 4, double);
  MAT_CONV(1, X, double);
  MAT_CONV(3, 4, double);
  MAT_CONV(2, X, double);

#if EIGEN_VERSION_AT_LEAST(3, 3, 0)

  TENSOR_ROW_MAJOR_CONV(float, 3);
  TENSOR_ROW_MAJOR_CONV(float, 4);
  TENSOR_ROW_MAJOR_CONV(double, 3);
  TENSOR_ROW_MAJOR_CONV(double, 4);

  TENSOR_CONV(int, 3);
  TENSOR_CONV(int, 4);
  TENSOR_CONV(float, 3);
  TENSOR_CONV(float, 4);
  TENSOR_CONV(double, 3);
  TENSOR_CONV(double, 4);

#endif // EIGEN_VERSION_AT_LEAST(3, 3, 0)

#if PY_VERSION_HEX >= 0x03000000
  return 0;
#endif
}