/******************************************************************************
 *   Copyright (C) 2006-2019 by the resistivity.net development team          *
 *   Carsten Rücker carsten@resistivity.net                                   *
 *                                                                            *
 *   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 "bertMisc.h"

#include "bertDataContainer.h"
#include "electrode.h"

#include <mesh.h>
#include <node.h>
#include <numericbase.h>

namespace GIMLI{

void initKWaveList(const Mesh & mesh, RVector & kValues, RVector & weights,
                   bool verbose){
    std::vector < RVector3 > sources;
    initKWaveList(mesh, kValues, weights, sources, verbose);
}

void initKWaveList(const Mesh & mesh, RVector & kValues, RVector & weights,
                   const std::vector < RVector3 > & sources, bool verbose){
    kValues.clear();
    weights.clear();

    if (mesh.dim() == 3){
        kValues.resize(1, 0.0);
        weights.resize(1, 1.0);
        return ;
    }

    R3Vector sourcesPos;

    if (sources.empty()){
        sourcesPos = mesh.positions(mesh.findNodesIdxByMarker(MARKER_NODE_ELECTRODE));
        if (verbose) std::cout << "No sensors given for initializing the k wave list .. "
                                  " mesh contains " << sourcesPos.size() << " nodes with marker=-99" << std::endl;
    } else sourcesPos = sources;

    int nElecs = sourcesPos.size();

    double rMin = MAX_DOUBLE, rMax = -MAX_DOUBLE, dist = 0;

    if (nElecs < 2) {
        std::cerr << WHERE_AM_I << " Warning! No sources found, taking some defaults" << std::endl;
        verbose = true;
        rMin = 1.0;
        rMax = (mesh.xmax() - mesh.xmin()) / 20.0;
    } else {
        for (int i = 0; i < nElecs; i ++){
            for (int j = i + 1; j < nElecs; j ++){
                dist = sourcesPos[i].dist(sourcesPos[j]);
                rMin = std::min(dist, rMin);
                rMax = std::max(dist, rMax);
            }
        }
    }

    rMin /= 2.0;
    rMax *= 2.0;
    if (verbose) std::cout << "rMin = " << rMin << ", rMax = " << rMax << std::endl;

    uint nGauLegendre = std::max(static_cast< int >(floor(6.0 * std::log10(rMax / rMin))), 4) ;
    uint nGauLaguerre = 4;
    //nGauLegendre = 10; nGauLaguerre = 10;

    initKWaveList(rMin, rMax, nGauLegendre, nGauLaguerre, kValues, weights);

    if (verbose) std::cout << "NGauLeg + NGauLag for inverse Fouriertransformation: " << nGauLegendre << " + " << nGauLaguerre << std::endl;

}

void initKWaveList(double rMin, double rMax, int nGauLegendre, int nGauLaguerre,
                   RVector & kValues, RVector & weights){

    RVector k, w;
    double k0 = 1.0 / (2.0 * rMin);

    GaussLegendre(0.0, 1.0, nGauLegendre, k, w);
    RVector kLeg(k0 * k * k);
    RVector wLeg(2.0 * k0 * k * w / PI);

    GaussLaguerre(nGauLaguerre, k, w);
    RVector kLag(k0 * (k + 1.0));
    RVector wLag(k0 * exp(k) * w / PI);

    kValues = cat(kLeg, kLag);
    weights = cat(wLeg, wLag);

//     kValues.resize(nGauLegendre + nGauLaguerre);
//     weights.resize(nGauLegendre + nGauLaguerre);

//    GaussLegendre(0.0, 1.0, nGauLegendre, k, w);
//     for (int i = 0; i < nGauLegendre; i++){
//         //** BackSubstitution der Gauss Integration
//         kValues[i] = (k0 * k[i] * k[i]);
//         //** BackSubst. GewichtsTERM Gauss Integration
//         weights[i] = (2.0 * k0 * k[i] * w[i] / PI);
//     }

//     GaussLaguerre(nGauLaguerre, k, w);
//     for (int i = 0; i < nGauLaguerre; i++){
//         //** BackSubstitution der Gauss Integration
//         kValues[nGauLegendre + i] = (k0 * (k[i] + 1));
//         //** BackSubst. GewichtsTERM Gauss Integration
//         weights[nGauLegendre + i] = (k0 * std::exp(k[i]) * w[i] / PI);
//     }
//
//     std::cout << kValues << std::endl;
//     std::cout << cat(kLeg, kLag) << std::endl;
//     std::cout << weights << std::endl;
//     std::cout << cat(wLeg, wLag) << std::endl;

}

RVector geometricFactors(const DataContainerERT & data, int dim, bool forceFlatEarth){
    RVector k(data.size());

    if (dim == 2){
        DataContainerERT tmp(data);
        for (Index i = 0; i < data.sensorCount(); i ++){
            RVector3 p(tmp.sensorPosition(i));
            // if y  differ from 0 .. we assume its x|y -> 2D .. so switch y->z
            if (p[1] != 0.0){
                p[2] = p[1];
                p[1] = 0.;
            }
            tmp.setSensorPosition(i, p);
        }
        return geometricFactors(tmp, 3, forceFlatEarth);
    }

    if (forceFlatEarth){
        DataContainerERT tmp(data);
        for (Index i = 0; i < data.sensorCount(); i ++){
            RVector3 p(tmp.sensorPosition(i)); p[2] = 0.0;
            tmp.setSensorPosition(i, p);
        }
        return geometricFactors(tmp, false);
    } else {

        for (Index i = 0; i < k.size(); i ++){
            double uam = 0.0, ubm = 0.0, uan = 0.0, ubn = 0.0;

            int a = data("a")[i];
            int b = data("b")[i];
            int m = data("m")[i];
            int n = data("n")[i];

            if (a > -1 && m > -1) uam = exactDCSolution(data.sensorPosition(a), data.sensorPosition(m));
            if (b > -1 && m > -1) ubm = exactDCSolution(data.sensorPosition(b), data.sensorPosition(m));
            if (a > -1 && n > -1) uan = exactDCSolution(data.sensorPosition(a), data.sensorPosition(n));
            if (b > -1 && n > -1) ubn = exactDCSolution(data.sensorPosition(b), data.sensorPosition(n));
//         std::cout << data(i).eA() << "   " << data(i).eB() << " " << data(i).eM()
//         << " " << data(i).eN() << std::endl;
//         std::cout << uam << " " << ubm << " " << uan << " " << ubn << std::endl;
            k[i] = 1.0 / (uam - ubm - uan + ubn);
        }
    }
    return k;
}

double exactDCSolution(const RVector3 & pot, const RVector3 & src){
    //pot, src, k, surfaceZ, fallback
    return exactDCSolution(pot, src, 0.0, 0.0, 1.0);
}

//#include <boost/math/special_functions/detail/bessel_k0.hpp>
//#include <boost/math/tr1.hpp>

double exactDCSolution(const RVector3 & v, const RVector3 & source,
                       double k, double surfaceZ, double fallback){

    double r = v.dist(source);

    if (r < TOLERANCE) return fallback;

    uint dim = 3;
    if (k > 0) dim = 2;

    RVector3 sourceMirror(source);
    sourceMirror[dim - 1] = 2.0 * surfaceZ - source[dim - 1];

    if (surfaceZ == -MAX_DOUBLE || std::isnan(surfaceZ)){// ** asuming fullspace
        if (k == 0.0) {
            //** 3D fullspace
            return 1.0 / (4.0 * PI * r);
        } else {
//             std::cout<< "Fullspace" << " " << surfaceZ << std::endl;
            return (besselK0(r * k)) / (2.0 * PI);
        }
    } else { // halfspace with mirrorspace
        if (k == 0.0) {
            //** 3D mirrorspace
//             std::cout<< "Mirrorspace" << " " << sourceMirror
//                      << " s:" << source
//                      << " v:" << v
//                      << " r:" << r << "rm:" << v.distance(sourceMirror) << std::endl;
            return (1.0 / r + 1.0 / v.distance(sourceMirror)) / (4.0 * PI) ;
        } else {
            //** just for performance issues read this again

            if (source == sourceMirror){
//                 std::cout<< "FlathEarth" << std::endl;
                //return std::tr1::cyl_bessel_k(0, r*k)/ (PI); // fac.3 slower
                return (besselK0(r * k)) / (PI);
            } else {
               // std::cout<< "Mirror" << std::endl;
                return (besselK0(r * k) +
                        besselK0(v.distance(sourceMirror) * k)) / (2.0 * PI);
            }
        }
    }
    return fallback;
}

RVector exactDCSolution(const Mesh & mesh, const RVector3 & src, double k, double surfaceZ){
    RVector solution(mesh.nodeCount());

    double fallback = 0.0;
    uint i = 0;
    for (std::vector< Node * >::const_iterator it = mesh.nodes().begin();
                it != mesh.nodes().end(); it ++){
        solution[i] = exactDCSolution((*it)->pos(), src, k, surfaceZ, fallback);
        i++;
    }
/*    for (uint i = 0; i < mesh.nodeCount(); i ++){
        solution[i] = exactDCSolution(mesh.node(i).pos(), src, k, surfaceZ, fallback);
    }*/
    return solution;
}

RVector exactDCSolution(const Mesh & mesh, const Node * nA, const Node * nB, double k, double surfaceZ){
    RVector solution;
    solution  = exactDCSolution(mesh, nA->pos(), k, surfaceZ);
    solution -= exactDCSolution(mesh, nB->pos(), k, surfaceZ);
    return solution;
}

RVector exactDCSolution(const Mesh & mesh, const ElectrodeShape * elec,
                        double k, double surfaceZ, bool setSingValue){
    RVector solution(exactDCSolution(mesh, elec->pos(), k , surfaceZ));
    if (setSingValue) elec->setSingValue(solution, 0.0, k);
    return solution;
}

RVector exactDCSolution(const Mesh & mesh, int aID, int bID,
                        double k, double surfaceZ){
    RVector solution(exactDCSolution(mesh, aID, k, surfaceZ));
    if (bID > -1) solution -= exactDCSolution(mesh, bID, k, surfaceZ);
    return solution;
}

RVector exactDCSolution(const Mesh & mesh, int aID, double k, double surfaceZ){
    return exactDCSolution(mesh, mesh.node(aID).pos(), k, surfaceZ);
}

int countKWave(const Mesh & mesh){
    RVector kValues, weights;
    initKWaveList(mesh, kValues, weights, false);
    return kValues.size();
}

void DCErrorEstimation(DataContainerERT & data, double errPerc, double errVolt, double defaultCurrent, bool verbose){
    if (verbose) std::cout << "Estimate error: " << errPerc << "% + " << errVolt << "V" << std::endl;
    RVector voltage(abs(data("u")));
    if (min(voltage) == 0.0) {
        voltage = abs(RVector(data("rhoa") / data("k")));
        if (min(data("i")) > 0.0) voltage = voltage * data("i");
        else voltage = voltage * defaultCurrent;
    }
    if (verbose) std::cout << "u min = " << min(voltage) << " V max = " << max(voltage) << " V" << std::endl;
    data.set("err", errVolt / voltage + errPerc / 100.0);
}

double DCParaDepth(const DataContainerERT & data){

    double quasiInf = 1e5;
    RVector amq(data.size(), quasiInf), anq(data.size(), quasiInf);
    RVector bmq(data.size(), quasiInf), bnq(data.size(), quasiInf);

    for (size_t i = 0; i < data.size(); i ++){
        int a = data("a")[i];
        int b = data("b")[i];
        int m = data("m")[i];
        int n = data("n")[i];

        if (a > -1 && m > -1) amq[i] = data.sensorPosition(a).distSquared(data.sensorPosition(m));
        if (b > -1 && m > -1) bmq[i] = data.sensorPosition(b).distSquared(data.sensorPosition(m));
        if (a > -1 && n > -1) anq[i] = data.sensorPosition(a).distSquared(data.sensorPosition(n));
        if (b > -1 && n > -1) bnq[i] = data.sensorPosition(b).distSquared(data.sensorPosition(n));
    }

    RVector scale((2.0 * PI) / geometricFactors(data));
    RVector tmp(data.size());
    double depth = 0.1, sens = 0.0;
    double depthQ = 4.0 * depth* depth;
    while (sens < 0.95){
        depth = depth * 1.1;
        depthQ = 4.0 * depth * depth;
        tmp = 1.0 / sqrt(bnq + depthQ) - 1.0 / sqrt(bmq + depthQ) -
              1.0 / sqrt(anq + depthQ) + 1.0 / sqrt(amq + depthQ);
        sens = sum (1.0 - tmp / scale) / data.size();
    }
    return depth;
}

void setDefaultBERTBoundaryConditions(Mesh & mesh){
    BoundingBox bbox(mesh.boundingBox());
    mesh.createNeighbourInfos();
    for (uint i = 0; i < mesh.boundaryCount(); i ++){
        RVector3 cen(mesh.boundary(i).center());

        for (uint dim = 0; dim < mesh.dimension(); dim++){
            if (cen[dim] == bbox.min()[dim] || cen[dim] == bbox.max()[dim]){
                 mesh.boundary(i).setMarker(MARKER_BOUND_MIXED);
            }
        }

        if (cen[mesh.dimension() -1] == bbox.max()[mesh.dimension()-1]){
            mesh.boundary(i).setMarker(MARKER_BOUND_HOMOGEN_NEUMANN);
        }
    }
}

void setAllNeumannBoundaryConditions(Mesh & mesh){
    BoundingBox bbox(mesh.boundingBox());
    mesh.createNeighbourInfos();
    for (uint i = 0; i < mesh.boundaryCount(); i ++){
        RVector3 cen(mesh.boundary(i).center());

        for (uint dim = 0; dim < mesh.dimension(); dim++){
            if (cen[dim] == bbox.min()[dim] || cen[dim] == bbox.max()[dim]){
                 mesh.boundary(i).setMarker(MARKER_BOUND_HOMOGEN_NEUMANN);
            }
        }

    }
}

RVector prepExportPotentialData(const RVector & data, double logdrop){
    RVector tmp(data);

    for (uint i = 0; i < tmp.size(); i ++) {
        tmp[i] =std::fabs(tmp[i] / logdrop);
        if (tmp[i] < 1.0) tmp[i] = 1.0;
    }

    tmp = log10(tmp);
    tmp /= max(abs(tmp)) * sign(data);
    return tmp;
}

}


//
//  test_FIR.cpp
//
//  Created by Dylan Menzies on 9/3/2015.
//  Copyright (c) 2014 ISVR, University of Southampton. All rights reserved.
//

// avoid annoying warning about unsafe STL functions.
#ifdef _MSC_VER
#pragma warning(disable: 4996)
#endif

#include <librbbl/fir.hpp>

#include <libefl/basic_matrix.hpp>
#include <libefl/vector_functions.hpp>
#include <libvisr/constants.hpp>

#include <boost/test/unit_test.hpp>

#include <boost/filesystem.hpp>

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <memory>
#include <stdexcept>
#include <string>

namespace visr
{
namespace rbbl
{
namespace test
{

            BOOST_AUTO_TEST_CASE( TestFIR )
            {
                std::cout << "Size of FIR object: " << sizeof(FIR) << " bytes." << std::endl;
                // Avoid stack overflow problems by allocating the object on the heap.
                std::unique_ptr<FIR> filterPtr( new FIR() );
                // provide a reference to ease the use of the filter object.
                FIR & filter = *filterPtr;

                FIR::Afloat in[FIR::nBlockSamples];

                int const numOutputs = 32;

                efl::BasicMatrix<FIR::Afloat> outMatrix( numOutputs, FIR::nBlockSamples );
                // Create a pointer vector to the output signals
                std::vector<FIR::Afloat*> outPtrs( numOutputs );
                int idx( 0 );
                std::generate( outPtrs.begin(), outPtrs.end(), [&] { return outMatrix.row(idx++); } );
                int i;

                boost::filesystem::path const baseDir(CMAKE_CURRENT_SOURCE_DIR);

                boost::filesystem::path const bfile = baseDir / boost::filesystem::path("fir/quasiAllpassFIR_f32_n63.txt");
//                boost::filesystem::path const bfile = baseDir / boost::filesystem::path("FIR/test_f2_n3.txt");

                FILE *file = fopen(bfile.string().c_str(),"r");
                BOOST_CHECK( file != nullptr );

                const std::size_t filterLength = 63;

                filter.setNumFIRs(numOutputs); //32 //2 test
                filter.setNumFIRsamples(filterLength); //63 //3 test
                filter.setUpsampleRatio(1); //2 // lengthens impulse without increase in time cost.

// Define whether to load the filter set from a file or from a matrix (which is currently created from a compiled-in text file).
#if 0
                BOOST_CHECK( filter.loadFIRs( file ) == 0 );
#else
                // create a filter matrix containing all filters
                // from a initializer list that is compiled into the program.
                efl::BasicMatrix<FIR::Afloat> allFirCoeffs( 64, filterLength,
#include "fir/quasiAllpassFIR_f64_n63_initializer_list.txt"
                 , cVectorAlignmentSamples );

                // Create a second filter matrix that matches the number of required filters.
                efl::BasicMatrix<FIR::Afloat> firCoeffs( numOutputs, filterLength, cVectorAlignmentSamples );
                for( std::size_t idx( 0 ); idx < firCoeffs.numberOfRows(); ++idx )
                {
                  efl::vectorCopy( allFirCoeffs.row( idx ), firCoeffs.row( idx ), filterLength, cVectorAlignmentSamples );
                }

                filter.loadFIRs( firCoeffs );
#endif
                // filter.createWhiteTristateFIRs(0.1);  // 1.0

                for(i = 0; i < FIR::nBlockSamples; i++) {
                    in[i] = 0;
                }

                in[0] = 1;  // delta input
                //in[1] = 1;

                // int j = 0; // not used at the moment
                // Do not run indefinitely, as this is a unit test.
                for( int loopIdx(0); loopIdx < 1000; ++loopIdx ) {
//                    for(i = 0; i < FIR::nBlockSamples; i++) { // ramp input
//                        in[i] = j++;
//                    }
                    filter.process( in, &outPtrs[0] );
                    in[0] = 0; in[1] = 0;  // delta input contd
                }

                fclose( file );
                file = 0;
            }

} // namespace test
} // namespace rbbl
} // namespace visr


#include "tangent_splitter.hpp"

#include "poly_line.hpp"
#include "geometry.hpp"

#include <algorithm>
#include <boost/assert.hpp>

/// Returns all maximal and minimal tangents that bound a facette.
/// Each tangent stores the idx of the edge that splits the half-line starting
/// at i, or NO_EGDE_ID if the tangent is not split.
std::vector<shortcut> tangent_splitter::operator()(unsigned i) const
{
    auto number_of_tangents = line.coordinates.size() - i;

    std::vector<shortcut> tangents(number_of_tangents);
    for (auto idx = 2u; idx < number_of_tangents; idx++)
    {
        tangents[idx] = shortcut {i, i+idx, NO_EDGE_ID, classify_shortcut(i, i+idx)};

        if (tangents[idx].classification == shortcut::type::MAXIMAL_TANGENT
         || tangents[idx].classification == shortcut::type::MINIMAL_TANGENT)
        {
            auto prev_idx = idx - 1;
            bool intersects = false;
            while (!intersects && prev_idx > 1)
            {
                intersects = geometry::segments_intersect(line.coordinates[i + prev_idx - 1],
                                                          line.coordinates[i + prev_idx],
                                                          line.coordinates[i],
                                                          line.coordinates[i + idx]);

                if (!intersects && tangents[prev_idx].classification == tangents[idx].classification)
                {
                    BOOST_ASSERT(prev_idx < idx);
                    BOOST_ASSERT(prev_idx > 0);
                    prev_idx = tangents[prev_idx].split_edge - i;
                }
                else if (!intersects)
                {
                    prev_idx--;
                }
            }
            tangents[idx].split_edge = i + prev_idx;
        }
    }

    auto new_end = std::remove_if(tangents.begin(), tangents.end(),
                                  [](const shortcut& s) -> bool {
                                  return (s.classification != shortcut::type::MAXIMAL_TANGENT)
                                      && (s.classification != shortcut::type::MINIMAL_TANGENT);
                                  });
    tangents.erase(new_end, tangents.end());

    return tangents;
}

shortcut::type
tangent_splitter::classify_shortcut(const unsigned first, const unsigned last) const
{
    // shortcut is a single edge
    BOOST_ASSERT(first < last);
    if (last - first <= 1)
    {
        return shortcut::type::NO_TANGENT;
    }

    // in the trivial case last == line.coordinates.size()-1
    // -> we only need to check the before element
    BOOST_ASSERT(last > 0);
    const auto& before_last = line.coordinates[last - 1];
    auto pos_before = geometry::position_to_line(line.coordinates[first], line.coordinates[last], before_last);

    // non-trivial case -> check both before and after
    if (last != line.coordinates.size() - 1)
    {
        BOOST_ASSERT(last < line.coordinates.size()-1);
        const auto& after_last = line.coordinates[last + 1];
        auto pos_after = geometry::position_to_line(line.coordinates[first], line.coordinates[last], after_last);

        if (pos_before != pos_after)
        {
            return shortcut::type::NO_TANGENT;
        }
    }

    switch (pos_before)
    {
        case geometry::point_position::LEFT_OF_LINE:
            return shortcut::type::MINIMAL_TANGENT;
        case geometry::point_position::RIGHT_OF_LINE:
            return shortcut::type::MAXIMAL_TANGENT;
        default:
            return shortcut::type::DEGENERATED_TANGENT;
    }
}


#pragma once

/*
  DisIntervals: a domain of disjunction of intervals described in the
  paper "Clousot: Static Contract Checking with Abstract
  Interpretation" by Fahndrich and Logozzo. It is based on the
  implementation available in CodeContracts.
*/

#include <crab/config.h>

#include <crab/domains/abstract_domain.hpp>
#include <crab/domains/backward_assign_operations.hpp>
#include <crab/domains/linear_interval_solver.hpp>
#include <crab/domains/separate_domains.hpp>
#include <crab/support/debug.hpp>
#include <crab/support/stats.hpp>

#include <boost/optional.hpp>
#include <boost/range/iterator_range.hpp>

namespace crab {
namespace domains {

template <typename Number> class dis_interval {

public:
  using bound_t = ikos::bound<Number>;
  using interval_t = ikos::interval<Number>;
  using dis_interval_t = dis_interval<Number>;

private:
  using state_t = enum { BOT, FINITE, TOP };
  using list_intervals_t = std::vector<interval_t>;

public:
  using iterator = typename list_intervals_t::iterator;
  using const_iterator = typename list_intervals_t::const_iterator;

private:
  state_t _state;
  list_intervals_t _list;

public:
  static dis_interval_t top() { return dis_interval_t(TOP); }

  static dis_interval_t bottom() { return dis_interval_t(BOT); }

private:
  // FIXME: this is assuming Number=z_number
  static bool are_consecutive(const interval_t &i1, const interval_t &i2) {
    return ((i1.lb() <= i2.lb() && i1.ub() <= i2.ub()) &&
            (i1.ub() + Number(1) == i2.lb())) ||
           ((i2.lb() <= i1.lb() && i2.ub() <= i1.ub()) &&
            (i2.ub() + Number(1) == i1.lb()));
  }

  static bool overlap(const interval_t &i1, const interval_t &i2) {
    return !((i1 & i2).is_bottom());
  }

  struct IsOnTheLeft {
    bool operator()(const interval_t &i1, const interval_t &i2) const {
      return ((i1.ub() <= i2.lb()) && (i1.ub() != i2.lb()));
    }
  };

  static bool check_well_formed(const dis_interval_t &x) {

    if (x.is_top() || x.is_bottom()) {
      return true;
    }

    if (!x.is_finite()) {
      CRAB_ERROR("sanity check -- state should be finite\n", x,
                 " not well formed");
      return false;
    }

    if (x._list.empty()) {
      CRAB_ERROR("sanity check -- list cannot be empty\n", x,
                 " not well formed");
      return false;
    }

    if (x._list.size() == 1) {
      if (x._list[0].is_top() || x._list[0].is_bottom()) {
        CRAB_ERROR("sanity check -- cannot be top or bottom\n", x,
                   " not well formed");
        return false;
      } else {
        return true;
      }
    }

    // -- check the list of intervals is strictly sorted. This also
    // -- checks for duplicates but it won't complain with two
    // -- intervals like [0,2], [3,4]
    IsOnTheLeft comp;
    list_intervals_t tmp(x._list);
    unsigned prev = 0;
    for (unsigned cur = 1; cur < tmp.size(); cur++, prev++) {
      if (comp(tmp[prev], tmp[cur]))
        continue;

      CRAB_ERROR("sanity check -- list is not strictly sorted: ", tmp[prev],
                 " not leq ", tmp[cur], "\n", x, " not well formed");
      return false;
    }

    return true;
  }

  list_intervals_t normalize(list_intervals_t l, bool &is_bottom) {

    if (l.size() <= 1) {
      CRAB_LOG("disint", crab::outs()
                             << "-- Normalize: singleton " << l[0] << "\n");
      is_bottom = false;
      return l;
    }

    std::sort(l.begin(), l.end(), [](const interval_t &a, const interval_t &b) {
      return (a.lb() <= b.lb() && a.lb() != b.lb());
    });

    list_intervals_t res;
    interval_t prev = interval_t::top();
    unsigned int bottoms = 0;

    for (unsigned int i = 0; i < l.size(); ++i) {
      interval_t intv = l[i];

      if (prev == intv) {
        CRAB_LOG("disint", crab::outs() << "-- Normalize: duplicate"
                                        << "\n");
        continue;
      }

      if (intv.is_bottom()) {
        CRAB_LOG("disint", crab::outs() << "-- Normalize: bottom interval"
                                        << "\n");
        bottoms++;
        continue;
      }

      if (intv.is_top()) {
        CRAB_LOG("disint", crab::outs() << "-- Normalize: top interval"
                                        << "\n");
        is_bottom = false;
        return list_intervals_t();
      }

      if (!prev.is_top()) {

        bool refined = true;
        while (refined && res.size() > 0) {
          prev = res[res.size() - 1];
          if (overlap(prev, intv) || are_consecutive(prev, intv)) {
            CRAB_LOG("disint",
                     crab::outs()
                         << "-- Normalize: overlapping or consecutive intervals"
                         << prev << " and " << intv << "\n");
            res.pop_back();
            intv = prev | intv;
          } else if (intv <= prev) {
            CRAB_LOG("disint", crab::outs()
                                   << "-- Normalize: skipping subsumed interval"
                                   << prev << " and " << intv << "\n");
            goto next_iter;
          } else {
            refined = false;
          }
        }
      }

      prev = intv;
      CRAB_LOG("disint", crab::outs()
                             << "-- Normalize: adding " << intv << "\n");
      if (intv.is_top()) {
        CRAB_LOG("disint", crab::outs() << "-- Normalize: top interval"
                                        << "\n");
        is_bottom = false;
        return list_intervals_t();
      }

      res.push_back(intv);
    next_iter:;
      ;
    }

    is_bottom = (bottoms == l.size());
    CRAB_LOG("disint", crab::outs() << "-- Normalize: number of bottoms = "
                                    << bottoms << "\n");
    // crab::outs() << "-- Normalize result="; for (auto i: res) { crab::outs()
    // << i << "|";} crab::outs() << "\n";
    return res;
  }

  dis_interval(state_t state) : _state(state) {}

  dis_interval(list_intervals_t l, bool Normalize = true)
      : _state(FINITE), _list(l) {

    // TODO: make it a template parameter
    const std::size_t max_num_disjunctions = 50;

    if (Normalize) {
      bool is_bottom = false;
      list_intervals_t res = normalize(_list, is_bottom);
      if (is_bottom) {
        _state = BOT;
        _list.clear();
      } else if (res.empty()) {
        _state = TOP;
        _list.clear();
      } else {
        std::swap(_list, res);
      }
    }

    if (is_finite() && _list.size() >= max_num_disjunctions) {
      // TODO: rather than merging all intervals do a more graceful
      // degradation e.g., start by merging the nearest intervals.
      CRAB_WARN(" reached maximum allowed number of disjunctions. ",
                "Merging all intervals ... ");
      interval_t alljoined = approx(_list);
      _list.clear();
      _list.push_back(alljoined);
    }

    assert(check_well_formed(*this));
  }

  // pre: x is normalized
  interval_t approx(list_intervals_t x) const {
    if (x.empty())
      CRAB_ERROR("list should not be empty");

    if (x.size() <= 1)
      return x[0];
    else
      return (x[0] | x[x.size() - 1]);
  }

public:
  dis_interval() : _state(TOP) {}

  dis_interval(interval_t i) : _state(FINITE) {
    if (i.is_top())
      _state = TOP;
    else if (i.is_bottom())
      _state = BOT;
    else
      _list.push_back(i);
  }

  dis_interval(const dis_interval_t &i) : _state(i._state), _list(i._list) {}

  dis_interval(dis_interval_t &&i)
      : _state(std::move(i._state)), _list(std::move(i._list)) {}

  dis_interval_t &operator=(const dis_interval_t &i) {
    if (this != &i) {
      _state = i._state;
      _list = i._list;
    }
    return *this;
  }

  dis_interval_t &operator=(dis_interval_t &&i) {
    if (this != &i) {
      _state = std::move(i._state);
      _list = std::move(i._list);
    }
    return *this;
  }

  bool is_bottom() const { return (_state == BOT); }

  bool is_top() const { return (_state == TOP); }

  bool is_finite() const { return (_state == FINITE); }

  // for the interval solver
  dis_interval_t lower_half_line() const {
    if (is_bottom()) {
      return bottom();
    } else {
      auto f = [](interval_t x) { return x.lower_half_line(); };
      return apply_unary_op(*this, f);
    }
  }

  // for the interval solver
  dis_interval_t upper_half_line() const {
    if (is_bottom()) {
      return bottom();
    } else {
      auto f = [](interval_t x) { return x.upper_half_line(); };
      return apply_unary_op(*this, f);
    }
  }

  boost::optional<Number> singleton() const {
    interval_t i = approx();
    return i.singleton();
  }

  // iterate over the disjunctive intervals
  iterator begin() { return _list.begin(); }

  iterator end() { return _list.end(); }

  const_iterator begin() const { return _list.begin(); }

  const_iterator end() const { return _list.end(); }

  interval_t approx() const {
    if (is_bottom())
      return interval_t::bottom();
    if (is_top())
      return interval_t::top();
    return approx(_list);
  }

  // required for separate_domains
  // pre: *this and o are normalized
  bool operator==(dis_interval_t o) const {
#if 0
       // -- semantic check
       return (*this <= o && o <= *this);
#else
    // -- syntactic check
    if (is_bottom() && o.is_bottom())
      return true;
    else if (is_top() && o.is_top())
      return true;
    else if (is_bottom() || o.is_bottom())
      return false;
    else if (is_top() || o.is_top())
      return false;
    else {
      if (_list.size() != o._list.size())
        return false;
      else {
        unsigned int j = 0;
        for (unsigned int i = 0; i < _list.size(); ++i, ++j) {
          if (!(_list[i] == o._list[j]))
            return false;
        }
        return true;
      }
    }
#endif
  }

  bool operator<=(const dis_interval_t &o) const {
    if (this->is_bottom()) {
      return true;
    } else if (o.is_bottom()) {
      return false;
    } else {

      unsigned j = 0;
      for (unsigned int i = 0; i < _list.size(); i++) {
        for (; j < o._list.size(); j++) {
          if (_list[i] <= o._list[j])
            goto next_iter;
        }
        return false; // _list[i] is not included in any interval of o._list
      next_iter:;
        ;
      }
      return true;
    }
  }

  // pre: *this and o are normalized
  dis_interval_t operator|(dis_interval_t o) const {

    CRAB_LOG("disint", crab::outs()
                           << "Join of " << *this << " and " << o << "\n");

    if (this->is_bottom()) {
      return o;
    } else if (o.is_bottom()) {
      return *this;
    } else if (this->is_top()) {
      return *this;
    } else if (o.is_top()) {
      return o;
    } else {

      unsigned int i = 0;
      unsigned int j = 0;
      list_intervals_t res;
      res.reserve(_list.size() + o._list.size());

      while (i < _list.size() && j < o._list.size()) {
        CRAB_LOG("disint", crab::outs()
                               << "Join -- left operand =" << _list[i]
                               << " right operand=" << o._list[j] << "\n");

        if (_list[i].is_top() || o._list[j].is_top()) {
          CRAB_LOG("disint", crab::outs()
                                 << "Join -- One of the operands is top"
                                 << "\n");
          return dis_interval_t();
        } else if (_list[i].is_bottom()) {
          CRAB_LOG("disint", crab::outs() << "Join -- Left operand is bottom"
                                          << "\n");
          i++;
        } else if (o._list[j].is_bottom()) {
          CRAB_LOG("disint", crab::outs() << "Join -- Right operand is bottom"
                                          << "\n");
          j++;
        } else if (_list[i] == o._list[j]) {
          CRAB_LOG("disint", crab::outs()
                                 << "Join -- Left operand is equal to right"
                                 << "\n");
          res.push_back(_list[i]);
          i++;
          j++;
        } else if (_list[i] <= o._list[j]) {
          CRAB_LOG("disint",
                   crab::outs()
                       << "Join -- Left operand is included in the right"
                       << "\n");
          res.push_back(o._list[j]);
          i++;
          j++;
        } else if (o._list[j] <= _list[i]) {
          CRAB_LOG("disint",
                   crab::outs()
                       << "Join -- Right operand is included in the left"
                       << "\n");
          res.push_back(_list[i]);
          i++;
          j++;
        } else if (overlap(_list[i], o._list[j]) ||
                   are_consecutive(_list[i], o._list[j])) {
          CRAB_LOG("disint", crab::outs()
                                 << "Join -- Left " << _list[i] << " and right "
                                 << o._list[j]
                                 << " operands overlap or are consecutive"
                                 << "\n");
          res.push_back(_list[i] | o._list[j]);
          i++;
          j++;
        } else if (IsOnTheLeft()(_list[i], o._list[j])) {
          CRAB_LOG("disint", crab::outs()
                                 << "Join -- Left operand " << _list[i]
                                 << " is on the left of the right operand "
                                 << o._list[j] << "\n");
          res.push_back(_list[i]);
          i++;
        } else {
          assert(IsOnTheLeft()(o._list[j], _list[i]));

          CRAB_LOG("disint", crab::outs()
                                 << "Join -- Right operand " << o._list[j]
                                 << " is on the left of the right operand "
                                 << _list[i] << "\n");

          res.push_back(o._list[j]);
          j++;
        }
      }

      // consume the rest of left operand
      while (i < _list.size()) {
        auto intv = _list[i];

        CRAB_LOG("disint", crab::outs()
                               << "Join -- Adding the rest of left operand "
                               << intv << "\n");

        bool refined = true;
        while (refined && res.size() > 0) {
          auto prev = res[res.size() - 1];
          if (overlap(prev, intv) || are_consecutive(prev, intv)) {
            CRAB_LOG("disint",
                     crab::outs()
                         << "\t-- Join : overlapping or consecutive intervals"
                         << prev << " and " << intv << "\n");
            res.pop_back();
            intv = prev | intv;
          } else if (intv <= prev) {
            CRAB_LOG("disint", crab::outs()
                                   << "\t-- Join: skipping subsumed interval"
                                   << prev << " and " << intv << "\n");
            goto next_iter_1;
          } else {
            refined = false;
          }
        }

        res.push_back(intv);
      next_iter_1:
        i++;
      }

      // consume the rest of right operand
      while (j < o._list.size()) {
        auto intv = o._list[j];
        CRAB_LOG("disint", crab::outs()
                               << "Join -- Adding the rest of right operands "
                               << intv << "\n");
        bool refined = true;
        while (refined && res.size() > 0) {
          auto prev = res[res.size() - 1];
          if (overlap(prev, intv) || are_consecutive(prev, intv)) {
            CRAB_LOG("disint",
                     crab::outs()
                         << "\t-- Join : overlapping or consecutive intervals"
                         << prev << " and " << intv << "\n");
            res.pop_back();
            intv = prev | intv;
          } else if (intv <= prev) {
            CRAB_LOG("disint", crab::outs()
                                   << "\t-- Join: skipping subsumed interval"
                                   << prev << " and " << intv << "\n");
            goto next_iter_2;
          } else {
            refined = false;
          }
        }

        res.push_back(intv);
      next_iter_2:
        j++;
      }

      if (res.empty()) {
        CRAB_LOG("disint", crab::outs() << "Join result=_|_"
                                        << "\n");
        return dis_interval_t(BOT);
      } else if (res.size() <= 1 && res[0].is_top()) {
        CRAB_LOG("disint", crab::outs() << "Joing result=[-oo,+oo]"
                                        << "\n");
        return dis_interval_t(TOP);
      } else {
        // It needs normalization. E.g., the join of {[0, 7] | [9, 11]}
        // and {[0, 6] | [8, 11]} returns {[0, 7] | [8, 11]} which
        // should be further simplified to [0,11].

        dis_interval_t join(res);
        CRAB_LOG("disint", crab::outs() << "Joing result=" << join << "\n");
        return join;
      }
    }
  }

  // pre: *this and o are normalized
  dis_interval_t operator&(dis_interval_t o) const {
    if (this->is_bottom() || o.is_bottom()) {
      return this->bottom();
    } else if (this->is_top()) {
      return o;
    } else if (o.is_top()) {
      return *this;
    } else {

      list_intervals_t res;
      res.reserve(_list.size() + o._list.size());

      bool is_bot = true;
      for (unsigned int i = 0; i < _list.size(); ++i) {
        for (unsigned int j = 0; j < o._list.size(); ++j) {
          auto meet = _list[i] & o._list[j];
          if (!meet.is_bottom()) {
            res.push_back(meet);
            is_bot = false;
          }
        }
      }

      if (is_bot) {
        return (this->bottom());
      } else if (res.empty()) {
        return (this->top());
      } else {
        return dis_interval_t(res);
      }
    }
  }

private:
  struct WidenOp {
    interval_t apply(interval_t before, interval_t after) {
      return before || after;
    }
  };

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

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

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

  template <typename WidenOp>
  dis_interval_t widening(dis_interval_t o, WidenOp widen_op) const {
    if (this->is_bottom()) {
      return o;
    } else if (o.is_bottom()) {
      return *this;
    } else if (this->is_top()) {
      return *this;
    } else if (o.is_top()) {
      return o;
    } else {

      // --- trivial cases first
      if (_list.size() == 1 && o._list.size() == 1) {
        return dis_interval_t(widen_op.apply(_list[0], o._list[0]));
      }

      if (_list.size() == 1 && o._list.size() > 1) {
        interval_t x = approx(o._list);
        return dis_interval_t(widen_op.apply(_list[0], x));
      }

      if (_list.size() > 1 && o._list.size() == 1) {
        interval_t x = approx(_list);
        return dis_interval_t(widen_op.apply(x, o._list[0]));
      }

      assert(_list.size() >= 2 && o._list.size() >= 2);

      // The widening implemented in CodeContracts widens the
      // extremes and keep only stable intervals. For this query:
      // widening( [1,1] | [4, 4], [1,1] | [3,3] | [4, 4]) the
      // result would to be [1,1] | [4,4]. But this is not even an
      // upper bound of the right argument so it cannot be a
      // widening.

      // -- widen the extremes
      interval_t lb_widen = widen_op.apply(_list[0], o._list[0]);
      interval_t ub_widen =
          widen_op.apply(_list[_list.size() - 1], o._list[o._list.size() - 1]);

      list_intervals_t res;
      res.reserve(_list.size() + o._list.size());

      res.push_back(lb_widen);

      // for (unsigned int i=1; i < _list.size() - 1; i++) {
      //   for (unsigned int j=1; j < o._list.size() - 1; j++) {
      //     if (o._list [j] <= _list [i]) {
      //       res.push_back(_list [i]);
      //       break;
      //     }
      //   }
      // }

      // keep all the intervals, normalize will do the rest
      res.insert(res.end(), _list.begin() + 1, _list.end() - 1);
      res.insert(res.end(), o._list.begin() + 1, o._list.end() - 1);

      res.push_back(ub_widen);

      return dis_interval_t(res);
    }
  }

public:
  // pre: *this and o are normalized
  dis_interval_t operator||(dis_interval_t o) const {
    WidenOp op;
    return widening(o, op);
  }

  // pre: *this and o are normalized
  template <typename Thresholds>
  dis_interval_t widening_thresholds(dis_interval_t o, const Thresholds &ts) {
    WidenWithThresholdsOp<Thresholds> op(ts);
    return widening(o, op);
  }

  // pre: *this and o are normalized
  dis_interval_t operator&&(dis_interval_t o) const {
    // CRAB_WARN(" DisIntervals narrowing operator replaced with meet");
    return (*this & o);
  }

private:
  // pre: x and y are normalized
  template <typename BinOp>
  dis_interval_t apply_bin_op(dis_interval_t x, dis_interval_t y, BinOp op,
                              bool shortcut_top) const {

    // if shortcut_top is true then the result is top if one of the
    // operands is top

    if (x.is_bottom() || y.is_bottom())
      return this->bottom();

    if (x.is_top() && y.is_top())
      return this->top();

    if (shortcut_top && (x.is_top() || y.is_top()))
      return this->top();

    list_intervals_t res;
    res.reserve(x._list.size() + y._list.size());
    bool is_bot = true;

    if (shortcut_top || (x.is_finite() && y.is_finite())) {
      for (unsigned int i = 0; i < x._list.size(); ++i) {
        for (unsigned int j = 0; j < y._list.size(); ++j) {
          interval_t intv = op(x._list[i], y._list[j]);
          if (intv.is_bottom())
            continue;

          if (intv.is_top())
            return this->top();

          is_bot = false;
          res.push_back(intv);
        }
      }
    } else {

      assert(x.is_top() || y.is_top());

      dis_interval_t non_top_arg = (!x.is_top() ? x : y);
      bool is_non_top_left = (!x.is_top());
      for (unsigned int i = 0; i < non_top_arg._list.size(); ++i) {

        interval_t intv =
            (is_non_top_left ? op(non_top_arg._list[i], interval_t::top())
                             : op(interval_t::top(), non_top_arg._list[i]));

        if (intv.is_bottom())
          continue;

        if (intv.is_top())
          return this->top();

        is_bot = false;
        res.push_back(intv);
      }
    }

    if (is_bot)
      return this->bottom();
    else
      return dis_interval_t(res);
  }

  // pre: x is normalized
  template <typename UnaryOp>
  dis_interval_t apply_unary_op(dis_interval_t x, UnaryOp op) const {

    if (x.is_bottom())
      return this->bottom();

    if (x.is_top())
      return this->top();

    assert(x.is_finite());

    if (x._list.empty())
      CRAB_ERROR("list should not be empty");

    list_intervals_t res;
    res.reserve(x._list.size());
    bool is_bot = true;

    for (unsigned int i = 0; i < x._list.size(); ++i) {
      interval_t intv = op(x._list[i]);
      if (intv.is_bottom())
        continue;
      if (intv.is_top())
        return this->top();
      is_bot = false;
      res.push_back(intv);
    }

    if (is_bot)
      return this->bottom();
    else
      return dis_interval_t(res);
  }

public:
  dis_interval_t operator+(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a + b; };
      return apply_bin_op(*this, x, f, true);
    }
  }

  dis_interval_t &operator+=(dis_interval_t x) {
    return this->operator=(this->operator+(x));
  }

  dis_interval_t operator-() const {
    if (this->is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a) { return -a; };
      return apply_unary_op(*this, f);
    }
  }

  dis_interval_t operator-(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a - b; };
      return apply_bin_op(*this, x, f, true);
    }
  }

  dis_interval_t &operator-=(dis_interval_t x) {
    return this->operator=(this->operator-(x));
  }

  dis_interval_t operator*(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a * b; };
      return apply_bin_op(*this, x, f, true);
    }
  }

  dis_interval_t &operator*=(dis_interval_t x) {
    return this->operator=(this->operator*(x));
  }

  dis_interval_t operator/(dis_interval_t x) {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a / b; };
      return apply_bin_op(*this, x, f, false);
    }
  }

  dis_interval_t &operator/=(dis_interval_t x) {
    return this->operator=(this->operator/(x));
  }

  void normalize() {
    if (is_bottom() || is_top())
      return;
    bool is_bot = false;
    list_intervals_t res = normalize(_list, is_bot);

    if (is_bot) {
      _state = BOT;
    } else if (res.empty()) {
      _state = TOP;
    } else {
      _state = FINITE;
      std::swap(_list, res);
    }
  }

  void write(crab_os &o) const {
    if (is_bottom()) {
      o << "_|_";
    } else if (is_top()) {
      o << "[-oo,+oo]";
    } else {
      assert(_state == FINITE);

      for (typename list_intervals_t::const_iterator it = _list.begin(),
                                                     et = _list.end();
           it != et;) {
        o << *it;
        ++it;
        if (it != et)
          o << " | ";
      }
    }
  }

  // division and remainder operations

  dis_interval_t UDiv(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a / b; };
      return apply_bin_op(*this, x, f, false);
    }
  }

  dis_interval_t SRem(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a.SRem(b); };
      return apply_bin_op(*this, x, f, false);
    }
  }

  dis_interval_t URem(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a.URem(b); };
      return apply_bin_op(*this, x, f, false);
    }
  }

  // bitwise operations
  dis_interval_t And(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a.And(b); };
      return apply_bin_op(*this, x, f, false);
    }
  }

  dis_interval_t Or(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a.Or(b); };
      return apply_bin_op(*this, x, f, false);
    }
  }

  dis_interval_t Xor(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a.Xor(b); };
      return apply_bin_op(*this, x, f, false);
    }
  }

  dis_interval_t Shl(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a.Shl(b); };
      return apply_bin_op(*this, x, f, false);
    }
  }

  dis_interval_t LShr(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a.LShr(b); };
      return apply_bin_op(*this, x, f, false);
    }
  }

  dis_interval_t AShr(dis_interval_t x) const {
    if (this->is_bottom() || x.is_bottom()) {
      return this->bottom();
    } else {
      auto f = [](interval_t a, interval_t b) { return a.AShr(b); };
      return apply_bin_op(*this, x, f, false);
    }
  }

}; //  class dis_interval

template <typename N>
inline crab_os &operator<<(crab_os &o, const dis_interval<N> &i) {
  i.write(o);
  return o;
}

} // end namespace domains
} // end namespace crab

namespace ikos {
namespace linear_interval_solver_impl {
/// --- for interval solver of disequalities

using dis_z_interval_t = crab::domains::dis_interval<z_number>;
using dis_q_interval_t = crab::domains::dis_interval<q_number>;

template <>
inline dis_z_interval_t trim_interval(const dis_z_interval_t &x, const dis_z_interval_t &y) {
  if (x.is_bottom())
    return x;

  boost::optional<z_number> s = y.singleton();
  if (!s)
    return x;
  z_number c = *s;

  dis_z_interval_t res = dis_z_interval_t::bottom();
  if (x.is_top()) {
    res = res | dis_z_interval_t(z_interval(c - 1).lower_half_line());
    res = res | dis_z_interval_t(z_interval(c + 1).upper_half_line());
  } else {
    for (auto i : boost::make_iterator_range(x.begin(), x.end())) {
      if (!(z_interval(c) <= i)) {
        res = res | i;
        continue;
      }

      if (i.lb() == c) {
        res = res | dis_z_interval_t(z_interval(c + 1, i.ub()));
      } else if (i.ub() == c) {
        res = res | dis_z_interval_t(z_interval(i.lb(), c - 1));
      } else {
        res = res | dis_z_interval_t(z_interval(i.lb(), c - 1));
        res = res | dis_z_interval_t(z_interval(c + 1, i.ub()));
      }
    }
  }

  return res;
}

template <>
inline dis_q_interval_t trim_interval(const dis_q_interval_t &i,
                                      const dis_q_interval_t &/* j */) {
  // No refinement possible for disequations over rational numbers
  return i;
}

template <>
inline dis_z_interval_t lower_half_line(const dis_z_interval_t &i,
                                        bool /*is_signed*/) {
  return i.lower_half_line();
}

template <>
inline dis_q_interval_t lower_half_line(const dis_q_interval_t &i,
                                        bool /*is_signed*/) {
  return i.lower_half_line();
}

template <>
inline dis_z_interval_t upper_half_line(const dis_z_interval_t &i,
                                        bool /*is_signed*/) {
  return i.upper_half_line();
}

template <>
inline dis_q_interval_t upper_half_line(const dis_q_interval_t &i,
                                        bool /*is_signed*/) {
  return i.upper_half_line();
}

} // end namespace linear_interval_solver_impl
} // end namespace ikos

namespace crab {
namespace domains {

template <typename Number, typename VariableName>
class dis_interval_domain final
    : public abstract_domain_api<dis_interval_domain<Number, VariableName>> {
  using dis_interval_domain_t = dis_interval_domain<Number, VariableName>;
  using abstract_domain_t = abstract_domain_api<dis_interval_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;
  using interval_domain_t = ikos::interval_domain<number_t, varname_t>;
  using dis_interval_t = dis_interval<number_t>;

private:
  using separate_domain_t = ikos::separate_domain<variable_t, dis_interval_t>;
  using solver_t =
      ikos::linear_interval_solver<number_t, varname_t, separate_domain_t>;

public:
  using iterator = typename separate_domain_t::iterator;

private:
  separate_domain_t _env;

  dis_interval_domain(separate_domain_t env) : _env(env) {}

public:
  dis_interval_domain_t make_top() const override {
    return dis_interval_domain_t(separate_domain_t::top());
  }

  dis_interval_domain_t make_bottom() const override {
    return dis_interval_domain_t(separate_domain_t::bottom());
  }

  void set_to_top() override {
    dis_interval_domain abs(separate_domain_t::top());
    std::swap(*this, abs);
  }

  void set_to_bottom() override {
    dis_interval_domain abs(separate_domain_t::bottom());
    std::swap(*this, abs);
  }

  dis_interval_domain() : _env(separate_domain_t::top()) {}

  dis_interval_domain(const dis_interval_domain_t &o) : _env(o._env) {
    crab::CrabStats::count(domain_name() + ".count.copy");
    crab::ScopedCrabStats __st__(domain_name() + ".copy");
  }

  dis_interval_domain_t &operator=(const dis_interval_domain_t &o) {
    crab::CrabStats::count(domain_name() + ".count.copy");
    crab::ScopedCrabStats __st__(domain_name() + ".copy");
    if (this != &o)
      this->_env = o._env;
    return *this;
  }

  bool is_bottom() const override { return this->_env.is_bottom(); }

  bool is_top() const override { return this->_env.is_top(); }

  iterator begin() { return this->_env.begin(); }

  iterator end() { return this->_env.end(); }

  bool operator<=(const dis_interval_domain_t &e) const override {
    crab::CrabStats::count(domain_name() + ".count.leq");
    crab::ScopedCrabStats __st__(domain_name() + ".leq");

    // crab::outs() << "*** Leq " << *this << " and " << e << "\n";
    bool res = this->_env <= e._env;
    // crab::outs() << "result=" << res << "\n";
    return res;
  }

  void operator|=(const dis_interval_domain_t &e) override {
    this->_env = this->_env | e._env;
  }

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

    // crab::outs() << "*** Join " << *this << " and " << e << "\n";
    dis_interval_domain_t res(this->_env | e._env);
    // crab::outs() << "result=" << res << "\n";
    return res;
  }

  dis_interval_domain_t
  operator&(const dis_interval_domain_t &e) const override {
    crab::CrabStats::count(domain_name() + ".count.meet");
    crab::ScopedCrabStats __st__(domain_name() + ".meet");

    // crab::outs() << "*** Meet " << *this << " and " << e << "\n";
    dis_interval_domain_t res(this->_env & e._env);
    // crab::outs() << "result=" << res << "\n";
    return res;
  }

  dis_interval_domain_t
  operator||(const dis_interval_domain_t &e) const override {
    crab::CrabStats::count(domain_name() + ".count.widening");
    crab::ScopedCrabStats __st__(domain_name() + ".widening");

    // crab::outs() << "*** Widening " << *this << " and " << e << "\n";
    dis_interval_domain_t res(this->_env || e._env);
    // crab::outs() << "result=" << res << "\n";
    return res;
  }

  dis_interval_domain_t widening_thresholds(
      const dis_interval_domain_t &e,
      const iterators::thresholds<number_t> &ts) const override {
    crab::CrabStats::count(domain_name() + ".count.widening");
    crab::ScopedCrabStats __st__(domain_name() + ".widening");

    // crab::outs() << "*** Widening w/ thresholds " << *this << " and " << e <<
    // "\n";
    dis_interval_domain_t res = this->_env.widening_thresholds(e._env, ts);
    // crab::outs() << "result=" << res << "\n";
    return res;
  }

  dis_interval_domain_t
  operator&&(const dis_interval_domain_t &e) const override {
    crab::CrabStats::count(domain_name() + ".count.narrowing");
    crab::ScopedCrabStats __st__(domain_name() + ".narrowing");

    // crab::outs() << "*** Narrowing " << *this << " and " << e << "\n";
    dis_interval_domain_t res(this->_env && e._env);
    // crab::outs() << "result=" << res << "\n";
    return res;
  }

  void operator-=(const variable_t &v) override {
    crab::CrabStats::count(domain_name() + ".count.forget");
    crab::ScopedCrabStats __st__(domain_name() + ".forget");

    this->_env -= v;
  }

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

    dis_interval_t x = this->_env[v];
    return x.approx();
  }

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

    this->_env.set(v, dis_interval_t(intv));
  }

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

    if (!this->is_bottom()) {
      // crab::outs() << "*** add constraints " << csts << " in " << *this <<
      // "\n";
      const size_t threshold = 10; // make this template parameter
      // XXX: filter out unsigned linear inequalities
      linear_constraint_system_t signed_csts;
      for (auto const &c : csts) {
        if (c.is_inequality() && c.is_unsigned()) {
          CRAB_WARN("unsigned inequality skipped");
          continue;
        }
        signed_csts += c;
      }
      solver_t solver(signed_csts, threshold);
      solver.run(this->_env);
      // crab::outs() << "result=" << *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");

    // crab::outs() << "*** " <<  x << ":=" << e << " in " << *this << "\n";
    if (boost::optional<variable_t> v = e.get_variable()) {
      this->_env.set(x, this->_env[(*v)]);
    } else {
      dis_interval_t r(e.constant());
      for (auto t : e)
        r += dis_interval_t(t.first) * this->_env[t.second];
      this->_env.set(x, r);
    }
    // crab::outs() << "result=" << *this << "\n";
  }

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

    // crab::outs() << "*** " << x << ":=" << y << op << z << " in " << *this <<
    // "\n";
    dis_interval_t yi = this->_env[y];
    dis_interval_t zi(z);
    dis_interval_t xi = dis_interval_t::top();
    switch (op) {
    case OP_ADDITION:
      xi = yi + zi;
      break;
    case OP_SUBTRACTION:
      xi = yi - zi;
      break;
    case OP_MULTIPLICATION:
      xi = yi * zi;
      break;
    case OP_SDIV:
      xi = yi / zi;
      break;
    case OP_UDIV:
      xi = yi.UDiv(zi);
      break;
    case OP_SREM:
      xi = yi.SRem(zi);
      break;
    case OP_UREM:
      xi = yi.URem(zi);
      break;
    }
    this->_env.set(x, xi);
    // crab::outs() << "result=" << *this << "\n";
  }

  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");

    // crab::outs() << "*** " << x << ":=" << y << op << z << " in " << *this <<
    // "\n";
    dis_interval_t yi = this->_env[y];
    dis_interval_t zi = this->_env[z];
    dis_interval_t xi = dis_interval_t::top();
    switch (op) {
    case OP_ADDITION:
      xi = yi + zi;
      break;
    case OP_SUBTRACTION:
      xi = yi - zi;
      break;
    case OP_MULTIPLICATION:
      xi = yi * zi;
      break;
    case OP_SDIV:
      xi = yi / zi;
      break;
    case OP_UDIV:
      xi = yi.UDiv(zi);
      break;
    case OP_SREM:
      xi = yi.SRem(zi);
      break;
    case OP_UREM:
      xi = yi.URem(zi);
      break;
    }
    this->_env.set(x, xi);
    // crab::outs() << "result=" << *this << "\n";
  }

  void backward_assign(const variable_t &x, const linear_expression_t &e,
                       const dis_interval_domain_t &inv) override {
    crab::domains::BackwardAssignOps<dis_interval_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 dis_interval_domain_t &inv) override {
    crab::domains::BackwardAssignOps<dis_interval_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 dis_interval_domain_t &inv) override {
    crab::domains::BackwardAssignOps<dis_interval_domain_t>::apply(*this, op, x,
                                                                   y, z, inv);
  }

  void apply(int_conv_operation_t /*op*/, const variable_t &dst,
             const variable_t &src) override {
    // ignore 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");

    dis_interval_t yi = this->_env[y];
    dis_interval_t zi = this->_env[z];
    dis_interval_t xi = dis_interval_t::top();
    switch (op) {
    case OP_AND:
      xi = yi.And(zi);
      break;
    case OP_OR:
      xi = yi.Or(zi);
      break;
    case OP_XOR:
      xi = yi.Xor(zi);
      break;
    case OP_SHL:
      xi = yi.Shl(zi);
      break;
    case OP_LSHR:
      xi = yi.LShr(zi);
      break;
    case OP_ASHR:
      xi = yi.AShr(zi);
      break;
    }
    this->_env.set(x, xi);
  }

  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");

    dis_interval_t yi = this->_env[y];
    dis_interval_t zi(k);
    dis_interval_t xi = dis_interval_t::top();
    switch (op) {
    case OP_AND:
      xi = yi.And(zi);
      break;
    case OP_OR:
      xi = yi.Or(zi);
      break;
    case OP_XOR:
      xi = yi.Xor(zi);
      break;
    case OP_SHL:
      xi = yi.Shl(zi);
      break;
    case OP_LSHR:
      xi = yi.LShr(zi);
      break;
    case OP_ASHR:
      xi = yi.AShr(zi);
      break;
    }
    this->_env.set(x, xi);
  }

  DEFAULT_SELECT(dis_interval_domain_t)

  /// dis_interval_domain implements only standard abstract operations
  /// of a numerical domain so it is intended to be used as a leaf
  /// domain in the hierarchy of domains.
  BOOL_OPERATIONS_NOT_IMPLEMENTED(dis_interval_domain_t)
  ARRAY_OPERATIONS_NOT_IMPLEMENTED(dis_interval_domain_t)
  REGION_AND_REFERENCE_OPERATIONS_NOT_IMPLEMENTED(dis_interval_domain_t)

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

    if (is_bottom() || is_top()) {
      return;
    }
    this->_env.set(new_x, this->_env[x]);
  }

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

    _env.project(variables);
  }

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

    for (const variable_t &v : variables) {
      this->operator-=(v);
    }
  }

  void normalize() override {
    crab::CrabStats::count(domain_name() + ".count.normalize");
    crab::ScopedCrabStats __st__(domain_name() + ".normalize");
    if (is_bottom() || is_top())
      return;
    separate_domain_t env;
    for (auto p : boost::make_iterator_range(_env.begin(), _env.end())) {
      auto disint = p.second;
      disint.normalize();
      env.set(p.first, disint);
    }
    std::swap(_env, env);
  }

  void minimize() override {}

  /* 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 dis_interval_domain_t &invariant) override {
    CRAB_WARN("Intrinsics ", name, " not implemented by ", domain_name());
  }
  /* end intrinsics operations */

  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");

    _env.rename(from, to);
  }

  interval_domain_t approx() const {
    crab::CrabStats::count(domain_name() + ".count.approx");
    crab::ScopedCrabStats __st__(domain_name() + ".approx");

    interval_domain_t res;
    if (_env.is_bottom()) {
      res.set_to_bottom();
    } else if (_env.is_top()) {
      res.set_to_top();
    } else {
      for (auto const p :
           boost::make_iterator_range(_env.begin(), _env.end())) {
        res.set(p.first, p.second.approx());
      }
    }
    return res;
  }

  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");

    interval_domain_t intervals = this->approx();
    return intervals.to_linear_constraint_system();
  }

  disjunctive_linear_constraint_system_t
  to_disjunctive_linear_constraint_system() const override {
    CRAB_ERROR(
        "TODO: to_disjunctive_linear_constraint_system in dis_intervals");
  }

  void write(crab_os &o) const override { this->_env.write(o); }

  std::string domain_name() const override { return "DisjunctiveIntervals"; }
};

template <typename Number, typename VariableName>
struct abstract_domain_traits<dis_interval_domain<Number, VariableName>> {
  using number_t = Number;
  using varname_t = VariableName;
};

} // namespace domains
} // namespace crab


/**
 * @file    Utility.cpp
 *
 * @author  btran
 *
 */

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

#include <sensors_calib/utils/ParamConfig.hpp>
#include <sensors_calib/utils/Utility.hpp>

namespace perception
{
TransformInfo getTransformInfo(const std::string transformationInfoPath)
{
    rapidjson::Document jsonDoc = readFromJsonFile(transformationInfoPath);

    TransformInfo info;
    info(0) = getValueAs<double>(jsonDoc, "x");
    info(1) = getValueAs<double>(jsonDoc, "y");
    info(2) = getValueAs<double>(jsonDoc, "z");

    // change to radian
    info(3) = getValueAs<double>(jsonDoc, "r_deg") * boost::math::double_constants::degree;
    info(4) = getValueAs<double>(jsonDoc, "p_deg") * boost::math::double_constants::degree;
    info(5) = getValueAs<double>(jsonDoc, "y_deg") * boost::math::double_constants::degree;

    return info;
}
}  // namespace perception


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

/*
 Copyright (C) 2014 Catley Lakeman Ltd.
 The moral rights of the author, David Rees, have been asserted.

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

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

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

#ifndef quantlib_test_autocall_hpp
#define quantlib_test_autocall_hpp

#include <boost/test/unit_test.hpp>

#include <ql/quantlib.hpp>
#include <ql/experimental/exoticoptions/autocall.hpp>

using namespace QuantLib;

class AutocallTest {
public:
    // Tests for autocall set up to replicate a fixed bond
    static void testBondBeforeIssueBeforeStrike();
    static void testBondBeforeIssueAfterStrike();
    static void testBondOnCouponDate();
    static void testBondOffCouponDate();
    static void testBondAtMaturity();
    static void testBondAfterMaturity();

    // Test for normal autocall
    static void testBeforeIssueBeforeStrike();
    static void testBeforeIssueAfterStrike();
    static void testOnCouponDate();
    static void testOffCouponDate();
    static void testAtMaturity();
    static void testAfterMaturity();

    static void testExample();

    static void testVsZeroStrikeCall();
    static void testVsCallOption();
    static void testVsDigitalOption();
    static void testVsCallSpread();
    static void testVsBarrierOption();
    static void testVsEverest();

    static boost::unit_test_framework::test_suite* suite();

private:
    // Enum used for setting the difference evaluation date tests
    enum EvaluationDateType {
        BeforeIssueBeforeStrike,
        BeforeIssueAfterStrike,
        OnCouponDate,
        OffCouponDate,
        AtMaturity,
        AfterMaturity
    };

    // Test a autocall set up as a fixed bond
    static void testBondStyle(
        const std::string& method,
        const EvaluationDateType& evaluationDateType,
        const Real& tolerance
        );

    // Test a normal autocall
    static void testNormal(
        const std::string& method,
        const EvaluationDateType& evaluationDateType,
        const Real& expectedResult,
        const Real& tolerance
        );

    // Create an autocall with same cashflows as fixed bond
    static void createBondStyleAutocall(
        const Date& issueDate,
        const Size& numUnderlyings,
        const bool strikeLevelsSet,
        boost::shared_ptr<Autocall>& bondStyleAutocall,
        boost::shared_ptr<FixedRateBond>& bond
        );

    // Create a normal autocall
    static void createNormalAutocall(
        const Date& issueDate,
        const Size& numUnderlyings,
        const bool strikeLevelsSet,
        boost::shared_ptr<Autocall>& autocall
        );

    // Create market data as of valuation date
    static void createMarketData(
        const Date& valuationDate,
        const Size& numUnderlyings,
        Handle<YieldTermStructure>& riskFreeRate,
        boost::shared_ptr<StochasticProcessArray>& process_array
        );

    // Create some more realistic market data as of valuation date
    static void createMarketDataRealistic(
        const Size& numUnderlyings,
        Handle<YieldTermStructure>& riskFreeRate,
        boost::shared_ptr<StochasticProcessArray>& process_array
        );
};

#endif


// Author(s): Unknown
// Copyright: see the accompanying file COPYING or copy at
// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
/// \file invelm_test.cpp
/// \brief Add your file description here.

//#define MCRL2_LPS_PARELM_DEBUG

#define BOOST_TEST_MODULE invelm_test
#include "mcrl2/lps/find.h"
#include "mcrl2/lps/invariant_checker.h"
#include "mcrl2/lps/invelm_algorithm.h"
#include "mcrl2/lps/parse.h"
#include "mcrl2/lps/print.h"
#include "mcrl2/lps/specification.h"
#include "mcrl2/utilities/text_utility.h"
#include "test_specifications.h"
#include <boost/test/included/unit_test_framework.hpp>
#include <iostream>
#include <string>

using namespace mcrl2;

inline
lps::specification invelm(const lps::specification& spec,
                          const data::data_expression& invariant,
                          bool simplify_all = false,
                          bool no_elimination = false
                         )
{
  lps::specification specification = spec;
  data::rewriter::strategy rewrite_strategy = data::jitty;
  int time_limit = 0;
  bool path_eliminator = false;
  data::detail::smt_solver_type solver_type = mcrl2::data::detail::solver_type_cvc;
  bool apply_induction = false;

  lps::detail::Invariant_Checker<lps::specification> v_invariant_checker(specification,
                                                     rewrite_strategy,
                                                     time_limit,
                                                     path_eliminator,
                                                     solver_type,
                                                     apply_induction,
                                                     simplify_all
                                                    );
  if (v_invariant_checker.check_invariant(invariant))
  {
    lps::invelm_algorithm<lps::specification> algorithm(specification,
                                    rewrite_strategy,
                                    time_limit,
                                    path_eliminator,
                                    solver_type,
                                    apply_induction,
                                    simplify_all
                                   );
    algorithm.run(invariant, !no_elimination);
  }
  return specification;
}

BOOST_AUTO_TEST_CASE(test_abp)
{
  lps::specification specification = lps::parse_linear_process_specification(LINEAR_ABP);
  data::data_expression invariant = data::parse_data_expression("true");

  lps::specification result = invelm(specification, invariant);
}

template <typename Expr>
bool has_identifier(const Expr& x, const std::string& id)
{
  std::set<core::identifier_string> ids = lps::find_identifiers(x);
  return ids.find(core::identifier_string(id)) != ids.end();
}

BOOST_AUTO_TEST_CASE(test_invariant)
{
  std::string SPEC =
    "act a, b, c, d;                         \n"
    "                                        \n"
    "proc P(b1, b2: Bool) =                  \n"
    "       b1 -> a . P(!b1, b2)             \n"
    "     + b2 -> b . P(true, b1 && b2)      \n"
    "     + (b1 && b2) -> c . P(false, false)\n"
    "     + d . P(false, true)               \n"
    "     + delta;                           \n"
    "                                        \n"
    "init P(false, true);                    \n"
    ;

  std::string INVARIANT = "!(b1 && b2)";

  data::data_expression invariant = data::parse_data_expression(INVARIANT, data::parse_variables("b1, b2: Bool;"));
  lps::specification spec = lps::parse_linear_process_specification(SPEC);
  bool simplify_all;
  bool no_elimination;
  lps::specification spec1;
  lps::linear_process proc;
  std::cout << lps::pp(spec) << std::endl;

  simplify_all = false;
  no_elimination = false;
  spec1 = invelm(spec, invariant, simplify_all, no_elimination);
  std::cout << lps::pp(spec1) << std::endl;
  proc = spec1.process();
  BOOST_CHECK(proc.action_summands().size() == 3);
  BOOST_CHECK(has_identifier(proc.action_summands()[2], "d"));
  BOOST_CHECK(proc.deadlock_summands().back().condition() == data::sort_bool::true_());

  simplify_all = true;
  no_elimination = false;
  spec1 = invelm(spec, invariant, simplify_all, no_elimination);
  std::cout << lps::pp(spec1) << std::endl;
  proc = spec1.process();
  BOOST_CHECK(proc.action_summands().size() == 3);
  BOOST_CHECK(has_identifier(proc.action_summands()[2], "d"));
  BOOST_CHECK(proc.deadlock_summands().back().condition() != invariant);
  BOOST_CHECK(proc.deadlock_summands().back().condition() != data::sort_bool::true_());
  BOOST_CHECK(has_identifier(proc.deadlock_summands().back().condition(), "b1"));

  simplify_all = true;
  no_elimination = true;
  spec1 = invelm(spec, invariant, simplify_all, no_elimination);
  std::cout << lps::pp(spec1) << std::endl;
  proc = spec1.process();
  BOOST_CHECK(proc.action_summands().size() == 4);
  BOOST_CHECK(has_identifier(proc.action_summands()[2], "c"));
  BOOST_CHECK(proc.deadlock_summands().back().condition() == invariant);
}


/*
  @copyright Russell Standish 2000-2013
  @author Russell Standish
  This file is part of EcoLab

  Open source licensed under the MIT license. See LICENSE for details.
*/

#include "graph.h"
#include "tcl++.h"
#include "ecolab_epilogue.h"

#include <boost/graph/edge_list.hpp>
using namespace std;
using namespace boost;
using namespace ecolab;

NEWCMD(test_boostgraph,0)
{
  ConcreteGraph<DiGraph> g;
  urand uni;

  ErdosRenyi_generate(g,100,500,uni);

  typedef boost::edge_list<ConcreteGraph<DiGraph>::const_iterator> BoostGraph;
  BoostGraph bg(g.begin(),g.end());
  typedef BoostGraph::edge_iterator Ei;
  assert(num_edges(bg)==g.links());
  std::pair<Ei,Ei> edge_range=edges(bg);
  unsigned i=0;
  for (Ei e=edge_range.first; e!=edge_range.second; ++i, ++e)
    assert(g.contains(Edge(source(*e,bg),target(*e,bg))));
  assert(i==g.links());

  // many BGL algorithms will write into an output iterator. We can
  // also std::copy from a Boost Graph object
  ConcreteGraph<DiGraph> g1;
  copy(edge_range.first, edge_range.second, g1.back_inserter(bg));
  assert(g1==g);

}


// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE SparsePartionerTests
#include "../lib/popsparse/SparsePartitionerImpl.hpp"
#include "../lib/popsparse/SparseStorageInternal.hpp"
#include "poplar/Type.hpp"
#include "poplibs_support/Algorithm.hpp"
#include "poplibs_support/logging.hpp"
#include "poplibs_test/Util.hpp"
#include "poputil/exceptions.hpp"
#include <boost/multi_array.hpp>
#include <boost/program_options.hpp>
#include <boost/random.hpp>
#include <cmath>

using namespace poplibs_support;
using namespace poplibs_test::util;

// Used data types
// TODO: We could make the testing of actual bucket creation by passing
// this as a test option
const poplar::Type dataType = poplar::FLOAT;
const poplar::Type accumType = poplar::FLOAT;

// Build CSR matrix
static popsparse::CSRMatrix<double>
buildCSRMatrix(const std::vector<size_t> &dimensions, double sparsityFactor,
               const std::array<std::size_t, 2> &blockDimensions = {1, 1}) {
  boost::multi_array<double, 2> matrix(
      boost::extents[dimensions[0]][dimensions[1]]);
  logging::popsparse::debug("Dimension in CSR Matrix generation {}",
                            dimensions);

  assert(dimensions[0] % blockDimensions[0] == 0 &&
         dimensions[1] % blockDimensions[1] == 0);

  std::vector<double> nzValues;
  std::vector<std::size_t> columnIndices;
  std::vector<std::size_t> rowIndices;

  boost::random::mt19937 rng;
  auto randUniform = boost::random::uniform_real_distribution<float>(0, 1.0);
  auto randNormal = boost::random::normal_distribution<float>(0, 1.0);

  std::size_t numNzRowElements = 0;
  rowIndices.push_back(0);
  for (std::size_t row = 0; row != dimensions[0]; row += blockDimensions[0]) {
    for (std::size_t col = 0; col != dimensions[1]; col += blockDimensions[1]) {
      if (randUniform(rng) < sparsityFactor) {
        for (std::size_t i = 0; i < blockDimensions[0] * blockDimensions[1];
             ++i) {
          nzValues.push_back(randNormal(rng));
        }
        numNzRowElements += blockDimensions[0] * blockDimensions[1];
        columnIndices.push_back(col);
      }
    }
    rowIndices.push_back(numNzRowElements);
  }

  logging::popsparse::debug("NZ Values {} : {}", nzValues.size(), nzValues);
  logging::popsparse::debug("Columns Indices {} : {}", columnIndices.size(),
                            columnIndices);
  logging::popsparse::debug("Row Indices {} : {}", rowIndices.size(),
                            rowIndices);

  return popsparse::CSRMatrix<double>(nzValues, columnIndices, rowIndices,
                                      blockDimensions);
}

static bool validatePartition(const std::vector<std::size_t> &dimensions,
                              const std::vector<std::size_t> &grainSizes,
                              const std::vector<std::size_t> &xSplits,
                              const std::vector<std::size_t> &ySplits,
                              const std::vector<std::size_t> &zSplits,
                              double sparsityFactor,
                              std::size_t metaInfoBucketSize,
                              std::size_t nzElementsBucketSize,
                              std::size_t bucketsPerZ, bool useBlockMetaInfo,
                              bool includeGradA, bool includeGradW,
                              bool checkSparsityDataImpl) {

  const auto blockSizeX = grainSizes.at(0);
  const auto blockSizeY = grainSizes.at(1);
  const auto blockSize = blockSizeX * blockSizeY;
  const auto numRowBlocks = dimensions[0] / blockSizeX;

  auto csrMatrix =
      buildCSRMatrix(dimensions, sparsityFactor, {blockSizeX, blockSizeY});

  // Create partitioner object with plan information
  //
  // The following doesn't actually affect partition generation so just
  // set them to some value.
  const std::size_t metaInfoBucketSizeGradA = 0;
  // Set shared buckets so we don't have to deal with size of gradA impl
  // buckets.
  const bool sharedBuckets = true;
  // TODO: Test partitioner options
  const popsparse::PartitionerOptions options;
  popsparse::PartitionerImpl partitioner(
      dimensions, grainSizes, {grainSizes.at(0), grainSizes.at(1)}, xSplits,
      ySplits, zSplits, metaInfoBucketSize, metaInfoBucketSizeGradA,
      nzElementsBucketSize, 6, bucketsPerZ, useBlockMetaInfo, includeGradA,
      includeGradW, sharedBuckets, dataType, accumType, options);

  auto pnBucketsImpl = partitioner.createBuckets(csrMatrix);
  auto pnBuckets = pnBucketsImpl.pnBuckets;
  const auto &nzValues = pnBucketsImpl.nzValues;

  partitioner.overflowInfoForFwd(pnBuckets);

  if (checkSparsityDataImpl) {
    auto impl = partitioner.bucketImplAllPasses(pnBucketsImpl);
    auto csrMatrixRecovered =
        partitioner.bucketsToCSRMatrix(impl.first, impl.second);
    if (csrMatrix.nzValues != csrMatrixRecovered.nzValues ||
        csrMatrix.columnIndices != csrMatrixRecovered.columnIndices ||
        csrMatrix.rowIndices != csrMatrixRecovered.rowIndices) {
      return false;
    }
  }

  // tile partitions must be less than the number of splits
  if (pnBuckets.size() !=
      xSplits.size() * ySplits.size() * zSplits.size() * bucketsPerZ) {
    return false;
  }

  // convert bucket information back to a CSR format
  std::vector<std::vector<std::pair<std::size_t, std::size_t>>>
      perRowPositionNzIndexPairs(numRowBlocks);
  std::size_t totalNzBlocks = 0;
  for (const auto &pn : pnBuckets) {
    for (const auto &p : pn.subGroups) {
      const auto &rowInterval = p.tile.getRows();
      const auto &colInterval = p.tile.getColumns();

      for (const auto &r : p.tileInfo) {
        const auto row = r.rowNumber + rowInterval.begin();
        for (const auto &c : r.positionValues) {
          const auto col = c.first + colInterval.begin();
          perRowPositionNzIndexPairs.at(row / blockSizeX)
              .emplace_back(col, c.second);
        }
        totalNzBlocks += r.positionValues.size();
      }
    }
  }

  std::vector<std::size_t> colIndicesActual;
  std::vector<double> nzValuesActual;
  std::vector<std::size_t> rowIndicesActual;
  colIndicesActual.reserve(totalNzBlocks);
  nzValuesActual.reserve(totalNzBlocks * blockSize);
  rowIndicesActual.reserve(numRowBlocks + 1);

  std::size_t totalNzElems = 0;
  for (std::size_t rowBlock = 0; rowBlock < numRowBlocks; ++rowBlock) {
    rowIndicesActual.push_back(totalNzElems);

    auto &nzPositionAndIndex = perRowPositionNzIndexPairs.at(rowBlock);

    // Sort by column index. Column index should be unique
    // and so the default std::pair sort should be sufficient.
    std::sort(nzPositionAndIndex.begin(), nzPositionAndIndex.end());
    for (const auto &entry : nzPositionAndIndex) {
      colIndicesActual.push_back(entry.first);
      assert((entry.second + 1) * blockSize <= nzValues.size());
      nzValuesActual.insert(nzValuesActual.end(),
                            nzValues.begin() + entry.second * blockSize,
                            nzValues.begin() + (entry.second + 1) * blockSize);
      totalNzElems += blockSize;
    }
  }
  rowIndicesActual.push_back(totalNzElems);

  logging::popsparse::debug(" Actual nz values: {} ", nzValuesActual);
  logging::popsparse::debug(" expect nz values: {} ", csrMatrix.nzValues);
  logging::popsparse::debug(" Actual column values: {} ", colIndicesActual);
  logging::popsparse::debug(" expect column values: {} ",
                            csrMatrix.columnIndices);
  logging::popsparse::debug(" Actual row values: {} ", rowIndicesActual);
  logging::popsparse::debug(" expect row values: {} ", csrMatrix.rowIndices);

  if (nzValuesActual.size() != csrMatrix.nzValues.size()) {
    return false;
  }
  if (!std::equal(nzValuesActual.begin(), nzValuesActual.end(),
                  csrMatrix.nzValues.begin(), csrMatrix.nzValues.end())) {
    return false;
  }

  if (colIndicesActual.size() != csrMatrix.columnIndices.size()) {
    return false;
  }
  if (!std::equal(colIndicesActual.begin(), colIndicesActual.end(),
                  csrMatrix.columnIndices.begin(),
                  csrMatrix.columnIndices.end())) {
    return false;
  }

  if (rowIndicesActual.size() != csrMatrix.rowIndices.size()) {
    return false;
  }

  if (!std::equal(rowIndicesActual.begin(), rowIndicesActual.end(),
                  csrMatrix.rowIndices.begin(), csrMatrix.rowIndices.end())) {
    return false;
  }
  return true;
}

int main(int argc, char **argv) {
  namespace po = boost::program_options;
  double sparsityLevel = 0.1;

  ShapeOption<std::size_t> matShape;
  ShapeOption<std::size_t> splitShape;
  ShapeOption<std::size_t> blockShape;
  blockShape.val = {1, 1};
  std::size_t minMultipleGrainSizeZ = dataType == poplar::FLOAT ? 2 : 4;
  std::size_t grainSizeZ = minMultipleGrainSizeZ;
  std::size_t numBucketsZ = 1;
  bool includeGradW = true;
  bool includeGradA = true;
  double excess = 0.1;

  po::options_description desc("Options");
  // clang-format off
  desc.add_options()
    ("help", "Produce help message")
    ("matmul-shape",
     po::value<ShapeOption<std::size_t>>(&matShape)->required(),
     "Triplet representing matmul {Rows, Columns, Batches}")
    ("split-shape",
     po::value<ShapeOption<std::size_t>>(&splitShape)->required(),
     "Triplet representing number of splits {Rows, Columns, Batches")
    ("sparsity-level",
     po::value<double>(&sparsityLevel)->default_value(sparsityLevel),
     "Level of sparsity")
    ("block-shape",
     po::value<ShapeOption<std::size_t>>(&blockShape)->default_value(blockShape),
     "Pair representing block-size of sparse elements {BlockRows, BlockColumns}")
    ("batch-grain-size",
     po::value<std::size_t>(&grainSizeZ)->default_value(grainSizeZ),
     "Number of grains in batch dimension (must be multiple of 2 for float, "
     "and multiple of 4 for half")
    ("excess",
      po::value<double>(&excess)->default_value(excess),
      "Excess bucket size")
    ("num-buckets-z",
     po::value<std::size_t>(&numBucketsZ)->default_value(numBucketsZ),
     "Number of buckets per Z")
    ("disable-sparsity-data-impl-checks", "Don't run checks that generate on-device sparsity data implementation")
    ("include-gradw",
     po::value<bool>(&includeGradW)->default_value(includeGradW),
     "Include GradW")
    ("include-grada",
     po::value<bool>(&includeGradA)->default_value(includeGradA),
     "Include GradA")
  ;
  // clang-format on
  po::variables_map vm;
  try {
    po::store(po::parse_command_line(argc, argv, desc), vm);
    if (vm.count("help")) {
      std::cout << desc << "\n";
      return 1;
    }
    po::notify(vm);
  } catch (std::exception &e) {
    std::cerr << "error: " << e.what() << "\n";
    return 1;
  }

  if (matShape.val.size() != 3) {
    throw poputil::poplibs_error("shape of matrix must be 3-dimensional");
  }

  if (splitShape.val.size() != 3) {
    throw poputil::poplibs_error("shape of splits must be 3-dimensional");
  }

  if (matShape[0] % blockShape[0] != 0 || matShape[1] % blockShape[1] != 0) {
    throw poputil::poplibs_error(
        "sparse matrix dimensions (" + std::to_string(matShape[0]) + "," +
        std::to_string(matShape[1]) + ") are not divisible by block size (" +
        std::to_string(blockShape[0]) + "," + std::to_string(blockShape[1]));
  }

  if (grainSizeZ % minMultipleGrainSizeZ) {
    throw poputil::poplibs_error("Grain size must be a multiple of " +
                                 std::to_string(minMultipleGrainSizeZ) +
                                 " for data type");
  }

  if (matShape[2] % grainSizeZ) {
    throw poputil::poplibs_error("Batch must be a multiple of grain size");
  }

  if (splitShape[2] % grainSizeZ) {
    throw poputil::poplibs_error(
        "Batch split must be a multiple of grain size");
  }

  for (unsigned i = 0; i != matShape.val.size(); ++i) {
    if (splitShape[i] > matShape[i]) {
      throw poputil::poplibs_error("split for dim " + std::to_string(i) +
                                   " must be less than size");
    }
  }

  const auto blockSize = blockShape[0] * blockShape[1];
  const bool useBlockMetaInfoFormat = blockSize > 1;
  const unsigned numWorkers = 6;
  auto nzBlocksPerfectlyUniform = sparsityLevel *
                                  (matShape[0] / blockShape[0]) *
                                  (matShape[1] / blockShape[1]);
  auto nzBucketSize = nzBlocksPerfectlyUniform * blockSize * (1 + excess);
  auto metaInfoBucketSize =
      (popsparse::fixedMetaInfoCost(useBlockMetaInfoFormat, numWorkers,
                                    includeGradW) *
           3.0 +
       (matShape[0] / blockShape[0]) * 2.0 + nzBlocksPerfectlyUniform) *
      (1 + excess);

  const bool disableSparsityDataImplCheck =
      vm.count("disable-sparsity-data-impl-checks");

  auto createSplit = [](unsigned size, unsigned partitionSize,
                        unsigned grainSize) {
    auto grains = poplibs_support::ceildiv(size, grainSize);
    std::vector<std::size_t> split;
    const auto grainsPerPartition = ceildiv(grains, partitionSize);
    for (unsigned i = 0; i != partitionSize; ++i) {
      const auto tileBegin = i * grainsPerPartition * grainSize;
      split.push_back(tileBegin);
    }
    return split;
  };

  const std::vector<std::size_t> grainSizes = {blockShape[0], blockShape[1],
                                               numBucketsZ};
  std::vector<std::vector<std::size_t>> splits(3);
  for (unsigned i = 0; i != 3; ++i) {
    splits[i] = createSplit(matShape[i], splitShape[i], grainSizes[i]);
  }

  const bool checkSparsityDataImpl = !disableSparsityDataImplCheck;
  return !validatePartition(matShape.val, grainSizes, splits[0], splits[1],
                            splits[2], sparsityLevel, metaInfoBucketSize,
                            nzBucketSize, numBucketsZ, useBlockMetaInfoFormat,
                            includeGradA, includeGradW, checkSparsityDataImpl);
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_TRIGONOMETRIC_FUNCTIONS_GENERIC_ASIN_HPP_INCLUDED
#define NT2_TRIGONOMETRIC_FUNCTIONS_GENERIC_ASIN_HPP_INCLUDED

#include <nt2/trigonometric/functions/asin.hpp>
#include <nt2/trigonometric/functions/scalar/impl/invtrig.hpp>
#include <nt2/trigonometric/functions/simd/common/impl/invtrig.hpp>
#include <nt2/include/functions/simd/abs.hpp>
#include <nt2/include/functions/simd/is_greater.hpp>
#include <nt2/include/functions/simd/sign.hpp>
#include <nt2/include/functions/simd/if_allbits_else.hpp>
#include <nt2/include/functions/simd/tofloat.hpp>
#include <nt2/include/constants/pio_2.hpp>
#include <nt2/include/constants/one.hpp>
#include <boost/simd/sdk/simd/meta/is_native.hpp>
#include <nt2/sdk/meta/as_floating.hpp>
#include <nt2/sdk/meta/as_floating.hpp>

namespace nt2 { namespace ext
{
  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::asin_,tag::cpu_
                            , (A0)
                            , ((generic_<arithmetic_<A0> >))
                            )
  {

    typedef typename meta::as_floating<A0>::type result_type;

    NT2_FUNCTOR_CALL(1)
    {
      return nt2::if_nan_else(nt2::gt(nt2::abs(a0), nt2::One<A0>()),
                              nt2::sign(nt2::tofloat(a0))*nt2::Pio_2<result_type>());
    }
  };
  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::asin_, tag::cpu_
                            , (A0)
                            , (generic_< floating_<A0> >)
                            )
  {

    typedef A0 result_type;
    typedef typename boost::simd::meta::is_native<A0>::type     is_native_t;
    NT2_FUNCTOR_CALL(1)
    {
      return impl::invtrig_base<result_type,radian_tag,is_native_t>::asin(a0);
    }
  };
} }

#endif