#  Copyright (c) 1997-2018
#  Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
#  http://www.polymake.org
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by the
#  Free Software Foundation; either version 2, or (at your option) any
#  later version: http://www.gnu.org/licenses/gpl.txt.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#-------------------------------------------------------------------------------

object PolyhedralFan {

rule FAN_AMBIENT_DIM : {
  my $d=0;
  foreach (qw(RAYS INPUT_RAYS LINEALITY_SPACE INPUT_LINEALITY FACET_NORMALS LINEAR_SPAN_NORMALS)) {
    my $M;
    if (defined ($M=$this->lookup($_)) && $M->cols()>0) {
      $d=$M->cols();
      last;
    }
  }
  $this->FAN_AMBIENT_DIM=$d;
}
precondition : defined(RAYS | INPUT_RAYS | LINEALITY_SPACE | INPUT_LINEALITY | FACET_NORMALS | LINEAR_SPAN_NORMALS);
weight 0.10;

rule N_RAYS : RAYS {
   $this->N_RAYS=$this->RAYS->rows;
}
weight 0.1;

rule N_INPUT_RAYS : INPUT_RAYS {
   $this->N_INPUT_RAYS=$this->INPUT_RAYS->rows;
}
weight 0.1;

rule N_MAXIMAL_CONES : MAXIMAL_CONES {
   $this->N_MAXIMAL_CONES=$this->MAXIMAL_CONES->rows;
}
weight 0.1;

rule N_CONES : CONES {
   $this->N_CONES=$this->CONES->rows;
}
weight 0.1;

rule N_FACET_NORMALS : FACET_NORMALS {
   $this->N_FACET_NORMALS=$this->FACET_NORMALS->rows;
}
weight 0.1;

rule COMBINATORIAL_DIM : FAN_DIM, LINEALITY_DIM {
   $this->COMBINATORIAL_DIM = $this->FAN_DIM - $this->LINEALITY_DIM - 1;
}
weight 0.1;

rule FAN_DIM : COMBINATORIAL_DIM, LINEALITY_DIM {
   $this->FAN_DIM = $this->COMBINATORIAL_DIM + $this->LINEALITY_DIM + 1;
}
weight 0.1;

#If we know beforehand that the fan is pure, we don't have to compute individual cone dimensions.
rule FAN_DIM : RAYS, MAXIMAL_CONES, LINEALITY_SPACE {
   if ($this->MAXIMAL_CONES->rows() == 0) {
      $this->FAN_DIM = $this->LINEALITY_SPACE->rows();
   }
   else {
      $this->FAN_DIM = (new polytope::Cone(RAYS=>$this->RAYS->minor($this->MAXIMAL_CONES->row(0),All),LINEALITY_SPACE=>$this->LINEALITY_SPACE))->CONE_DIM;
   }
}
precondition : PURE;
weight 2.10;

rule LINEALITY_DIM : COMBINATORIAL_DIM, FAN_DIM {
   $this->LINEALITY_DIM = $this->FAN_DIM - $this->COMBINATORIAL_DIM - 1;
}
weight 0.1;

rule LINEALITY_DIM : LINEALITY_SPACE {
   $this->LINEALITY_DIM=$this->LINEALITY_SPACE->rows;
}
weight 0.1;

rule POINTED : LINEALITY_DIM {
   $this->POINTED= $this->LINEALITY_DIM==0;
}
weight 0.1;


rule FULL_DIM : FAN_DIM, FAN_AMBIENT_DIM {
  $this->FULL_DIM= ($this->FAN_DIM==$this->FAN_AMBIENT_DIM);
}
weight 0.1;


# If no [[LINEALITY_SPACE]] is given, it is assumed to be trivial.
# Note: as there is only one target this rule is only called if LINEALITY_SPACE has not been written by the user, but RAYS have been.
rule LINEALITY_SPACE : RAYS, MAXIMAL_CONES {
   $this->LINEALITY_SPACE=new Matrix<Scalar>(0, $this->RAYS->cols);
}
weight 0.10;

rule MAXIMAL_CONES_COMBINATORIAL_DIMS : RAYS, MAXIMAL_CONES, LINEALITY_SPACE {
   my @dims = map { (new polytope::Cone(RAYS=>$this->RAYS->minor($_,All),LINEALITY_SPACE=>$this->LINEALITY_SPACE))->COMBINATORIAL_DIM } @{$this->MAXIMAL_CONES};
   $this->MAXIMAL_CONES_COMBINATORIAL_DIMS=\@dims;
}
weight 3.10;

rule MAXIMAL_CONES_COMBINATORIAL_DIMS : COMBINATORIAL_DIM, N_MAXIMAL_CONES {
   my @dims = map { $this->COMBINATORIAL_DIM } 1..$this->N_MAXIMAL_CONES;
   $this->MAXIMAL_CONES_COMBINATORIAL_DIMS(temporary)=\@dims;
}
precondition : PURE;
weight 1.10;

rule COMBINATORIAL_DIM, PURE : MAXIMAL_CONES_COMBINATORIAL_DIMS {
   if ($this->MAXIMAL_CONES_COMBINATORIAL_DIMS->size == 0) {
      $this->PURE=1;
      $this->COMBINATORIAL_DIM=-2;
      return;
   }

   my $dim=$this->MAXIMAL_CONES_COMBINATORIAL_DIMS->[0];
   my $pure=1;
   foreach (@{$this->MAXIMAL_CONES_COMBINATORIAL_DIMS})  {
      if ($_>$dim) {
         $dim =$_;
	 $pure=0;
      } elsif ($_<$dim) {
          $pure=0;
      }
   }
   $this->COMBINATORIAL_DIM=$dim;
   $this->PURE=$pure;
}
weight 1.10;


rule MAXIMAL_CONES_COMBINATORIAL_DIMS : MAXIMAL_CONES_INCIDENCES, N_MAXIMAL_CONES {
  my $mc=$this->N_MAXIMAL_CONES;
  my $inc=$this->MAXIMAL_CONES_INCIDENCES;
  my $dims=new Array<Int>($mc);
  foreach my $i (0..$mc-1) {
    my $m=new IncidenceMatrix($inc->[$i]);
    $m->squeeze_cols;
    $dims->[$i]=$m->cols==0?0:polytope::dim_from_incidence($m);
  }
  $this->MAXIMAL_CONES_COMBINATORIAL_DIMS=$dims;
}
weight 2.10;

# FIXME no testcases with non-pointed cones
rule MAXIMAL_CONES_INCIDENCES : RAYS, MAXIMAL_CONES, FACET_NORMALS, MAXIMAL_CONES_FACETS, N_FACET_NORMALS {
  my $cmp=$this->prepare_computations;
  my $mc=$this->MAXIMAL_CONES;
  my $r=$this->RAYS;
  my $fn=$this->FACET_NORMALS;
  my $nfn=$this->N_FACET_NORMALS;
  my $mcf=$this->MAXIMAL_CONES_FACETS;

  my $n_mc=$mc->rows;

  my $inc=polytope::incidence_matrix($fn,$r);

  my $mci=new Array<IncidenceMatrix>($n_mc);
  foreach my $i (0..$n_mc-1) {
    $mci->[$i]=$inc->minor(indices($mcf->[$i]),All);
    my $sh=new Set<Int>;
    foreach (@{sequence(0,$mc->cols)-$mc->[$i]}) {
      $mci->[$i]->col($_)=$sh;
    }
  }
  $this->MAXIMAL_CONES_INCIDENCES=$mci;
}

rule HASSE_DIAGRAM.ADJACENCY, HASSE_DIAGRAM.DECORATION, HASSE_DIAGRAM.INVERSE_RANK_MAP, HASSE_DIAGRAM.TOP_NODE, HASSE_DIAGRAM.BOTTOM_NODE : \
     MAXIMAL_CONES, MAXIMAL_CONES_INCIDENCES, MAXIMAL_CONES_COMBINATORIAL_DIMS, COMBINATORIAL_DIM {
   $this->HASSE_DIAGRAM=hasse_diagram($this);
}
weight 6.50;

rule HASSE_DIAGRAM.ADJACENCY, HASSE_DIAGRAM.DECORATION, HASSE_DIAGRAM.INVERSE_RANK_MAP, HASSE_DIAGRAM.TOP_NODE, HASSE_DIAGRAM.BOTTOM_NODE : \
     MAXIMAL_CONES, COMBINATORIAL_DIM {
   $this->HASSE_DIAGRAM=hasse_diagram($this,1,1);
}
precondition : COMPLETE;
weight 6.10;

rule HASSE_DIAGRAM.ADJACENCY, HASSE_DIAGRAM.DECORATION, HASSE_DIAGRAM.INVERSE_RANK_MAP, HASSE_DIAGRAM.TOP_NODE, HASSE_DIAGRAM.BOTTOM_NODE : \
     MAXIMAL_CONES, COMBINATORIAL_DIM, MAXIMAL_CONES_INCIDENCES {
   $this->HASSE_DIAGRAM=hasse_diagram($this,1,0);
}
precondition : PURE;
weight 6.20;

rule MAXIMAL_CONES : HASSE_DIAGRAM.ADJACENCY, HASSE_DIAGRAM.DECORATION, HASSE_DIAGRAM.TOP_NODE {
   my @cones = map { $this->HASSE_DIAGRAM->DECORATION->[$_]->face } @{$this->HASSE_DIAGRAM->ADJACENCY->in_adjacent_nodes($this->HASSE_DIAGRAM->TOP_NODE)};
   $this->MAXIMAL_CONES = new IncidenceMatrix(\@cones);
}
weight 1.10;

rule CONES : HASSE_DIAGRAM.DECORATION, HASSE_DIAGRAM.INVERSE_RANK_MAP, HASSE_DIAGRAM.TOP_NODE, HASSE_DIAGRAM.BOTTOM_NODE, N_RAYS {
   my $d=$this->HASSE_DIAGRAM->rank;
   $this->CONES=[ map {
                     my @rows=map { $this->HASSE_DIAGRAM->DECORATION->[$_]->face } @{$this->HASSE_DIAGRAM->nodes_of_rank($_)};
                     @rows ? new IncidenceMatrix(\@rows, $this->N_RAYS) : new IncidenceMatrix(0, $this->N_RAYS)
                  } 1..$d-1 ];
}
weight 1.20;


rule F_VECTOR : HASSE_DIAGRAM.DECORATION, HASSE_DIAGRAM.INVERSE_RANK_MAP, HASSE_DIAGRAM.TOP_NODE, HASSE_DIAGRAM.BOTTOM_NODE {
   my $rk = $this->HASSE_DIAGRAM->rank();
   my @f = map { scalar(@{$this->HASSE_DIAGRAM->nodes_of_rank($_)}) } (1 .. $rk-1);
   $this->F_VECTOR= \@f;
}
weight 0.10;

rule REGULAR : RAYS, MAXIMAL_CONES {
  $this->REGULAR = pseudo_regular($this)->first;
}
precondition : COMPLETE;
weight 4.10;

rule COMPLETE, REGULAR :  {
   $this->REGULAR=0;
   $this->COMPLETE=0;
}
precondition : !DUAL_GRAPH.CONNECTED;
weight 0.1;

rule COMPLETE, REGULAR :  {
   $this->REGULAR=0;
   $this->COMPLETE=0;
}
precondition : !FULL_DIM;
weight 0.1;

rule REGULAR : {
   $this->REGULAR=0;
}
precondition : !COMPLETE;
weight 0.1;

rule REGULAR : {
   $this->REGULAR=0;
}
precondition : !PSEUDO_REGULAR;
weight 0.1;

rule REGULAR = PSEUDO_REGULAR;
precondition : COMPLETE;

rule PSEUDO_REGULAR : RAYS, MAXIMAL_CONES {
   $this->PSEUDO_REGULAR = pseudo_regular($this)->first;
}
weight 4.10;

rule PSEUDO_REGULAR = REGULAR;
precondition : COMPLETE;

rule INTERSECTION_COMPLEX.FACETS : MAXIMAL_CONES {
  $this->INTERSECTION_COMPLEX->FACETS(temporary)=new Array<Set<Int> >(rows($this->MAXIMAL_CONES));
}
weight 1.10;

rule COMPLETE : FULL_DIM, INTERSECTION_COMPLEX.SPHERE {
   $this->COMPLETE=$this->FULL_DIM && $this->INTERSECTION_COMPLEX->SPHERE;
}
precondition : SIMPLICIAL;
weight 0.1;

rule COMPLETE : MAXIMAL_CONES_FACETS, DUAL_GRAPH.NODE_DEGREES, N_MAXIMAL_CONES {
   for (my $i=0; $i<$this->N_MAXIMAL_CONES; ++$i)  {
      if ($this->MAXIMAL_CONES_FACETS->row($i)->size!=$this->DUAL_GRAPH->NODE_DEGREES->[$i]) {
         $this->COMPLETE=0;
         return;
      }
   }
   $this->COMPLETE=1;
}
precondition : DUAL_GRAPH.CONNECTED;
weight 1.10;

rule GRAPH.ADJACENCY : MAXIMAL_CONES {
   my $n=$this->MAXIMAL_CONES->cols;
   my $G = new props::Graph($n);
   for (my $i=0; $i<$n; ++$i)  {
      foreach (@{$this->MAXIMAL_CONES->col($i)}) {
        foreach (@{$this->MAXIMAL_CONES->row($_)}) {
          if ($i!=$_) {
	    $G->edge($i,$_);
	  }
	}
     }
   }
   $this->GRAPH->ADJACENCY=$G;
}
precondition : SIMPLICIAL;
weight 3.10;

rule GRAPH.ADJACENCY : HASSE_DIAGRAM.ADJACENCY, HASSE_DIAGRAM.DECORATION, HASSE_DIAGRAM.INVERSE_RANK_MAP, HASSE_DIAGRAM.TOP_NODE, HASSE_DIAGRAM.BOTTOM_NODE {
   $this->GRAPH->ADJACENCY=vertex_graph($this->HASSE_DIAGRAM);
}
precondition : FAN_DIM { $this->FAN_DIM>=2 };
weight 1.20;

rule DUAL_GRAPH.ADJACENCY : HASSE_DIAGRAM.ADJACENCY, HASSE_DIAGRAM.DECORATION, HASSE_DIAGRAM.INVERSE_RANK_MAP, HASSE_DIAGRAM.TOP_NODE, HASSE_DIAGRAM.BOTTOM_NODE {
   $this->DUAL_GRAPH->ADJACENCY=facet_graph($this->HASSE_DIAGRAM);
}
precondition : PURE;
weight 1.20;

rule FAN_DIM = FAN_AMBIENT_DIM;
precondition : FULL_DIM;

rule FULL_DIM, DUAL_GRAPH.CONNECTED : {
   $this->FULL_DIM=1;
   $this->DUAL_GRAPH->CONNECTED=1;
}
precondition : COMPLETE;
weight 0.1;

rule FULL_DIM, DUAL_GRAPH.CONNECTED : {
   $this->FULL_DIM=1;
   $this->DUAL_GRAPH->CONNECTED=1;
}
precondition : REGULAR;
weight 0.1;

rule SIMPLICIAL : MAXIMAL_CONES, COMBINATORIAL_DIM {
   my $d=$this->COMBINATORIAL_DIM+1;
   my $simplicial=1;
   foreach (@{$this->MAXIMAL_CONES}) {
      $simplicial=0, last if ($_->size!=$d);
   }
   $this->SIMPLICIAL=$simplicial;
}
precondition : PURE;
weight 1.10;

rule SIMPLICIAL : MAXIMAL_CONES_COMBINATORIAL_DIMS, MAXIMAL_CONES {
   my $i=0;
   my $simplicial=1;
   foreach (@{$this->MAXIMAL_CONES}) {
      my $d=$this->MAXIMAL_CONES_COMBINATORIAL_DIMS->[$i++];
      $simplicial=0, last if ($_->size!=$d+1);
   }
   $this->SIMPLICIAL=$simplicial;
}
weight 1.30;




rule RAYS, MAXIMAL_CONES, LINEALITY_SPACE : INPUT_RAYS, INPUT_CONES, FAN_AMBIENT_DIM {
  remove_redundancies($this);
}
weight 3.10;
incurs RaysPerm;

}

# NOTE: Both raysToFacetNormals and facetsToRays now live in bundled:cdd for
# performance reasons.


# @category Producing a fan
# Orthogonally project a pointed fan to a coordinate subspace.
#
# The subspace the fan //P// is projected on is given by indices in the set //indices//.
# The option //revert// inverts the coordinate list.
# @param PolyhedralFan P
# @param Array<Int> indices
# @option Bool revert inverts the coordinate list
# @return PolyhedralFan
user_function projection<Scalar>(PolyhedralFan<Scalar>; $=[ ], { revert=>0 }) {
   my ($P, $indices, $options) = @_;
   my $first_coord = ($P->isa("PolyhedralComplex") ? 1 : 0);
   projection_impl($P, "FAN", "LINEAR_SPAN_NORMALS", $first_coord, $indices, $options);
}


# @category Producing a fan
# Orthogonally project a fan to a coordinate subspace such that redundant columns are omitted,
# i.e., the projection becomes full-dimensional without changing the combinatorial type.
# @param PolyhedralFan P
# @option Bool no_labels VERTEX_LABELS]] to the projection. default: 0
# @return PolyhedralFan
user_function project_full<Scalar>(PolyhedralFan<Scalar>; {no_labels=>0}) {
   projection(@_);
}


# @category Geometry
# The facets of a polyhedron that has the fan //P// as its [[normal_fan|normal fan]], or the empty matrix 
# if no such polyhedron exists.
# @param PolyhedralFan P
# @return Matrix<Scalar>
user_function generating_polyhedron_facets<Scalar>(PolyhedralFan<Scalar>) { 
   my $F = pseudo_regular(@_)->second;
   return $F;
}



# Local Variables:
# mode: perl
# cperl-indent-level:3
# indent-tabs-mode:nil
# End:
