// This file is part of the dune-hdd project:
//   http://users.dune-project.org/projects/dune-hdd
// Copyright holders: Felix Schindler
// License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)

#ifndef DUNE_HDD_LINEARELLIPTIC_PROBLEMS_ORS2016_HH
#define DUNE_HDD_LINEARELLIPTIC_PROBLEMS_ORS2016_HH

#include <iostream>
#include <vector>

#include <boost/numeric/conversion/cast.hpp>

#include <dune/stuff/functions/checkerboard.hh>
#include <dune/stuff/playground/functions/indicator.hh>

#include <dune/pymor/functions/default.hh>

#include "default.hh"

namespace Dune {
namespace HDD {
namespace LinearElliptic {
namespace Problems {
namespace internal {

template< class E, class D, class R >
class BatteryGeometry
{
  typedef Problems::Default< E, D, 3, R, 1 >                   ProblemType;
  typedef typename ProblemType::DomainType                     DomainType;
  typedef Stuff::LocalizableFunctionInterface< E, D, 3, R, 1 > NonparametricFunctionType;
  typedef typename ProblemType::DiffusionFactorType            DiffusionFactorType;

public:
  BatteryGeometry(const std::string& filename,
                  const DomainType& lower_left,
                  const DomainType& upper_right,
                  const Stuff::Common::FieldVector< size_t, 3 >& num_elements,
                  const std::string& separator_domain,
                  const std::string& name)
  {
    // Interpretation of the data as int:
    //    enum Subdomain
    //    {
    //      ANODE,      // 0
    //      CATHODE,    // 1
    //      CC_ANODE,   // 2
    //      CC_CATHODE, // 3
    //      ELECTROLYTE // 4
    //    };
    static_assert(sizeof(int) == 4, "This is not the correct architecture, sizeof(int) should be 4!");

    size_t num_entries = 1;
    for (size_t ii = 0; ii < 3; ++ii)
      num_entries *= num_elements[ii];
    std::vector< int > raw_data(num_entries);

    std::ifstream file(filename, std::ifstream::binary);
    if (!file)
      DUNE_THROW(IOError, "Could not open '" << filename << "'!");
    // check for the amount of data present in the file
    file.seekg(0, file.end);
    const auto length = file.tellg();
    if (length != boost::numeric_cast< decltype(length) >(raw_data.size()*sizeof(int)))
      DUNE_THROW(IOError,
                 "Given file '" << filename << "' has wrong size (should be " << raw_data.size()*sizeof(int) << ", is "
                 << length << ")!");
    // read the actual data
    file.seekg(0, file.beg);
    file.read((char *)(raw_data.data()), length);
    // check for the amount of read data
    const auto read_data = file.gcount();
    if (read_data != boost::numeric_cast< decltype(read_data) >(raw_data.size()*sizeof(int)))
      DUNE_THROW(IOError,
                 "Could not read the correct amount from given file '" << filename << "' (we would like to read "
                 << raw_data.size()*sizeof(int) << " and the file reported this many entries, but we could only read "
                 << read_data << "data)!");
    if (!file)
      DUNE_THROW(IOError, "Failed to read from file '" << filename << "'!");
    file.close();

    // create all but separator
    typedef Stuff::Functions::Checkerboard< E, D, 3, R, 1, 1 > PiecewiseConstantFunctionType;
    std::vector< typename PiecewiseConstantFunctionType::RangeType > data(num_entries);
    for (size_t ii = 0; ii < num_entries; ++ii)
      data[ii] = raw_data[ii];
    auto battery_function = std::make_shared< PiecewiseConstantFunctionType >(lower_left, upper_right, num_elements, data, name);

    // create separator
    separator_ = std::shared_ptr< Stuff::Functions::DomainIndicator< E, D, 3, R, 1 > >(
          Stuff::Functions::DomainIndicator< E, D, 3, R, 1 >::create(DSC::Configuration({"0.domain",        "0.value", "name"},
                                                                                {separator_domain,  "1",       "SEPARATOR"})));
    // create one component per battery part
    typedef Stuff::Functions::Constant< E, D, 3, R, 1, 1 >    ConstantFunctionType;
    typedef Stuff::Functions::LevelIndicator< E, D, 3, R, 1 > LevelIndicator;
    anode_      = std::make_shared< LevelIndicator >(battery_function, 0., 0., 1., "ANODE");
    cathode_    = std::make_shared< LevelIndicator >(battery_function, 1., 1., 1., "CATHODE");
    cc_anode_   = std::make_shared< LevelIndicator >(battery_function, 2., 2., 1., "CC_ANODE");
    cc_cathode_ = std::make_shared< LevelIndicator >(battery_function, 3., 3., 1., "CC_CATHODE");
    //   for the electrolyte, substract the seperator
    electrolyte_ = std::make_shared< LevelIndicator >(
                     Stuff::Functions::make_difference(battery_function,
                                                       Stuff::Functions::make_product(std::make_shared< ConstantFunctionType >(4.),
                                                                                separator_)),
                     4., 4., 1., "ELECTROLYTE");
    // create parametric diffusion factor
    battery_geometry_ = std::make_shared< Pymor::Functions::AffinelyDecomposableDefault< E, D, 3, R, 1 > >();
//    battery_geometry_->register_component(anode_,       new Pymor::ParameterFunctional("ANODE",       1, "ANODE[0]"));
//    battery_geometry_->register_component(cathode_,     new Pymor::ParameterFunctional("CATHODE",     1, "CATHODE[0]"));
//    battery_geometry_->register_component(cc_anode_,    new Pymor::ParameterFunctional("CC_ANODE",    1, "CC_ANODE[0]"));
//    battery_geometry_->register_component(cc_cathode_,  new Pymor::ParameterFunctional("CC_CATHODE",  1, "CC_CATHODE[0]"));
    battery_geometry_->register_component(electrolyte_, new Pymor::ParameterFunctional("ELECTROLYTE", 1, "ELECTROLYTE[0]"));
//    battery_geometry_->register_component(separator_,   new Pymor::ParameterFunctional("SEPARATOR",   1, "SEPARATOR[0]"));
    battery_geometry_->register_affine_part(
          Stuff::Functions::make_sum(Stuff::Functions::make_product(std::make_shared< ConstantFunctionType >(1.04),   anode_),
          Stuff::Functions::make_sum(Stuff::Functions::make_product(std::make_shared< ConstantFunctionType >(1.58),   cathode_),
          Stuff::Functions::make_sum(Stuff::Functions::make_product(std::make_shared< ConstantFunctionType >(238.),   cc_anode_),
          Stuff::Functions::make_sum(Stuff::Functions::make_product(std::make_shared< ConstantFunctionType >(398.),   cc_cathode_),
                                     Stuff::Functions::make_product(std::make_shared< ConstantFunctionType >(0.3344), separator_)))),
                                     "NON_ELECTROLYTE"));
  } // BatteryGeometry(...)

protected:
  std::shared_ptr< NonparametricFunctionType > anode_;
  std::shared_ptr< NonparametricFunctionType > cathode_;
  std::shared_ptr< NonparametricFunctionType > cc_anode_;
  std::shared_ptr< NonparametricFunctionType > cc_cathode_;
  std::shared_ptr< NonparametricFunctionType > electrolyte_;
  std::shared_ptr< NonparametricFunctionType > separator_;
  std::shared_ptr< Pymor::Functions::AffinelyDecomposableDefault< E, D, 3, R, 1 > > battery_geometry_;
}; // class BatteryGeometry

} // namespace internal

template< class E, class D, int d, class R, int r = 1 >
class ORS2016
  : public ProblemInterface< E, D, d, R, r >
{
  ORS2016() { static_assert(AlwaysFalse< E >::value, "Not available for these dimensions!"); }
};

template< class E, class D, class R >
class ORS2016< E, D, 3, R, 1 >
  : internal::BatteryGeometry< E, D, R >
  , public Problems::Default< E, D, 3, R, 1 >
{
  typedef internal::BatteryGeometry< E, D, R > DataType;
  typedef Problems::Default< E, D, 3, R, 1 > BaseType;
  typedef ORS2016< E, D, 3, R, 1 >           ThisType;
public:
  typedef typename BaseType::EntityType      EntityType;
  typedef typename BaseType::DomainFieldType DomainFieldType;
  static const unsigned int                  dimDomain = BaseType::dimDomain;
  typedef typename BaseType::DomainType      DomainType;
  typedef typename BaseType::RangeFieldType  RangeFieldType;
  static const unsigned int                  dimRange = BaseType::dimRange;
  using typename BaseType::FunctionType;
  using typename BaseType::DiffusionFactorType;
  using typename BaseType::DiffusionTensorType;

  static const bool available = true;

  static std::string static_id()
  {
    return BaseType::BaseType::static_id() + ".ORS2016";
  }

  static Stuff::Common::Configuration default_config(const std::string sub_name = "")
  {
    Stuff::Common::Configuration config;
    for (auto sub : {"diffusion_tensor", "dirichlet", "neumann"})
      config.add(BaseType::default_config().sub(sub), sub);
    config["type"] = static_id();
    config["diffusion_factor.filename"]    = "geometry";
    config["diffusion_factor.lower_left"]  = "[0.0 0.0 0.0]";
    config["diffusion_factor.upper_right"] = "[0.0184 0.008 0.008]";
    config["diffusion_factor.num_elements"] = "[46 20 20]";
    config["diffusion_factor.name"] = "battery_geometry";
    config["diffusion_factor.separator"] = "[0.0084 0.01; 0 0.008; 0 0.008]";
    config["force.value"] = "1";
    if (sub_name.empty())
      return config;
    else {
      Stuff::Common::Configuration tmp;
      tmp.add(config, sub_name);
      return tmp;
    }
  } // ... default_config(...)

  static std::unique_ptr< ThisType > create(const Stuff::Common::Configuration config = default_config(),
                                            const std::string sub_name = static_id())
  {
    const Stuff::Common::Configuration cfg = config.has_sub(sub_name) ? config.sub(sub_name) : config;
    const Stuff::Common::Configuration def_cfg = default_config();
    return DSC::make_unique< ThisType >(cfg.get("diffusion_factor.filename", def_cfg.get<std::string>("diffusion_factor.filename")),
                                        cfg.get("diffusion_factor.lower_left",
                                                def_cfg.get<DomainType>("diffusion_factor.lower_left")),
                                        cfg.get("diffusion_factor.upper_right",
                                                def_cfg.get<DomainType>("diffusion_factor.upper_right")),
                                        cfg.get("diffusion_factor.num_elements",
                                                def_cfg.get<Stuff::Common::FieldVector< size_t, 3 >>("diffusion_factor.num_elements")),
                                        cfg.get("diffusion_factor.separator",
                                                def_cfg.get<std::string>("diffusion_factor.separator")),
                                        cfg.get("diffusion_factor.name",
                                                def_cfg.get<std::string>("diffusion_factor.name")),
                                        cfg.get("force.value",
                                                def_cfg.get<RangeFieldType>("force.value")),
                                        BaseType::create_matrix_function("diffusion_tensor", cfg),
                                        BaseType::create_vector_function("dirichlet", cfg),
                                        BaseType::create_vector_function("neumann", cfg));
  } // ... create(...)

  ORS2016(const std::string& filename,
          const DomainType& lower_left,
          const DomainType& upper_right,
          const Stuff::Common::FieldVector< size_t, 3 >& num_elements,
          const std::string& separator,
          const std::string& name,
          const RangeFieldType& force_value,
          const std::shared_ptr< const DiffusionTensorType >& diff_ten,
          const std::shared_ptr< const FunctionType >& dir,
          const std::shared_ptr< const FunctionType >& neum)
    : DataType(filename, lower_left, upper_right, num_elements, separator, name)
    , BaseType(DataType::battery_geometry_,
               diff_ten,
               std::make_shared< Pymor::Functions::AffinelyDecomposableDefault< E, D, 3, R, 1 > >(
                  Stuff::Functions::make_product(std::make_shared< Stuff::Functions::Constant< E, D, 3, R, 1 > >(force_value),
                                                 Stuff::Functions::make_sum(DataType::anode_, DataType::cathode_),
                                                 "force")),
               dir,
               neum)
  {}
}; // class ORS2016< ..., 3, ... 1 >

} // namespace Problems
} // namespace LinearElliptic
} // namespace HDD
} // namespace Dune

#endif // DUNE_HDD_LINEARELLIPTIC_PROBLEMS_SPE10_HH


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2009 Ferdinando Ametrano

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

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

/*! \file ecb.hpp
    \brief European Central Bank reserve maintenance date functions
*/

#ifndef quantlib_ecb_hpp
#define quantlib_ecb_hpp

#include <ql/time/date.hpp>
#include <set>
#include <vector>

namespace QuantLib {

    //! European Central Bank reserve maintenance dates
    struct ECB {

        static const std::set<Date>& knownDates();
        static void addDate(const Date& d);
        static void removeDate(const Date& d);

        //! maintenance period start date in the given month/year
        static Date date(Month m,
                         Year y) { return nextDate(Date(1, m, y) - 1); }

        /*! returns the ECB date for the given ECB code
            (e.g. March xxth, 2013 for MAR10).

            \warning It raises an exception if the input
                     string is not an ECB code
        */
        static Date date(const std::string& ecbCode,
                         const Date& referenceDate = Date());

        /*! returns the ECB code for the given date
            (e.g. MAR10 for March xxth, 2010).

            \warning It raises an exception if the input
                     date is not an ECB date
        */
        static std::string code(const Date& ecbDate);

        //! next maintenance period start date following the given date
        static Date nextDate(const Date& d = Date());

        //! next maintenance period start date following the given ECB code
        static Date nextDate(const std::string& ecbCode,
                             const Date& referenceDate = Date()) {
            return nextDate(date(ecbCode, referenceDate));
        }

        //! next maintenance period start dates following the given date
        static std::vector<Date> nextDates(const Date& d = Date());

        //! next maintenance period start dates following the given code
        static std::vector<Date> nextDates(const std::string& ecbCode,
                                           const Date& referenceDate = Date()) {
            return nextDates(date(ecbCode, referenceDate));
        }

        /*! returns whether or not the given date is
            a maintenance period start date */
        static bool isECBdate(const Date& d) {
            Date date = nextDate(d-1);
            return d==date;
        }

        //! returns whether or not the given string is an ECB code
        static bool isECBcode(const std::string& in);

        //! next ECB code following the given date
        static std::string nextCode(const Date& d = Date()) {
            return code(nextDate(d));
        }

        //! next ECB code following the given code
        static std::string nextCode(const std::string& ecbCode);

    };

}

/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2009, 2011 Ferdinando Ametrano
 Copyright (C) 2015 Paolo Mazzocchi

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

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

#include <ql/settings.hpp>
#include <ql/utilities/dataparsers.hpp>
#if defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)) || (__GNUC__ > 4))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#endif
#include <boost/algorithm/string/case_conv.hpp>
#if defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)) || (__GNUC__ > 4))
#pragma GCC diagnostic pop
#endif
#include <algorithm>

using boost::algorithm::to_upper_copy;
using std::string;

namespace QuantLib {

    static std::set<Date> knownDateSet;

    inline const std::set<Date>& ECB::knownDates() {

        // one-off inizialization
        static const Date::serial_type knownDatesArray[] = {
              38371, 38391, 38420, 38455, 38483, 38511, 38546, 38574, 38602, 38637, 38665, 38692 // 2005
            , 38735, 38756, 38784, 38819, 38847, 38883, 38910, 38938, 38966, 39001, 39029, 39064 // 2006
            , 39099, 39127, 39155, 39190, 39217, 39246, 39274, 39302, 39337, 39365, 39400, 39428 // 2007
            , 39463, 39491, 39519, 39554, 39582, 39610, 39638, 39673, 39701, 39729, 39764, 39792 // 2008
            , 39834, 39855, 39883, 39911, 39946, 39974, 40002, 40037, 40065, 40100, 40128, 40155 // 2009
            , 40198, 40219, 40247, 40282, 40310, 40345, 40373, 40401, 40429, 40464, 40492, 40520 // 2010
            , 40562, 40583, 40611, 40646, 40674, 40709, 40737, 40765, 40800, 40828, 40856, 40891 // 2011
            // http://www.ecb.europa.eu/press/pr/date/2011/html/pr110520.en.html
            , 40926, 40954, 40982, 41010, 41038, 41073, 41101, 41129, 41164, 41192, 41227, 41255 // 2012
            , 41290, 41318, 41346, 41374, 41402, 41437, 41465, 41493, 41528, 41556, 41591, 41619 // 2013
            // http://www.ecb.europa.eu/press/pr/date/2013/html/pr130610.en.html
            , 41654, 41682, 41710, 41738, 41773, 41801, 41829, 41864, 41892, 41920, 41955, 41983 // 2014
            // http://www.ecb.europa.eu/press/pr/date/2014/html/pr140717_1.en.html
            , 42032, 42074, 42116, 42165, 42207, 42256, 42305, 42347// 2015
            // https://www.ecb.europa.eu/press/pr/date/2015/html/pr150622.en.html
            , 42396, 42445, 42487, 42529, 42578, 42627, 42669, 42718 // 2016
            // https://www.ecb.europa.eu/press/calendars/reserve/html/index.en.html
            , 42760, 42809, 42858, 42900, 42942, 42991, 43040, 43089 //2017
        };
        if (knownDateSet.empty()) {
            Size n = sizeof(knownDatesArray)/sizeof(Date::serial_type);
            for (Size i=0; i<n; ++i)
                knownDateSet.insert(Date(knownDatesArray[i]));
        }

        return knownDateSet;
    }

    inline void ECB::addDate(const Date& d) {
        knownDates(); // just to ensure inizialization
        knownDateSet.insert(d);
    }

    inline void ECB::removeDate(const Date& d) {
        knownDates(); // just to ensure inizialization
        knownDateSet.erase(d);
    }

    inline Date ECB::date(const string& ecbCode,
                   const Date& refDate) {

        QL_REQUIRE(isECBcode(ecbCode),
                   ecbCode << " is not a valid ECB code");

        string code = to_upper_copy(ecbCode);
        string monthString = code.substr(0, 3);
        Month m;
        if (monthString=="JAN")      m = January;
        else if (monthString=="FEB") m = February;
        else if (monthString=="MAR") m = March;
        else if (monthString=="APR") m = April;
        else if (monthString=="MAY") m = May;
        else if (monthString=="JUN") m = June;
        else if (monthString=="JUL") m = July;
        else if (monthString=="AUG") m = August;
        else if (monthString=="SEP") m = September;
        else if (monthString=="OCT") m = October;
        else if (monthString=="NOV") m = November;
        else if (monthString=="DEC") m = December;
        else QL_FAIL("not an ECB month (and it should have been)");

        // lexical_cast causes compilation errors with x64
        //Year y = boost::lexical_cast<Year>(code.substr(3, 2));

        Year y = io::to_integer(code.substr(3, 2));
        Date referenceDate = (refDate != Date() ?
                              refDate :
                              Date(Settings::instance().evaluationDate()));
        Year referenceYear = (referenceDate.year() % 100);
        y += referenceDate.year() - referenceYear;
        if (y<Date::minDate().year())
            return ECB::nextDate(Date::minDate());

        return ECB::nextDate(Date(1, m, y) - 1);
    }

    inline string ECB::code(const Date& ecbDate) {

        QL_REQUIRE(isECBdate(ecbDate),
                   ecbDate << " is not a valid ECB date");

        std::ostringstream ECBcode;
        unsigned int y = ecbDate.year() % 100;
        string padding;
        if (y < 10)
            padding = "0";
        switch(ecbDate.month()) {
          case January:
            ECBcode << "JAN" << padding << y;
            break;
          case February:
            ECBcode << "FEB" << padding << y;
            break;
          case March:
            ECBcode << "MAR" << padding << y;
            break;
          case April:
            ECBcode << "APR" << padding << y;
            break;
          case May:
            ECBcode << "MAY" << padding << y;
            break;
          case June:
            ECBcode << "JUN" << padding << y;
            break;
          case July:
            ECBcode << "JUL" << padding << y;
            break;
          case August:
            ECBcode << "AUG" << padding << y;
            break;
          case September:
            ECBcode << "SEP" << padding << y;
            break;
          case October:
            ECBcode << "OCT" << padding << y;
            break;
          case November:
            ECBcode << "NOV" << padding << y;
            break;
          case December:
            ECBcode << "DEC" << padding << y;
            break;
          default:
            QL_FAIL("not an ECB month (and it should have been)");
        }

        #if defined(QL_EXTRA_SAFETY_CHECKS)
        QL_ENSURE(isECBcode(ECBcode.str()),
                  "the result " << ECBcode.str() <<
                  " is an invalid ECB code");
        #endif
        return ECBcode.str();
    }

    inline Date ECB::nextDate(const Date& date) {
        Date d = (date == Date() ?
                  Settings::instance().evaluationDate() :
                  date);

        std::set<Date>::const_iterator i =
            std::upper_bound(knownDates().begin(), knownDates().end(), d);

        QL_REQUIRE(i!=knownDates().end(),
                   "ECB dates after " << *(--knownDates().end()) << " are unknown");
        return Date(*i);
    }

    inline std::vector<Date> ECB::nextDates(const Date& date) {
        Date d = (date == Date() ?
                  Settings::instance().evaluationDate() :
                  date);

        std::set<Date>::const_iterator i =
            std::upper_bound(knownDates().begin(), knownDates().end(), d);

        QL_REQUIRE(i!=knownDates().end(),
                   "ECB dates after " << *knownDates().end() << " are unknown");
        return std::vector<Date>(i, knownDates().end());
    }

    inline bool ECB::isECBcode(const std::string& ecbCode) {

        if (ecbCode.length() != 5)
            return false;

        string code = to_upper_copy(ecbCode);

        string str1("0123456789");
        string::size_type loc = str1.find(code.substr(3, 1), 0);
        if (loc == string::npos)
            return false;
        loc = str1.find(code.substr(4, 1), 0);
        if (loc == string::npos)
            return false;

        string monthString = code.substr(0, 3);
        if (monthString=="JAN")      return true;
        else if (monthString=="FEB") return true;
        else if (monthString=="MAR") return true;
        else if (monthString=="APR") return true;
        else if (monthString=="MAY") return true;
        else if (monthString=="JUN") return true;
        else if (monthString=="JUL") return true;
        else if (monthString=="AUG") return true;
        else if (monthString=="SEP") return true;
        else if (monthString=="OCT") return true;
        else if (monthString=="NOV") return true;
        else if (monthString=="DEC") return true;
        else return false;
    }

    inline string ECB::nextCode(const std::string& ecbCode) {
        QL_REQUIRE(isECBcode(ecbCode),
                   ecbCode << " is not a valid ECB code");

        string code = to_upper_copy(ecbCode);
        std::ostringstream result;

        string monthString = code.substr(0, 3);
        if (monthString=="JAN")      result << "FEB" << code.substr(3, 2);
        else if (monthString=="FEB") result << "MAR" << code.substr(3, 2);
        else if (monthString=="MAR") result << "APR" << code.substr(3, 2);
        else if (monthString=="APR") result << "MAY" << code.substr(3, 2);
        else if (monthString=="MAY") result << "JUN" << code.substr(3, 2);
        else if (monthString=="JUN") result << "JUL" << code.substr(3, 2);
        else if (monthString=="JUL") result << "AUG" << code.substr(3, 2);
        else if (monthString=="AUG") result << "SEP" << code.substr(3, 2);
        else if (monthString=="SEP") result << "OCT" << code.substr(3, 2);
        else if (monthString=="OCT") result << "NOV" << code.substr(3, 2);
        else if (monthString=="NOV") result << "DEC" << code.substr(3, 2);
        else if (monthString=="DEC") {
            // lexical_cast causes compilation errors with x64
            //Year y = boost::lexical_cast<Year>(code.substr(3, 2));
            unsigned int y = (io::to_integer(code.substr(3, 2)) + 1) % 100;
            string padding;
            if (y < 10)
                padding = "0";

            result << "JAN" << padding << y;
        } else QL_FAIL("not an ECB month (and it should have been)");

        #if defined(QL_EXTRA_SAFETY_CHECKS)
        QL_ENSURE(isECBcode(result.str()),
                  "the result " << result.str() <<
                  " is an invalid ECB code");
        #endif
        return result.str();
    }

}

#endif


#include <vector>

#include <boost/shared_ptr.hpp>
#include <gflags/gflags.h>
#include <glog/logging.h>

#include <cmath>
#include <fstream>

#include "caffe/blob.hpp"
#include "caffe/common.hpp"
#include "caffe/ultinous/affine_matrix_layer.hpp"
#include "caffe/util/math_functions.hpp"

namespace caffe
{
namespace ultinous
{

template<typename Dtype>
void AffineMatrixLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype> *> &bottom,
                                          const vector<Blob<Dtype> *> &top)
{

  CHECK(bottom[0]->channels() == 7);

  m_base_sx = this->layer_param_.affine_matrix_param().base_sx();
  m_base_sy = this->layer_param_.affine_matrix_param().base_sy();

  m_min_sx = this->layer_param_.affine_matrix_param().min_sx();
  m_max_sx = this->layer_param_.affine_matrix_param().max_sx();
  m_min_sy = this->layer_param_.affine_matrix_param().min_sy();
  m_max_sy = this->layer_param_.affine_matrix_param().max_sy();
  m_min_hx = this->layer_param_.affine_matrix_param().min_hx();
  m_max_hx = this->layer_param_.affine_matrix_param().max_hx();
  m_min_hy = this->layer_param_.affine_matrix_param().min_hy();
  m_max_hy = this->layer_param_.affine_matrix_param().max_hy();
  m_min_tx = this->layer_param_.affine_matrix_param().min_tx();
  m_max_tx = this->layer_param_.affine_matrix_param().max_tx();
  m_min_ty = this->layer_param_.affine_matrix_param().min_ty();
  m_max_ty = this->layer_param_.affine_matrix_param().max_ty();
  m_min_alpha = this->layer_param_.affine_matrix_param().min_alpha();
  m_max_alpha = this->layer_param_.affine_matrix_param().max_alpha();

  m_bias = this->layer_param_.affine_matrix_param().bias();

  m_max_diff = this->layer_param_.affine_matrix_param().max_diff();
  m_normalize_params = this->layer_param_.affine_matrix_param().normalize_params();
  m_moving_average_fraction = 0.9999;
  m_boundary_violation_step = 0.1;
  m_iter = 0;

  if (this->blobs_.size() > 0)
  {
    LOG(INFO) << "Skipping parameter initialization";
  } else
  {
    if( m_normalize_params || m_bias )
    {
      int blobNum = (m_bias && m_normalize_params)?2:1;

      this->blobs_.resize(blobNum);
      vector<int> sz;
      sz.push_back(bottom[0]->channels());

      for( int i = 0; i < blobNum; ++i )
      {
	this->blobs_[i].reset(new Blob<Dtype>(sz));
	caffe_set(this->blobs_[i]->count(), Dtype(0),
		  this->blobs_[i]->mutable_cpu_data());
      }
    }
  }

  std::vector<int> top_shape(2);
  top_shape[0] = bottom[0]->num();
  top_shape[1] = 6;
  top[0]->Reshape(top_shape);
}

template<typename Dtype>
void AffineMatrixLayer<Dtype>::Reshape(const vector<Blob<Dtype> *> &bottom,
                                       const vector<Blob<Dtype> *> &top)
{
  std::vector<int> top_shape(2);
  top_shape[0] = bottom[0]->num();
  top_shape[1] = 6;
  top[0]->Reshape(top_shape);
}

template<typename Dtype>
void AffineMatrixLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype> *> &bottom,
                                           const vector<Blob<Dtype> *> &top)
{
  ++m_iter;

  std::vector<Dtype> batch_averages(7, 0);
  Dtype const *averages = batch_averages.data();

  if (m_normalize_params)
  {

    if (this->phase_ == TRAIN)
    {
      // Update running averages
      Dtype *saved_averages = this->blobs_[0]->mutable_cpu_data();
      for (int n = 0; n < bottom[0]->num(); ++n)
      {
        Dtype const *bottom_data = bottom[0]->cpu_data() + 7 * n;

        for (int c = 0; c < 7; ++c)
          saved_averages[c] = m_moving_average_fraction * saved_averages[c]
                              + (1.0 - m_moving_average_fraction) * bottom_data[c];
      }

      if ((m_iter % 100) == 0)
      {
        std::cout << "Moving averages:";
        for (int c = 0; c < 7; ++c)
          std::cout << " " << saved_averages[c];
        std::cout << std::endl;
      }

      // Compute batch averages
      for (int n = 0; n < bottom[0]->num(); ++n)
      {
        Dtype const *bottom_data = bottom[0]->cpu_data() + 7 * n;

        for (int c = 0; c < 7; ++c)
          batch_averages[c] += bottom_data[c];
      }
      for (int c = 0; c < 7; ++c)
        batch_averages[c] /= bottom[0]->num();

      if ((m_iter % 100) == 0)
      {
        std::cout << "Batch averages: ";
        for (int c = 0; c < 7; ++c)
          std::cout << " " << batch_averages[c];
        std::cout << std::endl;
      }
    } else
    {
      averages = this->blobs_[0]->cpu_data();
    }
  }

  if(this->phase_ == TRAIN && (m_iter % 100) == 0)
  {
    Dtype const * bias = this->blobs_[m_normalize_params?1:0]->cpu_data();
    std::cout << "Bias:";
    for (int c = 0; c < 7; ++c)
      std::cout << " " << bias[c];
    std::cout << std::endl;
  }

  for (int n = 0; n < bottom[0]->num(); ++n)
  {
    Dtype const *bottom_data = bottom[0]->cpu_data() + 7 * n;

    Dtype sx = m_base_sx + bottom_data[0]; // scale param
    Dtype sy = m_base_sy + bottom_data[1]; // scale param
    Dtype hx = bottom_data[2] - averages[2]; // shear param
    Dtype hy = bottom_data[3] - averages[3]; // shear param
    Dtype tx = bottom_data[4] - averages[4];             // translate param
    Dtype ty = bottom_data[5] - averages[5]; // translate param
    Dtype al = bottom_data[6] - averages[6]; // alpha - rotatio angle;

    if( m_bias )
    {
      Dtype const * bias = this->blobs_[m_normalize_params?1:0]->cpu_data();
      sx += bias[0];
      sy += bias[1];
      hx += bias[2];
      hy += bias[3];
      tx += bias[4];
      ty += bias[5];
      al += bias[6];
    }

    Dtype ca = std::cos(al);
    Dtype sa = std::sin(al);

    Dtype *top_data = top[0]->mutable_cpu_data() + 6 * n;
    top_data[0] = sx * ca - hy * sy * sa;
    top_data[1] = hx * sx * ca - sy * sa;
    top_data[2] = tx;
    top_data[3] = sx * sa + hy * sy * ca;
    top_data[4] = hx * sx * sa + sy * ca;
    top_data[5] = ty;
  }

}

template<typename Dtype>
void AffineMatrixLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype> *> &top,
                                            const vector<bool> &propagate_down,
                                            const vector<Blob<Dtype> *> &bottom)
{

  if (!propagate_down[0]) return;

  std::vector<Dtype> batch_averages(7, 0);
  Dtype const *averages = batch_averages.data();

  if (m_normalize_params)
  {
    if (this->phase_ == TRAIN)
    {
      for (int n = 0; n < bottom[0]->num(); ++n)
      {
        Dtype const *bottom_data = bottom[0]->cpu_data() + 7 * n;

        for (int c = 0; c < 7; ++c)
          batch_averages[c] += bottom_data[c];
      }
      for (int c = 0; c < 7; ++c)
        batch_averages[c] /= bottom[0]->channels();
    } else
    {
      averages = this->blobs_[0]->cpu_data();
    }
  }

  for (int n = 0; n < bottom[0]->num(); ++n)
  {
    Dtype const *bottom_data = bottom[0]->cpu_data() + 7 * n;

    Dtype sx = m_base_sx + bottom_data[0]; // scale param
    Dtype sy = m_base_sy + bottom_data[1]; // scale param
    Dtype hx = bottom_data[2] - averages[2]; // shear param
    Dtype hy = bottom_data[3] - averages[3]; // shear param
    Dtype tx = bottom_data[4] - averages[4]; // translate param
    Dtype ty = bottom_data[5] - averages[5]; // translate param
    Dtype al = bottom_data[6] - averages[6]; // alpha - rotatio angle;

    if( m_bias )
    {
      Dtype const * bias = this->blobs_[m_normalize_params?1:0]->cpu_data();
      sx += bias[0];
      sy += bias[1];
      hx += bias[2];
      hy += bias[3];
      tx += bias[4];
      ty += bias[5];
      al += bias[6];
    }

    Dtype ca = std::cos(al);
    Dtype sa = std::sin(al);

    Dtype *bottom_diff = bottom[0]->mutable_cpu_diff() + 7 * n;
    Dtype const *top_diff = top[0]->cpu_diff() + 6 * n;

    if (sx < m_min_sx)
      bottom_diff[0] = (sx - m_min_sx) * m_boundary_violation_step;
    else if (sx > m_max_sx)
      bottom_diff[0] = (sx - m_max_sx) * m_boundary_violation_step;
    else
      bottom_diff[0] =
      (top_diff[0] * ca) + (top_diff[1] * hx * ca) + (top_diff[3] * sa) + (top_diff[4] * hx * sa);  // sx

    if (sy < m_min_sy)
      bottom_diff[1] = (sy - m_min_sy) * m_boundary_violation_step;
    else if (sy > m_max_sy)
      bottom_diff[1] = (sy - m_max_sy) * m_boundary_violation_step;
    else
      bottom_diff[1] =
      (top_diff[0] * -hy * sa) + (top_diff[1] * -sa) + (top_diff[3] * hy * ca) + (top_diff[4] * ca);  // sy

    if (hx < m_min_hx)
      bottom_diff[2] = (hx - m_min_hx) * m_boundary_violation_step;
    else if (hx > m_max_hx)
      bottom_diff[2] = (hx - m_max_hx) * m_boundary_violation_step;
    else
      bottom_diff[2] = (top_diff[1] * sx * ca) + (top_diff[4] * sx * sa);  // hx

    if (hy < m_min_hy)
      bottom_diff[3] = (hy - m_min_hy) * m_boundary_violation_step;
    else if (hy > m_max_hy)
      bottom_diff[3] = (hy - m_max_hy) * m_boundary_violation_step;
    else
      bottom_diff[3] = (top_diff[0] * -sy * sa) + (top_diff[3] * sy * ca);  // hy

    if (tx < m_min_tx)
      bottom_diff[4] = (tx - m_min_tx) * m_boundary_violation_step;
    else if (tx > m_max_tx)
      bottom_diff[4] = (tx - m_max_tx) * m_boundary_violation_step;
    else
      bottom_diff[4] = top_diff[2]; // tx

    if (ty < m_min_ty)
      bottom_diff[5] = (ty - m_min_ty) * m_boundary_violation_step;
    else if (ty > m_max_ty)
      bottom_diff[5] = (ty - m_max_ty) * m_boundary_violation_step;
    else
      bottom_diff[5] = top_diff[5]; // ty

    if (al < m_min_alpha)
      bottom_diff[6] = (al - m_min_alpha) * m_boundary_violation_step;
    else if (al > m_max_alpha)
      bottom_diff[6] = (al - m_max_alpha) * m_boundary_violation_step;
    else
      bottom_diff[6] = top_diff[0] * (sx * -sa - hy * sy * ca)
                       + top_diff[1] * (hx * sx * -sa - sy * ca)
                       + top_diff[3] * (sx * ca + hy * sy * -sa)
                       + top_diff[4] * (hx * sx * ca + sy * -sa);

    if( m_max_diff != 0 )
      for (int i = 0; i < 7; i++)
        bottom_diff[i] = std::max(-m_max_diff, std::min(m_max_diff, bottom_diff[i]));

    // Bias - only on specific params
    if( m_bias )
    {
      Dtype * bias_diff = this->blobs_[m_normalize_params?1:0]->mutable_cpu_diff();
      bias_diff[0] += bottom_diff[0]; // sx
      bias_diff[1] += bottom_diff[1]; // sy
      bias_diff[4] += bottom_diff[4]; // tx
//      bias_diff[5] += bottom_diff[5]; // ty
    }

  }
}

#ifdef CPU_ONLY
//STUB_GPU(AffineMatrixLayer);
#endif

INSTANTIATE_CLASS(AffineMatrixLayer);

REGISTER_LAYER_CLASS(AffineMatrix);

}  // namespace ultinous
}  // namespace caffe


/**
 * @file esx_impl.hpp
 * @author Leonardo Arcari (leonardo1.arcari@gmail.com)
 * @version 1.0.0
 * @date 2018-10-28
 *
 * @copyright Copyright (c) 2018 Leonardo Arcari
 *
 * 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.
 *
 */

#ifndef BOOST_EDGE_SUBSET_EXCLUSION_IMPL_HPP
#define BOOST_EDGE_SUBSET_EXCLUSION_IMPL_HPP

#include <boost/graph/astar_search.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/properties.hpp>

#include <arlib/details/arlib_utils.hpp>
#include <arlib/routing_kernels/bidirectional_dijkstra.hpp>
#include <arlib/routing_kernels/types.hpp>
#include <arlib/terminators.hpp>
#include <arlib/type_traits.hpp>

#include <limits>
#include <unordered_set>
#include <utility>

/**
 * An Alternative-Routing library for Boost.Graph
 */
namespace arlib {
/**
 * Implementations details of kSPwLO algorithms
 */
namespace details {

//===----------------------------------------------------------------------===//
//                      ESX algorithm support classes
//===----------------------------------------------------------------------===//

/**
 * Comparator for edges priority queue.
 *
 * Given two edges @c e1 and @c e2, if <tt>prio(e1) > prio(e2)</tt> then @c e1
 * must be popped before @c e2.
 *
 * @tparam Edge A Boost::Graph edge descriptor.
 */
template <typename Edge> struct EdgePriorityComparator {
  /**
   * The priority of Edge
   */
  using Priority = std::pair<Edge, int>;
  /**
   * Given two edges @c e1 and @c e2, if <tt>prio(e1) > prio(e2)</tt>
   * then @c e1 must be popped before @c e2.
   *
   * @param lhs first edge and its priority.
   * @param rhs second edge and its priority.
   * @return true if @p lhs has lower priority then @p rhs
   * @return false otherwise.
   */
  bool operator()(Priority lhs, Priority rhs) const {
    return lhs.second < rhs.second;
  }
};

/**
 * A filter functor for Boost::filtered_graph to hide edges deleted by
 * ESX.
 *
 * @tparam Edge A Boost::Graph edge descriptor
 */
template <typename Edge> class edge_deleted_filter {
public:
  /**
   * The deleted edges map.
   */
  using DeletedEdgeMap = std::unordered_set<Edge, boost::hash<Edge>>;
  /**
   * Empty constructor, required by Boost::Graph
   */
  edge_deleted_filter() : deleted_edge_map{nullptr} {}
  /**
   * Construct a new edge_deleted_filter object filtering edges contained
   * in @p deleted_edge_map.
   *
   * @param deleted_edge_map The set of deleted edges.
   */
  edge_deleted_filter(const DeletedEdgeMap &deleted_edge_map)
      : deleted_edge_map{std::addressof(deleted_edge_map)} {}
  /**
   * @param e Edge to check.
   * @return true if @p e is marked as deleted
   * @return false otherwise.
   */
  bool operator()(const Edge &e) const {
    return deleted_edge_map->find(e) == std::end(*deleted_edge_map);
  }

private:
  const DeletedEdgeMap *deleted_edge_map;
};

template <typename PMap, class Graph> class reverse_weight_functor {
public:
  using Edge = typename boost::graph_traits<
      boost::reverse_graph<Graph>>::edge_descriptor;
  using Length = typename boost::property_traits<PMap>::value_type;

  reverse_weight_functor(PMap &pmap, Graph const &G,
                         const boost::reverse_graph<Graph> &rev_G)
      : inner_weight{pmap}, G{G}, rev_G{rev_G} {}

  reverse_weight_functor(reverse_weight_functor const &other)
      : inner_weight{other.inner_weight}, G{other.G}, rev_G{other.rev_G} {}

  const Length &operator()(const Edge &e) const {
    auto forward_edge = get_forward_edge(e);

    return inner_weight[forward_edge];
  }

  Length &operator[](const Edge &e) {
    auto forward_edge = get_forward_edge(e);

    return inner_weight[forward_edge];
  }

  const Length &operator[](const Edge &e) const {
    auto forward_edge = get_forward_edge(e);

    return inner_weight[forward_edge];
  }

private:
  auto get_forward_edge(const Edge &e) const {
    using namespace boost;
    auto u = source(e, rev_G);
    auto v = target(e, rev_G);

    auto [forward_edge, is_valid] = edge(v, u, G);
    assert(is_valid);
    return forward_edge;
  }

  PMap &inner_weight;
  const Graph &G;
  const boost::reverse_graph<Graph> &rev_G;
};

//===----------------------------------------------------------------------===//
//                          ESX algorithm routines
//===----------------------------------------------------------------------===//
/**
 * Checks whether the path from @p s to @p t contains edge @p e or not.
 *
 * @pre @p predecessor is the PredecessorMap filled by a Boost::Graph
 * shortest path algorithm such that <tt>predecessor[s] == s</tt>
 *
 * @tparam Graph A Boost::EdgeList graph
 * @tparam PredMap A Boost::PredecessorMap
 * @param s The source vertex
 * @param t The target vertex
 * @param e The edge we want to check if present in <tt>path(s -> t)</tt>
 * @param G The graph containing edge @p e
 * @param predecessor The predecessor map
 * @return true If @p e is in the computed path from @p s to @p t
 * @return false otherwise.
 */
template <typename Graph, typename PredMap,
          typename Vertex = vertex_of_t<Graph>,
          typename Edge = edge_of_t<Graph>>
bool shortest_path_contains_edge(Vertex s, Vertex t, Edge e, const Graph &G,
                                 const PredMap &predecessor) {
  auto e_s = boost::source(e, G);
  auto e_t = boost::target(e, G);

  auto v = t;
  auto u = predecessor[v];
  while (u != s) {
    if (u == e_s && v == e_t) {
      return true;
    }
    v = u;
    u = predecessor[v];
  }
  return false;
}

template <typename Graph, typename WeightMap, typename DeletedEdgeMap,
          typename Vertex = vertex_of_t<Graph>,
          typename Edge = edge_of_t<Graph>>
std::optional<std::vector<Edge>>
dijkstra_shortest_path(const Graph &G, Vertex s, Vertex t,
                       const WeightMap &weight,
                       DeletedEdgeMap &deleted_edge_map) {
  using namespace boost;

  // Get a graph with deleted edges filtered out
  auto filter = edge_deleted_filter{deleted_edge_map};
  auto filtered_G = filtered_graph(G, filter);

  auto predecessor = std::vector<Vertex>(num_vertices(filtered_G), s);
  auto vertex_id = get(vertex_index, filtered_G);

  try {
    dijkstra_shortest_paths(
        filtered_G, s,
        weight_map(weight)
            .predecessor_map(make_iterator_property_map(std::begin(predecessor),
                                                        vertex_id, s))
            .visitor(make_dijkstra_visitor(
                make_target_visitor(t, on_examine_vertex{}))));
  } catch (target_found &) {
    auto edge_list = build_edge_list_from_dijkstra(G, s, t, predecessor);
    return std::make_optional(edge_list);
  }

  // In case t could not be found from astar_search and target_found is not
  // thrown, return empty optional
  return std::optional<std::vector<Edge>>{};
}

/**
 * Compute a shortest path between two vertices on a filtered graph using
 * an A* approach, provided the set of edges to filter and the heuristic.
 *
 * @tparam Graph A Boost::PropertyGraph having at least one edge
 *         property with tag boost::edge_weight_t.
 * @tparam AStarHeuristic
 * @tparam DeletedEdgeMap an edge_deleted_filter::DeletedEdgeMap
 * @param G The graph
 * @param s The source vertex
 * @param t The target vertex
 * @param heuristic The A* heuristic.
 * @param deleted_edge_map The set of edges to filter from @p G
 * @return A std::optional of the list of edges from @p s to @p t if a path
 * could be found. An empty optional otherwise.
 */
template <typename Graph, typename WeightMap, typename AStarHeuristic,
          typename DeletedEdgeMap, typename Vertex = vertex_of_t<Graph>,
          typename Edge = edge_of_t<Graph>>
std::optional<std::vector<Edge>>
astar_shortest_path(const Graph &G, Vertex s, Vertex t, const WeightMap &weight,
                    const AStarHeuristic &heuristic,
                    DeletedEdgeMap &deleted_edge_map) {
  using namespace boost;

  // Get a graph with deleted edges filtered out
  auto filter = edge_deleted_filter{deleted_edge_map};
  auto filtered_G = filtered_graph(G, filter);

  auto predecessor = std::vector<Vertex>(num_vertices(G), s);
  auto vertex_id = get(vertex_index, filtered_G);

  try {
    astar_search(filtered_G, s, heuristic,
                 predecessor_map(make_iterator_property_map(
                                     std::begin(predecessor), vertex_id, s))
                     .visitor(astar_target_visitor{t})
                     .weight_map(weight));
  } catch (target_found &) {
    auto edge_list = build_edge_list_from_dijkstra(G, s, t, predecessor);
    return std::make_optional(edge_list);
  }
  // In case t could not be found from astar_search and target_found is not
  // thrown, return empty optional
  return std::optional<std::vector<Edge>>{};
}

template <typename Graph, typename WeightMap, typename DeletedEdgeMap,
          typename Vertex = vertex_of_t<Graph>,
          typename Edge = edge_of_t<Graph>,
          typename Length = length_of_t<Graph>>
std::optional<std::vector<Edge>>
bidirectional_dijkstra_shortest_path(const Graph &G, Vertex s, Vertex t,
                                     const WeightMap &weight,
                                     DeletedEdgeMap &deleted_edge_map) {
  using namespace boost;

  // Get a graph with deleted edges filtered out
  using FilteredGraph = boost::filtered_graph<Graph, edge_deleted_filter<Edge>>;
  auto filter = edge_deleted_filter{deleted_edge_map};
  const auto filtered_G = filtered_graph(G, filter);

  auto index = get(vertex_index, filtered_G);
  auto predecessor_vec = std::vector<Vertex>(num_vertices(G), s);
  auto predecessor = make_iterator_property_map(predecessor_vec.begin(), index);
  auto distance_vec = std::vector<Length>(num_vertices(G));
  auto distance = make_iterator_property_map(distance_vec.begin(), index);

  auto rev_G = make_reverse_graph(filtered_G);
  using RevEdge =
      typename graph_traits<reverse_graph<FilteredGraph>>::edge_descriptor;
  auto rev_weight = make_function_property_map<RevEdge, Length>(
      reverse_weight_functor{weight, filtered_G, rev_G});
  auto rev_index = get(vertex_index, rev_G);

  try {
    bidirectional_dijkstra(filtered_G, s, t, predecessor, distance, weight,
                           rev_G, rev_weight, rev_index);
  } catch (target_not_found &) {
    // In case t could not be found return empty optional
    return std::optional<std::vector<Edge>>{};
  }

  auto edge_list = build_edge_list_from_dijkstra(G, s, t, predecessor);
  return std::make_optional(edge_list);
}

template <typename Graph, typename WeightMap, typename AStarHeuristic,
          typename DeletedEdgeMap, typename Vertex = vertex_of_t<Graph>,
          typename Edge = edge_of_t<Graph>>
constexpr std::function<std::optional<std::vector<Edge>>(
    const Graph &, Vertex, Vertex, const WeightMap &, DeletedEdgeMap &)>
build_shortest_path_fn(routing_kernels algorithm, const Graph &, Vertex, Vertex,
                       const WeightMap &, const AStarHeuristic &heuristic,
                       DeletedEdgeMap &) {
  switch (algorithm) {
  case routing_kernels::astar:
    return [&heuristic](const auto &G, auto s, auto t, const auto &weight,
                        auto &deleted_edge_map) {
      return astar_shortest_path(G, s, t, weight, heuristic, deleted_edge_map);
    };
  default:
    throw std::invalid_argument{"Invalid algorithm. Only [astar] "
                                "allowed."};
  }
}

template <typename Graph, typename WeightMap, typename DeletedEdgeMap,
          typename Vertex = vertex_of_t<Graph>,
          typename Edge = edge_of_t<Graph>>
constexpr std::function<std::optional<std::vector<Edge>>(
    const Graph &, Vertex, Vertex, const WeightMap &, DeletedEdgeMap &)>
build_shortest_path_fn(routing_kernels algorithm, const Graph &, Vertex, Vertex,
                       const WeightMap &, DeletedEdgeMap &) {
  switch (algorithm) {
  case routing_kernels::dijkstra:
    return [](const auto &G, auto s, auto t, const auto &weight,
              auto &deleted_edge_map) {
      return dijkstra_shortest_path(G, s, t, weight, deleted_edge_map);
    };
  case routing_kernels::bidirectional_dijkstra:
    return [](const auto &G, auto s, auto t, const auto &weight,
              auto &deleted_edge_map) {
      return bidirectional_dijkstra_shortest_path(G, s, t, weight,
                                                  deleted_edge_map);
    };
  default:
    throw std::invalid_argument{
        "Invalid algorithm. Only [dijkstra|bidirectional_dijkstra] "
        "allowed."};
  }
}

/**
 * Computes the ESX priority of an edge. Quoting the reference paper:
 *
 *
Given an edge e(a, b) on some alternative path p, let E_inc(a) be
 * the set of all incoming edges e(n_i, a) to a from some node n_i in N\{b} and
 * E_out(b) be the set of all outgoing edges e(b, n_j) from b to some node n_j
 * in N\{a}. First, ESX computes the set P_s which contains the shortest paths
 * from every node n_i in E_inc(a) to every node n_j in E_out(b). Then, ESX
 * defines the set P'_s which contains all paths p in P'_s that cross edge e.
 * Finally, ESX assigns a priority to edge e, denoted by prio(e), which is set
 * to cardinality of P'_s.

 *
 * @tparam Graph A Boost::PropertyGraph having at least one edge
 *         property with tag boost::edge_weight_t.
 * @tparam AStarHeuristic
 * @tparam DeletedEdgeMap  an edge_deleted_filter::DeletedEdgeMap
 * @param G The graph
 * @param e An edge of @p G
 * @param heuristic An A* heuristic to use in performing shortest paths search.
 * @param deleted_edge_map The set of edges to filter from @p G
 * @return The priority of @p e.
 */
template <typename Graph, typename WeightMap, typename DeletedEdgeMap,
          typename Edge = edge_of_t<Graph>,
          typename Length = length_of_t<Graph>>
int compute_priority(const Graph &G, const Edge &e, WeightMap &weight,
                     const DeletedEdgeMap &deleted_edge_map) {
  using namespace boost;
  using Vertex = typename graph_traits<Graph>::vertex_descriptor;
  int priority = 0;
  auto sources = std::vector<Vertex>{};
  auto targets = std::vector<Vertex>{};

  auto a = source(e, G);
  auto b = target(e, G);

  // Compute all the n_i nodes s.t. (n_i, a) is an incoming edge of a, with n_i
  // != b
  for (auto in_it = in_edges(a, G).first; in_it != in_edges(a, G).second;
       ++in_it) {
    auto n_i = source(*in_it, G);
    if (n_i != b) {
      sources.push_back(n_i);
    }
  }

  // Compute all the n_j nodes s.t. (b, n_j) is an outgoing edge of b, with n_j
  // != a
  for (auto out_it = out_edges(b, G).first; out_it != out_edges(b, G).second;
       ++out_it) {
    auto n_j = target(*out_it, G);
    if (n_j != a) {
      targets.push_back(n_j);
    }
  }

  // Get a graph with deleted edges filtered out
  auto filter = edge_deleted_filter{deleted_edge_map};
  const auto filtered_G = filtered_graph(G, filter);

  for (auto s_i : sources) {
    for (auto t_i : targets) {
      // Compute the shortest path from s_i to t_i
      auto index = get(vertex_index, filtered_G);
      auto predecessor_vec = std::vector<Vertex>(num_vertices(G), s_i);
      auto predecessor =
          make_iterator_property_map(predecessor_vec.begin(), index);
      auto distance_vec = std::vector<Length>(num_vertices(G));
      auto distance = make_iterator_property_map(distance_vec.begin(), index);

      auto rev_G = make_reverse_graph(filtered_G);
      using RevEdge =
          typename graph_traits<reverse_graph<Graph>>::edge_descriptor;
      auto rev_weight = make_function_property_map<RevEdge>(
          reverse_weight_functor{weight, filtered_G, rev_G});
      auto rev_index = get(vertex_index, rev_G);

      try {
        bidirectional_dijkstra(filtered_G, s_i, t_i, predecessor, distance,
                               weight, rev_G, rev_weight, rev_index);
        if (shortest_path_contains_edge(s_i, t_i, e, filtered_G, predecessor)) {
          ++priority;
        }
      } catch (target_not_found &ex) {
        // In case t could not be found do nothing
        std::cout << "Caught exception: " << ex.what() << "\n";
      }
    }
  }

  return priority;
}

/**
 * Computes the edge priorities of an alternative path.
 *
 * @pre @p edge_priorities is a vector of std::priority_queue of size al least
 * @p alt_index + 1.
 *
 * @post @c edge_priorities[alt_index] queue is filled with pairs <tt>(e,
 * priority(e))</tt>
 *
 * @tparam Graph A Boost::PropertyGraph having at least one edge
 *         property with tag boost::edge_weight_t.
 * @tparam PrioritiesVector a std::vector<std::priority_queue<std::pair<Edge,
 * Priority>>>
 * @tparam AStarHeuristic
 * @tparam EdgeMap an edge_deleted_filter::DeletedEdgeMap
 * @param alternative The alternative path
 * @param edge_priorities The edge priorities vector
 * @param alt_index The index of @p edge_priorities where to store priorities
 * at.
 * @param G The graph
 * @param heuristic An A* heuristic to use in performing shortest paths search.
 * @param deleted_edges The set of edges to filter from @p G
 */
template <typename Graph, typename PrioritiesVector, typename WeightMap,
          typename EdgeMap,
          typename Index = typename PrioritiesVector::size_type>
void init_edge_priorities(const Graph &alternative,
                          PrioritiesVector &edge_priorities, Index alt_index,
                          const Graph &G, const WeightMap &weight,
                          const EdgeMap &deleted_edges) {
  using namespace boost;
  for (auto it = edges(alternative).first; it != edges(alternative).second;
       ++it) {
    // Get a reference to (u, v) in G
    auto u = source(*it, alternative);
    auto v = target(*it, alternative);
    auto edge_in_G = edge(u, v, G);
    assert(edge_in_G.second); // (u, v) must exist in G
    auto prio_e_i = compute_priority(G, edge_in_G.first, weight, deleted_edges);
    edge_priorities[alt_index].push(std::make_pair(edge_in_G.first, prio_e_i));
  }
}

/**
 * Computes the edge priorities of an alternative path.
 *
 * @pre @p edge_priorities is a vector of std::priority_queue of size al least
 * @p alt_index + 1.
 *
 * @post @c edge_priorities[alt_index] queue is filled with pairs <tt>(e,
 * priority(e))</tt>
 *
 * @tparam Graph A Boost::PropertyGraph having at least one edge
 *         property with tag boost::edge_weight_t.
 * @tparam PrioritiesVector a std::vector<std::priority_queue<std::pair<Edge,
 * Priority>>>
 * @tparam AStarHeuristic
 * @tparam EdgeMap an edge_deleted_filter::DeletedEdgeMap
 * @param alternative The alternative path
 * @param edge_priorities The edge priorities vector
 * @param alt_index The index of @p edge_priorities where to store priorities
 * at.
 * @param G The graph
 * @param heuristic An A* heuristic to use in performing shortest paths search.
 * @param deleted_edges The set of edges to filter from @p G
 */
template <typename PrioritiesVector, typename Graph, typename WeightMap,
          typename EdgeMap, typename Edge = edge_of_t<Graph>,
          typename Index = typename PrioritiesVector::size_type>
void init_edge_priorities(const std::vector<Edge> &alternative,
                          PrioritiesVector &edge_priorities, Index alt_index,
                          const Graph &G, const WeightMap &weight,
                          const EdgeMap &deleted_edges) {
  for (const auto &e : alternative) {
    auto prio_e_i = compute_priority(G, e, weight, deleted_edges);
    edge_priorities[alt_index].push(std::make_pair(e, prio_e_i));
  }
}

/**
 * Checks whether another alternative path can be found by ESX.
 *
 * @param overlaps The vector of overlapping factors between @c path_tmp and the
 * alternative paths.
 * @return true if a solution can still be found.
 * @return false otherwise.
 */
bool check_feasibility(const std::vector<double> &overlaps);

/**
 * Checks whether a candidate path satisfies the condition to add it the
 * the alternative paths set. That is: @f$Sim(candidate, p_i) < \theta, \forall
 * p_i \in AlternativePaths@f$
 *
 * @tparam Graph A Boost::PropertyGraph having at least one edge
 *         property with tag boost::edge_weight_t.
 * @param candidate The candidate path.
 * @param alternatives The list of alternative paths.
 * @param theta The similarity threshold.
 * @return true If @p candidate is sufficiently dissimilar to all the other
 * alternative paths.
 * @return false Otherwise.
 */
template <typename Edge, typename WeightMap>
bool check_candidate_validity(
    const std::vector<Edge> &candidate,
    const std::vector<std::unordered_set<Edge, boost::hash<Edge>>>
        &alternatives,
    WeightMap const &weight, double theta) {
  bool candidate_is_valid = true;
  for (const auto &alt_path : alternatives) {
    if (compute_similarity(candidate, alt_path, weight) > theta) {
      candidate_is_valid = false;
      break;
    }
  }
  return candidate_is_valid;
}

template <typename Edge>
void move_to_dnr(Edge e,
                 std::unordered_set<Edge, boost::hash<Edge>> &deleted_edges,
                 std::unordered_set<Edge, boost::hash<Edge>> &dnr_edges) {
#ifndef NDEBUG
  auto old_size = deleted_edges.size();
#endif
  deleted_edges.erase(e); // Reinsert e_tmp into G
#ifndef NDEBUG
  assert(deleted_edges.size() + 1 == old_size);
#endif
  dnr_edges.insert(e); // Mark e_tmp as do_not_remove
}

//===----------------------------------------------------------------------===//
//                             ESX implementation
//===----------------------------------------------------------------------===//
template <typename Graph, typename WeightMap, typename MultiPredecessorMap,
          typename PriorityFunc, typename RoutingKernel, typename Terminator,
          typename Vertex = vertex_of_t<Graph>>
void esx(const Graph &G, WeightMap const &weight,
         MultiPredecessorMap &predecessors, Vertex s, Vertex t, int k,
         double theta, PriorityFunc &&priority_fn,
         RoutingKernel &routing_kernel, Terminator &&terminator) {
  using namespace boost;
  using Edge = typename graph_traits<Graph>::edge_descriptor;

  BOOST_CONCEPT_ASSERT((VertexAndEdgeListGraphConcept<Graph>));
  BOOST_CONCEPT_ASSERT((LvaluePropertyMapConcept<WeightMap, Edge>));

  // P_LO set of k paths
  auto resPathsEdges = std::vector<std::vector<Edge>>{};
  auto resEdges = std::vector<std::unordered_set<Edge, boost::hash<Edge>>>{};

  BOOST_CONCEPT_ASSERT((VertexAndEdgeListGraphConcept<Graph>));
  BOOST_CONCEPT_ASSERT((LvaluePropertyMapConcept<WeightMap, Edge>));

  // P_LO set of k paths
  auto resPaths = std::vector<Path<Graph>>{};
  // Compute shortest path from s to t
  auto sp = details::compute_shortest_path(G, weight, s, t);
  if (!sp) {
    auto oss = std::ostringstream{};
    oss << "Vertex " << t << " is unreachable from " << s;
    throw details::target_not_found{oss.str()};
  }

  // P_LO <-- {shortest path p_0(s, t)};
  resPathsEdges.push_back(*sp);
  resEdges.emplace_back(sp->begin(), sp->end());

  // If we need the shortest path only
  if (k == 1) {
    fill_multi_predecessor(resPathsEdges.begin(), resPathsEdges.end(), G,
                           predecessors);
    return;
  }

  // Every max-heap H_i is associated with p_i
  using Priority = std::pair<Edge, int>;
  using EdgePriorityQueue =
      std::priority_queue<Priority, std::vector<Priority>,
                          details::EdgePriorityComparator<Edge>>;
  auto edge_priorities = std::vector<EdgePriorityQueue>(k);

  // We keep a set of non-removable edges
  auto dnr_edges = std::unordered_set<Edge, boost::hash<Edge>>{};

  // We keep a set of deleted-edges
  auto deleted_edges = std::unordered_set<Edge, boost::hash<Edge>>{};

  // Compute lower bounds for AStar
  // auto heuristic = details::distance_heuristic<Graph, Length>(G, t);

  // Initialize max-heap H_0 with the priority of each edge of the shortest path
  priority_fn(*sp, edge_priorities, 0, G, weight, deleted_edges);

  auto overlaps = std::vector<double>(k, 0.0);
  overlaps[0] = 1.0; // Set p_c overlap with sp (itself) to 1

  bool still_feasible = true;
  while (resPathsEdges.size() < static_cast<std::size_t>(k) && still_feasible) {
    std::ptrdiff_t p_max_idx = 0;
    double overlap_ratio = 1.0;

    while (overlap_ratio >= theta) {
      // Get the max overlapping path
      auto max_it = std::max_element(std::begin(overlaps), std::end(overlaps));
      p_max_idx = max_it - std::begin(overlaps);
      overlap_ratio = *max_it;

      // Check if finding a result is feasible
      still_feasible = details::check_feasibility(overlaps);
      if (!still_feasible) {
        break; // Stop the algorithm
      }

      // Get e_tmp with higher priority from H_{p_max_idx}
      auto e_tmp = edge_priorities[p_max_idx].top().first;

      // If edge is in DO-NOT-REMOVE edges set, continue
      if (dnr_edges.find(e_tmp) != std::end(dnr_edges)) {
        edge_priorities[p_max_idx].pop();
        // Also, if H_{p_max_idx} queue is empty set its overlapping value to 0
        if (edge_priorities[p_max_idx].empty()) {
          overlaps[p_max_idx] = 0;
        }
        continue;
      } else {
        // Otherwise, add e_tmp to deleted edges
        deleted_edges.insert(e_tmp);
      }

      // The remainder code is the hot part of the algorithm. So we check here
      // if the algorithm should terminate
      if (terminator.should_stop()) {
        throw terminator_stop_error{
            "ESX terminated before completing due to a Terminator. Please "
            "discard partial output."};
      }

      // Compute p_tmp shortest path
      auto p_tmp = routing_kernel(G, s, t, weight, deleted_edges);

      // If shortest path did not find a path
      if (!p_tmp) {
        move_to_dnr(e_tmp, deleted_edges, dnr_edges);
        continue;
      }

      // Remove e_tmp from H_{p_max_idx}
      edge_priorities[p_max_idx].pop();

      // in cases there are no more edges to remove we set overlap to zero to
      // avoid choosing from the same path again. A path the overlap of which is
      // zero can never be chosen to remove its edges.
      if (edge_priorities[p_max_idx].empty()) {
        overlaps[p_max_idx] = 0;
      } else {
        const auto &alt_path = resEdges[p_max_idx];
        overlaps[p_max_idx] = compute_similarity(*p_tmp, alt_path, weight);
      }

      // Checking if the resulting path is valid
      bool candidate_is_valid =
          check_candidate_validity(*p_tmp, resEdges, weight, theta);
      if (candidate_is_valid) {
        // Add p_tmp to P_LO
        resPathsEdges.emplace_back(*p_tmp);
        resEdges.emplace_back(p_tmp->begin(), p_tmp->end());

        // Set p_c overlap with itself to 1
        std::ptrdiff_t p_c_idx = resPathsEdges.size() - 1;
        overlaps[p_c_idx] = 1.0;

        // Initialize max-heap H_i with the priority of each edge of new
        // alternative path
        priority_fn(*p_tmp, edge_priorities, p_c_idx, G, weight, deleted_edges);
        break; // From inner while loop
      }
    }
  }

  // Beforer returning, populate predecessors map
  fill_multi_predecessor(resPathsEdges.begin(), resPathsEdges.end(), G,
                         predecessors);
}

template <typename Graph, typename WeightMap, typename MultiPredecessorMap,
          typename PriorityFunc, typename Terminator,
          typename Vertex = vertex_of_t<Graph>>
void esx_dispatch2(const Graph &G, WeightMap const &weight,
                   MultiPredecessorMap &predecessors, Vertex s, Vertex t, int k,
                   double theta, PriorityFunc &&priority_fn,
                   routing_kernels algorithm, Terminator &&terminator) {
  using Edge = edge_of_t<Graph>;
  using Length = length_of_t<Graph>;

  auto deleted_edges = std::unordered_set<Edge, boost::hash<Edge>>{};
  if (algorithm == routing_kernels::astar) {
    auto heuristic = details::distance_heuristic<Graph, Length>(G, t);
    auto routing_kernel = details::build_shortest_path_fn(
        algorithm, G, s, t, weight, heuristic, deleted_edges);
    esx(G, weight, predecessors, s, t, k, theta,
        std::forward<PriorityFunc>(priority_fn), routing_kernel,
        std::forward<Terminator>(terminator));
  } else {
    auto routing_kernel = details::build_shortest_path_fn(
        algorithm, G, s, t, weight, deleted_edges);
    esx(G, weight, predecessors, s, t, k, theta,
        std::forward<PriorityFunc>(priority_fn), routing_kernel,
        std::forward<Terminator>(terminator));
  }
}

template <typename Graph, typename WeightMap, typename MultiPredecessorMap,
          typename Terminator, typename Vertex = vertex_of_t<Graph>>
void esx_dispatch(const Graph &G, WeightMap const &weight,
                  MultiPredecessorMap &predecessors, Vertex s, Vertex t, int k,
                  double theta, routing_kernels algorithm,
                  Terminator &&terminator) {
  auto priority_fn = [](auto const &alternative, auto &edge_priorities,
                        auto alt_index, auto const &G, auto const &weight,
                        auto const &deleted_edges) {
    init_edge_priorities(alternative, edge_priorities, alt_index, G, weight,
                         deleted_edges);
  };
  esx_dispatch2(G, weight, predecessors, s, t, k, theta, std::move(priority_fn),
                algorithm, std::forward<Terminator>(terminator));
}

template <typename Graph, typename WeightMap, typename MultiPredecessorMap,
          typename EdgeCentralityMap, typename Terminator,
          typename Vertex = vertex_of_t<Graph>>
void esx_dispatch(const Graph &G, WeightMap const &weight,
                  MultiPredecessorMap &predecessors,
                  EdgeCentralityMap const &edge_centrality, Vertex s, Vertex t,
                  int k, double theta, routing_kernels algorithm,
                  Terminator &&terminator) {
  auto priority_fn = [&edge_centrality](auto const &alternative,
                                        auto &edge_priorities, auto alt_index,
                                        auto const &, auto const &,
                                        auto const &) {
    for (const auto &e : alternative) {
      auto prio_e_i = get(edge_centrality, e);
      edge_priorities[alt_index].push(std::make_pair(e, prio_e_i));
    }
  };

  esx_dispatch2(G, weight, predecessors, s, t, k, theta, std::move(priority_fn),
                algorithm, std::forward<Terminator>(terminator));
}
} // namespace details
} // namespace arlib
#endif


#include <NTL/vec_GF2.h>

#include <NTL/new.h>
#include <stdio.h>

NTL_START_IMPL

void vec_GF2::SetLength(long n)
{
   long len = length();

   if (n == len) return;

   if (n < 0) Error("negative length in vec_GF2::SetLength");

   if (NTL_OVERFLOW(n, 1, 0))
      Error("vec_GF2::SetLength: excessive length");

   if (fixed()) Error("SetLength: can't change this vector's length");

   long wdlen = (n+NTL_BITS_PER_LONG-1)/NTL_BITS_PER_LONG;

   if (n < len) {
      // have to clear bits n..len-1

      long q = n/NTL_BITS_PER_LONG;
      long p = n - q*NTL_BITS_PER_LONG;

      _ntl_ulong *x = rep.elts();

      x[q] &= (1UL << p) - 1UL;

      long q1 = (len-1)/NTL_BITS_PER_LONG;
      long i;

      for (i = q+1; i <= q1; i++)
         x[i] = 0;

      _len = n;

      rep.QuickSetLength(wdlen);

      return;
   }

   long maxlen = MaxLength();

   if (n <= maxlen) {
      _len = n;
      rep.QuickSetLength(wdlen);
      return;
   }

   long alloc = rep.MaxLength();

   if (wdlen <= alloc) {
      _len = n;
      _maxlen = (n << 1);
      rep.QuickSetLength(wdlen);
      return;
   }

   // have to grow vector and initialize to zero

   rep.SetLength(wdlen);

   wdlen = rep.MaxLength(); // careful! rep.MaxLength() may exceed the
                            // old value of wdlen...this is due to
                            // the awkward semantics of WordVector.

   _ntl_ulong *x = rep.elts();

   long i;
   for (i = alloc; i < wdlen; i++)
      x[i] = 0;

   _len = n;
   _maxlen = (n << 1);

}

vec_GF2& vec_GF2::operator=(const vec_GF2& a)
{
   if (this == &a) return *this;

   long n = a.length();

   SetLength(n);

   long wdlen = (n+NTL_BITS_PER_LONG-1)/NTL_BITS_PER_LONG;

   _ntl_ulong *x = rep.elts();
   const _ntl_ulong *y = a.rep.elts();

   long i;
   for (i = 0; i < wdlen; i++)
      x[i] = y[i];

   return *this;
}

void vec_GF2::kill()
{
   if (fixed()) Error("can't kill this vec_GF2");
   rep.kill();
   _len = _maxlen = 0;
}

void vec_GF2::SetMaxLength(long n)
{
   long oldlen = length();
   if (n > oldlen) {
      SetLength(n);
      SetLength(oldlen);
   }
}

void vec_GF2::FixLength(long n)
{
   if (MaxLength() > 0 || fixed()) Error("can't fix this vector");

   SetLength(n);
   _maxlen |= 1;
}

const GF2 vec_GF2::get(long i) const
{
   const vec_GF2& v = *this;

   if (i < 0 || i >= v.length())
      Error("vec_GF2: subscript out of range");

   long q = i/NTL_BITS_PER_LONG;
   long p = i - q*NTL_BITS_PER_LONG;

   if (v.rep[q] & (1UL << p))
      return to_GF2(1);
   else
      return to_GF2(0);
}

ref_GF2 vec_GF2::operator[](long i)
{
   vec_GF2& v = *this;

   if (i < 0 || i >= v.length())
      Error("vec_GF2: subscript out of range");

   long q = i/NTL_BITS_PER_LONG;
   long p =  i - q*NTL_BITS_PER_LONG;
   return ref_GF2(INIT_LOOP_HOLE, &v.rep[q], p);
}

static
void SetBit(vec_GF2& v, long i)
{
   if (i < 0 || i >= v.length())
      Error("vec_GF2: subscript out of range");

   long q = i/NTL_BITS_PER_LONG;
   long p = i - q*NTL_BITS_PER_LONG;

   v.rep[q] |= (1UL << p);
}

static
void ClearBit(vec_GF2& v, long i)
{
   if (i < 0 || i >= v.length())
      Error("vec_GF2: subscript out of range");

   long q = i/NTL_BITS_PER_LONG;
   long p = i - q*NTL_BITS_PER_LONG;

   v.rep[q] &= ~(1UL << p);
}

void vec_GF2::put(long i, GF2 a)
{
   if (a == 1)
      SetBit(*this, i);
   else
      ClearBit(*this, i);
}

void swap(vec_GF2& x, vec_GF2& y)
{
   long xf = x.fixed();
   long yf = y.fixed();

   if (xf != yf || (xf && x.length() != y.length()))
      Error("swap: can't swap these vec_GF2s");

   swap(x.rep, y.rep);
   swap(x._len, y._len);
   swap(x._maxlen, y._maxlen);
}

void append(vec_GF2& v, const GF2& a)
{
   long n = v.length();
   v.SetLength(n+1);
   v.put(n, a);
}

void append(vec_GF2& x, const vec_GF2& a)
{
   long a_len = a.length();
   long x_len = x.length();

   if (a_len == 0) return;
   if (x_len == 0) {
      x = a;
      return;
   }

   x.SetLength(x_len + a_len);
   // new bits are guaranteed zero

   ShiftAdd(x.rep.elts(), a.rep.elts(), a.rep.length(), x_len);
}

long operator==(const vec_GF2& a, const vec_GF2& b)
{
   return a.length() == b.length() && a.rep == b.rep;
}

istream & operator>>(istream& s, vec_GF2& a)
{
   static ZZ ival;

   long c;
   if (!s) Error("bad vec_GF2 input");

   c = s.peek();
   while (IsWhiteSpace(c)) {
      s.get();
      c = s.peek();
   }

   if (c != '[') {
      Error("bad vec_GF2 input");
   }

   vec_GF2 ibuf;

   ibuf.SetLength(0);

   s.get();
   c = s.peek();
   while (IsWhiteSpace(c)) {
      s.get();
      c = s.peek();
   }

   while (c != ']' && c != EOF) {
      if (!(s >> ival)) Error("bad vec_GF2 input");
      append(ibuf, to_GF2(ival));

      c = s.peek();

      while (IsWhiteSpace(c)) {
         s.get();
         c = s.peek();
      }
   }

   if (c == EOF) Error("bad vec_GF2 input");
   s.get();

   a = ibuf;
   return s;
}

ostream& operator<<(ostream& s, const vec_GF2& a)
{
   long i, n;
   GF2 c;

   n = a.length();

   s << '[';

   for (i = 0; i < n; i++) {
      c = a.get(i);
      if (c == 0)
         s << "0";
      else
         s << "1";
      if (i < n-1) s << " ";
   }

   s << ']';

   return s;
}

// math operations:

void mul(vec_GF2& x, const vec_GF2& a, GF2 b)
{
   x = a;
   if (b == 0)
      clear(x);
}

void add(vec_GF2& x, const vec_GF2& a, const vec_GF2& b)
{
   long blen = a.length();

   if (b.length() != blen) Error("vec_GF2 add: length mismatch");

   x.SetLength(blen);

   long wlen = a.rep.length();
   long i;

   _ntl_ulong *xp = x.rep.elts();
   const _ntl_ulong *ap = a.rep.elts();
   const _ntl_ulong *bp = b.rep.elts();

   for (i = 0; i < wlen; i++)
      xp[i] = ap[i] ^ bp[i];
}

void clear(vec_GF2& x)
{
   long wlen = x.rep.length();
   long i;
   _ntl_ulong *xp = x.rep.elts();

   for (i = 0; i < wlen; i++)
      xp[i] = 0;
}

long IsZero(const vec_GF2& x)
{
   long wlen = x.rep.length();
   long i;
   const _ntl_ulong *xp = x.rep.elts();

   for (i = 0; i < wlen; i++)
      if (xp[i] != 0) return 0;

   return 1;
}

vec_GF2 operator+(const vec_GF2& a, const vec_GF2& b)
{
   vec_GF2 res;
   add(res, a, b);
   NTL_OPT_RETURN(vec_GF2, res);
}

vec_GF2 operator-(const vec_GF2& a, const vec_GF2& b)
{
   vec_GF2 res;
   add(res, a, b);
   NTL_OPT_RETURN(vec_GF2, res);
}

static
void ShiftToHigh(vec_GF2& x, const vec_GF2& a, long n)
// assumes 0 <= n < a.length()

{
   long l = a.length();

   x.SetLength(l);

   _ntl_ulong *xp = x.rep.elts();
   const _ntl_ulong *ap = a.rep.elts();

   long wn = n/NTL_BITS_PER_LONG;
   long bn = n - wn*NTL_BITS_PER_LONG;

   long sa = a.rep.length();

   long i;

   if (bn == 0) {
      for (i = sa-1; i >= wn; i--)
         xp[i] = ap[i-wn];
      for (i = wn-1; i >= 0; i--)
         xp[i] = 0;
   }
   else {
      for (i = sa-1; i >= wn+1; i--)
         xp[i] = (ap[i-wn] << bn) | (ap[i-wn-1] >> (NTL_BITS_PER_LONG-bn));
      xp[wn] = ap[0] << bn;
      for (i = wn-1; i >= 0; i--)
         xp[i] = 0;
   }

   long p = l % NTL_BITS_PER_LONG;

   if (p != 0)
      xp[sa-1] &= (1UL << p) - 1UL;

}

static
void ShiftToLow(vec_GF2& x, const vec_GF2& a, long n)
// assumes 0 <= n < a.length()

{
   long l = a.length();

   x.SetLength(l);

   _ntl_ulong *xp = x.rep.elts();
   const _ntl_ulong *ap = a.rep.elts();

   long wn = n/NTL_BITS_PER_LONG;
   long bn = n - wn*NTL_BITS_PER_LONG;

   long sa = a.rep.length();

   long i;

   if (bn == 0) {
      for (i = 0; i < sa-wn; i++)
         xp[i] = ap[i+wn];
   }
   else {
      for (i = 0; i < sa-wn-1; i++)
         xp[i] = (ap[i+wn] >> bn) | (ap[i+wn+1] << (NTL_BITS_PER_LONG - bn));

      xp[sa-wn-1] = ap[sa-1] >> bn;
   }

   for (i = sa-wn; i < sa; i++)
      xp[i] = 0;
}

void shift(vec_GF2& x, const vec_GF2& a, long n)
{
   long l = a.length();

   if (n >= l || n <= -l) {
      x.SetLength(l);
      clear(x);
   }
   else if (n < 0)
      ShiftToLow(x, a, -n); // |n| < l, so -n won't overflow!
   else
      ShiftToHigh(x, a, n);
}

// This code is simply canibalized from GF2X.c...
// so much for "code re-use" and "modularity"

static _ntl_ulong revtab[256] = {

0UL, 128UL, 64UL, 192UL, 32UL, 160UL, 96UL, 224UL, 16UL, 144UL,
80UL, 208UL, 48UL, 176UL, 112UL, 240UL, 8UL, 136UL, 72UL, 200UL,
40UL, 168UL, 104UL, 232UL, 24UL, 152UL, 88UL, 216UL, 56UL, 184UL,
120UL, 248UL, 4UL, 132UL, 68UL, 196UL, 36UL, 164UL, 100UL, 228UL,
20UL, 148UL, 84UL, 212UL, 52UL, 180UL, 116UL, 244UL, 12UL, 140UL,
76UL, 204UL, 44UL, 172UL, 108UL, 236UL, 28UL, 156UL, 92UL, 220UL,
60UL, 188UL, 124UL, 252UL, 2UL, 130UL, 66UL, 194UL, 34UL, 162UL,
98UL, 226UL, 18UL, 146UL, 82UL, 210UL, 50UL, 178UL, 114UL, 242UL,
10UL, 138UL, 74UL, 202UL, 42UL, 170UL, 106UL, 234UL, 26UL, 154UL,
90UL, 218UL, 58UL, 186UL, 122UL, 250UL, 6UL, 134UL, 70UL, 198UL,
38UL, 166UL, 102UL, 230UL, 22UL, 150UL, 86UL, 214UL, 54UL, 182UL,
118UL, 246UL, 14UL, 142UL, 78UL, 206UL, 46UL, 174UL, 110UL, 238UL,
30UL, 158UL, 94UL, 222UL, 62UL, 190UL, 126UL, 254UL, 1UL, 129UL,
65UL, 193UL, 33UL, 161UL, 97UL, 225UL, 17UL, 145UL, 81UL, 209UL,
49UL, 177UL, 113UL, 241UL, 9UL, 137UL, 73UL, 201UL, 41UL, 169UL,
105UL, 233UL, 25UL, 153UL, 89UL, 217UL, 57UL, 185UL, 121UL, 249UL,
5UL, 133UL, 69UL, 197UL, 37UL, 165UL, 101UL, 229UL, 21UL, 149UL,
85UL, 213UL, 53UL, 181UL, 117UL, 245UL, 13UL, 141UL, 77UL, 205UL,
45UL, 173UL, 109UL, 237UL, 29UL, 157UL, 93UL, 221UL, 61UL, 189UL,
125UL, 253UL, 3UL, 131UL, 67UL, 195UL, 35UL, 163UL, 99UL, 227UL,
19UL, 147UL, 83UL, 211UL, 51UL, 179UL, 115UL, 243UL, 11UL, 139UL,
75UL, 203UL, 43UL, 171UL, 107UL, 235UL, 27UL, 155UL, 91UL, 219UL,
59UL, 187UL, 123UL, 251UL, 7UL, 135UL, 71UL, 199UL, 39UL, 167UL,
103UL, 231UL, 23UL, 151UL, 87UL, 215UL, 55UL, 183UL, 119UL, 247UL,
15UL, 143UL, 79UL, 207UL, 47UL, 175UL, 111UL, 239UL, 31UL, 159UL,
95UL, 223UL, 63UL, 191UL, 127UL, 255UL  };

static inline
_ntl_ulong rev1(_ntl_ulong a)
{
   return NTL_BB_REV_CODE;
}

void reverse(vec_GF2& c, const vec_GF2& a)
// c = reverse of a

{
   long n = a.length();

   c = a;

   if (n <= 0) {
      return;
   }

   long wn = n/NTL_BITS_PER_LONG;
   long bn = n - wn*NTL_BITS_PER_LONG;

   if (bn != 0) {
      wn++;
      bn = NTL_BITS_PER_LONG - bn;
   }

   _ntl_ulong *cp = c.rep.elts();

   long i;

   if (bn != 0) {
      for (i = wn-1; i >= 1; i--)
         cp[i] = (cp[i] << bn) | (cp[i-1] >> (NTL_BITS_PER_LONG-bn));
      cp[0] = cp[0] << bn;
   }

   for (i = 0; i < wn/2; i++) {
      _ntl_ulong t; t = cp[i]; cp[i] = cp[wn-1-i]; cp[wn-1-i] = t;
   }

   for (i = 0; i < wn; i++)
      cp[i] = rev1(cp[i]);
}

static
long weight1(_ntl_ulong a)
{
   long res = 0;
   while (a) {
      if (a & 1) res ++;
      a >>= 1;
   }
   return res;
}

long weight(const vec_GF2& a)
{
   long wlen = a.rep.length();
   long res = 0;
   long i;
   for (i = 0; i < wlen; i++)
      res += weight1(a.rep[i]);

   return res;
}

void random(vec_GF2& x, long n)
{
   if (n < 0) Error("random: bad arg");

   x.SetLength(n);

   long wl = x.rep.length();
   long i;

   for (i = 0; i < wl-1; i++) {
      x.rep[i] = RandomWord();
   }

   if (n > 0) {
      long pos = n % NTL_BITS_PER_LONG;
      if (pos == 0) pos = NTL_BITS_PER_LONG;
      x.rep[wl-1] = RandomBits_ulong(pos);
   }
}

void VectorCopy(vec_GF2& x, const vec_GF2& a, long n)
{
   if (n < 0) Error("VectorCopy: negative length");
   if (NTL_OVERFLOW(n, 1, 0)) Error("overflow in VectorCopy");

   long m = min(n, a.length());

   x.SetLength(n);

   long wn = (n + NTL_BITS_PER_LONG - 1)/NTL_BITS_PER_LONG;
   long wm = (m + NTL_BITS_PER_LONG - 1)/NTL_BITS_PER_LONG;

   _ntl_ulong *xp = x.rep.elts();
   const _ntl_ulong *ap = a.rep.elts();

   long i;

   for (i = 0; i < wm; i++)
      xp[i] = ap[i];

   for (i = wm; i < wn; i++)
      xp[i] = 0;

   long p = n % NTL_BITS_PER_LONG;
   if (p != 0) {
      xp[wn-1] &= ((1UL << p) - 1UL);
   }
}

NTL_END_IMPL