/**
 * @file vpiHarness.cpp
 * Test harness for functions in vpi.h and related code.
 */

#include <limits>
#include <exception>
#include <cfloat>
#include "dec_brl/vpi.h"
#include "dec_brl/NormalGamma.h"
#include <iostream>
#include <boost/random/variate_generator.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/mersenne_twister.hpp>
#include "DiscreteFunction.h"

// private module namespace
namespace
{
using namespace dec_brl::dist;

/**
 * Number of samples used for testing VPI Monte Carlo estimation.
 */
const int MC_SAMPLE_SIZE_M = 2500;

/**
 * The standard error for Monte Carlo estimates, expressed as the number of
 * standard deviations from the sample mean.
 * Value is defined as 1.96/sqrt(MC_SAMPLE_SIZE), where
 * 1.96 is the 0.95 quantile for the standard normal distribution.
 */
const double STANDARD_ERROR_COEFFICIENT = 4.0 / 50.0;

/**
 * Check that two doubles are equal within margin of error.
 */
bool equalWithinTol_m(double v1, double v2, double tol=0.0001)
{
   return tol > (std::abs(v1-v2)/std::min(v1,v2));
}

/**
 * For a given NormalGamma parameter distribtuion, returns true iff
 * VPI calculation is consistent and non-negative when performed
 * analytically, and by sampling.
 * @param[in] paramDist the parameter distribution for the unknown action value.
 * @param[in] vpi1 VPI calculated using one method (analytical or sampled)
 * @param[in] vpi2 VPI calculated by the other method (analytical or sampled).
 */
bool consistentVPI_m(const NormalGamma& paramDist, double vpi1, double vpi2)
{
   //***************************************************************************
   // Calculate expected sampling error for given sample size
   // This is based on the 0.95 quantile of the normal approximation to the
   // sample distribution.
   //***************************************************************************
   using namespace boost::math;
   NonCentralT meanMarginalDistribution = meanMarginal(paramDist);
   if(!meanMarginalDistribution.hasVariance())
   {
      std::cout << "\nSkipping consistency check due to infinite "
         "marginal variance." << std::endl;
      return true;
   }
   double stdevOfMean = standard_deviation(meanMarginalDistribution);
   double stdErr = stdevOfMean * STANDARD_ERROR_COEFFICIENT;

   //***************************************************************************
   // Check that both VPI values are equal within tolerance
   //***************************************************************************
   double diff = vpi1-vpi2;
   if( (diff > stdErr) || (diff < -stdErr) )
   {
      std::cout << "\nInconsistent VPI: vpi1=" << vpi1 << " vpi2=" << vpi2
         << " differ by " << diff << std::endl;
      return false;
   }

   //***************************************************************************
   // Check that both are positive values
   //***************************************************************************
   if( (0>vpi1) || (0>vpi2) )
   {
      std::cout << "\nNegative VPI: vpi1=" << vpi1 << " vpi2=" << vpi2
         << std::endl;
      return false;
   }

   return true;

} // function consistentVPI_m

/**
 * Test that VPI is consistent when calculated in different ways for a
 * value function represented by a maxsum::DiscreteFunction object.
 */
bool testVecVPI()
{
   //***************************************************************************
   // Register some variables with the maxsum library for testing
   //***************************************************************************
   maxsum::registerVariable(1,2);
   maxsum::registerVariable(2,2);

   //***************************************************************************
   // Create a NormalGamma distribution for a full value function
   //***************************************************************************
   maxsum::DiscreteFunction zeroFunc;
   zeroFunc.expand(1);
   zeroFunc.expand(2);

   dec_brl::dist::NormalGamma_Tmpl<maxsum::DiscreteFunction> dist(zeroFunc,zeroFunc,
                                                         zeroFunc,zeroFunc);
   dist.alpha(0) = 1;
   dist.alpha(1) = 2;
   dist.alpha(2) = 3;
   dist.alpha(3) = 4;

   dist.beta(0) = 10;
   dist.beta(1) = 25;
   dist.beta(2) = 30;
   dist.beta(3) = 100;

   dist.lambda(0) = 1;
   dist.lambda(1) = 1;
   dist.lambda(2) = 4;
   dist.lambda(3) = 4;

   dist.m(0) = -2;
   dist.m(1) = 100;
   dist.m(2) = 10;
   dist.m(3) = 30;

   //***************************************************************************
   // Calculate VPI the vectorised way
   //***************************************************************************
   maxsum::DiscreteFunction vecResult;
   dec_brl::exactVPI(dist,vecResult);

   //***************************************************************************
   // Ensure result as correct domain size
   //***************************************************************************
   if(vecResult.domainSize()!=dist.m.domainSize())
   {
      std::cout << "Wrong size for VPI DiscreteFunction result." << std::endl;
      return false;
   }

   //***************************************************************************
   // Find first and second best values required for scalar results
   //***************************************************************************
   maxsum::ValIndex firstBestInd = dist.m.argmax();
   maxsum::ValIndex secondBestInd = dist.m.argmax2(firstBestInd);
   maxsum::ValType firstBestVal = dist.m(firstBestInd);
   maxsum::ValType secondBestVal = dist.m(secondBestInd);

   //***************************************************************************
   // Check for consistency against scalar results
   //***************************************************************************
   for(int k=0; k<dist.m.domainSize(); ++k)
   {
      //************************************************************************
      // Get distribution for just the current value
      //************************************************************************
      NormalGamma_Tmpl<maxsum::ValType> scalarDist(dist.alpha(k),
                                                   dist.beta(k),
                                                   dist.lambda(k),
                                                   dist.m(k));

      //************************************************************************
      // Calculate scalar VPI
      //************************************************************************
      bool isBestAction = (k==firstBestInd);
      maxsum::ValType scalarVPI = dec_brl::exactVPI(isBestAction,firstBestVal,
                                           secondBestVal,scalarDist);

      //************************************************************************
      // Check for consistency
      //************************************************************************
      if(!equalWithinTol_m(scalarVPI,vecResult(k)))
      {
         return false;
      }

   } // for

   //***************************************************************************
   // Test passed if we get this far
   //***************************************************************************
   return true;

} // testVecVPI

/**
 * Test VPI for given parameter distribution
 */
bool testVPI(const NormalGamma& paramDist)
{
   using namespace dec_brl;

   //***************************************************************************
   // Test VPI with increasing best value
   //***************************************************************************
   double prevBestVPI = -Limits<double>::infinity();
   double prevNotBestVPI = Limits<double>::infinity();
   for(double bestVal=-10; bestVal<10; bestVal+=0.25)
   {
      std::cout << "\nBestval: " << bestVal << ' ' << std::flush;
      //************************************************************************
      // Calculate VPI analytically (for both best action and other action)
      //************************************************************************
      std::cout << "." << std::flush;
      double exactBestVPI = exactVPI(true, bestVal, bestVal, paramDist);
      double exactNotBestVPI = exactVPI(false, bestVal, bestVal, paramDist);

      //************************************************************************
      // Calculate VPI by sampling (for both best action and other action)
      //************************************************************************
      std::cout << "." << std::flush;
      NonCentralT marginal = meanMarginal(paramDist);
      boost::mt19937 rng;
      boost::variate_generator<boost::mt19937&, NonCentralT>
         generator(rng,marginal);
      std::cout << "." << std::flush;
      double sampledBestVPI = sampledVPI(true, bestVal, bestVal, generator,
            MC_SAMPLE_SIZE_M);
      double sampledNotBestVPI = sampledVPI(false, bestVal, bestVal, generator,
            MC_SAMPLE_SIZE_M);

      //************************************************************************
      // Check that sampled and exact calculations are consistent within
      // tolerance.
      //************************************************************************
      std::cout << "." << std::flush;
      if(!consistentVPI_m(paramDist,sampledBestVPI,exactBestVPI))
      {
         std::cout << "\nSampled best VPI is inconsistent with exact best."
            << std::endl;
         return false;
      }

      if(!consistentVPI_m(paramDist,sampledNotBestVPI,exactNotBestVPI))
      {
         std::cout << "\nSampled not best VPI is inconsistent with exact not"
            " best." << std::endl;
         return false;
      }

      //************************************************************************
      // Check that gain increases or decreases correctly depending on
      // best (or 2nd best) value.
      //************************************************************************
      std::cout << "." << std::flush;
      if(prevBestVPI > exactBestVPI)
      {
         std::cout << "\nVPI for best action should be an increasing function "
            " of the 2nd best action value." << std::endl;
         return false;
      }

      if(prevNotBestVPI < exactNotBestVPI)
      {
         std::cout << "\nVPI for non best actions should be a decreasing "
            "function of the 1st best action value: " <<
            prevNotBestVPI << " < " << exactNotBestVPI << std::endl;
         return false;
      }

      //************************************************************************
      // Store the exact results for later comparision.
      //************************************************************************
      prevBestVPI = exactBestVPI;
      prevNotBestVPI = exactNotBestVPI;

   } // for loop

   //***************************************************************************
   // If we get this far, everything is ok, so we return true.
   //***************************************************************************
   return true;

} // function testVPI

} // private module namespace

/**
 * Main function for harness - executes all tests.
 */
int main()
{
   try
   {
      using namespace dec_brl;
      using namespace dec_brl::dist;
      std::cout << "Hello World!" << std::endl;

      //************************************************************************
      // Determine if this platform has its own representation of infinity,
      // and make a note in the log
      //************************************************************************
      if(std::numeric_limits<double>::has_infinity)
      {
         std::cout << "Platform has infinity" << std::endl;
      }
      else
      {
         std::cout << "Platform has no infinity, using max instead"
            << std::endl;
      }

      //************************************************************************
      // Construct a random number generator for a standard normal distribution.
      //************************************************************************
      boost::mt19937 rnd;  // uniform random number generator
      boost::normal_distribution<> normal; // normal distribution
      boost::variate_generator<boost::mt19937&,boost::normal_distribution<> >
         normrnd(rnd,normal); // generates normal distributed random variates.

      //************************************************************************
      //  Test DiscreteFunction version of VPI to make sure it is consistent
      //************************************************************************
      if(!testVecVPI())
      {
         std::cout << "VPI inconsistent for DiscreteFunction" << std::endl;
         return EXIT_FAILURE;
      }

      //************************************************************************
      // Construct a normal-gamma distribution with default hyperparameters.
      // Has to start with enough observations to make marginal variance
      // finite.
      //************************************************************************
      std::cout << "Initialising parameter distribution" << std::endl;
      NormalGamma paramDist;
      double observation = normrnd();
      observe(paramDist,observation);

      //************************************************************************
      // Repeatedly update the normal-gamma distribution with observations from
      // the standard normal, and validate VPI after each observation.
      //************************************************************************
      double prevBestVPI = Limits<double>::infinity();
      double prevNotBestVPI = Limits<double>::infinity();
      bool bestVPIChange=false;
      bool nonBestVPIChange=false;
      for(int sampleSize=0; sampleSize<60; ++sampleSize)
      {
         std::cout << "\n****************************************" << std::endl;
         std::cout << "ITERATION: " << sampleSize << std::endl;
         std::cout << "****************************************" << std::endl;
         //*********************************************************************
         // Update the parameter distribution with a new standard normal
         // observation. This should decrease the entropy of the parameter
         // distribution.
         //*********************************************************************
         std::cout << "Generating new observation" << std::endl;
         observation = normrnd();
         std::cout << "Observing new observation" << std::endl;
         observe(paramDist,observation);

         //*********************************************************************
         // Run batch of VPI tests given the current state of the parameter
         // distribution
         //*********************************************************************
         std::cout << "Performing main tests" << std::flush;
         if(!testVPI(paramDist))
         {
            std::cout << "Main VPI tests failed at " << sampleSize
               << " observations." << std::endl;
            return EXIT_FAILURE;
         }

         //*********************************************************************
         // Store the current VPI values for later comparsion.
         //*********************************************************************
         double bestVPI = exactVPI(true,1.0,-1.0,paramDist);
         double notBestVPI = exactVPI(false,1.0,-1.0,paramDist);

         if(prevBestVPI!=bestVPI)
         {
            prevBestVPI = bestVPI;
            bestVPIChange=true;
         }

         if(prevNotBestVPI!=notBestVPI)
         {
            prevNotBestVPI = notBestVPI;
            nonBestVPIChange=true;
         }

      } // for loop

      //************************************************************************
      // Ensure that some change in VPI happened at some point
      //************************************************************************
      if(!(bestVPIChange || nonBestVPIChange))
      {
         std::cout << "VPI never changed after any observations.\n";
         return EXIT_FAILURE;
      }
      else if(!bestVPIChange)
      {
         std::cout << "Best VPI never changed after any observations.\n";
         return EXIT_FAILURE;
      }
      else if(!nonBestVPIChange)
      {
         std::cout << "Non-Best VPI never changed after any observations.\n";
         return EXIT_FAILURE;
      }

   }
   catch(std::exception& e)
   {
      std::cout << "Caught error: " << e.what() << std::endl;
      return EXIT_FAILURE;
   }

   //***************************************************************************
   // If we get this far, everything is ok.
   //***************************************************************************
   std::cout << "All VPI tests passed." << std::endl;
   return EXIT_SUCCESS;

} // function main


#include "teca_cartesian_mesh_regrid.h"

#include "teca_cartesian_mesh.h"
#include "teca_array_collection.h"
#include "teca_variant_array.h"
#include "teca_metadata.h"
#include "teca_coordinate_util.h"

#include <algorithm>
#include <iostream>

#if defined(TECA_HAS_BOOST)
#include <boost/program_options.hpp>
#endif

using std::cerr;
using std::endl;

//#define TECA_DEBUG

// always use nearest neighbor interpolation for integers
// to avoid truncation errors. an alternative would be to
// implement rounding in the interpolator for integer types
template <typename data_t>
int get_interpolation_mode(int desired_mode,
    typename std::enable_if<std::is_integral<data_t>::value>::type* = 0)
{
    (void)desired_mode;
    return teca_cartesian_mesh_regrid::nearest;
}

// use the requested interpolation mode for floating point
// data
template <typename data_t>
int get_interpolation_mode(int desired_mode,
    typename std::enable_if<std::is_floating_point<data_t>::value>::type* = 0)
{
    return desired_mode;
}

// 3D
template<typename NT1, typename NT2, typename NT3, class interp_t>
int interpolate(unsigned long target_nx, unsigned long target_ny,
    unsigned long target_nz, const NT1 *p_target_xc, const NT1 *p_target_yc,
    const NT1 *p_target_zc, NT3 *p_target_a, const NT2 *p_source_xc,
    const NT2 *p_source_yc, const NT2 *p_source_zc, const NT3 *p_source_a,
    unsigned long source_ihi, unsigned long source_jhi, unsigned long source_khi,
    unsigned long source_nx, unsigned long source_nxy)
{
    interp_t f;
    unsigned long q = 0;
    for (unsigned long k = 0; k < target_nz; ++k)
    {
        NT2 tz = static_cast<NT2>(p_target_zc[k]);
        for (unsigned long j = 0; j < target_ny; ++j)
        {
            NT2 ty = static_cast<NT2>(p_target_yc[j]);
            for (unsigned long i = 0; i < target_nx; ++i, ++q)
            {
                NT2 tx = static_cast<NT2>(p_target_xc[i]);
                if (f(tx,ty,tz,
                    p_source_xc, p_source_yc, p_source_zc,
                    p_source_a, source_ihi, source_jhi, source_khi,
                    source_nx, source_nxy,
                    p_target_a[q]))
                {
                    TECA_ERROR("failed to interpolate i=(" << i << ", " << j << ", " << k
                        << ") x=(" << tx << ", " << ty << ", " << tz << ")")
                    return -1;
                }
            }
        }
    }
    return 0;
}

// 2D - x-y
template<typename NT1, typename NT2, typename NT3, class interp_t>
int interpolate(unsigned long target_nx, unsigned long target_ny,
    const NT1 *p_target_xc, const NT1 *p_target_yc,
    NT3 *p_target_a, const NT2 *p_source_xc,
    const NT2 *p_source_yc, const NT3 *p_source_a,
    unsigned long source_ihi, unsigned long source_jhi,
    unsigned long source_nx)
{
    interp_t f;
    unsigned long q = 0;
    for (unsigned long j = 0; j < target_ny; ++j)
    {
        NT2 ty = static_cast<NT2>(p_target_yc[j]);
        for (unsigned long i = 0; i < target_nx; ++i, ++q)
        {
            NT2 tx = static_cast<NT2>(p_target_xc[i]);
            if (f(tx,ty,
                p_source_xc, p_source_yc,
                p_source_a, source_ihi, source_jhi,
                source_nx, p_target_a[q]))
            {
                TECA_ERROR("failed to interpolate i=(" << i << ", " << j
                    << ") x=(" << tx << ", " << ty << ", " << ")")
                return -1;
            }
        }
    }
    return 0;
}

template<typename taget_coord_t, typename source_coord_t, typename array_t>
int interpolate(int mode, unsigned long target_nx, unsigned long target_ny,
    unsigned long target_nz, const taget_coord_t *p_target_xc,
    const taget_coord_t *p_target_yc, const taget_coord_t *p_target_zc,
    array_t *p_target_a, const source_coord_t *p_source_xc,
    const source_coord_t *p_source_yc, const source_coord_t *p_source_zc,
    const array_t *p_source_a, unsigned long source_ihi, unsigned long source_jhi,
    unsigned long source_khi, unsigned long source_nx, unsigned long source_ny,
    unsigned long source_nz)
{
    using nearest_interp_t = teca_coordinate_util::interpolate_t<0>;
    using linear_interp_t = teca_coordinate_util::interpolate_t<1>;

    unsigned long source_nxy = source_nx*source_ny;

    switch (get_interpolation_mode<array_t>(mode))
    {
        case teca_cartesian_mesh_regrid::nearest:
        {
            if ((target_nz == 1) && (source_nz == 1))
            {
                // 2D in the x-y plane
                return interpolate<taget_coord_t,
                    source_coord_t, array_t, nearest_interp_t>(
                        target_nx, target_ny, p_target_xc, p_target_yc,
                        p_target_a, p_source_xc, p_source_yc, p_source_a,
                        source_ihi, source_jhi, source_nx);
            }
            else
            {
                // 3D
                return interpolate<taget_coord_t,
                    source_coord_t, array_t, nearest_interp_t>(
                        target_nx, target_ny, target_nz, p_target_xc,
                        p_target_yc, p_target_zc, p_target_a, p_source_xc,
                        p_source_yc, p_source_zc, p_source_a, source_ihi,
                        source_jhi, source_khi, source_nx, source_nxy);
            }
            break;
        }
        case teca_cartesian_mesh_regrid::linear:
        {
            if ((target_nz == 1) && (source_nz == 1))
            {
                // 2D in the x-y plane
                return interpolate<taget_coord_t,
                    source_coord_t, array_t, linear_interp_t>(
                        target_nx, target_ny, p_target_xc, p_target_yc,
                        p_target_a, p_source_xc, p_source_yc, p_source_a,
                        source_ihi, source_jhi, source_nx);
            }
            else
            {
                // 3D
                return interpolate<taget_coord_t,
                    source_coord_t, array_t, linear_interp_t>(
                        target_nx, target_ny, target_nz, p_target_xc,
                        p_target_yc, p_target_zc, p_target_a, p_source_xc,
                        p_source_yc, p_source_zc, p_source_a, source_ihi,
                        source_jhi, source_khi, source_nx, source_nxy);
            }
            break;
        }
    }

    TECA_ERROR("invalid interpolation mode \"" << mode << "\"")
    return -1;
}

// --------------------------------------------------------------------------
teca_cartesian_mesh_regrid::teca_cartesian_mesh_regrid()
    : target_input(0), interpolation_mode(nearest)
{
    this->set_number_of_input_connections(2);
    this->set_number_of_output_ports(1);
}

// --------------------------------------------------------------------------
teca_cartesian_mesh_regrid::~teca_cartesian_mesh_regrid()
{}

#if defined(TECA_HAS_BOOST)

// --------------------------------------------------------------------------
void teca_cartesian_mesh_regrid::get_properties_description(
    const std::string &prefix, options_description &global_opts)
{
    options_description opts("Options for "
        + (prefix.empty()?"teca_cartesian_mesh_regrid":prefix));

    opts.add_options()
        TECA_POPTS_GET(int, prefix, target_input,
            "select input connection that contains metadata")
        TECA_POPTS_MULTI_GET(std::vector<std::string>, prefix, arrays,
            "list of arrays to move from source to target mesh")
        TECA_POPTS_GET(int, prefix, interpolation_mode,
            "linear or nearest interpolation")
        ;

    this->teca_algorithm::get_properties_description(prefix, opts);

    global_opts.add(opts);
}

// --------------------------------------------------------------------------
void teca_cartesian_mesh_regrid::set_properties(
    const std::string &prefix, variables_map &opts)
{
    this->teca_algorithm::set_properties(prefix, opts);

    TECA_POPTS_SET(opts, int, prefix, target_input)
    TECA_POPTS_SET(opts, std::vector<std::string>, prefix, arrays)
    TECA_POPTS_SET(opts, int, prefix, interpolation_mode)
}

#endif

// --------------------------------------------------------------------------
teca_metadata teca_cartesian_mesh_regrid::get_output_metadata(
    unsigned int port, const std::vector<teca_metadata> &input_md)
{
#ifdef TECA_DEBUG
    cerr << teca_parallel_id()
        << "teca_cartesian_mesh_regrid::get_output_metadata" << endl;
#endif
    (void)port;

    int md_src = this->target_input ? 0 : 1;
    int md_tgt = this->target_input ? 1 : 0;

    // start with a copy of metadata from the target
    teca_metadata output_md(input_md[md_tgt]);

    // get target metadata
    std::vector<std::string> target_vars;
    input_md[md_tgt].get("variables", target_vars);

    teca_metadata target_atts;
    input_md[md_tgt].get("attributes", target_atts);

    // get source metadata
    std::vector<std::string> source_vars;
    input_md[md_src].get("variables", source_vars);

    teca_metadata source_atts;
    input_md[md_src].get("attributes", source_atts);

    // merge metadata from source and target
    // variables should be unique lists.
    // attributes are indexed by variable names
    // in the case of collisions, the target variable
    // is kept, the source variable is ignored
    size_t n_source_vars = source_vars.size();
    for (size_t i = 0; i < n_source_vars; ++i)
    {
        const std::string &source = source_vars[i];

        // check that there's not a variable of that same name in target
        std::vector<std::string>::iterator first = target_vars.begin();
        std::vector<std::string>::iterator last = target_vars.end();
        if (find(first, last, source) == last)
        {
            // not present in target, ok to add it
            target_vars.push_back(source);

            teca_metadata atts;
            source_atts.get(source, atts);
            target_atts.set(source, atts);
        }

    }

    // update with merged lists
    output_md.set("variables", target_vars);
    output_md.set("attributes", target_atts);

    return output_md;
}

// --------------------------------------------------------------------------
std::vector<teca_metadata> teca_cartesian_mesh_regrid::get_upstream_request(
    unsigned int port, const std::vector<teca_metadata> &input_md,
    const teca_metadata &request)
{
    (void)port;
    std::vector<teca_metadata> up_reqs(2);

    // route requests for arrays to either target or the source.
    // if the array exists in both then it is take from the target
    teca_metadata target_req(request);
    teca_metadata source_req(request);

    // get target metadata
    int md_tgt = this->target_input ? 1 : 0;
    std::vector<std::string> target_vars;
    input_md[md_tgt].get("variables", target_vars);
    std::vector<std::string>::iterator tgt_0 = target_vars.begin();
    std::vector<std::string>::iterator tgt_1 = target_vars.end();

    // get source metadata
    int md_src = this->target_input ? 0 : 1;
    std::vector<std::string> source_vars;
    input_md[md_src].get("variables", source_vars);
    std::vector<std::string>::iterator src_0 = source_vars.begin();
    std::vector<std::string>::iterator src_1 = source_vars.end();

    // get the requested arrays
    std::vector<std::string> req_arrays;
    request.get("arrays", req_arrays);

    // add any explicitly named
    std::copy(this->arrays.begin(), this->arrays.end(),
        std::back_inserter(req_arrays));

    // route. if found in the target then  routed there, else
    // if found in the source routed there, else skipped
    std::vector<std::string> target_arrays;
    std::vector<std::string> source_arrays;

    std::vector<std::string>::iterator it = req_arrays.begin();
    std::vector<std::string>::iterator end = req_arrays.end();
    for (; it != end; ++it)
    {
        if (find(tgt_0, tgt_1, *it) != tgt_1)
            target_arrays.push_back(*it);
        else if (find(src_0, src_1, *it) != src_1)
            source_arrays.push_back(*it);
        else
            TECA_FATAL_ERROR("\"" << *it << "\" was not in target nor source")
    }

    target_req.set("arrays", target_arrays);
    source_req.set("arrays", source_arrays);

    // get the target extent and coordinates
    teca_metadata target_coords;
    p_teca_variant_array target_x, target_y, target_z;

    if (input_md[md_tgt].get("coordinates", target_coords)
        || !(target_x = target_coords.get("x"))
        || !(target_y = target_coords.get("y"))
        || !(target_z = target_coords.get("z")))
    {
        TECA_FATAL_ERROR("failed to locate target mesh coordinates")
        return up_reqs;
    }

    // get the actual bounds of what we will be served with. this will be a
    // region covering the requested bounds. we need to insure that source data
    // covers this region, not just the requested bounds.
    double request_bounds[6] = {0.0};
    unsigned long target_extent[6] = {0l};
    if (request.get("bounds", request_bounds, 6))
    {
        if (request.get("extent", target_extent, 6))
        {
            target_extent[1] = target_x->size() - 1;
            target_extent[3] = target_y->size() - 1;
            target_extent[5] = target_z->size() - 1;
        }
    }
    else
    {
        if (teca_coordinate_util::bounds_to_extent(request_bounds,
                target_x, target_y, target_z, target_extent) ||
            teca_coordinate_util::validate_extent(target_x->size(),
                target_y->size(), target_z->size(), target_extent, true))
        {
            TECA_FATAL_ERROR("invalid bounds requested [" << request_bounds[0]  << ", "
                << request_bounds[1] << ", " << request_bounds[2] << ", "
                << request_bounds[3] << ", " << request_bounds[4] << ", "
                << request_bounds[5] << "]")
            return up_reqs;
        }
    }

    double target_bounds[6] = {0.0};
    target_x->get(target_extent[0], target_bounds[0]);
    target_x->get(target_extent[1], target_bounds[1]);
    target_y->get(target_extent[2], target_bounds[2]);
    target_y->get(target_extent[3], target_bounds[3]);
    target_z->get(target_extent[4], target_bounds[4]);
    target_z->get(target_extent[5], target_bounds[5]);

    // if the source is 2D, the cf_reader may have faked the vertical dimension.
    // in that case, use the source's vertical coordinate in the requested bounds
    teca_metadata source_coords;
    p_teca_variant_array source_z;

    if (input_md[md_src].get("coordinates", source_coords)
        || !(source_z = source_coords.get("z")))
    {
        TECA_FATAL_ERROR("failed to locate source mesh coordinates")
        return up_reqs;
    }

    if (source_z->size() == 1)
    {
        source_z->get(0, target_bounds[4]);
        source_z->get(0, target_bounds[5]);
    }

    // send the target bounds to the source as well
    source_req.set("bounds", target_bounds, 6);

    // send the requests up
    up_reqs[md_tgt] = target_req;
    up_reqs[md_src] = source_req;

#ifdef TECA_DEBUG
    std::cerr << "source request = ";
    source_req.to_stream(std::cerr);
    std::cerr << std::endl;

    std::cerr << "target request = ";
    target_req.to_stream(std::cerr);
    std::cerr << std::endl;
#endif

    return up_reqs;
}

// --------------------------------------------------------------------------
const_p_teca_dataset teca_cartesian_mesh_regrid::execute(
    unsigned int port, const std::vector<const_p_teca_dataset> &input_data,
    const teca_metadata &request)
{
#ifdef TECA_DEBUG
    cerr << teca_parallel_id()
        << "teca_cartesian_mesh_regrid::execute" << endl;
#endif
    (void)port;

    int md_src = this->target_input ? 0 : 1;
    int md_tgt = this->target_input ? 1 : 0;

    p_teca_cartesian_mesh in_target
        = std::dynamic_pointer_cast<teca_cartesian_mesh>(
            std::const_pointer_cast<teca_dataset>(input_data[md_tgt]));

    const_p_teca_cartesian_mesh source
        = std::dynamic_pointer_cast<const teca_cartesian_mesh>(
            input_data[md_src]);

    if (!in_target || !source)
    {
        TECA_FATAL_ERROR("invalid input. target invalid "
            << !in_target << " source invalid " << !source )
        return nullptr;
    }

    // create the output
    p_teca_cartesian_mesh target = teca_cartesian_mesh::New();
    target->shallow_copy(in_target);

    // get the source and targent extents
    std::vector<unsigned long> source_ext;
    source->get_extent(source_ext);

    std::vector<unsigned long> target_ext;
    target->get_extent(target_ext);

    // get the list of arrays to move
    std::vector<std::string> req_arrays;
    request.get("arrays", req_arrays);

    // add any explicitly named
    std::copy(this->arrays.begin(), this->arrays.end(),
        std::back_inserter(req_arrays));

    // route. if found in the target then  routed there, else
    // if found in the source routed there, else skipped
    std::vector<std::string> source_arrays;

    std::vector<std::string>::iterator it = req_arrays.begin();
    std::vector<std::string>::iterator end = req_arrays.end();
    for (; it != end; ++it)
    {
        if (!target->get_point_arrays()->has(*it))
        {
            if (source->get_point_arrays()->has(*it))
            {
                source_arrays.push_back(*it);
            }
            else
            {
                TECA_FATAL_ERROR("Array \"" << *it
                    << "\" is neither present in source or target mesh")
                return nullptr;
            }
        }
    }

    // catch a user error
    if (!source_arrays.size() &&
        teca_mpi_util::mpi_rank_0(this->get_communicator()))
    {
        TECA_WARNING("No arrays will be interpolated")
    }

    // move the arrays
    const_p_teca_variant_array target_xc = target->get_x_coordinates();
    const_p_teca_variant_array target_yc = target->get_y_coordinates();
    const_p_teca_variant_array target_zc = target->get_z_coordinates();
    p_teca_array_collection target_ac = target->get_point_arrays();

    unsigned long target_nx = target_xc->size();
    unsigned long target_ny = target_yc->size();
    unsigned long target_nz = target_zc->size();
    unsigned long target_size = target_nx*target_ny*target_nz;
    unsigned long target_ihi = target_nx - 1;
    unsigned long target_jhi = target_ny - 1;
    unsigned long target_khi = target_nz - 1;

    const_p_teca_variant_array source_xc = source->get_x_coordinates();
    const_p_teca_variant_array source_yc = source->get_y_coordinates();
    const_p_teca_variant_array source_zc = source->get_z_coordinates();
    const_p_teca_array_collection source_ac = source->get_point_arrays();

    unsigned long source_nx = source_xc->size();
    unsigned long source_ny = source_yc->size();
    unsigned long source_nz = source_zc->size();
    unsigned long source_ihi = source_nx - 1;
    unsigned long source_jhi = source_ny - 1;
    unsigned long source_khi = source_nz - 1;

    // copy when the input and output meshes are the same. the meshes are the
    // same when they span the same world space and have the same resolution
    double tx0, tx1, sx0, sx1;
    source_xc->get(0, sx0);
    target_xc->get(0, tx0);
    source_xc->get(source_ihi, sx1);
    target_xc->get(target_ihi, tx1);

    double ty0, ty1, sy0, sy1;
    source_yc->get(0, sy0);
    target_yc->get(0, ty0);
    source_yc->get(source_jhi, sy1);
    target_yc->get(target_jhi, ty1);

    double tz0, tz1, sz0, sz1;
    source_zc->get(0, sz0);
    target_zc->get(0, tz0);
    source_zc->get(source_khi, sz1);
    target_zc->get(target_khi, tz1);

    if ((source_nx == target_nx) && (source_ny == target_ny) && (source_nz == target_nz) &&
        teca_coordinate_util::equal(sx0, tx0) && teca_coordinate_util::equal(sx1, tx1) &&
        teca_coordinate_util::equal(sy0, ty0) && teca_coordinate_util::equal(sy1, ty1) &&
        teca_coordinate_util::equal(sz0, tz0) && teca_coordinate_util::equal(sz1, tz1))
    {
        if (this->verbose)
            TECA_STATUS("Identical mesh detected. Copying data.")

        size_t n_arrays = source_arrays.size();
        for (size_t i = 0; i < n_arrays; ++i)
        {
            const_p_teca_variant_array source_a = source_ac->get(source_arrays[i]);
            p_teca_variant_array target_a = source_a->new_copy();
            target_ac->set(source_arrays[i], target_a);
        }
    }
    else
    {
        if (this->verbose)
            TECA_STATUS("Interpolating data.")

        NESTED_TEMPLATE_DISPATCH_FP(
            const teca_variant_array_impl,
            target_xc.get(),
            _TGT,

            const NT_TGT *p_target_xc = std::dynamic_pointer_cast<TT_TGT>(target_xc)->get();
            const NT_TGT *p_target_yc = std::dynamic_pointer_cast<TT_TGT>(target_yc)->get();
            const NT_TGT *p_target_zc = std::dynamic_pointer_cast<TT_TGT>(target_zc)->get();

            NESTED_TEMPLATE_DISPATCH_FP(
                const teca_variant_array_impl,
                source_xc.get(),
                _SRC,

                const NT_SRC *p_source_xc = std::dynamic_pointer_cast<TT_SRC>(source_xc)->get();
                const NT_SRC *p_source_yc = std::dynamic_pointer_cast<TT_SRC>(source_yc)->get();
                const NT_SRC *p_source_zc = std::dynamic_pointer_cast<TT_SRC>(source_zc)->get();

                size_t n_arrays = source_arrays.size();
                for (size_t i = 0; i < n_arrays; ++i)
                {
                    const_p_teca_variant_array source_a = source_ac->get(source_arrays[i]);
                    p_teca_variant_array target_a = source_a->new_instance();
                    target_a->resize(target_size);

                    NESTED_TEMPLATE_DISPATCH(
                        teca_variant_array_impl,
                        target_a.get(),
                        _DATA,

                        const NT_DATA *p_source_a = std::static_pointer_cast<const TT_DATA>(source_a)->get();
                        NT_DATA *p_target_a = std::static_pointer_cast<TT_DATA>(target_a)->get();

                        if (interpolate(this->interpolation_mode, target_nx, target_ny, target_nz,
                            p_target_xc, p_target_yc, p_target_zc, p_target_a, p_source_xc,
                            p_source_yc, p_source_zc, p_source_a, source_ihi, source_jhi,
                            source_khi, source_nx, source_ny, source_nz))
                        {
                            TECA_FATAL_ERROR("Failed to move \"" << source_arrays[i] << "\"")
                            return nullptr;
                        }
                        )
                    else
                    {
                        TECA_FATAL_ERROR("Unsupported array type " << source_a->get_class_name())
                    }

                    target_ac->set(source_arrays[i], target_a);
                }
                )
            else
            {
                TECA_FATAL_ERROR("Unupported coordinate type " << source_xc->get_class_name())
            }
            )
        else
        {
            TECA_FATAL_ERROR("Unupported coordinate type " << target_xc->get_class_name())
        }
    }

    return target;
}


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

  @copyright 2016 NumScale SAS

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

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!
    @ingroup group-arithmetic
    Function object implementing rec capabilities

    Returns the inverse (reciprocal) of the entry.

    @par semantic:
    For any given value @c x of type @c T:

    @code
    T r = rec(x);
    @endcode

    The code is similar to:

    @code
    T r = T(1)/x;
    @endcode

    @par Note

    For integral typed entries the result is always in the set \f$\{0,  \pm1, Valmax \}\f$

    @par Decorators

     For floating types @c rec has several decorated variations

     - raw_ Many simd architectures provide an intrinsic that computes some bits of the inverse (at least 12)
            and don't care of denormals or limiting values. If it exists this is obtained by the raw_ decorator.
            As usual if it doesn't the plain rec is called.

     - with no decorators ensure 1ulp but still doesn't care of limiting value or denormals

  **/
  Value rec(Value const & v0);
} }
#endif

#include <boost/simd/function/scalar/rec.hpp>
#include <boost/simd/function/simd/rec.hpp>

#endif


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_KASUMI_FUNCTIONS_CPP_HPP
#define CRYPTO3_KASUMI_FUNCTIONS_CPP_HPP

#include <boost/crypto3/block/detail/kasumi/kasumi_policy.hpp>

namespace boost {
    namespace crypto3 {
        namespace block {
            namespace detail {
                struct kasumi_functions : public kasumi_policy {
                    constexpr static const std::size_t word_bits = kasumi_policy::word_bits;
                    typedef typename kasumi_policy::word_type word_type;

                    static inline word_type FI(word_type I, word_type K) {
                        word_type D9 = (I >> 7);
                        word_type D7 = (I & 0x7F);
                        D9 = s9_substitution[D9] ^ D7;
                        D7 = s7_substitution[D7] ^ (D9 & 0x7F);

                        D7 ^= (K >> 9);
                        D9 = s9_substitution[D9 ^ (K & 0x1FF)] ^ D7;
                        D7 = s7_substitution[D7] ^ (D9 & 0x7F);
                        return static_cast<word_type>(D7 << 9) | D9;
                    }
                };
            }    // namespace detail
        }        // namespace block
    }            // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_KASUMI_FUNCTIONS_CPP_HPP


/**
 * @file
 * @copyright This code is licensed under the 3-clause BSD license.\n
 *            Copyright ETH Zurich, Laboratory of Physical Chemistry, Reiher Group.\n
 *            See LICENSE.txt for details.
 */
#include "DavidsonDiagonalizer.h"
#include "../MathUtils.h"
#include "DiagonalizerSettings.h"
#include "PreconditionerEvaluator.h"
#include "SubspaceOrthogonalizer.h"
#include <Core/Log.h>
#include <Eigen/Eigenvalues>

namespace Scine {
namespace Utils {

NonOrthogonalDavidson::NonOrthogonalDavidson(int eigenvaluesToCompute, int totalDimension)
  : KrylovDiagonalizer(eigenvaluesToCompute, totalDimension) {
}

NonOrthogonalDavidson::~NonOrthogonalDavidson() = default;

EigenContainer NonOrthogonalDavidson::eigenDecomposition(const Eigen::MatrixXd& projectedMatrix) const {
  auto algorithm = settings_->getString(gepAlgorithmForBalancedMethodOption);
  if (algorithm == "standard")
    return MathUtils::stabilizedGeneralizedEigendecomposition<MathUtils::GepAlgorithm::Standard>(
        projectedMatrix, basisOverlap_.selfadjointView<Eigen::Upper>());
  else if (algorithm == "cholesky")
    return MathUtils::stabilizedGeneralizedEigendecomposition<MathUtils::GepAlgorithm::Cholesky>(
        projectedMatrix, basisOverlap_.selfadjointView<Eigen::Upper>());
  else if (algorithm == "simultaneous_diag")
    return MathUtils::stabilizedGeneralizedEigendecomposition<MathUtils::GepAlgorithm::SimultaneousDiagonalization>(
        projectedMatrix, basisOverlap_.selfadjointView<Eigen::Upper>());
  else
    throw InvalidDiagonalizerInputException("Algorithm " + algorithm + " not available for stable GEP solution.");
}

inline void NonOrthogonalDavidson::onSigmaMatrixEvaluation(const Eigen::MatrixXd& projector) {
  decltype(basisOverlap_)::Index newCols = subspaceDimension_ - basisOverlap_.cols();
  basisOverlap_.conservativeResize(subspaceDimension_, subspaceDimension_);
  Eigen::MatrixXd rightCols = projector.rightCols(newCols);
  basisOverlap_.rightCols(newCols) = projector.transpose() * rightCols;
}

inline void NonOrthogonalDavidson::filterCorrectionVectors(const Eigen::MatrixXd& projector,
                                                           Eigen::MatrixXd& newGuessVectors) const {
  double absNorm =
      std::abs(newGuessVectors.col(newGuessVectors.cols() - 1).transpose() * projector.col(projector.cols() - 1));
  if (absNorm > 1.0 - settings_->getDouble(correctionToleranceOption)) {
    newGuessVectors.col(newGuessVectors.cols() - 1) = residualVectors_.col(newGuessVectors.cols() - 1);
  }
}

void NonOrthogonalDavidson::callCollapserImpl() {
  guessVectors_ = subspaceCollapser_->getCollapsedNonOrthogonalSubspace();
  subspaceDimension_ = guessVectors_.cols();
  basisOverlap_.resize(0, 0);
}

OrthogonalDavidson::OrthogonalDavidson(int eigenvaluesToCompute, int totalDimension)
  : KrylovDiagonalizer(eigenvaluesToCompute, totalDimension) {
}

OrthogonalDavidson::~OrthogonalDavidson() = default;

inline void OrthogonalDavidson::filterCorrectionVectors(const Eigen::MatrixXd& projector, Eigen::MatrixXd& newGuessVectors) const {
  Eigen::MatrixXd orthogonalizedCorrectionVector = newGuessVectors;
  Eigen::VectorXd correctionVectorsNorms = newGuessVectors.colwise().norm();

  int dimension = 0;
  double maxNorm = 0;
  Eigen::VectorXd maxNormOrthogonalizedVector;
  for (int i = 0; i < orthogonalizedCorrectionVector.cols(); ++i) {
    Eigen::VectorXd orthogonalizedVector;
    SubspaceOrthogonalizer::orthogonalizeToSubspace(orthogonalizedCorrectionVector.col(i), projector, orthogonalizedVector);

    double norm = orthogonalizedVector.norm() / correctionVectorsNorms(i);
    if (norm > maxNorm) {
      std::swap(norm, maxNorm);
      maxNormOrthogonalizedVector = orthogonalizedVector;
    }

    if (norm / correctionVectorsNorms(i) > settings_->getDouble(correctionToleranceOption)) {
      newGuessVectors.col(dimension++) = orthogonalizedVector;
    }
  }
  // Retain one at least if everything is thrown away, the one that corrects the most
  if (dimension == 0) {
    newGuessVectors.col(0) = maxNormOrthogonalizedVector;
    dimension = 1;
  }
  newGuessVectors.conservativeResize(Eigen::NoChange, dimension);
}

inline void OrthogonalDavidson::onIterationStart() {
  KrylovDiagonalizer::onIterationStart();
  SubspaceOrthogonalizer::qrOrthogonalize(guessVectors_, subspaceDimension_);
  guessVectors_.leftCols(subspaceDimension_).colwise().normalize();
}

EigenContainer OrthogonalDavidson::eigenDecomposition(const Eigen::MatrixXd& projectedMatrix) const {
  EigenContainer result;
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> subspaceDiagonalizer(projectedMatrix);
  result.eigenVectors = subspaceDiagonalizer.eigenvectors();
  result.eigenValues = subspaceDiagonalizer.eigenvalues();
  return result;
}

void OrthogonalDavidson::callCollapserImpl() {
  guessVectors_ = subspaceCollapser_->getCollapsedOrthogonalSubspace();
  subspaceDimension_ = guessVectors_.cols();
}
} // namespace Utils
} // namespace Scine


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2012 - 2014 MetaScale SAS
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_GENERIC_REMAINDER_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_GENERIC_REMAINDER_HPP_INCLUDED
#include <boost/simd/arithmetic/functions/remainder.hpp>
#include <boost/simd/include/functions/simd/idivround2even.hpp>
#include <boost/simd/include/functions/simd/divround2even.hpp>
#include <boost/simd/include/functions/simd/multiplies.hpp>
#include <boost/simd/include/functions/simd/minus.hpp>
#include <boost/simd/include/functions/simd/selsub.hpp>
#include <boost/simd/include/functions/simd/is_nez.hpp>
#include <boost/dispatch/attributes.hpp>

/////////////////////////////////////////////////////////////////////////////
// The remainder() function computes the remainder of dividing x by y.  The
// return value is x-n*y, where n is the value x / y, rounded to the nearest
// integer.  If the boost::simd::absolute value of x-n*y is 0.5, n is chosen
// to be even.
// The drem function is just an alias for the same thing.
// As the result can be negative the functor is not defined for unsigned
// entries
/////////////////////////////////////////////////////////////////////////////

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( remainder_, tag::cpu_
                                    , (A0)
                                    , (generic_< signed_<A0> >)
                                      (generic_< signed_<A0> >)
                                    )
  {
    typedef A0 result_type;
    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return selsub(is_nez(a1),a0,
                    boost::simd::multiplies(idivround2even(a0, a1), a1));
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( remainder_, tag::cpu_
                                    , (A0)
                                    , (generic_< floating_<A0> >)
                                      (generic_< floating_<A0> >)
                                    )
  {
    typedef A0 result_type;
    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return a0-divround2even(a0, a1)*a1;
    }
  };
} } }

#endif


#include <mex.h>
#include <math.h>
#include <Eigen/Dense>
#include <iostream>

using namespace Eigen;
using namespace std;

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{

    mxArray *output_mex;

    double *tri_num, *obj_tri, *q_target;
    double *output;

    tri_num = mxGetPr(prhs[0]);
    obj_tri = mxGetPr(prhs[1]);
    q_target = mxGetPr(prhs[2]);

    int tri_n = tri_num[0];

    output_mex = plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);

    output = mxGetPr(output_mex);

    int tri[3];

    output[0] = 1;

    tri[0] = obj_tri[0] - 1;
    tri[1] = obj_tri[0 + tri_n] - 1;
    tri[2] = obj_tri[0 + 2 * tri_n] - 1;

    Vector3d va(q_target[2 * tri[0]], q_target[2 * tri[0] + 1], 0);
    Vector3d vb(q_target[2 * tri[1]], q_target[2 * tri[1] + 1], 0);
    Vector3d vc(q_target[2 * tri[2]], q_target[2 * tri[2] + 1], 0);

    Vector3d e1 = va - vb;
    Vector3d e2 = vc - vb;
    Vector3d flag = e1.cross(e2);

    for(int i = 0; i < tri_n; i++)
    {
        tri[0] = obj_tri[i] - 1;
        tri[1] = obj_tri[i + tri_n] - 1;
        tri[2] = obj_tri[i + 2 * tri_n] - 1;

        Vector3d va(q_target[2 * tri[0]], q_target[2 * tri[0] + 1], 0);
        Vector3d vb(q_target[2 * tri[1]], q_target[2 * tri[1] + 1], 0);
        Vector3d vc(q_target[2 * tri[2]], q_target[2 * tri[2] + 1], 0);

        Vector3d e1 = va - vb;
        Vector3d e2 = vc - vb;
        Vector3d f1 = e1.cross(e2);

        if(f1.dot(flag) < 0)
        {
            output[0] = 0;
        }
    }

    return;

}


#include <CGAL/config.h>
#include <iostream>

#if defined(CGAL_USE_BOOST_PROGRAM_OPTIONS) && ! defined(DONT_USE_BOOST_PROGRAM_OPTIONS)

#include <iomanip>
#include <fstream>
#include <utility>
#include <cstdlib>
#include <cmath>

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

#include <CGAL/Random.h>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel_with_sqrt.h>
#include <CGAL/Simple_cartesian.h>

#include <CGAL/Polyhedron_3.h>
#include <CGAL/Polyhedron_items_with_id_3.h>

#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/property_map.hpp>

#include <CGAL/boost/graph/iterator.h>

#include <CGAL/Surface_mesh_shortest_path/Surface_mesh_shortest_path_traits.h>
#include <CGAL/Surface_mesh_shortest_path/Surface_mesh_shortest_path.h>
#include <CGAL/Surface_mesh_shortest_path/function_objects.h>
#include <CGAL/Surface_mesh_shortest_path/barycentric.h>
#include <CGAL/Surface_mesh_shortest_path/internal/misc_functions.h>

#include <CGAL/test_util.h>

using namespace CGAL::test;
namespace po = boost::program_options;

template <class Kernel>
struct TestMeshProgramInstance
{
  //typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
  typedef CGAL::Polyhedron_3<Kernel, CGAL::Polyhedron_items_with_id_3> Polyhedron_3;
  typedef CGAL::Surface_mesh_shortest_path_traits<Kernel, Polyhedron_3> Traits;
  typedef typename Traits::Barycentric_coordinates Barycentric_coordinates;
  typedef typename Traits::FT FT;
  typedef typename Traits::Point_3 Point_3;
  typedef typename Traits::Point_2 Point_2;
  typedef typename Traits::Triangle_3 Triangle_3;
  typedef typename Traits::Triangle_2 Triangle_2;
  typedef typename Traits::Segment_2 Segment_2;
  typedef boost::graph_traits<Polyhedron_3> Graph_traits;
  typedef typename Graph_traits::vertex_descriptor vertex_descriptor;
  typedef typename Graph_traits::vertex_iterator vertex_iterator;
  typedef typename Graph_traits::halfedge_descriptor halfedge_descriptor;
  typedef typename Graph_traits::halfedge_iterator halfedge_iterator;
  typedef typename Graph_traits::face_descriptor face_descriptor;
  typedef typename Graph_traits::face_iterator face_iterator;
  typedef CGAL::Surface_mesh_shortest_path<Traits> Surface_mesh_shortest_path;
  typedef typename Surface_mesh_shortest_path::Face_location Face_location;
  typedef typename boost::property_map<Polyhedron_3, CGAL::vertex_point_t>::const_type VPM;
  typedef typename boost::property_map<typename Traits::Triangle_mesh, boost::vertex_index_t>::const_type VIM;
  typedef typename boost::property_map<typename Traits::Triangle_mesh, boost::edge_index_t>::const_type EIM;
  typedef typename boost::property_map<typename Traits::Triangle_mesh, boost::halfedge_index_t>::const_type HIM;
  typedef typename boost::property_map<typename Traits::Triangle_mesh, boost::face_index_t>::const_type FIM;

  TestMeshProgramInstance()
  {
    debugMode = false;
    randomizer = nullptr;
    numVertices = 0;
    numIterations = 1;
  }

  size_t numIterations;
  std::string meshName;
  bool debugMode;

  CGAL::Random* randomizer;
  size_t numVertices;

  Face_location next_location(Surface_mesh_shortest_path& shortestPath, Polyhedron_3& polyhedron, const std::vector<vertex_descriptor>& vertices)
  {
    typename Traits::Construct_barycentric_coordinates construct_barycentric_coordinates;

    if (randomizer)
    {
      return shortestPath.face_location(vertices[randomizer->get_int(0, static_cast<int>(vertices.size()))]);
    }
    else
    {
      std::string type;

      std::cin >> type;

      boost::algorithm::to_lower(type);

      if (type == "v")
      {
        size_t x;
        std::cin >> x;

        return shortestPath.face_location(vertices[x]);
      }
      else if (type == "e")
      {
        size_t x, y;
        double alpha;
        std::cin >> x >> y >> alpha;
        std::pair<halfedge_descriptor, bool> he = halfedge(vertices[x], vertices[y], polyhedron);
        assert(he.second);
        return shortestPath.face_location(he.first, FT(alpha));
      }
      else if (type == "f")
      {
        size_t x, y;
        double alpha0, alpha1, alpha2;
        std::cin >> x >> y >> alpha0 >> alpha1 >> alpha2;
        std::pair<halfedge_descriptor, bool> he = halfedge(vertices[x], vertices[y], polyhedron);
        return Face_location(face(he.first, polyhedron), construct_barycentric_coordinates(FT(alpha0), FT(alpha1), FT(alpha2)));
      }

      return Face_location(Graph_traits::null_face(), construct_barycentric_coordinates(FT(0.0), FT(0.0), FT(0.0)));
    }
  }

  size_t next_vertex()
  {
    if (randomizer)
    {
      return randomizer->get_int(0, numVertices);
    }
    else
    {
      size_t x;
      std::cin >> x;
      return x;
    }
  }

  void test_mesh_function()
  {
    Traits traits;

    Polyhedron_3 polyhedron;
    std::ifstream in(meshName.c_str());

    in >> polyhedron;

    in.close();

    CGAL::set_halfedgeds_items_id(polyhedron);

    numVertices = num_vertices(polyhedron);

    VIM vertexIndexMap(get(boost::vertex_index, polyhedron));
    HIM halfedgeIndexMap(get(boost::halfedge_index, polyhedron));
    FIM faceIndexMap(get(boost::face_index, polyhedron));

    vertex_iterator verticesStart;
    vertex_iterator verticesEnd;

    std::vector<vertex_descriptor> vertices;

    boost::tie(verticesStart, verticesEnd) = CGAL::vertices(polyhedron);

    for (vertex_iterator it = verticesStart; it != verticesEnd; ++it)
    {
      vertices.push_back(*it);
    }

    face_iterator facesStart;
    face_iterator facesEnd;

    std::vector<face_descriptor> faces;

    boost::tie(facesStart, facesEnd) = CGAL::faces(polyhedron);

    for (face_iterator it = facesStart; it != facesEnd; ++it)
    {
      faces.push_back(*it);
    }

    Surface_mesh_shortest_path startToEndShortestPaths(polyhedron, traits);
    startToEndShortestPaths.m_debugOutput = debugMode;

    Surface_mesh_shortest_path endToStartShortestPaths(polyhedron, traits);
    endToStartShortestPaths.m_debugOutput = debugMode;

    std::cout << "Mesh: " << meshName << " " << num_vertices(polyhedron) << " " << num_faces(polyhedron) << " " << num_halfedges(polyhedron) << std::endl;

    std::cout << std::setprecision(20);

    for (size_t i = 0; i < numIterations; ++i)
    {
      Face_location startLocation = next_location(startToEndShortestPaths, polyhedron, vertices);
      Face_location endLocation = next_location(endToStartShortestPaths, polyhedron, vertices);

      std::cout << "STE(location): " << faceIndexMap[startLocation.first] << " , " << startLocation.second[0] << " " << startLocation.second[1] << " " << startLocation.second[2] << " " << std::endl;
      std::cout << "ETS(location): " << faceIndexMap[endLocation.first] << " , " << endLocation.second[0] << " " << endLocation.second[1] << " " << endLocation.second[2] << " " << std::endl;

      startToEndShortestPaths.clear();
      startToEndShortestPaths.add_source_point(startLocation.first, startLocation.second);
      startToEndShortestPaths.build_sequence_tree();

      CGAL::test::Edge_sequence_collector<Traits> startToEndCollector(vertexIndexMap, halfedgeIndexMap, faceIndexMap);

      startToEndShortestPaths.shortest_path_sequence_to_source_points(endLocation.first, endLocation.second, startToEndCollector);

      FT startToEnd = startToEndShortestPaths.shortest_distance_to_source_points(endLocation.first, endLocation.second).first;

      endToStartShortestPaths.clear();
      endToStartShortestPaths.add_source_point(endLocation.first, endLocation.second);
      endToStartShortestPaths.build_sequence_tree();

      CGAL::test::Edge_sequence_collector<Traits> endToStartCollector(vertexIndexMap, halfedgeIndexMap, faceIndexMap);
      endToStartShortestPaths.shortest_path_sequence_to_source_points(startLocation.first, startLocation.second, endToStartCollector);

      FT endToStart = endToStartShortestPaths.shortest_distance_to_source_points(startLocation.first, startLocation.second).first;

      std::cout << "STE(distance): " << startToEnd << std::endl;
      std::cout << "ETS(distance): " << endToStart << std::endl;
      if (CGAL::abs(startToEnd - endToStart) > FT(0.000001))
      {
        std::cout << "STE/ETS: distanceerror!" << std::endl;
      }
      std::cout << "STE(size): " << startToEndCollector.m_sequence.size() << std::endl;
      std::cout << "ETS(size): " << endToStartCollector.m_sequence.size() << std::endl;
      if (startToEndCollector.m_sequence.size() != endToStartCollector.m_sequence.size())
      {
        std::cout << "STE/ETS: sizeerror!" << std::endl;
      }

      std::string names[2] = { "STE", "ETS" };
      Surface_mesh_shortest_path* pathStructures[2] = { &startToEndShortestPaths, &endToStartShortestPaths };
      CGAL::test::Edge_sequence_collector<Traits>* collectors[2] = { &startToEndCollector, &endToStartCollector };

      for (size_t d = 0; d < 2; ++d)
      {
        for (size_t j = 0; j < collectors[d]->m_sequence.size(); ++j)
        {
          std::cout << names[d] << "(sequence:" << j << "): ";

          CGAL::test::Sequence_item<Traits>& seqItem = collectors[d]->m_sequence[j];

          if (seqItem.type == CGAL::test::SEQUENCE_ITEM_EDGE)
          {
            std::cout << vertexIndexMap[source(seqItem.halfedge, polyhedron)] << " , " << vertexIndexMap[target(seqItem.halfedge, polyhedron)] << " : " << seqItem.edgeAlpha << std::endl;
          }
          else if (seqItem.type == CGAL::test::SEQUENCE_ITEM_VERTEX)
          {
            std::cout << vertexIndexMap[seqItem.vertex] << " , Distance: " << pathStructures[d]->shortest_distance_to_source_points(seqItem.vertex).first << std::endl;
          }
        }
      }
    }
  }
};

template <class Kernel>
void run_program_instance(po::variables_map& vm)
{
  TestMeshProgramInstance<Kernel> programInstance;

  if (vm.count("randomseed"))
  {
    programInstance.randomizer = new CGAL::Random(vm["randomSeed"].as<unsigned int>());
  }

  programInstance.debugMode = vm["debugmode"].as<bool>();
  programInstance.numIterations = vm["trials"].as<size_t>();
  programInstance.meshName = vm["polyhedron"].as<std::string>();

  programInstance.test_mesh_function();
}

enum Kernel_type
{
  KERNEL_UNKNOWN,
  KERNEL_IPICK,
  KERNEL_EPICK,
  KERNEL_EPECK
};

Kernel_type parse_kernel_type(const std::string& s)
{
  if (boost::iequals(s, "ipick"))
  {
    return KERNEL_IPICK;
  }
  else if (boost::iequals(s, "epick"))
  {
    return KERNEL_EPICK;
  }
  else if (boost::iequals(s, "epeck"))
  {
    return KERNEL_EPECK;
  }
  else
  {
    return KERNEL_UNKNOWN;
  }
}

int main(int argc, char** argv)
{
  po::options_description options;

  options.add_options()
    ("help,h", "Display help message")
    ("polyhedron,p", po::value<std::string>(), "Polyhedron input file")
    ("debugmode,d", po::value<bool>()->default_value(false), "Enable debug output")
    ("randomseed,r", po::value<unsigned int>(), "Randomization seed value")
    ("trials,t", po::value<size_t>()->default_value(1), "Number of trials to run")
    ("kernel,k", po::value<std::string>()->default_value("epick"), "Kernel to use.  One of \'ipick\', \'epick\', \'epeck\'")
    ;

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

  if (vm.count("help"))
  {
    std::cout << options << std::endl;
  }
  else if (vm.count("polyhedron"))
  {
    switch (parse_kernel_type(vm["kernel"].as<std::string>()))
    {
      case KERNEL_IPICK:
        run_program_instance<CGAL::Simple_cartesian<double> >(vm);
        break;
      case KERNEL_EPICK:
        run_program_instance<CGAL::Exact_predicates_inexact_constructions_kernel>(vm);
        break;
      case KERNEL_EPECK:
        run_program_instance<CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt>(vm);
        break;
      default:
        std::cerr << "Invalid kernel: \"" << vm["kernel"].as<std::string>() << "\"." << std::endl;
    }
  }
  else
  {
    std::cerr << "No polyhedron specified, nothing will be tested." << std::endl;
  }

  return 0;
}

#else
 int main()
 {
   std::cout << "TestMesh.cpp needs Boost Program Options" << std::endl;
   return 0;
 }
#endif


// Boost.Geometry

// Copyright (c) 2019, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_STRATEGIES_SPHERICAL_POINT_ORDER_HPP
#define BOOST_GEOMETRY_STRATEGIES_SPHERICAL_POINT_ORDER_HPP

#include <boost/type_traits/is_same.hpp>

#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/formulas/spherical.hpp>

#include <boost/geometry/strategies/spherical/area.hpp>
#include <boost/geometry/strategies/spherical/point_in_point.hpp>
#include <boost/geometry/strategies/point_order.hpp>

#include <boost/geometry/util/math.hpp>
#include <boost/geometry/util/select_calculation_type.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace point_order
{

//template <typename CalculationType = void>
//struct spherical
//{
//    typedef azimuth_tag version_tag;
//
//    template <typename Geometry>
//    struct result_type
//    {
//        typedef typename geometry::select_calculation_type_alt
//            <
//                CalculationType, Geometry
//            >::type type;
//    };
//
//    template <typename Point>
//    inline bool apply(Point const& p1, Point const& p2,
//                      typename result_type<Point>::type & azi,
//                      typename result_type<Point>::type & razi) const
//    {
//        typedef typename result_type<Point>::type calc_t;
//
//        if (equals_point_point(p1, p2))
//        {
//            return false;
//        }
//
//        calc_t lon1 = geometry::get_as_radian<0>(p1);
//        calc_t lat1 = geometry::get_as_radian<1>(p1);
//        calc_t lon2 = geometry::get_as_radian<0>(p2);
//        calc_t lat2 = geometry::get_as_radian<1>(p2);
//
//        convert_latitudes<Point>(lat1, lat2);
//
//        formula::result_spherical<calc_t>
//            res = formula::spherical_azimuth<calc_t, true>(lon1, lat1, lon2, lat2);
//
//        azi = res.azimuth;
//        razi = res.reverse_azimuth;
//
//        return true;
//    }
//
//    template <typename Point>
//    inline typename result_type<Point>::type
//    apply(Point const& /*p0*/, Point const& /*p1*/, Point const& /*p2*/,
//          typename result_type<Point>::type const& azi1,
//          typename result_type<Point>::type const& azi2) const
//    {
//        // TODO: support poles
//        return math::longitude_distance_signed<radian>(azi1, azi2);
//    }
//
//private:
//    template <typename Point>
//    static bool equals_point_point(Point const& p0, Point const& p1)
//    {
//        return strategy::within::spherical_point_point::apply(p0, p1);
//    }
//
//    template <typename Point, typename CalcT>
//    static void convert_latitudes(CalcT & lat1, CalcT & lat2)
//    {
//        static const bool is_polar = boost::is_same
//            <
//                typename geometry::cs_tag<Point>::type,
//                spherical_polar_tag
//            >::value;
//
//        if (BOOST_GEOMETRY_CONDITION(is_polar))
//        {
//            CalcT pi_half = math::half_pi<CalcT>();
//            lat1 = pi_half - lat1;
//            lat2 = pi_half - lat2;
//        }
//    }
//};

template <typename CalculationType = void>
struct spherical
    : strategy::area::spherical<double, CalculationType>
{
    typedef area_tag version_tag;
};

namespace services
{

template <>
struct default_strategy<spherical_equatorial_tag>
{
    typedef spherical<> type;
};

/*template <>
struct default_strategy<spherical_polar_tag>
{
    typedef spherical<> type;
};*/

} // namespace services

}} // namespace strategy::point_order

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_SPHERICAL_POINT_ORDER_HPP


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

    @copyright 2016 NumScale SAS

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

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-exponential
    This function object computes: \f$1-e^{-\frac1x}\f$

    @par Header <boost/simd/function/exprecnegc.hpp>

    @par Notes

    - result is accurate even for large @c x

    @par Example:

      @snippet exprecnegc.cpp exprecnegc

    @par Possible output:

      @snippet exprecnegc.txt exprecnegc

  **/
  IEEEValue exprecnegc(IEEEValue const& x);
} }
#endif

#include <boost/simd/function/scalar/exprecnegc.hpp>
#include <boost/simd/function/simd/exprecnegc.hpp>

#endif


// Created by enenra on 19.11.2021.

#ifndef KURSOVAYA_1_HEADER_HPP
#define KURSOVAYA_1_HEADER_HPP

#include "nlohmann/json.hpp"
#include <boost/filesystem.hpp>
#include <algorithm>
#include <regex>
#include <string>
#include <iomanip>
#include <cmath>
#include <ctime>

using json = nlohmann::json;

size_t power(std::string &X);

size_t number_degree(const size_t &val);

int number_degree(const int &val);

class boolean {
private:
    std::vector<int> vec;

public:
    boolean()=default;

    explicit boolean(std::string &init) {
        for (size_t i = 0; i < init.size(); ++i) {
            if ((init[i] == '0') || (init[i] == '1')) {
                if (i == 0)
                    if ((init[i + 1] == '0') || (init[i + 1] == '1')) {
                        if (init[i] == '0')
                            vec.push_back(0);
                        else vec.push_back(1);
                    }
                if ((i > 0) && (i < init.size() - 1))
                    if ((init[i + 1] == '0') || (init[i + 1] == '1') || (init[i - 1] == '0') || (init[i - 1] == '1')) {
                        if (init[i] == '0')
                            vec.push_back(0);
                        else vec.push_back(1);
                    }
                if (i == init.size() - 1) {
                    if ((init[i - 1] == '0') || (init[i - 1] == '1')) {
                        if (init[i] == '0')
                            vec.push_back(0);
                        else vec.push_back(1);
                    }
                }
            }
        }
    }

    boolean(int val, int deg) {
        for (int i = 0; i < deg; ++i)
            vec.push_back(0);
        while (deg > 0) {
            vec[deg - 1] = val % 2;
            val /= 2;
            --deg;
        }
    }

    ~boolean() = default;

    void push_back(int val) {
        vec.push_back(val);
    }

    bool empty() {
        if (vec.empty())
            return true;
        return false;
    }

    int degree() {
        int deg = 0;
        while (pow(2.0, deg) != vec.size())
            deg += 1;
        return deg;
    }

    boolean operator+=(boolean &r) {
        for (size_t i = 0; i < vec.size(); ++i)
            vec[i] ^= r.vec[i];
        return *this;
    }

    boolean operator*=(boolean &r) {
        for (size_t i = 0; i < vec.size(); ++i)
            vec[i] &= r.vec[i];
        return *this;
    }

    int &operator[](size_t &index) {
        return vec[index];
    }

    int &operator[](int index) {
        return vec[index];
    }

    size_t to_number() {
        size_t result = 0;
        for (size_t i = 0; i < vec.size(); ++i)
            result += vec[i] * pow(2.0, vec.size() - 1 - i);
        return result;
    }

    bool operator==(const boolean& r) const {
        if (vec.size() != r.vec.size())
            return false;
        else
            for (size_t i = 0; i < vec.size(); ++i)
                if (vec[i] != r.vec[i])
                    return false;
        return true;
    }

    bool operator!=(const boolean &r) const {
        if (vec.size() != r.vec.size())
            return true;
        else
            for (size_t i = 0; i < vec.size(); ++i)
                if (vec[i] != r.vec[i])
                    return true;
        return false;
    }
};

class cryptalgorithm {
private:
    std::string text;
    std::string cipher;
    std::vector<int> keys;
    std::vector<std::string> functions;

public:
    cryptalgorithm() = default;

    cryptalgorithm(std::string &t, std::string &c, std::vector<int> &k, std::vector<std::string> &f) {
        text = t;
        cipher = c;
        keys = k;
        functions = f;
    }

    ~cryptalgorithm() = default;

    void absolute_stability(std::ofstream &out) {
        out << '\n' << " - ANALYZING ABSOLUTE STABILITY:" << '\n';
        if (power(text) <= keys.size())
            out << '\n' << "Your algorithm is absolutely stable" << '\n';
        else out << '\n' << "Your algorithm is not absolutely stable" << '\n';
        out << '\n';
    }

    void differential_attack(std::ofstream &out) {
        out << '\n' << " - DIFFERENTIAL ATTACK:" << '\n';
        if (functions.size() == 1) {
            out
                    << "Your algorithm does not cannot ba attacked with differential attack, or entered functions are incorrect"
                    << '\n';
            out
                    << "Make sure. that, if Your encryption function is a permutation, You enter it as a system of coordinate boolean functions"
                    << '\n';
            out << "Boolean functions are entered as a vector of 0 and 1" << '\n';
        } else {
            std::vector<boolean> system;
            for (auto &function: functions) {
                boolean f(function);
                if (!f.empty())
                    system.push_back(f);
            }
            if (system.empty()) {
                out
                        << "Your algorithm does not cannot ba attacked with differential attack, or entered functions are incorrect"
                        << '\n';
                out
                        << "Make sure. that, if Your encryption function is a permutation, You enter it as a system of coordinate boolean functions"
                        << '\n';
                out << "Boolean functions are entered as a vector of 0 and 1" << '\n';
            } else {
                unsigned int start = clock();
                int deg = system[0].degree();
                size_t n;
                if (text == "ASCII")
                    n = 256;
                else n = text.size();
                auto **table = new size_t *[n];
                for (size_t k = 0; k < n; ++k) {
                    table[k] = new size_t[n];
                    boolean delta_x(k, deg);
                    for (size_t i = 0; i < n; ++i) {
                        boolean delta_y, y;
                        for (auto &j: system)
                            y.push_back(j[i]);
                        boolean x(i, deg);
                        x += delta_x;
                        size_t index = x.to_number();
                        for (auto &j: system)
                            delta_y.push_back(j[index]);
                        delta_y += y;
                        size_t eq = delta_y.to_number();
                        table[k][i] = eq;
                    }
                    std::vector<size_t> v;
                    for (size_t i = 0; i < n; ++i)
                        v.push_back(0);
                    for (size_t r = 0; r < n; ++r)
                        v[table[k][r]] += 1;
                    for (size_t i = 0; i < n; ++i)
                        table[k][i] = v[i];
                }
                unsigned int end = clock();
                unsigned int time = end - start;
                // Вывод данных
                out << '\n';
                for (size_t i = 0; i <= n; ++i) {
                    for (size_t j = 0; j <= n; ++j) {
                        if (i * j == 0) {
                            if ((i == 0) && (j == 0))
                                out << "|     ";
                            if ((i == 0) && (j != 0)) {
                                size_t val = j - 1;
                                boolean output(val, deg);
                                out << ' ';
                                for (int k = 0; k < deg; ++k)
                                    out << output[k];
                            }
                            if ((i != 0) && (j == 0)) {
                                size_t val = i - 1;
                                boolean output(val, deg);
                                out << "| ";
                                for (int k = 0; k < deg; ++k)
                                    out << output[k];
                            }
                            out << " |";
                        } else {
                            out << "  " << table[i - 1][j - 1];
                            for (size_t h = 0; h < deg - number_degree(table[i - 1][j - 1]); ++h)
                                out << ' ';
                            out << '|';
                        }
                    }
                    out << '\n';
                }
                out
                        << '\n'
                        << "Estimated time of analyzing given S-block to find most probable outcomes from certain entered text is "
                        << time << " ns" << '\n';
                for (size_t i = 0; i < n; ++i)
                    delete[] table[i];
                delete[] table;
            }
        }
    }

    void brute_force(std::ofstream &out) {
        out << '\n' << " - BRUTE FORCE ATTACK:" << '\n';
        size_t n;
        if (text == "ASCII")
            n = 256;
        else
            n = text.size();
        out << '\n' << "This attack is always very long and needs many resources, in your case it will take about ";
        out << n << " factorial, multiplied by the time of one tact." << '\n';
    }

    void linear_attack(std::ofstream &out) {
        out << '\n' << " - LINEAR ATTACK:" << '\n';
        if (functions.size() == 1) {
            out
                    << "Your algorithm does not cannot ba attacked with linear attack, or entered functions are incorrect"
                    << '\n';
            out
                    << "Make sure. that, if Your encryption function is a permutation, You enter it as a system of coordinate boolean functions"
                    << '\n';
            out << "Boolean functions are entered as a vector of 0 and 1" << '\n';
        } else {
            std::vector<boolean> system;
            for (auto &function: functions) {
                boolean f(function);
                if (!f.empty())
                    system.push_back(f);
            }
            if (system.empty()) {
                out
                        << "Your algorithm does not cannot ba attacked with linear attack, or entered functions are incorrect"
                        << '\n';
                out
                        << "Make sure. that, if Your encryption function is a permutation, You enter it as a system of coordinate boolean functions"
                        << '\n';
                out << "Boolean functions are entered as a vector of 0 and 1" << '\n';
            } else {
                unsigned int start = clock();
                size_t n, deg = system[0].degree();
                if (text == "ASCII")
                    n = 256;
                else n = text.size();
                auto **table = new int *[n];
                for (size_t i = 0; i < n; ++i) {
                    table[i] = new int[n];
                    boolean x_linear(i, deg);
                    for (size_t j = 0; j < n; ++j) {
                        table[i][j] = 0;
                        boolean y_linear(j, deg);
                        for (size_t h = 0; h < n; ++h) {
                            boolean alfa(h, deg);
                            boolean beta;
                            for (auto &k: system)
                                beta.push_back(k[h]);
                            alfa *= x_linear;
                            beta *= y_linear;
                            int x = 0, y = 0;
                            for (size_t t = 0; t < deg; ++t) {
                                x ^= alfa[t];
                                y ^= beta[t];
                            }
                            if (x == y)
                                table[i][j] += 1;
                        }
                        table[i][j] = table[i][j] - n / 2;
                    }
                }
                unsigned int end = clock();
                unsigned int time = end - start;
                // Вывод данных
                out << '\n';
                for (size_t i = 0; i <= n; ++i) {
                    for (size_t j = 0; j <= n; ++j) {
                        if (i * j == 0) {
                            if ((i == 0) && (j == 0))
                                out << "|     ";
                            if ((i == 0) && (j != 0)) {
                                size_t val = j - 1;
                                boolean output(val, deg);
                                out << ' ';
                                for (size_t k = 0; k < deg; ++k)
                                    out << output[k];
                            }
                            if ((i != 0) && (j == 0)) {
                                size_t val = i - 1;
                                boolean output(val, deg);
                                out << "| ";
                                for (size_t k = 0; k < deg; ++k)
                                    out << output[k];
                            }
                            out << " |";
                        } else {
                            if (table[i - 1][j - 1] > 0) {
                                out << " +" << table[i - 1][j - 1];
                                for (size_t h = 0; h < deg - number_degree(table[i - 1][j - 1]); ++h)
                                    out << ' ';
                                out << '|';
                            }
                            if (table[i - 1][j - 1] == 0) {
                                out << "  " << table[i - 1][j - 1];
                                for (size_t h = 0; h < deg - 1; ++h)
                                    out << ' ';
                                out << '|';
                            }
                            if (table[i - 1][j - 1] < 0) {
                                out << ' ' << table[i - 1][j - 1];
                                for (size_t h = 0; h < deg - number_degree(table[i - 1][j - 1]); ++h)
                                    out << ' ';
                                out << '|';
                            }
                        }
                    }
                    out << '\n';
                }
                out
                        << '\n'
                        << "Estimated time to build needed approximations and analyse the probability of destabilization of given S-block is "
                        << time << " ns" << '\n';
                for (size_t i = 0; i < n; ++i)
                    delete[] table[i];
                delete[] table;
            }
        }
    }
};

#endif //KURSOVAYA_1_HEADER_HPP


/* NonlinearFitter.cpp
 * -*- coding: utf-8 -*-
 *
 */

#include <boost/test/unit_test.hpp>

// system includes =============================================================

#include <string>
#include <csv.h>
#include <Eigen/Dense>

// local includes ==============================================================

#include "../src/NonlinearFitter.hpp"

#include "utils/file.hpp"
#include "utils/log.hpp"

BOOST_AUTO_TEST_SUITE(nonlinear_fitting)

BOOST_AUTO_TEST_CASE(exp_fit){
    TEST_MESSAGE("exp_fit");

    std::string base_path = "test/resources/nonlinearfit/";
    io::CSVReader<9> test_data(base_path + "mb_ca_nonlinearexpfit.csv");
    test_data.read_header(io::ignore_extra_column,
        "in_maxn", "in_nlen", "in_x", "in_y", "in_yerr2",
        "out_a", "out_b", "out_erra", "out_errb"
    );
    std::string in_x, in_y, in_yerr2, out_a, out_b, out_erra, out_errb;
    int in_maxn, in_nlen;
    while (test_data.read_row(in_maxn, in_nlen, in_x, in_y, in_yerr2, out_a, out_b, out_erra, out_errb)){
        Eigen::VectorXd x = get_vector_from_csv(base_path + in_x, "x");
        Eigen::VectorXd y = get_vector_from_csv(base_path + in_y, "y");
        Eigen::VectorXd yerr2 = get_vector_from_csv(base_path + in_yerr2, "yerr2");

        double expected_a = cast_hex_to_double(out_a);
        double expected_b = cast_hex_to_double(out_b);
        double expected_erra = cast_hex_to_double(out_erra);
        double expected_errb = cast_hex_to_double(out_errb);

        NonlinearFitter fitter;
        fitter.exponential_fit(x, y, yerr2);

        BOOST_CHECK_EQUAL(fitter.get_a(), expected_a);
        BOOST_CHECK_EQUAL(fitter.get_b(), expected_b);
        BOOST_CHECK_EQUAL(fitter.get_err_a(), expected_erra);
        BOOST_CHECK_EQUAL(fitter.get_err_b(), expected_errb);

    }
    //fitter.exponential_fit(Eigen::VectorXd &x, Eigen::VectorXd y)
}

BOOST_AUTO_TEST_SUITE_END()

// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 fenc=utf-8


/*
 * $Revision: 477 $ $Date: 2010-08-31 11:49:37 -0700 (Tue, 31 Aug 2010) $
 *
 * Copyright by Astos Solutions GmbH, Germany
 *
 * this file is published under the Astos Solutions Free Public License
 * For details on copyright and terms of use see
 * http://www.astos.de/Astos_Solutions_Free_Public_License.html
 */

#include "ConeGeometry.h"
#include "RenderContext.h"
#include "Material.h"
#include "Units.h"
#include "GL/glew.h"
#include <Eigen/Geometry>
#include <algorithm>
#include <cmath>

using namespace vesta;
using namespace Eigen;

ConeGeometry::ConeGeometry(double apexAngle, double height) :
    m_apexAngle(apexAngle),
    m_height(height),
    m_color(Spectrum(1.0f, 1.0f, 1.0f)),
    m_opacity(1.0f),
    m_axis(Eigen::Vector3d::UnitZ())
{
}

ConeGeometry::~ConeGeometry()
{
}

void
ConeGeometry::render(RenderContext& rc, double /* clock */) const
{
    rc.setVertexInfo(VertexSpec::Position);

    unsigned int radialSubdivision = 30;
    unsigned int axialSubdivision = 5;
    Vector3f axis = m_axis.cast<float>();
    rc.drawCone(m_apexAngle, axis * float(m_height), m_color, m_opacity, radialSubdivision, axialSubdivision);

    unsigned int ringCount = 6;
    float baseRadius = float(std::tan(m_apexAngle / 2.0) * m_height);

    for (unsigned int ring = 1; ring <= ringCount; ++ring)
    {
        float t = float(ring) / float(ringCount);
        rc.drawCircle(t * baseRadius, float(m_height) * t * axis, axis, m_color, 0.6f, 40);
    }
}

float
ConeGeometry::boundingSphereRadius() const
{
    double baseRadius = tan(m_apexAngle / 2.0) * m_height;
    return (float) std::max(m_height, baseRadius);
}