Note
You can download this example as a Jupyter notebook or start it in interactive mode.
Modifying Models#
Given a model that is already built and possibly optimized, the user might want to modify single constraint or variable bounds by means of correction or exploration of the feasible space.
In the following we show how single elements can be tweaked or rewritten. Let’s start with the simple model of the Getting Started
section.
[1]:
import pandas as pd
import xarray as xr
import linopy
[2]:
m = linopy.Model()
time = pd.Index(range(10), name="time")
x = m.add_variables(
lower=0,
coords=[time],
name="x",
)
y = m.add_variables(lower=0, coords=[time], name="y")
factor = pd.Series(time, index=time)
con1 = m.add_constraints(3 * x + 7 * y >= 10 * factor, name="con1")
con2 = m.add_constraints(5 * x + 2 * y >= 3 * factor, name="con2")
m.add_objective(x + 2 * y)
m.solve()
m.solve()
sol = m.solution.to_dataframe()
sol.plot(grid=True, ylabel="Optimal Value")
Restricted license - for non-production use only - expires 2025-11-24
Read LP format model from file /tmp/linopy-problem-eh1y6qp0.lp
Reading time = 0.00 seconds
obj: 20 rows, 20 columns, 40 nonzeros
Gurobi Optimizer version 11.0.2 build v11.0.2rc0 (linux64 - "Ubuntu 24.04 LTS")
CPU model: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 20 rows, 20 columns and 40 nonzeros
Model fingerprint: 0x3434cd3b
Coefficient statistics:
Matrix range [2e+00, 7e+00]
Objective range [1e+00, 2e+00]
Bounds range [0e+00, 0e+00]
RHS range [3e+00, 9e+01]
Presolve removed 2 rows and 2 columns
Presolve time: 0.00s
Presolved: 18 rows, 18 columns, 36 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 0.0000000e+00 7.312500e+01 0.000000e+00 0s
18 1.2879310e+02 0.000000e+00 0.000000e+00 0s
Solved in 18 iterations and 0.01 seconds (0.00 work units)
Optimal objective 1.287931034e+02
Restricted license - for non-production use only - expires 2025-11-24
Read LP format model from file /tmp/linopy-problem-02dshuwq.lp
Reading time = 0.00 seconds
obj: 20 rows, 20 columns, 40 nonzeros
Gurobi Optimizer version 11.0.2 build v11.0.2rc0 (linux64 - "Ubuntu 24.04 LTS")
CPU model: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 20 rows, 20 columns and 40 nonzeros
Model fingerprint: 0x3434cd3b
Coefficient statistics:
Matrix range [2e+00, 7e+00]
Objective range [1e+00, 2e+00]
Bounds range [0e+00, 0e+00]
RHS range [3e+00, 9e+01]
Presolve removed 2 rows and 2 columns
Presolve time: 0.00s
Presolved: 18 rows, 18 columns, 36 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 0.0000000e+00 7.312500e+01 0.000000e+00 0s
18 1.2879310e+02 0.000000e+00 0.000000e+00 0s
Solved in 18 iterations and 0.01 seconds (0.00 work units)
Optimal objective 1.287931034e+02
[2]:
<Axes: xlabel='time', ylabel='Optimal Value'>

The figure above shows the optimal values of x(t)
and y(t)
.
Varying lower and upper bounds#
Now, let’s say we want to set the lower bound of x(t)
to 1. This would translate to:
[3]:
x.lower = 1
Note
The same could have been achieved by calling m.variables.x.lower = 1
Let’s solve it again!
[4]:
m.solve()
sol = m.solution.to_dataframe()
sol.plot(grid=True, ylabel="Optimal Value")
Restricted license - for non-production use only - expires 2025-11-24
Read LP format model from file /tmp/linopy-problem-1qcv8a7y.lp
Reading time = 0.00 seconds
obj: 20 rows, 20 columns, 40 nonzeros
Gurobi Optimizer version 11.0.2 build v11.0.2rc0 (linux64 - "Ubuntu 24.04 LTS")
CPU model: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 20 rows, 20 columns and 40 nonzeros
Model fingerprint: 0x6d88cc41
Coefficient statistics:
Matrix range [2e+00, 7e+00]
Objective range [1e+00, 2e+00]
Bounds range [1e+00, 1e+00]
RHS range [3e+00, 9e+01]
Presolve removed 4 rows and 4 columns
Presolve time: 0.00s
Presolved: 16 rows, 16 columns, 32 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 1.3085714e+02 0.000000e+00 0.000000e+00 0s
0 1.3085714e+02 0.000000e+00 0.000000e+00 0s
Solved in 0 iterations and 0.01 seconds (0.00 work units)
Optimal objective 1.308571429e+02
[4]:
<Axes: xlabel='time', ylabel='Optimal Value'>

[5]:
sol
[5]:
x | y | |
---|---|---|
time | ||
0 | 1.0 | 0.000000 |
1 | 1.0 | 1.000000 |
2 | 1.0 | 2.428571 |
3 | 1.0 | 3.857143 |
4 | 1.0 | 5.285714 |
5 | 1.0 | 6.714286 |
6 | 1.0 | 8.142857 |
7 | 1.0 | 9.571429 |
8 | 1.0 | 11.000000 |
9 | 1.0 | 12.428571 |
We see that the new lower bound of x is binding across all time steps.
Of course the implementation is flexible over the dimensions, so we can pass non-scalar values:
[6]:
x.lower = xr.DataArray(range(10, 0, -1), coords=(time,))
[7]:
m.solve()
sol = m.solution.to_dataframe()
sol.plot(grid=True, ylabel="Optimal Value")
Restricted license - for non-production use only - expires 2025-11-24
Read LP format model from file /tmp/linopy-problem-ul7x9ezm.lp
Reading time = 0.00 seconds
obj: 20 rows, 20 columns, 40 nonzeros
Gurobi Optimizer version 11.0.2 build v11.0.2rc0 (linux64 - "Ubuntu 24.04 LTS")
CPU model: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 20 rows, 20 columns and 40 nonzeros
Model fingerprint: 0x794fcf01
Coefficient statistics:
Matrix range [2e+00, 7e+00]
Objective range [1e+00, 2e+00]
Bounds range [1e+00, 1e+01]
RHS range [3e+00, 9e+01]
Presolve removed 14 rows and 14 columns
Presolve time: 0.00s
Presolved: 6 rows, 6 columns, 12 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 1.5100000e+02 0.000000e+00 0.000000e+00 0s
0 1.5100000e+02 0.000000e+00 0.000000e+00 0s
Solved in 0 iterations and 0.01 seconds (0.00 work units)
Optimal objective 1.510000000e+02
[7]:
<Axes: xlabel='time', ylabel='Optimal Value'>

You can manipulate the upper bound of a variable in the same way.
Varying Constraints#
A similar functionality is implemented for constraints. Here we can modify the left-hand-side, the sign and the right-hand-side.
Assume we want to relax the right-hand-side of the first constraint con1
to 8 * factor
. This would translate to:
[8]:
con1.rhs = 8 * factor
Note
The same could have been achieved by calling m.constraints.con1.rhs = 8 * factor
Let’s solve it again!
[9]:
m.solve()
sol = m.solution.to_dataframe()
sol.plot(grid=True, ylabel="Optimal Value")
Restricted license - for non-production use only - expires 2025-11-24
Read LP format model from file /tmp/linopy-problem-d3x0eosc.lp
Reading time = 0.00 seconds
obj: 20 rows, 20 columns, 40 nonzeros
Gurobi Optimizer version 11.0.2 build v11.0.2rc0 (linux64 - "Ubuntu 24.04 LTS")
CPU model: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 20 rows, 20 columns and 40 nonzeros
Model fingerprint: 0xf68e6558
Coefficient statistics:
Matrix range [2e+00, 7e+00]
Objective range [1e+00, 2e+00]
Bounds range [1e+00, 1e+01]
RHS range [3e+00, 7e+01]
Presolve removed 14 rows and 14 columns
Presolve time: 0.00s
Presolved: 6 rows, 6 columns, 12 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 1.2700000e+02 2.857143e-01 0.000000e+00 0s
1 1.2707882e+02 0.000000e+00 0.000000e+00 0s
Solved in 1 iterations and 0.01 seconds (0.00 work units)
Optimal objective 1.270788177e+02
[9]:
<Axes: xlabel='time', ylabel='Optimal Value'>

In contrast to previous figure, we now see that the optimal value of y
does not reach values above 10 in the end.
In the same way, we can modify the left-hand-side. Assume we want to weight y
with a coefficient of 8 in the constraints, this gives
[10]:
con1.lhs = 3 * x + 8 * y
Note: The same could have been achieved by calling
m.constraints['con1'].lhs = 3 * x + 8 * y
which leads to
[11]:
m.solve()
sol = m.solution.to_dataframe()
sol.plot(grid=True, ylabel="Optimal Value")
Restricted license - for non-production use only - expires 2025-11-24
Read LP format model from file /tmp/linopy-problem-_bpv8d83.lp
Reading time = 0.00 seconds
obj: 20 rows, 20 columns, 40 nonzeros
Gurobi Optimizer version 11.0.2 build v11.0.2rc0 (linux64 - "Ubuntu 24.04 LTS")
CPU model: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 20 rows, 20 columns and 40 nonzeros
Model fingerprint: 0x336d7cbb
Coefficient statistics:
Matrix range [2e+00, 8e+00]
Objective range [1e+00, 2e+00]
Bounds range [1e+00, 1e+01]
RHS range [3e+00, 7e+01]
Presolve removed 14 rows and 14 columns
Presolve time: 0.00s
Presolved: 6 rows, 6 columns, 12 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 1.1800000e+02 5.937500e-01 0.000000e+00 0s
1 1.1827941e+02 0.000000e+00 0.000000e+00 0s
Solved in 1 iterations and 0.01 seconds (0.00 work units)
Optimal objective 1.182794118e+02
[11]:
<Axes: xlabel='time', ylabel='Optimal Value'>

Varying the objective#
Varying the objective happens in the same way as for the left-hand-side of the constraint as it is a linear expression too. Note, when passing an unstacked linear expression, i.e. an expression with more than the _term
dimension, linopy
will automatically stack it.
So assume, we would like to modify the weight of y
in the objective function, this translates to:
[12]:
m.objective = x + 3 * y
[13]:
m.solve()
sol = m.solution.to_dataframe()
sol.plot(grid=True, ylabel="Optimal Value")
Restricted license - for non-production use only - expires 2025-11-24
Read LP format model from file /tmp/linopy-problem-1uc2lo21.lp
Reading time = 0.00 seconds
obj: 20 rows, 20 columns, 40 nonzeros
Gurobi Optimizer version 11.0.2 build v11.0.2rc0 (linux64 - "Ubuntu 24.04 LTS")
CPU model: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 20 rows, 20 columns and 40 nonzeros
Model fingerprint: 0x7030642e
Coefficient statistics:
Matrix range [2e+00, 8e+00]
Objective range [1e+00, 3e+00]
Bounds range [1e+00, 1e+01]
RHS range [3e+00, 7e+01]
Presolve removed 14 rows and 14 columns
Presolve time: 0.00s
Presolved: 6 rows, 6 columns, 12 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 1.3900000e+02 0.000000e+00 0.000000e+00 0s
0 1.3900000e+02 0.000000e+00 0.000000e+00 0s
Solved in 0 iterations and 0.01 seconds (0.00 work units)
Optimal objective 1.390000000e+02
[13]:
<Axes: xlabel='time', ylabel='Optimal Value'>

As a consequence, y
stays at zero for all time steps.
[14]:
m.objective
[14]:
Objective:
----------
LinearExpression: +1 x[0] + 3 y[0] + 1 x[1] ... +3 y[8] + 1 x[9] + 3 y[9]
Sense: min
Value: 139.0