constraint
The constraint object is a nested list of sum
objects. Thus, the constraint object is and contains sum
objects. Fundamentally, a sum
is defined much like a for
loop in many programming languages. It has one or more variables that are being iterated over, start and end conditions, and something that goes on in the body of the loop. In this case, what goes on inside the body of the loop is defined by the exprMain
field containing an expression.
When the sum
is executed, child sums are executed before the exprMain
of the parent. The result of exprMain
is added to the overall solution score or collected in a variable for use by the parent, signaled by the use of resultVar
in the child sum.
In the example below, the parent sum
is iterating over dimension T (time) and state (dimension SZ in this case). For each row of the set, a child sum
is executed with the resultVar
of sum_shifts
. This resultVar
is then used in the exprMain
of the parent.
If resultVar
was not specified, then the child sum
's exprMain
would be added to the solution score, but since in this case it is specified, only the parent's exprMain
is added to the solution score. Details of all constraint
fields and the different type of iterators are given in more detail below.
Example:
{
"constraint": {
"CID": "O3",
"enabled": true,
"type": "objective",
"comment": "Objective: Cover Requirements",
"sumIter": "iterDim",
"iterDim": "T",
"iterVars": [ "t" ],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "SZ",
"iterVars": [ "s" ],
"exprMain": "EQP(sum_shifts, cover_array(t, s, req), cover_array(t, s, wu), cover_array(t, s, wo))",
"sums": [
{
"sumIter": "iterDim",
"iterDim": "R",
"iterVars": [ "r" ],
"exprMain": "A(r, t) = s",
"resultVar": "sum_shifts"
}
]
}
]
}
}
Root Fields:
CID
: Required. This is the constraint ID and must be unique within the PD. This CID is used for reporting purposes and in result data.type
: (Optional). Can be one of[hard|soft|objective]
but is not required. It is primarily for reporting and analysis purposes.hard
: This denotes that the constraint must be satisfied (in that it is minimized to zero) in order for the solution to be considered feasible.soft
: The constraint is not required to be satisfied for a solution to be considered feasible.objective
: The constraint is or is a part of the objective function.
comment
: (Optional). Purely descriptive information.enabled
: (Optional). Can be one of[true|false]
. Enables or disabled a constraint. All are enabled by default. This can be used for testing or debugging.
Fields for root and all child sums:
sumIter
: Required. Can be one of[iterArray|iterVar|iterDim]
. Each of these types has their own related fields, and are described in their own section below.iterArray
: The sum will iterate over rows of a given 2D array, with columns of that array named in order by theiterVars
. See below for this sum type's specific fields.iterDim
: The sum will iterate over either the R, T, or S dimensions of the atom array. See below for this sum type's specific fields.iterVar
: The sum will iterate over a start and end condition (expression). See below for this sum type's specific fields.
sums
: (Optional). Array of child sums.
Fields if sumIter = iterVar
:
iterVars
: Required. Names the variable that is being iterated over and available to expressions.exprFrom
: Required. The expression that defines the starting value of the variable (defined initerVars
). This can be as simple as0
or a complex expression such as(1 + T) / 2
.exprTo
: Required. The expression that defines the ending value of the variable. Depending uponexprToEq
, the sum will iterate either to one less than this value, or this exact value.exprToEq
: (Optional). One of[true|false]
. Sets whether theexprTo
is equal to or less than. By default, false.
Example:
{
"constraint": {
"CID": "O1",
"sumIter": "iterVar",
"exprFrom": "0",
"exprTo": "T - 1",
"iterVars": [ "t" ],
"exprMain": "cost(At(t), At(t + 1))"
}
}
Fields if sumIter = iterDim
:
iterVars
: Required. Names the variable that is being iterated over and available to expressions.iterDim
: Required. One of[R|T||S|SZ]
R
: Iterate over theR
(resource count) dimension of the atom array.T
: Iterate over theT
(time count) dimension of the atom array.S
: Iterate over theS
(state count) dimension of the atom array.SZ
: Same asS
but starts at 1 instead of 0. This is for cases where the 0th state represents an empty atom - a zero state.
Example:
{
"constraint": {
"CID": "C9",
"comment": "Constraint #9 - Days Off",
"penaltyVar": "hardPenalty",
"sumIter": "iterDim",
"iterDim": "R",
"iterVars": [ "r" ],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "T",
"iterVars": [ "t" ],
"exprMain": "ANY(r, t) * days_off(r, t)"
}
]
}
}
Fields if sumIter = iterArray
:
arrayName
: Required. The name of the array to iterate over.iterVars
: Required. Names the columns as variables coming from the set. So, the first variable is the first column in the set. In the example below,r, t, s, w
would correspond to the first four columns of the set.
Example:
{
"constraint": {
"CID": "C2",
"comment": "Constraint #2 - Origin and destination must be different",
"penaltyVar": "hardPenalty",
"sumIter": "iterArray",
"arrayName": "phi",
"iterVars": [
"r",
"s"
],
"sums": [
{
"sumIter": "iterDim",
"iterDim": "S",
"iterVars": [
"i"
],
"exprMain": "Xr(r, i) * Xr(s, i)"
}
]
}
}
iterVar
vs. iterDim
?
You might be wondering what the difference is between:
{
"constraint": {
"sumIter": "iterVar",
"exprFrom": "0",
"exprTo": "T",
"iterVars": [ "t" ]
}
}
...and...
{
"constraint": {
"sumIter": "iterDim",
"iterDim": "T",
"iterVars": [ "t" ]
}
}
The answer would be theoretically nothing other than perhaps easier readability and less opportunity to make a typo. However, there are two very important reasons to use iterDim
instead of iterVar
when iterating over atom array dimensions:
- The solver does not have to evalute the end expression on each loop iteration - this can make a big difference in tight loops, especially when running in debug mode.
- When
iterDim
is used in the rootconstraint
'ssum
, the solver can optimize the recalculation of the solution score by only recalculating portions of constraints that have changed.
Thus, in short, use iterDim
for any and all complete atom array dimension sum
's.