Note

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

Migrating from Pyomo#

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:

[1]:
import linopy
import pandas as pd

m = linopy.Model()
coords = pd.RangeIndex(10), ["a", "b"]
x = m.add_variables(0, 100, coords, name='x')
x
[1]:
Variable (dim_0: 10, dim_1: 2)
------------------------------
[0, a]: x[0, a] ∈ [0, 100]
[0, b]: x[0, b] ∈ [0, 100]
[1, a]: x[1, a] ∈ [0, 100]
[1, b]: x[1, b] ∈ [0, 100]
[2, a]: x[2, a] ∈ [0, 100]
[2, b]: x[2, b] ∈ [0, 100]
[3, a]: x[3, a] ∈ [0, 100]
                ...
[6, b]: x[6, b] ∈ [0, 100]
[7, a]: x[7, a] ∈ [0, 100]
[7, b]: x[7, b] ∈ [0, 100]
[8, a]: x[8, a] ∈ [0, 100]
[8, b]: x[8, b] ∈ [0, 100]
[9, a]: x[9, a] ∈ [0, 100]
[9, b]: x[9, b] ∈ [0, 100]
[2]:
x[0, 'a']
[2]:
ScalarVariable: x[0, a]

Such a ScalarVariable is very light-weight and can be used in functions in order to create expressions, just like you know it from Pyomo. The following function shows how:

[3]:
def bound(m, i, j):
     if i % 2:
         return (i / 2) * x[i, j]
     else:
         return i * x[i, j]

expr = m.linexpr(bound, coords)
expr
[3]:
LinearExpression (dim_0: 10, dim_1: 2):
---------------------------------------
[0, a]: +0 x[0, a]
[0, b]: +0 x[0, b]
[1, a]: +0.5 x[1, a]
[1, b]: +0.5 x[1, b]
[2, a]: +2 x[2, a]
[2, b]: +2 x[2, b]
[3, a]: +1.5 x[3, a]
                ...
[6, b]: +6 x[6, b]
[7, a]: +3.5 x[7, a]
[7, b]: +3.5 x[7, b]
[8, a]: +8 x[8, a]
[8, b]: +8 x[8, b]
[9, a]: +4.5 x[9, a]
[9, b]: +4.5 x[9, b]

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

[4]:
x[0, 'a'] <= 3
[4]:
AnonymousScalarConstraint: +1 x[0, a] <= 3
[5]:
def bound(m, i, j):
     if i % 2:
         return (i / 2) * x[i, j] >= i
     else:
         return i * x[i, j]  == 0.

con = m.add_constraints(bound, coords=coords)
con
[5]:
Constraint `con0` (dim_0: 10, dim_1: 2):
----------------------------------------
[0, a]: +0 x[0, a]   = 0.0
[0, b]: +0 x[0, b]   = 0.0
[1, a]: +0.5 x[1, a] ≥ 1.0
[1, b]: +0.5 x[1, b] ≥ 1.0
[2, a]: +2 x[2, a]   = 0.0
[2, b]: +2 x[2, b]   = 0.0
[3, a]: +1.5 x[3, a] ≥ 3.0
                ...
[6, b]: +6 x[6, b]   = 0.0
[7, a]: +3.5 x[7, a] ≥ 7.0
[7, b]: +3.5 x[7, b] ≥ 7.0
[8, a]: +8 x[8, a]   = 0.0
[8, b]: +8 x[8, b]   = 0.0
[9, a]: +4.5 x[9, a] ≥ 9.0
[9, b]: +4.5 x[9, b] ≥ 9.0