// Author(s): Koen Degeling
// Copyright: see the accompanying file COPYING or copy at
// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
//
// Distributed under 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)
//
/// \file solvelts_gjkw.cpp
/// Reads a parity game from input and converts it to an LTS and then
/// reduces it using Divergence-preserving branching bisimulation
/// equivalence with the O(m log m) algorithm
/// [Groote/Jansen/Keiren/Wijs 2017]

#include "mcrl2/lts/lts_fsm.h"
#include "mcrl2/utilities/input_output_tool.h"
#include "pg.h"
#include "pgsolver_io.h"
#include "utilities.h"
#include "mcrl2/lts/lts_algorithm.h"
#include <unordered_map>

#include <boost/graph/strong_components.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>

using namespace mcrl2::lts;
using namespace mcrl2::log;

namespace mcrl2 {

class pg_convert
{

  protected:
  /** Storing Parity Game */
  parity_game_t m_pg;
  /** Reduced Kripke structure in LTS */
  lts::lts_aut_t m_lts;

  public:
  explicit pg_convert(parity_game_t const& pg)
      : m_pg(pg)
  {}

  void reduce_pg_scc()
  /* Reduce Parity Game graph to remove Strongly Connected Components. */
  {
    typedef typename boost::graph_traits<parity_game_t>::vertices_size_type vertex_size_t;
    parity_game_t* reduced = new parity_game_t(m_pg);
    boost::graph_traits<parity_game_t>::edge_iterator e, m;
    /* We (deep) copied the graph and go over all edges and remove any edge for which
      the source and target label are not the same (to ensure our SCC have same label)*/
    for(boost::tie(e,m) = edges(m_pg); e != m; ++e)
    {
      vertex_size_t s = source(*e, m_pg);
      vertex_size_t t = target(*e, m_pg);
      if(m_pg[s].prio != m_pg[t].prio || m_pg[s].player != m_pg[t].player)
      {
        // Since we aren't removing edges from m_pg, this is safe.
        boost::remove_edge(s,t,*reduced);
      }
    }
    /* We compute the strong components and go over the edges in our original graph.
      We merge all vertices that are in the same SCC. */
    std::vector<vertex_size_t> components (boost::num_vertices(*reduced), 0);
    size_t num = boost::strong_components(*reduced, &components[0]);
    std::map<size_t, vertex_size_t> component_map;
    mCRL2log(verbose) << "Shrinking the graph" << std::endl;
    boost::graph_traits<parity_game_t >::vertex_iterator i, n;
    for(boost::tie(i, n) = vertices(m_pg); i != n; ++i)
    {
      size_t group = components[*i];
      if(component_map.count(group)==1)
        // If already present, we shrink the graph by merging i into u.
      {
        vertex_size_t u = component_map[group];
        merge(m_pg, u, *i);
        m_pg[*i].unused = true;
      }
      else
        // We haven't seen any vertices of this group yet, so we add it to the map.
      {
        component_map[group] = *i;
      }
    }
    // We go over all the vertices and remove those which are unused.
    for(auto i = 0; i < boost::num_vertices(m_pg);)
    {
      if(m_pg[i].unused)
      {
        boost::clear_vertex(i, m_pg);
        boost::remove_vertex(i, m_pg);
      }
      else
      {
        i++;
      }
    }
  }

  typedef typename boost::graph_traits<parity_game_t>::
  vertices_size_type vertex_size_t;
  void merge(parity_game_t& pg, vertex_size_t s, vertex_size_t r)
  /* Merge vertex r into vertex s*/
  {
    boost::graph_traits<parity_game_t>::out_edge_iterator oe, m;
    for(boost::tie(oe,m) = out_edges(r, pg); oe != m; ++oe)
    {
      boost::add_edge(s, target(*oe, pg), pg); // Add new edge
    }
    boost::graph_traits<parity_game_t>::in_edge_iterator ie, n;
    for(boost::tie(ie,n) = in_edges(r, pg); ie != n; ++ie)
    {
      boost::add_edge(source(*ie, pg), s, pg); // Add new edge
    }
  }

  void convert_pg_to_lts()
  /* Convert Parity Game to an LTS of type lts_aut */
  {
    // LTS in which we save the Kripke structure
    std::map<size_t, size_t> vertex_state_map;
    std::map<size_t, size_t> svertex_state_map;
    std::unordered_map<std::string, size_t> label_map;

    boost::graph_traits<parity_game_t >::vertex_iterator i, n;
    // We loop over vertices and add new states to the lts
    size_t bot = m_lts.add_action(action_label_string("bot"));
    label_map.insert(std::pair<std::string,size_t>("bot", bot));
    for(boost::tie(i, n) = vertices(m_pg); i != n; ++i)
    {
      size_t state = m_lts.add_state();
      size_t shadow_state = m_lts.add_state();
      // Add the (vertex,state) pair to map
      vertex_state_map[*i] = state;
      svertex_state_map[*i] = shadow_state;
      // Add bottom transitions
      m_lts.add_transition(transition(state, bot, shadow_state));
      // Add label transition
      size_t player = m_pg[*i].player;
      size_t priority = m_pg[*i].prio;
      mCRL2log(verbose) << "player: " << player << " prio " << priority << std::endl;
      std::stringstream string_label;
      string_label << player << "," << priority;
      size_t label_no;
      if(label_map.count(string_label.str()) == 0)
      { // Label does not yet exist
        label_no = m_lts.add_action(action_label_string(string_label.str()));
        label_map.insert(std::pair<std::string,size_t>(string_label.str(), label_no));
      }
      else
      {
        auto label = label_map.find(string_label.str());
        label_no = label->second;
      }
      m_lts.add_transition(transition(shadow_state, label_no, state));
      mCRL2log(verbose) << "Added state: " << state << ", " << *i << std::endl;
    }
    m_lts.set_initial_state(0);
    size_t tau = m_lts.add_action(action_label_string("tau"));
    m_lts.apply_hidden_label_map(tau);
    // Loop over all edges in the graph and add transitions to our lts
    boost::graph_traits<parity_game_t>::edge_iterator e, m;
    for(boost::tie(e,m) = edges(m_pg); e != m; ++e)
    {
      size_t src = source(*e, m_pg);
      size_t trgt = target(*e, m_pg);
      size_t s = vertex_state_map[src];
      size_t t = vertex_state_map[trgt];
      if(m_pg[src].player == m_pg[trgt].player && m_pg[src].prio == m_pg[trgt].prio)
      { // Add a tau transition
        m_lts.add_transition(transition(s, tau, t));
      }
      else
      { // Add L(t) transition
        size_t player = m_pg[trgt].player;
        size_t priority = m_pg[trgt].prio;
        std::stringstream string_label;
        string_label << player << "," << priority;
        auto label = label_map.find(string_label.str());
        size_t label_no = label->second;
        m_lts.add_transition(transition(s, label_no, t));
      }

    }
  }

  void convert_lts_to_pg()
  /*  Convert reduced LTS in lts_aut to a Parity Game in
      parity_game_t format. */
  {
    // Clear old Parity Game and construct new one from m_lts
    m_pg.clear();
    std::map<size_t, size_t> state_vertex_map;

    for(const transition& tr: m_lts.get_transitions())
    { /* We loop over all transitions and add vertices for the source and target
         states if they did not yet exist. If transition contains a label, we add
         the label information to the target state.*/
      if(state_vertex_map.count(tr.from()) == 0)
      {
        size_t v = boost::add_vertex(m_pg);
        state_vertex_map[tr.from()] = v;
        m_pg[v].unused = true;
      }
      if(state_vertex_map.count(tr.to()) == 0)
      {
        size_t u = boost::add_vertex(m_pg);
        state_vertex_map[tr.to()] = u;
        m_pg[u].unused = true;
      }
      boost::add_edge(state_vertex_map[tr.from()], state_vertex_map[tr.to()], m_pg);
      action_label_string string_label = m_lts.action_label(tr.label());
      mCRL2log(verbose) << "Label: " << string_label << std::endl;
      if(!(string_label == "tau" || string_label == "bot"))
      { // Transition with label L(t)
        size_t u = state_vertex_map[tr.to()];
        std::vector<size_t> labels = get_labels(string_label); // We get the labels from the action string
        if(m_pg[u].unused)
        { // If vertex was not yet initialised
          m_pg[u].player = labels[0] == 0 ? even : odd;
          m_pg[u].prio = labels[1];
          m_pg[u].unused = false; // Mark vertex as initialised
          mCRL2log(verbose) << "Set vertex: " << u << " to: " << labels[0] <<
                                                  " " << labels[1] << std::endl;
        }
      }
    }

    size_t i = 0;
    while(i < boost::num_vertices(m_pg))
    { /* We loop over all the vertices and remove those which have not been
         initialised. */
      if(m_pg[i].unused)
      {
        boost::clear_vertex(i, m_pg);
        boost::remove_vertex(i, m_pg);
      }
      else
      {
        i++;
      }
    }
  }

  std::vector<size_t> get_labels(std::string str)
  /* Auxiliary function to get a vector with the {player,priority} from the action
     string. */
  {
    std::vector<size_t> labels;
    size_t i = str.find(',');
    std::string player = str.substr(0, i);
    std::string priority = str.substr(i+1, str.length()-i-1);
    std::stringstream f(player);
    std::stringstream g(priority);
    size_t pl,pr;
    f >> pl;
    g >> pr;
    labels.push_back(pl);
    labels.push_back(pr);
    return labels;
  }

  void run(std::string file, std::string infile)
  {
    mCRL2log(verbose) << "Filename: " << infile << std::endl;
    mcrl2::utilities::execution_timer timer(infile,
               "/home/koen/Documents/TUe/Master/Quartile2/2IMF00Seminar/mCRL2/timing");
    timer.start("execution");
    reduce_pg_scc(); // Make sure we remove any cycles in the graph.
    convert_pg_to_lts(); // Convert parity game to LTS.
    /* Call GJKW algorithm and start timers for reducing and execution. */
    timer.start("reducing");
    lts::detail::bisim_partitioner_gjkw<lts_aut_t> part(m_lts, true, true);
    part.replace_transition_system(true, true);
    timer.finish("reducing");
    convert_lts_to_pg(); // Convert LTS to Parity Game.
    std::ofstream m_ofstream;
    std::ostream& os = open_output(file, m_ofstream);
    print_pgsolver(m_pg, os);
    timer.finish("execution");
    timer.report();
  }
};

}

using mcrl2::utilities::tools::input_output_tool;

class solvelts_tool: public input_output_tool
{
  private:
  std::ifstream m_ifstream;

  protected:
  typedef input_output_tool super;

  public:
    solvelts_tool()
      : super(
      "solvelts",
      "Koen Degeling",
      "Converts a parity game to an lts and uses the GJKW algorithm"
      " to compute the quotient structure and revert back to parity game",
      "Longer description"
    )
    {}

  bool run()
  {
    std::string infile = input_filename();
    std::istream& is = open_input(infile, m_ifstream);
    parity_game_t pg;
    parse_pgsolver(pg, is, timer());
    mcrl2::pg_convert c = mcrl2::pg_convert(pg);
    c.run(output_filename(), infile);
    mCRL2log(verbose) << "Terminating..." << std::endl;
    return true;
  }
};

int main(int argc, char* argv[])
{
  return solvelts_tool().execute(argc, argv);
}


#include <NTL/ZZ_pE.h>

NTL_START_IMPL

NTL_TLS_GLOBAL_DECL(SmartPtr<ZZ_pEInfoT>, ZZ_pEInfo_stg)

NTL_CHEAP_THREAD_LOCAL
ZZ_pEInfoT *ZZ_pEInfo = 0;

ZZ_pEInfoT::ZZ_pEInfoT(const ZZ_pX& NewP)
{
   build(p, NewP);

   _card_base = ZZ_p::modulus();
   _card_exp = deg(NewP);
}

const ZZ& ZZ_pE::cardinality()
{
   if (!ZZ_pEInfo) LogicError("ZZ_pE::cardinality: undefined modulus");

   do { // NOTE: thread safe lazy init
      Lazy<ZZ>::Builder builder(ZZ_pEInfo->_card);
      if (!builder()) break;
      UniquePtr<ZZ> p;
      p.make();
      power(*p, ZZ_pEInfo->_card_base, ZZ_pEInfo->_card_exp);
      builder.move(p);
   } while (0);

   return *ZZ_pEInfo->_card;
}

void ZZ_pE::init(const ZZ_pX& p)
{
   ZZ_pEContext c(p);
   c.restore();
}

void ZZ_pEContext::save()
{
   NTL_TLS_GLOBAL_ACCESS(ZZ_pEInfo_stg);
   ptr = ZZ_pEInfo_stg;
}

void ZZ_pEContext::restore() const
{
   NTL_TLS_GLOBAL_ACCESS(ZZ_pEInfo_stg);
   ZZ_pEInfo_stg = ptr;
   ZZ_pEInfo = ZZ_pEInfo_stg.get();
}

ZZ_pEBak::~ZZ_pEBak()
{
   if (MustRestore) c.restore();
}

void ZZ_pEBak::save()
{
   c.save();
   MustRestore = true;
}

void ZZ_pEBak::restore()
{
   c.restore();
   MustRestore = false;
}

const ZZ_pE& ZZ_pE::zero()
{
   static const ZZ_pE z(INIT_NO_ALLOC); // GLOBAL (assumes C++11 thread-safe init)
   return z;
}

istream& operator>>(istream& s, ZZ_pE& x)
{
   ZZ_pX y;

   NTL_INPUT_CHECK_RET(s, s >> y);
   conv(x, y);

   return s;
}

void div(ZZ_pE& x, const ZZ_pE& a, const ZZ_pE& b)
{
   ZZ_pE t;

   inv(t, b);
   mul(x, a, t);
}

void div(ZZ_pE& x, const ZZ_pE& a, long b)
{
   NTL_ZZ_pRegister(B);
   B = b;
   inv(B, B);
   mul(x, a, B);
}

void div(ZZ_pE& x, const ZZ_pE& a, const ZZ_p& b)
{
   NTL_ZZ_pRegister(B);
   B = b;
   inv(B, B);
   mul(x, a, B);
}

void div(ZZ_pE& x, long a, const ZZ_pE& b)
{
   ZZ_pE t;
   inv(t, b);
   mul(x, a, t);
}

void div(ZZ_pE& x, const ZZ_p& a, const ZZ_pE& b)
{
   ZZ_pE t;
   inv(t, b);
   mul(x, a, t);
}

void inv(ZZ_pE& x, const ZZ_pE& a)
{
   InvMod(x._ZZ_pE__rep, a._ZZ_pE__rep, ZZ_pE::modulus());
}

NTL_END_IMPL


// Copyright (c) 2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "amount.h"
#include "policy/feerate.h"
#include "test/test_dash.h"

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(amount_tests, BasicTestingSetup)

BOOST_AUTO_TEST_CASE(MoneyRangeTest)
{
    BOOST_CHECK_EQUAL(MoneyRange(CAmount(-1)), false);
    BOOST_CHECK_EQUAL(MoneyRange(MAX_MONEY + CAmount(1)), false);
    BOOST_CHECK_EQUAL(MoneyRange(CAmount(1)), true);
}

BOOST_AUTO_TEST_CASE(GetFeeTest)
{
    CFeeRate feeRate, altFeeRate;

    feeRate = CFeeRate(0);
    // Must always return 0
    BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0);
    BOOST_CHECK_EQUAL(feeRate.GetFee(1e5), 0);

    feeRate = CFeeRate(1000);
    // Must always just return the arg
    BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0);
    BOOST_CHECK_EQUAL(feeRate.GetFee(1), 1);
    BOOST_CHECK_EQUAL(feeRate.GetFee(121), 121);
    BOOST_CHECK_EQUAL(feeRate.GetFee(999), 999);
    BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), 1e3);
    BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), 9e3);

    feeRate = CFeeRate(-1000);
    // Must always just return -1 * arg
    BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0);
    BOOST_CHECK_EQUAL(feeRate.GetFee(1), -1);
    BOOST_CHECK_EQUAL(feeRate.GetFee(121), -121);
    BOOST_CHECK_EQUAL(feeRate.GetFee(999), -999);
    BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), -1e3);
    BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), -9e3);

    feeRate = CFeeRate(123);
    // Truncates the result, if not integer
    BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0);
    BOOST_CHECK_EQUAL(feeRate.GetFee(8), 1); // Special case: returns 1 instead of 0
    BOOST_CHECK_EQUAL(feeRate.GetFee(9), 1);
    BOOST_CHECK_EQUAL(feeRate.GetFee(121), 14);
    BOOST_CHECK_EQUAL(feeRate.GetFee(122), 15);
    BOOST_CHECK_EQUAL(feeRate.GetFee(999), 122);
    BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), 123);
    BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), 1107);

    feeRate = CFeeRate(-123);
    // Truncates the result, if not integer
    BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0);
    BOOST_CHECK_EQUAL(feeRate.GetFee(8), -1); // Special case: returns -1 instead of 0
    BOOST_CHECK_EQUAL(feeRate.GetFee(9), -1);

    // check alternate constructor
    feeRate = CFeeRate(1000);
    altFeeRate = CFeeRate(feeRate);
    BOOST_CHECK_EQUAL(feeRate.GetFee(100), altFeeRate.GetFee(100));

    // Check full constructor
    // default value
    BOOST_CHECK(CFeeRate(CAmount(-1), 1000) == CFeeRate(-1));
    BOOST_CHECK(CFeeRate(CAmount(0), 1000) == CFeeRate(0));
    BOOST_CHECK(CFeeRate(CAmount(1), 1000) == CFeeRate(1));
    // lost precision (can only resolve satoshis per kB)
    BOOST_CHECK(CFeeRate(CAmount(1), 1001) == CFeeRate(0));
    BOOST_CHECK(CFeeRate(CAmount(2), 1001) == CFeeRate(1));
    // some more integer checks
    BOOST_CHECK(CFeeRate(CAmount(26), 789) == CFeeRate(32));
    BOOST_CHECK(CFeeRate(CAmount(27), 789) == CFeeRate(34));
    // Maximum size in bytes, should not crash
    CFeeRate(MAX_MONEY, std::numeric_limits<size_t>::max() >> 1).GetFeePerK();
}

BOOST_AUTO_TEST_CASE(BinaryOperatorTest)
{
    CFeeRate a, b;
    a = CFeeRate(1);
    b = CFeeRate(2);
    BOOST_CHECK(a < b);
    BOOST_CHECK(b > a);
    BOOST_CHECK(a == a);
    BOOST_CHECK(a <= b);
    BOOST_CHECK(a <= a);
    BOOST_CHECK(b >= a);
    BOOST_CHECK(b >= b);
    // a should be 0.00000002 DASH/kB now
    a += a;
    BOOST_CHECK(a == b);
}

BOOST_AUTO_TEST_CASE(ToStringTest)
{
    CFeeRate feeRate;
    feeRate = CFeeRate(1);
    BOOST_CHECK_EQUAL(feeRate.ToString(), "0.00000001 DASH/kB");
}

BOOST_AUTO_TEST_SUITE_END()


/*
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2019, Locus Robotics
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the copyright holder nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */
#include <fuse_constraints/marginal_cost_function.h>

#include <fuse_core/eigen.h>
#include <fuse_core/local_parameterization.h>

#include <Eigen/Core>

#include <vector>
#include <iostream>

namespace fuse_constraints
{

MarginalCostFunction::MarginalCostFunction(
    const std::vector<fuse_core::MatrixXd>& A,
    const fuse_core::VectorXd& b,
    const std::vector<fuse_core::VectorXd>& x_bar,
    const std::vector<fuse_core::LocalParameterization::SharedPtr>& local_parameterizations) :
  A_(A),
  b_(b),
  local_parameterizations_(local_parameterizations),
  x_bar_(x_bar)
{
  set_num_residuals(b_.rows());
  for (const auto& x_bar : x_bar_)
  {
    mutable_parameter_block_sizes()->push_back(x_bar.size());
  }
}

bool MarginalCostFunction::Evaluate(
  double const* const* parameters,
  double* residuals,
  double** jacobians) const
{
  // Compute cost
  Eigen::Map<fuse_core::VectorXd> residuals_map(residuals, num_residuals());
  residuals_map = b_;
  for (size_t i = 0; i < A_.size(); ++i)
  {
    fuse_core::VectorXd delta(A_[i].cols());
    if (local_parameterizations_[i])
    {
      local_parameterizations_[i]->Minus(x_bar_[i].data(), parameters[i], delta.data());
    }
    else
    {
      for (int j = 0; j < x_bar_[i].rows(); ++j)
      {
        delta[j] = parameters[i][j] - x_bar_[i][j];
      }
    }
    residuals_map += A_[i] * delta;
  }

  // Compute requested Jacobians
  if (jacobians)
  {
    for (size_t i = 0; i < A_.size(); ++i)
    {
      if (jacobians[i])
      {
        if (local_parameterizations_[i])
        {
          const auto& local_parameterization = local_parameterizations_[i];
          fuse_core::MatrixXd J_local(local_parameterization->LocalSize(), local_parameterization->GlobalSize());
          local_parameterization->ComputeMinusJacobian(parameters[i], J_local.data());
          Eigen::Map<fuse_core::MatrixXd>(jacobians[i], num_residuals(), parameter_block_sizes()[i]) = A_[i] * J_local;
        }
        else
        {
          Eigen::Map<fuse_core::MatrixXd>(jacobians[i], num_residuals(), parameter_block_sizes()[i]) = A_[i];
        }
      }
    }
  }

  return true;
}

}  // namespace fuse_constraints


/*
 Copyright (C) 2018 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include "toplevelfixture.hpp"
#include <boost/make_shared.hpp>
#include <boost/test/unit_test.hpp>
#include <ql/currencies/all.hpp>
#include <ql/indexes/ibor/usdlibor.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/termstructures/yield/discountcurve.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
#include <ql/time/calendars/all.hpp>
#include <ql/time/daycounters/actual360.hpp>
#include <ql/types.hpp>
#include <qle/pricingengines/crossccyswapengine.hpp>
#include <qle/termstructures/crossccyfixfloatswaphelper.hpp>

using namespace std;
using namespace boost::unit_test_framework;
using namespace QuantLib;
using namespace QuantExt;

namespace {

struct CommonVars {

    Date asof;
    Natural settlementDays;
    Calendar payCalendar;
    BusinessDayConvention payConvention;
    Natural payLag;
    Period tenor;
    Currency fixedCurrency;
    Frequency fixedFrequency;
    DayCounter fixedDayCount;
    Real usdNominal;

    // 5Y TRY annual fixed rate vs. USD 3M Libor
    Handle<Quote> rate;
    // USD/TRY spot FX rate
    boost::shared_ptr<SimpleQuote> spotFx;
    // Spread on float leg of swap
    boost::shared_ptr<SimpleQuote> spread;
    // USD Libor 3M projection curve
    Handle<YieldTermStructure> liborProjCurve;
    // USD Libor 3M index
    boost::shared_ptr<IborIndex> index;
    // USD discount curve
    Handle<YieldTermStructure> usdDiscCurve;

    // Hold the helper created during testing
    boost::shared_ptr<CrossCcyFixFloatSwapHelper> helper;

    CommonVars() {
        asof = Date(11, Sep, 2018);
        settlementDays = 2;
        payCalendar = JointCalendar(UnitedStates(), UnitedKingdom(), Turkey());
        payConvention = Following;
        payLag = 0;
        tenor = 5 * Years;
        fixedCurrency = TRYCurrency();
        fixedFrequency = Annual;
        fixedDayCount = Actual360();
        usdNominal = 10000000.0;

        rate = Handle<Quote>(boost::make_shared<SimpleQuote>(0.25));
        spotFx = boost::make_shared<SimpleQuote>(6.4304);
        spread = boost::make_shared<SimpleQuote>(0.0);
        liborProjCurve =
            Handle<YieldTermStructure>(boost::make_shared<FlatForward>(0, NullCalendar(), 0.029773, Actual365Fixed()));
        index = boost::make_shared<USDLibor>(3 * Months, liborProjCurve);
        usdDiscCurve =
            Handle<YieldTermStructure>(boost::make_shared<FlatForward>(0, NullCalendar(), 0.026727, Actual365Fixed()));
    }
};

boost::shared_ptr<CrossCcyFixFloatSwap> makeTestSwap(const CommonVars& vars,
                                                     const Handle<YieldTermStructure>& discCurve) {

    // Swap start and end date
    Date referenceDate = Settings::instance().evaluationDate();
    referenceDate = vars.payCalendar.adjust(referenceDate);
    Date start = vars.payCalendar.advance(referenceDate, vars.settlementDays * Days);
    Date end = start + vars.tenor;

    // Fixed TRY schedule
    Schedule fixedSchedule(start, end, Period(vars.fixedFrequency), vars.payCalendar, vars.payConvention,
                           vars.payConvention, DateGeneration::Backward, false);

    // Float USD schedule
    Schedule floatSchedule(start, end, vars.index->tenor(), vars.payCalendar, vars.payConvention, vars.payConvention,
                           DateGeneration::Backward, false);

    // Create swap
    boost::shared_ptr<CrossCcyFixFloatSwap> swap(new CrossCcyFixFloatSwap(
        CrossCcyFixFloatSwap::Payer, vars.usdNominal * vars.spotFx->value(), vars.fixedCurrency, fixedSchedule,
        vars.rate->value(), vars.fixedDayCount, vars.payConvention, vars.payLag, vars.payCalendar, vars.usdNominal,
        vars.index->currency(), floatSchedule, vars.index, vars.spread->value(), vars.payConvention, vars.payLag,
        vars.payCalendar));

    // Attach pricing engine
    boost::shared_ptr<PricingEngine> engine = boost::make_shared<CrossCcySwapEngine>(
        vars.fixedCurrency, discCurve, vars.index->currency(), vars.usdDiscCurve, Handle<Quote>(vars.spotFx));
    swap->setPricingEngine(engine);

    return swap;
}

// Use the helper that we are testing to create a bootstrapped curve
Handle<YieldTermStructure> bootstrappedCurve(CommonVars& vars) {

    // Create a helper
    vector<boost::shared_ptr<RateHelper> > helpers(1);
    vars.helper.reset(new CrossCcyFixFloatSwapHelper(
        vars.rate, Handle<Quote>(vars.spotFx), vars.settlementDays, vars.payCalendar, vars.payConvention, vars.tenor,
        vars.fixedCurrency, vars.fixedFrequency, vars.payConvention, vars.fixedDayCount, vars.index, vars.usdDiscCurve,
        Handle<Quote>(vars.spread)));
    helpers[0] = vars.helper;

    // Create a yield curve referencing the helper
    return Handle<YieldTermStructure>(
        boost::make_shared<PiecewiseYieldCurve<Discount, LogLinear> >(0, NullCalendar(), helpers, Actual365Fixed()));
}

} // namespace

BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)

BOOST_AUTO_TEST_SUITE(CrossCurrencyFixFloatSwapHelperTest)

BOOST_AUTO_TEST_CASE(testBootstrap) {

    BOOST_TEST_MESSAGE("Test simple bootstrap against cross currency fix float swap");

    SavedSettings backup;

    CommonVars vars;

    Settings::instance().evaluationDate() = vars.asof;

    // Create a helper and bootstrapped curve
    Handle<YieldTermStructure> tryDiscCurve = bootstrappedCurve(vars);

    // Create the helper swap manually and price it using curve bootstrapped from helper
    boost::shared_ptr<CrossCcyFixFloatSwap> swap = makeTestSwap(vars, tryDiscCurve);

    // Swap should have NPV = 0.0. On notional = $10M i.e. TRY60.5M, 1e-5 is enough.
    Real absTol = 1e-5;
    BOOST_CHECK_SMALL(swap->NPV(), absTol);

    // Check fair fixed rates match. Bootstrap uses 1e-12 accuracy.
    Real relTol = 1e-10;
    BOOST_CHECK_CLOSE(vars.rate->value(), swap->fairFixedRate(), relTol);

    // Check the 5Y discount factor
    DiscountFactor expDisc = 0.3299260408883904;
    BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);
}

BOOST_AUTO_TEST_CASE(testSpotFxChange) {

    BOOST_TEST_MESSAGE("Test rebootstrap under spot FX change");

    SavedSettings backup;

    CommonVars vars;

    Settings::instance().evaluationDate() = vars.asof;

    // Create a helper and bootstrapped curve
    Handle<YieldTermStructure> tryDiscCurve = bootstrappedCurve(vars);

    // Create the helper swap manually and price it using curve bootstrapped from helper
    boost::shared_ptr<CrossCcyFixFloatSwap> swap = makeTestSwap(vars, tryDiscCurve);

    // Check NPV = 0.0
    Real absTol = 1e-5;
    BOOST_CHECK_SMALL(swap->NPV(), absTol);

    // Check the 5Y discount factor
    Real relTol = 1e-10;
    DiscountFactor expDisc = 0.3299260408883904;
    BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);

    // Check the nominal of the helper swap
    BOOST_CHECK_CLOSE(vars.spotFx->value(), vars.helper->swap()->fixedNominal(), relTol);

    // Bump the spot rate by 10%
    vars.spotFx->setValue(vars.spotFx->value() * 1.1);

    // Build a new swap using the updated spot FX rate
    swap = makeTestSwap(vars, tryDiscCurve);

    // Check that the new swap's NPV is 0.0
    BOOST_CHECK_SMALL(swap->NPV(), absTol);

    // Check the 5Y discount factor again. It should be the same.
    BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);

    // Check the nominal of the helper swap. Should now be the bumped amount
    BOOST_CHECK_CLOSE(vars.spotFx->value(), vars.helper->swap()->fixedNominal(), relTol);
}

BOOST_AUTO_TEST_CASE(testSpreadChange) {

    BOOST_TEST_MESSAGE("Test rebootstrap under helper spread change");

    SavedSettings backup;

    CommonVars vars;

    Settings::instance().evaluationDate() = vars.asof;

    // Create a helper and bootstrapped curve
    Handle<YieldTermStructure> tryDiscCurve = bootstrappedCurve(vars);

    // Create the helper swap manually and price it using curve bootstrapped from helper
    boost::shared_ptr<CrossCcyFixFloatSwap> swap = makeTestSwap(vars, tryDiscCurve);

    // Check NPV = 0.0
    Real absTol = 1e-5;
    BOOST_CHECK_SMALL(swap->NPV(), absTol);

    // Check the 5Y discount factor
    Real relTol = 1e-10;
    DiscountFactor expDisc = 0.3299260408883904;
    BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);

    // Check the spread of the helper swap
    BOOST_CHECK_CLOSE(vars.spread->value(), vars.helper->swap()->floatSpread(), relTol);

    // Add a 10bps spread
    vars.spread->setValue(0.0010);

    // Build a new swap using the updated spread of 10bps
    swap = makeTestSwap(vars, tryDiscCurve);

    // Check that the new swap's NPV is 0.0
    BOOST_CHECK_SMALL(swap->NPV(), absTol);

    // Check the 5Y discount factor again. Added spread on float => higher discount factor.
    expDisc = 0.3322218009717460;
    BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);

    // Check the spread of the helper swap. Should now be 10bps.
    BOOST_CHECK_CLOSE(vars.spread->value(), vars.helper->swap()->floatSpread(), relTol);
}

BOOST_AUTO_TEST_CASE(testMovingEvaluationDate) {

    BOOST_TEST_MESSAGE("Test rebootstrap after moving evaluation date");

    SavedSettings backup;

    CommonVars vars;

    Settings::instance().evaluationDate() = vars.asof;

    // Create a helper and bootstrapped curve
    Handle<YieldTermStructure> tryDiscCurve = bootstrappedCurve(vars);

    // Create the helper swap manually and price it using curve bootstrapped from helper
    boost::shared_ptr<CrossCcyFixFloatSwap> swap = makeTestSwap(vars, tryDiscCurve);

    // Check NPV = 0.0
    Real absTol = 1e-5;
    BOOST_CHECK_SMALL(swap->NPV(), absTol);

    // Check the 5Y discount factor
    Real relTol = 1e-10;
    DiscountFactor expDisc = 0.3299260408883904;
    BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);

    // Check the start date of the helper swap
    BOOST_CHECK_EQUAL(swap->startDate(), vars.helper->swap()->startDate());

    // Move evaluation date forward
    vars.asof = vars.asof + 1 * Days;
    Settings::instance().evaluationDate() = vars.asof;

    // Build a new swap using new evaluation date
    swap = makeTestSwap(vars, tryDiscCurve);

    // Check that the new swap's NPV is 0.0
    BOOST_CHECK_SMALL(swap->NPV(), absTol);

    // Check the 5Y discount factor again. Changes slightly due to helper holidays/weekends.
    expDisc = 0.3299334970640459;
    BOOST_CHECK_CLOSE(expDisc, tryDiscCurve->discount(vars.asof + 5 * Years), relTol);

    // Check the start date of the helper swap. Should be 1 day greater.
    BOOST_CHECK_EQUAL(swap->startDate(), vars.helper->swap()->startDate());
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


// Boost.Polygon library voronoi_benchmark.cpp file

//          Copyright Andrii Sydorchuk 2010-2012.
// Distributed under 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)

// See http://www.boost.org for updates, documentation, and revision history.

#include <algorithm>
#include <iomanip>
#include <iostream>
#include <fstream>
#include <numeric>
#include <vector>
#include <utility>

#include <boost/random/mersenne_twister.hpp>
#include <boost/timer/timer.hpp>

typedef boost::int32_t int32;
using boost::timer::cpu_times;
using boost::timer::nanosecond_type;

// Include for the Boost.Polygon Voronoi library.
#include <boost/polygon/voronoi.hpp>
typedef boost::polygon::voronoi_diagram<double> VD_BOOST;

// Includes for the CGAL library.
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Segment_Delaunay_graph_filtered_traits_2.h>
#include <CGAL/Segment_Delaunay_graph_2.h>

typedef CGAL::Simple_cartesian<double> K;
typedef CGAL::Segment_Delaunay_graph_filtered_traits_without_intersections_2<K> GT;
typedef CGAL::Segment_Delaunay_graph_2<GT>  SDT_CGAL;
typedef SDT_CGAL::Point_2 Point_CGAL;
typedef SDT_CGAL::Site_2 Site_CGAL;

// Include for the Boost.Polygon library.
#include <boost/polygon/polygon.hpp>
typedef boost::polygon::point_data<int32> POINT_POLYGON;
typedef boost::polygon::segment_data<int32> SEGMENT_POLYGON;
typedef std::vector<SEGMENT_POLYGON> SSD_POLYGON;

const int RANDOM_SEED = 27;
const int NUM_TESTS = 6;
const int NUM_SEGMENTS[] = {10, 100, 1000, 10000, 100000, 1000000};
const int NUM_RUNS[] = {100000, 10000, 1000, 100, 10, 1};
std::ofstream bf("benchmark_segments.txt",
                 std::ios_base::out | std::ios_base::app);
boost::timer::cpu_timer timer;

void format_line(int num_points, int num_tests, double time_per_test) {
  bf << "| " << std::setw(16) << num_points << " ";
  bf << "| " << std::setw(15) << num_tests << " ";
  bf << "| " << std::setw(17) << time_per_test << " ";
  bf << "|" << std::endl;
}

double get_elapsed_secs() {
  cpu_times elapsed_times(timer.elapsed());
  return 1E-9 * static_cast<double>(
      elapsed_times.system + elapsed_times.user);
}

void clean_segment_set(std::vector<SEGMENT_POLYGON>* data) {
  typedef int32 Unit;
  typedef boost::polygon::scanline_base<Unit>::Point Point;
  typedef boost::polygon::scanline_base<Unit>::half_edge half_edge;
  typedef int segment_id;
  std::vector<std::pair<half_edge, segment_id> > half_edges;
  std::vector<std::pair<half_edge, segment_id> > half_edges_out;
  segment_id id = 0;
  half_edges.reserve(data->size());
  for (std::vector<SEGMENT_POLYGON>::iterator it = data->begin();
       it != data->end(); ++it) {
    POINT_POLYGON l = it->low();
    POINT_POLYGON h = it->high();
    half_edges.push_back(std::make_pair(half_edge(l, h), id++));
  }
  half_edges_out.reserve(half_edges.size());
  // Apparently no need to pre-sort data when calling validate_scan.
  boost::polygon::line_intersection<Unit>::validate_scan(
      half_edges_out, half_edges.begin(), half_edges.end());
  std::vector<SEGMENT_POLYGON> result;
  result.reserve(half_edges_out.size());
  for (std::size_t i = 0; i < half_edges_out.size(); ++i) {
    id = half_edges_out[i].second;
    POINT_POLYGON l = half_edges_out[i].first.first;
    POINT_POLYGON h = half_edges_out[i].first.second;
    SEGMENT_POLYGON orig_seg = data->at(id);
    if (orig_seg.high() < orig_seg.low())
      std::swap(l, h);
    result.push_back(SEGMENT_POLYGON(l, h));
  }
  std::swap(result, *data);
}

std::vector<double> get_intersection_runtime() {
  std::vector<double> running_times;
  boost::mt19937 gen(RANDOM_SEED);
  for (int i = 0; i < NUM_TESTS; ++i) {
    timer.start();
    for (int j = 0; j < NUM_RUNS[i]; ++j) {
      SSD_POLYGON ssd;
      for (int k = 0; k < NUM_SEGMENTS[i]; ++k) {
        int32 x1 = gen();
        int32 y1 = gen();
        int32 dx = (gen() & 1023) + 1;
        int32 dy = (gen() & 1023) + 1;
        ssd.push_back(SEGMENT_POLYGON(
            POINT_POLYGON(x1, y1), POINT_POLYGON(x1 + dx, y1 + dy)));
      }
      clean_segment_set(&ssd);
    }
    running_times.push_back(get_elapsed_secs());
  }
  return running_times;
}

void run_boost_voronoi_test(const std::vector<double> &running_times) {
  boost::mt19937 gen(RANDOM_SEED);
  bf << "Boost.Polygon Voronoi of Segments:\n";
  for (int i = 0; i < NUM_TESTS; ++i) {
    timer.start();
    for (int j = 0; j < NUM_RUNS[i]; ++j) {
      SSD_POLYGON ssd;
      VD_BOOST vd;
      for (int k = 0; k < NUM_SEGMENTS[i]; ++k) {
        int32 x1 = gen();
        int32 y1 = gen();
        int32 dx = (gen() & 1023) + 1;
        int32 dy = (gen() & 1023) + 1;
        ssd.push_back(SEGMENT_POLYGON(
            POINT_POLYGON(x1, y1),
            POINT_POLYGON(x1 + dx, y1 + dy)));
      }
      clean_segment_set(&ssd);
      boost::polygon::construct_voronoi(ssd.begin(), ssd.end(), &vd);
    }
    double time_per_test =
        (get_elapsed_secs() - running_times[i]) / NUM_RUNS[i];
    format_line(NUM_SEGMENTS[i], NUM_RUNS[i], time_per_test);
  }
  bf << "\n";
}

void run_cgal_delaunay_test(const std::vector<double> &running_times) {
  boost::mt19937 gen(RANDOM_SEED);
  bf << "CGAL Triangulation of Segments:\n";
  for (int i = 0; i < NUM_TESTS; ++i) {
    timer.start();
    for (int j = 0; j < NUM_RUNS[i]; ++j) {
      SSD_POLYGON ssd;
      for (int k = 0; k < NUM_SEGMENTS[i]; ++k) {
        int32 x1 = gen();
        int32 y1 = gen();
        int32 dx = (gen() & 1023) + 1;
        int32 dy = (gen() & 1023) + 1;
        ssd.push_back(SEGMENT_POLYGON(
            POINT_POLYGON(x1, y1),
            POINT_POLYGON(x1 + dx, y1 + dy)));
      }
      clean_segment_set(&ssd);

      typedef std::vector<Point_CGAL> Points_container;
      typedef std::vector<Points_container>::size_type Index_type;
      typedef std::vector< std::pair<Index_type, Index_type> > Constraints_container;
      Points_container points;
      Constraints_container constraints;
      points.reserve(ssd.size() * 2);
      constraints.reserve(ssd.size());
      for (SSD_POLYGON::iterator it = ssd.begin(); it != ssd.end(); ++it) {
        points.push_back(Point_CGAL(
            boost::polygon::x(it->low()),
            boost::polygon::y(it->low())));
        points.push_back(Point_CGAL(
            boost::polygon::x(it->high()),
            boost::polygon::y(it->high())));
        constraints.push_back(
            std::make_pair(points.size() - 2, points.size() - 1));
      }

      SDT_CGAL sdg;
      sdg.insert_segments(
          points.begin(), points.end(),
          constraints.begin(), constraints.end());
    }
    double time_per_test =
        (get_elapsed_secs() - running_times[i]) / NUM_RUNS[i];
    format_line(NUM_SEGMENTS[i], NUM_RUNS[i], time_per_test);
  }
  bf << "\n";
}

int main() {
  bf << std::setiosflags(std::ios::right | std::ios::fixed)
     << std::setprecision(6);
  std::vector<double> running_times = get_intersection_runtime();
  run_boost_voronoi_test(running_times);
  run_cgal_delaunay_test(running_times);
  bf.close();
  return 0;
}


#include "SMLLibrary.hpp"

#include <cmath>
#include <chrono>
#include <iostream>

#include <cinttypes>

#include <boost/math/constants/constants.hpp>

namespace SMLCompiler::LibraryImpl {

template <typename F, typename... Args>
auto func(F f, Args... args) {
  return (Ptr)new genericFunctionTypeNative*[sizeof...(args) + 1]
         {(genericFunctionTypeNative*)f, args...};
}

Int boxReal(double d) {
  Int i;
  std::memcpy(&i, &d, sizeof d);
  return i | 1;
}

double unboxReal(Int i) {
  i &= ~1; // unbox float
  char c[sizeof(double)];
  std::memcpy(c, &i, sizeof c);
  return reinterpret_cast<double&>(c);
}

constexpr Int unboxNoCast(Int n) {
  return n >> 1;
}
constexpr Int boxNoCast(Int n) {
  return (n << 1) | 1;
}

void*(*allocate_small_ptr)(std::uint64_t);
void*(*allocate_large_ptr)(std::uint64_t);

namespace Timer {
  using clock_type = std::chrono::high_resolution_clock;
  Ptr startRealTimer(Ptr, Ptr[]) {
    return boxNative(clock_type::now().time_since_epoch().count());
  }
  Ptr checkRealTimer(Ptr arg, Ptr[]) {
    return boxNative(clock_type::now().time_since_epoch().count() - unboxNative(arg));
  }

}

namespace Time {
  Ptr toMilliseconds(Ptr arg, Ptr[]) {
    return boxNative(unboxNative(arg)/(Timer::clock_type::duration::period::den/1000));
  }
}

//namespace Vector {
//  Ptr tabulate(Ptr* args, Ptr[]) {
//    const auto fun = (Ptr(*)(Int))args[1];
//    const auto len = unboxNoCast(args[0])+1;
//    const auto begin = (Int*)allocate_large_ptr(len);
//    *begin = ((n+1) << 2) | 1;
//    for (std::size_t i = 1; i < len; ++i)
//      begin[i] = fun(boxNoCast(i-1));
//    return begin;
//  }
//
//  Ptr tabulate(Int n, Ptr[]) {
//    return func([] (Ptr(*arg)(Int), Int* n) {
//      const auto l = unboxNoCast(n[1])+1;
//      const auto begin = (Int*)allocate_large_ptr(l);
//      *begin = ((n+1) << 2) | 1;
//      for (std::size_t i = 1; i < l; ++i)
//        begin[i] = arg(boxNoCast(i-1));
//      return begin;
//    }, n);
//  }
//}

namespace String {
  auto length(tagTypeNative* str, Ptr[] = nullptr) {
    return str[1];
  }
  auto concat(tagTypeNative** record, Ptr[]) {
    auto len = 8 + length(record[1]) + length(record[2]);
    auto alloc = (tagTypeNative*)allocate_large_ptr(len);
    alloc[0] = boxNativeTag((len+7)/8);
    alloc[1] = len - 8;
    auto str_ptr = (char*)alloc + 8;
    std::memcpy(str_ptr                    , record[1] + 2, length(record[1]));
    std::memcpy(str_ptr + length(record[1]), record[2] + 2, length(record[2]));
    return alloc;
  }
}

namespace Core {
  Ptr i32ToInt(Ptr arg, Ptr[]) {
    return arg;
  }

  Ptr compareInfInt(Int* rcd, Ptr[]) {
    return boxNative(rcd[1] < rcd[2]);
  }

  Ptr print(tagTypeNative* str, Ptr[]) {
    std::cout.write((char const*)(str + 2), String::length(str, nullptr)).flush();
    return nullptr;
  }
}

namespace Real {

  tagTypeNative* toString(Int i, Ptr[]) {
    auto f = unboxReal(i);
    auto alloc = (tagTypeNative*)allocate_large_ptr(32);
    alloc[0] = boxNativeTag(4);
    alloc[1] = std::sprintf((char*)(alloc + 2), "%.12f", f);
    return alloc;
  }

  Int realFloor(Int i, Ptr[]) {
    return boxReal(std::floor(unboxReal(i)));
  }

  Int intFloor(Int i, Ptr[]) {
    return boxNoCast((Int)std::floor(unboxReal(i)));
  }

  Int fromLargeInt(Int i, Ptr[]) {
    return boxReal(i);
  }

}

namespace LargeInt {

  tagTypeNative* toString(Int i, Ptr[]) {
    auto alloc = (tagTypeNative*)allocate_large_ptr(32);
    alloc[0] = boxNativeTag(4);
    alloc[1] = std::sprintf((char*)(alloc + 2), "%" PRId64, i >> GC::valueFlagLength);
    return alloc;
  }

  const Int maxInt = std::numeric_limits<Int>::max();

}

namespace Option {

  Ptr valOf(Ptr p, Ptr[]) {
    if (!p) {
      std::cerr << "valOf: NONE!";
      std::abort();
    }
    return p;
  }

}

namespace Math {

  Int cosh(Int i, Ptr[]) {
    return boxReal(std::cosh(unboxReal(i)));
  }
  Int sinh(Int i, Ptr[]) {
    return boxReal(std::sinh(unboxReal(i)));
  }
  Int ln(Int i, Ptr[]) {
    return boxReal(std::log(unboxReal(i)));
  }
  Int atan(Int i, Ptr[]) {
    return boxReal(std::atan(unboxReal(i)));
  }
  Int exp(Int i, Ptr[]) {
    return boxReal(std::exp(unboxReal(i)));
  }
  const Int pi = boxReal(boost::math::constants::pi<double>());

}

namespace List {

  Ptr hd(Ptr arg, Ptr[]) {
    assert(arg);
    return ((Ptr*)arg)[1];
  }

  Int length(Ptr* record, Ptr[]) {
    Int l = 0;
    for (; record; ++l)
      record = (Ptr*)record[2];
    return boxNoCast(l);
  }

  Ptr* drop(Ptr* record, Ptr[]) {
    auto k = unboxNative(record[2]);
    record = (Ptr*)record[1];
    while (k--) {
      record = (Ptr*)record[2];
      if (UNLIKELY(!record)) {
        std::cerr << "List::drop: not enough elements in list!\n";
        std::abort();
      }
    }
    return record;
  }

}

const boost::unordered_map<std::string, boost::container::map<boost::container::vector<int>, Ptr>> libraryIdMap {{
  {"0AAB32C474C86764F4EDD7B5F7975050", {{{86, 1}, (Ptr)0xDEADBEEF},
                                        {{86, 0, 1}, (Ptr)0xDEADBEEF},
                                        {{86, 2}, (Ptr)0xDEADBEEF},
                                        {{79}, (Ptr)0xDEADBEEF},
                                        {{std::initializer_list<int>{85}}, func(String::concat)},
                                        {{86, 38}, func(Core::i32ToInt)},
                                        {{33, 30}, func(Core::compareInfInt)},
                                        {{std::initializer_list<int>{112}}, func(List::hd)},
                                        {{std::initializer_list<int>{130}}, func(Core::print)}}},
  {"96B2FB8000CFA053281A20BA159C7DD2",  {{{0, 8}, func(List::drop)},
                                         {{0, 9}, func(List::length)},
                                         {{0, 2}, func(List::hd)}}},
  {"6959FE45225D4BD55C2D2CCD9C538D66", {{{0, 2}, (Ptr)LargeInt::maxInt},
                                        {{0, 25}, func(LargeInt::toString)}}},
  {"CAD9434B073913D9EA0BF5829FBCA04E", {{{0, 25}, func(LargeInt::toString)}}},
  {"15264C47F5ED8119799A5101E44495E0", {{{0, 6}, func(Time::toMilliseconds)}}},
  {"A44C25B0AB637462740A552AFDE72D60", {{{0, 5}, func(Timer::startRealTimer)},
                                        {{0, 7}, func(Timer::checkRealTimer)}}},
  {"E1310EABC08F1107773C2652358627F1", {{{0, 37}, func(Real::toString)},
                                        {{0, 47}, func(Real::intFloor)},
                                        {{0, 51}, func(Real::realFloor)},
                                        {{0, 58}, func(Real::fromLargeInt)}}},
  {"F4360A6CAB125CD5827C4328E911CBCF", {{{0, 15}, func(Math::cosh)},
                                        {{0, 14}, func(Math::sinh)},
                                        {{0, 12}, func(Math::ln)},
                                        {{0, 0}, (Ptr)Math::pi},
                                        {{0, 10}, func(Math::exp)},
                                        {{0, 8}, func(Math::atan)},
                                        }},
  {"769FA65058A4551AC2A541F6CAA27F6F", {{{0, 3}, func(Option::valOf)}}}
//  {"F6EF4B2BCBE3E725BB9EB04ACA2B6DCC", {{{0, 2}, func(Vector::tabulate}},
//                                        {{0, 13}, func()}}
}};

}