Code
# The module pyomo.environ provides the components
# most commonly used for building Pyomo models:
import pyomo.environ as pyo
Wir verwenden in der Lehrveranstaltung das Python-Paket Pyomo. Pyomo ist ein Open-Source-Projekt und frei verfügbar. Pyomo ist eine algebraische Modellierungssprache zur benutzerfreundlichen Formulierung von mathematischen Optimierungsproblemen. Zum Lösen eines mit Pyomo formulierten Optimierungsproblems wird ein Solver benötigt. Pyomo unterstützt eine Vielzahl von kommerziellen und Open-Source-Solvern.
Links:
In diesem Abschnitt geben wir einen Überblick über andere High-Level Software zur Formulierung von Optimierungsproblemen, weil:
Liste von bekannten Algebraischen Modellierungssprachen:
Für Python gibt es unter anderem folgende Modellierungs- und Entwicklungsumgebungen für (MI)LPs:
linprog
and milp
: open source solver and interface, modelling in matrix/vector formListe der bekanntesten Solver:
TIPP: Wir empfehlen die Installation von Pyomo und den Solvern in einer conda environment! Eine Anleitung dafür finden Sie hier in der Lehrveranstaltung Programmiertechniken.
conda install -c conda-forge pyomo
pip install pyomo
conda install -c conda-forge glpk
cbc.exe
to the folder of your Python code. Use solver = pyo.SolverFactory('cbc.exe')
.conda install -c conda-forge highs
, and pip install highspy
. Copy the file highs.exe
from C:\Users\USERNAME\...\pkgs\highs-XY\Library\bin
to the folder of your Python code.conda install -c conda-forge glpk
conda install -c conda-forge coincbc
.conda install -c conda-forge highs
and pip install highspy
.Gurobi: first install the gurobi solver using your students.fhv.at
email account, cf. academic license. For the Python interface you have the following options:
conda config --add channels https://conda.anaconda.org/gurobi
, conda install gurobi
pip install gurobipy
Wir werden nun das folgende kleine LP mit Pyomo modellieren und mit verschiedenen Solvern lösen.
Fragestellung: Ein Bauer hat 3 Kühe, die in Summe 22 Gallonen (1 Gallone \(\simeq\) 3.785 Liter) Milch pro Woche geben. Aus der Milch kann er Eiscreme und Butter machen. Er braucht 2 Gallonen Milch für 1 kg Butter und 3 Gallonen Milch für 1 Gallone Eiscreme. Es gibt keine Lagerrestriktionen für Butter. Er kann maximal 6 Gallonen Eiscreme lagern. Er hat 6 Arbeitsstunden pro Woche für die Herstellung zur Verfügung. Für 4 Gallonen Eiscreme benötigt er 1 Stunde, für 1 kg Butter benötigt er ebenfalls 1 Stunde. Die gesamte Produktion kann er zu folgenden Preisen verkaufen (vollständiger Absatz): 5 USD pro Gallone Eiscreme, 4 USD pro kg Butter. Seine Kosten belaufen sich auf 1.5 USD pro Gallone Eiscreme und 1 USD pro kg Butter. Wie viele Gallonen Eiscreme und wie viele kg Butter soll er herstellen, sodass er seinen Profit maximiert?
Modellierung:
Entscheidungsvariablen:
Zielfunktion: Maximiere den Profit \(5x + 4y - (1.5 x + y) = 3.5x + 3y\).
Nebenbedingungen:
Das gesamte lineare Programm (LP):
\[ \begin{aligned} \text{max.}\; 3.5 x + 3 y & \\ \text{s. t.}\; x & \leq 6 \\ \frac{1}{4}x + y & \leq 6 \\ 3x + 2y & \leq 22 \\ x & \geq 0 \\ y & \geq 0 \end{aligned} \]
Quellen:
Model butter_and_ice_cream
Variables:
None
Objectives:
None
Constraints:
None
Entscheidungsvariablen:
pyo.NonNegativeReals
, pyo.NonNegativeIntegers
und pyo.Binary
, siehe hier.bounds
ist ein optionales Schlüsselwort zur Angabe eines Tupels, das Werte für die untere und obere Schranke enthält.Model butter_and_ice_cream
Variables:
x : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : 0 : None : 6 : False : True : Reals
y : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : 0 : None : None : False : True : NonNegativeReals
Objectives:
None
Constraints:
None
Ausdrücke (engl. expressions): Pyomo-Ausdrücke sind mathematische Formeln, die Entscheidungsvariablen enthalten. Pyomo-Ausdrücke werden verwendet, um die Zielfunktion und Nebenbedingungen zu definieren.
5*x + 4*y
1.5*x + y
Zielfunktion:
2 Var Declarations
x : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : 0 : None : 6 : False : True : Reals
y : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : 0 : None : None : False : True : NonNegativeReals
1 Objective Declarations
profit : Size=1, Index=None, Active=True
Key : Active : Sense : Expression
None : True : maximize : 5*x + 4*y - (1.5*x + y)
3 Declarations: x y profit
Nebenbedingungen: Eine Nebenbedingung besteht aus zwei Ausdrücken, die durch eine der logischen Beziehungen Gleichheit (==), Kleiner-als (<=) oder Größer-als (>=) getrennt sind.
2 Var Declarations
x : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : 0 : None : 6 : False : True : Reals
y : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : 0 : None : None : False : True : NonNegativeReals
1 Objective Declarations
profit : Size=1, Index=None, Active=True
Key : Active : Sense : Expression
None : True : maximize : 5*x + 4*y - (1.5*x + y)
2 Constraint Declarations
milk : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : -Inf : 3*x + 2*y : 22.0 : True
time : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : -Inf : 0.25*x + y : 6.0 : True
5 Declarations: x y profit time milk
Lösen des Optimierungsproblems: Ein Solver-Objekt wird mit SolverFactory erstellt und dann auf das Modell angewendet. Das optionale Schlüsselwort tee=True
bewirkt, dass der Solver seine Ausgabe ausdruckt.
solver = pyo.SolverFactory('cbc')
# solver = pyo.SolverFactory('glpk')
# solver = pyo.SolverFactory('appsi_highs')
# solver = pyo.SolverFactory('gurobi')
assert solver.available(), f"Solver {solver} is not available."
# If the solver is not available, i.e., solver.available() returns False,
# then the assert statement will raise an AssertionError with the message
# f"Solver {solver} is not available."
Welcome to the CBC MILP Solver
Version: 2.10.11
Build Date: Oct 26 2023
command line - /bin/cbc -printingOptions all -import /tmp/tmp4mgaf1j0.pyomo.lp -stat=1 -solve -solu /tmp/tmp4mgaf1j0.pyomo.soln (default strategy 1)
Option for printingOptions changed from normal to all
CoinLpIO::readLp(): Maximization problem reformulated as minimization
Coin0009I Switching back to maximization to get correct duals etc
Presolve 2 (0) rows, 2 (0) columns and 4 (0) elements
Statistics for presolved model
Problem has 2 rows, 2 columns (2 with objective) and 4 elements
Column breakdown:
1 of type 0.0->inf, 1 of type 0.0->up, 0 of type lo->inf,
0 of type lo->up, 0 of type free, 0 of type fixed,
0 of type -inf->0.0, 0 of type -inf->up, 0 of type 0.0->1.0
Row breakdown:
0 of type E 0.0, 0 of type E 1.0, 0 of type E -1.0,
0 of type E other, 0 of type G 0.0, 0 of type G 1.0,
0 of type G other, 0 of type L 0.0, 0 of type L 1.0,
2 of type L other, 0 of type Range 0.0->1.0, 0 of type Range other,
0 of type Free
Presolve 2 (0) rows, 2 (0) columns and 4 (0) elements
0 Obj 0 Dual inf 6.4999998 (2)
0 Obj 0 Dual inf 6.4999998 (2)
3 Obj 29
Optimal - objective value 29
Optimal objective 29 - 3 iterations time 0.002
Total time (CPU seconds): 0.00 (Wallclock seconds): 0.00
Zusammenfassung:
import pyomo.environ as pyo
model = pyo.ConcreteModel("butter_and_ice_cream")
model.x = pyo.Var(bounds=(0, 6))
model.y = pyo.Var(domain=pyo.NonNegativeReals)
model.profit = pyo.Objective(expr = 3.5*model.x + 3*model.y, sense = pyo.maximize)
model.time = pyo.Constraint(expr = 0.25*model.x + model.y <= 6)
model.milk = pyo.Constraint(expr = 3*model.x + 2*model.y <= 22)
solver = pyo.SolverFactory('cbc')
# solver = pyo.SolverFactory('glpk')
# solver = pyo.SolverFactory('appsi_highs')
# solver = pyo.SolverFactory('gurobi')
results = solver.solve(model, tee=True)
Welcome to the CBC MILP Solver
Version: 2.10.11
Build Date: Oct 26 2023
command line - /bin/cbc -printingOptions all -import /tmp/tmpwogw20x9.pyomo.lp -stat=1 -solve -solu /tmp/tmpwogw20x9.pyomo.soln (default strategy 1)
Option for printingOptions changed from normal to all
CoinLpIO::readLp(): Maximization problem reformulated as minimization
Coin0009I Switching back to maximization to get correct duals etc
Presolve 2 (0) rows, 2 (0) columns and 4 (0) elements
Statistics for presolved model
Problem has 2 rows, 2 columns (2 with objective) and 4 elements
Column breakdown:
1 of type 0.0->inf, 1 of type 0.0->up, 0 of type lo->inf,
0 of type lo->up, 0 of type free, 0 of type fixed,
0 of type -inf->0.0, 0 of type -inf->up, 0 of type 0.0->1.0
Row breakdown:
0 of type E 0.0, 0 of type E 1.0, 0 of type E -1.0,
0 of type E other, 0 of type G 0.0, 0 of type G 1.0,
0 of type G other, 0 of type L 0.0, 0 of type L 1.0,
2 of type L other, 0 of type Range 0.0->1.0, 0 of type Range other,
0 of type Free
Presolve 2 (0) rows, 2 (0) columns and 4 (0) elements
0 Obj 0 Dual inf 6.4999998 (2)
0 Obj 0 Dual inf 6.4999998 (2)
3 Obj 29
Optimal - objective value 29
Optimal objective 29 - 3 iterations time 0.002
Total time (CPU seconds): 0.00 (Wallclock seconds): 0.00
Analyse der Lösung:
2 Var Declarations
x : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : 0 : 4.0 : 6 : False : False : Reals
y : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : 0 : 5.0 : None : False : False : NonNegativeReals
1 Objective Declarations
profit : Size=1, Index=None, Active=True
Key : Active : Sense : Expression
None : True : maximize : 3.5*x + 3*y
2 Constraint Declarations
milk : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : -Inf : 3*x + 2*y : 22.0 : True
time : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : -Inf : 0.25*x + y : 6.0 : True
5 Declarations: x y profit time milk
profit : Size=1, Index=None, Active=True
Key : Active : Sense : Expression
None : True : maximize : 3.5*x + 3*y
Profit = 29.00 USD
x = 4.00 units
Time = 6.00 hours
optimal x = 4.0 gallons of ice cream
optimal y = 5.0 kg of butter
Sie wollen über die nächsten 24 Stunden \(t = 0, \ldots, 23\) Ihr Biomasse(heiz)kraftwerk mit maximalem Gesamterlös betreiben. Für jede Stunde können Sie die erzeugte Energiemenge \(x_t\) (MWh) zwischen 0 und 100 MWh wählen. Die Verkaufspreise \(p_t\) (€/MWh) für Ihre erzeugten Energiemengen \(x_t\) sind für alle 24 Stunden bekannt. Sie können in den nächsten 24 Stunden in Summe maximal 1000 MWh erzeugen. Von einer auf die nächste Stunde kann sich die erzeugte Energiemenge um maximal 20 MWh ändern. Bestimmen Sie einen optimalen Betriebsplan \(x_t\) für die nächsten 24 Stunden \(t = 0, \ldots, 23\).
Modellierung als LP:
Entscheidungsvariablen: Energiemengen \(x_t\) (MWh) für jede Stunde \(t = 0, \ldots, 23\) mit Schranken \(0 \leq x_t \leq 100\) MWh
Zielfunktion: Maximiere den Gesamterlös \(\sum_{t=0}^{23} p_t x_t\)
Nebenbedingungen:
Implementierung mit Pyomo:
times = np.arange(0, 24, step=1)
prices = np.array([23.46, 22.91, 22.35, 21.91, 22.13, 23.96,
34.83, 42.24, 47.45, 46.80, 44.58, 43.56,
42.33, 39.61, 40.49, 40.49, 38.83, 42.07,
44.27, 46.68, 43.27, 36.50, 31.71, 26.29])
plt.figure(figsize=(5, 3))
plt.step(times, prices, where='post', marker='.', color='red')
plt.xlabel('Stunde')
plt.ylabel('Preis [EUR/MWh]')
plt.grid(True)
Wir lösen das LP in Pyomo mit einer Indexmenge zum Indizieren der Entscheidungsvariablen und Nebenbedingungen, vgl. Pyomo: Sets:
model = pyo.ConcreteModel()
# Add the set of times to the model. This set will be used to index the variables:
model.T = pyo.Set(initialize=times)
# Add the variables to the model: energy (MWh) x_t produced in each hour
# include bounds on the variables: 0 <= x_t <= 100
model.x = pyo.Var(model.T, bounds=(0, 100))
# Add the objective function to the model including the sense of the optimization:
model.revenue = pyo.Objective(expr = pyo.quicksum(model.x[t]*prices[t] for t in model.T),
sense = pyo.maximize)
# Add the total energy constraint to the model:
model.resource = pyo.Constraint(expr = pyo.quicksum(model.x[t] for t in model.T) <= 1000)
# Add the constraints for ramping up: The @ decorates
# the ramp_up function as constraints for all times of the set model.T.
@model.Constraint(model.T)
def ramp_up(model, t):
if t >= 1:
# return the constraint expression:
return model.x[t] - model.x[t - 1] <= 20
else:
# skip this index:
return pyo.Constraint.Skip
# add the constraints for ramping down:
@model.Constraint(model.T)
def ramp_down(model, t):
if t >= 1:
# return the constraint expression:
return -20 <= model.x[t] - model.x[t - 1]
else:
# skip this index:
return pyo.Constraint.Skip
# model.pprint()
If you want to know what a decorator really does read for example this primer on decorators.
ok
optimal revenue = 42932.40 EUR
{0: 0.0,
1: 0.0,
2: 0.0,
3: 0.0,
4: 0.0,
5: 0.0,
6: 20.0,
7: 40.0,
8: 60.0,
9: 80.0,
10: 100.0,
11: 100.0,
12: 100.0,
13: 80.0,
14: 70.0,
15: 50.0,
16: 40.0,
17: 60.0,
18: 80.0,
19: 60.0,
20: 40.0,
21: 20.0,
22: 0.0,
23: 0.0}
# plot of the optimal production schedule:
plt.figure(figsize=(5, 6))
plt.subplot(2, 1, 1)
plt.step(times, prices, where='post', marker='.', color='red')
plt.xlabel('Stunde')
plt.ylabel('Preis [EUR/MWh]')
plt.grid(True)
plt.subplot(2, 1, 2)
plt.step(times, [sol_dict[t] for t in times], where='post', marker='.', color='green')
plt.xlabel('Stunde')
plt.ylabel('Erzeugung [MWh]')
plt.grid(True)
plt.tight_layout()
# Alternative plot of the optimal production schedule
# as a pandas dataframe with the times as index:
df = pd.DataFrame.from_dict(sol_dict, orient='index', columns=['x'])
display(df)
df.plot(drawstyle="steps-post", figsize=(5, 3), legend=False, color='green',
xlabel='Stunde', ylabel='Erzeugung [MWh]', marker='.', grid=True);
x | |
---|---|
0 | 0.0 |
1 | 0.0 |
2 | 0.0 |
3 | 0.0 |
4 | 0.0 |
5 | 0.0 |
6 | 20.0 |
7 | 40.0 |
8 | 60.0 |
9 | 80.0 |
10 | 100.0 |
11 | 100.0 |
12 | 100.0 |
13 | 80.0 |
14 | 70.0 |
15 | 50.0 |
16 | 40.0 |
17 | 60.0 |
18 | 80.0 |
19 | 60.0 |
20 | 40.0 |
21 | 20.0 |
22 | 0.0 |
23 | 0.0 |
Lösen Sie das folgende lineare Programm (LP) mit Pyomo, d. h.:
Gasoline-Mixture Problem: A gasoline refiner needs to produce a cost-minimizing blend of ethanol and traditional gasoline. The blend needs to have at least 65 % burning efficiency and a pollution level no greater than 85 %. The burning efficiency, pollution level, and per-ton cost of ethanol and traditional gasoline are given in the following table:
Product | Efficiency [%] | Pollution [%] | Cost [$/ton] |
---|---|---|---|
Gasoline | 70 | 90 | 200 |
Ethanol | 60 | 80 | 220 |
Quelle: Sioshansi, Ramteen; Conejo, Antonio J. (2017): Optimization in Engineering: Models and Algorithms. 2017, Springer. Seite 24 f.