#ifndef BOOST_GEOMETRY_PROJECTIONS_VANDG2_HPP
#define BOOST_GEOMETRY_PROJECTIONS_VANDG2_HPP

// Boost.Geometry - extensions-gis-projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// 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
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 4.9.1

// Original copyright notice:

// 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.

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

#include <boost/geometry/extensions/gis/projections/impl/base_static.hpp>
#include <boost/geometry/extensions/gis/projections/impl/base_dynamic.hpp>
#include <boost/geometry/extensions/gis/projections/impl/projects.hpp>
#include <boost/geometry/extensions/gis/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry { namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace vandg2
    {

            static const double TOL = 1e-10;
            static const double TWORPI = 0.63661977236758134308;

            struct par_vandg2
            {
                int    vdg3;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename Geographic, typename Cartesian, typename Parameters>
            struct base_vandg2_spheroid : public base_t_f<base_vandg2_spheroid<Geographic, Cartesian, Parameters>,
                     Geographic, Cartesian, Parameters>
            {

                 typedef double geographic_type;
                 typedef double cartesian_type;

                par_vandg2 m_proj_parm;

                inline base_vandg2_spheroid(const Parameters& par)
                    : base_t_f<base_vandg2_spheroid<Geographic, Cartesian, Parameters>,
                     Geographic, Cartesian, Parameters>(*this, par) {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    double x1, at, bt, ct;

                    bt = fabs(TWORPI * lp_lat);
                    if ((ct = 1. - bt * bt) < 0.)
                        ct = 0.;
                    else
                        ct = sqrt(ct);
                    if (fabs(lp_lon) < TOL) {
                        xy_x = 0.;
                        xy_y = geometry::math::pi<double>() * (lp_lat < 0. ? -bt : bt) / (1. + ct);
                    } else {
                        at = 0.5 * fabs(geometry::math::pi<double>() / lp_lon - lp_lon / geometry::math::pi<double>());
                        if (this->m_proj_parm.vdg3) {
                            x1 = bt / (1. + ct);
                            xy_x = geometry::math::pi<double>() * (sqrt(at * at + 1. - x1 * x1) - at);
                            xy_y = geometry::math::pi<double>() * x1;
                        } else {
                            x1 = (ct * sqrt(1. + at * at) - at * ct * ct) /
                                (1. + at * at * bt * bt);
                            xy_x = geometry::math::pi<double>() * x1;
                            xy_y = geometry::math::pi<double>() * sqrt(1. - x1 * (x1 + 2. * at) + TOL);
                        }
                        if ( lp_lon < 0.) xy_x = -xy_x;
                        if ( lp_lat < 0.) xy_y = -xy_y;
                    }
                }

                static inline std::string get_name()
                {
                    return "vandg2_spheroid";
                }

            };

            // van der Grinten II
            template <typename Parameters>
            void setup_vandg2(Parameters& par, par_vandg2& proj_parm)
            {
                proj_parm.vdg3 = 0;
            }

            // van der Grinten III
            template <typename Parameters>
            void setup_vandg3(Parameters& par, par_vandg2& proj_parm)
            {
                proj_parm.vdg3 = 1;
                par.es = 0.;
            }

        }} // namespace detail::vandg2
    #endif // doxygen

    /*!
        \brief van der Grinten II projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Miscellaneous
         - Spheroid
         - no inverse
        \par Example
        \image html ex_vandg2.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct vandg2_spheroid : public detail::vandg2::base_vandg2_spheroid<Geographic, Cartesian, Parameters>
    {
        inline vandg2_spheroid(const Parameters& par) : detail::vandg2::base_vandg2_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::vandg2::setup_vandg2(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief van der Grinten III projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Miscellaneous
         - Spheroid
         - no inverse
        \par Example
        \image html ex_vandg3.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct vandg3_spheroid : public detail::vandg2::base_vandg2_spheroid<Geographic, Cartesian, Parameters>
    {
        inline vandg3_spheroid(const Parameters& par) : detail::vandg2::base_vandg2_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::vandg2::setup_vandg3(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Factory entry(s)
        template <typename Geographic, typename Cartesian, typename Parameters>
        class vandg2_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_f<vandg2_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        class vandg3_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_f<vandg3_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        inline void vandg2_init(detail::base_factory<Geographic, Cartesian, Parameters>& factory)
        {
            factory.add_to_factory("vandg2", new vandg2_entry<Geographic, Cartesian, Parameters>);
            factory.add_to_factory("vandg3", new vandg3_entry<Geographic, Cartesian, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

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

#endif // BOOST_GEOMETRY_PROJECTIONS_VANDG2_HPP


/*
 * This file is a part of the TChecker project.
 *
 * See files AUTHORS and LICENSE for copyright details.
 *
 */

#ifndef TCHECKER_REFZG_SEMANTICS_HH
#define TCHECKER_REFZG_SEMANTICS_HH

#include <boost/dynamic_bitset.hpp>

#include "tchecker/basictypes.hh"
#include "tchecker/dbm/db.hh"
#include "tchecker/variables/clocks.hh"

/*!
 \file semantics.hh
 \brief Operational semantics on DBMs with reference clocks
 */

namespace tchecker {

namespace refzg {

/*!
 \class semantics_t
 \brief Semantics for zone graphs implemented by DBMs with reference clocks
 */
class semantics_t {
public:
  /*!
  \brief Destructor
   */
  virtual ~semantics_t() = default;

  /*!
  \brief Compute initial zone with reference clocks
  \param rdbm : a DBM
  \param r : reference clocks for rdbm
  \param delay_allowed : set of reference clocks allowed to delay
  \param invariant : invariant
  \param spread : reference clocks spread
  \pre rdbm is not nullptr (checked by assertion).
  rdbm is a r.size()*r.size() array of difference bounds.
  rdbm is tight and consistent.
  the size of delay_allowed is the number of reference clocks in r
  \post dbm is the initial zone w.r.t. delay_allowed, invariant and spread
  \return STATE_OK if the resulting DBM is not empty, other values if the
  resulting DBM is empty (see details in implementations)
   */
  virtual tchecker::state_status_t initial(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                                           boost::dynamic_bitset<> const & delay_allowed,
                                           tchecker::clock_constraint_container_t const & invariant) = 0;

  /*!
  \brief Compute next zone with reference clocks
  \param rdbm : a DBM
  \param r : reference clocks for rdbm
  \param src_delay_allowed : set of reference clocks allowed to delay in source
  state
  \param src_invariant : invariant in source state
  \param sync_ref_clocks : set of reference clocks to synchronize
  \param guard : transition guard
  \param clkreset : transition reset
  \param tgt_delay_allowed : set of reference clocks allowed to delay in target
  state
  \param tgt_invariant : invariant in target state
  \pre rdbm is not nullptr (checked by assertion).
  rdbm is a r.size()*r.size() array of difference bounds.
  rdbm is tight and consistent.
  the size of src_delay_allowed is the number of reference clocks in r.
  the size of sync_ref_clocks is the number of reference clocks in r.
  the size of tgt_delay_allowed is the number of reference clocks in r
  \post rdbm has been updated to its strongest postcondition w.r.t. src_delay_allowed,
  src_invariant, sync_ref_clocks, guard, clkreset, tgt_delay_allowed and tgt_invariant
  \return STATE_OK if the resulting DBM is not empty, other values if the resulting
  DBM is empty (see details in implementations)
   */
  virtual tchecker::state_status_t
  next(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
       boost::dynamic_bitset<> const & src_delay_allowed, tchecker::clock_constraint_container_t const & src_invariant,
       boost::dynamic_bitset<> const & sync_ref_clocks, tchecker::clock_constraint_container_t const & guard,
       tchecker::clock_reset_container_t const & clkreset, boost::dynamic_bitset<> const & tgt_delay_allowed,
       tchecker::clock_constraint_container_t const & tgt_invariant) = 0;
};

/*!
\class standard_semantics_t
\brief Standard semantics: each transition in the zone graph consists of a delay
(if allowed) followed by a transition from the timed automaton
*/
class standard_semantics_t final : public tchecker::refzg::semantics_t {
public:
  /*!
  \brief Destructor
  */
  virtual ~standard_semantics_t() = default;

  /*!
  \brief Compute initial zone with reference clocks
  \param rdbm : a DBM
  \param r : reference clocks for rdbm
  \param delay_allowed : set of reference clocks allowed to delay
  \param invariant : invariant
  \pre rdbm is not nullptr (checked by assertion).
  rdbm is a r.size()*r.size() array of difference bounds.
  rdbm is tight and consistent.
  the size of delay_allowed is the number of reference clocks in r (checked
  by assertion)
  \post rdbm is the zone that only containts the zero valuation
  \return tchecker::STATE_OK if the resulting DBM is not empty. Otherwise,
  tchecker::STATE_CLOCKS_SRC_INVARIANT_VIOLATED if the zero valuation does not
  satisfy invariant.
  */
  virtual tchecker::state_status_t initial(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                                           boost::dynamic_bitset<> const & delay_allowed,
                                           tchecker::clock_constraint_container_t const & invariant);

  /*!
  \brief Compute next zone with reference clocks
  \param rdbm : a DBM
  \param r : reference clocks for dbm
  \param src_delay_allowed : set of reference clocks allowed to delay in source
  state
  \param src_invariant : invariant in source state
  \param sync_ref_clocks : set of reference clocks to synchronize
  \param guard : transition guard
  \param clkreset : transition reset
  \param tgt_delay_allowed : set of reference clocks allowed to delay in target
  state
  \param tgt_invariant : invariant in target state
  \pre rdbm is not nullptr (checked by assertion).
  rdbm is a r.size()*r.size() array of difference bounds.
  rdbm is not tight and consistent.
  the size of src_delay_allowed is the number of reference clocks in r
  (checked by assertion).
  The size of sync_ref_clocks is the number of reference clocks in r (checked by
  assertion).)
  The size of tgt_delay_allowed is the number of reference clocks in r (checked
  by assertion).
  \post rdbm has been delayed (only reference clocks which are allowed in
  src_delay_allowed), then intersected with src_invariant, then all reference
  clocks in sync_ref_clocks have been synchronized in rdbm, then rdbm has been
  intersected with guard, then dbm has been reset w.r.t clkreset, then
  intersected with tgt_invariant
  \return tchecker::STATE_OK if the resulting DBM is not empty. Otherwise,
  tchecker::STATE_CLOCKS_SRC_INVARIANT_VIOLATED if intersection with src_invariant
  result in an empty zone, tchecker::STATE_CLOCKS_SYNC_EMPTY if synchronization
  of reference clocks in sync_ref_clocks yield an empty zone,
  tchecker::STATE_CLOCKS_GUARD_VIOLATED if intersection with guard result in an
  empty zone, tchecker::STATE_EMPTY_SYNC if synchronization of reference clocks
  result in an empty zone, tchecker::STATE_CLOCKS_TGT_INVARIANT_VIOLATED if
  intersection with tgt_invariant result in an empty zone
  */
  virtual tchecker::state_status_t
  next(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
       boost::dynamic_bitset<> const & src_delay_allowed, tchecker::clock_constraint_container_t const & src_invariant,
       boost::dynamic_bitset<> const & sync_ref_clocks, tchecker::clock_constraint_container_t const & guard,
       tchecker::clock_reset_container_t const & clkreset, boost::dynamic_bitset<> const & tgt_delay_allowed,
       tchecker::clock_constraint_container_t const & tgt_invariant);
};

/*!
\class elapsed_semantics_t
\brief elapsed semantics: each transition in the zone graph consists of a
transition from the automaton, followed by a delay (if allowed)
*/
class elapsed_semantics_t final : public tchecker::refzg::semantics_t {
public:
  /*!
  \brief Destructor
  */
  virtual ~elapsed_semantics_t() = default;

  /*!
  \brief Compute initial zone with reference clocks
  \param rdbm : a DBM
  \param r : reference clocks for rdbm
  \param delay_allowed : set of reference clocks allowed to delay
  \param invariant : invariant
  \pre rdbm is not nullptr (checked by assertion).
  rdbm is a r.size()*r.size() array of difference bounds.
  rdbm is not tight and consistent.
  the size of delay_allowed is the number of reference clocks in r (checked
  by assertion))
  \post rdbm is the zone that contains all the time successors of the zero
  valuation (for reference clocks that are allowed to delay in delay_allowed),
  and that satisfy invariant
  \return tchecker::STATE_OK if the resulting DBM is not empty. Otherwise,
  tchecker::STATE_CLOCKS_SRC_INVARIANT_VIOLATED if the (time successors of the)
  zero valuation does not satisfy invariant.
  */
  virtual tchecker::state_status_t initial(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                                           boost::dynamic_bitset<> const & delay_allowed,
                                           tchecker::clock_constraint_container_t const & invariant);

  /*!
  \brief Compute next zone with reference clocks
  \param rdbm : a DBM
  \param r : reference clocks for rdbm
  \param src_delay_allowed : set of reference clocks allowed to delay in source
  state
  \param src_invariant : invariant in source state
  \param sync_ref_clocks : set of reference clocks to synchronize
  \param guard : transition guard
  \param clkreset : transition reset
  \param tgt_delay_allowed : set of reference clocks allowed to delay in target
  state
  \param tgt_invariant : invariant in target state
  \pre rdbm is not nullptr (checked by assertion).
  rdbm is a r.size()*r.size() array of difference bounds.
  rdbm is not tight and consistent.
  the size of src_delay_allowed is the number of reference clocks in r
  (checked by assertion).
  The size of sync_ref_clocks is the number of reference clocks in r (checked by
  assertion).)
  The size of tgt_delay_allowed is the number of reference clocks in r (checked
  by assertion).
  \post rdbm has been intersected with src_invariant, then all reference clocks
  in sync_ref_clocks have been synchronized in rdbm, then rdbm has been
  intersected with guard, then rdbm has been reset w.r.t clkreset, then
  intersected with tgt_invariant, then rdbm has been delayed (only reference
  clocks that are allowed in tgt_delay_allowed) and intersected wth
  tgt_invariant again
  \return tchecker::STATE_OK if the resulting DBM is not empty. Otherwise,
  tchecker::STATE_CLOCKS_SRC_INVARIANT_VIOLATED if intersection with src_invariant
  result in an empty zone, tchecker::STATE_CLOCKS_EMPTY_SYNC if synchronization
  of reference clocks in sync_ref_clocks yields an empty zone,
  tchecker::STATE_CLOCKS_GUARD_VIOLATED if intersection with guard result in an
  empty zone, tchecker::STATE_EMPTY_SYNC if synchronization of reference clocks
  result in an empty zone, tchecker::STATE_CLOCKS_TGT_INVARIANT_VIOLATED if
  intersection with tgt_invariant result in an empty zone
  */
  virtual tchecker::state_status_t
  next(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
       boost::dynamic_bitset<> const & src_delay_allowed, tchecker::clock_constraint_container_t const & src_invariant,
       boost::dynamic_bitset<> const & sync_ref_clocks, tchecker::clock_constraint_container_t const & guard,
       tchecker::clock_reset_container_t const & clkreset, boost::dynamic_bitset<> const & tgt_delay_allowed,
       tchecker::clock_constraint_container_t const & tgt_invariant);
};

/*!
 \brief type of semantics
*/
enum semantics_type_t {
  STANDARD_SEMANTICS, /*!< see tchecker::refzg::standard_semantics_t */
  ELAPSED_SEMANTICS,  /*!< see tchecker::refzg::elapsed_semantics_t */
};

/*!
 \brief Zone semantics factory
 \param semantics_type : type zone graph semantics
 \return A zone semantics over DBMs with reference clocks of type semantics
 \note the returned semantics must be deallocated by the caller
 \throw std::invalid_argument : if semantics is unknown
*/
tchecker::refzg::semantics_t * semantics_factory(enum tchecker::refzg::semantics_type_t semantics_type);

} // end of namespace refzg

} // end of namespace tchecker

#endif // TCHECKER_REFZG_SEMANTICS_HH


// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// 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
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// Original copyright notice:

// 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_PROJECTIONS_IGH_HPP
#define BOOST_GEOMETRY_PROJECTIONS_IGH_HPP

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

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/proj/gn_sinu.hpp>
#include <boost/geometry/srs/projections/proj/moll.hpp>

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace igh
    {
            // TODO: consider replacing dynamically created projections
            // with member objects
            template <typename T, typename Parameters>
            struct par_igh
            {
                boost::shared_ptr<base_v<T, Parameters> > pj[12];
                T dy0;
            };

            /* 40d 44' 11.8" [degrees] */
            template <typename T>
            inline T d4044118() { return (T(40) + T(44)/T(60.) + T(11.8)/T(3600.)) * geometry::math::d2r<T>(); }

            template <typename T>
            inline T d10() { return T(10) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d20() { return T(20) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d30() { return T(30) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d40() { return T(40) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d50() { return T(50) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d60() { return T(60) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d80() { return T(80) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d90() { return T(90) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d100() { return T(100) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d140() { return T(140) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d160() { return T(160) * geometry::math::d2r<T>(); }
            template <typename T>
            inline T d180() { return T(180) * geometry::math::d2r<T>(); }

            static const double epsilon = 1.e-10; // allow a little 'slack' on zone edge positions

            // Converted from #define SETUP(n, proj, x_0, y_0, lon_0)
            template <template <typename, typename, typename> class Entry, typename Params, typename Parameters, typename T>
            inline void do_setup(int n, Params const& params, Parameters const& par, par_igh<T, Parameters>& proj_parm,
                                 T const& x_0, T const& y_0,
                                 T const& lon_0)
            {
                // NOTE: in the original proj4 these projections are initialized
                // with zeroed parameters which could be done here as well instead
                // of initializing with parent projection's parameters.
                Entry<Params, T, Parameters> entry;
                proj_parm.pj[n-1].reset(entry.create_new(params, par));
                proj_parm.pj[n-1]->mutable_params().x0 = x_0;
                proj_parm.pj[n-1]->mutable_params().y0 = y_0;
                proj_parm.pj[n-1]->mutable_params().lam0 = lon_0;
            }

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_igh_spheroid
                : public base_t_fi<base_igh_spheroid<T, Parameters>, T, Parameters>
            {
                par_igh<T, Parameters> m_proj_parm;

                inline base_igh_spheroid(const Parameters& par)
                    : base_t_fi<base_igh_spheroid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    static const T d4044118 = igh::d4044118<T>();
                    static const T d20  =  igh::d20<T>();
                    static const T d40  =  igh::d40<T>();
                    static const T d80  =  igh::d80<T>();
                    static const T d100 = igh::d100<T>();

                        int z;
                        if (lp_lat >=  d4044118) {          // 1|2
                          z = (lp_lon <= -d40 ? 1: 2);
                        }
                        else if (lp_lat >=  0) {            // 3|4
                          z = (lp_lon <= -d40 ? 3: 4);
                        }
                        else if (lp_lat >= -d4044118) {     // 5|6|7|8
                               if (lp_lon <= -d100) z =  5; // 5
                          else if (lp_lon <=  -d20) z =  6; // 6
                          else if (lp_lon <=   d80) z =  7; // 7
                          else z = 8;                       // 8
                        }
                        else {                              // 9|10|11|12
                               if (lp_lon <= -d100) z =  9; // 9
                          else if (lp_lon <=  -d20) z = 10; // 10
                          else if (lp_lon <=   d80) z = 11; // 11
                          else z = 12;                      // 12
                        }

                        lp_lon -= this->m_proj_parm.pj[z-1]->params().lam0;
                        this->m_proj_parm.pj[z-1]->fwd(lp_lon, lp_lat, xy_x, xy_y);
                        xy_x += this->m_proj_parm.pj[z-1]->params().x0;
                        xy_y += this->m_proj_parm.pj[z-1]->params().y0;
                }

                // INVERSE(s_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T xy_x, T xy_y, T& lp_lon, T& lp_lat) const
                {
                    static const T d4044118 = igh::d4044118<T>();
                    static const T d10  =  igh::d10<T>();
                    static const T d20  =  igh::d20<T>();
                    static const T d40  =  igh::d40<T>();
                    static const T d50  =  igh::d50<T>();
                    static const T d60  =  igh::d60<T>();
                    static const T d80  =  igh::d80<T>();
                    static const T d90  =  igh::d90<T>();
                    static const T d100 = igh::d100<T>();
                    static const T d160 = igh::d160<T>();
                    static const T d180 = igh::d180<T>();

                    static const T c2 = 2.0;

                    const T y90 = this->m_proj_parm.dy0 + sqrt(c2); // lt=90 corresponds to y=y0+sqrt(2.0)

                        int z = 0;
                        if (xy_y > y90+epsilon || xy_y < -y90+epsilon) // 0
                          z = 0;
                        else if (xy_y >=  d4044118)       // 1|2
                          z = (xy_x <= -d40? 1: 2);
                        else if (xy_y >=  0)              // 3|4
                          z = (xy_x <= -d40? 3: 4);
                        else if (xy_y >= -d4044118) {     // 5|6|7|8
                               if (xy_x <= -d100) z =  5; // 5
                          else if (xy_x <=  -d20) z =  6; // 6
                          else if (xy_x <=   d80) z =  7; // 7
                          else z = 8;                     // 8
                        }
                        else {                            // 9|10|11|12
                               if (xy_x <= -d100) z =  9; // 9
                          else if (xy_x <=  -d20) z = 10; // 10
                          else if (xy_x <=   d80) z = 11; // 11
                          else z = 12;                    // 12
                        }

                        if (z)
                        {
                          int ok = 0;

                          xy_x -= this->m_proj_parm.pj[z-1]->params().x0;
                          xy_y -= this->m_proj_parm.pj[z-1]->params().y0;
                          this->m_proj_parm.pj[z-1]->inv(xy_x, xy_y, lp_lon, lp_lat);
                          lp_lon += this->m_proj_parm.pj[z-1]->params().lam0;

                          switch (z) {
                            case  1: ok = (lp_lon >= -d180-epsilon && lp_lon <=  -d40+epsilon) ||
                                         ((lp_lon >=  -d40-epsilon && lp_lon <=  -d10+epsilon) &&
                                          (lp_lat >=   d60-epsilon && lp_lat <=   d90+epsilon)); break;
                            case  2: ok = (lp_lon >=  -d40-epsilon && lp_lon <=  d180+epsilon) ||
                                         ((lp_lon >= -d180-epsilon && lp_lon <= -d160+epsilon) &&
                                          (lp_lat >=   d50-epsilon && lp_lat <=   d90+epsilon)) ||
                                         ((lp_lon >=  -d50-epsilon && lp_lon <=  -d40+epsilon) &&
                                          (lp_lat >=   d60-epsilon && lp_lat <=   d90+epsilon)); break;
                            case  3: ok = (lp_lon >= -d180-epsilon && lp_lon <=  -d40+epsilon); break;
                            case  4: ok = (lp_lon >=  -d40-epsilon && lp_lon <=  d180+epsilon); break;
                            case  5: ok = (lp_lon >= -d180-epsilon && lp_lon <= -d100+epsilon); break;
                            case  6: ok = (lp_lon >= -d100-epsilon && lp_lon <=  -d20+epsilon); break;
                            case  7: ok = (lp_lon >=  -d20-epsilon && lp_lon <=   d80+epsilon); break;
                            case  8: ok = (lp_lon >=   d80-epsilon && lp_lon <=  d180+epsilon); break;
                            case  9: ok = (lp_lon >= -d180-epsilon && lp_lon <= -d100+epsilon); break;
                            case 10: ok = (lp_lon >= -d100-epsilon && lp_lon <=  -d20+epsilon); break;
                            case 11: ok = (lp_lon >=  -d20-epsilon && lp_lon <=   d80+epsilon); break;
                            case 12: ok = (lp_lon >=   d80-epsilon && lp_lon <=  d180+epsilon); break;
                          }

                          z = (!ok? 0: z); // projectable?
                        }
                     // if (!z) pj_errno = -15; // invalid x or y
                        if (!z) lp_lon = HUGE_VAL;
                        if (!z) lp_lat = HUGE_VAL;
                }

                static inline std::string get_name()
                {
                    return "igh_spheroid";
                }

            };

            // Interrupted Goode Homolosine
            template <typename Params, typename Parameters, typename T>
            inline void setup_igh(Params const& params, Parameters& par, par_igh<T, Parameters>& proj_parm)
            {
                static const T d0   =  0;
                static const T d4044118 = igh::d4044118<T>();
                static const T d20  =  igh::d20<T>();
                static const T d30  =  igh::d30<T>();
                static const T d60  =  igh::d60<T>();
                static const T d100 = igh::d100<T>();
                static const T d140 = igh::d140<T>();
                static const T d160 = igh::d160<T>();

            /*
              Zones:

                -180            -40                       180
                  +--------------+-------------------------+    Zones 1,2,9,10,11 & 12:
                  |1             |2                        |      Mollweide projection
                  |              |                         |
                  +--------------+-------------------------+    Zones 3,4,5,6,7 & 8:
                  |3             |4                        |      Sinusoidal projection
                  |              |                         |
                0 +-------+------+-+-----------+-----------+
                  |5      |6       |7          |8          |
                  |       |        |           |           |
                  +-------+--------+-----------+-----------+
                  |9      |10      |11         |12         |
                  |       |        |           |           |
                  +-------+--------+-----------+-----------+
                -180    -100      -20         80          180
            */

                    T lp_lam = 0, lp_phi = d4044118;
                    T xy1_x, xy1_y;
                    T xy3_x, xy3_y;

                    // IMPORTANT: Force spherical sinu projection
                    // This is required because unlike in the original proj4 here
                    // parameters are used to initialize underlying projections.
                    // In the original code zeroed parameters are passed which
                    // could be done here as well though.
                    par.es = 0.;

                    // sinusoidal zones
                    do_setup<sinu_entry>(3, params, par, proj_parm, -d100, d0, -d100);
                    do_setup<sinu_entry>(4, params, par, proj_parm,   d30, d0,   d30);
                    do_setup<sinu_entry>(5, params, par, proj_parm, -d160, d0, -d160);
                    do_setup<sinu_entry>(6, params, par, proj_parm,  -d60, d0,  -d60);
                    do_setup<sinu_entry>(7, params, par, proj_parm,   d20, d0,   d20);
                    do_setup<sinu_entry>(8, params, par, proj_parm,  d140, d0,  d140);

                    // mollweide zones
                    do_setup<moll_entry>(1, params, par, proj_parm, -d100, d0, -d100);

                    // y0 ?
                     proj_parm.pj[0]->fwd(lp_lam, lp_phi, xy1_x, xy1_y); // zone 1
                     proj_parm.pj[2]->fwd(lp_lam, lp_phi, xy3_x, xy3_y); // zone 3
                    // y0 + xy1_y = xy3_y for lt = 40d44'11.8"
                    proj_parm.dy0 = xy3_y - xy1_y;

                    proj_parm.pj[0]->mutable_params().y0 = proj_parm.dy0;

                    // mollweide zones (cont'd)
                    do_setup<moll_entry>( 2, params, par, proj_parm,   d30,  proj_parm.dy0,   d30);
                    do_setup<moll_entry>( 9, params, par, proj_parm, -d160, -proj_parm.dy0, -d160);
                    do_setup<moll_entry>(10, params, par, proj_parm,  -d60, -proj_parm.dy0,  -d60);
                    do_setup<moll_entry>(11, params, par, proj_parm,   d20, -proj_parm.dy0,   d20);
                    do_setup<moll_entry>(12, params, par, proj_parm,  d140, -proj_parm.dy0,  d140);

                    // Already done before
                    //par.es = 0.;
            }

    }} // namespace detail::igh
    #endif // doxygen

    /*!
        \brief Interrupted Goode Homolosine projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Pseudocylindrical
         - Spheroid
        \par Example
        \image html ex_igh.gif
    */
    template <typename T, typename Parameters>
    struct igh_spheroid : public detail::igh::base_igh_spheroid<T, Parameters>
    {
        template <typename Params>
        inline igh_spheroid(Params const& params, Parameters const& par)
            : detail::igh::base_igh_spheroid<T, Parameters>(par)
        {
            detail::igh::setup_igh(params, this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_igh, igh_spheroid, igh_spheroid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(igh_entry, igh_spheroid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(igh_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(igh, igh_entry)
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_IGH_HPP


#ifndef BOOST_GEOMETRY_PROJECTIONS_LCC_HPP
#define BOOST_GEOMETRY_PROJECTIONS_LCC_HPP

// Boost.Geometry - extensions-gis-projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017.
// Modifications copyright (c) 2017, 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
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 4.9.1

// Original copyright notice:

// 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.

#include <boost/geometry/util/math.hpp>
#include <boost/math/special_functions/hypot.hpp>

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/pj_msfn.hpp>
#include <boost/geometry/srs/projections/impl/pj_phi2.hpp>
#include <boost/geometry/srs/projections/impl/pj_tsfn.hpp>

namespace boost { namespace geometry
{

namespace srs { namespace par4
{
    struct lcc {};

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace lcc
    {
            static const double EPS10 = 1.e-10;

            template <typename T>
            struct par_lcc
            {
                T   phi1;
                T   phi2;
                T   n;
                T   rho0;
                T   c;
                int ellips;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename CalculationType, typename Parameters>
            struct base_lcc_ellipsoid : public base_t_fi<base_lcc_ellipsoid<CalculationType, Parameters>,
                     CalculationType, Parameters>
            {

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_lcc<CalculationType> m_proj_parm;

                inline base_lcc_ellipsoid(const Parameters& par)
                    : base_t_fi<base_lcc_ellipsoid<CalculationType, Parameters>,
                     CalculationType, Parameters>(*this, par) {}

                // FORWARD(e_forward)  ellipsoid & spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    static const CalculationType FORTPI = detail::FORTPI<CalculationType>();
                    static const CalculationType HALFPI = detail::HALFPI<CalculationType>();

                    CalculationType rho;
                    if (fabs(fabs(lp_lat) - HALFPI) < EPS10) {
                        if ((lp_lat * this->m_proj_parm.n) <= 0.)
                            BOOST_THROW_EXCEPTION( projection_exception(-20) );
                        rho = 0.;
                    } else
                        rho = this->m_proj_parm.c * (this->m_proj_parm.ellips ? pow(pj_tsfn(lp_lat, sin(lp_lat),
                            this->m_par.e), this->m_proj_parm.n) : pow(tan(FORTPI + .5 * lp_lat), -this->m_proj_parm.n));
                    xy_x = this->m_par.k0 * (rho * sin( lp_lon *= this->m_proj_parm.n ) );
                    xy_y = this->m_par.k0 * (this->m_proj_parm.rho0 - rho * cos(lp_lon) );
                }

                // INVERSE(e_inverse)  ellipsoid & spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    static const CalculationType HALFPI = detail::HALFPI<CalculationType>();

                    CalculationType rho;
                    xy_x /= this->m_par.k0;
                    xy_y /= this->m_par.k0;
                    if( (rho = boost::math::hypot(xy_x, xy_y = this->m_proj_parm.rho0 - xy_y)) != 0.0) {
                        if (this->m_proj_parm.n < 0.) {
                            rho = -rho;
                            xy_x = -xy_x;
                            xy_y = -xy_y;
                        }
                        if (this->m_proj_parm.ellips) {
                            if ((lp_lat = pj_phi2(pow(rho / this->m_proj_parm.c, 1./this->m_proj_parm.n), this->m_par.e))
                                == HUGE_VAL)
                                BOOST_THROW_EXCEPTION( projection_exception(-20) );
                        } else
                            lp_lat = 2. * atan(pow(this->m_proj_parm.c / rho, 1./this->m_proj_parm.n)) - HALFPI;
                        lp_lon = atan2(xy_x, xy_y) / this->m_proj_parm.n;
                    } else {
                        lp_lon = 0.;
                        lp_lat = this->m_proj_parm.n > 0. ? HALFPI : -HALFPI;
                    }
                }

                // SPECIAL(fac)
                #ifdef SPECIAL_FACTORS_NOT_CONVERTED
                inline void fac(Geographic lp, Factors &fac) const
                {
                    static const CalculationType FORTPI = detail::FORTPI<CalculationType>();
                    static const CalculationType HALFPI = detail::HALFPI<CalculationType>();

                    CalculationType rho;
                    if (fabs(fabs(lp_lat) - HALFPI) < EPS10) {
                        if ((lp_lat * this->m_proj_parm.n) <= 0.) return;
                        rho = 0.;
                    } else
                        rho = this->m_proj_parm.c * (this->m_proj_parm.ellips ? pow(pj_tsfn(lp_lat, sin(lp_lat),
                            this->m_par.e), this->m_proj_parm.n) : pow(tan(FORTPI + .5 * lp_lat), -this->m_proj_parm.n));
                    this->m_fac.code |= IS_ANAL_HK + IS_ANAL_CONV;
                    this->m_fac.k = this->m_fac.h = this->m_par.k0 * this->m_proj_parm.n * rho /
                        pj_msfn(sin(lp_lat), cos(lp_lat), this->m_par.es);
                    this->m_fac.conv = - this->m_proj_parm.n * lp_lon;
                }
                #endif

                static inline std::string get_name()
                {
                    return "lcc_ellipsoid";
                }

            };

            // Lambert Conformal Conic
            template <typename Parameters, typename T>
            inline void setup_lcc(Parameters& par, par_lcc<T>& proj_parm)
            {
                static const T FORTPI = detail::FORTPI<T>();
                static const T HALFPI = detail::HALFPI<T>();

                T cosphi, sinphi;
                int secant;

                proj_parm.phi1 = pj_param(par.params, "rlat_1").f;
                if (pj_param(par.params, "tlat_2").i)
                    proj_parm.phi2 = pj_param(par.params, "rlat_2").f;
                else {
                    proj_parm.phi2 = proj_parm.phi1;
                    if (!pj_param(par.params, "tlat_0").i)
                        par.phi0 = proj_parm.phi1;
                }
                if (fabs(proj_parm.phi1 + proj_parm.phi2) < EPS10)
                    BOOST_THROW_EXCEPTION( projection_exception(-21) );
                proj_parm.n = sinphi = sin(proj_parm.phi1);
                cosphi = cos(proj_parm.phi1);
                secant = fabs(proj_parm.phi1 - proj_parm.phi2) >= EPS10;
                if( (proj_parm.ellips = (par.es != 0.)) ) {
                    double ml1, m1;

                    par.e = sqrt(par.es);
                    m1 = pj_msfn(sinphi, cosphi, par.es);
                    ml1 = pj_tsfn(proj_parm.phi1, sinphi, par.e);
                    if (secant) { /* secant cone */
                        proj_parm.n = log(m1 /
                           pj_msfn(sinphi = sin(proj_parm.phi2), cos(proj_parm.phi2), par.es));
                        proj_parm.n /= log(ml1 / pj_tsfn(proj_parm.phi2, sinphi, par.e));
                    }
                    proj_parm.c = (proj_parm.rho0 = m1 * pow(ml1, -proj_parm.n) / proj_parm.n);
                    proj_parm.rho0 *= (fabs(fabs(par.phi0) - HALFPI) < EPS10) ? 0. :
                        pow(pj_tsfn(par.phi0, sin(par.phi0), par.e), proj_parm.n);
                } else {
                    if (secant)
                        proj_parm.n = log(cosphi / cos(proj_parm.phi2)) /
                           log(tan(FORTPI + .5 * proj_parm.phi2) /
                           tan(FORTPI + .5 * proj_parm.phi1));
                    proj_parm.c = cosphi * pow(tan(FORTPI + .5 * proj_parm.phi1), proj_parm.n) / proj_parm.n;
                    proj_parm.rho0 = (fabs(fabs(par.phi0) - HALFPI) < EPS10) ? 0. :
                        proj_parm.c * pow(tan(FORTPI + .5 * par.phi0), -proj_parm.n);
                }
            }

    }} // namespace detail::lcc
    #endif // doxygen

    /*!
        \brief Lambert Conformal Conic projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
         - Ellipsoid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel (degrees)
         - lat_2: Latitude of second standard parallel (degrees)
         - lat_0: Latitude of origin
        \par Example
        \image html ex_lcc.gif
    */
    template <typename CalculationType, typename Parameters>
    struct lcc_ellipsoid : public detail::lcc::base_lcc_ellipsoid<CalculationType, Parameters>
    {
        inline lcc_ellipsoid(const Parameters& par) : detail::lcc::base_lcc_ellipsoid<CalculationType, Parameters>(par)
        {
            detail::lcc::setup_lcc(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::lcc, lcc_ellipsoid, lcc_ellipsoid)

        // Factory entry(s)
        template <typename CalculationType, typename Parameters>
        class lcc_entry : public detail::factory_entry<CalculationType, Parameters>
        {
            public :
                virtual base_v<CalculationType, Parameters>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<lcc_ellipsoid<CalculationType, Parameters>, CalculationType, Parameters>(par);
                }
        };

        template <typename CalculationType, typename Parameters>
        inline void lcc_init(detail::base_factory<CalculationType, Parameters>& factory)
        {
            factory.add_to_factory("lcc", new lcc_entry<CalculationType, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_LCC_HPP


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Nikita Kaskov <nbering@nil.foundation>
//
// MIT License
//
// 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.
//---------------------------------------------------------------------------//

#include <boost/program_options.hpp>

#include <nil/crypto3/zk/snark/default_types/ram_zksnark_pp.hpp>
#include <nil/crypto3/zk/snark/relations/ram_computations/memory/examples/memory_contents_examples.hpp>
#include <nil/crypto3/zk/snark/relations/ram_computations/rams/examples/ram_examples.hpp>
#include <nil/crypto3/zk/snark/relations/ram_computations/rams/tinyram/tinyram_params.hpp>
#include <nil/crypto3/zk/snark/schemes/zksnark/ram_zksnark/examples/run_ram_zksnark.hpp>
#include <nil/crypto3/zk/snark/schemes/zksnark/ram_zksnark/ram_zksnark.hpp>

using namespace nil::crypto3::zk::snark;

template<typename FieldType>
void simulate_random_memory_contents(const tinyram_architecture_params &ap, const std::size_t input_size,
                                     const std::size_t program_size) {
    const std::size_t num_addresses = 1ul << ap.dwaddr_len();
    const std::size_t value_size = 2 * ap.w;
    memory_contents init_random =
        random_memory_contents(num_addresses, value_size, program_size + (input_size + 1) / 2);

    std::cout << "Initialize random delegated memory" << std::endl;
    delegated_ra_memory<FieldType> dm_random(num_addresses, value_size, init_random);
}

template<typename CurveType>
void profile_ram_zksnark_verifier(const tinyram_architecture_params &ap, const std::size_t input_size,
                                  const std::size_t program_size) {
    typedef ram_zksnark_machine_pp<CurveType> RAMType;
    const std::size_t time_bound = 10;

    const std::size_t boot_trace_size_bound = program_size + input_size;
    const ram_example<RAMType> example = gen_ram_example_complex<RAMType>(ap, boot_trace_size_bound, time_bound, true);

    ram_zksnark_proof<CurveType> pi;
    ram_zksnark_verification_key<CurveType> vk = ram_zksnark_verification_key<CurveType>::dummy_verification_key(ap);

    std::cout << "Verify fake proof" << std::endl;
    ram_zksnark_verifier<CurveType>(vk, example.boot_trace, time_bound, pi);
}

template<typename CurveType>
void print_ram_zksnark_verifier_profiling() {
    algebra::inhibit_profiling_info = true;
    for (std::size_t w : {16, 32}) {
        const std::size_t k = 16;

        for (std::size_t input_size : {0, 10, 100}) {
            for (std::size_t program_size = 10; program_size <= 10000; program_size *= 10) {
                const tinyram_architecture_params ap(w, k);

                profile_ram_zksnark_verifier<CurveType>(ap, input_size, program_size);

                const double input_map = algebra::last_times["Call to ram_zksnark_verifier_input_map"];
                const double preprocessing = algebra::last_times["Call to r1cs_ppzksnark_process_verification_key"];
                const double accumulate = algebra::last_times["Call to r1cs_ppzksnark_IC_query::accumulate"];
                const double pairings = algebra::last_times["Online pairing computations"];
                const double total = algebra::last_times["Call to ram_zksnark_verifier"];
                const double rest = total - (input_map + preprocessing + accumulate + pairings);

                const double delegated_ra_memory_init =
                    algebra::last_times["Construct delegated_ra_memory from memory map"];
                simulate_random_memory_contents<algebra::Fr<typename CurveType::curve_A_pp>>(ap, input_size,
                                                                                program_size);
                const double delegated_ra_memory_init_random =
                    algebra::last_times["Initialize random delegated memory"];
                const double input_map_random = input_map - delegated_ra_memory_init + delegated_ra_memory_init_random;
                const double total_random = total - delegated_ra_memory_init + delegated_ra_memory_init_random;

                printf(
                    "w = %zu, k = %zu, program_size = %zu, input_size = %zu, input_map = %0.2fms, preprocessing = "
                    "%0.2fms, accumulate = %0.2fms, pairings = %0.2fms, rest = %0.2fms, total = %0.2fms "
                    "(input_map_random = %0.2fms, total_random = %0.2fms)\n",
                    w, k, program_size, input_size, input_map * 1e-6, preprocessing * 1e-6, accumulate * 1e-6,
                    pairings * 1e-6, rest * 1e-6, total * 1e-6, input_map_random * 1e-6, total_random * 1e-6);
            }
        }
    }
}

template<typename CurveType>
void profile_ram_zksnark(const tinyram_architecture_params &ap, const std::size_t program_size,
                         const std::size_t input_size, const std::size_t time_bound) {
    typedef ram_zksnark_machine_pp<CurveType> RAMType;

    const std::size_t boot_trace_size_bound = program_size + input_size;
    const ram_example<RAMType> example = gen_ram_example_complex<RAMType>(ap, boot_trace_size_bound, time_bound, true);
    const bool bit = run_ram_zksnark<CurveType>(example);
    assert(bit);
}

namespace po = boost::program_options;

bool process_command_line(const int argc, const char **argv, bool &profile_gp, std::size_t &w, std::size_t &k,
                          bool &profile_v, std::size_t &l) {
    try {
        po::options_description desc("Usage");
        desc.add_options()("help", "print this help message")("profile_gp", "profile generator and prover")(
            "w", po::value<std::size_t>(&w)->default_value(16), "word size")(
            "k", po::value<std::size_t>(&k)->default_value(16), "register count")("profile_v", "profile verifier")(
            "v", "print version info")("l", po::value<std::size_t>(&l)->default_value(10), "program length");

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

        if (vm.count("v")) {
            algebra::print_compilation_info();
            exit(0);
        }

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

        profile_gp = vm.count("profile_gp");
        profile_v = vm.count("profile_v");

        if (!(vm.count("profile_gp") ^ vm.count("profile_v"))) {
            std::cout << "Must choose between profiling generator/prover and profiling verifier (see --help)\n";
            return false;
        }

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

    return true;
}

int main(int argc, const char *argv[]) {
    bool profile_gp;
    std::size_t w;
    std::size_t k;
    bool profile_v;
    std::size_t l;

    if (!process_command_line(argc, argv, profile_gp, w, k, profile_v, l)) {
        return 1;
    }

    tinyram_architecture_params ap(w, k);

    if (profile_gp) {
        profile_ram_zksnark<default_ram_zksnark_pp>(ap, 100, 100, 10);    // w, k, l, n, T
    }

    if (profile_v) {
        profile_ram_zksnark_verifier<default_ram_zksnark_pp>(ap, l / 2, l / 2);
    }
}


//---------------------------------------------------------------------------//
//!
//! \file   MonteCarlo_StandardCompleteDopplerBroadenedPhotonEnergyDistribution_def.hpp
//! \author Alex Robinson
//! \brief  The complete Doppler broadened photon energy distribution def.
//!
//---------------------------------------------------------------------------//

#ifndef MONTE_CARLO_STANDARD_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_DEF_HPP
#define MONTE_CARLO_STANDARD_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_DEF_HPP

// Boost Includes
#include <boost/function.hpp>
#include <boost/bind.hpp>

// FRENSIE Includes
#include "MonteCarlo_PhotonKinematicsHelpers.hpp"
#include "Utility_DiscreteDistribution.hpp"
#include "Utility_GaussKronrodIntegrator.hpp"
#include "Utility_RandomNumberGenerator.hpp"
#include "Utility_ExplicitTemplateInstantiationMacros.hpp"
#include "Utility_DesignByContract.hpp"

namespace MonteCarlo{

// Constructor
template<typename ComptonProfilePolicy>
StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::StandardCompleteDopplerBroadenedPhotonEnergyDistribution(
		const std::vector<double>& endf_subshell_occupancies,
                const std::vector<Data::SubshellType>& endf_subshell_order,
                const std::shared_ptr<const ComptonProfileSubshellConverter>&
                subshell_converter,
                const ComptonProfileArray& electron_momentum_dist_array )
  : d_endf_subshell_occupancy_distribution(),
    d_endf_subshell_order(),
    d_endf_subshell_occupancies( endf_subshell_occupancies ),
    d_subshell_converter( subshell_converter ),
    d_compton_profile_array( electron_momentum_dist_array )
{
  // Make sure the shell interaction data is valid
  testPrecondition( endf_subshell_occupancies.size() > 0 );
  testPrecondition( endf_subshell_order.size() ==
		    endf_subshell_occupancies.size() );
  // Make sure that the subshell converter is valid
  testPrecondition( subshell_converter.get() );
  // Make sure the comptron profile array is valid
  testPrecondition( electron_momentum_dist_array.size() > 0 );
  testPrecondition( ComptonProfilePolicy::isValidProfile( *electron_momentum_dist_array.front() ) );
  testPrecondition( ComptonProfilePolicy::isValidProfile( *electron_momentum_dist_array.back() ) );

  // Create the ENDF subshell interaction distribution
  std::vector<double> dummy_indep_vals( endf_subshell_occupancies.size() );

  d_endf_subshell_occupancy_distribution.reset(
	      new Utility::DiscreteDistribution( dummy_indep_vals,
						 endf_subshell_occupancies ) );

  // Create the endf subshell order bimap
  for( unsigned i = 0; i < endf_subshell_order.size(); ++i )
  {
    d_endf_subshell_order.insert( SubshellOrderMapType::value_type(
                                                 i, endf_subshell_order[i] ) );
  }
}

// Evaluate the distribution with the electron momentum projection
/*! \details The electron momentum projection must be in me*c units
 * (a momentum value of me*c kg*m/s is 1.0 in me*c units). The distribution
 * will have units of barns since the unitless momentum is being used.
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateWithElectronMomentumProjection(
                                   const double incoming_energy,
                                   const double electron_momentum_projection,
                                   const double scattering_angle_cosine ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the electron momentum projection is valid
  testPrecondition( electron_momentum_projection >= -1.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  // The total double differential cross section
  double cross_section = 0.0;

  // Evaluate each subshell
  SubshellOrderMapType::const_iterator subshell_it =
    d_endf_subshell_order.begin();

  while( subshell_it != d_endf_subshell_order.end() )
  {
    cross_section += this->evaluateSubshellWithElectronMomentumProjection(
                                                  incoming_energy,
                                                  electron_momentum_projection,
                                                  scattering_angle_cosine,
                                                  subshell_it->right );

    ++subshell_it;
  }

  // Make sure the cross section is valid
  testPrecondition( cross_section >= 0.0 );

  return cross_section;
}

// Evaluate the exact distribution
/*! \details The distribution has units of barns/MeV.
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateExact(
				   const double incoming_energy,
				   const double outgoing_energy,
				   const double scattering_angle_cosine ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the outgoing energy is valid
  testPrecondition( outgoing_energy <= incoming_energy );
  testPrecondition( outgoing_energy >= 0.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  // The total double differential cross section
  double cross_section = 0.0;

  // Evaluate each subshell
  SubshellOrderMapType::const_iterator subshell_it =
    d_endf_subshell_order.begin();

  while( subshell_it != d_endf_subshell_order.end() )
  {
    cross_section += this->evaluateSubshellExact( incoming_energy,
                                                  outgoing_energy,
                                                  scattering_angle_cosine,
                                                  subshell_it->right );

    ++subshell_it;
  }

  // Make sure the cross section is valid
  testPostcondition( cross_section >= 0.0 );

  return cross_section;
}

// Evaluate the subshell distribution with the electron momentum projection
/*! \details The electron momentum projection must be in me*c units
 * (a momentum value of me*c kg*m/s is 1.0 in me*c units). The distribution
 * will have units of barns since the unitless momentum is being used.
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateSubshellWithElectronMomentumProjection(
                                                                                const double incoming_energy,
                                     const double electron_momentum_projection,
                                     const double scattering_angle_cosine,
                                     const Data::SubshellType subshell ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the electron momentum projection is valid
  testPrecondition( electron_momentum_projection >= -1.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );
  // Make sure the subshell is valid
  testPrecondition( this->isValidSubshell( subshell ) );

  // Get the subshell binding energy
  const double subshell_binding_energy =
    this->getSubshellBindingEnergy( subshell );

  // Calculate the max electron momentum projection
  ComptonProfile::MomentumQuantity max_electron_momentum_projection =
    calculateMaxElectronMomentumProjection( incoming_energy,
                                            subshell_binding_energy,
                                            scattering_angle_cosine )*
    ComptonProfile::MomentumUnit();

  // Get the subshell occupancy
  const double subshell_occupancy =
    this->getSubshellOccupancy( subshell );

  // Get the Compton profile for the subshell
  const ComptonProfile& compton_profile =
    this->getComptonProfile( subshell );

  // Evaluate the Compton profile
  ComptonProfile::ProfileQuantity compton_profile_quantity =
    ComptonProfilePolicy::evaluateWithPossibleLimit(
                   compton_profile,
                   electron_momentum_projection*ComptonProfile::MomentumUnit(),
                   max_electron_momentum_projection );

  // Evaluate the cross section
  const double multiplier = this->evaluateMultiplier(incoming_energy,
                                                     scattering_angle_cosine );

  const double relativistic_term = this->evaluateRelativisticTerm(
                                                     incoming_energy,
                                                     scattering_angle_cosine );

  const double cross_section = multiplier*relativistic_term*subshell_occupancy*
    compton_profile_quantity.value();

  // Make sure the cross section is valid
  testPostcondition( cross_section >= 0.0 );

  return cross_section;
}

// Evaluate the exact subshell distribution
  /*! \details The distribution has units of barns/MeV.
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateSubshellExact(
				      const double incoming_energy,
                                      const double outgoing_energy,
                                      const double scattering_angle_cosine,
				      const Data::SubshellType subshell ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the outgoing energy is valid
  testPrecondition( outgoing_energy <= incoming_energy );
  testPrecondition( outgoing_energy >= 0.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );
  // Make sure the subshell is valid
  testPrecondition( this->isValidSubshell( subshell ) );

  // Get the subshell binding energy
  const double subshell_binding_energy =
    this->getSubshellBindingEnergy( subshell );

  // The evaluated double differential cross section
  double cross_section;

  if( outgoing_energy <= incoming_energy - subshell_binding_energy )
  {
    // Get the Compton profile for the subshell
    const ComptonProfile& compton_profile =
      this->getComptonProfile( subshell );

    // Get the subshell occupancy
    const double subshell_occupancy = this->getSubshellOccupancy( subshell );

    // Calculate the electron momentum projection
    const ComptonProfile::MomentumQuantity electron_momentum_projection =
      ComptonProfile::MomentumUnit()*
      calculateElectronMomentumProjection( incoming_energy,
                                           outgoing_energy,
                                           scattering_angle_cosine);

    // Evaluate the Compton profile
    ComptonProfile::ProfileQuantity compton_profile_quantity =
      ComptonProfilePolicy::evaluate( compton_profile,
                                      electron_momentum_projection );

    // Evaluate the cross section
    const double multiplier = this->evaluateMultiplierExact(
                                                     incoming_energy,
                                                     outgoing_energy,
                                                     scattering_angle_cosine );

    const double relativistic_term = this->evaluateRelativisticTermExact(
                                                     incoming_energy,
                                                     outgoing_energy,
                                                     scattering_angle_cosine );

    cross_section = multiplier*relativistic_term*subshell_occupancy*
      compton_profile_quantity.value();
  }
  else
    cross_section = 0.0;

  // Make sure the cross section is valid
  testPostcondition( cross_section >= 0.0 );

  return cross_section;
}

// Evaluate the PDF with the electron momentum projection
/*! \details The electron momentum projection must be in me*c units
 * (a momentum value of me*c kg*m/s is 1.0 in me*c units). The PDF
 * will be unitless since the unitless momentum is being used.
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluatePDFWithElectronMomentumProjection(
                                   const double incoming_energy,
                                   const double electron_momentum_projection,
                                   const double scattering_angle_cosine,
                                   const double precision ) const
{
  // Make sure the precision is valid
  testPrecondition( precision > 0.0 );
  testPrecondition( precision < 1.0 );

  const double diff_cross_section =
    this->evaluateWithElectronMomentumProjection( incoming_energy,
                                                  electron_momentum_projection,
                                                  scattering_angle_cosine );
  const double integrated_cross_section =
    this->evaluateIntegratedCrossSection( incoming_energy,
                                          scattering_angle_cosine,
                                          precision );

  if( integrated_cross_section > 0.0 )
    return diff_cross_section/integrated_cross_section;
  else
    return 0.0;
}

// Evaluate the exact PDF
/*! \details The PDF has units of inverse MeV.
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluatePDFExact(
				   const double incoming_energy,
				   const double outgoing_energy,
				   const double scattering_angle_cosine,
                                   const double precision ) const
{
  // Make sure the precision is valid
  testPrecondition( precision > 0.0 );
  testPrecondition( precision < 1.0 );

  const double diff_cross_section = this->evaluateExact(
                                                     incoming_energy,
                                                     outgoing_energy,
                                                     scattering_angle_cosine );

  const double integrated_cross_section =
    this->evaluateIntegratedCrossSectionExact( incoming_energy,
                                               scattering_angle_cosine,
                                               precision );

  if( integrated_cross_section > 0.0 )
    return diff_cross_section/integrated_cross_section;
  else
    return 0.0;
}

// Evaluate the subshell PDF with the electron momentum projection
/*! \details The electron momentum projection must be in me*c units
 * (a momentum value of me*c kg*m/s is 1.0 in me*c units). The PDF
 * will be unitless since the unitless momentum is being used.
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateSubshellPDFWithElectronMomentumProjection(
                                 const double incoming_energy,
                                 const double electron_momentum_projection,
                                 const double scattering_angle_cosine,
                                 const Data::SubshellType subshell,
                                 const double precision ) const
{
  // Make sure the precision is valid
  testPrecondition( precision > 0.0 );
  testPrecondition( precision < 1.0 );

  const double diff_cross_section =
    this->evaluateSubshellWithElectronMomentumProjection(
                                                  incoming_energy,
                                                  electron_momentum_projection,
                                                  scattering_angle_cosine,
                                                  subshell );

  const double integrated_cross_section =
    this->evaluateSubshellIntegratedCrossSection( incoming_energy,
                                                  scattering_angle_cosine,
                                                  subshell,
                                                  precision );

  if( integrated_cross_section > 0.0 )
    return diff_cross_section/integrated_cross_section;
  else
    return 0.0;
}

// Evaluate the exact subshell PDF
/*! \details The PDF has units of inverse MeV.
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateSubshellPDFExact(
					  const double incoming_energy,
					  const double outgoing_energy,
				          const double scattering_angle_cosine,
					  const Data::SubshellType subshell,
                                          const double precision ) const
{
  // Make sure the precision is valid
  testPrecondition( precision > 0.0 );
  testPrecondition( precision < 1.0 );

  const double diff_cross_section =
    this->evaluateSubshellExact( incoming_energy,
                                 outgoing_energy,
                                 scattering_angle_cosine,
                                 subshell );

  const double integrated_cross_section =
    this->evaluateSubshellIntegratedCrossSectionExact( incoming_energy,
                                                       scattering_angle_cosine,
                                                       subshell,
                                                       precision );

  if( integrated_cross_section > 0.0 )
    return diff_cross_section/integrated_cross_section;
  else
    return 0.0;
}

// Evaluate the integrated cross section (b/mu)
/*! \details This will integrate the approximate double differential cross
 * section as function of unitless momentum from pz=-1.0 to pz=pz_max.
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateIntegratedCrossSection(
					  const double incoming_energy,
					  const double scattering_angle_cosine,
					  const double precision ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  double cross_section = 0.0;

  // Evaluate the integrated cross section for each subshell
  SubshellOrderMapType::const_iterator subshell_it =
    d_endf_subshell_order.begin();

  while( subshell_it != d_endf_subshell_order.end() )
  {
    cross_section += this->evaluateSubshellIntegratedCrossSection(
                                                       incoming_energy,
                                                       scattering_angle_cosine,
                                                       subshell_it->right,
                                                       precision );

    ++subshell_it;
  }

  // Make sure the integrated cross section is valid
  testPrecondition( cross_section >= 0.0 );

  return cross_section;
}

// Evaluate the exact integrated cross section (b/mu)
/*! \details This will integrate the exact double differential cross
 * section as function of outgoing energy from E=0.0 MeV to E=E_in-E_b^max.
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateIntegratedCrossSectionExact(
					  const double incoming_energy,
					  const double scattering_angle_cosine,
					  const double precision ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  double cross_section = 0.0;

  // Evaluate the integrated cross section for each subshell
  SubshellOrderMapType::const_iterator subshell_it =
    d_endf_subshell_order.begin();

  while( subshell_it != d_endf_subshell_order.end() )
  {
    cross_section += this->evaluateSubshellIntegratedCrossSectionExact(
                                                       incoming_energy,
                                                       scattering_angle_cosine,
                                                       subshell_it->right,
                                                       precision );

    ++subshell_it;
  }

  // Make sure the integrated cross section is valid
  testPrecondition( cross_section >= 0.0 );

  return cross_section;
}

// Evaluate the integrated cross section (b/mu)
/*! \details This will integrate the approximate double differential cross
 * section as a function of unitless momentum. If full profiles are being
 * used the limits of integration are pz=-1.0 and pz=pz_max. If half profiles
 * are being used the limits of integration are pz=-pz_max and pz=pz_max
 * (unless pz_max is <= 0.0 in which case the integrated cross section
 * will be 0.0).
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateSubshellIntegratedCrossSection(
				          const double incoming_energy,
					  const double scattering_angle_cosine,
					  const Data::SubshellType subshell,
					  const double precision ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );
  // Make sure the subshell is valid
  testPrecondition( this->isValidSubshell( subshell ) );

  // Create the evaluation function wrapper
  boost::function<double (double x)> double_diff_cs_wrapper =
    boost::bind<double>( &StandardCompleteDopplerBroadenedPhotonEnergyDistribution::evaluateSubshellWithElectronMomentumProjection,
                         boost::cref( *this ),
                         incoming_energy,
                         _1,
                         scattering_angle_cosine,
                         subshell );

  // Get the subshell binding energy
  const double subshell_binding_energy =
    this->getSubshellBindingEnergy( subshell );

  // Calculate the max electron momentum projection
  double pz_max =
    calculateMaxElectronMomentumProjection( incoming_energy,
                                            subshell_binding_energy,
                                            scattering_angle_cosine );

  // Don't go above the table max (profile will evaluate to zero beyond it)
  pz_max = ComptonProfilePolicy::getUpperLimitOfIntegration(
                               this->getComptonProfile( subshell ),
                               pz_max*ComptonProfile::MomentumUnit() ).value();

  // Calculate the min electron momentum projection
  double pz_min = ComptonProfilePolicy::getLowerLimitOfIntegration(
                               pz_max*ComptonProfile::MomentumUnit() ).value();

  // Calculate the absolute error and the integrated cross section
  double abs_error, diff_cs;

  Utility::GaussKronrodIntegrator<double> quadrature_set( precision );

  if( pz_min < pz_max )
  {
    quadrature_set.integrateAdaptively<15>( double_diff_cs_wrapper,
                                            pz_min,
                                            pz_max,
                                            diff_cs,
                                            abs_error );
  }
  else
  {
    abs_error = 0.0;
    diff_cs = 0.0;
  }

  // Make sure that the differential cross section is valid
  testPostcondition( diff_cs >= 0.0 );

  return diff_cs;
}

// Evaluate the integrated cross section (b/mu)
/*! \details This will integrate the exact double differential cross
 * section as a function of outgoing energy. The limits of integration are
 * E=0.0 and E=E_in-E_b,i (for both half profiles and full profiles).
 */
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::evaluateSubshellIntegratedCrossSectionExact(
				          const double incoming_energy,
					  const double scattering_angle_cosine,
					  const Data::SubshellType subshell,
					  const double precision ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the scattering angle is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );
  // Make sure the subshell is valid
  testPrecondition( this->isValidSubshell( subshell ) );

  // Create the evaluation function wrapper
  boost::function<double (double x)> double_diff_cs_wrapper =
    boost::bind<double>( &StandardCompleteDopplerBroadenedPhotonEnergyDistribution::evaluateSubshellExact,
                         boost::cref( *this ),
                         incoming_energy,
                         _1,
                         scattering_angle_cosine,
                         subshell );

  // Get the Compton profile for this subshell
  const ComptonProfile& compton_profile = this->getComptonProfile( subshell );

  // Calculate the max energy
  double energy_max = incoming_energy -
    this->getSubshellBindingEnergy( subshell );

  // Calculate the max electron momentum projection
  double pz_max = calculateMaxElectronMomentumProjection(
                                  incoming_energy,
                                  this->getSubshellBindingEnergy( subshell ),
                                  scattering_angle_cosine );

  // Calculate the max table energy
  const double pz_table_max =
    ComptonProfilePolicy::getUpperBoundOfMomentum( compton_profile ).value();

  // Don't go above the table max (profile will evaluate to zero beyond it)
  if( pz_max > pz_table_max )
  {
    bool energetically_possible;

    energy_max = calculateDopplerBroadenedEnergy( pz_table_max,
                                                  incoming_energy,
                                                  scattering_angle_cosine,
                                                  energetically_possible );
  }

  // Calculate the absolute error and the integrated cross section
  double abs_error, diff_cs;

  Utility::GaussKronrodIntegrator<double> quadrature_set( precision );

  quadrature_set.integrateAdaptively<15>( double_diff_cs_wrapper,
                                          0.0,
                                          energy_max,
                                          diff_cs,
                                          abs_error );

  // Make sure that the differential cross section is valid
  testPostcondition( diff_cs >= 0.0 );

  return diff_cs;
}

// Sample an outgoing energy from the distribution
template<typename ComptonProfilePolicy>
void StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::sample(
			       const double incoming_energy,
                               const double scattering_angle_cosine,
                               double& outgoing_energy,
			       Data::SubshellType& shell_of_interaction ) const
{
  Counter trial_dummy;

  this->sampleAndRecordTrials( incoming_energy,
			       scattering_angle_cosine,
			       outgoing_energy,
			       shell_of_interaction,
			       trial_dummy );
}

// Sample an outgoing energy and record the number of trials
/*! \details The sampling of the Compton profile and the interaction subshell
 * are decoupled in this procedure.
 */
template<typename ComptonProfilePolicy>
void StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::sampleAndRecordTrials(
				     const double incoming_energy,
				     const double scattering_angle_cosine,
				     double& outgoing_energy,
				     Data::SubshellType& shell_of_interaction,
				     Counter& trials ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the scattering angle cosine is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  // The electron momentum projection
  double pz;

  // Sample the electron momentum projection
  this->sampleMomentumAndRecordTrials( incoming_energy,
                                       scattering_angle_cosine,
                                       pz,
                                       shell_of_interaction,
                                       trials );

  bool energetically_possible;

  outgoing_energy = calculateDopplerBroadenedEnergy( pz,
                                                     incoming_energy,
                                                     scattering_angle_cosine,
                                                     energetically_possible );

  // If a valid outgoing energy could not be calculated default to the
  // Compton line energy (no Doppler broadening).
  if( !energetically_possible || outgoing_energy < 0.0 )
  {
      outgoing_energy = calculateComptonLineEnergy( incoming_energy,
                                                    scattering_angle_cosine );
  }
  else
  {
    // An energy of zero isn't allowed by the rest of the code
    if( outgoing_energy == 0.0 )
      outgoing_energy = std::numeric_limits<double>::min();
  }

  // Make sure the outgoing energy is valid
  testPostcondition( outgoing_energy <= incoming_energy );
  testPostcondition( outgoing_energy > 0.0 );
  // Make sure that the sampled subshell is valid
  testPostcondition( shell_of_interaction !=Data::UNKNOWN_SUBSHELL );
  testPostcondition( shell_of_interaction != Data::INVALID_SUBSHELL );
}

// Sample an electron momentum from the distribution
/*! \details The sampling of the Compton profile and the interaction subshell
 * are decoupled in this procedure.
 */
template<typename ComptonProfilePolicy>
void StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::sampleMomentumAndRecordTrials(
                                    const double incoming_energy,
                                    const double scattering_angle_cosine,
                                    double& electron_momentum,
                                    Data::SubshellType& shell_of_interaction,
                                    Counter& trials ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the scattering angle cosine is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  // Record the number of iterations
  Counter iterations = 0;

  // Sample the shell that is interacted with
  size_t compton_subshell_index;
  double subshell_binding_energy;

  // Only allow the selection of subshells where an incoherent interaction is
  // energetically possible - there is definitely a more efficient way to do
  // this!
  while( true )
  {
    ++iterations;

    this->sampleInteractionSubshell( compton_subshell_index,
                                     subshell_binding_energy,
                                     shell_of_interaction );

    // Calculate the maximum outgoing photon energy
    double energy_max = incoming_energy - subshell_binding_energy;

    if( energy_max >= 0.0 )
      break;
  }

  // Get the Compton profile for the sampled subshell
  const ComptonProfile& compton_profile =
    *d_compton_profile_array[compton_subshell_index];

  electron_momentum = this->sampleSubshellMomentum( incoming_energy,
                                                    scattering_angle_cosine,
                                                    subshell_binding_energy,
                                                    compton_profile );

  // Increment the number of trials
  trials += iterations;
}

// Sample an electron momentum from the subshell distribution
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::sampleSubshellMomentum(
                                     const double incoming_energy,
                                     const double scattering_angle_cosine,
                                     Data::SubshellType subshell ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy >=
                    this->getSubshellBindingEnergy( subshell ) );
  // Make sure the scattering angle cosine is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );
  // Make sure the subshell is valid
  testPrecondition( this->isValidSubshell( subshell ) );

  // Get the subshell binding energy
  const double subshell_binding_energy =
    this->getSubshellBindingEnergy( subshell );

  // Get the Compton profile for the subshell
  const ComptonProfile& compton_profile = this->getComptonProfile( subshell );

  return this->sampleSubshellMomentum( incoming_energy,
                                       scattering_angle_cosine,
                                       subshell_binding_energy,
                                       compton_profile );
}

// Sample an electron momentum from the subshell distribution
template<typename ComptonProfilePolicy>
double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::sampleSubshellMomentum(
                                 const double incoming_energy,
                                 const double scattering_angle_cosine,
                                 const double subshell_binding_energy,
                                 const ComptonProfile& compton_profile ) const
{
  // Make sure the incoming energy is valid
  testPrecondition( incoming_energy > 0.0 );
  // Make sure the scattering angle cosine is valid
  testPrecondition( scattering_angle_cosine >= -1.0 );
  testPrecondition( scattering_angle_cosine <= 1.0 );

  // Calculate the maximum electron momentum projection
  ComptonProfile::MomentumQuantity pz_max = ComptonProfile::MomentumUnit()*
    calculateMaxElectronMomentumProjection( incoming_energy,
                                            subshell_binding_energy,
                                            scattering_angle_cosine );

  // Sample an electron momentum projection
  ComptonProfile::MomentumQuantity pz =
    ComptonProfilePolicy::sample( compton_profile, pz_max );

  return pz.value();
}

// Check if the subshell is valid
template<typename ComptonProfilePolicy>
bool StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::isValidSubshell(
                                            const Data::SubshellType subshell ) const
{
  return d_endf_subshell_order.right.find( subshell ) !=
    d_endf_subshell_order.right.end();
}

// Return the occupancy of a subshell (default is the ENDF occupancy)
template<typename ComptonProfilePolicy>
inline double StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::getSubshellOccupancy( const Data::SubshellType subshell ) const
{
  // Make sure the subshell is valid
  testPrecondition( this->isValidSubshell( subshell ) );

  unsigned endf_subshell_index = this->getENDFSubshellIndex( subshell );

  return d_endf_subshell_occupancies[endf_subshell_index];
}

// Return the old subshell index corresponding to the subshell
template<typename ComptonProfilePolicy>
unsigned StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::getOldSubshellIndex(
                                            const Data::SubshellType subshell ) const
{
  return d_subshell_converter->convertSubshellToIndex( subshell );
}

// Return the endf subshell index corresponding to the subshell
template<typename ComptonProfilePolicy>
unsigned StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::getENDFSubshellIndex(
                                            const Data::SubshellType subshell ) const
{
  // Make sure the subshell is valid
  testPrecondition( this->isValidSubshell( subshell ) );

  return d_endf_subshell_order.right.find( subshell )->second;
}

// Return the subshell corresponding to the endf subshell index
template<typename ComptonProfilePolicy>
Data::SubshellType StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::getSubshell(
                                     const size_t endf_subshell_index ) const
{
  SubshellOrderMapType::left_map::const_iterator endf_subshell_index_it =
    d_endf_subshell_order.left.find( endf_subshell_index );

  // Make sure the index was found
  testPostcondition( endf_subshell_index_it !=
                     d_endf_subshell_order.left.end() );

  return endf_subshell_index_it->second;
}

// Return the Compton profile for a subshell
template<typename ComptonProfilePolicy>
const ComptonProfile& StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::getComptonProfile(
                                           const Data::SubshellType& subshell ) const
{
  // Make sure the subshell is valid
  testPrecondition( this->isValidSubshell( subshell ) );

  // Get the old subshell corresponding to the subshell type
  unsigned old_subshell_index = this->getOldSubshellIndex( subshell );

  return *d_compton_profile_array[old_subshell_index];
}

// Return the Compton profile for an old subshell index
template<typename ComptonProfilePolicy>
const ComptonProfile& StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::getComptonProfile(
                                     const unsigned& old_subshell_index ) const
{
  // Make sure the old subshell index is valid
  testPrecondition( old_subshell_index <
                    d_compton_profile_array.size() );

  return *d_compton_profile_array[old_subshell_index];
}

// Sample an ENDF subshell
template<typename ComptonProfilePolicy>
Data::SubshellType StandardCompleteDopplerBroadenedPhotonEnergyDistribution<ComptonProfilePolicy>::sampleENDFInteractionSubshell() const
{
  size_t endf_subshell_index;

  d_endf_subshell_occupancy_distribution->sampleAndRecordBinIndex(
                                                         endf_subshell_index );

  return this->getSubshell( endf_subshell_index );
}

EXTERN_EXPLICIT_TEMPLATE_CLASS_INST( StandardCompleteDopplerBroadenedPhotonEnergyDistribution<FullComptonProfilePolicy> );
EXTERN_EXPLICIT_TEMPLATE_CLASS_INST( StandardCompleteDopplerBroadenedPhotonEnergyDistribution<HalfComptonProfilePolicy> );
EXTERN_EXPLICIT_TEMPLATE_CLASS_INST( StandardCompleteDopplerBroadenedPhotonEnergyDistribution<DoubledHalfComptonProfilePolicy> );

} // end MonteCarlo namespace

#endif // end MONTE_CARLO_STANDARD_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_DEF_HPP

//---------------------------------------------------------------------------//
// end MonteCarlo_StandardCompleteDopplerBroadenedPhotonEnergyDistribution_def.hpp
//---------------------------------------------------------------------------//