/** * */ package vroom.common.utilities.gurobi; import gurobi.GRB.IntAttr; import gurobi.GRBConstr; import gurobi.GRBException; import gurobi.GRBModel; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.PriorityQueue; import java.util.Set; import vroom.common.utilities.logging.LoggerHelper; /** * <code>GRBConstraintManager</code> is a utility class to manage a set of constraints. * <p> * In particular it can be used in cut generation to manage a set of cut and automatically remove cuts that are no * longer active * </p> * <p> * Creation date: 19/08/2010 - 10:10:43 * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a> * @version 1.0 */ public class GRBConstraintManager { public static final LoggerHelper LOGGER = LoggerHelper.getLogger(GRBConstraintManager.class .getSimpleName()); public static enum OrderingCriterion { AGE, ACTIVE_COUNT, INACTIVE_COUNT } private final GRBModel mModel; private final Set<GRBConstraint> mConstraints; /** * Returns the number of constraints in this manager * * @return the number of constraints in this manager */ public int getConstraintCount() { return mConstraints.size(); } /** The ordering criterion for this constraint manager **/ private OrderingCriterion mOrderingCriterion; /** * Getter for the ordering criterion for this constraint manager * * @return the ordering criterion */ public OrderingCriterion getOrderingCriterion() { return mOrderingCriterion; } /** * Setter for the ordering criterion for this constraint manager * * @param criterion * the value to be set for name */ public void setOrderingCriterion(OrderingCriterion criterion) { mOrderingCriterion = criterion; } /** A threshold determining which constraints should be trashed **/ private int mTrashThreshold; /** * Getter for the threshold determining which constraints should be trashed * * @return the value of the trash threshold */ public int getTrashThreshold() { return mTrashThreshold; } /** * Setter for the threshold determining which constraints should be trashed * * @param threshold * the value to be set for the trash threshold */ public void setTrashThreshold(int threshold) { mTrashThreshold = threshold; } /** the maximum number of constraints to be kept **/ private int mMaxConstraints; /** * Getter for maxConstraints : the maximum number of constraints to be kept * * @return the value of maxConstraints */ public int getMaxConstraints() { return mMaxConstraints; } /** * Setter for maxConstraints : the maximum number of constraints to be kept * * @param maxConstraints * the value to be set for maxConstraints */ public void setMaxConstraints(int maxConstraints) { mMaxConstraints = maxConstraints; } /** * Creates a new <code>GRBConstraintManager</code> * * @param model * @param orderingCriterion * @param trashThreshold */ public GRBConstraintManager(GRBModel model, OrderingCriterion orderingCriterion, int trashThreshold) { mModel = model; mOrderingCriterion = orderingCriterion; mTrashThreshold = trashThreshold; mConstraints = new HashSet<GRBConstraint>(); mMaxConstraints = Integer.MAX_VALUE; } /** * Add a constraint to this manager and to the underlying model * * @param constraint * @throws GRBException * @throws {@link IllegalStateException} if the constraint was already added to another model */ public void addConstraint(GRBConstraint constraint) throws GRBException { if (constraint.getConstraint() == null) { constraint.addToModel(mModel); } registerConstraint(constraint); } /** * Add a constraint to this manager, checking that it is already in the underlying model * * @param constraint * @throws {@link IllegalStateException} if the constraint was already added to another model */ public void registerConstraint(GRBConstraint constraint) { if (constraint.getModel() != mModel) { throw new IllegalStateException("The constraint was already added to another model"); } mConstraints.add(constraint); } /** * Update the constraint statistics */ public void updateStats() { GRBConstr[] constrs = new GRBConstr[mConstraints.size()]; GRBConstraint[] constraints = new GRBConstraint[mConstraints.size()]; int i = 0; for (GRBConstraint c : mConstraints) { constraints[i] = c; constrs[i++] = c.getConstraint(); } int[] states = null; try { GRBModel model = mModel; if (mModel.get(IntAttr.IsMIP) == 1) { model = mModel.fixedModel(); model.optimize(); } // Get the constraint basic state (0 in basis, -1 out) states = model.get(IntAttr.CBasis, constrs); LOGGER.lowDebug("updateStats: Constraint stats updated"); } catch (GRBException e) { LOGGER.exception("GRBConstraintManager.updateStats", e); } finally { if (states == null) { return; } } for (int j = 0; j < states.length; j++) { constraints[j].updateAge(states[j] == 0); } } /** * Trash constraints that do not satisfy the {@linkplain #getTrashThreshold() threshold} defined for the given * {@linkplain #getOrderingCriterion() ordering criterion}. * * @param autoRemove * <code>true</code> if the trashed constraints should be automatically removed from the model */ public void trashConstraints(boolean autoRemove) { PriorityQueue<GRBConstraint> trashQueue = null; if (getConstraintCount() > getMaxConstraints()) { trashQueue = new PriorityQueue<GRBConstraint>(getConstraintCount(), new ConstraintComparator()); } LinkedList<GRBConstraint> trashConstraints = new LinkedList<GRBConstraint>(); for (GRBConstraint c : mConstraints) { // Check threshold if (!checkThreshold(c)) { trashConstraints.add(c); // Add to trash queue if too many constraints } else if (trashQueue != null) { trashQueue.add(c); } } // Trash constraints // Exceeding constraints int exc = trashQueue == null ? 0 : getMaxConstraints() - trashQueue.size(); // Too many constraints while (exc > 0) { GRBConstraint c = trashQueue.poll(); trashConstraints.add(c); exc--; } // Remove the trashed constraints from the model if (autoRemove) { for (GRBConstraint c : trashConstraints) { try { mModel.remove(c.getConstraint()); LOGGER.debug("updateStats: Constraint removed - %s", c); } catch (GRBException e) { LOGGER.exception("GRBConstraintManager.trashConstraints (constraint %s)", e, c); } } try { mModel.update(); } catch (GRBException e) { LOGGER.exception("GRBConstraintManager.trashConstraints - model update", e); } } mConstraints.removeAll(trashConstraints); } /** * Check whether or not a constraint satisfies the trash threshold * * @param grbConstraint * @return <code>true</code> if the constraint is to be kept, <code>false</code> if it should be trashed */ private boolean checkThreshold(GRBConstraint grbConstraint) { switch (getOrderingCriterion()) { case AGE: return grbConstraint.getAge() < getTrashThreshold(); case ACTIVE_COUNT: return grbConstraint.getActiveCount() > getTrashThreshold(); case INACTIVE_COUNT: return grbConstraint.getInactiveCount() < getTrashThreshold(); default: return true; } } private class ConstraintComparator implements Comparator<GRBConstraint> { @Override public int compare(GRBConstraint o1, GRBConstraint o2) { switch (getOrderingCriterion()) { case AGE: return o2.getAge() - o1.getAge(); case ACTIVE_COUNT: return o1.getActiveCount() - o2.getActiveCount(); case INACTIVE_COUNT: return o2.getInactiveCount() - o1.getInactiveCount(); default: return 0; } } } }