# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .jl
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.19.4
#   kernel_info:
#     name: julia
#   kernelspec:
#     display_name: Julia
#     language: julia
#     name: julia
# ---

# %% [markdown]
# # 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](../lectures/lecture-01-graphisches-loesen.qmd):
#
# $$
# \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](setup.qmd) (einmalig, mit
# `] add JuMP HiGHS Plots IJulia`); in jedem Skript werden sie dann mit
# `using` geladen:

# %%
using JuMP, HiGHS

# %% [markdown]
# - **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)

# %% [markdown]
# **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

# %% [markdown]
# **Schritt 3: Zielfunktion.** `Max` oder `Min`, danach der Term:

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

# %% [markdown]
# **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

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

# %%
print(modell)

# %% [markdown]
# # 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)

# %% [markdown]
# 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^*$.
#
# > **Tip**
# >
# > 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)

# %% [markdown]
# `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))

# %% [markdown]
# > **Ergebnisse 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](../lectures/lecture-05-sonderfaelle.qmd)):
#
# | 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 $X_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

# %% [markdown]
# `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
# $j$, und eine einzige `@constraint`-Zeile mit `[i in 1:2]` erzeugt je
# Index $i$ 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)

# %% [markdown]
# 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)

# %% [markdown]
# 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))

# %% [markdown]
# # 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

# %% [markdown]
# # 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](../general/cheatsheet-jump.qmd) und in
# Aktion ab [Übung 1](../uebungen/uebung-01.qmd).
