Skip to content

Commit

Permalink
Merge branch 'release/1.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
jkinable committed Feb 24, 2016
2 parents 3833acf + 220553a commit eecca2e
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 25 deletions.
14 changes: 14 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# HISTORY #

Changes to jORLib in each version:


- **version 1.1** (24-Feb-2016):
- Fixed a rare bug which could cause the Branch-and-Price procedure to end prematurely.
- Fixed an issue where seperating cuts was not counted in the computation time of the master problem
-When running Branch-and-Price, you can now also query the objective of the root node without having re-solve
-Fixed a bug in AbstractBranchAndPrice where hasSolution() could return a nullpointer.
-Fixed: the bound on BAP instances solved to optimality was always 0, instead of the optimal solution

- **version 1.0** (April-2015) : Initial public release.

7 changes: 6 additions & 1 deletion jorlib-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.jorlib</groupId>
<artifactId>jorlib</artifactId>
<version>1.0</version>
<version>1.1</version>
</parent>
<artifactId>jorlib-core</artifactId>
<name>JORLib - Core</name>
Expand Down Expand Up @@ -46,6 +46,11 @@
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>cplex</groupId>
<artifactId>cplex</artifactId>
<version>12.6.1</version>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public abstract class AbstractBranchAndPrice<T extends ModelInterface, U extends
/** Stores the objective of the best (integer) solution **/
protected int objectiveIncumbentSolution;
/** List containing the columns corresponding to the best integer solution (empty list when no feasible solution has been found) **/
protected List<U> incumbentSolution =null;
protected List<U> incumbentSolution =new ArrayList<>();
/** Indicator whether the best solution is optimal **/
protected boolean isOptimal=false;

Expand All @@ -89,6 +89,8 @@ public abstract class AbstractBranchAndPrice<T extends ModelInterface, U extends
protected Queue<BAPNode<T,U>> queue;
/** Counter used to provide a unique ID for each node (counter gets incremented each time a new node is created) **/
protected int nodeCounter=0;
/** A reference to the root node in the tree **/
protected BAPNode<T,U> rootNode;

/** Upper bound on the optimal solution **/
protected double upperBoundOnObjective=Double.MAX_VALUE;
Expand Down Expand Up @@ -139,7 +141,7 @@ public AbstractBranchAndPrice(T dataModel,
List<Integer> rootPath=new ArrayList<>();
int nodeID=nodeCounter++;
rootPath.add(nodeID);
BAPNode<T,U> rootNode=new BAPNode<>(nodeID, rootPath, new ArrayList<>(), new ArrayList<>(), 0, Collections.emptyList());
rootNode=new BAPNode<>(nodeID, rootPath, new ArrayList<>(), new ArrayList<>(), 0, Collections.emptyList());
queue.add(rootNode);
graphManipulator=new GraphManipulator(rootNode);

Expand Down Expand Up @@ -198,7 +200,7 @@ public AbstractBranchAndPrice(T dataModel,
* @param initialSolution columns constituting the initial solution
*/
public void warmStart(int objectiveInitialSolution, List<U> initialSolution){
BAPNode<T,U> rootNode=queue.peek();
rootNode=queue.peek();
if(rootNode.nodeID != 0)
throw new RuntimeException("This method can only be invoked at the start of the Branch-and-Price procedure, before runBranchAndPrice is invoked");
rootNode.addInitialColumns(initialSolution);
Expand Down Expand Up @@ -298,6 +300,10 @@ public void runBranchAndPrice(long timeLimit){
//Update statistics
if(queue.isEmpty()){ //Problem solved to optimality
this.isOptimal=true;
if(optimizationSenseMaster == OptimizationSense.MINIMIZE)
this.lowerBoundOnObjective=this.objectiveIncumbentSolution;
else
this.upperBoundOnObjective=this.objectiveIncumbentSolution;
}else{ //Problem NOT solved to optimality
this.isOptimal=false;
if(optimizationSenseMaster == OptimizationSense.MINIMIZE) {
Expand Down Expand Up @@ -325,7 +331,7 @@ public void runBranchAndPrice(long timeLimit){
protected void solveBAPNode(BAPNode<T,U> bapNode, long timeLimit) throws TimeLimitExceededException {
ColGen<T,U,V> cg=null;
try {
cg = new ColGen<>(dataModel, master, pricingProblems, solvers, pricingProblemManager, bapNode.initialColumns, objectiveIncumbentSolution); //Solve the node
cg = new ColGen<>(dataModel, master, pricingProblems, solvers, pricingProblemManager, bapNode.initialColumns, objectiveIncumbentSolution, bapNode.getBound()); //Solve the node
for(CGListener listener : columnGenerationEventListeners) cg.addCGEventListener(listener);
cg.solve(timeLimit);
}finally{
Expand Down Expand Up @@ -356,6 +362,14 @@ protected int getUniqueNodeID(){
public int getObjective(){
return this.objectiveIncumbentSolution;
}

/**
* Returns the strongest available bound after the root node has been solved. Whenever the root node was solved to optimality, the value returned equals the objective of (optimal solution) of the root node. Whenever the
* node is not solved to optimality, e.g. due to a time limit,the strongest lower bound (minimization problem) or strongest upper bound (maximization problem) is returned. Not that this function
* is equivalent to solving the master problem relaxation through column generation.
* @return
*/
public double getBoundRootNode(){ return rootNode.getBound();}

/**
* Return whether a solution has been found
Expand Down Expand Up @@ -455,8 +469,8 @@ public List<U> getSolution(){
* @return true if the node can be pruned
*/
protected boolean nodeCanBePruned(BAPNode<T,U> node){
return (optimizationSenseMaster == OptimizationSense.MINIMIZE && Math.ceil(node.bound) >= upperBoundOnObjective ||
optimizationSenseMaster == OptimizationSense.MAXIMIZE && Math.floor(node.bound) <= lowerBoundOnObjective);
return (optimizationSenseMaster == OptimizationSense.MINIMIZE && Math.ceil(node.bound-config.PRECISION) >= upperBoundOnObjective ||
optimizationSenseMaster == OptimizationSense.MAXIMIZE && Math.floor(node.bound+config.PRECISION) <= lowerBoundOnObjective);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,22 @@ public class ColGen<T extends ModelInterface, U extends AbstractColumn<T, V>, V
* @param solvers pricing problem solvers
* @param initSolution initial solution
* @param cutoffValue cutoff Value. If the master is a minimization problem, the Colgen procedure is terminated if {@code ceil(boundOnMasterObjective) >= cutoffValue}. If the master is a maximization problem, the Colgen procedure is terminated if {@code floor(boundOnMasterObjective) <= cutoffValue}.
* @param boundOnMasterObjective Bound on the best attainable objective value from the master problem. Assuming that the master is a minimization problem, the Colgen procedure is terminated if {@code ceil(boundOnMasterObjective) >= cutoffValue}.
*/
public ColGen(T dataModel,
AbstractMaster<T, U, V, ? extends MasterData> master,
List<V> pricingProblems,
List<Class<? extends AbstractPricingProblemSolver<T, U, V>>> solvers,
List<U> initSolution,
int cutoffValue){
int cutoffValue,
double boundOnMasterObjective){
this.dataModel=dataModel;
this.master=master;
optimizationSenseMaster=master.getOptimizationSense();
this.pricingProblems=pricingProblems;
this.solvers=solvers;
this.cutoffValue = cutoffValue;
this.boundOnMasterObjective=boundOnMasterObjective;
master.addColumns(initSolution);

//Generate the pricing problem instances
Expand All @@ -143,14 +146,16 @@ public ColGen(T dataModel,
* @param solvers pricing problem solvers
* @param initSolution initial solution
* @param cutoffValue cutoff Value. If the master is a minimization problem, the Colgen procedure is terminated if {@code ceil(boundOnMasterObjective) >= cutoffValue}. If the master is a maximization problem, the Colgen procedure is terminated if {@code floor(boundOnMasterObjective) <= cutoffValue}.
* @param boundOnMasterObjective Bound on the best attainable objective value from the master problem. Assuming that the master is a minimization problem, the Colgen procedure is terminated if {@code ceil(boundOnMasterObjective) >= cutoffValue}.
*/
public ColGen(T dataModel,
AbstractMaster<T, U, V, ? extends MasterData> master,
V pricingProblem,
List<Class<? extends AbstractPricingProblemSolver<T, U, V>>> solvers,
List<U> initSolution,
int cutoffValue){
this(dataModel, master, Collections.singletonList(pricingProblem), solvers, initSolution, cutoffValue);
int cutoffValue,
double boundOnMasterObjective){
this(dataModel, master, Collections.singletonList(pricingProblem), solvers, initSolution, cutoffValue, boundOnMasterObjective);
}

/**
Expand All @@ -162,14 +167,16 @@ public ColGen(T dataModel,
* @param pricingProblemManager pricing problem manager
* @param initSolution initial solution
* @param cutoffValue cutoff Value. If the master is a minimization problem, the Colgen procedure is terminated if {@code ceil(boundOnMasterObjective) >= cutoffValue}. If the master is a maximization problem, the Colgen procedure is terminated if {@code floor(boundOnMasterObjective) <= cutoffValue}.
* @param boundOnMasterObjective Bound on the best attainable objective value from the master problem. Assuming that the master is a minimization problem, the Colgen procedure is terminated if {@code ceil(boundOnMasterObjective) >= cutoffValue}.
*/
public ColGen(T dataModel,
AbstractMaster<T, U, V, ? extends MasterData> master,
List<V> pricingProblems,
List<Class<? extends AbstractPricingProblemSolver<T, U, V>>> solvers,
PricingProblemManager<T,U, V> pricingProblemManager,
List<U> initSolution,
int cutoffValue){
int cutoffValue,
double boundOnMasterObjective){
this.dataModel=dataModel;
this.master=master;
optimizationSenseMaster=master.getOptimizationSense();
Expand All @@ -178,6 +185,7 @@ public ColGen(T dataModel,
this.pricingProblemManager=pricingProblemManager;
master.addColumns(initSolution);
this.cutoffValue = cutoffValue;
this.boundOnMasterObjective=boundOnMasterObjective;

//Create a new notifier
notifier=new CGNotifier();
Expand Down Expand Up @@ -218,9 +226,14 @@ public void solve(long timeLimit) throws TimeLimitExceededException{
//We can stop when the optimality gap is closed. We still need to check for violated initialInequalities though.
if(Math.abs(objectiveMasterProblem - boundOnMasterObjective)<config.PRECISION){
//Check whether there are inequalities. Otherwise potentially an infeasible integer solution (e.g. TSP solution with subtours) might be returned.
if(config.CUTSENABLED && master.hasNewCuts()){
hasNewCuts=true;
continue;
if(config.CUTSENABLED){
long time=System.currentTimeMillis();
hasNewCuts=master.hasNewCuts();
masterSolveTime+=(System.currentTimeMillis()-time); //Generating inequalities is considered part of the master problem
if(hasNewCuts)
continue;
else
break;
}else
break;
}
Expand Down Expand Up @@ -412,9 +425,9 @@ public List<AbstractInequality> getCuts(){
*/
protected boolean boundOnMasterExceedsCutoffValue(){
if(optimizationSenseMaster == OptimizationSense.MINIMIZE)
return Math.ceil(boundOnMasterObjective) >= cutoffValue;
return Math.ceil(boundOnMasterObjective-config.PRECISION) >= cutoffValue;
else
return Math.floor(boundOnMasterObjective) <= cutoffValue;
return Math.floor(boundOnMasterObjective+config.PRECISION) <= cutoffValue;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public void startPricing(StartPricingEvent startPricing) {

@Override
public void finishPricing(FinishPricingEvent finishPricingEvent) {
logger.debug("Finished pricing ({} initialColumns generated) -> CG objectiveMasterProblem: {}, CG bound: {}, CG cutoff: {}", new Object[]{finishPricingEvent.columns.size(), finishPricingEvent.objective, finishPricingEvent.boundOnMasterObjective, finishPricingEvent.cutoffValue});
logger.debug("Finished pricing ({} columns generated) -> CG objectiveMasterProblem: {}, CG bound: {}, CG cutoff: {}", new Object[]{finishPricingEvent.columns.size(), finishPricingEvent.objective, finishPricingEvent.boundOnMasterObjective, finishPricingEvent.cutoffValue});
for(AbstractColumn<?, ?> column : finishPricingEvent.columns){
logger.debug(column.toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,8 @@ public static void readFromFile(Properties properties){
throw new RuntimeException("You can only provide a configuration once");
instance=new Configuration(properties);
}





//---------------- CONFIGURATION --------------------------

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
*/
package org.jorlib.frameworks;

import org.jorlib.frameworks.columnGeneration.tsp.BAPTSPTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

Expand All @@ -37,7 +38,7 @@
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
//BAPTSPTest.class
BAPTSPTest.class
})

public final class AllFrameworksTests {
Expand Down
2 changes: 1 addition & 1 deletion jorlib-demo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.jorlib</groupId>
<artifactId>jorlib</artifactId>
<version>1.0</version>
<version>1.1</version>
</parent>
<artifactId>jorlib-demo</artifactId>
<name>JORLib - Demo</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ public CuttingStockSolver(CuttingStock dataModel){

//Create a set of initial initialColumns.
List<CuttingPattern> initSolution=this.getInitialSolution(pricingProblem);
double lowerBound=0; //Lower bound on solution

//Create a column generation instance
ColGen<CuttingStock, CuttingPattern, PricingProblem> cg=new ColGen<>(dataModel, master, pricingProblem, solvers, initSolution, upperBound);
ColGen<CuttingStock, CuttingPattern, PricingProblem> cg=new ColGen<>(dataModel, master, pricingProblem, solvers, initSolution, upperBound, lowerBound);

//OPTIONAL: add a debugger
SimpleDebugger debugger=new SimpleDebugger(cg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,10 @@ public TSPCGSolver(TSP tsp){
TSPLibTour initTour=TSPLibTour.createCanonicalTour(tsp.N); //Feasible solution
int tourLength=tsp.getTourLength(initTour); //Upper bound (Stronger is better)
List<Matching> initSolution=this.convertTourToColumns(initTour, pricingProblems); //Create a set of initial initialColumns.
double lowerBound=0; //Lower bound on solution

//Create a column generation instance
ColGen<TSP, Matching, PricingProblemByColor> cg=new ColGen<>(tsp, master, pricingProblems, solvers, initSolution, tourLength);
ColGen<TSP, Matching, PricingProblemByColor> cg=new ColGen<>(tsp, master, pricingProblems, solvers, initSolution, tourLength, lowerBound);

//OPTIONAL: add a debugger
SimpleDebugger debugger=new SimpleDebugger(cg, cutHandler);
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<artifactId>jorlib</artifactId>
<packaging>pom</packaging>
<name>JORLib - Parent</name>
<version>1.0</version>
<version>1.1</version>
<description>Java Operations Research Library.</description>
<url>https://github.com/jkinable/jorlib</url>
<licenses>
Expand Down

0 comments on commit eecca2e

Please sign in to comment.