/*******************************************************************************
 * This domain is a simplified version of the paper:
 *
 * "A Partial-Order Approach to Array Content Analysis" by
 *  Gange, Navas, Schachte, Sondergaard, and Stuckey
 *  available here http://arxiv.org/pdf/1408.1754v1.pdf.
 *
 * It reasons about array contents and the idea is to compute all the
 * feasible partial orderings between array indexes. It keeps a single
 * graph where vertices are the array indexes (potentially all scalar
 * variables) and edges are labelled with weights (that includes
 * scalar and array variables).  An edge (i,j) with weight w denotes
 * that the property w holds for the all elements in the array between
 * [i,j).
 ******************************************************************************/

/* Limitations:

  - The implementation is just a proof-of-concept so it is horribly
    inefficient. I have not tried to make it more efficient yet or
    ran even with real programs.
  - Assume all array accesses are aligned wrt to the size of the array
    element (e.g., if the size of the array element is 4 bytes then
    all array accesses must be multiple of 4).
  - Assume that the size of the array element is always
    1. Therefore, if the array indexes are incremented or decremented
    by 2,4,... we will lose all the precision.

  FIXMEs:

  - Use AdaptGraph instead of boost::graph
  - Have a flag is_normalized and normalize only if the flag is false
  - Perform incremental Floyd-Warshall by keeping track of changed edges.
  - array_graph::widening is normalizing both operands. The first
    operand cannot be normalized.
  - perform common renaming before binary operations (join/widening/narrowing/meet)
  - Do no use shared_ptr because we are doing deep copies Use magic
    move semantics and also shared references with copy-on-write to
    avoid unnecessary copies.
  - etc.

 */

#ifndef ARRAY_GRAPH_HPP
#define ARRAY_GRAPH_HPP

#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/copy.hpp>
#include <boost/lexical_cast.hpp>

#include <crab/common/types.hpp>
#include <crab/common/debug.hpp>
#include <crab/common/stats.hpp>
#include <crab/domains/patricia_trees.hpp>
#include <crab/domains/operators_api.hpp>
#include <crab/domains/domain_traits.hpp>

using namespace std;
using namespace boost;
using namespace ikos;

namespace crab {

  namespace domains {

     /*
       A weighted array graph is a graph (V,E,L) where V is the set of
       vertices, E the edges and L is a label function: E -> W such that:
       - If there is an edge e from vertex i to j whose weight is not
       bottom (i.e., L(e) != bot) then it means that i < j.
       - It is possible one non-bottom edge from i to j and another
       non-bottom one from j to i. This means that both i < j and j < i
       are possible.
       - If the both edges from i to j and from j to i are bottom then it
       must be that (i>=j) && (j>=i) = (i==j)
     */
     template< typename VertexName, typename Weight,
               typename ScalarNumDomain, bool IsDistWeight >
     class array_graph: public writeable{

       typedef VertexName VertexNameKey;

       template < typename Any1, typename Any2, bool Any5>
       friend class array_graph_domain;

       typedef index_t key_t;
       typedef boost::shared_ptr<VertexName> VertexNamePtr;
       typedef boost::shared_ptr<Weight>     WeightPtr;
       struct  graph_vertex_t { VertexNamePtr name; };
       struct  graph_edge_t   { WeightPtr weight; };

      public:
       typedef array_graph<VertexName,Weight,ScalarNumDomain,IsDistWeight> array_graph_t;
       typedef boost::tuple<VertexName, VertexName, Weight> edge_t;

      private:
       typedef adjacency_list<listS,listS,bidirectionalS,graph_vertex_t,graph_edge_t> graph_t;

       typedef typename graph_traits<graph_t>::edge_iterator     edge_iterator;
       typedef typename graph_traits<graph_t>::vertex_iterator   vertex_iterator;
       typedef typename graph_traits<graph_t>::edge_descriptor   edge_descriptor_t;
       typedef typename graph_traits<graph_t>::vertex_descriptor vertex_descriptor_t;
       typedef typename graph_traits<graph_t>::out_edge_iterator out_edge_iterator;
       typedef typename graph_traits<graph_t>::in_edge_iterator  in_edge_iterator;

       typedef typename ScalarNumDomain::linear_constraint_t     linear_constraint_t;
       typedef typename ScalarNumDomain::variable_t              variable_t;

       typedef boost::unordered_map<key_t, vertex_descriptor_t >  vertex_map_t;

       typedef std::set<VertexName> vertex_names_set_t;

       typedef boost::shared_ptr <graph_t> graph_ptr;
       typedef boost::shared_ptr< vertex_map_t > vertex_map_ptr;
       typedef boost::shared_ptr< vertex_names_set_t > vertex_names_set_ptr;

       bool _is_bottom;
       graph_ptr  _graph;
       vertex_map_ptr _vertex_map;   //! map a VertexName to a graph vertex
       vertex_names_set_ptr _vertices_set;

       bool find_vertex_map (VertexName v) {
         return (_vertex_map->find(v.index()) != _vertex_map->end());
       }

       void insert_vertex_map (VertexName key, vertex_descriptor_t value)
       {
         if (find_vertex_map(key))
           CRAB_ERROR (key," already in the vertex map");

         _vertex_map->insert (make_pair(key.index(), value));
         _vertices_set->insert (key);
       }

       void remove_vertex_map (VertexName key)
       {
         _vertex_map->erase (key.index());
         _vertices_set->erase (key);
       }

       vertex_descriptor_t lookup_vertex_map (VertexName key) const
       {
         auto it = _vertex_map->find(key.index());
         if (it != _vertex_map->end())
           return it->second;

         CRAB_ERROR ("No vertex with name ",key," found in the graph");
       }

       // All methods that add new vertices should call this one.
       void add (vector<VertexName> vertices, vector<edge_t> edges)
       {
         for(auto v: vertices)
         {
           vertex_descriptor_t u = add_vertex(*_graph);
           (*_graph)[u].name = VertexNamePtr (new VertexName(v));
           insert_vertex_map(v, u);
         }

         for(auto e: edges)
         {
           vertex_descriptor_t u =  lookup_vertex_map(e.template get<0>());
           vertex_descriptor_t v =  lookup_vertex_map(e.template get<1>());
           edge_descriptor_t k; bool b;
           boost::tie(k,b) = add_edge(u, v, *_graph);
           if (!b)
             CRAB_ERROR ("edge is already in the graph");

           (*_graph)[k].weight = WeightPtr(new Weight(e.template get<2>()));
         }

         canonical();
       }

       // All methods that remove vertices should call this one.
       void remove (VertexName v)
       {
         if (!find_vertex_map (v)) return ;

         canonical();
         vertex_descriptor_t u = lookup_vertex_map(v);

         // remove all in and out edges to/from u
         clear_vertex(u, *_graph);

         // remove the vertex
         remove_vertex(u, *_graph);
         remove_vertex_map(v);
       }

       ///////////////////////////////////////////////////////////////////////
       // For our canonical form, we would like to compute the greatest
       // fixed point to the set of inequalities:
       //    \forall i,j,k. G[i,j] \subseteq G[i,k] \cup G[k,j]
       // If the weight domain is distributive we can solve this set of
       // inequations by solving:
       //    \forall i,j,k. G[i,j] = G[i,j] \cap G[i,k] \cup G[k,j]
       // The Floyd-Warshall algorithm does exactly that.
       // Otherwise, we iterate the Floyd-Warshall algorithm until no change.
       ///////////////////////////////////////////////////////////////////////
       bool oneStep()
       {
         binary_join_op join;
         binary_meet_op meet;
         vertex_iterator ki, ke, ii, ie, ji, je;
         bool change = false;
         for (tie(ki, ke) = vertices(*_graph); ki != ke; ++ki)
         {
           for (tie(ii, ie) = vertices(*_graph); ii != ie; ++ii)
           {
             if (edge(*ii, *ki, *_graph).second)
             {
               for (tie(ji, je) = vertices(*_graph); ji != je; ++ji)
               {
                 if (edge(*ii, *ji, (*_graph)).second &&
                     edge(*ki, *ji, (*_graph)).second)
                 {
                   auto e_ij = edge(*ii, *ji, (*_graph)).first;
                   auto e_ik = edge(*ii, *ki, (*_graph)).first;
                   auto e_kj = edge(*ki, *ji, (*_graph)).first;
                   auto Old = (*_graph)[e_ij].weight;
                   auto New = meet((*_graph)[e_ij].weight,
                                   (join((*_graph)[e_ik].weight,
                                         (*_graph)[e_kj].weight)));
                   change |= (!(*Old <= *New && *New <= *Old));
                   (*_graph)[e_ij].weight = New;
                 }
               }
             }
           }
         }
         return change;
       }

       void canonical()
       {
         if (IsDistWeight)
           oneStep ();
         else
         {
           bool change = true;
           while (change)
             change = oneStep ();
         }
       }

       void insert_vertex (VertexName u, Weight val = Weight::top())
       {
         if (!is_bottom () && !find_vertex_map(u))
         {
           vector<VertexName> new_vertices;
           new_vertices.push_back(u);
           vector<edge_t> new_edges;
           vertex_iterator i, e;
           for (tie(i, e) = vertices(*_graph); i != e; ++i)
           {
             VertexNamePtr v = (*_graph)[*i].name;
             // add two edges in both directions
             new_edges.push_back(edge_t(u, *v, val));
             new_edges.push_back(edge_t(*v, u, val));
           }
           add(new_vertices, new_edges);
         }
       }

       template<typename Iterator>
       void insert_vertices(array_graph_t &g, Iterator begin, Iterator end)
       {
         for(;begin!=end;++begin)
           g.insert_vertex(begin->name());
       }

       // pre: caller must ensure the graph is in canonical form
       void set_incoming (const VertexName &v, const Weight &weight)
       {
         if (!is_bottom ())
         {
           vertex_descriptor_t u = lookup_vertex_map(v);
           in_edge_iterator in_it, in_et;
           for (tie(in_it, in_et) = in_edges(u, *_graph); in_it != in_et; ++in_it)
             (*_graph)[*in_it].weight = WeightPtr( new Weight(weight));
         }
       }

       // pre: caller must ensure the graph is in canonical form
       void set_outgoing (const VertexName &v, const Weight &weight)
       {
         if (!is_bottom ())
         {
           vertex_descriptor_t u = lookup_vertex_map(v);
           out_edge_iterator out_it, out_et;
           for (tie(out_it, out_et) = out_edges(u, *_graph);
                out_it != out_et; ++out_it)
             (*_graph)[*out_it].weight = WeightPtr( new Weight(weight));
         }
       }

       struct binary_join_op{
         WeightPtr operator()(WeightPtr w1, WeightPtr w2) {
           return WeightPtr(new Weight(*w1 | *w2));
         }
       };

       struct binary_meet_op{
         WeightPtr operator()(WeightPtr w1, WeightPtr w2) {
           return WeightPtr(new Weight(*w1 & *w2));
         }
       };

       struct binary_widening_op{
         WeightPtr operator()(WeightPtr w1, WeightPtr w2) {
           return WeightPtr(new Weight(*w1 || *w2));
         }
       };

       struct binary_narrowing_op{
         WeightPtr operator()(WeightPtr w1, WeightPtr w2) {
           return WeightPtr(new Weight(*w1 && *w2));
         }
       };

       template<typename Op>
       void pointwise_binop_helper (array_graph_t &g1,
                                    const array_graph_t &g2)
       {
         // pre: g1 and g2 have the same adjacency structure
         edge_iterator it_1, et_1;
         for(tie(it_1,et_1) = edges(*g1._graph); it_1 != et_1; ++it_1)
         {
           edge_descriptor_t e_1   = *it_1;
           vertex_descriptor_t u_1 = source(e_1, *g1._graph);
           vertex_descriptor_t v_1 = target(e_1, *g1._graph);
           VertexNamePtr u_name_1  = (*g1._graph)[u_1].name;
           VertexNamePtr v_name_1  = (*g1._graph)[v_1].name;
           WeightPtr     weight_1  = (*g1._graph)[e_1].weight;
           vertex_descriptor_t u_2 = g2.lookup_vertex_map(*u_name_1);
           vertex_descriptor_t v_2 = g2.lookup_vertex_map(*v_name_1);
           if (edge(u_2, v_2, *g2._graph).second)
           {
             Op op;
             edge_descriptor_t e_2 = edge(u_2, v_2, *g2._graph).first;
             (*g1._graph)[e_1].weight =
                 op((*g1._graph)[e_1].weight, (*g2._graph)[e_2].weight);
           }
           else
             CRAB_ERROR("unreachable");
         }
       }

       template<typename Op>
       array_graph_t pointwise_binop (array_graph_t g1,
                                      array_graph_t g2)
       {
         g1.canonical();
         g2.canonical();

         // if (*(g1._vertices_set) != *(g2._vertices_set)){
         //   set<VertexName> all_vs;
         //   set_union(g1._vertices_set->begin(), g1._vertices_set->end(),
         //             g2._vertices_set->begin(), g2._vertices_set->end(), inserter(all_vs, all_vs.end()));
         //   vector<VertexName> new_g1, new_g2;
         //   set_difference(all_vs.begin(), all_vs.end(),
         //                  g1._vertices_set->begin(), g1._vertices_set->end(), inserter(new_g1, new_g1.end()));
         //   set_difference(all_vs.begin(), all_vs.end(),
         //                  g2._vertices_set->begin(), g2._vertices_set->end(), inserter(new_g2, new_g2.end()));
         //   insert_vertices<typename vector<VertexName>::iterator>(g1, new_g1.begin(), new_g1.end());
         //   insert_vertices<typename vector<VertexName>::iterator>(g2, new_g2.begin(), new_g2.end());
         // }

         // pre: g1 and g2 have the same set of vertices and edges at this
         // point
         pointwise_binop_helper<Op>(g1,g2);
         return g1;
       }

       array_graph(bool is_bot):
           _is_bottom(is_bot),
           _graph(new graph_t(0)),
           _vertex_map(new vertex_map_t()),
           _vertices_set(new vertex_names_set_t())
       {  }

      public:

       static array_graph_t bottom()  { return array_graph(true); }

       static array_graph_t top() { return array_graph(false); }

       // Deep copy of the array graph
       array_graph(const array_graph_t &other):
           writeable(),
           _is_bottom(other._is_bottom),
           //_graph(new graph_t(*other._graph)),
           //_vertex_map(new vertex_map_t(*other._vertex_map)),
           //_vertices_set( new vertex_names_set_t(*other._vertices_set))
           _graph (new graph_t(0)),
           _vertex_map (new vertex_map_t()),
           _vertices_set (new vertex_names_set_t())
       {
         crab::CrabStats::count ("Domain.count.copy");
         crab::ScopedCrabStats __st__("Domain.copy");

         if (!is_bottom ())
         {
           // copy vertices, _vertex_map and _vertices_set
           vertex_iterator i, e;
           for (tie(i, e) = vertices(*other._graph); i != e; ++i)
           {
             vertex_descriptor_t u  = add_vertex(*_graph);
             VertexName u_name = *((*other._graph)[*i].name);
             (*_graph)[u].name = VertexNamePtr(new VertexName(u_name));
             insert_vertex_map(u_name, u);
           }

           // copy edges
           edge_iterator ie, ee;
           for(tie(ie,ee) = edges(*other._graph); ie != ee; ++ie)
           {
             VertexName u = *((*other._graph)[source(*ie, *other._graph)].name);
             VertexName v = *((*other._graph)[target(*ie, *other._graph)].name);
             Weight     w = *((*other._graph)[*ie].weight);
             vertex_descriptor_t _u = lookup_vertex_map(u);
             vertex_descriptor_t _v = lookup_vertex_map(v);
             edge_descriptor_t _e; bool b;
             boost::tie(_e,b) = add_edge(_u, _v, *_graph);
             (*_graph)[_e].weight = WeightPtr(new Weight(w));
           }
         }
       }

       array_graph_t& operator=(const array_graph_t &other)
       {
         crab::CrabStats::count ("Domain.count.copy");
         crab::ScopedCrabStats __st__("Domain.copy");
         if (this != &other)
         {
           _is_bottom      = other._is_bottom;
           _graph          = other._graph;
           _vertex_map     = other._vertex_map;
           _vertices_set   = other._vertices_set;
         }
         return *this;
       }

       bool is_bottom() { return this->_is_bottom; }

       bool is_top()
       {
         if (this->is_bottom())
           return false;
         else
         {
           // FIXME: speedup this operation
           canonical();
           edge_iterator it, et;
           for(tie(it,et) = edges(*_graph); it != et; ++it)
           {
             edge_descriptor_t e = *it;
             if (!(*(*_graph)[e].weight).is_top ())
               return false;
           }
           return true;
         }
       }

       void reduce(ScalarNumDomain scalar)
       {
         if (is_bottom ()) return;

         canonical();

         edge_iterator it, et;
         for(tie(it,et) = edges(*_graph); it != et; ++it)
         {
           edge_descriptor_t e   = *it;
           VertexNamePtr u = (*_graph)[source(e, *_graph)].name;
           VertexNamePtr v = (*_graph)[target(e, *_graph)].name;
           ScalarNumDomain tmp(scalar);
           linear_constraint_t cst ( variable_t(*u) <= variable_t(*v) - 1);
           tmp += cst;
           if (tmp.is_bottom())
             (*_graph)[e].weight = WeightPtr(new Weight(Weight::bottom()));
         }

         canonical();
       }

       // Point-wise application of <= in the weight domain
       bool operator <=(array_graph_t other)
       {
         if (is_bottom())
           return true;
         else if (other.is_bottom())
           return false;
         else
         {
           canonical();
           other.canonical();
           edge_iterator it_1, et_1;
           edge_iterator it_2, et_2;
           for(tie(it_1,et_1) = edges(*_graph); it_1 != et_1; ++it_1)
           {
             edge_descriptor_t e_1 = *it_1;
             vertex_descriptor_t u_1 = source(e_1, *_graph);
             vertex_descriptor_t v_1 = target(e_1, *_graph);
             VertexNamePtr u_name_1 = (*_graph)[u_1].name;
             VertexNamePtr v_name_1 = (*_graph)[v_1].name;
             WeightPtr     weight_1 = (*_graph)[e_1].weight;
             vertex_descriptor_t u_2 = other.lookup_vertex_map(*u_name_1);
             vertex_descriptor_t v_2 = other.lookup_vertex_map(*v_name_1);
             if (edge(u_2,v_2, *other._graph).second)
             {
               edge_descriptor_t e_2 = edge(u_2,v_2, *other._graph).first;
               WeightPtr weight_2 = (*other._graph)[e_2].weight;
               if (!(*weight_1 <= *weight_2))
                 return false;
             }
             else
               CRAB_ERROR ("operator<= with graphs with different adjacency structure");
           }
           return true;
         }
       }

       bool operator==(array_graph_t other)
       {
         if (is_bottom()) return other.is_bottom();
         else
           return (*(_vertices_set) == *(other._vertices_set) &&
                   ( *this <= other && other <= *this));
       }

       void operator-=(VertexName v)
       {
         if (!is_bottom ())
           remove (v);
       }

       void operator|=(array_graph_t other) {
         *this = *this | other;
       }

       // Point-wise join in the weight domain
       array_graph_t operator|(array_graph_t other)
       {
         if (is_bottom())
           return other;
         else if (other.is_bottom())
           return *this;
         else
           return pointwise_binop<binary_join_op>(*this, other);
       }

       // Point-wise widening in the weight domain
       array_graph_t operator||(array_graph_t other)
       {
         if (is_bottom())
           return other;
         else if (other.is_bottom())
           return *this;
         else
           return pointwise_binop<binary_widening_op>(*this, other);
       }

       // Point-wise meet in the weight domain
       array_graph_t operator&(array_graph_t other)
       {
         if (this->is_bottom())
           return *this;
         else if (other.is_bottom())
           return other;
         else {
           return pointwise_binop<binary_meet_op>(*this, other);
         }
       }

       // Point-wise narrowing in the weight domain
       array_graph_t operator&&(array_graph_t other)
       {
         if (this->is_bottom())
           return *this;
         else if (other.is_bottom())
           return other;
         else {
           return pointwise_binop<binary_narrowing_op>(*this, other);
         }
       }

       void meet_weight (const VertexName &src, const VertexName &dest,
                         Weight weight)
       {
         if (find_vertex_map(src) && find_vertex_map(dest))
         {
           vertex_descriptor_t u = lookup_vertex_map(src);
           vertex_descriptor_t v = lookup_vertex_map(dest);
           if (edge(u,v,*_graph).second) {
             edge_descriptor_t e = edge(u,v,*_graph).first;
             Weight meet = weight & (*(*_graph)[e].weight);
             (*_graph)[e].weight = WeightPtr(new Weight(meet));
           }
           else {
             vector<VertexName> vertices;
             vector<edge_t>     edges;
             edges.push_back(edge_t(src,dest,weight));
             add(vertices,edges);
           }
         }
       }

       void set_weight (const VertexName &src, const VertexName &dest,
                        Weight weight)
       {
         if (find_vertex_map(src) && find_vertex_map(dest))
         {
           vertex_descriptor_t u = lookup_vertex_map(src);
           vertex_descriptor_t v = lookup_vertex_map(dest);
           if (edge(u,v,*_graph).second) {
             edge_descriptor_t e = edge(u,v,*_graph).first;
             (*_graph)[e].weight = WeightPtr(new Weight(weight));
           }
           else {
             vector<VertexName> vertices;
             vector<edge_t>     edges;
             edges.push_back(edge_t(src,dest,weight));
             add(vertices,edges);
           }
         }
       }

       Weight& get_weight (const VertexName &src, const VertexName &dest)
       {
         if (find_vertex_map(src) && find_vertex_map(dest))
         {
           vertex_descriptor_t u = lookup_vertex_map(src);
           vertex_descriptor_t v = lookup_vertex_map(dest);
           if (edge(u,v,*_graph).second)
           {
             edge_descriptor_t e = edge(u,v,*_graph).first;
             return *((*_graph)[e].weight);
           }
         }
         CRAB_ERROR ("No edge found with given vertices");
       }

       void write(crab_os& o)
       {
         if (is_bottom())
           o << "_|_";
         else
         {
           {
             vertex_iterator it, et;
             o << "(V={";
             for (tie(it, et) = vertices(*_graph); it != et; ++it){
               vertex_descriptor_t u = *it;
               VertexNamePtr u_name  = (*_graph)[u].name;
               o << *u_name << " ";
             }
             o << "},";
           }
           {
             edge_iterator it, et;
             o << "E={";
             for(tie(it,et) = edges(*_graph); it!= et; ++it)
             {
               edge_descriptor_t e   = *it;
               vertex_descriptor_t u = source(e, *_graph);
               vertex_descriptor_t v = target(e, *_graph);
               VertexNamePtr u_name = (*_graph)[u].name;
               VertexNamePtr v_name = (*_graph)[v].name;
               WeightPtr     weight = (*_graph)[e].weight;
               if (!weight->is_bottom())
                 o << "(" << *u_name << "," << *v_name << "," << *weight << ") ";
             }
             o << "})";
           }
         }
       }
     }; // end class array_graph

    /*
      Reduced product of a scalar numerical domain with a weighted array
      graph.
    */
    template<typename ScalarNumDomain, typename WeightDomain, bool IsDistWeight = false>
    class array_graph_domain:
        public writeable,
        public numerical_domain<typename ScalarNumDomain::number_t,
                                typename ScalarNumDomain::varname_t>,
        public bitwise_operators<typename ScalarNumDomain::number_t,
                                 typename ScalarNumDomain::varname_t>,
        public division_operators<typename ScalarNumDomain::number_t,
                                  typename ScalarNumDomain::varname_t>,
        public array_operators<typename ScalarNumDomain::number_t,
                               typename ScalarNumDomain::varname_t >,
        public pointer_operators<typename ScalarNumDomain::number_t,
                                 typename ScalarNumDomain::varname_t > {

      template<typename Key, typename Value>
      class merge_op_check_equal: public patricia_tree< Key, Value >::binary_op_t {
        boost::optional< Value > apply(Value x, Value y) {
          if (x == y) return x;
          else
            CRAB_ERROR("merging a key with two different values");
        };
        bool default_is_absorbing() { return false; }
      };

      template<typename Key, typename Value>
      class merge_op_first: public patricia_tree< Key, Value >::binary_op_t {
        boost::optional< Value > apply(Value x, Value y)  {
          return x;
        };
        bool default_is_absorbing() { return false; }
      };

      template<typename Key, typename Value>
      class merge_op_second: public patricia_tree< Key, Value >::binary_op_t {
        boost::optional< Value > apply(Value x, Value y)  {
          return y;
        };
        bool default_is_absorbing() { return false; }
      };

     template < typename Key, typename Value,
                typename MergeOp = merge_op_check_equal <Key, Value> >
     class mergeable_map: public writeable {

      private:
       typedef patricia_tree< Key, Value > patricia_tree_t;
       typedef typename patricia_tree_t::binary_op_t binary_op_t;

      public:
       typedef mergeable_map< Key, Value > mergeable_map_t;
       typedef typename patricia_tree_t::iterator iterator;

      private:
       patricia_tree_t _tree;

       static patricia_tree_t do_union(patricia_tree_t t1, patricia_tree_t t2) {
         MergeOp o;
         t1.merge_with(t2, o);
         return t1;
       }

       mergeable_map(patricia_tree_t t): _tree(t) { }

      public:

       mergeable_map(): _tree(patricia_tree_t()) { }

       mergeable_map(const mergeable_map_t& e): writeable(), _tree(e._tree) { }

       mergeable_map_t& operator=(mergeable_map_t e) {
         _tree = e._tree;
         return *this;
       }

       iterator begin() { return _tree.begin(); }

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

       std::size_t size(){ return _tree.size(); }

       void set(Key k, Value v) { _tree.insert(k, v); }

       mergeable_map_t& operator-=(Key k) {
         _tree.remove(k);
         return *this;
       }
       mergeable_map_t operator|(mergeable_map_t e) {
         mergeable_map_t u(do_union(_tree, e._tree));
         return u;
       }

       boost::optional<Value> operator[](Key k) { return _tree.lookup(k); }
       void clear() { _tree = patricia_tree_t(); }

       void write(crab_os& o) {
         o << "{";
         for (auto it = _tree.begin(); it != _tree.end(); ) {
           Key k = it->first;
           k.write(o);
           o << " -> ";
           Value v = it->second;
           o << v;
           ++it;
           if (it != _tree.end()) {
             o << "; ";
           }
         }
         o << "}";
       }
     }; // class mergeable_map

     public:
      typedef typename ScalarNumDomain::number_t Number;
      typedef typename ScalarNumDomain::varname_t VariableName;

      // WARNING: assumes ScalarNumDomain::number_t = WeightDomain::number_t and
      //                  ScalarNumDomain::varname_t = WeightDomain::varname_t
      using typename numerical_domain< Number, VariableName>::linear_expression_t;
      using typename numerical_domain< Number, VariableName>::linear_constraint_t;
      using typename numerical_domain< Number, VariableName>::linear_constraint_system_t;
      using typename numerical_domain< Number, VariableName>::variable_t;
      using typename numerical_domain< Number, VariableName>::number_t;
      using typename numerical_domain< Number, VariableName>::varname_t;
      typedef WeightDomain content_domain_t;
      typedef ScalarNumDomain index_domain_t;

     private:
      typedef array_graph< VariableName,WeightDomain,ScalarNumDomain,IsDistWeight> array_graph_t;
      typedef array_graph_domain<ScalarNumDomain,WeightDomain,IsDistWeight> array_graph_domain_t;

      typedef mergeable_map<VariableName,VariableName> succ_index_map_t;
      typedef boost::shared_ptr< succ_index_map_t > succ_index_map_ptr;

      bool  _is_bottom;
      ScalarNumDomain _scalar;
      array_graph_t _g;
      // for each array index i we keep track of a special index that
      // represent i+1
      succ_index_map_ptr _succ_idx_map;

      void abstract (VariableName v)
      {
        if (_g.find_vertex_map(v))
        {
          _g.set_incoming(v , WeightDomain::top());
          _g.set_outgoing(v , WeightDomain::top());
          optional<VariableName> succ_v = get_succ_idx(v);
          if (succ_v)
          {
            _g.set_incoming(*succ_v, WeightDomain::top());
            _g.set_outgoing(*succ_v, WeightDomain::top());
          }
        }
      }

      optional<VariableName> get_succ_idx (VariableName v) const
      {
        return (*_succ_idx_map)[v];
      }

      template <typename VariableFactory>
      VariableName add_variable (Number n, VariableFactory &vfac)
      {
        // FIXME: really big assumption that the variable factory
        // understands strings. For instance, this is not true if the
        // factory is created by Crab-llvm.
        VariableName var_n = vfac["#C" + n.get_str()];

        if (n >= 0)
        {
          _g.insert_vertex(var_n);
          _scalar.assign(var_n, n);
        }
        return var_n;
      }

      void add_variable (VariableName v)
      {
        if (is_array_index(v))
        {
          /*assign to v_succ a fresh var must be always the same*/
          VariableName v_succ = v.get_var_factory().get (v.index());

          _g.insert_vertex (v);
          _g.insert_vertex (v_succ);
          _succ_idx_map->set (v, v_succ);

          /// FIXME: assume that the array element size is 1.

          // --- Enforce: i+ == i+1
          _scalar += linear_constraint_t( variable_t (v_succ) == variable_t(v) + 1);
          // needed if scalar domain is non-relational:
          _g.set_weight (v_succ,v,WeightDomain::bottom ());
        }
      }

      void meet_weight (VariableName i, VariableName j, WeightDomain w)
      {
        add_variable (i);
        add_variable (j);
        _g.meet_weight (i,j,w);
        reduce();
      }

      template <typename VariableFactory>
      void meet_weight (Number i, Number j, WeightDomain w, VariableFactory &vfac)
      {
        _g.meet_weight (add_variable (i, vfac),
                        add_variable (j, vfac) ,
                        w);
        reduce();
      }

      void meet_weight (Number i, VariableName j, WeightDomain w)
      {
        add_variable (j);
        _g.meet_weight (add_variable(i, j.get_var_factory ()),j,w);
        reduce();
      }

      void meet_weight (VariableName i, Number j, WeightDomain w)
      {
        add_variable(i);
        _g.meet_weight(i,add_variable(j, i.get_var_factory ()),w);
        reduce();
      }

      bool IsDefiniteOne (Number x)  {
        return x == Number (1);
      }

      bool IsDefiniteOne (VariableName x)  {
        auto n = _scalar [x].singleton ();
        if (n) return *n == Number (1);
        else return false;
      }

      // x := x op k
      // Most of the magic happens here.
      template<typename VarNum>
      void apply_helper (operation_t op, VariableName x, VarNum k)
      {
        if (is_bottom()) return;

        /// step 1: add x_old in the graph
        VariableName x_old = x.get_var_factory ().get (); /*fresh var*/
        VariableName x_old_succ = x.get_var_factory ().get (); /*fresh var*/
        _g.insert_vertex(x_old);
        _g.insert_vertex(x_old_succ);
        _succ_idx_map->set(x_old, x_old_succ);

        /// --- Enforce the following relationships:
        ///     { x_old = x, x_old+ = x+, x_old+ = x_old + 1}

        /// x_old = x
        _scalar.assign(x_old, linear_expression_t(x));
        // needed if scalar domain is non-relational:
        // enforcing x_old = x
        _g.set_weight (x_old,x,WeightDomain::bottom ());
        _g.set_weight (x,x_old,WeightDomain::bottom ());

        /// x_old+ = x_old +1
        _scalar += linear_constraint_t(variable_t(x_old_succ) == variable_t(x_old) + 1);
        // needed if scalar domain is non-relational:
        // enforcing x_old+ < x_old is false
        _g.set_weight (x_old_succ,x_old,WeightDomain::bottom ());
        // enforcing x_old+ < x is false
        _g.set_weight (x_old_succ,x,WeightDomain::bottom ());

        /// x_old+ = x+
        optional<VariableName> x_succ = get_succ_idx(x);
        if (x_succ) {
          _scalar += linear_constraint_t( variable_t(x_old_succ) == variable_t(*x_succ));
          // needed if scalar domain is non-relational:
          // enforcing x_old + = x+
          _g.set_weight (x_old_succ,*x_succ,WeightDomain::bottom ());
          _g.set_weight (*x_succ,x_old_succ,WeightDomain::bottom ());
          // enforcing x+ < x_old is false
          _g.set_weight (*x_succ,x_old,WeightDomain::bottom ());
        }
        // propagate the scalar constraints to the graph
        reduce();

        /// step 2: abstract all incoming/outgoing edges of x
        abstract(x);

        /// step 3: update the graph with the scalar domain after applying
        ///         x = x op k.
        _scalar.apply(op, x, x, k);

#if 1
        // This is not needed at all if the scalar domain is relational.
        // Otherwise, we would like to keep the relationship between
        // x_old+ and x. We do it in a completely adhoc way but at least
        // we cover common cases when the array is traversed forward or
        // backwards one element by one.
        if (op == OP_ADDITION && IsDefiniteOne (k)) {
          _g.set_weight (x,x_old_succ,WeightDomain::bottom ());
          _g.set_weight (x_old_succ,x,WeightDomain::bottom ());
        }
        else if (op == OP_SUBTRACTION && IsDefiniteOne (k)) {
          _g.set_weight (x_old,*x_succ,WeightDomain::bottom ());
          _g.set_weight (*x_succ,x_old,WeightDomain::bottom ());
        }
#endif

        if (x_succ){
          _scalar -= *x_succ;
          /// --- Enforce x+ == x + 1
          _scalar += linear_constraint_t(variable_t(*x_succ) == variable_t(x) + 1);
          // needed if scalar domain is non-relational:
          _g.set_weight (*x_succ,x,WeightDomain::bottom ());
        }

        /* { x = x op k, x+ = x+1} */
        reduce();

        /// step 4: delete x_old
        _g -= x_old;
        _g -= x_old_succ;
        (*_succ_idx_map) -= x_old;
        _scalar -= x_old;
        _scalar -= x_old_succ;

        //this->reduce();
      }

      //! In case we can statically determine which variables should be
      //  considered array indexes. Note that any subset is sound but it
      //  might be imprecise. By default we consider all.
      bool is_array_index(VariableName v) const {
        return true;
      }

      // model array reads: return the weight from the edge i to i+
      WeightDomain array_read (VariableName i)
      {
        if (is_bottom())
          return WeightDomain::bottom();

        if (!is_array_index(i))
          return WeightDomain::top();

        //this->reduce();
        optional<VariableName> i_succ = get_succ_idx(i);
        if (i_succ)
          return _g.get_weight(i, *i_succ);

        CRAB_ERROR ("There is no successor index associated with ",i);
      }

      // model array writes
      void array_write (VariableName arr, VariableName i, WeightDomain w)
      {
        if (is_bottom()) return;
        //this->reduce();

        //--- strong update
        optional<VariableName> i_succ = get_succ_idx(i);
        if (!i_succ)
          CRAB_ERROR ("There is no successor index associated with ",i);

        WeightDomain& old_w = _g.get_weight(i, *i_succ);
        old_w -= arr;
        _g.meet_weight(i, *i_succ, w);
        WeightDomain new_w = _g.get_weight(i, *i_succ);

        //--- weak update:
        // An edge (p,q) must be weakened if p <= i <= q and p < q
        typename array_graph_t::edge_iterator it, et;
        for(tie(it,et) = edges(*_g._graph); it!= et; ++it)
        {
          typename array_graph_t::edge_descriptor_t e = *it;
          typename array_graph_t::VertexNamePtr  p = (*_g._graph)[source(e, *_g._graph)].name;
          typename array_graph_t::VertexNamePtr  q = (*_g._graph)[target(e, *_g._graph)].name;
          typename array_graph_t::WeightPtr weight = (*_g._graph)[e].weight;
          if ( ((*p == i) &&  (*q == *i_succ)) || weight->is_bottom())
            continue;
          // p < q
          ScalarNumDomain tmp(_scalar);
          tmp += linear_constraint_t( variable_t(*p) <= variable_t(i));
          tmp += linear_constraint_t( variable_t(*i_succ)  <= variable_t(*q));
          if (tmp.is_bottom())
            continue;
          // p <= i <= q and p < q
          typename array_graph_t::binary_join_op join;
          (*_g._graph)[e].weight = join (weight,
                                         typename array_graph_t::WeightPtr (new WeightDomain(new_w)));
        }
        _g.canonical();

      }

      void set_to_bottom()
      {
        _is_bottom = true;
        _scalar = ScalarNumDomain::bottom();
        _g = array_graph_t::bottom();
        _succ_idx_map->clear();
      }

      array_graph_domain(ScalarNumDomain scalar,
                         array_graph_t g,
                         succ_index_map_ptr map):
          writeable(),
          _is_bottom(false),
          _scalar(scalar),
          _g(g),
          _succ_idx_map(new succ_index_map_t(*map))
      {
        if (_scalar.is_bottom() || _g.is_bottom())
          set_to_bottom();
        else
          reduce();
      }

     public:

      array_graph_domain():
          writeable(),
          _is_bottom(false),
          _scalar(ScalarNumDomain::top()),
          _g(array_graph_t::top()),
          _succ_idx_map(new succ_index_map_t())
      { }

      static array_graph_domain_t top()
      {
        return array_graph_domain(ScalarNumDomain::top(),
                                  array_graph_t::top(),
                                  succ_index_map_ptr(new succ_index_map_t()));
      }

      static array_graph_domain_t bottom()
      {
        return array_graph_domain(ScalarNumDomain::bottom(),
                                  array_graph_t::bottom(),
                                  succ_index_map_ptr(new succ_index_map_t()));
      }

      array_graph_domain(const array_graph_domain_t& other):
          writeable(),
          _is_bottom(other._is_bottom),
          _scalar(other._scalar) ,
          _g(other._g),
          _succ_idx_map(new succ_index_map_t(*other._succ_idx_map))
      {
        crab::CrabStats::count (getDomainName() + ".count.copy");
        crab::ScopedCrabStats __st__(getDomainName() + ".copy");
      }

      array_graph_domain_t& operator=(array_graph_domain_t other)
      {
        crab::CrabStats::count (getDomainName() + ".count.copy");
        crab::ScopedCrabStats __st__(getDomainName() + ".copy");

        if (this != &other)
        {
          this->_is_bottom = other._is_bottom;
          this->_scalar = other._scalar;
          this->_g = other._g;
          this->_succ_idx_map = other._succ_idx_map;
        }
        return *this;
      }

      bool is_bottom() { return _is_bottom; }

      // --- top operation in the graph is expensive because we need to
      //     traverse the whole graph and check each edge.
      // bool is_top() { return (_scalar.is_top() && _g.is_top()); }

      bool is_top() { return (_scalar.is_top()); }

      void reduce ()
      {
        crab::CrabStats::count (getDomainName() + ".count.reduce");
        crab::ScopedCrabStats __st__(getDomainName() + ".reduce");

        if (is_bottom ()) return;

        domain_traits<ScalarNumDomain>::normalize(_scalar);

        if (_scalar.is_bottom() || _g.is_bottom())
          set_to_bottom();
        else
          _g.reduce(_scalar);
      }

      bool operator<=(array_graph_domain_t other)
      {
        crab::CrabStats::count (getDomainName() + ".count.leq");
        crab::ScopedCrabStats __st__(getDomainName() + ".leq");

        if (is_bottom ()) {
          return true;
        } else if (other.is_bottom ()) {
          return false;
        } else {
          return (_scalar <= other._scalar && _g <= other._g);
        }
      }

      void operator|=(array_graph_domain_t other)  {
        *this = *this | other;
      }

      array_graph_domain_t operator|(array_graph_domain_t other)
      {
        crab::CrabStats::count (getDomainName() + ".count.join");
        crab::ScopedCrabStats __st__(getDomainName() + ".join");

        if (is_bottom ()) {
          return other;
        } else if (other.is_bottom ()) {
          return *this;
        } else {
          succ_index_map_ptr map(new succ_index_map_t(*(_succ_idx_map) |
                                                      *(other._succ_idx_map)));
          return array_graph_domain_t(_scalar | other._scalar,
                                      _g | other._g, map);
        }
      }

      array_graph_domain_t operator&(array_graph_domain_t other)
      {
        crab::CrabStats::count (getDomainName() + ".count.meet");
        crab::ScopedCrabStats __st__(getDomainName() + ".meet");

        if (is_bottom () || other.is_bottom ()) {
          return bottom();
        } else {
          succ_index_map_ptr map(new succ_index_map_t(*(_succ_idx_map) |
                                                      *(other._succ_idx_map)));
          return array_graph_domain_t(_scalar & other._scalar,
                                      _g & other._g, map);
        }
      }

      array_graph_domain_t operator||(array_graph_domain_t other)
      {
        crab::CrabStats::count (getDomainName() + ".count.widening");
        crab::ScopedCrabStats __st__(getDomainName() + ".widening");

        if (is_bottom ())  return other;
        else if (other.is_bottom ())  return *this;
        else
        {
          succ_index_map_ptr map (new succ_index_map_t(*(_succ_idx_map) |
                                                       *(other._succ_idx_map)));
          array_graph_domain_t widen (_scalar || other._scalar,
                                      _g || other._g, map);
          CRAB_LOG("array-graph" , crab::outs() << "Widening: " << *this<<"\n";);
          return widen;
        }
      }

      template<typename Thresholds>
      array_graph_domain_t widening_thresholds (array_graph_domain_t other,
                                                const Thresholds & /*ts*/) {
        return (*this || other);
      }

      array_graph_domain_t operator&& (array_graph_domain_t other)
      {
        crab::CrabStats::count (getDomainName() + ".count.narrowing");
        crab::ScopedCrabStats __st__(getDomainName() + ".narrowing");

        if (is_bottom ()|| other.is_bottom ())
          return bottom();
        else
        {
          succ_index_map_ptr map (new succ_index_map_t(*(_succ_idx_map) |
                                                       *(other._succ_idx_map)));
          return array_graph_domain_t (_scalar && other._scalar,
                                       _g && other._g, map);
        }
      }

      void operator-=(VariableName var)
      {
        crab::CrabStats::count (getDomainName() + ".count.forget");
        crab::ScopedCrabStats __st__(getDomainName() + ".forget");

        if (is_bottom ()) return;

        // scalar domain
        _scalar -= var;
        _g -= var;
        optional<VariableName> var_succ = get_succ_idx(var);
        if (var_succ) {
          _scalar -= *var_succ;
          _g -= *var_succ;
          (*_succ_idx_map) -= var;
        }

        // graph domain
        typename array_graph_t::edge_iterator it, et;
        for(tie(it,et) = edges(*(_g._graph)); it!= et; ++it) {
          auto e  = *it;
          auto weight = (*(_g._graph))[e].weight;
          (*weight) -= var;
        }
        // this->reduce();
      }

      /////
      // Transfer functions
      /////

      void operator += (linear_constraint_system_t csts)
      {
        crab::CrabStats::count (getDomainName() + ".count.add_constraints");
        crab::ScopedCrabStats __st__(getDomainName() + ".add_constraints");

        if (is_bottom()) return;

        // graph domain: make sure that all the relevant variables
        // (included special "0") are inserted in the graph
        for (auto cst : csts) {
          // TODO
          //Number n = cst.expression().constant();
          //if (n == 0) add_variable(n, vfac);
          for (auto v : cst.variables())
            add_variable (v.name());
        }

        _scalar += csts;
        reduce();

        CRAB_LOG("array-graph",
                 crab::outs() << "Assume("<< csts<< ") --- "<< *this<<"\n";);
      }

      void assign (VariableName x, linear_expression_t e)
      {
        crab::CrabStats::count (getDomainName() + ".count.assign");
        crab::ScopedCrabStats __st__(getDomainName() + ".assign");

        if (is_bottom()) return;

        if (optional<variable_t> y = e.get_variable())
        {
          if ((*y).name() == x) return;
        }

        // scalar domain
        _scalar.assign(x, e);

        // graph domain
        if (e.is_constant() && (e.constant() == 0))
          add_variable (e.constant(), x.get_var_factory ());

        if (_g.find_vertex_map(x))
        {
          abstract(x);
          // wrong results if we do not restore the relationship between x
          // and x+ in the scalar domain
          optional<VariableName> x_succ = get_succ_idx(x);
          if (x_succ){
            _scalar -= *x_succ;
            /// --- Enforce x+ == x+1
            _scalar += linear_constraint_t( variable_t(*x_succ) == variable_t(x) + 1);
            // needed if scalar domain is non-relational:
            _g.set_weight (*x_succ,x,WeightDomain::bottom ());
          }
        }
        else
          add_variable(x);

        reduce();

        CRAB_LOG("array-graph",
                 crab::outs() << "Assign "<<x<<" := "<<e<<" ==> "<<*this<<"\n";);
      }

      void apply (operation_t op, VariableName x, VariableName y, Number z)
      {
        crab::CrabStats::count (getDomainName() + ".count.apply");
        crab::ScopedCrabStats __st__(getDomainName() + ".apply");

        assign (x, linear_expression_t(y));
        apply_helper<Number> (op, x, z);

        CRAB_LOG("array-graph",
                 crab::outs() << "Apply "<<x<<" := "<<y<<" "<<op<<" "<<z<<" ==> "<<*this<<"\n";);
      }

      void apply(operation_t op, VariableName x, VariableName y, VariableName z)
      {
        crab::CrabStats::count (getDomainName() + ".count.apply");
        crab::ScopedCrabStats __st__(getDomainName() + ".apply");

        assign (x, linear_expression_t(y));
        apply_helper<VariableName> (op, x, z);

        CRAB_LOG("array-graph",
                 crab::outs() << "Apply "<<x<<" := "<<y<<" "<<op<<" "<<z<<" ==> "<<*this<<"\n";);
      }

      void apply(operation_t op, VariableName x, Number k)
      {
        crab::CrabStats::count (getDomainName() + ".count.apply");
        crab::ScopedCrabStats __st__(getDomainName() + ".apply");

        apply_helper<Number> (op, x, k);

        CRAB_LOG("array-graph",
                 crab::outs() << "Apply "<<x<<" := "<<x<<" "<<op<<" "<<k<<" ==> "<<*this<<"\n";);
      }

      // bitwise_operators_api
      void apply(conv_operation_t op, VariableName x, VariableName y, unsigned width) {
        assign (x, variable_t (y));
      }

      void apply(conv_operation_t op, VariableName x, Number k, unsigned width) {
        assign (x, k);
      }

      void apply(bitwise_operation_t op, VariableName x, VariableName y, VariableName z) {
        CRAB_WARN ("bitwise operations not implemented in array_graph");
      }

      void apply(bitwise_operation_t op, VariableName x, VariableName y, Number k) {
        CRAB_WARN ("bitwise operations not implemented in array_graph");
      }

      // division_operators_api
      void apply(div_operation_t op, VariableName x, VariableName y, VariableName z) {
        CRAB_WARN ("division operations not implemented in array_graph");
      }

      void apply(div_operation_t op, VariableName x, VariableName y, Number k) {
        CRAB_WARN ("division operations not implemented in array_graph");
      }

      // array_operators_api

      virtual void array_init (VariableName a, const vector<ikos::z_number> &values)  override {
        CRAB_WARN ("array_graph_domain init not implemented");
      }

      virtual void array_assume (VariableName a,
                                 boost::optional<Number> lb, boost::optional<Number> ub) override {
        CRAB_WARN ("array_graph_domain assume not implemented");
      }

      virtual void array_load (VariableName lhs, VariableName arr, VariableName idx,
                               z_number /*bytes*/) override {
        crab::CrabStats::count (getDomainName() + ".count.load");
        crab::ScopedCrabStats __st__(getDomainName() + ".load");

        WeightDomain w = array_read (idx);
        // --- Simplification wrt Gange et.al.:
        //     Only non-relational invariants are passed from the graph
        //     domain to the scalar domain.
        //     We use operator[] as the conversion function
        _scalar.set (lhs, w [arr]);

        CRAB_LOG("array-graph",
                 crab::outs() << "Array read "<<lhs<<" := "<< arr<<"["<<idx<<"] ==> "
                           << *this <<"\n";);
      }

      virtual void array_store (VariableName arr, VariableName idx, linear_expression_t val,
                                 z_number /*n_bytes*/, bool /*is_singleton*/) override {

        crab::CrabStats::count (getDomainName() + ".count.store");
        crab::ScopedCrabStats __st__(getDomainName() + ".store");

        // --- Simplification wrt Gange et.al.:
        //     Only non-relational invariants are passed from the scalar
        //     domain to the graph domain.
        //     We use operator[] as the conversion function
        WeightDomain w = WeightDomain::top ();
        if (val.is_constant ())
          w.assign (arr, val);
        else if (auto v = val.get_variable ()){
          w.set (arr, _scalar[(*v).name()]);
        }
        else {
          // If you see this warning you can switch to intervals.
          crab::outs() << "Warning: scalar domain does not support assignments with arbitrary rhs.\n";
        }
        array_write (arr, idx, w);

        CRAB_LOG("array-graph",
                 crab::outs() << "Array write "<<arr<<"["<<idx<<"] := "<<val<< " ==> "<< *this <<"\n";);
      }

      void write(crab_os& o)
      {
        o << "(" ;
#if 1
        // less verbose: remove the special variables i+ from the scalar
        // domain
        ScalarNumDomain inv (_scalar);
        for(auto p : *_succ_idx_map) {
          inv -= p.second;
        }
        o << inv;
#else
        o << _scalar;
#endif
        o << "," << _g;
        o << ")";
      }

      linear_constraint_system_t to_linear_constraint_system (){
        CRAB_ERROR ("array_graph: to_linear_constraint_system not implemented");
      }

      static string getDomainName () {
        string name ("ArrayGraph(" +
                     ScalarNumDomain::getDomainName () +  "," +
                     WeightDomain::getDomainName () +
                     ")");
        return name;
      }

    }; // end array_graph_domain

    }// namespace domain_traits

} // namespace crab

#endif


#include <itkImage.h>
#include <itkImageFileReader.h>
#include <itkImageFileWriter.h>
#include <itkImageRegionIteratorWithIndex.h>
#include <itkImageLinearIteratorWithIndex.h>
#include <itkImageRegionIterator.h>
#include <itkDiffusionTensor3D.h>

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/vector.hpp>

#include <boost/math/special_functions/ellint_rf.hpp>
#include <boost/math/special_functions/ellint_rd.hpp>

#include "tkdCmdParser.h"

#include <algorithm> // for sort
#include <cmath> // for pow and sqrt
#include <valarray> // for atan
#include <iostream>
#include <iterator>
#include <vector>

#include <vnl/vnl_vector.h>
#include <vnl/vnl_matrix.h>
#include <vnl/algo/vnl_symmetric_eigensystem.h>

#include "vnl_vector_to_std_vector.h"
#include "std_vector_to_vnl_vector.h"

namespace dki
{
	/**
	 * Calculate diffusion kurtosis maps from given input tensors.
	 *
	 * Modification to Tabesh et al: If eigenvalues are negative set them to 0!
	 *
	 */
	class DkiMaps
	{
	public:

		struct parameters
		{
			std::string inputFileName;
			std::string maskFileName;
			std::string outputFileName;
		};

		typedef double PixelType;

		typedef itk::Image< PixelType, 4 > ImageType;
		typedef itk::Image< PixelType, 3 > OutputImageType;
		typedef itk::ImageFileReader< OutputImageType > MaskReaderType;
		typedef itk::ImageFileReader< ImageType > ReaderType;
		typedef itk::ImageLinearConstIteratorWithIndex< ImageType > ConstIterator4DType;
		typedef itk::ImageLinearIteratorWithIndex< ImageType > Iterator4DType;
		typedef itk::ImageRegionIteratorWithIndex< OutputImageType > Iterator3DType;

		typedef itk::ImageRegionConstIteratorWithIndex< OutputImageType > ConstIterator3DType;
		typedef itk::ImageFileWriter< OutputImageType > WriterType;

		typedef vnl_vector< PixelType > VectorType;
		typedef vnl_matrix< PixelType > MatrixType;

		typedef std::pair< PixelType, VectorType > EigenType;
		typedef std::vector< EigenType > EigenContainerType;

		/**
		 * Constructor.
		 */
		DkiMaps( const parameters& args )
		{
			SetInput( args.inputFileName );
			InitMask( args.maskFileName );
			AllocateOutput();
			InitGlobalIndices();
			CalculateMaps();
			Write( args.outputFileName );
		}

	protected:

		ImageType::Pointer m_Input;
		OutputImageType::Pointer m_Mask;

		// dti maps

		OutputImageType::Pointer m_FA;
		OutputImageType::Pointer m_Trace;
		OutputImageType::Pointer m_Lradial;
		OutputImageType::Pointer m_Laxial;

		// dki maps

		OutputImageType::Pointer m_MK;
		OutputImageType::Pointer m_Kradial;
		OutputImageType::Pointer m_Kaxial;

		// indices
		VectorType m_kvec_to_table;
		MatrixType m_indices_w0000;
		MatrixType m_indices_w1111;
		MatrixType m_indices_w2222;
		MatrixType m_indices_w1122;
		MatrixType m_indices_w0022;
		MatrixType m_indices_w0011;

		/**
		 * Init indices for Wtilda rotation.
		 */
		void InitGlobalIndices()
		{
			MatrixType index_table = GetIndexTable();
			MatrixType kvec_indices = GetKvecIndices();
			m_kvec_to_table = GetKvec2Table( GetRowSort( index_table ), kvec_indices );

			m_indices_w0000 = GetIndicesW( 0, 0, 0, 0, index_table );
			m_indices_w1111 = GetIndicesW( 1, 1, 1, 1, index_table );
			m_indices_w2222 = GetIndicesW( 2, 2, 2, 2, index_table );
			m_indices_w1122 = GetIndicesW( 1, 1, 2, 2, index_table );
			m_indices_w0022 = GetIndicesW( 0, 0, 2, 2, index_table );
			m_indices_w0011 = GetIndicesW( 0, 0, 1, 1, index_table );
		}

		/**
		 * DKI maps.
		 */
		void CalculateMaps()
		{
			ConstIterator4DType it( m_Input, m_Input->GetLargestPossibleRegion() );
			ConstIterator3DType mit( m_Mask, m_Mask->GetLargestPossibleRegion() );

			Iterator3DType itFA( m_FA, m_FA->GetLargestPossibleRegion() );
			Iterator3DType itTrace( m_Trace, m_Trace->GetLargestPossibleRegion() );
			Iterator3DType itLaxial( m_Laxial, m_Laxial->GetLargestPossibleRegion() );
			Iterator3DType itLradial( m_Lradial, m_Lradial->GetLargestPossibleRegion() );

			Iterator3DType itMK( m_MK, m_MK->GetLargestPossibleRegion() );
			Iterator3DType itKaxial( m_Kaxial, m_Kaxial->GetLargestPossibleRegion() );
			Iterator3DType itKradial( m_Kradial, m_Kradial->GetLargestPossibleRegion() );

			it.SetDirection( 3 );
			it.GoToBegin();

			mit.GoToBegin();

			itFA.GoToBegin();
			itTrace.GoToBegin();
			itLaxial.GoToBegin();
			itLradial.GoToBegin();

			itMK.GoToBegin();
			itKaxial.GoToBegin();
			itKradial.GoToBegin();

			if ( m_Input->GetLargestPossibleRegion().GetSize()[3] < 21 )
			{
				std::cerr << "*** ERROR ***: input does not contain less than 21 tensor elements!" << std::endl;
				exit( EXIT_FAILURE );
			}

			unsigned int sliceIndex = 0;

			// for each voxel

			while ( !it.IsAtEnd(), !mit.IsAtEnd() )
			{
				if ( mit.Get() != 0 )
				{

					// slice index
					if ( sliceIndex != it.GetIndex()[2] )
					{
						std::cout << "Processing slice: " << sliceIndex << std::endl;
						sliceIndex = it.GetIndex()[2];
					}

					VectorType b( 6 );
					VectorType kvec( 15 );

					while ( !it.IsAtEndOfLine() )
					{
						unsigned int i = ( it.GetIndex() )[3];

						if ( i < 6 )
							b( i ) = it.Get();
						else
							kvec( i - 6 ) = it.Get();

						++it;
					}

					MatrixType DT = GetDTITensor( b );

					itFA.Set( GetFA( DT ) );
					itTrace.Set( GetTrace( DT ) );

					EigenContainerType eig = GetEigenSystem( DT );

					// set negative eigenvalues to 0

					for ( unsigned int i = 0; i < eig.size(); i++ )
						if ( eig.at( i ).first < 0 )
							eig.at( i ).first = 0;

					itLaxial.Set( eig.at( 0 ).first );
					itLradial.Set( ( eig.at( 1 ).first + eig.at( 2 ).first ) / 2. );

					try
					{
						VectorType kvec_scaled = kvec * ( 1. / std::pow( GetTrace( DT ), 2 ) );
						VectorType k = GetKurtosisValues( kvec_scaled, eig ); // TODO
						itMK.Set( k( 0 ) );
						itKaxial.Set( k( 1 ) );
						itKradial.Set( k( 2 ) );
					} catch ( boost::math::evaluation_error e )
					{
						// convergence error, leave kurtosis output zero?
					}
				}

				// go to next voxel

				it.NextLine();
				++mit;
				++itFA;
				++itTrace;
				++itLaxial;
				++itLradial;

				++itMK;
				++itKaxial;
				++itKradial;
			}
		}

		// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

		/**
		 * Return eigenvalues and eigenvector sorted from l1 > l2 > l3.
		 */
		EigenContainerType GetEigenSystem( const MatrixType& DT )
		{
			vnl_symmetric_eigensystem< PixelType > eig( DT );

			EigenContainerType container( 3 );
			container.at( 0 ) = EigenType( eig.get_eigenvalue( 2 ), eig.get_eigenvector( 2 ) );
			container.at( 1 ) = EigenType( eig.get_eigenvalue( 1 ), eig.get_eigenvector( 1 ) );
			container.at( 2 ) = EigenType( eig.get_eigenvalue( 0 ), eig.get_eigenvector( 0 ) );
			return container;
		}
		/**
		 * Return sum eigenvalues.
		 */
		PixelType GetTrace( const MatrixType& DT )
		{
			return DT( 0, 0 ) + DT( 1, 1 ) + DT( 2, 2 );
		}

		/**
		 * Return fractional anisotropy (FA).
		 */
		PixelType GetFA( const MatrixType& DT )
		{
			PixelType isp = inner_product( DT, DT );
			if ( isp > 0.0 )
			{
				PixelType trace = GetTrace( DT );
				PixelType anisotropy = 3.0 * isp - trace * trace;
				PixelType fractionalAnisotropy = std::sqrt( anisotropy / ( 2.0 * isp ) );
				return fractionalAnisotropy;
			}
			return 0.0;
		}

		/**
		 * Index table W sort.
		 */
		MatrixType GetIndexTable()
		{
			MatrixType table( 81, 4 );

			unsigned int lin_idx = 0;

			for ( unsigned int i = 0; i < 3; i++ )
				for ( unsigned int j = 0; j < 3; j++ )
					for ( unsigned int k = 0; k < 3; k++ )
						for ( unsigned int l = 0; l < 3; l++ )
						{
							VectorType row( 4 );
							row( 0 ) = i;
							row( 1 ) = j;
							row( 2 ) = k;
							row( 3 ) = l;
							table.set_row( lin_idx, row );
							lin_idx++;
						}

			return table;
		}

		/**
		 * Sort rows in matrix ('ascend' way).
		 */
		MatrixType GetRowSort( const MatrixType& M )
		{
			MatrixType out( M );

			for ( unsigned int r = 0; r < out.rows(); r++ )
			{
				std::vector< PixelType > v = vnl_vector_to_std_vector( out.get_row( r ) );
				std::sort( v.begin(), v.end() );
				out.set_row( r, std_vector_to_vnl_vector( v ) );
			}

			return out;
		}

		/**
		 * Return matching indices.
		 */
		VectorType GetKvec2Table( const MatrixType& table, const MatrixType& kvec )
		{
			VectorType indices( table.rows(), 0 );

			for ( unsigned int i = 0; i < table.rows(); i++ )
			{
				for ( unsigned int j = 0; j < kvec.rows(); j++ )
				{
					if ( kvec.get_row( j ) == table.get_row( i ) )
					{
						indices( i ) = j;
					}
				}
			}
			return indices;
		}

		/**
		 * Kvec:
		 *
		 * k1111 k2222 k3333
		 * k1112 k1113 k1222
		 * k2223 k1333 k2333
		 * k1122 k1133 k2233
		 * k1123 k1223 k1233
		 */
		MatrixType GetKvecIndices()
		{
			MatrixType t( 15, 4 );

			PixelType data[60] =
			{ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 1, 1, 1, 1, 1, 1, 2, 0, 2, 2, 2, 1, 2, 2, 2, 0, 0, 1, 1, 0, 0,
					2, 2, 1, 1, 2, 2, 0, 0, 1, 2, 0, 1, 1, 2, 0, 1, 2, 2 };

			t.set( data );

			return t;
		}

		/**
		 * Matlab sub2ind.
		 */
		unsigned int sub2ind( unsigned int nrow, unsigned int x, unsigned int y )
		{

			return ( x + y * nrow );
		}

		/**
		 * Return W_ijkl in terms of indices.
		 */
		MatrixType GetIndicesW( unsigned int i, unsigned int j, unsigned int k, unsigned int l, const MatrixType indices )
		{
			MatrixType W( 81, 4 );

			for ( unsigned int r = 0; r < W.rows(); r++ )
			{
				W( r, 0 ) = sub2ind( 3, indices( r, 0 ), i );
				W( r, 1 ) = sub2ind( 3, indices( r, 1 ), j );
				W( r, 2 ) = sub2ind( 3, indices( r, 2 ), k );
				W( r, 3 ) = sub2ind( 3, indices( r, 3 ), l );
			}

			return W;
		}

		/**
		 * Resort kvec.
		 */
		VectorType GetKvecExtended( const VectorType& kvec, const VectorType& indices )
		{
			VectorType out( indices.size(), 0 );

			for ( unsigned int i = 0; i < out.size(); i++ )
			{
				out( i ) = kvec( indices( i ) );
			}
			return out;
		}

		/**
		 * Get column-wise vector of sorted eigen vectors.
		 */
		VectorType EigenVectorsToStretchedFormat( const VectorType& e1, const VectorType& e2, const VectorType& e3 )
		{
			VectorType s( 9 );
			s( 0 ) = e1( 0 );
			s( 1 ) = e2( 0 );
			s( 2 ) = e3( 0 );

			s( 3 ) = e1( 1 );
			s( 4 ) = e2( 1 );
			s( 5 ) = e3( 1 );

			s( 6 ) = e1( 2 );
			s( 7 ) = e2( 2 );
			s( 8 ) = e3( 2 );

			return s;
		}

		/**
		 * Return W~, rotated in DT coordinates.
		 */
		PixelType GetRotatedW( const MatrixType& indices, const EigenContainerType& eig, const VectorType& kvec )
		{
			VectorType V = EigenVectorsToStretchedFormat( eig.at( 0 ).second, eig.at( 1 ).second, eig.at( 2 ).second );

			VectorType col1( indices.rows() );
			VectorType col2( indices.rows() );
			VectorType col3( indices.rows() );
			VectorType col4( indices.rows() );

			for ( unsigned int i = 0; i < col1.size(); i++ )
			{
				col1( i ) = V( indices( i, 0 ) );
				col2( i ) = V( indices( i, 1 ) );
				col3( i ) = V( indices( i, 2 ) );
				col4( i ) = V( indices( i, 3 ) );
			}

			VectorType tmp = element_product( element_product( col1, col2 ), element_product( col3, col4 ) );

			return dot_product( tmp, kvec );
		}

		/**
		 * Return MK, Kaxial and Kradial.
		 *
		 * Given eig
		 * Given kvec (15 kurtosis parameters)
		 */
		VectorType GetKurtosisValues( const VectorType kvec, const EigenContainerType& eig )
		{
			VectorType kvec_extended = GetKvecExtended( kvec, m_kvec_to_table );

			PixelType Wtilda_0000 = GetRotatedW( m_indices_w0000, eig, kvec_extended );
			PixelType Wtilda_0011 = GetRotatedW( m_indices_w0011, eig, kvec_extended );
			PixelType Wtilda_0022 = GetRotatedW( m_indices_w0022, eig, kvec_extended );
			PixelType Wtilda_1111 = GetRotatedW( m_indices_w1111, eig, kvec_extended );
			PixelType Wtilda_1122 = GetRotatedW( m_indices_w1122, eig, kvec_extended );
			PixelType Wtilda_2222 = GetRotatedW( m_indices_w2222, eig, kvec_extended );

			PixelType l1 = eig.at( 0 ).first;
			PixelType l2 = eig.at( 1 ).first;
			PixelType l3 = eig.at( 2 ).first;

			PixelType MK = F1( l1, l2, l3 ) * Wtilda_0000 + F1( l2, l1, l3 ) * Wtilda_1111 + F1( l3, l2, l1 ) * Wtilda_2222 + F2( l1, l2,
					l3 ) * Wtilda_1122 + F2( l2, l1, l3 ) * Wtilda_0022 + F2( l3, l2, l1 ) * Wtilda_0011;

			PixelType Kaxial = ( std::pow( l1 + l2 + l3, 2 ) / 9 * std::pow( l1, 2 ) ) * Wtilda_0000;

			PixelType Kradial = G1( l1, l2, l3 ) * Wtilda_1111 + G1( l1, l3, l2 ) * Wtilda_2222 + G2( l1, l2, l3 ) * Wtilda_1122;

			VectorType k( 3 );
			k( 0 ) = MK;
			k( 1 ) = Kaxial;
			k( 2 ) = Kradial;

			return k;
		}

		/**
		 * Eq (33) Tabesh et al.
		 */
		PixelType G1( PixelType l1, PixelType l2, PixelType l3 )
		{
			if ( l2 != l3 )
			{
				PixelType a = std::pow( l1 + l2 + l3, 2 ) / 18 * l1 * std::pow( l2 - l3, 2 );
				PixelType b = 2 * l2 + ( std::pow( l3, 2 ) - 3 * l2 * l3 ) / std::sqrt( l2 * l3 );

				return a * ( 2 * l2 + b );
			}

			return std::pow( l1 + 2 * l2, 2 ) / std::pow( 24. * l2, 2 );
		}

		/**
		 * Eq (34) Tabesh et al.
		 */
		PixelType G2( PixelType l1, PixelType l2, PixelType l3 )
		{
			if ( l2 != l3 )
			{
				PixelType a = std::pow( l1 + l2 + l3, 2 ) / 3 * std::pow( l2 - l3, 2 );
				PixelType b = l2 + l3 / std::sqrt( l2 + l3 );

				return a * ( b - 2 );
			}

			return 6 * std::pow( l1 + 2 * l2, 2 ) / std::pow( 72 * l2, 2 );
		}

		/**
		 * Eq (27) Tabesh et al.
		 */
		PixelType F1( PixelType l1, PixelType l2, PixelType l3 )
		{
			if ( ( l1 == l2 ) && ( l2 == l3 ) )
			{
				return 0.2;
			} else if ( l1 == l2 )
			{
				return 0.5 * F2( l3, l1, l1 );
			} else if ( l1 == l3 )
			{
				return 0.5 * F2( l2, l1, l1 );
			}

			PixelType a = std::pow( l1 + l2 + l3, 2 ) / 18 * ( l1 - l2 ) * ( l1 - l3 );
			PixelType b = std::sqrt( l2 * l3 ) / l1;
			PixelType c = boost::math::ellint_rf( l1 / l2, l1 / l3, 1. );
			PixelType d = ( 3 * std::pow( l1, 2 ) - l1 * l2 - l1 * l3 - l2 * l3 ) / 3 * l1 * std::sqrt( l2 * l3 );
			PixelType e = boost::math::ellint_rd( l1 / l2, l1 / l3, 1. );

			return a * ( b * c + d * e - 1 );
		}

		/**
		 * Eq (28) Tabesh et al.
		 */
		PixelType F2( PixelType l1, PixelType l2, PixelType l3 )
		{
			if ( l2 != l3 )
			{
				PixelType a = std::pow( l1 + l2 + l3, 2 ) / ( 3 * std::pow( l2 - l3, 2 ) );
				PixelType b = ( l2 + l3 ) / std::sqrt( l2 * l3 );
				PixelType c = boost::math::ellint_rf( l1 / l2, l1 / l3, 1. );
				PixelType d = ( 2 * l1 - l2 + l3 ) / ( 3 * std::sqrt( l2 * l3 ) );
				PixelType e = boost::math::ellint_rd( l1 / l2, l1 / l3, 1. );

				return a * ( b * c + d * e - 2 );
			} else if ( l1 != l3 )
			{
				PixelType a = std::pow( l1 + 2 * l3, 2 ) / std::pow( 144 * l3, 2 ) * std::pow( l1 - l3, 2 );
				PixelType b = l3 * ( l1 + 2 * l3 );
				PixelType c = 1 - ( l1 / l3 );
				PixelType d = l1 * ( l1 - 4 * l3 );

				c = std::sqrt( std::abs< PixelType >( c ) );
				d = d * ( 1. / c ) * std::atan( c );

				return 6 * a * ( b + d );
			}

			return 6. / 15.;
		}

		/**
		 * Return 2-rank DTI tensor.
		 *
		 * layout =>
		 * 		| 0  1  2  |
		 *	    | 1  3  4  |
		 *      | 2  4  5  |
		 */
		MatrixType GetDTITensor( const VectorType& x )
		{
			MatrixType DT( 3, 3 );

			/*
			 m_A_D( i, 0 ) =     Gx * Gx;
			 m_A_D( i, 1 ) =     Gy * Gy;
			 m_A_D( i, 2 ) = 2 * Gx * Gy;
			 m_A_D( i, 3 ) =     Gz * Gz;
			 m_A_D( i, 4 ) = 2 * Gy * Gz;
			 m_A_D( i, 5 ) = 2 * Gx * Gz;
			 */

			DT( 0, 0 ) = x( 0 ); // x * x
			DT( 0, 1 ) = x( 2 );
			DT( 0, 2 ) = x( 5 );

			DT( 1, 0 ) = x( 2 );
			DT( 1, 1 ) = x( 1 ); // y * y
			DT( 1, 2 ) = x( 4 );

			DT( 2, 0 ) = x( 5 );
			DT( 2, 1 ) = x( 4 );
			DT( 2, 2 ) = x( 3 ); // z * z

			return DT;
		}

		/**
		 * Set input image.
		 */
		void SetInput( const std::string& inputFileName )
		{
			if ( !inputFileName.empty() )
			{
				ReaderType::Pointer reader = ReaderType::New();
				reader->SetFileName( inputFileName );
				reader->Update();
				m_Input = reader->GetOutput();
			} else
			{
				std::cerr << "Could not read input: " << inputFileName << "!" << std::endl;
				exit( EXIT_FAILURE );
			}
		}

		/**
			 * Init mask if file given, else create empty mask from input file.
			 */
			void InitMask( const std::string& maskFileName )
			{
				if ( !maskFileName.empty() )
				{
					MaskReaderType::Pointer reader = MaskReaderType::New();
					reader->SetFileName( maskFileName );
					reader->Update();
					m_Mask = reader->GetOutput();
				} else
				{
					OutputImageType::Pointer output = OutputImageType::New();
					OutputImageType::RegionType region3D;
					OutputImageType::IndexType index3D;
					OutputImageType::SizeType size3D;
					OutputImageType::SpacingType spacing3D;
					OutputImageType::PointType origin3D;

					ImageType::RegionType region = m_Input->GetLargestPossibleRegion();
					ImageType::SizeType size = region.GetSize();
					ImageType::IndexType index = region.GetIndex();
					ImageType::SpacingType spacing = m_Input->GetSpacing();
					ImageType::PointType origin = m_Input->GetOrigin();

					size3D[0] = size[0];
					size3D[1] = size[1];
					size3D[2] = size[2];
					index3D[0] = index[0];
					index3D[1] = index[1];
					index3D[2] = index[2];
					origin3D[0] = origin[0];
					origin3D[1] = origin[1];
					origin3D[2] = origin[2];
					spacing3D[0] = spacing[0];
					spacing3D[1] = spacing[1];
					spacing3D[2] = spacing[2];

					region3D.SetSize( size3D );
					region3D.SetIndex( index3D );

					// set
					output->SetRegions( region3D );
					output->SetSpacing( spacing3D );
					output->SetOrigin( origin3D );
					output->Allocate();
					output->FillBuffer( 1 );

					m_Mask = output;
				}
			}

		/**
		 * Allocate all output images to 0.
		 */
		void AllocateOutput()
		{
			OutputImageType::RegionType region3D;
			OutputImageType::IndexType index3D;
			OutputImageType::SizeType size3D;
			OutputImageType::SpacingType spacing3D;
			OutputImageType::PointType origin3D;

			ImageType::RegionType region = m_Input->GetLargestPossibleRegion();
			ImageType::SizeType size = region.GetSize();
			ImageType::IndexType index = region.GetIndex();
			ImageType::SpacingType spacing = m_Input->GetSpacing();
			ImageType::PointType origin = m_Input->GetOrigin();

			size3D[0] = size[0];
			size3D[1] = size[1];
			size3D[2] = size[2];
			index3D[0] = index[0];
			index3D[1] = index[1];
			index3D[2] = index[2];
			origin3D[0] = origin[0];
			origin3D[1] = origin[1];
			origin3D[2] = origin[2];
			spacing3D[0] = spacing[0];
			spacing3D[1] = spacing[1];
			spacing3D[2] = spacing[2];

			region3D.SetSize( size3D );
			region3D.SetIndex( index3D );

			// create
			m_FA = OutputImageType::New();
			m_Trace = OutputImageType::New();
			m_Laxial = OutputImageType::New();
			m_Lradial = OutputImageType::New();

			m_MK = OutputImageType::New();
			m_Kaxial = OutputImageType::New();
			m_Kradial = OutputImageType::New();

			// set
			m_FA->SetRegions( region3D );
			m_FA->SetSpacing( spacing3D );
			m_FA->SetOrigin( origin3D );
			m_FA->Allocate();
			m_FA->FillBuffer( 0 );

			m_Trace->SetRegions( region3D );
			m_Trace->SetSpacing( spacing3D );
			m_Trace->SetOrigin( origin3D );
			m_Trace->Allocate();
			m_Trace->FillBuffer( 0 );

			m_Laxial->SetRegions( region3D );
			m_Laxial->SetSpacing( spacing3D );
			m_Laxial->SetOrigin( origin3D );
			m_Laxial->Allocate();
			m_Laxial->FillBuffer( 0 );

			m_Lradial->SetRegions( region3D );
			m_Lradial->SetSpacing( spacing3D );
			m_Lradial->SetOrigin( origin3D );
			m_Lradial->Allocate();
			m_Lradial->FillBuffer( 0 );

			m_MK->SetRegions( region3D );
			m_MK->SetSpacing( spacing3D );
			m_MK->SetOrigin( origin3D );
			m_MK->Allocate();
			m_MK->FillBuffer( 0 );

			m_Kaxial->SetRegions( region3D );
			m_Kaxial->SetSpacing( spacing3D );
			m_Kaxial->SetOrigin( origin3D );
			m_Kaxial->Allocate();
			m_Kaxial->FillBuffer( 0 );

			m_Kradial->SetRegions( region3D );
			m_Kradial->SetSpacing( spacing3D );
			m_Kradial->SetOrigin( origin3D );
			m_Kradial->Allocate();
			m_Kradial->FillBuffer( 0 );
		}

		/**
		 * Write output images.
		 */
		void Write( const std::string& outputFileName )
		{
			// Writer
			WriterType::Pointer writer = WriterType::New();

			// FA
			writer->SetFileName( ( outputFileName + std::string( "_FA.nii.gz" ) ).c_str() );
			writer->SetInput( m_FA );
			writer->Update();

			// Trace
			writer->SetFileName( ( outputFileName + std::string( "_Trace.nii.gz" ) ).c_str() );
			writer->SetInput( m_Trace );
			writer->Update();

			// L axial
			writer->SetFileName( ( outputFileName + std::string( "_Laxial.nii.gz" ) ).c_str() );
			writer->SetInput( m_Laxial );
			writer->Update();

			// L radial
			writer->SetFileName( ( outputFileName + std::string( "_Lradial.nii.gz" ) ).c_str() );
			writer->SetInput( m_Lradial );
			writer->Update();

			// MK
			writer->SetFileName( ( outputFileName + std::string( "_MK.nii.gz" ) ).c_str() );
			writer->SetInput( m_MK );
			writer->Update();

			// K axial
			writer->SetFileName( ( outputFileName + std::string( "_Kaxial.nii.gz" ) ).c_str() );
			writer->SetInput( m_Kaxial );
			writer->Update();

			// K radial
			writer->SetFileName( ( outputFileName + std::string( "_Kradial.nii.gz" ) ).c_str() );
			writer->SetInput( m_Kradial );
			writer->Update();
		}

	};

} // end namespace dki

/**
 * Create dki maps.
 */
int main( int argc, char ** argv )
{
	tkd::CmdParser p( argv[0], "Create diffusion kurtosis maps." );

	dki::DkiMaps::parameters args;

	p.AddArgument( args.inputFileName, "input" ) ->AddAlias( "i" ) ->SetDescription( "Input 4D image with 21 tensor elements" ) ->SetRequired(
			true );

	p.AddArgument( args.outputFileName, "output" ) ->AddAlias( "o" ) ->SetDescription( "Output filename base" ) ->SetRequired( true );

	p.AddArgument( args.maskFileName, "mask" ) ->AddAlias( "m" ) ->SetDescription( "Mask 3D image" );

	if ( !p.Parse( argc, argv ) )
	{
		p.PrintUsage( std::cout );
		return EXIT_FAILURE;
	}

	dki::DkiMaps maps( args );

	return EXIT_SUCCESS;
}


//==================================================================================================
/*!
  @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_FUNCTION_CEIL_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_CEIL_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-arithmetic
    This function object computes the smallest integral representable value of
    its parameter type which is greater or equal to it.

    @par Header <boost/simd/function/ceil.hpp>

    @par Notes

     - @c ceil is also used as parameter to pass to @ref div or @ref rem

    @par Decorators

    - std_ for floating entries call std::ceil

    @see  floor, round, nearbyint, trunc, iceil

    @par Example:

      @snippet ceil.cpp ceil

    @par Possible output:

      @snippet ceil.txt ceil

  **/
  Value ceil(Value const& x);
} }
#endif

#include <boost/simd/function/scalar/ceil.hpp>
#include <boost/simd/function/simd/ceil.hpp>

#endif


#include "IntegratorRect.hh"

#include <Eigen/Dense>
#include <iterator>

#include "TypesFunctions.hh"

using namespace Eigen;
using namespace std;

IntegratorRect::IntegratorRect(int order, const std::string& mode) : IntegratorBase(order)
{
  init(mode);
}

IntegratorRect::IntegratorRect(size_t bins, int orders, double* edges, const std::string& mode) : IntegratorBase(bins, orders, edges)
{
  init(mode);
}

IntegratorRect::IntegratorRect(size_t bins, int* orders, double* edges, const std::string& mode) : IntegratorBase(bins, orders, edges)
{
  init(mode);
}

void IntegratorRect::init(const std::string& mode) {
  m_mode = mode;

  if(m_mode=="left"){
    m_rect_offset=-1;
  }
  else if(m_mode=="center"){
    m_rect_offset=0;
  }
  else if(m_mode=="right"){
    m_rect_offset=1;
  }
  else{
    throw std::runtime_error("invalid rectangular integration mode");
  }

  init_sampler();
}

void IntegratorRect::sample(FunctionArgs& fargs){
  auto& rets=fargs.rets;
  rets[1].x = m_edges.cast<double>();
  rets[2].x = 0.0;
  auto npoints=m_edges.size()-1;
  rets[3].x = 0.5*(m_edges.tail(npoints)+m_edges.head(npoints));

  auto& abscissa=rets[0].x;

  auto nbins=m_edges.size()-1;
  auto& binwidths=m_edges.tail(nbins) - m_edges.head(nbins);
  ArrayXd samplewidths=binwidths/m_orders.cast<double>();

  ArrayXd low, high;
  switch(m_rect_offset){
    case -1: {
      low=m_edges.head(nbins);
      high=m_edges.tail(nbins)-samplewidths;
      break;
      }
    case 0: {
      ArrayXd offsetwidth=samplewidths*0.5;
      low=m_edges.head(nbins)+offsetwidth;
      high=m_edges.tail(nbins)-offsetwidth;
      break;
      }
    case 1: {
      samplewidths=binwidths/m_orders.cast<double>();
      low=m_edges.head(nbins)+samplewidths;
      high=m_edges.tail(nbins);
      break;
      }
  }

  size_t offset=0;
  for (size_t i = 0; i < static_cast<size_t>(m_orders.size()); ++i) {
    auto n=m_orders[i];
    if(n>1){
      abscissa.segment(offset, n)=ArrayXd::LinSpaced(n, low[i], high[i]);
      m_weights.segment(offset, n)=samplewidths[i];
    }
    else{
      abscissa[i]=low[i];
      m_weights[i]=binwidths[i];
    }
    offset+=n;
  }
  rets.untaint();
  rets.freeze();
}


//
// Created by Hamza El-Kebir on 6/21/21.
//

#ifndef LODESTAR_SINGLETON_HPP
#define LODESTAR_SINGLETON_HPP

#include <type_traits>
#include <Eigen/Dense>
#include "Lodestar/aux/CompileTimeQualifiers.hpp"

#include "SetExpression.hpp"
#include "SetUnion.hpp"
#include "EmptySet.hpp"
#include "SetComplement.hpp"

namespace ls {
    namespace primitives {
        namespace sets {
            /**
             * @brief A singleton set.
             *
             * @brief A singleton contains a single element, represented by the vector \c value_.
             *
             * @tparam TDimension The dimension of the singleton; if -1, it is dynamically sized.
             * @tparam TScalarType The scalar type.
             */
            template<int TDimension = -1, typename TScalarType = double>
            class Singleton : public SetExpression<Singleton<TDimension, TScalarType>> {
            public:
                template<typename, typename>
                friend
                class SetUnion;

                using Base = SetExpression<Singleton<TDimension, TScalarType>>; //! Base class.
                using type = Singleton<TDimension, TScalarType>; //! Expression type.

                typedef Eigen::Matrix<TScalarType, LS_STATIC_UNLESS_DYNAMIC(TDimension), 1> TDValue; //! Value typedef.

                /**
                 * @brief Default constructor.
                 */
                Singleton() : value_{TDValue::Zero()}
                {
                    this->sEnum_ = SetEnum::Singleton;
                }

                /**
                 * @brief Constructs a Singleton instance from a vector.
                 *
                 * @tparam TDerived Derived EigenBase class.
                 *
                 * @param value Singleton value.
                 */
                template<typename TDerived>
                Singleton(const Eigen::EigenBase<TDerived> &value) : value_(value)
                {
                    this->sEnum_ = SetEnum::Singleton;
                }

                /**
                 * @brief Returns the singleton dimension.
                 *
                 * @details This specialization deals with the static case.
                 *
                 * @tparam T_TDimension Copy of \c TDimension.
                 *
                 * @return Dimension of the singleton instance.
                 */
                template<int T_TDimension = TDimension>
                int
                dimension(typename std::enable_if<(T_TDimension < 0)>::type * = nullptr) const
                {
                    return T_TDimension;
                }

                /**
                 * @brief Returns the singleton dimension.
                 *
                 * @details This specialization deals with the dynamic case.
                 *
                 * @tparam T_TDimension Copy of \c TDimension.
                 *
                 * @return Dimension of the singleton instance.
                 */
                template<int T_TDimension = TDimension>
                int
                dimension(typename std::enable_if<(T_TDimension >= 0)>::type * = nullptr) const
                {
                    return value_.rows();
                }

                /**
                 * @brief Changes the dimension of the singleton.
                 *
                 * @details This specialization raises a static assert since statically type Singleton instances cannot
                 * be resized.
                 *
                 * @tparam T_TDimension Copy of \c TDimension.
                 *
                 * @param dim New dimension.
                 */
                template<int T_TDimension = TDimension>
                void
                setDimension(size_t dim, typename std::enable_if<(T_TDimension < 0)>::type * = nullptr)
                {
                    static_assert(TDimension > -1, "Cannot set dimension of statically sized singleton.");
                }

                /**
                 * @brief Changes the dimension of the singleton.
                 *
                 * @details This specialization conservatively resizes \c value_.
                 *
                 * @tparam T_TDimension Copy of \c TDimension.
                 *
                 * @param dim New dimension.
                 */
                template<int T_TDimension = TDimension>
                void
                setDimension(size_t dim, typename std::enable_if<(T_TDimension >= 0)>::type * = nullptr)
                {
                    value_.conservativeResize(dim);
                }

                /**
                 * @brief Returns true if this expression contains \c expr.
                 *
                 * @details This specialization handles a singleton expression of the same type.
                 *
                 * @tparam TExpression Type of the other expression.
                 *
                 * @param expr Expression to check containment of.
                 * @param tol Numerical tolerance.
                 *
                 * @return True if this expression contains \c expr, false otherwise.
                 */
                template<typename TExpression>
                typename std::enable_if<std::is_same<TExpression, type>::value, bool>::type
                contains(const SetExpression<TExpression> &expr, double tol = 1e-6) const
                {
                    return (value_ - static_cast<const TExpression *>(&expr)->value_).isMuchSmallerThan(tol);
                }

                /**
                 * @brief Returns true if this expression contains \c expr.
                 *
                 * @details This specialization handles all other cases.
                 *
                 * @tparam TExpression Type of the other expression.
                 *
                 * @param expr Expression to check containment of.
                 * @param tol Numerical tolerance.
                 *
                 * @return False.
                 */
                template<typename TExpression>
                typename std::enable_if<!std::is_same<TExpression, type>::value, bool>::type
                contains(const SetExpression<TExpression> &expr, double tol = 1e-6) const
                {
                    return false;
                }

                /**
                 * @brief Checks if this expression is equal to \expr.
                 *
                 * @details This specialization deals with singletons of the same type.
                 *
                 * @tparam TExpression Type of the other expression.
                 *
                 * @param expr Expression to check equality with.
                 *
                 * @return True if this expression is equal to \c expr, false otherwise.
                 */
                template<typename TExpression>
                typename std::enable_if<std::is_same<TExpression, type>::value, bool>::type
                operator==(const SetExpression<TExpression> &expr) const
                {
                    return (value_ - static_cast<const TExpression *>(&expr)->value_).isMuchSmallerThan(1e-6);
                }

                // TODO: Add dynamic case.

                /**
                 * @brief Checks if this expression is equal to \expr.
                 *
                 * @details This specialization deals with all other cases.
                 *
                 * @tparam TExpression Type of the other expression.
                 *
                 * @param expr Expression to check equality with.
                 *
                 * @return False.
                 */
                template<typename TExpression>
                typename std::enable_if<!std::is_same<TExpression, type>::value, bool>::type
                operator==(const SetExpression<TExpression> &expr) const
                {
                    return false;
                }

                /**
                 * @brief Checks if this expression is not equal to \expr.
                 *
                 * @details This specialization deals with singletons of the same type.
                 *
                 * @tparam TExpression Type of the other expression.
                 *
                 * @param expr Expression to check inequality with.
                 *
                 * @return True if this expression is not equal to \c expr, false otherwise.
                 */
                template<typename TExpression>
                typename std::enable_if<std::is_same<TExpression, type>::value, bool>::type
                operator!=(const SetExpression<TExpression> &expr) const
                {
                    return !((value_ - static_cast<const TExpression *>(&expr)->value_).isMuchSmallerThan(1e-6));
                }

                /**
                 * @brief Checks if this expression is not equal to \expr.
                 *
                 * @details This specialization deals with all other cases.
                 *
                 * @tparam TExpression Type of the other expression.
                 *
                 * @param expr Expression to check inequality with.
                 *
                 * @return True.
                 */
                template<typename TExpression>
                typename std::enable_if<!std::is_same<TExpression, type>::value, bool>::type
                operator!=(const SetExpression<TExpression> &expr) const
                {
                    return true;
                }

                /**
                 * @brief Creates a union between this expression and another SetExpression.
                 *
                 * @tparam TExpression Type of the other expression.
                 *
                 * @param expr Expression to create a union with.
                 *
                 * @return Union.
                 */
                template<typename TExpression>
                SetUnion<type, TExpression> unionize(const SetExpression<TExpression> &expr)
                {
                    return SetUnion<type, TExpression>(*this, *static_cast<const TExpression *>(&expr));
                }

                /**
                 * @brief Checks if this expression is a subset of \c expr.
                 *
                 * @tparam TExpression Type of the other expression.
                 *
                 * @param expr Expression to check inclusion on.
                 *
                 * @return True if this expression is a subset of \c expr, false otherwise.
                 */
                template<typename TExpression>
                bool isSubset(const SetExpression<TExpression> &expr)
                {
                    return static_cast<const TExpression *>(&expr)->contains(*this);
                }

                /**
                 * @brief Checks if this expression is a superset of \c expr.
                 *
                 * @tparam TExpression Type of the other expression.
                 *
                 * @param expr Expression to check subsumption on.
                 *
                 * @return True if this expression is a superset of \c expr, false otherwise.
                 */
                template<typename TExpression>
                bool isSuperset(const SetExpression<TExpression> &expr)
                {
                    return contains(expr);
                }

                /**
                 * @brief Returns true if the expression is the empty set.
                 *
                 * @note This always returns false, since a singleton always contains one element.
                 *
                 * @return False.
                 */
                bool isEmpty() const
                {
                    return false;
                }

                /**
                 * @brief Computes the relative complement of this expression and \c expr.
                 *
                 * @details The syntax is as follows:
                 * \code
                 * // A \ B
                 * A.relComplement(B);
                 * \endcode
                 *
                 * @tparam TExpression Type of the other expression.
                 *
                 * @param expr Expression to take relative complement with.
                 *
                 * @return Relative complement.
                 */
                template<typename TExpression>
                SetComplement<type, TExpression> relComplement(const SetExpression<TExpression> &expr)
                {
                    return SetComplement<type, TExpression>(*this, *static_cast<const TExpression *>(&expr),
                                                            isSubset(expr));
                }

                /**
                 * @brief Returns signed distance to \c p.
                 *
                 * @tparam TDerived Derived MatrixBase class.
                 *
                 * @param p A point.
                 *
                 * @return Signed distance.
                 */
                template<typename TDerived>
                double sdf(Eigen::MatrixBase<TDerived> &p) const
                {
                    p.resize(dimension());
                    return (value_ - p).norm();
                }

            protected:
                TDValue value_; //! Singleton value.
            };
        }
    }
}

#endif //LODESTAR_SINGLETON_HPP


//          Copyright Erik Lundin 2016.
// 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 <iostream>
#include <iomanip>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <boost/program_options.hpp>

#include "gausskruger.h"

using namespace gausskruger;
namespace po = boost::program_options;

#define EXE_NAME "gausskruger"

class ParameterProjection : public Projection
{
public:
    ParameterProjection(double flattening, double equatorialRadius, double centralMeridian,
            double scale, double falseNorthing, double falseEasting) :
        mFlattening(flattening),
        mEquatorialRadius(equatorialRadius),
        mCentralMeridian(centralMeridian),
        mScale(scale),
        mFalseNorthing(falseNorthing),
        mFalseEasting(falseEasting) {}
    double flattening() { return mFlattening; }
    double equatorialRadius() { return mEquatorialRadius; }
    double centralMeridian() { return mCentralMeridian; }
    double scale() { return mScale; }
    double falseNorthing() { return mFalseNorthing; }
    double falseEasting() { return mFalseEasting; }
private:
    double mFlattening;
    double mEquatorialRadius;
    double mCentralMeridian;
    double mScale;
    double mFalseNorthing;
    double mFalseEasting;
};

int main(int argc, char *argv[])
{
    double inverseFlattening;
    double equatorialRadius;
    double centralMeridian;
    double scale;
    double falseNorthing;
    double falseEasting;
    std::vector<double> coords;
    int nDecimals;

    try {
        // Projection parameters
        po::options_description projection_parameters("Projection parameters (mandatory)");
        projection_parameters.add_options()
                ("invflattening,i", po::value<double>(&inverseFlattening),
                        "inverse flattening of the ellipsoid")
                ("radius,a", po::value<double>(&equatorialRadius),
                        "equatorial radius, a.k.a. semi-major axis of the ellipsoid")
                ("meridian,m", po::value<double>(&centralMeridian),
                        "longitude of the central meridian")
                ("scale,s", po::value<double>(&scale),
                        "scale factor along the central meridian")
                ("falsenorthing,n", po::value<double>(&falseNorthing),
                        "false northing")
                ("falseeasting,e", po::value<double>(&falseEasting),
                        "false easting")
                ;

        // Options
        po::options_description options("Options");
        options.add_options()
                ("help,h", "print this help")
                ("decimals,d", po::value<int>(&nDecimals)->default_value(3), "number of decimals")
                ("reverse,r",
                        "reverse transformation (grid to geodetic), default is geodetic to grid")
                ;

        // Positional parameters (input coordinates)
        po::options_description hidden_parameters;
        hidden_parameters.add_options()
                ("coords", po::value<std::vector<double> >(&coords))
                ;
        po::positional_options_description positional_parameters;
        positional_parameters.add("coords", 2);

        // Store the parameters
        po::options_description all_parameters;
        all_parameters.add(options).add(projection_parameters).add(hidden_parameters);
        po::variables_map vm;
        po::store(po::command_line_parser(argc, argv).options(all_parameters).positional(positional_parameters).run(), vm);
        po::notify(vm);

        // Print help (if requested or no other options is given)
        po::options_description visible_parameters;
        visible_parameters.add(projection_parameters).add(options);
        if (argc == 1 || vm.count("help")) {
            std::cout << "Usage: " << EXE_NAME << " <projection> [options] latitude longitude\n"
                      << "       " << EXE_NAME << " <projection> [options] -r northing easting\n"
                      << visible_parameters << std::endl;
            return 1;
        }

        // Check for all mandatory parameters
        if (!(vm.count("invflattening") && vm.count("radius") && vm.count("meridian") && vm.count("scale")
                && vm.count("falsenorthing") && vm.count("falseeasting"))) {
            std::cout << "Missing mandatory projection parameter(s)" << std::endl;
            return 2;
        }
        if (coords.size() != 2) {
            std::cerr << "Exactly two coordinate values have to be entered" << std::endl;
            return 3;
        }

        // Set number of decimals in output
        std::cout.setf(std::ios::fixed);
        std::cout.precision(nDecimals);

        // Do the actual transformation
        ParameterProjection projection(1 / inverseFlattening, equatorialRadius,
                centralMeridian, scale, falseNorthing, falseEasting);
        if (vm.count("reverse")) {
            double lat, lon;
            projection.gridToGeodetic(coords.at(0), coords.at(1), lat, lon);
            std::cout << std::fixed << "Latitude: " << lat << "\nLongitude: " << lon << std::endl;
        } else {
            double northing, easting;
            projection.geodeticToGrid(coords.at(0), coords.at(1), northing, easting);
            std::cout << std::fixed << "Northing: " << northing << "\nEasting: " << easting << std::endl;
        }
    } catch (std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 4;
    }

    return 0;
}