// Copyright 2020 Peter Dimov
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/mp11.hpp>
#include <boost/core/lightweight_test_trait.hpp>

using namespace boost::mp11;

template<class L, template<class...> class P> using is_sorted = mp_none_of<mp_pairwise_fold_q<L, mp_bind<P, _2, _1>>, mp_to_bool>;

int main()
{
    BOOST_TEST_TRAIT_TRUE((is_sorted<mp_list<mp_int<0>, mp_int<0>, mp_int<1>, mp_int<3>, mp_int<3>, mp_int<7>>, mp_less>));
    BOOST_TEST_TRAIT_FALSE((is_sorted<mp_list<mp_int<0>, mp_int<0>, mp_int<1>, mp_int<3>, mp_int<3>, mp_int<2>>, mp_less>));

    return boost::report_errors();
}


#include <cassert>
#include <boost/math/distributions/chi_squared.hpp>
#include <Eigen/Core>
#include "genfile/Error.hpp"
#include "genfile/VariantEntry.hpp"
#include "components/SNPSummaryComponent/DifferentialMissingnessComputation.hpp"
#include "metro/FishersExactTest.hpp"
#include "metro/likelihood/Multinomial.hpp"
#include "metro/likelihood/ProductOfMultinomials.hpp"

namespace stats {
	DifferentialMissingnessComputation::UniquePtr DifferentialMissingnessComputation::create( std::string const& stratification_name, StrataMembers const& strata_members ) {
		return DifferentialMissingnessComputation::UniquePtr(
			new DifferentialMissingnessComputation( stratification_name, strata_members )
		) ;
	}

	DifferentialMissingnessComputation::DifferentialMissingnessComputation( std::string const& stratification_name, StrataMembers const& strata_members, double threshhold ):
		m_stratification_name( stratification_name ),
		m_strata_members( strata_members ),
		m_strata_levels( compute_strata_levels( m_strata_members ) ),
		m_threshhold( threshhold )
	{
	#if 0
		if( strata_members.size() != 2 ) {
			throw genfile::BadArgumentError( "DifferentialMissingnessComputation::DifferentialMissingnessComputation()", "strata_members( size " + genfile::string_utils::to_string( strata_members.size() ) + ")" ) ;
		}
	#endif
	}

	std::vector< int > DifferentialMissingnessComputation::compute_strata_levels( StrataMembers const& strata_members ) const {
		std::vector< int > result ;
		int level = 0 ;
		for( StrataMembers::const_iterator i = strata_members.begin(); i != strata_members.end(); ++i, ++level ) {
			for( std::size_t j = 0; j < i->second.size(); ++j ) {
				std::size_t const index = i->second[j] ;
				result.resize( std::max( result.size(), index+1 ), -1 ) ;
				result[ index ] = level ;
			}
		}
		return result ;
	}

	void DifferentialMissingnessComputation::operator()( VariantIdentifyingData const& snp, Genotypes const& genotypes, Ploidy const&, genfile::VariantDataReader&, ResultCallback callback ) {
		// construct a table
		//
		//                 missing     not missing
		//   stratum 1       a              b
		//   stratum 2       c              d
		//
		// on which we can do a Fisher's exact test or a product of binomial-type test.
		//
		int const number_of_strata = 1 + *std::max_element( m_strata_levels.begin(), m_strata_levels.end() ) ;
		Eigen::MatrixXd table( number_of_strata, 2 ) ;
		table.setZero() ;
		for( int i = 0; i < genotypes.rows(); ++i ) {
			if( m_strata_levels[i] >= 0 ) {
				if( genotypes.row( i ).sum() < m_threshhold ) {
					table( m_strata_levels[i], 0 )++ ;
				}
				else {
					table( m_strata_levels[i], 1 )++ ;
				}
			}
		}

		{
			StrataMembers::const_iterator i = m_strata_members.begin() ;
			StrataMembers::const_iterator const end_i = m_strata_members.end() ;
			for( int level = 0; i != end_i; ++level, ++i ) {
				std::string tag = "[" + m_stratification_name + "=" + genfile::string_utils::to_string( i->first ) + "]" ;
				callback( "missing" + tag, table( level, 0 ) ) ;
				callback( "non_missing" + tag, table( level, 1 ) ) ;
			}
		}

		std::string const stub = "missingness_by_" + m_stratification_name ;
	//	callback( stub + "_sample_odds_ratio", table(0,0) * table(1,1) / ( table(0,1) * table(1,0) ) ) ;

		if( table.row(0).sum() > 0 && table.row(1).sum() > 0 ) {
			// perform the exact test.  For speed reasons this is only done if the chi-square approximation may be inaccurate.
			if( table.rows() == 2 ) {
				try {
					metro::FishersExactTest test( table ) ;
					callback( stub + "_exact_pvalue", test.get_pvalue( metro::FishersExactTest::eTwoSided ) )  ;
				}
				catch( std::exception const& e ) {
					callback( stub + "_exact_pvalue", genfile::MissingValue() ) ;
				}
			} else {
				callback( stub + "_exact_pvalue", genfile::MissingValue() ) ;
			}
			{
				metro::likelihood::Multinomial< double, Eigen::VectorXd, Eigen::MatrixXd > null_model( table.colwise().sum() ) ;
				null_model.evaluate_at( null_model.get_MLE() ) ;
				metro::likelihood::ProductOfMultinomials< double, Eigen::VectorXd, Eigen::MatrixXd > alternative_model( table ) ;
				alternative_model.evaluate_at( alternative_model.get_MLE() ) ;
				double likelihood_ratio_statistic = -2.0 * ( null_model.get_value_of_function() - alternative_model.get_value_of_function() ) ;

				if( likelihood_ratio_statistic != likelihood_ratio_statistic || likelihood_ratio_statistic < 0.0 ) {
					likelihood_ratio_statistic = std::numeric_limits< double >::quiet_NaN() ;
				}
				else {
					boost::math::chi_squared_distribution< double > chi_squared( table.rows() - 1 ) ;

					double p_value = boost::math::cdf(
						boost::math::complement(
							chi_squared,
							likelihood_ratio_statistic
						)
					) ;

					callback( stub + "_lrt_pvalue", p_value ) ;
					callback( stub + "_lrt_df", genfile::VariantEntry::Integer( table.rows() - 1 )) ;
				}
			}
		}
	}

	std::string DifferentialMissingnessComputation::get_summary( std::string const& prefix, std::size_t column_width ) const {
		return prefix + "DifferentialMissingnessComputation" ;
	}
}


#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE JPetRecoImageToolsTests
#include <boost/test/unit_test.hpp>
#include <fstream>
#include <iostream>
#include <utility>
#include <vector>

#include "./JPetFilterCosine.h"
#include "./JPetFilterHamming.h"
#include "./JPetFilterNone.h"
#include "./JPetFilterRamLak.h"
#include "./JPetFilterRidgelet.h"
#include "./JPetFilterSheppLogan.h"
#include "./JPetRecoImageTools.h"
#include "JPetCommonTools/JPetCommonTools.h"

const std::string test_files_path = "unitTestData/JPetRecoImageToolsTest/";

const auto inFile = std::vector<std::string>{"rsinogram_slice_0_0.000000_to_0.833332.ppm",    "rsinogram_slice_1_0.833332_to_1.666666.ppm",
                                             "rsinogram_slice_2_1.666666_to_2.500000.ppm",    "rsinogram_slice_21_17.500000_to_18.333332.ppm",
                                             "rsinogram_slice_22_18.333332_to_19.166664.ppm", "rsinogram_slice_23_19.166664_to_20.000000.ppm",
                                             "rsinogram_slice_-1_-0.833334_to_0.000000.ppm",  "rsinogram_slice_-2_-1.666668_to_-0.833334.ppm"};

int getMaxValue(const JPetRecoImageTools::SparseMatrix& result)
{
  int maxValue = 0;
  for (unsigned int i = 0; i < result.size1(); i++)
  {
    for (unsigned int j = 0; j < result.size2(); j++)
    {
      if (static_cast<int>(result(i, j)) > maxValue)
        maxValue = static_cast<int>(result(i, j));
    }
  }
  return maxValue;
}

void saveResult(const JPetRecoImageTools::SparseMatrix& result, const std::string& outputFileName)
{
  int maxValue = getMaxValue(result);
  std::ofstream res(outputFileName);
  res << "P2" << std::endl;
  res << result.size2() << " " << result.size1() << std::endl;
  res << maxValue << std::endl;
  for (unsigned int i = 0; i < result.size1(); i++)
  {
    for (unsigned int j = 0; j < result.size2(); j++)
    {
      int resultInt = static_cast<int>(result(i, j));
      if (resultInt < 0)
      {
        resultInt = 0;
      }
      res << resultInt << " ";
    }
    res << std::endl;
  }
  res.close();
}

JPetRecoImageTools::SparseMatrix readFile(const std::string& inputFile)
{
  std::ifstream in(inputFile);
  BOOST_REQUIRE(in);
  std::string line;
  getline(in, line);
  unsigned int width;
  unsigned int height;
  in >> width;
  in >> height;
  int val;
  in >> val; // skip max val
  JPetRecoImageTools::SparseMatrix sinogram(height, width);
  for (unsigned int i = 0; i < height; i++)
  {
    for (unsigned int j = 0; j < width; j++)
    {
      in >> val;
      sinogram(i, j) = val;
    }
  }
  return sinogram;
}

BOOST_AUTO_TEST_SUITE(FirstSuite)

BOOST_AUTO_TEST_CASE(backProjectSinogramRamLak)
{
  for (const auto& in : inFile)
  {
    for (double threshold = 0.01; threshold <= 1.; threshold += 0.01)
    {
      const auto outFile = "ramlak/" + in + "RamLakT" + std::to_string(threshold) + ".ppm";
      std::cout << "Reading file: " << test_files_path + in << " output file: " << outFile << std::endl;
      JPetRecoImageTools::SparseMatrix sinogram = readFile(test_files_path + in);

      JPetFilterRamLak ramLakFilter(threshold);
      JPetRecoImageTools::FourierTransformFunction f = JPetRecoImageTools::doFFTW;
      JPetRecoImageTools::SparseMatrix filteredSinogram = JPetRecoImageTools::FilterSinogram(f, ramLakFilter, sinogram);
      JPetRecoImageTools::SparseMatrix result =
          JPetRecoImageTools::backProject(filteredSinogram, sinogram.size2(), JPetRecoImageTools::nonRescale, 0, 255);

      saveResult(result, outFile);

      BOOST_REQUIRE(JPetCommonTools::ifFileExisting(outFile));
    }
  }
}

BOOST_AUTO_TEST_SUITE_END()


/*******************************************************************************
 * An abstract domain that lifts a value domain using term
 * equivalences based on the paper "An Abstract Domain of
 * Uninterpreted Functions" by Gange, Navas, Schachte, Sondergaard,
 * and Stuckey published in VMCAI'16.
 *
 * Author: Graeme Gange (gkgange@unimelb.edu.au)
 *
 * Contributors: Jorge A. Navas (jorge.navas@sri.com)
 ******************************************************************************/

#pragma once

#include <crab/domains/abstract_domain.hpp>
#include <crab/domains/abstract_domain_specialized_traits.hpp>
#include <crab/domains/backward_assign_operations.hpp>
#include <crab/domains/intervals.hpp>
#include <crab/domains/term/inverse.hpp>
#include <crab/domains/term/simplify.hpp>
#include <crab/domains/term/term_expr.hpp>
#include <crab/domains/term/term_operators.hpp>
#include <crab/numbers/bignums.hpp>
#include <crab/support/debug.hpp>
#include <crab/support/stats.hpp>
#include <crab/types/varname_factory.hpp>

#include <algorithm>
#include <set>
#include <utility>
#include <vector>

#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <boost/optional.hpp>
#include <boost/range.hpp>

//#define VERBOSE
//#define DEBUG_VARMAP
//#define DEBUG_MEET

#define USE_TERM_INTERVAL_NORMALIZER

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-compare"

namespace crab {

namespace domains {

namespace term {
template <class Num, class VName, class Abs> class TDomInfo {
public:
  using Number = Num;
  using VariableName = VName;
  using variable_t = variable<Num, VName>;
  using Alloc = crab::var_factory_impl::str_var_alloc_col;
  using domain_t = Abs;

  static_assert(std::is_same<typename Abs::varname_t, Alloc::varname_t>::value,
		"Base abstract domain must use str_var_alloc_col as varname factory");
};
} // namespace term

template <class Info, class Abs> class TermNormalizer;

template <typename Info>
class term_domain final : public abstract_domain_api<term_domain<Info>> {
  friend class TermNormalizer<Info, typename Info::domain_t>;

  // Number and VariableName can be different from
  // dom_t::number_t and dom_t::varname_t although currently
  // Number and dom_t::number_t must be the same type.
  using Number = typename Info::Number;
  using VariableName = typename Info::VariableName;
  using dom_t = typename Info::domain_t;
  using dom_var_t = typename dom_t::variable_t;
  using dom_var_alloc_t = typename Info::Alloc;
  using dom_varname_t = typename dom_var_alloc_t::varname_t;
  using domvar_set_t = ikos::patricia_tree_set<dom_var_t>;
  using bound_t = ikos::bound<Number>;

  using term_domain_t = term_domain<Info>;
  using abstract_domain_t = abstract_domain_api<term_domain_t>;

public:
  using typename abstract_domain_t::disjunctive_linear_constraint_system_t;
  using typename abstract_domain_t::interval_t;
  using typename abstract_domain_t::linear_constraint_system_t;
  using typename abstract_domain_t::linear_constraint_t;
  using typename abstract_domain_t::linear_expression_t;
  using typename abstract_domain_t::reference_constraint_t;
  using typename abstract_domain_t::variable_or_constant_t;
  using typename abstract_domain_t::variable_t;
  using typename abstract_domain_t::variable_vector_t;
  using typename abstract_domain_t::variable_or_constant_vector_t;
  using number_t = Number;
  using varname_t = VariableName;

private:
  using ttbl_t = term::term_table<number_t, term::term_operator_t>;
  using term_id_t = typename ttbl_t::term_id_t;

  // WARNING: assumes the underlying domain uses the same number type.
  using dom_number = typename Info::Number;
  using dom_lincst_t = typename dom_t::linear_constraint_t;
  using dom_linsys_t = typename dom_t::linear_constraint_system_t;
  using dom_linexp_t = typename dom_t::linear_expression_t;
  using linterm_t = typename linear_expression_t::component_t;
  using term_map_t = boost::container::flat_map<term_id_t, dom_var_t>;
  using rev_map_t = boost::container::flat_map<dom_var_t, variable_t>;
  using term_set_t = boost::container::flat_set<term_id_t>;
  using var_map_t = boost::container::flat_map<variable_t, term_id_t>;
  // the reverse of var_map: from term_id_t to a set of variable_t
  using var_set_t = std::set<variable_t>;
  using rev_var_map_t = boost::container::flat_map<term_id_t, var_set_t>;
  using simplifier_t = term::NumSimplifier<number_t>;

  bool _is_bottom;
  // Uses a single state of the underlying domain.
  ttbl_t _ttbl;
  dom_t _impl;
  dom_var_alloc_t _alloc;
  var_map_t _var_map;
  rev_var_map_t _rev_var_map; // to extract equalities efficiently
  term_map_t _term_map;
  term_set_t changed_terms;

  term_domain(bool is_top) : _is_bottom(!is_top) {}

  term_domain(dom_var_alloc_t &&alloc, var_map_t &&vm, rev_var_map_t &&rvm,
              ttbl_t &&tbl, term_map_t &&tmap, dom_t &&impl)
      : _is_bottom((impl.is_bottom()) ? true : false), _ttbl(std::move(tbl)),
        _impl(std::move(impl)), _alloc(std::move(alloc)),
        _var_map(std::move(vm)), _rev_var_map(std::move(rvm)),
        _term_map(std::move(tmap)) {
    check_terms(__LINE__);
  }

  // x = y op [lb,ub]
  term_id_t term_of_itv(bound_t lb, bound_t ub) {
    boost::optional<number_t> n_lb = lb.number();
    boost::optional<number_t> n_ub = ub.number();

    if (n_lb && n_ub && (*n_lb == *n_ub))
      return term_of_const(*n_lb);

    term_id_t t_itv = _ttbl.fresh_var();
    dom_var_t dom_itv = domvar_of_term(t_itv);
    _impl.set(dom_itv, interval_t(lb, ub));
    return t_itv;
  }

  // term_id_t term_of_expr(arith_operation_t op, term_id_t ty, term_id_t tz)
  // {
  //   boost::optional<term_id_t> opt_tx = _ttbl.find_ftor(op, ty, tz);
  //   if(opt_tx)
  //   {
  //     // If the term already exists, we can learn nothing.
  //     return *opt_tx;
  //   } else {
  //     // Otherwise, assign the term, and evaluate.
  //     term_id_t tx = _ttbl.apply_ftor(op, ty, tz);
  //     _impl.apply(op,
  //                 domvar_of_term(tx),
  //                 domvar_of_term(ty), domvar_of_term(tz));
  //     return tx;
  //   }
  // }

  // void apply(arith_operation_t op, variable_t x, variable_t y, bound_t lb,
  // bound_t ub){
  //   term_id_t t_x = term_of_expr(op, term_of_var(y), term_of_itv(lb, ub));
  //   // JNL: check with Graeme
  //   //      insert only adds an entry if the key does not exist
  //   //_var_map.insert(std::make_pair(x, t_x));
  //   rebind_var(x, t_x);
  //   check_terms(__LINE__);
  // }

  void apply(dom_t &dom, term::term_operator_t op, dom_var_t x, dom_var_t y,
             dom_var_t z) {
    if (auto top = conv2arith(op)) {
      dom.apply(*top, x, y, z);
    } else if (auto top = conv2bitwise(op)) {
      dom.apply(*top, x, y, z);
    } else if (op == term::TERM_OP_FUNCTION) {
      // uninterpreted function: do nothing in the underlying
      // numerical domain.
    } else {
      CRAB_ERROR("unsupported binary operator ", op);
    }
  }

  // Apply a given functor in the underlying domain.
  // GKG: Looks the current implementation could actually
  // lose information; as it's not taking the meet with
  // the current value.
  void eval_ftor(dom_t &dom, ttbl_t &tbl, term_id_t t) {
    // Get the term info.
    typename ttbl_t::term_t *t_ptr = tbl.get_term_ptr(t);

    // Only apply functors.
    if (t_ptr->kind() == term::TERM_APP) {
      term::term_operator_t op = term::term_ftor(t_ptr);

      std::vector<term_id_t> &args(term::term_args(t_ptr));
      assert(args.size() == 2);
      apply(dom, op, domvar_of_term(t), domvar_of_term(args[0]),
            domvar_of_term(args[1]));
    }
  }

  void eval_ftor_down(dom_t &dom, ttbl_t &tbl, term_id_t t) {
    // Get the term info.
    typename ttbl_t::term_t *t_ptr = tbl.get_term_ptr(t);

    // Only apply functors.
    if (t_ptr->kind() == term::TERM_APP) {
      term::term_operator_t op = term::term_ftor(t_ptr);
      std::vector<term_id_t> &args(term::term_args(t_ptr));
      assert(args.size() == 2);

      if (boost::optional<arith_operation_t> arith_op = conv2arith(op)) {
        term::InverseOps<dom_number, dom_var_t, dom_t>::apply(
            dom, *arith_op, domvar_of_term(t), domvar_of_term(args[0]),
            domvar_of_term(args[1]));
      }
    }
  }

  dom_t eval_ftor_copy(dom_t &dom, ttbl_t &tbl, term_id_t t) {
    dom_t ret = dom;
    eval_ftor(ret, tbl, t);
    return ret;
  }

  term::term_operator_t conv2termop(arith_operation_t op) {
    switch (op) {
    case OP_ADDITION:
      return term::TERM_OP_ADD;
    case OP_SUBTRACTION:
      return term::TERM_OP_SUB;
    case OP_MULTIPLICATION:
      return term::TERM_OP_MUL;
    case OP_SDIV:
      return term::TERM_OP_SDIV;
    case OP_UDIV:
      return term::TERM_OP_UDIV;
    case OP_SREM:
      return term::TERM_OP_SREM;
    default:
      return term::TERM_OP_UREM;
    }
  }

  boost::optional<arith_operation_t> conv2arith(term::term_operator_t op) {
    switch (op) {
    case term::TERM_OP_ADD:
      return OP_ADDITION;
    case term::TERM_OP_SUB:
      return OP_SUBTRACTION;
    case term::TERM_OP_MUL:
      return OP_MULTIPLICATION;
    case term::TERM_OP_SDIV:
      return OP_SDIV;
    case term::TERM_OP_UDIV:
      return OP_UDIV;
    case term::TERM_OP_SREM:
      return OP_SREM;
    case term::TERM_OP_UREM:
      return OP_UREM;
    default:
      return boost::optional<arith_operation_t>();
    }
  }

  term::term_operator_t conv2termop(bitwise_operation_t op) {
    switch (op) {
    case OP_AND:
      return term::TERM_OP_AND;
    case OP_OR:
      return term::TERM_OP_OR;
    case OP_XOR:
      return term::TERM_OP_XOR;
    case OP_SHL:
      return term::TERM_OP_SHL;
    case OP_LSHR:
      return term::TERM_OP_LSHR;
    default:
      return term::TERM_OP_ASHR;
    }
  }

  boost::optional<bitwise_operation_t> conv2bitwise(term::term_operator_t op) {
    switch (op) {
    case term::TERM_OP_AND:
      return OP_AND;
    case term::TERM_OP_OR:
      return OP_OR;
    case term::TERM_OP_XOR:
      return OP_XOR;
    case term::TERM_OP_SHL:
      return OP_SHL;
    case term::TERM_OP_LSHR:
      return OP_LSHR;
    case term::TERM_OP_ASHR:
      return OP_ASHR;
    default:
      return boost::optional<bitwise_operation_t>();
    }
  }

  void check_terms(int line) const {
#ifdef DEBUG_VARMAP
    for (auto const p : _var_map) {
      if (!(p.second < _ttbl.size())) {
        CRAB_ERROR("term_equiv.hpp at line=", line, ": ",
                   "term id is not the table term");
      }
    }

    for (auto kv : _rev_var_map) {
      for (auto v : kv.second) {
        auto it = _var_map.find(v);
        if (it->second != kv.first) {
          CRAB_ERROR("term_equiv.hpp at line=", line, ": ", v,
                     " is mapped to t", it->second,
                     " but the reverse map says that should be t", kv.first);
        }
      }
    }
#endif
  }

  void deref(term_id_t t) {
    std::vector<term_id_t> forgotten;
    _ttbl.deref(t, forgotten);
    for (term_id_t f : forgotten) {
      typename term_map_t::iterator it(_term_map.find(f));
      if (it != _term_map.end()) {
        _impl -= (*it).second;
        _term_map.erase(it);
      }
    }
  }

  /* Begin manipulate the reverse variable map */
  void add_rev_var_map(rev_var_map_t &rvmap, term_id_t t, variable_t v) const {
    auto it = rvmap.find(t);
    if (it != rvmap.end()) {
      it->second.insert(v);
    } else {
      var_set_t varset;
      varset.insert(v);
      rvmap.insert(std::make_pair(t, varset));
    }
  }

  void remove_rev_var_map(term_id_t t, const variable_t &v) {
    auto it = _rev_var_map.find(t);
    if (it != _rev_var_map.end()) {
      it->second.erase(v);
      if (it->second.empty()) {
        _rev_var_map.erase(it);
      }
    }
  }
  /* End manipulate the reverse variable map */

  void rebind_var(const variable_t &x, term_id_t tx) {
    _ttbl.add_ref(tx);

    auto it(_var_map.find(x));
    if (it != _var_map.end()) {
      remove_rev_var_map((*it).second, x);
      deref((*it).second);
      _var_map.erase(it);
    }
    _var_map.insert(std::make_pair(x, tx));
    add_rev_var_map(_rev_var_map, tx, x);
  }

  // Build the tree for a linexpr, and ensure that
  // values for the subterms are sustained.

  term_id_t term_of_const(const number_t &n) {
    dom_number dom_n(n);
    boost::optional<term_id_t> opt_n(_ttbl.find_const(dom_n));
    if (opt_n) {
      return *opt_n;
    } else {
      term_id_t term_n(_ttbl.make_const(dom_n));
      dom_var_t v = domvar_of_term(term_n);

      dom_linexp_t exp(n);
      _impl.assign(v, exp);
      return term_n;
    }
  }

  term_id_t term_of_var(variable_t v, var_map_t &var_map,
                        rev_var_map_t &rvar_map, ttbl_t &ttbl) {
    auto it(var_map.find(v));
    if (it != var_map.end()) {
      // assert((*it).first == v);
      assert(ttbl.size() > (*it).second);
      return (*it).second;
    } else {
      // Allocate a fresh term
      term_id_t id(ttbl.fresh_var());
      var_map[v] = id;
      add_rev_var_map(rvar_map, id, v);
      ttbl.add_ref(id);
      return id;
    }
  }

  term_id_t term_of_var(variable_t v) {
    return term_of_var(v, _var_map, _rev_var_map, _ttbl);
  }

  term_id_t term_of_linterm(linterm_t term) {
    if (term.first == 1) {
      return term_of_var(term.second);
    } else {
      return build_term(OP_MULTIPLICATION, term_of_const(term.first),
                        term_of_var(term.second));
    }
  }

  // OpTy = [arith_operation_t | bitwise_operation_t]
  template <typename OpTy>
  term_id_t build_term(OpTy op, term_id_t ty, term_id_t tz) {
    // Check if the term already exists
    term::term_operator_t binop = conv2termop(op);
    boost::optional<term_id_t> eopt(_ttbl.find_ftor(binop, ty, tz));
    if (eopt) {
      return *eopt;
    } else {
      // Create the term
      term_id_t tx = _ttbl.apply_ftor(binop, ty, tz);
      dom_var_t v(domvar_of_term(tx));
      dom_var_t y(domvar_of_term(ty));
      dom_var_t z(domvar_of_term(tz));

      // Set evaluation
      CRAB_LOG("term", crab::outs() << "Prev: " << _impl << "\n");
      _impl.apply(op, v, y, z);

      CRAB_LOG("term", crab::outs() << "Should have " << v << " := " << y << op
                                    << z << "\n";
               crab::outs() << _impl << "\n";);
      return tx;
    }
  }

  term_id_t build_function(term_id_t ty, term_id_t tz) {
    term::term_operator_t op = term::TERM_OP_FUNCTION;
    // Check if the term already exists
    boost::optional<term_id_t> eopt(_ttbl.find_ftor(op, ty, tz));
    if (eopt) {
      return *eopt;
    } else {
      // Create the term
      term_id_t tx = _ttbl.apply_ftor(op, ty, tz);
      return tx;
    }
  }

  term_id_t build_linexpr(const linear_expression_t &e) {
    number_t cst = e.constant();
    typename linear_expression_t::const_iterator it(e.begin());
    if (it == e.end())
      return term_of_const(cst);

    term_id_t t;
    if (cst == 0) {
      t = term_of_linterm(*it);
      ++it;
    } else {
      t = term_of_const(cst);
    }
    for (; it != e.end(); ++it) {
      t = build_term(OP_ADDITION, t, term_of_linterm(*it));
    }

    CRAB_LOG("term", crab::outs() << "Should have " << domvar_of_term(t)
                                  << " := " << e << "\n"
                                  << _impl << "\n");
    return t;
  }

  dom_var_t domvar_of_term(term_id_t id) {
    auto it = _term_map.find(id);
    if (it != _term_map.end()) {
      return (*it).second;
    } else {
      // Allocate a fresh variable
      dom_var_t dvar(_alloc.next());
      _term_map.insert(std::make_pair(id, dvar));
      return dvar;
    }
  }

  // used by to_linear_constraint_system()
  boost::optional<dom_var_t> domvar_of_term(term_id_t id) const {
    auto it = _term_map.find(id);
    if (it != _term_map.end()) {
      return (*it).second;
    } else {
      return boost::none;
    }
  }

  dom_var_t domvar_of_var(variable_t v) {
    return domvar_of_term(term_of_var(v));
  }

  // Remap a linear constraint to the domain.
  dom_linexp_t rename_linear_expr(linear_expression_t exp) {
    number_t cst(exp.constant());
    dom_linexp_t dom_exp(cst);
    for (auto const &v : exp.variables()) {
      dom_exp = dom_exp + exp[v] * domvar_of_var(v);
    }
    return dom_exp;
  }

  dom_lincst_t rename_linear_cst(linear_constraint_t cst) {
    return dom_lincst_t(rename_linear_expr(cst.expression()),
                        (typename dom_lincst_t::kind_t)cst.kind());
  }

  // Assumption: vars(exp) subseteq keys(map)
  // XXX JNL: exp can have variables that are not in rev_map (e.g.,
  // some generated by build_linexpr).
  boost::optional<linear_expression_t>
  rename_linear_expr_rev(dom_linexp_t exp, rev_map_t rev_map) const {
    number_t cst(exp.constant());
    linear_expression_t rev_exp(cst);
    for (auto const &v : exp.variables()) {
      auto it = rev_map.find(v);
      if (it != rev_map.end()) {
        variable_t v_out((*it).second);
        rev_exp = rev_exp + exp[v] * v_out;
      } else
        return boost::optional<linear_expression_t>();
    }
    return rev_exp;
  }

  boost::optional<linear_constraint_t>
  rename_linear_cst_rev(dom_lincst_t cst, rev_map_t rev_map) const {
    boost::optional<linear_expression_t> e =
        rename_linear_expr_rev(cst.expression(), rev_map);
    if (e)
      return linear_constraint_t(
          *e, (typename linear_constraint_t::kind_t)cst.kind());
    else
      return boost::optional<linear_constraint_t>();
  }

  boost::optional<std::pair<variable_t, variable_t>>
  get_eq_or_diseq(linear_constraint_t cst) {
    if (cst.is_equality() || cst.is_disequation()) {
      if (cst.size() == 2 && cst.constant() == 0) {
        auto it = cst.begin();
        auto nx = it->first;
        auto vx = it->second;
        ++it;
        assert(it != cst.end());
        auto ny = it->first;
        auto vy = it->second;
        if (nx == (ny * -1)) {
          return std::make_pair(vx, vy);
        }
      }
    }
    return boost::optional<std::pair<variable_t, variable_t>>();
  }

  struct WidenOp {
    dom_t apply(dom_t before, dom_t after) { return before || after; }
  };

  template <typename Thresholds> struct WidenWithThresholdsOp {
    const Thresholds &m_ts;

    WidenWithThresholdsOp(const Thresholds &ts) : m_ts(ts) {}

    dom_t apply(dom_t before, dom_t after) {
      return before.widening_thresholds(after, m_ts);
    }
  };

  template <typename WidenOp>
  term_domain_t widening(const term_domain_t &o, WidenOp widen_op) const {

    // The left operand of the widenning cannot be closed, otherwise
    // termination is not ensured. However, if the right operand is
    // close precision may be improved.
    term_domain_t right(o);
    right.normalize();
    if (is_bottom()) {
      return right;
    } else if (right.is_bottom()) {
      return *this;
    } else {
      term_domain_t left(*this);

      // First, we need to compute the new term table.
      ttbl_t out_tbl;
      // Mapping of (term, term) pairs to terms in the join state
      typename ttbl_t::gener_map_t gener_map;

      var_map_t out_vmap;
      rev_var_map_t out_rvmap;
      dom_var_alloc_t palloc(left._alloc, right._alloc);

      // For each program variable in state, compute a generalization
      for (auto p : left._var_map) {
        const variable_t &v = p.first;
        // term_id_t tx(term_of_var(v));
        term_id_t tx = p.second;
        term_id_t ty(right.term_of_var(v));

        term_id_t tz =
            left._ttbl.generalize(right._ttbl, tx, ty, out_tbl, gener_map);
        out_vmap[v] = tz;
        add_rev_var_map(out_rvmap, tz, v);
      }

      // Rename the common terms together
      dom_t x_impl(std::move(left._impl));
      dom_t y_impl(std::move(right._impl));

      // Perform the mapping
      term_map_t out_map;
      std::vector<dom_var_t> out_varnames;
      for (auto p : gener_map) {
        auto txy = p.first;
        term_id_t tz = p.second;
        dom_var_t vt(palloc.next());
        out_map.insert(std::make_pair(tz, vt));

        dom_var_t vx = left.domvar_of_term(txy.first);
        dom_var_t vy = right.domvar_of_term(txy.second);

        out_varnames.push_back(vt);

        x_impl.assign(vt, vx);
        y_impl.assign(vt, vy);
      }

      x_impl.project(out_varnames);
      y_impl.project(out_varnames);

      dom_t x_widen_y = widen_op.apply(x_impl, y_impl);

      for (auto p : out_vmap)
        out_tbl.add_ref(p.second);

      CRAB_LOG("term", crab::outs()
                           << "============ WIDENING ==================";
               crab::outs() << x_impl << "\n~~~~~~~~~~~~~~~~";
               crab::outs() << y_impl << "\n----------------";
               crab::outs() << x_widen_y << "\n================"
                            << "\n");

      term_domain_t res(std::move(palloc), std::move(out_vmap),
                        std::move(out_rvmap), std::move(out_tbl),
                        std::move(out_map), std::move(x_widen_y));
      return res;
    }
  }

  // Choose one non-var term from the equivalence class
  // associated with t.
  template <typename Range>
  boost::optional<term_id_t> choose_non_var(ttbl_t &ttbl,
                                            const Range &terms) const {
    std::vector<term_id_t> non_var_terms(terms.size());
    auto it = std::copy_if(terms.begin(), terms.end(), non_var_terms.begin(),
                           [&ttbl](term_id_t t) {
                             typename ttbl_t::term_t *t_ptr =
                                 ttbl.get_term_ptr(t);
                             return (t_ptr && t_ptr->kind() == term::TERM_APP);
                           });
    non_var_terms.resize(std::distance(non_var_terms.begin(), it));
    if (non_var_terms.empty()) {
      return boost::optional<term_id_t>();
    } else {
      // TODO: the heuristics as described in the VMCAI'16 paper
      // that chooses the one that has more references each class.
      return *(non_var_terms.begin());
    }
  }

  term_id_t build_dag_term(ttbl_t &ttbl, int t,
                           term::congruence_closure_solver<ttbl_t> &solver,
                           ttbl_t &out_ttbl, std::vector<int> &stack,
                           std::map<int, term_id_t> &cache) const {

    // already processed
    auto it = cache.find(t);
    if (it != cache.end()) {
#ifdef DEBUG_MEET
      crab::outs() << "Found in cache: ";
      crab::outs() << "t" << t << " --> "
                   << "t" << it->second << "\n";
#endif
      return it->second;
    }

    // break the cycle with a fresh variable
    if (std::find(stack.begin(), stack.end(), t) != stack.end()) {
      term_id_t v = out_ttbl.fresh_var();
#ifdef DEBUG_MEET
      crab::outs() << "Detected cycle: ";
      crab::outs() << "t" << t << " --> "
                   << "t" << v << "\n";
#endif
      return v;
    }

    stack.push_back(t);
    auto membs = solver.get_members(t);
    boost::optional<term_id_t> f = choose_non_var(ttbl, membs);

    if (!f) {
      // no concrete definition exists return a fresh variable
      term_id_t v = out_ttbl.fresh_var();
      auto res = cache.insert(std::make_pair(t, v));
      stack.pop_back();
#ifdef DEBUG_MEET
      crab::outs() << "No concrete definition: ";
      crab::outs() << "t" << t << " --> "
                   << "t" << (res.first)->second << "\n";
#endif
      return (res.first)->second;
    } else {
      // traverse recursively the term
      typename ttbl_t::term_t *f_ptr = ttbl.get_term_ptr(*f);
#ifdef DEBUG_MEET
      crab::outs() << "Traversing recursively the term "
                   << "t" << *f << ":";
      crab::outs() << *f_ptr << "\n";
#endif
      std::vector<term_id_t> &args(term::term_args(f_ptr));
      std::vector<term_id_t> res_args;
      res_args.reserve(args.size());
      for (term_id_t c : args) {
        res_args.push_back(build_dag_term(ttbl, solver.get_class(c), solver,
                                          out_ttbl, stack, cache));
      }
      auto res = cache.insert(std::make_pair(
          t, out_ttbl.apply_ftor(term::term_ftor(f_ptr), res_args)));
      stack.pop_back();
#ifdef DEBUG_MEET
      crab::outs() << "Finished recursive case: ";
      crab::outs() << "t" << t << " --> "
                   << "t" << (res.first)->second << "\n";
#endif
      return (res.first)->second;
    }
  }

public:
  term_domain_t make_top() const override { return term_domain_t(true); }

  term_domain_t make_bottom() const override { return term_domain_t(false); }

  void set_to_top() override {
    term_domain abs(true);
    std::swap(*this, abs);
  }

  void set_to_bottom() override {
    term_domain abs(false);
    std::swap(*this, abs);
  }

  term_domain() : _is_bottom(false) {}

  term_domain(const term_domain_t &o)
      : _is_bottom(o._is_bottom), _ttbl(o._ttbl), _impl(o._impl),
        _alloc(o._alloc), _var_map(o._var_map), _rev_var_map(o._rev_var_map),
        _term_map(o._term_map), changed_terms(o.changed_terms) {
    crab::CrabStats::count(domain_name() + ".count.copy");
    crab::ScopedCrabStats __st__(domain_name() + ".copy");
    check_terms(__LINE__);
  }

  term_domain_t &operator=(const term_domain_t &o) {
    crab::CrabStats::count(domain_name() + ".count.copy");
    crab::ScopedCrabStats __st__(domain_name() + ".copy");

    o.check_terms(__LINE__);
    if (this != &o) {
      _is_bottom = o._is_bottom;
      _ttbl = o._ttbl;
      _impl = o._impl;
      _alloc = o._alloc;
      _var_map = o._var_map;
      _rev_var_map = o._rev_var_map;
      _term_map = o._term_map;
      changed_terms = o.changed_terms;
    }
    check_terms(__LINE__);
    return *this;
  }

  bool is_bottom() const override { return _is_bottom; }

  bool is_top() const override { return !_var_map.size() && !is_bottom(); }

  bool is_normalized() {
    return changed_terms.size() == 0;
    // return _is_normalized(_impl);
  }

  // Lattice operations
  bool operator<=(const term_domain_t &o) const override {
    crab::CrabStats::count(domain_name() + ".count.leq");
    crab::ScopedCrabStats __st__(domain_name() + ".leq");

    // Require normalization of the first argument
    term_domain_t left(*this);
    left.normalize();

    if (left.is_bottom()) {
      return true;
    } else if (o.is_bottom()) {
      return false;
    } else {
      term_domain_t right(o);
      typename ttbl_t::term_map_t gen_map;
      dom_var_alloc_t palloc(left._alloc, right._alloc);

      // Build up the mapping of right onto left, variable by variable.
      // Assumption: the set of variables in left & right are common.
      for (auto p : left._var_map) {
        if (!left._ttbl.map_leq(right._ttbl, left.term_of_var(p.first),
                                right.term_of_var(p.first), gen_map))
          return false;
      }
      // We now have a mapping of reachable y-terms to x-terms.
      // Create copies of left._impl and right._impl with a common
      // variable set.
      dom_t x_impl(std::move(left._impl));
      dom_t y_impl(std::move(right._impl));

      // Perform the mapping
      std::vector<dom_var_t> out_varnames;
      for (auto p : gen_map) {
        // dom_var_t vt = _alloc.next();
        dom_var_t vt(palloc.next());
        dom_var_t vx = left.domvar_of_term(p.second);
        dom_var_t vy = right.domvar_of_term(p.first);

        out_varnames.push_back(vt);

        x_impl.assign(vt, vx);
        y_impl.assign(vt, vy);
      }

      x_impl.project(out_varnames);
      y_impl.project(out_varnames);

      return x_impl <= y_impl;
    }
  }

  void operator|=(const term_domain_t &o) override {
    crab::CrabStats::count(domain_name() + ".count.join");
    crab::ScopedCrabStats __st__(domain_name() + ".join");

    // Requires normalization of both operands
    term_domain_t right(o);
    normalize();
    right.normalize();

    if (is_bottom() || right.is_top()) {
      *this = right;
    } else if (right.is_bottom() || is_top()) {
      return;
    } else {
      // First, we need to compute the new term table.
      ttbl_t out_tbl;

      // Mapping of (term, term) pairs to terms in the join state
      typename ttbl_t::gener_map_t gener_map;

      var_map_t out_vmap;
      rev_var_map_t out_rvmap;
      dom_var_alloc_t palloc(_alloc, right._alloc);

      // For each program variable in state, compute a generalization
      for (auto p : _var_map) {
        const variable_t &v = p.first;
        // term_id_t tx(term_of_var(v));
        term_id_t tx = p.second;
        term_id_t ty(right.term_of_var(v));

        term_id_t tz =
            _ttbl.generalize(right._ttbl, tx, ty, out_tbl, gener_map);
        assert(tz < out_tbl.size());
        out_vmap[v] = tz;
        add_rev_var_map(out_rvmap, tz, v);
      }

      // Rename the common terms together
      // Perform the mapping
      term_map_t out_map;
      std::vector<dom_var_t> out_varnames;
      for (auto p : gener_map) {
        auto txy = p.first;
        term_id_t tz = p.second;
        dom_var_t vt(palloc.next());
        out_map.insert(std::make_pair(tz, vt));

        dom_var_t vx = domvar_of_term(txy.first);
        dom_var_t vy = right.domvar_of_term(txy.second);

        out_varnames.push_back(vt);

        _impl.assign(vt, vx);
        right._impl.assign(vt, vy);
      }

      _impl.project(out_varnames);
      right._impl.project(out_varnames);

      _impl |= right._impl;

      for (auto p : out_vmap)
        out_tbl.add_ref(p.second);

      std::swap(_alloc, palloc);
      std::swap(_var_map, out_vmap);
      std::swap(_rev_var_map, out_rvmap);
      std::swap(_ttbl, out_tbl);
      std::swap(_term_map, out_map);
      _is_bottom = (_impl.is_bottom() ? true : false);
    }
  }

  term_domain_t operator|(const term_domain_t &o) const override {
    crab::CrabStats::count(domain_name() + ".count.join");
    crab::ScopedCrabStats __st__(domain_name() + ".join");

    term_domain_t left(*this);
    term_domain_t right(o);

    // Requires normalization of both operands
    left.normalize();
    right.normalize();

    if (left.is_bottom() || right.is_top()) {
      return right;
    } else if (right.is_bottom() || left.is_top()) {
      return left;
    } else {

      // First, we need to compute the new term table.
      ttbl_t out_tbl;
      // Mapping of (term, term) pairs to terms in the join state
      typename ttbl_t::gener_map_t gener_map;

      var_map_t out_vmap;
      rev_var_map_t out_rvmap;
      dom_var_alloc_t palloc(left._alloc, right._alloc);

      // For each program variable in state, compute a generalization
      for (auto p : left._var_map) {
        const variable_t &v = p.first;
        // term_id_t tx(term_of_var(v));
        term_id_t tx = p.second;
        term_id_t ty = right.term_of_var(v);

        term_id_t tz =
            left._ttbl.generalize(right._ttbl, tx, ty, out_tbl, gener_map);
        assert(tz < out_tbl.size());
        out_vmap[v] = tz;
        add_rev_var_map(out_rvmap, tz, v);
      }

      // Rename the common terms together
      dom_t x_impl(std::move(left._impl));
      dom_t y_impl(std::move(right._impl));

      // Perform the mapping
      term_map_t out_map;
      std::vector<dom_var_t> out_varnames;
      for (auto p : gener_map) {
        auto txy = p.first;
        term_id_t tz = p.second;
        dom_var_t vt(palloc.next());
        out_map.insert(std::make_pair(tz, vt));

        dom_var_t vx = left.domvar_of_term(txy.first);
        dom_var_t vy = right.domvar_of_term(txy.second);

        out_varnames.push_back(vt);

        x_impl.assign(vt, vx);
        y_impl.assign(vt, vy);
      }

      CRAB_LOG("term", crab::outs() << "============ JOIN =================="
                                    << *this << "\n"
                                    << "~~~~~~~~~~~~~~~~" << o << "\n"
                                    << "----------------"
                                    << "ren_0(x) = " << x_impl
                                    << "ren_0(y) = " << y_impl << "\n");

      x_impl.project(out_varnames);
      y_impl.project(out_varnames);

      dom_t x_join_y = x_impl | y_impl;

      for (auto p : out_vmap)
        out_tbl.add_ref(p.second);

      term_domain_t res(std::move(palloc), std::move(out_vmap),
                        std::move(out_rvmap), std::move(out_tbl),
                        std::move(out_map), std::move(x_join_y));

      CRAB_LOG("term", crab::outs() << "After elimination:\n" << res << "\n");

      return res;
    }
  }

  term_domain_t operator||(const term_domain_t &other) const override {
    crab::CrabStats::count(domain_name() + ".count.widening");
    crab::ScopedCrabStats __st__(domain_name() + ".widening");
    WidenOp op;
    return widening(other, op);
  }

  term_domain_t widening_thresholds(
      const term_domain_t &other,
      const iterators::thresholds<number_t> &ts) const override {
    crab::CrabStats::count(domain_name() + ".count.widening");
    crab::ScopedCrabStats __st__(domain_name() + ".widening");
    WidenWithThresholdsOp<iterators::thresholds<number_t>> op(ts);
    return widening(other, op);
  }

  // Meet
  term_domain_t operator&(const term_domain_t &o) const override {
    crab::CrabStats::count(domain_name() + ".count.meet");
    crab::ScopedCrabStats __st__(domain_name() + ".meet");

    // Does not require normalization of any of the two operands
    if (is_bottom() || o.is_top()) {
      return *this;
    } else if (is_top() || o.is_bottom()) {
      return o;
    } else {
      ttbl_t out_ttbl(_ttbl);
      std::map<term_id_t, term_id_t> copy_map;
      // bring all terms to one ttbl
      for (auto p : o._var_map) {
        // const variable_t &v = p.first;
        // term_id_t tx(o.term_of_var(v));
        term_id_t tx = p.second;
        out_ttbl.copy_term(o._ttbl, tx, copy_map);
      }

      // build unifications between terms from this and o
      std::vector<std::pair<term_id_t, term_id_t>> eqs;
      for (auto p : _var_map) {
        variable_t v(p.first);
        auto it = o._var_map.find(v);
        if (it != o._var_map.end()) {
          // term_id_t tx(term_of_var(v));
          term_id_t tx = p.second;
          eqs.push_back(std::make_pair(tx, copy_map[it->second]));
        }
      }

      // compute equivalence classes
      term::congruence_closure_solver<ttbl_t> solver(&out_ttbl);
      solver.run(eqs);

      std::vector<int> stack;
      std::map<int, term_id_t> cache;
      var_map_t out_vmap;
      rev_var_map_t out_rvmap;
      // new map from variable to an acyclic term
      for (auto p : _var_map) {
        const variable_t &v = p.first;
        // term_id_t t_old(term_of_var(v));
        term_id_t t_old = p.second;
        term_id_t t_new = build_dag_term(out_ttbl, solver.get_class(t_old),
                                         solver, out_ttbl, stack, cache);
        out_vmap[v] = t_new;
        add_rev_var_map(out_rvmap, t_new, v);
      }
      for (auto p : o._var_map) {
        variable_t v(p.first);
        if (out_vmap.find(v) != out_vmap.end())
          continue;
        term_id_t t_old(copy_map[p.second /*o.term_of_var(v)*/]);
        term_id_t t_new = build_dag_term(out_ttbl, solver.get_class(t_old),
                                         solver, out_ttbl, stack, cache);
        out_vmap[v] = t_new;
        add_rev_var_map(out_rvmap, t_new, v);
      }

      for (auto p : out_vmap)
        out_ttbl.add_ref(p.second);

      // Rename the base domains
      dom_var_alloc_t palloc(_alloc, o._alloc);
      dom_t x_impl(_impl);
      dom_t y_impl(o._impl);
      term_map_t out_map; // map term to dom var
      std::vector<dom_var_t> out_varnames;
      for (auto p : out_vmap) {
        const variable_t &v = p.first;
        term_id_t t_new = p.second;
        dom_var_t vt(palloc.next());
        out_map.insert(std::make_pair(t_new, vt));
        // renaming this's base domain
        auto xit = _var_map.find(v);
        if (xit != _var_map.end()) {
          if (boost::optional<dom_var_t> vx = domvar_of_term(xit->second)) {
            x_impl.assign(vt, *vx);
          }
        }
        // renaming o's base domain
        auto yit = o._var_map.find(v);
        if (yit != o._var_map.end()) {
          if (boost::optional<dom_var_t> vy = o.domvar_of_term(yit->second)) {
            y_impl.assign(vt, *vy);
          }
        }
        out_varnames.push_back(vt);
      }

      x_impl.project(out_varnames);
      y_impl.project(out_varnames);

      dom_t x_meet_y = x_impl & y_impl;

      term_domain_t res(std::move(palloc), std::move(out_vmap),
                        std::move(out_rvmap), std::move(out_ttbl),
                        std::move(out_map), std::move(x_meet_y));

      CRAB_LOG("term", crab::outs() << "============ MEET ==================";
               crab::outs() << *this << "\n----------------";
               crab::outs() << o << "\n----------------";
               crab::outs() << res << "\n================"
                            << "\n");
      return res;
    }
  }

  // Narrowing
  term_domain_t operator&&(const term_domain_t &o) const override {
    crab::CrabStats::count(domain_name() + ".count.narrowing");
    crab::ScopedCrabStats __st__(domain_name() + ".narrowing");

    CRAB_WARN("Term narrowing operator replaced with meet");
    return *this & o;
  }

  // Remove a variable from the scope
  void operator-=(const variable_t &v) override {
    crab::CrabStats::count(domain_name() + ".count.forget");
    crab::ScopedCrabStats __st__(domain_name() + ".forget");

    auto it(_var_map.find(v));
    if (it != _var_map.end()) {
      term_id_t t = (*it).second;
      _var_map.erase(it);
      remove_rev_var_map(t, v);
      deref(t);
    }
    CRAB_LOG("term", crab::outs()
                         << "After removing " << v << ": " << *this << "\n";);
  }

  void assign(const variable_t &x, const linear_expression_t &e) override {
    crab::CrabStats::count(domain_name() + ".count.assign");
    crab::ScopedCrabStats __st__(domain_name() + ".assign");

    if (this->is_bottom()) {
      return;
    } else {
      // dom_linexp_t dom_e(rename_linear_expr(e));
      term_id_t tx(build_linexpr(e));
      rebind_var(x, tx);

      check_terms(__LINE__);

      CRAB_LOG("term", crab::outs() << "*** Assign " << x << ":=" << e << ":"
                                    << *this << "\n");
      return;
    }
  }

  // Apply operations to variables.

  // x = y op z
  void apply(arith_operation_t op, const variable_t &x, const variable_t &y,
             const variable_t &z) override {
    crab::CrabStats::count(domain_name() + ".count.apply");
    crab::ScopedCrabStats __st__(domain_name() + ".apply");
    check_terms(__LINE__);
    if (this->is_bottom()) {
      return;
    } else {
      term_id_t tx(build_term(op, term_of_var(y), term_of_var(z)));
      rebind_var(x, tx);
    }
    check_terms(__LINE__);
    CRAB_LOG("term", crab::outs() << "*** " << x << ":=" << y << " " << op
                                  << " " << z << ":" << *this << "\n");
  }

  // x = y op k
  void apply(arith_operation_t op, const variable_t &x, const variable_t &y,
             number_t k) override {
    crab::CrabStats::count(domain_name() + ".count.apply");
    crab::ScopedCrabStats __st__(domain_name() + ".apply");

    if (this->is_bottom()) {
      return;
    } else {
      term_id_t tx(build_term(op, term_of_var(y), term_of_const(k)));
      rebind_var(x, tx);
    }
    check_terms(__LINE__);
    CRAB_LOG("term", crab::outs() << "*** " << x << ":=" << y << " " << op
                                  << " " << k << ":" << *this << "\n");
    return;
  }

  void backward_assign(const variable_t &x, const linear_expression_t &e,
                       const term_domain_t &inv) override {
    crab::CrabStats::count(domain_name() + ".count.backward_assign");
    crab::ScopedCrabStats __st__(domain_name() + ".backward_assign");

    crab::domains::BackwardAssignOps<term_domain_t>::assign(*this, x, e, inv);
  }

  void backward_apply(arith_operation_t op, const variable_t &x,
                      const variable_t &y, number_t z,
                      const term_domain_t &inv) override {
    crab::CrabStats::count(domain_name() + ".count.backward_apply");
    crab::ScopedCrabStats __st__(domain_name() + ".backward_apply");

    crab::domains::BackwardAssignOps<term_domain_t>::apply(*this, op, x, y, z,
                                                           inv);
  }

  void backward_apply(arith_operation_t op, const variable_t &x,
                      const variable_t &y, const variable_t &z,
                      const term_domain_t &inv) override {
    crab::CrabStats::count(domain_name() + ".count.backward_apply");
    crab::ScopedCrabStats __st__(domain_name() + ".backward_apply");

    crab::domains::BackwardAssignOps<term_domain_t>::apply(*this, op, x, y, z,
                                                           inv);
  }

  void operator+=(const linear_constraint_t &cst) {
    crab::CrabStats::count(domain_name() + ".count.add_constraints");
    crab::ScopedCrabStats __st__(domain_name() + ".add_constraints");

    CRAB_LOG("term", crab::outs() << "*** Before assume " << cst << ":" << *this
                                  << "\n");

    using pair_var_t = std::pair<variable_t, variable_t>;

    if (boost::optional<pair_var_t> eq = get_eq_or_diseq(cst)) {
      term_id_t tx(term_of_var((*eq).first));
      term_id_t ty(term_of_var((*eq).second));
      if (cst.is_disequation()) {
        if (tx == ty) {
          set_to_bottom();
          CRAB_LOG("term", crab::outs() << "*** After assume " << cst << ":"
                                        << *this << "\n");
          return;
        }
      } else {
        // not bother if they are already equal
        if (tx == ty)
          return;

        // congruence closure to compute equivalence classes
        term::congruence_closure_solver<ttbl_t> solver(&_ttbl);
        std::vector<std::pair<term_id_t, term_id_t>> eqs = {
            std::make_pair(tx, ty)};
        solver.run(eqs);

        std::vector<int> stack;
        std::map<int, term_id_t> cache;
        dom_t x_impl(_impl);
        std::vector<dom_var_t> out_varnames;
        // new map from variable to an acyclic term
        // and also renaming of the base domain
        for (auto p : _var_map) {
          const variable_t &v = p.first;
          term_id_t t_old(term_of_var(v));
          term_id_t t_new = build_dag_term(_ttbl, solver.get_class(t_old),
                                           solver, _ttbl, stack, cache);

          dom_var_t vt = domvar_of_term(t_new);
          dom_var_t vx = domvar_of_term(t_old);

          out_varnames.push_back(vt);
          x_impl.assign(vt, vx);

          rebind_var(v, t_new);
        }
        x_impl.project(out_varnames);
        std::swap(_impl, x_impl);
      }
    }

    dom_lincst_t cst_rn(rename_linear_cst(cst));
    _impl += cst_rn;
    // Possibly tightened some variable in cst
    for (auto const &v : cst.variables()) {
      CRAB_LOG("term-normalization",
               crab::outs() << "Added to the normalization queue "
                            << "t" << term_of_var(v) << "["
                            << domvar_of_term(term_of_var(v)) << "] "
                            << "from variable " << v << "\n";);
      changed_terms.insert(term_of_var(v));
    }

    // Probably doesn't need to done so eagerly.
    normalize();

    CRAB_LOG("term", crab::outs()
                         << "*** After assume " << cst << ":" << *this << "\n");
    return;
  }

  void operator+=(const linear_constraint_system_t &csts) override {
    for (auto cst : csts) {
      this->operator+=(cst);
    }
  }

  /*
  // If the children of t have changed, see if re-applying
  // the definition of t tightens the domain.
  void tighten_term(term_id_t t)
  {
  dom_t tight = _impl&eval_ftor_copy(_impl, _ttbl, t);

  if(!(_impl <= tight))
  {
  // Applying the functor has changed the domain
  _impl = tight;
  for(term_id_t p : _ttbl.parents(t))
  tighten_term(p);
  }
  check_terms(__LINE__);
  }
  */

  interval_t operator[](const variable_t &x) override {
    crab::CrabStats::count(domain_name() + ".count.to_intervals");
    crab::ScopedCrabStats __st__(domain_name() + ".to_intervals");

    // Needed for accuracy
    normalize();

    if (is_bottom())
      return interval_t::bottom();

    auto it = _var_map.find(x);
    if (it == _var_map.end())
      return interval_t::top();

    dom_var_t dom_x = domvar_of_term(it->second);

    return _impl[dom_x];
  }

  void set(const variable_t &x, interval_t intv) {
    crab::CrabStats::count(domain_name() + ".count.assign");
    crab::ScopedCrabStats __st__(domain_name() + ".assign");

    rebind_var(x, term_of_itv(intv.lb(), intv.ub()));
  }

  void apply(int_conv_operation_t /*op*/, const variable_t &dst,
             const variable_t &src) override {
    // since reasoning about infinite precision we simply assign and
    // ignore the widths.
    assign(dst, src);
  }

  void apply(bitwise_operation_t op, const variable_t &x, const variable_t &y,
             const variable_t &z) override {
    crab::CrabStats::count(domain_name() + ".count.apply");
    crab::ScopedCrabStats __st__(domain_name() + ".apply");

    if (this->is_bottom()) {
      return;
    } else {
      term_id_t tx(build_term(op, term_of_var(y), term_of_var(z)));
      rebind_var(x, tx);
    }
    check_terms(__LINE__);
    CRAB_LOG("term", crab::outs() << "*** " << x << ":=" << y << " " << op
                                  << " " << z << ":" << *this << "\n");
  }

  void apply(bitwise_operation_t op, const variable_t &x, const variable_t &y,
             number_t k) override {
    crab::CrabStats::count(domain_name() + ".count.apply");
    crab::ScopedCrabStats __st__(domain_name() + ".apply");

    if (this->is_bottom()) {
      return;
    } else {
      term_id_t tx(build_term(op, term_of_var(y), term_of_const(k)));
      rebind_var(x, tx);
    }

    check_terms(__LINE__);
    CRAB_LOG("term", crab::outs() << "*** " << x << ":=" << y << " " << op
                                  << " " << k << ":" << *this << "\n");
    return;
  }

  /* Array operations */

  virtual void array_init(const variable_t & /*a*/,
                          const linear_expression_t & /*elem_size*/,
                          const linear_expression_t & /*lb_idx*/,
                          const linear_expression_t & /*ub_idx*/,
                          const linear_expression_t & /*val*/) override {
    // TODO: perform a loop of array stores if [lb_idx, ub_idx]
    //       is finite.
  }

  virtual void array_load(const variable_t &lhs, const variable_t &a,
                          const linear_expression_t & /*elem_size*/,
                          const linear_expression_t &i) override {
    crab::CrabStats::count(domain_name() + ".count.array_read");
    crab::ScopedCrabStats __st__(domain_name() + ".array_read");

    if (this->is_bottom()) {
      return;
    } else {
      /**
       *  We treat the array load as an uninterpreted function
       *  lhs := array_load(a, i) -->  lhs := f(a,i)
       */
      term_id_t t_uf(build_function(term_of_var(a), build_linexpr(i)));
      rebind_var(lhs, t_uf);
    }
    check_terms(__LINE__);
    CRAB_LOG("term", crab::outs() << lhs << ":=" << a << "[" << i << "]  -- "
                                  << *this << "\n";);
  }

  virtual void array_store(const variable_t &a,
                           const linear_expression_t & /*elem_size*/,
                           const linear_expression_t &i,
                           const linear_expression_t &val,
                           bool /*is_strong_update*/) override {
    crab::CrabStats::count(domain_name() + ".count.array_store");
    crab::ScopedCrabStats __st__(domain_name() + ".array_store");

    if (this->is_bottom()) {
      return;
    } else {
      auto &vfac = const_cast<varname_t *>(&(a.name()))->get_var_factory();
      /**
       *  We treat the array store as an uninterpreted function
       *  array_store(a, i, val) -->  tmp := f(a,i); assume(tmp == val);
       */
      /// -- tmp := f(a,i)
      term_id_t t_uf(build_function(term_of_var(a), build_linexpr(i)));
      // map always t_uf to the same variable tmp
      variable_t tmp(vfac.get(t_uf));
      // forget tmp
      this->operator-=(tmp);
      // forget the old value for t_uf, otherwise we can get
      // incorrectly bottom when we add the constraint val == tmp.
      _impl -= domvar_of_term(t_uf);
      rebind_var(tmp, t_uf);
      /// -- assume(tmp == val)
      this->operator+=(val == tmp);
    }
    check_terms(__LINE__);
    CRAB_LOG("term", crab::outs() << a << "[" << i << "]:=" << val << " -- "
                                  << *this << "\n";);
  }

  virtual void array_store_range(const variable_t &a,
                                 const linear_expression_t &elem_size,
                                 const linear_expression_t &i,
                                 const linear_expression_t &j,
                                 const linear_expression_t &v) override {
    // do nothing
  }

  virtual void array_assign(const variable_t &lhs,
                            const variable_t &rhs) override {
    // do nothing
  }

  // backward array operations
  void backward_array_init(const variable_t &a,
                           const linear_expression_t &elem_size,
                           const linear_expression_t &lb_idx,
                           const linear_expression_t &ub_idx,
                           const linear_expression_t &val,
                           const term_domain_t &invariant) override {}
  void backward_array_load(const variable_t &lhs, const variable_t &a,
                           const linear_expression_t &elem_size,
                           const linear_expression_t &i,
                           const term_domain_t &invariant) override {
    *this -= lhs;
  }
  void backward_array_store(const variable_t &a,
                            const linear_expression_t &elem_size,
                            const linear_expression_t &i,
                            const linear_expression_t &v, bool is_strong_update,
                            const term_domain_t &invariant) override {}
  void backward_array_store_range(const variable_t &a,
                                  const linear_expression_t &elem_size,
                                  const linear_expression_t &i,
                                  const linear_expression_t &j,
                                  const linear_expression_t &v,
                                  const term_domain_t &invariant) override {}
  void backward_array_assign(const variable_t &lhs, const variable_t &rhs,
                             const term_domain_t &invariant) override {}

  /// term_domain implements standard abstract operations of a
  /// numerical domain and some array operations. It is intended to be
  /// used as a leaf domain in the hierarchy of domains.
  BOOL_OPERATIONS_NOT_IMPLEMENTED(term_domain_t)
  REGION_AND_REFERENCE_OPERATIONS_NOT_IMPLEMENTED(term_domain_t)
  DEFAULT_SELECT(term_domain_t)

  void rename(const variable_vector_t &from,
              const variable_vector_t &to) override {
    crab::CrabStats::count(domain_name() + ".count.rename");
    crab::ScopedCrabStats __st__(domain_name() + ".rename");

    if (is_top() || is_bottom())
      return;

    CRAB_LOG("term", crab::outs() << "Renaming {"; for (auto v
                                                        : from) crab::outs()
                                                   << v << ";";
             crab::outs() << "} with "; for (auto v
                                             : to) crab::outs()
                                        << v << ";";
             crab::outs() << "}:\n"; crab::outs() << *this << "\n";);

    for (unsigned i = 0, sz = from.size(); i < sz; ++i) {
      const variable_t &v = from[i];
      const variable_t &new_v = to[i];
      if (v == new_v) { // nothing to rename
        continue;
      }

      {
        auto it = _var_map.find(new_v);
        if (it != _var_map.end()) {
          CRAB_ERROR(domain_name() + "::rename assumes that ", new_v,
                     " does not exist");
        }
      }

      auto it = _var_map.find(v);
      if (it != _var_map.end()) {
        term_id_t id = it->second;
        _var_map.erase(it);
        _var_map.insert(std::make_pair(new_v, id));
        remove_rev_var_map(id, v);
        add_rev_var_map(_rev_var_map, id, new_v);
      }
    }

    CRAB_LOG("term", crab::outs() << "RESULT=" << *this << "\n");
  }

  // extract operation is used during reduction with other domains.
  void extract(const variable_t &x, linear_constraint_system_t &csts,
               bool only_equalities /*unused*/) {
    crab::CrabStats::count(domain_name() + ".count.extract");
    crab::ScopedCrabStats __st__(domain_name() + ".extract");

    if (!is_normalized())
      normalize();

    if (is_bottom()) {
      return;
    }

    // TODO: make user parameter
    const unsigned max_eq = 10;
    // We limit the number of equalities via
    // constants. Otherwise, the number of equalities can be too
    // large (e.g., with domains like array_expansion) and it
    // would make the reduction very slow.

    // Extract equalities
    auto it = _var_map.find(x);
    if (it != _var_map.end()) {
      term_id_t tx = it->second;
      auto tx_ptr = _ttbl.get_term_ptr(tx);

      bool active_threshold = false;
      if (tx_ptr->kind() == term::TERM_CONST) {
        active_threshold = true;
      }
      auto &varset = _rev_var_map[tx];
      unsigned num_eq = 0;
      for (auto var : varset) {
        if (active_threshold && num_eq > max_eq) {
          return;
        }
        if (var.index() != x.index()) {
          num_eq++;
          linear_constraint_t cst(linear_expression_t(x) ==
                                  linear_expression_t(var));
          CRAB_LOG("terms", crab::outs() << "Extracting " << cst << "\n";);
          csts += cst;
        }
      }
    }
  }

  void forget(const variable_vector_t &variables) override {
    if (is_bottom() || is_top())
      return;

    for (auto v : variables) {
      *this -= v;
    }
  }

  void project(const variable_vector_t &variables) override {
    crab::CrabStats::count(domain_name() + ".count.project");
    crab::ScopedCrabStats __st__(domain_name() + ".project");

    if (is_bottom() || is_top())
      return;

    if (variables.empty()) {
      set_to_top();
      return;
    }

    std::set<variable_t> s1, s2;
    variable_vector_t s3;
    for (auto p : _var_map)
      s1.insert(p.first);
    s2.insert(variables.begin(), variables.end());
    std::set_difference(s1.begin(), s1.end(), s2.begin(), s2.end(),
                        std::back_inserter(s3));
    forget(s3);
  }

  void expand(const variable_t &x, const variable_t &y) override {
    crab::CrabStats::count(domain_name() + ".count.expand");
    crab::ScopedCrabStats __st__(domain_name() + ".expand");

    if (is_bottom() || is_top()) {
      return;
    }

    linear_expression_t e(x);
    term_id_t tx(build_linexpr(e));
    rebind_var(y, tx);

    check_terms(__LINE__);
  }

  /* begin intrinsics operations */
  void intrinsic(std::string name,
		 const variable_or_constant_vector_t &inputs,
                 const variable_vector_t &outputs) override {
    CRAB_WARN("Intrinsics ", name, " not implemented by ", domain_name());
  }

  void backward_intrinsic(std::string name,
			  const variable_or_constant_vector_t &inputs,
                          const variable_vector_t &outputs,
                          const term_domain_t &invariant) override {
    CRAB_WARN("Intrinsics ", name, " not implemented by ", domain_name());
  }
  /* end intrinsics operations */

  // Propagate information from tightened terms to
  // parents/children.
  void normalize() override {
    crab::CrabStats::count(domain_name() + ".count.normalize");
    crab::ScopedCrabStats __st__(domain_name() + ".normalize");
    TermNormalizer<Info, typename Info::domain_t>::normalize(*this);
  }

  void minimize() override {}

  /// Simplify the term associated with x by given the standard
  /// arithmetic meaning to the functors
  bool simplify(const variable_t &x) {
    crab::CrabStats::count(domain_name() + ".count.simplify");
    crab::ScopedCrabStats __st__(domain_name() + ".simplify");

    auto it = _var_map.find(x);
    if (it != _var_map.end()) {
      term_id_t t = it->second;
      simplifier_t simp(_ttbl);
      auto nt = simp.simplify_term(t);
      if (nt) {
        rebind_var(x, *nt);
        return true;
      }
    }
    return false;
  }

  // Output function
  void write(crab_os &o) const override {
    crab::CrabStats::count(domain_name() + ".count.write");
    crab::ScopedCrabStats __st__(domain_name() + ".write");

    // XXX: we can avoid the copy as we do in
    // to_linear_constraint_system but we don't bother for
    // pretty-printing.
    term_domain_t tmp(*this);

    // Normalization is not enforced in order to maintain accuracy
    // but we force it to display all the relationships.
    tmp.normalize();

    if (tmp.is_bottom()) {
      o << "_|_";
      return;
    }
    if (tmp._var_map.empty()) {
      o << "{}";
      return;
    }

    bool first = true;
    o << "{";
    for (auto p : tmp._var_map) {
      if (first)
        first = false;
      else
        o << ", ";
      o << p.first << " -> t" << p.second << "[" << tmp.domvar_of_term(p.second)
        << "]";
    }
    o << "}";

    // print underlying domain
    o << tmp._impl;

#ifdef VERBOSE
    /// For debugging purposes
    o << " ttbl={" << tmp._ttbl << "}\n";
#endif
  }

  linear_constraint_system_t to_linear_constraint_system() const override {
    crab::CrabStats::count(domain_name() +
                           ".count.to_linear_constraint_system");
    crab::ScopedCrabStats __st__(domain_name() +
                                 ".to_linear_constraint_system");

    // Collect the visible terms
    rev_map_t rev_map;
    std::vector<std::pair<variable_t, variable_t>> equivs;
    for (auto p : _var_map) {
      boost::optional<dom_var_t> dv_opt = domvar_of_term(p.second);
      if (!dv_opt)
        continue;

      dom_var_t dv = *dv_opt;
      auto it = rev_map.find(dv);
      if (it == rev_map.end()) {
        // The term has not yet been seen.
        rev_map.insert(std::make_pair(dv, p.first));
      } else {
        // The term is already mapped to (*it).second,
        // so add an equivalence.
        equivs.push_back(std::make_pair((*it).second, p.first));
      }
    }

    // Create a copy of _impl with only visible variables.
    dom_t d_vis(_impl);
    for (auto p : _term_map) {
      dom_var_t dv = p.second;
      if (rev_map.find(dv) == rev_map.end())
        d_vis -= dv;
    }

    // Now build and rename the constraint system, plus equivalences.
    dom_linsys_t dom_sys(d_vis.to_linear_constraint_system());

    linear_constraint_system_t out_sys;
    for (dom_lincst_t cst : dom_sys) {
      auto out_cst = rename_linear_cst_rev(cst, rev_map);
      if (!out_cst)
        continue;
      out_sys += *out_cst;
    }

    for (auto p : equivs) {
      CRAB_LOG("term", crab::outs() << "Added equivalence " << p.first << "="
                                    << p.second << "\n");
      out_sys += (p.first - p.second == 0);
    }

    // Now rename it back into the external scope.
    return out_sys;
  }

  disjunctive_linear_constraint_system_t
  to_disjunctive_linear_constraint_system() const override {
    auto lin_csts = to_linear_constraint_system();
    if (lin_csts.is_false()) {
      return disjunctive_linear_constraint_system_t(true /*is_false*/);
    } else if (lin_csts.is_true()) {
      return disjunctive_linear_constraint_system_t(false /*is_false*/);
    } else {
      return disjunctive_linear_constraint_system_t(lin_csts);
    }
  }

  std::string domain_name() const override {
    std::string name("Term(" + _impl.domain_name() + ")");
    return name;
  }

}; // class term_domain

// Propagate information from tightened terms to
// parents/children.
template <class Info, class Abs> class TermNormalizer {
public:
  using term_domain_t = typename term_domain<Info>::term_domain_t;
  using term_id_t = typename term_domain_t::term_id_t;
  using dom_t = Abs;
  using ttbl_t = typename term_domain_t::ttbl_t;
  using term_set_t = boost::container::flat_set<term_id_t>;

  static void queue_push(ttbl_t &tbl,
                         std::vector<std::vector<term_id_t>> &queue,
                         term_id_t t) {
    int d = tbl.depth(t);
    if (d == 0) {
      // if depth 0 then t is a free variable: nothing to propagate.
      return;
    }
    while (queue.size() <= d) {
      queue.push_back(std::vector<term_id_t>());
    }
    queue[d].push_back(t);
  }

  static void normalize(term_domain_t &abs) {
    // First propagate down, then up.
    std::vector<std::vector<term_id_t>> queue;

    ttbl_t &ttbl(abs._ttbl);
    dom_t &impl = abs._impl;

    for (term_id_t t : abs.changed_terms) {
      queue_push(ttbl, queue, t);
    }

    dom_t d_prime = impl;
    // Propagate information to children.
    // Don't need to propagate level 0, since it's for free variables
    for (int d = queue.size() - 1; d > 0; d--) {
      for (term_id_t t : queue[d]) {
        typename ttbl_t::term_t *t_ptr = ttbl.get_term_ptr(t);
        if (t_ptr->kind() != term::TERM_APP)
          continue;

        CRAB_LOG("term-normalization", crab::outs() << "Propagate to children ";
                 t_ptr->write(crab::outs()); crab::outs() << "\n";
                 crab::outs() << "\tTerm table: "; ttbl.write(crab::outs());
                 crab::outs() << "\n";);

        abs.eval_ftor_down(d_prime, ttbl, t);
        if (!(abs._impl <= d_prime)) {
          impl = d_prime;

          CRAB_LOG(
              "term-normalization",
              crab::outs() << "\trefinement done: enqueue children.\n";
              if (impl.is_bottom()) { crab::outs() << "\tfound bottom\n"; });

          // Enqueue the args.
          typename ttbl_t::term_t *t_ptr = ttbl.get_term_ptr(t);
          std::vector<term_id_t> &args(term::term_args(t_ptr));
          for (term_id_t c : args) {
            if (abs.changed_terms.find(c) == abs.changed_terms.end()) {
              abs.changed_terms.insert(c);
              queue[ttbl.depth(c)].push_back(c);
            }
          }
        } else {
          CRAB_LOG("term-normalization", crab::outs()
                                             << "\tno refinement done.\n";);
        }
      }
    }

    // Collect the parents of changed terms.
    term_set_t up_terms;
    std::vector<std::vector<term_id_t>> up_queue;
    for (term_id_t t : abs.changed_terms) {
      for (term_id_t p : ttbl.parents(t)) {
        if (up_terms.find(p) == up_terms.end()) {
          up_terms.insert(p);
          CRAB_LOG("term-normalization",
                   crab::outs()
                       << "t" << p << "[" << abs.domvar_of_term(p) << "]"
                       << " is a parent of "
                       << "t" << t << "[" << abs.domvar_of_term(t) << "]\n";);
          queue_push(ttbl, up_queue, p);
        }
      }
    }

    // Now propagate up, level by level.
    // This may miss inferences; for example with [[x = y - z]]
    // information about y can propagate to z.
    assert(up_queue.size() == 0 || up_queue[0].size() == 0);
    for (int d = 1; d < up_queue.size(); d++) {
      // up_queue[d] shouldn't change.
      for (term_id_t t : up_queue[d]) {
        abs.eval_ftor(d_prime, ttbl, t);
        CRAB_LOG("term-normalization",
                 typename ttbl_t::term_t *t_ptr = ttbl.get_term_ptr(t);
                 crab::outs() << "Propagate to parent ";
                 t_ptr->write(crab::outs()); crab::outs() << "\n";
                 crab::outs() << "\tTerm table: "; ttbl.write(crab::outs());
                 crab::outs() << "\n";);

        if (!(impl <= d_prime)) {
          CRAB_LOG("term-normalization",
                   crab::outs() << "Before up propagation: " << impl << "\n";
                   crab::outs()
                   << "After up propagation : " << d_prime << "\n";);

          // We need to do a meet here, as
          // impl and F(stmt)impl may be
          // incomparable
          impl = impl & d_prime;
          // impl = d_prime; // Old code

          CRAB_LOG(
              "term-normalization",
              crab::outs() << "\trefinement done: enqueue parents.\n";
              if (impl.is_bottom()) { crab::outs() << "\tfound bottom\n"; });

          for (term_id_t p : ttbl.parents(t)) {
            if (up_terms.find(p) == up_terms.end()) {
              up_terms.insert(p);
              queue_push(ttbl, up_queue, p);
            }
          }
        } else {
          CRAB_LOG("term-normalization", crab::outs()
                                             << "\tno refinement done.\n";);
        }
      }
    }

    abs.changed_terms.clear();

    if (abs._impl.is_bottom())
      abs.set_to_bottom();
  }
};

// Specialized implementation for interval domain.
// GKG: Should modify to work with any independent attribute domain.
#ifdef USE_TERM_INTERVAL_NORMALIZER
template <class Info, class Num, class Var>
class TermNormalizer<Info, ikos::interval_domain<Num, Var>> {
public:
  using term_domain_t = typename term_domain<Info>::term_domain_t;
  using term_id_t = typename term_domain_t::term_id_t;
  using dom_t = ikos::interval_domain<Num, Var>;
  using var_t = typename term_domain_t::dom_var_t;

  using ttbl_t = typename term_domain_t::ttbl_t;
  using term_set_t = boost::container::flat_set<term_id_t>;

  using interval_t = typename dom_t::interval_t;

  static void queue_push(ttbl_t &tbl,
                         std::vector<std::vector<term_id_t>> &queue,
                         term_id_t t) {
    int d = tbl.depth(t);
    if (d == 0) {
      // if depth 0 then t is a free variable: nothing to
      // propagate.
      return;
    }
    while (queue.size() <= d) {
      queue.push_back(std::vector<term_id_t>());
    }
    queue[d].push_back(t);
  }

  static void normalize(term_domain_t &abs) {
    // First propagate down, then up.
    std::vector<std::vector<term_id_t>> queue;

    ttbl_t &ttbl(abs._ttbl);
    dom_t &impl = abs._impl;
    if (impl.is_bottom()) {
      abs.set_to_bottom();
      return;
    }

    for (term_id_t t : abs.changed_terms) {
      queue_push(ttbl, queue, t);
    }

    // Propagate information to children.
    // Don't need to propagate level 0, since it's for free variables
    for (int d = queue.size() - 1; d > 0; d--) {
      for (term_id_t t : queue[d]) {
        typename ttbl_t::term_t *t_ptr = ttbl.get_term_ptr(t);
        if (t_ptr->kind() != term::TERM_APP)
          continue;

        std::vector<term_id_t> &args(term::term_args(t_ptr));
        std::vector<interval_t> arg_intervals;
        for (term_id_t c : args)
          arg_intervals.push_back(impl[abs.domvar_of_term(c)]);
        abs.eval_ftor_down(impl, ttbl, t);

        // Enqueue the args
        for (size_t ci = 0; ci < args.size(); ci++) {
          term_id_t c(args[ci]);
          var_t v = abs.domvar_of_term(c);
          interval_t v_upd(impl[v]);
          if (!(arg_intervals[ci] <= v_upd)) {
            impl.set(v, arg_intervals[ci] & v_upd);
            if (abs.changed_terms.find(c) == abs.changed_terms.end()) {
              abs.changed_terms.insert(c);
              queue[ttbl.depth(c)].push_back(c);
            }
          }
        }
      }
    }

    // Collect the parents of changed terms.
    term_set_t up_terms;
    std::vector<std::vector<term_id_t>> up_queue;
    for (term_id_t t : abs.changed_terms) {
      for (term_id_t p : ttbl.parents(t)) {
        if (up_terms.find(p) == up_terms.end()) {
          up_terms.insert(p);
          queue_push(ttbl, up_queue, p);
        }
      }
    }

    // Now propagate up, level by level.
    assert(up_queue.size() == 0 || up_queue[0].size() == 0);
    for (int d = 1; d < up_queue.size(); d++) {
      // up_queue[d] shouldn't change.
      for (term_id_t t : up_queue[d]) {
        var_t v = abs.domvar_of_term(t);
        interval_t v_interval = impl[v];

        abs.eval_ftor(impl, ttbl, t);

        interval_t v_upd = impl[v];
        if (!(v_interval <= v_upd)) {
          impl.set(v, v_interval & v_upd);
          for (term_id_t p : ttbl.parents(t)) {
            if (up_terms.find(p) == up_terms.end()) {
              up_terms.insert(p);
              queue_push(ttbl, up_queue, p);
            }
          }
        }
      }
    }

    abs.changed_terms.clear();

    if (impl.is_bottom())
      abs.set_to_bottom();
  }
};
#endif

template <typename Info> struct abstract_domain_traits<term_domain<Info>> {
  using number_t = typename Info::Number;
  using varname_t = typename Info::VariableName;
};

template <typename Info> class reduced_domain_traits<term_domain<Info>> {
public:
  using term_domain_t = term_domain<Info>;
  using variable_t = typename term_domain_t::variable_t;
  using linear_constraint_system_t =
      typename term_domain_t::linear_constraint_system_t;

  static void extract(term_domain_t &dom, const variable_t &x,
                      linear_constraint_system_t &csts, bool only_equalities) {
    dom.extract(x, csts, only_equalities);
  }
};

} // namespace domains
} // namespace crab

#pragma GCC diagnostic pop


// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "paddle/phi/kernels/qr_kernel.h"

#include <Eigen/Dense>

#include "paddle/phi/backends/cpu/cpu_context.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/funcs/complex_functors.h"
#include "paddle/phi/kernels/funcs/parse_qr_mode.h"

namespace phi {

template <typename T, typename Context>
void QrKernel(const Context& ctx,
              const DenseTensor& x,
              const std::string& mode,
              DenseTensor* q,
              DenseTensor* r) {
  bool compute_q;
  bool reduced_mode;
  std::tie(compute_q, reduced_mode) = phi::funcs::ParseQrMode(mode);
  auto numel = x.numel();
  PADDLE_ENFORCE_GT(
      numel, 0, errors::PreconditionNotMet("The input of QR is empty."));
  auto x_dims = x.dims();
  int x_rank = x_dims.size();
  int m = x_dims[x_rank - 2];
  int n = x_dims[x_rank - 1];
  int min_mn = std::min(m, n);
  int k = reduced_mode ? min_mn : m;
  int batch_size = numel / (m * n);
  int x_stride = m * n;
  int q_stride = m * k;
  int r_stride = k * n;
  auto* x_data = x.data<phi::dtype::Real<T>>();
  T* q_data = nullptr;
  if (compute_q) {
    q_data = ctx.template Alloc<phi::dtype::Real<T>>(
        q, batch_size * m * k * sizeof(phi::dtype::Real<T>));
  }
  auto* r_data = ctx.template Alloc<phi::dtype::Real<T>>(
      r, batch_size * k * n * sizeof(phi::dtype::Real<T>));

  // Implement QR by calling Eigen
  for (int i = 0; i < batch_size; ++i) {
    const T* x_matrix_ptr = x_data + i * x_stride;
    T* r_matrix_ptr = r_data + i * r_stride;
    using EigenDynamicMatrix =
        Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
    auto x_matrix = Eigen::Map<const EigenDynamicMatrix>(x_matrix_ptr, m, n);
    Eigen::HouseholderQR<EigenDynamicMatrix> qr(x_matrix);
    if (reduced_mode) {
      auto qr_top_matrix = qr.matrixQR().block(0, 0, min_mn, n);
      auto r_matrix_view =
          qr_top_matrix.template triangularView<Eigen::Upper>();
      auto r_matrix = EigenDynamicMatrix(r_matrix_view);
      memcpy(r_matrix_ptr, r_matrix.data(), r_matrix.size() * sizeof(T));
    } else {
      auto r_matrix_view =
          qr.matrixQR().template triangularView<Eigen::Upper>();
      auto r_matrix = EigenDynamicMatrix(r_matrix_view);
      memcpy(r_matrix_ptr, r_matrix.data(), r_matrix.size() * sizeof(T));
    }

    if (compute_q) {
      T* q_matrix_ptr = q_data + i * q_stride;
      if (reduced_mode) {
        auto q_matrix =
            qr.householderQ() * EigenDynamicMatrix::Identity(m, min_mn);
        q_matrix.transposeInPlace();
        memcpy(q_matrix_ptr, q_matrix.data(), q_matrix.size() * sizeof(T));
      } else {
        auto q_matrix = qr.householderQ() * EigenDynamicMatrix::Identity(m, m);
        q_matrix.transposeInPlace();
        memcpy(q_matrix_ptr, q_matrix.data(), q_matrix.size() * sizeof(T));
      }
    }
  }
}

}  // namespace phi

PD_REGISTER_KERNEL(qr, CPU, ALL_LAYOUT, phi::QrKernel, float, double) {}


/// \file biggles_likelihood.cpp Test for p(D|T, theta)

#include "biggles/observation.hpp"
#include "biggles/detail/random.hpp"
#include "biggles/kalman_filter.hpp"
#include "biggles/mh_moves/mh_moves.hpp"
#include "biggles/mh_moves/utility.hpp"
#include "biggles/simulate.hpp"
#include "biggles/partition.hpp"
#include "biggles/samplers.hpp"
#include <boost/foreach.hpp>
#include <boost/format.hpp>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <fstream>
#include <string>
#include <Eigen/Dense>
#include "biggles/tools/sundries.hpp"

// include this last to stop pre-processor macros breaking things
extern "C" {
#include <ccan/tap/tap.h>
}

using namespace biggles;
using boost::format;

const float INV_EXP(1.f/expf(1.f));
const float EXP_ONE(expf(1.f));
const float EPSILON(std::numeric_limits<float>::epsilon());

int clutter_likelihood_test() {
    observation_collection clutter_obs;
    clutter_obs.insert(new_obs(0.f, 0.f, 0));
    clutter_obs.insert(new_obs(0.f, 0.f, 1));
    clutter_obs.insert(new_obs(0.f, 0.f, 2));
    clutter_obs.insert(new_obs(0.f, 0.f, 3));
    clutter_obs.insert(new_obs(0.f, 0.f, 4));
    boost::shared_ptr<track_collection> tracks(new track_collection());
    clutter_ptr clutter(new clutter_t(clutter_obs.begin(), clutter_obs.end()));
    partition part(tracks, clutter, partition::expansion_2d(0.f, EXP_ONE, 0.f, 1.f));
    model::parameters paras;
    float log_pdf = model::log_clutter_given_parameters_density(part, paras);
    diag("log PDF = %f, anticipated %f", log_pdf, -5.f);
    ok(std::abs(float(clutter_obs.size()) + log_pdf) < EPSILON, "clutter test 1");
    return 0;
}

int clutter_likelihood_test2() {
    observation_collection clutter_obs;
    clutter_obs.insert(new_obs(1.f, 0.f, 0));
    clutter_obs.insert(new_obs(0.f, 0.f, 0));
    clutter_obs.insert(new_obs(0.f, 0.f, 1));
    clutter_obs.insert(new_obs(0.f, 0.f, 2));
    clutter_obs.insert(new_obs(1.f, 0.f, 2));
    clutter_obs.insert(new_obs(0.f, 1.f, 2));
    clutter_obs.insert(new_obs(0.f, 0.f, 4));
    boost::shared_ptr<track_collection> tracks(new track_collection());
    clutter_ptr clutter(new clutter_t(clutter_obs.begin(), clutter_obs.end()));
    partition part(tracks, clutter, partition::expansion_2d(0.f, 2., 0.f, 2.f));
    model::parameters paras;
    float log_pdf = model::log_clutter_given_parameters_density(part, paras);
    diag("log PDF = %f, anticipated %f", log_pdf, -float(clutter_obs.size()) * logf(4.f));
    ok(std::abs(float(clutter_obs.size()) * logf(4.f) + log_pdf) < EPSILON, "clutter test 2");
    return 0;
}

int partition_likelihood_test1() {
    observation_collection clutter_obs;
    clutter_obs.insert(new_obs(0.f, 0.f, 0));
    clutter_obs.insert(new_obs(0.f, 0.f, 1));
    clutter_obs.insert(new_obs(0.f, 0.f, 2));
    clutter_obs.insert(new_obs(0.f, 0.f, 3));
    clutter_obs.insert(new_obs(0.f, 0.f, 4));
    boost::shared_ptr<track_collection> tracks(new track_collection());
    clutter_ptr clutter(new clutter_t(clutter_obs.begin(), clutter_obs.end()));
    partition part(tracks, clutter, partition::expansion_2d(0.f, 2.718121828f, 0.f, 1.f));
    model::parameters paras;
    model::birth_rate(paras) = 1.f;
    model::clutter_rate(paras) = 1.f;
    model::observation_probability(paras) = 0.5f;
    model::survival_probability(paras) = 0.5f;
    float log_pdf = model::log_partition_given_parameters_density(part, paras);
    diag("log pdf %f, anticipated %f", log_pdf, logf(powf(INV_EXP, 10.f)));
    ok(std::abs(log_pdf + 10.f) < EPSILON, "partition test 1");
    return 0;
}

int partition_likelihood_test2() {
    observation_collection clutter_obs;
    clutter_obs.insert(new_obs(0.f, 0.f, 0));
    clutter_obs.insert(new_obs(0.f, 0.f, 1));
    clutter_obs.insert(new_obs(0.f, 0.f, 2));
    clutter_obs.insert(new_obs(0.f, 0.f, 3));
    clutter_obs.insert(new_obs(0.f, 0.f, 4));
    boost::shared_ptr<track_collection> tracks(new track_collection());
    clutter_ptr clutter(new clutter_t(clutter_obs.begin(), clutter_obs.end()));
    observation_collection obs_col;
    tracks->insert(track(0, 2, obs_col.begin(), obs_col.end(), 1.f));
    tracks->insert(track(1, 3, obs_col.begin(), obs_col.end(), 1.f));
    tracks->insert(track(2, 4, obs_col.begin(), obs_col.end(), 1.f));
    tracks->insert(track(3, 5, obs_col.begin(), obs_col.end(), 1.f));
    partition part(tracks, clutter, partition::expansion_2d(0.f, EXP_ONE, 0.f, 1.f));
    model::parameters paras;
    model::birth_rate(paras) = 1.f;
    model::clutter_rate(paras) = 1.f;
    model::observation_probability(paras) = 0.5f;
    model::survival_probability(paras) = 0.5f;
    float log_pdf = model::log_partition_given_parameters_density(part, paras);
    diag("log pdf %f, anticipated %f", log_pdf, -10.f + 6.f*logf(0.5) + 3.f*logf(0.25));
    ok(std::abs(log_pdf + 10.f - 6.f*logf(0.5) - 3.f*logf(0.25)) < EPSILON, "partition test 2");
    return 0;
}

int partition_likelihood_test3() {
    boost::shared_ptr<track_collection> tracks(new track_collection());
    clutter_ptr clutter(new clutter_t());
    observation_collection obs_col1;
    observation_collection obs_col2;
    observation_collection obs_col3;
    observation_collection obs_col4;
    obs_col1.insert(new_obs(0.f, 0.f, 0));
    obs_col1.insert(new_obs(0.f, 0.f, 2));
    obs_col2.insert(new_obs(0.f, 0.f, 1));
    obs_col2.insert(new_obs(0.f, 0.f, 2));
    obs_col2.insert(new_obs(0.f, 0.f, 3));
    obs_col3.insert(new_obs(0.f, 0.f, 2));
    obs_col3.insert(new_obs(0.f, 0.f, 3));
    obs_col4.insert(new_obs(0.f, 0.f, 2));
    obs_col4.insert(new_obs(0.f, 0.f, 4));
    tracks->insert(track(0, 3, obs_col1.begin(), obs_col1.end(), 1.f));
    tracks->insert(track(1, 5, obs_col2.begin(), obs_col2.end(), 1.f));
    tracks->insert(track(2, 4, obs_col3.begin(), obs_col3.end(), 1.f));
    tracks->insert(track(2, 5, obs_col4.begin(), obs_col4.end(), 1.f));
    partition part(tracks, clutter, partition::expansion_2d(0.f, EXP_ONE, 0.f, 1.f));
    model::parameters paras;
    model::birth_rate(paras) = 1.5f;
    model::clutter_rate(paras) = .8f;
    model::observation_probability(paras) = 0.6f;
    model::survival_probability(paras) = 0.8f;
    float expected = -5*0.8f; // clutter
    expected += - 2.f * 1.0945348918918356f - 1.3822169643436166f - 2.f * 1.5; //birth
    expected += logf(0.8f) + log(0.64f) + logf(0.4096f) + logf(0.384f); // surv
    expected += logf(0.6f) + 2.f*logf(0.48f) + logf(0.1296f) + logf(0.432f); // obs
    float log_pdf = model::log_partition_given_parameters_density(part, paras);
    diag("log pdf %f, anticipated %f", log_pdf, expected);
    ok(std::abs(log_pdf - expected) < EPSILON, "partition test 3");
    return 0;
}

void print_kalman_filter_states(const boost::shared_ptr<const track>& t_ptr, const model::parameters paras) {
    const Eigen::Matrix2f& R(model::observation_error_covariance(paras));
    boost::shared_ptr<const kalman_filter> p_kf(t_ptr->make_kalman_filter(paras));
    Eigen::Matrix<float, 2, 4> B;
    B << 1, 0, 0, 0,
         0, 0, 1, 0;
    BOOST_FOREACH(const kalman_filter::state_covariance_pair& state_and_cov, p_kf->predictions()) {
        Eigen::Matrix2f covariance = B * state_and_cov.second * B.transpose() + R;
        Eigen::Vector2f predicted_obs = B * state_and_cov.first;
        diag("cov [[%f, %f], [%f, %f]]", covariance(0, 0), covariance(0, 1), covariance(1, 0), covariance(1, 1));
        diag("loc [%f, %f]", predicted_obs(0), predicted_obs(1));
    }
};

int observation_likelihood1() {
    model::parameters paras;
    model::observation_error_covariance(paras) = Eigen::Matrix2f::Identity() * 0.09f;
    model::process_noise_covariance(paras) = detail::initQ();
    partition::expansion_2d square(0.f, 2.0, 0.f, 2.0f);
    for (int max_ts = 2; max_ts < 50; ++max_ts) {
        observation_collection obs_col1;
        for (int ts = 0; ts < max_ts; ++ts)
            obs_col1.insert(new_obs(1.f, 1.f, ts));

        boost::shared_ptr<track_collection> tracks(new track_collection());
        clutter_ptr clutter(new clutter_t());
        tracks->insert(track(0, max_ts, obs_col1.begin(), obs_col1.end(), 1.f));
        partition part_tr(tracks, clutter, square);

        boost::shared_ptr<track_collection> tracks2(new track_collection());
        clutter_ptr clutter2(new clutter_t(obs_col1.begin(), obs_col1.end()));
        partition part_cl(tracks2, clutter2, square);

        const boost::shared_ptr<const track>& t_ptr = *part_tr.tracks().begin();
        float log_tr_pdf = model::log_track_given_parameters_density(t_ptr, paras);
        float log_cl_pdf = model::log_clutter_given_parameters_density(part_cl, paras);

        int total = 10000;

        float log_part_pdf_tr = -std::numeric_limits<float>::max();
        partition& part = part_tr;
        for (int i = 0; i < total; ++i) {
            sample_birth_rate_given_partition(part, paras);
            sample_clutter_rate_given_partition(part, paras);
            sample_observation_probability_given_partition(part, paras);
            sample_survival_probability_given_partition(part, paras);
            float log_pdf = model::log_partition_given_parameters_density(part, paras);
            if (log_part_pdf_tr < log_pdf) log_part_pdf_tr = log_pdf;
        }

        float log_part_pdf_cl = -std::numeric_limits<float>::max();
        part = part_cl;
        for (int i = 0; i < total; ++i) {
            sample_birth_rate_given_partition(part, paras);
            sample_clutter_rate_given_partition(part, paras);
            sample_observation_probability_given_partition(part, paras);
            sample_survival_probability_given_partition(part, paras);
            float log_pdf = model::log_partition_given_parameters_density(part, paras);
            if (log_part_pdf_cl < log_pdf) log_part_pdf_cl = log_pdf;
        }
        diag("%d, %f, %f", max_ts, log_part_pdf_tr + log_tr_pdf, log_part_pdf_cl + log_cl_pdf );
    }
    return 0;
}

int main(int argc, char** argv)
{
    general_paras gen_pars;
    gen_pars.total = 100000;
    gen_pars.seed = 0;
    gen_pars.rgp.lambda = 2;
    gen_pars.rgp.p_no = 0.;
    gen_pars.rgp.p_yes = 0.;
    gen_pars.rgp.p_tr = 0.4;
    gen_pars.rgp.min_tracks = 5;
    gen_pars.rgp.max_tracks = 6;
    if (not parse_args(argc, argv, gen_pars))
        return exit_status();
    biggles::detail::seed_prng(gen_pars.seed);

    plan_tests(2);
    clutter_likelihood_test();
    clutter_likelihood_test2();
    /* these have changed
    partition_likelihood_test1();
    partition_likelihood_test2();
    partition_likelihood_test3();
    */
    observation_likelihood1();
    diag("random seed = 0x%x", gen_pars.seed);
    return exit_status();
}


/*
 * Copyright 2018, LAAS-CNRS
 * Author: Steve Tonneau
 */

#ifndef BEZIER_COM_TRAJ_DEFINITIONS_H
#define BEZIER_COM_TRAJ_DEFINITIONS_H

#include <hpp/centroidal-dynamics/centroidal_dynamics.hh>
#include <hpp/spline/bezier_curve.h>
#include <Eigen/Dense>

namespace bezier_com_traj
{

typedef double value_type;
typedef Eigen::Matrix <value_type, 3, 3>                           Matrix3;
typedef Eigen::Matrix <value_type, 6, 3>                           Matrix63;
typedef Eigen::Matrix <value_type, 3, 9>                           Matrix39;
typedef Eigen::Matrix <value_type, Eigen::Dynamic, 3>              MatrixX3;
typedef Eigen::Matrix <value_type, Eigen::Dynamic, Eigen::Dynamic> MatrixXX;
typedef centroidal_dynamics::Vector3 Vector3;
typedef centroidal_dynamics::Vector6 Vector6;
typedef centroidal_dynamics::VectorX VectorX;

typedef Eigen::Ref<Vector3>     Ref_vector3;
typedef Eigen::Ref<VectorX>     Ref_vectorX;
typedef Eigen::Ref<MatrixX3>    Ref_matrixX3;
typedef Eigen::Ref<MatrixXX>    Ref_matrixXX;

typedef const Eigen::Ref<const Vector3>     & Cref_vector3;
typedef const Eigen::Ref<const Vector6>     & Cref_vector6;
typedef const Eigen::Ref<const VectorX>     & Cref_vectorX;
typedef const Eigen::Ref<const MatrixXX>    & Cref_matrixXX;
typedef const Eigen::Ref<const MatrixX3>    & Cref_matrixX3;

typedef Matrix63 matrix6_t;
typedef Vector6 point6_t;
typedef Matrix3 matrix3_t;
typedef Vector3 point3_t;

typedef Eigen::Vector3d point_t;
typedef const Eigen::Ref<const point_t>& point_t_tC;

/**
* @brief waypoint_t a waypoint is composed of a  6*3 matrix that depend
* on the variable x, and of a 6d vector independent of x, such that
* each control point of the target bezier curve is given by pi = wix * x + wis
*/
typedef std::pair<matrix6_t, point6_t> waypoint6_t;
typedef std::pair<matrix3_t, point3_t> waypoint3_t;
typedef std::pair<Matrix39, point3_t> waypoint9_t;
struct waypoint_t; // forward declaration

typedef spline::bezier_curve  <double, double, 3, true, point_t > bezier_t;
typedef spline::bezier_curve  <double, double, 3, true, waypoint_t > bezier_wp_t;
typedef spline::bezier_curve  <double, double, 6, true, point6_t> bezier6_t;

typedef std::vector< std::pair<double, int> > T_time;
typedef T_time::const_iterator CIT_time;

typedef std::pair<double,point3_t> coefs_t;

} // end namespace bezier_com_traj

#endif


#include <rem_tree/rem_tree.h>

#include <iostream>
#include <chrono>
#include <NTL/ZZ.h>
#include <NTL/vector.h>

using namespace std;
using namespace NTL;

ZZ startFunc(ZZ modProd);

int main(){
	int startBound = 10;
	int endBound = 20;

	Vec<ZZ> A;
	A.setLength(endBound);
	Vec<ZZ> m;
	m.setLength(startBound);
	for(int i = 0; i < endBound; i++){
		A[i] = i;
		m[i] = (ProbPrime(ZZ(i))) ? i : 1;
	}

	ZZ (*const startVal)(ZZ) = startFunc;

	Vec<ZZ> newA;
	newA.setLength(endBound - startBound);
	Vec<ZZ> newm;
	newm.setLength(endBound - startBound);
	for(int i = 0; i < endBound - startBound; i++){
		newA[i] = A[i + startBound];
		newm[i] = m[i + startBound];
	}

	// create object
	rem_tree T(10, 20, startVal, newA, newm);
}

ZZ startFunc(ZZ modProd){
	// have to fill in
}


#ifndef YQVMC_CI_BINDER_HPP
#define YQVMC_CI_BINDER_HPP

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/moment.hpp>

namespace yqvmc { namespace ci {
  class BinderCumulant {
  public:
    BinderCumulant() {}
    typedef double result_type;

    template <typename C>
    void measure(const C& conf, std::size_t stamp) {
      double m = 0.;
      for (bool spin : conf)
        m += spin ? +1. : -1.;
      m_acc(m);
    }

    result_type result() {
      double m4 = boost::accumulators::moment<4>(m_acc);
      double m2 = boost::accumulators::moment<2>(m_acc);
      return 1. - m4/3./m2/m2;
    }

  private:
    boost::accumulators::accumulator_set<double,
      boost::accumulators::stats<
        boost::accumulators::tag::moment<2>,
        boost::accumulators::tag::moment<4>
      >
    > m_acc;
  };
} }
#endif