From 11c7bca9bceb923b0ecbce918c1ee39de25910bb Mon Sep 17 00:00:00 2001 From: dgodard Date: Sat, 11 Mar 2017 11:48:15 +0100 Subject: [PATCH 1/9] Change release version to 1.0.2-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 714d382..08825a4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ group 'com.decisionbrain' -version '1.0.1' +version '1.0.2-SNAPSHOT' apply plugin: 'java' apply plugin: 'scala' From 345e26aa827f33fee93f35906c97d7bc2535660a Mon Sep 17 00:00:00 2001 From: dgodard Date: Sat, 11 Mar 2017 22:05:48 +0100 Subject: [PATCH 2/9] Add example SchedTime --- .../decisionbrain/cplex/cp/SchedTime.scala | 129 ++++++++++++++++++ .../com/decisionbrain/cplex/cp/CpModel.scala | 94 ++++++++++++- .../cplex/cp/SchedTimeTest.scala | 40 ++++++ 3 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 src/examples/scala/com/decisionbrain/cplex/cp/SchedTime.scala create mode 100644 src/test/scala/com/decisionbrain/cplex/cp/SchedTimeTest.scala diff --git a/src/examples/scala/com/decisionbrain/cplex/cp/SchedTime.scala b/src/examples/scala/com/decisionbrain/cplex/cp/SchedTime.scala new file mode 100644 index 0000000..88c323d --- /dev/null +++ b/src/examples/scala/com/decisionbrain/cplex/cp/SchedTime.scala @@ -0,0 +1,129 @@ +/* + * Source file provided under Apache License, Version 2.0, January 2004, + * http://www.apache.org/licenses/ + * (c) Copyright DecisionBrain SAS 2016,2017 + */ + +package com.decisionbrain.cplex.cp + +import com.decisionbrain.cplex.cp.CpModel._ +import ilog.concert.IloNumToNumSegmentFunction +import ilog.cp.IloCP + +/** + * This is a problem of building a house. The masonry, roofing, painting, etc. must be scheduled. Some tasks must + * necessarily take place before others and these requirements are expressed through precedence constraints. + * + * Moreover, there are earliness and tardiness costs associated with some tasks. The objective is to minimize these + * costs. + */ +object SchedTime { + + val nbTasks = 10 + + val tasks = List( + ("masonry", 35), // pair of task name and duration + ("carpentry", 15), + ("plumbing", 40), + ("ceiling", 15), + ("roofing", 5), + ("painting", 10), + ("windows", 5), + ("facade", 10), + ("garden", 5), + ("moving", 5) + ) + + def computeEarlinessCostExp(task: IntervalVar, rd: Double, weight: Double, useFunction: Boolean): NumExpr = { + if (useFunction) { + val arrX = Array(rd) + val arrV = Array(-weight, 0.0) + val f: NumToNumSegmentFunction = model.piecewiseLinearFunction(arrX, arrV, rd, 0.0) + return startEval(task,f) + } else { + return weight * max(.0, rd - startOf(task)) + } + } + + def computeTardinessCostExp(task: IntervalVar, dd: Double, weight: Double, useFunction: Boolean): NumExpr = { + if (useFunction) { + val arrX = Array(dd) + val arrV = Array(0.0, weight) + val f = model.piecewiseLinearFunction(arrX, arrV, dd, 0.0) + return endEval(task,f) + } else { + return weight * max(.0, endOf(task) - dd) + } + } + + implicit var model: CpModel = _ + + var taskVars: Map[String, IntervalVar] = _ + + def build(): CpModel = { + + model = CpModel("SchedCumul") + + taskVars = (for (task <- tasks; (tname, tduration) = task) + yield (tname , model.intervalVar(sizeMin = tduration, sizeMax = tduration, name = tname))).toMap + + // precedence constraints + model.add(taskVars("masonry") < taskVars("carpentry")) + model.add(taskVars("masonry") < taskVars("plumbing")) + model.add(taskVars("masonry") < taskVars("ceiling")) + model.add(taskVars("carpentry") < taskVars("roofing")) + model.add(taskVars("ceiling") < taskVars("painting")) + model.add(taskVars("roofing") < taskVars("windows")) + model.add(taskVars("roofing") < taskVars("facade")) + model.add(taskVars("plumbing") < taskVars("facade")) + model.add(taskVars("roofing") < taskVars("garden")) + model.add(taskVars("plumbing") < taskVars("garden")) + model.add(taskVars("windows") < taskVars("moving")) + model.add(taskVars("facade") < taskVars("moving")) + model.add(taskVars("garden") < taskVars("moving")) + model.add(taskVars("painting") < taskVars("moving")) + + val useFunction = true + val costExpr = (computeEarlinessCostExp(taskVars("masonry"), 25, 200.0, useFunction) + + computeEarlinessCostExp(taskVars("carpentry"), 75, 300.0, useFunction) + + computeEarlinessCostExp(taskVars("ceiling"), 75, 100.0, useFunction) + + computeTardinessCostExp(taskVars("moving"), 100, 400.0, useFunction)) + + + model.add(minimize(costExpr)) + + model + } + + def solve(): Boolean = { + + println(s"Solving model $model....") + +// model.exportModel("SchedTime.cpo") + + // val status = model.solve(timeLimit=20, logPeriod=3000) + val status = model.solve() + + if (status) { + println(s"Solution status: $status") + println("Solution with objective " + model.getObjectiveValue()) + for ((name, task) <- taskVars) { + println(model.getDomain(task)) + } + } + + true + } + + def run(): Boolean = { + val model = build() + val status = solve() + model.end() + status + } + + def main(args: Array[String]): Unit = { + run() + } + +} diff --git a/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala b/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala index 3dae5a4..32e849f 100644 --- a/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala +++ b/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala @@ -361,6 +361,26 @@ class CpModel(name: String=null) { NumExpr(cp.max(exprs.toIloArray))(implicitly(this)) } + /** + * Returns the maximum of a numeric expressions. + * + * @param exprs is an array of numeric variables + * @return a numeric expression that represents the maximum of the numeric expressions + */ + def max(exprs: Array[NumExpr]) : NumExpr = { + NumExpr(cp.max(exprs.map(e => e.getIloNumExpr())))(implicitly(this)) + } + + /** + * Returns the maximum of a numeric expressions. + * + * @param exprs is a variable number of numeric variables + * @return a numeric expression that represents the maximum of the numeric expressions + */ + def max(exprs: NumExpr*) : NumExpr = { + NumExpr(cp.max(exprs.map(e => e.getIloNumExpr()).toArray))(implicitly(this)) + } + /** * Returns the minimum of numeric expressions. * @@ -368,7 +388,27 @@ class CpModel(name: String=null) { * @return a numeric expression that represents the minimum of the numeric expressions */ def min(exprs: NumExprArray) : NumExpr = { - NumExpr(cp.max(exprs.toIloArray))(implicitly(this)) + NumExpr(cp.min(exprs.toIloArray))(implicitly(this)) + } + + /** + * Returns the minimum of a numeric expressions. + * + * @param exprs is an array of numeric variables + * @return a numeric expression that represents the minimum of the numeric expressions + */ + def min(exprs: Array[NumExpr]) : NumExpr = { + NumExpr(cp.min(exprs.map(e => e.getIloNumExpr())))(implicitly(this)) + } + + /** + * Returns the minimum of a numeric expressions. + * + * @param exprs is a variable number of numeric variables + * @return a numeric expression that represents the minimum of the numeric expressions + */ + def min(exprs: NumExpr*) : NumExpr = { + NumExpr(cp.min(exprs.map(e => e.getIloNumExpr()).toArray))(implicitly(this)) } /** @@ -1498,6 +1538,24 @@ class CpModel(name: String=null) { def alwaysIn(f: CumulFunctionExpr, start: Int, end: Int, vmin: Int, vmax: Int): Constraint = Constraint(cp.alwaysIn(f.getIloCumulFunctionExpr(), start, end, vmin, vmax))(implicitly(this)) + /** + * Creates and returns a piecewise linear function defined everywhere. The array point contains the n breakpoints of + * the function such that point [i-1] <= point [i] for i = 1, . . ., n-1. The array slope contains the n+1 slopes of + * the n+1 segments of the function. The values a and fa must be coordinates of a point such that fa = f(a). + * + * When point[i-1] = point[i], there is a step at the x-coordinate point[i-1] and its height is slope[i] to reach + * the y-coordinate of point[i]. + * + * @param point is the array of breakpoints + * @param slope is the array of slopes + * @param a is x-coordinate + * @param fa the y-coordinate + * @return a piecewise linear function + */ + def piecewiseLinearFunction(point: Array[Double], slope: Array[Double], a: Double, fa: Double): NumToNumSegmentFunction = { + cp.piecewiseLinearFunction(point, slope, a, fa) + } + /** * Add an addable object in the model. * @@ -1903,6 +1961,8 @@ object CpModel { type TransitionDistance = IloTransitionDistance type Solution = IloSolution type MultiCriterionExpr = IloMultiCriterionExpr + type NumToNumStepFunction = IloNumToNumStepFunction + type NumToNumSegmentFunction = IloNumToNumSegmentFunction /** * Create and return a new mathematical programming model. @@ -1952,6 +2012,22 @@ object CpModel { */ def max(exprs: NumExprArray)(implicit model: CpModel): NumExpr = model.max(exprs) + /** + * Returns the maximum of a numeric expressions. + * + * @param exprs is an array of numeric variables + * @return a numeric expression that represents the maximum of the numeric expressions + */ + def max(exprs: Array[NumExpr])(implicit model: CpModel): NumExpr = model.max(exprs) + + /** + * Returns the maximum of a numeric expressions. + * + * @param exprs is a variable number of numeric variables + * @return a numeric expression that represents the maximum of the numeric expressions + */ + def max(exprs: NumExpr*)(implicit model: CpModel): NumExpr = model.max(exprs) + /** * Returns the minimum of a set of numeric expressions. * @@ -1960,6 +2036,22 @@ object CpModel { */ def min(exprs: NumExprArray)(implicit model: CpModel): NumExpr = model.min(exprs) + /** + * Returns the minimum of a numeric expressions. + * + * @param exprs is an array of numeric variables + * @return a numeric expression that represents the minimum of the numeric expressions + */ + def min(exprs: Array[NumExpr])(implicit model: CpModel): NumExpr = model.min(exprs) + + /** + * Returns the minimum of a numeric expressions. + * + * @param exprs is a variable number of numeric variables + * @return a numeric expression that represents the minimum of the numeric expressions + */ + def min(exprs: NumExpr*)(implicit model: CpModel): NumExpr = model.min(exprs) + /** * Creates a new constrained integer expression equal to the number of variables that are equals to the * value v. diff --git a/src/test/scala/com/decisionbrain/cplex/cp/SchedTimeTest.scala b/src/test/scala/com/decisionbrain/cplex/cp/SchedTimeTest.scala new file mode 100644 index 0000000..0ebc5d7 --- /dev/null +++ b/src/test/scala/com/decisionbrain/cplex/cp/SchedTimeTest.scala @@ -0,0 +1,40 @@ +/* + * Source file provided under Apache License, Version 2.0, January 2004, + * http://www.apache.org/licenses/ + * (c) Copyright DecisionBrain SAS 2016,2017 + */ + +package com.decisionbrain.cplex.cp + +import ilog.cp.IloCP +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner +import org.scalatest.{FunSuite, Matchers} + +@RunWith(classOf[JUnitRunner]) +class SchedTimeTest extends FunSuite with Matchers { + + val epsilon = 1e-6 + + test("SchedTime") { + val model = SchedTime.build() + + val status = model.solve() + + status should equal(true) + model.getObjectiveValue() should equal(5000.0 +- epsilon) + + model.getStart(SchedTime.taskVars("masonry")) should equal (20) + model.getStart(SchedTime.taskVars("carpentry")) should equal (75) + model.getStart(SchedTime.taskVars("plumbing")) should equal (55) + model.getStart(SchedTime.taskVars("ceiling")) should equal (75) + model.getStart(SchedTime.taskVars("roofing")) should equal (90) + model.getStart(SchedTime.taskVars("painting")) should equal (90) + model.getStart(SchedTime.taskVars("windows")) should equal (95) + model.getStart(SchedTime.taskVars("facade")) should equal (95) + model.getStart(SchedTime.taskVars("garden")) should equal (95) + model.getStart(SchedTime.taskVars("moving")) should equal (105) + + model.end() + } +} From 3ebef534bb751e05e53a23fc5706146e6db8df32 Mon Sep 17 00:00:00 2001 From: dgodard Date: Mon, 13 Mar 2017 23:37:17 +0100 Subject: [PATCH 3/9] Add constraint forbidStart, forbidEnd, forbidExtent. Add class NumToNumStepFunction. Add example SchedCalendar. --- README.md | 4 +- build.gradle | 3 - .../cplex/cp/SchedCalendar.scala | 172 ++++++++++++++++++ .../com/decisionbrain/cplex/cp/CpModel.scala | 120 +++++++++++- .../decisionbrain/cplex/cp/IntervalVar.scala | 4 +- .../cplex/cp/NumToNumStepFunction.scala | 149 +++++++++++++++ .../cplex/cp/SchedCalendarTest.scala | 28 +++ 7 files changed, 470 insertions(+), 10 deletions(-) create mode 100644 src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala create mode 100644 src/main/scala/com/decisionbrain/cplex/cp/NumToNumStepFunction.scala create mode 100644 src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala diff --git a/README.md b/README.md index 819fcfb..921a734 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ To run the tests, do: $ gradle test ``` -Reports are generated in directory `\build\reports\tests` +Reports are generated in directory `build/reports/tests`. To generate the scala docs, do: @@ -47,4 +47,4 @@ To generate the scala docs, do: $ gradle scaladoc ``` -The scala documentation is generated in directory `build/docs/scaladoc` \ No newline at end of file +The scala documentation is generated in directory `build/docs/scaladoc`. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 08825a4..da12343 100644 --- a/build.gradle +++ b/build.gradle @@ -24,9 +24,6 @@ else if (file(mavenFilename).exists()) { println("Importing gradle file $filename") apply from: filename } -else { - println("Warning: File $mavenFilename not found") -} // diff --git a/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala b/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala new file mode 100644 index 0000000..8a96717 --- /dev/null +++ b/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala @@ -0,0 +1,172 @@ +/* + * Source file provided under Apache License, Version 2.0, January 2004, + * http://www.apache.org/licenses/ + * (c) Copyright DecisionBrain SAS 2016,2017 + */ + +package com.decisionbrain.cplex.cp + +import com.decisionbrain.cplex.cp.CpModel._ +import ilog.cp.IloCP + +/** + * This is a problem of building five houses. The masonry, roofing, painting, etc. must be scheduled. Some tasks must + * necessarily take place before others and these requirements are expressed through precedence constraints. + * + * There are two workers and each task requires a specific worker. The worker has a calendar of days off that must be + * taken into account. The objective is to minimize the overall completion date. + */ +object SchedCalendar { + + val nbWorkers = 3 + val nbHouses = 5 + val nbTasks = 10 + + val tasks = List( + ("masonry", 35), // pair of task name and duration + ("carpentry", 15), + ("plumbing", 40), + ("ceiling", 15), + ("roofing", 5), + ("painting", 10), + ("windows", 5), + ("facade", 10), + ("garden", 5), + ("moving", 5) + ) + + + implicit var model: CpModel = _ + + var allTaskVars: List[IntervalVar] = _ + var endVars: List[IntExpr] = _ + var joeTaskVars: List[IntervalVar] = _ + var jimTaskVars: List[IntervalVar] = _ + + def makeHouse(id: Int): (IntExpr, Iterable[IntervalVar], Iterable[IntervalVar], Iterable[IntervalVar]) = { + + val taskVars: Map[String, IntervalVar] = (for (task <- tasks; (tname, tduration) = task) + yield (tname , model.intervalVar(sizeMin = tduration, sizeMax = tduration, name = "H" + id + "-" + tname)))(collection.breakOut) + + // The lines below are equivalent + model.add(taskVars("masonry") < taskVars("carpentry")) + model.add(taskVars("masonry") < taskVars("plumbing")) + model.add(taskVars("masonry") < taskVars("ceiling")) + model.add(taskVars("carpentry") < taskVars("roofing")) + model.add(taskVars("ceiling") < taskVars("painting")) + model.add(taskVars("roofing") < taskVars("windows")) + model.add(taskVars("roofing") < taskVars("facade")) + model.add(taskVars("plumbing") < taskVars("facade")) + model.add(taskVars("roofing") < taskVars("garden")) + model.add(taskVars("plumbing") < taskVars("garden")) + model.add(taskVars("windows") < taskVars("moving")) + model.add(taskVars("facade") < taskVars("moving")) + model.add(taskVars("garden") < taskVars("moving")) + model.add(taskVars("painting") < taskVars("moving")) + + val joeTaskVars = List(taskVars("masonry") + , taskVars("carpentry") + , taskVars("roofing") + , taskVars("facade") + , taskVars("garden") + ) + + val jimTaskVars = taskVars.values.filterNot(v => joeTaskVars.contains(v)) + + + (model.endOf(taskVars("moving")), taskVars.values, joeTaskVars, jimTaskVars) + } + + def build(): CpModel = { + + model = CpModel("SchedCumul") + + val results = for (h <- 0 until nbHouses) yield makeHouse(h) + + endVars = results.map(_._1).toList + allTaskVars = results.flatMap(_._2).toList + joeTaskVars = results.flatMap(_._3).toList + jimTaskVars = results.flatMap(_._4).toList + + model.add(noOverlap(joeTaskVars)) + model.add(noOverlap(jimTaskVars)) + + + val joeCalendar = model.numToNumStepFunction + joeCalendar.setValue(0, 2 * 365, 100) + val jimCalendar = model.numToNumStepFunction + jimCalendar.setValue(0, 2 * 365, 100) + + /* WEEK ENDS. */ + for (w <- 0 until 2 * 52) { + joeCalendar.setValue(5 + (7 * w), 7 + (7 * w), 0) + jimCalendar.setValue(5 + (7 * w), 7 + (7 * w), 0) + } + + /* HOLIDAYS. */ + joeCalendar.setValue(5, 12, 0) + joeCalendar.setValue(124, 131, 0) + joeCalendar.setValue(215, 236, 0) + joeCalendar.setValue(369, 376, 0) + joeCalendar.setValue(495, 502, 0) + joeCalendar.setValue(579, 600, 0) + jimCalendar.setValue(26, 40, 0) + jimCalendar.setValue(201, 225, 0) + jimCalendar.setValue(306, 313, 0) + jimCalendar.setValue(397, 411, 0) + jimCalendar.setValue(565, 579, 0) + +// println("Joe: " + joeCalendar.toString()) +// println("Jim: " + jimCalendar) + + for (i <- joeTaskVars.indices) { + joeTaskVars(i).setIntensity(joeCalendar) + model.add(forbidStart(joeTaskVars(i), joeCalendar)) + model.add(forbidEnd(joeTaskVars(i), joeCalendar)) + } + for (i <- jimTaskVars.indices) { + jimTaskVars(i).setIntensity(jimCalendar) + model.add(forbidStart(jimTaskVars(i), jimCalendar)) + model.add(forbidEnd(jimTaskVars(i), jimCalendar)) + } + + + model.add(minimize(max(endVars))) + + model + } + + def solve(): Boolean = { + + println(s"Solving model $model....") + +// model.exportModel("SchedCalendar.cpo") + + model.cp.setParameter(IloCP.IntParam.FailLimit, 10000) + + // val status = model.solve(timeLimit=20, logPeriod=3000) + val status = model.solve() + + if (status) { + println(s"Solution status: $status") + println("Solution with objective " + model.getObjectiveValue()) + for (i <- allTaskVars.indices) { + println(model.getDomain(allTaskVars(i))) + } + } + + true + } + + def run(): Boolean = { + val model = build() + val status = solve() + model.end() + status + } + + def main(args: Array[String]): Unit = { + run() + } + +} diff --git a/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala b/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala index 32e849f..d9151ff 100644 --- a/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala +++ b/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala @@ -307,9 +307,9 @@ class CpModel(name: String=null) { def intervalVar(sizeMin: Int, sizeMax: Int, optional: Boolean, - intensity: IloNumToNumStepFunction, + intensity: NumToNumStepFunction, granularity: Int): IntervalVar = - IntervalVar(cp.intervalVar(sizeMin, sizeMax, optional, intensity, granularity))(implicitly(this)) + IntervalVar(cp.intervalVar(sizeMin, sizeMax, optional, intensity.getIloNumToNumStepFunction(), granularity))(implicitly(this)) /** * Return the sum of numeric expressions. @@ -1030,6 +1030,57 @@ class CpModel(name: String=null) { def sizeEval(a: IntervalVar, f: IloNumToNumSegmentFunction, absVal: Double=.0): NumExpr = NumExpr(cp.sizeEval(a.getIloIntervalVar(), f, absVal))(implicitly(this)) + /** + * This function returns a constraint that states that whenever interval variable a is present, it cannot start at a + * value t such that f(t)=0. + * + * Typically, this constraint can be used in combination with an intensity function to state that the interval + * variable cannot start at a point where its intensity function is null. + * + * Note: This constraint cannot be used in a logical constraint. + * + * @param v is the interval variable + * @param f is the step function + * @param model is the constraint programming model + * @return a new forbid start constraint + */ + def forbidStart(v: IntervalVar, f: NumToNumStepFunction)(implicit model: CpModel): Constraint = + Constraint(cp.forbidStart(v.getIloIntervalVar(), f.getIloNumToNumStepFunction())) + + /** + * This function returns a constraint that states that whenever interval variable a is present, it cannot end at a + * value t such that f(t)=0. + * + * Typically, this constraint can be used in combination with an intensity function to state that the interval + * variable cannot end at a point where its intensity function is null. + * + * Note: This constraint cannot be used in a logical constraint. + * + * @param v is the interval variable + * @param f is the step function + * @param model is the constraint programming model + * @return a new forbid end constraint + */ + def forbidEnd(v: IntervalVar, f: NumToNumStepFunction)(implicit model: CpModel): Constraint = + Constraint(cp.forbidEnd(v.getIloIntervalVar(), f.getIloNumToNumStepFunction())) + + /** + * This function returns a constraint that states that whenever interval variable a is present, it cannot contain a + * value t such that f(t)=0. + * + * Typically, this constraint can be used in combination with an intensity function to state that the interval + * variable cannot overlap intervals where its intensity function is null. + * + * Note: This constraint cannot be used in a logical constraint. + * + * @param v is the interval variable + * @param f is the step function + * @param model is the constraint programming model + * @return a new forbid extent constraint + */ + def forbidExtent(v: IntervalVar, f: NumToNumStepFunction)(implicit model: CpModel): Constraint = + Constraint(cp.forbidExtent(v.getIloIntervalVar(), f.getIloNumToNumStepFunction())) + /** * This method creates a no-overlap constraint on the set of interval variables defined by array a. * Note: This constraint cannot be used in a logical constraint. @@ -1538,6 +1589,18 @@ class CpModel(name: String=null) { def alwaysIn(f: CumulFunctionExpr, start: Int, end: Int, vmin: Int, vmax: Int): Constraint = Constraint(cp.alwaysIn(f.getIloCumulFunctionExpr(), start, end, vmin, vmax))(implicitly(this)) + /** + * This method creates a step function defined everywhere with value 0. + * + * @return a step function + */ + def numToNumStepFunction(): NumToNumStepFunction = + NumToNumStepFunction(cp.numToNumStepFunction())(implicitly(this)) + + + def numToNumStepFunctionCursor(f: NumToNumSegmentFunction, x: Double = -IloCP.Infinity): NumToNumStepFunctionCursor = + cp.numToNumSegmentFunctionCursor(f, x) + /** * Creates and returns a piecewise linear function defined everywhere. The array point contains the n breakpoints of * the function such that point [i-1] <= point [i] for i = 1, . . ., n-1. The array slope contains the n+1 slopes of @@ -1961,8 +2024,8 @@ object CpModel { type TransitionDistance = IloTransitionDistance type Solution = IloSolution type MultiCriterionExpr = IloMultiCriterionExpr - type NumToNumStepFunction = IloNumToNumStepFunction type NumToNumSegmentFunction = IloNumToNumSegmentFunction + type NumToNumStepFunctionCursor = IloNumToNumSegmentFunctionCursor /** * Create and return a new mathematical programming model. @@ -2630,6 +2693,57 @@ object CpModel { def sizeEval(a: IntervalVar, f: IloNumToNumSegmentFunction, absVal: Double=.0)(implicit model: CpModel): NumExpr = model.sizeEval(a, f, absVal) + /** + * This function returns a constraint that states that whenever interval variable a is present, it cannot start at a + * value t such that f(t)=0. + * + * Typically, this constraint can be used in combination with an intensity function to state that the interval + * variable cannot start at a point where its intensity function is null. + * + * Note: This constraint cannot be used in a logical constraint. + * + * @param v is the interval variable + * @param f is the step function + * @param model is the constraint programming model + * @return a new forbid start constraint + */ + def forbidStart(v: IntervalVar, f: NumToNumStepFunction)(implicit model: CpModel): Constraint = + model.forbidStart(v, f) + + /** + * This function returns a constraint that states that whenever interval variable a is present, it cannot end at a + * value t such that f(t)=0. + * + * Typically, this constraint can be used in combination with an intensity function to state that the interval + * variable cannot end at a point where its intensity function is null. + * + * Note: This constraint cannot be used in a logical constraint. + * + * @param v is the interval variable + * @param f is the step function + * @param model is the constraint programming model + * @return a new forbid end constraint + */ + def forbidEnd(v: IntervalVar, f: NumToNumStepFunction)(implicit model: CpModel): Constraint = + model.forbidEnd(v, f) + + /** + * This function returns a constraint that states that whenever interval variable a is present, it cannot contain a + * value t such that f(t)=0. + * + * Typically, this constraint can be used in combination with an intensity function to state that the interval + * variable cannot overlap intervals where its intensity function is null. + * + * Note: This constraint cannot be used in a logical constraint. + * + * @param v is the interval variable + * @param f is the step function + * @param model is the constraint programming model + * @return a new forbid extent constraint + */ + def forbidExtent(v: IntervalVar, f: NumToNumStepFunction)(implicit model: CpModel): Constraint = + model.forbidExtent(v, f) + /** * This method creates a no-overlap constraint on the set of interval variables defined by array a. * Note: This constraint cannot be used in a logical constraint. diff --git a/src/main/scala/com/decisionbrain/cplex/cp/IntervalVar.scala b/src/main/scala/com/decisionbrain/cplex/cp/IntervalVar.scala index a1e6c37..623dca6 100644 --- a/src/main/scala/com/decisionbrain/cplex/cp/IntervalVar.scala +++ b/src/main/scala/com/decisionbrain/cplex/cp/IntervalVar.scala @@ -206,8 +206,8 @@ class IntervalVar(v: IloIntervalVar)(implicit model: CpModel) extends Addable { * @param intensity is the intensity function * @param granularity is the granularity of the intensity function */ - def setIntensity(intensity: IloNumToNumStepFunction, granularity: Int = 100) = - v.setIntensity(intensity, granularity) + def setIntensity(intensity: NumToNumStepFunction, granularity: Int = 100) = + v.setIntensity(intensity.getIloNumToNumStepFunction(), granularity) /** * diff --git a/src/main/scala/com/decisionbrain/cplex/cp/NumToNumStepFunction.scala b/src/main/scala/com/decisionbrain/cplex/cp/NumToNumStepFunction.scala new file mode 100644 index 0000000..c509576 --- /dev/null +++ b/src/main/scala/com/decisionbrain/cplex/cp/NumToNumStepFunction.scala @@ -0,0 +1,149 @@ +/* + * Source file provided under Apache License, Version 2.0, January 2004, + * http://www.apache.org/licenses/ + * (c) Copyright DecisionBrain SAS 2016,2017 + */ + +package com.decisionbrain.cplex.cp + +import com.decisionbrain.cplex.cp.CpModel.NumArray +import ilog.concert.IloNumToNumStepFunction +import ilog.concert.cppimpl.IloConcertUtils + +/** + * Constructor of class NumToNumStepFunction. + * + * @param f is a CPLEX step function + * @param model is the constraint programming model + */ +class NumToNumStepFunction(f: IloNumToNumStepFunction)(implicit model: CpModel) { + + /** + * Returns the CPLEX step function + * + * @return the CPLEX step function + */ + def getIloNumToNumStepFunction(): IloNumToNumStepFunction = f + + /** + * + * @return + */ + def getDefinitionIntervalMin(): Double = f.getDefinitionIntervalMin + + /** + * + * @return + */ + def getDefinitionIntervalMax(): Double = f.getDefinitionIntervalMax + + /** + * This member function returns the value of the invoking step function at x. + * + * @return the value of the step function at the given point + */ + def getValue(x: Double): Double = f.getValue(x) + + def getMin(x1: Double, x2: Double): Double = f.getMin(x1, x2) + + def getMax(x1: Double, x2: Double): Double = f.getMax(x1, x2) + + /** + * This member function returns the sum of the invoking step function on the interval [x1, x2). + * + * @param x1 is the start of the interval + * @param x2 is the end of the interval + * @return + */ + def getArea(x1: Double, x2: Double): Double = f.getArea(x1, x2) + + /** + * This member function sets the value of the invoking step function to be v on the interval [x1, x2). + * + * @param x1 is the start of the interval + * @param x2 the end of the interval + * @param v is the value on the interval + */ + def setValue(x1: Double, x2: Double, v: Double): Unit = f.setValue(x1, x2, v) + + def addValue(x1: Double, x2: Double, v: Double): Unit = f.addValue(x1, x2, v) + + def add(f: NumToNumStepFunction): Unit = this.f.add(f.getIloNumToNumStepFunction()) + + def setMin(f: NumToNumStepFunction): Unit = this.f.setMin(f.getIloNumToNumStepFunction()) + def setMin(x1: Double, x2: Double, v: Double): Unit = f.setMin(x1, x2, v) + + def setMax(f: NumToNumStepFunction): Unit = this.f.setMax(f.getIloNumToNumStepFunction()) + def setMax(x1: Double, x2: Double, v: Double): Unit = f.setMin(x1, x2, v) + + def dilate(k: Double): Unit = f.dilate(k) + + def setSteps(x: NumArray, v: NumArray): Unit = { + setSteps(x.toArray, v.toArray) + } + + def setSteps(x: Array[Double], v: Array[Double]): Unit = { + val xa = IloConcertUtils.ToCppIloNumArray(model.cp.getEnvImpl, x) + val va = IloConcertUtils.ToCppIloNumArray(model.cp.getEnvImpl, v) + f.setSteps(xa, va) + } + + def setPeriodic(f: IloNumToNumStepFunction, x0: Double, n: Double, dval: Double): Unit = + this.f.setPeriodic(f, x0, n, dval) + + def setPeriodic(f: IloNumToNumStepFunction, x0: Double, n: Double): Unit = + this.f.setPeriodic(f, x0, n) + + def setPeriodic(f: IloNumToNumStepFunction, x0: Double): Unit = + this.f.setPeriodic(f, x0) + + def setPeriodicValue(x1: Double, x2: Double, f: NumToNumStepFunction, offset: Double): Unit = + this.f.setPeriodicValue(x1, x2, f.getIloNumToNumStepFunction(), offset) + + def setPeriodicValue(x1: Double, x2: Double, f: NumToNumStepFunction): Unit = + this.f.setPeriodicValue(x1, x2, f.getIloNumToNumStepFunction()) + + def sub(f: NumToNumStepFunction): Unit = this.f.sub(f.getIloNumToNumStepFunction()) + + def prod(k: Double): Unit = this.f.prod(k) + + def shift(dx: Double): Unit = this.f.shift(dx) + + def shift(dx: Double, dval: Double): Unit = f.shift(dx, dval) + + /** + * + * @return + */ + override def toString() = { + val xmin = f.getDefinitionIntervalMin + val xmax = f.getDefinitionIntervalMax + var prevX = xmin + var prevV = .0 + val builder = new StringBuilder + builder.append("NumToNuMStepFunction: [").append(xmin).append(" .. ").append(xmax).append(") {") + val cursor = model.cp.numToNumStepFunctionCursor(f, f.getDefinitionIntervalMin) + while (cursor.ok()) { + val x = cursor.getSegmentMin + val v = cursor.getValue + if (prevX < x) builder.append("[").append(prevX).append(" .. ").append(x).append(") -> ").append(prevV).append(" ; ") + prevX = x + prevV = v + cursor.next() + } + builder.append("[").append(prevX).append(" .. ").append(xmax).append(") -> ").append(prevV).append(" ; ") + builder.append("}") + builder.toString + } +} + +object NumToNumStepFunction { + /** + * Converts a CPLEX step function to a step function. + * + * @param f is the CPLEX step function + * @param model is the constraint programming model + * @return step function + */ + def apply(f: IloNumToNumStepFunction)(implicit model: CpModel): NumToNumStepFunction = new NumToNumStepFunction(f) +} \ No newline at end of file diff --git a/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala b/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala new file mode 100644 index 0000000..36aa1f1 --- /dev/null +++ b/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala @@ -0,0 +1,28 @@ +/* + * Source file provided under Apache License, Version 2.0, January 2004, + * http://www.apache.org/licenses/ + * (c) Copyright DecisionBrain SAS 2016,2017 + */ + +package com.decisionbrain.cplex.cp + +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner +import org.scalatest.{FunSuite, Matchers} + +@RunWith(classOf[JUnitRunner]) +class SchedCalendarTest extends FunSuite with Matchers { + + val epsilon = 1e-6 + + test("SchedCalendar") { + val model = SchedCalendar.build() + + val status = model.solve() + + status should equal(true) + model.getObjectiveValue() should equal(638.0 +- epsilon) + + model.end() + } +} From 2d26b571d43b5fca3734ade5f7e979a13fbc9c31 Mon Sep 17 00:00:00 2001 From: dgodard Date: Tue, 14 Mar 2017 12:36:24 +0100 Subject: [PATCH 4/9] Add documentation on class NumToNumStepFunction --- .../com/decisionbrain/cplex/cp/CpModel.scala | 18 +- .../cplex/cp/NumToNumStepFunction.scala | 206 ++++++++++++++++-- 2 files changed, 198 insertions(+), 26 deletions(-) diff --git a/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala b/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala index d9151ff..da32b0b 100644 --- a/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala +++ b/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala @@ -1597,9 +1597,16 @@ class CpModel(name: String=null) { def numToNumStepFunction(): NumToNumStepFunction = NumToNumStepFunction(cp.numToNumStepFunction())(implicitly(this)) - - def numToNumStepFunctionCursor(f: NumToNumSegmentFunction, x: Double = -IloCP.Infinity): NumToNumStepFunctionCursor = - cp.numToNumSegmentFunctionCursor(f, x) + /** + * This method creates a cursor to inspect step function f. This cursor lets you iterate forward or backward over + * the steps of the function. The cursor initially specifies the step of the function that contains x. + * + * @param f is the step function + * @param x is the initial step + * @return a new step function cursor + */ + def numToNumStepFunctionCursor(f: NumToNumStepFunction, x: Double = -IloCP.Infinity): NumToNumStepFunctionCursor = + cp.numToNumStepFunctionCursor(f.getIloNumToNumStepFunction(), x) /** * Creates and returns a piecewise linear function defined everywhere. The array point contains the n breakpoints of @@ -1839,7 +1846,6 @@ class CpModel(name: String=null) { */ def getDomain(v: IntervalVar): String = cp.getDomain(v.getIloIntervalVar()) - /** * This member function assumes that the cumul function expression f is fixed. It returns the number of segments of * the corresponding stepwise non-negative function. A segment is an interval [start, end) on which the value of f @@ -2022,10 +2028,10 @@ object CpModel { type IntervalSequenceVar = IloIntervalSequenceVar type TransitionDistance = IloTransitionDistance - type Solution = IloSolution + type Solution = IloSolutionS type MultiCriterionExpr = IloMultiCriterionExpr type NumToNumSegmentFunction = IloNumToNumSegmentFunction - type NumToNumStepFunctionCursor = IloNumToNumSegmentFunctionCursor + type NumToNumStepFunctionCursor = IloNumToNumStepFunctionCursor /** * Create and return a new mathematical programming model. diff --git a/src/main/scala/com/decisionbrain/cplex/cp/NumToNumStepFunction.scala b/src/main/scala/com/decisionbrain/cplex/cp/NumToNumStepFunction.scala index c509576..9307db6 100644 --- a/src/main/scala/com/decisionbrain/cplex/cp/NumToNumStepFunction.scala +++ b/src/main/scala/com/decisionbrain/cplex/cp/NumToNumStepFunction.scala @@ -9,6 +9,7 @@ package com.decisionbrain.cplex.cp import com.decisionbrain.cplex.cp.CpModel.NumArray import ilog.concert.IloNumToNumStepFunction import ilog.concert.cppimpl.IloConcertUtils +import ilog.cp.IloCP /** * Constructor of class NumToNumStepFunction. @@ -26,14 +27,16 @@ class NumToNumStepFunction(f: IloNumToNumStepFunction)(implicit model: CpModel) def getIloNumToNumStepFunction(): IloNumToNumStepFunction = f /** + * This member function returns the left-most point of the definition interval of the invoking step function. * - * @return + * @return the first point of the definition interval */ def getDefinitionIntervalMin(): Double = f.getDefinitionIntervalMin /** + * This member function returns the right-most point of the definition interval of the invoking step function. * - * @return + * @return the last point of the definition interval */ def getDefinitionIntervalMax(): Double = f.getDefinitionIntervalMax @@ -44,8 +47,22 @@ class NumToNumStepFunction(f: IloNumToNumStepFunction)(implicit model: CpModel) */ def getValue(x: Double): Double = f.getValue(x) + /** + * This member function returns the minimal value of the invoking step function on the interval [x1, x2). + * + * @param x1 is the start of the interval + * @param x2 is the end of the interval + * @return the minimal value of the step function on the given interval + */ def getMin(x1: Double, x2: Double): Double = f.getMin(x1, x2) + /** + * This member function returns the maximal value of the invoking step function on the interval [x1, x2). + * + * @param x1 is the start of the interval + * @param x2 is the end of the interval + * @return the maximal value of the step function on the given interval + */ def getMax(x1: Double, x2: Double): Double = f.getMax(x1, x2) /** @@ -66,54 +83,203 @@ class NumToNumStepFunction(f: IloNumToNumStepFunction)(implicit model: CpModel) */ def setValue(x1: Double, x2: Double, v: Double): Unit = f.setValue(x1, x2, v) + /** + * This member function adds v to the value of the invoking step function everywhere on the interval [x1, x2). + * + * @param x1 is the start of the interval + * @param x2 is the end of the interval + * @param v is the value added to the step function on the given interval + */ def addValue(x1: Double, x2: Double, v: Double): Unit = f.addValue(x1, x2, v) - def add(f: NumToNumStepFunction): Unit = this.f.add(f.getIloNumToNumStepFunction()) - + /** + * This member function sets the value of the invoking step function to be the minimum between the current value and + * the value of f everywhere on the definition interval of the invoking function. The definition interval + * of f must be the same as the one of the invoking step function. + * + * @param f is the other step function + */ def setMin(f: NumToNumStepFunction): Unit = this.f.setMin(f.getIloNumToNumStepFunction()) + + /** + * This member function sets the value of the invoking step function to be the minimum between the current value and + * the value of f everywhere on the definition interval of the invoking function. The definition interval + * of f must be the same as the one of the invoking step function. + * + * @param x1 is the start of the interval + * @param x2 is the end of the interval + * @param v is the minimal value + */ def setMin(x1: Double, x2: Double, v: Double): Unit = f.setMin(x1, x2, v) + /** + * This member function sets the value of the invoking step function to be the maximum between the current value and + * the value of f everywhere on the definition interval of the invoking function. The interval of definition + * of f must be the same as that of the invoking step function. + * + * @param f is the other step function + */ def setMax(f: NumToNumStepFunction): Unit = this.f.setMax(f.getIloNumToNumStepFunction()) + + /** + * This member function sets the value of the invoking step function to be the maximum between the current value and + * v everywhere on the interval [x1, x2). + * + * @param x1 is the start of the interval + * @param x2 is the end of the interval + * @param v is the maximal value + */ def setMax(x1: Double, x2: Double, v: Double): Unit = f.setMin(x1, x2, v) + /** + * This member function multiplies by k the scale of x for the invoking step function, k must be a + * nonnegative numeric value. More precisely, if the invoking function was defined over an interval + * [xMin, xMax), it will be redefined over the interval [k*xMin, k*xMax) and the value at + * x will be the former value at x/k. + * + * @param k is the scaling factor + */ def dilate(k: Double): Unit = f.dilate(k) + /** + * This member function initializes the invoking function as a step function whose steps are defined by the two + * arguments arrays x and v. More precisely, if n is the size of array x, size + * of array v must be n+1 and, if the invoking function is defined on the interval + * [xMin,xMax), its values will be: + *
    + *
  • v[0] on interval [xMin,x[0]),
  • + *
  • v[i] on interval [x[i-1],x[i]) for all i in [0,n-1],
  • + *
  • v[n] on interval [x[n-1],xMax).
  • + * + * @param x is the array of points + * @param v is the array of values + */ def setSteps(x: NumArray, v: NumArray): Unit = { setSteps(x.toArray, v.toArray) } + /** + * This member function initializes the invoking function as a step function whose steps are defined by the two + * arguments arrays x and v. More precisely, if n is the size of array x, size + * of array v must be n+1 and, if the invoking function is defined on the interval + * [xMin,xMax), its values will be: + *
      + *
    • v[0] on interval [xMin,x[0]),
    • + *
    • v[i] on interval [x[i-1],x[i]) for all i in [0,n-1],
    • + *
    • v[n] on interval [x[n-1],xMax).
    • + *
    + * + * @param x is the array of points + * @param v is the array of values + */ def setSteps(x: Array[Double], v: Array[Double]): Unit = { val xa = IloConcertUtils.ToCppIloNumArray(model.cp.getEnvImpl, x) val va = IloConcertUtils.ToCppIloNumArray(model.cp.getEnvImpl, v) f.setSteps(xa, va) } - def setPeriodic(f: IloNumToNumStepFunction, x0: Double, n: Double, dval: Double): Unit = + /** + * This member function initializes the invoking function as a step function that repeats the step function f, n + * times after x0. More precisely, if f is defined on [xfpMin,xfpMax) and if the invoking function is defined on + * [xMin,xMax), the value of the invoking function will be: + *
      + *
    • dval on [xMin, x0),
    • f((x-x0) % (xfpMax-xfpMin)) for x in [x0, Min(x0+n*(xfpMax-xfpMin), xMax)), + *
    • dval on [Min(x0+n*(xfpMax-xfpMin), xMax), xMax).
    • + *
    + * + * @param f is the other step function + * @param x0 is the starting point + * @param n is the number of repetitions + * @param dval is the default value + */ + def setPeriodic(f: IloNumToNumStepFunction, x0: Double, n: Double = IloCP.Infinity, dval: Double = 0): Unit = this.f.setPeriodic(f, x0, n, dval) - def setPeriodic(f: IloNumToNumStepFunction, x0: Double, n: Double): Unit = - this.f.setPeriodic(f, x0, n) - - def setPeriodic(f: IloNumToNumStepFunction, x0: Double): Unit = - this.f.setPeriodic(f, x0) - - def setPeriodicValue(x1: Double, x2: Double, f: NumToNumStepFunction, offset: Double): Unit = + /** + * This member function changes the value of the invoking function on the interval [x1,x2). On this interval, the + * invoking function is set to equal a repetition of the pattern function f with an initial offset of offset. The + * invoking function is not modified outside the interval [x1,x2). More precisely, if [min,max) denotes the + * definition interval of f, for all t in [x1,x2), the invoking function at t is set to equal + * f(min + (offset+t-x1)%(max-min))) where % denotes the modulo operator. By default, the offset is equal to 0. + * + * @param x1 is the start of the interval + * @param x2 is the end of the interval + * @param f is the other step function + * @param offset is the offset + */ + def setPeriodicValue(x1: Double, x2: Double, f: NumToNumStepFunction, offset: Double = 0): Unit = this.f.setPeriodicValue(x1, x2, f.getIloNumToNumStepFunction(), offset) - def setPeriodicValue(x1: Double, x2: Double, f: NumToNumStepFunction): Unit = - this.f.setPeriodicValue(x1, x2, f.getIloNumToNumStepFunction()) + /** + * This function adds the argument function fct to the invoking step function. + * + * @param f + */ + def +=(f: NumToNumStepFunction): Unit = this.f.add(f.getIloNumToNumStepFunction()) + + /** + * Returns a new step function that is the sum of this step function and and the step function given as argument. + * + * @param f is the other step function + * @return a new step function that is the sum of the two step functions + */ + def +(f: NumToNumStepFunction): NumToNumStepFunction = { + val nf = getIloNumToNumStepFunction().copy() + nf.add(f.getIloNumToNumStepFunction()) + NumToNumStepFunction(nf) + } - def sub(f: NumToNumStepFunction): Unit = this.f.sub(f.getIloNumToNumStepFunction()) + /** + * Returns a new step function that is the sum of this step function and and the step function given as argument. + * + * @param f is the other step function + * @return a new step function that is the sum of the two step functions + */ + def -(f: NumToNumStepFunction): NumToNumStepFunction = { + val nf = getIloNumToNumStepFunction().copy() + nf.sub(f.getIloNumToNumStepFunction()) + NumToNumStepFunction(nf) + } - def prod(k: Double): Unit = this.f.prod(k) + /** + * This operator subtracts the argument function fct from the invoking step function. + * + * @param f is the other step function + */ + def -=(f: NumToNumStepFunction): Unit = this.f.sub(f.getIloNumToNumStepFunction()) - def shift(dx: Double): Unit = this.f.shift(dx) + /** + * This operator multiplies by a factor k the value of the invoking step function everywhere on the definition + * interval. + * + * @param k is the factor + */ + def *=(k: Double): Unit = this.f.prod(k) - def shift(dx: Double, dval: Double): Unit = f.shift(dx, dval) + /** + * This member function shifts the invoking function from dx to the right if dx > 0 or from -dx to the left if dx < + * 0. It has no effect if dx = 0. More precisely, if the invoking function is defined on [xMin,xMax) and dx > 0, the + * new value of the invoking function is: + *
      + *
    • dval on the interval [xMin, xMin+dx),
    • + *
    • for all x in [xMin+dx, xMax), the former value at x-dx.
    • + *
    + * If dx < 0, the new value of the invoking function is: + *
      + *
    • for all x in [xMin, xMax+dx), the former value at x-dx,
    • + *
    • dval on the interval [xMax+dx,xMax).
    • + *
    + * + * @param dx is the shift on x + * @param dval is the default value + */ + def shift(dx: Double, dval: Double = 0): Unit = f.shift(dx, dval) /** + * Converts the step function to a character string? * - * @return + * @return a character string */ override def toString() = { val xmin = f.getDefinitionIntervalMin @@ -122,7 +288,7 @@ class NumToNumStepFunction(f: IloNumToNumStepFunction)(implicit model: CpModel) var prevV = .0 val builder = new StringBuilder builder.append("NumToNuMStepFunction: [").append(xmin).append(" .. ").append(xmax).append(") {") - val cursor = model.cp.numToNumStepFunctionCursor(f, f.getDefinitionIntervalMin) + val cursor = model.numToNumStepFunctionCursor(this, this.getDefinitionIntervalMin) while (cursor.ok()) { val x = cursor.getSegmentMin val v = cursor.getValue From 24e2211a4948a6dac8da7a41502d938fc5808984 Mon Sep 17 00:00:00 2001 From: dgodard Date: Tue, 14 Mar 2017 12:49:46 +0100 Subject: [PATCH 5/9] Fix typo --- src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala b/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala index da32b0b..5fc031e 100644 --- a/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala +++ b/src/main/scala/com/decisionbrain/cplex/cp/CpModel.scala @@ -2028,7 +2028,7 @@ object CpModel { type IntervalSequenceVar = IloIntervalSequenceVar type TransitionDistance = IloTransitionDistance - type Solution = IloSolutionS + type Solution = IloSolution type MultiCriterionExpr = IloMultiCriterionExpr type NumToNumSegmentFunction = IloNumToNumSegmentFunction type NumToNumStepFunctionCursor = IloNumToNumStepFunctionCursor From 2d4c438dbd0470d70313d984e412b25318913eb2 Mon Sep 17 00:00:00 2001 From: dgodard Date: Tue, 14 Mar 2017 13:24:35 +0100 Subject: [PATCH 6/9] Syntax highlighting in README for scala code blocks --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 921a734..daa8fcf 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,7 @@ scientific papers. For instance, a constraint such as: can be written as: -``` -#!scala +```scala model.add(sum (for (i <- 1 to n) yield a(i) * x(i)) <= c(j)) ``` From d4d60f29cec337747baa86f41a0e35934ed1ec1f Mon Sep 17 00:00:00 2001 From: dgodard Date: Tue, 14 Mar 2017 18:19:40 +0100 Subject: [PATCH 7/9] Check calendar first task of joe is ok wrt its calendar --- .../cplex/cp/SchedCalendar.scala | 23 +++++++++++-------- .../cplex/cp/SchedCalendarTest.scala | 6 +++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala b/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala index 8a96717..0849637 100644 --- a/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala +++ b/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala @@ -43,6 +43,9 @@ object SchedCalendar { var joeTaskVars: List[IntervalVar] = _ var jimTaskVars: List[IntervalVar] = _ + var joeCalendar: NumToNumStepFunction = _ + var jimCalendar: NumToNumStepFunction = _ + def makeHouse(id: Int): (IntExpr, Iterable[IntervalVar], Iterable[IntervalVar], Iterable[IntervalVar]) = { val taskVars: Map[String, IntervalVar] = (for (task <- tasks; (tname, tduration) = task) @@ -92,9 +95,9 @@ object SchedCalendar { model.add(noOverlap(jimTaskVars)) - val joeCalendar = model.numToNumStepFunction + joeCalendar = model.numToNumStepFunction joeCalendar.setValue(0, 2 * 365, 100) - val jimCalendar = model.numToNumStepFunction + jimCalendar = model.numToNumStepFunction jimCalendar.setValue(0, 2 * 365, 100) /* WEEK ENDS. */ @@ -116,9 +119,6 @@ object SchedCalendar { jimCalendar.setValue(397, 411, 0) jimCalendar.setValue(565, 579, 0) -// println("Joe: " + joeCalendar.toString()) -// println("Jim: " + jimCalendar) - for (i <- joeTaskVars.indices) { joeTaskVars(i).setIntensity(joeCalendar) model.add(forbidStart(joeTaskVars(i), joeCalendar)) @@ -150,12 +150,17 @@ object SchedCalendar { if (status) { println(s"Solution status: $status") println("Solution with objective " + model.getObjectiveValue()) - for (i <- allTaskVars.indices) { - println(model.getDomain(allTaskVars(i))) - } + println("Joe Calendar: " + joeCalendar) + println("Jim Calendar: " + jimCalendar) + println("Joe Schedule:") + for (v <- SchedCalendar.joeTaskVars) + println("\t" + model.getDomain(v)) + println("Jim Schedule:") + for (v <- SchedCalendar.jimTaskVars) + println("\t" + model.getDomain(v)) } - true + status } def run(): Boolean = { diff --git a/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala b/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala index 36aa1f1..8c0e86f 100644 --- a/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala +++ b/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala @@ -23,6 +23,12 @@ class SchedCalendarTest extends FunSuite with Matchers { status should equal(true) model.getObjectiveValue() should equal(638.0 +- epsilon) + SchedCalendar.joeTaskVars(0).getName() should equal ("H0-masonry") + model.getStart(SchedCalendar.joeTaskVars(0)) should equal (0) + model.getEnd(SchedCalendar.joeTaskVars(0)) should equal (54) + model.getSize(SchedCalendar.joeTaskVars(0)) should equal (35) + model.getLength(SchedCalendar.joeTaskVars(0)) should equal (54) + model.end() } } From 8fcbe3a16ec8a5fa4c0b71b3676b9f9127bb7f40 Mon Sep 17 00:00:00 2001 From: dgodard Date: Tue, 14 Mar 2017 18:49:22 +0100 Subject: [PATCH 8/9] Check first task of joe is ok wrt its calendar --- .../cplex/cp/SchedCalendar.scala | 21 +++++++++++-------- .../cplex/cp/SchedCalendarTest.scala | 11 +++++----- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala b/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala index 0849637..65029e0 100644 --- a/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala +++ b/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala @@ -19,8 +19,8 @@ import ilog.cp.IloCP object SchedCalendar { val nbWorkers = 3 - val nbHouses = 5 - val nbTasks = 10 + + val houses = List(31, 0, 90, 120, 90) // 5 houses with different release dates val tasks = List( ("masonry", 35), // pair of task name and duration @@ -36,9 +36,10 @@ object SchedCalendar { ) + implicit var model: CpModel = _ - var allTaskVars: List[IntervalVar] = _ + var allTaskVars: Map[String, IntervalVar] = _ var endVars: List[IntExpr] = _ var joeTaskVars: List[IntervalVar] = _ var jimTaskVars: List[IntervalVar] = _ @@ -46,7 +47,7 @@ object SchedCalendar { var joeCalendar: NumToNumStepFunction = _ var jimCalendar: NumToNumStepFunction = _ - def makeHouse(id: Int): (IntExpr, Iterable[IntervalVar], Iterable[IntervalVar], Iterable[IntervalVar]) = { + def makeHouse(id: Int, rd: Int): (IntExpr, Iterable[IntervalVar], Iterable[IntervalVar], Iterable[IntervalVar]) = { val taskVars: Map[String, IntervalVar] = (for (task <- tasks; (tname, tduration) = task) yield (tname , model.intervalVar(sizeMin = tduration, sizeMax = tduration, name = "H" + id + "-" + tname)))(collection.breakOut) @@ -67,6 +68,8 @@ object SchedCalendar { model.add(taskVars("garden") < taskVars("moving")) model.add(taskVars("painting") < taskVars("moving")) + taskVars("masonry").setStartMin(rd) + val joeTaskVars = List(taskVars("masonry") , taskVars("carpentry") , taskVars("roofing") @@ -84,12 +87,12 @@ object SchedCalendar { model = CpModel("SchedCumul") - val results = for (h <- 0 until nbHouses) yield makeHouse(h) + val results = for ((rd, index) <- houses.zipWithIndex) yield makeHouse(index, rd) - endVars = results.map(_._1).toList - allTaskVars = results.flatMap(_._2).toList - joeTaskVars = results.flatMap(_._3).toList - jimTaskVars = results.flatMap(_._4).toList + endVars = results.map(_._1) + allTaskVars = results.flatMap(_._2).map(v => (v.getName().getOrElse(""), v)).toMap + joeTaskVars = results.flatMap(_._3) + jimTaskVars = results.flatMap(_._4) model.add(noOverlap(joeTaskVars)) model.add(noOverlap(jimTaskVars)) diff --git a/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala b/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala index 8c0e86f..970d948 100644 --- a/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala +++ b/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala @@ -23,11 +23,12 @@ class SchedCalendarTest extends FunSuite with Matchers { status should equal(true) model.getObjectiveValue() should equal(638.0 +- epsilon) - SchedCalendar.joeTaskVars(0).getName() should equal ("H0-masonry") - model.getStart(SchedCalendar.joeTaskVars(0)) should equal (0) - model.getEnd(SchedCalendar.joeTaskVars(0)) should equal (54) - model.getSize(SchedCalendar.joeTaskVars(0)) should equal (35) - model.getLength(SchedCalendar.joeTaskVars(0)) should equal (54) + SchedCalendar.allTaskVars("H1-masonry") should not equal None + val joeFirstTask = SchedCalendar.allTaskVars("H1-masonry") + model.getStart(joeFirstTask) should equal (0) + model.getEnd(joeFirstTask) should equal (54) + model.getSize(joeFirstTask) should equal (35) + model.getLength(joeFirstTask) should equal (54) model.end() } From fae621caab80f3ebdcd672acc4245c43cfb19813 Mon Sep 17 00:00:00 2001 From: dgodard Date: Tue, 14 Mar 2017 19:02:21 +0100 Subject: [PATCH 9/9] Prepare release 1.1.0. Update readme. --- README.md | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index daa8fcf..ccb3ba2 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ To get up to speed, the easiest way to start with this library is to study the e * src/examples/mp: examples of optimization models based on mathematical programming * src/examples/cp: examples of optimization models based on constraint programming -This library has been tested using IBM ILOG CPLEX 12.6.1, 12.6.2, 12.6.3, 12.7.0 and scala 2.11.8. +This library has been tested using IBM ILOG CPLEX 12.6.1, 12.6.2, 12.6.3, 12.7.0, Scala 2.11.8 and Java JDK 1.8.0_121. To build the library install gradle 2.10 and set the environment variable `CPLEX_STUDIO_HOME` (e.g. on windows `C:\IBM\ILOG\CPLEX_Studio1263`). diff --git a/build.gradle b/build.gradle index da12343..e724c89 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ group 'com.decisionbrain' -version '1.0.2-SNAPSHOT' +version '1.1.0' apply plugin: 'java' apply plugin: 'scala'