# 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 indices.

Note

This page covers the use of the “at” operator on arrays (mono-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 travelled 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
distanceTravelled <- 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 distanceTravelled = 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;
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 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];
```