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

#include <iostream>
#include <iomanip>
#include <chrono>
#include <cassert>
#include <cmath>
#include <set>

#include <ignition/plugin/Loader.hh>
#include <ignition/common/SystemPaths.hh>

#include "plugins/integrators.hh"

#ifdef HAVE_BOOST_PROGRAM_OPTIONS
#include <boost/program_options.hpp>
namespace bpo = boost::program_options;
#endif

using NumericalIntegrator = ignition::plugin::examples::NumericalIntegrator;
using ODESystem = ignition::plugin::examples::ODESystem;
using ODESystemFactory = ignition::plugin::examples::ODESystemFactory;

// The macro that this uses is provided as a compile definition in the
// examples/CMakeLists.txt file.
const std::string PluginLibDir = IGN_PLUGIN_EXAMPLES_LIBDIR;

/// \brief Return structure for numerical integration test results. If the name
/// is blank, that means the test was not run.
struct TestResult
{
  /// \brief Name of the test run
  public: std::string name;

  /// \brief Name of the actual time spent computing
  public: long timeSpent_us;

  /// \brief The percent error in each component of the state when compared to
  /// an exact solution.
  public: std::vector<double> percentError;
};

/// \brief A simple container to hold a plugin and the name that it was
/// instantiated from.
struct PluginHolder
{
  std::string name;
  ignition::plugin::PluginPtr plugin;
};

/// \brief Compute the component-wise percent error of the estimate produced by
/// a numerical integrator, compared to the theoretical exact solution of the
/// system.
std::vector<double> ComputeError(
    const NumericalIntegrator::State &_estimate,
    const NumericalIntegrator::State &_exact)
{
  assert(_estimate.size() == _exact.size());
  std::vector<double> result(_estimate.size());

  for(std::size_t i = 0 ; i < _estimate.size(); ++i)
    result[i] = (_estimate[i] - _exact[i])/_exact[i] * 100.0;

  return result;
}

/// \brief Pass in a plugin that provides the numerical integration interface.
/// The numerical integration results of the plugin will be tested against the
/// results of the _exactFunction.
TestResult TestIntegrator(
    const PluginHolder &_pluginHolder,
    const ODESystem &_system,
    const double _timeStep,
    const unsigned int _numSteps)
{
  const ignition::plugin::PluginPtr &plugin = _pluginHolder.plugin;
  NumericalIntegrator* integrator =
      plugin->QueryInterface<NumericalIntegrator>();

  if(!integrator)
  {
    std::cout << "The plugin named [" << _pluginHolder.name << "] does not "
              << "provide a NumericalIntegrator interface. It will not be "
              << "tested." << std::endl;
    return TestResult();
  }

  TestResult result;
  result.name = _pluginHolder.name;

  integrator->SetFunction(_system.ode);
  integrator->SetTimeStep(_timeStep);

  double time = _system.initialTime;
  NumericalIntegrator::State state = _system.initialState;

  auto performanceStart = std::chrono::high_resolution_clock::now();
  for(std::size_t i=0; i < _numSteps; ++i)
  {
    state = integrator->Integrate(time, state);
    time += integrator->GetTimeStep();
  }
  auto performanceStop = std::chrono::high_resolution_clock::now();

  result.timeSpent_us = std::chrono::duration_cast<std::chrono::microseconds>(
        performanceStop - performanceStart).count();

  result.percentError = ComputeError(state, _system.exact(time));

  return result;
}

/// \brief Print out the result of the test.
void PrintResult(const TestResult &_result)
{
  if (_result.name.empty())
  {
    // An empty name means that something went wrong with the test.
    return;
  }

  std::cout << "\nMethod: " << _result.name << "\n";

  std::cout << "Runtime(us): " << std::setfill(' ') << std::setw(8)
            << std::right << _result.timeSpent_us << "\n";

  std::cout << std::setw(0);

  std::cout << "Component-wise error: ";
  for (const double result : _result.percentError)
  {
    std::cout << std::setfill(' ') << std::setw(9) << std::right
              << std::setprecision(3) << std::fixed << result;

    std::cout << std::setw(0) << "%";
  }

  std::cout << "\n";
}

/// \brief Test a set of plugins against each system, using the specified
/// parameters.
void TestPlugins(
    const std::vector<PluginHolder> &_factories,
    const std::vector<PluginHolder> &_integrators,
    const double _timeStep,
    const unsigned int _numSteps)
{
  bool quit = false;
  if (_factories.empty())
  {
    std::cout << "You did not specify any ODE System plugins to test against!"
#ifdef HAVE_BOOST_PROGRAM_OPTIONS
              << "\n -- Pass in the -a flag to automatically use all plugins"
#endif
              << std::endl;
    quit = true;
  }

  if (_integrators.empty())
  {
    std::cout << "You did not specify any numerical integrator plugins to test "
              << "against!"
#ifdef HAVE_BOOST_PROGRAM_OPTIONS
              << "\n -- Pass in the -a flag to automatically use all plugins"
#endif
              << std::endl;
    quit = true;
  }

  if(quit)
    return;

  for (const PluginHolder &factory : _factories)
  {
    const std::vector<ODESystem> systems =
        factory.plugin->QueryInterface<ODESystemFactory>()->CreateSystems();

    for (const ODESystem &system : systems)
    {
      std::cout << "\n\n ================================================== \n";
      std::cout << "System [" << system.name << "] from factory ["
                << factory.name << "]\n";

      for (const PluginHolder &integrator : _integrators)
      {
        const TestResult result = TestIntegrator(
              integrator, system, _timeStep, _numSteps);

        PrintResult(result);
      }
    }
  }
}

/// \brief Load all the plugins that implement _interface.
std::vector<PluginHolder> LoadPlugins(
    const ignition::plugin::Loader &_loader,
    const std::string &_interface)
{
  // Fill in the holders object with each plugin.
  std::vector<PluginHolder> holders;

  const auto pluginNames = _loader.PluginsImplementing(_interface);

  for (const std::string &name : pluginNames)
  {
    ignition::plugin::PluginPtr plugin = _loader.Instantiate(name);
    if (!plugin)
    {
      std::cout << "Failed to load [" << name << "] as a class"
                << std::endl;
      continue;
    }

    holders.push_back({name, plugin});
  }

  return holders;
}

/// \brief Load all plugins that implement the NumericalIntegrator interface.
std::vector<PluginHolder> LoadIntegratorPlugins(
    const ignition::plugin::Loader &_loader)
{
  return LoadPlugins(
        _loader, "ignition::plugin::examples::NumericalIntegrator");
}

/// \brief Load all plugins that implement the ODESystemFactory interface
std::vector<PluginHolder> LoadSystemFactoryPlugins(
    const ignition::plugin::Loader &_loader)
{
  return LoadPlugins(
        _loader, "ignition::plugin::examples::ODESystemFactory");
}

/// \brief Prime the plugin loader with the paths and library names that it
/// should try to get plugins from.
void PrimeTheLoader(
    ignition::common::SystemPaths &_paths, /* TODO: This should be const */
    ignition::plugin::Loader &_loader,
    const std::set<std::string> &_pluginNames)
{
  for (const std::string &name : _pluginNames)
  {
    const std::string pluginPath = _paths.FindSharedLibrary(name);
    if (pluginPath.empty())
    {
      std::cout << "Failed to find path for plugin library [" << name << "]"
#ifdef HAVE_BOOST_PROGRAM_OPTIONS
              << "\n -- Use the -I flag to specify the plugin library directory"
#endif
                << std::endl;
      continue;
    }

    std::cout << "Path for [" << name << "] is [" << pluginPath << "]"
              << std::endl;

    if (_loader.LoadLib(pluginPath).empty())
    {
      std::cout << "Failed to load [" << name << "] as a plugin library"
                << std::endl;
    }
  }
}

int main(int argc, char *argv[])
{
  // Create an object that can search the system paths for the plugin libraries.
  ignition::common::SystemPaths paths;

  // Create a plugin loader
  ignition::plugin::Loader loader;

  // Add the build directory path for the plugin libraries so the SystemPaths
  // object will know to search through it.
  paths.AddPluginPaths(PluginLibDir);

  // Add the default plugins
  std::set<std::string> pluginNames = {
    "ForwardEuler", "RungeKutta4",
    "PolynomialODE", "ExponentialODE"
  };

#ifdef HAVE_BOOST_PROGRAM_OPTIONS

  double timeStep;
  unsigned int numSteps;

  std::string usage;
  usage +=
      "The 'integrators' example performs benchmark tests on numerical\n"
      "integrator plugins, testing them against differential equation plugins."
      "\nNumerical integrator plugins must inherit the NumericalIntegrator \n"
      "interface, and differential equation plugins must inherit the \n"
      "ODESystemFactory interface. Both interfaces can be found in the header\n"
      "ign-plugin/examples/plugins/Interfaces.hh.\n\n"

      "Custom plugins can be used by passing in the custom plugin library\n"
      "directory to the -I flag, and the library name(s) to the -p flag,\n"
      "as described below";

  bpo::options_description desc(usage);
  desc.add_options()

      ("help,h", "Print this usage message")

      ("plugins,p", bpo::value<std::vector<std::string>>(),
       "Plugins libraries to use")

      ("all,a", "Use all plugin libraries that come with this example")

      ("include-dirs,I", bpo::value<std::vector<std::string>>()->multitoken(),
       "Additional directories that may contain plugin libraries")

      ("timestep,s", bpo::value<double>(&timeStep)->default_value(0.01),
       "Size of the time step (s) to take")

      ("numsteps,n", bpo::value<unsigned int>(&numSteps)->default_value(10000),
       "Number of time steps to take")
      ;

  bpo::positional_options_description p;
  p.add("plugins", -1);

  bpo::variables_map vm;
  bpo::store(bpo::command_line_parser(argc, argv)
             .options(desc).positional(p).run(), vm);
  bpo::notify(vm);

  if (vm.count("help") > 0)
  {
    std::cout << desc << std::endl;
    return 1;
  }

  if (vm.count("all") == 0)
  {
    pluginNames.clear();
  }

  if (vm.count("plugins"))
  {
    const std::vector<std::string> inputPlugins =
        vm["plugins"].as<std::vector<std::string>>();

    for (const std::string &input : inputPlugins)
      pluginNames.insert(input);
  }

  if (vm.count("include-dirs"))
  {
    const std::vector<std::string> inputDirs =
        vm["include-dirs"].as<std::vector<std::string>>();

    for (const std::string &input : inputDirs)
    {
      std::cout << "Including additional plugin directory: ["
                << input << "]" << std::endl;

      paths.AddPluginPaths(input);
    }
  }

#else

  const double timeStep = 0.01;
  const unsigned int numSteps = 10000;

  std::cout
      << "boost::program_options was not found when this example was\n"
      << "compiled, so we will default to using all the plugin libraries\n"
      << "that came with this example program. We will also default to:\n"
      << " -- time step = " << timeStep << "\n"
      << " -- num steps = " << numSteps << "\n"
      << std::endl;

#endif

  PrimeTheLoader(paths, loader, pluginNames);

  // Load the plugins
  const std::vector<PluginHolder> integrators = LoadIntegratorPlugins(loader);
  const std::vector<PluginHolder> systems = LoadSystemFactoryPlugins(loader);

  TestPlugins(systems, integrators, timeStep, numSteps);
}


#ifndef MHD2D_HPP_INCLUDED
#define MHD2D_HPP_INCLUDED

// debug groups, uncomment needed ones
#ifdef DEBUG
//#define DEBUG_MIDPOINT_EIGENVALUES
//#define DEBUG_MAX_VELOCITY
//#define DEBUG_DT_UPDATE
//#define DEBUG_SHOW_SIMULATED_TIME
//#define DEBUG_FLUX_LF
#endif // DEBUG

// C headers
#include <cmath>
#include <cstdio>

// C++ headers
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <iterator>
#include <string>
#include <sstream>
#include <chrono>

// Headers required for exception handling
#ifdef USE_THREAD_EXCEPTIONS
#include <thread>
#include <mutex>
#endif // USE_THREAD_EXCEPTIONS

// Headers required for OpenMP parallelization
#ifdef OPENMP
#include <omp.h>
#endif // OPENMP

// Boost headers
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/algorithm/string.hpp>

// Local headers
#include "vector_utilities.hpp"
#include "enums.hpp"

// OUT and IN can be redefined as a filestream
// to enable direct file input/output
// In MinGW already defined in windef.h, so first undefine
#ifdef OUT
#undef OUT
#endif // OUT
#define OUT std::cout
#ifdef IN
#undef IN
#endif
#define IN  std::cin
#define ERROUT std::cerr
#define LF  std::endl
#define ABS std::abs

// Problem size and number of velocity dimensions to keep track of,
// to avoid magic numbers in loops
#define PRB_DIM 8
#define VEL_DIM 3

// Array sizes
//  - data is in [2..n+1] with [0], [1], [n+2] and [n+3] as buffers
#define NX      (params.nx+4)
#define NY      (params.ny+4)
#define NXFIRST (2)
#define NYFIRST (2)
#define NXLAST  (params.nx+2)
#define NYLAST  (params.ny+2)

#define EPS            (1e-20)
#define EPS_EQUAL(a,b) (std::abs((a)-(b))<EPS)
#define EPS_ZERO(x)    (std::abs(x)<EPS)
#define MAX(a,b)       ((a)>(b) ? (a) : (b))

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif // M_PI

typedef struct {
   // Binary or text
   bool binary;
   bool binary_double;

   // Single or per-step
   bool single_file;

   // Output categories
   bool natural;
   bool conservation;

   // Skipping parameters
   WriteSkipMode skip_mode;
   int    skip_steps;
   double skip_t;

   // Mask formats
   std::string format_simtime;
   std::string format_index;
   std::string format_step;

   // File
   //TODO
   std::string base_filename;
   std::string filename;
   FILE *file;
} t_output;

typedef struct {
   bool r_step;
   bool r_end;
   bool divb_step;
   bool characteristics;
} t_log_params;

typedef struct {
   bool           isError;
   ReturnStatus   status;
   std::string    message;
} t_status;

typedef struct {
   BoundaryCondition right;
   BoundaryCondition top;
   BoundaryCondition left;
   BoundaryCondition bottom;
} t_boundary;

typedef struct {
   // Grid parameters
   int    nx;
   int    ny;
   double dx;
   double dy;
   double start_x;
   double start_y;

   // Time parameters
   TimeStepMode time_mode;
   int    steps;
   double cfl_number;
   double dt_min;
   double dt_max;
   double t_max;

   // Simulation parameters
   const int b_count  = 4;
   const int b_left   = 0;
   const int b_bottom = 1;
   const int b_right  = 2;
   const int b_top    = 3;
   BoundaryCondition boundary[4];
   t_vectors         boundary_dirichlet_U[4];
   TimeStepMethod    time_stepping;
   IntegrationMethod scheme;

   // Physical parameters
   double gamma;

   // Div B corrector parameters
   DivBCorrectionMethod divb_method;
   int    divb_skip_steps;
   double divb_sor_rmax;
   double divb_sor_rsteps;
   double divb_sor_max;
   int    divb_sor_steps;
   bool   divb_sor_omega_use_static;
   double divb_sor_omega;

   bool break_on_neg_pressure;

   // logging parameters
   t_log_params log_params;
} t_params;

typedef struct {
   t_vectors left;
   t_vectors right;
   t_vectors up;
   t_vectors down;
} borderVectors;

typedef struct {
   // Conserved variables; density, momentum, magnetic field, energy
   t_matrices U;
   // Primitive variables; velocity and pressure
   t_matrices u;
   t_matrix   p;
   // Characteristic velocities
   t_matrices cx;
   t_matrices cy;
   // Characteristic variables
   t_matrices LUx;
   t_matrices LUy;
   // Border fluxes
   borderVectors borderFlux;
   // Time
   double dt;
   double t_current;
} t_data;

// Utility - error handling
void criticalError
   ( ReturnStatus       error
   , const std::string  message );

// File access - open & close
void openFile
   ( t_output &output );

void closeFile
   ( t_output &output );

// File access - input from ini
void inputData
   ( const std::string filename
   , t_output &output_grid
   , t_output &output_non_grid
   , t_params &params
   , t_data   &data );

// File access - output grid data file
void outputGridData
   ( const t_output &output
   , const t_params &params
   , const t_data   &data
   , int step
   , int index );

// File access - output non-grid data to file
void outputNonGridData
   ( const t_output &output
   , const t_params &params
   , const t_data   &data
   , int step
   , int index );

// Output characteristics in binary format
void outputCharacteristicsBinary
   ( const t_output    &output
   , const t_params    &params
   , const t_matrices  &cx
   , const t_matrices  &cy
   , const t_matrices  &LUx
   , const t_matrices  &LUy
   , double time
   , int    step
   , int    index );

// Conversion - conservation to natural variables
void toNaturalPoint
   ( const t_params &params
   , double U[PRB_DIM]
   , double u[VEL_DIM]
   , double &p );

// Conversion - natural to conservation variables
void toConservationPoint
   ( const t_params &params
   , double U[PRB_DIM]
   , double u[VEL_DIM]
   , double &p );

// Conversion - conservation to natural variables
void toNaturalData
   ( const t_params &params
   , t_data &data );

// Conversion - natural to conservation variables
void toConservationData
   ( const t_params &params
   , t_data &data );

// Time stepper - Third order optimal TVD Runge-Kutta time stepping method
t_status stepRK3TVD
   ( t_matrices  U
   , double     &dt
   , const t_params &params );

// Time stepper - Euler time stepping method
t_status stepEuler
   ( t_matrices  U
   , double     &dt
   , const t_params &params );

// Scheme - central FD
t_status methodCentralFD
   ( t_matrices  U
   , t_matrices  UL
   , double     &dt_step
   , const t_params &params );

// Scheme - ENO-Roe with characteristics decomposition
t_status methodENOSystem
   ( t_matrices  U
   , t_matrices  UL
   , double     &dt_step
   , const t_params &params );

// Div B correction - calculated in place, using SOR (Successive Over-Relaxation)
t_status divBCorrectionSOR
   ( t_matrices U
   , const t_params &params );

// Div B calculation - maximum and total
void divBCalculation
   ( t_matrices U
   , const t_params &params
   , double &maxdivb
   , double &totdivb );

#ifdef USE_THREAD_EXCEPTIONS
// Class for exception handling that can be used in OpenMP parallelized code
// Source: http://stackoverflow.com/questions/11828539/elegant-exceptionhandling-in-openmp
class ThreadException {
   std::exception_ptr Ptr;
   std::mutex         Lock;

public:
   ThreadException
      ( );
   ~ThreadException
      ( );

   void Rethrow
      ( );

   void CaptureException
      ( );

   template <typename Function, typename... Parameters>
   void ThreadException::Run
      ( Function f
      , Parameters... params
   ){
      try {
         f( params... );
      } catch( ... ) {
         CaptureException();
      }
   }
};
#endif // USE_THREAD_EXCEPTIONS

#endif // MHD2D_HPP_INCLUDED


#include "SPlisHSPlasH/Common.h"
#include <Eigen/Dense>
#include "extern/cxxopts/cxxopts.hpp"
#include "Utilities/Timing.h"
#include "Utilities/OBJLoader.h"
#include "SPlisHSPlasH/TriangleMesh.h"
#include "Utilities/PartioReaderWriter.h"
#include "Utilities/Version.h"
#include "Utilities/FileSystem.h"
#include "SPlisHSPlasH/NeighborhoodSearch.h"
#include "SPlisHSPlasH/SPHKernels.h"
#include "SPlisHSPlasH/Utilities/SceneLoader.h"
#include "extern/happly/happly.h"
#include "extern/partio/src/lib/Partio.h"

// Enable memory leak detection
#ifdef _DEBUG
#ifndef EIGEN_ALIGN
	#define new DEBUG_NEW
#endif
#endif

INIT_TIMING
INIT_LOGGING

using namespace SPH;
using namespace Eigen;
using namespace std;
using namespace Utilities;

void meshSkinning();
void computeMatrixL();
void computeF();
void deformMesh();
void performMeshSkinning();
void loadObj(const std::string& filename, TriangleMesh& mesh, const Vector3r& translation, const Matrix3r& rotation, const Vector3r& scale);
bool readFrame(std::vector<Vector3r>& x, const unsigned int frame);
void writeCurrentFrame();
void init();
void precomputeValues();
void exportOBJ();
void exportVTK();
void exportPLY();

struct Comparator {
	Comparator(const Vector3r &xi, std::vector<Vector3r>* x) : m_xi(xi), m_x(x) {};
	bool operator()(unsigned int a, unsigned int b)
	{
		return ((*m_x)[a] - m_xi).squaredNorm() < ((*m_x)[b] - m_xi).squaredNorm();
	}

	Vector3r m_xi;
	std::vector<Vector3r> *m_x;
};

const Real density0 = 1000.0;
string input = "";
string output = "";
string meshFile = "";
string sceneFile = "";
bool overwriteExistingFiles = false;
Vector3r scale(1.0, 1.0, 1.0);
Vector3r translation(1.0, 1.0, 1.0);
Matrix3r rotation = Matrix3r::Identity();
unsigned int startFrame = 1;
unsigned int endFrame = 0xffffffff;
unsigned int currentFrame = 1;
string exePath;
bool useSplitting = false;
int output_format = 0; // 0: OBJ, 1: vtk, 2: ply
Real radius = static_cast<Real>(0.025);
Real diameter = radius * static_cast<Real>(2.0);
std::string partioPath = "";
Real supportRadius;
Real mass = 1.0;
TriangleMesh mesh;
std::vector<Vector3r> x0;
std::vector<Vector3r> x;
std::vector<int> id;
std::vector<Vector3r> meshX;
std::vector<Matrix3r> L;
std::vector<Matrix3r> F;
std::vector<Real> densities;
std::vector<Real> restVolumes;
std::vector<Real> shepard;
Real supportRadiusFactor = 6.0;
unsigned int maxNeighbors = 30;
Real W_zero;
Real(*kernelFct)(const Vector3r&);
Vector3r(*gradKernelFct)(const Vector3r& r);
NeighborhoodSearch* neighborhoodSearch;
std::vector<unsigned int> activeParticles;
std::vector<std::vector<unsigned int>> initialNeighbors;
std::vector<std::vector<unsigned int>> initialMeshNeighbors;
#ifdef USE_AVX
std::vector<Vector3f8, Eigen::aligned_allocator<Vector3f8>> precomp_V_gradW8;
std::vector<unsigned int> precomputed_indices8;
#else
std::vector<Vector3r, Eigen::aligned_allocator<Vector3r>> precomp_V_gradW;
std::vector<unsigned int> precomputed_indices;
#endif

unsigned int numberOfNeighbors(const unsigned int pointSetIndex, const unsigned int index)
{
	return static_cast<unsigned int>(neighborhoodSearch->point_set(0).n_neighbors(pointSetIndex, index));
}

unsigned int getNeighbor(const unsigned int pointSetIndex, const unsigned int index, const unsigned int k)
{
	return neighborhoodSearch->point_set(0).neighbor(pointSetIndex, index, k);
}

std::istream& operator >> (std::istream& istream, Vector3r& r)
{
	return istream >> std::skipws >> r[0] >> r[1] >> r[2];
}

std::ostream& operator << (std::ostream& out, const Vector3r& r)
{
	out << r[0] << ", " << r[1] << ", " << r[2];
	return out;
}

// main
int main( int argc, char **argv )
{
	REPORT_MEMORY_LEAKS;

	Utilities::logger.addSink(unique_ptr<Utilities::ConsoleSink>(new Utilities::ConsoleSink(Utilities::LogLevel::INFO)));
	exePath = FileSystem::getProgramPath();

	try
	{
		cxxopts::Options options(argv[0], "MeshSkinning");

		options.add_options()
			("i,input", "Input file ", cxxopts::value<std::string>())
			("o,output", "Output file", cxxopts::value<std::string>())
			("m,mesh", "Mesh file ", cxxopts::value<std::string>())
			("scene", "Scene file (all settings are imported from the scene file)", cxxopts::value<std::string>())
			("partioPath", "Path of the partio files (when using a scene file). If not set, it is assumed that the files are in the standard output path.", cxxopts::value<std::string>())
			("scale", "Scaling of input geometry (e.g. --scale \"2 1 2\")", cxxopts::value<Vector3r>()->default_value("1 1 1"))
			("t,translation", "Translation of input geometry (e.g. --translation \"2 1 2\")", cxxopts::value<Vector3r>()->default_value("1 1 1"))
			("axis", "Rotation axis of input geometry (e.g. --axis \"1 0 0\")", cxxopts::value<Vector3r>()->default_value("1 0 0"))
			("angle", "Angle of input geometry (e.g. --angle 1)", cxxopts::value<Real>()->default_value("0.0"))
			("s,startframe", "Start frame", cxxopts::value<unsigned int>()->default_value("1"))
			("e,endframe", "End frame", cxxopts::value<unsigned int>())
			("r,radius", "Particle radius", cxxopts::value<Real>()->default_value("0.025"))
			("supportRadiusFactor", "The support radius is defined as factor*particleRadius", cxxopts::value<Real>()->default_value("6.0"))
			("maxNeighbors", "The maximum number of neighbors that are used for the interpolation.", cxxopts::value<unsigned int>()->default_value("60"))
			("splitting", "Read a scene which used the object splitting export option.")
			("overwrite", "Overwrite existing files.")
			("h,help", "Print help")
			;

		auto result = options.parse(argc, argv);

		if (result.count("help"))
		{
			std::cout << options.help({ "", "Group" }) << std::endl;
			exit(0);
		}

		if (result.count("input"))
		{
			input = result["input"].as<std::string>();
			LOG_INFO << "Input = " << input;
		}
		if (result.count("output"))
		{
			output = result["output"].as<std::string>();
			LOG_INFO << "Output = " << output;
			output_format = 0;
			if (Utilities::StringTools::to_upper(FileSystem::getFileExt(output)) == "VTK")
				output_format = 1;
			else if (Utilities::StringTools::to_upper(FileSystem::getFileExt(output)) == "PLY")
				output_format = 2;
		}
		if (result.count("mesh"))
		{
			meshFile = result["mesh"].as<std::string>();
			LOG_INFO << "mesh = " << meshFile;
		}
		if (result.count("scene"))
		{
			sceneFile = result["scene"].as<std::string>();
		}

		if (result.count("partioPath"))
		{
			partioPath = result["partioPath"].as<std::string>();
			if (FileSystem::isRelativePath(partioPath))
				partioPath = FileSystem::normalizePath(exePath + "/" + partioPath);
			LOG_INFO << "Partio path = " << partioPath;
		}

		if (result.count("splitting"))
			useSplitting = true;

		if (result.count("overwrite"))
			overwriteExistingFiles = true;

		if (sceneFile == "")
		{
			if (input == "")
			{
				LOG_ERR << "Input is missing!";
				exit(1);
			}
			if (output == "")
			{
				LOG_ERR << "Output is missing!";
				exit(1);
			}
			if (meshFile == "")
			{
				LOG_ERR << "Mesh file is missing!";
				exit(1);
			}

			if (result.count("scale"))
				scale = result["scale"].as<Vector3r>();
			LOG_INFO << "Scale: " << scale;

			if (result.count("translation"))
				translation = result["translation"].as<Vector3r>();
			LOG_INFO << "Translation: " << translation;

			Vector3r axis = Vector3r::Zero();
			Real angle = 0.0;
			rotation = Matrix3r::Identity();
			if (result.count("axis"))
				axis = result["axis"].as<Vector3r>();
			if (result.count("angle"))
			{
				angle = result["angle"].as<Real>();
				axis.normalize();
				rotation = AngleAxisr(angle, axis);

				LOG_INFO << "Axis: " << axis;
				LOG_INFO << "Angle: " << angle;
			}

			if (result.count("radius"))
				radius = result["radius"].as<Real>();

			LOG_INFO << "Particle radius: " << radius;
		}

		if (result.count("startframe"))
			startFrame = result["startframe"].as<unsigned int>();

		if (result.count("endframe"))
			endFrame = result["endframe"].as<unsigned int>();

		if (result.count("supportRadiusFactor"))
			supportRadiusFactor = result["supportRadiusFactor"].as<Real>();

		if (result.count("maxNeighbors"))
			maxNeighbors = result["maxNeighbors"].as<unsigned int>();
	}
	catch (const cxxopts::OptionException& e)
	{
		LOG_ERR << "error parsing options: " << e.what();
		exit(1);
	}

	performMeshSkinning();

	Timing::printAverageTimes();
	Timing::printTimeSums();

	return 0;
}

void init()
{
	diameter = static_cast<Real>(2.0) * radius;
	mass = static_cast<Real>(1.0) * diameter * diameter * diameter * density0;

	restVolumes.resize(x0.size());
	shepard.resize(mesh.numVertices());
	densities.resize(x0.size());
	L.resize(x0.size());
	F.resize(x0.size());

	supportRadius = supportRadiusFactor * radius;

	CubicKernel::setRadius(supportRadius);
#ifdef USE_AVX
	CubicKernel_AVX::setRadius(supportRadius);
#endif
	W_zero = CubicKernel::W_zero();
	kernelFct = CubicKernel::W;
	gradKernelFct = CubicKernel::gradW;

	// Init neighborhood search
	neighborhoodSearch = new NeighborhoodSearch(supportRadius, false);
	neighborhoodSearch->set_radius(supportRadius);
	neighborhoodSearch->add_point_set(&x0[0][0], x0.size(), true, true);

	// find initial neighbors
	START_TIMING("neighborhoodSearch");
	neighborhoodSearch->find_neighbors();
	STOP_TIMING_AVG;

	// store initial neighbors and init rest volumes
	int numParticles = (int) x0.size();
	initialNeighbors.resize(numParticles);
	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < numParticles; i++)
		{
			// only neighbors in same phase will influence elasticity
			const unsigned int numNeighbors = numberOfNeighbors(0, i);
			initialNeighbors[i].resize(numNeighbors);
			for (unsigned int j = 0; j < numNeighbors; j++)
				initialNeighbors[i][j] = getNeighbor(0, i, j);

			std::sort(initialNeighbors[i].begin(), initialNeighbors[i].end(), Comparator(x0[i], &x0));

			if (initialNeighbors[i].size() > maxNeighbors)
				initialNeighbors[i].resize(maxNeighbors);

			// compute volume
			Real d = W_zero;
			const Vector3r& xi = x0[i];
			for (unsigned int j = 0; j < numberOfNeighbors(0, i); j++)
			{
				const unsigned int neighborIndex = getNeighbor(0, i, j);
				const Vector3r& xj = x0[neighborIndex];
				d += kernelFct(xi - xj);
			}
			restVolumes[i] = static_cast<Real>(1.0) / d;
		}
	}

	// compute the neighbors for each vertex in the mesh
	unsigned int numVertices = mesh.numVertices();
	initialMeshNeighbors.resize(numVertices);
	Vector3r* v = mesh.getVertices().data();

	std::vector<std::vector<unsigned int>> neighbors;
	neighbors.reserve(200);
	meshX.resize(numVertices);
	#pragma omp parallel default(shared), private(neighbors)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < (int) numVertices; i++)
		{
			const Vector3r& xi = v[i];
			// copy mesh x
			meshX[i] = xi;

			// determine particle type
			neighborhoodSearch->find_neighbors(xi.data(), neighbors);

			// only neighbors in same phase will influence elasticity
			const unsigned int numNeighbors = (unsigned int) neighbors[0].size();
			initialMeshNeighbors[i].resize(numNeighbors);
			for (unsigned int j = 0; j < numNeighbors; j++)
				initialMeshNeighbors[i][j] = neighbors[0][j];

			std::sort(initialMeshNeighbors[i].begin(), initialMeshNeighbors[i].end(), Comparator(meshX[i], &x0));

			if (initialMeshNeighbors[i].size() > maxNeighbors)
				initialMeshNeighbors[i].resize(maxNeighbors);
		}

		#pragma omp for schedule(static)
		for (int i = 0; i < (int)mesh.numVertices(); i++)
		{
			shepard[i] = 0.0;
			const Vector3r& xi = meshX[i];
			for (unsigned int j = 0; j < (unsigned int) initialMeshNeighbors[i].size(); j++)
			{
				const unsigned int neighborIndex = initialMeshNeighbors[i][j];
				const Vector3r& xj = x0[neighborIndex];
				shepard[i] += restVolumes[neighborIndex] * kernelFct(xi - xj);
			}

			shepard[i] = static_cast<Real>(1.0) / shepard[i];
		}
	}

	int counter = 0;
	std::vector<bool> active;
	active.resize(numParticles, false);
	unsigned int minNeighbors = 0xffffffff;
	unsigned int maxNeighbors = 0;
	for (unsigned int i = 0; i < numVertices; i++)
	{
		const unsigned int numNeighbors = (unsigned int)initialMeshNeighbors[i].size();
		minNeighbors = std::min(minNeighbors, numNeighbors);
		maxNeighbors = std::max(maxNeighbors, numNeighbors);

		if (numNeighbors == 0)
			LOG_INFO << i;

		for (unsigned int j = 0; j < numNeighbors; j++)
		{
			unsigned int neighbor = initialMeshNeighbors[i][j];
			if (!active[neighbor])
			{
				active[neighbor] = true;
				counter++;
			}
		}
	}
	activeParticles.clear();
	activeParticles.reserve(counter);
	for (int i = 0; i < numParticles; i++)
	{
		if (active[i])
			activeParticles.push_back(i);
	}
	LOG_INFO << "Active particles: " << activeParticles.size();
	LOG_INFO << "Min. # neighbors: " << minNeighbors;
	LOG_INFO << "Max. # neighbors: " << maxNeighbors;

	START_TIMING("computeMatrixL");
	computeMatrixL();
	STOP_TIMING_AVG;

	START_TIMING("precomputeValues");
	precomputeValues();
	STOP_TIMING_AVG;

	delete neighborhoodSearch;
	neighborhoodSearch = nullptr;
}

void performMeshSkinning(const std::string &id, const int objId)
{
	const std::string outputPath = FileSystem::normalizePath(exePath + "/output/" + FileSystem::getFileName(sceneFile));
	if (partioPath == "")
		partioPath = FileSystem::normalizePath(outputPath + "/partio");

	std::string fileName;
	if (useSplitting)
	{
		fileName = FileSystem::normalizePath(partioPath + "/ParticleData_" + id + "_" + std::to_string(objId) + "_" + std::to_string(startFrame) + ".bgeo");
		input = FileSystem::normalizePath(partioPath + "/ParticleData_" + id + "_" + std::to_string(objId) + "_#.bgeo");
		output = FileSystem::normalizePath(partioPath + "/../meshes/ParticleData_" + id + "_" + std::to_string(objId) + "_#.ply");
	}
	else
	{
		fileName = FileSystem::normalizePath(partioPath + "/ParticleData_" + id + "_" + std::to_string(startFrame) + ".bgeo");
		input = FileSystem::normalizePath(partioPath + "/ParticleData_" + id + "_#.bgeo");
		output = FileSystem::normalizePath(partioPath + "/../meshes/ParticleData_" + id + "_#.ply");
	}

	if (FileSystem::fileExists(fileName))
	{
		output_format = 2;

		if (!FileSystem::fileExists(meshFile))
		{
			LOG_ERR << "Visualization mesh is missing in scene file";
			return;
		}

		LOG_INFO << "Input: " << input;
		LOG_INFO << "Output: " << output;
		LOG_INFO << "Mesh file: " << meshFile;
		LOG_INFO << "Particle radius: " << radius;
		LOG_INFO << "Scale: " << scale;
		LOG_INFO << "Translation: " << translation;

		loadObj(meshFile, mesh, translation, rotation, scale);

		// read reference configuration
		readFrame(x0, 1);
		LOG_INFO << "# particles: " << x0.size();

		init();

		meshSkinning();
	}
	else
	{
		LOG_ERR << "Input file not found: " << fileName;
	}
}

void performMeshSkinning()
{
	if (sceneFile != "")
	{
		SceneLoader* sceneLoader = new SceneLoader();
		Utilities::SceneLoader::Scene scene;
		sceneLoader->readScene(sceneFile.c_str(), scene);

		const std::string outputPath = FileSystem::normalizePath(exePath + "/output/" + FileSystem::getFileName(sceneFile));
		if (partioPath == "")
			partioPath = FileSystem::normalizePath(outputPath + "/partio");
		const std::string scene_path = FileSystem::getFilePath(sceneFile);

		int objId = 0;
		for (size_t i = 0; i < scene.fluidBlocks.size(); i++)
		{
			translation = 0.5 * (scene.fluidBlocks[i]->box.m_maxX + scene.fluidBlocks[i]->box.m_minX);
			rotation = Matrix3r::Identity();
			scale = scene.fluidBlocks[i]->box.m_maxX - scene.fluidBlocks[i]->box.m_minX;
			radius = scene.particleRadius;
			std::string id = scene.fluidBlocks[i]->id;
			meshFile = FileSystem::normalizePath(FileSystem::getFilePath(sceneFile) + "/" + scene.fluidBlocks[i]->visMeshFile);

			performMeshSkinning(id, objId);
			objId++;
		}

		for (size_t i = 0; i < scene.fluidModels.size(); i++)
		{
			translation = scene.fluidModels[i]->translation;
			rotation = scene.fluidModels[i]->rotation;
			scale = scene.fluidModels[i]->scale;
			radius = scene.particleRadius;
			std::string id = scene.fluidModels[i]->id;
			meshFile = FileSystem::normalizePath(FileSystem::getFilePath(sceneFile) + "/" + scene.fluidModels[i]->visMeshFile);

			performMeshSkinning(id, objId);
			objId++;
		}

		delete sceneLoader;
	}
	else
	{
		loadObj(meshFile, mesh, translation, rotation, scale);

		// read reference configuration
		readFrame(x0, 1);
		LOG_INFO << "# particles: " << x0.size();

		init();

		meshSkinning();
	}
}

void loadObj(const std::string& filename, TriangleMesh& mesh, const Vector3r& translation, const Matrix3r &rotation, const Vector3r& scale)
{
	std::vector<OBJLoader::Vec3f> x;
	std::vector<OBJLoader::Vec3f> normals;
	std::vector<MeshFaceIndices> faces;
	OBJLoader::Vec3f s = { (float)scale[0], (float)scale[1], (float)scale[2] };
	OBJLoader::loadObj(filename, &x, &faces, &normals, nullptr, s);

	mesh.release();
	const unsigned int nPoints = (unsigned int)x.size();
	const unsigned int nFaces = (unsigned int)faces.size();
	mesh.initMesh(nPoints, nFaces);
	for (unsigned int i = 0; i < nPoints; i++)
	{
		mesh.addVertex(rotation * Vector3r(x[i][0], x[i][1], x[i][2]) + translation);
	}
	for (unsigned int i = 0; i < nFaces; i++)
	{
		// Reduce the indices by one
		int posIndices[3];
		for (int j = 0; j < 3; j++)
		{
			posIndices[j] = faces[i].posIndices[j] - 1;
		}

		mesh.addFace(&posIndices[0]);
	}

	LOG_INFO << "Number of triangles: " << nFaces;
	LOG_INFO << "Number of vertices: " << nPoints;
}

std::string zeroPadding(const unsigned int number, const unsigned int length)
{
	ostringstream out;
	out << std::internal << std::setfill('0') << std::setw(length) << number;
	return out.str();
}

std::string convertFileName(const std::string &inputFileName, const unsigned int currentFrame)
{
	std::string fileName = inputFileName;
	std::string::size_type pos1 = fileName.find_first_of("#", 0);
	if (pos1 == std::string::npos)
	{
		LOG_ERR << "# missing in file name.";
		exit(1);
	}
	std::string::size_type pos2 = fileName.find_first_not_of("#", pos1);
	std::string::size_type length = pos2 - pos1;

	std::string numberStr = zeroPadding(currentFrame, (unsigned int)length);
	fileName.replace(pos1, length, numberStr);
	return fileName;
}

bool readFrame(std::vector<Vector3r>& x, const unsigned int frame)
{
	std::string fileName = convertFileName(input, frame);
	x.clear();
	id.clear();

	// check if file exists
	if (!FileSystem::fileExists(fileName))
		return false;

	// read partio file
	Partio::ParticlesDataMutable* data = Partio::read(fileName.c_str());
	if (!data)
		return false;

	// find position and id attribute
	unsigned int posIndex = 0xffffffff;
	unsigned int idIndex = 0xffffffff;
	for (int i = 0; i < data->numAttributes(); i++)
	{
		Partio::ParticleAttribute attr;
		data->attributeInfo(i, attr);
		if (attr.name == "position")
			posIndex = i;
		if (attr.name == "id")
			idIndex = i;
	}

	// read IDs
	Partio::ParticleAttribute attr;
	if (idIndex != 0xffffffff)
	{
		id.resize(data->numParticles());
		data->attributeInfo(idIndex, attr);
		for (int i = 0; i < data->numParticles(); i++)
		{
			const int* id_ = data->data<int>(attr, i);
			id[i] = id_[0];
		}
	}

	// read positions
	if (posIndex != 0xffffffff)
	{
		x.resize(data->numParticles());
		data->attributeInfo(posIndex, attr);
		for (int i = 0; i < data->numParticles(); i++)
		{
			const float* pos = data->data<float>(attr, i);

			// use id as index if possible to consider z-sorting in the particle data
			if (idIndex != 0xffffffff)
				x[id[i]] = Vector3r(pos[0], pos[1], pos[2]);
			else
				x[i] = Vector3r(pos[0], pos[1], pos[2]);
		}
	}

	data->release();
	return true;
}

void computeMatrixL()
{
	const int numParticles = (int) x0.size();

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < numParticles; i++)
		{
			const Vector3r &xi0 = x0[i];
			Matrix3r L_;
			L_.setZero();

			const size_t numNeighbors = initialNeighbors[i].size();

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			for (unsigned int j = 0; j < numNeighbors; j++)
			{
				const unsigned int neighborIndex = initialNeighbors[i][j];

				const Vector3r &xj0 = x0[neighborIndex];
				const Vector3r xj_xi_0 = xj0 - xi0;
				const Vector3r gradW = gradKernelFct(xj_xi_0);

				// minus because gradW(xij0) == -gradW(xji0)
				L_ -= restVolumes[neighborIndex] * gradW * xj_xi_0.transpose();
			}

			//// add 1 to z-component. otherwise we get a singular matrix in 2D
			//if (sim->is2DSimulation())
			//	L(2, 2) = 1.0;

			bool invertible = false;
			L_.computeInverseWithCheck(L[i], invertible, 1e-9);
			if (!invertible)
			{
				//MathFunctions::pseudoInverse(L, m_L[i]);
				L[i] = Matrix3r::Identity();
			}
		}
	}
}

#ifdef USE_AVX

void computeF()
{
	const int numParticles = (int)activeParticles.size();

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < (int)numParticles; i++)
		{
			const unsigned int index = activeParticles[i];
			const Vector3r &xi = x[index];
			const Vector3r& xi0 = x0[index];

			const Vector3f8 xi_avx(xi);
			const Vector3f8 xi0_avx(xi0);

			const unsigned int numNeighbors = (unsigned int) initialNeighbors[index].size();

			Matrix3f8 F_avx;
			F_avx.setZero();
			for (unsigned int j = 0; j < numNeighbors; j += 8)
			{
				const unsigned int count = std::min(numNeighbors - j, 8u);

				const Vector3f8 xj_avx = convertVec_zero(&initialNeighbors[index][j], &x[0], count);
				const Vector3f8 xj_xi = xj_avx - xi_avx;
				const Vector3f8& V_gradW = precomp_V_gradW8[precomputed_indices8[i] + j / 8];

				const Scalarf8 restVolume_j_avx = convert_zero(&initialNeighbors[index][j], &restVolumes[0], count);
				const Vector3f8 xj0_avx = convertVec_zero(&initialNeighbors[index][j], &x0[0], count);
				//const Vector3f8& V_gradW = Matrix3f8(L[index]) * CubicKernel_AVX::gradW(xi0_avx - xj0_avx) * restVolume_j_avx;
				Matrix3f8 dyad;
				dyadicProduct(xj_xi, V_gradW, dyad);
				F_avx += dyad;
			}
			F[index] = F_avx.reduce();
		}
	}
}

void deformMesh()
{
	const int numVertices = (int)mesh.numVertices();
	auto& meshX0 = mesh.getVertices();

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < numVertices; i++)
		{
			Vector3r &mesh_xi = meshX[i];
			const Vector3r& mesh_xi0 = meshX0[i];
			const Vector3f8 xi0_avx(mesh_xi0);

			const unsigned int numNeighbors = (unsigned int)initialMeshNeighbors[i].size();

			Vector3f8 xNew;
			xNew.setZero();
			for (unsigned int j = 0; j < numNeighbors; j += 8)
			{
				const unsigned int count = std::min(numNeighbors - j, 8u);

				const Vector3f8 xj_avx = convertVec_zero(&initialMeshNeighbors[i][j], &x[0], count);
				const Vector3f8 xj0_avx = convertVec_zero(&initialMeshNeighbors[i][j], &x0[0], count);
				const Matrix3f8 Fj_avx = convertMat_zero(&initialMeshNeighbors[i][j], &F[0], count);

				const Vector3f8 xi_xj_0 = xi0_avx - xj0_avx;
				const Scalarf8 restVolume_j_avx = convert_zero(&initialMeshNeighbors[i][j], &restVolumes[0], count);
				const Scalarf8 W = CubicKernel_AVX::W(xi_xj_0);

				xNew += (Fj_avx * xi_xj_0 + xj_avx) * restVolume_j_avx * W;
			}
			mesh_xi = xNew.reduce();
			mesh_xi *= shepard[i];
		}
	}
}

#else

void computeF()
{
	const int numParticles = (int)activeParticles.size();

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < (int)numParticles; i++)
		{
			const unsigned int index = activeParticles[i];
			const Vector3r &xi = x[index];
			const Vector3r& xi0 = x0[index];
			F[index].setZero();

			const size_t numNeighbors = initialNeighbors[index].size();

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			for (unsigned int j = 0; j < numNeighbors; j++)
			{
				const unsigned int neighborIndex = initialNeighbors[index][j];

				const Vector3r &xj = x[neighborIndex];
				const Vector3r &xj0 = x0[neighborIndex];
				const Vector3r xj_xi = xj - xi;
				const Vector3r xi_xj_0 = xi0 - xj0;
				//const Vector3r correctedKernel = L[index] * gradKernelFct(xi_xj_0);
				const Vector3r V_gradW = precomp_V_gradW[precomputed_indices[i] + j];
				F[index] += xj_xi * V_gradW.transpose();
			}
		}
	}
}

void deformMesh()
{
	const int numVertices = (int)mesh.numVertices();
	auto& meshX0 = mesh.getVertices();

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < numVertices; i++)
		{
			Vector3r &mesh_xi = meshX[i];
			const Vector3r& mesh_xi0 = meshX0[i];

			const size_t numNeighbors = initialMeshNeighbors[i].size();

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			Vector3r deltaX;
			deltaX.setZero();
			for (unsigned int j = 0; j < numNeighbors; j++)
			{
				const unsigned int neighborIndex = initialMeshNeighbors[i][j];

				const Vector3r &xj = x[neighborIndex];
				const Vector3r &xj0 = x0[neighborIndex];
				const Vector3r xi_xj_0 = mesh_xi0 - xj0;
				deltaX += shepard[i] * restVolumes[neighborIndex] * (F[neighborIndex] * xi_xj_0 + xj) * kernelFct(xi_xj_0);
			}
			mesh_xi = deltaX;
		}
	}
}
#endif

void precomputeValues()
{
	const int numParticles = (int)activeParticles.size();
	unsigned int numVertices = mesh.numVertices();
	auto& meshX0 = mesh.getVertices();

#ifdef USE_AVX
	precomputed_indices8.clear();
	precomp_V_gradW8.clear();
	precomputed_indices8.resize(numParticles);
#else
	precomputed_indices.clear();
	precomp_V_gradW.clear();
	precomputed_indices.resize(numParticles);
#endif

	unsigned int sumNeighborParticles = 0;
	unsigned int sumNeighborParticles8 = 0;
	for (int i = 0; i < numParticles; i++)
	{
		const unsigned int index = activeParticles[i];
		const size_t numNeighbors = initialNeighbors[index].size();

#ifdef USE_AVX
		precomputed_indices8[i] = sumNeighborParticles8;

		// steps of 8 values due to avx
		sumNeighborParticles8 += (unsigned int) numNeighbors / 8u;
		if (numNeighbors % 8 != 0)
			sumNeighborParticles8++;
#else
		precomputed_indices[i] = sumNeighborParticles;
		sumNeighborParticles += (int) numNeighbors;
#endif
	}

#ifdef USE_AVX
	precomp_V_gradW8.resize(sumNeighborParticles8);
#else
	precomp_V_gradW.resize(sumNeighborParticles);
#endif

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < (int)numParticles; i++)
		{
			const unsigned int index = activeParticles[i];
			const Vector3r& xi0 = x0[index];
			const unsigned int numNeighbors = (unsigned int)initialNeighbors[index].size();

#ifdef USE_AVX
			const Vector3f8 xi0_avx(xi0);
			unsigned int base8 = precomputed_indices8[i];
			unsigned int idx = 0;
			Matrix3f8 L_avx(L[index]);
			for (unsigned int j = 0; j < numNeighbors; j += 8)
			{
				const unsigned int count = std::min(numNeighbors - j, 8u);
				const Scalarf8 restVolume_j_avx = convert_zero(&initialNeighbors[index][j], &restVolumes[0], count);
				const Vector3f8 xj0_avx = convertVec_zero(&initialNeighbors[index][j], &x0[0], count);

				const Vector3f8 gradW = CubicKernel_AVX::gradW(xi0_avx - xj0_avx);
				precomp_V_gradW8[base8 + idx] = (L_avx * gradW) * restVolume_j_avx;
				idx++;
			}
#else
			unsigned int base = precomputed_indices[i];

			for (unsigned int j = 0; j < numNeighbors; j++)
			{
				const unsigned int neighborIndex = initialNeighbors[index][j];
				const Vector3r& xj0 = x0[neighborIndex];
				Vector3r gradW = gradKernelFct(xi0 - xj0);
				precomp_V_gradW[base + j] = restVolumes[neighborIndex] * L[index] * gradW;
			}
#endif
		}
	}
}

void meshSkinning()
{
	currentFrame = startFrame;
	std::string fileName = convertFileName(output, currentFrame);
	if (overwriteExistingFiles || !FileSystem::fileExists(fileName))
		writeCurrentFrame();
	else
		LOG_INFO << "Skipping file: " << fileName;

	bool chk = true;
	while (chk)
	{
		std::string fileName = convertFileName(output, currentFrame);
		if (!overwriteExistingFiles && FileSystem::fileExists(fileName))
		{
			LOG_INFO << "Skipping file: " << fileName;
			currentFrame++;
			if (currentFrame > endFrame)
				break;
			continue;
		}

		LOG_INFO << "Reading frame: " << currentFrame;
		chk = readFrame(x, currentFrame);
		if (!chk)
			break;

		START_TIMING("computeF");
		computeF();
		STOP_TIMING_AVG;

		START_TIMING("deformMesh");
		deformMesh();
		STOP_TIMING_AVG;

		writeCurrentFrame();

		currentFrame++;
		if (currentFrame > endFrame)
			break;
	}
}

void exportOBJ()
{
	std::string fileName = convertFileName(output, currentFrame);
	LOG_INFO << "Writing: " << fileName;

	FileSystem::makeDir(FileSystem::getFilePath(FileSystem::normalizePath(output)));

	// Open the file
	std::ofstream outfile(fileName);
	if (!outfile)
	{
		LOG_WARN << "Cannot open a file to save OBJ mesh.";
		return;
	}

	// Header
	outfile << "# Created by SPlisHSPlasH version " << SPLISHSPLASH_VERSION << "\n";
	outfile << "g default\n";

	const std::vector<Vector3r>& vertices = meshX;
	const std::vector<unsigned int>& faces = mesh.getFaces();
	int n_vertices = (int)vertices.size();
	int n_triangles = (int)faces.size() / 3;

	// Vertices
	{
		for (int j = 0u; j < n_vertices; j++)
		{
			Vector3r x = vertices[j];
			outfile << "v " << x[0] << " " << x[1] << " " << x[2] << "\n";
		}
	}

	// faces
	{
		for (int j = 0; j < n_triangles; j++)
		{
			outfile << "f " << faces[3 * j + 0] + 1 << " " << faces[3 * j + 1] + 1 << " " << faces[3 * j + 2] + 1 << "\n";
		}
	}
	outfile.close();
}

// VTK expects big endian
template<typename T>
inline void swapByteOrder(T* v)
{
	constexpr size_t n = sizeof(T);
	uint8_t* bytes = reinterpret_cast<uint8_t*>(v);
	for (unsigned int c = 0u; c < n / 2; c++)
		std::swap(bytes[c], bytes[n - c - 1]);
}

void exportVTK()
{
	std::string fileName = convertFileName(output, currentFrame);
	LOG_INFO << "Writing: " << fileName;

	FileSystem::makeDir(FileSystem::getFilePath(FileSystem::normalizePath(output)));

	// Open the file
	std::ofstream outfile(fileName, std::ios::binary);
	if (!outfile)
	{
		LOG_WARN << "Cannot open a file to save VTK mesh.";
		return;
	}

#ifdef USE_DOUBLE
	const char* real_str = " double\n";
#else
	const char* real_str = " float\n";
#endif

	// Header
	outfile << "# vtk DataFile Version 4.2\n";
	outfile << "Created by SPlisHSPlasH version " << SPLISHSPLASH_VERSION << "\n";
	outfile << "BINARY\n";
	outfile << "DATASET UNSTRUCTURED_GRID\n";

	const std::vector<Vector3r>& vertices = meshX;
	const std::vector<unsigned int>& faces = mesh.getFaces();
	int n_vertices = (int)vertices.size();
	int n_triangles = (int)faces.size() / 3;

	// Vertices
	{
		std::vector<Vector3r> positions;
		positions.reserve(n_vertices);
		for (int j = 0u; j < n_vertices; j++)
		{
			Vector3r x = vertices[j];
			swapByteOrder(&x[0]);
			swapByteOrder(&x[1]);
			swapByteOrder(&x[2]);
			positions.emplace_back(x);
		}
		// export to vtk
		outfile << "POINTS " << n_vertices << real_str;
		outfile.write(reinterpret_cast<char*>(positions[0].data()), 3 * n_vertices * sizeof(Real));
		outfile << "\n";
	}

	// Connectivity
	{
		std::vector<int> connectivity_to_write;
		connectivity_to_write.reserve(4 * n_triangles);
		for (int tri_i = 0; tri_i < n_triangles; tri_i++)
		{
			int val = 3;
			swapByteOrder(&val);
			connectivity_to_write.push_back(val);
			val = faces[3 * tri_i + 0];
			swapByteOrder(&val);
			connectivity_to_write.push_back(val);
			val = faces[3 * tri_i + 1];
			swapByteOrder(&val);
			connectivity_to_write.push_back(val);
			val = faces[3 * tri_i + 2];
			swapByteOrder(&val);
			connectivity_to_write.push_back(val);
		}
		// export to vtk
		outfile << "CELLS " << n_triangles << " " << 4 * n_triangles << "\n";
		outfile.write(reinterpret_cast<char*>(&connectivity_to_write[0]), connectivity_to_write.size() * sizeof(int));
		outfile << "\n";
	}

	// Cell types
	{
		outfile << "CELL_TYPES " << n_triangles << "\n";
		int cell_type_swapped = 5;
		swapByteOrder(&cell_type_swapped);
		std::vector<int> cell_type_arr(n_triangles, cell_type_swapped);
		outfile.write(reinterpret_cast<char*>(&cell_type_arr[0]), cell_type_arr.size() * sizeof(int));
		outfile << "\n";
	}

	outfile.close();
}

void exportPLY()
{
	std::string fileName = convertFileName(output, currentFrame);
	LOG_INFO << "Writing: " << fileName;

	FileSystem::makeDir(FileSystem::getFilePath(FileSystem::normalizePath(output)));

	// Suppose these hold your data
	std::vector<std::array<double, 3>> meshVertexPositions;
	std::vector<std::vector<size_t>> meshFaceIndices;

	const std::vector<Vector3r>& vertices = meshX;
	const std::vector<unsigned int>& faces = mesh.getFaces();
	int n_vertices = (int)vertices.size();
	int n_triangles = (int)faces.size() / 3;

	meshVertexPositions.resize(n_vertices);
	for (int i = 0; i < n_vertices; i++)
	{
		meshVertexPositions[i] = { vertices[i][0], vertices[i][1], vertices[i][2] };
	}

	meshFaceIndices.resize(n_triangles);
	for (int i = 0; i < n_triangles; i++)
	{
		meshFaceIndices[i].resize(3);
		meshFaceIndices[i] = { faces[3*i], faces[3 * i+1], faces[3 * i+2] };
	}

	// Create an empty object
	happly::PLYData plyOut;

	// Add mesh data (elements are created automatically)
	plyOut.addVertexPositions(meshVertexPositions);
	plyOut.addFaceIndices(meshFaceIndices);

	// Write the object to file
	plyOut.write(fileName, happly::DataFormat::Binary);
}

void writeCurrentFrame()
{
	if (output_format == 1)
		exportVTK();
	else if (output_format == 2)
		exportPLY();
	else
		exportOBJ();
}


//==================================================================================================
/*!

  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)
*/
//==================================================================================================
#include <boost/simd/function/scalar/rem_pio2_medium.hpp>
#include <scalar_test.hpp>
#include <boost/simd/constant/inf.hpp>
#include <boost/simd/constant/minf.hpp>
#include <boost/simd/constant/nan.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/mone.hpp>
#include <boost/simd/constant/zero.hpp>
#include <boost/simd/constant/mzero.hpp>
#include <boost/simd/constant/pio_4.hpp>
#include <boost/simd/constant/pio_2.hpp>
#include <boost/simd/constant/half.hpp>

STF_CASE_TPL (" rem_pio2_medium",  STF_IEEE_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;

  using bs::rem_pio2_medium;
  using r_t = decltype(rem_pio2_medium(T()));

  STF_TYPE_IS( r_t, (std::pair<T,T>));

  {
    r_t res = rem_pio2_medium(bs::Zero<T>());
    STF_ULP_EQUAL( res.first, bs::Zero<T>(), 0.5);
    STF_ULP_EQUAL( res.second, bs::Zero<T>(), 0.5);
  }
  {
    r_t res = rem_pio2_medium(bs::Pio_4<T>());
    STF_ULP_EQUAL( res.first, bs::Zero<T>(), 0.5);
    STF_ULP_EQUAL( res.second, bs::Pio_4<T>(), 0.5);
  }
  {
    r_t res = rem_pio2_medium(bs::Pio_2<T>());
    STF_ULP_EQUAL( res.first, bs::One<T>(), 0.5);
    STF_ULP_EQUAL( res.second, bs::Zero<T>(), 0.5);
  }
  {
    r_t res = rem_pio2_medium(bs::Pio_4<T>()*bs::Half<T>());
    STF_ULP_EQUAL( res.first, bs::Zero<T>(), 0.5);
    STF_ULP_EQUAL(res.second, bs::Pio_4<T>()*bs::Half<T>(), 0.5);
  }
}


#include <SoftmaxModel.h>
#include <Utils.h>
#include <Eigen/Dense>
#include <cmath>
#include <algorithm>
#include <Checksum.h>

namespace cirrus {

SoftmaxModel::SoftmaxModel(uint64_t classes, uint64_t d) :
    nclasses(classes), d(d) {
    weights.resize(d);
    for (auto& v : weights)
        v.resize(nclasses);
}

// Row major order
SoftmaxModel::SoftmaxModel(const FEATURE_TYPE* data, uint64_t nclasses, uint64_t d) :
    nclasses(nclasses), d(d) {
    weights.resize(d);

    for (auto& v : weights) {
        v.resize(nclasses);
    }

    loadSerialized(data);
}

SoftmaxModel::SoftmaxModel(
        std::vector<std::vector<FEATURE_TYPE>> data, uint64_t nclasses, uint64_t d) :
    nclasses(nclasses), d(d) {
    weights.resize(d);

    int i = 0;
    for (auto& v : weights) {
        v.resize(nclasses);
        std::copy(data[i].begin(), data[i].end(), v.begin());
    }
}

void SoftmaxModel::randomize() {
  for (auto& vv : weights) {
    for (auto& v : vv) {
      v = 0.01 * (get_rand_between_0_1() - 0.5);
    }
  }
}

/** Serialization Format:
  * d (uint64_t)
  * n_classes (uint64_t)
  * d * nclasses weights (FEATURE_TYPE)
  */
std::pair<std::unique_ptr<char[]>, uint64_t>
SoftmaxModel::serialize() const {
    std::pair<std::unique_ptr<char[]>, uint64_t> res;

    res.second = getSerializedSize();
    res.first.reset(new char[res.second]);

    uint64_t* uint_ptr = reinterpret_cast<uint64_t*>(res.first.get());
    *uint_ptr++ = d;
    *uint_ptr++ = nclasses;

    // copy contents from weights vector to the serialized buffer
    FEATURE_TYPE* w = reinterpret_cast<FEATURE_TYPE*>(uint_ptr);
    for (uint64_t i = 0; i < d; ++i) {
        for (uint64_t j = 0; j < nclasses; ++j) {
            w[i * nclasses + j] = weights[i][j];
        }
    }
    return res;
}

void SoftmaxModel::serializeTo(void* mem) const {
    uint64_t* uint_ptr = reinterpret_cast<uint64_t*>(mem);
    *uint_ptr++ = d;
    *uint_ptr++ = nclasses;

    FEATURE_TYPE* w = reinterpret_cast<FEATURE_TYPE*>(uint_ptr);
    for (uint64_t i = 0; i < d; ++i) {
        for (uint64_t j = 0; j < nclasses; ++j) {
            w[i * nclasses + j] = weights[i][j];
        }
    }
}

void SoftmaxModel::loadSerialized(const void* data) {
    const uint64_t* uint_ptr = reinterpret_cast<const uint64_t*>(data);
    uint64_t dim = *uint_ptr++;
    uint64_t classes = *uint_ptr++;

    weights.resize(dim);
    for (auto& v : weights) {
        v.resize(classes);
    }

    const FEATURE_TYPE* w = reinterpret_cast<const FEATURE_TYPE*>(uint_ptr);

    for (uint64_t i = 0; i < d; ++i) {
        std::copy(w + i * nclasses, w + (i + 1) * nclasses, weights[i].begin());
    }
}

std::unique_ptr<CirrusModel> SoftmaxModel::deserialize(void* data,
            uint64_t /* size */) const {
    uint64_t* uint_ptr = reinterpret_cast<uint64_t*>(data);
    uint64_t dim = *uint_ptr++;
    uint64_t classes = *uint_ptr++;

    std::unique_ptr<SoftmaxModel> model =
        std::make_unique<SoftmaxModel>(
                reinterpret_cast<FEATURE_TYPE*>(uint_ptr), classes, dim);
    return model;
}

std::unique_ptr<CirrusModel> SoftmaxModel::copy() const {
    std::unique_ptr<SoftmaxModel> new_model =
        std::make_unique<SoftmaxModel>(weights, nclasses, d);
    return new_model;
}

void SoftmaxModel::sgd_update(
        double learning_rate, const ModelGradient* gradient) {
    const SoftmaxGradient* grad =
        dynamic_cast<const SoftmaxGradient*>(gradient);

    if (grad == nullptr) {
        throw std::runtime_error("Error in dynamic cast");
    }

    for (uint64_t i = 0; i < d; ++i) {
        for (uint64_t j = 0; j < nclasses; ++j) {
            weights[i][j] -= learning_rate * grad->weights[i][j];
        }
    }
}

uint64_t SoftmaxModel::getSerializedSize() const {
    return sizeof(FEATURE_TYPE) * d * nclasses + sizeof(uint64_t) * 2;
}

std::unique_ptr<ModelGradient> SoftmaxModel::minibatch_grad(
            const Matrix& m,
            FEATURE_TYPE* labels,
            uint64_t labels_size,
            double epsilon) const {
    assert(labels_size == m.rows);

    const FEATURE_TYPE* m_data = reinterpret_cast<const FEATURE_TYPE*>(m.data.get());
    Eigen::Matrix<FEATURE_TYPE, -1, -1> dataset(m.rows, m.cols);
    //Eigen::MatrixXd dataset(m.rows, m.cols);
    for (unsigned int row = 0; row < m.rows; ++row) {
        for (unsigned int col = 0; col < m.cols; ++col) {
            dataset(row, col) = m_data[row * m.cols + col];
        }
    }

    Eigen::Matrix<FEATURE_TYPE, -1, -1> W(dataset.cols(), nclasses);
    //Eigen::MatrixXd W(dataset.cols(), nclasses);
    for (unsigned int d = 0; d < dataset.cols(); ++d) {
        for (unsigned int k = 0; k < nclasses; ++k) {
            W(d, k) = weights[d][k];
        }
    }

    auto scores = dataset * W;

    // we exponentiate those scores
    // [N * K]
    auto exp_scores = scores.unaryExpr([](FEATURE_TYPE v) {
        FEATURE_TYPE new_v = std::exp(v);
        if (std::isnan(new_v) || std::isinf(new_v)) {
            throw std::runtime_error("Invalid value after exp");
        }

        return new_v;
    });

    // [N * K]
    auto exp_scores_sum = exp_scores.sum();

    // [N * K]
    auto probs = exp_scores.unaryExpr([exp_scores_sum](FEATURE_TYPE v) {
            return v / exp_scores_sum;
            });

    std::vector<FEATURE_TYPE> logprobs(dataset.rows());
    FEATURE_TYPE sum = 0;

    for (unsigned int i = 0; i < dataset.rows(); ++i) {
        if (probs(i, labels[i]) < 1e-10) {
            logprobs[i] -= 1e-10;
        } else {
            logprobs[i] -= std::log(probs(i, labels[i]));
        }
        if (std::isnan(logprobs[i]) || std::isinf(logprobs[i])) {
            std::cerr << probs(i, labels[i]) << " " << labels[i] << std::endl;
            throw std::runtime_error("Invalid logprob");
        }
        sum += logprobs[i];
    }

    // [N * K]
    Eigen::Matrix<FEATURE_TYPE, -1, -1> dscores;
    //Eigen::MatrixXd dscores;
    dscores.noalias() = probs;
    for (unsigned int i = 0; i < dataset.rows(); ++i) {
        dscores(i, labels[i]) -= 1;
        dscores(i, labels[i]) /= dataset.rows();
    }

    Eigen::Matrix<FEATURE_TYPE, -1, -1> dW;
    //Eigen::MatrixXd dW;
    // [D * N] * [N * K] = [D * K]
    dW.noalias() = dataset.transpose() * dscores;
    dW += epsilon * W;

    assert(static_cast<uint64_t>(dW.rows()) == d &&
           static_cast<uint64_t>(dW.cols()) == nclasses);

    // transform dW eigen matrix into std::vector
    std::vector<std::vector<FEATURE_TYPE>> ret_gradient;
    ret_gradient.resize(d);
    for (uint64_t i = 0; i < d; ++i) {
        ret_gradient[i].resize(nclasses);
        for (uint64_t j = 0; j < nclasses; ++j) {
            ret_gradient[i][j] = dW(i, j);
        }
    }

    return std::make_unique<SoftmaxGradient>(ret_gradient);
}

std::pair<double, double> SoftmaxModel::calc_loss(Dataset& data) const {
    const Matrix& m = data.samples_;
    // XXX Fix, there is some code repetition here
    const FEATURE_TYPE* m_data = reinterpret_cast<const FEATURE_TYPE*>(m.data.get());
    Eigen::Matrix<FEATURE_TYPE, -1, -1> dataset(m.rows, m.cols);
    //Eigen::MatrixXd dataset(m.rows, m.cols);
    for (unsigned int row = 0; row < m.rows; ++row) {
        for (unsigned int col = 0; col < m.cols; ++col) {
            dataset(row, col) = m_data[row * m.cols + col];
        }
    }

    Eigen::Matrix<FEATURE_TYPE, -1, -1> W(dataset.cols(), nclasses);
    //Eigen::MatrixXd W(dataset.cols(), nclasses);
    for (unsigned int d = 0; d < dataset.cols(); ++d) {
        for (unsigned int k = 0; k < nclasses; ++k) {
            W(d, k) = weights[d][k];
        }
    }

    auto scores = dataset * W;

    // we exponentiate those scores
    // [N * K]
    auto exp_scores = scores.unaryExpr([](FEATURE_TYPE v) {
        FEATURE_TYPE new_v = std::exp(v);
        if (std::isnan(new_v) || std::isinf(new_v)) {
            throw std::runtime_error("Invalid value after exp");
        }

        return new_v;
    });

    // [N * K]
    auto exp_scores_sum = exp_scores.sum();

    // [N * K]
    auto probs = (exp_scores.unaryExpr([exp_scores_sum](FEATURE_TYPE v) {
        return v / exp_scores_sum;
    })).eval();

    std::vector<FEATURE_TYPE> logprobs(dataset.rows());
    FEATURE_TYPE sum = 0;
    uint64_t count_wrong = 0;  // how many samples are wrongly classified

    for (unsigned int i = 0; i < dataset.rows(); ++i) {
        FEATURE_TYPE class_i = reinterpret_cast<const FEATURE_TYPE*>(data.labels_.get())[i];
        for (int c = 0; c < probs.cols(); ++c) {
            // if there is a different class with higher probability
            // we count it as wrong
            if (probs(i, c) > probs(i, class_i)) {
                count_wrong++;
                break;
            }
        }

        if (probs(i, class_i) < 1e-10) {
            logprobs[i] = 1e-10;
        } else {
            logprobs[i] = -std::log(probs(i, class_i));
        }
        if (std::isnan(logprobs[i]) || std::isinf(logprobs[i])) {
            std::cerr
                << probs(i, class_i) << " "
                << class_i << std::endl;
            throw std::runtime_error("Invalid logprob");
        }
        sum += logprobs[i];
    }

    FEATURE_TYPE accuracy = (1.0 - (1.0 * count_wrong / dataset.rows()));
    std::cout
        << "Accuracy: " << accuracy
        << " wrong: " << count_wrong << " samples: " << dataset.rows()
        << std::endl;

    // constant
    FEATURE_TYPE data_loss = sum / dataset.rows();

    return std::make_pair(data_loss, accuracy);
}

/**
 * Return the size of the gradient when serialized
 */
uint64_t SoftmaxModel::getSerializedGradientSize() const {
    return nclasses * d * sizeof(FEATURE_TYPE);
}

std::unique_ptr<ModelGradient> SoftmaxModel::loadGradient(void* mem) const {
    auto grad = std::make_unique<SoftmaxGradient>(nclasses, d);
    grad->loadSerialized(mem);
    return grad;
}

double SoftmaxModel::checksum() const {
    double sum = 0;
    for (uint64_t i = 0; i < weights.size(); ++i) {
        sum += crc32(weights[i].data(), weights[i].size() * sizeof(FEATURE_TYPE));
    }
    return sum;
}

} // namespace cirrus


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          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
//==============================================================================
// cover for functor ellik in scalar mode
#include <nt2/elliptic/include/functions/ellipke.hpp>

#include <boost/simd/sdk/simd/io.hpp>
#include <cmath>
#include <iostream>
#include <nt2/include/constants/eps.hpp>
#include <nt2/include/functions/ellpk.hpp>
#include <nt2/include/functions/ellpe.hpp>
#include <nt2/include/constants/zero.hpp>
#include <nt2/include/functions/oneminus.hpp>
#include <nt2/sdk/meta/as_integer.hpp>
#include <nt2/sdk/unit/args.hpp>
#include <nt2/sdk/unit/module.hpp>
#include <nt2/sdk/unit/tests/cover.hpp>
#include <vector>

template < class T > struct ellipk1
{
  T operator()(const T & x)
  {
    T a1 = nt2::Zero<T>();
    T a0 = nt2::ellipke(x, nt2::Eps<T>(),a1);
    return a0;
  }
};

 template < class T > struct ellipk2
{
  T operator()(const T & x)
  {
    T a1 = nt2::Zero<T>();
    T a0 = nt2::ellipke(x, nt2::Eps<T>(), a1);
    return a1;
  }
};

NT2_TEST_CASE_TPL(ellpk_0,  NT2_SIMD_REAL_TYPES)
{
  using nt2::unit::args;
  const std::size_t NR = args("samples", NT2_NB_RANDOM_TEST);
  const double ulpd = args("ulpd", 2);

  typedef typename nt2::meta::as_integer<T>::type iT;
  const T min0 = args("min0", T(0));
  const T max0 = args("max0", T(1));
  std::cout << "Argument samples #0 chosen in range: [" << min0 << ",  " << max0 << "]" << std::endl;
  NT2_CREATE_BUF(a0,T, NR, min0, max0);

  std::vector<T> ref1(NR), ref2(NR);
  for(std::size_t i=0; i!=NR; ++i)
  {
    ref1[i] = nt2::ellpk(nt2::oneminus(a0[i]));
    ref2[i] = nt2::ellpe(nt2::oneminus(a0[i]));
  }

  NT2_COVER_FN_ULP_EQUAL(ellipk1<T>() , ((T, a0)), ref1, ulpd);
  NT2_COVER_FN_ULP_EQUAL(ellipk2<T>() , ((T, a0)), ref2, ulpd);

}


#include <vector>
#include "./helpers.hpp"

#include <algorithm>
#include <numeric>
#include<boost/foreach.hpp>

#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/mean.hpp>

using namespace  boost::accumulators;

template<class Innercode, class Outercode, class DNAmap>
class EnDecode {
	public:
	Innercode innercode;
	Outercode outercode;
	DNAmap dnamap;
	vector< vector< typename Innercode::Symbol > > groundtruth;
	unsigned l;
	unsigned m;
    unsigned numblocks;

	EnDecode(){};
	EnDecode(const Innercode& innercode_, const Outercode& outercode_, const DNAmap& dnamap_,unsigned l_){
		innercode = innercode_;
		outercode = outercode_;
		dnamap = dnamap_;
		l = l_;
		assert(innercode.n>l);
		m = innercode.k - l;

	};

	void encode(string& str, vector<string>& urn);

	void decode(string& str, const vector<string>& drawnseg);
};

////////////////////////////
template<class Innercode, class Outercode, class DNAmap>
void EnDecode<Innercode,Outercode,DNAmap>::encode(string& str, vector<string>& urn){

    //const int prime = pfe::prime;

    // convert the string to a representation in GF(47)
    vector<typename Innercode::Symbol> data_b47;
    char2pfe<typename Innercode::Symbol>(str,data_b47); // data in pfe vector

    // add the filelength at the beginning:
    vector<typename Innercode::Symbol> data_b47_lv = tobase<typename Innercode::Symbol>(str.size(),47,6);
    data_b47.insert(data_b47.begin(),data_b47_lv.begin(),data_b47_lv.end()); // not very efficient..

	// determine the number of blocks
    if(data_b47.size() % (m*outercode.k) != 0) {
        numblocks = data_b47.size()/(m*outercode.k)+1;
		unsigned oldsize = data_b47.size();
		data_b47.resize(numblocks*m*outercode.k);
    	// fill the rest with zeros..
		for(unsigned i=oldsize;i<data_b47.size();++i) data_b47[i] = typename Innercode::Symbol(0);
	} else numblocks = data_b47.size()/(m*outercode.k);

    urn.resize(numblocks*outercode.n); // the number of DNA segments

    unsigned b = 0;

    for(unsigned b=0;b<numblocks;++b){ // run over blocks

        vector<typename Outercode::Symbol> infvec(outercode.k); // outer information vector

		for(unsigned i=0;i<outercode.k;++i) {
			vector<typename Innercode::Symbol> tmpefevec(data_b47.begin()+b*m*outercode.k+i*m,data_b47.begin()+b*m*outercode.k+i*m+m);
			assert(tmpefevec.size() == m);
			infvec[i] = typename Outercode::Symbol(tmpefevec);
			//cout << infvec[i] << endl;
		}

		vector<typename Outercode::Symbol> c; // outer codeword
		outercode.RSencode(infvec,c); // encode the information, obtain outer codeword

        for(unsigned i =0;i<outercode.n;++i){ // run over elements in a block

            // compute and append index of each
            vector<typename Innercode::Symbol> indexpfe = tobase<typename Innercode::Symbol>(i+outercode.n*b,47,l);
			vector<typename Innercode::Symbol> infveci = c[i].tovec();
			//append index at back:
			//infveci.insert(infveci.end(),indexpfe.begin(),indexpfe.end());
			//append index at front:
			infveci.insert(infveci.begin(),indexpfe.begin(),indexpfe.end());
			assert(infveci.size()== innercode.k );
			//cout << "infvec+index: " << infveci << endl;

            // encode the inner code, write it to urn[i]
            vector<typename Innercode::Symbol> icw;
			innercode.RS_shortened_encode(infveci,icw);
			dnamap.cw2frag(icw,urn[i+outercode.n*b]);
        }
		cout << "encoded block " << b << " of " << numblocks << endl;
    }

	// return numblocks;

}

template<class T>
bool pairCompare(const T & x, const T & y) {
  return x.second < y.second;
}

////////////////////////////
template<class Innercode, class Outercode, class DNAmap>
void EnDecode<Innercode,Outercode,DNAmap>::decode(string& str, const vector<string>& drawnseg){

	//// inner decoding

	// to keep track
	pair<unsigned,unsigned> erctric(0,0);
	vector<int> frag_freqs(numblocks*outercode.n, 0); // to count the frequencies of each DNA sequence
	unsigned ic_decerr = 0; // counts the inner decoding errors
	unsigned errctr = 0;
	unsigned numfrag = 0;

	typedef typename Innercode::Symbol pfe;

	vector<vector<pfe> > segm_ordered(numblocks*outercode.n, vector<pfe>());

	typedef map<vector<pfe>, unsigned > seqctrmap;
	vector< seqctrmap > segm_ordered_map(numblocks*outercode.n, seqctrmap() );
	vector<unsigned> curerr(numblocks*outercode.n, innercode.n ); // error of the element in segm_ordered_map;

	// run over all sequences
	BOOST_FOREACH(const string& sLine, drawnseg){

		vector<pfe> rececw; // the received block
		pair<unsigned,unsigned> erctrictmp;
		vector<pfe> irec(innercode.k); // recovered information from inner codeword

		dnamap.frag2cw(rececw,sLine); // map DNA segment to pfe vector
		erctrictmp = innercode.RS_shortened_decode(irec,rececw);

		if(erctrictmp.second != innercode.n_u){ // else we have a decoding error
			erctric.first += erctrictmp.first;
			erctric.second += erctrictmp.second;
			unsigned ind = frombase<pfe>(vector<pfe>(irec.begin(),irec.begin()+l),47);
			if(ind < frag_freqs.size()){
				if(erctrictmp.second < curerr[ind]){ // discard list,
					curerr[ind] = erctrictmp.second;
					segm_ordered_map[ind].empty();
					segm_ordered_map[ind].insert(make_pair(irec,1));
				} else if (erctrictmp.second == curerr[ind]){
					typename seqctrmap::iterator cur = segm_ordered_map[ind].find(irec);
					if(cur != segm_ordered_map[ind].end()){ // segment already appeared
						cur->second++;
					} else {
						segm_ordered_map[ind].insert(make_pair(irec,1));
					}

				}

				// compare to groundtruth whether there is a decoding error

				if(! groundtruth.empty() ){
					if(groundtruth[ind] != irec) {
						errctr++;
					} else {
						// this fragment has been decoded without error
						frag_freqs[ind]++;
					}
				}
			}else{ // if the index is not in the appropriate range, there must be an error
				errctr ++;
			}
		} else {ic_decerr++;}
		numfrag++;
	}

for(unsigned ind=0;ind<segm_ordered_map.size();++ind){
	if(! segm_ordered_map[ind].empty() ){

		// take the one (out of all segments with smallest error) with largest frequency as the estimate
		typename seqctrmap::iterator max = max_element(segm_ordered_map[ind].begin(), segm_ordered_map[ind].end(), pairCompare<typename seqctrmap::value_type> );
		segm_ordered[ind] = max->first;

/*
		if( segm_ordered[ind] != groundtruth[ind]){
			cout << ind << "	" << curerr[ind] <<  ": ";
			for (seqctrmap::iterator it = segm_ordered_map[ind].begin();it != segm_ordered_map[ind].end();++it){

				cout << "(" << it->second << ", " << (groundtruth[ind] == it->first) << ") ";
			}
			cout << endl;
		}
*/
	}
}

cout << "fragm. error prob: " << (float) errctr / (float) numfrag << endl;
cout << "numer of sequences: " << numfrag << endl;
cout << "inner code: " << (float)erctric.first / (float) numfrag << " erasures on average corrected" << endl;
cout << "inner code: " << (float)erctric.second/ (float) numfrag << " errors on average corrected" << endl;
cout << "inner code, dec. errors: " << ic_decerr << endl;

	unsigned total_erasure_ctr = 0;
	unsigned total_error_ctr = 0;
	unsigned total_blockerror_ctr = 0;
	//// determine the number of errors and erasures in each block
	for(unsigned b=0;b<numblocks;++b){
		unsigned block_erasure_ctr = 0;
		unsigned block_error_ctr = 0;
		for(unsigned i =0;i<outercode.n;++i){ // run over elements in a block
			if( ! segm_ordered[b*outercode.n+i].empty()){
            	//compare to groundtruth
				if(! groundtruth.empty() ){
					if(groundtruth[b*outercode.n+i] != segm_ordered[b*outercode.n+i]) block_error_ctr++;
				}
			} // else we have an erasure
            else{
                block_erasure_ctr++;
            }
		}
		total_erasure_ctr += block_erasure_ctr;
		total_error_ctr += block_error_ctr;

		cout << b <<"-> errors: " << block_error_ctr << "	erasures: " << block_erasure_ctr <<"	total:" << block_error_ctr*2+block_erasure_ctr << endl;

		if(block_error_ctr*2+block_erasure_ctr > outercode.n-outercode.k){
			total_blockerror_ctr++;
		}

	}

	cout << "num block errors: " << total_blockerror_ctr << endl;
	cout << "OC: symbol err. prob.: " << (float) total_error_ctr / (float) (outercode.n*numblocks) << endl;
	cout << "OC: symbol era. prob.: " << (float) total_erasure_ctr / (float) (outercode.n*numblocks) << endl;

	cout << "OC: symbol err. prob.: " << total_error_ctr  << endl;
	cout << "OC: symbol era. prob.: " << total_erasure_ctr << endl;
	//cout << "(" << frac << "," << (double) total_error_ctr/ (double) numit << ")" << endl;

	// decode the blocks

	unsigned filelengthstr;
	for(unsigned b=0;b<numblocks;++b){ // run over blocks
        vector<typename Outercode::Symbol >reccw(outercode.n);
		unsigned erasure_ctr = 0;
		for(unsigned i =0;i<outercode.n;++i){ // run over elements in a block
			if( ! segm_ordered[b*outercode.n+i].empty()){
				vector<pfe> tmppfevec = vector<pfe>(segm_ordered[b*outercode.n + i].begin()+l, segm_ordered[b*outercode.n + i].end() );
				reccw[i] = typename Outercode::Symbol( tmppfevec );
			} // else we have an erasure
			else{
				reccw[i] = typename Outercode::Symbol();
				erasure_ctr++;
			}
		}

		cout << erasure_ctr << " many erasures in block " << b << " of length " << outercode.n <<  endl;

		// decode outer codword

		vector<typename Outercode::Symbol> infvecrec;
		//pair<unsigned,unsigned> erctroc = RS_decode_spec<efe,dftefe>(infvecrec,reccw,a,k,n,dftefe_d);
		pair<unsigned,unsigned> erctroc = outercode.RS_decode_spec(infvecrec,reccw);
		cout << "decoded block " << b << endl;
		cout << "outer code: " << erctroc.first << " errasures, " << erctroc.second << " errors corrected"<< endl;

		for(unsigned i=0;i<outercode.k;++i){
			string tmp;
			vector<pfe> tmpinfvec;
			if( !((b==0) && (i==0)) ){
				tmpinfvec = infvecrec[i].tovec();
			}else{
				//determine filelength first
				tmpinfvec = infvecrec[i].tovec();
        		filelengthstr = frombase<pfe>(vector<pfe>(tmpinfvec.begin(),tmpinfvec.begin()+6  ),47);
				tmpinfvec = vector<pfe>(tmpinfvec.begin()+6,tmpinfvec.end());
			}
			pfe2char<pfe>(tmp,tmpinfvec);
			str.append(tmp);
		}
	}

	str.resize(filelengthstr);

}


/*
 * binary_svm_split.hpp
 *
 *  Created on: Oct 12, 2013
 *      Author: mdantone
 */
#include <boost/serialization/shared_ptr.hpp>
#include "boost/smart_ptr.hpp"
#include "cpp/learning/svm/liblinear_svm.hpp"
#include "cpp/learning/svm/liblinear_svm_problem.hpp"

#ifndef BINARY_SVM_SPLIT_HPP_
#define BINARY_SVM_SPLIT_HPP_

namespace learning
{
namespace forest
{

class BinarySVMSplit
{
public:
  BinarySVMSplit(){
    info = boost::numeric::bounds<double>::lowest();
    gain = boost::numeric::bounds<double>::lowest();
    oob = boost::numeric::bounds<double>::highest();
    threshold = 0;
    used_subsampling = true;
  };

  template<typename Sample>
  float get_feature_value(const Sample* sample) const {
    const cv::Mat_<float>& desc = sample->get_descriptor();
    return predict(desc);
  }

  template<typename Sample>
  bool eval( const Sample* sample ) const {
    return get_feature_value(sample) <= threshold;
  }

  template<typename Sample>
  void split(const std::vector<Sample*>& data,
    std::vector<Sample*>& set_a, std::vector<Sample*>& set_b) const {

    std::vector< std::pair<float, unsigned int> >index(data.size());
    for (unsigned int l = 0; l < data.size(); ++l) {
      index[l].first = get_feature_value(data[l]);
      index[l].second = l;
    }
    std::sort(index.begin(), index.end());
    for (unsigned int l = 0; l < index.size(); ++l) {
      int index_sample = index[l].second;
      if( index[l].first  <= threshold ) {
        set_a.push_back(data[index_sample]);
      }else{
        set_b.push_back(data[index_sample]);

      }
    }
    // reverse set a
    std::reverse(set_a.begin(), set_a.end());
    CHECK_GE( get_feature_value(set_a[0]),
              get_feature_value(set_a[set_a.size()-1]));
    CHECK_LE( get_feature_value(set_b[0]),
              get_feature_value(set_b[set_b.size()-1]));
  }

  template<typename Sample>
  void train(const std::vector<Sample*>& set,
      std::vector<int>& pos_sample_ids,
      std::vector<int>& neg_sample_ids,
      std::vector<int> class_weights_labels = std::vector<int>(),
      std::vector<double> class_weights = std::vector<double>() ) {

    awesomeness::learning::svm::LibLinearSvmParameters svm_param;

    boost::scoped_ptr<awesomeness::learning::svm::LibLinearSvmProblem> svm_problem;
    boost::scoped_ptr<awesomeness::learning::svm::LibLinearSvm> svm;

    int n_samples = pos_sample_ids.size() + neg_sample_ids.size();
    cv::Mat_<float> desc = set[0]->get_descriptor();

    svm_problem.reset(
        (awesomeness::learning::svm::LibLinearSvmProblem*)awesomeness::learning::svm::SvmProblem::create(
            svm_param, 2, n_samples, n_samples * desc.cols));

    for(int i=0; i < pos_sample_ids.size(); i++) {
      int pos_index = pos_sample_ids[i];
      svm_problem->push_sample(set[pos_index]->get_descriptor(), 1);
    }

    // add negative examples
    for(int i=0; i < neg_sample_ids.size(); i++) {
      int neg_index = neg_sample_ids[i];
      svm_problem->push_sample(set[neg_index]->get_descriptor(), 0);
    }

    if(class_weights.size() == 0 ) {
      svm.reset( (awesomeness::learning::svm::LibLinearSvm*)svm_problem->train());
    }else{
      svm.reset( (awesomeness::learning::svm::LibLinearSvm*)svm_problem->train(class_weights_labels, class_weights));
    }
    std::vector<double> svm_weights;
    svm->get_weights(svm_weights);

    weights = cv::Mat::zeros(1, svm_weights.size(),
        cv::DataType<float>::type);
    for(int i=0; i < svm_weights.size(); i++) {
      weights(i) = static_cast<float>(svm_weights[i]);
    }
  }

  float predict(const cv::Mat_<float>& desc) const {
    int n = MIN(desc.cols, weights.cols);
    double r = 0.0;
    for(int i=0; i < n; i++) {
      r += desc(i)*weights(i);
    }
    CHECK_EQ(desc.cols, weights.cols );
    return r;
  }

  double info;
  double gain;
  double oob;
  cv::Mat_<float> weights;
  float threshold;
  bool used_subsampling;

private:
  friend class boost::serialization::access;
  template<class Archive>
  void serialize(Archive & ar, const unsigned int version) {
     ar & info;
     ar & gain;
     ar & oob;
     ar & weights;
     ar & threshold;
  }
};

}/* forest */
}/* learning */
#endif /* BINARY_SVM_SPLIT_HPP_ */


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

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

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

#include <boost/simd/function/simd/lo.hpp>
#include <boost/simd/function/simd/shr.hpp>
#include <boost/dispatch/function/overload.hpp>
#include <boost/dispatch/meta/as_integer.hpp>
#include <boost/dispatch/meta/scalar_of.hpp>
#include <boost/config.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  BOOST_DISPATCH_OVERLOAD ( hi_
                          , (typename A0)
                          , bd::cpu_
                          , bd::generic_< bd::arithmetic_<A0> >
                          )
  {
    using result_t = bd::as_integer_t<A0,unsigned>;
    using s_t = bd::scalar_of_t<result_t>;

    BOOST_FORCEINLINE result_t operator() ( A0 const& a0) const BOOST_NOEXCEPT
    {
      const s_t half_bits = sizeof(s_t)*(CHAR_BIT/2);
      return lo( shr(a0, half_bits) );
    }
  };
} } }

#endif


#ifndef REF_MPL_HPP
#define REF_MPL_HPP

#include <boost/mpl/vector.hpp>
#include <boost/mpl/empty.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/pop_front.hpp>
#include <boost/utility.hpp>

namespace ref
{
    typedef boost::mpl::vector<> EmptyList;

    template < typename T, typename Enabled = void >
    struct InheritFromList :
        public boost::mpl::front< T >::type,
        public InheritFromList< typename boost::mpl::pop_front< T >::type >
    {};

    template < typename T >
    struct InheritFromList< T,
        typename boost::enable_if< typename boost::mpl::empty< T >::type >::type >
    {};
} // namespace ref

#endif // REF_MPL_HPP


#define BOOST_TEST_MODULE TestSkylineLU
#include <boost/test/unit_test.hpp>

#include <amgcl/io/mm.hpp>
#include <amgcl/adapter/crs_tuple.hpp>
#include <amgcl/profiler.hpp>
#include "sample_problem.hpp"

namespace amgcl {
    profiler<> prof;
}

BOOST_AUTO_TEST_SUITE( test_io )

BOOST_AUTO_TEST_CASE(io_mm)
{
    std::vector<ptrdiff_t> ptr, ptr2;
    std::vector<ptrdiff_t> col, col2;
    std::vector<double>    val, val2;
    std::vector<double>    rhs, rhs2;

    size_t n = sample_problem(16, val, col, ptr, rhs);

    auto A = std::tie(n, ptr, col, val);

    amgcl::io::mm_write("test_io_crs.mm", A);
    amgcl::io::mm_write("test_io_vec.mm", rhs.data(), n, 1);

    size_t rows, cols;
    std::tie(rows, cols) = amgcl::io::mm_reader("test_io_crs.mm")(ptr2, col2, val2);

    BOOST_REQUIRE_EQUAL(n, rows);
    BOOST_REQUIRE_EQUAL(n, cols);
    BOOST_REQUIRE_EQUAL(ptr.back(), ptr2.back());
    for(size_t i = 0; i < n; ++i) {
        BOOST_REQUIRE_EQUAL(ptr[i], ptr2[i]);
        for(ptrdiff_t j = ptr[i], e = ptr[i+1]; j < e; ++j) {
            BOOST_CHECK_EQUAL(col[j], col2[j]);
            BOOST_CHECK_SMALL(val[j] - val2[j], 1e-12);
        }
    }

    std::tie(rows, cols) = amgcl::io::mm_reader("test_io_vec.mm")(rhs2);
    BOOST_REQUIRE_EQUAL(n, rows);
    BOOST_REQUIRE_EQUAL(1, cols);
    for(size_t i = 0; i < n; ++i) {
        BOOST_CHECK_SMALL(rhs[i] - rhs2[i], 1e-12);
    }
}

BOOST_AUTO_TEST_SUITE_END()