Skip to content
Snippets Groups Projects
FlowModel.java 3.87 KiB
Newer Older
Paul A. Rubin's avatar
Paul A. Rubin committed
package models;

import ilog.concert.IloException;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;
import ilog.cplex.IloCplex.Status;
import java.util.HashSet;
import java.util.Set;
import socialnet.Graph;

/**
 * FlowModel implements a MIP model for the problem based on flows between
 * nodes.
 *
 * @author Paul A. Rubin (rubin@msu.edu)
 */
public final class FlowModel implements AutoCloseable {
  private static final double HALF = 0.5;  // for rounding tests
  private final int nVertices;  // number of vertices in the graph
  private final int nEdges;     // number of edges

  // CPLEX objects.
  private final IloCplex mip;        // the MIP model
  private final IloNumVar[] select;  // indicators for selecting vertices
  private final IloNumVar[] flowF;   // forward edge flows
  private final IloNumVar[] flowR;   // reverse edge flows

  /**
   * Constructor.
   * @param graph the graph to solve
   * @param nSelect the number of nodes to select
   * @throws IloException if the model cannot be constructed
   */
  public FlowModel(final Graph graph, final int nSelect) throws IloException {
    nVertices = graph.getnVertices();
    nEdges = graph.getnEdges();
    // Initialize the model.
    mip = new IloCplex();
    // Create the variables.
    select = new IloNumVar[nVertices];
    flowF = new IloNumVar[nEdges];
    flowR = new IloNumVar[nEdges];
    for (int i = 0; i < nVertices; i++) {
      select[i] = mip.boolVar("select_" + i);
    }
    for (int i = 0; i < nEdges; i++) {
      flowF[i] = mip.numVar(0, nVertices, "forward_" + i);
      flowR[i] = mip.numVar(0, nVertices, "backward_" + i);
    }
    // The objective is to minimize the sum of all flows.
    mip.addMinimize(mip.sum(mip.sum(flowF), mip.sum(flowR)));
    // The number of nodes selected must match the requirement.
    mip.addEq(mip.sum(select), nSelect, "selection_count");
    // At every node, there must be exactly one unit more of outflow than of
    // inflow unless the node is selected.
    int m = nVertices - nSelect;  // "big M"
    for (int i = 0; i < nVertices; i++) {
      // Form an expression of the form flow out of i minus flow into i.
      IloLinearNumExpr expr = mip.linearNumExpr();
      // Check each edge incident at i.
      for (int j : graph.incidentAt(i)) {
        if (graph.isHead(i, j)) {
          // The forward direction enters i, the reverse direction leaves i.
          expr.addTerm(1.0, flowR[j]);
          expr.addTerm(-1.0, flowF[j]);
        } else {
          // The forward direction leaves i, the reverse direction enters i.
          expr.addTerm(-1.0, flowR[j]);
          expr.addTerm(1.0, flowF[j]);
        }
      }
      // Flow out - flow in must be >= 1 - m * select[i].
      mip.addGe(expr, mip.diff(1.0, mip.prod(m, select[i])), "balance_" + i);
    }
  }

  /**
   * Solves the model.
   * @param timeLimit the time limit for the solver (in seconds)
   * @return the final solver status
   * @throws IloException if the solution fails
   */
  public Status solve(final double timeLimit) throws IloException {
    mip.setParam(IloCplex.DoubleParam.TimeLimit, timeLimit);
    mip.solve();
    return mip.getStatus();
  }

  /**
   * Gets the final objective value.
   * @return the objective value
   * @throws IloException if there is no objective value
   */
  public double getObjValue() throws IloException {
    return mip.getObjValue();
  }

  /**
   * Gets the set of nodes selected by the solution.
   * @return the set of selected nodes
   * @throws IloException if no solution exists
   */
  public Set<Integer> getSolution() throws IloException {
    HashSet<Integer> sol = new HashSet<>();
    double[] x = mip.getValues(select);
    for (int i = 0; i < nVertices; i++) {
      if (x[i] > HALF) {
        sol.add(i);
      }
    }
    return sol;
  }

  /**
   * Closes the model.
   */
  @Override
  public void close() {
    mip.close();
  }

}