At operator¶
The operator “at” is an operator whose first operand is an array or a list and whose following operands are integers (the indices). The number of indices must match the dimension of the array (1 in case of a list). Its value is the value of the element in the array at the given indice.
Note
This page covers the use of the “at” operator on arrays (one-dimensional or multi-dimensional). For a description of the use of the “at” operator on list variables, see list variables.
The 1-dimensional at operator¶
Assume that we model the behavior of a machine whose status can be on (1) or off (0). This status is modeled as a binary variable. Now the operating cost of this machine can depend on its status what would be modeled straightforwardly in LocalSolver as:
status <- bool();
mcost <- status ? onCost : offCost;
Note that onCost
and offCost
are not necessarily constants.
They can depend on other expressions, for instance we may have an optional
special technology (say a binary variable specialTechno
) impacting the
cost as follows: onCost <- 100 + 20 * specialTechno
.
The at
operator can be seen as a generalization of this conditional
expression (c ? a : b
). Let us generalize the above example to a machine
having several possible operating modes. Now the status is an integer variable
whose value is among {0, 1, 2, 3, 4, 5}
, and the operating cost depends on
the operating mode (the status
).
Operating mode
Operating cost
0
Cost 0
1
Cost 1
2
Cost 2
3
Cost 3
4
Cost 4
5
Cost 5
Here again cost0, ... cost5
are not necessarily constants. They can depend
on other expressions or decisions. It would be possible to model the operating
cost of this machine with 5 nested conditional expressions but this approach
would be tedious and inappropriate in case of hundreds of operating modes.
Instead the at
operator allows us to have all costs in an array and to
define the operating cost by simply accessing this array with
the index status
:
status <- int(0,5);
costs = {cost0, cost1, cost2, cost3, cost4, cost5};
// In LSP the at operator is naturally written with the [] notation on a map
mcost <- costs[status];
In the APIs, the behavior is the same but requires a supplementary step. Indeed,
although the LSP language enables you to directly use arrays, maps or lists, no
such thing is possible for other languages and your array must be converted to
an LSExpression
before use. This LSExpression
is an operator of type
O_Array
. It has no numeric value (calling getValue
on it is forbidden)
but contains an array of expressions instead. Expressions in the array can be
constant or not.
To convert a Python list to a LocalSolver array, you can simply
create an LSExpression with LSModel.create_expression()
then
add each element manually as operands or use the provided shortcut that
takes a Python list or a python iterable object.
status = model.int(0, 5)
vv = [ cost0, cost1, cost2, cost3, cost4, cost5 ]
# Convert the list to LSExpression.
lsv = model.array(vv)
# Creates the at (operator [] is overloaded in the Python API).
mcost = lsv[status]
To convert a C++ structure to a LocalSolver array, you can simply
create an LSExpression with createExpression()
then add each element manually as operands, or use the provided shortcut
that takes iterators pointing to the beginning and the end of your
structure.
LSExpression status = model.intVar(0, 5);
std::vector<int> vv { cost0, cost1, cost2, cost3, cost4, cost5 };
// Convert the vector to LSExpression.
LSExpression lsv = model.array(vv.begin(), vv.end());
// Creates the at (operator [] is overloaded in the C++ API).
LSExpression mcost = lsv[status];
To convert a C# list to a LocalSolver array, you can simply
create an LSExpression with LSModel.CreateExpression
then add each element manually as operands, or use the provided shortcut
that takes an enumerable object.
LSExpression status = model.Int(0, 5);
List<LSExpression> list = new List<LSExpression> { cost0, cost1, cost2, cost3, cost4, cost5 };
// Convert the list to LSExpression.
LSExpression lsv = model.Array(list);
// Creates the at
LSExpression mcost = model.At(lsv, status);
To convert a Java list to a LocalSolver array, you can simply
create an LSExpression with LSModel.createExpression
then add each element manually as operands, or use the provided shortcut
that takes an iterable object.
LSExpression status = model.intVar(0, 5);
List<LSExpression> list = Arrays.asList(cost0, cost1, cost2, cost3, cost4, cost5);
// Convert the list to LSExpression.
LSExpression lsv = model.array(list);
// Creates the at
LSExpression mcost = model.at(lsv, status);
The multi-dimensional at operator¶
The at
operator also provides a way to access arrays of arrays. It can be
really convenient to avoid creating quadratic expressions.
Assume that we model a TSP where the ith city visited is stored in the
expression city[i]
and where the distance between each pair of cities is
determined by the distance matrix distance
. The distance traveled from
the ith city to the next one can be simply defined by an at
operator on
the distance matrix with indices city[i]
and city[i+1]
:
city = {exprCity0, exprCity1, ..., exprCityn};
distance = {{d00, d01, ..., d0n}, {d10, d11, ...,d1n}, ..., {dn0, dn1, ..., dnn}};
// In LSP, even the multi-index at operator is naturally written
// with the [][] notation on a map
distanceTraveled <- distance[city[i]][city[i+1]];
As is the case with the 1-dimensional at
, the APIs’ equivalent of the
above code also requires creating an O_Array
expression, but this time it
will be an array of arrays.
std::vector<LSExpression> city {exprCity0, exprCity1, ..., exprCityn};
std::vector<std::vector<int> > distance =
{{d00, d01, ..., d0n}, {d10, d11, ...,d1n}, ..., {dn0, dn1, ..., dnn}};
LSExpression distanceArray = model.array();
for(int k = 0; k < n; k++)
distanceArray.addOperand(model.array(distance[k].begin(), distance[k].end()));
LSExpression distanceTraveled = model.at(distanceArray, city[i], city[i+1]);
Jagged arrays¶
Arrays of arrays do not have to represent square matrices. They can perfectly store jagged arrays:
a = { 1, 2, 3 };
b = { 4, 5 };
c = { 6 };
myArray <- {a, b, c};
Pitfalls¶
Arrays must have a uniform dimension¶
It is not required that arrays have the same number of elements (jagged arrays), but they must have the same dimension. Thus, it is not possible to mix, in the same expression, a 2-dimensional array, with a 3-dimensional array.
// Will throw an error because the first operand is an expression of
// dimension 0 (a boolean decision) whereas the second is an array of
// dimension 1
std::vector<int> a { 1, 2, 3 };
LSExpression array = model.array(a.begin(), a.end());
LSExpression error = model.array();
error.addOperand(model.bool());
error.addOperand(array);
Indices start at zero¶
The indexing of the array starts at 0. Hence when creating an at
expression
in the LSP language, the map must have a value defined for index 0.
For example, the following code will throw an exception:
status <- int(1,3);
costs[1] = 10;
costs[2] = 18.2;
costs[3] = 20;
// will throw an exception because indices of map "costs" do not start at zero
mcost <- costs[status];
Indeed when creating such an expression through a map, an O_Array
expression is implicitly created, hence indices in the map must be consecutive
and starting at 0, as in a regular array.
Array overflows¶
When the indices of an at
expression take values outside of the bounds of
the array, this expression is considered as undefined. Undefined expressions
are allowed in LocalSolver provided that they do not result in an undefined
contraints or an undefined objective, in which case the LocalSolver solution
has the Infeasible
status (or Inconsistent
if the solver can prove
that no feasible solution exists).
For instance the following code has no explicit constraint but the model will
be proven inconsistent because of the implicit constraints induced by the
at
operator. Indeed you can observe that no value of x
can prevent the
objective function to be undefined:
x <- int(-3, 3);
c[i in 0...30] <- 3*i + 1;
maximize c[-3 + 2*x] + c[3 - 2*x];
On the contrary, the following model is consistent even if for all values of
x
one branch of the condition will remain undefined:
x <- int(-3, 3);
c[i in 0...30] <- 3*i + 1;
maximize 2*x >= 3 ? c[-3 + 2*x] : c[3 - 2*x];
In the APIs, an operator of type O_Array
must be created¶
Altough the LSP language allows creating array
expressions from maps,
API functions require the explicit creation of an O_Array
expression.
See the C++ code below.
std::vector<int> vv { 10, 18, 20 };
LSExpression x = model.intVar(0, 2);
// Will not compile because vv is not an LSExpression
LSExpression wwx_bad = vv[x];
// OK since lsv is an LSExpression of type O_Array
LSExpression lsv = model.array(vv.begin(), vv.end());
LSExpression wwx_good = lsv[x];