Från ekvation till optimum

Lär dig matematik genom att modellera den i GAMS

GAMS är ett språk för matematiska modeller: mängder, summor, ekvationer och optimering skrivs ungefär som du skriver dem på papper. Här går vi igenom notationen steg för steg – och löser riktiga problem på vägen.

6 kapitel1 interaktivt LP-labb10 övningar med facit1 fuskblad

Kapitel 00Kom igång

GAMS (General Algebraic Modeling System) är inte ett programmeringsspråk i vanlig mening. Det är ett modelleringsspråk: du beskriver ett matematiskt problem – mängder, data, variabler och ekvationer – och överlåter åt en lösare (solver) att hitta svaret. Det gör GAMS till ett ovanligt bra verktyg för att lära sig matematik, för du tvingas skriva problemet exakt så som det står i boken.

Matematiken
max z = Σi pi xi
då Σi xi ≤ 100, xi ≥ 0
Samma sak i GAMS
obj.. z =e= sum(i, p(i)*x(i));
kap.. sum(i, x(i)) =l= 100;
x.lo(i) = 0;

Så arbetar du med kursen

  1. Läs kapitlen i ordning – varje kapitel bygger på det föregående.
  2. Klistra in kodexemplen i en .gms-fil och kör dem. Filändelsen är alltid .gms och du kör med gams filnamn.gms.
  3. Gör övningarna i kapitel 10. Facit finns bakom varje uppgift – försök först själv.
  4. Använd live-labbet när du vill se hur en ändrad koefficient flyttar optimum.

Installera GAMS

GAMS är kommersiellt men har en gratis demoversion som räcker gott för allt i den här kursen. Ladda ner den från gams.com/download och installera. Vill du inte installera något kan du köra GAMS-kod gratis via NEOS Server i webbläsaren.

Notera

Demoversionen har storleksbegränsningar på modellerna. För kursens exempel (några tiotal variabler) är det aldrig ett problem – men om du bygger stora modeller behöver du en licens. Alla exempel här fungerar också i open source-verktyg som Pyomo eller JuMP om du hellre vill det.

Programmets anatomi

Varje GAMS-program har samma fem byggstenar. Lär dig ordningen – den återkommer i varje kapitel.

StegNyckelordMatematisk motsvarighet
1. IndexSetIndexmängder, i ∈ I
2. DataParameter, Table, ScalarKonstanter, vektorer, matriser
3. BeslutVariable, Positive VariableDe obekanta x, y
4. SambandEquationVillkor och målfunktion
5. LösModel, Solve”bestäm optimum”
* Vårt första program: räkna ut en summa
Set    i   "de tre talen"        / ett, tva, tre /;
Parameter v(i) "talens värde"   / ett 4, tva 7, tre 11 /;
Scalar   s   "summan av talen";

s = sum(i, v(i));
Display s;

Kör du det här får du utskriften s = 22.00. Ingen lösare, ingen optimering – bara ren matematik. Det är en bra början: i GAMS är en summa en summa.

Kapitel 01Mängder – matematikens index

Nästan all matematik bygger på index. I stället för att skriva x1, x2, x3 skriver vi xi, i ∈ I. I GAMS deklarerar du mängden en gång och använder den sedan som index överallt.

Matematiken
I = {vete, majs}
J = {nord, syd}
GAMS
Set i "grödor" / vete, majs /;
Set j "regioner" / nord, syd /;

Deklaration och innehåll

Mängdens element listas mellan snedstreck, separerade med kommatecken eller mellanslag. Texten inom citattecken är en beskrivning som hamnar i utskrifterna och är ovärderlig när modellen växer.

Set i "grödor"      / vete, majs /;
Set j "regioner"    / nord, syd  /;

* Intervall med * eller ..  ger nord, mellan, syd automatiskt
Set k "kvartal"     / q1*q4 /;
Set t "perioder"    / 2024*2030 /;

Display i, j, k, t;

Flerdimensionella mängder (par)

En mängd behöver inte vara en lista av enskilda objekt – den kan bestå av par. Det är precis vad en tabell med tillåtna kombinationer är: (i, j) ∈ IJ.

* Vilka grödor får odlas var? Bara dessa par finns.
Set växt(i,j) "tillåtna kombinationer" /
   vete.nord, vete.syd, majs.syd /;

* Summera bara över de par som finns:
Parameter areal(i,j);
areal(i,j) = 10;
Scalar total "summan över tillåtna par";
total = sum(växt(i,j), areal(i,j));   * = 30
Varför detta är viktigt

I vanlig programmering skriver du loopar och if-satser för att hoppa över ogiltiga kombinationer. I GAMS deklarerar du dem bara som icke-existerande i mängden – och alla summor och ekvationer respekterar det automatiskt. Det är kärnan i varför GAMS-modeller blir korta.

Alias – när samma index behövs två gånger

Vill du jämföra i med j över samma mängd (t.ex. avstånd mellan städer) behöver GAMS två namn på samma mängd. Det fixar Alias.

Set stad / Sthlm, Gbg, Malmö /;
Alias(stad, stad2);

Parameter avstånd(stad,stad2) "km";
avstånd("Sthlm","Gbg") = 470;
Display avstånd;

Mängdoperationer

Mängder kan byggas dynamiskt – ungefär som mängdlära med ∪, ∩ och ∖. Tecknet + betyder union, * snitt och - differens.

Set alla   / a, b, c, d /;
Set udda   / a, c /;
Set jamna  / b, d /;
Set union_     "alla igen"        ;
Set snitt      "gemensamma"       ;

union_(alla) = yes;          * kopiera hela mängden
snitt(udda)  = yes;
snitt(jamna) = no;           * ta bort det som finns i jamna
Display snitt;               * blir a, c

Symbolen yes/no är mängdens ”medlemskap”: ett element finns i mängden om dess värde är yes. Det gör att du kan bygga filtervillkor mitt i en modell.

Kapitel 02Parametrar, tabeller och matriser

En parameter är en talad tabell. Har den noll index är den en skalär, har den ett index är den en vektor, har den två index är den en matris. Det enda som skiljer dem åt i GAMS är hur många index du anger.

ObjektMatematikDeklarationExempel på värde
SkalärcScalar c;c = 3.5;
VektorpiParameter p(i);p("vete") = 12;
MatrisAijParameter A(i,j);A("vete","nord") = 1;
Tabellsamma som matrisTable A(i,j);skrivs som ett rutnät

Två sätt att fylla i data

Antingen inline mellan snedstreck, eller med tilldelningssatser. Tabellformen är överlägsen när du ska läsa in en matris från en lärobok.

Set i / vete, majs /;
Set j / nord, syd  /;

* 1) Inline
Parameter p(i) "pris per ton" / vete 12, majs 9 /;

* 2) Tabell – skrivs som den ser ut i boken
Table avkastning(i,j) "ton per hektar"
        nord   syd
vete      4      2
majs      1      6;

* 3) Tilldelning rad för rad
Parameter kostnad(i);
kostnad("vete") = 3;
kostnad("majs") = 2;

Display p, avkastning, kostnad;
Fällan

I en Table bestämmer kolumnrubriken vilket index som får värdet. Skriver du nord först blir det den första indexpositionen. Byter du ordning på rader i tabellen spelar ingen roll – men ordningen på kolumnrubrikerna måste matcha mängden.

Data som räknas fram

Parametrar behöver inte vara givna – de kan beräknas med matematiska uttryck. Här blir en kostnad som är proportionell mot avkastningen:

Parameter intakt(i,j) "intäkt per hektar";
intakt(i,j) = p(i) * avkastning(i,j);

Scalar snitt "genomsnittlig intäkt";
snitt = sum(i, sum(j, intakt(i,j))) / card(i) / card(j);

Parameter andel(i,j) "hur stor del av totalen";
andel(i,j) = intakt(i,j) / sum((i,j), intakt(i,j));   * notera dubbelsumman

Funktionerna card(mängd) ger antalet element – matematikens |I|. Den är ovärderlig för medelvärden.

Vanliga matematiska funktioner

GAMSMatematikKommentar
card(i)|I|Antal element i mängd
sum(i, x(i))Σi xiSumma
prod(i, x(i))Πi xiProdukt
smax(i, x(i))maxi xiStörsta värdet
smin(i, x(i))mini xiMinsta värdet
power(a,2) / a**2a²Potens
sqrt, exp, log√, ex, lnStandardfunktioner
abs(a), round(a)|a|Belopp, avrundning

Kapitel 03Summor, produkter och villkor

Sättet du skriver Σ på i GAMS är kursens kanske viktigaste enskilda färdighet. Formen är alltid densamma:

Matematiken
Σi ∈ I pi · xi
GAMS
sum(i, p(i)*x(i))

Tre saker att lägga märke till: indexmängden skrivs utan∈, indexet får inte förekomma fritt i uttrycket efter kommatecknet, och multiplikation skrivs med * – GAMS gissar aldrig.

Dubbelsummor

Σi Σj aij skrivs antingen nästlat eller med två index i samma summa. Den senare formen är kortare och vanligast:

Scalar s1, s2;
s1 = sum(i, sum(j, a(i,j)));   * nästlad
s2 = sum((i,j), a(i,j));       * kompakt – samma resultat

* Radsummor: index j summeras bort, i blir kvar
Parameter radsumma(i);
radsumma(i) = sum(j, a(i,j));

* Kolumnsummor: omvänt
Parameter kolsumma(j);
kolsumma(j) = sum(i, a(i,j));
Regeln att minnas

De index du summerar över försvinner ur resultatet. De index som blir kvar bestämmer resultatets dimension. Det är därför radsumma(i) har ett index men sum((i,j),…) blir en skalär.

Villkor med $

Dollartecknet är GAMS ”så att”-konstruktion. sum(i$villkor(i), x(i)) betyder Σ över de i där villkoret är sant.

* Summera bara positiva värden
Scalar pos;
pos = sum(i$ (v(i) > 0), v(i));

* Villkor på två index samtidigt
Parameter dyrt(i,j);
dyrt(i,j) = avkastning(i,j) $ (p(i) > 10);

* Tilldela bara där mängden är definierad – undviker division med noll
Parameter kvot(i,j);
kvot(i,j) $ avkastning(i,j) = intakt(i,j) / avkastning(i,j);

Ett komplett exempel: viktat medelvärde

Låt wi vara vikter som summerar till 1 och ri avkastningar. Då är portföljens avkastning Σ wi ri, och variansen kräver en dubbelsumma med kovariansmatrisen ΣiΣj wi wj σij:

Set i "tillgångar" / aktie, obligation, guld /;

Parameter r(i)   "förväntad avkastning" / aktie 0.08, obligation 0.03, guld 0.05 /;
Parameter w(i)   "portföljvikt";
w(i) = 1/card(i);                     * likaviktad portfölj

Parameter sigma(i,j) "kovarians (förenklad)";
sigma(i,i) = 0.04;
sigma(i,j) $ (not sameas(i,j)) = 0.005;

Scalar mu     "portföljavkastning";
Scalar varians "portföljvarians";

mu     = sum(i, w(i)*r(i));
varians = sum((i,j), w(i)*w(j)*sigma(i,j));

Display mu, varians;

sameas(i,j) är en inbyggd funktion som är sann när de två indexen pekar på samma element. Den används flitigt för diagonaler.

Kapitel 04Optimering: linjär programmering

Nu blir det matematik på riktigt. Ett linjärt programmeringsproblem (LP) består av en målfunktion och ett antal bivillkor – allt linjärt. Vi tar ett klassiskt produktionsplaneringsproblem och följer det från lärobokens formler till GAMS-kod.

Problemet
max z = 3x + 2y
då   x + 2y ≤ 8   (maskintid)
      3x + y ≤ 9   (arbetstid)
      x, y ≥ 0

Steg 1 – variabler

De obekanta blir Variable. Skriver du Positive Variable får du x ≥ 0 gratis; det motsvarar x.lo = 0.

Positive Variable x "antal enheter produkt X";
Positive Variable y "antal enheter produkt Y";
Variable          z "total vinst";

Steg 2 – ekvationer

I GAMS kallas både målfunktionen och bivillkoren för Equation. Skillnaden ligger bara i relationstecknet:

GAMSMatematikBetydelse
=e==Likhet (equality)
=l=≤Mindre än eller lika (less)
=g=≥Större än eller lika (greater)
Equation obj  "målfunktion";
Equation maskin "maskintid";
Equation arbeta "arbetstid";

obj..    z =e= 3*x + 2*y;
maskin.. x + 2*y =l= 8;
arbeta.. 3*x + y =l= 9;

Steg 3 – modell och lösning

Model verkstad / all /;                 * 'all' = alla ekvationer ingår
Solve verkstad using lp maximizing z;

Display x.l, y.l, z.l;

Resultatet blir x = 2, y = 3, z = 12. Kontrollera gärna för hand: maskintid 2 + 6 = 8 (fullt utnyttjad), arbetstid 6 + 3 = 9 (också fullt utnyttjad).

Vad betyder ”using lp”?

lp säger åt GAMS vilken typ av problem det är, så att rätt lösare väljs. Andra vanliga: mip (heltal), nlp (olinjärt), rmip (relaxation). Skriver du fel typ får du ofta ett obegripligt felmeddelande – kontrollera alltid att problemklassen stämmer med modellen.

Samma problem, men med mängder

Det stora värdet av GAMS kommer först när du generaliserar. Byt ut x, y mot xp över produkter p, och resurserna mot r:

Set p "produkter"      / X, Y /;
Set r "resurser"       / maskin, arbete /;

Parameter vinst(p)     / X 3, Y 2 /;
Parameter kapacitet(r) / maskin 8, arbete 9 /;
Table atgang(r,p) "åtgång per enhet"
           X    Y
maskin     1    2
arbete     3    1;

Positive Variable x(p) "producerad mängd";
Variable          z    "total vinst";

Equation obj "maximera vinsten";
Equation res(r) "resursbegränsning";

obj..    z =e= sum(p, vinst(p)*x(p));
res(r).. sum(p, atgang(r,p)*x(p)) =l= kapacitet(r);

Model verkstad2 / all /;
Solve verkstad2 using lp maximizing z;
Display x.l, z.l;

Exakt samma svar – men nu räcker det att lägga till en rad i tabellen för att modellen ska hantera en tredje produkt. Det är den enda ändringen. Jämför med att skriva om alla formler för hand.

Sambandet mellan de två modellerna

I den generella modellen motsvarar res(r)en ekvationsdeklaration, men två ekvationer när modellen genereras (en för varje r). GAMS skapar ekvationerna åt dig. I lösningsrapporten ser du dem som res(maskin) och res(arbete).

Målfunktioner som minimeras

Vill du minimera i stället skriver du bara minimizing. Ett transportproblem – minimera kostnaden för att frakta varor från lager i till kunder j – är ett typexempel:

Set i "lager"   / l1, l2 /;
Set j "kunder"  / k1, k2, k3 /;

Parameter tillgang(i) "utbud"   / l1 20, l2 30 /;
Parameter behov(j)    "efterfrågan" / k1 15, k2 20, k3 15 /;
Table kostnad(i,j) "kr per enhet"
       k1   k2   k3
l1      4    6    9
l2      5    3    7;

Positive Variable flode(i,j) "transporterad mängd";
Variable          tot       "totalkostnad";

Equation mal;
Equation utbud(i);
Equation efterfragan(j);

mal..              tot =e= sum((i,j), kostnad(i,j)*flode(i,j));
utbud(i)..         sum(j, flode(i,j)) =l= tillgang(i);
efterfragan(j)..   sum(i, flode(i,j)) =g= behov(j);

Model transport / all /;
Solve transport using lp minimizing tot;
Display flode.l, tot.l;

Observera symmetrin: rader i tabellen blir i-villkor, kolumner blir j-villkor. Att kunna läsa av en tabell så här är halva arbetet med all modellering.

Kapitel 05Live-labb: flytta optimum själv

Ändra koefficienterna nedan och se hur det tillåtna området, optimum och den genererade GAMS-koden förändras direkt. Det här är det snabbaste sättet att bygga intuition för vad ett bivillkor egentligen gör.

Indata

max z =· x +· y

Bivillkor  a·x + b·y ≤ c

abc
#1
#2
#3

Alla bivillkor är av formen a·x + b·y ≤ c. Sätt a = 0 för att släppa villkoret.

Det tillåtna området

GAMS-kod för exakt det problem du ser

--
Prova detta

1) Sätt målfunktionens koefficient för x till 2 och för y till 3. Optimum flyttar sig – varför? 2) Sätt #3 till 1·y ≤ 1 och se vad som händer med lösningen. 3) Gör ett bivillkor omöjligt (t.ex. 1·x + 1·y ≤ -1) och läs felmeddelandet i rutan.

Kapitel 06Linjär algebra i GAMS

GAMS är byggt för att skriva matriser och vektorer utan att du behöver tänka på loopar. Allt du gör med index görs ”på en gång” för alla element.

Skalärprodukt och norm

Matematiken
u · v = Σi uivi
‖u‖ = √(Σi ui²)
GAMS
dot = sum(i, u(i)*v(i));
norm_u = sqrt(sum(i, u(i)*u(i)));
Set i / 1*4 /;
Parameter u(i) / 1 1, 2 2, 3 3, 4 4 /;
Parameter v(i) / 1 2, 2 0, 3 -1, 4 5 /;

Scalar dot, normu, vinkel;
dot    = sum(i, u(i)*v(i));
normu  = sqrt(sum(i, u(i)*u(i)));

* Vinkeln mellan vektorerna: cos θ = u·v / (‖u‖‖v‖)
vinkel = arccos( dot / (normu * sqrt(sum(i, v(i)*v(i)))) );
Display dot, normu, vinkel;

Matrismultiplikation

C = A·B där A är m×n och B är n×p kräver ett summationsindex över n. Eftersom n-mängden redan används som index måste vi skapa ett alias:

Set m / r1*r2 /;
Set n / c1*c3 /;
Set p / q1*q2 /;
Alias(n, n2);                       * två namn på samma mängd

Parameter A(m,n) / r1.c1 1, r1.c2 2, r1.c3 3
                   r2.c1 4, r2.c2 5, r2.c3 6 /;
Parameter B(n,p) / c1.q1 1, c1.q2 0
                   c2.q1 2, c2.q2 1
                   c3.q1 0, c3.q2 3 /;

Parameter C(m,p) "produkten";
C(m,p) = sum(n, A(m,n)*B(n,p));

Display C;
Transponat och identitet

Transponat är bara ett indexbyte: At(n,m) = A(m,n);. Identitetsmatrisen skrivs I(m,n) $ sameas(m,n) = 1;. Att kontrollera A·A−1 = I är en bra övning – se kapitel 10.

Linjära ekvationssystem

Att lösa Ax = b är i GAMS samma sak som att lösa ekvationssystemet – det finns ingen speciell matrisoperator. Du skriver ut systemet rad för rad, med summan över kolumnindex:

Set i / 1*3 /;
Alias(i, j);

Table A(i,j) "koefficientmatris"
        1    2    3
1       2   -1    0
2       1    3    1
3       0    1    2;

Parameter b(i) / 1 5, 2 10, 3 7 /;

Variable x(i) "de obekanta";
Equation ekv(i) "rad i av systemet";
Equation dummy  "konstgjord målfunktion";

ekv(i)..   sum(j, A(i,j)*x(j)) =e= b(i);
dummy..    obj =e= 0;               * krävs: GAMS vill optimera något

Model system / all /;
Solve system using lp minimizing obj;
Display x.l;

Knepet med en konstgjord målfunktion som är noll gör att lösaren bara letar efter en tillåten punkt – vilken som helst – och den punkten är systemets lösning. Ett alternativ är att deklarera Solve system using cns; (constraint satisfaction), men LP-varianten fungerar överallt.

Finns lösningen?

Är systemet singulärt eller saknar lösning får du MODEL STATUS 4 (infeasible) i stället för ett svar. Det är matematisk information, inte ett programfel: ekvationerna är oförenliga. Gör om systemet för hand och jämför med radoperationer – GAMS och Gauss-elimination ger samma svar.

Matrisuttryck som blir optimering

Många centrala problem i linjär algebra är optimeringsproblem i förklädnad. Minstakvadratlösningen min ‖Ax − b‖² skrivs direkt:

Variable x(j);
Variable avvikelse(i);
Variable sumkvad;

Equation definiera(i);
Equation mal;

definiera(i).. avvikelse(i) =e= sum(j, A(i,j)*x(j)) - b(i);
mal..         sumkvad =e= sum(i, avvikelse(i)*avvikelse(i));

Model minstakvadrat / all /;
Solve minstakvadrat using nlp minimizing sumkvad;   * nlp – inte lp!

Här blir x fri (både positiva och negativa värden är tillåtna), och problemet är kvadratiskt – därför nlp.

Kapitel 07Heltal och logik

Många matematiska beslut är diskreta: man väljer ett objekt eller inte, man producerar hela enheter. Då byter du variabeltyp och lösaren gör ett enormt sökarbete åt dig.

DeklarationMatematikAnvänds till
Integer Variable n;n ∈ ℤAntal enheter, fordon, anställda
Binary Variable y;y ∈ {0,1}Ja/nej-beslut
Positive Variable x;x ≥ 0Mängder, flöden
Free Variable f;f ∈ ℝRestposter, avvikelser

Kappsäcksproblemet

Välj föremål så att värdet maximeras utan att vikten överstiger kapaciteten: max Σ vpyp då Σ wpyp ≤ K, yp ∈ {0,1}.

Set p "föremål" / karta, kamera, talt, bok, vin /;

Parameter varde(p) / karta 10, kamera 13, talt 8, bok 6, vin 12 /;
Parameter vikt(p)  / karta 2,  kamera 5,  talt 4, bok 3, vin 6  /;
Scalar    kapacitet / 15 /;

Binary Variable y(p) "1 om föremålet packas";
Variable        z    "totalt värde";

Equation mal, kap;
mal.. z =e= sum(p, varde(p)*y(p));
kap.. sum(p, vikt(p)*y(p)) =l= kapacitet;

Model ryggsack / all /;
Solve ryggsack using mip maximizing z;
Display y.l, z.l;

Tillordningsproblem – permutationer som optimering

Att hitta den bästa kopplingen mellan personer i och uppgifter j är ett permutationsproblem. Varje person får exakt en uppgift, varje uppgift exakt en person:

Set i "personer" / anna, bo, cissi /;
Set j "uppgifter" / disk, tvatt, stryk /;

Table kostnad(i,j) "tidsåtgång i minuter"
        disk  tvatt  stryk
anna      20     12     30
bo        25     18     20
cissi     15     25     22;

Binary Variable x(i,j) "1 om i gör j";
Variable        tot    "total tid";

Equation mal;
Equation varje_person(i);
Equation varje_uppgift(j);

mal..                tot =e= sum((i,j), kostnad(i,j)*x(i,j));
varje_person(i)..    sum(j, x(i,j)) =e= 1;
varje_uppgift(j)..   sum(i, x(i,j)) =e= 1;

Model schema / all /;
Solve schema using mip minimizing tot;
Display x.l, tot.l;

Antalet möjliga scheman är 3! = 6, och GAMS provar dem utan att du skriver en enda loop. Med 20 personer är antalet 20! ≈ 2,4·1018 – ändå löses problemet på bråkdelar av en sekund, tack vare branch-and-bound.

Logiska villkor med binära variabler

”Om vi producerar x måste vi starta maskinen (y = 1)” är inte linjärt – men det går att skriva linjärt med en stor konstant M:

* x > 0  =>  y = 1      skrivs som   x <= M*y
Equation koppling;
Scalar M / 1000 /;
koppling.. x =l= M*y;
Välj M med omsorg

En alldeles för stor M försämrar lösarens numerik och kan ge fel svar. Sätt M till den största värde x rimligen kan anta, eller använd x.up som övre gräns. Det här är ett av de vanligaste sätten att förstöra en helt korrekt modell.

Heltalsvariabler med gränser

Integer Variable n "antal maskiner";
n.lo = 1;      * minst 1
n.up = 5;      * högst 5
n.prior = 2;   * lösaren prioriterar denna vid branching

Kapitel 08Tolka lösningen – där matematiken blir insikt

Att få ett tal är inte samma sak som att förstå problemet. GAMS ger dig tre sorters information om varje variabel och ekvation: värde, gränser och marginalvärde.

AttributBetydelseLäs som
x.lLevel – lösningens värdex*, det optimala värdet
x.mMarginal – skuggpris / dualvärdeVad en enhets ändring är värd
x.lo, x.upUndre och övre gränsx ≥ 0 blir x.lo = 0
x.fxFasta värdet (låser variabeln)x = 5

Skuggpriset – kapitlets viktigaste begrepp

I verkstadsmodellen: maskin.m talar om hur mycket målfunktionen ökar om vi får en extra enhet maskintid. Är den 1,5 betyder det att z ökar med 1,5 per extra maskintimme – alltså är det värt upp till 1,5 kr att köpa en extra timme.

Display z.l;        * optimalt värde
Display x.l, y.l;   * optimala nivåer
Display maskin.m;   * skuggpris för maskintid
Display maskin.slack;   * hur mycket kapacitet som blev över
Tumregeln

Är slack = 0 är bivillkoret bindande och skuggpriset nästan alltid skilt från noll. Är slack > 0 finns outnyttjad kapacitet och skuggpriset är 0 – extra kapacitet är då värdelös. Det är LP-dualitetens kärna, och den syns direkt i utskriften.

Läs lösningsrapporten

Varje Solve skriver en rapport. Kontrollera alltid två rader innan du tror på siffrorna:

  • SOLVER STATUS – att lösaren själv fungerade. 1 Normal Completion är bra.
  • MODEL STATUS – att problemet hade en lösning. 1 Optimal, 2 Locally Optimal (för nlp), 4 Infeasible, 3 Unbounded.
Solve verkstad using lp maximizing z;

* Stoppa programmet om något gick fel
Abort $ (verkstad.modelstat <> 1) "Modellen löste sig inte korrekt";
Display "Optimum:", z.l;

Ett Abort $-test är god sed: hellre ett tydligt fel än vackra men meningslösa siffror.

Skriv ut som en rapport

Display duger för att felsöka, men för riktiga resultat används put som skriver till en textfil:

File res / resultat.txt /;
Put res;
Put "Produktion per produkt", /;
Loop(p, Put p.tl:10, x.l(p):10:2, /);
Put "Total vinst: ", z.l:10:2, /;
PutClose res;

Kapitel 09Vanliga fel – och hur du löser dem

GAMS felmeddelanden är korthuggna men pekar på en rad och en kolumn. Nio av tio fel beror på något av följande.

Meddelande / symptomOrsakÅtgärd
Expecting ’;’ or ’=’Semikolon saknasVarje deklaration och sats avslutas med ;
Domain violation for setDu använder ett element som inte finns i mängden – ofta felstavatKontrollera stavning, eller lägg till elementet i mängden
Identifier not declaredNamnet är inte deklarerat, eller deklarerat efter användningDeklarera överst i filen
Symbol ... declared but no valuesEn parameter används innan den fått dataFyll i data före ekvationerna
Endogenous variable not allowed hereEn variabel (obekant) används som om den var dataVariabler får bara finnas i ekvationer; använd parametrar
= ... = in equationEnkelt likhetstecken i en ekvationAnvänd =e=, =l= eller =g=
Set is already used ...Samma index används två gånger på samma platsLägg till Alias
MODEL STATUS 4 / InfeasibleIngen tillåten lösning finnsLätta på bivillkoren eller kontrollera tecken
MODEL STATUS 3 / UnboundedMålfunktionen kan växa obegränsatSaknas ett bivillkor? Fel tecken på ett =l=?

Fem vanor som förebygger fel

  • Skriv beskrivningar på allt: Parameter p(i) "pris i kr per ton";. De syns i utskrifterna och dokumenterar modellen gratis.
  • Dela upp filen med rubrikrader:
    * ===== DATA =====
  • Deklarera allt överst, tilldela data i mitten, lösa sist.
  • Använd Display tidigt och ofta – särskilt för att verifiera att tabeller laddats som du tror.
  • En modell i taget: kommentera bort det som inte fungerar, bygg upp stegvis.
Skiftläget lurar ingen – men väl syntaxen

GAMS skiljer inte på stora och små bokstäver i namn (A och a är samma symbol). Men det gör mängdelement: "Vete" och vete är olika. Välj en stil och håll dig till den.

Kapitel 10Övningar med facit

Tio uppgifter i stigande svårighet. Skriv koden själv innan du öppnar facit – det är där inlärningen sker. Alla svar kan kontrolleras i GAMS eller för hand.

1. Summan av en aritmetisk serie

Skapa mängden n / 1*10 / och beräkna Σk=110 k² samt medelvärdet av talen.

Set n / 1*10 /;
Parameter k(n) "talen";
k(n) = ord(n);                    * ord ger elementets position: 1,2,...,10

Scalar summa_kvadrater, medel;
summa_kvadrater = sum(n, k(n)*k(n));   * = 385
medel          = sum(n, k(n)) / card(n);  * = 5.5
Display summa_kvadrater, medel;

ord(n) ger löpnumret – användbart när mängden är 1*10 men elementen är text.

2. Skalärprodukt och vinkel

Låt u = (1, 2, 2) och v = (2, 0, 1). Beräkna u·v och vinkeln mellan vektorerna.

Set i / 1*3 /;
Parameter u(i) / 1 1, 2 2, 3 2 /;
Parameter v(i) / 1 2, 2 0, 3 1 /;

Scalar dot, nu, nv, theta;
dot   = sum(i, u(i)*v(i));                       * 4
nu    = sqrt(sum(i, u(i)*u(i)));                 * 3
nv    = sqrt(sum(i, v(i)*v(i)));                 * sqrt(5)
theta = arccos(dot/(nu*nv));
Display dot, theta;

Kontroll: cos θ = 4/(3√5) ≈ 0,596 ger θ ≈ 53,4°. GAMS räknar i radianer – multiplicera med 180/pi för grader.

3. Matrismultiplikation med Alias

Beräkna C = A·B för A = [[1,2],[3,4]] och B = [[0,1],[1,0]]. Vad representerar B?

Set i / r1*r2 /;
Alias(i, k);

Parameter A(i,k) / r1.r1 1, r1.r2 2, r2.r1 3, r2.r2 4 /;
Parameter B(k,i) / r1.r1 0, r1.r2 1, r2.r1 1, r2.r2 0 /;
Parameter C(i,i);

C(i,i) = sum(k, A(i,k)*B(k,i));
Display C;      * ger [[2,1],[4,3]] – kolumnerna i A har bytt plats

B är en permutationsmatris (bytesmatris): den byter ordning på kolumnerna. A·B ger därför A med omkastade kolumner.

4. Lös ett ekvationssystem

Lös 2x + y = 7, x − y = 2 med GAMS och kontrollera för hand.

Variable x, y, obj;
Equation e1, e2, dummy_sys;

e1..      2*x +  y =e= 7;
e2..        x -  y =e= 2;
dummy_sys.. obj =e= 0;

Model sys / all /;
Solve sys using lp minimizing obj;
Display x.l, y.l;      * x = 3, y = 1
5. LP för hand – och i GAMS

Maximera z = 5x + 4y då 6x + 4y ≤ 24, x + 2y ≤ 6, x, y ≥ 0. Rita området, hitta hörnen och bestäm optimum – lös sedan med GAMS.

Hörnen blir (0,0), (4,0), (0,3) och skärningspunkten (3, 1{,}5). Värden: 0, 20, 12 respektive 21. Optimum är alltså x = 3, y = 1{,}5, z = 21.

Positive Variable x, y;
Variable z;
Equation obj, c1, c2;
obj.. z =e= 5*x + 4*y;
c1..  6*x + 4*y =l= 24;
c2..    x + 2*y =l= 6;
Model m / all /;
Solve m using lp maximizing z;
Display x.l, y.l, z.l;

Testa gärna samma siffror i labbet och se hörnet markeras.

6. Kappsäck med heltal

Du har 12 kg kapacitet och föremålen (vikt, värde): A (2, 8), B (3, 11), C (4, 15), D (5, 20), E (1, 4). Vilken kombination ger högst värde?

Sortera efter värde per kg: D 4,0; A 4,0; B 3,67; C 3,75; E 4,0 – girig algoritm ger inte alltid optimum, därför behövs MIP.

Set p / A, B, C, D, E /;
Parameter vard(p) / A 8, B 11, C 15, D 20, E 4 /;
Parameter vikt(p) / A 2, B 3,  C 4,  D 5,  E 1 /;
Scalar kap / 12 /;

Binary Variable y(p);
Variable z;
Equation mal, kapac;
mal..   z =e= sum(p, vard(p)*y(p));
kapac.. sum(p, vikt(p)*y(p)) =l= kap;

Model knap / all /;
Solve knap using mip maximizing z;
Display y.l, z.l;      * optimum: A+B+C+E = 10 kg, värde 38
7. Minsta kostnad – lasagneproblemet

Ett foder ska innehålla minst 30 g protein och minst 20 g fett per dag. Ingrediens 1 ger 10 g protein och 2 g fett per enhet och kostar 5 kr; ingrediens 2 ger 4 g protein och 6 g fett och kostar 4 kr. Minimera kostnaden.

Set i / ing1, ing2 /;
Parameter kostnad(i) / ing1 5, ing2 4 /;
Table nar(i,*) "näringsinnehåll"
          protein  fett
ing1         10      2
ing2          4      6;

Parameter krav(*)  / protein 30, fett 20 /;
Positive Variable x(i);
Variable k;
Equation mal;
Equation minst(*);

mal..        k =e= sum(i, kostnad(i)*x(i));
minst("protein").. sum(i, nar(i,"protein")*x(i)) =g= krav("protein");
minst("fett")..    sum(i, nar(i,"fett")*x(i))    =g= krav("fett");

Model foder / all /;
Solve foder using lp minimizing k;
Display x.l, k.l;

Här används en Table med * i andra indexet – ett smart sätt att deklarera näringsämnen utan att skapa en egen mängd. Svaret blir ungefär x_1 ≈ 2{,}7, x_2 ≈ 2{,}4.

8. Skuggpris – tolka .m

I övning 5: vad är skuggpriset för bivillkor c1? Om kapaciteten höjs från 24 till 25, hur mycket ökar z?

Skriv Display c1.m; efter lösningen. Värdet är ungefär 0{,}75, så z ökar med cirka 0{,}75 per extra enhet – så länge optimum ligger i samma hörn. Höj till 25 i modellen och se att z blir 21,75.

Varför?

Skuggpriset gäller bara lokalt: när hörnet byts (vid cirka 27) ändras marginalvärdet. Det är därför LP-dualiteten är ett lokal känslighetsmått.

9. Portfölj med summor

Tre tillgångar med avkastning 8 %, 3 % och 5 %. Beräkna avkastningen för (a) likaviktad portfölj och (b) vikterna 0,5 / 0,3 / 0,2. Kontrollera att vikterna summerar till 1.

Set i / aktie, obl, guld /;
Parameter r(i) / aktie 0.08, obl 0.03, guld 0.05 /;
Parameter w(i);

w(i) = 1/card(i);
Display sum(i, w(i));                * kontroll: 1.0
Display sum(i, w(i)*r(i));           * ca 0.0533

w("aktie") = 0.5; w("obl") = 0.3; w("guld") = 0.2;
Display sum(i, w(i)*r(i));           * 0.059
10. Generalisera verkstaden till tre produkter

Lägg till produkten Z med vinst 4, åtgång 2 enheter maskintid och 2 enheter arbetstid i modellen från kapitel 4. Lös igen.

* Endast dessa rader ändras:
Set p / X, Y, Z /;
Parameter vinst(p) / X 3, Y 2, Z 4 /;
Table atgang(r,p)
           X    Y    Z
maskin     1    2    2
arbete     3    1    2;

Allt annat är oförändrat – ekvationerna är redan skrivna över mängden p. Det här är hela poängen med algebraisk modellering: data ändras, strukturen består.

Quiz: känner du igen syntaxen?

1. Vilket tecken betyder ”mindre än eller lika” inne i en ekvation?

Rätt: =l= (less). =e= är likhet och =g= är större än eller lika. Ett vanligt <= ger felmeddelande.

2. Du vill summera a(i,j) över både i och j. Vad skriver du?

Rätt: sum((i,j), …) – indexen paketeras i en parentes. Nästlad summa sum(i, sum(j, a(i,j))) fungerar också, men de andra formerna är syntaxfel.

3. Vad betyder x.m i lösningen?

Rätt: marginalvärdet. Lösningens värde heter x.l (level), gränserna x.lo och x.up.

4. Modellen innehåller Binary Variable y(p);. Vilken lösartyp ska du ange?

Rätt: mip (mixed integer programming). rmip löser relaxationen där heltalskravet släpps – användbart för att få en gräns, men inte det du vill ha som svar.

5. Vad är fel i koden?
Parameter p(i); Variable x(i); p(i) = 5 * x(i);

Rätt: en Variable är obekant innan modellen löses och kan inte ingå i data. Använd x.l om du vill läsa av lösningen efteråt.

6. Lösaren skriver MODEL STATUS 4. Vad betyder det?

Rätt: infeasible. Bivillkoren kan inte uppfyllas samtidigt. Obegränsat problem ger status 3, och syntaxfel hindrar programmet från att köras alls.

Checklista – bocka av när du behärskar momentet

  • Jag har kört ett GAMS-program och sett utskriften från Display.
  • Jag kan deklarera en mängd och använda den som index.
  • Jag kan skriva en dubbelsumma och förklara vilka index som försvinner.
  • Jag kan skriva ett LP med =e=, =l= och =g= utan att titta i fuskbladet.
  • Jag kan tolka .l, .m och .slack i lösningsrapporten.
  • Jag kan lösa ett ekvationssystem och ett heltalsproblem i GAMS.

Kapitel 11Fuskblad

Allt på ett ställe. Skriv ut och ha bredvid tangentbordet.

Skelettet

Set      i "index"        / a, b, c /;
Parameter p(i) "data"     / a 1, b 2, c 3 /;
Scalar    c0  "skalär"    / 100 /;

Positive Variable x(i) "beslut";
Variable          z    "målfunktion";

Equation obj, rest(i);

obj..     z =e= sum(i, p(i)*x(i));
rest(i).. sum(i, x(i)) =l= c0;

Model modell / all /;
Solve modell using lp maximizing z;
Display x.l, z.l;
BehovSyntax
MängdSet i / a, b /;  ·  intervall: / 1*10 /
Par (tillåtna kombinationer)Set ij(i,j) / a.x, b.y /;
AliasAlias(i, j);
Skalär / vektor / matrisScalar c; · Parameter p(i); · Parameter A(i,j);
TabellTable A(i,j) "text" + rad- och kolumnrubriker
VariabeltyperPositive, Integer, Binary, Free
Relationer=e= likhet · =l= ≤ · =g= ≥
Summa / produktsum(i, …) · sum((i,j), …) · prod(i, …)
Villkorsum(i $ (v(i) > 0), v(i)) · p(i) $ cond = …
LösaSolve m using lp maximizing z;
Lösartyperlp · mip · nlp · rmip · cns
Resultat.l värde · .m marginal · .slack slack · .lo/.up gränser · .fx lås
Statusm.modelstat (1 = optimal), m.solvestat
UtskriftDisplay x.l; · Put för fil
Användbara funktionercard, ord, sameas, smax, smin, sqrt, exp, log, abs, round, power, arccos
Kommentar* hel rad · $ontext … $offtext för block

Från formel till kod – i fem steg

  1. Vilka index finns? Varje index blir en Set.
  2. Vad är givet? Varje given siffra blir en Parameter eller Table.
  3. Vad ska bestämmas? Det blir Variable – sätt typ och gränser.
  4. Vilka samband gäller? Varje rad i problemformuleringen blir en Equation med rätt relations­tecken.
  5. Vad ska maximeras/minimeras? Skriv Solve … maximizing/minimizing och läs .l.
Kom ihåg

GAMS gissar aldrig: multiplikation kräver *, index måste deklareras, och varje sats avslutas med ;. Får du ett fel, läs meddelandet och gå till kapitel 9 – nio av tio fel står i tabellen där.