#include <fstream>

#include <boost/format.hpp>

#include "DelaunayTriangulation.h"
#include "DT_Circle.h"

bool isContained(Triangle * t, Vertex * p);
bool inCircle(Triangle *a, Vertex * pr);
bool inCircle(Vertex * a, Vertex * b, Vertex * c, Vertex * d);
Circle circleForPoints(Vertex * a, Vertex * b, Vertex * c);
Vertex pointAlongLine2D(Vertex * a, Vertex * b, double tn = 0.5);
bool onEdge(Vertex * a, Vertex * b, Vertex * pr);
double distance2Point(Vertex * a, Vertex * b, Vertex * pr);

//
// Friend functions of Triangle & Vertex
//
bool isContained(Triangle * t, Vertex * p) {
    double x = p->getX();
    double y = p->getY();

    double x1 = t->data[0]->getX();
    double y1 = t->data[0]->getY();

    double x2 = t->data[1]->getX();
    double y2 = t->data[1]->getY();

    double x3 = t->data[2]->getX();
    double y3 = t->data[2]->getY();

    double denom = 1.0 / (x1*(y2 - y3) + y1*(x3 - x2) + x2*y3 - y2*x3);

    double t1 = (x*(y3 - y1) + y*(x1 - x3) - x1*y3 + y1*x3) *  denom;
    double t2 = (x*(y2 - y1) + y*(x1 - x2) - x1*y2 + y1*x2) * -denom;

    if (0.0 <= t1 && t1 <= 1.0 &&
        0.0 <= t2 && t2 <= 1.0 &&
        t1 + t2 <= 1.0)
        return true;
    else
        return false;
}

bool inCircle(Triangle *a, Vertex * pr)
{
    Vertex ** p = a->getVertices();
    return inCircle(p[0],p[1],p[2],pr);
}

bool inCircle(Vertex * a, Vertex * b, Vertex * c, Vertex * d)
{
    double xa = a->getX();
    double xb = b->getX();
    double xc = c->getX();
    double xd = d->getX();

    double ya = a->getY();
    double yb = b->getY();
    double yc = c->getY();
    double yd = d->getY();

    double sa = xa * xa + ya * ya;
    double sb = xb * xb + yb * yb;
    double sc = xc * xc + yc * yc;
    double sd = xd * xd + yd * yd;

    double det = 0.0;
    det  = xa * ( yb * (sc - sd) - sb * (yc - yd) + (yc * sd - yd * sc) );
    det -= ya * ( xb * (sc - sd) - sb * (xc - xd) + (xc * sd - xd * sc) );
    det += sa * ( xb * (yc - yd) - yb * (xc - xd) + (xc * yd - xd * yc) );
    det -=      ( xb * (yc * sd - yd * sc) - yb * (xc * sd - xd * sc) + sb * (xc * yd - xd * yc) );

    if (det > 0.0)
        return true;
    else
        return false;
}

Circle circleForPoints(Vertex * a, Vertex * b, Vertex * c)
{
    double x1 = a->getX();
    double x2 = b->getX();
    double x3 = c->getX();

    double y1 = a->getY();
    double y2 = b->getY();
    double y3 = c->getY();

    double X2 = (double)(x2-x1);
    double X3 = (double)(x3-x1);
    double Y2 = (double)(y2-y1);
    double Y3 = (double)(y3-y1);

    double alpha = X3 / X2;

    double bx2 = (x2+x1) * X2;
    double bx3 = (x3+x1) * X3;
    double by2 = (y2+y1) * Y2;
    double by3 = (y3+y1) * Y3;

    double h = 0.0;
    double k = 0.0;
    double r = 0.0;

    k = bx3 + by3 - alpha * (bx2 + by2);
    k /= 2 * (Y3 - alpha * Y2);

    h = bx2 + by2 - 2 * k * Y2;
    h /= 2 * X2;

    r = sqrt( (x1 - h)*(x1 - h) + (y1 - k)*(y1 - k) );

    return Circle(h,k,r);
}

Vertex pointAlongLine2D(Vertex * a, Vertex * b, double tn)
{
    double nx = a->getX() + tn * (b->getX() - a->getX());
    double ny = a->getY() + tn * (b->getY() - a->getY());
    return Vertex(nx,ny);
}

bool onEdge(Vertex * a, Vertex * b, Vertex * pr) {
    double minx = (a->getX() <= b->getX())? a->getX() : b->getX();
    double maxx = (a->getX() >= b->getX())? a->getX() : b->getX();

    double miny = (a->getY() <= b->getY())? a->getY() : b->getY();
    double maxy = (a->getY() >= b->getY())? a->getY() : b->getY();

    if (pr->getX() < minx)
        return false;
    else if (pr->getX() > maxx)
        return false;
    else if (pr->getY() < miny)
        return false;
    else if (pr->getY() > maxy)
        return false;
    else
        return distance2Point(a,b,pr) < Constants::DIST_THRESH;
}

double distance2Point(Vertex * a, Vertex * b, Vertex * pr) {
    double nx = b->getX() - a->getX();
    double ny = b->getY() - a->getY();

    double nmag = sqrt(nx * nx + ny * ny);

    double px = pr->getX() - a->getX();
    double py = pr->getY() - a->getY();

    //double pmag = sqrt(px * px + py * py);

    double dnp = nx * px + ny * py; // dot(n,p)

    double dnpx = nx * dnp / (nmag * nmag);
    double dnpy = ny * dnp / (nmag * nmag);

    double perx = dnpx - px;
    double pery = dnpy - py;

    return sqrt(perx * perx + pery * pery);
}

void DelaunayTriangulation::permute()
{
    std::srand(pts.len());
    for (int i = 0; i < pts.len(); i++) {
        rPerm[i] = i;
    }
    for (int i = pts.len() - 1; i >= 0; i--) {
        int j = std::rand() % (i + 1);
        int temp = rPerm[i];
        rPerm[i] = rPerm[j];
        rPerm[j] = temp;
    }
}

void DelaunayTriangulation::init()
{
    omg1.set( 3.0 * M, 0.0);
    omg2.set( 0.0, 3.0 * M);
    omg3.set(-3.0 * M, -3.0 * M);
    Triangle * itri = dag.get();
    itri->setVertices(&omg1, &omg2, &omg3);
}

DelaunayTriangulation::DelaunayTriangulation() : M(0)
{
}

DelaunayTriangulation::~DelaunayTriangulation()
{
}

void DelaunayTriangulation::addPt(double x, double y, double z = 0.0)
{
	Vertex * v = pts.get();
	if (nullptr != v) {
		v->set(x, y, z);

        int max = (x >= y) ? x : y;
        if (M <= max)
            M = max;
	}
	else {
		fprintf(stdout,"WARNING: Pool of points is empty!\n");
	}
}

Vertex * DelaunayTriangulation::getPoint(int i)
{
    int rPerm_[10] = {2, 9, 7, 8, 4, 1, 3, 0, 6, 5};
    return pts[rPerm_[i]];
    //return pts[rPerm[i]];
}

///
// ValidEdge(∆, pr,D(P))
// Let ∆adj be the triangle opposite to pr and adjacent to ∆
// if InCircle(∆adj , pr) then
//     (* We have to make an edge flip *)
//     flip(∆,∆adj, pr,D(P))
//     Let ∆′ and ∆′′ be the two new triangles, recursively legalize them
//     ValidEdge(∆′, pr,D(P))
//     ValidEdge(∆′′, pr,D(P))
// end if
///
void DelaunayTriangulation::validEdge(Triangle *a, Vertex *pr)
{
    TriangleList<Constants::adjListSize> copy;

    // Let ∆adj be the triangle opposite to pr and adjacent to ∆
    dag.findAdjacentTriangle(a, pr, false);
    copy.copy(dag.adjList);
    if (copy.len > 1) {

        Vertex ** points = copy[1]->getVertices();
        Circle c = circleForPoints(points[0], points[1], points[2]);
        bool answer1 = c.pointInside(pr->getX(), pr->getY());
        bool answer2 = inCircle(copy[1], pr);

        if (answer1 != answer2) {
            fprintf(stdout, "WARNING: inCircle produced wrong answer.\n");
            fprintf(stdout, "\t%s\n", c.to_json().dump().c_str());
            fprintf(stdout, "\t%s\n", a->to_json().dump().c_str());
            fprintf(stdout, "\t%s\n", pr->to_json().dump().c_str());
        }

        if (answer1) {
            Triangle *n[2];
            dag.flip(copy[0],copy[1],pr,n);
            validEdge(n[0], pr);
            validEdge(n[1], pr);
        }
    }
}

void DelaunayTriangulation::compute()
{
	if (pts.len() < 3) {
		fprintf(stdout, "WARNING: Not enough points added to Pool\n");
		return;
	}

	init();
	permute();

    logPoints();
    json j;

	for (int i = 0; i < pts.len(); i++) {
        Vertex * p = getPoint(i);

        logStep(i, p);

        dag.findTriangleContainingPoint(p);
        if (2 == dag.adjList.len) { // on edge
            dag.divideOnEdge(dag.adjList[0], dag.adjList[1], p);
        } else { // on interior
            dag.divideOnInterior(dag.adjList[0], p);
        }

        TriangleList<Constants::splitListSize> copy;
        copy.copy(dag.splitList);
        for (int i = 0; i < copy.len; i++) {
            assert(true == copy[i]->isValid());
            validEdge(copy[i], p);
        }
	}

    //
    // removing all triangles with the initial fake points added to DAG.
    //
    dag.removeTriangleContainingPoint(omg1);
    dag.removeTriangleContainingPoint(omg2);
    dag.removeTriangleContainingPoint(omg3);
    json sol = dag.to_json();
    std::ofstream log;
    log.open ("solution.json");
    log << sol.dump();
    log.close();
}

void DelaunayTriangulation::logStep(int loop, Vertex * p)
{
    json j;
    j["loop"] = loop;
    j["point"] = p->to_json();
    j["dag"] = dag.len();

    std::ofstream log;
    log.open (str(boost::format("loop_%05d.json") % dag.len()));
    log << j.dump();
    log.close();
}

void DelaunayTriangulation::logPoints()
{
    std::ofstream log;
    log.open ("points.json");
    json j;
    j.push_back(omg1.to_json());
    j.push_back(omg2.to_json());
    j.push_back(omg3.to_json());
    for (int i = 0; i < pts.len(); i++) {
        j.push_back( pts[i]->to_json() );
    }
    log << j.dump();
    log.close();
}


//  Routines for computing p-values of occurrence overlaps using the hypergeometric distribution
//

#ifndef INCLUDED_overlap_probs_HH
#define INCLUDED_overlap_probs_HH

#include "misc.hh"
#include <boost/math/distributions/hypergeometric.hpp>

inline
Real
product_cdf( Real const x )
{
	return x - x*log(x);
}

inline
Real
compute_overlap_pvalue(
	Size const overlap,
	Size const count1,
	Size const count2,
	Size const total
)
{
	using namespace boost::math;
	// whats the smallest possible overlap? max( 0, count1+count2-total )
	if ( overlap==0 || count1==total || count2==total || count1+count2 >= total+overlap ) return 1.0;
	hypergeometric_distribution<> hgd( count1, count2, total );
	return cdf( complement( hgd, overlap-1 ) ); // need overlap-1 to be a valid overlap value!
}

inline // silly?
Real
compute_overlap_pvalue_with_bias(
	bools const & occs1,
	bools const & occs2,
	Reals const & subject_bias_factor
)
{
	Real pval(1.);

	Size table[2][2];

	table[0][0] = table[0][1] = table[1][0] = table[1][1] = 0;

	Size const total( occs1.size() );
	runtime_assert( occs2.size() == total );
	runtime_assert( subject_bias_factor.size() == total );

	for ( Size i=0; i< total; ++i ) { ++table[ occs1[i] ][ occs2[i] ]; }

	Size const overlap( table[1][1] ), total1( table[1][0] + table[1][1] ), total2( table[0][1] + table[1][1] );

	if ( overlap > 0 && total1 < total && total2 < total ) {
		Real bias(1.0);
		for ( Size i=0; i< total; ++i ) {
			if ( occs1[i] && occs2[i] ) bias *= subject_bias_factor[i];
		}
		pval = min( 1.0, bias * compute_overlap_pvalue( overlap, total1, total2, total ) );
	}

	return pval;
}

#endif


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*  This file is part of the library KASKADE 7                               */
/*    see http://www.zib.de/Numerik/numsoft/kaskade7/                        */
/*                                                                           */
/*  Copyright (C) 2002-2009 Zuse Institute Berlin                            */
/*                                                                           */
/*  KASKADE 7 is distributed under the terms of the ZIB Academic License.    */
/*    see $KASKADE/academic.txt                                              */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifndef STRAINTENSORS_HH
#define STRAINTENSORS_HH

#include <utility>
#include <boost/math/constants/constants.hpp>
#include <dune/common/fmatrix.hh>

#include "fem/fixdune.hh"
#include "fem/diffops/elasto.hh"
#include "linalg/wrappedMatrix.hh"

namespace Kaskade
{
  /// \todo docme
  template <class Scalar, int dim, bool byValue = true>
  class CauchyGreenTensor
  {
  public:
    typedef Dune::FieldMatrix<Scalar,dim,dim> Argument;
    typedef Argument ReturnType;

    explicit CauchyGreenTensor(Argument const& F_) : F(F_) {}

    CauchyGreenTensor(CauchyGreenTensor const&) = default;
    CauchyGreenTensor& operator=(CauchyGreenTensor const&) = default;

    ReturnType d0() const { return transpose(F)*F; }

    ReturnType d1(Argument const& dF1) const
    {
      Argument const& G = transpose(dF1)*F;
      return G + transpose(G);
    }

    ReturnType d2(Argument const& dF1, Argument const& dF2) const
    {
      Argument const& G = transpose(dF1)*dF2;
      return G + transpose(G);
    }

    ReturnType d3(Argument const&, Argument const&, Argument const&) const { return zero; }

  private:
    typename std::conditional<byValue,Argument,Argument const&>::type F;
    Argument zero = Argument(0);
  };

  template <class Scalar,bool byValue>
  class CauchyGreenTensor<Scalar,3,byValue>
  {
    static constexpr int dim = 3;
  public:
    typedef Dune::FieldMatrix<Scalar,dim,dim> Argument;
    typedef Argument ReturnType;

    explicit CauchyGreenTensor(Argument const& F_) : F(F_) {}

    CauchyGreenTensor(CauchyGreenTensor const&) = default;
    CauchyGreenTensor& operator=(CauchyGreenTensor const&) = default;

    __attribute__((always_inline)) ReturnType d0() const
    {
      tmp = 0;
      for(size_t i=0; i<dim; ++i)
        for(size_t j=0; j<dim; ++j)
          for(size_t k=0; k<dim; ++k)
            tmp[i][j] += F[k][i]*F[k][j];

      return tmp;// transpose(F)*F;
    }

    __attribute__((always_inline)) ReturnType d1(Argument const& dF1) const
    {
      tmp = 0;
      for(size_t i=0; i<dim; ++i)
        for(size_t j=0; j<dim; ++j)
          for(size_t k=0; k<dim; ++k)
            tmp[i][j] += F[k][i]*dF1[k][j] + dF1[k][i]*F[k][j];

      return tmp;// G + transpose(G);
    }

    __attribute__((always_inline)) ReturnType d2(Argument const& dF1, Argument const& dF2) const
    {
      tmp = 0;
      for(size_t i=0; i<dim; ++i)
        for(size_t j=0; j<dim; ++j)
          for(size_t k=0; k<dim; ++k)
            tmp[i][j] += dF2[k][i]*dF1[k][j] + dF1[k][i]*dF2[k][j];
      return tmp;
    }

    __attribute__((always_inline)) ReturnType d3(Argument const&, Argument const&, Argument const&) const { return zero; }

  private:
    typename std::conditional<byValue,Argument,Argument const&>::type F;
    Argument zero = Argument(0);
    mutable Argument tmp;
  };

//   /// \todo docme
//   template <class Scalar, int dim>
//   class ElasticLinearizedGreenLagrangeTensor
//   {
//   public:
//     ElasticLinearizedGreenLagrangeTensor() = default;
//
//     ElasticLinearizedGreenLagrangeTensor(Dune::FieldMatrix<Scalar,dim,dim> const& du, Dune::FieldMatrix<Scalar,dim,dim> const& inelasticStrain_)
//       : elasticStrain(du), inelasticStrain(inelasticStrain_)
//     {}
//
//     auto d0() const { return elasticStrain.d0() - inelasticStrain; }
//
//     template <class Arg>
//     auto d1(Arg const& arg) const { return elasticStrain.d1(arg); }
//
//     template <class Arg>
//     auto d2(Arg const& arg1, Arg const& arg2) const { return elasticStrain.d2(arg1,arg2); }
//
//     template <class Arg>
//     auto d3(Arg const& arg1, Arg const& arg2, Arg const& arg3) const { return elasticStrain.d3(arg1,arg2,arg3); }
//
//   private:
//     LinearizedGreenLagrangeTensor<Scalar,dim> elasticStrain;
//     Dune::FieldMatrix<Scalar,dim,dim> inelasticStrain;
//   };
//

  /// \todo docme
  template <class Scalar, int dim, class Tensor = WrappedMatrix<Scalar,dim> >
  class Deviator
  {
  public:
    typedef typename Tensor::Argument Argument;
    typedef Dune::FieldMatrix<Scalar,dim,dim> ReturnType;

    explicit Deviator(Tensor const& s_) : s(s_), I(unitMatrix<Scalar,dim>()) {}

    ReturnType d0() const
    {
      return s.d0() - boost::math::constants::third<Scalar>() * trace(s.d0()) * I;
    }

    ReturnType d1(Argument const& dF) const
    {
      return s.d1(dF) - boost::math::constants::third<Scalar>() * trace(s.d1(dF)) * I;
    }

    ReturnType d2(Tensor const& dF1, Tensor const& dF2) const
    {
      return s.d2(dF1,dF2) - boost::math::constants::third<Scalar>() * trace(s.d2(dF1,dF2)) * I;
    }

    ReturnType d3(Tensor const& dF1, Tensor const& dF2, Tensor const& dF3) const
    {
      return s.d3(dF1,dF2,dF3) - boost::math::constants::third<Scalar>() * trace(s.d3s(dF1,dF2,dF3)) * I;
    }

  private:
    Tensor s;
    ReturnType zero = ReturnType(0), I;
  };
}

#endif


/*
 * File:   Activation.hpp
 * Author: heshan
 *
 * Created on June 7, 2018, 11:17 AM
 */

#ifndef ACTIVATION_HPP
#define ACTIVATION_HPP

#include <cmath>
#include <Eigen>

class Activation {
public:
    Activation();
    Activation(const Activation& orig);
    virtual ~Activation();

    static double sigmoid(double);
    static Eigen::MatrixXd sigmoid(Eigen::MatrixXd);
    static double sigmoidDeriv(double);
    static Eigen::MatrixXd sigmoidDeriv(Eigen::MatrixXd);
    static Eigen::MatrixXd maxPoolDelta(double, double, Eigen::MatrixXd, int ,int);

private:

};

#endif /* ACTIVATION_HPP */


// Albert Huang <albert@csail.mit.edu>
//
// Implementation of
//
//    Berthold K. P. Horn,
//    "Closed-form solution of absolute orientation using unit quaternions",
//    Journal of the Optical society of America A, Vol. 4, April 1987

#ifndef __absolute_orientation_horn__
#define __absolute_orientation_horn__

#include <assert.h>
#include <Eigen/Geometry>
#include <Eigen/Eigenvalues>

/**
 * absolute_orientation_horn:
 * @P1: a matrix of dimension [3 x num_points]
 * @P2: a matrix of dimension [3 x num_points]
 * @result: output parameter
 *
 * Given two point sets P1 and P2, with each point p1_i in set P1 matched with
 * a point p2_i in P2, compute the rigid body transformation (isometry) M that
 * minimizes
 *
 * \sum ||p2_i - M p1_i||
 *
 * returns: 0 on success, -1 on failure
 */
template <typename DerivedA, typename DerivedB>
int absolute_orientation_horn(const Eigen::MatrixBase<DerivedA>& P1,
        const Eigen::MatrixBase<DerivedB>& P2,
        Eigen::Isometry3d* result)
{
    int num_points = P1.cols();
    assert(P1.cols() == P2.cols());
    assert(P1.rows() == 3 && P2.rows() == 3);
    if(num_points < 3)
        return -1;

    // compute centroids of point sets
    Eigen::Vector3d P1_centroid = P1.rowwise().sum() / num_points;
    Eigen::Vector3d P2_centroid = P2.rowwise().sum() / num_points;

    Eigen::MatrixXd R1 = P1;
    R1.colwise() -= P1_centroid;
    Eigen::MatrixXd R2 = P2;
    R2.colwise() -= P2_centroid;

    // compute matrix M
    double Sxx = R1.row(0).dot(R2.row(0));
    double Sxy = R1.row(0).dot(R2.row(1));
    double Sxz = R1.row(0).dot(R2.row(2));
    double Syx = R1.row(1).dot(R2.row(0));
    double Syy = R1.row(1).dot(R2.row(1));
    double Syz = R1.row(1).dot(R2.row(2));
    double Szx = R1.row(2).dot(R2.row(0));
    double Szy = R1.row(2).dot(R2.row(1));
    double Szz = R1.row(2).dot(R2.row(2));

    double A00 = Sxx + Syy + Szz;
    double A01 = Syz - Szy;
    double A02 = Szx - Sxz;
    double A03 = Sxy - Syx;
    double A11 = Sxx - Syy - Szz;
    double A12 = Sxy + Syx;
    double A13 = Szx + Sxz;
    double A22 = -Sxx + Syy - Szz;
    double A23 = Syz + Szy;
    double A33 = -Sxx - Syy + Szz;

    // prepare matrix for eigen analysis
    Eigen::Matrix4d N;
    N << A00, A01, A02, A03,
        A01, A11, A12, A13,
        A02, A12, A22, A23,
        A03, A13, A23, A33;

    Eigen::SelfAdjointEigenSolver<Eigen::Matrix4d> eigensolver(N);

    // rotation quaternion is the eigenvector with greatest eigenvalue
    Eigen::Vector4d eigvals = eigensolver.eigenvalues();
    int max_eigen_ind = 0;
    double max_eigen_val = eigvals(0);
    for(int i=1; i<4; i++) {
        if(eigvals(i) > max_eigen_val) {
            max_eigen_val = eigvals(i);
            max_eigen_ind = i;
        }
    }
    Eigen::Vector4d quat = eigensolver.eigenvectors().col(max_eigen_ind);
    Eigen::Quaterniond rotation(quat[0], quat[1], quat[2], quat[3]);

    // now compute the resulting isometry
    result->setIdentity();
    result->translate(P2_centroid - rotation * P1_centroid);
    result->rotate(rotation);

    return 0;
}
#endif


// Copyright  (C)  2008  Ruben Smits <ruben dot smits at mech dot kuleuven dot be>

// Version: 1.0
// Author: Ruben Smits <ruben dot smits at mech dot kuleuven dot be>
// Maintainer: Ruben Smits <ruben dot smits at mech dot kuleuven dot be>
// URL: http://www.orocos.org/kdl

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library 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 GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

//implementation of svd according to (Maciejewski and Klein,1989)
//and (Braun, Ulrey, Maciejewski and Siegel,2002)

/**
 * \file svd_eigen_Macie.hpp
 * provides Maciejewski's implementation for SVD.
 */

#ifndef SVD_EIGEN_MACIE
#define SVD_EIGEN_MACIE

#include <Eigen/Core>

namespace KDL
{

	/**
	 * svd_eigen_Macie provides Maciejewski implementation for SVD.
	 *
	 * computes the singular value decomposition of a matrix A, such that
	 * A=U*Sm*V
	 *
	 * (Maciejewski and Klein,1989) and (Braun, Ulrey, Maciejewski and Siegel,2002)
	 *
	 * \param A [INPUT] is an \f$m \times n\f$-matrix, where \f$ m \geq n \f$.
	 * \param S [OUTPUT] is an \f$n\f$-vector, representing the diagonal elements of the diagonal matrix Sm.
	 * \param U [INPUT/OUTPUT] is an \f$m \times m\f$ orthonormal matrix.
	 * \param V [INPUT/OUTPUT] is an \f$n \times n\f$ orthonormal matrix.
	 * \param B [TEMPORARY] is an \f$m \times n\f$ matrix used for temporary storage.
	 * \param tempi [TEMPORARY] is an \f$m\f$ vector used for temporary storage.
	 * \param threshold [INPUT] Threshold to determine orthogonality.
	 * \param toggle [INPUT] toggle this boolean variable on each call of this routine.
	 * \return number of sweeps.
	 */
    int svd_eigen_Macie(const Eigen::MatrixXd& A,Eigen::MatrixXd& U,Eigen::VectorXd& S, Eigen::MatrixXd& V,
                        Eigen::MatrixXd& B, Eigen::VectorXd& tempi,
                        double threshold,bool toggle);

}
#endif


/*
// Copyright 2018 University of Liege
//
// 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.
//
// Authors:
// - Adrien Crovato
*/

//// Body to body/field potential influence coefficient computation
// Compute doublet and source potential influence coefficient between 2 surface panels
// Katz and Plotkin (2001), Low-Speed Aerodynamics. Program 12
//
// Inputs:
// - wakeFlag: defines if wake or body panel
// - idTgt: index of target panel
// - idSrc: index of influencing panel
// - x, y, z: coordinates of target panel center in panel axis
// - x1, y1, x2, y2, x3, y3, x4, y4: coordinates of influencing panel vertices in panel axis
//
// Output:
// - coeff: array of AIC ([0] = mu, [1] = tau)

#include <iostream>
#include <Eigen/Dense>
#include <array>
#include "infcB.h"

#define PI 3.14159

using namespace std;
using namespace Eigen;

array<double,2> infcB(bool wakeFlag,int idTgt, int idSrc, double x, double y, double z,
                      double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {

    // Temporary
    RowVector4d xV, yV; // Local vertices
    RowVector4d d, r, e, h, F, G; // coefficients
    double mu = 0, tau = 0; // AIC
    array<double,2> coeff;

    xV(0) = x1; xV(1) = x2; xV(2) = x3; xV(3) = x4;
    yV(0) = y1; yV(1) = y2; yV(2) = y3; yV(3) = y4;

    //// Wake panels
    if (wakeFlag) {

        // Coefficients
        for (int i = 1; i <= 4; ++i) {
            int j = i%4 + 1;

            r(i-1) = sqrt((x - xV(i-1))*(x - xV(i-1)) + (y - yV(i-1))*(y - yV(i-1)) + z * z);
            e(i-1) = (x - xV(i-1))*(x - xV(i-1)) + z*z;
            h(i-1) = (x - xV(i-1)) * (y - yV(i-1));
        }
        for (int i = 1; i <= 4; ++i) {
            int j = i%4 + 1;

            F(i-1) = (yV(j-1) - yV(i-1))*e(i-1) - (xV(j-1) - xV(i-1))*h(i-1);
            G(i-1) = (yV(j-1) - yV(i-1))*e(j-1) - (xV(j-1) - xV(i-1))*h(j-1);
        }

        // Doublet
        for (int i = 1; i <= 4; ++i) {
            int j = i % 4 + 1;

            mu = mu + atan2(z * (xV(j-1) - xV(i-1)) * (F(i-1) * r(j-1) - G(i-1) * r(i-1)),
                            z * z * (xV(j-1) - xV(i-1)) * (xV(j-1) - xV(i-1)) * r(i-1) * r(j-1) + F(i-1) * G(i-1));
        }
        mu = -1 / (4 * PI) * mu;
        tau = 0;
    }

    //// Body panels
    else {
        // Coefficients
        for (int i = 1; i <= 4; ++i) {
            int j = i%4 + 1;

            d(i-1) = sqrt((xV(j-1) - xV(i-1))*(xV(j-1) - xV(i-1)) + (yV(j-1) - yV(i-1))*(yV(j-1) - yV(i-1)));
            r(i-1) = sqrt((x - xV(i-1))*(x - xV(i-1)) + (y - yV(i-1))*(y - yV(i-1)) + z * z);
        }

        if (idTgt == idSrc) {
            // Source
            for (int i = 1; i <= 4; ++i) {
                int j = i % 4 + 1;

                tau = tau + ((x - xV(i-1)) * (yV(j-1) - yV(i-1)) - (y - yV(i-1)) * (xV(j-1) - xV(i-1))) / d(i-1) *
                            log((r(i-1) + r(j-1) + d(i-1)) / (r(i-1) + r(j-1) - d(i-1)));
            }
            mu = 0.5;
            tau = -1 / (4 * PI) * tau;
        }
        else {
            // Coefficients
            for (int i = 1; i <= 4; ++i) {
                e(i-1) = (x - xV(i-1))*(x - xV(i-1)) + z*z;
                h(i-1) = (x - xV(i-1)) * (y - yV(i-1));
            }
            for (int i = 1; i <= 4; ++i) {
                int j = i%4 + 1;

                F(i-1) = (yV(j-1) - yV(i-1))*e(i-1) - (xV(j-1) - xV(i-1))*h(i-1);
                G(i-1) = (yV(j-1) - yV(i-1))*e(j-1) - (xV(j-1) - xV(i-1))*h(j-1);
            }

            // Doublet
            for (int i = 1; i <= 4; ++i) {
                int j = i % 4 + 1;

                mu = mu + atan2(z * (xV(j-1) - xV(i-1)) * (F(i-1) * r(j-1) - G(i-1) * r(i-1)),
                                      z * z * (xV(j-1) - xV(i-1)) * (xV(j-1) - xV(i-1)) * r(i-1) * r(j-1) + F(i-1) * G(i-1));
            }
            mu = -1 / (4 * PI) * mu;
            // Source
            for (int i = 1; i <= 4; ++i) {
                int j = i % 4 + 1;

                tau = tau + ((x - xV(i-1)) * (yV(j-1) - yV(i-1)) - (y - yV(i-1)) * (xV(j-1) - xV(i-1))) / d(i-1) *
                                log((r(i-1) + r(j-1) + d(i-1)) / (r(i-1) + r(j-1) - d(i-1)));
            }
            tau = -1 / (4 * PI) * tau - z * mu;
        }
    }
    coeff[0] = mu;
    coeff[1] = tau;

    return  coeff;
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_CONSTANT_LOGSQRT2PI_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_LOGSQRT2PI_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!
    @ingroup group-constant

    Generate value \f$\log\sqrt{2\pi}\f$

    @par Header <boost/simd/constant/logsqrt2pi.hpp>

    @par Semantic:

    @code
    T r = Logsqrt2pi<T>();
    @endcode

    is similar to:

    @code
    T r = log(sqrt(2*Pi<T>());
    @endcode

    @return The Logsqrt2pi constant for the proper type
  **/
  template<typename T> T Logsqrt2pi();

  namespace functional
  {
    /*!
      @ingroup group-callable-constant
      Generate the  constant logsqrt2pi.

      @return The Logsqrt2pi constant for the proper type
    **/
    Value Logsqrt2pi<Value>();
  }
} }
#endif

#include <boost/simd/constant/scalar/logsqrt2pi.hpp>
#include <boost/simd/constant/simd/logsqrt2pi.hpp>

#endif


#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
using namespace std;
int main()
{
    MatrixXd m = MatrixXd::Random(3,3);
    m = (m + MatrixXd::Constant(3,3,1.2)) * 50;
    cout << "m =" << endl << m << endl;
    VectorXd v(3);
    v << 1, 2, 3;
    cout << "m * v =" << endl << m * v << endl;

    MatrixXf mat = MatrixXf::Random(2, 3);
    std::cout << mat << std::endl << std::endl;
    mat = (MatrixXf(2,2) << 0, 1, 1, 0).finished() * mat;
    std::cout << mat << std::endl;
}


#pragma once

/**
 *  Classical sign domain.
 *
 *  This is actually the extended sign domain defined in "Tutorial on
 *  Static Inference of Numeric Invariants by Abstract Interpretation"
 *  by A. Mine 2017.
 */

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

#include <boost/optional.hpp>

#include <type_traits>

namespace crab {
namespace domains {

enum class sign_interval {
  BOT, // empty interval
  LTZ, // [-oo,0)
  GTZ, // (0, +oo]
  EQZ, // [0,0]
  NEZ, // [-oo,0) U (0, +oo]
  GEZ, // [0, +oo]
  LEZ, // [-oo,0]
  TOP, // [-oo,+oo]
       /*
                TOP
               / | \
              /  |  \
            LEZ NEZ  GEZ
             |\ /  \/|
             |/ EQZ \|
            LTZ  |  GTZ
              \  |  /
               \ | /
                BOT
        */
};

template <typename Number> class sign {
  static_assert(std::is_same<Number, ikos::z_number>::value,
                "Class sign only defined over ikos::z_number");

  using sign_t = sign<Number>;
  using interval_t = ikos::interval<Number>;
  using bound_t = ikos::bound<Number>;
  sign_interval m_sign;

  constexpr Number get_zero() const { return Number(0); }
  constexpr Number get_plus_one() const { return Number(1); }
  constexpr Number get_minus_one() const { return Number(-1); }

  sign_t shiftOp(const sign_t &o) const {
    // The shift operation is shl, lshr, or ashr
    // zero is special
    if (is_bottom() || o.is_bottom()) {
      return bottom();
    } else {
      if (equal_zero() || o.equal_zero()) {
        return *this;
      } else {
        return top();
      }
    }
  }

  sign_t defaultOp(const sign_t &o) const {
    if (is_bottom() || o.is_bottom()) {
      return bottom();
    } else {
      return top();
    }
  }

  explicit sign(sign_interval s) : m_sign(s) {}

public:
  explicit sign(bool is_bottom)
      : m_sign(is_bottom ? sign_interval::BOT : sign_interval::TOP) {}

  explicit sign(Number c) : m_sign(sign_interval::TOP) {
    Number zero(get_zero());
    if (c == zero) {
      m_sign = sign_interval::EQZ;
    } else if (c < zero) {
      m_sign = sign_interval::LTZ;
    } else /* (c > zero) */ {
      m_sign = sign_interval::GTZ;
    }
  }

  /* MODIFY HERE if not integers */
  sign_t from_interval(const interval_t &i) const {
    Number zero(get_zero());
    Number plus_one(get_plus_one());
    Number minus_one(get_minus_one());
    if (i.is_bottom()) {
      return sign_t::bottom();
    } else if (i.is_top()) {
      return sign_t::top();
    } else if (i <= interval_t(zero, zero)) {
      return sign_t(sign_interval::EQZ);
    } else if (i <= interval_t(bound_t::minus_infinity(), minus_one)) {
      return sign_t(sign_interval::LTZ);
    } else if (i <= interval_t(bound_t::minus_infinity(), zero)) {
      return sign_t(sign_interval::LEZ);
    } else if (i <= interval_t(plus_one, bound_t::plus_infinity())) {
      return sign_t(sign_interval::GTZ);
    } else if (i <= interval_t(zero, bound_t::plus_infinity())) {
      return sign_t(sign_interval::GEZ);
    } else {
      // unreachable
      return sign_t::top();
    }
  }

  /* MODIFY HERE if not integers */
  interval_t to_interval() const {
    Number zero(get_zero());
    Number plus_one(get_plus_one());
    Number minus_one(get_minus_one());
    if (is_bottom()) {
      return interval_t::bottom();
    } else if (is_top()) {
      return interval_t::top();
    } else if (equal_zero()) {
      return interval_t(zero);
    } else if (less_than_zero()) {
      return interval_t(bound_t::minus_infinity(), minus_one);
    } else if (greater_than_zero()) {
      return interval_t(plus_one, bound_t::plus_infinity());
    } else if (less_or_equal_than_zero()) {
      return interval_t(bound_t::minus_infinity(), zero);
    } else if (greater_or_equal_than_zero()) {
      return interval_t(zero, bound_t::plus_infinity());
    } else /* not_equal_zero*/ {
      // we cannot express not_equal_zero in an interval
      return interval_t::top();
    }
  }

  static sign_t bottom() { return sign(true); }

  static sign_t top() { return sign(false); }

  static sign_t mk_equal_zero() { return sign_t(sign_interval::EQZ); }

  static sign_t mk_less_than_zero() { return sign_t(sign_interval::LTZ); }

  static sign_t mk_greater_than_zero() { return sign_t(sign_interval::GTZ); }

  static sign_t mk_less_or_equal_than_zero() {
    return sign_t(sign_interval::LEZ);
  }

  static sign_t mk_greater_or_equal_than_zero() {
    return sign_t(sign_interval::GEZ);
  }

  static sign_t mk_not_equal_zero() { return sign_t(sign_interval::NEZ); }

  bool is_bottom() const { return m_sign == sign_interval::BOT; }

  bool is_top() const { return m_sign == sign_interval::TOP; }

  bool equal_zero() const { return m_sign == sign_interval::EQZ; }

  bool less_than_zero() const { return m_sign == sign_interval::LTZ; }

  bool greater_than_zero() const { return m_sign == sign_interval::GTZ; }

  bool less_or_equal_than_zero() const { return m_sign == sign_interval::LEZ; }

  bool greater_or_equal_than_zero() const {
    return m_sign == sign_interval::GEZ;
  }

  bool not_equal_zero() const { return m_sign == sign_interval::NEZ; }

  bool operator<=(const sign_t &o) const {
    if (is_bottom() || o.is_top()) {
      return true;
    } else if (o.is_bottom() || is_top()) {
      return false;
    } else {
      // operands are not either top or bottom
      if (m_sign == sign_interval::LTZ) {
        return o.m_sign == m_sign || o.m_sign == sign_interval::LEZ ||
               o.m_sign == sign_interval::NEZ;
      } else if (m_sign == sign_interval::GTZ) {
        return o.m_sign == m_sign || o.m_sign == sign_interval::GEZ ||
               o.m_sign == sign_interval::NEZ;
      } else if (m_sign == sign_interval::EQZ) {
        return o.m_sign == m_sign || o.m_sign == sign_interval::LEZ ||
               o.m_sign == sign_interval::GEZ;
      } else {
        /* m_sign == sign_interval::LEZ || m_sign == sign_interval::GEZ m_sign
         * == sign_interval::NEZ */
        return m_sign == o.m_sign;
      }
    }
  }

  bool operator==(const sign_t &o) const { return (m_sign == o.m_sign); }

  sign_t operator|(const sign_t &o) const {
    if (is_bottom() || o.is_top()) {
      return o;
    } else if (is_top() || o.is_bottom()) {
      return *this;
    } else {
      // operands are not either top or bottom
      if (m_sign == sign_interval::LTZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
          return *this;
        case sign_interval::GTZ:
        case sign_interval::NEZ:
          return sign_t(sign_interval::NEZ);
        case sign_interval::EQZ:
        case sign_interval::LEZ:
          return sign_t(sign_interval::LEZ);
        case sign_interval::GEZ:
          return sign_t(sign_interval::TOP);
        default:
          CRAB_ERROR("sign::operator| unreachable 1");
        }
      } else if (m_sign == sign_interval::GTZ) {
        switch (o.m_sign) {
        case sign_interval::GTZ:
          return *this;
        case sign_interval::LTZ:
        case sign_interval::NEZ:
          return sign_t(sign_interval::NEZ);
        case sign_interval::EQZ:
        case sign_interval::GEZ:
          return sign_t(sign_interval::GEZ);
        case sign_interval::LEZ:
          return sign_t(sign_interval::TOP);
        default:
          CRAB_ERROR("sign::operator| unreachable 2");
        }
      } else if (m_sign == sign_interval::EQZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
        case sign_interval::LEZ:
          return sign_t(sign_interval::LEZ);
        case sign_interval::GTZ:
        case sign_interval::GEZ:
          return sign_t(sign_interval::GEZ);
        case sign_interval::EQZ:
          return *this;
        case sign_interval::NEZ:
          return top();
        default:
          CRAB_ERROR("sign::operator| unreachable 3");
        }
      } else if (m_sign == sign_interval::LEZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
        case sign_interval::EQZ:
        case sign_interval::LEZ:
          return *this;
        case sign_interval::GTZ:
        case sign_interval::NEZ:
        case sign_interval::GEZ:
          return top();
        default:
          CRAB_ERROR("sign::operator| unreachable 4");
        }
      } else if (m_sign == sign_interval::NEZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
        case sign_interval::GTZ:
        case sign_interval::NEZ:
          return *this;
        case sign_interval::EQZ:
        case sign_interval::LEZ:
        case sign_interval::GEZ:
          return top();
        default:
          CRAB_ERROR("sign::operator| unreachable 5");
        }
      } else if (m_sign == sign_interval::GEZ) {
        switch (o.m_sign) {
        case sign_interval::EQZ:
        case sign_interval::GTZ:
        case sign_interval::GEZ:
          return *this;
        case sign_interval::LTZ:
        case sign_interval::LEZ:
        case sign_interval::NEZ:
          return top();
        default:
          CRAB_ERROR("sign::operator| unreachable 6");
        }
      }
    }
    CRAB_ERROR("sign::operator| unreachable 7");
  }

  sign_t operator&(const sign_t &o) const {
    if (is_bottom() || o.is_top())
      return *this;
    else if (is_top() || o.is_bottom()) {
      return o;
    } else {
      // operands are not either top or bottom
      if (m_sign == sign_interval::LTZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
          return *this;
        case sign_interval::GTZ:
        case sign_interval::EQZ:
        case sign_interval::GEZ:
          return bottom();
        case sign_interval::LEZ:
        case sign_interval::NEZ:
          return sign_t(sign_interval::LTZ);
        default:
          CRAB_ERROR("sign::operator& unreachable 1");
        }
      } else if (m_sign == sign_interval::GTZ) {
        switch (o.m_sign) {
        case sign_interval::GTZ:
          return *this;
        case sign_interval::LTZ:
        case sign_interval::EQZ:
        case sign_interval::LEZ:
          return bottom();
        case sign_interval::GEZ:
        case sign_interval::NEZ:
          return sign_t(sign_interval::GTZ);
        default:
          CRAB_ERROR("sign::operator& unreachable 2");
        }
      } else if (m_sign == sign_interval::EQZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
        case sign_interval::GTZ:
        case sign_interval::NEZ:
          return bottom();
        case sign_interval::GEZ:
        case sign_interval::LEZ:
        case sign_interval::EQZ:
          return *this;
        default:
          CRAB_ERROR("sign::operator& unreachable 3");
        }
      } else if (m_sign == sign_interval::LEZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
        case sign_interval::EQZ:
        case sign_interval::LEZ:
          return o;
        case sign_interval::NEZ:
          return sign_t(sign_interval::LTZ);
        case sign_interval::GTZ:
          return bottom();
        case sign_interval::GEZ:
          return sign_t(sign_interval::EQZ);
        default:
          CRAB_ERROR("sign::operator& unreachable 4");
        }
      } else if (m_sign == sign_interval::GEZ) {
        switch (o.m_sign) {
        case sign_interval::EQZ:
        case sign_interval::GTZ:
        case sign_interval::GEZ:
          return o;
        case sign_interval::LTZ:
          return bottom();
        case sign_interval::LEZ:
          return sign_t(sign_interval::EQZ);
        case sign_interval::NEZ:
          return sign_t(sign_interval::GTZ);
        default:
          CRAB_ERROR("sign::operator& unreachable 5");
        }
      } else if (m_sign == sign_interval::NEZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
        case sign_interval::GTZ:
        case sign_interval::NEZ:
          return o;
        case sign_interval::LEZ:
          return sign_t(sign_interval::LTZ);
        case sign_interval::GEZ:
          return sign_t(sign_interval::GTZ);
        case sign_interval::EQZ:
          return bottom();
        default:
          CRAB_ERROR("sign::operator& unreachable 6");
        }
      }
    }
    CRAB_ERROR("sign::operator& unreachable 7");
  }

  // addition
  sign_t operator+(const sign_t &o) const {
    if (is_bottom() || o.is_bottom()) {
      return bottom();
    } else if (is_top() || o.is_top()) {
      return top();
    } else {
      // operands are not either top or bottom
      if (m_sign == sign_interval::LTZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
        case sign_interval::EQZ:
        case sign_interval::LEZ:
          return *this;
        case sign_interval::GTZ:
        case sign_interval::NEZ:
        case sign_interval::GEZ:
          return top();
        default:
          CRAB_ERROR("sign::operator+ unreachable 1");
        }
      } else if (m_sign == sign_interval::GTZ) {
        switch (o.m_sign) {
        case sign_interval::GTZ:
        case sign_interval::EQZ:
        case sign_interval::GEZ:
          return *this;
        case sign_interval::LTZ:
        case sign_interval::LEZ:
        case sign_interval::NEZ:
          return top();
        default:
          CRAB_ERROR("sign::operator+ unreachable 2");
        }
      } else if (m_sign == sign_interval::LEZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
        case sign_interval::EQZ:
        case sign_interval::LEZ:
          return *this;
        case sign_interval::GTZ:
        case sign_interval::NEZ:
        case sign_interval::GEZ:
          return top();
        default:
          CRAB_ERROR("sign::operator+ unreachable 2");
        }
      } else if (m_sign == sign_interval::GEZ) {
        switch (o.m_sign) {
        case sign_interval::GTZ:
        case sign_interval::GEZ:
        case sign_interval::EQZ:
          return *this;
        case sign_interval::LTZ:
        case sign_interval::LEZ:
        case sign_interval::NEZ:
          return top();
        default:
          CRAB_ERROR("sign::operator+ unreachable 2");
        }
      } else if (m_sign == sign_interval::EQZ) {
        return o;
      } else /* (m_sign == sign_interval::NEZ)*/ {
        return top();
      }
    }
  }

  // subtraction
  sign_t operator-(const sign_t &o) const {
    if (is_bottom() || o.is_bottom()) {
      return bottom();
    } else if (is_top() || o.is_top()) {
      return top();
    } else {
      switch (o.m_sign) {
      case sign_interval::GTZ:
        return *this + sign_t(sign_interval::LTZ);
      case sign_interval::GEZ:
        return *this + sign_t(sign_interval::LEZ);
      case sign_interval::EQZ:
        return *this;
      case sign_interval::LTZ:
        return *this + sign_t(sign_interval::GTZ);
      case sign_interval::LEZ:
        return *this + sign_t(sign_interval::GEZ);
      default: /*sign_interval::NEZ*/
        return top();
      }
    }
  }

  // multiplication
  sign_t operator*(const sign_t &o) const {
    if (is_bottom() || o.is_bottom()) {
      return bottom();
    } else if (equal_zero() || o.equal_zero()) {
      return sign_t(sign_interval::EQZ);
    } else if (is_top() || o.is_top()) {
      return top();
    } else if (not_equal_zero() || o.not_equal_zero()) {
      return top();
    } else {
      // operands are not either top, bottom, zero, or non-zero
      if (m_sign == sign_interval::LTZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
          return sign_t(sign_interval::GTZ);
        case sign_interval::LEZ:
          return sign_t(sign_interval::GEZ);
        case sign_interval::GTZ:
          return sign_t(sign_interval::LTZ);
        case sign_interval::GEZ:
          return sign_t(sign_interval::LEZ);
        default:
          CRAB_ERROR("sign::operator* unreachable 1");
        }
      } else if (m_sign == sign_interval::GTZ) {
        return o;
      } else if (m_sign == sign_interval::LEZ) {
        switch (o.m_sign) {
        case sign_interval::LTZ:
        case sign_interval::LEZ:
          return sign_t(sign_interval::GEZ);
        case sign_interval::GTZ:
        case sign_interval::GEZ:
          return sign_t(sign_interval::LEZ);
        default:
          CRAB_ERROR("sign::operator* unreachable 2");
        }
      } else if (m_sign == sign_interval::GEZ) {
        switch (o.m_sign) {
        case sign_interval::GTZ:
        case sign_interval::GEZ:
          return sign_t(sign_interval::GEZ);
        case sign_interval::LTZ:
        case sign_interval::LEZ:
          return sign_t(sign_interval::LEZ);
        default:
          CRAB_ERROR("sign::operator* unreachable 3");
        }
      }
    }
    CRAB_ERROR("sign::operator* unreachable 4");
  }

  // signed division
  sign_t operator/(const sign_t &o) const {
    if (is_bottom() || o.is_bottom()) {
      return bottom();
    } else if (o.equal_zero()) {
      return bottom();
    } else if (equal_zero()) {
      return *this;
    } else if (is_top() || o.is_top()) {
      return top();
    } else if (not_equal_zero() || o.not_equal_zero()) {
      return top();
    } else {
      // Once we exclude top, bottom, zero, and non-zero
      // signed division is like multiplication
      return (*this) * o;
    }
  }

  // division and remainder operations

  sign_t UDiv(const sign_t &o) const { return defaultOp(o); }

  sign_t SRem(const sign_t &o) const { return defaultOp(o); }

  sign_t URem(const sign_t &o) const { return defaultOp(o); }

  // bitwise operations
  sign_t And(const sign_t &o) const {
    // zero is special
    if (is_bottom() || o.is_bottom()) {
      return bottom();
    } else {
      if (equal_zero() || o.equal_zero()) {
        return sign_t(sign_interval::EQZ);
      } else {
        return top();
      }
    }
  }

  sign_t Or(const sign_t &o) const {
    // zero is special
    if (is_bottom() || o.is_bottom()) {
      return bottom();
    } else {
      if (equal_zero()) {
        return o;
      } else if (o.equal_zero()) {
        return *this;
      } else {
        return top();
      }
    }
  }

  sign_t Xor(const sign_t &o) const { return Or(o); }

  sign_t Shl(const sign_t &o) const { return shiftOp(o); }

  sign_t LShr(const sign_t &o) const { return shiftOp(o); }

  sign_t AShr(const sign_t &o) const { return shiftOp(o); }

  void write(crab::crab_os &o) const {
    switch (m_sign) {
    case sign_interval::BOT:
      o << "_|_";
      return;
    case sign_interval::LTZ:
      o << "[-oo,-1]";
      return;
    case sign_interval::GTZ:
      o << "[1,+oo]";
      return;
    case sign_interval::EQZ:
      o << "[0,0]";
      return;
    case sign_interval::NEZ:
      o << "[-oo,-1] U [1,+oo]";
      return;
    case sign_interval::GEZ:
      o << "[0,+oo]";
      return;
    case sign_interval::LEZ:
      o << "[-oo,0]";
      return;
    default: /*sign_interval::TOP*/
      o << "[-oo,+oo]";
    }
  }

  friend inline crab_os &operator<<(crab_os &o, const sign_t &c) {
    c.write(o);
    return o;
  }
};

template <typename Number, typename VariableName>
class sign_domain final : public crab::domains::abstract_domain_api<
                              sign_domain<Number, VariableName>> {
public:
  using sign_domain_t = sign_domain<Number, VariableName>;
  using abstract_domain_t = crab::domains::abstract_domain_api<sign_domain_t>;
  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 sign_t = sign<Number>;
  using number_t = Number;
  using varname_t = VariableName;

private:
  using interval_domain_t = ikos::interval_domain<number_t, varname_t>;
  using separate_domain_t = ikos::separate_domain<variable_t, sign_t>;

private:
  separate_domain_t m_env;

  sign_domain(separate_domain_t &&env) : m_env(std::move(env)) {}

  void solve_constraints(const linear_constraint_system_t &csts) {

    // Solve < and > constraints
    auto solve_strict_inequality = [this](const variable_t &v, sign_t rhs, bool is_less_than) {
          sign_t lhs = m_env[v];
	  CRAB_LOG("sign-domain",
		   crab::outs() << v << "=" << lhs;
		   if (is_less_than) {
		     crab::outs() << " < ";
		   } else {
		     crab::outs() << " > ";
		   }
		   crab::outs() << rhs << "\n";);

	  if (rhs.equal_zero()) {
	    m_env.set(v, m_env[v] & (is_less_than ?
				     sign_t::mk_less_than_zero():
				     sign_t::mk_greater_than_zero()));
	    CRAB_LOG("sign-domain",
		     crab::outs() << "\t" << "Refined " << v  << "=" << m_env[v] << "\n";);
	    if (is_bottom()) return;
	  } else {
	    if (!is_less_than) {
	      std::swap(lhs, rhs);
	    }
	    if ((lhs.greater_than_zero() || lhs.greater_or_equal_than_zero()) &&
		(rhs.less_than_zero() || rhs.less_or_equal_than_zero())) {
	      set_to_bottom();
	      CRAB_LOG("sign-domain",
		       crab::outs() << "\t" << "Refined _|_\n";);
	      return;
	    }
	  }};

    // Solve <= and >= constraints
    auto solve_inequality = [this](const variable_t &v, sign_t rhs, bool is_less_equal) {
          sign_t lhs = m_env[v];
	  CRAB_LOG("sign-domain",
		   crab::outs() << v << "=" << lhs;
		   if (is_less_equal) {
		     crab::outs() << " <= ";
		   } else {
		     crab::outs() << " >= ";
		   }
		   crab::outs() << rhs << "\n";);

	  if (rhs.equal_zero()) {
	    m_env.set(v, m_env[v] & (is_less_equal ?
				     sign_t::mk_less_or_equal_than_zero():
				     sign_t::mk_greater_or_equal_than_zero()));
	    CRAB_LOG("sign-domain",
		     crab::outs() << "\t" << "Refined " << v  << "=" << m_env[v] << "\n";);
	    if (is_bottom()) return;
	  } else {
	    if (!is_less_equal) {
	      std::swap(lhs, rhs);
	    }
	    if (lhs.greater_or_equal_than_zero() && rhs.less_than_zero()) {
	      set_to_bottom();
	      CRAB_LOG("sign-domain",
		       crab::outs() << "\t" << "Refined _|_\n";);
	      return;
	    }
	    if (lhs.greater_than_zero() && (rhs.less_than_zero() || rhs.less_or_equal_than_zero())) {
	      set_to_bottom();
	      CRAB_LOG("sign-domain",
		       crab::outs() << "\t" << "Refined _|_\n";);
	      return;
	    }
	  }};

    if (!is_bottom()) {
      for (auto const &c : csts) {
        if (c.is_inequality() && c.is_unsigned()) {
          continue;
        }
        if (c.is_tautology()) {
          continue;
        }
        if (c.is_contradiction()) {
          set_to_bottom();
          return;
        }

	std::vector<std::pair<variable_t, sign_t>> less_than, less_equal;
	std::vector<std::pair<variable_t, sign_t>> greater_than, greater_equal;
	std::vector<std::pair<variable_t, sign_t>> equal, not_equal;

        extract_sign_constraints(c, less_than, less_equal,
				 greater_than, greater_equal, equal, not_equal);

	CRAB_LOG("sign-domain",
		 crab::outs() << "Adding " << c << "\n";);

	for (auto kv: less_than) {
	  solve_strict_inequality(kv.first, kv.second, true /*less_than*/);
	}
	for (auto kv: greater_than) {
	  solve_strict_inequality(kv.first, kv.second, false /*greater_than*/);
	}

	for (auto kv: less_equal) {
	  solve_inequality(kv.first, kv.second, true /*less_equal_than*/);
	}

	for (auto kv: greater_equal) {
	  solve_inequality(kv.first, kv.second, false /*greater_equal_than*/);
	}

	for (auto kv: equal) {
          sign_t lhs = m_env[kv.first];
          sign_t rhs = kv.second;
	  CRAB_LOG("sign-domain",
		   crab::outs() << kv.first << "=" << lhs << " == " << rhs << "\n";);
	  m_env.set(kv.first, m_env[kv.first] & rhs);
	  CRAB_LOG("sign-domain",
		   crab::outs() << "\t" << "Refined " << kv.first  << "=" << m_env[kv.first] << "\n";);
	  if (is_bottom()) return;
	}

	for (auto kv: not_equal) {
          sign_t lhs = m_env[kv.first];
          sign_t rhs = kv.second;
	  CRAB_LOG("sign-domain",
		   crab::outs() << kv.first << "=" << lhs << " != " << rhs << "\n";);
	  if (rhs.equal_zero()) {
	    m_env.set(kv.first,
		      m_env[kv.first] & sign_t::mk_not_equal_zero());
	    CRAB_LOG("sign-domain",
		     crab::outs() << "\t" << "Refined " << kv.first  << "=" << m_env[kv.first] << "\n";);
	    if (is_bottom()) return;
	  } else if (rhs.not_equal_zero()) {
	    m_env.set(kv.first, m_env[kv.first] & sign_t::mk_equal_zero());
	    CRAB_LOG("sign-domain",
		     crab::outs() << "\t" << "Refined " << kv.first  << "=" << m_env[kv.first] << "\n";);
	    if (is_bottom()) return;
	  }
	}
      }
    }
  }

  // Extract constraints of the form v OP sign where OP = {<, <=, >, >=, ==, != }
  void
  extract_sign_constraints(const linear_constraint_t &c,
			   std::vector<std::pair<variable_t, sign_t>> &less_than,
                           std::vector<std::pair<variable_t, sign_t>> &less_equal,
			   std::vector<std::pair<variable_t, sign_t>> &greater_than,
                           std::vector<std::pair<variable_t, sign_t>> &greater_equal,
			   std::vector<std::pair<variable_t, sign_t>> &equal,
			   std::vector<std::pair<variable_t, sign_t>> &not_equal) {
    auto e = c.expression();
    for (auto kv : e) {
      variable_t pivot = kv.second;
      sign_t res = compute_residual(e, pivot) / sign_t(kv.first);
      if (res.is_bottom()) {
	// this shouldn't happen
	continue;
      }
      if (!res.is_top()) {
	if (c.is_strict_inequality()) {
	  if (kv.first < number_t(0)) {
	    greater_than.push_back({pivot,res});
	  } else {
	    less_than.push_back({pivot,res});
	  }
	} else if (c.is_inequality()) {
	  if (kv.first < number_t(0)) {
	    greater_equal.push_back({pivot,res});
	  } else {
	    less_equal.push_back({pivot,res});
	  }
	} else if (c.is_equality()) {
	  equal.push_back({pivot,res});
	} else if (c.is_disequation()) {
	  not_equal.push_back({pivot,res});
	}
      }
    }
  }

  sign_t compute_residual(const linear_expression_t &e, variable_t pivot) {
    sign_t residual(-e.constant());
    for (auto kv : e) {
      const variable_t &v = kv.second;
      if (v.index() != pivot.index()) {
        residual = residual - (sign_t(kv.first) * m_env[v]);
      }
    }
    return residual;
  }

  sign_t eval_expr(const linear_expression_t &expr) const {
    if (is_bottom())
      return sign_t::bottom();

    sign_t r(expr.constant());
    for (auto kv : expr) {
      sign_t c(kv.first);
      r = r + (c * m_env[kv.second]);
    }
    return r;
  }

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

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

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

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

  sign_t get_sign(const variable_t &v) const {
    return m_env[v];
  }

  void set_sign(const variable_t &v, sign_t s) {
    m_env.set(v, s);
  }

  sign_domain() : m_env(separate_domain_t::top()) {}

  sign_domain(const sign_domain_t &e) : m_env(e.m_env) {
    crab::CrabStats::count(domain_name() + ".count.copy");
    crab::ScopedCrabStats __st__(domain_name() + ".copy");
  }

  sign_domain(sign_domain_t &&e) : m_env(std::move(e.m_env)) {}

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

  sign_domain_t &operator=(sign_domain_t &&o) {
    if (this != &o) {
      m_env = std::move(o.m_env);
    }
    return *this;
  }

  bool is_bottom() const override { return m_env.is_bottom(); }

  bool is_top() const override { return m_env.is_top(); }

  bool operator<=(const sign_domain_t &o) const override {
    crab::CrabStats::count(domain_name() + ".count.leq");
    crab::ScopedCrabStats __st__(domain_name() + ".leq");
    return (m_env <= o.m_env);
  }

  void operator|=(const sign_domain_t &o) override {
    crab::CrabStats::count(domain_name() + ".count.join");
    crab::ScopedCrabStats __st__(domain_name() + ".join");
    CRAB_LOG("sign-domain",
             crab::outs() << "Join " << m_env << " and " << o.m_env << "\n";);
    m_env = m_env | o.m_env;
    CRAB_LOG("sign-domain", crab::outs() << "Res=" << m_env << "\n";);
  }

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

  sign_domain_t operator&(const sign_domain_t &o) const override {
    crab::CrabStats::count(domain_name() + ".count.meet");
    crab::ScopedCrabStats __st__(domain_name() + ".meet");
    return (m_env & o.m_env);
  }

  sign_domain_t operator||(const sign_domain_t &o) const override {
    crab::CrabStats::count(domain_name() + ".count.widening");
    crab::ScopedCrabStats __st__(domain_name() + ".widening");
    return (m_env | o.m_env);
  }

  sign_domain_t widening_thresholds(
      const sign_domain_t &o,
      const crab::iterators::thresholds<number_t> &ts) const override {
    crab::CrabStats::count(domain_name() + ".count.widening");
    crab::ScopedCrabStats __st__(domain_name() + ".widening");
    return (m_env | o.m_env);
  }

  sign_domain_t operator&&(const sign_domain_t &o) const override {
    crab::CrabStats::count(domain_name() + ".count.narrowing");
    crab::ScopedCrabStats __st__(domain_name() + ".narrowing");
    return (m_env & o.m_env);
  }

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

  interval_t operator[](const variable_t &v) override {
    return m_env[v].to_interval();
  }

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

  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_LOG("sign-domain",
	     crab::outs() << x << " := " << e << "\n";);
    if (boost::optional<variable_t> v = e.get_variable()) {
      m_env.set(x, m_env[(*v)]);
    } else {
      m_env.set(x, eval_expr(e));
    }
    CRAB_LOG("sign-domain",
	     crab::outs() << "RES=" << m_env[x] << "\n";);
  }

  void apply(crab::domains::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");

    sign_t yi = m_env[y];
    sign_t zi = m_env[z];
    sign_t xi = sign_t::bottom();

    switch (op) {
    case crab::domains::OP_ADDITION:
      xi = yi + zi;
      break;
    case crab::domains::OP_SUBTRACTION:
      xi = yi - zi;
      break;
    case crab::domains::OP_MULTIPLICATION:
      xi = yi * zi;
      break;
    case crab::domains::OP_SDIV:
      xi = yi / zi;
      break;
    case crab::domains::OP_UDIV:
      xi = yi.UDiv(zi);
      break;
    case crab::domains::OP_SREM:
      xi = yi.SRem(zi);
      break;
    case crab::domains::OP_UREM:
      xi = yi.URem(zi);
      break;
    default:
      CRAB_ERROR("Operation ", op, " not supported");
    }
    m_env.set(x, xi);
  }

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

    sign_t yi = m_env[y];
    sign_t zi(k);
    sign_t xi = sign_t::bottom();

    switch (op) {
    case crab::domains::OP_ADDITION:
      xi = yi + zi;
      break;
    case crab::domains::OP_SUBTRACTION:
      xi = yi - zi;
      break;
    case crab::domains::OP_MULTIPLICATION:
      xi = yi * zi;
      break;
    case crab::domains::OP_SDIV:
      xi = yi / zi;
      break;
    case crab::domains::OP_UDIV:
      xi = yi.UDiv(zi);
      break;
    case crab::domains::OP_SREM:
      xi = yi.SRem(zi);
      break;
    case crab::domains::OP_UREM:
      xi = yi.URem(zi);
      break;
    default:
      CRAB_ERROR("Operation ", op, " not supported");
    }
    m_env.set(x, xi);
  }

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

  // backward arithmetic operations
  void backward_assign(const variable_t &x, const linear_expression_t &e,
                       const sign_domain_t &inv) override {
    crab::CrabStats::count(domain_name() + ".count.backward_assign");
    crab::ScopedCrabStats __st__(domain_name() + ".backward_assign");
    // TODO
  }

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

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

  // cast operations
  void apply(crab::domains::int_conv_operation_t /*op*/, const variable_t &dst,
             const variable_t &src) override {
    // ignore the widths
    assign(dst, src);
  }

  // bitwise operations
  void apply(crab::domains::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");

    sign_t yi = m_env[y];
    sign_t zi = m_env[z];
    sign_t xi = sign_t::bottom();

    switch (op) {
    case crab::domains::OP_AND: {
      xi = yi.And(zi);
      break;
    }
    case crab::domains::OP_OR: {
      xi = yi.Or(zi);
      break;
    }
    case crab::domains::OP_XOR: {
      xi = yi.Xor(zi);
      break;
    }
    case crab::domains::OP_SHL: {
      xi = yi.Shl(zi);
      break;
    }
    case crab::domains::OP_LSHR: {
      xi = yi.LShr(zi);
      break;
    }
    case crab::domains::OP_ASHR: {
      xi = yi.AShr(zi);
      break;
    }
    default:
      CRAB_ERROR("unreachable");
    }
    m_env.set(x, xi);
  }

  void apply(crab::domains::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");

    sign_t yi = m_env[y];
    sign_t zi(k);
    sign_t xi = sign_t::bottom();

    switch (op) {
    case crab::domains::OP_AND: {
      xi = yi.And(zi);
      break;
    }
    case crab::domains::OP_OR: {
      xi = yi.Or(zi);
      break;
    }
    case crab::domains::OP_XOR: {
      xi = yi.Xor(zi);
      break;
    }
    case crab::domains::OP_SHL: {
      xi = yi.Shl(zi);
      break;
    }
    case crab::domains::OP_LSHR: {
      xi = yi.LShr(zi);
      break;
    }
    case crab::domains::OP_ASHR: {
      xi = yi.AShr(zi);
      break;
    }
    default:
      CRAB_ERROR("unreachable");
    }
    m_env.set(x, xi);
  }

  virtual void select(const variable_t &lhs, const linear_constraint_t &cond,
                      const linear_expression_t &e1,
                      const linear_expression_t &e2) override {
    crab::CrabStats::count(domain_name() + ".count.select");
    crab::ScopedCrabStats __st__(domain_name() + ".select");

    if (!is_bottom()) {
      sign_domain_t inv1(*this);
      inv1 += cond;
      if (inv1.is_bottom()) {
        assign(lhs, e2);
        return;
      }

      sign_domain_t inv2(*this);
      inv2 += cond.negate();
      if (inv2.is_bottom()) {
        assign(lhs, e1);
        return;
      }

      m_env.set(lhs, eval_expr(e1) | eval_expr(e2));
    }
  }

  /// sign_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(sign_domain_t)
  ARRAY_OPERATIONS_NOT_IMPLEMENTED(sign_domain_t)
  REGION_AND_REFERENCE_OPERATIONS_NOT_IMPLEMENTED(sign_domain_t)

  void forget(const variable_vector_t &variables) override {
    if (is_bottom() || is_top()) {
      return;
    }
    for (auto const &var : variables) {
      this->operator-=(var);
    }
  }

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

    m_env.project(variables);
  }

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

    m_env.rename(from, to);
  }

  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;
    }

    m_env.set(new_x, m_env[x]);
  }

  void normalize() override {}

  void minimize() override {}

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

    m_env.write(o);
  }

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

    linear_constraint_system_t csts;

    if (this->is_bottom()) {
      csts += linear_constraint_t::get_false();
      return csts;
    }

    for (auto it = m_env.begin(); it != m_env.end(); ++it) {
      const variable_t &v = it->first;
      const sign_t &s = it->second;
      if (s.equal_zero()) {
        csts += linear_constraint_t(v == number_t(0));
      } else if (s.less_than_zero()) {
        csts += linear_constraint_t(v < number_t(0));
      } else if (s.greater_than_zero()) {
        csts += linear_constraint_t(v > number_t(0));
      } else if (s.less_or_equal_than_zero()) {
        csts += linear_constraint_t(v <= number_t(0));
      } else if (s.greater_or_equal_than_zero()) {
        csts += linear_constraint_t(v >= number_t(0));
      } else {
        // we cannot represent not_equal_zero as a linear constraint
        continue;
      }
    }
    return csts;
  }

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

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

}; // class sign_domain
} // namespace domains
} // namespace crab

namespace crab {
namespace domains {
template <typename Number, typename VariableName>
struct abstract_domain_traits<sign_domain<Number, VariableName>> {
  using number_t = Number;
  using varname_t = VariableName;
};

} // namespace domains
} // namespace crab


/*
 * Copyright (c) 2011 Seiya Tokui <beam.web@gmail.com>
 * Copyright (c) 2014 Burkhard Ritter <burkhard@ualberta.ca>
 * This code is distributed under the MIT license.
 *
 * A very simple example program demonstrating how to use Arpaca.
 *
 * To compile this program:
 * g++ \
 *    -I [/path/to/eigen] \
 *    really_simple_example.cpp \
 *    -L [/path/to/libarpack.a] \
 *    -larpack \
 *    -o really_simple_example
 *
 * This assumes that arpaca.hpp is in the same directory.
 */

#include <iostream>
#include <Eigen/Dense>
#include <Eigen/Sparse>
#include "arpaca.hpp"
using namespace Eigen;
using namespace arpaca;

int main()
{
    // Matrix dimension
    const int n_dim = 4;
    // Number of eigenvalues to compute
    const int n_ev = 3;
    // Which eigenvalues to compute
    const EigenvalueType type = ALGEBRAIC_SMALLEST;

    // A self-adjoint random dense matrix of size n_dim x n_dim and the
    // corresponding sparse matrix
    MatrixXd dm = MatrixXd::Random(n_dim,n_dim).selfadjointView<Upper>();
    SparseMatrix<double> sm = dm.sparseView();

    // Solve for the eigenvalues
    SymmetricEigenSolver<double> s = Solve(sm, n_ev, type);

    // Output
    std::cout << "Matrix: " << std::endl << std::endl
              << dm << std::endl << std::endl << std::endl
              << "Its " << n_ev << " smallest eigenvalues: "
              << std::endl << std::endl
              << s.eigenvalues() << std::endl << std::endl
              << "And the corresponding eigenvectos: "
              << std::endl << std::endl
              << s.eigenvectors() << std::endl;
}


#include <boost/numeric/ublas/matrix_sparse.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/bindings/ublas/vector.hpp>
#include <boost/numeric/bindings/ublas/matrix_sparse.hpp>
#include <boost/numeric/bindings/mumps/mumps_driver.hpp>
#include <iostream>
#include <fstream>
#include <complex>

template <typename T>
int test(double eps)
{
  namespace ublas = ::boost::numeric::ublas ;
  namespace mumps = ::boost::numeric::bindings::mumps ;

  int const n = 10 ;

  typedef ublas::coordinate_matrix<T, ublas::column_major, 1, ublas::unbounded_array<int> > coo_type ;

  coo_type coo(n, n, n + 6) ;

  for(int i=0; i<n; ++i) coo(i,i) = i+1.0 ;
  coo(2,3) = T(1.0) ;
  coo(2,4) = T(1.0) ;
  coo(5,6) = T(-1.0) ;
  coo(2,6) = T(1.0) ;
  coo(9,0) = T(1.0) ;
  coo(2,7) = T(-1.0) ;

  coo.sort() ;
  std::cout << "matrix " << coo << std::endl ;

  ublas::vector<T> v(10) ;
  ublas::vector<T> w(10) ;

  std::fill(w.begin(), w.end(), 1.0) ;

  for(int i=1; i<n; ++i)
  {
    w(i) += w(i-1) ;
  }

  for(int i=0; i<n; ++i)
  {
    v[i] = T(coo(i,i)) * w[i] ;
  }
  v[2] += T(coo(2,3)) * w[3] ;
  v[2] += T(coo(2,4)) * w[4] ;
  v[5] += T(coo(5,6)) * w[6] ;
  v[2] += T(coo(2,6)) * w[6] ;
  v[9] += T(coo(9,0)) * w[0] ;
  v[2] += T(coo(2,7)) * w[7] ;
  std::cout << "rhs : " << v << std::endl ;

  mumps::mumps< coo_type > mumps_coo ;

  mumps_coo.icntl[2]=mumps_coo.icntl[3] = 0 ;

  // Analysis
  mumps_coo.job = 1 ;
  matrix_integer_data(mumps_coo, coo) ;
  driver(mumps_coo) ;

  // Factorization
  mumps_coo.job = 2 ;
  matrix_value_data(mumps_coo, coo) ;
  driver(mumps_coo) ;

  // Solve
  mumps_coo.job = 3 ;
  rhs_sol_value_data(mumps_coo, v) ;
  driver(mumps_coo) ;

  std::cout << "w : " << w << std::endl ;
  std::cout << "v : " << v << std::endl ;

  if(norm_2(v - w) > eps * norm_2(v)) return 1 ;

  return 0 ;
}

int main()
{
  if(test<float>(1e-5)) return 1 ;
  if(test<double>(1e-10)) return 2 ;
  if(test< std::complex<float> >(1e-5)) return 3 ;
  if(test< std::complex<double> >(1e-10)) return 4 ;
  return 0 ;
}


// Implement SparseLibrary requirements using Eigen
#include <Eigen/Sparse>
#include <Eigen/SparseQR>
#include <Eigen/SparseLU>

struct EigenShim {
    using value_t = double;
    using triplet_t = Eigen::Triplet<value_t>;

    template<typename Value>
    struct sparse_wrapper_t {
        using wrapped_t = Eigen::SparseMatrix<Value>;
        using index_t = typename wrapped_t::StorageIndex;
        template<typename Iter>     // or use ForwardIterator concept
        sparse_wrapper_t(index_t rows, index_t cols, Iter a, Iter b) : mat_(rows, cols) {
            mat_.setFromTriplets(a, b);
        }
        sparse_wrapper_t(wrapped_t mat) : mat_(std::move(mat)) {}

        // define the product of two sparse wrappers
        friend sparse_wrapper_t operator*(sparse_wrapper_t const& a, sparse_wrapper_t const& b) {
            return Eigen::SparseMatrix<value_t>(a.wrapped() * b.wrapped());
        }

        friend std::ostream & operator<<(std::ostream& os, sparse_wrapper_t const& m) {
            using namespace Eigen;
            IOFormat OctaveFmt(FullPrecision, 0, ", ", ";\n", "", "", "[", "]");

            Matrix<value_t, Dynamic, Dynamic> dense = m.wrapped();   // convert to dense

            os << dense.format(OctaveFmt);
            return os;
        }

        wrapped_t const & wrapped() const { return mat_; }

    private:

        wrapped_t mat_;
    };

    using sparsemat_t = sparse_wrapper_t<value_t>;
    using index_t = sparsemat_t::index_t;

    template<typename Value, typename Index>
    struct lu_wrapper_t {
        using wrapped_t = Eigen::SparseLU<Eigen::SparseMatrix<Value>, Eigen::COLAMDOrdering<Index>>;

        lu_wrapper_t( sparsemat_t const & mat ) : lu_(mat.wrapped()) {
            assert(lu_.info() == Eigen::Success);
        }

        sparse_wrapper_t<Value> solve( sparsemat_t const & rhs ) const {
            return sparse_wrapper_t<Value>(lu_.solve(rhs.wrapped()));
        }

    private:
        wrapped_t lu_;
    };

    template<typename Value, typename Index>
    struct qr_wrapper_t {
        using wrapped_t = Eigen::SparseQR<Eigen::SparseMatrix<Value>, Eigen::COLAMDOrdering<Index>>;

        qr_wrapper_t( sparsemat_t const & mat ) : qr_(mat.wrapped()) {}

        sparsemat_t Q() const {
            using namespace Eigen;
            // Sadly Eigen cannot directly return the Q as a sparse matrix
            // What it *can* do is multiply times a dense matrix
            Matrix<value_t, Dynamic, Dynamic> identity(qr_.rows(), qr_.rank());
            identity.setIdentity();

            Matrix<value_t, Dynamic, Dynamic> result = qr_.matrixQ() * identity;

            // finally, convert to sparse
            return SparseMatrix<value_t>(result.sparseView());
        }

    private:
        wrapped_t qr_;
    };

    using lu_t = lu_wrapper_t<value_t, index_t>;
    using qr_t = qr_wrapper_t<value_t, index_t>;

};


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#define _USE_MATH_DEFINES
#include <cmath>

#include <boost/math/special_functions/polygamma.hpp>

#include "beanmachine/graph/graph.h"
#include "beanmachine/graph/util.h"

namespace beanmachine {
namespace util {

// see https://core.ac.uk/download/pdf/41787448.pdf
const double PHI_APPROX_GAMMA = 1.702;

bool approx_zero(double val) {
  return std::abs(val) < graph::PRECISION;
}

bool sample_logodds(std::mt19937& gen, double logodds) {
  if (logodds < 0) {
    double wt = exp(logodds);
    std::bernoulli_distribution dist(wt / (1 + wt));
    return dist(gen);
  } else {
    double wt = exp(-logodds);
    std::bernoulli_distribution dist(wt / (1 + wt));
    return not dist(gen);
  }
}

bool sample_logprob(std::mt19937& gen, double logprob) {
  if (logprob > 0)
    return true;
  else {
    std::bernoulli_distribution dist(std::exp(logprob));
    return dist(gen);
  }
}

bool flip_coin_with_log_prob(std::mt19937& gen, double logprob) {
  return sample_logprob(gen, logprob);
}

double sample_beta(std::mt19937& gen, double a, double b) {
  std::gamma_distribution<double> distrib_a(a, 1);
  std::gamma_distribution<double> distrib_b(b, 1);
  double x = distrib_a(gen);
  double y = distrib_b(gen);
  if ((x + y) == 0.0) {
    return graph::PRECISION;
  }
  double p = x / (x + y);
  return p;
}

double logistic(double logodds) {
  return 1.0 / (1.0 + std::exp(-logodds));
}

double Phi(double x) {
  return 0.5 * (1 + std::erf(x / M_SQRT2));
}

double Phi_approx(double x) {
  return 1.0 / (1.0 + std::exp(-PHI_APPROX_GAMMA * x));
}

double Phi_approx_inv(double z) {
  return (std::log(z) - std::log(1 - z)) / PHI_APPROX_GAMMA;
}

double log_sum_exp(const std::vector<double>& values) {
  // find the max and subtract it out
  double max = values[0];
  for (std::vector<double>::size_type idx = 1; idx < values.size(); idx++) {
    if (values[idx] > max) {
      max = values[idx];
    }
  }
  double sum = 0;
  for (auto value : values) {
    sum += std::exp(value - max);
  }
  return std::log(sum) + max;
}

double log_sum_exp(double a, double b) {
  double max_val = a > b ? a : b;
  double sum = std::exp(a - max_val) + std::exp(b - max_val);
  return std::log(sum) + max_val;
}

double polygamma(int n, double x) {
  return boost::math::polygamma(n, x);
}

double log1pexp(double x) {
  if (x <= -37) {
    return std::exp(x);
  } else if (x <= 18) {
    return std::log1p(std::exp(x));
  } else if (x <= 33.3) {
    return x + std::exp(-x);
  } else {
    return x;
  }
}

double log1mexp(double x) {
  assert(x <= 0);
  if (x < -0.693) {
    return std::log1p(-std::exp(x));
  } else {
    return std::log(-std::expm1(x));
  }
}

} // namespace util
} // namespace beanmachine


/* http://melpon.org/wandbox/permlink/paCC61fxm2muS5lc */

//=======================================================================
// Copyright 2001 Jeremy G. Siek, Andrew Lumsdaine, Lie-Quan Lee,
//
// 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)
//=======================================================================
#include "max_plus.hpp"
#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/bellman_ford_shortest_paths.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <vector>

using namespace boost;

template <typename Graph, typename ParentMap> struct edge_writer {
  edge_writer(const Graph &g, const ParentMap &p) : m_g(g), m_parent(p) {}

  template <typename Edge>
  void operator()(std::ostream &out, const Edge &e) const {
    out << "[label=\"" << get(edge_weight, m_g, e) << "\"";
    typename graph_traits<Graph>::vertex_descriptor u = source(e, m_g),
                                                    v = target(e, m_g);
    if (m_parent[v] == u)
      out << ", color=\"black\"";
    else
      out << ", color=\"grey\"";
    out << "]";
  }
  const Graph &m_g;
  ParentMap m_parent;
};
template <typename Graph, typename Parent>
edge_writer<Graph, Parent> make_edge_writer(const Graph &g, const Parent &p) {
  return edge_writer<Graph, Parent>(g, p);
}

struct EdgeProperties {
  fun::maxPlus<int> weight;
};

int main() {
  enum { u, v, x, y, z, N };
  char name[] = {'u', 'v', 'x', 'y', 'z'};
  typedef std::pair<int, int> E;
  const int n_edges = 10;
  E edge_array[] = {E(u, y), E(u, x), E(u, v), E(v, u), E(x, y),
                    E(x, v), E(y, v), E(y, z), E(z, u), E(z, x)};
  fun::maxPlus<int> weight[n_edges] = {-4, 8, 5, -2, 9, -3, 7, 2, 6, 7};

  typedef adjacency_list<vecS, vecS, directedS, no_property, EdgeProperties>
      Graph;
  Graph g(edge_array, edge_array + n_edges, N);

  graph_traits<Graph>::edge_iterator ei, ei_end;
  property_map<Graph, fun::maxPlus<int> EdgeProperties::*>::type weight_pmap =
      get(&EdgeProperties::weight, g);
  int i = 0;
  for (tie(ei, ei_end) = edges(g); ei != ei_end; ++ei, ++i)
    weight_pmap[*ei] = weight[i];

  std::vector<fun::maxPlus<int>> distance(N,
                                          (std::numeric_limits<short>::max)());
  std::vector<std::size_t> parent(N);
  for (i = 0; i < N; ++i)
    parent[i] = i;
  distance[z] = 0;

  bool r = bellman_ford_shortest_paths(g, int(N),
                                       weight_map(weight_pmap)
                                           .distance_map(&distance[0])
                                           .predecessor_map(&parent[0]));

  if (r)
    for (i = 0; i < N; ++i)
      std::cout << name[i] << ": " << std::setw(3) << distance[i] << " "
                << name[parent[i]] << std::endl;
  else
    std::cout << "negative cycle" << std::endl;
  std::cout << '\n';

  // std::ofstream dot_file("figs/bellman-eg.dot");
  auto &dot_file = std::cout;

  dot_file << "digraph D {\n"
           << "  rankdir=LR\n"
           << "  size=\"5,3\"\n"
           << "  ratio=\"fill\"\n"
           << "  edge[style=\"bold\"]\n"
           << "  node[shape=\"circle\"]\n";

  {
    for (tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) {
      graph_traits<Graph>::edge_descriptor e = *ei;
      graph_traits<Graph>::vertex_descriptor u = source(e, g), v = target(e, g);
      // VC++ doesn't like the 3-argument get function, so here
      // we workaround by using 2-nested get()'s.
      dot_file << "  " << name[u] << " -> " << name[v] << "[label=\""
               << get(get(&EdgeProperties::weight, g), e) << "\"";
      if (parent[v] == u)
        dot_file << ", color=\"black\"";
      else
        dot_file << ", color=\"grey\"";
      dot_file << "]\n";
    }
  }
  dot_file << "}\n";
  return EXIT_SUCCESS;
}

//=======================================================================
// Copyright 2001 Jeremy G. Siek, Andrew Lumsdaine, Lie-Quan Lee,
//
// 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)
//=======================================================================
#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/bellman_ford_shortest_paths.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <vector>

//#include "max_plus.hpp"

using namespace boost;

template <typename Graph, typename ParentMap> struct edge_writer {
  edge_writer(const Graph &g, const ParentMap &p) : m_g(g), m_parent(p) {}

  template <typename Edge>
  void operator()(std::ostream &out, const Edge &e) const {
    out << "[label=\"" << get(edge_weight, m_g, e) << "\"";
    typename graph_traits<Graph>::vertex_descriptor u = source(e, m_g),
                                                    v = target(e, m_g);
    if (m_parent[v] == u)
      out << ", color=\"black\"";
    else
      out << ", color=\"grey\"";
    out << "]";
  }
  const Graph &m_g;
  ParentMap m_parent;
};
template <typename Graph, typename Parent>
edge_writer<Graph, Parent> make_edge_writer(const Graph &g, const Parent &p) {
  return edge_writer<Graph, Parent>(g, p);
}

struct EdgeProperties {
  int weight;
};

int main() {
  enum { u, v, x, y, z, N };
  char name[] = {'u', 'v', 'x', 'y', 'z'};
  typedef std::pair<int, int> E;
  const int n_edges = 10;
  E edge_array[] = {E(u, y), E(u, x), E(u, v), E(v, u), E(x, y),
                    E(x, v), E(y, v), E(y, z), E(z, u), E(z, x)};
  int weight[n_edges] = {-4, 8, 5, -2, 9, -3, 7, 2, 6, 7};

  typedef adjacency_list<vecS, vecS, directedS, no_property, EdgeProperties>
      Graph;
  Graph g(edge_array, edge_array + n_edges, N);

  graph_traits<Graph>::edge_iterator ei, ei_end;
  property_map<Graph, int EdgeProperties::*>::type weight_pmap =
      get(&EdgeProperties::weight, g);
  int i = 0;
  for (tie(ei, ei_end) = edges(g); ei != ei_end; ++ei, ++i)
    weight_pmap[*ei] = weight[i];

  std::vector<int> distance(N, (std::numeric_limits<short>::max)());
  std::vector<std::size_t> parent(N);
  for (i = 0; i < N; ++i)
    parent[i] = i;
  distance[z] = 0;

  bool r = bellman_ford_shortest_paths(g, int(N),
                                       weight_map(weight_pmap)
                                           .distance_map(&distance[0])
                                           .predecessor_map(&parent[0]));

  if (r)
    for (i = 0; i < N; ++i)
      std::cout << name[i] << ": " << std::setw(3) << distance[i] << " "
                << name[parent[i]] << std::endl;
  else
    std::cout << "negative cycle" << std::endl;
  std::cout << '\n';

  // std::ofstream dot_file("figs/bellman-eg.dot");
  auto &dot_file = std::cout;

  dot_file << "digraph D {\n"
           << "  rankdir=LR\n"
           << "  size=\"5,3\"\n"
           << "  ratio=\"fill\"\n"
           << "  edge[style=\"bold\"]\n"
           << "  node[shape=\"circle\"]\n";

  {
    for (tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) {
      graph_traits<Graph>::edge_descriptor e = *ei;
      graph_traits<Graph>::vertex_descriptor u = source(e, g), v = target(e, g);
      // VC++ doesn't like the 3-argument get function, so here
      // we workaround by using 2-nested get()'s.
      dot_file << "  " << name[u] << " -> " << name[v] << "[label=\""
               << get(get(&EdgeProperties::weight, g), e) << "\"";
      if (parent[v] == u)
        dot_file << ", color=\"black\"";
      else
        dot_file << ", color=\"grey\"";
      dot_file << "]\n";
    }
  }
  dot_file << "}\n";
  return EXIT_SUCCESS;
}


/*
   Copyright (C) 2015-2021 by Synge Todo <wistaria@phys.s.u-tokyo.ac.jp>

   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.
*/

// Critical temperature of square lattice Ising model

// reference: L. Onsager, Phys. Rev. 65, 117 (1944)

#pragma once

#include <cmath>
#include <stdexcept>
#include <boost/math/differentiation/autodiff.hpp>
#include <standards/newton.hpp>

namespace {

template<typename T>
struct func {
  func(T Jx, T Jy) : Jx_(Jx), Jy_(Jy) {}
  auto operator()(T beta) const -> decltype(boost::math::differentiation::make_fvar<T, 1>(beta)) {
    using std::sinh;
    auto beta_fvar = boost::math::differentiation::make_fvar<T, 1>(beta);
    return sinh(2 * Jx_ * beta_fvar) * sinh(2 * Jy_ * beta_fvar) - 1;
  }
  T Jx_, Jy_;
};

}

namespace ising {
namespace tc {

template<typename T>
inline T square(T Jx, T Jy) {
  Jx = abs(Jx);
  Jy = abs(Jy);
  if (Jx * Jy <= 0) throw(std::invalid_argument("Jx * Jy should be non-zero"));
  auto result = standards::newton_1d(func<T>(Jx, Jy), 1 / (2 * (Jx + Jy)));
  if (!result.second) throw(std::runtime_error("convergence error"));
  return 1 / result.first;
}

} // end namespace tc
} // end namespace ising


#include <cstdlib>
#include <iostream>
#include <cmath>
#include <ctime>
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
#include <Eigen/Dense>

using Eigen::MatrixXd;
using Eigen::VectorXd;
using namespace std;

const int MATCH_MIN = 0;
const int MATCH_MAX = 1;

/*
 * readMatrix
 * read a file into a matrix object
 * file must have matrix dimension as first line
 * matrix must be square
 */
void readMatrix(const char* filename, MatrixXd& m) {
  ifstream fin(filename);

  if (!fin) {
    cout << "Cannot open file." << endl;
    return;
  }

  // read in matrix dimensions and
  // create a matrix of that size
  int n_rows, n_cols;
  fin >> n_rows >> n_cols;
  MatrixXd temp(n_rows, n_cols);

  // read elements into the temporary matrix
  for (int i=0; i<n_rows; i++) {
    for (int j=0; j<n_cols; j++) {
      fin >> temp(i,j);
    }
  }
  fin.close();

  // if the dimensions are equal (square matrix), we're done
  // else, have to figure out larger dimension and pad with matrix max
  if (n_rows == n_cols) {
    m = temp;
  }
  else {
    float max_elem = temp.maxCoeff(); // find the max element
    float dim = max(n_rows, n_cols); // find the dimension for the new, square matrix
    m.resize(dim,dim);
    // fill the matrix with the elements from temp and pad with max element
    for (int i=0; i < dim; i++) {
      for (int j=0; j < dim; j++) {
        if (i >= n_rows || j >= n_cols) m(i,j) = max_elem;
        else m(i,j) = temp(i,j);
      }
    }
  }
}

/*
 * reduce
 * reduces matrix based on row and column minimums
 */
void reduce(MatrixXd& m) {
  // subtract row minimum from each row
  for (int i=0; i<m.rows(); i++) {
    float minElement = m.row(i).minCoeff();
    VectorXd rMinusMin(m.rows());
    rMinusMin.fill(-minElement);
    m.row(i) += rMinusMin;
  }
}

/*
 * hasMark
 * if there is a starred/primed zero in the given row/col, returns it's index
 * else, returns -1
 */
int hasMark(VectorXd& v) {
  for (int i=0; i<v.size(); i++) {
    if (v(i)) {
      return i;
    }
  }
  return -1;
}

/*
 * swapStarsAndPrimes
 * Swap stars and primes based on step 5 of Hungarian algorithm
 * Z0 is uncovered primed zero we've found
 * Z1 is the stared zero in the column of Z0 (if any)
 * Z2 is the primed zero in the row of Z1 (will always be one)
 * ...continue series until we reach a primed zero with no starred zero in its column
 * Unstar each starred zero, star each primed zero, erase all primes and uncover every line in the matrix
 */
void swapStarsAndPrimes(int i, int j, MatrixXd& stars, MatrixXd& primes) {
  int primeRow = i;
  int primeCol = j;

  bool done = false;
  while (!done) {
    // find row index of row that has a 0* in the same col as the current 0'
    VectorXd col = stars.col(primeCol);
    int starInPrimeColRow = hasMark(col);

    if (starInPrimeColRow < 0) {
      // star the prime we're looking at
      primes(primeRow, primeCol) = 0;
      stars(primeRow, primeCol) = 1;
      done = true;
    }
    else {
      // find which col has a 0' in the same row as z1
      VectorXd row = primes.row(starInPrimeColRow);
      int primeInStarRowCol = hasMark(row);

      // star first primed zero
      primes(primeRow, primeCol) = 0;
      stars(primeRow, primeCol) = 1;
      //primes(starInPrimeColRow, primeInStarRowCol) = 0;
      //stars(starInPrimeColRow, primeInStarRowCol) = 1;

      // unstar starred zero
      stars(starInPrimeColRow, primeCol) = 0;

      // set index of last prime, will check it's column for 0*s next
      primeRow = starInPrimeColRow;
      primeCol = primeInStarRowCol;
    }
  }
  // clear primes
  primes.fill(0);
}

/*
 * findMatching
 * implementation of the Hungarian matching algorithm
 * referenced from: http://csclab.murraystate.edu/bob.pilgrim/445/munkres.html
 */
void findMatching(MatrixXd& m, MatrixXd& result, int type) {
  MatrixXd n = m; // make a copy of m for reducing
  int dim = n.rows(); // dimension of matrix, used for checking if we've reduced
                      // the matrix enough yet

  MatrixXd stars(m.rows(), m.cols()); // matrix for storing our "starred" 0s (0*)
  stars.fill(0);
  MatrixXd primes(m.rows(), m.cols()); // matrix for storing our "primed" 0s (0')
  primes.fill(0);
  VectorXd rowCover(m.rows()); // keep track of which rows are "covered"
  rowCover.fill(0);
  VectorXd colCover(m.cols()); // keep track of which columns are "covered"
  colCover.fill(0);

  // to do maximization rather than minimization, we have to
  // transform the matrix by subtracting every value from the maximum
  if (type == MATCH_MAX) {
    float max = n.maxCoeff();
    MatrixXd maxMat(n.rows(), n.cols());
    maxMat.fill(max);
    n = maxMat - n;
  }

  // Step 1
  // Reduce matrix
  reduce(n);

  // Step 2
  // Find a zero in the matrix. If there is no starred zero in
  // its row or column, star Z. Repeat for each element in the matrix.
  for (int i=0; i<n.rows(); i++) {
    for (int j=0; j<n.cols(); j++) {
      if (n(i,j) == 0 && !rowCover(i) && !colCover(j)) {
        stars(i,j) = 1;
        rowCover(i) = 1;
        colCover(j) = 1;
      }
    }
  }
  // covers need to be cleared for following steps
  rowCover.fill(0);
  colCover.fill(0);

  while (true) {
    // Step 3
    // Cover all columns that have a starred zero
    // If the number of columns with starred zeroes equals the matrix
    // dimensions, we are done! Otherwise, move on to step 4.
    step3:
    for (int j=0; j<n.cols(); j++) {
      VectorXd col = stars.col(j);
      if (hasMark(col) >= 0) {
        colCover(j) = 1;
      }
    }
    if (colCover.sum() == dim) {
      result = stars;
      return;
    }

    // Step 4
    // Find a non-covered zero and prime it
    step4:
    for (int i=0; i<n.rows(); i++) {
      for (int j=0; j<n.cols(); j++) {
        if (n(i,j) == 0 && !rowCover(i) && !colCover(j)) {
          primes(i,j) = 1;
          // if no starred zero in the row...
          VectorXd row = stars.row(i);
          if (hasMark(row) < 0) {
            // Step 5
            // swap stars and primes
            swapStarsAndPrimes(i, j, stars, primes);

            // clear lines
            rowCover.fill(0);
            colCover.fill(0);

            goto step3;
          }
          else {
            // cover row
            rowCover(i) = 1;

            // uncover column of the starred zero in the same row
            int col = hasMark(row);
            colCover(col) = 0;
          }
        }
      }
    }

    // Step 6
    // Should now be no more uncovered zeroes
    // Get the minimum uncovered element
    float min = 1000000;
    for (int i=0; i<n.rows(); i++) {
      for (int j=0; j<n.cols(); j++) {
        if (!rowCover(i) && !colCover(j) && n(i,j) < min) {
          min = n(i,j);
        }
      }
    }

    // Subtract minimum from uncovered elements, add it to elements covered twice
    for (int i=0; i<n.rows(); i++) {
      for (int j=0; j<n.cols(); j++) {
        if (!rowCover(i) && !colCover(j)) {
          n(i,j) -= min;
        }
        else if (rowCover(i) && colCover(j)) {
          n(i,j) += min;
        }
      }
    }

    goto step4;
  }
}

/*
 * main()
 */
int main(int argc, char *argv[])
{
  if (argc != 2) {
    cout << "Usage: ./a.out [matrixfile]" << endl;
    return 0;
  }

  // times
	clock_t start, end;
  float elapsedTime;

  // random seed
  srand((unsigned)time(0));

	// start time
  start = clock();

  // read in the matrix file
  MatrixXd m(1,1);
  readMatrix(argv[1], m);

  // create an empty matrix to put the result in
  MatrixXd result(m.rows(), m.cols());
  result.fill(0);

  // run the Hungarian (Munkres) algorithm to find the maximal matching
  findMatching(m, result, MATCH_MIN);

  MatrixXd mask = m.cwiseProduct(result);
  int sum = mask.sum();

  cout << "ORIGINAL MATRIX" << endl;
  cout << m << endl;
  cout << endl;
  cout << "ASSIGNMENT RESULT" << endl;
  cout << result << endl;
  cout << endl;
  cout << "FINAL SUM" << endl;
  cout << sum << endl;

  // stop time
  end = clock();
	elapsedTime = (float)(end - start) / CLOCKS_PER_SEC;
  cout << "Completed in: " << elapsedTime << " s" << endl;

  return 0;
}


#include <algorithm>
#include <iostream>
#include <list>
#include <numeric>
#include <random>
#include <vector>
#include "cnn/metric-util.h"
#include <initializer_list>
#include <Eigen/LU>

using namespace std;
namespace cnn { namespace metric {

    cnn::real cosine_similarity(const std::vector<cnn::real> &s1, const std::vector<cnn::real> &s2)
    {
        cnn::real flt = 0.0;
        cnn::real flt1 = 0.0, flt2 = 0.0;

        for (int k = 0; k < s1.size(); k++)
        {
            flt += s1[k] * s2[k];
            flt1 += s1[k] * s1[k];
            flt2 += s2[k] * s2[k];
        }

        if (flt1 == 0.0) flt1 = FLT_EPSILON;
        if (flt2 == 0.0) flt2 = FLT_EPSILON;
        flt1 = sqrt(flt1);
        flt2 = sqrt(flt2);

        cnn::real val = flt / (flt1 * flt2);

        return val;
    }

    int levenshtein_distance(const vector<std::string> &s1, const vector<std::string> &s2)
    {
        // To change the type this function manipulates and returns, change
        // the return type and the types of the two variables below.
        int s1len = s1.size();
        int s2len = s2.size();

        auto column_start = (decltype(s1len))1;

        auto column = new decltype(s1len)[s1len + 1];
        std::iota(column + column_start, column + s1len + 1, column_start);

        for (auto x = column_start; x <= s2len; x++) {
            column[0] = x;
            auto last_diagonal = x - column_start;
            for (auto y = column_start; y <= s1len; y++) {
                auto old_diagonal = column[y];
                auto possibilities = {
                    column[y] + 1,
                    column[y - 1] + 1,
                    last_diagonal + (s1[y - 1] == s2[x - 1] ? 0 : 1)
                };
                column[y] = std::min(possibilities);
                last_diagonal = old_diagonal;
            }
        }
        auto result = column[s1len];
        delete[] column;
        return result;
    }
} }