/**
 *****************************************************************************
 * @author     This file is part of tinyram_snark, developed by SCIPR Lab
 *             and contributors (see AUTHORS).
 * @copyright  MIT license (see LICENSE file)
 *****************************************************************************/
#include <fstream>
#include <iostream>
#ifndef MINDEPS
#include <boost/program_options.hpp>
#endif

#include <tinyram_snark/common/default_types/tinyram_ppzksnark_pp.hpp>
#include <tinyram_snark/relations/ram_computations/rams/tinyram/tinyram_params.hpp>
#include <tinyram_snark/zk_proof_systems/ppzksnark/ram_ppzksnark/ram_ppzksnark.hpp>

#ifndef MINDEPS
namespace po = boost::program_options;

bool process_prover_command_line(const int argc, const char** argv,
                                 std::string &processed_assembly_fn,
                                 std::string &proving_key_fn,
                                 std::string &primary_input_fn,
                                 std::string &auxiliary_input_fn,
                                 std::string &proof_fn)
{
    try
    {
        po::options_description desc("Usage");
        desc.add_options()
            ("help", "print this help message")
            ("processed_assembly", po::value<std::string>(&processed_assembly_fn)->required())
            ("proving_key", po::value<std::string>(&proving_key_fn)->required())
            ("primary_input", po::value<std::string>(&primary_input_fn)->required())
            ("auxiliary_input", po::value<std::string>(&auxiliary_input_fn)->required())
            ("proof", po::value<std::string>(&proof_fn)->required());

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);

        if (vm.count("help"))
        {
            std::cout << desc << "\n";
            return false;
        }

        po::notify(vm);
    }
    catch(std::exception& e)
    {
        std::cerr << "Error: " << e.what() << "\n";
        return false;
    }

    return true;
}
#endif

using namespace tinyram_snark;

int main(int argc, const char * argv[])
{
    default_tinyram_ppzksnark_pp::init_public_params();

#ifdef MINDEPS
    std::string processed_assembly_fn = "processed_assembly.txt";
    std::string proving_key_fn = "proving_key.txt";
    std::string primary_input_fn = "primary_input.txt";
    std::string auxiliary_input_fn = "auxiliary_input.txt";
    std::string proof_fn = "proof.txt";
#else
    std::string processed_assembly_fn;
    std::string proving_key_fn;
    std::string primary_input_fn;
    std::string auxiliary_input_fn;
    std::string proof_fn;

    if (!process_prover_command_line(argc, argv, processed_assembly_fn,
                                     proving_key_fn, primary_input_fn, auxiliary_input_fn, proof_fn))
    {
        return 1;
    }
#endif
    libff::start_profiling();

    /* load everything */
    libff::enter_block("Deserialize proving key");
    ram_ppzksnark_proving_key<default_tinyram_ppzksnark_pp> pk;
    std::ifstream pk_file(proving_key_fn);
    pk_file >> pk;
    pk_file.close();
    libff::leave_block("Deserialize proving key");

    std::ifstream processed(processed_assembly_fn);
    tinyram_program program = load_preprocessed_program(pk.ap, processed);

    std::ifstream f_primary_input(primary_input_fn);
    std::ifstream f_auxiliary_input(auxiliary_input_fn);
    tinyram_input_tape primary_input = load_tape(f_primary_input);
    tinyram_input_tape auxiliary_input = load_tape(f_auxiliary_input);

    const ram_boot_trace<default_tinyram_ppzksnark_pp> boot_trace = tinyram_boot_trace_from_program_and_input(pk.ap, pk.primary_input_size_bound, program, primary_input);
    const ram_ppzksnark_proof<default_tinyram_ppzksnark_pp> proof = ram_ppzksnark_prover<default_tinyram_ppzksnark_pp>(pk, boot_trace,  auxiliary_input);

    libff::enter_block("Serialize proof");
    std::ofstream proof_file(proof_fn);
    proof_file << proof;
    proof_file.close();
    libff::leave_block("Serialize proof");
}


/*

Copyright (c) 2005-2017, University of Oxford.
All rights reserved.

University of Oxford means the Chancellor, Masters and Scholars of the
University of Oxford, having an administrative office at Wellington
Square, Oxford OX1 2JD, UK.

This file is part of Chaste.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 * Neither the name of the University of Oxford nor the names of its
   contributors may be used to endorse or promote products derived from this
   software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/
#ifndef DYNAMICALLY_LOADABLE_LR91_HPP_
#define DYNAMICALLY_LOADABLE_LR91_HPP_

#include "ChasteSerialization.hpp"
#include <boost/serialization/base_object.hpp>

#include "AbstractCardiacCell.hpp"
#include "AbstractStimulusFunction.hpp"
#include "AbstractDynamicallyLoadableEntity.hpp"
#include <vector>

/**
 * This class represents the Luo-Rudy 1991 system of equations,
 * with support for being compiled into a .so and loaded at run-time.
 */
class DynamicallyLoadableLr91 : public AbstractCardiacCell, public AbstractDynamicallyLoadableEntity
{
private:
    /** Needed for serialization. */
    friend class boost::serialization::access;
    /**
     * Archive the member variables.
     *
     * @param archive
     * @param version
     */
    template<class Archive>
    void serialize(Archive & archive, const unsigned int version)
    {
        archive & boost::serialization::base_object<AbstractCardiacCell>(*this);
        archive & boost::serialization::base_object<AbstractDynamicallyLoadableEntity>(*this);
    }

    /* Constants for the model */

    /** membrane capcaitance*/
    static const double membrane_C;
    /** Faraday constant*/
    static const double membrane_F;
    /** Universal gas constant*/
    static const double membrane_R;
    /** Temeperature*/
    static const double membrane_T;
    /** Reversal potentila for background current*/
    static const double background_current_E_b;
    /** Maximal conductance for background current*/
    static const double background_current_g_b;
    /** Maximal conductance for sodium current*/
    static const double fast_sodium_current_g_Na;
    /** Intracellular potassium concentration*/
    static const double ionic_concentrations_Ki;
    /** Extracellular potassium concentration*/
    static const double ionic_concentrations_Ko;
    /** Intracellular sodium concentration*/
    static const double ionic_concentrations_Nai;
    /** Extracellular sodium concentration*/
    static const double ionic_concentrations_Nao;
    /** Maximal conductance for plateau current*/
    static const double plateau_potassium_current_g_Kp;
    /** Permeability ratio Na/K for potassium currents*/
    static const double time_dependent_potassium_current_PR_NaK;

    /** Another parameter, which is a function of the above */
    double fast_sodium_current_E_Na;

    /**
     *  Range-checking on the current values of the state variables. Make sure
     *  all gating variables have are within zero and one, and all concentrations
     *  are positive
     */
    void VerifyStateVariables();

public:
    /**
     * Constructor
     *
     * @param pSolver is a pointer to the ODE solver
     * @param pIntracellularStimulus is a pointer to the intracellular stimulus
     */
    DynamicallyLoadableLr91(boost::shared_ptr<AbstractIvpOdeSolver> pSolver,
                            boost::shared_ptr<AbstractStimulusFunction> pIntracellularStimulus);

    /**
     * Destructor
     */
    ~DynamicallyLoadableLr91();

    /**
     * Fill in a vector representing the RHS of the Luo-Rudy 1991 system
     * of Odes at each time step, y' = [y1' ... yn'].
     * Some ODE solver will call this function repeatedly to solve for y = [y1 ... yn].
     *
     * @param time  the current time, in milliseconds
     * @param rY  current values of the state variables
     * @param rDY  to be filled in with derivatives
     */
    void EvaluateYDerivatives(double time, const std::vector<double> &rY, std::vector<double> &rDY);

    /**
     * Returns the ionic current
     *
     * @param pStateVariables  optional state at which to evaluate the current
     * @return the total ionic current
     */
    double GetIIonic(const std::vector<double>* pStateVariables=NULL);

    /**
     * Get the intracellular calcium concentration
     *
     * @return the intracellular calcium concentration
     */
    double GetIntracellularCalciumConcentration();
};

#include "SerializationExportWrapper.hpp"
CHASTE_CLASS_EXPORT(DynamicallyLoadableLr91)

namespace boost
{
namespace serialization
{
/**
 * Allow us to not need a default constructor, by specifying how Boost should
 * instantiate a DynamicallyLoadableLr91 instance.
 */
template<class Archive>
inline void save_construct_data(
    Archive & ar, const DynamicallyLoadableLr91 * t, const unsigned int file_version)
{
    const boost::shared_ptr<AbstractIvpOdeSolver> p_solver = t->GetSolver();
    const boost::shared_ptr<AbstractStimulusFunction> p_stimulus = t->GetStimulusFunction();
    ar << p_solver;
    ar << p_stimulus;
}

/**
 * Allow us to not need a default constructor, by specifying how Boost should
 * instantiate a DynamicallyLoadableLr91 instance (using existing constructor).
 *
 * NB this constructor allocates memory for the other member variables too.
 */
template<class Archive>
inline void load_construct_data(
    Archive & ar, DynamicallyLoadableLr91 * t, const unsigned int file_version)
{

    boost::shared_ptr<AbstractIvpOdeSolver> p_solver;
    boost::shared_ptr<AbstractStimulusFunction> p_stimulus;
    ar >> p_solver;
    ar >> p_stimulus;
    ::new(t)DynamicallyLoadableLr91(p_solver, p_stimulus);
}
}
} // namespace ...

#endif // DYNAMICALLY_LOADABLE_LR91_HPP_


// __BEGIN_LICENSE__
//  Copyright (c) 2006-2013, United States Government as represented by the
//  Administrator of the National Aeronautics and Space Administration. All
//  rights reserved.
//
//  The NASA Vision Workbench is 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.
// __END_LICENSE__

#include <vw/Cartography/PointImageManipulation.h>
#include <boost/math/special_functions/fpclassify.hpp>

using namespace vw;

Vector3 cartography::GeodeticToCartesian::operator()( Vector3 const& v ) const {
  if ( boost::math::isnan(v[2]) )
    return Vector3();
  return m_datum.geodetic_to_cartesian(v);
}

Vector3 cartography::CartesianToGeodetic::operator()( Vector3 const& v ) const {
  if ( v == Vector3() )
    return Vector3(0,0,std::numeric_limits<double>::quiet_NaN());
  return m_datum.cartesian_to_geodetic(v);
}

Vector3 cartography::GeodeticToProjection::operator()( Vector3 const& v ) const {
  if ( boost::math::isnan(v[2]) )
    return v;
  Vector2 pix = m_reference.lonlat_to_pixel( subvector(v, 0, 2) );
  return Vector3( pix[0], pix[1], v[2] );
}

Vector3 cartography::ProjectionToGeodetic::operator()( Vector3 const& v ) const {
  Vector2 ll = m_reference.pixel_to_lonlat( subvector( v, 0, 2 ) );
  return Vector3( ll[0], ll[1], v[2] );
}

Vector3 cartography::GeodeticToPoint::operator()( Vector3 const& v ) const {
  if ( boost::math::isnan(v[2]) )
    return v;
  Vector2 pix = m_reference.lonlat_to_point( subvector(v, 0, 2) );
  return Vector3( pix[0], pix[1], v[2] );
}

Vector3 cartography::PointToGeodetic::operator()( Vector3 const& v ) const {
  Vector2 ll = m_reference.point_to_lonlat( subvector( v, 0, 2 ) );
  return Vector3( ll[0], ll[1], v[2] );
}


// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2015.
//
// This software is released under a three-clause BSD license:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of any author or any participating institution
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// For a full list of authors, refer to the file AUTHORS.
// --------------------------------------------------------------------------
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
// INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// --------------------------------------------------------------------------
// $Maintainer: Clemens Groepl $
// $Authors: $
// --------------------------------------------------------------------------

#include <OpenMS/TRANSFORMATIONS/FEATUREFINDER/EmgFitter1D.h>
#include <OpenMS/TRANSFORMATIONS/FEATUREFINDER/InterpolationModel.h>
#include <OpenMS/MATH/STATISTICS/StatisticFunctions.h>
#include <OpenMS/CONCEPT/Constants.h>
#include <OpenMS/CONCEPT/Factory.h>

#include <boost/math/special_functions/fpclassify.hpp>

namespace OpenMS
{
  int EmgFitter1D::EgmFitterFunctor::operator()(const Eigen::VectorXd& x, Eigen::VectorXd& fvec)
  {
    Size n = m_data->n;
    EmgFitter1D::RawDataArrayType set = m_data->set;

    EmgFitter1D::CoordinateType h = x(0);
    EmgFitter1D::CoordinateType w = x(1);
    EmgFitter1D::CoordinateType s = x(2);
    EmgFitter1D::CoordinateType z = x(3);

    EmgFitter1D::CoordinateType Yi = 0.0;

    // iterate over all points of the signal
    for (Size i = 0; i < n; i++)
    {
      double t = set[i].getPos();

      // Simplified EMG
      Yi = (h * w / s) * sqrt(2.0 * Constants::PI) * exp((pow(w, 2) / (2 * pow(s, 2))) - ((t - z) / s)) / (1 + exp((-2.4055 / sqrt(2.0)) * (((t - z) / w) - w / s)));

      fvec(i) = Yi - set[i].getIntensity();
    }
    return 0;
  }

  // compute Jacobian matrix for the different parameters
  int EmgFitter1D::EgmFitterFunctor::df(const Eigen::VectorXd& x, Eigen::MatrixXd& J)
  {
    Size n =  m_data->n;
    EmgFitter1D::RawDataArrayType set = m_data->set;

    EmgFitter1D::CoordinateType h = x(0);
    EmgFitter1D::CoordinateType w = x(1);
    EmgFitter1D::CoordinateType s = x(2);
    EmgFitter1D::CoordinateType z = x(3);

    const EmgFitter1D::CoordinateType emg_const = 2.4055;
    const EmgFitter1D::CoordinateType sqrt_2pi = sqrt(2 * Constants::PI);
    const EmgFitter1D::CoordinateType sqrt_2 = sqrt(2.0);

    EmgFitter1D::CoordinateType exp1, exp2, exp3 = 0.0;
    EmgFitter1D::CoordinateType derivative_height, derivative_width, derivative_symmetry, derivative_retention = 0.0;

    // iterate over all points of the signal
    for (Size i = 0; i < n; i++)
    {
      EmgFitter1D::CoordinateType t = set[i].getPos();

      exp1 = exp(((w * w) / (2 * s * s)) - ((t - z) / s));
      exp2 = (1 + exp((-emg_const / sqrt_2) * (((t - z) / w) - w / s)));
      exp3 = exp((-emg_const / sqrt_2) * (((t - z) / w) - w / s));

      // f'(h)
      derivative_height = w / s * sqrt_2pi * exp1 / exp2;

      // f'(h)
      derivative_width = h / s * sqrt_2pi * exp1 / exp2 + (h * w * w) / (s * s * s) * sqrt_2pi * exp1 / exp2 + (emg_const * h * w) / s * sqrt_2pi * exp1 * (-(t - z) / (w * w) - 1 / s) * exp3 / ((exp2 * exp2) * sqrt_2);

      // f'(s)
      derivative_symmetry = -h * w / (s * s) * sqrt_2pi * exp1 / exp2 + h * w / s * sqrt_2pi * (-(w * w) / (s * s * s) + (t - z) / (s * s)) * exp1 / exp2 + (emg_const * h * w * w) / (s * s * s) * sqrt_2pi * exp1 * exp3 / ((exp2 * exp2) * sqrt_2);

      // f'(z)
      derivative_retention = h * w / (s * s) * sqrt_2pi * exp1 / exp2 - (emg_const * h) / s * sqrt_2pi * exp1 * exp3 / ((exp2 * exp2) * sqrt_2);

      // set the jacobian matrix
      J(i, 0) = derivative_height;
      J(i, 1) = derivative_width;
      J(i, 2) = derivative_symmetry;
      J(i, 3) = derivative_retention;
    }
    return 0;
  }

  EmgFitter1D::EmgFitter1D() :
    LevMarqFitter1D()
  {
    setName(getProductName());
    defaults_.setValue("statistics:variance", 1.0, "Variance of the model.", ListUtils::create<String>("advanced"));
    defaultsToParam_();
  }

  EmgFitter1D::EmgFitter1D(const EmgFitter1D& source) :
    LevMarqFitter1D(source)
  {
    setParameters(source.getParameters());
    updateMembers_();
  }

  EmgFitter1D::~EmgFitter1D()
  {
  }

  EmgFitter1D& EmgFitter1D::operator=(const EmgFitter1D& source)
  {
    if (&source == this)
      return *this;

    LevMarqFitter1D::operator=(source);
    setParameters(source.getParameters());
    updateMembers_();

    return *this;
  }

  EmgFitter1D::QualityType EmgFitter1D::fit1d(const RawDataArrayType& set, InterpolationModel*& model)
  {
    // Calculate bounding box
    CoordinateType min_bb = set[0].getPos(), max_bb = set[0].getPos();
    for (Size pos = 1; pos < set.size(); ++pos)
    {
      CoordinateType tmp = set[pos].getPos();
      if (min_bb > tmp)
        min_bb = tmp;
      if (max_bb < tmp)
        max_bb = tmp;
    }

    // Enlarge the bounding box by a few multiples of the standard deviation
    const CoordinateType stdev = sqrt(statistics_.variance()) * tolerance_stdev_box_;
    min_bb -= stdev;
    max_bb += stdev;

    // Set advanced parameters for residual_  und jacobian_ method
    EmgFitter1D::Data d;
    d.n = set.size();
    d.set = set;

    // Compute start parameters
    setInitialParameters_(set);

    // Optimize parameter with Levenberg-Marquardt algorithm
//    CoordinateType x_init[4] = { height_, width_, symmetry_, retention_ };
    Eigen::VectorXd x_init(4);
    x_init(0) = height_;
    x_init(1) = width_;
    x_init(2) = symmetry_;
    x_init(3) = retention_;
    if (symmetric_ == false)
    {
      EgmFitterFunctor functor(4, &d);
      optimize_(x_init, functor);
    }

    // Set optimized parameters
    height_ = x_init[0];
    width_ = x_init[1];
    symmetry_ = x_init[2];
    retention_ = x_init[3];

#ifdef DEBUG_FEATUREFINDER
    if (getGslStatus_() != "success")
    {
      std::cout << "status: " << getGslStatus_() << std::endl;
    }
#endif

    // build model
    model = static_cast<InterpolationModel*>(Factory<BaseModel<1> >::create("EmgModel"));
    model->setInterpolationStep(interpolation_step_);

    Param tmp;
    tmp.setValue("bounding_box:min", min_bb);
    tmp.setValue("bounding_box:max", max_bb);
    tmp.setValue("statistics:variance", statistics_.variance());
    tmp.setValue("statistics:mean", statistics_.mean());
    tmp.setValue("emg:height", height_);
    tmp.setValue("emg:width", width_);
    tmp.setValue("emg:symmetry", symmetry_);
    tmp.setValue("emg:retention", retention_);
    model->setParameters(tmp);

    // calculate pearson correlation
    std::vector<float> real_data;
    real_data.reserve(set.size());
    std::vector<float> model_data;
    model_data.reserve(set.size());

    for (Size i = 0; i < set.size(); ++i)
    {
      real_data.push_back(set[i].getIntensity());
      model_data.push_back(model->getIntensity(DPosition<1>(set[i].getPosition())));
    }

    QualityType correlation = Math::pearsonCorrelationCoefficient(real_data.begin(), real_data.end(), model_data.begin(), model_data.end());
    if (boost::math::isnan(correlation))
      correlation = -1.0;

    return correlation;
  }

  void EmgFitter1D::setInitialParameters_(const RawDataArrayType& set)
  {
    // sum over all intensities
    CoordinateType sum = 0.0;
    for (Size i = 0; i < set.size(); ++i)
      sum += set[i].getIntensity();

    // calculate the median
    Size median = 0;
    float count = 0.0;
    for (Size i = 0; i < set.size(); ++i)
    {
      count += set[i].getIntensity();
      if (count <= sum / 2)
        median = i;
    }

    // calculate the height of the peak
    height_ = set[median].getIntensity();

    // calculate retention time
    retention_ = set[median].getPos();

    // default is an asymmetric peak
    symmetric_ = false;

    // calculate the symmetry (fronted peak: s<1 , tailed peak: s>1)
    symmetry_ = fabs(set[set.size() - 1].getPos() - set[median].getPos()) / fabs(set[median].getPos() - set[0].getPos());

    // check the symmetry
    if (boost::math::isinf(symmetry_) || boost::math::isnan(symmetry_))
    {
      symmetric_ = true;
      symmetry_ = 10;
    }

    // optimize the symmetry
    // The computations can lead to an overflow error at very low values of symmetry (s~0).
    // For s~5 the parameter can be approximated by the Levenberg-Marquardt algorithms.
    // (the other parameters are much greater than one)
    if (symmetry_ < 1)
      symmetry_ += 5;

    // calculate the width of the peak
    // rt-values with intensity zero are not allowed for calculation of the width
    // normally: width_ = fabs( set[set.size() - 1].getPos() - set[0].getPos() );
    // but its better for the emg function to proceed from narrow peaks
    width_ = symmetry_;
  }

  void EmgFitter1D::updateMembers_()
  {
    LevMarqFitter1D::updateMembers_();
    statistics_.setVariance(param_.getValue("statistics:variance"));
  }

}


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

#include <boost/endian/conversion.hpp>
#include "cryptmsg.h"
#include "crypto/chacha.h"

namespace {

static_assert(201103L <= __cplusplus, "C++11 compiler required");

#if 0
//can be used to check manually
#define native_to_little boost::endian::native_to_big
#define little_to_native boost::endian::big_to_native
#else
using namespace boost::endian;
#endif

inline size_t getEncryptChachaSize(size_t plainSize)
{
    return plainSize + sizeof(crypto::chacha_iv);
}

void encryptChacha(const uint8_t* plain, size_t plain_size, const crypto::secret_key &skey, uint8_t* cipher)
{
  crypto::chacha_key key;
  crypto::generate_chacha_key(&skey, sizeof(skey), key);
  crypto::chacha_iv& iv = *reinterpret_cast<crypto::chacha_iv*>(cipher);
  iv = crypto::rand<crypto::chacha_iv>();
  crypto::chacha8(plain, plain_size, key, iv, reinterpret_cast<char*>(cipher) + sizeof(iv));
}

void decryptChacha(const uint8_t* cipher, size_t cipher_size, const crypto::secret_key &skey, uint8_t* plain)
{
  const size_t prefix_size = sizeof(crypto::chacha_iv);
  crypto::chacha_key key;
  crypto::generate_chacha_key(&skey, sizeof(skey), key);
  const crypto::chacha_iv &iv = *reinterpret_cast<const crypto::chacha_iv*>(cipher);
  crypto::chacha8(reinterpret_cast<const char*>(cipher) + sizeof(iv), cipher_size - prefix_size, key, iv, reinterpret_cast<char*>(plain));
}

constexpr uint8_t cStart = 0xA5;
constexpr uint8_t cEnd = 0x5A;

#pragma pack(push, 1)

//Note, native order is little-endian

//SessionX in decrypted form contains session key x and constants to check that the decryption was correct
struct SessionX
{
    uint8_t cstart; //decrypted value cStart
    crypto::secret_key x;
    uint8_t cend; //decrypted value cEnd
};

struct XEntry
{
    uint32_t Bhash; //xor of B
    uint8_t cipherX[sizeof(SessionX) + sizeof(crypto::chacha_iv)]; //encrypted SessionX
};

struct CryptoMessageHead
{
    uint32_t plainSize; //size of decrypted data
    crypto::public_key R; //random key used to encrypt session key
    uint16_t count; //recipients count
    XEntry xentries[1];
};

#pragma pack(pop)

uint32_t getBhash(const crypto::public_key& B)
{
    uint32_t res = 0;
    static_assert(sizeof(B) % sizeof(res) == 0, "public key size must be a multiple of 4");
    const uint32_t* p = reinterpret_cast<const uint32_t*>(&B);
    for(int i = 0, cnt = sizeof(B) / sizeof(res); i < cnt; ++i, ++p)
    {
        res ^= *p;
    }
    return native_to_little(res);
}

/*!
 * \brief encryptMsg - encrypts data for recipients using their B public keys (assumed public view keys).
 *
 * The result has following structure [plainSize:32][R:32][count of XEntrys:16][XEntry]...[XEntry][x encrypted data:+8*8]
 * plainSize - size of original data, x encrypted data takes 8 more bytes
 * [XEntry] - [Bhash:32][rBX] pair for each recipient
 * where Bhash - xor of B (aka fingerprint of B, using which recipient can find his entry)
 * [rBX:+8*8] - encrypted X (aka SessionX)
 * [X] = [cstart:8][x:32*8][cend:8]
 *
 * \param inputSize - input buffer size.
 * \param input - input buffer to encrypt.
 * \param BkeysCount - count of B keys.
 * \param Bkeys - array of B keys for each recipients.
 * \param outputSize - output buffer size.
 * \param output - output buffer.
 * \returns output size required if output==nullptr or outputSize is not enough.
 * returns 0 on error
 */

size_t encryptMsg(size_t inputSize, const uint8_t* input, size_t BkeysCount, const crypto::public_key* Bkeys, size_t outputSize, uint8_t* output)
{
    if(!inputSize || !BkeysCount)
        return 0;

    //prepare
    size_t msgHeadSize = sizeof(CryptoMessageHead) + (BkeysCount - 1) * sizeof(XEntry);
    size_t msgSize = msgHeadSize + getEncryptChachaSize(inputSize);
    if(outputSize < msgSize)
        return msgSize;
    if(!input || !Bkeys || !output)
        return 0;

    //make decorated session key
    SessionX X; X.cstart = cStart; X.cend = cEnd;
    //generate session key X.x
    {
        crypto::public_key tmpX;
        crypto::generate_keys(tmpX,X.x);
    }
    //chacha encrypt input with x
    encryptChacha(input, inputSize, X.x, output + msgHeadSize);

    //fill head
    CryptoMessageHead& head = *reinterpret_cast<CryptoMessageHead*>(output);
    head.plainSize = native_to_little(uint32_t(inputSize));
    crypto::secret_key r;
    crypto::generate_keys(head.R, r);
    head.count = native_to_little(uint16_t(BkeysCount));
    //fill XEntry for each B
    const crypto::public_key* pB = Bkeys;
    XEntry* pxe = head.xentries;
    for(size_t i=0; i<BkeysCount; ++i, ++pxe, ++pB)
    {
        const crypto::public_key& B = *pB;
        XEntry& xe = *pxe;
        xe.Bhash = getBhash(B);
        //get rB key
        crypto::key_derivation rBv;
        crypto::generate_key_derivation(B, r, rBv);
        crypto::secret_key rB;
        crypto::derivation_to_scalar(rBv, 0, rB);
        //encrypt X with rB key
        encryptChacha(reinterpret_cast<const uint8_t*>(&X), sizeof(X), rB, xe.cipherX);
    }
    return msgSize;
}

/*!
 * \brief decryptMsg - (reverse of encryptMsg) decrypts data for one of the recipients using his secret key b.
 *
 * \param inputSize - input buffer size.
 * \param input - input buffer to decrypt.
 * \param bkey - secret key corresponding to one of Bs that were used to encrypt.
 * \param outputSize - output buffer size.
 * \param output - output buffer.
 * \returns output size required if output==nullptr or outputSize is not enough
 * returns 0 on error
 */

size_t decryptMsg(size_t inputSize, const uint8_t* input, const crypto::secret_key& bkey, size_t outputSize, uint8_t* output)
{
    if(!input || inputSize <= sizeof(CryptoMessageHead))
        return 0;
    //prepare
    const CryptoMessageHead& head = *reinterpret_cast<const CryptoMessageHead*>(input);
    size_t head_count = little_to_native(head.count);
    size_t head_plainSize = little_to_native(head.plainSize);
    size_t msgHeadSize = sizeof(CryptoMessageHead) + ((size_t)(head_count - 1)) * sizeof(XEntry);
    size_t msgSize = msgHeadSize + getEncryptChachaSize(head_plainSize);
    if(inputSize < msgSize)
        return 0;
    if(outputSize < head_plainSize)
        return head_plainSize;
    if(!output)
        return 0;

    //get Bhash from b
    const crypto::secret_key& b = bkey;
    uint32_t Bhash;
    {
        crypto::public_key B;
        bool res = crypto::secret_key_to_public_key(bkey, B);
        if(!res) return false; //corrupted key
        Bhash = getBhash(B);
    }
    //find XEntry for B
    const XEntry* pxe = head.xentries;
    for(size_t i=0; i<head_count; ++i, ++pxe)
    {
        const XEntry& xe = *pxe;
        if(xe.Bhash != Bhash) continue;
        //get bR key
        crypto::key_derivation bRv;
        crypto::generate_key_derivation(head.R, b, bRv);
        crypto::secret_key bR;
        crypto::derivation_to_scalar(bRv, 0, bR);
        //decrypt to X
        SessionX X;
        decryptChacha(xe.cipherX, sizeof(xe.cipherX), bR, reinterpret_cast<uint8_t*>(&X));
        if(X.cstart != cStart || X.cend != cEnd) continue;
        //decrypt with session key
        decryptChacha(input + msgHeadSize, getEncryptChachaSize(head_plainSize), X.x, output);
        return head_plainSize;
    }
    return 0;
}

} //namespace

namespace graft { namespace crypto_tools {

void encryptMessage(const std::string& input, const std::vector<crypto::public_key>& Bkeys, std::string& output)
{
    assert(!input.empty());
    //get output size
    size_t size = encryptMsg( input.size(), nullptr, Bkeys.size(), nullptr, 0, nullptr);
    assert(0<size);
    output.resize(size);
    //encrypt
    size_t res = encryptMsg( input.size(), reinterpret_cast<const uint8_t*>(input.data()),
                             Bkeys.size(), &Bkeys[0],
            output.size(), reinterpret_cast<uint8_t*>(&output[0]));
    assert(res == size);
}

void encryptMessage(const std::string& input, const crypto::public_key& Bkey, std::string& output)
{
    std::vector<crypto::public_key> v(1, Bkey);
    encryptMessage(input, v, output);
}

bool decryptMessage(const std::string& input, const crypto::secret_key& bkey, std::string& output)
{
    assert(!input.empty());
    //get output size
    size_t size = decryptMsg( input.size(), reinterpret_cast<const uint8_t*>(input.data()), bkey, 0, nullptr);
    if(!size)
    {
        output.clear();
        return false;
    }
    output.resize(size);
    //encrypt
    size_t res = decryptMsg( input.size(), reinterpret_cast<const uint8_t*>(input.data()),
                             bkey, output.size(), reinterpret_cast<uint8_t*>(&output[0]));
    if(!res)
    {
        output.clear();
        return false;
    }
    assert(res == size);
    return true;
}

}} //namespace graft::crypto_tools


#include <boost/math/special_functions/next.hpp>
#include <boost/random.hpp>

#include <limits>

#include "caffe/common.hpp"
#include "caffe/quantizer.hpp"
#include "caffe/util/math_functions.hpp"
#include "caffe/util/rng.hpp"

#ifdef USE_GEMMLOWP
#include "public/gemmlowp.h"
#endif

namespace caffe {

template<typename Dtype>
typename std::enable_if<unsigned_integer_is_same<Dtype>::value, bool>::type
gemmlowp_gemm(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
              const int_tp M, const int_tp N, const int_tp K,
              const Dtype alpha, const Dtype* A,
              const Dtype* B, const Dtype beta, Dtype* C,
              const QuantizerValues* const alpha_quant,
              const QuantizerValues* const a_quant,
              const QuantizerValues* const b_quant,
              const QuantizerValues* const beta_quant,
              const QuantizerValues* const c_quant) {
  // No gemmlowp available
  return false;
}

#ifdef USE_GEMMLOWP
template<>
typename std::enable_if<unsigned_integer_is_same<uint8_t>::value, bool>::type
gemmlowp_gemm(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
              const int_tp M, const int_tp N, const int_tp K,
              const uint8_t alpha, const uint8_t* A,
              const uint8_t* B, const uint8_t beta, uint8_t* C,
              const QuantizerValues* const alpha_quant,
              const QuantizerValues* const a_quant,
              const QuantizerValues* const b_quant,
              const QuantizerValues* const beta_quant,
              const QuantizerValues* const c_quant) {
  CHECK(a_quant && b_quant && c_quant)
       << "Integer type requires quantization values.";
  if (alpha == 1 && beta == 0 && alpha_quant == nullptr
                              && beta_quant == nullptr) {
    const int32_t lhs_offset = -a_quant->get_zero<int32_t>();
    const int32_t rhs_offset = -b_quant->get_zero<int32_t>();

    gemmlowp::OutputStageQuantizeDownInt32ByFixedPoint quantize_down_stage;
    quantize_down_stage.result_offset_after_shift =
        c_quant->get_zero<int32_t>();

    int32_t mult;
    int8_t shift;

    QuantizerBase::template MultiplicativeQuantVals<int32_t>(
        a_quant, b_quant, c_quant, &mult, &shift);

    quantize_down_stage.result_fixedpoint_multiplier = mult;
    quantize_down_stage.result_shift = static_cast<int32_t>(shift);

    if (quantize_down_stage.result_shift < 0) {
      // Gemmlowp doesn't handle negative exponents
      return false;
    }

    gemmlowp::OutputStageSaturatingCastToUint8 saturating_cast_stage;
    const auto& output_pipeline =
        std::make_tuple(quantize_down_stage, saturating_cast_stage);

    gemmlowp::GemmContext gemm_context;

    gemmlowp::MatrixMap<uint8_t, gemmlowp::MapOrder::RowMajor> rs(C, M, N);

    if (trans_A == CblasNoTrans && trans_B == CblasNoTrans) {
      gemmlowp::MatrixMap<const uint8_t, gemmlowp::MapOrder::RowMajor>
                                                                   lhs(A, M, K);
      gemmlowp::MatrixMap<const uint8_t, gemmlowp::MapOrder::RowMajor>
                                                                   rhs(B, K, N);
      gemmlowp::GemmWithOutputPipeline<uint8_t, uint8_t,
                                       gemmlowp::DefaultL8R8BitDepthParams>(
         &gemm_context, lhs, rhs, &rs, lhs_offset, rhs_offset, output_pipeline);
    } else if (trans_A == CblasNoTrans && trans_B == CblasTrans) {
      gemmlowp::MatrixMap<const uint8_t, gemmlowp::MapOrder::RowMajor>
                                                                   lhs(A, M, K);
      gemmlowp::MatrixMap<const uint8_t, gemmlowp::MapOrder::ColMajor>
                                                                   rhs(B, K, N);
      gemmlowp::GemmWithOutputPipeline<uint8_t, uint8_t,
                                       gemmlowp::DefaultL8R8BitDepthParams>(
         &gemm_context, lhs, rhs, &rs, lhs_offset, rhs_offset, output_pipeline);
    } else if (trans_A == CblasTrans && trans_B == CblasNoTrans) {
      gemmlowp::MatrixMap<const uint8_t, gemmlowp::MapOrder::ColMajor>
                                                                   lhs(A, M, K);
      gemmlowp::MatrixMap<const uint8_t, gemmlowp::MapOrder::RowMajor>
                                                                   rhs(B, K, N);
      gemmlowp::GemmWithOutputPipeline<uint8_t, uint8_t,
                                       gemmlowp::DefaultL8R8BitDepthParams>(
         &gemm_context, lhs, rhs, &rs, lhs_offset, rhs_offset, output_pipeline);
    } else {
      gemmlowp::MatrixMap<const uint8_t, gemmlowp::MapOrder::ColMajor>
                                                                   lhs(A, M, K);
      gemmlowp::MatrixMap<const uint8_t, gemmlowp::MapOrder::ColMajor>
                                                                   rhs(B, K, N);
      gemmlowp::GemmWithOutputPipeline<uint8_t, uint8_t,
                                       gemmlowp::DefaultL8R8BitDepthParams>(
         &gemm_context, lhs, rhs, &rs, lhs_offset, rhs_offset, output_pipeline);
    }
    return true;
  }
  // Gemmlowp can't handle the case
  return false;
}
#endif  // USE_GEMMLOWP

template<typename Dtype>
typename std::enable_if<unsigned_integer_is_same<Dtype>::value, void>::type
caffe_gemm(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
           const int_tp M, const int_tp N, const int_tp K,
           const Dtype alpha, const Dtype* A,
           const Dtype* B, const Dtype beta, Dtype* C,
           const QuantizerValues* const alpha_quant,
           const QuantizerValues* const a_quant,
           const QuantizerValues* const b_quant,
           const QuantizerValues* const beta_quant,
           const QuantizerValues* const c_quant) {
  CHECK(a_quant && b_quant && c_quant)
       << "Integer type requires quantization values.";

  if (gemmlowp_gemm<Dtype>(trans_A, trans_B, M, N, K, alpha, A, B, beta, C,
                          alpha_quant, a_quant, b_quant, beta_quant, c_quant)) {
    // Handled by gemmlowp implementation
    return;
  }

  typedef typename std::conditional<sizeof(Dtype) == 1, int16_t,
          typename std::conditional<sizeof(Dtype) == 2, int32_t,
                                    int64_t>::type>::type Difftype;
  typedef typename std::conditional<sizeof(Dtype) == 1,
                                    int32_t, int64_t>::type Acctype;

  // std::cout << "Difftype: " << sizeof(Difftype) << std::endl;
  // std::cout << "Acctype: " << sizeof(Acctype) << std::endl;

  int8_t shift_bits = (32 / sizeof(Dtype)) - 1;

  int32_t mult;
  int8_t shift;
  int32_t alpha_mult;
  int8_t alpha_shift;
  int32_t beta_mult;
  int8_t beta_shift;
  Acctype c_max = c_quant->get_max<Acctype>();
  Acctype c_min = c_quant->get_min<Acctype>();
  Dtype lhs_off = a_quant->get_zero<Dtype>();
  Dtype rhs_off = b_quant->get_zero<Dtype>();
  Dtype alpha_off = alpha_quant ? alpha_quant->get_zero<Dtype>() : Dtype(0);
  Dtype beta_off = beta_quant ? beta_quant->get_zero<Dtype>() : Dtype(0);
  const Acctype result_off = c_quant->get_zero<Acctype>();

  QuantizerBase::template MultiplicativeQuantVals<int32_t>(
      a_quant, b_quant, c_quant, &mult, &shift, shift_bits);
  QuantizerBase::template MultiplicativeQuantVals<int32_t>(
      c_quant, alpha_quant, c_quant, &alpha_mult, &alpha_shift, shift_bits);
  QuantizerBase::template MultiplicativeQuantVals<int32_t>(
      c_quant, beta_quant, c_quant, &beta_mult, &beta_shift, shift_bits);

  int_tp inc_a = (trans_A == CblasNoTrans) ? 1 : M;
  int_tp inc_b = (trans_B == CblasNoTrans) ? N : 1;
  for (int_tp m = 0; m < M; ++m) {
#pragma omp parallel for
    for (int_tp n = 0; n < N; ++n) {
      Acctype acc = Acctype(0);
      int_tp b_index = trans_B == CblasNoTrans ? n : K * n;
      int_tp a_index = trans_A == CblasNoTrans ? K * m : m;
      for (int_tp k = 0; k < K; ++k) {
        Difftype a_diff = A[a_index] - lhs_off;
        Difftype b_diff = B[b_index] - rhs_off;
        // std::cout << "a_diff: " << a_diff << std::endl;
        // std::cout << "b_diff: " << b_diff << std::endl;
        acc += static_cast<Acctype>(a_diff) * static_cast<Acctype>(b_diff);
        a_index += inc_a;
        b_index += inc_b;
      }
      Acctype reg = acc * (alpha_quant ? Acctype(1) : alpha);
      // std::cout << "1: " << m << ", "<< n << ": " << reg << std::endl;
      // std::cout << "M: " << mult << std::endl;
      // std::cout << "S: " << static_cast<int32_t>(shift_bits) << std::endl;
      reg = static_cast<Acctype>((static_cast<int64_t>(reg) *
                             static_cast<int64_t>(mult)) / (1ll << shift_bits));
      // std::cout << "2: " << m << ", "<< n << ": " << reg << std::endl;
      if (shift >= 0) {
        reg = reg >> shift;
      } else {
        reg = reg << -shift;
      }
      if (alpha_quant) {
        Difftype alpha_diff = alpha - alpha_off;
        reg = static_cast<Acctype>(alpha_diff) * static_cast<Acctype>(reg);
        reg = static_cast<Acctype>((static_cast<int64_t>(reg) *
                       static_cast<int64_t>(alpha_mult)) / (1ll << shift_bits));
        if (alpha_shift >= 0) {
          reg = reg >> alpha_shift;
        } else {
          reg = reg << -alpha_shift;
        }
      }
      // std::cout << "3: " << m << ", "<< n << ": " << reg << std::endl;
      if (beta_quant) {
        Difftype beta_diff = beta - beta_off;
        Difftype c_diff = C[m * N + n] - static_cast<Difftype>(result_off);
        Acctype creg = static_cast<Acctype>(beta_diff)
                     * static_cast<Acctype>(c_diff);
        creg = static_cast<Acctype>((static_cast<int64_t>(creg) *
                        static_cast<int64_t>(beta_mult)) / (1ll << shift_bits));
        if (beta_shift >= 0) {
          creg = creg >> beta_shift;
        } else {
          creg = creg << -beta_shift;
        }
        reg = reg + creg;
      } else if (beta == Dtype(1)) {
        reg = reg + (C[m * N + n] - result_off);
      }
      reg = reg + result_off;
      // std::cout << "4: " << m << ", "<< n << ": " << reg << std::endl;
      C[m * N + n] = static_cast<Dtype>(std::min(std::max(reg, c_min), c_max));
    }
  }
}

template<typename Dtype>
typename std::enable_if<float_is_same<Dtype>::value, void>::type
caffe_gemm(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
           const int_tp M, const int_tp N, const int_tp K,
           const Dtype alpha, const Dtype* A,
           const Dtype* B, const Dtype beta, Dtype* C,
           const QuantizerValues* const alpha_quant,
           const QuantizerValues* const a_quant,
           const QuantizerValues* const b_quant,
           const QuantizerValues* const beta_quant,
           const QuantizerValues* const c_quant) {
  int_tp inc_a = (trans_A == CblasNoTrans) ? 1 : M;
  int_tp inc_b = (trans_B == CblasNoTrans) ? N : 1;
  for (int_tp m = 0; m < M; ++m) {
#pragma omp parallel for
    for (int_tp n = 0; n < N; ++n) {
      Dtype acc = 0;
      int_tp b_index = trans_B == CblasNoTrans ? n : K * n;
      int_tp a_index = trans_A == CblasNoTrans ? K * m : m;
      for (int_tp k = 0; k < K; ++k) {
        acc += A[a_index] * B[b_index];
        a_index += inc_a;
        b_index += inc_b;
      }
      if (beta != 0) {
        C[m * N + n] = acc * alpha + beta * C[m * N + n];
      }
      else {
        C[m * N + n] = acc * alpha;
      }
    }
  }
}

template
typename std::enable_if<float_is_same<half_fp>::value, void>::type
caffe_gemm(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
                const int_tp M, const int_tp N, const int_tp K,
                const half_fp alpha, const half_fp* A,
                const half_fp* B, const half_fp beta, half_fp* C,
                const QuantizerValues* const alpha_quant,
                const QuantizerValues* const a_quant,
                const QuantizerValues* const b_quant,
                const QuantizerValues* const beta_quant,
                const QuantizerValues* const c_quant);
template
typename std::enable_if<unsigned_integer_is_same<uint8_t>::value, void>::type
caffe_gemm(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
                const int_tp M, const int_tp N, const int_tp K,
                const uint8_t alpha, const uint8_t* A,
                const uint8_t* B, const uint8_t beta, uint8_t* C,
                const QuantizerValues* const alpha_quant,
                const QuantizerValues* const a_quant,
                const QuantizerValues* const b_quant,
                const QuantizerValues* const beta_quant,
                const QuantizerValues* const c_quant);
template
typename std::enable_if<unsigned_integer_is_same<uint16_t>::value, void>::type
caffe_gemm(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
           const int_tp M, const int_tp N, const int_tp K,
           const uint16_t alpha, const uint16_t* A,
           const uint16_t* B, const uint16_t beta, uint16_t* C,
           const QuantizerValues* const alpha_quant,
           const QuantizerValues* const a_quant,
           const QuantizerValues* const b_quant,
           const QuantizerValues* const beta_quant,
           const QuantizerValues* const c_quant);
template
typename std::enable_if<unsigned_integer_is_same<uint32_t>::value, void>::type
caffe_gemm(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
           const int_tp M, const int_tp N, const int_tp K,
           const uint32_t alpha, const uint32_t* A,
           const uint32_t* B, const uint32_t beta, uint32_t* C,
           const QuantizerValues* const alpha_quant,
           const QuantizerValues* const a_quant,
           const QuantizerValues* const b_quant,
           const QuantizerValues* const beta_quant,
           const QuantizerValues* const c_quant);
template
typename std::enable_if<unsigned_integer_is_same<uint64_t>::value, void>::type
caffe_gemm(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
           const int_tp M, const int_tp N, const int_tp K,
           const uint64_t alpha, const uint64_t* A,
           const uint64_t* B, const uint64_t beta, uint64_t* C,
           const QuantizerValues* const alpha_quant,
           const QuantizerValues* const a_quant,
           const QuantizerValues* const b_quant,
           const QuantizerValues* const beta_quant,
           const QuantizerValues* const c_quant);

template<>
typename std::enable_if<float_is_same<float>::value, void>::type
caffe_gemm<float>(const CBLAS_TRANSPOSE trans_A,
                  const CBLAS_TRANSPOSE trans_B, const int_tp M,
                  const int_tp N, const int_tp K, const float alpha,
                  const float* A, const float* B, const float beta,
                  float* C,
                  const QuantizerValues* const alpha_quant,
                  const QuantizerValues* const a_quant,
                  const QuantizerValues* const b_quant,
                  const QuantizerValues* const beta_quant,
                  const QuantizerValues* const c_quant) {
  int_tp lda = (trans_A == CblasNoTrans) ? K : M;
  int_tp ldb = (trans_B == CblasNoTrans) ? N : K;
  cblas_sgemm(CblasRowMajor, trans_A, trans_B, M, N, K, alpha, A, lda, B, ldb,
              beta, C, N);
}

template<>
typename std::enable_if<float_is_same<float>::value, void>::type
caffe_gemm<double>(const CBLAS_TRANSPOSE trans_A,
                   const CBLAS_TRANSPOSE trans_B, const int_tp M,
                   const int_tp N, const int_tp K, const double alpha,
                   const double* A, const double* B, const double beta,
                   double* C,
                   const QuantizerValues* const alpha_quant,
                   const QuantizerValues* const a_quant,
                   const QuantizerValues* const b_quant,
                   const QuantizerValues* const beta_quant,
                   const QuantizerValues* const c_quant) {
  int_tp lda = (trans_A == CblasNoTrans) ? K : M;
  int_tp ldb = (trans_B == CblasNoTrans) ? N : K;
  cblas_dgemm(CblasRowMajor, trans_A, trans_B, M, N, K, alpha, A, lda, B, ldb,
              beta, C, N);
}

}  // namespace caffe


#include <iostream>
#include <vector>
#include <cassert>
#include <cstring>
#include <limits>
//#include <mpi.h>
//#include <Eigen/Eigenvalues>

enum KernelType {CubicSpline = 0, WendlandC2 = 1, WendlandC4 = 2};
class Physics;

#include "particle_simulator.hpp"
#include "hdr_time.hpp"
#include "hdr_run.hpp"
#include "vector_x86.hpp"
#include "hdr_dimension.hpp"
#include "hdr_kernel.hpp"
#include "hdr_sph.hpp"
#include "hdr_hgas.hpp"
#include "hdr_bhns.hpp"

class SPHAnalysis : public HelmholtzGas {
public:
    SPHAnalysis() {
        this->id    = 0;
        this->istar = 0;
        this->mass  = 0.;
        this->pos   = 0.;
        this->vel   = 0.;
        this->uene  = 0.;
        this->alph  = 0.;
        this->alphu = 0.;
        this->ksr   = 0.;
        for(PS::S32 k = 0; k < NR::NumberOfNucleon; k++) {
            this->cmps[k] = 0.;
        }
    }

    void readAscii(FILE * fp) {
        fscanf(fp, "%lld%lld%lf", &this->id, &this->istar, &this->mass);      //  3
        fscanf(fp, "%lf%lf%lf", &this->pos[0], &this->pos[1], &this->pos[2]); //  6
        fscanf(fp, "%lf%lf%lf", &this->vel[0], &this->vel[1], &this->vel[2]); //  9
        fscanf(fp, "%lf%lf%lf", &this->acc[0], &this->acc[1], &this->acc[2]); // 12
        fscanf(fp, "%lf%lf%lf", &this->uene, &this->alph, &this->alphu);      // 15
        fscanf(fp, "%lf%lf%6d", &this->dens, &this->ksr,  &this->np);         // 18
        fscanf(fp, "%lf%lf%lf", &this->vsnd, &this->pres, &this->temp);       // 21
        fscanf(fp, "%lf%lf%lf", &this->divv, &this->rotv, &this->bswt);       // 24
        fscanf(fp, "%lf%lf%lf", &this->pot,  &this->abar, &this->zbar);       // 27
        fscanf(fp, "%lf",       &this->enuc);                                 // 28
        fscanf(fp, "%lf%lf%lf", &this->vsmx, &this->udot, &this->dnuc);       // 31
        for(PS::S32 k = 0; k < NuclearReaction::NumberOfNucleon; k++) {       // 32 -- 44
            fscanf(fp, "%lf", &this->cmps[k]);
        }
        fscanf(fp, "%lf", &this->pot3);
        fscanf(fp, "%lf%lf%lf", &this->tempmax[0], &this->tempmax[1], &this->tempmax[2]);
        fscanf(fp, "%lf", &this->entr);
    }

    /*
    void write(FILE * fp = stdout) {
        fprintf(fp, "%8d %2d %+e",  this->id, this->istar, this->mass);
        fprintf(fp, " %+e %+e %+e", this->pos[0], this->pos[1], this->pos[2]);
        fprintf(fp, " %+e %+e %+e", this->vel[0], this->vel[1], this->vel[2]);
        fprintf(fp, " %+e %+e %+e", this->uene, this->alph, this->alphu);
        fprintf(fp, " %+e", this->ksr);
        for(PS::S32 k = 0; k < NR::NumberOfNucleon; k++) {
            fprintf(fp, " %+.3e", this->cmps[k]);
        }
        fprintf(fp, "\n");
    }
    */

    inline PS::F64mat calcMomentOfInertia(PS::F64vec xc) {
        PS::F64mat qq = 0.;
        PS::F64vec dx = this->pos - xc;
        PS::F64 r2 = dx * dx;
        qq.xx = this->mass * (r2 - dx[0] * dx[0]);
        qq.xy = this->mass * (   - dx[0] * dx[1]);
        qq.xz = this->mass * (   - dx[0] * dx[2]);
        qq.yy = this->mass * (r2 - dx[1] * dx[1]);
        qq.yz = this->mass * (   - dx[1] * dx[2]);
        qq.zz = this->mass * (r2 - dx[2] * dx[2]);
        return qq;
    }

    void copyFromForce(const Physics & physics);
};

class BlackHoleNeutronStarAnalysis : public BlackHoleNeutronStar {
public:
    BlackHoleNeutronStarAnalysis() {
        this->id    = 0;
        this->istar = 0;
        this->mass  = 0.;
        this->pos   = 0.;
        this->vel   = 0.;
        this->acc   = 0.;
        this->eps   = 0.;
        this->pot   = 0.;
    }

    void readAscii(FILE * fp) {
        fscanf(fp, "%lld%lld%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf",
               &this->id, &this->istar, &this->mass,
               &this->pos[0], &this->pos[1], &this->pos[2],
               &this->vel[0], &this->vel[1], &this->vel[2],
               &this->acc[0], &this->acc[1], &this->acc[2],
               &this->eps, &this->pot);
    }

    inline PS::F64mat calcMomentOfInertia(PS::F64vec xc) {
        PS::F64mat qq = 0.;
        PS::F64vec dx = this->pos - xc;
        PS::F64 r2 = dx * dx;
        qq.xx = this->mass * (r2 - dx[0] * dx[0]);
        qq.xy = this->mass * (   - dx[0] * dx[1]);
        qq.xz = this->mass * (   - dx[0] * dx[2]);
        qq.yy = this->mass * (r2 - dx[1] * dx[1]);
        qq.yz = this->mass * (   - dx[1] * dx[2]);
        qq.zz = this->mass * (r2 - dx[2] * dx[2]);
        return qq;
    }
};

class MeshData : public SPHAnalysis {
public:
    PS::F64 tempu;

    MeshData() {
        this->id    = 0;
        this->istar = 0;
        this->mass  = 0.;
        this->pos   = 0.;
        this->vel   = 0.;
        this->uene  = 0.;
        this->alph  = 0.;
        this->alphu = 0.;
        this->ksr   = 0.;
        for(PS::S32 k = 0; k < NR::NumberOfNucleon; k++) {
            this->cmps[k] = 0.;
        }
    }

    void writeAscii(FILE *fp) {
        fprintf(fp, "%8d", this->id);
        fprintf(fp, " %+e %+e %+e", this->pos[0], this->pos[1], this->pos[2]);
        fprintf(fp, " %+e %+e %+e", this->dens, this->temp, this->tempu);
        fprintf(fp, " %+e", this->vsnd);
        fprintf(fp, "\n");
    }

    void copyFromForce(const Physics & physics);
};

template <class Tmesh>
void generateMeshData(Tmesh & mesh) {
    if(PS::Comm::getRank() != 0) {
        return;
    }
    PS::F64 xmin = -5e8;
    PS::F64 xmax = +5e8;
    PS::S64 nx   = 100;
    PS::S64 nx3  = nx * nx * nx;
    PS::F64 dx   = (xmax - xmin) / (PS::F64)nx;
    mesh.setNumberOfParticleLocal(nx3);
    for(PS::S64 i = 0; i < nx; i++) {
        for(PS::S64 j = 0; j < nx; j++) {
            for(PS::S64 k = 0; k < nx; k++) {
                PS::S64 id = i * nx * nx + j * nx + k;
                mesh[id].id     = id;
                mesh[id].mass   = 0.;
                mesh[id].pos[0] = xmin + dx * (PS::F64)i;
                mesh[id].pos[1] = xmin + dx * (PS::F64)j;
                mesh[id].pos[2] = xmin + dx * (PS::F64)k;
                mesh[id].ksr    = 1e7;
                mesh[id].dens   = 0.;
                mesh[id].temp   = 0.;
            }
        }
    }
}

class Physics {
public:
    PS::F64 dens;
    PS::F64 temp;
    PS::F64 uene;
    void clear() {
        dens = 0.;
        temp = 0.;
        uene = 0.;
    }
};

void SPHAnalysis::copyFromForce(const Physics & physics) {
    this->dens = physics.dens;
    this->temp = physics.temp;
    this->uene = physics.uene;
}

void MeshData::copyFromForce(const Physics & physics) {
    this->dens = physics.dens;
    this->temp = physics.temp;
    this->uene = physics.uene;
}

class PhysicsEPI {
public:
    PS::S32    id;
    PS::F64    mass;
    PS::F64vec pos;
    bool       meshornot;

    void copyFromFP(const SPHAnalysis & sph) {
        this->id   = sph.id;
        this->mass = sph.mass;
        this->pos  = sph.pos;
        this->meshornot = false;
    }

    void copyFromFP(const MeshData & mesh) {
        this->id   = mesh.id;
        this->mass = mesh.mass;
        this->pos  = mesh.pos;
        this->meshornot = true;
    }

    PS::F64vec getPos() const {
        return this->pos;
    }

};

class PhysicsEPJ {
public:
    PS::S32    id;
    PS::F64    mass;
    PS::F64vec pos;
    PS::F64    ksr;
    PS::F64    hinv;
    PS::F64    dinv;
    PS::F64    temp;
    PS::F64    uene;
    bool       meshornot;

    void copyFromFP(const SPHAnalysis & sph) {
        this->id   = sph.id;
        this->mass = sph.mass;
        this->pos  = sph.pos;
        this->ksr  = sph.ksr;
        this->hinv = 1. / sph.ksr;
        this->dinv = 1. / sph.dens;
        this->temp = sph.temp;
        this->uene = sph.uene;
        this->meshornot = false;
    }

    void copyFromFP(const MeshData & mesh) {
        this->id   = mesh.id;
        this->mass = mesh.mass;
        this->pos  = mesh.pos;
        this->ksr  = mesh.ksr;
        this->hinv = 1. / mesh.ksr;
        this->dinv = 1. / mesh.dens;
        this->temp = mesh.temp;
        this->uene = mesh.uene;
        this->meshornot = true;
    }

    PS::F64vec getPos() const {
        return this->pos;
    }

    PS::F64 getRSearch() const {
        return this->ksr;
    }

    void setPos(const PS::F64vec pos_new) {
        pos = pos_new;
    }
};

struct calcPhysics {

    const PS::F64 ceff0 = +3.342253804929802286e+00;
    inline PS::F64 kernel0th(const PS::F64 r) {
        PS::F64 rmin  = ((1. - r > 0.) ? (1. - r) : 0.);
        PS::F64 rmin2 = rmin * rmin;
        return ceff0 * rmin2 * rmin2 * (1. + 4. * r);
    }

    void operator () (const PhysicsEPI * epi,
                      const PS::S32 nip,
                      const PhysicsEPJ * epj,
                      const PS::S32 njp,
                      Physics * physics) {

        for(PS::S64 i = 0; i < nip; i++) {

            if(!epi[i].meshornot) {
                continue;
            }

            PS::F64 dens = 0.;
            PS::F64 temp = 0.;
            PS::F64 uene = 0.;

            for(PS::S64 j = 0; j < njp; j++) {
                PS::F64vec dx  = epi[i].pos - epj[j].pos;
                PS::F64    r2  = dx * dx;
                PS::F64    r1  = sqrt(r2);
                PS::F64    q   = r1 * epj[j].hinv;
                PS::F64    kw0 = kernel0th(q);
                PS::F64    hi3 = epj[j].hinv * epj[j].hinv * epj[j].hinv;

                PS::F64 densi = epj[j].mass * hi3 * kw0;
                PS::F64 tempi = epj[j].mass * hi3 * kw0 * epj[j].temp * epj[j].dinv;
                PS::F64 uenei = epj[j].mass * hi3 * kw0 * epj[j].uene * epj[j].dinv;
                dens += ((epj[j].mass != 0.) ? densi : 0.);
                temp += ((epj[j].mass != 0.) ? tempi : 0.);
                uene += ((epj[j].mass != 0.) ? uenei : 0.);
            }

            physics[i].dens = dens;
            physics[i].temp = temp;
            physics[i].uene = uene;
        }

    }

};

template <class Tdinfo,
          class Tsph,
          class Tmesh,
          class Tphysics>
void calcMeshData(Tdinfo & dinfo,
                  Tsph & sph,
                  Tmesh & mesh,
                  Tphysics & physics) {
    PS::MT::init_genrand(0);
    dinfo.decomposeDomainAll(sph);
    sph.exchangeParticle(dinfo);
    mesh.exchangeParticle(dinfo);
    physics.setParticleLocalTree(mesh);
    physics.setParticleLocalTree(sph, false);
    physics.calcForceMakingTree(calcPhysics(), dinfo);
    PS::S64 nmesh = mesh.getNumberOfParticleLocal();
    for(PS::S64 i = 0; i < nmesh; i++) {
        mesh[i].copyFromForce(physics.getForce(i));
    }
    for(PS::S64 i = 0; i < nmesh; i++) {
        NR::Nucleon cmps;
        cmps[1] = cmps[2] = 0.5;
        PS::F64 dens = mesh[i].dens;
        PS::F64 uene = mesh[i].uene;
        PS::F64 temp = mesh[i].temp;
        PS::F64 tout = 0.;
        PS::F64 pout, cout, sout;
        if(dens != 0.) {
            flash_helmholtz_(&dens, &uene, &temp, cmps.getPointer(),
                             &pout, &cout, &tout, &sout);
        }
        mesh[i].tempu = tout;
        mesh[i].vsnd  = sout;
    }
}

template <class Tsph,
          class Tbhns>
void shiftCenter(Tsph & sph,
                 Tbhns & bhns) {
    PS::S64    nbhns = bhns.getNumberOfParticleLocal();
    PS::F64vec ploc  = 0.;
    if(nbhns == 1) {
        ploc = bhns[0].pos;
    } else {
        ploc = 0.;
    }
    PS::F64vec pbhns = PS::Comm::getSum(ploc);

    for(PS::S64 i = 0; i < sph.getNumberOfParticleLocal(); i++) {
        sph[i].pos -= pbhns;
    }
}

int main(int argc, char ** argv) {
    PS::Initialize(argc, argv);

    init_flash_helmholtz_(&CodeUnit::FractionOfCoulombCorrection);

    PS::DomainInfo dinfo;
    dinfo.initialize();
    PS::ParticleSystem<SPHAnalysis> sph;
    sph.initialize();
    sph.createParticle(0);
    sph.setNumberOfParticleLocal(0);
    PS::ParticleSystem<BlackHoleNeutronStarAnalysis> bhns;
    bhns.initialize();
    bhns.createParticle(0);
    bhns.setNumberOfParticleLocal(0);
    PS::ParticleSystem<MeshData> mesh;
    mesh.initialize();
    mesh.createParticle(0);
    mesh.setNumberOfParticleLocal(0);
    PS::TreeForForceShort<Physics, PhysicsEPI, PhysicsEPJ>::Scatter physics;
    physics.initialize(0);

    if(PS::Comm::getRank() == 0) {
        printf("AT comments: NPROCESS %8d\n", PS::Comm::getNumberOfProc());
        printf("AT comments: NTHREAD  %8d\n", PS::Comm::getNumberOfThread());
    }

    char idir[1024], otype[1024];
    PS::S64 ibgn, iend;
    FILE * fp = fopen(argv[1], "r");
    fscanf(fp, "%s", idir);
    fscanf(fp, "%s", otype);
    fscanf(fp, "%lld%lld", &ibgn, &iend);
    fclose(fp);

    generateMeshData(mesh);

    for(PS::S64 itime = ibgn; itime <= iend; itime++) {
        char sfile[1024], bfile[1024];
        sprintf(sfile, "%s/sph_t%04d.dat",  idir, itime);
        sprintf(bfile, "%s/bhns_t%04d.dat", idir, itime);
        fp = fopen(sfile, "r");
        if(fp == NULL) {
            continue;
        }
        sph.readParticleAscii(sfile);
        fclose(fp);
        fp = fopen(bfile, "r");
        if(fp == NULL) {
            continue;
        }
        bhns.readParticleAscii(bfile);
        fclose(fp);

        shiftCenter(sph, bhns);
        calcMeshData(dinfo, sph, mesh, physics);

        char ofile[1024];
        sprintf(ofile, "%s_t%04d.dat", otype, itime);
        mesh.writeParticleAscii(ofile);
    }

    PS::Finalize();

    return 0;
}


// Distributed under the MIT License.
// See LICENSE.txt for details.

#include "Evolution/Systems/GrMhd/GhValenciaDivClean/FiniteDifference/MonotisedCentral.hpp"

#include <array>
#include <boost/functional/hash.hpp>
#include <cstddef>
#include <memory>
#include <pup.h>
#include <utility>

#include "DataStructures/DataBox/Prefixes.hpp"
#include "DataStructures/DataVector.hpp"
#include "DataStructures/FixedHashMap.hpp"
#include "DataStructures/Index.hpp"
#include "DataStructures/Tensor/EagerMath/DeterminantAndInverse.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "DataStructures/Variables.hpp"
#include "Domain/Structure/Direction.hpp"
#include "Domain/Structure/Element.hpp"
#include "Domain/Structure/ElementId.hpp"
#include "Domain/Structure/MaxNumberOfNeighbors.hpp"
#include "Domain/Structure/Side.hpp"
#include "Evolution/DiscontinuousGalerkin/Actions/NormalCovectorAndMagnitude.hpp"
#include "Evolution/Systems/GeneralizedHarmonic/Tags.hpp"
#include "Evolution/Systems/GrMhd/GhValenciaDivClean/FiniteDifference/ReconstructWork.tpp"
#include "Evolution/Systems/GrMhd/GhValenciaDivClean/System.hpp"
#include "Evolution/Systems/GrMhd/ValenciaDivClean/Tags.hpp"
#include "NumericalAlgorithms/FiniteDifference/MonotisedCentral.hpp"
#include "NumericalAlgorithms/FiniteDifference/Unlimited.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "PointwiseFunctions/GeneralRelativity/Lapse.hpp"
#include "PointwiseFunctions/GeneralRelativity/Shift.hpp"
#include "PointwiseFunctions/GeneralRelativity/SpatialMetric.hpp"
#include "PointwiseFunctions/GeneralRelativity/Tags.hpp"
#include "PointwiseFunctions/Hydro/EquationsOfState/EquationOfState.hpp"
#include "PointwiseFunctions/Hydro/Tags.hpp"
#include "Utilities/GenerateInstantiations.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/TMPL.hpp"

namespace grmhd::GhValenciaDivClean::fd {
MonotisedCentralPrim::MonotisedCentralPrim(CkMigrateMessage* const msg)
    : Reconstructor(msg) {}

std::unique_ptr<Reconstructor> MonotisedCentralPrim::get_clone() const {
  return std::make_unique<MonotisedCentralPrim>(*this);
}

void MonotisedCentralPrim::pup(PUP::er& p) { Reconstructor::pup(p); }

// NOLINTNEXTLINE
PUP::able::PUP_ID MonotisedCentralPrim::my_PUP_ID = 0;

template <size_t ThermodynamicDim, typename TagsList>
void MonotisedCentralPrim::reconstruct(
    const gsl::not_null<std::array<Variables<TagsList>, dim>*>
        vars_on_lower_face,
    const gsl::not_null<std::array<Variables<TagsList>, dim>*>
        vars_on_upper_face,
    const Variables<hydro::grmhd_tags<DataVector>>& volume_prims,
    const Variables<typename System::variables_tag::type::tags_list>&
        volume_spacetime_and_cons_vars,
    const EquationsOfState::EquationOfState<true, ThermodynamicDim>& eos,
    const Element<dim>& element,
    const FixedHashMap<
        maximum_number_of_neighbors(dim) + 1,
        std::pair<Direction<dim>, ElementId<dim>>, std::vector<double>,
        boost::hash<std::pair<Direction<dim>, ElementId<dim>>>>& neighbor_data,
    const Mesh<dim>& subcell_mesh) const {
  reconstruct_prims_work<tmpl::list<gr::Tags::SpacetimeMetric<3>>>(
      vars_on_lower_face, vars_on_upper_face,
      [](auto upper_face_vars_ptr, auto lower_face_vars_ptr,
         const auto& volume_vars, const auto& ghost_cell_vars,
         const auto& subcell_extents, const size_t number_of_variables) {
        ::fd::reconstruction::monotised_central(
            upper_face_vars_ptr, lower_face_vars_ptr, volume_vars,
            ghost_cell_vars, subcell_extents, number_of_variables);
      },
      [](auto upper_face_vars_ptr, auto lower_face_vars_ptr,
         const auto& volume_vars, const auto& ghost_cell_vars,
         const auto& subcell_extents, const size_t number_of_variables) {
        ::fd::reconstruction::unlimited<2>(
            upper_face_vars_ptr, lower_face_vars_ptr, volume_vars,
            ghost_cell_vars, subcell_extents, number_of_variables);
      },
      [](const auto vars_on_face_ptr) {
        const auto& spacetime_metric =
            get<gr::Tags::SpacetimeMetric<3>>(*vars_on_face_ptr);
        auto& spatial_metric =
            get<gr::Tags::SpatialMetric<3>>(*vars_on_face_ptr);
        gr::spatial_metric(make_not_null(&spatial_metric), spacetime_metric);
        auto& inverse_spatial_metric =
            get<gr::Tags::InverseSpatialMetric<3>>(*vars_on_face_ptr);
        auto& sqrt_det_spatial_metric =
            get<gr::Tags::SqrtDetSpatialMetric<>>(*vars_on_face_ptr);

        determinant_and_inverse(make_not_null(&sqrt_det_spatial_metric),
                                make_not_null(&inverse_spatial_metric),
                                spatial_metric);
        get(sqrt_det_spatial_metric) = sqrt(get(sqrt_det_spatial_metric));

        auto& shift = get<gr::Tags::Shift<3>>(*vars_on_face_ptr);
        gr::shift(make_not_null(&shift), spacetime_metric,
                  inverse_spatial_metric);
        gr::lapse(make_not_null(&get<gr::Tags::Lapse<>>(*vars_on_face_ptr)),
                  shift, spacetime_metric);
      },
      volume_prims, volume_spacetime_and_cons_vars, eos, element, neighbor_data,
      subcell_mesh, ghost_zone_size());
}

template <size_t ThermodynamicDim, typename TagsList>
void MonotisedCentralPrim::reconstruct_fd_neighbor(
    const gsl::not_null<Variables<TagsList>*> vars_on_face,
    const Variables<hydro::grmhd_tags<DataVector>>& subcell_volume_prims,
    const Variables<tmpl::list<
        gr::Tags::SpacetimeMetric<3>, GeneralizedHarmonic::Tags::Phi<3>,
        GeneralizedHarmonic::Tags::Pi<3>>>& subcell_volume_spacetime_metric,
    const EquationsOfState::EquationOfState<true, ThermodynamicDim>& eos,
    const Element<dim>& element,
    const FixedHashMap<
        maximum_number_of_neighbors(dim) + 1,
        std::pair<Direction<dim>, ElementId<dim>>, std::vector<double>,
        boost::hash<std::pair<Direction<dim>, ElementId<dim>>>>& neighbor_data,
    const Mesh<dim>& subcell_mesh,
    const Direction<dim> direction_to_reconstruct) const {
  reconstruct_fd_neighbor_work(
      vars_on_face,
      [](const auto tensor_component_on_face_ptr,
         const auto& tensor_component_volume,
         const auto& tensor_component_neighbor,
         const Index<dim>& subcell_extents,
         const Index<dim>& ghost_data_extents,
         const Direction<dim>& local_direction_to_reconstruct) {
        ::fd::reconstruction::reconstruct_neighbor<
            Side::Lower,
            ::fd::reconstruction::detail::MonotisedCentralReconstructor>(
            tensor_component_on_face_ptr, tensor_component_volume,
            tensor_component_neighbor, subcell_extents, ghost_data_extents,
            local_direction_to_reconstruct);
      },
      [](const auto tensor_component_on_face_ptr,
         const auto& tensor_component_volume,
         const auto& tensor_component_neighbor,
         const Index<dim>& subcell_extents,
         const Index<dim>& ghost_data_extents,
         const Direction<dim>& local_direction_to_reconstruct) {
        ::fd::reconstruction::reconstruct_neighbor<
            Side::Lower,
            ::fd::reconstruction::detail::UnlimitedReconstructor<2>>(
            tensor_component_on_face_ptr, tensor_component_volume,
            tensor_component_neighbor, subcell_extents, ghost_data_extents,
            local_direction_to_reconstruct);
      },
      [](const auto tensor_component_on_face_ptr,
         const auto& tensor_component_volume,
         const auto& tensor_component_neighbor,
         const Index<dim>& subcell_extents,
         const Index<dim>& ghost_data_extents,
         const Direction<dim>& local_direction_to_reconstruct) {
        ::fd::reconstruction::reconstruct_neighbor<
            Side::Upper,
            ::fd::reconstruction::detail::MonotisedCentralReconstructor>(
            tensor_component_on_face_ptr, tensor_component_volume,
            tensor_component_neighbor, subcell_extents, ghost_data_extents,
            local_direction_to_reconstruct);
      },
      [](const auto tensor_component_on_face_ptr,
         const auto& tensor_component_volume,
         const auto& tensor_component_neighbor,
         const Index<dim>& subcell_extents,
         const Index<dim>& ghost_data_extents,
         const Direction<dim>& local_direction_to_reconstruct) {
        ::fd::reconstruction::reconstruct_neighbor<
            Side::Upper,
            ::fd::reconstruction::detail::UnlimitedReconstructor<2>>(
            tensor_component_on_face_ptr, tensor_component_volume,
            tensor_component_neighbor, subcell_extents, ghost_data_extents,
            local_direction_to_reconstruct);
      },
      [](const auto vars_on_face_ptr) {
        const auto& spacetime_metric =
            get<gr::Tags::SpacetimeMetric<3>>(*vars_on_face_ptr);
        auto& spatial_metric =
            get<gr::Tags::SpatialMetric<3>>(*vars_on_face_ptr);
        gr::spatial_metric(make_not_null(&spatial_metric), spacetime_metric);
        auto& inverse_spatial_metric =
            get<gr::Tags::InverseSpatialMetric<3>>(*vars_on_face_ptr);
        auto& sqrt_det_spatial_metric =
            get<gr::Tags::SqrtDetSpatialMetric<>>(*vars_on_face_ptr);

        determinant_and_inverse(make_not_null(&sqrt_det_spatial_metric),
                                make_not_null(&inverse_spatial_metric),
                                spatial_metric);
        get(sqrt_det_spatial_metric) = sqrt(get(sqrt_det_spatial_metric));

        auto& shift = get<gr::Tags::Shift<3>>(*vars_on_face_ptr);
        gr::shift(make_not_null(&shift), spacetime_metric,
                  inverse_spatial_metric);
        gr::lapse(make_not_null(&get<gr::Tags::Lapse<>>(*vars_on_face_ptr)),
                  shift, spacetime_metric);
      },
      subcell_volume_prims, subcell_volume_spacetime_metric, eos, element,
      neighbor_data, subcell_mesh, direction_to_reconstruct, ghost_zone_size());
}

bool operator==(const MonotisedCentralPrim& /*lhs*/,
                const MonotisedCentralPrim& /*rhs*/) {
  return true;
}

bool operator!=(const MonotisedCentralPrim& lhs,
                const MonotisedCentralPrim& rhs) {
  return not(lhs == rhs);
}

#define THERMO_DIM(data) BOOST_PP_TUPLE_ELEM(0, data)
#define TAGS_LIST(data)                                                       \
  tmpl::list<                                                                 \
      gr::Tags::SpacetimeMetric<3>, GeneralizedHarmonic::Tags::Pi<3>,         \
      GeneralizedHarmonic::Tags::Phi<3>, ValenciaDivClean::Tags::TildeD,      \
      ValenciaDivClean::Tags::TildeTau,                                       \
      ValenciaDivClean::Tags::TildeS<Frame::Inertial>,                        \
      ValenciaDivClean::Tags::TildeB<Frame::Inertial>,                        \
      ValenciaDivClean::Tags::TildePhi,                                       \
      hydro::Tags::RestMassDensity<DataVector>,                               \
      hydro::Tags::SpecificInternalEnergy<DataVector>,                        \
      hydro::Tags::SpatialVelocity<DataVector, 3>,                            \
      hydro::Tags::MagneticField<DataVector, 3>,                              \
      hydro::Tags::DivergenceCleaningField<DataVector>,                       \
      hydro::Tags::LorentzFactor<DataVector>,                                 \
      hydro::Tags::Pressure<DataVector>,                                      \
      hydro::Tags::SpecificEnthalpy<DataVector>,                              \
      hydro::Tags::LorentzFactorTimesSpatialVelocity<DataVector, 3>,          \
      ::Tags::Flux<ValenciaDivClean::Tags::TildeD, tmpl::size_t<3>,           \
                   Frame::Inertial>,                                          \
      ::Tags::Flux<ValenciaDivClean::Tags::TildeTau, tmpl::size_t<3>,         \
                   Frame::Inertial>,                                          \
      ::Tags::Flux<ValenciaDivClean::Tags::TildeS<Frame::Inertial>,           \
                   tmpl::size_t<3>, Frame::Inertial>,                         \
      ::Tags::Flux<ValenciaDivClean::Tags::TildeB<Frame::Inertial>,           \
                   tmpl::size_t<3>, Frame::Inertial>,                         \
      ::Tags::Flux<ValenciaDivClean::Tags::TildePhi, tmpl::size_t<3>,         \
                   Frame::Inertial>,                                          \
      gr::Tags::Lapse<>, gr::Tags::Shift<3, Frame::Inertial, DataVector>,     \
      gr::Tags::SpatialMetric<3>, gr::Tags::SqrtDetSpatialMetric<DataVector>, \
      gr::Tags::InverseSpatialMetric<3, Frame::Inertial, DataVector>,         \
      evolution::dg::Actions::detail::NormalVector<3>>

#define INSTANTIATION(r, data)                                                 \
  template void MonotisedCentralPrim::reconstruct(                             \
      gsl::not_null<std::array<Variables<TAGS_LIST(data)>, 3>*>                \
          vars_on_lower_face,                                                  \
      gsl::not_null<std::array<Variables<TAGS_LIST(data)>, 3>*>                \
          vars_on_upper_face,                                                  \
      const Variables<hydro::grmhd_tags<DataVector>>& volume_prims,            \
      const Variables<typename System::variables_tag::type::tags_list>&        \
          volume_spacetime_and_cons_vars,                                      \
      const EquationsOfState::EquationOfState<true, THERMO_DIM(data)>& eos,    \
      const Element<3>& element,                                               \
      const FixedHashMap<                                                      \
          maximum_number_of_neighbors(3) + 1,                                  \
          std::pair<Direction<3>, ElementId<3>>, std::vector<double>,          \
          boost::hash<std::pair<Direction<3>, ElementId<3>>>>& neighbor_data,  \
      const Mesh<3>& subcell_mesh) const;                                      \
  template void MonotisedCentralPrim::reconstruct_fd_neighbor(                 \
      gsl::not_null<Variables<TAGS_LIST(data)>*> vars_on_face,                 \
      const Variables<hydro::grmhd_tags<DataVector>>& subcell_volume_prims,    \
      const Variables<tmpl::list<                                              \
          gr::Tags::SpacetimeMetric<3>, GeneralizedHarmonic::Tags::Phi<3>,     \
          GeneralizedHarmonic::Tags::Pi<3>>>& subcell_volume_spacetime_metric, \
      const EquationsOfState::EquationOfState<true, THERMO_DIM(data)>& eos,    \
      const Element<3>& element,                                               \
      const FixedHashMap<                                                      \
          maximum_number_of_neighbors(3) + 1,                                  \
          std::pair<Direction<3>, ElementId<3>>, std::vector<double>,          \
          boost::hash<std::pair<Direction<3>, ElementId<3>>>>& neighbor_data,  \
      const Mesh<3>& subcell_mesh,                                             \
      const Direction<3> direction_to_reconstruct) const;

GENERATE_INSTANTIATIONS(INSTANTIATION, (1, 2))

#undef INSTANTIATION
#undef TAGS_LIST
#undef THERMO_DIM
}  // namespace grmhd::GhValenciaDivClean::fd


//---------------------------------------------------------------------------//
//!
//! \file   MonteCarlo_TetMeshTrackLengthFluxEstimator.hpp
//! \author Alex Robinson, Eli Moll
//! \brief  Tet mesh flux estimator class declaration.
//!
//---------------------------------------------------------------------------//

#ifndef MONTE_CARLO_TET_MESH_TRACK_LENGTH_FLUX_ESTIMATOR_HPP
#define MONTE_CARLO_TET_MESH_TRACK_LENGTH_FLUX_ESTIMATOR_HPP

// Std Lib Includes
#include <string>

// Boost Includes
#include <boost/mpl/vector.hpp>
#include <boost/unordered_map.hpp>
#include <boost/unordered_set.hpp>

// Moab Includes
#include <moab/Interface.hpp>
#include <moab/AdaptiveKDTree.hpp>
#include <moab/Matrix3.hpp>

// Trilinos Includes
#include <Teuchos_RCP.hpp>

// FRENSIE Includes
#include "MonteCarlo_StandardEntityEstimator.hpp"
#include "MonteCarlo_ParticleSubtrackEndingGlobalEventObserver.hpp"
#include "MonteCarlo_EstimatorContributionMultiplierPolicy.hpp"
#include "Geometry_ModuleTraits.hpp"
#include "MonteCarlo_ParticleState.hpp"

namespace MonteCarlo{

/*! The tet-mesh track length flux estimator class
 * \details This class is based off of the TrackLengthMeshTally written by
 * Kerry Dunn (UW-Madison CNERG group). The DAGMC repo that contains her
 * class can be found at https://github.com/svalinn/DAGMC.
 */
template<typename ContributionMutliplierPolicy = WeightMultiplier>
class TetMeshTrackLengthFluxEstimator : public StandardEntityEstimator<moab::EntityHandle>,
  public ParticleSubtrackEndingGlobalEventObserver
{

public:

  //! Typedef for the cell id type
  typedef Geometry::ModuleTraits::InternalCellHandle cellIdType;

  //! Typedef for event tags used for quick dispatcher registering
  typedef boost::mpl::vector<ParticleSubtrackEndingGlobalEventObserver::EventTag>
  EventTags;

  //! Constructor
  TetMeshTrackLengthFluxEstimator(
		     const Estimator::idType id,
		     const double multiplier,
		     const std::string input_mesh_file_name,
		     const std::string output_mesh_file_name = "tetmesh.h5m" );

  //! Destructor
  ~TetMeshTrackLengthFluxEstimator()
  { /* ... */ }

  //! Set the response functions
  void setResponseFunctions(
  const Teuchos::Array<Teuchos::RCP<ResponseFunction> >& response_functions );

  //! Set the particle types that can contribute to the estimator
  void setParticleTypes( const Teuchos::Array<ParticleType>& particle_types );

  //! Add current history estimator contribution
  void updateFromGlobalParticleSubtrackEndingEvent(
						 const ParticleState& particle,
						 const double start_point[3],
						 const double end_point[3] );

  //! Export the estimator data
  void exportData( EstimatorHDF5FileHandler& hdf5_file,
		   const bool process_data ) const;

  //! Print the estimator data
  void print( std::ostream& os ) const;

  //! Get all tet elements
  const moab::Range getAllTetElements() const;

  //! Test if a point is in the mesh
  bool isPointInMesh( const double point[3] );

  //! Determine which tet the point is in
  moab::EntityHandle whichTetIsPointIn( const double point[3] );

private:

  // Assign bin boundaries to an estimator dimension
  void assignBinBoundaries(
	const Teuchos::RCP<EstimatorDimensionDiscretization>& bin_boundaries );

  // The tolerance used for geometric tests
  static const double s_tol;

  // The moab instance that stores all mesh data
  Teuchos::RCP<moab::Interface> d_moab_interface;

  // The tet meshset
  moab::EntityHandle d_tet_meshset;

  // The kd-tree for finding point in tet
  Teuchos::RCP<moab::AdaptiveKDTree> d_kd_tree;

  // The root of the kd-tree
  moab::EntityHandle d_kd_tree_root;

  // The map of tet ids and barycentric coordinate transform matrices
  boost::unordered_map<moab::EntityHandle,moab::Matrix3>
  d_tet_barycentric_transform_matrices;

  // The map of tet ids and reference vertices
  boost::unordered_map<moab::EntityHandle, moab::CartVect>
  d_tet_reference_vertices;

  // The output mesh file name
  std::string d_output_mesh_name;
};

} // end MonteCarlo namespace

//---------------------------------------------------------------------------//
// Template Includes
//---------------------------------------------------------------------------//

#include "MonteCarlo_TetMeshTrackLengthFluxEstimator_def.hpp"

//---------------------------------------------------------------------------//

#endif // end MONTE_CARLO_TET_MESH_TRACK_LENGTH_FLUX_ESTIMATOR_HPP

//---------------------------------------------------------------------------//
// end MonteCarlo_TetMeshTrackLengthFluxEstimator.hpp
//---------------------------------------------------------------------------//


#include <iostream>

#include "gflags/gflags.h"
#include "sndfile.h"
#include "tbb/tbb.h"
#include "tbb/pipeline.h"
#include "tbb/scalable_allocator.h"
#include "tbb/cache_aligned_allocator.h"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include "bakuage/sndfile_wrapper.h"
#include "bakuage/dft.h"
#include "bakuage/memory.h"
#include "bakuage/utils.h"
#include "bakuage/window_func.h"
#include "bakuage/transient_filter.h"
#include "bakuage/transient_filter2.h"

DEFINE_string(input, "", "comma separated input wav file paths");
DEFINE_string(output, "", "output wav file path");
DEFINE_double(window_sec, 0.02, "dft window size in sec");
DEFINE_double(mean_sec, 0.02, "energy lowpass mean time in sec");
DEFINE_double(ratio, 2, "ratio");
DEFINE_double(noise_reduction_threshold, -20, "noise reduction threshold in dB");
DEFINE_bool(normalize, false, "whether if normalize is done before output");
DEFINE_string(mode, "transient", "mode (transient)");

namespace {
typedef float Float;

template <class Effect>
void CalculateEffect(Effect *effect, const std::vector<std::vector<Float>> &input, int channels, int samples, int sample_freq, Float *output) {

    std::cerr << "calculate start" << std::endl;
    bakuage::StopWatch stop_watch;
    stop_watch.Start();
    Float temp[2] = { 0 };
    Float zero[2] = { 0 };
    for (int i = 0; i < samples + effect->delay_samples(); i++) {
        if (i < samples) {
            for (int j = 0; j < channels; j++) {
                temp[j] = input[0][channels * i + j];
            }
            effect->Clock(temp, temp);
        } else {
            effect->Clock(zero, temp);
        }

        int output_i = i - effect->delay_samples();
        if (0 <= output_i) {
            for (int j = 0; j < channels; j++) {
                output[channels * output_i + j] = temp[j];
            }
        }
    }

    std::cerr << "calculate finish " << stop_watch.time() << std::endl;
}

template <class Float>
void SaveFloatWave(const std::vector<Float> &wave, const std::string &filename) {
    bakuage::SndfileWrapper snd_file;
    SF_INFO sfinfo = { 0 };
    std::memset(&sfinfo, 0, sizeof(sfinfo));

    sfinfo.channels = 2;
    sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
    int frames = wave.size() / sfinfo.channels;
    sfinfo.frames = frames;
    sfinfo.samplerate = 44100;

    if ((snd_file.set(sf_open(filename.c_str(), SFM_WRITE, &sfinfo))) == NULL) {
        std::stringstream message;
        message << "Not able to open output file " << filename << ", "
        << sf_strerror(NULL);
        throw std::logic_error(message.str());
    }

    sf_count_t size;
    if (sizeof(Float) == 4) {
        size = sf_writef_float(snd_file.get(), (float *)wave.data(), frames);
    } else {
        size = sf_writef_double(snd_file.get(), (double *)wave.data(), frames);
    }
    if (size != frames) {
        std::stringstream message;
        message << "sf_writef_float error: " << size;
        throw std::logic_error(message.str());
    }
}
}

// wavを受け取って、標準出力にrawvideoを出力
int main(int argc, char* argv[]) {
    gflags::SetVersionString("1.0.0");
    gflags::ParseCommandLineFlags(&argc, &argv, true);

    bakuage::SndfileWrapper infile;
    SF_INFO sfinfo = { 0 };
    int frames = 1 << 30;

    // カンマ区切りを分解して、load wave (全てが同じフォーマットである必要がある)
    std::vector<std::vector<float>> buffers;
    std::vector<std::string> input_paths;
    boost::algorithm::split(input_paths, FLAGS_input, boost::is_any_of(","));
    for (const auto &input_file_path: input_paths) {
        if ((infile.set(sf_open (input_file_path.c_str(), SFM_READ, &sfinfo))) == NULL) {
            fprintf(stderr, "Not able to open input file %s.\n", input_file_path.c_str());
            fprintf(stderr, "%s\n", sf_strerror(NULL));
            return 1;
        }

        // check format
        fprintf(stderr, "sfinfo.format 0x%08x.\n", sfinfo.format);
        switch (sfinfo.format & SF_FORMAT_TYPEMASK) {
            case SF_FORMAT_WAV:
            case SF_FORMAT_WAVEX:
                break;
            default:
                fprintf(stderr, "Not supported sfinfo.format 0x%08x.\n", sfinfo.format);
                return 2;
        }

        std::vector<float> buffer(sfinfo.channels * sfinfo.frames);
        int read_size = sf_readf_float(infile.get(), buffer.data(), sfinfo.frames);
        fprintf(stderr, "%d samples read.\n", read_size);
        if (read_size != sfinfo.frames) {
            fprintf(stderr, "sf_readf_float error: %d %d\n", read_size, (int)sfinfo.frames);
            return 3;
        }
        buffers.emplace_back(buffer);

        frames = std::min<int>(frames, sfinfo.frames);
    }

    // calculate spectrogram (energy)
    std::vector<float> output(frames * sfinfo.channels);

    if (FLAGS_mode == "transient") {
        bakuage::TransientFilter<Float, std::function<Float (Float, Float)>>::Config config;
        config.num_channels = sfinfo.channels;
        config.sample_rate = sfinfo.samplerate;
        config.long_mean_sec = 0.2;
        config.short_mean_sec = 0.02;
        config.gain_func = [](Float long_loundess, Float short_loudness) {
            return (short_loudness - long_loundess) * (FLAGS_ratio - 1);
        };
        bakuage::TransientFilter<Float, std::function<Float (Float, Float)>> transient_filter(config);
        CalculateEffect(&transient_filter, buffers, sfinfo.channels, frames, sfinfo.samplerate, output.data());
    } else if (FLAGS_mode == "transient2") {
        bakuage::TransientFilter2<Float, std::function<Float (Float, Float)>>::Config config;
        config.num_channels = sfinfo.channels;
        config.sample_rate = sfinfo.samplerate;
        config.long_mean_sec = 0.2;
        config.short_mean_sec = 0.02;
        config.gain_func = [](Float long_loundess, Float short_loudness) {
            return (short_loudness - long_loundess) * (FLAGS_ratio - 1);
        };
        bakuage::TransientFilter2<Float, std::function<Float (Float, Float)>> transient_filter(config);
        CalculateEffect(&transient_filter, buffers, sfinfo.channels, frames, sfinfo.samplerate, output.data());
    }

    // normalize
    if (FLAGS_normalize) {
        double peak = 1e-37;
        for (int i = 0; i < output.size(); i++) {
            peak = std::max<double>(peak, std::abs(output[i]));
        }
        for (int i = 0; i < output.size(); i++) {
            output[i] /= peak;
        }
    }

    // save output
    SaveFloatWave(output, FLAGS_output);
}