/*!
  \file gpp_python_expected_improvement.cpp
  \rst
  This file has the logic to invoke C++ functions pertaining to expected improvement from Python.
  The data flow follows the basic 4 step from gpp_python_common.hpp.

  .. NoteL: several internal functions of this source file are only called from ``Export*()`` functions,
  so their description, inputs, outputs, etc. comments have been moved. These comments exist in
  ``Export*()`` as Python docstrings, so we saw no need to repeat ourselves.
\endrst*/
// This include violates the Google Style Guide by placing an "other" system header ahead of C and C++ system headers.  However,
// it needs to be at the top, otherwise compilation fails on some systems with some versions of python: OS X, python 2.7.3.
// Putting this include first prevents pyport from doing something illegal in C++; reference: http://bugs.python.org/issue10910
#include "Python.h"  // NOLINT(build/include)

#include "gpp_python_expected_improvement.hpp"

// NOLINT-ing the C, C++ header includes as well; otherwise cpplint gets confused
#include <string>  // NOLINT(build/include_order)
#include <vector>  // NOLINT(build/include_order)

#include <boost/python/bases.hpp>  // NOLINT(build/include_order)
#include <boost/python/class.hpp>  // NOLINT(build/include_order)
#include <boost/python/def.hpp>  // NOLINT(build/include_order)
#include <boost/python/dict.hpp>  // NOLINT(build/include_order)
#include <boost/python/extract.hpp>  // NOLINT(build/include_order)
#include <boost/python/list.hpp>  // NOLINT(build/include_order)
#include <boost/python/object.hpp>  // NOLINT(build/include_order)

#include "gpp_common.hpp"
#include "gpp_domain.hpp"
#include "gpp_exception.hpp"
#include "gpp_expected_improvement_gpu.hpp"
#include "gpp_geometry.hpp"
#include "gpp_heuristic_expected_improvement_optimization.hpp"
#include "gpp_math.hpp"
#include "gpp_optimization.hpp"
#include "gpp_optimizer_parameters.hpp"
#include "gpp_python_common.hpp"

namespace optimal_learning {

namespace {

double ComputeExpectedImprovementWrapper(const GaussianProcess& gaussian_process,
                                         const boost::python::list& points_to_sample,
                                         const boost::python::list& points_being_sampled,
                                         int num_to_sample, int num_being_sampled,
                                         int max_int_steps, double best_so_far,
                                         bool force_monte_carlo,
                                         RandomnessSourceContainer& randomness_source) {
  PythonInterfaceInputContainer input_container(points_to_sample, points_being_sampled,
                                                gaussian_process.dim(), num_to_sample, num_being_sampled);

  bool configure_for_gradients = false;
  if ((num_to_sample == 1) && (num_being_sampled == 0) && (force_monte_carlo == false)) {
    OnePotentialSampleExpectedImprovementEvaluator ei_evaluator(gaussian_process, best_so_far);
    OnePotentialSampleExpectedImprovementEvaluator::StateType ei_state(ei_evaluator,
                                                                       input_container.points_to_sample.data(),
                                                                       configure_for_gradients);
    return ei_evaluator.ComputeExpectedImprovement(&ei_state);
  } else {
    ExpectedImprovementEvaluator ei_evaluator(gaussian_process, max_int_steps, best_so_far);
    ExpectedImprovementEvaluator::StateType ei_state(ei_evaluator, input_container.points_to_sample.data(),
                                                     input_container.points_being_sampled.data(),
                                                     input_container.num_to_sample,
                                                     input_container.num_being_sampled,
                                                     configure_for_gradients,
                                                     randomness_source.normal_rng_vec.data());
    return ei_evaluator.ComputeExpectedImprovement(&ei_state);
  }
}

boost::python::list ComputeGradExpectedImprovementWrapper(const GaussianProcess& gaussian_process,
                                                          const boost::python::list& points_to_sample,
                                                          const boost::python::list& points_being_sampled,
                                                          int num_to_sample, int num_being_sampled,
                                                          int max_int_steps, double best_so_far,
                                                          bool force_monte_carlo,
                                                          RandomnessSourceContainer& randomness_source) {
  PythonInterfaceInputContainer input_container(points_to_sample, points_being_sampled, gaussian_process.dim(),
                                                num_to_sample, num_being_sampled);

  std::vector<double> grad_EI(num_to_sample*input_container.dim);
  bool configure_for_gradients = true;
  if ((num_to_sample == 1) && (num_being_sampled == 0) && (force_monte_carlo == false)) {
    OnePotentialSampleExpectedImprovementEvaluator ei_evaluator(gaussian_process, best_so_far);
    OnePotentialSampleExpectedImprovementEvaluator::StateType ei_state(ei_evaluator,
                                                                       input_container.points_to_sample.data(),
                                                                       configure_for_gradients);
    ei_evaluator.ComputeGradExpectedImprovement(&ei_state, grad_EI.data());
  } else {
    ExpectedImprovementEvaluator ei_evaluator(gaussian_process, max_int_steps, best_so_far);
    ExpectedImprovementEvaluator::StateType ei_state(ei_evaluator, input_container.points_to_sample.data(),
                                                     input_container.points_being_sampled.data(),
                                                     input_container.num_to_sample,
                                                     input_container.num_being_sampled,
                                                     configure_for_gradients,
                                                     randomness_source.normal_rng_vec.data());
    ei_evaluator.ComputeGradExpectedImprovement(&ei_state, grad_EI.data());
  }

  return VectorToPylist(grad_EI);
}

/*!\rst
  Utility that dispatches EI optimization based on optimizer type and num_to_sample.
  This is just used to reduce copy-pasted code.

  \param
    :optimizer_parameters: python/cpp_wrappers/optimization._CppOptimizerParameters
      Python object containing the DomainTypes domain_type and OptimizerTypes optimzer_type to use as well as
      appropriate parameter structs e.g., NewtonParameters for type kNewton).
      See comments on the python interface for multistart_expected_improvement_optimization_wrapper
    :gaussian_process: GaussianProcess object (holds points_sampled, values, noise_variance, derived quantities) that describes the
      underlying GP
    :input_container: PythonInterfaceInputContainer object containing data about points_being_sampled
    :domain: object specifying the domain to optimize over (see gpp_domain.hpp)
    :optimizer_type: type of optimization to use (e.g., null, gradient descent)
    :num_to_sample: how many simultaneous experiments you would like to run (i.e., the q in q,p-EI)
    :best_so_far: value of the best sample so far (must be min(points_sampled_value))
    :max_int_steps: maximum number of MC iterations
    :max_num_threads: maximum number of threads for use by OpenMP (generally should be <= # cores)
    :randomness_source: object containing randomness sources (sufficient for multithreading) used in EI computation
    :status: pydict object; cannot be None
  \output
    :randomness_source: PRNG internal states modified
    :status: modified on exit to describe whether convergence occurred
    :best_points_to_sample[num_to_sample][dim]: next set of points to evaluate
\endrst*/
template <typename DomainType>
void DispatchExpectedImprovementOptimization(const boost::python::object& optimizer_parameters,
                                             const GaussianProcess& gaussian_process,
                                             const PythonInterfaceInputContainer& input_container,
                                             const DomainType& domain,
                                             OptimizerTypes optimizer_type,
                                             int num_to_sample, double best_so_far,
                                             int max_int_steps, int max_num_threads,
                                             bool use_gpu, int which_gpu,
                                             RandomnessSourceContainer& randomness_source,
                                             boost::python::dict& status,
                                             double * restrict best_points_to_sample) {
#ifndef OL_GPU_ENABLED
  (void) which_gpu;  // quiet the compiler warning (unused variable)
#endif

  bool found_flag = false;
  switch (optimizer_type) {
    case OptimizerTypes::kNull: {
      ThreadSchedule thread_schedule(max_num_threads, omp_sched_static);
      // optimizer_parameters must contain an int num_random_samples field, extract it
      int num_random_samples = boost::python::extract<int>(optimizer_parameters.attr("num_random_samples"));

      if (use_gpu == true) {
#ifdef OL_GPU_ENABLED
        CudaComputeOptimalPointsToSampleViaLatinHypercubeSearch(gaussian_process, domain, thread_schedule,
                                                                input_container.points_being_sampled.data(),
                                                                num_random_samples, num_to_sample,
                                                                input_container.num_being_sampled,
                                                                best_so_far, max_int_steps, which_gpu, &found_flag,
                                                                &randomness_source.uniform_generator,
                                                                best_points_to_sample);
#else
        OL_THROW_EXCEPTION(OptimalLearningException, "GPU is not installed or enabled!");
#endif
      } else {
        ComputeOptimalPointsToSampleViaLatinHypercubeSearch(gaussian_process, domain, thread_schedule,
                                                            input_container.points_being_sampled.data(),
                                                            num_random_samples, num_to_sample,
                                                            input_container.num_being_sampled,
                                                            best_so_far, max_int_steps,
                                                            &found_flag, &randomness_source.uniform_generator,
                                                            randomness_source.normal_rng_vec.data(),
                                                            best_points_to_sample);
      }
      status[std::string("lhc_") + domain.kName + "_domain_found_update"] = found_flag;
      break;
    }  // end case kNull optimizer_type
    case OptimizerTypes::kGradientDescent: {
      // optimizer_parameters must contain a optimizer_parameters field
      // of type GradientDescentParameters. extract it
      const GradientDescentParameters& gradient_descent_parameters = boost::python::extract<GradientDescentParameters&>(optimizer_parameters.attr("optimizer_parameters"));
      ThreadSchedule thread_schedule(max_num_threads, omp_sched_dynamic);
      int num_random_samples = boost::python::extract<int>(optimizer_parameters.attr("num_random_samples"));

      bool random_search_only = false;
      if (use_gpu == true) {
#ifdef OL_GPU_ENABLED
        CudaComputeOptimalPointsToSample(gaussian_process, gradient_descent_parameters, domain, thread_schedule,
                                         input_container.points_being_sampled.data(), num_to_sample,
                                         input_container.num_being_sampled, best_so_far, max_int_steps,
                                         random_search_only, num_random_samples, which_gpu, &found_flag,
                                         &randomness_source.uniform_generator, best_points_to_sample);
#else
        OL_THROW_EXCEPTION(OptimalLearningException, "GPU is not installed or enabled!");
#endif
      } else {
        ComputeOptimalPointsToSample(gaussian_process, gradient_descent_parameters, domain, thread_schedule,
                                     input_container.points_being_sampled.data(), num_to_sample,
                                     input_container.num_being_sampled, best_so_far, max_int_steps,
                                     random_search_only, num_random_samples, &found_flag,
                                     &randomness_source.uniform_generator,
                                     randomness_source.normal_rng_vec.data(), best_points_to_sample);
      }
      status[std::string("gradient_descent_") + domain.kName + "_domain_found_update"] = found_flag;
      break;
    }  // end case kGradientDescent optimizer_type
    default: {
      std::fill(best_points_to_sample, best_points_to_sample + input_container.dim*num_to_sample, 0.0);
      OL_THROW_EXCEPTION(OptimalLearningException, "ERROR: invalid optimizer choice. Setting all coordinates to 0.0.");
      break;
    }
  }  // end switch over optimizer_type
}

boost::python::list MultistartExpectedImprovementOptimizationWrapper(const boost::python::object& optimizer_parameters,
                                                                     const GaussianProcess& gaussian_process,
                                                                     const boost::python::list& domain_bounds,
                                                                     const boost::python::list& points_being_sampled,
                                                                     int num_to_sample, int num_being_sampled,
                                                                     double best_so_far, int max_int_steps,
                                                                     int max_num_threads, bool use_gpu, int which_gpu,
                                                                     RandomnessSourceContainer& randomness_source,
                                                                     boost::python::dict& status) {
  // TODO(GH-131): make domain objects constructible from python; and pass them in through
  // the optimizer_parameters python object

  // abort if we do not have enough sources of randomness to run with max_num_threads
  if (unlikely(max_num_threads > static_cast<int>(randomness_source.normal_rng_vec.size()))) {
    OL_THROW_EXCEPTION(LowerBoundException<int>, "Fewer randomness_sources than max_num_threads.", randomness_source.normal_rng_vec.size(), max_num_threads);
  }

  int num_to_sample_input = 0;  // No points to sample; we are generating these via EI optimization
  const boost::python::list points_to_sample_dummy;
  PythonInterfaceInputContainer input_container(points_to_sample_dummy, points_being_sampled, gaussian_process.dim(), num_to_sample_input, num_being_sampled);
  std::vector<ClosedInterval> domain_bounds_C(input_container.dim);
  CopyPylistToClosedIntervalVector(domain_bounds, input_container.dim, domain_bounds_C);

  std::vector<double> best_points_to_sample_C(input_container.dim*num_to_sample);

  DomainTypes domain_type = boost::python::extract<DomainTypes>(optimizer_parameters.attr("domain_type"));
  OptimizerTypes optimizer_type = boost::python::extract<OptimizerTypes>(optimizer_parameters.attr("optimizer_type"));
  switch (domain_type) {
    case DomainTypes::kTensorProduct: {
      TensorProductDomain domain(domain_bounds_C.data(), input_container.dim);

      DispatchExpectedImprovementOptimization(optimizer_parameters, gaussian_process, input_container,
                                              domain, optimizer_type, num_to_sample, best_so_far,
                                              max_int_steps, max_num_threads, use_gpu, which_gpu,
                                              randomness_source,
                                              status, best_points_to_sample_C.data());
      break;
    }  // end case OptimizerTypes::kTensorProduct
    case DomainTypes::kSimplex: {
      SimplexIntersectTensorProductDomain domain(domain_bounds_C.data(), input_container.dim);

      DispatchExpectedImprovementOptimization(optimizer_parameters, gaussian_process, input_container,
                                              domain, optimizer_type, num_to_sample, best_so_far,
                                              max_int_steps, max_num_threads, use_gpu, which_gpu,
                                              randomness_source,
                                              status, best_points_to_sample_C.data());
      break;
    }  // end case OptimizerTypes::kSimplex
    default: {
      std::fill(best_points_to_sample_C.begin(), best_points_to_sample_C.end(), 0.0);
      OL_THROW_EXCEPTION(OptimalLearningException, "ERROR: invalid domain choice. Setting all coordinates to 0.0.");
      break;
    }
  }  // end switch over domain_type

  return VectorToPylist(best_points_to_sample_C);
}

/*!\rst
  Utility that dispatches heuristic EI optimization (solving q,0-EI) based on optimizer type and num_to_sample.
  This is just used to reduce copy-pasted code.

  \param
    :optimizer_parameters: python/cpp_wrappers/optimization._CppOptimizerParameters
      Python object containing the DomainTypes domain_type and OptimizerTypes optimzer_type to use as well as
      appropriate parameter structs e.g., NewtonParameters for type kNewton).
      See comments on the python interface for multistart_expected_improvement_optimization_wrapper
    :gaussian_process: GaussianProcess object (holds points_sampled, values, noise_variance, derived quantities) that describes the
      underlying GP
    :domain: object specifying the domain to optimize over (see gpp_domain.hpp)
    :optimizer_type: type of optimization to use (e.g., null, gradient descent)
    :estimation_policy: the policy to use to produce (heuristic) objective function estimates during multi-points EI optimization
    :num_to_sample: how many simultaneous experiments you would like to run (i.e., the q in q,0-EI)
    :best_so_far: value of the best sample so far (must be min(points_sampled_value))
    :max_num_threads: maximum number of threads for use by OpenMP (generally should be <= # cores)
    :randomness_source: object containing randomness sources (sufficient for multithreading) used in EI computation
    :status: pydict object; cannot be None
  \output
    :randomness_source: PRNG internal states modified
    :status: modified on exit to describe whether convergence occurred
    :best_points_to_sample[num_to_sample][dim]: next set of points to evaluate
\endrst*/
template <typename DomainType>
void DispatchHeuristicExpectedImprovementOptimization(const boost::python::object& optimizer_parameters,
                                                      const GaussianProcess& gaussian_process,
                                                      const DomainType& domain,
                                                      OptimizerTypes optimizer_type,
                                                      const ObjectiveEstimationPolicyInterface& estimation_policy,
                                                      int num_to_sample, double best_so_far, int max_num_threads,
                                                      RandomnessSourceContainer& randomness_source,
                                                      boost::python::dict& status,
                                                      double * restrict best_points_to_sample) {
  ThreadSchedule thread_schedule(max_num_threads, omp_sched_dynamic);
  bool found_flag = false;
  switch (optimizer_type) {
    case OptimizerTypes::kNull: {
      // optimizer_parameters must contain an int num_multistarts field, extract it
      int num_random_samples = boost::python::extract<int>(optimizer_parameters.attr("num_random_samples"));

      bool random_search_only = true;
      GradientDescentParameters gradient_descent_parameters(0, 0, 0, 0, 1.0, 1.0, 1.0, 0.0);  // dummy struct; we aren't using gradient descent
      ComputeHeuristicPointsToSample(gaussian_process, gradient_descent_parameters, domain,
                                     estimation_policy, thread_schedule, best_so_far,
                                     random_search_only, num_random_samples, num_to_sample,
                                     &found_flag, &randomness_source.uniform_generator,
                                     best_points_to_sample);

      status[std::string("lhc_") + domain.kName + "_domain_found_update"] = found_flag;
      break;
    }  // end case kNull optimizer_type
    case OptimizerTypes::kGradientDescent: {
      // optimizer_parameters must contain a optimizer_parameters field
      // of type GradientDescentParameters. extract it
      const GradientDescentParameters& gradient_descent_parameters = boost::python::extract<GradientDescentParameters&>(optimizer_parameters.attr("optimizer_parameters"));
      int num_random_samples = boost::python::extract<int>(optimizer_parameters.attr("num_random_samples"));

      bool random_search_only = false;
      ComputeHeuristicPointsToSample(gaussian_process, gradient_descent_parameters, domain,
                                     estimation_policy, thread_schedule, best_so_far,
                                     random_search_only, num_random_samples, num_to_sample,
                                     &found_flag, &randomness_source.uniform_generator,
                                     best_points_to_sample);

      status[std::string("gradient_descent_") + domain.kName + "_domain_found_update"] = found_flag;
      break;
    }  // end case kGradientDescent optimizer_type
    default: {
      std::fill(best_points_to_sample, best_points_to_sample + gaussian_process.dim()*num_to_sample, 0.0);
      OL_THROW_EXCEPTION(OptimalLearningException, "ERROR: invalid optimizer choice. Setting all coordinates to 0.0.");
      break;
    }
  }  // end switch over optimizer_type
}

boost::python::list HeuristicExpectedImprovementOptimizationWrapper(const boost::python::object& optimizer_parameters,
                                                                    const GaussianProcess& gaussian_process,
                                                                    const boost::python::list& domain_bounds,
                                                                    const ObjectiveEstimationPolicyInterface& estimation_policy,
                                                                    int num_to_sample, double best_so_far, int max_num_threads,
                                                                    RandomnessSourceContainer& randomness_source,
                                                                    boost::python::dict& status) {
  // TODO(GH-131): make domain objects constructible from python; and pass them in through
  // the optimizer_parameters python object
  int dim = gaussian_process.dim();
  std::vector<ClosedInterval> domain_bounds_C(dim);
  CopyPylistToClosedIntervalVector(domain_bounds, dim, domain_bounds_C);

  std::vector<double> best_points_to_sample_C(dim*num_to_sample);

  DomainTypes domain_type = boost::python::extract<DomainTypes>(optimizer_parameters.attr("domain_type"));
  OptimizerTypes optimizer_type = boost::python::extract<OptimizerTypes>(optimizer_parameters.attr("optimizer_type"));
  switch (domain_type) {
    case DomainTypes::kTensorProduct: {
      TensorProductDomain domain(domain_bounds_C.data(), dim);

      DispatchHeuristicExpectedImprovementOptimization(optimizer_parameters, gaussian_process, domain,
                                                       optimizer_type, estimation_policy, num_to_sample,
                                                       best_so_far, max_num_threads, randomness_source,
                                                       status, best_points_to_sample_C.data());
      break;
    }  // end case OptimizerTypes::kTensorProduct
    case DomainTypes::kSimplex: {
      SimplexIntersectTensorProductDomain domain(domain_bounds_C.data(), dim);

      DispatchHeuristicExpectedImprovementOptimization(optimizer_parameters, gaussian_process, domain,
                                                       optimizer_type, estimation_policy, num_to_sample,
                                                       best_so_far, max_num_threads, randomness_source,
                                                       status, best_points_to_sample_C.data());
      break;
    }  // end case OptimizerTypes::kSimplex
    default: {
      std::fill(best_points_to_sample_C.begin(), best_points_to_sample_C.end(), 0.0);
      OL_THROW_EXCEPTION(OptimalLearningException, "ERROR: invalid domain choice. Setting all coordinates to 0.0.");
      break;
    }
  }  // end switch over domain_type

  return VectorToPylist(best_points_to_sample_C);
}

boost::python::list EvaluateEIAtPointListWrapper(const GaussianProcess& gaussian_process,
                                                 const boost::python::list& initial_guesses,
                                                 const boost::python::list& points_being_sampled,
                                                 int num_multistarts, int num_to_sample,
                                                 int num_being_sampled, double best_so_far,
                                                 int max_int_steps, int max_num_threads,
                                                 RandomnessSourceContainer& randomness_source,
                                                 boost::python::dict& status) {
  // abort if we do not have enough sources of randomness to run with max_num_threads
  if (unlikely(max_num_threads > static_cast<int>(randomness_source.normal_rng_vec.size()))) {
    OL_THROW_EXCEPTION(LowerBoundException<int>, "Fewer randomness_sources than max_num_threads.", randomness_source.normal_rng_vec.size(), max_num_threads);
  }

  int num_to_sample_input = 0;  // No points to sample; we are generating these via EI optimization
  const boost::python::list points_to_sample_dummy;
  PythonInterfaceInputContainer input_container(points_to_sample_dummy, points_being_sampled, gaussian_process.dim(),
                                                num_to_sample_input, num_being_sampled);
  std::vector<double> result_point_C(input_container.dim);  // not used
  std::vector<double> result_function_values_C(num_multistarts);
  std::vector<double> initial_guesses_C(input_container.dim * num_multistarts);

  CopyPylistToVector(initial_guesses, input_container.dim * num_multistarts, initial_guesses_C);

  ThreadSchedule thread_schedule(max_num_threads, omp_sched_static);
  bool found_flag = false;
  EvaluateEIAtPointList(gaussian_process, thread_schedule, initial_guesses_C.data(),
                        input_container.points_being_sampled.data(), num_multistarts,
                        num_to_sample, input_container.num_being_sampled, best_so_far,
                        max_int_steps, &found_flag, randomness_source.normal_rng_vec.data(),
                        result_function_values_C.data(), result_point_C.data());

  status["evaluate_EI_at_point_list"] = found_flag;

  return VectorToPylist(result_function_values_C);
}

}  // end unnamed namespace

void ExportEstimationPolicies() {
  boost::python::class_<ObjectiveEstimationPolicyInterface, boost::noncopyable>("ObjectiveEstimationPolicyInterface", R"%%(
    Pure abstract (in the C++ sense) base class for objective function estimation (e.g., Constant Liar, Kriging Believer).
    Serves no purpose in Python but is needed by boost to allow pointer casts of derived types to this type.
    )%%", boost::python::no_init);

  boost::python::class_<ConstantLiarEstimationPolicy, boost::python::bases<ObjectiveEstimationPolicyInterface> >("ConstantLiarEstimationPolicy", R"%%(
    Produces objective function estimates at a "point" using the "Constant Liar" heuristic.

    Always outputs function_value = lie_value (the "constant lie") and noise_variance = lie_noise_variance, regardless
    of the value of "point".

    :ivar lie_value: (*float64*) the "constant lie" that this estimator should return
    :ivar lie_noise_variance: (*float64*) the noise_variance to associate to the lie_value (MUST be >= 0.0)
    )%%", boost::python::init<double, double>(R"%%(
    Constructs a ConstantLiarEstimationPolicy object.

    :param lie_value: the "constant lie" that this estimator should return
    :type lie_value: float64 (finite)
    :param lie_noise_variance: the noise_variance to associate to the lie_value (MUST be >= 0.0)
    :type lie_noise_variance: float64 >= 0.0
    )%%"));

  boost::python::class_<KrigingBelieverEstimationPolicy, boost::python::bases<ObjectiveEstimationPolicyInterface> >("KrigingBelieverEstimationPolicy", R"%%(
    Produces objective function estimates at a "point" using the "Kriging Believer" heuristic.

    Requires a valid GaussianProcess (GP) to produce estimates. Computes estimates as:

    * function_value = GP.Mean(point) + std_deviation_coef * sqrt(GP.Variance(point))
    * noise_variance = kriging_noise_variance

    :ivar std_deviation_coef: (*float64*) the relative amount of bias (in units of GP std deviation) to introduce into the GP mean
    :ivar kriging_noise_variance: (*float64*) the noise_variance to associate to each function value estimate (MUST be >= 0.0)
    )%%", boost::python::init<double, double>(R"%%(
    Constructs for KrigingBelieverEstimationPolicy object.

    :param std_deviation_coef: the relative amount of bias (in units of GP std deviation) to introduce into the GP mean
    :type std_deviation_coef: float64 (finite)
    :param kriging_noise_variance: the noise_variance to associate to each function value estimate (MUST be >= 0.0)
    :type kriging_noise_variance: float64 >= 0.0
    )%%"));
}

void ExportExpectedImprovementFunctions() {
  boost::python::def("compute_expected_improvement", ComputeExpectedImprovementWrapper, R"%%(
    Compute expected improvement.
    If ``num_to_sample == 1`` and ``num_being_sampled == 0`` AND ``force_monte_carlo is false``, this will
    use (fast/accurate) analytic evaluation.
    Otherwise monte carlo-based EI computation is used.

    :param gaussian_process: GaussianProcess object (holds points_sampled, values, noise_variance, derived quantities)
    :type gaussian_process: GPP.GaussianProcess (boost::python ctor wrapper around optimal_learning::GaussianProcess)
    :param points_to_sample: initial points to load into state (must be a valid point for the problem);
      i.e., points at which to evaluate EI and/or its gradient
    :type points_to_sample: list of float64 with shape (num_to_sample, dim)
    :param points_being_sampled: points that are being sampled in concurrently experiments
    :type points_being_sampled: list of float64 with shape (num_being_sampled, dim)
    :param num_to_sample: number of potential future samples; gradients are evaluated wrt these points (i.e., the "q" in q,p-EI)
    :type num_to_sample: int > 0
    :param num_being_sampled: number of points being sampled concurrently (i.e., the p in q,p-EI)
    :type num_being_sampled: int >= 0
    :param max_int_steps: number of MC integration points in EI
    :type max_int_steps: int >= 0
    :param best_so_far: best known value of objective so far
    :type best_so_far: float64
    :param force_monte_carlo: true to force monte carlo evaluation of EI
    :type force_monte_carlo: bool
    :param randomness_source: object containing randomness sources; only thread 0's source is used
    :type randomness_source: GPP.RandomnessSourceContainer
    :return: computed EI
    :rtype: float64 >= 0.0
    )%%");

  boost::python::def("compute_grad_expected_improvement", ComputeGradExpectedImprovementWrapper, R"%%(
    Compute the gradient of expected improvement evaluated at points_to_sample.
    If num_to_sample = 1 and num_being_sampled = 0 AND force_monte_carlo is false, this will
    use (fast/accurate) analytic evaluation.
    Otherwise monte carlo-based EI computation is used.

    :param gaussian_process: GaussianProcess object (holds points_sampled, values, noise_variance, derived quantities)
    :type gaussian_process: GPP.GaussianProcess (boost::python ctor wrapper around optimal_learning::GaussianProcess)
    :param points_to_sample: initial points to load into state (must be a valid point for the problem);
      i.e., points at which to evaluate EI and/or its gradient
    :type points_to_sample: list of float64 with shape (num_to_sample, dim)
    :param points_being_sampled: points that are being sampled in concurrently experiments
    :type points_being_sampled: list of float64 with shape (num_being_sampled, dim)
    :param num_to_sample: number of potential future samples; gradients are evaluated wrt these points (i.e., the "q" in q,p-EI)
    :type num_to_sample: int > 0
    :param num_being_sampled: number of points being sampled concurrently (i.e., the p in q,p-EI)
    :type num_being_sampled: int >= 0
    :param max_int_steps: number of MC integration points in EI
    :type max_int_steps: int >= 0
    :param best_so_far: best known value of objective so far
    :type best_so_far: float64
    :param force_monte_carlo: true to force monte carlo evaluation of EI
    :type force_monte_carlo: bool
    :param randomness_source: object containing randomness sources; only thread 0's source is used
    :type randomness_source: GPP.RandomnessSourceContainer
    :return: gradient of EI (computed at points_to_sample + points_being_sampled, wrt points_to_sample)
    :rtype: list of float64 with shape (num_to_sample, dim)
    )%%");

  boost::python::def("multistart_expected_improvement_optimization", MultistartExpectedImprovementOptimizationWrapper, R"%%(
    Optimize expected improvement (i.e., solve q,p-EI) over the specified domain using the specified optimization method.
    Can optimize for num_to_sample new points to sample (i.e., aka "q", experiments to run) simultaneously.
    Allows the user to specify num_being_sampled (aka "p") ongoing/concurrent experiments.

    The _CppOptimizerParameters object is a python class defined in:
    python/cpp_wrappers/optimization._CppOptimizerParameters
    See that class definition for more details.

    This function expects it to have the fields:

    * domain_type (DomainTypes enum from this file)
    * optimizer_type (OptimizerTypes enum from this file)
    * num_random_samples (int, number of samples to 'dumb' search over, if 'dumb' search is being used.
      e.g., if optimizer = kNull or if to_sample > 1)
    * optimizer_parameters (*Parameters struct (gpp_optimizer_parameters.hpp) where * matches optimizer_type
      unused if optimizer_type == kNull)

    This function also has the option of using GPU to compute general q,p-EI via MC simulation. To enable it,
    make sure you have installed GPU components of MOE, otherwise, it will throw Runtime excpetion.

    .. WARNING:: this function FAILS and returns an EMPTY LIST if the number of random sources < max_num_threads

    :param optimizer_parameters: python object containing the DomainTypes domain_type and
      OptimizerTypes optimzer_type to use as well as
      appropriate parameter structs e.g., NewtonParameters for type kNewton)
    :type optimizer_parameters: _CppOptimizerParameters
    :param gaussian_process: GaussianProcess object (holds points_sampled, values, noise_variance, derived quantities)
    :type gaussian_process: GPP.GaussianProcess (boost::python ctor wrapper around optimal_learning::GaussianProcess)
    :param domain: [lower, upper] bound pairs for each dimension
    :type domain: list of float64 with shape (dim, 2)
    :param points_being_sampled: points that are being sampled in concurrently experiments
    :type points_being_sampled: list of float64 with shape (num_being_sampled, dim)
    :param num_to_sample: number of potential future samples; gradients are evaluated wrt these points (i.e., the "q" in q,p-EI)
    :type num_to_sample: int > 0
    :param num_being_sampled: number of points being sampled concurrently (i.e., the p in q,p-EI)
    :type num_being_sampled: int >= 0
    :param best_so_far: best known value of objective so far
    :type best_so_far: float64
    :param max_int_steps: number of MC integration points in EI
    :type max_int_steps: int >= 0
    :param max_num_threads: max number of threads to use during EI optimization
    :type max_num_threads: int >= 1
    :param use_gpu: set to 1 if user wants to use GPU for MC computation
    :type use_gpu: bool
    :param which_gpu: GPU device ID
    :type which_gpu: int >= 0
    :param randomness_source: object containing randomness sources; only thread 0's source is used
    :type randomness_source: GPP.RandomnessSourceContainer
    :param status: pydict object (cannot be None!); modified on exit to describe whether convergence occurred
    :type status: dict
    :return: next set of points to eval
    :rtype: list of float64 with shape (num_to_sample, dim)
    )%%");

  boost::python::def("heuristic_expected_improvement_optimization", HeuristicExpectedImprovementOptimizationWrapper, R"%%(
    Compute a heuristic approximation to the result of multistart_expected_improvement_optimization(). That is, it
    optimizes an approximation to q,0-EI over the specified domain using the specified optimization method.
    Can optimize for num_to_sample (aka "q") new points to sample (i.e., experiments to run) simultaneously.

    Computing q,p-EI for q > 1 or p > 1 is expensive. To avoid that cost, this method "solves" q,0-EI by repeatedly
    optimizing 1,0-EI. We do the following (in C++)::

      for i in xrange(num_to_sample):
        new_point = optimize_1_EI(gaussian_process, ...)
        new_function_value, new_noise_variance = estimation_policy.compute_estimate(new_point, gaussian_process, i)
        gaussian_process.add_point(new_point, new_function_value, new_noise_variance)

    So using estimation_policy, we guess what the real-world objective function value would be, evaluated at the result
    of each 1-EI optimization. Then we treat this estimate as *truth* and feed it back to the gaussian process. The
    ConstantLiar and KrigingBelieverEstimationPolicy objects reproduce the heuristics described in Ginsbourger 2008.

    See gpp_heuristic_expected_improvement_optimization.hpp for further details on the algorithm.

    The _CppOptimizerParameters object is a python class defined in:
    ``python/cpp_wrappers/optimization._CppOptimizerParameters``
    See that class definition for more details.

    This function expects it to have the fields:

    * domain_type (DomainTypes enum from this file)
    * optimizer_type (OptimizerTypes enum from this file)
    * num_random_samples (int, number of samples to 'dumb' search over, if 'dumb' search is being used.
      e.g., if optimizer = kNull or if to_sample > 1)
    * optimizer_parameters (*Parameters struct (gpp_optimizer_parameters.hpp) where * matches optimizer_type
      unused if optimizer_type == kNull)

    .. WARNING:: this function FAILS and returns an EMPTY LIST if the number of random sources < max_num_threads

    :param optimizer_parameters: python object containing the DomainTypes domain_type and
      OptimizerTypes optimzer_type to use as well as
      appropriate parameter structs e.g., NewtonParameters for type kNewton)
    :type optimizer_parameters: _CppOptimizerParameters
    :param gaussian_process: GaussianProcess object (holds points_sampled, values, noise_variance, derived quantities)
    :type gaussian_process: GPP.GaussianProcess (boost::python ctor wrapper around optimal_learning::GaussianProcess)
    :param domain: [lower, upper] bound pairs for each dimension
    :type domain: list of float64 with shape (dim, 2)
    :param estimation_policy: the policy to use to produce (heuristic) objective function estimates
      during q,0-EI optimization (e.g., ConstantLiar, KrigingBeliever)
    :type estimation_policy: ObjectiveEstimationPolicyInterface
    :param num_to_sample: number of potential future samples; gradients are evaluated wrt these points (i.e., the "q" in q,p-EI)
    :type num_to_sample: int > 0
    :param best_so_far: best known value of objective so far
    :type best_so_far: float64
    :param max_num_threads: max number of threads to use during EI optimization
    :type max_num_threads: int >= 1
    :param randomness_source: object containing randomness sources; only thread 0's source is used
    :type randomness_source: GPP.RandomnessSourceContainer
    :param status: pydict object (cannot be None!); modified on exit to describe whether convergence occurred
    :type status: dict
    :return: next set of points to eval
    :rtype: list of float64 with shape (num_to_sample, dim)
    )%%");

  boost::python::def("evaluate_EI_at_point_list", EvaluateEIAtPointListWrapper, R"%%(
    Evaluates the expected improvement at each point in initial_guesses; can handle q,p-EI.
    Useful for plotting.

    Equivalent to::

      result = []
      for point in initial_guesses:
          result.append(compute_expected_improvement(point, ...))

    But this method is substantially faster (loop in C++ and multithreaded).

    .. WARNING:: this function FAILS and returns an EMPTY LIST if the number of random sources < max_num_threads

    :param num_to_sample: number of potential future samples; gradients are evaluated wrt these points (i.e., the "q" in q,p-EI)
    :type num_to_sample: int > 0

    :param gaussian_process: GaussianProcess object (holds points_sampled, values, noise_variance, derived quantities)
    :type gaussian_process: GPP.GaussianProcess (boost::python ctor wrapper around optimal_learning::GaussianProcess)
    :param initial_guesses: points at which to evaluate EI
    :type initial_guesses: list of flaot64 with shape (num_multistarts, num_to_sample, dim)
    :param points_being_sampled: points that are being sampled in concurrently experiments
    :type points_being_sampled: list of float64 with shape (num_being_sampled, dim)
    :param num_multistarts: number of points at which to evaluate EI
    :type num_multistarts: int > 0
    :param num_to_sample: number of potential future samples; gradients are evaluated wrt these points (i.e., the "q" in q,p-EI)
    :type num_to_sample: int > 0
    :param num_being_sampled: number of points being sampled concurrently (i.e., the p in q,p-EI)
    :type num_being_sampled: int >= 0
    :param best_so_far: best known value of objective so far
    :type best_so_far: float64
    :param max_int_steps: number of MC integration points in EI
    :type max_int_steps: int >= 0
    :param max_num_threads: max number of threads to use during EI optimization
    :type max_num_threads: int >= 1
    :param randomness_source: object containing randomness sources; only thread 0's source is used
    :type randomness_source: GPP.RandomnessSourceContainer
    :param status: pydict object (cannot be None!); modified on exit to describe whether convergence occurred
    :type status: dict
    :return: EI values at each point of the initial_guesses list, in the same order
    :rtype: list of float64 with shape (num_multistarts, )
    )%%");
}

}  // end namespace optimal_learning


// This file is part of LatNet Builder.
//
// Copyright (C) 2012-2021  The LatNet Builder author's, supervised by Pierre L'Ecuyer, Universite de Montreal.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "latbuilder/Parser/MeritFilter.h"
#include "latbuilder/Parser/LevelWeights.h"
#include "latbuilder/MeritFilter.h"
#include "latbuilder/Norm/Normalizer.h"
#include "latbuilder/Norm/PAlphaSL10.h"
#include "latbuilder/Norm/PAlphaDPW08.h"
#include "latbuilder/Norm/PAlphaTilde.h"
#include "latbuilder/Norm/IAAlpha.h"
#include "latbuilder/Norm/IB.h"
#include "latbuilder/Functor/LowPass.h"

#include <boost/lexical_cast.hpp>

namespace LatBuilder { namespace Parser {

namespace {

   template <LatticeType LR, class NORM>
   void setLevelWeights(
         Norm::Normalizer<LR, EmbeddingType::UNILEVEL, NORM>&,
         const std::string&,
         const LatBuilder::SizeParam<LR, EmbeddingType::UNILEVEL>&
         )
   {}

   template <LatticeType LR, class NORM>
   void setLevelWeights(
         Norm::Normalizer<LR, EmbeddingType::MULTILEVEL, NORM>& normalizer,
         const std::string& levelWeights,
         const LatBuilder::SizeParam<LR, EmbeddingType::MULTILEVEL>& sizeParam
         )
   { normalizer.setWeights(LevelWeights<LR>::parse(levelWeights, sizeParam)); }

   template <LatticeType LR, class NORM, EmbeddingType ET>
   std::unique_ptr<BasicMeritFilter<LR, ET>> createNormalizer(
         unsigned int alpha,
         const LatBuilder::SizeParam<LR, ET>& sizeParam,
         const LatticeTester::Weights& weights,
         Real normType,
         const std::string& levelWeights
         )
   {
      auto normalizer = new LatBuilder::Norm::Normalizer<LR, ET, NORM>(NORM(alpha, weights, normType));
      setLevelWeights(*normalizer, levelWeights, sizeParam);
      return std::unique_ptr<BasicMeritFilter<LR, ET>>(normalizer);
   }

   template <LatticeType LR, EmbeddingType ET> void correctLevelWeights(
         const LatBuilder::SizeParam<LR, ET>& sizeParam,
         const std::string& combiner,
         std::string& levelWeights);

   template <LatticeType LR>
   void correctLevelWeights(
         const LatBuilder::SizeParam<LR, EmbeddingType::UNILEVEL>& sizeParam,
         const std::string& combiner,
         std::string& levelWeights)
      {}

   template <LatticeType LR>
   void correctLevelWeights(
         const LatBuilder::SizeParam<LR, EmbeddingType::MULTILEVEL>& sizeParam,
         const std::string& combiner,
         std::string& levelWeights)
      {
            const auto strCombinerSplit = splitPair<>(combiner, ':');
            if (strCombinerSplit.first == "level") {
                  if (strCombinerSplit.second == "max"){
                        levelWeights = "select:" + std::to_string(sizeParam.maxLevel());
                  }
                  else {
                        try {
                              levelWeights = "select:" + std::to_string(boost::lexical_cast<Level>(strCombinerSplit.second)) + "," + std::to_string(boost::lexical_cast<Level>(strCombinerSplit.second));
                        }
                        catch (boost::bad_lexical_cast&) {}
                  }
            }
      }

   /**
    * Parses the bound PAlphaSL10.
    *
    * Example strings: \c P2-SL10, \c P4-SL10, \c P2-DPW08, \c P4-DPW08
    */
   template <LatticeType LR, EmbeddingType ET>
   std::unique_ptr<BasicMeritFilter<LR, ET>> parseNormalizer(
         const std::string& str,
         const std::string& figure,
         const LatBuilder::SizeParam<LR, ET>& sizeParam,
         const LatticeTester::Weights& weights,
         Real normType,
         std::string combiner
         )
   {
      auto figureSplit = splitPair(figure, ':');
      std::string figureString = (figureSplit.second == "") ? figure : figureSplit.second;
      std::set<std::string> admissibleNormalizations;
      if (figureString[0] == 'P') {
            admissibleNormalizations.insert("P" + figureString.substr(1));
            if (LR == LatticeType::ORDINARY)
            {
                  admissibleNormalizations.insert("P" + figureString.substr(1) + "-SL10");
                  admissibleNormalizations.insert("P" + figureString.substr(1) + "-DPW08");
            }
      }
      else if (figureString[0] == 'I')
      {
            if (figureString[1] == 'A')
            {
                  admissibleNormalizations.insert("IA" + figureString.substr(2));
            }
            else if (figureString[1] == 'B') {
                  admissibleNormalizations.insert("IB");
            }
      }

      std::string newNormString = str;
      if (figure != "" && admissibleNormalizations.size() == 0)
            throw BadFilter("No normalizations are available with figure " + figure + ".");

      if ( (str.size() >= 6 && str.substr(0,6)=="select") || str.size() == 0)
      {
            newNormString =  *admissibleNormalizations.begin() + ":" + newNormString;
      }

      auto args = splitPair(newNormString, ':');
      if (figure != "" && admissibleNormalizations.find(args.first) == admissibleNormalizations.end())
      {
            throw BadFilter("Normalization " + str + " is incompatible with figure" + figure + ( (LR == LatticeType::POLYNOMIAL) ? " with lattice type polynomial." : "."));
      }
      const auto strSplit = splitPair<>(args.first, '-');

      correctLevelWeights(sizeParam, combiner, args.second);

      try {
      if (strSplit.first[0] == 'P') {
            const auto alpha = boost::lexical_cast<unsigned int>(strSplit.first.substr(1));
            if (strSplit.second == "")
            {
                  if (LR == LatticeType::ORDINARY)
                  {
                        return createNormalizer<LR, LatBuilder::Norm::PAlphaSL10, ET>(alpha, sizeParam, weights, normType, args.second);
                  }
                  else if (LR == LatticeType::POLYNOMIAL)
                  {
                        return createNormalizer<LR, LatBuilder::Norm::PAlphaTilde, ET>(alpha, sizeParam, weights, normType, args.second);
                  }
            }
            if (strSplit.second == "SL10")
            return createNormalizer<LR, LatBuilder::Norm::PAlphaSL10, ET>(alpha, sizeParam, weights, normType, args.second);
            else if (strSplit.second == "DPW08")
            return createNormalizer<LR, LatBuilder::Norm::PAlphaDPW08, ET>(alpha, sizeParam, weights, normType, args.second);
      }
      if (strSplit.first[0] == 'I')
      {
            if (strSplit.first[1] == 'A')
            {
                  const auto alpha = boost::lexical_cast<unsigned int>(strSplit.first.substr(2));
                  return createNormalizer<LR, LatBuilder::Norm::IAAlpha, ET>(alpha, sizeParam, weights, normType, args.second);
            }
            if (strSplit.first[1] == 'B')
            {
                  return createNormalizer<LR, LatBuilder::Norm::IB, ET>(weights.interlacingFactor(), sizeParam, weights, normType, args.second);
            }
      }
      }
      catch (boost::bad_lexical_cast&) {}
      throw BadFilter("cannot parse norm: " + str);

   }
}

template <LatticeType LR, EmbeddingType ET>
std::unique_ptr<BasicMeritFilter<LR, ET>>
MeritFilter<LR,ET>::parse(
      const std::string& str,
      const std::string& figure,
      const LatBuilder::SizeParam<LR, ET>& sizeParam,
      const LatticeTester::Weights& weights,
      Real normType,
      std::string combiner
      )
{
   const auto x = splitPair(str, ':');
   if (x.first == "norm")
      return parseNormalizer(x.second, figure, sizeParam, weights, normType, combiner);
   else if (x.first == "low-pass") {
      auto threshold = boost::lexical_cast<Real>(x.second);
      return std::unique_ptr<BasicMeritFilter<LR, ET>>(new LatBuilder::MeritFilter<LR, ET>(Functor::LowPass<Real>(threshold), str));
   }
   throw BadFilter(x.first);
}

template struct LatBuilder::Parser::MeritFilter <LatticeType::ORDINARY, EmbeddingType::UNILEVEL> ;
template struct LatBuilder::Parser::MeritFilter <LatticeType::ORDINARY, EmbeddingType::MULTILEVEL> ;
template struct LatBuilder::Parser::MeritFilter <LatticeType::POLYNOMIAL, EmbeddingType::UNILEVEL> ;
template struct LatBuilder::Parser::MeritFilter <LatticeType::POLYNOMIAL, EmbeddingType::MULTILEVEL> ;

}}


///////////////////////////////////////////////////////////////
//  Copyright 2012 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_MATH_FLOAT_BACKEND_HPP
#define BOOST_MATH_FLOAT_BACKEND_HPP

#include <iostream>
#include <iomanip>
#include <sstream>
#include <boost/cstdint.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/math/concepts/real_concept.hpp>
#include <nil/crypto3/multiprecision/number.hpp>
#include <boost/integer/common_factor_rt.hpp>
#include <boost/type_traits/common_type.hpp>
#include <boost/container_hash/hash.hpp>

namespace nil {
    namespace crypto3 {
        namespace multiprecision {
            namespace backends {

#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable : 4389 4244 4018 4244 4127)
#endif

                template<class Arithmetic>
                struct arithmetic_backend {
                    typedef boost::mpl::list<short, int, long, long long> signed_types;
                    typedef boost::mpl::list<unsigned short, unsigned, unsigned long, unsigned long long>
                        unsigned_types;
                    typedef boost::mpl::list<float, double, long double> float_types;
                    typedef int exponent_type;

                    BOOST_MP_CXX14_CONSTEXPR arithmetic_backend() : m_value(0) {
                    }
                    BOOST_MP_CXX14_CONSTEXPR arithmetic_backend(const arithmetic_backend& o) : m_value(o.m_value) {
                    }
                    template<class A>
                    BOOST_MP_CXX14_CONSTEXPR
                        arithmetic_backend(const A& o,
                                           const typename boost::enable_if<boost::is_arithmetic<A>>::type* = 0) :
                        m_value(o) {
                    }
                    template<class A>
                    BOOST_MP_CXX14_CONSTEXPR arithmetic_backend(const arithmetic_backend<A>& o) : m_value(o.data()) {
                    }
                    BOOST_MP_CXX14_CONSTEXPR arithmetic_backend& operator=(const arithmetic_backend& o) {
                        m_value = o.m_value;
                        return *this;
                    }
                    template<class A>
                    BOOST_MP_CXX14_CONSTEXPR
                        typename boost::enable_if<boost::is_arithmetic<A>, arithmetic_backend&>::type
                        operator=(A i) {
                        m_value = i;
                        return *this;
                    }
                    template<class A>
                    BOOST_MP_CXX14_CONSTEXPR arithmetic_backend& operator=(const arithmetic_backend<A>& i) {
                        m_value = i.data();
                        return *this;
                    }
                    arithmetic_backend& operator=(const char* s) {
#ifndef BOOST_NO_EXCEPTIONS
                        try {
#endif
                            m_value = boost::lexical_cast<Arithmetic>(s);
#ifndef BOOST_NO_EXCEPTIONS
                        } catch (const boost::bad_lexical_cast&) {
                            throw std::runtime_error(std::string("Unable to interpret the string provided: \"") + s +
                                                     std::string("\" as a compatible number type."));
                        }
#endif
                        return *this;
                    }
                    BOOST_MP_CXX14_CONSTEXPR void swap(arithmetic_backend& o) {
                        std::swap(m_value, o.m_value);
                    }
                    std::string str(std::streamsize digits, std::ios_base::fmtflags f) const {
                        std::stringstream ss;
                        ss.flags(f);
                        ss << std::setprecision(digits ? digits : std::numeric_limits<Arithmetic>::digits10 + 4)
                           << m_value;
                        return ss.str();
                    }
                    BOOST_MP_CXX14_CONSTEXPR void do_negate(const boost::mpl::true_&) {
                        m_value = 1 + ~m_value;
                    }
                    BOOST_MP_CXX14_CONSTEXPR void do_negate(const boost::mpl::false_&) {
                        m_value = -m_value;
                    }
                    BOOST_MP_CXX14_CONSTEXPR void negate() {
                        do_negate(boost::mpl::bool_<boost::is_unsigned<Arithmetic>::value>());
                    }
                    BOOST_MP_CXX14_CONSTEXPR int compare(const arithmetic_backend& o) const {
                        return m_value > o.m_value ? 1 : (m_value < o.m_value ? -1 : 0);
                    }
                    template<class A>
                    BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_arithmetic<A>, int>::type
                        compare(A i) const {
                        return m_value > static_cast<Arithmetic>(i) ? 1 :
                                                                      (m_value < static_cast<Arithmetic>(i) ? -1 : 0);
                    }
                    BOOST_MP_CXX14_CONSTEXPR Arithmetic& data() {
                        return m_value;
                    }
                    BOOST_MP_CXX14_CONSTEXPR const Arithmetic& data() const {
                        return m_value;
                    }

                private:
                    Arithmetic m_value;
                };

                template<class R, class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if_c<boost::is_integral<R>::value>::type
                    eval_convert_to(R* result, const arithmetic_backend<Arithmetic>& backend) {
                    typedef typename boost::common_type<R, Arithmetic>::type c_type;
                    BOOST_CONSTEXPR const c_type max = static_cast<c_type>((std::numeric_limits<R>::max)());
                    BOOST_CONSTEXPR const c_type min = static_cast<c_type>((std::numeric_limits<R>::min)());
                    c_type ct = static_cast<c_type>(backend.data());
                    if ((backend.data() < 0) && !std::numeric_limits<R>::is_signed)
                        BOOST_THROW_EXCEPTION(std::range_error("Attempt to convert negative number to unsigned type."));
                    if (ct > max)
                        *result = boost::is_signed<R>::value ? (std::numeric_limits<R>::max)() : backend.data();
                    else if (std::numeric_limits<Arithmetic>::is_signed && (ct < min))
                        *result = (std::numeric_limits<R>::min)();
                    else
                        *result = backend.data();
                }

                template<class R, class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::disable_if_c<boost::is_integral<R>::value>::type
                    eval_convert_to(R* result, const arithmetic_backend<Arithmetic>& backend) {
                    *result = backend.data();
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR bool eval_eq(const arithmetic_backend<Arithmetic>& a,
                                                             const arithmetic_backend<Arithmetic>& b) {
                    return a.data() == b.data();
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_arithmetic<A2>, bool>::type
                    eval_eq(const arithmetic_backend<Arithmetic>& a, const A2& b) {
                    return a.data() == static_cast<Arithmetic>(b);
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR bool eval_lt(const arithmetic_backend<Arithmetic>& a,
                                                             const arithmetic_backend<Arithmetic>& b) {
                    return a.data() < b.data();
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_arithmetic<A2>, bool>::type
                    eval_lt(const arithmetic_backend<Arithmetic>& a, const A2& b) {
                    return a.data() < static_cast<Arithmetic>(b);
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR bool eval_gt(const arithmetic_backend<Arithmetic>& a,
                                                             const arithmetic_backend<Arithmetic>& b) {
                    return a.data() > b.data();
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_arithmetic<A2>, bool>::type
                    eval_gt(const arithmetic_backend<Arithmetic>& a, const A2& b) {
                    return a.data() > static_cast<Arithmetic>(b);
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_add(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& o) {
                    result.data() += o.data();
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_subtract(arithmetic_backend<Arithmetic>& result,
                                                                   const arithmetic_backend<Arithmetic>& o) {
                    result.data() -= o.data();
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_multiply(arithmetic_backend<Arithmetic>& result,
                                                                   const arithmetic_backend<Arithmetic>& o) {
                    result.data() *= o.data();
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR
                    typename boost::enable_if_c<std::numeric_limits<Arithmetic>::has_infinity>::type
                    eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o) {
                    result.data() /= o.data();
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR
                    typename boost::disable_if_c<std::numeric_limits<Arithmetic>::has_infinity>::type
                    eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o) {
                    if (!o.data())
                        BOOST_THROW_EXCEPTION(std::overflow_error("Divide by zero"));
                    result.data() /= o.data();
                }

                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_arithmetic<A2>>::type
                    eval_add(arithmetic_backend<Arithmetic>& result, const A2& o) {
                    result.data() += o;
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_arithmetic<A2>>::type
                    eval_subtract(arithmetic_backend<Arithmetic>& result, const A2& o) {
                    result.data() -= o;
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_arithmetic<A2>>::type
                    eval_multiply(arithmetic_backend<Arithmetic>& result, const A2& o) {
                    result.data() *= o;
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR
                    typename boost::enable_if_c<(boost::is_arithmetic<A2>::value &&
                                                 !std::numeric_limits<Arithmetic>::has_infinity)>::type
                    eval_divide(arithmetic_backend<Arithmetic>& result, const A2& o) {
                    if (!o)
                        BOOST_THROW_EXCEPTION(std::overflow_error("Divide by zero"));
                    result.data() /= o;
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR
                    typename boost::enable_if_c<(boost::is_arithmetic<A2>::value &&
                                                 std::numeric_limits<Arithmetic>::has_infinity)>::type
                    eval_divide(arithmetic_backend<Arithmetic>& result, const A2& o) {
                    result.data() /= o;
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_add(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& a,
                                                              const arithmetic_backend<Arithmetic>& b) {
                    result.data() = a.data() + b.data();
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_subtract(arithmetic_backend<Arithmetic>& result,
                                                                   const arithmetic_backend<Arithmetic>& a,
                                                                   const arithmetic_backend<Arithmetic>& b) {
                    result.data() = a.data() - b.data();
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_multiply(arithmetic_backend<Arithmetic>& result,
                                                                   const arithmetic_backend<Arithmetic>& a,
                                                                   const arithmetic_backend<Arithmetic>& b) {
                    result.data() = a.data() * b.data();
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR
                    typename boost::enable_if_c<std::numeric_limits<Arithmetic>::has_infinity>::type
                    eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a,
                                const arithmetic_backend<Arithmetic>& b) {
                    result.data() = a.data() / b.data();
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR
                    typename boost::disable_if_c<std::numeric_limits<Arithmetic>::has_infinity>::type
                    eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a,
                                const arithmetic_backend<Arithmetic>& b) {
                    if (!b.data())
                        BOOST_THROW_EXCEPTION(std::overflow_error("Divide by zero"));
                    result.data() = a.data() / b.data();
                }

                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_arithmetic<A2>>::type
                    eval_add(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a,
                             const A2& b) {
                    result.data() = a.data() + b;
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_arithmetic<A2>>::type
                    eval_subtract(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a,
                                  const A2& b) {
                    result.data() = a.data() - b;
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_arithmetic<A2>>::type
                    eval_multiply(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a,
                                  const A2& b) {
                    result.data() = a.data() * b;
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR
                    typename boost::enable_if_c<(boost::is_arithmetic<A2>::value &&
                                                 !std::numeric_limits<Arithmetic>::has_infinity)>::type
                    eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a,
                                const A2& b) {
                    if (!b)
                        BOOST_THROW_EXCEPTION(std::overflow_error("Divide by zero"));
                    result.data() = a.data() / b;
                }
                template<class Arithmetic, class A2>
                inline BOOST_MP_CXX14_CONSTEXPR
                    typename boost::enable_if_c<(boost::is_arithmetic<A2>::value &&
                                                 std::numeric_limits<Arithmetic>::has_infinity)>::type
                    eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a,
                                const A2& b) {
                    result.data() = a.data() / b;
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR bool eval_is_zero(const arithmetic_backend<Arithmetic>& val) {
                    return val.data() == 0;
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR
                    typename boost::enable_if_c<(!std::numeric_limits<Arithmetic>::is_specialized ||
                                                 std::numeric_limits<Arithmetic>::is_signed),
                                                int>::type
                    eval_get_sign(const arithmetic_backend<Arithmetic>& val) {
                    return val.data() == 0 ? 0 : val.data() < 0 ? -1 : 1;
                }
                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR
                    typename boost::disable_if_c<(std::numeric_limits<Arithmetic>::is_specialized ||
                                                  std::numeric_limits<Arithmetic>::is_signed),
                                                 int>::type
                    eval_get_sign(const arithmetic_backend<Arithmetic>& val) {
                    return val.data() == 0 ? 0 : 1;
                }

                template<class T>
                inline BOOST_MP_CXX14_CONSTEXPR typename boost::enable_if<boost::is_unsigned<T>, T>::type abs(T v) {
                    return v;
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_abs(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& o) {
                    using nil::crypto3::multiprecision::backends::abs;
                    using std::abs;
                    result.data() = abs(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_fabs(arithmetic_backend<Arithmetic>& result,
                                                               const arithmetic_backend<Arithmetic>& o) {
                    result.data() = std::abs(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_floor(arithmetic_backend<Arithmetic>& result,
                                                                const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = floor(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_ceil(arithmetic_backend<Arithmetic>& result,
                                                               const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = ceil(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_sqrt(arithmetic_backend<Arithmetic>& result,
                                                               const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = sqrt(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR int eval_fpclassify(const arithmetic_backend<Arithmetic>& o) {
                    return (boost::math::fpclassify)(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_trunc(arithmetic_backend<Arithmetic>& result,
                                                                const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = trunc(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_round(arithmetic_backend<Arithmetic>& result,
                                                                const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = round(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_frexp(arithmetic_backend<Arithmetic>& result,
                                                                const arithmetic_backend<Arithmetic>& a, int* v) {
                    BOOST_MATH_STD_USING
                    result.data() = frexp(a.data(), v);
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_ldexp(arithmetic_backend<Arithmetic>& result,
                                                                const arithmetic_backend<Arithmetic>& a, int v) {
                    BOOST_MATH_STD_USING
                    result.data() = ldexp(a.data(), v);
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_exp(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = exp(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_log(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = log(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_log10(arithmetic_backend<Arithmetic>& result,
                                                                const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = log10(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_sin(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = sin(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_cos(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = cos(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_tan(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = tan(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_acos(arithmetic_backend<Arithmetic>& result,
                                                               const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = acos(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_asin(arithmetic_backend<Arithmetic>& result,
                                                               const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = asin(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_atan(arithmetic_backend<Arithmetic>& result,
                                                               const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = atan(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_sinh(arithmetic_backend<Arithmetic>& result,
                                                               const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = sinh(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_cosh(arithmetic_backend<Arithmetic>& result,
                                                               const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = cosh(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_tanh(arithmetic_backend<Arithmetic>& result,
                                                               const arithmetic_backend<Arithmetic>& o) {
                    BOOST_MATH_STD_USING
                    result.data() = tanh(o.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_fmod(arithmetic_backend<Arithmetic>& result,
                                                               const arithmetic_backend<Arithmetic>& a,
                                                               const arithmetic_backend<Arithmetic>& b) {
                    BOOST_MATH_STD_USING
                    result.data() = fmod(a.data(), b.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_pow(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& a,
                                                              const arithmetic_backend<Arithmetic>& b) {
                    BOOST_MATH_STD_USING
                    result.data() = pow(a.data(), b.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_atan2(arithmetic_backend<Arithmetic>& result,
                                                                const arithmetic_backend<Arithmetic>& a,
                                                                const arithmetic_backend<Arithmetic>& b) {
                    BOOST_MATH_STD_USING
                    result.data() = atan2(a.data(), b.data());
                }

                template<class Arithmetic, class I>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_left_shift(arithmetic_backend<Arithmetic>& result, I val) {
                    result.data() <<= val;
                }

                template<class Arithmetic, class I>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_right_shift(arithmetic_backend<Arithmetic>& result, I val) {
                    result.data() >>= val;
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_modulus(arithmetic_backend<Arithmetic>& result,
                                                                  const arithmetic_backend<Arithmetic>& a) {
                    result.data() %= a.data();
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_bitwise_and(arithmetic_backend<Arithmetic>& result,
                                                                      const arithmetic_backend<Arithmetic>& a) {
                    result.data() &= a.data();
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_bitwise_or(arithmetic_backend<Arithmetic>& result,
                                                                     const arithmetic_backend<Arithmetic>& a) {
                    result.data() |= a.data();
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_bitwise_xor(arithmetic_backend<Arithmetic>& result,
                                                                      const arithmetic_backend<Arithmetic>& a) {
                    result.data() ^= a.data();
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_complement(arithmetic_backend<Arithmetic>& result,
                                                                     const arithmetic_backend<Arithmetic>& a) {
                    result.data() = ~a.data();
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_gcd(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& a,
                                                              const arithmetic_backend<Arithmetic>& b) {
                    result.data() = boost::integer::gcd(a.data(), b.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR void eval_lcm(arithmetic_backend<Arithmetic>& result,
                                                              const arithmetic_backend<Arithmetic>& a,
                                                              const arithmetic_backend<Arithmetic>& b) {
                    result.data() = boost::integer::lcm(a.data(), b.data());
                }

                template<class Arithmetic>
                inline BOOST_MP_CXX14_CONSTEXPR std::size_t hash_value(const arithmetic_backend<Arithmetic>& a) {
                    boost::hash<Arithmetic> hasher;
                    return hasher(a.data());
                }

#ifdef BOOST_MSVC
#pragma warning(pop)
#endif

            }    // namespace backends

            using nil::crypto3::multiprecision::backends::arithmetic_backend;

            template<class Arithmetic>
            struct number_category<arithmetic_backend<Arithmetic>>
                : public boost::mpl::int_<boost::is_integral<Arithmetic>::value ? number_kind_integer :
                                                                                number_kind_floating_point> { };

            namespace detail {

                template<class Backend>
                struct double_precision_type;

                template<class Arithmetic, nil::crypto3::multiprecision::expression_template_option ET>
                struct double_precision_type<number<arithmetic_backend<Arithmetic>, ET>> {
                    typedef number<arithmetic_backend<typename double_precision_type<Arithmetic>::type>, ET> type;
                };
                template<>
                struct double_precision_type<arithmetic_backend<boost::int32_t>> {
                    typedef arithmetic_backend<boost::int64_t> type;
                };

            }    // namespace detail

        }    // namespace multiprecision
    }        // namespace crypto3
}    // namespace nil
#if !(defined(__SGI_STL_PORT) || defined(BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS))
     //
// We shouldn't need these to get code to compile, however for the sake of
// "level playing field" performance comparisons they avoid the very slow
// lexical_cast's that would otherwise take place.  Definition has to be guarded
// by the inverse of pp-logic in real_concept.hpp which defines these as a workaround
// for STLPort plus some other old/broken standartd libraries.
//
namespace boost {
    namespace math {
        namespace tools {

            template<>
            inline unsigned int real_cast<unsigned int, concepts::real_concept>(concepts::real_concept r) {
                return static_cast<unsigned int>(r.value());
            }

            template<>
            inline int real_cast<int, concepts::real_concept>(concepts::real_concept r) {
                return static_cast<int>(r.value());
            }

            template<>
            inline long real_cast<long, concepts::real_concept>(concepts::real_concept r) {
                return static_cast<long>(r.value());
            }

            // Converts from T to narrower floating-point types, float, double & long double.

            template<>
            inline float real_cast<float, concepts::real_concept>(concepts::real_concept r) {
                return static_cast<float>(r.value());
            }
            template<>
            inline double real_cast<double, concepts::real_concept>(concepts::real_concept r) {
                return static_cast<double>(r.value());
            }
            template<>
            inline long double real_cast<long double, concepts::real_concept>(concepts::real_concept r) {
                return r.value();
            }

        }    // namespace tools
    }        // namespace math
}    // namespace boost
#endif

namespace std {

    template<class Arithmetic, nil::crypto3::multiprecision::expression_template_option ExpressionTemplates>
    class numeric_limits<nil::crypto3::multiprecision::number<
        nil::crypto3::multiprecision::arithmetic_backend<Arithmetic>, ExpressionTemplates>>
        : public std::numeric_limits<Arithmetic> {
        typedef std::numeric_limits<Arithmetic> base_type;
        typedef nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::arithmetic_backend<Arithmetic>,
                                                     ExpressionTemplates>
            number_type;

    public:
        BOOST_STATIC_CONSTEXPR number_type(min)() BOOST_NOEXCEPT {
            return (base_type::min)();
        }
        BOOST_STATIC_CONSTEXPR number_type(max)() BOOST_NOEXCEPT {
            return (base_type::max)();
        }
        BOOST_STATIC_CONSTEXPR number_type lowest() BOOST_NOEXCEPT {
            return -(max)();
        }
        BOOST_STATIC_CONSTEXPR number_type epsilon() BOOST_NOEXCEPT {
            return base_type::epsilon();
        }
        BOOST_STATIC_CONSTEXPR number_type round_error() BOOST_NOEXCEPT {
            return epsilon() / 2;
        }
        BOOST_STATIC_CONSTEXPR number_type infinity() BOOST_NOEXCEPT {
            return base_type::infinity();
        }
        BOOST_STATIC_CONSTEXPR number_type quiet_NaN() BOOST_NOEXCEPT {
            return base_type::quiet_NaN();
        }
        BOOST_STATIC_CONSTEXPR number_type signaling_NaN() BOOST_NOEXCEPT {
            return base_type::signaling_NaN();
        }
        BOOST_STATIC_CONSTEXPR number_type denorm_min() BOOST_NOEXCEPT {
            return base_type::denorm_min();
        }
    };

    template<>
    class numeric_limits<boost::math::concepts::real_concept> : public std::numeric_limits<long double> {
        typedef std::numeric_limits<long double> base_type;
        typedef boost::math::concepts::real_concept number_type;

    public:
        static const number_type(min)() BOOST_NOEXCEPT {
            return (base_type::min)();
        }
        static const number_type(max)() BOOST_NOEXCEPT {
            return (base_type::max)();
        }
        static const number_type lowest() BOOST_NOEXCEPT {
            return -(max)();
        }
        static const number_type epsilon() BOOST_NOEXCEPT {
            return base_type::epsilon();
        }
        static const number_type round_error() BOOST_NOEXCEPT {
            return epsilon() / 2;
        }
        static const number_type infinity() BOOST_NOEXCEPT {
            return base_type::infinity();
        }
        static const number_type quiet_NaN() BOOST_NOEXCEPT {
            return base_type::quiet_NaN();
        }
        static const number_type signaling_NaN() BOOST_NOEXCEPT {
            return base_type::signaling_NaN();
        }
        static const number_type denorm_min() BOOST_NOEXCEPT {
            return base_type::denorm_min();
        }
    };

}    // namespace std

#include <nil/crypto3/multiprecision/detail/integer_ops.hpp>

#endif