// Distributed under the MIT License.
// See LICENSE.txt for details.

/// \file
/// Defines class AdamsBashforthN

#pragma once

#include <algorithm>
#include <boost/iterator/transform_iterator.hpp>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <map>
#include <ostream>
#include <pup.h>
#include <type_traits>
#include <vector>

#include "ErrorHandling/Assert.hpp"
#include "ErrorHandling/Error.hpp"
#include "NumericalAlgorithms/Interpolation/LagrangePolynomial.hpp"
#include "Options/Options.hpp"
#include "Parallel/CharmPupable.hpp"
#include "Time/Time.hpp"
#include "Time/TimeId.hpp"
#include "Time/TimeSteppers/TimeStepper.hpp"  // IWYU pragma: keep
#include "Utilities/CachedFunction.hpp"
#include "Utilities/ConstantExpressions.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/MakeWithValue.hpp"
#include "Utilities/TMPL.hpp"

/// \cond
namespace TimeSteppers {
template <typename LocalVars, typename RemoteVars, typename CouplingResult>
class BoundaryHistory;  // IWYU pragma: keep
template <typename Vars, typename DerivVars>
class History;
}  // namespace TimeSteppers
/// \endcond

// IWYU pragma: no_include <sys/types.h>

namespace TimeSteppers {

/// \ingroup TimeSteppersGroup
///
/// An Nth Adams-Bashforth time stepper.
class AdamsBashforthN : public TimeStepper::Inherit {
 public:
  static constexpr const size_t maximum_order = 8;

  struct TargetOrder {
    using type = size_t;
    static constexpr OptionString help = {
        "Target order of Adams-Bashforth method."};
    static type lower_bound() { return 1; }
    static type upper_bound() { return maximum_order; }
  };
  struct SelfStart {
    using type = bool;
    static constexpr OptionString help = {
        "Start at first order and increase."};
    static type default_value() { return false; }
  };
  using options = tmpl::list<TargetOrder, SelfStart>;
  static constexpr OptionString help = {
      "An Adams-Bashforth Nth order time-stepper. The target order is the\n"
      "order of the method. If a self-starting approach is chosen then the\n"
      "method starts at first order and increases the step-size until the\n"
      "desired order is reached."};

  AdamsBashforthN() = default;
  explicit AdamsBashforthN(size_t target_order, bool self_start = false,
                           const OptionContext& context = {});
  AdamsBashforthN(const AdamsBashforthN&) noexcept = default;
  AdamsBashforthN& operator=(const AdamsBashforthN&) noexcept = default;
  AdamsBashforthN(AdamsBashforthN&&) noexcept = default;
  AdamsBashforthN& operator=(AdamsBashforthN&&) noexcept = default;
  ~AdamsBashforthN() noexcept override = default;

  template <typename Vars, typename DerivVars>
  void update_u(gsl::not_null<Vars*> u,
                gsl::not_null<History<Vars, DerivVars>*> history,
                const TimeDelta& time_step) const noexcept;

  // This is defined as a separate type alias to keep the doxygen page
  // width somewhat under control.
  template <typename LocalVars, typename RemoteVars, typename Coupling>
  using BoundaryHistoryType =
      BoundaryHistory<LocalVars, RemoteVars,
                      std::result_of_t<const Coupling&(LocalVars, RemoteVars)>>;

  /*!
   * An explanation of the computation being performed by this
   * function:
   * \f$\newcommand\tL{t^L}\newcommand\tR{t^R}\newcommand\tU{\tilde{t}\!}
   * \newcommand\mat{\mathbf}\f$
   *
   * Suppose the local and remote sides of the interface are evaluated
   * at times \f$\ldots, \tL_{-1}, \tL_0, \tL_1, \ldots\f$ and
   * \f$\ldots, \tR_{-1}, \tR_0, \tR_1, \ldots\f$, respectively, with
   * the starting location of the numbering arbitrary in each case.
   * Let the step we wish to calculate the effect of be the step from
   * \f$\tL_{m_S}\f$ to \f$\tL_{m_S+1}\f$.  We call the sequence
   * produced from the union of the local and remote time sequences
   * \f$\ldots, \tU_{-1}, \tU_0, \tU_1, \ldots\f$.  For example, one
   * possible sequence of times is:
   * \f{equation}
   *   \begin{aligned}
   *     \text{Local side:} \\ \text{Union times:} \\ \text{Remote side:}
   *   \end{aligned}
   *   \cdots
   *   \begin{gathered}
   *     \, \\ \tU_1 \\ \tR_5
   *   \end{gathered}
   *   \leftarrow \Delta \tU_1 \rightarrow
   *   \begin{gathered}
   *     \tL_4 \\ \tU_2 \\ \,
   *   \end{gathered}
   *   \leftarrow \Delta \tU_2 \rightarrow
   *   \begin{gathered}
   *     \, \\ \tU_3 \\ \tR_6
   *   \end{gathered}
   *   \leftarrow \Delta \tU_3 \rightarrow
   *   \begin{gathered}
   *    \, \\ \tU_4 \\ \tR_7
   *   \end{gathered}
   *   \leftarrow \Delta \tU_4 \rightarrow
   *   \begin{gathered}
   *     \tL_5 \\ \tU_5 \\ \,
   *   \end{gathered}
   *   \cdots
   * \f}
   * We call the indices of the step's start and end times in the
   * union time sequence \f$n_S\f$ and \f$n_E\f$, respectively.  We
   * define \f$n^L_m\f$ to be the union-time index corresponding to
   * \f$\tL_m\f$ and \f$m^L_n\f$ to be the index of the last local
   * time not later than \f$\tU_n\f$ and similarly for the remote
   * side.  So for the above example, \f$n^L_4 = 2\f$ and \f$m^R_2 =
   * 5\f$, and if we wish to compute the step from \f$\tL_4\f$ to
   * \f$\tL_5\f$ we would have \f$m_S = 4\f$, \f$n_S = 2\f$, and
   * \f$n_E = 5\f$.
   *
   * If we wish to evaluate the change over this step to \f$k\f$th
   * order, we can write the change in the value as a linear
   * combination of the values of the coupling between the elements at
   * unequal times:
   * \f{equation}
   *   \mat{F}_{m_S} =
   *   \mspace{-10mu}
   *   \sum_{q^L = m_S-(k-1)}^{m_S}
   *   \,
   *   \sum_{q^R = m^R_{n_S}-(k-1)}^{m^R_{n_E-1}}
   *   \mspace{-10mu}
   *   \mat{D}_{q^Lq^R}
   *   I_{q^Lq^R},
   * \f}
   * where \f$\mat{D}_{q^Lq^R}\f$ is the coupling function evaluated
   * between data from \f$\tL_{q^L}\f$ and \f$\tR_{q^R}\f$.  The
   * coefficients can be written as the sum of three terms,
   * \f{equation}
   *   I_{q^Lq^R} = I^E_{q^Lq^R} + I^R_{q^Lq^R} + I^L_{q^Lq^R},
   * \f}
   * which can be interpreted as a contribution from equal-time
   * evaluations and contributions related to the remote and local
   * evaluation times.  These are given by
   * \f{align}
   *   I^E_{q^Lq^R} &=
   *   \mspace{-10mu}
   *   \sum_{n=n_S}^{\min\left\{n_E, n^L+k\right\}-1}
   *   \mspace{-10mu}
   *   \tilde{\alpha}_{n,n-n^L} \Delta \tU_n
   *   &&\text{if $\tL_{q^L} = \tR_{q^R}$, otherwise 0}
   *   \\
   *   I^R_{q^Lq^R} &=
   *   \ell_{q^L - m_S + k}\!\left(
   *     \tU_{n^R}; \tL_{m_S - (k-1)}, \ldots, \tL_{m_S}\right)
   *   \mspace{-10mu}
   *   \sum_{n=\max\left\{n_S, n^R\right\}}
   *       ^{\min\left\{n_E, n^R+k\right\}-1}
   *   \mspace{-10mu}
   *   \tilde{\alpha}_{n,n-n^R} \Delta \tU_n
   *   &&\text{if $\tR_{q^R}$ is not in $\{\tL_{\vphantom{|}\cdots}\}$,
   *     otherwise 0}
   *   \\
   *   I^L_{q^Lq^R} &=
   *   \mspace{-10mu}
   *   \sum_{n=\max\left\{n_S, n^R\right\}}
   *       ^{\min\left\{n_E, n^L+k, n^R_{q^R+k}\right\}-1}
   *   \mspace{-10mu}
   *   \ell_{q^R - m^R_n + k}\!\left(\tU_{n^L};
   *     \tR_{m^R_n - (k-1)}, \ldots, \tR_{m^R_n}\right)
   *   \tilde{\alpha}_{n,n-n^L} \Delta \tU_n
   *   &&\text{if $\tL_{q^L}$ is not in $\{\tR_{\vphantom{|}\cdots}\}$,
   *     otherwise 0,}
   * \f}
   * where for brevity we write \f$n^L = n^L_{q^L}\f$ and \f$n^R =
   * n^R_{q^R}\f$, and where \f$\ell_a(t; x_1, \ldots, x_k)\f$ a
   * Lagrange interpolating polynomial and \f$\tilde{\alpha}_{nj}\f$
   * is the \f$j\f$th coefficient for an Adams-Bashforth step over the
   * union times from step \f$n\f$ to step \f$n+1\f$.
   */
  template <typename LocalVars, typename RemoteVars, typename Coupling>
  std::result_of_t<const Coupling&(LocalVars, RemoteVars)>
  compute_boundary_delta(
      const Coupling& coupling,
      gsl::not_null<BoundaryHistoryType<LocalVars, RemoteVars, Coupling>*>
          history,
      const TimeDelta& time_step) const noexcept;

  uint64_t number_of_substeps() const noexcept override;

  size_t number_of_past_steps() const noexcept override;

  bool is_self_starting() const noexcept override;

  double stable_step() const noexcept override;

  TimeId next_time_id(const TimeId& current_id,
                      const TimeDelta& time_step) const noexcept override;

  template <typename Vars, typename DerivVars>
  bool can_change_step_size(
      const TimeId& time_id,
      const TimeSteppers::History<Vars, DerivVars>& history) const noexcept;

  WRAPPED_PUPable_decl_template(AdamsBashforthN);  // NOLINT

  explicit AdamsBashforthN(CkMigrateMessage* /*unused*/) noexcept {}

  // clang-tidy: do not pass by non-const reference
  void pup(PUP::er& p) noexcept override;  // NOLINT

 private:
  friend bool operator==(const AdamsBashforthN& lhs,
                         const AdamsBashforthN& rhs) noexcept;

  /// Get coefficients for a time step.  Arguments are an iterator
  /// pair to past times, oldest to newest, and the time step to take.
  template <typename Iterator>
  static std::vector<double> get_coefficients(const Iterator& times_begin,
                                              const Iterator& times_end,
                                              const TimeDelta& step) noexcept;

  static std::vector<double> get_coefficients_impl(
      const std::vector<double>& steps) noexcept;

  static std::vector<double> variable_coefficients(
      const std::vector<double>& steps) noexcept;

  static std::vector<double> constant_coefficients(size_t order) noexcept;

  /// Comparator for ordering by "simulation time"
  class SimulationLess {
   public:
    explicit SimulationLess(const bool forward_in_time) noexcept
        : forward_in_time_(forward_in_time) {}

    bool operator()(const Time& a, const Time& b) const noexcept {
      return forward_in_time_ ? a < b : b < a;
    }

   private:
    bool forward_in_time_;
  };

  size_t target_order_ = 3;
  bool is_self_starting_ = true;
};

bool operator!=(const AdamsBashforthN& lhs,
                const AdamsBashforthN& rhs) noexcept;

template <typename Vars, typename DerivVars>
void AdamsBashforthN::update_u(
    const gsl::not_null<Vars*> u,
    const gsl::not_null<History<Vars, DerivVars>*> history,
    const TimeDelta& time_step) const noexcept {
  ASSERT(is_self_starting_ or target_order_ == history->size(),
         "Length of history should be the order, so "
         << target_order_ << ", but is: " << history->size());
  ASSERT(history->size() > 0, "No history provided");
  ASSERT(history->size() <= target_order_,
         "Length of history (" << history->size() << ") "
         << "should not exceed target order (" << target_order_ << ")");

  const auto& coefficients =
      get_coefficients(history->begin(), history->end(), time_step);

  const auto do_update =
      [u, &time_step, &coefficients, &history](auto order) noexcept {
    *u += time_step.value() * constexpr_sum<order>(
        [order, &coefficients, &history](auto i) noexcept {
          return coefficients[order - 1 - i] *
              (history->begin() + static_cast<ssize_t>(i)).derivative();
        });
  };

  switch (history->size()) {
    case 1:
      do_update(std::integral_constant<size_t, 1>{});
      break;
    case 2:
      do_update(std::integral_constant<size_t, 2>{});
      break;
    case 3:
      do_update(std::integral_constant<size_t, 3>{});
      break;
    case 4:
      do_update(std::integral_constant<size_t, 4>{});
      break;
    case 5:
      do_update(std::integral_constant<size_t, 5>{});
      break;
    case 6:
      do_update(std::integral_constant<size_t, 6>{});
      break;
    case 7:
      do_update(std::integral_constant<size_t, 7>{});
      break;
    case 8:
      do_update(std::integral_constant<size_t, 8>{});
      break;
    default:
      ERROR("Bad amount of history data: " << history->size());
  }

  history->mark_unneeded(history->begin() + 1);
}

template <typename LocalVars, typename RemoteVars, typename Coupling>
std::result_of_t<const Coupling&(LocalVars, RemoteVars)>
AdamsBashforthN::compute_boundary_delta(
    const Coupling& coupling,
    const gsl::not_null<BoundaryHistoryType<LocalVars, RemoteVars, Coupling>*>
        history,
    const TimeDelta& time_step) const noexcept {
  const auto order = history->local_size();

  // Avoid billions of casts
  const auto order_s = static_cast<ssize_t>(order);

  ASSERT(is_self_starting_ or order == target_order_,
         "Local history has wrong length (" << order
         << " should be " << target_order_ << ")");
  ASSERT(order <= target_order_,
         "Local history is too long for target order (" << order
         << " should not exceed " << target_order_ << ")");
  ASSERT(history->remote_size() >= order,
         "Remote history is too short (" << history->remote_size()
         << " should be at least " << order << ")");

  // Start and end of the step we are trying to take
  const Time start_time = *(history->local_end() - 1);
  const Time end_time = start_time + time_step;

  // If a remote evaluation is done at the start of the step then that
  // is part of the history for the first union step.  When we did
  // history cleanup at the end of the previous step we didn't know we
  // were going to get this point so we kept an extra remote history
  // value.
  if (history->remote_size() > order and
      *(history->remote_begin() + order_s) == start_time) {
    history->remote_mark_unneeded(history->remote_begin() + 1);
  }

  // Result variable.  We evaluate the coupling only for the
  // structure.  This evaluation may be expensive, but by choosing the
  // most recent times on both sides we should guarantee that it is a
  // result we need later, so this will serve to get it into the
  // coupling cache so we don't have to compute it when we actually use it.
  auto accumulated_change =
      make_with_value<std::result_of_t<const Coupling&(LocalVars, RemoteVars)>>(
          history->coupling(coupling, history->local_end() - 1,
                            history->remote_end() - 1),
          0.);

  if (history->local_size() == history->remote_size() and
      std::equal(history->local_begin(), history->local_end(),
                 history->remote_begin())) {
    // No local time-stepping going on.
    const auto coefficients = get_coefficients(history->local_begin(),
                                               history->local_end(), time_step);

    auto local_it = history->local_begin();
    auto remote_it = history->remote_begin();
    for (auto coefficients_it = coefficients.rbegin();
         coefficients_it != coefficients.rend();
         ++coefficients_it, ++local_it, ++remote_it) {
      accumulated_change +=
          *coefficients_it * history->coupling(coupling, local_it, remote_it);
    }
    accumulated_change *= time_step.value();

    // The remote-side values will all be needed if the remote step is
    // larger than our local one.  If not, they will be cleaned up
    // next time.
    history->local_mark_unneeded(history->local_begin() + 1);

    return accumulated_change;
  }

  ASSERT(order == target_order_,
         "Cannot perform local time-stepping while self-starting.");

  const SimulationLess simulation_less(time_step.is_positive());

  ASSERT(std::is_sorted(history->local_begin(), history->local_end(),
                        simulation_less),
         "Local history not in order");
  ASSERT(std::is_sorted(history->remote_begin(), history->remote_end(),
                        simulation_less),
         "Remote history not in order");
  ASSERT(not simulation_less(start_time,
                             *(history->remote_begin() + (order_s - 1))),
         "Remote history does not extend far enough back");
  ASSERT(simulation_less(*(history->remote_end() - 1), end_time),
         "Please supply only older data: " << *(history->remote_end() - 1)
         << " is not before " << end_time);

  // Union of times of all step boundaries on any side.
  const auto union_times = [&end_time, &history, &simulation_less]() noexcept {
    std::vector<Time> ret;
    ret.reserve(history->local_size() + history->remote_size() + 1);
    std::set_union(history->local_begin(), history->local_end(),
                   history->remote_begin(), history->remote_end(),
                   std::back_inserter(ret), simulation_less);
    ret.push_back(end_time);
    return ret;
  }();

  using UnionIter = typename decltype(union_times)::const_iterator;

  // Find the union times iterator for a given time.
  const auto union_step =
      [&union_times, &simulation_less](const Time& t) noexcept {
    return std::lower_bound(union_times.cbegin(), union_times.cend(), t,
                            simulation_less);
  };

  // The union time index for the step start and end.
  const auto union_step_start = union_step(start_time);
  const auto union_step_end = union_times.cend() - 1;

  // min(union_step_end, it + order_s) except being careful not
  // to create out-of-range iterators.
  const auto advance_within_step =
      [order_s, union_step_end](const UnionIter& it) noexcept {
    return union_step_end - it > order_s ? it + order_s : union_step_end;
  };

  // Calculating the Adams-Bashforth coefficients is somewhat
  // expensive, so we cache them.  ab_coefs(it) returns the
  // coefficients used to step from it to *(it + 1).
  auto ab_coefs = make_cached_function<UnionIter, std::map>([order_s](
      const UnionIter& times_end) noexcept {
    return get_coefficients(times_end - (order_s - 1), times_end + 1,
                            *(times_end + 1) - *times_end);
  });

  for (auto local_evaluation_step = history->local_begin();
       local_evaluation_step != history->local_end();
       ++local_evaluation_step) {
    const auto union_local_evaluation_step = union_step(*local_evaluation_step);
    for (auto remote_evaluation_step = history->remote_begin();
         remote_evaluation_step != history->remote_end();
         ++remote_evaluation_step) {
      double deriv_coef = 0.;

      // The value of the coefficient of `evaluation_step` when doing
      // a standard Adams-Bashforth integration over the union times
      // from `step` to `step + 1`.
      const auto base_summand = [&ab_coefs](
          const UnionIter& step, const UnionIter& evaluation_step) noexcept {
        return ((step + 1)->value() - step->value()) *
               ab_coefs(step)[static_cast<size_t>(step - evaluation_step)];
      };

      if (*local_evaluation_step == *remote_evaluation_step) {
        // The two elements stepped at the same time.  This gives a
        // standard Adams-Bashforth contribution to each segment
        // making up the current step.
        const auto union_step_upper_bound =
            advance_within_step(union_local_evaluation_step);
        for (auto step = union_step_start;
             step < union_step_upper_bound;
             ++step) {
          deriv_coef += base_summand(step, union_local_evaluation_step);
        }
      } else {
        // In this block we consider a coupling evaluation that is not
        // performed at equal times on the two sides of the mortar.

        // Makes an iterator with a map to give time as a double.
        const auto make_lagrange_iterator = [](const auto& it) noexcept {
          return boost::make_transform_iterator(
              it, [](const Time& t) noexcept { return t.value(); });
        };

        const auto union_remote_evaluation_step =
            union_step(*remote_evaluation_step);
        const auto union_step_lower_bound =
            std::max(union_step_start, union_remote_evaluation_step);

        // Compute the contribution to an interpolation over the local
        // times to `remote_evaluation_step->value()`, which we will
        // use as the coupling value for that time.  If there is an
        // actual evaluation at that time then skip this because the
        // Lagrange polynomial will be zero.
        if (not std::binary_search(history->local_begin(), history->local_end(),
                                   *remote_evaluation_step, simulation_less)) {
          const auto union_step_upper_bound =
              advance_within_step(union_remote_evaluation_step);
          for (auto step = union_step_lower_bound;
               step < union_step_upper_bound;
               ++step) {
            deriv_coef += base_summand(step, union_remote_evaluation_step);
          }
          deriv_coef *= lagrange_polynomial(
              make_lagrange_iterator(local_evaluation_step),
              remote_evaluation_step->value(),
              make_lagrange_iterator(history->local_begin()),
              make_lagrange_iterator(history->local_end()));
        }

        // Same qualitative calculation as the previous block, but
        // interpolating over the remote times.  This case is somewhat
        // more complicated because the latest remote time that can be
        // used varies for the different segments making up the step.
        if (not std::binary_search(history->remote_begin(),
                                   history->remote_end(),
                                   *local_evaluation_step, simulation_less)) {
          auto union_step_upper_bound =
              advance_within_step(union_local_evaluation_step);
          if (history->remote_end() - remote_evaluation_step > order_s) {
            union_step_upper_bound = std::min(
                union_step_upper_bound,
                union_step(*(remote_evaluation_step + order_s)));
          }

          auto control_points = make_lagrange_iterator(
              remote_evaluation_step - history->remote_begin() >= order_s
                  ? remote_evaluation_step - (order_s - 1)
                  : history->remote_begin());
          for (auto step = union_step_lower_bound;
               step < union_step_upper_bound;
               ++step, ++control_points) {
            deriv_coef += base_summand(step, union_local_evaluation_step) *
                          lagrange_polynomial(
                              make_lagrange_iterator(remote_evaluation_step),
                              local_evaluation_step->value(),
                              control_points,
                              control_points + order_s);
          }
        }
      }

      if (deriv_coef != 0.) {
        // Skip the (potentially expensive) coupling calculation if
        // the coefficient is zero.
        accumulated_change +=
            deriv_coef * history->coupling(coupling, local_evaluation_step,
                                           remote_evaluation_step);
      }
    }  // for remote_evaluation_step
  }  // for local_evaluation_step

  // Clean up old history

  // We know that the local side will step at end_time, so the step
  // containing that time will be the next step, which is not
  // currently in the history.  We therefore know we won't need the
  // oldest value for the next step.
  history->local_mark_unneeded(history->local_begin() + 1);
  // We don't know whether the remote side will step at end_time, so
  // we have to be conservative and assume they will not.  If it does
  // we will remove the first value at the start of the next call to
  // this function.
  history->remote_mark_unneeded(history->remote_end() - order_s);

  return accumulated_change;
}

template <typename Vars, typename DerivVars>
bool AdamsBashforthN::can_change_step_size(
    const TimeId& time_id,
    const TimeSteppers::History<Vars, DerivVars>& history) const noexcept {
  // We need to forbid local time-stepping before initialization is
  // complete.  The self-start procedure itself should never consider
  // changing the step size, but we need to wait during the main
  // evolution until the self-start history has been replaced with
  // "real" values.
  const SimulationLess less(time_id.time_runs_forward());
  return history.size() == 0 or
         (less(history.back(), time_id.time()) and
          std::is_sorted(history.begin(), history.end(), less));
}

template <typename Iterator>
std::vector<double> AdamsBashforthN::get_coefficients(
    const Iterator& times_begin, const Iterator& times_end,
    const TimeDelta& step) noexcept {
  std::vector<double> steps;
  // This may be slightly more space than we need, but we can't get
  // the exact amount without iterating through the iterators, which
  // is not necessarily cheap depending on the iterator type.
  steps.reserve(maximum_order);
  for (auto t = times_begin; std::next(t) != times_end; ++t) {
    steps.push_back((*std::next(t) - *t) / step);
  }
  steps.push_back(1.);
  return get_coefficients_impl(steps);
}

}  // namespace TimeSteppers


#include "wali/graph/InterGraph.hpp"
#include "wali/graph/IntraGraph.hpp"
#include "wali/graph/RegExp.hpp"
#include "wali/graph/Functional.hpp"

#include "wali/util/Timer.hpp"

#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <sstream>
#include <boost/cast.hpp>

// ::wali
#include "wali/SemElemTensor.hpp"

typedef int INTER_GRAPH_INT;

using namespace std;

namespace wali {

    namespace graph {

        UnionFind::UnionFind(int len) {
            n = len;
            count = len;
            arr = new int[n];
            for(int i=0; i < n;i++) {
                arr[i] = i;
            }
        }

        UnionFind::UnionFind(UnionFind& from) : n(from.n), count(from.count){
          arr = new int[n];
          for(int i=0; i<n; ++i)
            arr[i] = from.arr[i];
        }

        UnionFind::~UnionFind() {
            delete [] arr;
            arr = NULL;
            n = 0;
        }

        void UnionFind::reset() {
            for(int i=0; i < n;i++) {
                arr[i] = i;
            }
        }

        int UnionFind::find(int a) {
            int r = a,p;
            while(arr[r] != r) {
                r = arr[r];
            }
            // path compression
            while(arr[a] != a) {
                p = arr[a];
                arr[a] = r;
                a = p;
            }
            return r;
        }

        void UnionFind::takeUnion(int a, int b) {
            int ar = find(a);
            int br = find(b);
            arr[ar] = br; // FIXME: Randomize this
            //TODO: Used ranked union

            // if two sets were merged, the total number of sets has decreaseed
            // by one
            if(ar != br) count--;
            assert(count > 0 && "Count of sets reached zero: There is a bug\n");
        }

        int UnionFind::countSets(){
          return count;
        }

        std::ostream& UnionFind::print(std::ostream& o)
        {
          std::multiset< std::pair< int, int > > shadow;
          int curset = -1;

          o << "[ ";
          for(int i=0; i<n; ++i){
            shadow.insert(std::pair< int, int >(find(i),i));
          }

          for(std::multiset< std::pair< int, int > >::iterator iter = shadow.begin();
              iter != shadow.end();
              ++iter){
            if(iter->first > curset){
              if(curset != -1)
                o << ">>>>>>>>>" << std::endl;
              curset = iter->first;
            }
            o << iter->second << " ";
          }
          o << "]";
          return o;
        }

        inter_node_t promote_type(inter_node_t t1, inter_node_t t2) {
            if(t1 == InterNone)
                return t2;
            if(t2 == InterNone)
                return t1;
            if(t1 == t2)
                return t1;
            return InterSourceOutNode;
        }

        bool is_source_type(inter_node_t t1) {
            return (t1 == InterSource || t1 == InterSourceOutNode);
        }

        sem_elem_tensor_t tensorSetUpFP(sem_elem_tensor_t a, sem_elem_tensor_t b)
        {
          return a->tensor(b.get_ptr());
        }

        void ETransHandler::addEdge(int call, int ret, sem_elem_t wtCallRule) {
          EdgeMap::iterator it = edgeMap.find(ret);
          if(it != edgeMap.end()) {
            Dependency &d = it->second;
            d.second = d.second->combine(wtCallRule);
          } else {
            edgeMap[ret] = Dependency(call, wtCallRule);
          }
        }

        void ETransHandler::tensorAllWeights()
        {
          if(edgeMap.size() == 0)
            return;
          Dependency dy = edgeMap.begin()->second;
          sem_elem_tensor_t one = boost::polymorphic_downcast<SemElemTensor*>(dy.second->one().get_ptr());
          for(EdgeMap::iterator it = edgeMap.begin(); it != edgeMap.end(); ++it)
            edgeMap[it->first] = Dependency(it->second.first, tensorSetUpFP(one, boost::polymorphic_downcast<SemElemTensor*>(it->second.second.get_ptr())).get_ptr());
        }

        bool ETransHandler::exists(int ret) {
          EdgeMap::iterator it = edgeMap.find(ret);
          if(it != edgeMap.end()) {
            return true;
          }
          return false;
        }

        // return NULL if no match found
        sem_elem_t ETransHandler::get_dependency(int ret, int &call) {
          EdgeMap::iterator it = edgeMap.find(ret);
          if(it != edgeMap.end()) {
            Dependency &d = it->second;
            call = d.first;
            return d.second;
          }
          return sem_elem_t(0);
        }

        InterGraph::InterGraph(wali::sem_elem_t s, bool e, bool pre, bool n)
        {
          sem = s;
          intra_graph_uf = NULL;
          running_ewpds = e;
          running_nwpds = n;
          running_prestar = pre;
          max_scc_computed = 0;
          newtonGr = NULL;
          runningNewton = false;
          dag = new RegExpDag();
          count = 0;
          isOutputAutomatonTensored = false;
        }

        InterGraph::~InterGraph() {
          delete dag;
          std::set<IntraGraph*> deleteGr;
          for(unsigned i = 0; i < nodes.size(); i++) {
            if(nodes[i].gr && intra_graph_uf->find(i) == (int)i) {
              deleteGr.insert(nodes[i].gr);
              nodes[i].gr=NULL;
            }
              nodes[i].sccgr = NULL;
          }
          for(std::set<IntraGraph*>::iterator it = deleteGr.begin(); it != deleteGr.end(); ++it)
            delete *it;

          if(intra_graph_uf) {
            delete intra_graph_uf;
            intra_graph_uf = NULL;
          }
          if(newtonGr){
            delete newtonGr;
            newtonGr = NULL;
          }
        }

        int InterGraph::nodeno(Transition &t) {
          TransMap::iterator it = node_number.find(t);
          if(it == node_number.end()) {
            node_number[t] = static_cast<INTER_GRAPH_INT>(nodes.size());
            nodes.push_back(GraphNode(t));
            return (static_cast<INTER_GRAPH_INT>(nodes.size()) - 1);
          }
          return it->second;
        }

        bool InterGraph::exists(Transition &t) {
          TransMap::iterator it = node_number.find(t);
          if(it == node_number.end()) {
            return false;
          }
          return true;
        }

        int InterGraph::intra_edgeno(Transition &src, Transition &tgt) {
          int s = nodeno(src);
          int t = nodeno(tgt);
          std::list<int>::iterator it = nodes[s].outgoing.begin();
          for(; it != nodes[s].outgoing.end(); it++) {
            if(intra_edges[*it].tgt == t) {
              return *it;
            }
          }
          return -1;
        }

        int InterGraph::inter_edgeno(Transition &src1, Transition &src2, Transition &tgt) {
          int s1 = nodeno(src1);
          int s2 = nodeno(src2);
          int t = nodeno(tgt);
          std::list<int>::iterator it = nodes[s2].out_hyper_edges.begin();
          for(; it != nodes[s2].out_hyper_edges.end(); it++) {
            if(inter_edges[*it].tgt == t && inter_edges[*it].src1 == s1) {
              return *it;
            }
          }
          return -1;
        }

        bool InterGraph::exists(int state, int stack, WT_CHECK op) {
          size_t i;
          for(i=0;i<nodes.size();i++) {
            if(static_cast<INTER_GRAPH_INT>(nodes[i].trans.src) == state
                && static_cast<INTER_GRAPH_INT>(nodes[i].trans.stack) == stack)
            {
              if(newtonGr){
                if(op((newtonGr->get_weight(nodes[i].intra_nodeno)).get_ptr()))
                  return true;
              }else{
                  if(op((nodes[i].gr->get_weight(nodes[i].intra_nodeno)).get_ptr()))
                    return true;
              }
            }
          }
          return false;
        }

        std::ostream &InterGraph::print(std::ostream &out, PRINT_OP pop) {
          unsigned i;
          if(CONSTANT_CONDITION(1))//intra_graph_uf == NULL)
          {
            out << "Source Transitions:\n";
            for(i=0;i<nodes.size();i++) {
              if(is_source_type(nodes[i].type)) {
                IntraGraph::print_trans(nodes[i].trans, out, pop);
                nodes[i].weight->print(out);
                out << "\n";
              }
            }
            out << "IntraEdges:\n";
            for(i=0;i<intra_edges.size();i++) {
              Transition src = nodes[intra_edges[i].src].trans;
              Transition tgt = nodes[intra_edges[i].tgt].trans;
              IntraGraph::print_trans(src, out, pop);
              out << "-->";
              IntraGraph::print_trans(tgt, out, pop);
              intra_edges[i].weight->print(out);
              out << "\n";
            }
          } else {
            for(i = 0; i < nodes.size(); i++) {
              if(nodes[i].gr && intra_graph_uf->find(i) == (int)i) {
                nodes[i].gr->print(out,pop);
                out << "\n";
              }
            }
          }
          out << "HyperEdges:\n";
          for(i = 0; i < inter_edges.size(); i++) {
            Transition src1 = nodes[inter_edges[i].src1].trans;
            Transition src2 = nodes[inter_edges[i].src2].trans;
            Transition tgt = nodes[inter_edges[i].tgt].trans;
            IntraGraph::print_trans(src1, out, pop);
            out << ",";
            IntraGraph::print_trans(src2, out, pop);
            out << "-->";
            IntraGraph::print_trans(tgt, out, pop);
            if(inter_edges[i].weight.get_ptr()) {
              inter_edges[i].weight->print(out);
            } else {
              inter_edges[i].mf->print(out);
            }
            out << "\n";
          }
          return out;
        }

        void InterGraph::addEdge(Transition src, Transition tgt, wali::sem_elem_t se) {
          int eno = intra_edgeno(src,tgt);
          if(eno != -1) { // edge already present
            intra_edges[eno].weight = intra_edges[eno].weight->combine(se);
            return;
          }
          int s = nodeno(src);
          int t = nodeno(tgt);
          GraphEdge ed(s,t,se);
          intra_edges.push_back(ed);
          int e = intra_edges.size() - 1;
          nodes[s].outgoing.push_back(e);
          nodes[t].incoming.push_back(e);
        }

        void InterGraph::addCallRetEdge(Transition src, Transition tgt, wali::sem_elem_t se) {
          addEdge(src, tgt, se->one());
          int s = nodeno(src);
          int t = nodeno(tgt);
          eHandler.addEdge(s, t, se);
        }

        void InterGraph::addEdge(Transition src1, Transition src2, Transition tgt, wali::sem_elem_t se) {
          int eno = inter_edgeno(src1,src2,tgt);
          if(eno != -1) { // edge already present
            inter_edges[eno].weight = inter_edges[eno].weight->combine(se);
            return;
          }
          int s1 = nodeno(src1);
          int s2 = nodeno(src2);
          int t = nodeno(tgt);
          HyperEdge ed(s1,s2,t,se);
          nodes[s2].type = promote_type(nodes[s2].type, InterOutNode);
          inter_edges.push_back(ed);
          nodes[s2].out_hyper_edges.push_back(inter_edges.size() - 1);
          nodes[s1].out1_hyper_edges.push_back(inter_edges.size() - 1);

        }

        void InterGraph::addEdge(Transition src1, Transition src2, Transition tgt, merge_fn_t mf) {
          assert(running_ewpds);
          int eno = inter_edgeno(src1,src2,tgt);
          if(eno != -1 && mf == inter_edges[eno].mf) { // edge already present
            return;
          }
          int s1 = nodeno(src1);
          int s2 = nodeno(src2);
          int t = nodeno(tgt);
          HyperEdge ed(s1,s2,t,mf);
          nodes[s2].type = promote_type(nodes[s2].type, InterOutNode);
          inter_edges.push_back(ed);
          nodes[s2].out_hyper_edges.push_back(inter_edges.size() - 1);
          nodes[s1].out1_hyper_edges.push_back(inter_edges.size() - 1);
        }

        void InterGraph::addCallEdge(Transition src1, Transition src2) {
          call_edges.push_back(call_edge_t(nodeno(src1),nodeno(src2)));
        }

        void InterGraph::setSource(Transition t, wali::sem_elem_t se) {
          int n = nodeno(t);
          nodes[n].type = promote_type(nodes[n].type, InterSource);
          nodes[n].weight = se;
        }

        void InterGraph::setESource(Transition t, wali::sem_elem_t wtAtCall, wali::sem_elem_t wtAfterCall) {
          // setSource
          int n = nodeno(t);
          nodes[n].type = promote_type(nodes[n].type, InterSource);
          nodes[n].weight = wtAtCall;
          // Extra dependency
          eHandler.addEdge(-1, n, wtAfterCall);
        }

        unsigned InterGraph::SCCLight(SCCGraphs& grlist, SCCGraphs& grsorted)
        {
          SCCGraphs::iterator gr_it;
          // reset visited
          for(gr_it = grlist.begin(); gr_it != grlist.end(); gr_it++) {
            (*gr_it)->visited = false;
            (*gr_it)->scc_number = 0;
          }
          SCCGraphs finished;
          std::map<scc_graph_t , SCCGraphs, SCCGraphLE> rev_edges;
          // Do DFS
          SCCGraphs::reverse_iterator gr_rit;
          for(gr_rit = grlist.rbegin(); gr_rit != grlist.rend(); gr_rit++) {
            scc_graph_t gr = *gr_rit;
            if(gr->visited)
              continue;
            dfsLight(gr,finished,rev_edges);
          }

          unsigned scc = 0;

          for(gr_it = grlist.begin(); gr_it != grlist.end(); gr_it++) {
            (*gr_it)->visited = false;
          }

          for(gr_it = finished.begin(); gr_it != finished.end(); gr_it++) {
            scc_graph_t gr = *gr_it;
            if(gr->visited)
              continue;
            scc++;
            typedef pair<scc_graph_t, SCCGraphs::iterator> StackEl;
            std::list<StackEl> stack;
            stack.push_back(StackEl(gr, rev_edges[gr].begin()));
            while(!stack.empty()) {
              StackEl p = stack.front();
              stack.pop_front();
              scc_graph_t v = p.first;
              SCCGraphs::iterator it = p.second;
              v->scc_number = scc;
              if(!v->visited) grsorted.push_back(v);
              v->visited = true;
              while(it != rev_edges[v].end()) {
                scc_graph_t c = *it;
                if(c->visited) {
                  it++;
                } else {
                  stack.push_front(StackEl(v,++it));
                  stack.push_front(StackEl(c, rev_edges[c].begin()));
                  break;
                }
              }
            }
          }
          // reset visited
          for(gr_it = grlist.begin(); gr_it != grlist.end(); gr_it++) {
            (*gr_it)->visited = false;
          }
          return scc;
        }

        void InterGraph::dfsLight(scc_graph_t gr, SCCGraphs& finished, std::map<scc_graph_t, SCCGraphs, SCCGraphLE >& rev_edges)
        {
          gr->visited = true;
          for(SCCGraphs::iterator it = gr->nextGraphs.begin();
              it != gr->nextGraphs.end();
              it++){
            scc_graph_t ch = *it;
            rev_edges[ch].push_back(gr);
            if(!ch->visited)
              dfsLight(ch, finished, rev_edges);
          }

          finished.push_front(gr);
        }

        unsigned InterGraph::SCC(list<IntraGraph *> &grlist, std::list<IntraGraph *> &grsorted) {
          std::list<IntraGraph *>::iterator gr_it;
          // reset visited
          for(gr_it = grlist.begin(); gr_it != grlist.end(); gr_it++) {
            (*gr_it)->visited = false;
            (*gr_it)->scc_number = 0;
            (*gr_it)->bfs_number = (unsigned)(-1);
          }
          std::list<IntraGraph *> finished;
          std::map<IntraGraph *, std::list<IntraGraph *> > rev_edges;
          // Do DFS
          std::list<IntraGraph *>::reverse_iterator gr_rit;
          for(gr_rit = grlist.rbegin(); gr_rit != grlist.rend(); gr_rit++) {
            IntraGraph *gr = *gr_rit;
            if(gr->visited)
              continue;
            dfsIntraForward(gr,finished,rev_edges);
          }

          unsigned scc = 0;

          for(gr_it = grlist.begin(); gr_it != grlist.end(); gr_it++) {
            (*gr_it)->visited = false;
          }

          for(gr_it = finished.begin(); gr_it != finished.end(); gr_it++) {
            IntraGraph *gr = *gr_it;
            if(gr->visited)
              continue;
            scc++;
            typedef pair<IntraGraph *, std::list<IntraGraph *>::iterator> StackEl;
            std::list<StackEl> stack;
            stack.push_back(StackEl(gr, rev_edges[gr].begin()));
            while(!stack.empty()) {
              StackEl p = stack.front();
              stack.pop_front();
              IntraGraph *v = p.first;
              std::list<IntraGraph *>::iterator it = p.second;
              v->scc_number = scc;
              if(!v->visited) grsorted.push_back(v);
              v->visited = true;
              while(it != rev_edges[v].end()) {
                IntraGraph *c = *it;
                if(c->visited) {
                  it++;
                } else {
                  stack.push_front(StackEl(v,++it));
                  stack.push_front(StackEl(c, rev_edges[c].begin()));
                  break;
                }
              }
            }
          }
          // reset visited
          for(gr_it = grlist.begin(); gr_it != grlist.end(); gr_it++) {
            (*gr_it)->visited = false;
          }
          return scc;
        }

        void InterGraph::dfsIntraForward(IntraGraph *gr, std::list<IntraGraph *> &finished, std::map<IntraGraph *, std::list<IntraGraph *> > &rev_edges) {
          gr->visited = true;
          std::list<int> *outnodes = gr->getOutTransitions();
          std::list<int>::iterator it;
          for(it = outnodes->begin(); it != outnodes->end(); it++) {
            int n = *it;
            std::list<int>::iterator beg = nodes[n].out_hyper_edges.begin();
            std::list<int>::iterator end = nodes[n].out_hyper_edges.end();
            for(; beg != end; beg++) {
              IntraGraph *ch = nodes[inter_edges[*beg].tgt].gr;
              rev_edges[ch].push_back(gr);
              if(!ch->visited)
                dfsIntraForward(ch, finished,rev_edges);
            }
          }
          finished.push_front(gr);
        }

        // BFS of a SCC
        void InterGraph::bfsIntra(IntraGraph *start, unsigned int scc_number) {
          std::list <IntraGraph *> workset;
          workset.push_back(start);
          IntraGraph *gr;
          start->bfs_number = 0;
          while(!workset.empty()) {
            gr = workset.front();
            workset.pop_front();
            if(gr->visited) continue;

            gr->visited = true;

            std::list<int> *outnodes = gr->getOutTransitions();
            std::list<int>::iterator it;
            for(it = outnodes->begin(); it != outnodes->end(); it++) {
              int n = *it;
              std::list<int>::iterator beg = nodes[n].out_hyper_edges.begin();
              std::list<int>::iterator end = nodes[n].out_hyper_edges.end();
              for(; beg != end; beg++) {
                IntraGraph *ch = nodes[inter_edges[*beg].tgt].gr;
                if(ch->scc_number != scc_number) continue;
                if(!ch->visited) {
                  ch->bfs_number = (ch->bfs_number > (gr->bfs_number) + 1) ? (gr->bfs_number+1) : ch->bfs_number;
                  workset.push_back(ch);
                }
              }
            }
          }
        }

        // BFS of a SCC
        void InterGraph::resetSCCedges(IntraGraph *gr, unsigned int scc_number) {
          std::list<int> *outnodes = gr->getOutTransitions();
          std::list<int>::iterator it;
          for(it = outnodes->begin(); it != outnodes->end(); it++) {
            int n = *it;
            std::list<int>::iterator beg = nodes[n].out_hyper_edges.begin();
            std::list<int>::iterator end = nodes[n].out_hyper_edges.end();
            for(; beg != end; beg++) {
              int inode = inter_edges[*beg].tgt;
              int onode1 = inter_edges[*beg].src1;
              IntraGraph *ch = nodes[inode].gr;
              if(ch->scc_number != scc_number) continue;
              ch->updateEdgeWeight(nodes[onode1].intra_nodeno, nodes[inode].intra_nodeno, sem->zero());
            }
          }
        }

        void InterGraph::setup_worklist(list<IntraGraph *> &gr_sorted, std::list<IntraGraph *>::iterator &gr_it, unsigned int scc_n,
            multiset<tup> &worklist) {
          worklist.clear();
          while(gr_it != gr_sorted.end() && (*gr_it)->scc_number == scc_n ) {
            std::list<int>::iterator tbeg = (*gr_it)->getOutTransitions()->begin();
            std::list<int>::iterator tend = (*gr_it)->getOutTransitions()->end();
            while(tbeg != tend) {
              int onode = *tbeg;
              worklist.insert(tup((*gr_it)->bfs_number,onode));
              //nodes[onode].weight = nodes[onode].gr->get_weight(nodes[onode].trans);
              tbeg++;
            }
            gr_it++;
          }
        }

#if defined(PPP_DBG) && PPP_DBG >= 2
        static std::ostream& pp_weight(std::ostream& o, sem_elem_t weight)
        {
          if(weight == NULL){
            o << "NULL";
            return o;
          }

          stringstream ss;
          weight->print(ss);
          string res = "";
          string ssstr = ss.str();
          for(string::iterator iter = ssstr.begin(); iter != ssstr.end();
              iter++){
            if(*iter == '>'){
              res.push_back(*iter);
              res.append("\\n");
            }else
              res.push_back(*iter);
          }
          o << res;
          return o;
        }
#endif

        /**
         * @brief From the given TDG (The original InterGraph), create linearized TDGs corresponding
         * to each step of Newton. Then, solve the poststar problem by executing steps of the newton's
         * method till fix-point.
         *
         * @note Only works for poststar
         * @note When the function returns, the result automaton may obtain tensored or non-tensored weights
         * based on some heuristics. You must be prepared to obtain either.
         * @see isOutputAutomatonTensored
         * @see setupInterSolution -- This function evolved originally from that.
         * @see IntraGraph, RegExp, SCCGraph. Uses them heavily.
         *
         * Function description:
         * A TDG is an InterGraph (yes, me!) that consists of IntraGraphs.
         * The nodes of the TDG (referred to as n/s/c/x/r) are effectively transitions in the output
         * automaton
         * The edges of the TDG (referred to as e) are effectively the Grammar Flow Problem
         * equations (poststar rules, if you will)
         * (1) Find out SCCs in the graph.
         * Consider a graph where each node is an IntraGraph (this is close to the callgraph of the
         * original program).  The fix-point problem will be solved by linearizing each SCC in turn,
         * while walking the list of SCCs in reverse topological order.
         *
         * For each SCC:
         *   // Create one actual IntraGraph for current Intragraph.
         *   // There are two options here:
         *   //   - if |SCC| == 1 (i.e., SCC is just one procedure), mimic non-newton FWPDS
         *   //   - if |SCC| > 1 (i.e., we must coalesce multiple procedures by linearizing), use
         *   //     Newton's linearization
         *   (2) Add nodes to IntraGraph, along with unique source node s.
         *   (3) Compute |SCC|
         *   if |SCC| == 1 then
         *     // Mimic FWPDS
         *     // Add IntraEdges: since there is no recursive call, all edges will be immutable
         *        (4.1.1) For every source node n in InterGraph (roughly, procedure entry), add an
         *        Edge from s to n with weight 1 (call to setSource())
         *        (4.1.2) For every Edge (\delta_1 rule) n1-e->n2, add an edge n1-e->n2 to the
         *        IntraGraph
         *        (4.1.3) For every HyperEdge (\delta_2 rule) c-e-x->r (x is the external source),
         *        add an edge c-e->r with weight w(x). Since the call can't possibly be recursive,
         *        the summary for this procedure must already have been computed in some earlier
         *        SCC. Use that weight.
         *   else
         *     // There are multiple SCCGraphs in this SCC. We will coalesce them into one
         *     // IntraGraph.
         *     // The basic outline here follows that in (4.1), but the equation system we are
         *     // forming in this case is the linear system.
         *     // For source nodes and \delta_1 rules, instead of edges with weight w, we create
         *     // edges with weight (w,1^T) where (.,.) is tensor and ^T is transpose
         *     // The case for \delta_2 is split in two -- if the call is made to a function
         *     // belonging to a function with lower SCC number, then this case is handled
         *     // identically to (4.1.3). Instead, if the call is made to a function within this
         *     // SCC, for the call c-e-x->r, we add three edges: c-e1->r with functional(x);
         *     // x-e2->r with functional(c) and s-e3->r with functional(c,x) all mutable. This
         *     // corresponds. A functional is a recipe that is later used to compute the weight
         *     // that must go on the edge (between newton rounds) from the weights on the argument
         *     // nodes. For details of the exact weights and functionals that go on various edges,
         *     // see comments below within the function.
         *     (4.2.1) Add edges for source nodes
         *     (4.2.2) Add edges for \delta_1 rules
         *     (4.2.3.1) Add edges for \delta_2 rules where the call is to a function with lower SCC
         *     (4.2.3.2) Add edges for \delta_2 rules where the call is to a function with from same
         *     SCC.
         *     TODO: Argue that mutable edges are not needed in (4.2.1) and (4.2.2)
         *   (5) Use Tarjan's path listing algorithm to generate regular expressions for the
         *   IntraGraph.
         *   (6) Solve the linearized problem by saturation.
         *   @see IntraGraph::saturate
         *   Till fix-point:
         *      -- Evaluate the regular expressions (This corresponds to one Newton's round)
         *      -- update functionals and repeat.
         * (7) Decide whether the output automaton is tensored.
         * Because the weights on IntraGraphs can be both tensored and non-tensored (tensored in
         * 4.2, non-tensored in 4.1), we must decide how the weights on the output automaton are.
         * We do that by counting the number of extra tensor / detensor operations needed in either
         * choice, and then using a heuristic to decide between the two.
         * FIXME: Currently, we override the choice, and always tensor weights. This might be the
         * best choice anyway, because detensor is a lot costlier than tensor in most cases. (Not
         * true with nwa_detensor)
         * (8) Set some more stuff that IntraGraph, FWPDS need after poststar. Mostly copied from
         * setupInterSolution.
         *
         **/
        void InterGraph::setupNewtonSolution()
        {
          runningNewton = true;
          // First populate SCCGraph objects

          int n = nodes.size();
          int i;
          unsigned int max_scc_required;
          SCCGraphs scc_gr_list;
          SCCGraphs gr_sorted;
          // (1) The SCC computation.
          // We will use a light-weight graph data structure called SCCGraph to find out the
          // SCCs in the TDG. Each SCCGraph roughly corresponds to what will later (in the function) become
          // an IntraGraph. But SCCGraph have no extra information beyond the graph node and edge information,
          // just enough to find the SCCs.
          intra_graph_uf = new UnionFind(n);
          {
            vector<GraphEdge>::iterator it;
            vector<HyperEdge>::iterator it2;
            for(it = intra_edges.begin(); it != intra_edges.end(); it++) {
              intra_graph_uf->takeUnion((*it).src,(*it).tgt);
            }
            for(it2 = inter_edges.begin(); it2 != inter_edges.end(); it2++) {
              intra_graph_uf->takeUnion((*it2).src1,(*it2).tgt);
            }

            // Add nodes to SCCGraphs
            for(i = 0; i < n;i++) {
              int j = intra_graph_uf->find(i);
              if(nodes[j].sccgr == NULL){
                nodes[j].sccgr = new SCCGraph();
                scc_gr_list.push_back(nodes[j].sccgr);
              }
              nodes[i].sccgr = nodes[j].sccgr;
              nodes[i].sccgr->nodes.push_back(i);
            }
            // Add edges tp SCCGraphs
            for(it = intra_edges.begin(); it != intra_edges.end(); it++)
              nodes[(*it).src].sccgr->intraEdges.push_back(*it);
            for(it2 = inter_edges.begin(); it2 != inter_edges.end(); it2++)
              nodes[(*it2).tgt].sccgr->interEdges.push_back(*it2);
            // Also populate inter graph maps for the scc
            for (it2 = inter_edges.begin(); it2 != inter_edges.end(); ++it2)
              nodes[(*it2).src2].sccgr->nextGraphs.push_back(nodes[(*it2).tgt].sccgr);

            // Do SCC decomposition
            max_scc_required = SCCLight(scc_gr_list, gr_sorted);
            STAT(stats.ncomponents = max_scc_required);
          }

          //Setup weights so that everything is tensored
          sem_elem_tensor_t sem_old = boost::polymorphic_downcast<SemElemTensor*>(sem.get_ptr());
          sem = tensorSetUpFP(sem_old, sem_old);
#if defined(PPP_DBG) && PPP_DBG >= 0
          long totCombines=0, totExtends=0, totStars=0;
          unsigned maxNewtonRounds = 0;
          unsigned totNewtonRounds = 0;
#endif

          // For each SCC, solve completely using Newton's method.
          {
            SCCGraphs::iterator gr_it = gr_sorted.begin();
            sem_elem_tensor_t one = boost::polymorphic_downcast<SemElemTensor*>(sem_old->one().get_ptr());
            sem_elem_tensor_t zerot = boost::polymorphic_downcast<SemElemTensor*>((sem->zero()).get_ptr()); //sem is tensored
            sem_elem_tensor_t zero = boost::polymorphic_downcast<SemElemTensor*>((sem_old->zero()).get_ptr());
            for(unsigned scc_n = 1; scc_n <= max_scc_required; scc_n++) {
              ////////////////We will now create the Newton IntraGraph which will store the
              ////////////////actual weights, and from which RegExp will be generated.
              ////////////////This is the TDG for the linearized problem for the current SCC

              SCCGraphs::iterator scc_head = gr_it;
              IntraGraph * graph = new IntraGraph(dag, false, sem); //pre = false
              linear_gr_list.push_back(graph);
              // (2) Add nodes to IntraGraph
              while(gr_it != gr_sorted.end() && (*gr_it)->scc_number == scc_n){
                scc_graph_t gr = *gr_it;
                for(vector<int>::iterator iter = gr->nodes.begin(); iter != gr->nodes.end(); ++iter){
                  int i = *iter;
                  nodes[i].intra_nodeno = graph->makeNode(nodes[i].trans);
                  nodes[i].gr = graph;
                }
                gr_it++;
              }

              //Reset gr_it
              gr_it = scc_head;
              // (3) Determine if this SCC has a recursive call
              bool isRecursive = false;
              while(gr_it != gr_sorted.end() && (*gr_it)->scc_number == scc_n){
                scc_graph_t gr = *gr_it;
                for(vector<HyperEdge>::iterator iter = gr->interEdges.begin(); iter != gr->interEdges.end(); ++iter){
                  if(nodes[iter->src2].gr == graph){
                    isRecursive = true;
                    break;
                  }
                }
                if(isRecursive)
                  break;
                gr_it++;
              }

              // Will the current graph have tensored weights?
              // We need to know this for weight queries on the graph after saturation.
              graph->hasTensoredWeights = isRecursive;

              //Reset gr_it
              gr_it = scc_head;
              // Now add in the myriad edges.
              if(isRecursive){
                //Do Newton Magic
                dag->startSatProcess(sem);
                while(gr_it != gr_sorted.end() && (*gr_it)->scc_number == scc_n){
                  scc_graph_t gr = *gr_it;

                  // (4.2.1) Source nodes:
                  for(vector<int>::iterator iter = gr->nodes.begin(); iter != gr->nodes.end(); ++iter){
                    int i = *iter;
                    if(is_source_type(nodes[i].type)) {
                      //This is a source node.
                      //Create an immutable edge with weight:
                      //Post*:  w -> (w,1^T)
                      sem_elem_tensor_t wt = boost::polymorphic_downcast<SemElemTensor*>((nodes[i].weight).get_ptr());
                      sem_elem_tensor_t one = boost::polymorphic_downcast<SemElemTensor*>((wt->one()).get_ptr());
                      wt = tensorSetUpFP(wt,one);
                      graph->setSource(nodes[i].intra_nodeno, wt);
                    }
                    // zero all weights (some are set by InterGraph::setSource() )
                    if(nodes[i].weight.get_ptr() != NULL)
                      nodes[i].weight = zerot;
                  }

                  // (4.2.2) Intra Edges:
                  for(vector<GraphEdge>::iterator iter = gr->intraEdges.begin(); iter != gr->intraEdges.end(); iter++){
                    //This is an edge (src--w-->tgt)
                    //Add an immutable edge src--w'-->tgt)
                    // w' = (w,1^T)
                    sem_elem_tensor_t wt = boost::polymorphic_downcast<SemElemTensor*>((iter->weight).get_ptr());
                    sem_elem_tensor_t one = boost::polymorphic_downcast<SemElemTensor*>((wt->one()).get_ptr());
                    wt = tensorSetUpFP(wt,one);
                    graph->addEdge(nodes[iter->src].intra_nodeno, nodes[iter->tgt].intra_nodeno, wt);
#if 0
                    //Also add a mutable edge (s--f-->tgt) from the source vertex s with weight 0 (tensored) and
                    //functional f = (DetTrans(wt(src)) x w,1^T) (one untensored)
                    sem_elem_tensor_t zerot = boost::polymorphic_downcast<SemElemTensor*>(sem->zero().get_ptr());
                    wt = boost::polymorphic_downcast<SemElemTensor*>((iter->weight).get_ptr());
                    functional_t f =
                      SemElemFunctional::tensor(
                          SemElemFunctional::extend(
                            SemElemFunctional::detensorTranspose(
                              SemElemFunctional::in(nodes[iter->src].intra_nodeno)),
                            SemElemFunctional::constant(wt)),
                          SemElemFunctional::constant(one));
                    int e = graph->setSource(nodes[iter->tgt].intra_nodeno, zerot, f);
                    // Back references in the node to edges that depend on it.
                    graph->addDependentEdge(e, nodes[iter->src].intra_nodeno);
#endif
                  }
                  int trash;
                  // Inter Edges:
                  for(vector<HyperEdge>::iterator iter = gr->interEdges.begin(); iter != gr->interEdges.end(); ++iter){

                    //Obtain the weight on the call edge
                    assert(eHandler.exists(iter->src1));
                    sem_elem_tensor_t wtCallRule =
                      boost::polymorphic_downcast<SemElemTensor*>(eHandler.get_dependency(iter->src1,trash).get_ptr());
                    assert(trash != -1);

                    // src2 is the external source for a hyperedge
                    if(nodes[iter->src2].gr != NULL && nodes[iter->src2].gr != graph){
                      // (4.2.3.1) The entry node belongs to a graph that has a lower scc.
                      // Treat this case as an intraedge

                      //Add an immutable edge src1--w'-->tgt)
                      // If the graph for src2 has tensoredWeights, then
                      // w' = (wtCallRule x DetTrans(wt(src2)), 1^T)
                      // If it has base weights, then
                      // w' = (wtCallRule x wt(src2), 1^T)
                      sem_elem_t wtsrc2 = nodes[iter->src2].gr->getWeight(nodes[iter->src2].intra_nodeno);
                      sem_elem_tensor_t wt = boost::polymorphic_downcast<SemElemTensor*>(wtsrc2.get_ptr());
                      if(nodes[iter->src2].gr->hasTensoredWeights)
                        wt = wt->detensorTranspose();
                      wt = boost::polymorphic_downcast<SemElemTensor*>(wtCallRule->extend(wt.get_ptr()).get_ptr());
                      sem_elem_tensor_t one = boost::polymorphic_downcast<SemElemTensor*>((wt->one()).get_ptr());
                      graph->addEdge(nodes[iter->src1].intra_nodeno, nodes[iter->tgt].intra_nodeno,
                          tensorSetUpFP(wt,one));
#if 0
                      //Also add a mutable edge (s--f-->tgt) from the source vertex s with weight 0 (tensored) and
                      //if graph for src2 has tensored weights, then
                      //functional f = (DetTrans(wt(src1)) x (Constant(wtCallRule) x DetTrans(wt(src2))), 1^T) (one untensored)
                      //else
                      //functional f = (DetTrans(wt(src1)) x (Constant(wtCallRule) x DetTrans(wt(src2))), 1^T) (one untensored)
                      sem_elem_tensor_t zerot = boost::polymorphic_downcast<SemElemTensor*>(sem->zero().get_ptr());
                      functional_t f =
                        SemElemFunctional::tensor(
                            SemElemFunctional::extend(
                              SemElemFunctional::detensorTranspose(
                                SemElemFunctional::in(nodes[iter->src1].intra_nodeno)),
                              SemElemFunctional::constant(wt)),
                            SemElemFunctional::constant(one));
                      int e = graph->setSource(nodes[iter->tgt].intra_nodeno, zerot, f);
                      // Back references in the node to edges that depend on it.
                      graph->addDependentEdge(e, nodes[iter->src1].intra_nodeno);
#endif
                    }else{
                      // (4.2.3.2) The entry node does not belong to a graph that has a lower scc.
                      // Note: It must belong to the same scc then
                      assert(nodes[iter->src2].gr == graph);

                      // Add mutable edge src1--f-->tgt with weight 0 (tensored) and
                      // f = (Constant(callWt) x DetTrans(wt(src2)), 1^T)
                      sem_elem_tensor_t zerot = boost::polymorphic_downcast<SemElemTensor*>(sem->zero().get_ptr());
                      functional_t f =
                        SemElemFunctional::tensor(
                            SemElemFunctional::extend(
                              SemElemFunctional::constant(wtCallRule),
                              SemElemFunctional::detensorTranspose(
                                SemElemFunctional::in(nodes[iter->src2].intra_nodeno))),
                            SemElemFunctional::constant(one));
                      int e = graph->addEdge(nodes[iter->src1].intra_nodeno, nodes[iter->tgt].intra_nodeno, zerot, true, f);
                      // Back references in the node to edges that depend on it.
                      graph->addDependentEdge(e, nodes[iter->src2].intra_nodeno);

                      // Add mutable edge src2--f-->tgt with weight 0 (tensored) and
                      // f = (1, (DetTrans(wt(src1)) x callWt)^T)
                      f =
                        SemElemFunctional::tensor(
                            SemElemFunctional::constant(one),
                            SemElemFunctional::transpose(
                              SemElemFunctional::extend(
                                SemElemFunctional::detensorTranspose(
                                  SemElemFunctional::in(nodes[iter->src1].intra_nodeno)),
                                SemElemFunctional::constant(wtCallRule))));
                      e = graph->addEdge(nodes[iter->src2].intra_nodeno, nodes[iter->tgt].intra_nodeno, zerot, true, f);
                      // Back references in the node to edges that depend on it.
                      graph->addDependentEdge(e, nodes[iter->src1].intra_nodeno);

                      // Add mutable edge s--f-->tgt from source node s with weight 0(tensored) and
                      // f = (DetTrans(wt(src1)) x callWt x DetTrans(wt(src2)), 1^T)
                      f =
                        SemElemFunctional::tensor(
                            SemElemFunctional::extend(
                              SemElemFunctional::extend(
                                SemElemFunctional::detensorTranspose(
                                  SemElemFunctional::in(nodes[iter->src1].intra_nodeno)),
                                SemElemFunctional::constant(wtCallRule)),
                              SemElemFunctional::detensorTranspose(
                                SemElemFunctional::in(nodes[iter->src2].intra_nodeno))),
                            SemElemFunctional::constant(one));
                      e = graph->setSource(nodes[iter->tgt].intra_nodeno, zerot, f);
                      // Back references in the node to edges that depend on it.
                      graph->addDependentEdge(e, nodes[iter->src1].intra_nodeno);
                      graph->addDependentEdge(e, nodes[iter->src2].intra_nodeno);
                    }
                  }

                  gr_it++;
                }
              }
              else
              {
                //Do Fwpds Magic
                dag->startSatProcess(sem_old); //non tensored
                while(gr_it != gr_sorted.end() && (*gr_it)->scc_number == scc_n){
                  scc_graph_t gr = *gr_it;

                  // (4.1.1)
                  // Source nodes:
                  for(vector<int>::iterator iter = gr->nodes.begin(); iter != gr->nodes.end(); ++iter){
                    int i = *iter;
                    if(is_source_type(nodes[i].type)) {
                      //This is a source node.
                      //Create an immutable edge with weight w
                      sem_elem_t wt = nodes[i].weight;
                      graph->setSource(nodes[i].intra_nodeno, wt);
                    }
                    // zero all weights (some are set by InterGraph::setSource() )
                    if(nodes[i].weight.get_ptr() != NULL)
                      nodes[i].weight = zero;
                  }

                  // (4.1.2)
                  // Intra Edges:
                  for(vector<GraphEdge>::iterator iter = gr->intraEdges.begin(); iter != gr->intraEdges.end(); iter++){
                    //This is an edge (src--w-->tgt)
                    //Add an immutable edge src--w-->tgt)
                    sem_elem_t wt = iter->weight;
                    graph->addEdge(nodes[iter->src].intra_nodeno, nodes[iter->tgt].intra_nodeno, wt);
                  }
                  int trash;
                  // (4.1.3)
                  // Inter Edges:
                  for(vector<HyperEdge>::iterator iter = gr->interEdges.begin(); iter != gr->interEdges.end(); ++iter){
                    // Obtain the weight on the call edge
                    assert(eHandler.exists(iter->src1));
                    sem_elem_tensor_t wtCallRule =
                      boost::polymorphic_downcast<SemElemTensor*>(eHandler.get_dependency(iter->src1,trash).get_ptr());
                    assert(trash != -1);

                    // src2 is the external source for a hyperedge. This can't be recursive, hence --
                    assert(nodes[iter->src2].gr != NULL && nodes[iter->src2].gr != graph);
                    // Treat this case as an intraedge

                    // Add an immutable edge src1--w'-->tgt)
                    // If the graph for src2 has tensoredWeights, then
                    // w' = wtCallRule x DetTrans(wt(src2))
                    // else
                    // w' = wtCallRule x wt(src2)
                    sem_elem_t wtsrc2 = nodes[iter->src2].gr->getWeight(nodes[iter->src2].intra_nodeno);
                    sem_elem_tensor_t wt = boost::polymorphic_downcast<SemElemTensor*>(wtsrc2.get_ptr());
                    if(nodes[iter->src2].gr->hasTensoredWeights)
                      wt = wt->detensorTranspose();
                    wt = boost::polymorphic_downcast<SemElemTensor*>(wtCallRule->extend(wt.get_ptr()).get_ptr());
                    graph->addEdge(nodes[iter->src1].intra_nodeno, nodes[iter->tgt].intra_nodeno, wt);
                  }
                  gr_it++;
                }
              }
              // (5)
              // Use Tarjan's path listing algorithm to generate regular expressions for nodes.
              graph->setupIntraSolution();

#if defined(PPP_DBG) && PPP_DBG >= 1
              // We have the intra graph ready at this point.
                // DEBUGGING
                std::stringstream ss;
                ss << "graph" << scc_n << ".dot";
                std::ofstream foo(ss.str().c_str());
                string dot = graph->toDot();
                foo << "digraph {\n";
                foo << dot;
                foo << "}";
                foo.close();
              cout << "GRAPH " << scc_n << "\n";
#endif
              // (6) Now solve the linearized problem by saturating.
              unsigned numRounds = 0;
              graph->saturate(numRounds);
#if defined(PPP_DBG) && PPP_DBG >= 0
              maxNewtonRounds = numRounds > maxNewtonRounds ? numRounds : maxNewtonRounds;
              totNewtonRounds += numRounds;
#endif
              // The next SCC will use another sat process phase.
              dag->stopSatProcess();
            }
          }

          // (7)
          // It is time to decide whether the output automaton should have
          // tensored weights or not.
          // Currently, we will decide by counting the number of edges in the
          // output automaton (i.e. the number of IntraGraph nodes that have
          // tensored/non-tensored weights, and then switching at some
          // ciritical ratio (decided emprically)
          int totTensoredNodes=0, totNonTensoredNodes=0;
          for(std::list<IntraGraph*>::iterator gr_it = linear_gr_list.begin(); gr_it != linear_gr_list.end(); ++gr_it){
            IntraGraph * graph = *gr_it;
            if(graph->hasTensoredWeights)
              totTensoredNodes += graph->nnodes;
            else
              totNonTensoredNodes += graph->nnodes;
          }
          if(totTensoredNodes == 0 || ((float)totNonTensoredNodes)/((float)totTensoredNodes) > 0.33)
            isOutputAutomatonTensored = true;
          //XXX:HACK
          isOutputAutomatonTensored = true; //override
          cerr << "Warning: overriding to OutputAutomatonTensored\n";

          if(isOutputAutomatonTensored){
            // Before saying you're done, tensor the weights lying around as call rule weights so that
            // path summary will see tensored weights.
            eHandler.tensorAllWeights();
          }

#if defined(PPP_DBG) && PPP_DBG >= 0
          dag->sanitizeRootsAcrossSatProcesses();
          totCombines = dag->countTotalCombines();
          totExtends = dag->countTotalExtends();
          totStars = dag->countTotalStars();

          cout << "Maximum number of Newton rounds: " << maxNewtonRounds << endl;
          cout << "Total number of Newton rounds: " << totNewtonRounds << endl;
          cout << "Total number of combines: " << totCombines << endl;
          cout << "Total number of Extends: " << totExtends << endl;
          cout << "Total number of Stars: " << totStars << endl;

          dag->printStructureInformation();
#endif
          max_scc_computed = max_scc_required;
          dag->executingPoststar(!running_prestar);

#if defined(PPP_DBG) && PPP_DBG >= 2
            std::stringstream ss;
            ss << "digraph{\n";
            for(i=0; i<n; i++){
              ss << "node" << i << " [label=\"[intergraphno: " << i << "](" <<
                key2str(nodes[i].trans.src) <<
                ", " << key2str(nodes[i].trans.stack) <<
                ", " << key2str(nodes[i].trans.tgt) << ")\\n";
              sem_elem_t wt =nodes[i].gr->get_weight(nodes[i].intra_nodeno);
              if(wt == NULL)
                pp_weight(ss, wt);
              else{
                sem_elem_tensor_t wtt =
                  boost::polymorphic_downcast<SemElemTensor*>(wt.get_ptr());
                pp_weight(ss, wtt->detensorTranspose());
              }
              ss<< "\"];\n";
            }
            for(vector<GraphEdge>::iterator it = intra_edges.begin(); it != intra_edges.end(); ++it){
              ss << "node" << it->src << " -> node" << it->tgt
                << " [label=\"";
              pp_weight(ss, it->weight);
              ss  << "\"];\n";
            }
            for(vector<HyperEdge>::iterator it2 = inter_edges.begin(); it2 != inter_edges.end(); ++it2){
              ss << "node" << it2->src1 << " -> node" << it2->tgt
                << " [label=\"";
              pp_weight(ss, it2->weight);
              ss << "\" color=brown];\n";
              if(it2->mf.get_ptr()){
                ss << "node" << it2->src2 << " -> node" << it2->tgt
                  << " [color=red, label=\"Has Merge Fn\"];\n";
              }else{
                ss << "node" << it2->src2 << " -> node" << it2->tgt
                  << " [color=red, label=\"No Merge Fn\"];\n";
              }
            }
            ss << "}\n";
            ofstream foo("tdg.dot");
            foo << ss.str();
            foo.close();
#endif
        }

        // If an argument is passed in then only weights on those transitions will be available
        // I can fix this (i.e., weights for others will be available on demand), but not right now.
        void InterGraph::setupInterSolution(std::list<Transition> *wt_required) {
          dag->startSatProcess(sem);
          // First, find the IntraGraphs
          int n = nodes.size();
          int i;
          unsigned int max_scc_required;
          intra_graph_uf = new UnionFind(n);

          vector<GraphEdge>::iterator it;
          vector<HyperEdge>::iterator it2;
          std::list<IntraGraph *>::iterator gr_it;
          multiset<tup > worklist;

          for(it = intra_edges.begin(); it != intra_edges.end(); it++) {
            intra_graph_uf->takeUnion((*it).src,(*it).tgt);
          }
          for(it2 = inter_edges.begin(); it2 != inter_edges.end(); it2++) {
            intra_graph_uf->takeUnion((*it2).src1,(*it2).tgt);
          }
          IntraGraph::SharedMemBuffer * memBuf = NULL;
#ifdef INTRAGRAPH_SHARED_MEMORY
          // Before creating IntraGraphs, create a CommonBuffer, if needed.
          int max_size = 0;
          for(gr_it = gr_list.begin(); gr_it != gr_list.end(); gr_it++) {
            max_size = (max_size > (*gr_it)->getSize()) ? max_size : (*gr_it)->getSize();
          }
          memBuf  = new IntraGraph::SharedMemBuffer(max_size);
#endif

          for(i = 0; i < n;i++) {
            int j = intra_graph_uf->find(i);
            if(nodes[j].gr == NULL) {
              nodes[j].gr = new IntraGraph(dag, running_prestar,sem, memBuf);
              gr_list.push_back(nodes[j].gr);
            }
            nodes[i].gr = nodes[j].gr;
            nodes[i].intra_nodeno = nodes[i].gr->makeNode(nodes[i].trans);
            if(is_source_type(nodes[i].type)) {
              nodes[i].gr->setSource(nodes[i].intra_nodeno, nodes[i].weight);
            }
            // zero all weights (some are set by setSource() )
            if(nodes[i].weight.get_ptr() != NULL)
              nodes[i].weight = nodes[i].weight->zero();
          }

          // Now fill up the IntraGraphs
          for(it = intra_edges.begin(); it != intra_edges.end(); it++) {
            int s = (*it).src;
            int t = (*it).tgt;
            nodes[s].gr->addEdge(nodes[s].intra_nodeno, nodes[t].intra_nodeno, (*it).weight);
          }

          for(it2 = inter_edges.begin(); it2 != inter_edges.end(); it2++) {
            IntraGraph *gr = nodes[(*it2).tgt].gr;
            gr->addEdge(nodes[(*it2).src1].intra_nodeno, nodes[(*it2).tgt].intra_nodeno, sem->zero(), true);
            IntraGraph *gr2 = nodes[(*it2).src2].gr;
            gr2->setOutNode(nodes[(*it2).src2].intra_nodeno, (*it2).src2);
          }

          // For SWPDS
          vector<call_edge_t>::iterator it3;
          for(it3 = call_edges.begin(); it3 != call_edges.end(); it3++) {
            IntraGraph *gr1 = nodes[(*it3).first].gr;
            IntraGraph *gr2 = nodes[(*it3).second].gr;
            gr1->addCallEdge(gr2);
          }

          // Setup Worklist
#if defined(PPP_DBG) && PPP_DBG >= 0
          vector<reg_exp_t> outNodeRegExps;
#endif
          for(gr_it = gr_list.begin(); gr_it != gr_list.end(); gr_it++) {
            (*gr_it)->setupIntraSolution(false);
#if defined(PPP_DBG) && PPP_DBG >= 0
            for(list<int>::const_iterator cit = (*gr_it)->out_nodes_intra->begin(); cit != (*gr_it)->out_nodes_intra->end(); ++cit)
              outNodeRegExps.push_back((*gr_it)->nodes[*cit].regexp);
#endif
          }

#if defined(PPP_DBG) && PPP_DBG >= 0
          long totNodes = 0, totNotComputed = 0;
          totNodes = dag->countTotalCombines() +
            dag->countTotalExtends() +
            dag->countTotalStars();
          totNotComputed = dag->countExcept(outNodeRegExps);
          cout << "Total number of combines: " << dag->countTotalCombines() << endl;
          cout << "Total number of Extends: " << dag->countTotalExtends() << endl;
          cout << "Total number of Stars: " << dag->countTotalStars() << endl;
          cout << "#nodes definitely not evaluated during saturation: " << dag->countExcept(outNodeRegExps) << endl;
          cout << "%Nodes never computed > " << 100 * (double)(((double)totNotComputed)/ (double)totNodes) << endl;;
#endif
          // Do SCC decomposition of IntraGraphs
          std::list<IntraGraph *> gr_sorted;
          unsigned components = SCC(gr_list, gr_sorted);
          STAT(stats.ncomponents = components);

          int numSteps = 0;
          // Saturate
          if(wt_required == NULL) {
            max_scc_required = components;
          } else {
            max_scc_required = 0;
            std::list<Transition>::iterator trans_it;
            for(trans_it = wt_required->begin(); trans_it != wt_required->end(); trans_it++) {
              int nno = nodeno(*trans_it);
              max_scc_required = (max_scc_required >= nodes[nno].gr->scc_number) ? max_scc_required : nodes[nno].gr->scc_number;
            }
          }
          gr_it = gr_sorted.begin();
          for(unsigned scc_n = 1; scc_n <= max_scc_required; scc_n++) {
            bfsIntra(*gr_it, scc_n);
            setup_worklist(gr_sorted, gr_it, scc_n, worklist);
            numSteps += saturate(worklist,scc_n);
          }
#if defined(PPP_DBG) && PPP_DBG >= 0
          cout << "Total number of steps: " << numSteps << endl;
#endif
        max_scc_computed = max_scc_required;

        //DEBUGGING
#if defined(PPP_DBG) && PPP_DBG >= 1
        {
          stringstream ss;
          ss << "kleene_regexp.dot";
          string filename = ss.str();
          fstream foo;
          foo.open(filename.c_str(), fstream::out);
          const reg_exp_hash_t& roots = dag->getRoots();
          foo << "digraph {\n";
          std::set<long> seen;
          for(reg_exp_hash_t::const_iterator iter = roots.begin();
              iter != roots.end();
              ++iter){
            (iter->second)->toDot(foo, seen, true, true);
          }
          foo << "}\n";
          foo.close();
        }
        int graphnum = 0;
        for(gr_it = gr_list.begin(); gr_it != gr_list.end(); ++gr_it)
        {
          ++graphnum;
          stringstream ss;
          ss << "kleene_graph_" << graphnum << ".dot";
          string filename = ss.str();
          fstream foo;
          foo.open(filename.c_str(), fstream::out);
          foo << "digraph{\n";
          foo << (*gr_it)->toDot();
          foo << "}";
          foo.close();
        }
#endif
        dag->stopSatProcess();
        dag->executingPoststar(!running_prestar);
#ifdef INTRAGRAPH_SHARED_MEMORY
        delete memBuf;
#endif
    }

    std::ostream &InterGraph::print_stats(std::ostream &out) {
      InterGraphStats total_stats = stats;
      int n = nodes.size();
      int i;
      set<RegExp *> reg_equations;
      int ned = 0;
      for(i = 0; i < n; i++) {
        if(intra_graph_uf->find(i) == i) {
          std::list<int>::iterator tbeg = nodes[i].gr->getOutTransitions()->begin();
          std::list<int>::iterator tend = nodes[i].gr->getOutTransitions()->end();
          for(; tbeg != tend; tbeg++) {
            int onode = nodes[i].intra_nodeno;
            reg_equations.insert(nodes[i].gr->nodes[onode].regexp.get_ptr());
          }
          total_stats.ngraphs ++;
          IntraGraphStats st = nodes[i].gr->get_stats();
          total_stats.ncombine += st.ncombine;
          total_stats.nextend += st.nextend;
          total_stats.nstar += st.nstar;
          total_stats.nupdatable += st.nupdatable * st.nupdatable;
          total_stats.ncutset += st.ncutset * st.ncutset * st.ncutset;
          total_stats.nget_weight += st.nget_weight;
          total_stats.ndom_sequence += st.ndom_sequence;
          total_stats.ndom_components += st.ndom_components;
          total_stats.ndom_componentsize += (st.ndom_componentsize * st.ndom_componentsize);
          total_stats.ndom_componentcutset += st.ndom_componentcutset;
          ned += st.nedges;
        }
      }
      int changestat = dag->out_node_height(reg_equations);
      total_stats.nhyperedges = inter_edges.size();
      total_stats.nedges = intra_edges.size();
      total_stats.nnodes = nodes.size();

      RegExpStats rst = dag->get_stats();
      total_stats.ncombine += rst.ncombine;
      total_stats.nextend += rst.nextend;
      total_stats.nstar += rst.nstar;
      total_stats.ngraphs = (total_stats.ngraphs == 0) ? 1 : total_stats.ngraphs;
      rst.out_nodes = (rst.out_nodes == 0) ? 1 : rst.out_nodes;
      out << "----------------------------------\n";
      out << "          FWPDS Stats             \n";
      out << "----------------------------------\n";
      out << "InterGraph nodes : " << total_stats.nnodes << "\n";
      out << "InterGraph edges : " << total_stats.nedges << "\n";
      out << "InterGraph hyperedges : " << total_stats.nhyperedges << "\n";
      out << "InterGraph iterations : " << total_stats.niter << "\n";
      out << "InterGraph get_weight : " << total_stats.nget_weight << "\n";
      out << "IntraGraphs : " << total_stats.ngraphs << "\n";
      out << "IntraGraph SCC : " << total_stats.ncomponents << "\n";
      out << "IntraGraph SCC Computed: " << max_scc_computed << "\n";
      out << "Avg. IntraGraph nodes : " << (total_stats.nnodes / total_stats.ngraphs) << "\n";
      out << "Avg. IntraGraph edges : " << (ned / total_stats.ngraphs) << "\n";
      out << "Avg. IntraGraph cutset : " << (pow(total_stats.ncutset/total_stats.ngraphs,0.33)) << "\n";
      out << "Avg. IntraGraph updatable : " << (int)(pow((double)total_stats.nupdatable/total_stats.ngraphs,0.5)) << "\n";
      out << "Avg. IntraGraph dom-sequence length : " << (total_stats.ndom_sequence / total_stats.ngraphs) << "\n";
      out << "Avg. IntraGraph dom-component size : " << (int)pow((double)total_stats.ndom_componentsize / (total_stats.ndom_components+1),0.5) << "\n";
      out << "Avg. IntraGraph dom-component cuset : " << setprecision(2) << (double)total_stats.ndom_componentcutset / (total_stats.ndom_components+1) << "\n";
      out << "Semiring Combine : " << total_stats.ncombine << "\n";
      out << "Semiring Extend : " << total_stats.nextend << "\n";
      out << "Semiring Star : " << total_stats.nstar << "\n";
      out << "RegExp HashMap hits : " << rst.hashmap_hits << "\n";
      out << "RegExp HashMap misses : " << rst.hashmap_misses << "\n";
      out << "OutNode Height : " << setprecision(4) << (rst.height / rst.out_nodes) << "\n";
      out << "OutNode Loop ND : " << setprecision(4) << (rst.lnd / rst.out_nodes) << "\n";
      out << "Change Stat : " << changestat << "\n";
      out << "\n";
      return out;
    }

    // New Saturation Procedure -- minimize calls to get_weight
    int InterGraph::saturate(multiset<tup> &worklist, unsigned scc_n) {
      int numSteps = 0;
      sem_elem_t weight;
      std::list<int> *moutnodes;

      while(!worklist.empty()) {
        // Get an outnode whose weight is to be propagated
        multiset<tup>::iterator wit = worklist.begin();
        int onode = (*wit).second;
        worklist.erase(wit);
        //int onode = worklist.front();
        //worklist.pop_front();

        numSteps++;
        weight = nodes[onode].gr->get_weight(nodes[onode].intra_nodeno);
        if(nodes[onode].weight.get_ptr() != NULL && nodes[onode].weight->equal(weight))
          continue;
        nodes[onode].weight = weight;

        STAT(stats.niter++);

        FWPDSDBGS(
            cout << "Popped ";
            IntraGraph::print_trans(nodes[onode].trans,cout) << "with weight ";
            weight->print(cout) << "\n";
            );

        // Go through all its targets and modify their weights
        std::list<int>::iterator beg = nodes[onode].out_hyper_edges.begin();
        std::list<int>::iterator end = nodes[onode].out_hyper_edges.end();
        for(; beg != end; beg++) {
          int inode = inter_edges[*beg].tgt;
          int onode1 = inter_edges[*beg].src1;
          sem_elem_t uw;
          if(running_ewpds && inter_edges[*beg].mf.get_ptr()) {
            uw = inter_edges[*beg].mf->apply_f(sem->one(), weight);
            FWPDSDBGS(
                cout << "Apply merge function ";
                inter_edges[*beg].mf->print(cout) << " to ";
                weight->print(cout) << "\n";
                uw->print(cout << "Got ") << "\n";
                );
          } else {
            uw = inter_edges[*beg].weight->extend(weight);
          }
          STAT(stats.nextend++);
          nodes[inode].gr->updateEdgeWeight(nodes[onode1].intra_nodeno, nodes[inode].intra_nodeno, uw);
        }
        // Go through all targets again and insert them into the workist without
        // seeing if they actually got modified or not
        beg = nodes[onode].out_hyper_edges.begin();
        for(; beg != end; beg++) {
          int inode = inter_edges[*beg].tgt;
          IntraGraph *gr = nodes[inode].gr;
          if(gr->scc_number != scc_n) {
            assert(gr->scc_number > scc_n);
            continue;
          }
          moutnodes = gr->getOutTransitions();
          std::list<int>::iterator mbeg = moutnodes->begin();
          std::list<int>::iterator mend = moutnodes->end();
          for(; mbeg != mend; mbeg++) {
            int mnode = (*mbeg);
            worklist.insert(tup(gr->bfs_number, mnode));
          }
        }
      }
      //DEBUGGING
      //cout << "Kleene saturation # Steps: " << numSteps << endl;
      return numSteps;

    }

    // Must be called after saturation
    sem_elem_t InterGraph::get_call_weight(Transition t) {
      unsigned orig_size = nodes.size();
      unsigned n = nodeno(t);
      assert(orig_size == nodes.size()); // Transition t must not be a new one

      if(newtonGr)
        return newtonGr->get_weight(nodes[n].intra_nodeno);
      else
        return nodes[n].gr->get_weight(nodes[n].intra_nodeno);
    }

    sem_elem_t InterGraph::get_weight(Transition t){
      unsigned orig_size = nodes.size();
      unsigned n = nodeno(t);
      assert(orig_size == nodes.size()); // Transition t must not be a new one
      return get_weight(n);
    }

    // Changed for Newton Solver.
    // When the output automaton is tensored (@see comment above setupNewtonSolution), make sure
    // that the returned weight is also tensored.
    // When the output automaton is non-tensored, make sure that the returned weight is
    // non-tensored.
    sem_elem_t InterGraph::get_weight(unsigned n)
    {
      // check eHandler
      if(eHandler.exists(n)) {
        // This must be a return transition
        int nc;
        sem_elem_t wtCallRule = eHandler.get_dependency(n, nc);
        sem_elem_t wt;
        if(nc != -1) {
          wt = nodes[nc].gr->get_weight(nodes[nc].intra_nodeno);
          sem_elem_tensor_t twt = dynamic_cast<SemElemTensor*>(wt.get_ptr());
          if(twt != NULL){
            if(isOutputAutomatonTensored && ! nodes[nc].gr->hasTensoredWeights)
              wt = tensorSetUpFP(twt,boost::polymorphic_downcast<SemElemTensor*>(twt->one().get_ptr()));
            if(! isOutputAutomatonTensored && nodes[nc].gr->hasTensoredWeights)
              wt = twt->detensorTranspose();
          }
        } else {
          // ESource
          wt = wtCallRule->one();
        }
        return wt->extend(wtCallRule.get_ptr());
      }
      sem_elem_t wt = nodes[n].gr->get_weight(nodes[n].intra_nodeno);
      sem_elem_tensor_t twt = dynamic_cast<SemElemTensor*>(wt.get_ptr());
      if(twt != NULL){
        if(isOutputAutomatonTensored && ! nodes[n].gr->hasTensoredWeights)
          wt = tensorSetUpFP(twt,boost::polymorphic_downcast<SemElemTensor*>(twt->one().get_ptr()));
        if(! isOutputAutomatonTensored && nodes[n].gr->hasTensoredWeights)
          wt = twt->detensorTranspose();
      }
      return wt;
    }

    void InterGraph::update_all_weights() {
      unsigned int i;
      for(i=0;i<nodes.size();i++) {
        sem_elem_t w;
        if(newtonGr)
          w = newtonGr->get_weight(nodes[i].intra_nodeno);
        else
          w = nodes[i].gr->get_weight(nodes[i].intra_nodeno);
        nodes[i].weight = w;
      }
    }

    inline int get_number(map<int,int> &intra_node_map, int src, IntraGraph *ca) {
      std::map<int,int>::iterator it = intra_node_map.find(src);
      if(it != intra_node_map.end()) {
        return it->second;
      }
      int s = ca->makeNode();
      intra_node_map[src] = s;
      return s;
    }

    bool InterGraph::path_summary(int state, int stack, int accept, WT_CORRECT correct, WT_CHECK op) {
      // Build a hashmap: transition.src -> transitions
      typedef wali::HashMap<int, std::list<int> > trans_map_t;
      std::map<int, int> intra_node_map; // transition.src -> intra node number

      trans_map_t trans_map;
      set<int> states_visited;
      std::list<int> worklist;
      unsigned int i;
      IntraGraph *ca = new IntraGraph(dag, true, sem); // running_prestar = true because extend goes backward
      //Transition initial_st(state, 0, 0);

      ca->setSource(get_number(intra_node_map,state,ca), sem->one());
      for(i=0;i<nodes.size();i++) {
        trans_map_t::iterator it = trans_map.find(nodes[i].trans.src);
        if(it == trans_map.end()) {
          std::list<int> temp;
          temp.push_back(i);
          trans_map.insert(nodes[i].trans.src, temp);
        } else {
          it->second.push_back(i);
        }
        // add initial (state, stack, _) transitions
        if(((int)nodes[i].trans.src) == state &&
           ((int)nodes[i].trans.stack) == stack)
        {
          Transition t1(state,0,0);
          Transition t2(nodes[i].trans.tgt,0,0);
          if(newtonGr)
            ca->addEdge(get_number(intra_node_map,state,ca), get_number(intra_node_map, nodes[i].trans.tgt,ca),
                correct(newtonGr->get_weight(nodes[i].intra_nodeno).get_ptr()));
          else
            ca->addEdge(get_number(intra_node_map,state,ca), get_number(intra_node_map, nodes[i].trans.tgt,ca),
                correct(nodes[i].gr->get_weight(nodes[i].intra_nodeno).get_ptr()));
          worklist.push_back(nodes[i].trans.tgt);
        }
      }
      states_visited.insert(state);
      while(!worklist.empty()) {
        int st = worklist.front();
        worklist.pop_front();
        if(states_visited.find(st) != states_visited.end())
          continue;
        states_visited.insert(st);
        trans_map_t::iterator trans_it = trans_map.find(st);
        if(trans_it == trans_map.end())
          continue;
        std::list<int> &trans = trans_it->second;
        std::list<int>::iterator it;
        for(it = trans.begin(); it != trans.end(); it++) {
          i = *it;
          int t1 = get_number(intra_node_map,nodes[i].trans.src,ca);
          int t2 = get_number(intra_node_map,nodes[i].trans.tgt,ca);
          if(newtonGr)
            ca->addEdge(t1, t2, correct(newtonGr->get_weight(nodes[i].intra_nodeno).get_ptr()));
          else
            ca->addEdge(t1, t2, correct(nodes[i].gr->get_weight(nodes[i].intra_nodeno).get_ptr()));
          if(states_visited.find(nodes[i].trans.tgt) == states_visited.end()) {
            worklist.push_back(nodes[i].trans.tgt);
          }
        }
      }
      int final_st = get_number(intra_node_map,accept, ca);
      ca->setOutNode(final_st, 1); // second argument is not required
      ca->setupIntraSolution(false);
      bool r = op(ca->get_weight(final_st).get_ptr());
      delete ca;
      return r;
    }

} // namespace graph

} // namespace wali