This page is for an old version of Hexaly Optimizer. We recommend that you update your version and read the documentation for the latest stable release.

The at operator

The operator “at” is a binary operator whose first operand is an array or a list and whose second operand is an integer (the index). Its value is the value of the element in the array at the given index.

Principles

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). Lets 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 merely 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 equivalient of the above code requires creating an O_Array expression. Such an expression 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.

LSExpression status = model.intVar(0, 5);
std::vector<int> vv { cost0,cost1,cost2,cost3,cost4,cost5 };
LSExpression lsv = model.array(vv.begin(), vv.end());
LSExpression mcost = lsvn[status];

Pitfalls

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;
cost[2] = 18.2;
cost[3] = 20;

// will throw an exception because indices of map "costs" do not start at zero
mcost <- costs[status];

Indeed when creating such a 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.

Beware of out of bounds exceptions

An at expression must be valid for any assignment of values to variables, not only for feasible assignments. Indeed LocalSolver may start from an infeasible solution and during the search it may explore infeasible regions of the search space (as a shortcut to promising regions).

For example, the following code will throw an exception during the search:

x <- int(-5, 15);
y <- int(-5, 15);
constraint x + y > 0;
c[i in 0..30] <- 3*i + 1;

// Will throw an exception during search, as soon as x + y is negative
z <- c[x + y];

What happens here is that at some point we encounter a solution where x + y is negative, what make the at operator invalid. The min and max operators should be used to avoid this kind of issue, as follows:

x <- int(-5, 15);
y <- int(-5, 15);
constraint x + y > 0;
c[i in 0..30] <- 3*i + 1;

// OK since the index is now between 0 and 30
z <- c[max(0, x + y)];

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(10, 18, 20);
LSExpression wwx_good = lsv[x];