Class: Model
Model captures the problem to be solved. It contains variables, constraints and objective function.
To create an optimization model, you must first create a Model object. Then you can use the methods of the Model to create variables (e.g. intervalVar), the objective function (minimize or maximize) and constraints (e.g. constraint or noOverlap). Note that a boolean expression becomes a constraint only by passing it to the function constraint; otherwise, it is not enforced.
To solve a model, pass it to function solve or to Solver class.
Available modeling elements
Variables
Interval variables can be created by function intervalVar, integer variables by function intVar.
Basic integer expressions
- startOf: start of an interval variable (optional integer expression).
- startOr: start of an interval variable or a constant when it is absent.
- endOf: end of an interval variable (optional integer expression).
- endOr: end of an interval variable or a constant when it is absent.
- lengthOf: length of an interval variable (optional integer expression).
- lengthOr: length of an interval variable or a constant when it is absent.
- guard: replaces absent value by a constant.
Integer arithmetics
- plus: addition.
- minus: subtraction.
- neg: negation (changes sign).
- times: multiplication.
- div: division (rounds to zero).
- abs: absolute value.
- min2: minimum of two integer expressions.
- min: minimum of an array of integer expressions.
- max2: maximum of two integer expressions.
- max: maximum of an array of integer expressions.
- sum: sum of an array of integer expressions.
Comparison operators for integer expressions
- eq: equality.
- ne: inequality.
- lt: less than.
- le: less than or equal to.
- gt: greater than.
- ge: greater than or equal to.
- identity: constraints two integer expressions to be equal, including the presence status.
Boolean operators
Functions returning BoolExpr
- presenceOf: whether the argument is present or absent.
- inRange: whether an integer expression is within the given range
Basic constraints on interval variables
- alternative: an alternative between multiple interval variables.
- span: span (cover) of interval variables.
- endBeforeEnd, endBeforeStart, startBeforeEnd, startBeforeStart, endAtStart, startAtEnd: precedence constraints.
Disjunction (noOverlap)
- sequenceVar: sequence variable over a set of interval variables.
- noOverlap: constraints a set of interval variables to not overlap (possibly with transition times).
- position: returns the position of an interval variable in a sequence.
Basic cumulative expressions
- pulse: changes value during the interval variable.
- stepAtStart: changes value at the start of the interval variable.
- stepAtEnd: changes value at the end of the interval variable.
- stepAt: changes value at a given time.
Combining cumulative expressions
- cumulNeg: negation.
- cumulPlus: addition.
- cumulMinus: subtraction.
- cumulSum: sum of multiple expressions.
Constraints on cumulative expressions
Mapping/batching
- itvMapping: map tasks (interval variables) to slots (other interval variables).
Constraints on integer variables/expressions
- pack: pack items of various sizes into a set of bins.
Objective
Example
Our goal is to schedule a set of tasks such that it is finished as soon as possible (i.e., the makespan is minimized). Each task has a fixed duration, and cannot be interrupted. Moreover, each task needs a certain number of workers to be executed, and the total number of workers is limited. The input data are generated randomly.
import * as CP from '@scheduleopt/optalcp';
// Constants for random problem generation:
const nbTasks = 100;
const nbWorkers = 5;
const maxDuration = 100;
// Start by creating the model:
let model = new CP.Model();
// For each task we will have an interval variable and a cumulative expression:
let tasks : CP.IntervalVar[] = [];
let workerUsage: CP.CumulExpr[] = [];
// Loop over the tasks:
for (let i = 0; i < nbTasks; i++) {
// Generate random task length:
const taskLength = 1 + Math.floor(Math.random() * (maxDuration - 1));
// Create the interval variable for the task:
let task = model.intervalVar({ name: "Task" + (i + 1), length: taskLength });
// And store it in the array:
tasks.push(task);
// Generate random number of workers needed for the task:
const workersNeeded = 1 + Math.floor(Math.random() * (nbWorkers - 1));
// Create the pulse that increases the number of workers used during the task:
workerUsage.push(task.pulse(workersNeeded));
}
// Limit the sum of the pulses to the number of workers available:
model.cumulSum(workerUsage).cumulLe(nbWorkers);
// From an array of tasks, create an array of their ends:
let ends = tasks.map(t => t.end());
// And minimize the maximum of the ends:
model.max(ends).minimize();
try {
// Solve the model with the provided parameters:
let result = await CP.solve(model, {
timeLimit: 3, // Stop after 3 seconds
nbWorkers: 4, // Use for CPU threads
});
if (result.nbSolutions == 0)
console.log("No solution found.");
else {
const solution = result.bestSolution!;
// Note that in the evaluation version of the solver, the variable values in
// the solution are masked, i.e. they are all _absent_ (`null` in JavaScript).
// Objective value is not masked though.
console.log("Solution found with makespan " + solution.getObjective());
for (let task of tasks) {
let start = solution.getStart(task);
if (start !== null)
console.log("Task " + task.getName() + " starts at " + );
else
console.log("Task " + task.getName() + " is absent (not scheduled).")
}
}
} catch (e) {
// In case of error, CP.solve returns a rejected promise.
// Therefore, "await CP.solve" throws an exception.
console.log("Error: " + (e as Error).message);
}
See
Constructors
new Model()
new Model(
name
?:string
):Model
Creates a new empty model.
Naming the model is optional. The main purpose of the name is to distinguish between different models during benchmarking (see benchmark).
Parameters
Parameter | Type | Description |
---|---|---|
name ? | string | Name of the model. |
Returns
Methods
abs()
Creates an integer expression which is absolute value of arg
.
Parameters
Parameter | Type |
---|---|
arg | number | IntExpr |
Returns
Remarks
If arg
has value absent then the resulting expression has also value absent.
Same as IntExpr.abs.
alternative()
alternative(
main
:IntervalVar
,options
:IntervalVar
[]):void
Creates alternative constraint between interval variables.
Parameters
Parameter | Type | Description |
---|---|---|
main | IntervalVar | The main interval variable. |
options | IntervalVar [] | Array of optional interval variables to choose from. |
Returns
void
Remarks
Alternative constraint is a way to model various kinds of choices. For example, we can model a task that could be done by worker A, B, or C. To model such alternative, we use interval variable main
that represents the task regardless the chosen worker and three interval variables options = [A, B, C]
that represent the task when done by worker A, B, or C. Interval variables A
, B
, and C
should be optional. This way, if e.g. option B is chosen, then B
will be present and equal to main
(they will start at the same time and end at the same time), the remaining options, A and C, will be absent.
We may also decide not to execute the main
task at all (if it is optional). Then main
will be absent and all options A
, B
and C
will be absent too.
Formal definition
The constraint alternative(main, options)
is satisfied in the following two cases:
- Interval
main
is absent and alloptions[i]
are absent too. - Interval
main
is present and exactly one ofoptions[i]
is present (the remaining options are absent). Letk
be the index of the present option. Thenmain.start() == options[k].start()
andmain.end() == options[k].end()
.
Example
Let's consider task T, which can be done by workers A, B, or C. The length of the task and a cost associated with it depends on the chosen worker:
- If done by worker A, then its length is 10, and the cost is 5.
- If done by worker B, then its length is 20, and the cost is 2.
- If done by worker C, then its length is 3, and the cost is 10.
Each worker can execute only one task at a time. However, the remaining tasks are omitted in the model below. The objective could be, e.g., to minimize the total cost (also omitted in the model).
let model = new CP.Model;
let T = model.intervalVar({ name: "T" });
let T_A = model.intervalVar({ name: "T_A", optional: true, length: 10 });
let T_B = model.intervalVar({ name: "T_B", optional: true, length: 20 });
let T_C = model.intervalVar({ name: "T_C", optional: true, length: 3 });
// T_A, T_B and T_C are different ways to execute task T:
model.alternative(T, [T_A, T_B, T_C]);
// The cost depends on the chosen option:
let costOfT = model.sum([
T_A.presence().times(5),
T_B.presence().times(2),
T_C.presence().times(10)
]);
// Each worker A can perform only one task at a time:
model.noOverlap([T_A, ...]); // Worker A
model.noOverlap([T_B, ...]); // Worker B
model.noOverlap([T_C, ...]); // Worker C
// Minimize the total cost:
model.sum([costOfT, ...]).minimize();
and()
and(
arg1
:boolean
|BoolExpr
,arg2
:boolean
|BoolExpr
):BoolExpr
Logical AND of boolean expressions arg1
and arg2
.
Parameters
Parameter | Type |
---|---|
arg1 | boolean | BoolExpr |
arg2 | boolean | BoolExpr |
Returns
Remarks
If one of the arguments has value absent, then the resulting expression also has value absent.
Same as BoolExpr.and.
constraint()
constraint(
constraint
:boolean
|BoolExpr
):void
Creates a constraint from a boolean expression that must be satisfied in the solution.
A constraint is satisfied if it is not false. In other words, a constraint is satisfied if it is true or absent.
Parameters
Parameter | Type | Description |
---|---|---|
constraint | boolean | BoolExpr | The boolean expression to turn into a constraint. |
Returns
void
A constraint that must be satisfied in the solution.
Remarks
A boolean expression that is not turned into a constraint can have arbitrary value in the solution.
Example
In the following example, we create a boolean expression endsBefore50
that checks whether the end of interval variable x
is before 50.
We don't turn it into a constraint yet:
let model = new CP.Model();
let x = model.intervalVar({ name: "x", length: 10, optional: true });
let endsBefore50 = x.end().le(50);
Because endsBefore50
is not a constraint, it can take arbitrary
value in a solution, in particular:
endsBefore50
is true ifx
is present and its end is less than or equal to 50.endsBefore50
is false ifx
is present and its end is greater than 50.endsBefore50
is absent ifx
is absent.
Now we turn endsBefore50
into a constraint:
model.constraint(endsBefore50);
When endsBefore50
is a constraint, it can only be true or absent in
the solution. Therefore, cases 1 and 3 above can happen, but case 2 cannot.
Difference between constraints and boolean expressions
Boolean expressions can take arbitrary value (true, false, or absent) and can be combined into composed expressions (e.g., using and or or).
Constraints can only be true or absent (in a solution) and cannot be combined into composed expressions.
Some functions create constraints directly, e.g. noOverlap.
Then, passing them to function constraint is unnecessary.
It is also not possible to combine constraints into composed expressions
such as or(noOverlap(..), noOverlap(..))
.
cumulGe()
cumulGe(
cumul
:CumulExpr
,minCapacity
:number
):void
Constrains cumulative function cumul
to be everywhere greater or equal to minCapacity
.
Parameters
Parameter | Type |
---|---|
cumul | CumulExpr |
minCapacity | number |
Returns
void
Remarks
This function can be used to specify the minimum limit of resource usage at any time. For example to make sure that there is never less than zero material on stock.
See Model.stepAtStart for an example with cumulGe
.
See
- CumulExpr.cumulGe for the equivalent function on CumulExpr.
- Model.cumulLe for the opposite constraint.
cumulLe()
cumulLe(
cumul
:CumulExpr
,maxCapacity
:number
):void
Constrains cumulative function cumul
to be everywhere less or equal to maxCapacity
.
Parameters
Parameter | Type |
---|---|
cumul | CumulExpr |
maxCapacity | number |
Returns
void
Remarks
This function can be used to specify the maximum limit of resource usage at any time. For example, to limit the number of workers working simultaneously, limit the maximum amount of material on stock, etc.
See Model.pulse for an example with cumulLe
.
See
- CumulExpr.cumulLe for the equivalent function on CumulExpr.
- Model.cumulGe for the opposite constraint.
cumulMinus()
Subtraction of two cumulative expressions.
Parameters
Parameter | Type |
---|---|
lhs | CumulExpr |
rhs | CumulExpr |
Returns
Remarks
Computes subtraction of two cumulative functions.
Formal definition
Let result = cumulMinus(lhs, rhs)
. Then for any number x
in range IntervalMin..IntervalMax the value of result
at x
is equal to lhs
at x
minus rhs
at x
.
cumulMinus(lhs, rhs)
is the same as cumulSum([lhs, cumulNeg(rhs)])
.
See
- CumulExpr.cumulMinus for the equivalent function on CumulExpr.
- cumulSum, cumulPlus, cumulNeg for other ways to combine cumulative functions.
cumulNeg()
Negation of a cumulative expression.
Parameters
Parameter | Type |
---|---|
arg | CumulExpr |
Returns
Remarks
Computes negation of a cumulative function. That is, the resulting function has the opposite values.
See
- CumulExpr.cumulNeg for the equivalent function on CumulExpr.
- cumulSum, cumulPlus, cumulMinus for other ways to combine cumulative functions.
cumulPlus()
Addition of two cumulative expressions.
Parameters
Parameter | Type |
---|---|
lhs | CumulExpr |
rhs | CumulExpr |
Returns
Remarks
Computes addition of two cumulative functions.
Formal definition
Let result = cumulPlus(lhs, rhs)
. Then for any number x
in range IntervalMin..IntervalMax the value of result
at x
is equal to lhs
at x
plus rhs
at x
.
cumulPlus(lhs, rhs)
is the same as cumulSum([lhs, rhs])
.
See
- CumulExpr.cumulPlus for the equivalent function on CumulExpr.
- cumulSum, cumulMinus, cumulNeg for other ways to combine cumulative functions.
cumulSum()
Sum of cumulative expressions.
Parameters
Parameter | Type |
---|---|
array | CumulExpr [] |
Returns
Remarks
Computes the sum of cumulative functions. The sum can be used, e.g., to combine contributions of individual tasks to total resource consumption.
See
cumulPlus, cumulMinus, cumulNeg for other ways to combine cumulative functions.
div()
div(
arg1
:number
|IntExpr
,arg2
:number
|IntExpr
):IntExpr
Creates an integer division of the two integer expressions, i.e. arg1 div arg2
. The division rounds towards zero.
Parameters
Parameter | Type |
---|---|
arg1 | number | IntExpr |
arg2 | number | IntExpr |
Returns
Remarks
If one of the arguments has value absent, the resulting expression also has value absent.
Same as IntExpr.div.
endAtEnd()
endAtEnd(
predecessor
:IntervalVar
,successor
:IntervalVar
,delay
:number
|IntExpr
):void
Creates a precedence constraint between two interval variables.
Parameters
Parameter | Type | Default value |
---|---|---|
predecessor | IntervalVar | undefined |
successor | IntervalVar | undefined |
delay | number | IntExpr | 0 |
Returns
void
Remarks
Same as:
model.constraint(predecessor.end().plus(delay).eq(successor.end())).
In other words, end of predecessor
plus delay
must be equal to end of successor
.
When one of the two interval variables is absent, then the constraint is satisfied.
See
- IntervalVar.endAtEnd is equivalent function on IntervalVar.
- Model.constraint
- IntervalVar.start, IntervalVar.end
- IntExpr.eq
endAtStart()
endAtStart(
predecessor
:IntervalVar
,successor
:IntervalVar
,delay
:number
|IntExpr
):void
Creates a precedence constraint between two interval variables.
Parameters
Parameter | Type | Default value |
---|---|---|
predecessor | IntervalVar | undefined |
successor | IntervalVar | undefined |
delay | number | IntExpr | 0 |
Returns
void
Remarks
Same as:
model.constraint(predecessor.end().plus(delay).eq(successor.start())).
In other words, end of predecessor
plus delay
must be equal to start of successor
.
When one of the two interval variables is absent, then the constraint is satisfied.
See
- IntervalVar.endAtStart is equivalent function on IntervalVar.
- Model.constraint
- IntervalVar.start, IntervalVar.end
- IntExpr.eq
endBeforeEnd()
endBeforeEnd(
predecessor
:IntervalVar
,successor
:IntervalVar
,delay
:number
|IntExpr
):void
Creates a precedence constraint between two interval variables.
Parameters
Parameter | Type | Default value |
---|---|---|
predecessor | IntervalVar | undefined |
successor | IntervalVar | undefined |
delay | number | IntExpr | 0 |
Returns
void
Remarks
Same as:
model.constraint(predecessor.end().plus(delay).le(successor.end())).
In other words, end of predecessor
plus delay
must be less than or equal to end of successor
.
When one of the two interval variables is absent, then the constraint is satisfied.
See
- IntervalVar.endBeforeEnd is equivalent function on IntervalVar.
- Model.constraint
- IntervalVar.start, IntervalVar.end
- IntExpr.le
endBeforeStart()
endBeforeStart(
predecessor
:IntervalVar
,successor
:IntervalVar
,delay
:number
|IntExpr
):void
Creates a precedence constraint between two interval variables.
Parameters
Parameter | Type | Default value |
---|---|---|
predecessor | IntervalVar | undefined |
successor | IntervalVar | undefined |
delay | number | IntExpr | 0 |
Returns
void
Remarks
Same as:
model.constraint(predecessor.end().plus(delay).le(successor.start())).
In other words, end of predecessor
plus delay
must be less than or equal to start of successor
.
When one of the two interval variables is absent, then the constraint is satisfied.
See
- IntervalVar.endBeforeStart is equivalent function on IntervalVar.
- Model.constraint
- IntervalVar.start, IntervalVar.end
- IntExpr.le
endOf()
endOf(
interval
:IntervalVar
):IntExpr
Creates an integer expression for the end of an interval variable.
Parameters
Parameter | Type |
---|---|
interval | IntervalVar |
Returns
Remarks
If the interval is absent, the resulting expression is also absent.
Example
In the following example, we constraint interval variable y
to start after the end of y
with a delay of at least 10. In addition, we constrain the length of x
to be less or equal to the length of y
.
let model = new CP.Model;
let x = model.intervalVar({ name: "x", ... });
let y = model.intervalVar({ name: "y", ... });
model.constraint(model.endOf(x).plus(10).le(model.startOf(y)));
model.constraint(model.lengthOf(x).le(model.lengthOf(y)));
When x
or y
is absent then value of both constraints above is absent and therefore they are satisfied.
See
- IntervalVar.end is equivalent function on IntervalVar.
- Function Model.endOr is a similar function that replaces value absent by a constant.
endOr()
endOr(
interval
:IntervalVar
,absentValue
:number
):IntExpr
Creates an integer expression for the end of the interval variable. If the interval is absent, then its value is absentValue
.
Parameters
Parameter | Type |
---|---|
interval | IntervalVar |
absentValue | number |
Returns
Remarks
This function is equivalent to endOf(interval).guard(absentValue)
.
See
eq()
eq(
arg1
:number
|IntExpr
,arg2
:number
|IntExpr
):BoolExpr
Creates Boolean expression arg1
= arg2
.
Parameters
Parameter | Type |
---|---|
arg1 | number | IntExpr |
arg2 | number | IntExpr |
Returns
Remarks
If one of the arguments has value absent, then the resulting expression also has value absent.
Use function Model.constraint to create a constraint from this expression.
Same as IntExpr.eq.
forbidEnd()
forbidEnd(
interval
:IntervalVar
,func
:IntStepFunction
):void
Constrains the end of the interval variable to be outside of the zero-height segments of the step function.
Parameters
Parameter | Type |
---|---|
interval | IntervalVar |
func | IntStepFunction |
Returns
void
Remarks
This function is equivalent to:
model.constraint(model.ne(model.stepFunctionEval(func, interval.end()), 0));
I.e., the function value at the end of the interval variable cannot be zero.
See
- IntervalVar.forbidEnd for the equivalent function on IntervalVar.
- Model.forbidStart for similar function that constrains start an interval variable.
- Model.stepFunctionEval for evaluation of a step function.
forbidExtent()
forbidExtent(
interval
:IntervalVar
,func
:IntStepFunction
):void
Forbid the interval variable to overlap with segments of the function where the value is zero.