/* Polyhedra_PowerSet class implementation: inline functions.
   Copyright (C) 2001-2004 Roberto Bagnara <bagnara@cs.unipr.it>

This file is part of the Parma Polyhedra Library (PPL).

The PPL 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 of the License, or (at your
option) any later version.

The PPL 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.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.

For the most up-to-date information see the Parma Polyhedra Library
site: http://www.cs.unipr.it/ppl/ . */

#ifndef PPL_Polyhedra_PowerSet_inlines_hh
#define PPL_Polyhedra_PowerSet_inlines_hh 1

#include "BHRZ03_Certificate.types.hh"
#include "ConSys.defs.hh"
#include "ConSys.inlines.hh"
#include "algorithms.hh"
#include "Widening_Function.defs.hh"
#include <algorithm>
#include <deque>
#include <string>
#include <iostream>

namespace Parma_Polyhedra_Library {

template <typename PH>
dimension_type
Polyhedra_PowerSet<PH>::max_space_dimension() {
  return PH::max_space_dimension();
}

template <typename PH>
Polyhedra_PowerSet<PH>::Polyhedra_PowerSet(dimension_type num_dimensions,
					   Polyhedron::Degenerate_Kind kind)
  : space_dim(num_dimensions) {
  if (kind == Polyhedron::UNIVERSE)
    push_back(Determinate<PH>(num_dimensions, true));
}

template <typename PH>
Polyhedra_PowerSet<PH>::Polyhedra_PowerSet(const Polyhedra_PowerSet& y)
  : Base(y), space_dim(y.space_dim) {
}

template <typename PH>
Polyhedra_PowerSet<PH>::Polyhedra_PowerSet(const ConSys& cs)
  : space_dim(cs.space_dimension()) {
  push_back(Determinate<PH>(cs));
}

template <typename PH>
Polyhedra_PowerSet<PH>&
Polyhedra_PowerSet<PH>::operator=(const Polyhedra_PowerSet& y) {
  Base::operator=(y);
  space_dim = y.space_dim;
  return *this;
}

template <typename PH>
inline void
Polyhedra_PowerSet<PH>::swap(Polyhedra_PowerSet& y) {
  Base::swap(y);
  std::swap(space_dim, y.space_dim);
}

template <typename PH>
dimension_type
Polyhedra_PowerSet<PH>::space_dimension() const {
  return space_dim;
}

template <typename PH>
void
Polyhedra_PowerSet<PH>::concatenate_assign(const Polyhedra_PowerSet& y) {
  // Ensure omega-reduction here, since what follows has quadratic complexity.
  Base::omega_reduce();
  y.Base::omega_reduce();
  Sequence new_sequence;
  const Polyhedra_PowerSet<PH>& x = *this;
  for (const_iterator xi = x.begin(), x_end = x.end(),
	 y_begin = y.begin(), y_end = y.end(); xi != x_end; ) {
    for (const_iterator yi = y_begin; yi != y_end; ++yi) {
      CS zi = *xi;
      zi.concatenate_assign(*yi);
      assert(!zi.is_bottom());
      new_sequence.push_back(zi);
    }
    ++xi;
    if (abandon_expensive_computations && xi != x_end && y_begin != y_end) {
      // Hurry up!
      PH xph = xi->element();
      for (++xi; xi != x_end; ++xi)
	xph.poly_hull_assign(xi->element());
      const_iterator yi = y_begin;
      PH yph = yi->element();
      for (++yi; yi != y_end; ++yi)
	yph.poly_hull_assign(yi->element());
      xph.concatenate_assign(yph);
      std::swap(Base::sequence, new_sequence);
      add_disjunct(xph);
      goto done;
    }
  }
  std::swap(Base::sequence, new_sequence);
 done:
  space_dim += y.space_dim;
  assert(OK());
}

template <typename PH>
void
Polyhedra_PowerSet<PH>::add_constraint(const Constraint& c) {
  for (iterator xi = Base::begin(), x_end = Base::end(); xi != x_end; ++xi)
    xi->element().add_constraint(c);
  Base::reduced = false;
}

template <typename PH>
bool
Polyhedra_PowerSet<PH>::add_constraint_and_minimize(const Constraint& c) {
  for (iterator xi = Base::begin(), xin = xi, x_end = Base::end(); xi != x_end; xi = xin) {
    ++xin;
    if (!xi->element().add_constraint_and_minimize(c)) {
      erase(xi);
      x_end = Base::end();
    }
    else
      Base::reduced = false;
  }
  return !Base::empty();
}

template <typename PH>
void
Polyhedra_PowerSet<PH>::add_constraints(const ConSys& cs) {
  for (iterator xi = Base::begin(), x_end = Base::end(); xi != x_end; ++xi)
    xi->element().add_constraints(cs);
  Base::reduced = false;
}

template <typename PH>
bool
Polyhedra_PowerSet<PH>::add_constraints_and_minimize(const ConSys& cs) {
  for (iterator xi = Base::begin(),
	 xin = xi, x_end = Base::end(); xi != x_end; xi = xin) {
    ++xin;
    if (!xi->element().add_constraints_and_minimize(cs)) {
      erase(xi);
      x_end = Base::end();
    }
    else
      Base::reduced = false;
  }
  return !Base::empty();
}

template <typename PH>
void
Polyhedra_PowerSet<PH>::add_dimensions_and_embed(dimension_type m) {
  for (iterator i = Base::begin(), send = Base::end(); i != send; ++i)
    i->add_dimensions_and_embed(m);
  space_dim += m;
  assert(OK());
}

template <typename PH>
void
Polyhedra_PowerSet<PH>::add_dimensions_and_project(dimension_type m) {
  for (iterator i = Base::begin(), send = Base::end(); i != send; ++i)
    i->add_dimensions_and_project(m);
  space_dim += m;
  assert(OK());
}

template <typename PH>
void
Polyhedra_PowerSet<PH>::remove_dimensions(const Variables_Set& to_be_removed) {
  Variables_Set::size_type num_removed = to_be_removed.size();
  if (num_removed > 0) {
    for (iterator i = Base::begin(), send = Base::end(); i != send; ++i) {
      i->remove_dimensions(to_be_removed);
      Base::reduced = false;
    }
    space_dim -= num_removed;
    assert(OK());
  }
}

template <typename PH>
void
Polyhedra_PowerSet<PH>::remove_higher_dimensions(dimension_type
						 new_dimension) {
  if (new_dimension < space_dim) {
    for (iterator i = Base::begin(), send = Base::end(); i != send; ++i) {
      i->remove_higher_dimensions(new_dimension);
      Base::reduced = false;
    }
    space_dim = new_dimension;
    assert(OK());
  }
}

template <typename PH>
bool
Polyhedra_PowerSet<PH>::
geometrically_covers(const Polyhedra_PowerSet& y) const {
  for (const_iterator yi = y.Base::begin(),
	 y_end = y.Base::end(); yi != y_end; ++yi)
    if (!check_containment(yi->element(), *this))
      return false;
  return true;
}

template <typename PH>
bool
Polyhedra_PowerSet<PH>::
geometrically_equals(const Polyhedra_PowerSet& y) const {
  const Polyhedra_PowerSet& x = * this;
  return x.geometrically_covers(y) && y.geometrically_covers(x);
}

template <typename PH>
template <typename PartialFunction>
void
Polyhedra_PowerSet<PH>::map_dimensions(const PartialFunction& pfunc) {
  if (Base::is_bottom()) {
    dimension_type n = 0;
    for (dimension_type i = space_dim; i-- > 0; ) {
      dimension_type new_i;
      if (pfunc.maps(i, new_i))
	++n;
    }
    space_dim = n;
  }
  else {
    iterator sbegin = Base::begin();
    for (iterator i = sbegin, send = Base::end(); i != send; ++i)
      i->map_dimensions(pfunc);
    space_dim = sbegin->space_dimension();
    Base::reduced = false;
  }
  assert(OK());
}

template <typename PH>
void
Polyhedra_PowerSet<PH>::pairwise_reduce() {
  // It is wise to omega-reduce before pairwise-reducing.
  Base::omega_reduce();

  size_type n = Base::size();
  size_type deleted;
  do {
    Sequence new_sequence;
    std::deque<bool> marked(n, false);
    deleted = 0;
    iterator sbegin = Base::begin();
    iterator send = Base::end();
    unsigned i_index = 0;
    for (iterator i = sbegin; i != send; ++i, ++i_index) {
      if (marked[i_index])
	continue;
      PH& pi = i->element();
      const_iterator j = i;
      int j_index = i_index;
      for (++j, ++j_index; j != send; ++j, ++j_index) {
	if (marked[j_index])
	  continue;
	const PH& pj = j->element();
	if (poly_hull_assign_if_exact(pi, pj)) {
	  marked[i_index] = marked[j_index] = true;
	  add_non_bottom_disjunct(new_sequence, pi);
	  ++deleted;
	  goto next;
	}
      }
    next:
      ;
    }
    iterator nsbegin = new_sequence.begin();
    iterator nsend = new_sequence.end();
    i_index = 0;
    for (const_iterator i = sbegin; i != send; ++i, ++i_index)
      if (!marked[i_index])
	add_non_bottom_disjunct(new_sequence, *i, nsbegin, nsend);
    std::swap(Base::sequence, new_sequence);
    n -= deleted;
  } while (deleted > 0);
  assert(OK());
}

template <typename PH>
template <typename Widening>
void
Polyhedra_PowerSet<PH>::
BGP99_heuristics_assign(const Polyhedra_PowerSet& y, Widening wf) {
#ifndef NDEBUG
  {
    // We assume that y entails *this.
    const Polyhedra_PowerSet<PH> x_copy = *this;
    const Polyhedra_PowerSet<PH> y_copy = y;
    assert(y_copy.definitely_entails(x_copy));
  }
#endif

  size_type n = Base::size();
  Sequence new_sequence;
  std::deque<bool> marked(n, false);
  const_iterator sbegin = Base::begin();
  const_iterator send = Base::end();
  unsigned i_index = 0;
  for (const_iterator i = sbegin; i != send; ++i, ++i_index)
    for (const_iterator j = y.Base::begin(),
	   y_end = y.Base::end(); j != y_end; ++j) {
      const PH& pi = i->element();
      const PH& pj = j->element();
      if (pi.contains(pj)) {
	PH pi_copy = pi;
	wf(pi_copy, pj);
	add_non_bottom_disjunct(new_sequence, pi_copy);
	marked[i_index] = true;
      }
    }
  iterator nsbegin = new_sequence.begin();
  iterator nsend = new_sequence.end();
  i_index = 0;
  for (const_iterator i = sbegin; i != send; ++i, ++i_index)
    if (!marked[i_index])
      add_non_bottom_disjunct(new_sequence, *i, nsbegin, nsend);
  std::swap(Base::sequence, new_sequence);
  assert(OK());
  assert(Base::is_omega_reduced());
}

template <typename PH>
template <typename Widening>
void
Polyhedra_PowerSet<PH>::
BGP99_extrapolation_assign(const Polyhedra_PowerSet& y,
			   Widening wf,
			   unsigned max_disjuncts) {
  // `x' is the current iteration value.
  Polyhedra_PowerSet<PH>& x = *this;

#ifndef NDEBUG
  {
    // We assume that y entails *this.
    const Polyhedra_PowerSet<PH> x_copy = x;
    const Polyhedra_PowerSet<PH> y_copy = y;
    assert(y_copy.definitely_entails(x_copy));
  }
#endif

  x.pairwise_reduce();
  if (max_disjuncts != 0)
    x.collapse(max_disjuncts);
  x.BGP99_heuristics_assign(y, wf);
}

template <typename PH>
template <typename Cert>
void
Polyhedra_PowerSet<PH>::
collect_certificates(std::map<Cert, size_type,
		              typename Cert::Compare>& cert_ms) const {
  assert(Base::is_omega_reduced());
  assert(cert_ms.size() == 0);
  for (const_iterator i = Base::begin(),
	 iend = Base::end(); i != iend; i++) {
    Cert ph_cert(i->element());
    ++cert_ms[ph_cert];
  }
}

template <typename PH>
template <typename Cert>
bool
Polyhedra_PowerSet<PH>::
is_cert_multiset_stabilizing(const std::map<Cert, size_type,
			                    typename Cert::Compare>& y_cert_ms
			     ) const {
  typedef std::map<Cert, size_type, typename Cert::Compare> Cert_Multiset;
  Cert_Multiset x_cert_ms;
  collect_certificates(x_cert_ms);
  typename Cert_Multiset::const_iterator
    xi = x_cert_ms.begin(),
    xend = x_cert_ms.end(),
    yi = y_cert_ms.begin(),
    yend = y_cert_ms.end();
  while (xi != xend && yi != yend) {
    const Cert& xi_cert = xi->first;  
    const Cert& yi_cert = yi->first;
    switch (xi_cert.compare(yi_cert)) {
    case 0:
      // xi_cert == yi_cert: check the number of multiset occurrences.
      {
	const size_type& xi_count = xi->second;
	const size_type& yi_count = yi->second;
	if (xi_count == yi_count) {
	  // Same number of occurrences: compare the next pair.
	  ++xi;
	  ++yi;
	}
	else
	  // Different number of occurrences: can decide ordering.
	  return xi_count < yi_count;
	break;
      }
    case 1:
      // xi_cert > yi_cert: it is not stabilizing.
      return false;

    case -1:
      // xi_cert < yi_cert: it is stabilizing.
      return true;
    }
  }
  // Here xi == xend or yi == yend.
  // Stabilization is achieved if `y_cert_ms' still has other elements.
  return (yi != yend);
}

template <typename PH>
template <typename Cert, typename Widening>
void
Polyhedra_PowerSet<PH>::BHZ03_widening_assign(const Polyhedra_PowerSet& y,
					      Widening wf) {
  // `x' is the current iteration value.
  Polyhedra_PowerSet<PH>& x = *this;

#ifndef NDEBUG
  {
    // We assume that y entails *this.
    const Polyhedra_PowerSet<PH> x_copy = x;
    const Polyhedra_PowerSet<PH> y_copy = y;
    assert(y_copy.definitely_entails(x_copy));
  }
#endif

  // First widening technique: do nothing.

  // If `y' is the empty collection, do nothing.
  assert(x.size() > 0);
  if (y.size() == 0)
    return;

  // Compute the poly-hull of `x'.
  PH x_hull(x.space_dim, PH::EMPTY);
  for (const_iterator i = x.begin(), iend = x.end(); i != iend; ++i)
    x_hull.poly_hull_assign(i->element());

  // Compute the poly-hull of `y'.
  PH y_hull(y.space_dim, PH::EMPTY);
  for (const_iterator i = y.begin(), iend = y.end(); i != iend; ++i)
    y_hull.poly_hull_assign(i->element());
  // Compute the certificate for `y_hull'.
  const Cert y_hull_cert(y_hull);

  // If the hull is stabilizing, do nothing.
  int hull_stabilization = y_hull_cert.compare(x_hull);
  if (hull_stabilization == 1)
    return;

  // Multiset ordering is only useful when `y' is not a singleton. 
  const bool y_is_not_a_singleton = y.size() > 1;

  // The multiset certificate for `y':
  // we want to be lazy about its computation.
  typedef std::map<Cert, size_type, typename Cert::Compare> Cert_Multiset;
  Cert_Multiset y_cert_ms;
  bool y_cert_ms_computed = false;

  if (hull_stabilization == 0 && y_is_not_a_singleton) {
    // Collect the multiset certificate for `y'.
    y.collect_certificates(y_cert_ms);
    y_cert_ms_computed = true;
    // If multiset ordering is stabilizing, do nothing.
    if (x.is_cert_multiset_stabilizing(y_cert_ms))
      return;
  }

  // Second widening technique: try the BGP99 powerset heuristics.
  Polyhedra_PowerSet<PH> bgp99_heuristics = x;
  bgp99_heuristics.BGP99_heuristics_assign(y, wf);

  // Compute the poly-hull of `bgp99_heuristics'.
  PH bgp99_heuristics_hull(x.space_dim, PH::EMPTY);
  for (const_iterator i = bgp99_heuristics.begin(),
	 iend = bgp99_heuristics.end(); i != iend; ++i)
    bgp99_heuristics_hull.poly_hull_assign(i->element());
  
  // Check for stabilization and, if successful,
  // commit to the result of the extrapolation.
  hull_stabilization = y_hull_cert.compare(bgp99_heuristics_hull);
  if (hull_stabilization == 1) {
    // The poly-hull is stabilizing.
    std::swap(x, bgp99_heuristics);
    return;
  }
  else if (hull_stabilization == 0 && y_is_not_a_singleton) {
    // If not already done, compute multiset certificate for `y'.
    if (!y_cert_ms_computed) {
      y.collect_certificates(y_cert_ms);
      y_cert_ms_computed = true;
    }
    if (bgp99_heuristics.is_cert_multiset_stabilizing(y_cert_ms)) {
      std::swap(x, bgp99_heuristics);
      return;
    }
    // Third widening technique: pairwise-reduction on `bgp99_heuristics'.
    // Note that pairwise-reduction does not affect the computation
    // of the poly-hulls, so that we only have to check the multiset
    // certificate relation.
    Polyhedra_PowerSet<PH> reduced_bgp99_heuristics(bgp99_heuristics);
    reduced_bgp99_heuristics.pairwise_reduce();
    if (reduced_bgp99_heuristics.is_cert_multiset_stabilizing(y_cert_ms)) {
      std::swap(x, reduced_bgp99_heuristics);
      return;
    }
  }

  // Fourth widening technique: this is applicable only when
  // `y_hull' is a proper subset of `bgp99_heuristics_hull'.
  if (bgp99_heuristics_hull.strictly_contains(y_hull)) {
    // Compute (y_hull \widen bgp99_heuristics_hull).
    PH ph = bgp99_heuristics_hull;
    wf(ph, y_hull);
    // Compute the poly-difference between `ph' and `bgp99_heuristics_hull'.
    ph.poly_difference_assign(bgp99_heuristics_hull);
    x.add_disjunct(ph);
    return;
  }

  // Fallback to the computation of the poly-hull.
  Polyhedra_PowerSet<PH> x_hull_singleton(x.space_dim, PH::EMPTY);
  x_hull_singleton.add_disjunct(x_hull);
  std::swap(x, x_hull_singleton);
}

template <typename PH>
template <typename Widening>
inline void
Polyhedra_PowerSet<PH>::
BHZ03_widening_assign(const Polyhedra_PowerSet& y, Widening wf) {
  BHZ03_widening_assign<BHRZ03_Certificate>(y, wf);
}

template <typename PH>
void
Polyhedra_PowerSet<PH>::ascii_dump(std::ostream& s) const {
  s << "size " << Base::size()
    << "\nspace_dim " << space_dim
    << std::endl;
  for (const_iterator i = Base::begin(), send = Base::end(); i != send; ++i)
    i->element().ascii_dump(s);
}

template <typename PH>
bool
Polyhedra_PowerSet<PH>::ascii_load(std::istream& s) {
  std::string str;

  if (!(s >> str) || str != "size")
    return false;

  size_type sz;

  if (!(s >> sz))
    return false;

  if (!(s >> str) || str != "space_dim")
    return false;

  if (!(s >> space_dim))
    return false;

  Polyhedra_PowerSet new_pps(space_dim, Polyhedron::EMPTY);
  while (sz-- > 0) {
    PH ph;
    if (!ph.ascii_load(s))
      return false;
    new_pps.add_disjunct(ph);
  }
  swap(new_pps);

  // Check for well-formedness.
  assert(OK());
  return true;
}

template <typename PH>
bool
Polyhedra_PowerSet<PH>::OK() const {
  for (const_iterator i = Base::begin(), send = Base::end(); i != send; ++i)
    if (i->space_dimension() != space_dim) {
#ifndef NDEBUG
      std::cerr << "Space dimension mismatch: is " << i->space_dimension()
		<< " in an element of the sequence,\nshould be "
		<< space_dim << " ."
		<< std::endl;
#endif
      return false;
    }
  return Base::OK();
}

} // namespace Parma_Polyhedra_Library


namespace std {

/*! \relates Parma_Polyhedra_Library::Polyhedra_PowerSet */
template <typename PH>
inline void
swap(Parma_Polyhedra_Library::Polyhedra_PowerSet<PH>& x,
     Parma_Polyhedra_Library::Polyhedra_PowerSet<PH>& y) {
  x.swap(y);
}

} // namespace std

#endif // !defined(PPL_Polyhedra_PowerSet_inlines_hh)


syntax highlighted by Code2HTML, v. 0.9.1