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.
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.
då Σi xi ≤ 100, xi ≥ 0
kap.. sum(i, x(i)) =l= 100;
x.lo(i) = 0;
Så arbetar du med kursen
- Läs kapitlen i ordning – varje kapitel bygger på det föregående.
- Klistra in kodexemplen i en
.gms-fil och kör dem. Filändelsen är alltid.gmsoch du kör medgams filnamn.gms. - Gör övningarna i kapitel 10. Facit finns bakom varje uppgift – försök först själv.
- 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.
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.
| Steg | Nyckelord | Matematisk motsvarighet |
|---|---|---|
| 1. Index | Set | Indexmängder, i ∈ I |
| 2. Data | Parameter, Table, Scalar | Konstanter, vektorer, matriser |
| 3. Beslut | Variable, Positive Variable | De obekanta x, y |
| 4. Samband | Equation | Villkor och målfunktion |
| 5. Lös | Model, 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.
J = {nord, syd}
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
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.
| Objekt | Matematik | Deklaration | Exempel på värde |
|---|---|---|---|
| Skalär | c | Scalar c; | c = 3.5; |
| Vektor | pi | Parameter p(i); | p("vete") = 12; |
| Matris | Aij | Parameter A(i,j); | A("vete","nord") = 1; |
| Tabell | samma som matris | Table 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;
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
| GAMS | Matematik | Kommentar |
|---|---|---|
card(i) | |I| | Antal element i mängd |
sum(i, x(i)) | Σi xi | Summa |
prod(i, x(i)) | Πi xi | Produkt |
smax(i, x(i)) | maxi xi | Största värdet |
smin(i, x(i)) | mini xi | Minsta värdet |
power(a,2) / a**2 | a² | Potens |
sqrt, exp, log | √, ex, ln | Standardfunktioner |
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:
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));
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.
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:
| GAMS | Matematik | Betydelse |
|---|---|---|
=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).
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.
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
Bivillkor a·x + b·y ≤ c
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
--
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
‖u‖ = √(Σi ui²)
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 ä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.
Ä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.
| Deklaration | Matematik | Används till |
|---|---|---|
Integer Variable n; | n ∈ ℤ | Antal enheter, fordon, anställda |
Binary Variable y; | y ∈ {0,1} | Ja/nej-beslut |
Positive Variable x; | x ≥ 0 | Mä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;
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.
| Attribut | Betydelse | Läs som |
|---|---|---|
x.l | Level – lösningens värde | x*, det optimala värdet |
x.m | Marginal – skuggpris / dualvärde | Vad en enhets ändring är värd |
x.lo, x.up | Undre och övre gräns | x ≥ 0 blir x.lo = 0 |
x.fx | Fasta 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
Ä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 / symptom | Orsak | Åtgärd |
|---|---|---|
Expecting ’;’ or ’=’ | Semikolon saknas | Varje deklaration och sats avslutas med ; |
Domain violation for set | Du använder ett element som inte finns i mängden – ofta felstavat | Kontrollera stavning, eller lägg till elementet i mängden |
Identifier not declared | Namnet är inte deklarerat, eller deklarerat efter användning | Deklarera överst i filen |
Symbol ... declared but no values | En parameter används innan den fått data | Fyll i data före ekvationerna |
Endogenous variable not allowed here | En variabel (obekant) används som om den var data | Variabler får bara finnas i ekvationer; använd parametrar |
= ... = in equation | Enkelt likhetstecken i en ekvation | Använd =e=, =l= eller =g= |
Set is already used ... | Samma index används två gånger på samma plats | Lägg till Alias |
MODEL STATUS 4 / Infeasible | Ingen tillåten lösning finns | Lätta på bivillkoren eller kontrollera tecken |
MODEL STATUS 3 / Unbounded | Målfunktionen kan växa obegränsat | Saknas 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
Displaytidigt 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.
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.
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?
=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?
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?
x.l (level), gränserna x.lo och x.up.4. Modellen innehåller Binary Variable y(p);. Vilken lösartyp ska du ange?
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);
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?
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,.moch.slacki 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;
| Behov | Syntax |
|---|---|
| Mängd | Set i / a, b /; · intervall: / 1*10 / |
| Par (tillåtna kombinationer) | Set ij(i,j) / a.x, b.y /; |
| Alias | Alias(i, j); |
| Skalär / vektor / matris | Scalar c; · Parameter p(i); · Parameter A(i,j); |
| Tabell | Table A(i,j) "text" + rad- och kolumnrubriker |
| Variabeltyper | Positive, Integer, Binary, Free |
| Relationer | =e= likhet · =l= ≤ · =g= ≥ |
| Summa / produkt | sum(i, …) · sum((i,j), …) · prod(i, …) |
| Villkor | sum(i $ (v(i) > 0), v(i)) · p(i) $ cond = … |
| Lösa | Solve m using lp maximizing z; |
| Lösartyper | lp · mip · nlp · rmip · cns |
| Resultat | .l värde · .m marginal · .slack slack · .lo/.up gränser · .fx lås |
| Status | m.modelstat (1 = optimal), m.solvestat |
| Utskrift | Display x.l; · Put för fil |
| Användbara funktioner | card, 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
- Vilka index finns? Varje index blir en
Set. - Vad är givet? Varje given siffra blir en
ParameterellerTable. - Vad ska bestämmas? Det blir
Variable– sätt typ och gränser. - Vilka samband gäller? Varje rad i problemformuleringen blir en
Equationmed rätt relationstecken. - Vad ska maximeras/minimeras? Skriv
Solve … maximizing/minimizingoch läs.l.
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.