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: + *