6.3 DRILL — Differenslikninger
Full drill på H-sjangeren, med roterende innpakning og fast fokus på oppdateringsrekkefølge og plott.
Dette er drillkapitlet for sjanger H — differenslikninger, altså «regn ut hvordan en eller flere størrelser utvikler seg skritt for skritt». Sjangeren står i 6 av 8 gjennomgåtte avsluttende sett (75 prosent) og i 4 av 4 siste, og teller 5–12 poeng. Trenden er tydelig: den har vært med hvert år siden 2022.
Prioriteten er nivå 2 i bokas skala — kunne — men gitt at den har vært på hvert av de fire siste settene, bør du behandle den som om den var nivå 1.
Oppgaven er nesten alltid todelt: (a) skriv funksjonen som simulerer, og (b) plott resultatet. Innpakningen roterer — rovdyr og byttedyr, et smitteutbrudd, to sammenkoblede beholdere, en diskret vekstmodell — men koden er den samme fire linjene hver gang.
Du besvarer alt som fritekst i Inspera, UiOs digitale eksamenssystem, uten hjelpemidler og uten å kunne kjøre koden.
Kapitlet har tre deler: én løsningsoppskrift på sju steg, ett gjennomarbeidet eksamenscase med sensorkommentar ved hvert steg, og ti oppgaver på eksamensnivå med roterende innpakning.
Om delpoeng: sensor gir uttelling for hvert steg for seg — allokering, startverdier, løkkegrense, oppdateringslinjer, retur og plott. Riktig løkkestruktur belønnes selv med en indeksfeil. Det ene stedet delvis riktig ikke hjelper, er oppdateringsrekkefølgen i koblede likninger: bytter du om linjene, har du regnet på en annen modell.
Sist du var her
Drillen forutsetter kap. 6.2 (differenslikninger), kap. 4.1 (NumPy-tabeller) og kap. 4.3 (plotting). Her er de tre grepene du kommer til å bruke i hver eneste oppgave, oppfrisket med kjørt utskrift.
1. Allokeringsmønsteret. Lag tabellen, sett startverdien før løkka, oppdater n+1.
import numpy as np
def simuler(x0, N):
x = np.zeros(N+1)
x[0] = x0
for n in range(N):
x[n+1] = 0.8*x[n] + 4
return x
print(simuler(50.0, 4))Utskrift:
[50. 44. 39.2 35.36 32.288]2. Tellingen. N skritt gir N+1 verdier, fordi startverdien også er en verdi.
import numpy as np
x = np.zeros(5)
x[0] = 2.0
for n in range(3):
x[n+1] = 3*x[n] - 1
print(x)
print(x.size, x[-1])Utskrift:
[ 2. 5. 14. 41. 0.]
5 0.0Legg merke til den siste nullen: tabellen har fem plasser, men løkka gjorde bare tre oppdateringer, så x[4] er aldri skrevet til. Det er nettopp av-én-feilen i sin mest lumske form — programmet krasjer ikke, det gir bare en tabell med en null på slutten.
3. Plottet. Én label= per kurve, ett plt.legend(), og navngitte akser.
import matplotlib.pyplot as plt
plt.plot(t, B, label='byttedyr')
plt.plot(t, R, label='rovdyr')
plt.xlabel('tid (maaneder)')
plt.ylabel('antall dyr')
plt.legend()
plt.show()Blokken over er et fragment: t, B og R kommer fra simuleringen og finnes ikke alene, så den er ikke kjørbar som den står.
Er noe av dette uklart, les kap. 6.2 først — drillen forklarer ikke konstruksjonene på nytt.
Forkunnskaper
- Differenslikninger — allokering, startverdi og oppdateringsrekkefølge: kap. 6.2.
- NumPy-tabeller, np.zeros og np.linspace: kap. 4.1.
- Plotting med label= og plt.legend(): kap. 4.3.
- Løkker og range: kap. 1.3.
- Funksjoner som returnerer flere verdier: kap. 1.6.
- f-strenger til pen utskrift: kap. 1.4.
Les likningene i oppgaven, og gjør så dette, i rekkefølge.
1. Tell størrelsene. Én likning gir én tabell; to koblede gir to tabeller. Navnene tas fra oppgaven: B og R, S og I, x og y.
2. Allokér med np.zeros(N+1) — én linje per størrelse. N er antall skritt, og N+1 er antall verdier.
3. Sett startverdiene før løkka: x[0] = x0, y[0] = y0. Glemmer du én, starter den størrelsen på 0 og hele forløpet blir feil.
4. Én løkke, for n in range(N): — ikke to løkker etter hverandre. Begge størrelsene oppdateres i samme runde.
5. Bestem oppdateringsrekkefølgen. Spør: bruker den ene likningen den nye verdien av den andre? Står det i uttrykket for , må y[n+1] regnes først. Bruker begge bare og , spiller rekkefølgen ingen rolle.
6. return tabellene i den rekkefølgen oppgaven navngir dem, utenfor løkka.
7. Del (b) — plottet. Lag tidsaksen med np.linspace(0, T, N+1) eller bruk range(len(x)), ett plot-kall per kurve med label=, aksenavn, legend() og show().
Skjelettet er alltid dette, og det er verdt å skrive ned først, før du fyller inn uttrykkene:
import numpy as np
def simuler(x0, y0, N):
x = np.zeros(N+1)
y = np.zeros(N+1)
x[0] = x0
y[0] = y0
for n in range(N):
y[n+1] = y[n]
x[n+1] = x[n]
return x, y
x, y = simuler(3.0, 1.0, 4)
print(x, y)Utskrift:
[3. 3. 3. 3. 3.] [1. 1. 1. 1. 1.]Skjelettet gir uttelling i seg selv — her holder hver størrelse seg konstant, fordi oppdateringslinjene ennå ikke gjør noe. Fyller du inn uttrykkene feil, mister du ett eller to poeng; glemmer du skjelettet, mister du oppgaven.
y[n+1] regnes først. Bytter du om, leser du en plass i tabellen som ennå er 0.0, og feilen forplanter seg gjennom hele forløpet. Dette er ikke en detaljfeil — det er en annen modell.#1 — av-én på tabellstørrelse eller løkkegrense. np.zeros(N) gir én plass for lite og krasjer med IndexError på siste runde. range(N+1) gjør det samme. Og np.zeros(N+1) med range(N-1) etterlater nuller på slutten uten noen feilmelding — den varianten er verst, fordi den ser riktig ut.
Glemt startverdi. Er y[0] ikke satt, starter den på 0, og alle senere verdier blir gale. Sjekk at antall [0] = -linjer er lik antall tabeller.
Oppdatering «in place». Skriver du x = x + dt*(...) uten indeks, eller regner du x[n] om til den nye verdien før y[n+1] har brukt den gamle, går informasjon tapt. Bruk alltid x[n+1] = x[n] + ...; det er hele poenget med å ha en tabell.
Og én til som gjelder del (b): glemt label= eller glemt plt.legend(). Figuren tegnes pent, men ingen kan se hvilken kurve som er hvilken, og det koster et halvt til ett poeng hver gang.
Rekursjon er utenfor pensum i IN1900. En differenslikning ser ut som en invitasjon til å la en funksjon kalle seg selv; det er utenfor pensum, markeres uønsket i de offisielle løsningsforslagene, og gir dessuten ingen tabell å plotte. Løkka er den forventede løsningen — alltid.
Det gjennomarbeidede caset (~20 min)
Under står en komplett, todelt sjanger-H-oppgave av samme type og vekt som på et virkelig sett, med sensorkommentar ved hvert steg.
Oppgaven (9 poeng). I et økosystem beskrives antall byttedyr og rovdyr ved tidspunkt av differenslikningene
der er byttedyrenes fødselsrate, hvor effektivt rovdyrene jakter, hvor mye et fanget byttedyr bidrar til rovdyrbestanden, og rovdyrenes dødsrate.
a) (6 p) Skriv lotka_volterra(B0, R0, a, b, c, d, dt, N) som returnerer tabellene B og R.
b) (3 p) Skriv koden som simulerer 200 måneder med , , , , , , og , og plotter begge bestandene mot tid. Beskriv figuren.
Del (a), 6 poeng: lotka_volterra(B0, R0, a, b, c, d, dt, N) skal returnere tabellene B og R.
Gå gjennom oppskriften. To størrelser gir to tabeller. Begge likningene bruker bare og — de gamle verdiene — så rekkefølgen inne i løkka spiller ingen rolle her. Det er verdt å si i én setning i besvarelsen: det viser at du har sjekket.
import numpy as np
def lotka_volterra(B0, R0, a, b, c, d, dt, N):
B = np.zeros(N+1)
R = np.zeros(N+1)
B[0] = B0
R[0] = R0
for n in range(N):
B[n+1] = B[n] + dt*(a*B[n] - b*B[n]*R[n])
R[n+1] = R[n] + dt*(c*B[n]*R[n] - d*R[n])
return B, R
B, R = lotka_volterra(40.0, 9.0, 0.4, 0.02, 0.01, 0.3, 0.05, 4000)
print(f'B[0] = {B[0]:.2f} R[0] = {R[0]:.2f}')
print(f'B[1] = {B[1]:.4f} R[1] = {R[1]:.4f}')
print(f'maks B = {B.max():.2f} maks R = {R.max():.2f}')
print(f'B[4000] = {B[-1]:.2f} R[4000] = {R[-1]:.2f}')Utskrift:
B[0] = 40.00 R[0] = 9.00
B[1] = 40.4400 R[1] = 9.0450
maks B = 97.85 maks R = 58.04
B[4000] = 29.87 R[4000] = 3.65Regn gjerne første skritt for hånd som kontroll: . Utskriften bekrefter det.
Sensorkommentar, steg for steg:
- import numpy as np — 0,5 p. Nødvendige importer skal med når oppgaven ber om kjørbar kode.
- B = np.zeros(N+1) og R = np.zeros(N+1) — 1,5 p. Her ligger av-én-poenget: np.zeros(N) gir IndexError på siste runde.
- B[0] = B0 og R[0] = R0 før løkka — 1 p. Glemmer du én, starter den bestanden på 0 og hele forløpet kollapser.
- for n in range(N): — 1 p. N skritt, ikke N+1.
- De to oppdateringslinjene — 1,5 p, altså 0,75 hver. Bommer du på ett fortegn, får du halve.
- return B, R utenfor løkka — 0,5 p. Står return inne i løkka, returneres tabellene etter første skritt, med nuller i resten.
Hva som er godt nok. Regner du de to endringene i egne variabler først, er det like riktig og lettere å lese:
import numpy as np
def lotka_volterra(B0, R0, a, b, c, d, dt, N):
B = np.zeros(N+1)
R = np.zeros(N+1)
B[0] = B0
R[0] = R0
for n in range(N):
dB = a*B[n] - b*B[n]*R[n]
dR = c*B[n]*R[n] - d*R[n]
B[n+1] = B[n] + dt*dB
R[n+1] = R[n] + dt*dR
return B, R
B, R = lotka_volterra(40.0, 9.0, 0.4, 0.02, 0.01, 0.3, 0.05, 4000)
print(f'{B[1]:.4f} {R[1]:.4f}')Utskrift:
40.4400 9.0450Merk at dB og dR her regnes begge ut fra de gamle verdiene før noen tabell oppdateres. Det er en robust vane: skriver du det slik, kan rekkefølgen aldri bli feil.
Del (b), 3 poeng: simuler 200 måneder og plott begge bestandene mot tid.
Tidsaksen lages med np.linspace. Med og 4000 skritt dekker simuleringen måneder, og tabellene har 4001 verdier — derfor np.linspace(0, 200, 4001).
import numpy as np
import matplotlib.pyplot as plt
def lotka_volterra(B0, R0, a, b, c, d, dt, N):
B = np.zeros(N+1)
R = np.zeros(N+1)
B[0] = B0
R[0] = R0
for n in range(N):
B[n+1] = B[n] + dt*(a*B[n] - b*B[n]*R[n])
R[n+1] = R[n] + dt*(c*B[n]*R[n] - d*R[n])
return B, R
B, R = lotka_volterra(40.0, 9.0, 0.4, 0.02, 0.01, 0.3, 0.05, 4000)
t = np.linspace(0, 200, 4001)
plt.plot(t, B, label='byttedyr')
plt.plot(t, R, label='rovdyr')
plt.xlabel('tid (maaneder)')
plt.ylabel('antall dyr')
plt.legend()
plt.show()
print('likevekt B =', 0.3/0.01, ' R =', 0.4/0.02)Utskrift:
likevekt B = 30.0 R = 20.0Figuren i ord. Førsteaksen viser tid fra 0 til 200 måneder, andreaksen antall dyr fra 0 til rundt 100. Byttedyrkurven starter i 40 og stiger til en topp på nesten 98; rovdyrkurven starter i 9, ligger lavt en stund, og stiger så til sin topp på rundt 58 etter byttedyrtoppen. Deretter faller byttedyrene bratt, rovdyrene følger etter med samme forsinkelse, og mønsteret gjentar seg. Kurvene svinger altså i takt, men rovdyrene ligger et kvart svingeslag bak — det er den karakteristiske forsinkelsen i rovdyr–byttedyr-dynamikk. Forklaringen navngir de to kurvene.
Svingningene er ikke helt jevne over de 200 månedene: amplituden vokser sakte. Det er ikke en feil i modellen, men i metoden — den enkle skrittformelen legger til litt energi hver runde. Nevner du det, viser du at du forstår forskjellen på modell og numerisk metode. Det kreves ikke for full score.
Likevektspunktet kan regnes ut for hånd: bestandene står stille når og , altså og . Utskriften bekrefter tallene, og de er sentrum kurvene svinger rundt.
Sensorkommentar del (b): tidsaksen med riktig antall punkter (1 p) · to plot-kall med label= (1 p) · aksenavn, legend() og show() (1 p). Bruker du range(len(B)) som førsteakse i stedet for np.linspace, er det like riktig — men da er enheten skritt, ikke måneder, og aksenavnet må si det.
Oppgaver: samme mønster, ny innpakning (~30 min for oppgave 1–5)
— naturlig pausepunkt —
Alle oppgavene under er på eksamensnivå. Innpakningen roterer bevisst, og noen av likningene er koblet gjennom nye verdier mens andre ikke er det. Steg 5 i oppskriften er derfor det første du skal svare på hver gang.
(Midtveisnivå, sjanger A — kodesporing, altså «hva skriver dette programmet ut?».) Hva skriver programmet ut? NumPy skriver tabeller uten komma.
import numpy as np
x = np.zeros(5)
x[0] = 2.0
for n in range(3):
x[n+1] = 3*x[n] - 1
print(x)
print(x.size, x[-1])der er veksttakten per uke og er bæreevnen.
a) Skriv logistisk(x0, r, K, N) som returnerer tabellen x.
b) Kjør den med , , og , og skriv ut startverdien, første skritt, verdien etter 10 uker og etter 40 uker.
c) Hvor mange verdier har tabellen, og hvorfor?
Legg merke til at bruker den nye verdien .
a) Skriv simuler(x0, y0, a, N) som returnerer tabellene x og y.
b) Kjør med , , og , og skriv ut alle verdiene.
c) Hva skjer hvis du bytter om de to oppdateringslinjene? Vis tallene.
Oppgave 2 testet steg 1 til 6 i den enkleste formen: én størrelse, én tabell, ingen rekkefølgeproblematikk. Legg merke til at deloppgave c) om antall verdier er verdt like mye som en kodelinje. Sensor spør om den fordi den avslører om du forstår tellingen eller bare har pugget mønsteret.
Oppgave 3 testet steg 5 alene. Signalet i oppgaveteksten er at det står — med indeks — inne i uttrykket for . Ser du en indeks på høyre side av likhetstegnet, er rekkefølgen kritisk. Ser du bare -indekser på høyresidene, spiller den ingen rolle, og da bør du si det i én setning.
a) Skriv sir(S0, I0, R0, beta, gamma, N) som returnerer de tre tabellene.
b) Kjør med , , , , og . Skriv ut første skritt, toppen i med dagen den kommer, og sluttverdiene.
c) Skriv plottekoden for de tre kurvene mot dag, og beskriv figuren.
d) Summen er en fornuftssjekk. Forklar hvorfor.
Programmet har tre feil.
import numpy as np
def tanker(A0, B0, k, dt, N):
A = np.zeros(N)
B = np.zeros(N)
A[0] = A0
for n in range(N):
overfort = k*dt*(A[n] - B[n])
A[n+1] = A[n] - overfort
B[n+1] = B[n] - overfort
return A, Ba) Nevn de tre feilene og si hva hver av dem fører til.
b) Skriv den rettede koden, og kjør den med , , , og .
Oppgave 6–10: variantene som skiller (~25 min)
— naturlig pausepunkt —
De neste fem oppgavene ligner mest på det du møter når sjangeren er verdt ti poeng eller mer: en fasekurve i stedet for et tidsplott, en modell der koblingen går gjennom nye verdier, en påstandstabell, og en full tredelt oppgave.
Du har tabellene B (byttedyr) og R (rovdyr) fra det gjennomarbeidede caset.
a) Skriv koden som plotter fasekurven, altså rovdyr mot byttedyr, med byttedyr langs førsteaksen.
b) Kurven blir en lukket løkke som beveger seg mot klokka rundt punktet . Forklar i ord hva det forteller om de to bestandene, og hva punktet er.
c) Hva er forskjellen på et tidsplott og en fasekurve, og hvilken informasjon mister du i den siste?
der er tykkelsen i millimeter etter år og er årets tilvekst.
a) Skriv sediment(h0, v0, a, b, N) som returnerer tabellene h og v.
b) Kjør med , , , og , og skriv ut begge tabellene år for år.
c) Hvilken av de to likningene må regnes først, og hvordan ser du det i oppgaveteksten?
(Midtveisnivå, sjanger D — «hvilke av påstandene er riktige?». Sjanger D er matche-typen der du avgjør om hver påstand holder.) Se på denne funksjonen:
def simuler(x0, N):
x = np.zeros(N+1)
x[0] = x0
for n in range(N):
x[n+1] = 0.9*x[n] + 2
return xAvgjør for hver påstand om den er riktig eller gal.
| Rad | Påstand |
|---|---|
| 1 | Tabellen x har N+1 plasser |
| 2 | Løkka gjør N+1 oppdateringer |
| 3 | x[N] blir skrevet til i siste runde |
| 4 | Bytter du til range(N+1), får du IndexError |
| 5 | Startverdien kan like gjerne settes inne i løkka |
| 6 | Med N = 0 returneres en tabell med bare startverdien |
med målt i milligram per kubikkmeter og i døgn.
a) Skriv biomasse(P0, Z0, a, b, c, d, dt, N).
b) Kjør med , , , , , , og . Skriv ut første skritt, samt høyeste og laveste verdi av .
c) Regn ut likevektsverdiene for hånd, og sjekk at simuleringen svinger rundt dem.
d) Skriv plottekoden for begge kurvene mot tid.
Bruk logistisk(x0, r, K, N) fra oppgave 2.
a) Kjør modellen med , , og tre verdier av : , og . Skriv ut de fem siste verdiene for hver.
b) Beskriv hva som skjer i hvert av de tre tilfellene.
c) Er dette en programmeringsfeil eller en egenskap ved modellen? Begrunn.
Oppgave 5 testet feilkatalogen baklengs. De tre du skal kjenne igjen på et blunk er tabellstørrelsen, en glemt startverdi og et fortegn som bryter bevaringen.
Oppgave 6 testet fasekurven, som er den andre plottevarianten i sjangeren. Signalet i oppgaveteksten er at ingen av aksene skal være tid.
Oppgave 7 testet steg 5 igjen, nå med signalet skrevet rett inn i formelen: en indeks på høyre side.
Oppgave 8 var midtveisformatet: ingen kode skal skrives, bare tellingen vurderes. Fire av seks påstander handler om forholdet mellom skritt og verdier.
Oppgave 9 var den tredelte oppgaven med likevektsregning — den eneste deloppgaven i kapitlet som gjøres med penn og papir, ikke med kode.
Oppgave 10 var «forklar hva du ser». Den kan besvares selv om simuleringen ikke virker, og den er derfor et sted du alltid skal skrive noe.
Begrepsbank
Begrepsbanken er flashcard-/repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
Differenslikninger: oppgaven der du regner ut hvordan en eller flere størrelser utvikler seg skritt for skritt. Sjangeren står i 6 av 8 gjennomgåtte avsluttende sett og i 4 av 4 siste, og teller 5–12 poeng. Den er nesten alltid todelt: (a) skriv simuleringen, (b) plott resultatet.
Én likning gir én tabell, to koblede gir to. Hver tabell lages med np.zeros(N+1), der N er antall skritt. Å allokere betyr å be om lagerplassen på forhånd, før du vet hva som skal stå der.
x[0] = x0 og y[0] = y0 settes før løkka, én linje per tabell. Er antall startverdier mindre enn antall tabeller, starter en av størrelsene på 0 og hele forløpet blir feil.for n in range(N):, og begge oppdateringene inne i samme runde. To løkker etter hverandre er feil: da bruker den andre løkka ferdige verdier fra den første i stedet for verdier fra samme skritt.Spørsmålet er: bruker den ene likningen den nye verdien av den andre? Signalet er en indeks på høyre side av likhetstegnet. Da må den likningen stå sist. Har alle høyresider bare -indekser, er rekkefølgen likegyldig — og det er verdt en setning å si at du har sjekket.
N skritt gir N+1 verdier, fordi startverdien også er en verdi. range(N) gir n fra 0 til N-1, og siste tilordning treffer x[N] — den siste plassen i en tabell med N+1 plasser. Alt passer nøyaktig.np.zeros(N) gir IndexError på siste runde. range(N+1) gir samme feil. np.zeros(N+1) med for kort løkke gir ingen feilmelding i det hele tatt, bare nuller på slutten av tabellen — den siste er verst, fordi den ser riktig ut.I modeller der ingenting forsvinner — smittemodellen, to sammenkoblede beholdere — skal summen av størrelsene være konstant. Det er den eneste kontrollen du kan gjøre uten fasit og uten datamaskin, og den er verdt et poeng å nevne.
Verdiene der ingen av størrelsene endrer seg, altså der alle endringsuttrykkene er null. For rovdyr–byttedyr-modellen er de og . Simuleringen svinger rundt dem, og de regnes ut med penn og papir, ikke med kode.
I et tidsplott er førsteaksen tid og hver kurve viser én størrelse. I en fasekurve er begge aksene størrelser, og tiden er bare rekkefølgen punktene tegnes i. Fasekurven viser om systemet nærmer seg en likevekt, fjerner seg fra den, eller gjentar seg — men ikke når noe skjer.
np.linspace(0, T, N+1) der , eller range(len(x)) når enheten er skritt. Aksen må ha like mange punkter som tabellene, ellers stopper plottekallet med en feil om ulik lengde.Endrer den rare oppførselen seg når du endrer en parameter, er den modellens; er svaret feil for alle parametere, er den kodens. Den diskrete logistiske likningen gir rolig vekst, periodiske svingninger eller uregelmessig forløp avhengig av én parameter alene — helt uten at koden er gal.
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.
Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.