#ifndef BOOST_GEOMETRY_PROJECTIONS_TMERC_HPP
#define BOOST_GEOMETRY_PROJECTIONS_TMERC_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/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/function_overloads.hpp>
#include <boost/geometry/srs/projections/impl/pj_mlfn.hpp>

namespace boost { namespace geometry
{

namespace srs { namespace par4
{
    struct tmerc {};
    struct utm {};

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace tmerc
    {

            static const double EPS10 = 1.e-10;
            //static const double FC1 = 1.;
            //static const double FC2 = .5;
            //static const double FC3 = .16666666666666666666;
            //static const double FC4 = .08333333333333333333;
            //static const double FC5 = .05;
            //static const double FC6 = .03333333333333333333;
            //static const double FC7 = .02380952380952380952;
            //static const double FC8 = .01785714285714285714;

            template <typename T>
            inline T FC1() { return 1.; }
            template <typename T>
            inline T FC2() { return .5; }
            template <typename T>
            inline T FC3() { return .16666666666666666666666666666666666666; }
            template <typename T>
            inline T FC4() { return .08333333333333333333333333333333333333; }
            template <typename T>
            inline T FC5() { return .05; }
            template <typename T>
            inline T FC6() { return .03333333333333333333333333333333333333; }
            template <typename T>
            inline T FC7() { return .02380952380952380952380952380952380952; }
            template <typename T>
            inline T FC8() { return .01785714285714285714285714285714285714; }

            template <typename T>
            struct par_tmerc
            {
                T    esp;
                T    ml0;
                T    en[EN_SIZE];
            };

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

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_tmerc<CalculationType> m_proj_parm;

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

                // FORWARD(e_forward)  ellipse
                // 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 HALFPI = detail::HALFPI<CalculationType>();
                    static const CalculationType FC1 = tmerc::FC1<CalculationType>();
                    static const CalculationType FC2 = tmerc::FC2<CalculationType>();
                    static const CalculationType FC3 = tmerc::FC3<CalculationType>();
                    static const CalculationType FC4 = tmerc::FC4<CalculationType>();
                    static const CalculationType FC5 = tmerc::FC5<CalculationType>();
                    static const CalculationType FC6 = tmerc::FC6<CalculationType>();
                    static const CalculationType FC7 = tmerc::FC7<CalculationType>();
                    static const CalculationType FC8 = tmerc::FC8<CalculationType>();

                    CalculationType al, als, n, cosphi, sinphi, t;

                    /*
                     * Fail if our longitude is more than 90 degrees from the
                     * central meridian since the results are essentially garbage.
                     * Is error -20 really an appropriate return value?
                     *
                     *  http://trac.osgeo.org/proj/ticket/5
                     */
                    if( lp_lon < -HALFPI || lp_lon > HALFPI )
                    {
                        xy_x = HUGE_VAL;
                        xy_y = HUGE_VAL;
                        BOOST_THROW_EXCEPTION( projection_exception(-14) );
                        return;
                    }

                    sinphi = sin(lp_lat);
                    cosphi = cos(lp_lat);
                    t = fabs(cosphi) > 1e-10 ? sinphi/cosphi : 0.;
                    t *= t;
                    al = cosphi * lp_lon;
                    als = al * al;
                    al /= sqrt(1. - this->m_par.es * sinphi * sinphi);
                    n = this->m_proj_parm.esp * cosphi * cosphi;
                    xy_x = this->m_par.k0 * al * (FC1 +
                        FC3 * als * (1. - t + n +
                        FC5 * als * (5. + t * (t - 18.) + n * (14. - 58. * t)
                        + FC7 * als * (61. + t * ( t * (179. - t) - 479. ) )
                        )));
                    xy_y = this->m_par.k0 * (pj_mlfn(lp_lat, sinphi, cosphi, this->m_proj_parm.en) - this->m_proj_parm.ml0 +
                        sinphi * al * lp_lon * FC2 * ( 1. +
                        FC4 * als * (5. - t + n * (9. + 4. * n) +
                        FC6 * als * (61. + t * (t - 58.) + n * (270. - 330 * t)
                        + FC8 * als * (1385. + t * ( t * (543. - t) - 3111.) )
                        ))));
                }

                // INVERSE(e_inverse)  ellipsoid
                // 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>();
                    static const CalculationType FC1 = tmerc::FC1<CalculationType>();
                    static const CalculationType FC2 = tmerc::FC2<CalculationType>();
                    static const CalculationType FC3 = tmerc::FC3<CalculationType>();
                    static const CalculationType FC4 = tmerc::FC4<CalculationType>();
                    static const CalculationType FC5 = tmerc::FC5<CalculationType>();
                    static const CalculationType FC6 = tmerc::FC6<CalculationType>();
                    static const CalculationType FC7 = tmerc::FC7<CalculationType>();
                    static const CalculationType FC8 = tmerc::FC8<CalculationType>();

                    CalculationType n, con, cosphi, d, ds, sinphi, t;

                    lp_lat = pj_inv_mlfn(this->m_proj_parm.ml0 + xy_y / this->m_par.k0, this->m_par.es, this->m_proj_parm.en);
                    if (fabs(lp_lat) >= HALFPI) {
                        lp_lat = xy_y < 0. ? -HALFPI : HALFPI;
                        lp_lon = 0.;
                    } else {
                        sinphi = sin(lp_lat);
                        cosphi = cos(lp_lat);
                        t = fabs(cosphi) > 1e-10 ? sinphi/cosphi : 0.;
                        n = this->m_proj_parm.esp * cosphi * cosphi;
                        d = xy_x * sqrt(con = 1. - this->m_par.es * sinphi * sinphi) / this->m_par.k0;
                        con *= t;
                        t *= t;
                        ds = d * d;
                        lp_lat -= (con * ds / (1.-this->m_par.es)) * FC2 * (1. -
                            ds * FC4 * (5. + t * (3. - 9. *  n) + n * (1. - 4 * n) -
                            ds * FC6 * (61. + t * (90. - 252. * n +
                                45. * t) + 46. * n
                           - ds * FC8 * (1385. + t * (3633. + t * (4095. + 1574. * t)) )
                            )));
                        lp_lon = d*(FC1 -
                            ds*FC3*( 1. + 2.*t + n -
                            ds*FC5*(5. + t*(28. + 24.*t + 8.*n) + 6.*n
                           - ds * FC7 * (61. + t * (662. + t * (1320. + 720. * t)) )
                        ))) / cosphi;
                    }
                }

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

            };

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

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_tmerc<CalculationType> m_proj_parm;

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

                // FORWARD(s_forward)  sphere
                // 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 HALFPI = detail::HALFPI<CalculationType>();

                    CalculationType b, cosphi;

                    /*
                     * Fail if our longitude is more than 90 degrees from the
                     * central meridian since the results are essentially garbage.
                     * Is error -20 really an appropriate return value?
                     *
                     *  http://trac.osgeo.org/proj/ticket/5
                     */
                    if( lp_lon < -HALFPI || lp_lon > HALFPI )
                    {
                        xy_x = HUGE_VAL;
                        xy_y = HUGE_VAL;
                        BOOST_THROW_EXCEPTION( projection_exception(-14) );
                        return;
                    }

                    cosphi = cos(lp_lat);
                    b = cosphi * sin(lp_lon);
                    if (fabs(fabs(b) - 1.) <= EPS10)
                        BOOST_THROW_EXCEPTION( projection_exception(-20) );

                    xy_x = this->m_proj_parm.ml0 * log((1. + b) / (1. - b));
                    xy_y = cosphi * cos(lp_lon) / sqrt(1. - b * b);

                    b = fabs( xy_y );
                    if (b >= 1.) {
                        if ((b - 1.) > EPS10)
                            BOOST_THROW_EXCEPTION( projection_exception(-20) );
                        else xy_y = 0.;
                    } else
                        xy_y = acos(xy_y);

                    if (lp_lat < 0.)
                        xy_y = -xy_y;
                    xy_y = this->m_proj_parm.esp * (xy_y - this->m_par.phi0);
                }

                // INVERSE(s_inverse)  sphere
                // 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
                {
                    CalculationType h, g;

                    h = exp(xy_x / this->m_proj_parm.esp);
                    g = .5 * (h - 1. / h);
                    h = cos(this->m_par.phi0 + xy_y / this->m_proj_parm.esp);
                    lp_lat = asin(sqrt((1. - h * h) / (1. + g * g)));
                    if (xy_y < 0.) lp_lat = -lp_lat;
                    lp_lon = (g || h) ? atan2(g, h) : 0.;
                }

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

            };

            template <typename Parameters, typename T>
            inline void setup(Parameters& par, par_tmerc<T>& proj_parm)  /* general initialization */
            {
                if (par.es) {
                    if (!pj_enfn(par.es, proj_parm.en))
                        BOOST_THROW_EXCEPTION( projection_exception(0) );
                    proj_parm.ml0 = pj_mlfn(par.phi0, sin(par.phi0), cos(par.phi0), proj_parm.en);
                    proj_parm.esp = par.es / (1. - par.es);
                } else {
                    proj_parm.esp = par.k0;
                    proj_parm.ml0 = .5 * proj_parm.esp;
                }
            }

            // Transverse Mercator
            template <typename Parameters, typename T>
            inline void setup_tmerc(Parameters& par, par_tmerc<T>& proj_parm)
            {
                setup(par, proj_parm);
            }

            // Universal Transverse Mercator (UTM)
            template <typename Parameters, typename T>
            inline void setup_utm(Parameters& par, par_tmerc<T>& proj_parm)
            {
                static const T ONEPI = detail::ONEPI<T>();

                int zone;

                par.y0 = pj_param(par.params, "bsouth").i ? 10000000. : 0.;
                par.x0 = 500000.;
                if (pj_param(par.params, "tzone").i) /* zone input ? */
                    if ((zone = pj_param(par.params, "izone").i) > 0 && zone <= 60)
                        --zone;
                    else
                        BOOST_THROW_EXCEPTION( projection_exception(-35) );
                else /* nearest central meridian input */
                    if ((zone = int_floor((adjlon(par.lam0) + ONEPI) * 30. / ONEPI)) < 0)
                        zone = 0;
                    else if (zone >= 60)
                        zone = 59;
                par.lam0 = (zone + .5) * ONEPI / 30. - ONEPI;
                par.k0 = 0.9996;
                par.phi0 = 0.;
                setup(par, proj_parm);
            }

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

    /*!
        \brief Transverse Mercator projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
         - Ellipsoid
        \par Example
        \image html ex_tmerc.gif
    */
    template <typename CalculationType, typename Parameters>
    struct tmerc_ellipsoid : public detail::tmerc::base_tmerc_ellipsoid<CalculationType, Parameters>
    {
        inline tmerc_ellipsoid(const Parameters& par) : detail::tmerc::base_tmerc_ellipsoid<CalculationType, Parameters>(par)
        {
            detail::tmerc::setup_tmerc(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Transverse Mercator projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
         - Ellipsoid
        \par Example
        \image html ex_tmerc.gif
    */
    template <typename CalculationType, typename Parameters>
    struct tmerc_spheroid : public detail::tmerc::base_tmerc_spheroid<CalculationType, Parameters>
    {
        inline tmerc_spheroid(const Parameters& par) : detail::tmerc::base_tmerc_spheroid<CalculationType, Parameters>(par)
        {
            detail::tmerc::setup_tmerc(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Universal Transverse Mercator (UTM) projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
        \par Projection parameters
         - zone: UTM Zone (integer)
         - south: Denotes southern hemisphere UTM zone (boolean)
        \par Example
        \image html ex_utm.gif
    */
    template <typename CalculationType, typename Parameters>
    struct utm_ellipsoid : public detail::tmerc::base_tmerc_ellipsoid<CalculationType, Parameters>
    {
        inline utm_ellipsoid(const Parameters& par) : detail::tmerc::base_tmerc_ellipsoid<CalculationType, Parameters>(par)
        {
            detail::tmerc::setup_utm(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Universal Transverse Mercator (UTM) projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
        \par Projection parameters
         - zone: UTM Zone (integer)
         - south: Denotes southern hemisphere UTM zone (boolean)
        \par Example
        \image html ex_utm.gif
    */
    template <typename CalculationType, typename Parameters>
    struct utm_spheroid : public detail::tmerc::base_tmerc_spheroid<CalculationType, Parameters>
    {
        inline utm_spheroid(const Parameters& par) : detail::tmerc::base_tmerc_spheroid<CalculationType, Parameters>(par)
        {
            detail::tmerc::setup_utm(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::tmerc, tmerc_spheroid, tmerc_ellipsoid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::utm, utm_spheroid, utm_ellipsoid)

        // Factory entry(s) - dynamic projection
        template <typename CalculationType, typename Parameters>
        class tmerc_entry : public detail::factory_entry<CalculationType, Parameters>
        {
            public :
                virtual base_v<CalculationType, Parameters>* create_new(const Parameters& par) const
                {
                    if (par.es)
                        return new base_v_fi<tmerc_ellipsoid<CalculationType, Parameters>, CalculationType, Parameters>(par);
                    else
                        return new base_v_fi<tmerc_spheroid<CalculationType, Parameters>, CalculationType, Parameters>(par);
                }
        };

        template <typename CalculationType, typename Parameters>
        class utm_entry : public detail::factory_entry<CalculationType, Parameters>
        {
            public :
                virtual base_v<CalculationType, Parameters>* create_new(const Parameters& par) const
                {
                    if (par.es)
                        return new base_v_fi<utm_ellipsoid<CalculationType, Parameters>, CalculationType, Parameters>(par);
                    else
                        return new base_v_fi<utm_spheroid<CalculationType, Parameters>, CalculationType, Parameters>(par);
                }
        };

        template <typename CalculationType, typename Parameters>
        inline void tmerc_init(detail::base_factory<CalculationType, Parameters>& factory)
        {
            factory.add_to_factory("tmerc", new tmerc_entry<CalculationType, Parameters>);
            factory.add_to_factory("utm", new utm_entry<CalculationType, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_TMERC_HPP


/*
 *
 * BigInt
 * ledger-core
 *
 * Created by Pierre Pollastri on 15/09/2016.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * 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 "BigInt.h"
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include "../collections/collections.hpp"
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <cstdlib>
#include <utils/endian.h>

namespace ledger {
    namespace core {

        const BigInt BigInt::ZERO = BigInt(0);
        const BigInt BigInt::ONE = BigInt(1);
        const BigInt BigInt::TEN = BigInt(10);
        const std::string BigInt::DIGITS = std::string("0123456789abcdefghijklmnopqrstuvwxyz");
        const int BigInt::MIN_RADIX = 2;
        const int BigInt::MAX_RADIX = 36;

        BigInt::BigInt() {
            _bigd = bdNew();
            _negative = false;
        }

        BigInt::BigInt(const BigInt& cpy) {
            _bigd = bdNew();
            _negative = cpy._negative;
            bdSetEqual(_bigd, cpy._bigd);
        }

        BigInt::BigInt(const void *data, size_t length, bool negative) : BigInt() {
            bdConvFromOctets(_bigd, reinterpret_cast<const unsigned char *>(data), length);
            _negative = negative;
        }

        BigInt::BigInt(const std::vector<uint8_t> &data, bool negative)
        : BigInt(data.data(), data.size(), negative){

        }

        BigInt::BigInt(int value)
                : BigInt() {
            bdSetShort(_bigd, (bdigit_t)std::abs(value));
            if (value < 0) {
                _negative = true;
            }
        }

        BigInt::BigInt(unsigned int value) : BigInt() {
            bdSetShort(_bigd, (bdigit_t)value);
            _negative = false;
        }

        BigInt::BigInt(unsigned long long value) : BigInt() {
            auto bytes = endianness::scalar_type_to_array<unsigned long long>(value, endianness::Endianness::BIG);
            bdConvFromOctets(_bigd, reinterpret_cast<const unsigned char *>(bytes), sizeof(unsigned long long));
            std::free(bytes);
            _negative = false;
        }

        BigInt::BigInt(int64_t value) : BigInt() {
            auto bytes = endianness::scalar_type_to_array<int64_t >(std::abs(value), endianness::Endianness::BIG);
            bdConvFromOctets(_bigd, reinterpret_cast<const unsigned char *>(bytes), sizeof(int64_t));
            std::free(bytes);
            _negative = value < 0LL;
        }

        BigInt::BigInt(const std::string& str) : BigInt(str, 10)
        {};

        BigInt& BigInt::assignI64(int64_t value) {
            auto bytes = endianness::scalar_type_to_array<int64_t >(std::abs(value), endianness::Endianness::BIG);
            bdConvFromOctets(_bigd, reinterpret_cast<const unsigned char *>(bytes), sizeof(int64_t));
            std::free(bytes);
            _negative = value < 0LL;
            return *this;
        }

        BigInt::BigInt(const std::string &str, int radix) : BigInt() {
            if (radix == 10) {
                // handle digit representation
                if (!all_digits(str)) {
                    throw std::invalid_argument("Non-numeric base 10 big int");
                }

                _negative = str[0] == '-';
                bdConvFromDecimal(_bigd, str.c_str());
            } else if (radix == 16) {
                bdConvFromHex(_bigd, str.c_str());
            } else {
                throw std::invalid_argument("Cannot handle radix");
            }
        }

        BigInt::~BigInt() {
            if (_bigd != nullptr) {
                bdFree(&_bigd);
            }
        }

        int BigInt::toInt() const {
            return bdToShort(_bigd) * (_negative ? -1 : 1);
        }

        unsigned int BigInt::toUnsignedInt() const {
            return bdToShort(_bigd);
        }

        std::string BigInt::toString() const {
            size_t nchars = bdConvToDecimal(_bigd, NULL, 0);
            auto s = std::shared_ptr<char>(new char[nchars + 1]);
            bdConvToDecimal(_bigd, s.get(), nchars + 1);
            auto out = std::string(s.get());
            if (this->isNegative()) {
                out = "-" + out;
            }
            return out;
        }

        std::string BigInt::toHexString() const {
            size_t nchars = bdConvToHex(_bigd, NULL, 0);
            auto s = std::shared_ptr<char>(new char[nchars + 1]);
            bdConvToHex(_bigd, s.get(), nchars + 1);
            auto out = std::string(s.get());
            if (out.length() % 2 != 0) {
                out = "0" + out;
            }
            return out;
        }

        unsigned long BigInt::getBitSize() const {
            return bdSizeof(_bigd) * sizeof(SimpleInt) * 8;
        }

        BigInt *BigInt::from_hex(const std::string &str) {
            return new BigInt(str, 16);
        }

        BigInt *BigInt::from_dec(const std::string &str) {
            return new BigInt(str, 10);
        }

        BigInt BigInt::operator+(const BigInt &rhs) const {
            if (rhs.isNegative() && !this->isNegative()) {
                return *this - rhs.positive();
            } else if (this->isNegative() && !rhs.isNegative()) {
                return rhs - this->positive();
            }
            BigInt result;
            bdAdd(result._bigd, this->_bigd, rhs._bigd);
            result._negative = rhs.isNegative() && this->isNegative();
            return result;
        }

        BigInt BigInt::operator-(const BigInt &rhs) const {
            if (this->isPositive() && rhs.isNegative()) {
                return *this + rhs.positive();
            } else if (this->isNegative() && rhs.isPositive()) {
                return *this + rhs.negative();
            } else if (rhs > *this) {
                return (rhs - *this).negative();
            }
            BigInt result;
            bdSubtract(result._bigd, this->_bigd, rhs._bigd);
            return result;
        }

        BigInt BigInt::operator*(const BigInt &rhs) const {
            BigInt result;
            bdMultiply(result._bigd, this->_bigd, rhs._bigd);
            result._negative = this->isNegative() != rhs.isNegative();
            return result;
        }

        BigInt BigInt::operator/(const BigInt &rhs) const {
            BigInt result;
            BigInt remainder;
            bdDivide(result._bigd, remainder._bigd, this->_bigd, rhs._bigd);
            result._negative = this->isNegative() != rhs.isNegative();
            return result;
        }

        BigInt BigInt::operator%(const BigInt &rhs) const {
            BigInt result;
            BigInt remainder;
            bdDivide(result._bigd, remainder._bigd, this->_bigd, rhs._bigd);
            remainder._negative = this->isNegative();
            return remainder;
        }

        BigInt &BigInt::operator++() {
            if (this->isNegative()) {
                bdDecrement(_bigd);
            } else {
                bdIncrement(_bigd);
            }
            return *this;
        }

        BigInt BigInt::operator++(int) {
            BigInt temp = *this;
            ++*this;
            return temp;
        }

        BigInt &BigInt::operator--() {
            if (this->isPositive()) {
                bdDecrement(_bigd);
            } else {
                bdIncrement(_bigd);
            }
            return *this;
        }

        BigInt BigInt::operator--(int) {
            BigInt temp = *this;
            ++*this;
            return temp;
        }

        BigInt& BigInt::operator=(const BigInt &a) {
            if (this != &a) {
                bdSetEqual(_bigd, a._bigd);
                _negative = a._negative;
            }

            return *this;
        }

        BigInt& BigInt::operator=(BigInt&& a) {
            if (this == &a) {
                return *this;
            }

            if (_bigd != nullptr) {
                bdFree(&_bigd);
            }

            _bigd = a._bigd;
            _negative = a._negative;
            a._bigd = nullptr;

            return *this;
        }

        bool BigInt::isNegative() const {
            return _negative && !this->isZero();
        }

        bool BigInt::isPositive() const {
            return !_negative || this->isZero();
        }

        bool BigInt::isZero() const {
            return bdIsZero(_bigd) != 0;
        }

        BigInt BigInt::negative() const {
            BigInt result = *this;
            result._negative = true;
            return result;
        }

        BigInt BigInt::positive() const {
            BigInt result = *this;
            result._negative = false;
            return result;
        }

        bool BigInt::operator<(const BigInt &rhs) const {
            if (this->isNegative() && rhs.isPositive()) {
                return true;
            } else if (this->isPositive() && rhs.isNegative()) {
                return false;
            } else if (this->isNegative() && rhs.isNegative()) {
                return bdCompare(this->_bigd, rhs._bigd) == 1;
            }
            return bdCompare(this->_bigd, rhs._bigd) == -1;
        }

        bool BigInt::operator<=(const BigInt &rhs) const {
            if (this->isNegative() && rhs.isPositive()) {
                return true;
            } else if (this->isPositive() && rhs.isNegative()) {
                return false;
            } else if (this->isNegative() && rhs.isNegative()) {
                return bdCompare(this->_bigd, rhs._bigd) >= 0;
            }
            return bdCompare(this->_bigd, rhs._bigd) <= 0;
        }

        bool BigInt::operator==(const BigInt &rhs) const {
            return this->_negative == rhs._negative && bdCompare(this->_bigd, rhs._bigd) == 0;
        }

        bool BigInt::operator!=(const BigInt &rhs) const {
            return !(*this == rhs);
        }

        bool BigInt::operator>(const BigInt &rhs) const {
            return rhs < *this;
        }

        bool BigInt::operator>=(const BigInt &rhs) const {
            return rhs == *this || rhs < *this;
        }

        BigInt BigInt::pow(unsigned short p) const {
            BigInt result;
            bdPower(result._bigd, _bigd, p);
            result._negative = isNegative() && (p % 2 != 0 || p == 0);
            return result;
        }

        std::vector<uint8_t> BigInt::toByteArray() const {
            size_t nchars = bdConvToOctets(_bigd, NULL, 0);
            std::vector<uint8_t> out = std::vector<uint8_t >(nchars);
            bdConvToOctets(_bigd, reinterpret_cast<unsigned char *>(out.data()), nchars);
            return out;
        }

        uint64_t BigInt::toUint64() const {
            std::vector<uint8_t> result(sizeof(uint64_t));
            bdConvToOctets(_bigd, result.data(), sizeof(uint64_t));
            if (ledger::core::endianness::isSystemLittleEndian()) {
                std::reverse(result.begin(), result.end());
            }
            return reinterpret_cast<uint64_t *>(result.data())[0];
        }

        int64_t BigInt::toInt64() const {
            std::vector<uint8_t> result(sizeof(uint64_t));
            bdConvToOctets(_bigd, result.data(), sizeof(uint64_t));
            if (ledger::core::endianness::isSystemLittleEndian()) {
                std::reverse(result.begin(), result.end());
            }
            return reinterpret_cast<int64_t *>(result.data())[0] * (_negative ? -1 : 1);
        }

        int BigInt::compare(const BigInt &rhs) const {
            if (this->isNegative() && rhs.isPositive()) {
                return -1;
            } else if (this->isPositive() && rhs.isNegative()) {
                return 1;
            } else if (this->isNegative() && rhs.isNegative()) {
                return -bdCompare(this->_bigd, rhs._bigd);
            }
            return bdCompare(this->_bigd, rhs._bigd);
        }

        BigInt BigInt::fromHex(const std::string &str) {
            return BigInt(str, 16);
        }

        BigInt BigInt::fromDecimal(const std::string &str) {
            return BigInt(str, 10);
        }

        BigInt BigInt::fromString(const std::string &str) {
            if (strings::startsWith(str, "0x")) {
                return BigInt::fromHex(str.substr(2, str.length()));
            } else {
                return BigInt::fromDecimal(str);
            }
        }

        BigInt::BigInt(BigInt &&mov) {
            _bigd = mov._bigd;
            _negative = mov._negative;
            mov._bigd = nullptr;
        }

        bool BigInt::all_digits(std::string const& s) {
            auto it = s.cbegin();
            auto end = s.cend();

            if (it != end && (*it == '-' || *it == '+')) {
                it++; // move past the first character
            }

            return std::all_of(it, end, [](char c) { return isdigit(c); });
        }

        BigInt BigInt::fromFloatString(const std::string &str, int scaleFactor) {
            namespace mp = boost::multiprecision;

            mp::cpp_dec_float_50 f(str);
            mp::cpp_dec_float_50 scale = mp::pow(mp::cpp_dec_float_50(10), (float) scaleFactor);
            f = f * scale;

            bool isNegative = f < 0;

            if (isNegative)
                f = f * -1;

            mp::uint256_t i;
            i.assign(f);

            auto size = i.backend().size();
            auto *limbs = i.backend().limbs();

            // Here is the weird part. Boost is dividing the number into "limbs", the limbs size may differ depending on the
            // platform but there are always ordered like little endian (the least significant limb is always first).
            // We can't forget that limb endianness depends on the architecture. To avoid having to perform multiple
            // swaps for every architecture we forces the whole limb "byte array" to be ordered like a bit uint256 in little endian
            // number and then swap all of it in big endian.
            if (endianness::getSystemEndianness() == endianness::Endianness::BIG) {
                for (auto offset = 0; offset < size; offset++) {
                    endianness::swapToEndianness(limbs + offset, sizeof(mp::limb_type),
                                                 endianness::getSystemEndianness(), endianness::Endianness::LITTLE);
                }
            }
            endianness::swapToEndianness(limbs, size * sizeof(mp::limb_type), endianness::Endianness::LITTLE, endianness::Endianness::BIG);
            return BigInt(limbs, size * sizeof(mp::limb_type), isNegative);
        }

    }
}


/**
 * @file
 * @copyright This code is licensed under the 3-clause BSD license.\n
 *            Copyright ETH Zurich, Laboratory of Physical Chemistry, Reiher Group.\n
 *            See LICENSE.txt for details.
 */

#include "Utils/GeometryOptimization/NtOptimizer.h"
#include "Utils/CalculatorBasics/CalculationRoutines.h"
#include "Utils/Geometry/AtomCollection.h"
#include "Utils/Geometry/ElementInfo.h"
#include "Utils/GeometryOptimization/NtOptimizerSettings.h"
#include "Utils/Optimizer/GradientBased/Bfgs.h"
#include <array>
#include <boost/exception/diagnostic_information.hpp>
#include <cmath>
#include <valarray>

namespace Scine {
namespace Utils {

int NtOptimizer::optimize(AtomCollection& atoms, Core::Log& log) {
  this->sanityCheck(atoms);
  // Configure Calculator
  _calculator.setStructure(atoms);
  _calculator.setRequiredProperties(Utils::Property::Energy | Utils::Property::Gradients);
  Utils::PositionCollection coordinates = atoms.getPositions();
  const unsigned int nAtoms = atoms.size();
  int cycle = 0;
  _values.clear();
  for (unsigned int loop = 0; loop < this->check.maxIter; loop++) {
    cycle++;
    // Micro cycles performed in true Cartesians due to constraints
    if (cycle > 1 && useMicroCycles && nAtoms > 2) {
      // Define micro iteration optimizer
      Bfgs bfgs;
      bfgs.projection = nullptr;
      bfgs.trustRadius = 0.2;
      bfgs.useTrustRadius = true;
      // Define micro iteration convergence
      GradientBasedCheck microIterCheck;
      microIterCheck.maxIter = (fixedNumberOfMicroCycles ? numberOfMicroCycles : std::min(cycle, numberOfMicroCycles));
      // Define micro iteration update
      auto const microIterUpdate = [&](const Eigen::VectorXd& parameters, double& value, Eigen::VectorXd& gradients) {
        coordinates = Eigen::Map<const Utils::PositionCollection>(parameters.data(), nAtoms, 3);
        _calculator.modifyPositions(coordinates);
        _calculator.setRequiredProperties(Utils::Property::Energy | Utils::Property::Gradients);
        atoms.setPositions(coordinates);
        Results results = CalculationRoutines::calculateWithCatch(_calculator, log, "Calculation in NT optimization failed.");
        value = results.get<Property::Energy>();
        // Apply Cartesian constraints
        auto gradientMatrix = results.get<Property::Gradients>();
        this->updateGradients(atoms, value, gradientMatrix);
        gradients = Eigen::Map<const Eigen::VectorXd>(gradientMatrix.data(), nAtoms * 3);
      };
      // Run micro iterations
      Eigen::VectorXd positions = Eigen::Map<const Eigen::VectorXd>(atoms.getPositions().data(), atoms.size() * 3);
      try {
        bfgs.optimize(positions, microIterUpdate, microIterCheck);
      }
      catch (...) {
        if (cycle > 5) {
          coordinates = this->extractTsGuess();
          atoms.setPositions(coordinates);
          _calculator.modifyPositions(coordinates);
          return cycle;
        }
        throw std::runtime_error("NT micro iterations failed.");
      }
      coordinates = Eigen::Map<const Utils::PositionCollection>(positions.data(), nAtoms, 3);
      atoms.setPositions(coordinates);
    }

    _calculator.modifyPositions(coordinates);

    // Calculate data
    Utils::Results results;
    try {
      results = _calculator.calculate("NT Macro Cycle"); // might throw exception
      if (!results.get<Property::SuccessfulCalculation>()) {
        throw std::runtime_error("Calculator signalled unsuccessful calculation.");
      }
    }
    catch (...) {
      // try extraction if we already walked a bit, calculation failure might occur because atoms are already quite close
      if (cycle > 5) {
        coordinates = this->extractTsGuess();
        atoms.setPositions(coordinates);
        _calculator.modifyPositions(coordinates);
        return cycle;
      }
      throw Core::UnsuccessfulCalculationException("Calculation in NT optimization failed: " +
                                                   boost::current_exception_diagnostic_information());
    }
    double value = results.get<Property::Energy>();
    auto gradients = results.get<Property::Gradients>();
    this->triggerObservers(cycle, value, Eigen::Map<const Eigen::VectorXd>(coordinates.data(), nAtoms * 3));
    // Evaluate additional force
    this->updateGradients(atoms, value, gradients, true);
    _values.push_back(value);
    _trajectory.push_back(coordinates);
    // Check convergence
    if (this->convergedOptimization(atoms)) {
      coordinates = this->extractTsGuess();
      atoms.setPositions(coordinates);
      _calculator.modifyPositions(coordinates);
      return cycle;
    }
    // Update positions (SD)
    this->updateCoordinates(coordinates, atoms, gradients);
    atoms.setPositions(coordinates);
    _calculator.modifyPositions(coordinates);
  }
  return cycle;
}

void NtOptimizer::sanityCheck(const AtomCollection& atoms) const {
  if (lhsList.empty() || rhsList.empty()) {
    throw std::logic_error("Called NT optimization without atoms to apply a force to.");
  }
  // check for negative indices
  if (std::any_of(lhsList.begin(), lhsList.end(), [&](int i) { return i < 0; })) {
    throw std::logic_error(
        "At least one of the given indices in the 'nt_lhs_list' is negative, which is not possible.");
  }
  if (std::any_of(rhsList.begin(), rhsList.end(), [&](int i) { return i < 0; })) {
    throw std::logic_error(
        "At least one of the given indices in the 'nt_rhs_list' is negative, which is not possible.");
  }
  // check for too large indices
  const int nAtoms = atoms.size();
  for (const auto i : lhsList) {
    if (i >= nAtoms) {
      throw std::logic_error("Index '" + std::to_string(i) + "' in 'nt_lhs_list' too large for " +
                             std::to_string(nAtoms) + " atoms.");
    }
  }
  for (const auto i : rhsList) {
    if (i >= nAtoms) {
      throw std::logic_error("Index '" + std::to_string(i) + "' in 'nt_rhs_list' too large for " +
                             std::to_string(nAtoms) + " atoms.");
    }
  }
  // check if there is an identical index on both sides
  for (const auto lhs : lhsList) {
    if (std::find(rhsList.begin(), rhsList.end(), lhs) != rhsList.end()) {
      throw std::logic_error("The index " + std::to_string(lhs) + " is present in lhs and rhs, which is not valid.");
    }
  }
  // check if there is an overlap of reaction indices and constrained atoms and too large indices of restrained atoms
  if (!this->fixedAtoms.empty()) {
    for (const auto& a : fixedAtoms) {
      if (a >= nAtoms) {
        throw std::logic_error("Constrained atom index " + std::to_string(a) + " is too large for " +
                               std::to_string(nAtoms) + " atoms.");
      }
      std::vector<int> movableReactionIndicesList;
      if (this->movableSide == "both") {
        movableReactionIndicesList = lhsList;
        movableReactionIndicesList.insert(movableReactionIndicesList.end(), rhsList.begin(), rhsList.end());
      }
      else if (this->movableSide == "lhs") {
        movableReactionIndicesList = lhsList;
      }
      else if (this->movableSide == "rhs") {
        movableReactionIndicesList = rhsList;
      }
      else {
        // should be handled by settings
        throw std::logic_error("Unknown input " + movableSide + " for " + NtOptimizer::ntMovableSide);
      }
      if (std::find(movableReactionIndicesList.begin(), movableReactionIndicesList.end(), a) !=
          movableReactionIndicesList.end()) {
        throw std::logic_error("The atom index " + std::to_string(a) +
                               " was specified to be constrained although "
                               "it is within a movable reaction side. You can change '" +
                               NtOptimizer::ntMovableSide + "' if you want to constrain atoms.");
      }
    }
  }
}

void NtOptimizer::updateGradients(const AtomCollection& atoms, const double& /* energy */,
                                  GradientCollection& gradients, bool addForce) const {
  // define reaction coordinate based on LHS and RHS list
  GradientCollection reactionCoordinate(gradients);
  reactionCoordinate.setZero();
  Displacement c2c = this->centerToCenterVector(atoms.getPositions());
  c2c /= c2c.norm();
  for (const auto l : lhsList) {
    reactionCoordinate.row(l) = -c2c;
  }
  for (const auto r : rhsList) {
    reactionCoordinate.row(r) = c2c;
  }
  /* Update gradients */
  // remove gradient along reaction coordinate
  for (const auto l : lhsList) {
    if (lhsList.size() > 1) {
      gradients.row(l) -= (gradients.row(l).cwiseProduct(reactionCoordinate.row(l)).sum()) * reactionCoordinate.row(l);
    }
    else {
      gradients.row(l).setZero();
    }
    // replace gradient along reaction coordinate with fixed force only for lhs
    if (addForce && this->movableSide == "lhs") {
      double factor = (attractive) ? 1.0 : -1.0;
      gradients.row(l) += factor * totalForceNorm * reactionCoordinate.row(l);
    }
  }
  for (const auto r : rhsList) {
    if (rhsList.size() > 1) {
      gradients.row(r) -= (gradients.row(r).cwiseProduct(reactionCoordinate.row(r)).sum()) * reactionCoordinate.row(r);
    }
    else {
      gradients.row(r).setZero();
    }
    // replace gradient along reaction coordinate with fixed force only for rhs
    if (addForce && this->movableSide == "rhs") {
      double factor = (attractive) ? 1.0 : -1.0;
      gradients.row(r) += factor * totalForceNorm * reactionCoordinate.row(r);
    }
  }
  // replace gradient along reaction coordinate with fixed force for both sides
  if (addForce && this->movableSide == "both") {
    double factor = (attractive) ? 0.5 : -0.5;
    gradients += factor * totalForceNorm * reactionCoordinate;
  }
  // Apply Cartesian constraints
  if (!this->fixedAtoms.empty()) {
    for (const auto& a : fixedAtoms) {
      gradients.row(a).setZero();
    }
  }
}

Displacement NtOptimizer::centerToCenterVector(const PositionCollection& positions) const {
  Position lhsCenter = Position::Zero();
  Position rhsCenter = Position::Zero();
  for (const auto l : lhsList) {
    lhsCenter += positions.row(l);
  }
  lhsCenter.array() /= static_cast<double>(lhsList.size());
  for (const auto r : rhsList) {
    rhsCenter += positions.row(r);
  }
  rhsCenter.array() /= static_cast<double>(rhsList.size());
  Displacement c2c = rhsCenter - lhsCenter;
  return c2c;
}

bool NtOptimizer::convergedOptimization(const AtomCollection& atoms) const {
  const auto& positions = atoms.getPositions();
  double c2cNorm = this->centerToCenterVector(positions).norm();
  if (this->attractive) {
    if (c2cNorm < this->check.attractiveStop) {
      return true;
    }
    for (const auto& l : this->lhsList) {
      for (const auto& r : this->rhsList) {
        Eigen::Vector3d dir = positions.row(l) - positions.row(r);
        const double dist = dir.norm();
        const double r12cov =
            ElementInfo::covalentRadius(atoms.getElement(l)) + ElementInfo::covalentRadius(atoms.getElement(r));
        if (dist < this->check.attractiveStop * r12cov) {
          return true;
        }
      }
    }
    return false;
  }
  bool done = true;
  for (const auto& l : this->lhsList) {
    for (const auto& r : this->rhsList) {
      Eigen::Vector3d dir = positions.row(l) - positions.row(r);
      const double dist = dir.norm();
      const double r12cov =
          ElementInfo::covalentRadius(atoms.getElement(l)) + ElementInfo::covalentRadius(atoms.getElement(r));
      if (dist < this->check.repulsiveStop * r12cov) {
        done = false;
        break;
      }
    }
  }
  if (c2cNorm <= this->check.repulsiveStop) {
    done = false;
  }
  return done;
}

PositionCollection NtOptimizer::extractTsGuess() const {
  // Apply a Savitzky-Golay filter (width 5) and take the 1st derivative
  std::vector<double> filteredValues(_values);
  std::vector<double> filteredGradients(_values.size());
  for (int i = 0; i < this->filterPasses; i++) {
    // prepend and post pend data
    std::vector<double> tmp;
    tmp.reserve(filteredValues.size() + 4);
    tmp.push_back(filteredValues[0]);
    tmp.push_back(filteredValues[0]);
    tmp.insert(tmp.begin() + 2, filteredValues.begin(), filteredValues.end());
    tmp.push_back(filteredValues[filteredValues.size() - 1]);
    tmp.push_back(filteredValues[filteredValues.size() - 1]);
    for (unsigned int j = 2; j < filteredValues.size() + 2; j++) {
      const double a0 = (-3.0 * tmp[j - 2] + 12.0 * tmp[j - 1] + 17.0 * tmp[j] + 12.0 * tmp[j + 1] - 3.0 * tmp[j + 2]) / 35.0;
      const double a1 = (tmp[j - 2] - 8.0 * tmp[j - 1] + 8.0 * tmp[j + 1] - 1.0 * tmp[j + 2]) / 12.0;
      filteredValues[j - 2] = a0;
      filteredGradients[j - 2] = a1;
    }
  }

  // Find all maxima of energy curve as indicated by a plus to minus zero pass (read from the left)
  // Search from the back if the force is attractive
  std::vector<int> maximaList;
  if (this->attractive) {
    for (int i = static_cast<int>(filteredGradients.size()) - 2; i > 0; i--) {
      if (filteredGradients[i] >= 0.0 && filteredGradients[i + 1] < 0.0) {
        maximaList.push_back((fabs(filteredGradients[i]) < fabs(filteredGradients[i + 1])) ? i : i + 1);
      }
    }
  }
  else {
    const int N = static_cast<int>(_values.size()) - 1;
    for (int i = 0; i < N; i++) {
      if (filteredGradients[i + 1] <= 0.0 && filteredGradients[i] > 0.0) {
        maximaList.push_back((fabs(filteredGradients[i]) < fabs(filteredGradients[i + 1])) ? i : i + 1);
      }
    }
  }

  if (maximaList.empty()) {
    throw std::runtime_error("No transition state guess was found in Newton Trajectory scan.");
  }
  // Extract TS guess from point with highest energy
  double maxValue = -std::numeric_limits<double>::max();
  int maxIndex = -1;
  for (auto& i : maximaList) {
    if (_values[i] > maxValue) {
      maxIndex = i;
      maxValue = _values[i];
    }
  }

  return _trajectory[maxIndex];
}

void NtOptimizer::updateCoordinates(PositionCollection& coordinates, const AtomCollection& atoms,
                                    const GradientCollection& gradients) const {
  // transform coordinates and gradients if transformation was specified
  if (this->coordinateSystem == CoordinateSystem::Internal) {
    try {
      auto transformation = std::make_shared<InternalCoordinates>(atoms);
      auto internalCoordinates = transformation->coordinatesToInternal(coordinates);
      auto internalGradients = transformation->gradientsToInternal(gradients);
      internalCoordinates -= optimizer.factor * internalGradients;
      coordinates = transformation->coordinatesToCartesian(internalCoordinates);
    }
    catch (const InternalCoordinatesException& e) {
      // if true internals fail, fall back to Cartesians without rotation and translation
      auto transformation = std::make_shared<InternalCoordinates>(atoms, true);
      auto internalCoordinates = transformation->coordinatesToInternal(coordinates);
      auto internalGradients = transformation->gradientsToInternal(gradients);
      internalCoordinates -= optimizer.factor * internalGradients;
      coordinates = transformation->coordinatesToCartesian(internalCoordinates);
    }
  }
  else if (this->coordinateSystem == CoordinateSystem::CartesianWithoutRotTrans) {
    auto transformation = std::make_shared<InternalCoordinates>(atoms, true);
    auto internalCoordinates = transformation->coordinatesToInternal(coordinates);
    auto internalGradients = transformation->gradientsToInternal(gradients);
    internalCoordinates -= optimizer.factor * internalGradients;
    coordinates = transformation->coordinatesToCartesian(internalCoordinates);
  }
  else if (this->coordinateSystem == CoordinateSystem::Cartesian) {
    coordinates -= optimizer.factor * gradients;
  }
  else {
    throw std::runtime_error("Unknown coordinate system, please check your '" + std::string(ntCoordinateSystemKey) + "' input.");
  }
}

void NtOptimizer::addSettingsDescriptors(UniversalSettings::DescriptorCollection& /* collection */) const {
  // Not implemented yet
  throw std::runtime_error("You reached a function in the NTOptimizer that should not be called.");
}

void NtOptimizer::setSettings(const Settings& settings) {
  // optimizer.applySettings(settings);
  if (!settings.valid()) {
    settings.throwIncorrectSettings();
  }
  this->optimizer.factor = settings.getDouble(NtOptimizer::ntSdFactorKey);
  this->check.maxIter = settings.getInt(NtOptimizer::ntMaxIterKey);
  this->check.repulsiveStop = settings.getDouble(NtOptimizer::ntRepulsiveStopKey);
  this->check.attractiveStop = settings.getDouble(NtOptimizer::ntAttractiveStopKey);
  this->rhsList = settings.getIntList(NtOptimizer::ntRHSListKey);
  this->lhsList = settings.getIntList(NtOptimizer::ntLHSListKey);
  this->attractive = settings.getBool(NtOptimizer::ntAttractiveKey);
  this->totalForceNorm = settings.getDouble(NtOptimizer::ntTotalForceNormKey);
  this->coordinateSystem =
      CoordinateSystemInterpreter::getCoordinateSystemFromString(settings.getString(NtOptimizer::ntCoordinateSystemKey));
  this->useMicroCycles = settings.getBool(NtOptimizer::ntUseMicroCycles);
  this->fixedNumberOfMicroCycles = settings.getBool(NtOptimizer::ntFixedNumberOfMicroCycles);
  this->numberOfMicroCycles = settings.getInt(NtOptimizer::ntNumberOfMicroCycles);
  this->filterPasses = settings.getInt(NtOptimizer::ntFilterPasses);
  this->fixedAtoms = settings.getIntList(NtOptimizer::ntFixedAtomsKey);
  this->movableSide = settings.getString(NtOptimizer::ntMovableSide);

  // Check whether constraints and coordinate transformations are both switched on:
  if (!this->fixedAtoms.empty() && this->coordinateSystem != CoordinateSystem::Cartesian) {
    throw std::logic_error("Cartesian constraints cannot be set when using coordinate transformations! Set "
                           "'nt_coordinate_system' to 'cartesian'.");
  }
}

void NtOptimizer::applySettings(const Settings& settings) {
  this->setSettings(settings);
}

Settings NtOptimizer::getSettings() const {
  return NtOptimizerSettings(*this);
}

} // namespace Utils
} // namespace Scine


//
//  electron_optimisation.cpp
//  indigo-bondorder
//
//  Created by Welsh, Ivan on 12/09/17.
//  Copyright © 2017 Allison Group. All rights reserved.
//

#include <algorithm>
#include <climits>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

#include <boost/algorithm/string.hpp>

#include "algorithm/electron_optimisation.hpp"
#include "classes/atom.hpp"
#include "classes/electron_graph.hpp"
#include "classes/molecular_graph.hpp"
#include "algorithm/formalbonds/astar.hpp"
#include "algorithm/formalbonds/fpt.hpp"
#include "algorithm/formalbonds/local_optimisation.hpp"
#include "classes/periodictable.hpp"
#include "utils/filereader.hpp"
#include "utils/options.hpp"

namespace indigo_bondorder {
  enum class SortOrder {
    F_ONE,
    CL_ONE,
    BR_ONE,
    O_ONE,
    S_ONE,
    O_TWO,
    S_TWO,
    C_TWO_N_ONE,
    C_TWO_C_TWO,
    C_THREE_C_THREE,
    N_ONE,
    P_ONE,
    N_TWO,
    P_TWO,
    N_THREE,
    P_THREE,
    C_THREE_O_ONE,
    O_ONE_S_FOUR,
    C_THREE_S_ONE,
    O_ONE_P_FOUR,
    C_THREE_N_TWO,
    N_TWO_N_TWO,
    N_THREE_O_ONE,
    UNDEFINED
  };
}

using namespace indigo_bondorder;
typedef Options::AssignElectrons opt_;

ElectronOpt::ElectronOpt()
: electronsToAdd_(0), molGraph_(std::make_shared<MolecularGraph>()),
elnGraph_(std::make_shared<ElectronGraph>()) { }

ElectronOpt::ElectronOpt(std::shared_ptr<MolecularGraph> G)
: electronsToAdd_(0), molGraph_(G),
elnGraph_(std::make_shared<ElectronGraph>()) { }

void ElectronOpt::SetMolecularGraph(std::shared_ptr<MolecularGraph> G) {
  molGraph_ = G;
  elnGraph_.reset(new ElectronGraph(*G));
}

size_t ElectronOpt::Run() {
  possibleLocations_.clear();
  if (scores_.empty()) LoadScores();

  switch (opt_::ALGORITHM) {
    case opt_::Algorithm::LOCAL_OPTIMISATION:
      algo_.reset(new algorithm::LocalOptimisation(this));
      break;
    case opt_::Algorithm::ASTAR:
      algo_.reset(new algorithm::AStarOptimisation(this));
      break;
    case opt_::Algorithm::FPT:
      algo_.reset(new algorithm::FPTOptimisation(this));
      break;

    default:
      throw std::runtime_error("Unsupported optimisation algorithm");
      break;
  }
  SetMolecularGraph(molGraph_);
  DetermineElectronsToAdd();
  DeterminePotentialElectronLocations();
  if (opt_::ALGORITHM == opt_::Algorithm::ASTAR) SortPotentialLocations();
  algo_->PopulateMVP2EV();
  algo_->Run();
  size_t res = algo_->GetResultCount();
  finalScore_ = algo_->GetResultEnergy();
  return res;
}

bool ElectronOpt::ApplyElectronAssigment(Uint i) {
  return algo_->ApplyElectronAssignment(i);
}

void ElectronOpt::DetermineElectronsToAdd() {
  electronsToAdd_ = -molGraph_->GetTotalCharge();
  MolVertIterPair vs = molGraph_->GetVertices();
  for (MolVertexIter v = vs.first; v != vs.second; ++v)
    electronsToAdd_ += molGraph_->GetProperties(*v)->atom->GetElement()->GetValenceElectronCount();
  // All bonds should have order of at least 1
  // Preplace code
  ElnVertIterPair elnvs = elnGraph_->GetVertices();
  for (; elnvs.first != elnvs.second; ++elnvs.first)
    electronsToAdd_ -= elnGraph_->GetProperties(*elnvs.first)->pre_placed;
  //electronsToAdd_ -= 2 * molGraph_->NumEdges();
  // End Preplace code

  if (opt_::AUTO_USE_ELECTRON_PAIRS && electronsToAdd_ % 2)
    opt_::USE_ELECTRON_PAIRS = false;
  else if (opt_::AUTO_USE_ELECTRON_PAIRS)
    opt_::USE_ELECTRON_PAIRS = true;

  if (opt_::USE_ELECTRON_PAIRS && electronsToAdd_ % 2)
    throw std::runtime_error("Unable to handle odd number of electrons when using electron pairs.");
  else if (opt_::USE_ELECTRON_PAIRS)
    electronsToAdd_ /= 2;
}

void ElectronOpt::DeterminePotentialElectronLocations() {
  MolVertIterPair vs = molGraph_->GetVertices();
  for (MolVertexIter v = vs.first; v != vs.second; ++v) {
    int8_t octet;
    if (molGraph_->Degree(*v) > 2)
      octet = molGraph_->GetProperties(*v)->atom->GetElement()->GetHypervalentOctet();
    else
      octet = molGraph_->GetProperties(*v)->atom->GetElement()->GetOctet();

    int8_t bondedElectrons = 2 * molGraph_->Degree(*v);
    int8_t missingElectrons = octet - bondedElectrons;

    MolVertPair id = std::make_pair(*v, *v);
    // Preplace code
    ElnVertex ev = elnGraph_->GetVertex(id);
    missingElectrons -= elnGraph_->GetProperties(ev)->pre_placed;
    // End Preplace code

    while (missingElectrons > 0) {
      possibleLocations_.push_back(id);
      if (opt_::USE_ELECTRON_PAIRS)
        missingElectrons -= 2;
      else
        missingElectrons -= 1;
    }
  }

  MolEdgeIterPair es = molGraph_->GetEdges();
  for (MolEdgeIter e = es.first; e != es.second; ++e) {
    MolVertex u = molGraph_->GetSource(*e);
    MolVertex v = molGraph_->GetTarget(*e);
    if (u > v) {
      MolVertex tmp = u;
      u = v;
      v = tmp;
    }
    int8_t u_oct, v_oct, u_bond, v_bond, u_miss, v_miss;
    if (molGraph_->Degree(u) > 2)
      u_oct = molGraph_->GetProperties(u)->atom->GetElement()->GetHypervalentOctet();
    else
      u_oct = molGraph_->GetProperties(u)->atom->GetElement()->GetOctet();
    if (molGraph_->Degree(v) > 2)
      v_oct = molGraph_->GetProperties(v)->atom->GetElement()->GetHypervalentOctet();
    else
      v_oct = molGraph_->GetProperties(v)->atom->GetElement()->GetOctet();
    u_bond = 2 * molGraph_->Degree(u);
    v_bond = 2 * molGraph_->Degree(v);
    u_miss = u_oct - u_bond;
    v_miss = v_oct - v_bond;
    uint8_t order = 1;
    MolVertPair id = std::make_pair(u, v);
    // TODO: Check energy tables for available bond orders
    while (u_miss > 0 && v_miss > 0 && order <= opt_::MAXIMUM_BOND_ORDER) {
      possibleLocations_.push_back(id);
      if (!opt_::USE_ELECTRON_PAIRS)
        possibleLocations_.push_back(id);
      u_miss -= 2;
      v_miss -= 2;
      order++;
    }
  }
}

void ElectronOpt::LoadScores() {
  PeriodicTable_p pt = PeriodicTable::GetInstance();
  if (Options::DATA_DIRECTORY.back() != '/') {
    Options::DATA_DIRECTORY.append("/");
  }
  String atmFile = Options::DATA_DIRECTORY + opt_::ATOM_ENERGY_FILE;
  utils::FileReader at(atmFile);
  String bndFile = Options::DATA_DIRECTORY + opt_::BOND_ENERGY_FILE;
  utils::FileReader bn(bndFile);
  std::vector<std::string> at_items, bn_items;
  at.GetAllItems(at_items);
  bn.GetAllItems(bn_items);

  int64_t global_min = LLONG_MAX;
  std::map<String, int64_t> la_mins;
  std::map<std::pair<String, String>, int64_t> lb_mins;

  // Convert atom energies to ints
#define NUM_DECIMAL_PLACE 5
  for (size_t i = 2; i < at_items.size(); i += 3) {
    std::string lead, tail;
    size_t sep_pos = at_items[i].find_first_of('.');
    if (sep_pos == std::string::npos)
      throw std::invalid_argument(at_items[i] + std::string(" is not a decimal value."));
    lead = at_items[i].substr(0, sep_pos);
    tail = at_items[i].substr(sep_pos + 1, NUM_DECIMAL_PLACE);
    while (tail.size() < NUM_DECIMAL_PLACE) tail.append("0");
    int64_t score;
    try {
      score = std::stoll(lead + tail);
    } catch (const std::invalid_argument& e) {
      throw std::invalid_argument(at_items[i] + std::string(" is not a decimal value."));
    }
    if (la_mins.find(at_items[i-2]) == la_mins.end()) {
      la_mins.emplace(at_items[i-2], score);
    } else if (la_mins.at(at_items[i-2]) > score) {
      la_mins.at(at_items[i-2]) = score;
    }
    if (score < global_min) global_min = score;
    at_items[i] = lead + tail;
  }

  // Convert bond energies to ints
  for (size_t i = 3; i < bn_items.size(); i += 4) {
    std::string lead, tail;
    size_t sep_pos = bn_items[i].find_first_of('.');
    if (sep_pos == std::string::npos)
      throw std::invalid_argument(bn_items[i] + std::string(" is not a decimal value."));
    lead = bn_items[i].substr(0, sep_pos);
    tail = bn_items[i].substr(sep_pos + 1, NUM_DECIMAL_PLACE);
    while (tail.size() < NUM_DECIMAL_PLACE) tail.append("0");
    int64_t score;
    try {
      score = std::stoll(lead + tail);
    } catch (const std::invalid_argument& e) {
      throw std::invalid_argument(bn_items[i] + std::string(" is not a decimal value."));
    }
    if (score < global_min) global_min = score;

    String atomA = bn_items[i-3];
    String atomB = bn_items[i-2];
    size_t pos_sign_a, pos_sign_b;
    pos_sign_a = atomA.find_first_of('+');
    pos_sign_b = atomB.find_first_of('+');

    if (pos_sign_a != std::string::npos) {
      atomA = atomA.substr(0, pos_sign_a);
    } else {
      pos_sign_a = atomA.find_first_of('-');
      if (pos_sign_a != std::string::npos) atomA = atomA.substr(0, pos_sign_a);
    }

    if (pos_sign_b != std::string::npos) {
      atomB = atomB.substr(0, pos_sign_b);
    } else {
      pos_sign_b = atomB.find_first_of('-');
      if (pos_sign_b != std::string::npos) atomB = atomB.substr(0, pos_sign_b);
    }

    std::pair<String, String> b1 = std::make_pair(atomA, atomB);
    std::pair<String, String> b2 = std::make_pair(atomB, atomA);
    if (lb_mins.find(b1) == lb_mins.end()) {
      lb_mins.emplace(b1, score);
    } else if (lb_mins.at(b1) > score) {
      lb_mins.at(b1) = score;
    }
    if (lb_mins.find(b2) == lb_mins.end()) {
      lb_mins.emplace(b2, score);
    } else if (lb_mins.at(b2) > score) {
      lb_mins.at(b2) = score;
    }
    bn_items[i] = lead + tail;
  }

  // Load atom energies
  for (size_t i = 0; i < at_items.size(); i += 3) {
    uint8_t Z = pt->GetElement(at_items[i])->GetAtomicNumber();
    if (Z == 0) throw std::invalid_argument(at_items[i] + std::string(" is not a valid atomic symbol."));
    int fc;
    try {
      fc = std::stoi(at_items[i + 1]);
    } catch (const std::invalid_argument& e) {
      throw std::invalid_argument(at_items[i+1] + std::string(" is not an integer value."));
    }
    uint32_t k = Z + (std::abs(fc) << 8);
    if (fc < 0) k += (1 << 15);
    Score val = (Score)(std::stoll(at_items[i + 2]) - la_mins.at(at_items[i]));
    scores_.emplace(k, val);
  }

  // Load bond energies
  for (size_t i = 0; i < bn_items.size(); i += 4) {
    char a_sign, b_sign;
    size_t pos_sign_a, pos_sign_b;
    pos_sign_a = bn_items[i].find_first_of('+');
    pos_sign_b = bn_items[i + 1].find_first_of('+');

    if (pos_sign_a != std::string::npos) {
      a_sign = '+';
      bn_items[i] = bn_items[i].substr(0, pos_sign_a);
    } else {
      pos_sign_a = bn_items[i].find_first_of('-');
      if (pos_sign_a != std::string::npos) {
        a_sign = '-';
        bn_items[i] = bn_items[i].substr(0, pos_sign_a);
      } else a_sign = '0';
    }

    if (pos_sign_b != std::string::npos) {
      b_sign = '+';
      bn_items[i+1] = bn_items[i+1].substr(0, pos_sign_b);
    } else {
      pos_sign_b = bn_items[i+1].find_first_of('-');
      if (pos_sign_b != std::string::npos) {
        b_sign = '-';
        bn_items[i+1] = bn_items[i+1].substr(0, pos_sign_b);
      } else b_sign = '0';
    }

    uint8_t Za = pt->GetElement(bn_items[i])->GetAtomicNumber();
    uint8_t Zb = pt->GetElement(bn_items[i+1])->GetAtomicNumber();
    if (Za == 0) throw std::invalid_argument(at_items[i] + std::string(" is not a valid atomic symbol."));
    if (Zb == 0) throw std::invalid_argument(at_items[i+1] + std::string(" is not a valid atomic symbol."));

    int order;
    try {
      order = std::stoi(bn_items[i + 2]);
    } catch (const std::invalid_argument& e) {
      throw std::invalid_argument(bn_items[i+2] + std::string(" is not an integer value."));
    }
    if (order < 1) throw std::invalid_argument(bn_items[i+2] + std::string(" is an invalid bond order."));

    uint32_t k1 = Za + (Zb << 8) + ((order * 2) << 20);
    uint32_t k2 = Zb + (Za << 8) + ((order * 2) << 20);

    if (a_sign == '+') {
      k1 += (1 << 16);
      k2 += (1 << 18);
    } else if (a_sign == '-') {
      k1 += (2 << 16);
      k2 += (2 << 18);
    }

    if (b_sign == '+') {
      k2 += (1 << 16);
      k1 += (1 << 18);
    } else if (b_sign == '-') {
      k2 += (2 << 16);
      k1 += (2 << 18);
    }
    std::pair<String, String> bondType = std::make_pair(bn_items[i], bn_items[i+1]);
    Score val = (Score)(std::stoll(bn_items[i + 3]) - lb_mins.at(bondType));
//    Score val = (Score)(std::stoll(bn_items[i + 3]) - global_min);
    scores_.emplace(k1, val);
    if (k1 != k2) scores_.emplace(k2, val);
  }
}

void ElectronOpt::SortPotentialLocations() {
  std::vector<ElnVertProp*> sortedUniques;
  sortedUniques.reserve(possibleLocations_.size());
  for (MolVertPair& vp : possibleLocations_) {
    ElnVertProp* p = elnGraph_->GetProperties(elnGraph_->GetVertex(vp));
    if (vp.first == vp.second) {
      MolVertProp* prop = molGraph_->GetProperties(vp.first);
      switch (prop->atom->GetElement()->GetAtomicNumber()) {
        case 7:  // Nitrogen
          switch (molGraph_->Degree(vp.first)) {
            case 1: p->sort_score = SortOrder::N_ONE; break;
            case 2: p->sort_score = SortOrder::N_TWO; break;
            case 3: p->sort_score = SortOrder::N_THREE; break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        case 8:  // Oxygen
          switch (molGraph_->Degree(vp.first)) {
            case 1: p->sort_score = SortOrder::O_ONE; break;
            case 2: p->sort_score = SortOrder::O_TWO; break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        case 9:  // Fluorine
          switch (molGraph_->Degree(vp.first)) {
            case 1: p->sort_score = SortOrder::F_ONE; break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        case 15:  // Phosphorus
          switch (molGraph_->Degree(vp.first)) {
            case 1: p->sort_score = SortOrder::P_ONE; break;
            case 2: p->sort_score = SortOrder::P_TWO; break;
            case 3: p->sort_score = SortOrder::P_THREE; break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        case 16:  // Sulfur
          switch (molGraph_->Degree(vp.first)) {
            case 1: p->sort_score = SortOrder::S_ONE; break;
            case 2: p->sort_score = SortOrder::S_ONE; break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        case 17:  // Chlorine
          switch (molGraph_->Degree(vp.first)) {
            case 1: p->sort_score = SortOrder::CL_ONE; break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        case 35:
          switch (molGraph_->Degree(vp.first)) {
            case 1: p->sort_score = SortOrder::BR_ONE; break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        default: p->sort_score = SortOrder::UNDEFINED; break;
      }
    } else {
      MolVertProp* propa = molGraph_->GetProperties(vp.first);
      MolVertProp* propb = molGraph_->GetProperties(vp.second);
      switch (propa->atom->GetElement()->GetAtomicNumber()) {
        case 6:
          switch (propb->atom->GetElement()->GetAtomicNumber()) {
            case 6:
              if (molGraph_->Degree(vp.first) == 2
                  && molGraph_->Degree(vp.second) == 2)
                p->sort_score = SortOrder::C_TWO_C_TWO;
              else if (molGraph_->Degree(vp.first) == 3
                    && molGraph_->Degree(vp.second) == 3)
                  p->sort_score = SortOrder::C_THREE_C_THREE;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            case 7:
              if (molGraph_->Degree(vp.first) == 2
                  && molGraph_->Degree(vp.second) == 1)
                p->sort_score = SortOrder::C_TWO_N_ONE;
              else if (molGraph_->Degree(vp.first) == 3
                       && molGraph_->Degree(vp.second) == 2)
                p->sort_score = SortOrder::C_THREE_N_TWO;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            case 8:
              if (molGraph_->Degree(vp.first) == 3
                  && molGraph_->Degree(vp.second) == 1)
                p->sort_score = SortOrder::C_THREE_O_ONE;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            case 16:
              if (molGraph_->Degree(vp.first) == 3
                  && molGraph_->Degree(vp.second) == 1)
                p->sort_score = SortOrder::C_THREE_S_ONE;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        case 7:
          switch (propb->atom->GetElement()->GetAtomicNumber()) {
            case 6:
              if (molGraph_->Degree(vp.first) == 1
                  && molGraph_->Degree(vp.second) == 2)
                p->sort_score = SortOrder::C_TWO_N_ONE;
              else if (molGraph_->Degree(vp.first) == 2
                       && molGraph_->Degree(vp.second) == 3)
                p->sort_score = SortOrder::C_THREE_N_TWO;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            case 7:
              if (molGraph_->Degree(vp.first) == 2
                  && molGraph_->Degree(vp.second) == 2)
                p->sort_score = SortOrder::N_TWO_N_TWO;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            case 8:
              if (molGraph_->Degree(vp.first) == 3
                  && molGraph_->Degree(vp.second) == 1)
                p->sort_score = SortOrder::N_THREE_O_ONE;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        case 8:
          switch (propb->atom->GetElement()->GetAtomicNumber()) {
            case 6:
              if (molGraph_->Degree(vp.first) == 1
                  && molGraph_->Degree(vp.second) == 3)
                p->sort_score = SortOrder::C_THREE_O_ONE;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            case 7:
              if (molGraph_->Degree(vp.first) == 1
                  && molGraph_->Degree(vp.second) == 3)
                p->sort_score = SortOrder::N_THREE_O_ONE;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            case 15:
              if (molGraph_->Degree(vp.first) == 1
                  && molGraph_->Degree(vp.second) == 4)
                p->sort_score = SortOrder::O_ONE_P_FOUR;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            case 16:
              if (molGraph_->Degree(vp.first) == 1
                  && molGraph_->Degree(vp.second) == 4)
                p->sort_score = SortOrder::O_ONE_S_FOUR;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        case 15:
          switch (propb->atom->GetElement()->GetAtomicNumber()) {
            case 8:
              if (molGraph_->Degree(vp.first) == 4
                  && molGraph_->Degree(vp.second) == 1)
                p->sort_score = SortOrder::O_ONE_P_FOUR;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        case 16:
          switch (propb->atom->GetElement()->GetAtomicNumber()) {
            case 6:
              if (molGraph_->Degree(vp.first) == 1
                  && molGraph_->Degree(vp.second) == 3)
                p->sort_score = SortOrder::C_THREE_S_ONE;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            case 8:
              if (molGraph_->Degree(vp.first) == 4
                  && molGraph_->Degree(vp.second) == 1)
                p->sort_score = SortOrder::O_ONE_S_FOUR;
              else p->sort_score = SortOrder::UNDEFINED;
              break;
            default: p->sort_score = SortOrder::UNDEFINED; break;
          }
          break;
        default: p->sort_score = SortOrder::UNDEFINED; break;
      }
    }

    sortedUniques.push_back(p);
  }
  std::stable_sort(sortedUniques.begin(), sortedUniques.end(),
                   [](const ElnVertProp* lhs, const ElnVertProp* rhs) {
                     if (opt_::ALGORITHM == opt_::Algorithm::ASTAR)
                       return lhs->sort_score < rhs->sort_score;
                     else return lhs->sort_score > rhs->sort_score;
                   });

  for (unsigned int i = 0; i < sortedUniques.size(); ++i)
    possibleLocations_[i] = sortedUniques[i]->id;
}