Note

You can download this example as a Jupyter notebook or start it in interactive mode.

# Creating constraints

Constraints are created and at the same time assigned to the model using the function

```
model.add_constraints
```

where `model`

is a `linopy.Model`

instance. Again, we want to understand this function and its argument. So, let’s create a model first.

```
[1]:
```

```
from linopy import Model
import numpy as np
import pandas as pd
import xarray as xr
m = Model()
```

`linopy`

follows the convention that all variables stand on the left-hand-side (lhs) of a constraint. In contrast, constant values are on the right-hand-side (rhs). Given a variable `x`

which has to by lower than 10/3, the constraint would be formulated as

or

and **not** as

## Using arithmetic operations

Typically the lhs is given as a linear expression built by an arithmetic linear combination of variables, e.g.

```
[2]:
```

```
x = m.add_variables()
```

```
[3]:
```

```
lhs = 3 * x
lhs
```

```
[3]:
```

<linopy.LinearExpression> Dimensions: (_term: 1) Dimensions without coordinates: _term Data: coeffs (_term) int64 3 vars (_term) int64 0

When applying one of the operators `<=`

, `>=`

, `==`

to the expression, an anomymous constraint is built:

```
[4]:
```

```
con = lhs <= 10
con
```

```
[4]:
```

```
Anonymous Constraint:
---------------------
coeffs (_term) int64 3
vars (_term) int64 0
<=
10
```

Why is it anonymous? Because it is not yet added to the model. We can inspect the elements of the anonymous constraint:

```
[5]:
```

```
con.lhs
```

```
[5]:
```

<linopy.LinearExpression> Dimensions: (_term: 1) Dimensions without coordinates: _term Data: coeffs (_term) int64 3 vars (_term) int64 0

```
[6]:
```

```
con.rhs
```

```
[6]:
```

<xarray.DataArray ()> array(10)

The attributes of the `AnonymousConstraint`

are immutable, thus `con.rhs = 20`

would raise an error.

We can now add the constraint to the model by passing the `AnonymousConstraint`

to the `.add_constraint`

function.

```
[7]:
```

```
c = m.add_constraints(con, name='my-constraint')
c
```

```
[7]:
```

<linopy.Constraint 'my-constraint' ()> array(0)

Note the same output would be generated if passing lhs, sign and rhs as separate arguments to the function:

```
[8]:
```

```
m.add_constraints(lhs, "<=", 10, name='the-same-constraint')
```

```
[8]:
```

<linopy.Constraint 'the-same-constraint' ()> array(1)

Note that the return value of the operation is a `Constraint`

which contains the reference labels to the constraints in the optimization model. Also is redirects to its lhs, sign and rhs, for example we can call

```
[9]:
```

```
c.lhs
```

```
[9]:
```

<linopy.LinearExpression> Dimensions: (_term: 1) Dimensions without coordinates: _term Data: coeffs (_term) int64 3 vars (_term) int64 0

to inspect the lhs of a defined constraint.

### Multiplication with arrays

When multiplying variables with coefficients, the dimension handling follows the convention of `xarray`

. That is, non-overlapping dimensions are spanned and broadcasted. For example, let’s multiply `x`

with an array going from 0 to 5:

```
[10]:
```

```
coeff = xr.DataArray(np.arange(5), dims='my-dim')
coeff * x
```

```
[10]:
```

<linopy.LinearExpression> Dimensions: (my-dim: 5, _term: 1) Dimensions without coordinates: my-dim, _term Data: coeffs (my-dim, _term) int64 0 1 2 3 4 vars (my-dim, _term) int64 0 0 0 0 0

Now, an expression of shape 5 with one term is created.

**Note:** It is strongly recommended to use `xarray.DataArray`

’s for multiplying coefficients with `Variable`

’s. It is also possible to use numpy arrays, however these are less secure considering the dimension handling. It is not recommended to use `pandas`

objects, as these do not preserve the `linopy`

types.

## Using tuples

For long expression, it can be more performant to create linear expressions with tuples instead of arithmetic operations, as the latter are calculated iteratively. Therefore, the model’s `.linexpr`

combines the expression parallelly and also ensures the correct conversion of data types. Let’s create two other variables first

```
[11]:
```

```
y = m.add_variables()
z = m.add_variables()
```

and a expression using the `.linexpr`

function. Here, the convention is to pass pair of coefficients and variables for each term:

```
[12]:
```

```
tuples = (3, x), (-2, y), (6, z)
expr = m.linexpr(*tuples)
expr
```

```
[12]:
```

<linopy.LinearExpression> Dimensions: (_term: 3) Dimensions without coordinates: _term Data: coeffs (_term) int64 3 -2 6 vars (_term) int64 0 1 2

We can now use this expression in the `add_constraints`

function.

```
[13]:
```

```
con = m.add_constraints(expr >= 30)
```

Again, combining variables with arrays of coefficients is possible and more secure with the usage of tuples.

```
[14]:
```

```
coeff = xr.DataArray(range(3), dims='additional-dim')
tuples = (coeff, x), (-2, y), (6, z)
expr = m.linexpr(*tuples)
expr
```

```
[14]:
```

<linopy.LinearExpression> Dimensions: (additional-dim: 3, _term: 3) Dimensions without coordinates: additional-dim, _term Data: coeffs (additional-dim, _term) int64 0 -2 6 1 -2 6 2 -2 6 vars (additional-dim, _term) int64 0 1 2 0 1 2 0 1 2

Moreover, the usage of pandas objects as coefficients is possible. However in most cases, these have to have explicit dimension names, otherwise it will raise an error.

```
[15]:
```

```
coeff = pd.Series(range(3))
tuples = (coeff, x), (-2, y), (6, z)
try:
expr = m.linexpr(*tuples)
except ValueError as e:
print("This raises an error:", e)
```

```
This raises an error: different number of dimensions on data and dims: 1 vs 0
```

Correct would be:

```
[16]:
```

```
coeff = coeff.rename_axis('additional-dim')
tuples = (coeff, x), (-2, y), (6, z)
m.linexpr(*tuples)
```

```
[16]:
```

<linopy.LinearExpression> Dimensions: (additional-dim: 3, _term: 3) Coordinates: * additional-dim (additional-dim) int64 0 1 2 Dimensions without coordinates: _term Data: coeffs (additional-dim, _term) int64 0 -2 6 1 -2 6 2 -2 6 vars (additional-dim, _term) int64 0 1 2 0 1 2 0 1 2

## Using rules

Similar to the implementation in Pyomo, expressions and constraints can be created using a combination of a function and a set of coordinates to iterate over. For creating expressions, the function itself has to return a `ScalarLinearExpression`

which can be obtained by selecting single values of the variables are combining them:

```
[17]:
```

```
3 * x[0]
```

```
[17]:
```

```
ScalarLinearExpression(coeffs=(3,), vars=(0,), coords=None)
```

For example

```
[18]:
```

```
coords = pd.RangeIndex(10), ["a", "b"]
b = m.add_variables(0, 100, coords)
def bound(m, i, j):
if i % 2:
return (i - 1) * b[i - 1, j]
else:
return i * b[i, j]
expr = m.linexpr(bound, coords)
expr
```

```
[18]:
```

<linopy.LinearExpression> Dimensions: (dim_0: 10, dim_1: 2, _term: 1) Coordinates: * dim_0 (dim_0) int64 0 1 2 3 4 5 6 7 8 9 * dim_1 (dim_1) <U1 'a' 'b' Dimensions without coordinates: _term Data: coeffs (dim_0, dim_1, _term) int64 0 0 0 0 2 2 2 2 4 4 4 4 6 6 6 6 8 8 8 8 vars (dim_0, dim_1, _term) int64 3 4 3 4 7 8 7 ... 16 15 16 19 20 19 20

Note that the function’s first argument has to be the model itself, even though it might not be used in the function.

This functionality is also supported by the `.add_constraints`

function. When passing a function as a first argument, `.add_constraints`

expects `coords`

to by non-empty. The function itself has to return a `AnonymousScalarConstraint`

, as done by

```
[19]:
```

```
x[0] <= 3
```

```
[19]:
```

```
AnonymousScalarConstraint(lhs=ScalarLinearExpression(coeffs=(1,), vars=(0,), coords=None), sign='<=', rhs=3)
```

For example

```
[20]:
```

```
coords = pd.RangeIndex(10), ["a", "b"]
b = m.add_variables(0, 100, coords)
def bound(m, i, j):
if i % 2:
return (i - 1) * b[i - 1, j] >= i
else:
return i * b[i, j] == 0.
con = m.add_constraints(bound, coords=coords)
con.lhs
```

```
[20]:
```

<linopy.LinearExpression> Dimensions: (dim_0: 10, dim_1: 2, _term: 1) Coordinates: * dim_0 (dim_0) int64 0 1 2 3 4 5 6 7 8 9 * dim_1 (dim_1) <U1 'a' 'b' Dimensions without coordinates: _term Data: coeffs (dim_0, dim_1, _term) int64 0 0 0 0 2 2 2 2 4 4 4 4 6 6 6 6 8 8 8 8 vars (dim_0, dim_1, _term) int64 23 24 23 24 27 28 ... 35 36 39 40 39 40