// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_VECTOR_MAP_VIEW_INCLUDE
#define MTL_VECTOR_MAP_VIEW_INCLUDE

#include <utility>
#include <boost/shared_ptr.hpp>
#include <boost/numeric/mtl/concept/collection.hpp>
#include <boost/numeric/mtl/utility/category.hpp>
#include <boost/numeric/mtl/utility/range_generator.hpp>
#include <boost/numeric/mtl/utility/property_map.hpp>
#include <boost/numeric/mtl/utility/copy_expression_const_ref_container.hpp>
#include <boost/numeric/mtl/operation/sfunctor.hpp>
#include <boost/numeric/mtl/operation/tfunctor.hpp>
#include <boost/numeric/mtl/operation/conj.hpp>
#include <boost/numeric/mtl/operation/real.hpp>
#include <boost/numeric/mtl/operation/imag.hpp>
#include <boost/numeric/mtl/operation/signum.hpp>
#include <boost/numeric/mtl/vector/vec_expr.hpp>

namespace mtl { namespace vec { namespace detail {
    // Forward declaration for friend declaration
    template <typename, typename> struct map_value;
}}}

namespace mtl { namespace vec {

template <typename Functor, typename Vector>
struct map_view
  : public vec_expr< map_view<Functor, Vector> >
{
    typedef map_view                                   self;
    typedef vec_expr< self >                           expr_base;
    typedef Vector                                     other;

    typedef typename Functor::result_type              value_type;
    typedef typename Functor::result_type              const_reference;
    typedef typename Collection<Vector>::size_type     size_type;

    map_view (const Functor& functor, const other& ref)
      : expr_base(*this), functor(functor), ref(ref)
    {
  ref.delay_assign();
    }

    map_view (const Functor& functor, boost::shared_ptr<Vector> p)
      : expr_base(*this), functor(functor), my_copy(p), ref(*p)
    {
  ref.delay_assign();
    }

#ifdef MTL_WITH_MOVE
  map_view (self&& that) : my_copy(std::move(that.my_copy)), functor(that.functor), ref(that.ref) {}
  map_view (const self& that) : functor(that.functor), ref(that.ref) { assert(that.my_copy.use_count() == 0); }
#endif

    friend size_type inline num_rows(const self& v) { return num_rows(v.ref); }
    friend size_type inline num_cols(const self& v) { return num_cols(v.ref); }

    size_type stride() const {   return ref.stride(); }
    const_reference operator() (size_type i) const { return functor(ref(i)); }
    const_reference operator[] (size_type i) const { return functor(ref[i]); }
    void delay_assign() const {}

    template <typename, typename> friend struct detail::map_value;

  protected:
    boost::shared_ptr<Vector>           my_copy;
  public:
    Functor           functor;
    // ref is a const& if Vector is a true vector and a copy if it is an expression
    typename mtl::traits::copy_expression_const_ref_container<Vector>::type ref;
};

template <typename Functor, typename Vector>
inline std::size_t size(const map_view<Functor, Vector>& v)
{     return mtl::vec::size(v.ref); }

// ================
// Free functions
// ================

    namespace detail {

  template <typename Functor, typename Vector>
  struct map_value
  {
      typedef typename Vector::key_type                      key_type;
      typedef typename vec::map_view<Functor, Vector>::value_type value_type;

      map_value(vec::map_view<Functor, Vector> const& map_vector)
    : map_vector(map_vector), its_value(map_vector.ref)
      {}

      value_type operator() (key_type const& key) const
      {
    return map_vector.functor(its_value(key));
      }

    protected:
      vec::map_view<Functor, Vector> const&   map_vector;
      typename ::mtl::traits::const_value<Vector>::type its_value;
        };

    } // detail

}} // namespace mtl::vector

namespace mtl { namespace traits {

    // ================
    // Property maps
    // ================

    template <typename Functor, typename Vector>
    struct index<vec::map_view<Functor, Vector> >
  : public index<Vector>
    {};

    template <typename Functor, typename Vector>
    struct const_value<vec::map_view<Functor, Vector> >
    {
  typedef vec::detail::map_value<Functor, Vector>  type;
    };

    // ================
    // Range generators
    // ================

    // Use range_generator of original vector
    template <typename Tag, typename Functor, typename Vector>
    struct range_generator<Tag, vec::map_view<Functor, Vector> >
  : public range_generator<Tag, Vector>
    {};

}} // mtl::traits

namespace mtl { namespace vec {

template <typename Scaling, typename Vector>
struct scaled_view
    : public map_view<tfunctor::scale<Scaling, typename Vector::value_type>, Vector>
{
    typedef tfunctor::scale<Scaling, typename Vector::value_type>  functor_type;
    typedef map_view<functor_type, Vector>                         base;
    typedef scaled_view                                            self;

    explicit scaled_view(const Scaling& scaling, const Vector& vector)
      : base(functor_type(scaling), vector)
    {}

    explicit scaled_view(const Scaling& scaling, boost::shared_ptr<Vector> p)
      : base(functor_type(scaling), p)
    {}

#ifdef MTL_WITH_MOVE
    scaled_view (self&& that) : base(that) {}
    scaled_view (const self& that) : base(that) {}
#endif
};

// added by Hui Li
template <typename Vector, typename RScaling>
struct rscaled_view
  : public map_view<tfunctor::rscale<typename Vector::value_type, RScaling>, Vector>
{
    typedef tfunctor::rscale<typename Vector::value_type, RScaling>  functor_type;
    typedef map_view<functor_type, Vector>                          base;
    typedef rscaled_view                                            self;

    explicit rscaled_view(const Vector& vector, const RScaling& rscaling)
      : base(functor_type(rscaling), vector)
    {}

    explicit rscaled_view(boost::shared_ptr<Vector> p, const RScaling& rscaling)
      : base(functor_type(rscaling), p)
    {}

#ifdef MTL_WITH_MOVE
    rscaled_view (self&& that) : base(that) {}
    rscaled_view (const self& that) : base(that) {}
#endif
};

// added by Hui Li
template <typename Vector, typename Divisor>
struct divide_by_view
  : public map_view<tfunctor::divide_by<typename Vector::value_type, Divisor>, Vector>
{
    typedef tfunctor::divide_by<typename Vector::value_type, Divisor>  functor_type;
    typedef map_view<functor_type, Vector>                             base;
    typedef divide_by_view                                             self;

    explicit divide_by_view(const Vector& vector, const Divisor& div)
      : base(functor_type(div), vector)
    {}

    explicit divide_by_view(boost::shared_ptr<Vector> p, const Divisor& div)
      : base(functor_type(div), p)
    {}

#ifdef MTL_WITH_MOVE
    divide_by_view (self&& that) : base(that) {}
    divide_by_view (const self& that) : base(that) {}
#endif
};

/// View for raising vector element to power of scalar exponent
template <typename Vector, typename Exponent>
struct pow_by_view
  : public map_view<tfunctor::pow_by<typename Vector::value_type, Exponent>, Vector>
{
    typedef tfunctor::pow_by<typename Vector::value_type, Exponent>  functor_type;
    typedef map_view<functor_type, Vector>                           base;
    typedef pow_by_view                                              self;

    explicit pow_by_view(const Vector& vector, const Exponent& exp)
      : base(functor_type(exp), vector)
    {}

    explicit pow_by_view(boost::shared_ptr<Vector> p, const Exponent& exp)
      : base(functor_type(exp), p)
    {}

#ifdef MTL_WITH_MOVE
    pow_by_view (self&& that) : base(that) {}
    pow_by_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct conj_view
  : public map_view<mtl::sfunctor::conj<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::conj<typename Vector::value_type>            functor_type;
    typedef map_view<functor_type, Vector>                         base;
    typedef conj_view                                                   self;

    explicit conj_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit conj_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    conj_view (self&& that) : base(that) {}
    conj_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct real_view
  : public map_view<mtl::sfunctor::real<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::real<typename Vector::value_type>            functor_type;
    typedef map_view<functor_type, Vector>                         base;
  typedef real_view                                              self;

    explicit real_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit real_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    real_view (self&& that) : base(that) {}
    real_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct imag_view
  : public map_view<mtl::sfunctor::imag<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::imag<typename Vector::value_type>            functor_type;
    typedef map_view<functor_type, Vector>                         base;
    typedef imag_view                                              self;

    explicit imag_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit imag_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    imag_view (self&& that) : base(that) {}
    imag_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct negate_view
  : public map_view<mtl::sfunctor::negate<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::negate<typename Vector::value_type>            functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef negate_view                                                   self;

    explicit negate_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit negate_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    negate_view (self&& that) : base(that) {}
    negate_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct abs_view
  : public map_view<mtl::sfunctor::abs<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::abs<typename Vector::value_type>               functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef abs_view                                                      self;

    explicit abs_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit abs_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    abs_view (self&& that) : base(that) {}
    abs_view (const self& that) : base(that) {}
#endif
};

// Inverse trigonometric

template <typename Vector>
struct acos_view
  : public map_view<mtl::sfunctor::acos<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::acos<typename Vector::value_type>              functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef acos_view                                                     self;

    explicit acos_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit acos_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    acos_view (self&& that) : base(that) {}
    acos_view (const self& that) : base(that) {}
#endif
};

# ifdef MTL_WITH_MATH_ELEVEN
template <typename Vector>
struct acosh_view
  : public map_view<mtl::sfunctor::acosh<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::acosh<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef acosh_view                                                    self;

    explicit acosh_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit acosh_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    acosh_view (self&& that) : base(that) {}
    acosh_view (const self& that) : base(that) {}
#endif
};
#endif

template <typename Vector>
struct asin_view
  : public map_view<mtl::sfunctor::asin<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::asin<typename Vector::value_type>              functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef asin_view                                                     self;

    explicit asin_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit asin_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    asin_view (self&& that) : base(that) {}
    asin_view (const self& that) : base(that) {}
#endif
};

# ifdef MTL_WITH_MATH_ELEVEN
template <typename Vector>
struct asinh_view
  : public map_view<mtl::sfunctor::asinh<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::asinh<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef asinh_view                                                    self;

    explicit asinh_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit asinh_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    asinh_view (self&& that) : base(that) {}
    asinh_view (const self& that) : base(that) {}
#endif
};
#endif

template <typename Vector>
struct atan_view
  : public map_view<mtl::sfunctor::atan<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::atan<typename Vector::value_type>              functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef atan_view                                                     self;

    explicit atan_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit atan_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    atan_view (self&& that) : base(that) {}
    atan_view (const self& that) : base(that) {}
#endif
};

# ifdef MTL_WITH_MATH_ELEVEN
template <typename Vector>
struct atanh_view
  : public map_view<mtl::sfunctor::atanh<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::atanh<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef atanh_view                                                    self;

    explicit atanh_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit atanh_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    atanh_view (self&& that) : base(that) {}
    atanh_view (const self& that) : base(that) {}
#endif
};
#endif

// Trigonometric

template <typename Vector>
struct cos_view
  : public map_view<mtl::sfunctor::cos<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::cos<typename Vector::value_type>              functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef cos_view                                                     self;

    explicit cos_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit cos_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    cos_view (self&& that) : base(that) {}
    cos_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct cosh_view
  : public map_view<mtl::sfunctor::cosh<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::cosh<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef cosh_view                                                    self;

    explicit cosh_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit cosh_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    cosh_view (self&& that) : base(that) {}
    cosh_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct sin_view
  : public map_view<mtl::sfunctor::sin<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::sin<typename Vector::value_type>              functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef sin_view                                                     self;

    explicit sin_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit sin_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    sin_view (self&& that) : base(that) {}
    sin_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct sinh_view
  : public map_view<mtl::sfunctor::sinh<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::sinh<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef sinh_view                                                    self;

    explicit sinh_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit sinh_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    sinh_view (self&& that) : base(that) {}
    sinh_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct tan_view
  : public map_view<mtl::sfunctor::tan<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::tan<typename Vector::value_type>              functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef tan_view                                                     self;

    explicit tan_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit tan_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    tan_view (self&& that) : base(that) {}
    tan_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct tanh_view
  : public map_view<mtl::sfunctor::tanh<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::tanh<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef tanh_view                                                    self;

    explicit tanh_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit tanh_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    tanh_view (self&& that) : base(that) {}
    tanh_view (const self& that) : base(that) {}
#endif
};

// Rounding
template <typename Vector>
struct ceil_view
  : public map_view<mtl::sfunctor::ceil<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::ceil<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef ceil_view                                                    self;

    explicit ceil_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit ceil_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    ceil_view (self&& that) : base(that) {}
    ceil_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct floor_view
  : public map_view<mtl::sfunctor::floor<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::floor<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef floor_view                                                    self;

    explicit floor_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit floor_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    floor_view (self&& that) : base(that) {}
    floor_view (const self& that) : base(that) {}
#endif
};

# ifdef MTL_WITH_MATH_ELEVEN
template <typename Vector>
struct round_view
  : public map_view<mtl::sfunctor::round<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::round<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef round_view                                                    self;

    explicit round_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit round_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    round_view (self&& that) : base(that) {}
    round_view (const self& that) : base(that) {}
#endif
};
#endif

# ifdef MTL_WITH_MATH_ELEVEN
template <typename Vector>
struct trunc_view
  : public map_view<mtl::sfunctor::trunc<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::trunc<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef trunc_view                                                    self;

    explicit trunc_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit trunc_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    trunc_view (self&& that) : base(that) {}
    trunc_view (const self& that) : base(that) {}
#endif
};
#endif

template <typename Vector>
struct log_view
  : public map_view<mtl::sfunctor::log<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::log<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef log_view                                                    self;

    explicit log_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit log_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    log_view (self&& that) : base(that) {}
    log_view (const self& that) : base(that) {}
#endif
};

# ifdef MTL_WITH_MATH_ELEVEN
template <typename Vector>
struct log2_view
  : public map_view<mtl::sfunctor::log2<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::log2<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef log2_view                                                    self;

    explicit log2_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit log2_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    log2_view (self&& that) : base(that) {}
    log2_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct log10_view
  : public map_view<mtl::sfunctor::log10<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::log10<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef log10_view                                                    self;

    explicit log10_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit log10_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    log10_view (self&& that) : base(that) {}
    log10_view (const self& that) : base(that) {}
#endif
};
# endif

template <typename Vector>
struct exp_view
  : public map_view<mtl::sfunctor::exp<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::exp<typename Vector::value_type>               functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef exp_view                                                      self;

    explicit exp_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit exp_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    exp_view (self&& that) : base(that) {}
    exp_view (const self& that) : base(that) {}
#endif
};

# ifdef MTL_WITH_MATH_ELEVEN
template <typename Vector>
struct exp2_view
  : public map_view<mtl::sfunctor::exp2<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::exp2<typename Vector::value_type>              functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef exp2_view                                                     self;

    explicit exp2_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit exp2_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    exp2_view (self&& that) : base(that) {}
    exp2_view (const self& that) : base(that) {}
#endif
};
#endif

template <typename Vector>
struct exp10_view
  : public map_view<mtl::sfunctor::exp10<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::exp10<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef exp10_view                                                    self;

    explicit exp10_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit exp10_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    exp10_view (self&& that) : base(that) {}
    exp10_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct sqrt_view
  : public map_view<mtl::sfunctor::sqrt<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::sqrt<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef sqrt_view                                                    self;

    explicit sqrt_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit sqrt_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    sqrt_view (self&& that) : base(that) {}
    sqrt_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct rsqrt_view
  : public map_view<mtl::sfunctor::rsqrt<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::rsqrt<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef rsqrt_view                                                    self;

    explicit rsqrt_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit rsqrt_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    rsqrt_view (self&& that) : base(that) {}
    rsqrt_view (const self& that) : base(that) {}
#endif
};

# ifdef MTL_WITH_MATH_ELEVEN
template <typename Vector>
struct erf_view
  : public map_view<mtl::sfunctor::erf<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::erf<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef erf_view                                                    self;

    explicit erf_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit erf_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    erf_view (self&& that) : base(that) {}
    erf_view (const self& that) : base(that) {}
#endif
};

template <typename Vector>
struct erfc_view
  : public map_view<mtl::sfunctor::erfc<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::erfc<typename Vector::value_type>             functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef erfc_view                                                    self;

    explicit erfc_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit erfc_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    erfc_view (self&& that) : base(that) {}
    erfc_view (const self& that) : base(that) {}
#endif
};
# endif

template <typename Vector>
struct signum_view
  : public map_view<mtl::sfunctor::signum<typename Vector::value_type>, Vector>
{
    typedef mtl::sfunctor::signum<typename Vector::value_type>              functor_type;
    typedef map_view<functor_type, Vector>                                base;
    typedef signum_view                                                     self;

    explicit signum_view(const Vector& vector)
      : base(functor_type(), vector)
    {}

    explicit signum_view(boost::shared_ptr<Vector> p)
      : base(functor_type(), p)
    {}

#ifdef MTL_WITH_MOVE
    signum_view (self&& that) : base(that) {}
    signum_view (const self& that) : base(that) {}
#endif
};

}} // namespace mtl::vector

namespace mtl { namespace sfunctor {

    template <typename Vector>
    struct conj_aux<Vector, tag::vector>
    {
  typedef mtl::vec::conj_view<Vector> result_type;

  static inline result_type apply(const Vector& vector)
  {
      return result_type(vector);
  }

  result_type operator() (const Vector& vector) const
  {
      return apply(vector);
  }
    };

}}

#endif // MTL_VECTOR_MAP_VIEW_INCLUDE


/*
  Author(s):      Matthew Celnik (msc37)
  Project:        sweepc (population balance solver)
  Sourceforge:    http://sourceforge.net/projects/mopssuite

  Copyright (C) 2008 Matthew S Celnik.

  File purpose:
    Implementation of the Solver class declared in the
    swp_solver.h header file.

  Licence:
    This file is part of "sweepc".

    sweepc is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  Contact:
    Dr Markus Kraft
    Dept of Chemical Engineering
    University of Cambridge
    New Museums Site
    Pembroke Street
    Cambridge
    CB2 3RA
    UK

    Email:       mk306@cam.ac.uk
    Website:     http://como.cheng.cam.ac.uk
*/

#include "swp_solver.h"
#include "local_geometry1d.h"

#include "choose_index.hpp"

#include <stdlib.h>
#include <cmath>
#include "string_functions.h"
#include <stdexcept>
#include <ctime>
#include <limits>
#include <boost/random/exponential_distribution.hpp>
#include <boost/random/uniform_01.hpp>
#include <boost/random/variate_generator.hpp>

using namespace Sweep;
using namespace std;
using namespace Strings;

// CONSTRUCTORS AND DESTRUCTORS.

// Default constructor.
Solver::Solver(void)
: m_splitratio(1.0e9)
{
//  srnd(time(0));			//added by ms785
//    srnd(getpid());
 #ifdef USE_MPI
  Sweep::init_genrand(getpid());			//added by ms785
 #endif

}

//! Copy constructor
Solver::Solver(const Solver &sol)
: m_splitratio(sol.m_splitratio) {}

// Default destructor.
Solver::~Solver(void)
{
    // Nothing special to destruct.
}

// RUNNING THE SOLVER.

// Performs stochastic stepping algorithm up to specified stop time using
// the given mechanism to define the stochastic processes.  Updates given
// system accordingly.  On error returns <0, otherwise returns 0.
int Solver::Run(double &t, double tstop, Cell &sys, const Mechanism &mech,
                rng_type &rng)
{
    int err = 0;
    double tsplit, dtg, jrate, tflow(t);
    fvector rates(mech.TermCount(), 0.0);
    // Global maximum time step.
    dtg     = tstop - t;
    double tin = t; //store start time

    // Loop over time until we reach the stop time.
    while (t < tstop)
    {
        if (mech.AnyDeferred() && (sys.ParticleCount() + sys.Particles().GetTotalParticleNumber() > 0))  {
            // Get the process jump rates (and the total rate).
            jrate = mech.CalcJumpRateTerms(t, sys, Geometry::LocalGeometry1d(), rates);

            // Calculate split end time.
            tsplit = calcSplitTime(t, std::min(t+dtg, tstop), jrate, sys.ParticleCount() + sys.Particles().GetTotalParticleNumber());
        } else {
            // There are no deferred processes, therefore there
            // is no need to perform LPDA splitting steps.
            tsplit = tstop;
        }
	tin = t;

        // Perform stochastic jump processes.
        while (t < tsplit) {

            // Sweep does not do transport
            jrate = mech.CalcJumpRateTerms(t, sys, Geometry::LocalGeometry1d(), rates);
            timeStep(t, std::min(t + dtg / 3.0, tsplit), sys, Geometry::LocalGeometry1d(),
                     mech, rates, jrate, rng);

            // Do particle transport
            if (sys.OutflowCount() > 0 || sys.InflowCount() > 0)
                mech.DoParticleFlow(t, t - tflow, sys, Geometry::LocalGeometry1d(), rng);
            tflow = t;
        }

        // Perform Linear Process Deferment Algorithm to
        // update all deferred processes.
        mech.LPDA(t, sys, rng);

        // Perform deferred processes on particle-number particles
	if (mech.IsHybrid() && sys.Particles().GetTotalParticleNumber() > 0)
	{
		sys.Particles().RecalcPNPropertySums();
		mech.UpdateSections(t, t - tin, sys, rng);
	}
    }

    return err;
}

// TIME STEPPING ROUTINES.

/*!
 * Calculates the splitting end time after which all particles
 * shall be updated using LPDA.
 *
 *@param[in]	t		Current time
 *@param[in]	tstop	Latest possible end for the splitting
 *@param[in]	jrate	Computational jump rate
 *@param[in]	n		Number of computational particles
 *
 *@return	End time for next splitting step
 */
double Solver::calcSplitTime(double t, double tstop, double jrate,
                           unsigned int n) const
{
    // Calculate the splitting time step, ensuring that it is
    // not longer than the maximum allowable time.
    double tsplit = (n + 1) * m_splitratio / (jrate + 1.0);

	//double tsplit = m_splitratio / (jrate + 1.0);

    // Now put the split end time into tsplit, again
    // checking that it is not beyond the stop time.
    return min(tsplit+t, tstop);
}

/*!
 * Performs a single stochastic event on the ensemble from the given
 * mechanism or move to t_stop, whichever comes first.
 *
 *@param[in,out]    t           Current time, which will be updated
 *@param[in]        t_stop      Time past which step may not go
 *@param[in,out]    sys         System in which jump will take place
 *@param[in]        geom        Specify size and neighbours of cell (use a default constructed object which will apply unit scaling when no geometry information present)
 *@param[in]        mech        Mechanism specifying the jump
 *@param[in]        rates       Vector of computational jump rates, one for each jump process
 *@param[in]        jrate       Sum of entries in rates (total jump rate)
 *@param[in,out]    rng         Random number generator
 *
 *@pre      t <= t_stop
 *@post     t <= t_stop
 */
void Solver::timeStep(double &t, double t_stop, Cell &sys, const Geometry::LocalGeometry1d &geom,
                      const Mechanism &mech, const fvector &rates, double jrate,
                      rng_type &rng)
{
    // The purpose of this routine is to perform a single stochastic jump process.  This
    // involves summing the total rate of all processes, generating a waiting time,
    // selecting a process and performing that process.
    double dt;

    //std::cout << "Solver::timeStep in cell " << &sys << " from " << t << " with rate " << jrate;

    // Calculate exponentially distributed time step size.
    if (jrate > 0.0) {
        boost::exponential_distribution<double> waitDistrib(jrate);
        boost::variate_generator<Sweep::rng_type&, boost::exponential_distribution<double> > waitGenerator(rng, waitDistrib);
        dt = waitGenerator();
        //std::cout << ' ' << dt;
    } else {
        // Avoid divide by zero.
        dt = std::numeric_limits<double>::max();
    }

    // Truncate if step is too long or select a process
    // to perform.
    if (t+dt <= t_stop) {
        boost::uniform_01<rng_type &> uniformGenerator(rng);
        const int i = chooseIndex(rates, uniformGenerator);
        mech.DoProcess(i, t+dt, sys, geom, rng);
        t += dt;
    } else {
        t = t_stop;
        //std:cout << " step truncated to end at " << t_stop;
    }
    //std::cout << std::endl;
}

// Selects a process using a DIV algorithm and the process rates
// as weights.
int Solver::chooseProcess(const fvector &rates, double (*rand_u01)())
{
    // This routine implements a DIV algorithm to select a process from a
    // discrete list of processes when each process's rate is given.

    // Add together all process rates.
    fvector::const_iterator i;
    double sum = 0.0;
    for (i=rates.begin(); i!=rates.end(); ++i) {
        sum += *i;
    }

    // Generate a uniform random number.
    double r = rand_u01() * sum ;

    // Select a process using DIV algorithm.
    int j=-1;
    i = rates.begin();
    while((r > 0.0) && (i != rates.end())) {
        r -= *i;
        ++i; ++j;
    }
    return j;
};


// Boost.Geometry
// This file is manually converted from PROJ4

// This file was modified by Oracle on 2017, 2018.
// Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is subject to 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)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// This file was converted to Geometry Library by Adam Wulkiewicz

// Original copyright notice:

// Copyright (c) 2000, Frank Warmerdam

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef BOOST_GEOMETRY_SRS_PROJECTIONS_IMPL_PJ_TRANSFORM_HPP
#define BOOST_GEOMETRY_SRS_PROJECTIONS_IMPL_PJ_TRANSFORM_HPP

#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/radian_access.hpp>

#include <boost/geometry/srs/projections/impl/geocent.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/invalid_point.hpp>

#include <boost/geometry/util/range.hpp>

#include <cstring>
#include <cmath>

namespace boost { namespace geometry { namespace projections
{

namespace detail
{

// -----------------------------------------------------------
// Boost.Geometry helpers begin
// -----------------------------------------------------------

template
<
    typename Point,
    bool HasCoord2 = (dimension<Point>::value > 2)
>
struct z_access
{
    typedef typename coordinate_type<Point>::type type;
    static inline type get(Point const& point)
    {
        return geometry::get<2>(point);
    }
    static inline void set(Point & point, type const& h)
    {
        return geometry::set<2>(point, h);
    }
};

template <typename Point>
struct z_access<Point, false>
{
    typedef typename coordinate_type<Point>::type type;
    static inline type get(Point const& )
    {
        return type(0);
    }
    static inline void set(Point & , type const& )
    {}
};

template <typename XYorXYZ>
inline typename z_access<XYorXYZ>::type
get_z(XYorXYZ const& xy_or_xyz)
{
    return z_access<XYorXYZ>::get(xy_or_xyz);
}

template <typename XYorXYZ>
inline void set_z(XYorXYZ & xy_or_xyz,
                  typename z_access<XYorXYZ>::type const& z)
{
    return z_access<XYorXYZ>::set(xy_or_xyz, z);
}

template
<
    typename Range,
    bool AddZ = (dimension<typename boost::range_value<Range>::type>::value < 3)
>
struct range_wrapper
{
    typedef Range range_type;
    typedef typename boost::range_value<Range>::type point_type;
    typedef typename coordinate_type<point_type>::type coord_t;

    range_wrapper(Range & range)
        : m_range(range)
    {}

    range_type & get_range() { return m_range; }

    coord_t get_z(std::size_t i) { return detail::get_z(range::at(m_range, i)); }
    void set_z(std::size_t i, coord_t const& v) { return detail::set_z(range::at(m_range, i), v); }

private:
    Range & m_range;
};

template <typename Range>
struct range_wrapper<Range, true>
{
    typedef Range range_type;
    typedef typename boost::range_value<Range>::type point_type;
    typedef typename coordinate_type<point_type>::type coord_t;

    range_wrapper(Range & range)
        : m_range(range)
        , m_zs(boost::size(range), coord_t(0))
    {}

    range_type & get_range() { return m_range; }

    coord_t get_z(std::size_t i) { return m_zs[i]; }
    void set_z(std::size_t i, coord_t const& v) { m_zs[i] = v; }

private:
    Range & m_range;
    std::vector<coord_t> m_zs;
};

// -----------------------------------------------------------
// Boost.Geometry helpers end
// -----------------------------------------------------------

/*#ifndef SRS_WGS84_SEMIMAJOR
#define SRS_WGS84_SEMIMAJOR 6378137.0
#endif

#ifndef SRS_WGS84_ESQUARED
#define SRS_WGS84_ESQUARED 0.0066943799901413165
#endif*/

template <typename Par>
inline typename Par::type Dx_BF(Par const& defn) { return defn.datum_params[0]; }
template <typename Par>
inline typename Par::type Dy_BF(Par const& defn) { return defn.datum_params[1]; }
template <typename Par>
inline typename Par::type Dz_BF(Par const& defn) { return defn.datum_params[2]; }
template <typename Par>
inline typename Par::type Rx_BF(Par const& defn) { return defn.datum_params[3]; }
template <typename Par>
inline typename Par::type Ry_BF(Par const& defn) { return defn.datum_params[4]; }
template <typename Par>
inline typename Par::type Rz_BF(Par const& defn) { return defn.datum_params[5]; }
template <typename Par>
inline typename Par::type M_BF(Par const& defn) { return defn.datum_params[6]; }

/*
** This table is intended to indicate for any given error code in
** the range 0 to -44, whether that error will occur for all locations (ie.
** it is a problem with the coordinate system as a whole) in which case the
** value would be 0, or if the problem is with the point being transformed
** in which case the value is 1.
**
** At some point we might want to move this array in with the error message
** list or something, but while experimenting with it this should be fine.
*/

static const int transient_error[50] = {
    /*             0  1  2  3  4  5  6  7  8  9   */
    /* 0 to 9 */   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* 10 to 19 */ 0, 0, 0, 0, 1, 1, 0, 1, 1, 1,
    /* 20 to 29 */ 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
    /* 30 to 39 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* 40 to 49 */ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 };

template <typename T, typename Range>
inline int pj_geocentric_to_geodetic( T const& a, T const& es,
                                      Range & range );
template <typename T, typename Range>
inline int pj_geodetic_to_geocentric( T const& a, T const& es,
                                      Range & range );

/************************************************************************/
/*                            pj_transform()                            */
/*                                                                      */
/*      Currently this function doesn't recognise if two projections    */
/*      are identical (to short circuit reprojection) because it is     */
/*      difficult to compare PJ structures (since there are some        */
/*      projection specific components).                                */
/************************************************************************/

template <typename SrcPrj, typename DstPrj2, typename Par, typename Range>
inline bool pj_transform(SrcPrj const& srcprj, Par const& srcdefn,
                         DstPrj2 const& dstprj, Par const& dstdefn,
                         Range & range)

{
    typedef typename boost::range_value<Range>::type point_type;
    typedef typename coordinate_type<point_type>::type coord_t;
    static const std::size_t dimension = geometry::dimension<point_type>::value;
    std::size_t point_count = boost::size(range);
    bool result = true;

/* -------------------------------------------------------------------- */
/*      Transform unusual input coordinate axis orientation to          */
/*      standard form if needed.                                        */
/* -------------------------------------------------------------------- */
    // Ignored

/* -------------------------------------------------------------------- */
/*      Transform Z to meters if it isn't already.                      */
/* -------------------------------------------------------------------- */
    if( srcdefn.vto_meter != 1.0 && dimension > 2 )
    {
        for( std::size_t i = 0; i < point_count; i++ )
        {
            point_type & point = geometry::range::at(range, i);
            set_z(point, get_z(point) * srcdefn.vto_meter);
        }
    }

/* -------------------------------------------------------------------- */
/*      Transform geocentric source coordinates to lat/long.            */
/* -------------------------------------------------------------------- */
    if( srcdefn.is_geocent )
    {
        // Point should be cartesian 3D (ECEF)
        if (dimension < 3)
            BOOST_THROW_EXCEPTION( projection_exception(PJD_ERR_GEOCENTRIC) );
            //return PJD_ERR_GEOCENTRIC;

        if( srcdefn.to_meter != 1.0 )
        {
            for(std::size_t i = 0; i < point_count; i++ )
            {
                point_type & point = range::at(range, i);
                if( ! is_invalid_point(point) )
                {
                    set<0>(point, get<0>(point) * srcdefn.to_meter);
                    set<1>(point, get<1>(point) * srcdefn.to_meter);
                }
            }
        }

        range_wrapper<Range, false> rng(range);
        int err = pj_geocentric_to_geodetic( srcdefn.a_orig, srcdefn.es_orig,
                                             rng );
        if( err != 0 )
            BOOST_THROW_EXCEPTION( projection_exception(err) );
            //return err;

        // NOTE: here 3D cartesian ECEF is converted into 3D geodetic LLH
    }

/* -------------------------------------------------------------------- */
/*      Transform source points to lat/long, if they aren't             */
/*      already.                                                        */
/* -------------------------------------------------------------------- */
    else if( !srcdefn.is_latlong )
    {
        // Point should be cartesian 2D or 3D (map projection)

        /* Check first if projection is invertible. */
        /*if( (srcdefn->inv3d == NULL) && (srcdefn->inv == NULL))
        {
            pj_ctx_set_errno( pj_get_ctx(srcdefn), -17 );
            pj_log( pj_get_ctx(srcdefn), PJ_LOG_ERROR,
                    "pj_transform(): source projection not invertible" );
            return -17;
        }*/

        /* If invertible - First try inv3d if defined */
        //if (srcdefn->inv3d != NULL)
        //{
        //    /* Three dimensions must be defined */
        //    if ( z == NULL)
        //    {
        //        pj_ctx_set_errno( pj_get_ctx(srcdefn), PJD_ERR_GEOCENTRIC);
        //        return PJD_ERR_GEOCENTRIC;
        //    }

        //    for (i=0; i < point_count; i++)
        //    {
        //        XYZ projected_loc;
        //        XYZ geodetic_loc;

        //        projected_loc.u = x[point_offset*i];
        //        projected_loc.v = y[point_offset*i];
        //        projected_loc.w = z[point_offset*i];

        //        if (projected_loc.u == HUGE_VAL)
        //            continue;

        //        geodetic_loc = pj_inv3d(projected_loc, srcdefn);
        //        if( srcdefn->ctx->last_errno != 0 )
        //        {
        //            if( (srcdefn->ctx->last_errno != 33 /*EDOM*/
        //                 && srcdefn->ctx->last_errno != 34 /*ERANGE*/ )
        //                && (srcdefn->ctx->last_errno > 0
        //                    || srcdefn->ctx->last_errno < -44 || point_count == 1
        //                    || transient_error[-srcdefn->ctx->last_errno] == 0 ) )
        //                return srcdefn->ctx->last_errno;
        //            else
        //            {
        //                geodetic_loc.u = HUGE_VAL;
        //                geodetic_loc.v = HUGE_VAL;
        //                geodetic_loc.w = HUGE_VAL;
        //            }
        //        }

        //        x[point_offset*i] = geodetic_loc.u;
        //        y[point_offset*i] = geodetic_loc.v;
        //        z[point_offset*i] = geodetic_loc.w;

        //    }

        //}
        //else
        {
            /* Fallback to the original PROJ.4 API 2d inversion - inv */
            for( std::size_t i = 0; i < point_count; i++ )
            {
                point_type & point = range::at(range, i);

                if( is_invalid_point(point) )
                    continue;

                try
                {
                    pj_inv(srcprj, srcdefn, point, point);
                }
                catch(projection_exception const& e)
                {
                    if( (e.code() != 33 /*EDOM*/
                        && e.code() != 34 /*ERANGE*/ )
                        && (e.code() > 0
                            || e.code() < -44 /*|| point_count == 1*/
                            || transient_error[-e.code()] == 0) ) {
                        BOOST_RETHROW
                    } else {
                        set_invalid_point(point);
                        result = false;
                        if (point_count == 1)
                            return result;
                    }
                }
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      But if they are already lat long, adjust for the prime          */
/*      meridian if there is one in effect.                             */
/* -------------------------------------------------------------------- */
    if( srcdefn.from_greenwich != 0.0 )
    {
        for( std::size_t i = 0; i < point_count; i++ )
        {
            point_type & point = range::at(range, i);

            if( ! is_invalid_point(point) )
                set<0>(point, get<0>(point) + srcdefn.from_greenwich);
        }
    }

/* -------------------------------------------------------------------- */
/*      Do we need to translate from geoid to ellipsoidal vertical      */
/*      datum?                                                          */
/* -------------------------------------------------------------------- */
    /*if( srcdefn->has_geoid_vgrids && z != NULL )
    {
        if( pj_apply_vgridshift( srcdefn, "sgeoidgrids",
                                 &(srcdefn->vgridlist_geoid),
                                 &(srcdefn->vgridlist_geoid_count),
                                 0, point_count, point_offset, x, y, z ) != 0 )
            return pj_ctx_get_errno(srcdefn->ctx);
    }*/

/* -------------------------------------------------------------------- */
/*      Convert datums if needed, and possible.                         */
/* -------------------------------------------------------------------- */
    if ( ! pj_datum_transform( srcdefn, dstdefn, range ) )
    {
        result = false;
    }

/* -------------------------------------------------------------------- */
/*      Do we need to translate from ellipsoidal to geoid vertical      */
/*      datum?                                                          */
/* -------------------------------------------------------------------- */
    /*if( dstdefn->has_geoid_vgrids && z != NULL )
    {
        if( pj_apply_vgridshift( dstdefn, "sgeoidgrids",
                                 &(dstdefn->vgridlist_geoid),
                                 &(dstdefn->vgridlist_geoid_count),
                                 1, point_count, point_offset, x, y, z ) != 0 )
            return dstdefn->ctx->last_errno;
    }*/

/* -------------------------------------------------------------------- */
/*      But if they are staying lat long, adjust for the prime          */
/*      meridian if there is one in effect.                             */
/* -------------------------------------------------------------------- */
    if( dstdefn.from_greenwich != 0.0 )
    {
        for( std::size_t i = 0; i < point_count; i++ )
        {
            point_type & point = range::at(range, i);

            if( ! is_invalid_point(point) )
                set<0>(point, get<0>(point) - dstdefn.from_greenwich);
        }
    }

/* -------------------------------------------------------------------- */
/*      Transform destination latlong to geocentric if required.        */
/* -------------------------------------------------------------------- */
    if( dstdefn.is_geocent )
    {
        // Point should be cartesian 3D (ECEF)
        if (dimension < 3)
            BOOST_THROW_EXCEPTION( projection_exception(PJD_ERR_GEOCENTRIC) );
            //return PJD_ERR_GEOCENTRIC;

        // NOTE: In the original code the return value of the following
        // function is not checked
        range_wrapper<Range, false> rng(range);
        int err = pj_geodetic_to_geocentric( dstdefn.a_orig, dstdefn.es_orig,
                                             rng );
        if( err == -14 )
            result = false;
        else
            BOOST_THROW_EXCEPTION( projection_exception(err) );

        if( dstdefn.fr_meter != 1.0 )
        {
            for( std::size_t i = 0; i < point_count; i++ )
            {
                point_type & point = range::at(range, i);
                if( ! is_invalid_point(point) )
                {
                    set<0>(point, get<0>(point) * dstdefn.fr_meter);
                    set<1>(point, get<1>(point) * dstdefn.fr_meter);
                }
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Transform destination points to projection coordinates, if      */
/*      desired.                                                        */
/* -------------------------------------------------------------------- */
    else if( !dstdefn.is_latlong )
    {

        //if( dstdefn->fwd3d != NULL)
        //{
        //    for( i = 0; i < point_count; i++ )
        //    {
        //        XYZ projected_loc;
        //        LPZ geodetic_loc;

        //        geodetic_loc.u = x[point_offset*i];
        //        geodetic_loc.v = y[point_offset*i];
        //        geodetic_loc.w = z[point_offset*i];

        //        if (geodetic_loc.u == HUGE_VAL)
        //            continue;

        //        projected_loc = pj_fwd3d( geodetic_loc, dstdefn);
        //        if( dstdefn->ctx->last_errno != 0 )
        //        {
        //            if( (dstdefn->ctx->last_errno != 33 /*EDOM*/
        //                 && dstdefn->ctx->last_errno != 34 /*ERANGE*/ )
        //                && (dstdefn->ctx->last_errno > 0
        //                    || dstdefn->ctx->last_errno < -44 || point_count == 1
        //                    || transient_error[-dstdefn->ctx->last_errno] == 0 ) )
        //                return dstdefn->ctx->last_errno;
        //            else
        //            {
        //                projected_loc.u = HUGE_VAL;
        //                projected_loc.v = HUGE_VAL;
        //                projected_loc.w = HUGE_VAL;
        //            }
        //        }

        //        x[point_offset*i] = projected_loc.u;
        //        y[point_offset*i] = projected_loc.v;
        //        z[point_offset*i] = projected_loc.w;
        //    }

        //}
        //else
        {
            for(std::size_t i = 0; i < point_count; i++ )
            {
                point_type & point = range::at(range, i);

                if( is_invalid_point(point) )
                    continue;

                try {
                    pj_fwd(dstprj, dstdefn, point, point);
                } catch (projection_exception const& e) {

                    if( (e.code() != 33 /*EDOM*/
                         && e.code() != 34 /*ERANGE*/ )
                        && (e.code() > 0
                            || e.code() < -44 /*|| point_count == 1*/
                            || transient_error[-e.code()] == 0) ) {
                        BOOST_RETHROW
                    } else {
                        set_invalid_point(point);
                        result = false;
                        if (point_count == 1)
                            return result;
                    }
                }
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      If a wrapping center other than 0 is provided, rewrap around    */
/*      the suggested center (for latlong coordinate systems only).     */
/* -------------------------------------------------------------------- */
    else if( dstdefn.is_latlong && dstdefn.is_long_wrap_set )
    {
        for( std::size_t i = 0; i < point_count; i++ )
        {
            point_type & point = range::at(range, i);
            coord_t x = get_as_radian<0>(point);

            if( is_invalid_point(point) )
                continue;

            // TODO - units-dependant constants could be used instead
            while( x < dstdefn.long_wrap_center - math::pi<coord_t>() )
                x += math::two_pi<coord_t>();
            while( x > dstdefn.long_wrap_center + math::pi<coord_t>() )
                x -= math::two_pi<coord_t>();

            set_from_radian<0>(point, x);
        }
    }

/* -------------------------------------------------------------------- */
/*      Transform Z from meters if needed.                              */
/* -------------------------------------------------------------------- */
    if( dstdefn.vto_meter != 1.0 && dimension > 2 )
    {
        for( std::size_t i = 0; i < point_count; i++ )
        {
            point_type & point = geometry::range::at(range, i);
            set_z(point, get_z(point) * dstdefn.vfr_meter);
        }
    }

/* -------------------------------------------------------------------- */
/*      Transform normalized axes into unusual output coordinate axis   */
/*      orientation if needed.                                          */
/* -------------------------------------------------------------------- */
    // Ignored

    return result;
}

/************************************************************************/
/*                     pj_geodetic_to_geocentric()                      */
/************************************************************************/

template <typename T, typename Range, bool AddZ>
inline int pj_geodetic_to_geocentric( T const& a, T const& es,
                                      range_wrapper<Range, AddZ> & range_wrapper )

{
    //typedef typename boost::range_iterator<Range>::type iterator;
    typedef typename boost::range_value<Range>::type point_type;
    //typedef typename coordinate_type<point_type>::type coord_t;

    Range & rng = range_wrapper.get_range();
    std::size_t point_count = boost::size(rng);

    int ret_errno = 0;

    T const b = (es == 0.0) ? a : a * sqrt(1-es);

    GeocentricInfo<T> gi;
    if( pj_Set_Geocentric_Parameters( gi, a, b ) != 0 )
    {
        return PJD_ERR_GEOCENTRIC;
    }

    for( std::size_t i = 0 ; i < point_count ; ++i )
    {
        point_type & point = range::at(rng, i);

        if( is_invalid_point(point) )
            continue;

        T X = 0, Y = 0, Z = 0;
        if( pj_Convert_Geodetic_To_Geocentric( gi,
                                               get_as_radian<0>(point),
                                               get_as_radian<1>(point),
                                               range_wrapper.get_z(i), // Height
                                               X, Y, Z ) != 0 )
        {
            ret_errno = -14;
            set_invalid_point(point);
            /* but keep processing points! */
        }
        else
        {
            set<0>(point, X);
            set<1>(point, Y);
            range_wrapper.set_z(i, Z);
        }
    }

    return ret_errno;
}

/************************************************************************/
/*                     pj_geodetic_to_geocentric()                      */
/************************************************************************/

template <typename T, typename Range, bool AddZ>
inline int pj_geocentric_to_geodetic( T const& a, T const& es,
                                      range_wrapper<Range, AddZ> & range_wrapper )

{
    //typedef typename boost::range_iterator<Range>::type iterator;
    typedef typename boost::range_value<Range>::type point_type;
    //typedef typename coordinate_type<point_type>::type coord_t;

    Range & rng = range_wrapper.get_range();
    std::size_t point_count = boost::size(rng);

    T const b = (es == 0.0) ? a : a * sqrt(1-es);

    GeocentricInfo<T> gi;
    if( pj_Set_Geocentric_Parameters( gi, a, b ) != 0 )
    {
        return PJD_ERR_GEOCENTRIC;
    }

    for( std::size_t i = 0 ; i < point_count ; ++i )
    {
        point_type & point = range::at(rng, i);

        if( is_invalid_point(point) )
            continue;

        T Longitude = 0, Latitude = 0, Height = 0;
        pj_Convert_Geocentric_To_Geodetic( gi,
                                           get<0>(point),
                                           get<1>(point),
                                           range_wrapper.get_z(i), // z
                                           Longitude, Latitude, Height );

        set_from_radian<0>(point, Longitude);
        set_from_radian<1>(point, Latitude);
        range_wrapper.set_z(i, Height); // Height
    }

    return 0;
}

/************************************************************************/
/*                         pj_compare_datums()                          */
/*                                                                      */
/*      Returns TRUE if the two datums are identical, otherwise         */
/*      FALSE.                                                          */
/************************************************************************/

template <typename Par>
inline bool pj_compare_datums( Par & srcdefn, Par & dstdefn )
{
    if( srcdefn.datum_type != dstdefn.datum_type )
    {
        return false;
    }
    else if( srcdefn.a_orig != dstdefn.a_orig
             || math::abs(srcdefn.es_orig - dstdefn.es_orig) > 0.000000000050 )
    {
        /* the tolerance for es is to ensure that GRS80 and WGS84 are
           considered identical */
        return false;
    }
    else if( srcdefn.datum_type == PJD_3PARAM )
    {
        return (srcdefn.datum_params[0] == dstdefn.datum_params[0]
                && srcdefn.datum_params[1] == dstdefn.datum_params[1]
                && srcdefn.datum_params[2] == dstdefn.datum_params[2]);
    }
    else if( srcdefn.datum_type == PJD_7PARAM )
    {
        return (srcdefn.datum_params[0] == dstdefn.datum_params[0]
                && srcdefn.datum_params[1] == dstdefn.datum_params[1]
                && srcdefn.datum_params[2] == dstdefn.datum_params[2]
                && srcdefn.datum_params[3] == dstdefn.datum_params[3]
                && srcdefn.datum_params[4] == dstdefn.datum_params[4]
                && srcdefn.datum_params[5] == dstdefn.datum_params[5]
                && srcdefn.datum_params[6] == dstdefn.datum_params[6]);
    }
    else if( srcdefn.datum_type == PJD_GRIDSHIFT )
    {
        return pj_param(srcdefn.params,"snadgrids").s
            == pj_param(dstdefn.params,"snadgrids").s;
    }
    else
        return true;
}

/************************************************************************/
/*                       pj_geocentic_to_wgs84()                        */
/************************************************************************/

template <typename Par, typename Range, bool AddZ>
inline int pj_geocentric_to_wgs84( Par const& defn,
                                   range_wrapper<Range, AddZ> & range_wrapper )

{
    typedef typename boost::range_value<Range>::type point_type;
    typedef typename coordinate_type<point_type>::type coord_t;

    Range & rng = range_wrapper.get_range();
    std::size_t point_count = boost::size(rng);

    if( defn.datum_type == PJD_3PARAM )
    {
        for(std::size_t i = 0; i < point_count; i++ )
        {
            point_type & point = range::at(rng, i);

            if( is_invalid_point(point) )
                continue;

            set<0>(point,                   get<0>(point) + Dx_BF(defn));
            set<1>(point,                   get<1>(point) + Dy_BF(defn));
            range_wrapper.set_z(i, range_wrapper.get_z(i) + Dz_BF(defn));
        }
    }
    else if( defn.datum_type == PJD_7PARAM )
    {
        for(std::size_t i = 0; i < point_count; i++ )
        {
            point_type & point = range::at(rng, i);

            if( is_invalid_point(point) )
                continue;

            coord_t x = get<0>(point);
            coord_t y = get<1>(point);
            coord_t z = range_wrapper.get_z(i);

            coord_t x_out, y_out, z_out;

            x_out = M_BF(defn)*(             x - Rz_BF(defn)*y + Ry_BF(defn)*z) + Dx_BF(defn);
            y_out = M_BF(defn)*( Rz_BF(defn)*x +             y - Rx_BF(defn)*z) + Dy_BF(defn);
            z_out = M_BF(defn)*(-Ry_BF(defn)*x + Rx_BF(defn)*y +             z) + Dz_BF(defn);

            set<0>(point, x_out);
            set<1>(point, y_out);
            range_wrapper.set_z(i, z_out);
        }
    }

    return 0;
}

/************************************************************************/
/*                      pj_geocentic_from_wgs84()                       */
/************************************************************************/

template <typename Par, typename Range, bool AddZ>
inline int pj_geocentric_from_wgs84( Par const& defn,
                                     range_wrapper<Range, AddZ> & range_wrapper )

{
    typedef typename boost::range_value<Range>::type point_type;
    typedef typename coordinate_type<point_type>::type coord_t;

    Range & rng = range_wrapper.get_range();
    std::size_t point_count = boost::size(rng);

    if( defn.datum_type == PJD_3PARAM )
    {
        for(std::size_t i = 0; i < point_count; i++ )
        {
            point_type & point = range::at(rng, i);

            if( is_invalid_point(point) )
                continue;

            set<0>(point,                   get<0>(point) - Dx_BF(defn));
            set<1>(point,                   get<1>(point) - Dy_BF(defn));
            range_wrapper.set_z(i, range_wrapper.get_z(i) - Dz_BF(defn));
        }
    }
    else if( defn.datum_type == PJD_7PARAM )
    {
        for(std::size_t i = 0; i < point_count; i++ )
        {
            point_type & point = range::at(rng, i);

            if( is_invalid_point(point) )
                continue;

            coord_t x = get<0>(point);
            coord_t y = get<1>(point);
            coord_t z = range_wrapper.get_z(i);

            coord_t x_tmp = (x - Dx_BF(defn)) / M_BF(defn);
            coord_t y_tmp = (y - Dy_BF(defn)) / M_BF(defn);
            coord_t z_tmp = (z - Dz_BF(defn)) / M_BF(defn);

            x =              x_tmp + Rz_BF(defn)*y_tmp - Ry_BF(defn)*z_tmp;
            y = -Rz_BF(defn)*x_tmp +             y_tmp + Rx_BF(defn)*z_tmp;
            z =  Ry_BF(defn)*x_tmp - Rx_BF(defn)*y_tmp +             z_tmp;

            set<0>(point, x);
            set<1>(point, y);
            range_wrapper.set_z(i, z);
        }
    }

    return 0;
}

inline bool pj_datum_check_error(int err)
{
    return err != 0 && (err > 0 || transient_error[-err] == 0);
}

/************************************************************************/
/*                         pj_datum_transform()                         */
/*                                                                      */
/*      The input should be long/lat/z coordinates in radians in the    */
/*      source datum, and the output should be long/lat/z               */
/*      coordinates in radians in the destination datum.                */
/************************************************************************/

template <typename Par, typename Range>
inline bool pj_datum_transform( Par const& srcdefn, Par const& dstdefn,
                                Range & range )

{
    typedef typename Par::type calc_t;
    bool result = true;

    calc_t      src_a, src_es, dst_a, dst_es;

/* -------------------------------------------------------------------- */
/*      We cannot do any meaningful datum transformation if either      */
/*      the source or destination are of an unknown datum type          */
/*      (ie. only a +ellps declaration, no +datum).  This is new        */
/*      behavior for PROJ 4.6.0.                                        */
/* -------------------------------------------------------------------- */
    if( srcdefn.datum_type == PJD_UNKNOWN
        || dstdefn.datum_type == PJD_UNKNOWN )
        return result;

/* -------------------------------------------------------------------- */
/*      Short cut if the datums are identical.                          */
/* -------------------------------------------------------------------- */
    if( pj_compare_datums( srcdefn, dstdefn ) )
        return result;

    src_a = srcdefn.a_orig;
    src_es = srcdefn.es_orig;

    dst_a = dstdefn.a_orig;
    dst_es = dstdefn.es_orig;

/* -------------------------------------------------------------------- */
/*      Create a temporary Z array if one is not provided.              */
/* -------------------------------------------------------------------- */

    range_wrapper<Range> z_range(range);

/* -------------------------------------------------------------------- */
/*      If this datum requires grid shifts, then apply it to geodetic   */
/*      coordinates.                                                    */
/* -------------------------------------------------------------------- */
    /*if( srcdefn.datum_type == PJD_GRIDSHIFT )
    {
        try {
            pj_apply_gridshift_2( srcdefn, 0, point_count, point_offset, x, y, z );
        } catch (projection_exception const& e) {
            if (pj_datum_check_error(e.code())) {
                BOOST_RETHROW
            }
        }

        src_a = SRS_WGS84_SEMIMAJOR;
        src_es = SRS_WGS84_ESQUARED;
    }

    if( dstdefn.datum_type == PJD_GRIDSHIFT )
    {
        dst_a = SRS_WGS84_SEMIMAJOR;
        dst_es = SRS_WGS84_ESQUARED;
    }*/

/* ==================================================================== */
/*      Do we need to go through geocentric coordinates?                */
/* ==================================================================== */
    if( src_es != dst_es || src_a != dst_a
        || srcdefn.datum_type == PJD_3PARAM
        || srcdefn.datum_type == PJD_7PARAM
        || dstdefn.datum_type == PJD_3PARAM
        || dstdefn.datum_type == PJD_7PARAM)
    {
/* -------------------------------------------------------------------- */
/*      Convert to geocentric coordinates.                              */
/* -------------------------------------------------------------------- */
        int err = pj_geodetic_to_geocentric( src_a, src_es, z_range );
        if (pj_datum_check_error(err))
            BOOST_THROW_EXCEPTION( projection_exception(err) );
        else if (err != 0)
            result = false;

/* -------------------------------------------------------------------- */
/*      Convert between datums.                                         */
/* -------------------------------------------------------------------- */
        if( srcdefn.datum_type == PJD_3PARAM
            || srcdefn.datum_type == PJD_7PARAM )
        {
            try {
                pj_geocentric_to_wgs84( srcdefn, z_range );
            } catch (projection_exception const& e) {
                if (pj_datum_check_error(e.code())) {
                    BOOST_RETHROW
                }
            }
        }

        if( dstdefn.datum_type == PJD_3PARAM
            || dstdefn.datum_type == PJD_7PARAM )
        {
            try {
                pj_geocentric_from_wgs84( dstdefn, z_range );
            } catch (projection_exception const& e) {
                if (pj_datum_check_error(e.code())) {
                    BOOST_RETHROW
                }
            }
        }

/* -------------------------------------------------------------------- */
/*      Convert back to geodetic coordinates.                           */
/* -------------------------------------------------------------------- */
        err = pj_geocentric_to_geodetic( dst_a, dst_es, z_range );
        if (pj_datum_check_error(err))
            BOOST_THROW_EXCEPTION( projection_exception(err) );
        else if (err != 0)
            result = false;
    }

/* -------------------------------------------------------------------- */
/*      Apply grid shift to destination if required.                    */
/* -------------------------------------------------------------------- */
    /*if( dstdefn.datum_type == PJD_GRIDSHIFT )
    {
        try {
            pj_apply_gridshift_2( dstdefn, 1, point_count, point_offset, x, y, z );
        } catch (projection_exception const& e) {
            if (pj_datum_check_error(e.code()))
                BOOST_RETHROW
        }
    }*/

    return result;
}

} // namespace detail

}}} // namespace boost::geometry::projections

#endif // BOOST_GEOMETRY_SRS_PROJECTIONS_IMPL_PJ_TRANSFORM_HPP