Pakete & JuMP

Grundlagen Operations Research

Auf dieser Seite bauen wir Schritt für Schritt ein vollständiges Optimierungsmodell in JuMP auf, vom leeren Modell bis zur ausgewerteten Lösung. Als Beispiel dient das Frachtführer-Problem aus Vorlesung 1:

maximiere F=20X1+30X2u.d.N.X1+X2≤5X1+3X2≤9X1,X2≥0 \begin{aligned} \text{maximiere } \; & F = 20 X_1 + 30 X_2 \\ \text{u.d.N.} \quad & X_1 + X_2 \le 5 \\ & X_1 + 3 X_2 \le 9 \\ & X_1, X_2 \ge 0 \end{aligned}

Pakete laden

Julia-Funktionalität steckt in Paketen. Installiert haben Sie die Kurs-Pakete bereits in Installation & Setup (einmalig, mit ] add JuMP HiGHS Plots IJulia); in jedem Skript werden sie dann mit using geladen:

using JuMP, HiGHS
  • JuMP ist die Modellierungssprache: Sie schreiben Variablen, Zielfunktion und Nebenbedingungen fast wie auf dem Papier.
  • HiGHS ist der Solver, der das Modell tatsächlich löst, ein Open-Source-Verfahren, das den (dualen) Simplex-Algorithmus und Branch & Bound implementiert.

Das Modell aufbauen

Schritt 1: leeres Modell anlegen. Dabei geben wir an, welcher Solver später rechnen soll:

modell = Model(HiGHS.Optimizer)

Schritt 2: Entscheidungsvariablen deklarieren. @variable legt eine Variable an; Schranken schreiben Sie direkt dazu:

@variable(modell, X1 >= 0)   # Abnahmemenge Auftrag 1
@variable(modell, X2 >= 0)   # Abnahmemenge Auftrag 2

Schritt 3: Zielfunktion. Max oder Min, danach der Term:

@objective(modell, Max, 20X1 + 30X2)   # Umsatzerlöse in EUR

Schritt 4: Nebenbedingungen. Jede Nebenbedingung bekommt einen Namen (hier Paletten und Gewicht), so können wir später gezielt auf sie zugreifen:

@constraint(modell, Paletten, X1 + X2 <= 5)    # max. 5 Paletten
@constraint(modell, Gewicht,  X1 + 3X2 <= 9)   # max. 9 Tonnen

Mit print(modell) können Sie jederzeit prüfen, ob das Modell so aussieht wie auf dem Papier:

print(modell)
Max 20 X1 + 30 X2
Subject to
 Paletten : X1 + X2 ≤ 5
 Gewicht : X1 + 3 X2 ≤ 9
 X1 ≥ 0
 X2 ≥ 0

Lösen und die Solver-Ausgabe lesen

optimize! übergibt das Modell an HiGHS. Beim ersten Mal lassen wir die Solver-Ausgabe bewusst eingeschaltet:

optimize!(modell)
Running HiGHS 1.15.1 (git hash: 04024d701f): Copyright (c) 2026 under MIT licence terms
Includes third-party software components, see THIRD_PARTY_NOTICES.md for full details
Using BLAS: Apple Accelerate 
LP has 2 rows; 2 cols; 4 nonzeros
Coefficient ranges:
  Matrix  [1e+00, 3e+00]
  Cost    [2e+01, 3e+01]
  Bound   [0e+00, 0e+00]
  RHS     [5e+00, 9e+00]
Presolving model
2 rows, 2 cols, 4 nonzeros 0s
2 rows, 2 cols, 4 nonzeros 0s
Presolve reductions: rows 2(-0); columns 2(-0); nonzeros 4(-0) - Not reduced
Problem not reduced by presolve: solving the LP
Using dual simplex solver
  Iteration        Objective     Infeasibilities num(sum)
          0    -4.9999960786e+01 Ph1: 2(6); Du: 2(50) 0.0s
          2     1.2000000000e+02 Pr: 0(0) 0.0s

Model status        : Optimal
Simplex   iterations: 2
Objective value     :  1.2000000000e+02
P-D objective error :  0.0000000000e+00
HiGHS run time      :          0.00

Die wichtigsten Zeilen dieser Ausgabe:

  • Presolve: HiGHS vereinfacht das Modell vor dem eigentlichen Lösen (entfernt z. B. überflüssige Zeilen und Spalten).
  • Simplex iterations: so viele Basistausche hat der Simplex-Algorithmus gebraucht (vgl. Vorlesung 2).
  • Model status: Optimal, es wurde eine optimale Lösung gefunden.
  • Objective value: der optimale Zielfunktionswert F*F^*.

Im Kurs schalten wir die Ausgabe fast immer mit set_silent(modell) ab. Direkt nach Model(...) aufgerufen, bleibt die Konsole sauber und nur unsere eigenen println-Zeilen erscheinen.

Ergebnisse abfragen

Nach dem Lösen prüfen Sie zuerst den Status und lesen dann die Werte aus:

termination_status(modell)
OPTIMAL::TerminationStatusCode = 1

OPTIMAL heißt: Die Lösung ist optimal. Erst dann sind die folgenden Abfragen sinnvoll:

println("F* = ", objective_value(modell),
        " mit X1 = ", value(X1), ", X2 = ", value(X2))
F* = 120.0 mit X1 = 3.0, X2 = 2.0
WarnungErgebnisse ausgeben

Auf dieser Seite steht unter jeder Zelle automatisch der Wert ihrer letzten Zeile. Beim eigenen Ausführen ist das anders: Zed zeigt nur den Wert der letzten Zeile einer Zelle, im Terminal (julia datei.jl) erscheint gar nichts. Geben Sie Ergebnisse deshalb ausdrücklich aus, mit println wie oben oder kürzer mit @show. So gibt @show value(X1) die Zeile value(X1) = 3.0 aus.

Die drei Statuswerte, die Ihnen in diesem Kurs begegnen (vgl. Vorlesung 5):

Status Bedeutung
OPTIMAL optimale Lösung gefunden
INFEASIBLE das Modell ist unzulässig (kein zulässiger Punkt)
DUAL_INFEASIBLE das Modell ist unbeschränkt (Zielfunktion wächst ohne Grenze)

Indizierte Variablen

Bei zwei Variablen reichen die Namen X1 und X2. Größere Modelle schreiben wir wie auf dem Papier mit Index, also XjX_j. Dazu stehen die Daten getrennt vom Modell in Vektoren und einer Matrix:

c = [20, 30]   # c[j]: Zielfunktionskoeffizient von X[j]
a = [1 1;      # a[i, j]: Koeffizient von X[j] in Nebenbedingung i
     1 3]      #          (Zeile 1: Paletten, Zeile 2: Gewicht)
b = [5, 9]     # b[i]: rechte Seite der Nebenbedingung i

X[1:2] legt die Variablen X[1] und X[2] auf einmal an (JuMP nennt das einen Container). Die Zielfunktion ist eine Summe über den Index jj, und eine einzige @constraint-Zeile mit [i in 1:2] erzeugt je Index ii eine Nebenbedingung:

modell = Model(HiGHS.Optimizer)
set_silent(modell)

@variable(modell, X[1:2] >= 0)
@objective(modell, Max, sum(c[j] * X[j] for j in 1:2))
@constraint(modell, [i in 1:2], sum(a[i, j] * X[j] for j in 1:2) <= b[i])

print(modell)
Max 20 X[1] + 30 X[2]
Subject to
 X[1] + X[2] ≤ 5
 X[1] + 3 X[2] ≤ 9
 X[1] ≥ 0
 X[2] ≥ 0

Das ist dasselbe Modell wie oben. Die Zahlen stehen aber nur noch in den Daten, das Modell selbst besteht aus Symbolen und Indexmengen. Diese Schreibweise heißt implizite Form: Ändern sich die Zahlen, passen Sie nur die Daten an, die Modellformulierung bleibt gleich.

Nach dem Lösen liefert value.(X) (mit Broadcasting-Punkt) die Werte aller Variablen auf einmal:

optimize!(modell)
value.(X)
2-element Vector{Float64}:
 3.0
 2.0

JuMP merkt sich jede Variable unter ihrem Namen im Modell. Mit modell[:X] (Doppelpunkt vor dem Namen) rufen Sie den Container darüber ab, auch wenn die Julia-Variable X gerade nicht zur Hand ist. Die Hinweise in den Übungen nutzen diese Schreibweise, etwa um eine Variable auf einen Wert festzusetzen (force = true ist nötig, weil X[2] schon die Schranke >= 0 hat):

fix(modell[:X][2], 0; force = true)   # X[2] auf 0 festsetzen
optimize!(modell)

println("F = ", objective_value(modell), " mit X = ", value.(X))
F = 100.0 mit X = [5.0, 0.0]

Das Muster für jedes Kurs-Modell

Alle Modelle des Kurses folgen genau diesem Aufbau:

using JuMP, HiGHS

modell = Model(HiGHS.Optimizer)
set_silent(modell)

@variable(modell, ...)      # 1. Entscheidungsvariablen
@objective(modell, ..., ...) # 2. Zielfunktion (Max/Min)
@constraint(modell, ...)    # 3. Nebenbedingungen

optimize!(modell)           # 4. Lösen

@show termination_status(modell)  # 5. Status prüfen, dann:
@show objective_value(modell)     #    Zielfunktionswert und
@show value.(...)                 #    Variablenwerte ausgeben

Wie geht es weiter?

Damit können Sie jede Julia-Zelle des Kurses lesen und selbst ausführen. Alle weiteren JuMP-Bausteine (Variablenmatrizen, ganzzahlige Variablen, Sensitivitätswerte wie shadow_price und reduced_cost) finden Sie kompakt im Cheatsheet JuMP und in Aktion ab Übung 1.