//===-- HeuristicSolver.h - Heuristic PBQP Solver --------------*- C++ --*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Heuristic PBQP solver. This solver is able to perform optimal reductions for
// nodes of degree 0, 1 or 2. For nodes of degree >2 a plugable heuristic is
// used to to select a node for reduction. 
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CODEGEN_PBQP_HEURISTICSOLVER_H
#define LLVM_CODEGEN_PBQP_HEURISTICSOLVER_H

#include "Solver.h"
#include "AnnotatedGraph.h"
#include "llvm/Support/raw_ostream.h"
#include <limits>

namespace PBQP {

/// \brief Important types for the HeuristicSolverImpl.
/// 
/// Declared seperately to allow access to heuristic classes before the solver
/// is fully constructed.
template <typename HeuristicNodeData, typename HeuristicEdgeData>
class HSITypes {
public:

  class NodeData;
  class EdgeData;

  typedef AnnotatedGraph<NodeData, EdgeData> SolverGraph;
  typedef typename SolverGraph::NodeIterator GraphNodeIterator;
  typedef typename SolverGraph::EdgeIterator GraphEdgeIterator;
  typedef typename SolverGraph::AdjEdgeIterator GraphAdjEdgeIterator;

  typedef std::list<GraphNodeIterator> NodeList;
  typedef typename NodeList::iterator NodeListIterator;

  typedef std::vector<GraphNodeIterator> NodeStack;
  typedef typename NodeStack::iterator NodeStackIterator;

  class NodeData {
    friend class EdgeData;

  private:

    typedef std::list<GraphEdgeIterator> LinksList;

    unsigned numLinks;
    LinksList links, solvedLinks;
    NodeListIterator bucketItr;
    HeuristicNodeData heuristicData;

  public:

    typedef typename LinksList::iterator AdjLinkIterator;

  private:

    AdjLinkIterator addLink(const GraphEdgeIterator &edgeItr) {
      ++numLinks;
      return links.insert(links.end(), edgeItr);
    }

    void delLink(const AdjLinkIterator &adjLinkItr) {
      --numLinks;
      links.erase(adjLinkItr);
    }

  public:

    NodeData() : numLinks(0) {}

    unsigned getLinkDegree() const { return numLinks; }

    HeuristicNodeData& getHeuristicData() { return heuristicData; }
    const HeuristicNodeData& getHeuristicData() const {
      return heuristicData;
    }

    void setBucketItr(const NodeListIterator &bucketItr) {
      this->bucketItr = bucketItr;
    }

    const NodeListIterator& getBucketItr() const {
      return bucketItr;
    }

    AdjLinkIterator adjLinksBegin() {
      return links.begin();
    }

    AdjLinkIterator adjLinksEnd() {
      return links.end();
    }

    void addSolvedLink(const GraphEdgeIterator &solvedLinkItr) {
      solvedLinks.push_back(solvedLinkItr);
    }

    AdjLinkIterator solvedLinksBegin() {
      return solvedLinks.begin();
    }

    AdjLinkIterator solvedLinksEnd() {
      return solvedLinks.end();
    }

  };

  class EdgeData {
  private:

    SolverGraph &g;
    GraphNodeIterator node1Itr, node2Itr;
    HeuristicEdgeData heuristicData;
    typename NodeData::AdjLinkIterator node1ThisEdgeItr, node2ThisEdgeItr;

  public:

    EdgeData(SolverGraph &g) : g(g) {}

    HeuristicEdgeData& getHeuristicData() { return heuristicData; }
    const HeuristicEdgeData& getHeuristicData() const {
      return heuristicData;
    }

    void setup(const GraphEdgeIterator &thisEdgeItr) {
      node1Itr = g.getEdgeNode1Itr(thisEdgeItr);
      node2Itr = g.getEdgeNode2Itr(thisEdgeItr);

      node1ThisEdgeItr = g.getNodeData(node1Itr).addLink(thisEdgeItr);
      node2ThisEdgeItr = g.getNodeData(node2Itr).addLink(thisEdgeItr);
    }

    void unlink() {
      g.getNodeData(node1Itr).delLink(node1ThisEdgeItr);
      g.getNodeData(node2Itr).delLink(node2ThisEdgeItr);
    }

  };

};

template <typename Heuristic>
class HeuristicSolverImpl {
public:
  // Typedefs to make life easier:
  typedef HSITypes<typename Heuristic::NodeData,
                   typename Heuristic::EdgeData> HSIT;
  typedef typename HSIT::SolverGraph SolverGraph;
  typedef typename HSIT::NodeData NodeData;
  typedef typename HSIT::EdgeData EdgeData;
  typedef typename HSIT::GraphNodeIterator GraphNodeIterator;
  typedef typename HSIT::GraphEdgeIterator GraphEdgeIterator;
  typedef typename HSIT::GraphAdjEdgeIterator GraphAdjEdgeIterator;

  typedef typename HSIT::NodeList NodeList;
  typedef typename HSIT::NodeListIterator NodeListIterator;

  typedef std::vector<GraphNodeIterator> NodeStack;
  typedef typename NodeStack::iterator NodeStackIterator;

  /// \brief Constructor, which performs all the actual solver work.
  HeuristicSolverImpl(const SimpleGraph &orig) :
    solution(orig.getNumNodes(), true)
  {
    copyGraph(orig);
    simplify();
    setup();
    computeSolution();
    computeSolutionCost(orig);
  }

  /// \brief Returns the graph for this solver.
  SolverGraph& getGraph() { return g; }

  /// \brief Return the solution found by this solver.
  const Solution& getSolution() const { return solution; }

private:

  /// \brief Add the given node to the appropriate bucket for its link
  /// degree.
  void addToBucket(const GraphNodeIterator &nodeItr) {
    NodeData &nodeData = g.getNodeData(nodeItr);

    switch (nodeData.getLinkDegree()) {
      case 0: nodeData.setBucketItr(
                r0Bucket.insert(r0Bucket.end(), nodeItr));
              break;                                            
      case 1: nodeData.setBucketItr(
                r1Bucket.insert(r1Bucket.end(), nodeItr));
              break;
      case 2: nodeData.setBucketItr(
                r2Bucket.insert(r2Bucket.end(), nodeItr));
              break;
      default: heuristic.addToRNBucket(nodeItr);
               break;
    }
  }

  /// \brief Remove the given node from the appropriate bucket for its link
  /// degree.
  void removeFromBucket(const GraphNodeIterator &nodeItr) {
    NodeData &nodeData = g.getNodeData(nodeItr);

    switch (nodeData.getLinkDegree()) {
      case 0: r0Bucket.erase(nodeData.getBucketItr()); break;
      case 1: r1Bucket.erase(nodeData.getBucketItr()); break;
      case 2: r2Bucket.erase(nodeData.getBucketItr()); break;
      default: heuristic.removeFromRNBucket(nodeItr); break;
    }
  }

public:

  /// \brief Add a link.
  void addLink(const GraphEdgeIterator &edgeItr) {
    g.getEdgeData(edgeItr).setup(edgeItr);

    if ((g.getNodeData(g.getEdgeNode1Itr(edgeItr)).getLinkDegree() > 2) ||
        (g.getNodeData(g.getEdgeNode2Itr(edgeItr)).getLinkDegree() > 2)) {
      heuristic.handleAddLink(edgeItr);
    }
  }

  /// \brief Remove link, update info for node.
  ///
  /// Only updates information for the given node, since usually the other
  /// is about to be removed.
  void removeLink(const GraphEdgeIterator &edgeItr,
                  const GraphNodeIterator &nodeItr) {

    if (g.getNodeData(nodeItr).getLinkDegree() > 2) {
      heuristic.handleRemoveLink(edgeItr, nodeItr);
    }
    g.getEdgeData(edgeItr).unlink();
  }

  /// \brief Remove link, update info for both nodes. Useful for R2 only.
  void removeLinkR2(const GraphEdgeIterator &edgeItr) {
    GraphNodeIterator node1Itr = g.getEdgeNode1Itr(edgeItr);

    if (g.getNodeData(node1Itr).getLinkDegree() > 2) {
      heuristic.handleRemoveLink(edgeItr, node1Itr);
    }
    removeLink(edgeItr, g.getEdgeNode2Itr(edgeItr));
  }

  /// \brief Removes all links connected to the given node.
  void unlinkNode(const GraphNodeIterator &nodeItr) {
    NodeData &nodeData = g.getNodeData(nodeItr);

    typedef std::vector<GraphEdgeIterator> TempEdgeList;

    TempEdgeList edgesToUnlink;
    edgesToUnlink.reserve(nodeData.getLinkDegree());

    // Copy adj edges into a temp vector. We want to destroy them during
    // the unlink, and we can't do that while we're iterating over them.
    std::copy(nodeData.adjLinksBegin(), nodeData.adjLinksEnd(),
              std::back_inserter(edgesToUnlink));

    for (typename TempEdgeList::iterator
         edgeItr = edgesToUnlink.begin(), edgeEnd = edgesToUnlink.end();
         edgeItr != edgeEnd; ++edgeItr) {

      GraphNodeIterator otherNode = g.getEdgeOtherNode(*edgeItr, nodeItr);

      removeFromBucket(otherNode);
      removeLink(*edgeItr, otherNode);
      addToBucket(otherNode);
    }
  }

  /// \brief Push the given node onto the stack to be solved with
  /// backpropagation.
  void pushStack(const GraphNodeIterator &nodeItr) {
    stack.push_back(nodeItr);
  }

  /// \brief Set the solution of the given node.
  void setSolution(const GraphNodeIterator &nodeItr, unsigned solIndex) {
    solution.setSelection(g.getNodeID(nodeItr), solIndex);

    for (GraphAdjEdgeIterator adjEdgeItr = g.adjEdgesBegin(nodeItr),
         adjEdgeEnd = g.adjEdgesEnd(nodeItr);
         adjEdgeItr != adjEdgeEnd; ++adjEdgeItr) {
      GraphEdgeIterator edgeItr(*adjEdgeItr);
      GraphNodeIterator adjNodeItr(g.getEdgeOtherNode(edgeItr, nodeItr));
      g.getNodeData(adjNodeItr).addSolvedLink(edgeItr);
    }
  }

private:

  SolverGraph g;
  Heuristic heuristic;
  Solution solution;

  NodeList r0Bucket,
           r1Bucket,
           r2Bucket;

  NodeStack stack;

  // Copy the SimpleGraph into an annotated graph which we can use for reduction.
  void copyGraph(const SimpleGraph &orig) {

    assert((g.getNumEdges() == 0) && (g.getNumNodes() == 0) &&
           "Graph should be empty prior to solver setup.");

    assert(orig.areNodeIDsValid() &&
           "Cannot copy from a graph with invalid node IDs.");

    std::vector<GraphNodeIterator> newNodeItrs;

    for (unsigned nodeID = 0; nodeID < orig.getNumNodes(); ++nodeID) {
      newNodeItrs.push_back(
        g.addNode(orig.getNodeCosts(orig.getNodeItr(nodeID)), NodeData()));
    }

    for (SimpleGraph::ConstEdgeIterator
         origEdgeItr = orig.edgesBegin(), origEdgeEnd = orig.edgesEnd();
         origEdgeItr != origEdgeEnd; ++origEdgeItr) {

      unsigned id1 = orig.getNodeID(orig.getEdgeNode1Itr(origEdgeItr)),
               id2 = orig.getNodeID(orig.getEdgeNode2Itr(origEdgeItr));

      g.addEdge(newNodeItrs[id1], newNodeItrs[id2],
                orig.getEdgeCosts(origEdgeItr), EdgeData(g));
    }

    // Assign IDs to the new nodes using the ordering from the old graph,
    // this will lead to nodes in the new graph getting the same ID as the
    // corresponding node in the old graph.
    g.assignNodeIDs(newNodeItrs);
  }

  // Simplify the annotated graph by eliminating independent edges and trivial
  // nodes. 
  void simplify() {
    disconnectTrivialNodes();
    eliminateIndependentEdges();
  }

  // Eliminate trivial nodes.
  void disconnectTrivialNodes() {
    for (GraphNodeIterator nodeItr = g.nodesBegin(), nodeEnd = g.nodesEnd();
         nodeItr != nodeEnd; ++nodeItr) {

      if (g.getNodeCosts(nodeItr).getLength() == 1) {

        std::vector<GraphEdgeIterator> edgesToRemove;

        for (GraphAdjEdgeIterator adjEdgeItr = g.adjEdgesBegin(nodeItr),
             adjEdgeEnd = g.adjEdgesEnd(nodeItr);
             adjEdgeItr != adjEdgeEnd; ++adjEdgeItr) {

          GraphEdgeIterator edgeItr = *adjEdgeItr;

          if (g.getEdgeNode1Itr(edgeItr) == nodeItr) {
            GraphNodeIterator otherNodeItr = g.getEdgeNode2Itr(edgeItr);
            g.getNodeCosts(otherNodeItr) +=
              g.getEdgeCosts(edgeItr).getRowAsVector(0);
          }
          else {
            GraphNodeIterator otherNodeItr = g.getEdgeNode1Itr(edgeItr);
            g.getNodeCosts(otherNodeItr) +=
              g.getEdgeCosts(edgeItr).getColAsVector(0);
          }

          edgesToRemove.push_back(edgeItr);
        }

        while (!edgesToRemove.empty()) {
          g.removeEdge(edgesToRemove.back());
          edgesToRemove.pop_back();
        }
      }
    }
  }

  void eliminateIndependentEdges() {
    std::vector<GraphEdgeIterator> edgesToProcess;

    for (GraphEdgeIterator edgeItr = g.edgesBegin(), edgeEnd = g.edgesEnd();
         edgeItr != edgeEnd; ++edgeItr) {
      edgesToProcess.push_back(edgeItr);
    }

    while (!edgesToProcess.empty()) {
      tryToEliminateEdge(edgesToProcess.back());
      edgesToProcess.pop_back();
    }
  }

  void tryToEliminateEdge(const GraphEdgeIterator &edgeItr) {
    if (tryNormaliseEdgeMatrix(edgeItr)) {
      g.removeEdge(edgeItr); 
    }
  }

  bool tryNormaliseEdgeMatrix(const GraphEdgeIterator &edgeItr) {

    Matrix &edgeCosts = g.getEdgeCosts(edgeItr);
    Vector &uCosts = g.getNodeCosts(g.getEdgeNode1Itr(edgeItr)),
               &vCosts = g.getNodeCosts(g.getEdgeNode2Itr(edgeItr));

    for (unsigned r = 0; r < edgeCosts.getRows(); ++r) {
      PBQPNum rowMin = edgeCosts.getRowMin(r);
      uCosts[r] += rowMin;
      if (rowMin != std::numeric_limits<PBQPNum>::infinity()) {
        edgeCosts.subFromRow(r, rowMin);
      }
      else {
        edgeCosts.setRow(r, 0);
      }
    }

    for (unsigned c = 0; c < edgeCosts.getCols(); ++c) {
      PBQPNum colMin = edgeCosts.getColMin(c);
      vCosts[c] += colMin;
      if (colMin != std::numeric_limits<PBQPNum>::infinity()) {
        edgeCosts.subFromCol(c, colMin);
      }
      else {
        edgeCosts.setCol(c, 0);
      }
    }

    return edgeCosts.isZero();
  }

  void setup() {
    setupLinks();
    heuristic.initialise(*this);
    setupBuckets();
  }

  void setupLinks() {
    for (GraphEdgeIterator edgeItr = g.edgesBegin(), edgeEnd = g.edgesEnd();
         edgeItr != edgeEnd; ++edgeItr) {
      g.getEdgeData(edgeItr).setup(edgeItr);
    }
  }

  void setupBuckets() {
    for (GraphNodeIterator nodeItr = g.nodesBegin(), nodeEnd = g.nodesEnd();
         nodeItr != nodeEnd; ++nodeItr) {
      addToBucket(nodeItr);
    }
  }

  void computeSolution() {
    assert(g.areNodeIDsValid() &&
           "Nodes cannot be added/removed during reduction.");

    reduce();
    computeTrivialSolutions();
    backpropagate();
  }

  void printNode(const GraphNodeIterator &nodeItr) {
    llvm::errs() << "Node " << g.getNodeID(nodeItr) << " (" << &*nodeItr << "):\n"
                 << "  costs = " << g.getNodeCosts(nodeItr) << "\n"
                 << "  link degree = " << g.getNodeData(nodeItr).getLinkDegree() << "\n"
                 << "  links = [ ";

    for (typename HSIT::NodeData::AdjLinkIterator 
         aeItr = g.getNodeData(nodeItr).adjLinksBegin(),
         aeEnd = g.getNodeData(nodeItr).adjLinksEnd();
         aeItr != aeEnd; ++aeItr) {
      llvm::errs() << "(" << g.getNodeID(g.getEdgeNode1Itr(*aeItr))
                   << ", " << g.getNodeID(g.getEdgeNode2Itr(*aeItr))
                   << ") ";
    }
    llvm::errs() << "]\n";
  }

  void dumpState() {
    llvm::errs() << "\n";

    for (GraphNodeIterator nodeItr = g.nodesBegin(), nodeEnd = g.nodesEnd();
         nodeItr != nodeEnd; ++nodeItr) {
      printNode(nodeItr);
    }

    NodeList* buckets[] = { &r0Bucket, &r1Bucket, &r2Bucket };

    for (unsigned b = 0; b < 3; ++b) {
      NodeList &bucket = *buckets[b];

      llvm::errs() << "Bucket " << b << ": [ ";

      for (NodeListIterator nItr = bucket.begin(), nEnd = bucket.end();
           nItr != nEnd; ++nItr) {
        llvm::errs() << g.getNodeID(*nItr) << " ";
      }

      llvm::errs() << "]\n";
    }

    llvm::errs() << "Stack: [ ";
    for (NodeStackIterator nsItr = stack.begin(), nsEnd = stack.end();
         nsItr != nsEnd; ++nsItr) {
      llvm::errs() << g.getNodeID(*nsItr) << " ";
    }
    llvm::errs() << "]\n";
  }

  void reduce() {
    bool reductionFinished = r1Bucket.empty() && r2Bucket.empty() &&
      heuristic.rNBucketEmpty();

    while (!reductionFinished) {

      if (!r1Bucket.empty()) {
        processR1();
      }
      else if (!r2Bucket.empty()) {
        processR2();
      }
      else if (!heuristic.rNBucketEmpty()) {
        solution.setProvedOptimal(false);
        solution.incRNReductions();
        heuristic.processRN();
      } 
      else reductionFinished = true;
    }
      
  };

  void processR1() {

    // Remove the first node in the R0 bucket:
    GraphNodeIterator xNodeItr = r1Bucket.front();
    r1Bucket.pop_front();

    solution.incR1Reductions();

    //llvm::errs() << "Applying R1 to " << g.getNodeID(xNodeItr) << "\n";

    assert((g.getNodeData(xNodeItr).getLinkDegree() == 1) &&
           "Node in R1 bucket has degree != 1");

    GraphEdgeIterator edgeItr = *g.getNodeData(xNodeItr).adjLinksBegin();

    const Matrix &edgeCosts = g.getEdgeCosts(edgeItr);

    const Vector &xCosts = g.getNodeCosts(xNodeItr);
    unsigned xLen = xCosts.getLength();

    // Duplicate a little code to avoid transposing matrices:
    if (xNodeItr == g.getEdgeNode1Itr(edgeItr)) {
      GraphNodeIterator yNodeItr = g.getEdgeNode2Itr(edgeItr);
      Vector &yCosts = g.getNodeCosts(yNodeItr);
      unsigned yLen = yCosts.getLength();

      for (unsigned j = 0; j < yLen; ++j) {
        PBQPNum min = edgeCosts[0][j] + xCosts[0];
        for (unsigned i = 1; i < xLen; ++i) {
          PBQPNum c = edgeCosts[i][j] + xCosts[i];
          if (c < min)
            min = c;
        }
        yCosts[j] += min;
      }
    }
    else {
      GraphNodeIterator yNodeItr = g.getEdgeNode1Itr(edgeItr);
      Vector &yCosts = g.getNodeCosts(yNodeItr);
      unsigned yLen = yCosts.getLength();

      for (unsigned i = 0; i < yLen; ++i) {
        PBQPNum min = edgeCosts[i][0] + xCosts[0];

        for (unsigned j = 1; j < xLen; ++j) {
          PBQPNum c = edgeCosts[i][j] + xCosts[j];
          if (c < min)
            min = c;
        }
        yCosts[i] += min;
      }
    }

    unlinkNode(xNodeItr);
    pushStack(xNodeItr);
  }

  void processR2() {

    GraphNodeIterator xNodeItr = r2Bucket.front();
    r2Bucket.pop_front();

    solution.incR2Reductions();

    // Unlink is unsafe here. At some point it may optimistically more a node
    // to a lower-degree list when its degree will later rise, or vice versa,
    // violating the assumption that node degrees monotonically decrease
    // during the reduction phase. Instead we'll bucket shuffle manually.
    pushStack(xNodeItr);

    assert((g.getNodeData(xNodeItr).getLinkDegree() == 2) &&
           "Node in R2 bucket has degree != 2");

    const Vector &xCosts = g.getNodeCosts(xNodeItr);

    typename NodeData::AdjLinkIterator tempItr =
      g.getNodeData(xNodeItr).adjLinksBegin();

    GraphEdgeIterator yxEdgeItr = *tempItr,
                      zxEdgeItr = *(++tempItr);

    GraphNodeIterator yNodeItr = g.getEdgeOtherNode(yxEdgeItr, xNodeItr),
                      zNodeItr = g.getEdgeOtherNode(zxEdgeItr, xNodeItr);

    removeFromBucket(yNodeItr);
    removeFromBucket(zNodeItr);

    removeLink(yxEdgeItr, yNodeItr);
    removeLink(zxEdgeItr, zNodeItr);

    // Graph some of the costs:
    bool flipEdge1 = (g.getEdgeNode1Itr(yxEdgeItr) == xNodeItr),
         flipEdge2 = (g.getEdgeNode1Itr(zxEdgeItr) == xNodeItr);

    const Matrix *yxCosts = flipEdge1 ?
      new Matrix(g.getEdgeCosts(yxEdgeItr).transpose()) :
      &g.getEdgeCosts(yxEdgeItr),
                     *zxCosts = flipEdge2 ?
      new Matrix(g.getEdgeCosts(zxEdgeItr).transpose()) :
        &g.getEdgeCosts(zxEdgeItr);

    unsigned xLen = xCosts.getLength(),
             yLen = yxCosts->getRows(),
             zLen = zxCosts->getRows();

    // Compute delta:
    Matrix delta(yLen, zLen);

    for (unsigned i = 0; i < yLen; ++i) {
      for (unsigned j = 0; j < zLen; ++j) {
        PBQPNum min = (*yxCosts)[i][0] + (*zxCosts)[j][0] + xCosts[0];
        for (unsigned k = 1; k < xLen; ++k) {
          PBQPNum c = (*yxCosts)[i][k] + (*zxCosts)[j][k] + xCosts[k];
          if (c < min) {
            min = c;
          }
        }
        delta[i][j] = min;
      }
    }

    if (flipEdge1)
      delete yxCosts;

    if (flipEdge2)
      delete zxCosts;

    // Deal with the potentially induced yz edge.
    GraphEdgeIterator yzEdgeItr = g.findEdge(yNodeItr, zNodeItr);
    if (yzEdgeItr == g.edgesEnd()) {
      yzEdgeItr = g.addEdge(yNodeItr, zNodeItr, delta, EdgeData(g));
    }
    else {
      // There was an edge, but we're going to screw with it. Delete the old
      // link, update the costs. We'll re-link it later.
      removeLinkR2(yzEdgeItr);
      g.getEdgeCosts(yzEdgeItr) +=
        (yNodeItr == g.getEdgeNode1Itr(yzEdgeItr)) ?
        delta : delta.transpose();
    }

    bool nullCostEdge = tryNormaliseEdgeMatrix(yzEdgeItr);

    // Nulled the edge, remove it entirely.
    if (nullCostEdge) {
      g.removeEdge(yzEdgeItr);
    }
    else {
      // Edge remains - re-link it.
      addLink(yzEdgeItr);
    }

    addToBucket(yNodeItr);
    addToBucket(zNodeItr);
    }

  void computeTrivialSolutions() {

    for (NodeListIterator r0Itr = r0Bucket.begin(), r0End = r0Bucket.end();
         r0Itr != r0End; ++r0Itr) {
      GraphNodeIterator nodeItr = *r0Itr;

      solution.incR0Reductions();
      setSolution(nodeItr, g.getNodeCosts(nodeItr).minIndex());
    }

  }

  void backpropagate() {
    while (!stack.empty()) {
      computeSolution(stack.back());
      stack.pop_back();
    }
  }

  void computeSolution(const GraphNodeIterator &nodeItr) {

    NodeData &nodeData = g.getNodeData(nodeItr);

    Vector v(g.getNodeCosts(nodeItr));

    // Solve based on existing links.
    for (typename NodeData::AdjLinkIterator
         solvedLinkItr = nodeData.solvedLinksBegin(),
         solvedLinkEnd = nodeData.solvedLinksEnd();
         solvedLinkItr != solvedLinkEnd; ++solvedLinkItr) {

      GraphEdgeIterator solvedEdgeItr(*solvedLinkItr);
      Matrix &edgeCosts = g.getEdgeCosts(solvedEdgeItr);

      if (nodeItr == g.getEdgeNode1Itr(solvedEdgeItr)) {
        GraphNodeIterator adjNode(g.getEdgeNode2Itr(solvedEdgeItr));
        unsigned adjSolution =
          solution.getSelection(g.getNodeID(adjNode));
        v += edgeCosts.getColAsVector(adjSolution);
      }
      else {
        GraphNodeIterator adjNode(g.getEdgeNode1Itr(solvedEdgeItr));
        unsigned adjSolution =
          solution.getSelection(g.getNodeID(adjNode));
        v += edgeCosts.getRowAsVector(adjSolution);
      }

    }

    setSolution(nodeItr, v.minIndex());
  }

  void computeSolutionCost(const SimpleGraph &orig) {
    PBQPNum cost = 0.0;

    for (SimpleGraph::ConstNodeIterator
         nodeItr = orig.nodesBegin(), nodeEnd = orig.nodesEnd();
         nodeItr != nodeEnd; ++nodeItr) {

      unsigned nodeId = orig.getNodeID(nodeItr);

      cost += orig.getNodeCosts(nodeItr)[solution.getSelection(nodeId)];
    }

    for (SimpleGraph::ConstEdgeIterator
         edgeItr = orig.edgesBegin(), edgeEnd = orig.edgesEnd();
         edgeItr != edgeEnd; ++edgeItr) {

      SimpleGraph::ConstNodeIterator n1 = orig.getEdgeNode1Itr(edgeItr),
                                     n2 = orig.getEdgeNode2Itr(edgeItr);
      unsigned sol1 = solution.getSelection(orig.getNodeID(n1)),
               sol2 = solution.getSelection(orig.getNodeID(n2));

      cost += orig.getEdgeCosts(edgeItr)[sol1][sol2];
    }

    solution.setSolutionCost(cost);
  }

};

template <typename Heuristic>
class HeuristicSolver : public Solver {
public:
  Solution solve(const SimpleGraph &g) const {
    HeuristicSolverImpl<Heuristic> solverImpl(g);
    return solverImpl.getSolution();
  }
};

}

#endif // LLVM_CODEGEN_PBQP_HEURISTICSOLVER_H
