/*
This file is part of JOP, the Java Optimized Processor
see <http://www.jopdesign.com/>
Copyright (C) 2010, Benedikt Huber (benedikt@vmars.tuwien.ac.at)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.wcet.ipet;
import com.jopdesign.common.graphutils.IDProvider;
import com.jopdesign.common.misc.MiscUtils;
import lpsolve.LpSolveException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Purpose: Invoke an ILP solver, to solve an IPET problem.
* An IPET problem consists of:<ul/>
* <li/> Execution Edges
* <li/> Constraints involving edges
* <li/> Costs for edges
* </ul>
*
* @author Benedikt Huber (benedikt@vmars.tuwien.ac.at)
*/
public class IPETSolver<T> {
/**
* If you use the BIGM method, be aware that you risk numeric instabilities for larger flows,
* and that the method will fail to work if the total flow exceeds BIGM
*/
public static final long BIGM = Long.MAX_VALUE;
private static final boolean USE_PRESOLVE = true;
private List<LinearConstraint<T>> edgeConstraints = new ArrayList<LinearConstraint<T>>();
private Map<T, Long> edgeCost = new HashMap<T, Long>();
private Set<T> edgeSet = new HashSet<T>();
private HashMap<T, Integer> edgeIdMap = null;
private HashMap<Integer, T> idEdgeMap = null;
private File outDir;
private String problemName;
/**
* @param problemName A name for the IPET Problem (for debugging/logging purposes)
* @param config Configuration for the ILP solver
*/
public IPETSolver(String problemName, IPETConfig config) {
this.problemName = problemName;
outDir = config.doDumpIlp() ? config.getOutDir() : null;
}
public void addConstraint(LinearConstraint<T> lc) {
this.edgeConstraints.add(lc);
if(lc.isContradiction()) {
throw new AssertionError("Adding contradition to ILP: "+lc);
}
for (T edge : lc.getLinearVectorOnLHS().getCoeffs().keySet()) {
this.edgeSet.add(edge);
}
}
public void addConstraints(Collection<LinearConstraint<T>> cs) {
for (LinearConstraint<T> lc : cs) addConstraint(lc);
}
public void addConstraints(Collection<LinearConstraint<T>> cs, String debugMsg) {
for (LinearConstraint<T> lc : cs) {
System.err.println("[constraint][" + debugMsg + "]: " + lc);
addConstraint(lc);
}
}
public void addEdgeCost(T e, long cost) {
if (this.edgeCost.containsKey(e)) edgeCost.put(e, edgeCost.get(e) + cost);
else edgeCost.put(e, cost);
this.edgeSet.add(e);
}
/**
* @param key
* @return
*/
public long getEdgeCost(T key) {
if (edgeCost.containsKey(key)) return edgeCost.get(key);
else return 0;
}
public Map<T, Long> getCostVector() {
return this.edgeCost;
}
public void setCostVector(Map<T, Long> edgeCost) {
this.edgeCost = edgeCost;
}
/**
* Solve the max cost network flow problem using {@link LpSolveWrapper}.
*
* @param flowMapOut if not null, write solution into this map, assigning a flow to each edge
* @return the cost of the solution
* @throws Exception if the ILP solver fails
*/
public double solve(Map<T, Long> flowMapOut) throws LpSolveException {
return solve(flowMapOut, true);
}
/**
* Solve the max cost network flow problem using {@link LpSolveWrapper}.
*
* @param flowMapOut if not null, write solution into this map, assigning a flow to each edge
* @param isILP if false, assumes all variables are rational (relaxed problem)
* @return the cost of the solution
* @throws LpSolveException
* @throws Exception if the ILP solver fails
*/
public double solve(Map<T, Long> flowMapOut, boolean isILP) throws LpSolveException {
IDProvider<Object> idProvider = this.generateMapping();
LpSolveWrapper<Object> wrapper = new LpSolveWrapper<Object>(edgeSet.size(), isILP, idProvider);
/* Add Constraints */
for (LinearConstraint<T> lc : edgeConstraints) {
wrapper.addConstraint(lc);
}
/* build cost objective */
LinearVector<T> costVec = new LinearVector<T>();
for (Entry<T, Long> entry : this.edgeCost.entrySet()) {
long costFactor = entry.getValue();
costVec.add(entry.getKey(), costFactor);
}
wrapper.setObjective(costVec, true);
wrapper.freeze();
File dumpFile = null;
if (this.outDir != null) {
try {
dumpFile = dumpILP(wrapper);
} catch (IOException e) {
throw new LpSolveException("Failed to write ILP: " + e.getMessage());
}
}
double sol;
if (flowMapOut != null) {
double[] objVec = new double[edgeSet.size()];
sol = Math.round(wrapper.solve(objVec));
for (int i = 0; i < idEdgeMap.size(); i++) {
flowMapOut.put(idEdgeMap.get(i + 1), Math.round(objVec[i]));
}
} else {
try {
sol = Math.round(wrapper.solve(USE_PRESOLVE));
} catch(LpSolveException ex) {
throw new LpSolveException(ex.getMessage() + ". ILP dump: "+dumpFile);
}
}
return sol;
}
private File dumpILP(LpSolveWrapper<?> wrapper) throws LpSolveException, IOException {
outDir.mkdirs();
File outFile = File.createTempFile(MiscUtils.sanitizeFileName(this.problemName), ".lp", outDir);
wrapper.dumpToFile(outFile);
FileWriter fw = null;
try {
fw = new FileWriter(outFile, true);
} catch (IOException e1) {
throw new LpSolveException("Failed to open ILP file: "+e1.getMessage());
}
try {
fw.append("/* Mapping: \n");
for (Entry<T, Integer> e : this.edgeIdMap.entrySet()) {
fw.append(" " + e.getKey() + " -> C" + e.getValue() + "\n");
}
fw.append(this.toString());
fw.append("*/\n");
} catch (IOException e) {
throw new LpSolveException("Failed to write to ILP file: "+e.getMessage());
} finally {
try {
fw.close();
} catch (IOException e) {
throw new LpSolveException("Failed to close ILP file: "+e.getMessage());
}
}
return outFile;
}
@Override
public String toString() {
StringBuffer s = new StringBuffer();
s.append("Max-Cost-Flow problem with cost vector: ");
boolean first = true;
for (Entry<T, Long> e : edgeCost.entrySet()) {
if (first) first = false;
else s.append(" + ");
s.append(e.getValue());
s.append(' ');
s.append(e.getKey());
}
s.append("\nFlow\n");
for (LinearConstraint<T> lc : edgeConstraints) {
s.append(lc);
s.append('\n');
}
return s.toString();
}
/*-------------------------------------------------------------------------------------------
* Why we do not need decision variables:
*
* We used them to say that if at least one execution edge in a set E has a frequency > 0,
* the decision variable should be true, and associated some cost with the decision variable.
*
* But it is not necessary to use decision variables for this. Instead, add two cost variables
* e_f and e_t for each edge e, and set e_f + e_t = e. Next, we will have a constraint on how
* often the e_t edges are executed with respect to some linear expression lub.
* This is modeled by adding a constraint {@code sum(e in E) e_t <= lub}. For decsision variables
* lub is 1. Finally, we used to attribute cost c to the decision variable. This is done by
* setting {@code cost(e_t) = cost(e_f) + cost(decision)}.
*
* Therefore, decision variables where unnecessary for all of the use cases we had. As they
* additionally seem to add unneccessary complexity for the ILP solver, I've removed them.
*
*-------------------------------------------------------------------------------------------*/
private IDProvider<Object> generateMapping() {
this.edgeIdMap = new HashMap<T, Integer>();
this.idEdgeMap = new HashMap<Integer, T>();
int key = 1;
for (T e : edgeSet) {
edgeIdMap.put(e, key);
idEdgeMap.put(key, e);
key += 1;
}
/* create ID provider */
return new IDProvider<Object>() {
/* Note: No closures in java, so edgeIdvIdMap/idEdgeMap have to be instance variables */
public Object fromID(int id) {
return idEdgeMap.get(id);
}
public int getID(Object t) {
return edgeIdMap.get(t);
}
};
}
}