diff --git a/README.md b/README.md
index 819fcfb..ccb3ba2 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))
```
@@ -19,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`).
@@ -39,7 +38,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 +46,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 714d382..e724c89 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,5 @@
group 'com.decisionbrain'
-version '1.0.1'
+version '1.1.0'
apply plugin: 'java'
apply plugin: 'scala'
@@ -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..65029e0
--- /dev/null
+++ b/src/examples/scala/com/decisionbrain/cplex/cp/SchedCalendar.scala
@@ -0,0 +1,180 @@
+/*
+ * 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 houses = List(31, 0, 90, 120, 90) // 5 houses with different release dates
+
+ 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: Map[String, IntervalVar] = _
+ var endVars: List[IntExpr] = _
+ var joeTaskVars: List[IntervalVar] = _
+ var jimTaskVars: List[IntervalVar] = _
+
+ var joeCalendar: NumToNumStepFunction = _
+ var jimCalendar: NumToNumStepFunction = _
+
+ 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)
+
+ // 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"))
+
+ taskVars("masonry").setStartMin(rd)
+
+ 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 ((rd, index) <- houses.zipWithIndex) yield makeHouse(index, rd)
+
+ 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))
+
+
+ joeCalendar = model.numToNumStepFunction
+ joeCalendar.setValue(0, 2 * 365, 100)
+ 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)
+
+ 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())
+ 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))
+ }
+
+ status
+ }
+
+ def run(): Boolean = {
+ val model = build()
+ val status = solve()
+ model.end()
+ status
+ }
+
+ def main(args: Array[String]): Unit = {
+ run()
+ }
+
+}
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..5fc031e 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.
@@ -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))
}
/**
@@ -990,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.
@@ -1498,6 +1589,43 @@ 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))
+
+ /**
+ * 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
+ * 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.
*
@@ -1718,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
@@ -1903,6 +2030,8 @@ object CpModel {
type TransitionDistance = IloTransitionDistance
type Solution = IloSolution
type MultiCriterionExpr = IloMultiCriterionExpr
+ type NumToNumSegmentFunction = IloNumToNumSegmentFunction
+ type NumToNumStepFunctionCursor = IloNumToNumStepFunctionCursor
/**
* Create and return a new mathematical programming model.
@@ -1952,6 +2081,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 +2105,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.
@@ -2538,6 +2699,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..9307db6
--- /dev/null
+++ b/src/main/scala/com/decisionbrain/cplex/cp/NumToNumStepFunction.scala
@@ -0,0 +1,315 @@
+/*
+ * 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
+import ilog.cp.IloCP
+
+/**
+ * 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
+
+ /**
+ * This member function returns the left-most point of the definition interval of the invoking step function.
+ *
+ * @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 the last point of the definition interval
+ */
+ 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)
+
+ /**
+ * 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)
+
+ /**
+ * 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)
+
+ /**
+ * 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)
+
+ /**
+ * 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)
+ }
+
+ /**
+ * 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)
+
+ /**
+ * 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)
+
+ /**
+ * 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)
+ }
+
+ /**
+ * 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)
+ }
+
+ /**
+ * 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())
+
+ /**
+ * 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)
+
+ /**
+ * 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 a character string
+ */
+ 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.numToNumStepFunctionCursor(this, this.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..970d948
--- /dev/null
+++ b/src/test/scala/com/decisionbrain/cplex/cp/SchedCalendarTest.scala
@@ -0,0 +1,35 @@
+/*
+ * 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)
+
+ 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()
+ }
+}
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()
+ }
+}