Erste Schritte

Grundlagen Operations Research

Diese Seite fasst die Julia-Grundlagen zusammen, die in den Vorlesungen und Übungen dieses Kurses vorkommen, nicht mehr und nicht weniger. Alle Zellen sind ausgeführt; am besten tippen Sie die Beispiele parallel in Ihrer eigenen REPL nach (siehe Installation & Setup).

Variablen und Typen

Variablen entstehen durch einfache Zuweisung. Einen Typ müssen Sie nicht angeben, Julia leitet ihn selbst ab:

kosten = 25          # ganze Zahl (Int64)
gewicht = 2.5        # Kommazahl (Float64)
name = "Auftrag 1"   # Zeichenkette (String)
zulaessig = true     # Wahrheitswert (Bool)

typeof(kosten), typeof(gewicht)
(Int64, Float64)

Gerechnet wird mit +, -, *, / und ^. Steht eine Zahl direkt vor einem Namen, ist das die Kurzform der Multiplikation; in den Modellen steht deshalb 20X1 statt 20 * X1. Alles hinter # ist ein Kommentar und wird nicht ausgeführt:

2 * kosten + 3gewicht   # 3gewicht bedeutet 3 * gewicht
57.5

In Zeichenketten können Sie Werte mit $ einsetzen (Interpolation). So geben wir im Kurs Ergebnisse aus:

println("$name kostet $kosten EUR und wiegt $gewicht Tonnen.")
Auftrag 1 kostet 25 EUR und wiegt 2.5 Tonnen.

Ganze Ausdrücke setzen Sie mit $(…) ein. Dieselbe Zeile erhalten Sie, wenn Sie die Teile mit Kommas aneinanderreihen, wie in den Ausgabezeilen der Kurs-Modelle:

println("Mit Zuschlag: $(kosten + 5) EUR")
println("Mit Zuschlag: ", kosten + 5, " EUR")
Mit Zuschlag: 30 EUR
Mit Zuschlag: 30 EUR

Kürzer geht es mit @show: Es gibt den Ausdruck zusammen mit seinem Wert aus, @show kosten + 5 also die Zeile kosten + 5 = 30. Das ist praktisch, um Ergebnisse schnell zu prüfen.

Vektoren und Matrizen

Vektoren schreibt man in eckigen Klammern. Wichtig: Julia zählt ab 1, nicht ab 0. Das erste Element ist v[1]:

d = [140, 110, 190, 120]   # z. B. Nachfragen von 4 Perioden

d[1], d[end], length(d), sum(d)
(140, 120, 4, 560)

Mit push! hängen Sie ein Element an, mit d[2:3] greifen Sie einen Ausschnitt heraus:

push!(d, 200)   # das ! signalisiert: d wird verändert
d[2:3]
2-element Vector{Int64}:
 110
 190

Matrizen trennen Spalten mit Leerzeichen und Zeilen mit Semikolon; der Zugriff erfolgt mit [Zeile, Spalte]:

c = [130 140 170;    # z. B. Transportkosten:
     210 150 130]    # 2 Anbieter x 3 Nachfrager

c[2, 3]
130

Ein Doppelpunkt allein steht für „alle“: c[1, :] ist die ganze erste Zeile.

c[1, :], sum(c[2, :])   # Zeile 1 und die Summe der Zeile 2
([130, 140, 170], 490)

Tupel und Dictionaries

Ein Tupel fasst mehrere Werte in runden Klammern zusammen, im Kurs z. B. eine Verbindung zwischen zwei Knoten samt ihrer Länge. Der Zugriff funktioniert wie beim Vektor, ändern lässt sich ein Tupel aber nicht:

kante = (1, 2, 55)   # von Knoten 1 nach Knoten 2, Länge 55

kante[3]
55

Ein Tupel können Sie in einzelne Variablen zerlegen. Mit derselben Schreibweise weisen Sie mehrere Werte auf einmal zu; ein Semikolon trennt dagegen zwei eigenständige Anweisungen in einer Zeile:

von, nach, laenge = kante       # Tupel zerlegen
f, h = 500, 1                   # zwei Zuweisungen auf einmal
a = [15, 15]; b = [13, 11, 6]   # zwei Anweisungen in einer Zeile

laenge, f, b
(55, 500, [13, 11, 6])

Ein Dictionary (Dict) ordnet jedem Schlüssel einen Wert zu, hier jedem Vorgang seine Dauer. Sie greifen über den Schlüssel zu, keys liefert alle Schlüssel (ihre Reihenfolge ist nicht festgelegt):

dauer = Dict(1 => 3, 2 => 2, 3 => 3)   # Vorgang => Dauer

dauer[2], keys(dauer)
(2, [3, 2, 1])

Ranges

Ein Bereich wie 1:5 steht für die Zahlen 1 bis 5. Das nutzen wir ständig für Indexmengen (z. B. „alle Perioden t∈{1,…,5}t \in \{1, \dots, 5\}“):

T = 1:5
collect(T)   # collect zeigt die Werte als Vektor
5-element Vector{Int64}:
 1
 2
 3
 4
 5

Broadcasting: der Punkt-Operator

Ein Punkt vor einem Operator oder nach einem Funktionsnamen wendet die Operation elementweise auf einen Vektor an:

mengen = [10, 20, 15]
preise = [2.0, 1.5, 3.0]

erloes = mengen .* preise   # elementweise multiplizieren
3-element Vector{Float64}:
 20.0
 30.0
 45.0
sum(erloes), maximum(erloes)
(95.0, 45.0)

Das Gleiche funktioniert mit Funktionen, z. B. value.(X) in JuMP: „lies den Wert jeder Variablen im Container X“.

Verzweigungen

Fallunterscheidungen folgen dem Muster if / elseif / else und enden (wie alle Blöcke in Julia) mit end:

bestand = 4

if bestand == 0
    println("Lager leer")
elseif bestand < 10
    println("Lager fast leer: $bestand Stück")
else
    println("Lager gefüllt")
end
Lager fast leer: 4 Stück

Vergleiche liefern Wahrheitswerte (==, !=, <, <=, >, >=), kombiniert wird mit && (und), || (oder) und ! (nicht).

Für kurze Entweder-oder-Fälle gibt es die Kurzform Bedingung ? A : B: Ist die Bedingung wahr, gilt A, sonst B.

meldung = bestand > 0 ? "lieferbar" : "ausverkauft"
"lieferbar"

In den Modellen steht sie z. B. als t == 1 ? 0 : I[t-1]: In Periode 1 gibt es noch keinen Lagerbestand aus der Vorperiode.

Schleifen

Eine for-Schleife läuft über einen Range oder Vektor:

for t in 1:3
    println("Periode $t: Nachfrage = $(d[t])")
end
Periode 1: Nachfrage = 140
Periode 2: Nachfrage = 110
Periode 3: Nachfrage = 190

Besteht jedes Element aus einem Tupel, können Sie es direkt im Schleifenkopf zerlegen:

kanten = [(1, 2, 55), (1, 3, 15), (2, 5, 16)]

for (i, j, laenge) in kanten
    println("Von $i nach $j: Länge $laenge")
end
Von 1 nach 2: Länge 55
Von 1 nach 3: Länge 15
Von 2 nach 5: Länge 16

Sehr oft brauchen wir Summen mit Laufindex, genau wie das Summenzeichen ∑\sum in der Mathematik:

sum(d[t] for t in 1:4)   # entspricht der Summe von d1 bis d4
560

Diese Schreibweise werden Sie in jedem JuMP-Modell wiedersehen, z. B. in @objective(modell, Min, sum(f * X[t] for t in T)).

Hinter dem Laufindex kann eine Bedingung mit if stehen, dann zählen nur die passenden Elemente. Passt gar keines (von Knoten 9 geht keine Kante aus), gibt init an, was die leere Summe ergeben soll:

ab1 = sum(k[3] for k in kanten if k[1] == 1)
ab9 = sum(k[3] for k in kanten if k[1] == 9; init = 0)

ab1, ab9   # Summe der Längen ab Knoten 1 und ab Knoten 9
(70, 0)

Mehrere Laufindizes trennen Sie mit Komma, das ergibt eine Doppelsumme:

sum(c[i, j] for i in 1:2, j in 1:3)   # alle Einträge der Matrix c
930

In eckigen Klammern entsteht aus derselben Schreibweise ein Vektor:

[t for t in 1:4 if d[t] > 115]   # Perioden mit Nachfrage über 115
3-element Vector{Int64}:
 1
 3
 4

Zwei for hintereinander, wie in [s for z in Z for s in S], wirken wie zwei geschachtelte Schleifen.

Funktionen

Eigene Funktionen definieren Sie mit function … end; der letzte Ausdruck ist automatisch der Rückgabewert:

function deckungsbeitrag(erloes, kosten)
    return erloes - kosten
end

deckungsbeitrag(500, 320)
180

Für Einzeiler gibt es die Kurzform:

quadrat(x) = x^2
quadrat(9)
81

Braucht die Kurzform doch mehrere Zeilen, stehen diese zwischen begin und end.

Manche Argumente haben einen Namen und stehen als name = wert am Ende des Aufrufs, hinter einem Semikolon oder Komma. So runden wir Ergebnisse:

round(2.4567; digits = 2), round(Int, 2.7)   # 2 Stellen, ganze Zahl
(2.46, 3)

Eine Funktion kann mehrere Werte zurückgeben, die Sie beim Aufruf wie ein Tupel zerlegen. Ein Unterstrich _ steht für einen Wert, den Sie nicht brauchen:

function spanne(v)
    return minimum(v), maximum(v)
end

_, groesste = spanne(d)
groesste
200

Ergebnisse prüfen

Die Selbstkontrollen der Übungen prüfen Ihr Ergebnis mit @assert: Ist die Bedingung dahinter wahr, passiert nichts; ist sie falsch, bricht Julia mit der angegebenen Meldung ab. Kommazahlen vergleichen wir dabei nicht mit ==, sondern mit isapprox und einer Toleranz (1e-4 steht für 0,0001):

ergebnis = 0.1 + 0.2   # Kommazahlen sind nur fast exakt

@assert isapprox(ergebnis, 0.3; atol = 1e-4) "Noch nicht korrekt."
println("Korrekt: ", ergebnis)
Korrekt: 0.30000000000000004

Wie geht es weiter?

Damit kennen Sie alle Sprachbausteine, die in den Kurs-Modellen vorkommen. Begegnet Ihnen dort eine unbekannte Funktion (etwa cld oder vcat), tippen Sie in der REPL ? und danach ihren Namen, dann zeigt Julia die Beschreibung an.

Weiter geht es mit Pakete & JuMP. Dort setzen wir daraus das erste Optimierungsmodell zusammen. Zum schnellen Nachschlagen dient das Cheatsheet Julia.