Løkker#
Læringsutbytte
Etter å ha arbeidet med dette temaet, skal du kunne:
bruke
while- ogfor-løkker til å gjenta kodeforklare når det passer å bruke en telleløkke, og når det passer å bruke en tilstandsløkke
bruke
rangeog lister i for-løkkerbruke løkker til å behandle kjemiske data og utforske enkle kjemiske modeller
lage og vurdere en enkel modell for tilfeldig partikkelbevegelse
Definisjon#
Den kanskje viktigste strukturen i programmering er løkker. De lar datamaskinen gjenta en operasjon så mange ganger vi vil. Det er dette som er en av datamaskinens store styrker.
I kjemi møter vi gjentakelser overalt. Vi kan utføre den samme beregningen for hver bølgelengde i et spektrum, undersøke hver måling i et datasett, oppdatere konsentrasjonene i en reaksjon for mange små tidssteg eller simulere bevegelsen til mange partikler. Uten løkker måtte vi ha skrevet den samme koden om igjen for hver måling, hvert tidssteg eller hver partikkel.
Løkker
En løkke er en struktur som gjør at vi kan gjenta kode. Ofte skiller vi mellom en telleløkke, som gjentar noe et bestemt antall ganger, og en tilstandsløkke, som gjentar noe så lenge et vilkår er sant. I Python heter disse henholdsvis for-løkke og while-løkke.
Vi begynner med en visuell framstilling av gjentakelser. Deretter skal vi bruke de samme prinsippene til å arbeide med kjemiske data og modeller.
Skilpaddegrafikk#
Vi skal først se hvordan løkker fungerer ved å introdusere deg for en skilpadde. Han heter Gunnar. Gunnar følger enkle kommandoer som forward, backward, right og left.
from turtle import *
shape("turtle") # gir pekeren skilpaddeform
color("limegreen") # gjør skilpadden limegrønn
forward(80) # går framover 80 piksler
left(60) # snur 60 grader mot venstre
Den første linja henter inn kommandoene fra biblioteket turtle. Et bibliotek er en samling ferdiglagde funksjoner som vi kan bruke i programmene våre. Vi har ikke laget funksjonene forward og left selv; de er definert i turtle-biblioteket.
En forenklet representasjon av en benzenring#
Et mulig kjemisk utgangspunkt er den sekskantede geometrien vi forbinder med benzen. Dersom vi skulle ha skrevet kommandoene én gang for hver side, måtte vi ha gjentatt forward(80) og left(60) seks ganger. Det er både tungvint og vanskelig å endre. Med en løkke kan vi skrive det slik:
from turtle import *
shape("turtle")
color("limegreen")
speed(3)
antall_sider = 6
sidelengde = 80
dreievinkel = 360 / antall_sider
for side in range(antall_sider):
forward(sidelengde)
left(dreievinkel)
done()
Løkka gjentar alt som er rykket inn seks ganger. Første gang løkka kjører, er side = 0. Deretter får side verdiene 1, 2, 3, 4 og 5. Funksjonen range(6) gir altså seks heltall fra og med 0 til, men ikke med, 6.
Underveisoppgave
Hvorfor snur skilpadden 60° når vinklene mellom bindingene i den plane benzenringen er omtrent 120°?
Hvilken funksjon har variabelen
sidei programmet?Hva må endres dersom du vil tegne ringen med kortere sider?
Løsningsforslag
Gunnar følger kanten av sekskanten og må derfor snu den ytre vinkelen. Den indre vinkelen er 120°, mens dreievinkelen blir \(180^\circ-120^\circ=60^\circ\).
Variabelen side får en ny verdi for hver løkkerunde, men verdien brukes ikke i beregningene. Den sørger likevel for at løkka går seks ganger. For å tegne kortere sider kan vi redusere verdien til sidelengde.
Tilfeldighet og random-biblioteket#
Bevegelsen til partikler i en væske eller gass påvirkes av et enormt antall kollisjoner. Vi kan ikke følge alle disse kollisjonene direkte i en enkel modell. I stedet kan vi la en partikkel velge en tilfeldig retning for hvert steg. Da trenger vi funksjonalitet fra random-biblioteket, som følger med Python.
Vi begynner med å importere funksjonen choice:
from random import choice
mulige_retninger = [0, 90, 180, 270]
retning = choice(mulige_retninger)
print(retning)
Kommandoen from random import choice betyr at vi henter funksjonen choice fra biblioteket random. Funksjonen velger ett tilfeldig element fra ei liste. Dersom du kjører programmet flere ganger, kan du derfor få ulike retninger.
Tidligere skrev vi from turtle import *. Stjerna betyr at vi importerer alle navnene i biblioteket. Det er praktisk i små turtle-programmer. Fra random trenger vi derimot bare én funksjon, og derfor importerer vi bare choice.
Datamaskinen lager egentlig pseudotilfeldige tall: De blir generert av en algoritme, men oppfører seg tilfeldig nok for denne modellen. Senere kan det være nyttig å kontrollere den tilfeldige tallfølgen slik at en simulering kan gjentas nøyaktig, men det trenger vi ikke gjøre her.
Tilfeldig vandring som diffusjonsmodell#
Vi kan nå kombinere choice med turtle og en for-løkke:
from turtle import *
from random import choice
shape("turtle")
speed(0)
pensize(2)
mulige_retninger = [0, 90, 180, 270]
antall_steg = 200
steglengde = 10
dot(10, "red") # startpunkt
for steg in range(antall_steg):
retning = choice(mulige_retninger)
setheading(retning)
forward(steglengde)
dot(10, "blue") # sluttpunkt
done()
For hvert steg velger programmet én av fire retninger med lik sannsynlighet. Kommandoen setheading peker Gunnar i den valgte retningen, og forward flytter ham ett steg. Den røde prikken viser startpunktet, mens den blå viser sluttpunktet.
Dette er en tilfeldig vandring. Modellen kan illustrere hvordan mange tilfeldige bevegelser kan gi en netto forflytning over tid. Samtidig er den svært forenklet:
Et molekyl beveger seg i tre dimensjoner, mens Gunnar beveger seg i to.
Et molekyl kan bevege seg i langt flere enn fire retninger.
Bevegelsen skjer ikke i like lange steg på et rutenett.
Én bane viser bevegelsen til én modellpartikkel, ikke hvordan en hel samling partikler sprer seg.
Modellen er altså ikke en bokstavelig framstilling av molekylbevegelse. Den kan likevel hjelpe oss med å utforske noen grunnleggende trekk ved diffusjon.
Underveisoppgave
Kjør programmet flere ganger dersom du har en editor som støtter turtle-grafikk.
Får partikkelen samme sluttposisjon hver gang?
Hva skjer dersom du øker
antall_steg?Hva skjer dersom du endrer
steglengde?Hvorfor kan vi ikke konkludere om diffusjon ut fra bare én tilfeldig bane?
Løsningsforslag
Sluttposisjonen vil vanligvis variere fordi programmet gjør nye tilfeldige valg. Flere eller lengre steg gir partikkelen mulighet til å komme lenger fra startpunktet, men den kan også bevege seg tilbake mot områder den allerede har besøkt.
Diffusjon beskriver hvordan en stor samling partikler sprer seg. Én tilfeldig bane kan være uvanlig og sier lite om hele fordelingen. For å undersøke diffusjon mer systematisk måtte vi ha simulert mange partikler eller gjentatt simuleringen mange ganger og analysert resultatene samlet.
While-løkker#
En while-løkke er en tilstandsløkke. Den gjentar kode så lenge et vilkår er sant. Vi bruker ofte en while-løkke når vi ikke vet på forhånd nøyaktig hvor mange gjentakelser som trengs.
Anta at vi gjennomfører en serie 1:2-fortynninger. Etter hver fortynning er konsentrasjonen halvparten av den forrige. Vi ønsker å finne hvor mange fortynninger som trengs før konsentrasjonen blir lavere enn en bestemt grense.
konsentrasjon = 0.80 # mol/L
grense = 0.05 # mol/L
antall_fortynninger = 0
while konsentrasjon >= grense:
print(f"Etter {antall_fortynninger} fortynninger: {konsentrasjon:.3f} mol/L")
konsentrasjon = konsentrasjon / 2
antall_fortynninger = antall_fortynninger + 1
print(f"Konsentrasjonen er nå {konsentrasjon:.3f} mol/L.")
Før hver løkkerunde undersøker Python om konsentrasjon >= grense er sant. Dersom det er sant, utføres de tre linjene som er rykket inn. Deretter undersøkes vilkåret på nytt. Når konsentrasjonen har blitt lavere enn grensa, er vilkåret usant og løkka avsluttes.
Det er avgjørende at noe inni løkka kan gjøre vilkåret usant. Dersom vi glemmer å oppdatere konsentrasjon, vil vilkåret alltid være sant og programmet fortsetter i en uendelig løkke.
Underveisoppgave
Les programmet linje for linje og forutsi hvor mange fortynninger som trengs. Hva skjer dersom vilkåret endres fra >= til >? Lag gjerne en løkketabell med kolonnene løkkerunde, konsentrasjon og antall fortynninger.
Løsningsforslag
Konsentrasjonen får verdiene 0,80, 0,40, 0,20, 0,10 og 0,05 mol/L før den blir 0,025 mol/L. Med vilkåret >= gjennomføres derfor fem fortynninger før konsentrasjonen blir lavere enn 0,05 mol/L.
Dersom vi bruker >, avsluttes løkka når konsentrasjonen er nøyaktig 0,05 mol/L. Da gjennomføres fire fortynninger. Valget av sammenlikningsoperator må derfor stemme med spørsmålet vi ønsker å besvare.
Du kan kjøre og endre fortynningsprogrammet i editoren nedenfor.
For-løkker#
En for-løkke er en telleløkke. Den passer godt når vi vet hvor mange ganger noe skal gjentas, eller når vi vil gjøre den samme operasjonen for hvert element i ei liste.
range-funksjonen#
Funksjonen range lager en sekvens av heltall som vi kan gå gjennom med en løkke:
Kommando |
Verdier |
|---|---|
|
0, 1, 2 |
|
2, 3 |
|
1, 3, 5, 7 |
Vi kan skrive en generell range-kommando som range(fra og med, til, steglengde). Det siste tallet er ikke med. Dersom vi bare bruker ett argument, starter sekvensen på 0. Dersom vi ikke oppgir steglengde, brukes steglengde 1.
for måling in range(5):
print(f"Gjennomfører måling nummer {måling + 1}")
Variabelen måling får verdiene 0, 1, 2, 3 og 4. Vi legger til 1 i utskriften fordi mennesker vanligvis nummererer den første målingen som måling 1, ikke måling 0.
Løkker gjennom lister#
Vi trenger ikke alltid range. Dersom vi vil behandle alle verdiene i ei liste, kan løkkevariabelen få selve verdiene direkte:
absorbansverdier = [0.42, 0.39, 0.44, 0.41]
total_absorbans = 0
for absorbans in absorbansverdier:
total_absorbans = total_absorbans + absorbans
gjennomsnitt = total_absorbans / len(absorbansverdier)
print(f"Gjennomsnittlig absorbans: {gjennomsnitt:.3f}")
Første gang løkka kjører, er absorbans = 0.42. Neste gang er absorbans = 0.39, og slik fortsetter det til alle elementene er brukt. Variabelen total_absorbans oppdateres for hver måling. Etter løkka deler vi totalen på antall målinger.
Å følge variablene gjennom ei løkke#
Vi kan systematisere løkkene med en løkketabell. Den holder styr på verdiene til variablene underveis:
konsentrasjon = 0.80
for trinn in range(4):
konsentrasjon = konsentrasjon / 2
Underveisoppgave
Fyll inn resten av løkketabellen. Tabellen skal vise verdien til variablene etter at den innrykkede kodelinja er utført i hver løkkerunde.
Løkkerunde |
|
|
|---|---|---|
Startverdi |
– |
0,80 |
1 |
0 |
0,40 |
2 |
1 |
|
3 |
||
4 |
Løsningsforslag
Løkkerunde |
|
|
|---|---|---|
Startverdi |
– |
0,80 |
1 |
0 |
0,40 |
2 |
1 |
0,20 |
3 |
2 |
0,10 |
4 |
3 |
0,05 |
Løkka går fire ganger selv om den siste verdien til trinn er 3. Dette er en vanlig kilde til misforståelser: Vi teller fire verdier, men starter på 0.
I noen løkker trenger vi ikke å bruke tellevariabelen. Da er det vanlig å kalle den _ for å vise at verdien ikke er viktig:
mengde = 10.0 # mg
for _ in range(5):
mengde = mengde / 2
print(f"Etter fem halveringstider gjenstår {mengde:.3f} mg.")
Underveisoppgave
Skriv om programmet ovenfor slik at det skriver ut mengden etter hver halveringstid. Dersom du vil ha med nummeret på halveringstida i utskriften, bør du da fortsatt bruke _ som løkkevariabel?
Løsningsforslag
Når vi vil bruke nummeret i utskriften, bør løkkevariabelen få et beskrivende navn:
mengde = 10.0 # mg
for halveringstid in range(1, 6):
mengde = mengde / 2
print(f"Etter {halveringstid} halveringstider: {mengde:.3f} mg")
Å tenke i løkker#
Løkker kan brukes til alt fra å summere tallrekker til å behandle måledata og utforske hvordan kjemiske systemer utvikler seg over tid. Når vi bruker løkker, må vi dele problemet opp i trinn som kan gjentas.
I matematikk og naturvitenskap representerer vi ofte sammenhenger med kontinuerlige funksjoner og formler. I et program kan vi i stedet utføre en liten operasjon om gangen. Dette kalles å løse problemet iterativt, fordi iterasjon betyr gjentakelse. Litt mer uformelt kan vi kalle det å løse et problem trinnvis med små, repeterende operasjoner for «å tenke i løkker».
Behandling av kjemiske data#
Løkker er nyttige når det samme kriteriet skal brukes på mange målinger. Anta at absorbansverdier mellom 0,10 og 1,00 ligger i det anbefalte arbeidsområdet for en bestemt analysemetode. Vi kan hente ut de aktuelle målingene slik:
absorbansverdier = [0.08, 0.42, 0.39, 1.18, 0.44]
gyldige_målinger = []
for absorbans in absorbansverdier:
if 0.10 <= absorbans <= 1.00:
gyldige_målinger.append(absorbans)
print(gyldige_målinger)
Her kombinerer vi ei liste, ei for-løkke og et vilkår. Hver absorbansverdi vurderes etter tur. Bare verdiene som oppfyller kriteriet, legges til i den nye lista.
Det anbefalte absorbansområdet er bare et eksempel. I en virkelig analyse må grensene bestemmes ut fra instrumentet, kalibreringen og metoden som brukes. Et program kan anvende kriteriene konsekvent, men det kan ikke avgjøre om kriteriene er faglig gode.
En enkel kinetisk modell#
Vi kan også bruke ei løkke til å undersøke hvordan en reaksjon utvikler seg over tid. Vi ser på den irreversible reaksjonen
og antar at reaksjonsfarten er proporsjonal med konsentrasjonen av A:
I løpet av et lite tidssteg \(\Delta t\) kan vi tilnærme konsentrasjonsendringen som
Da kan vi oppdatere konsentrasjonene litt etter litt:
k = 0.15 # ratekonstant i s^-1
A = 1.00 # startkonsentrasjon av A i mol/L
B = 0.00 # startkonsentrasjon av B i mol/L
dt = 0.10 # tidssteg i s
tid_slutt = 10 # sluttid i s
antall_steg = int(tid_slutt / dt)
for steg in range(antall_steg):
endring = k * A * dt
A = A - endring
B = B + endring
print(f"Etter {tid_slutt} s er [A] = {A:.3f} mol/L.")
print(f"Etter {tid_slutt} s er [B] = {B:.3f} mol/L.")
print(f"Total konsentrasjon: {A + B:.3f} mol/L.")
For hvert tidssteg beregner vi først hvor mye A som omdannes. Den samme endringen trekkes fra A og legges til B. Dermed bevares den totale konsentrasjonen i denne forenklede modellen.
Dette er en numerisk tilnærming. Vi antar blant annet at reaksjonsfarten er omtrent konstant innenfor hvert lite tidssteg. Dersom dt er for stor, blir tilnærmingen dårligere. Senere skal vi se nærmere på slike simuleringer og hvordan vi kan teste om resultatene er pålitelige.
Underveisoppgave
Kjør programmet med dt = 1, dt = 0.1 og dt = 0.01. Hvordan påvirkes sluttverdien til A? Hvorfor må antall_steg endres når tidssteget endres?
Løsningsforslag
Når tidssteget blir mindre, oppdateres reaksjonsfarten oftere. Resultatet nærmer seg derfor en stabil verdi. Et mindre tidssteg gir samtidig flere løkkerunder og dermed flere beregninger.
Sluttida skal være den samme i alle simuleringene. Derfor må antall steg beregnes som tid_slutt / dt. Med en sluttid på 10 s gir dt = 1 ti steg, mens dt = 0.01 gir tusen steg.
Tallfølger og rekkesummer#
Løkker brukes ofte til å generere tallfølger. En tallfølge er en oppramsing av tall som kan være endelig eller uendelig. Etter hver halveringstid er for eksempel andelen av et radioaktivt stoff som gjenstår
Hvert tall i en tallfølge kalles et ledd. Dersom \(N_n\) er mengden etter \(n\) halveringstider, kan vi beskrive følgen rekursivt:
En rekursiv formel bruker altså en tidligere verdi til å beregne den neste. Det passer svært godt med ei løkke:
mengde = 4.0 # kg
for antall_halveringstider in range(11):
print(f"n = {antall_halveringstider}: {mengde:.6f} kg")
mengde = mengde / 2
Programmet skriver først ut startmengden ved \(n=0\). Deretter oppdateres mengden før neste løkkerunde.
Når vi beskriver en tallfølge som en serie av tall som adderes, kaller vi det ei rekke. Et matematisk eksempel er den uendelige geometriske rekka
Det \(n\)-te leddet kan skrives som \((2/3)^n\). En datamaskin kan ikke summere uendelig mange ledd, men vi kan tilnærme summen med et stort, endelig antall:
antall_ledd = 100
summen = 0
for n in range(antall_ledd):
ledd = (2 / 3)**n
summen = summen + ledd
print(summen)
Den analytiske summen er 3. Programmet gir en verdi som er svært nær 3, men dette er fremdeles en numerisk tilnærming. Eksemplet viser også et generelt mønster som vi ofte bruker i kjemiske beregninger: Vi begynner med en startverdi, beregner et nytt bidrag og oppdaterer en akkumulert verdi for hver løkkerunde.
Underveisoppgave
Modifiser programmet slik at det bruker en while-løkke og fortsetter å summere helt til det neste leddet er mindre enn \(10^{-8}\). Hvorfor passer en while-løkke godt når stoppkriteriet formuleres på denne måten?
Løsningsforslag
n = 0
ledd = 1
summen = 0
while ledd >= 1E-8:
summen = summen + ledd
n = n + 1
ledd = (2 / 3)**n
print(f"Summen er omtrent {summen}.")
print(f"Vi brukte {n} ledd.")
Vi vet ikke på forhånd nøyaktig hvor mange ledd som trengs. Løkka skal fortsette så lenge leddet er større enn toleransen, og derfor passer en tilstandsløkke godt.
Nøstede løkker#
Nøstede løkker er løkker inni løkker. Da gjentas den innerste løkka for hver runde i den ytterste løkka.
Et kjemisk eksempel er elektronoverganger i hydrogenatomet. Dersom elektronet starter i nivå \(n\), kan det deeksitere til alle nivåer \(m<n\). Vi kan skrive ut overgangene fra nivåene 2 til og med 5 slik:
for n in range(2, 6):
for m in range(1, n):
print(f"Mulig overgang: n = {n} til m = {m}")
Når n = 2, får den indre løkka bare verdien m = 1. Når n = 3, får m verdiene 1 og 2. Den indre løkka blir altså lengre for hvert nye energinivå. Totalt skriver programmet ut \(1+2+3+4=10\) mulige overganger.
Underveisoppgave
Utvid programmet til startnivåene 2–7. Hvor mange overganger blir skrevet ut? Modifiser deretter programmet slik at det bare skriver ut overganger som ender i nivået m = 2.
Løsningsforslag
Fra nivåene 2–7 får vi \(1+2+3+4+5+6=21\) overganger totalt. Dersom bare overganger til m = 2 skal skrives ut, trenger vi ikke den indre løkka:
for n in range(3, 8):
m = 2
print(f"Mulig overgang: n = {n} til m = {m}")
Dette er også et viktig programmeringspoeng: Vi bør ikke bruke en nøstet løkke dersom problemet kan løses enklere uten.
Oppgaver#
Oppgave 4.1
Sammenlikn programmene nedenfor. Beskriv forskjeller og likheter. Hva blir skrevet ut?
konsentrasjon = 0.80
for trinn in range(4):
konsentrasjon = konsentrasjon / 2
print(konsentrasjon)
konsentrasjon = 0.80
trinn = 0
while trinn < 4:
konsentrasjon = konsentrasjon / 2
print(konsentrasjon)
trinn = trinn + 1
Løsningsforslag
Begge programmene skriver ut 0,40, 0,20, 0,10 og 0,05. For-løkka gjentar operasjonen fire ganger fordi range(4) inneholder fire verdier. While-løkka gjentar operasjonen så lenge trinn < 4. Her må vi selv opprette og oppdatere tellevariabelen trinn.
Når antall gjentakelser er kjent på forhånd, er for-løkka vanligvis enklest. En while-løkke er særlig nyttig når vi vil fortsette til en faglig betingelse er oppfylt.
Oppgave 4.2
Lag en løkketabell med løkkerunde, i, mengde og målt_mengde for følgende program:
mengde = 8.0
for i in range(4):
mengde = mengde / 2
målt_mengde = mengde * 0.95
Løsningsforslag
Løkkerunde |
|
|
|
|---|---|---|---|
Startverdi |
– |
8,0 |
– |
1 |
0 |
4,0 |
3,8 |
2 |
1 |
2,0 |
1,9 |
3 |
2 |
1,0 |
0,95 |
4 |
3 |
0,5 |
0,475 |
Oppgave 4.3
Lag et program som skriver ut «Måling registrert» fem ganger. Hvilken funksjon har løkkevariabelen? Velg et variabelnavn som viser om verdien brukes eller ikke.
Løsningsforslag
for _ in range(5):
print("Måling registrert")
Løkkevariabelen sørger for at løkka går fem ganger, men verdien brukes ikke. Derfor bruker vi _ som variabelnavn.
Oppgave 4.4
Programmet nedenfor skal finne summen av de åtte første tallene i en tallfølge der hvert ledd er det dobbelte av det forrige. Forklar hvordan programmet fungerer, og finn ut hvilke åtte tall som summeres.
tall = 1
summen = 0
for _ in range(8):
summen = summen + tall
tall = tall * 2
print(summen)
Løsningsforslag
Variabelen tall inneholder leddet som skal legges til, mens summen inneholder den akkumulerte summen. Etter at leddet er lagt til, dobles tall før neste løkkerunde. Programmet summerer
Oppgave 4.5
Skriv om programmet i oppgave 4.4 slik at det bruker en while-løkke i stedet for en for-løkke.
Løsningsforslag
tall = 1
summen = 0
antall_ledd = 0
while antall_ledd < 8:
summen = summen + tall
tall = tall * 2
antall_ledd = antall_ledd + 1
print(summen)
Oppgave 4.6
Hva skrives ut i følgende program? Undersøk først programmet for hånd, og kontroller deretter svaret i en editor.
konsentrasjon = 1.0
for i in range(4):
print(i, konsentrasjon)
konsentrasjon = konsentrasjon / 2
print("Slutt:", konsentrasjon)
Løsningsforslag
Programmet skriver ut:
0 1.0
1 0.5
2 0.25
3 0.125
Slutt: 0.0625
Utskriften inni løkka skjer før konsentrasjonen halveres. Utskriften utenfor løkka viser sluttverdien etter fire halveringer.
Oppgave 4.7
Bruk turtle-grafikk til å tegne en enkel sikksakk-kjede med ti bindinger. La Gunnar skifte mellom å svinge 60° mot venstre og 60° mot høyre. Du kan bruke modulusoperatoren (%) og et vilkår til å skille mellom partall og oddetall.
Hvordan skiller denne todimensjonale strektegningen seg fra en molekylmodell?
Løsningsforslag
from turtle import *
shape("turtle")
speed(3)
for binding in range(10):
forward(40)
if binding % 2 == 0:
left(60)
else:
right(60)
done()
Tegningen viser bare en strekrepresentasjon i to dimensjoner. Den viser ikke atomer, elektroner, bindingstyper eller molekylets tredimensjonale konformasjon. Den kan derfor illustrere gjentakelse og et karbonskjelett, men er ikke en fullstendig molekylmodell.
Oppgave 4.8
Modifiser programmet for tilfeldig vandring slik at partikkelen kan bevege seg i åtte retninger: 0°, 45°, 90°, 135°, 180°, 225°, 270° og 315°. Sammenlikn banene med modellen som bare bruker fire retninger. Hvilken modell ser mest kontinuerlig ut? Hvilke viktige forenklinger gjenstår?
Løsningsforslag
from turtle import *
from random import choice
shape("turtle")
speed(0)
mulige_retninger = [0, 45, 90, 135, 180, 225, 270, 315]
for steg in range(200):
retning = choice(mulige_retninger)
setheading(retning)
forward(10)
done()
Åtte retninger gir flere mulige baner og kan se mindre rutenettpreget ut. Partikkelen beveger seg likevel i to dimensjoner, med faste steglengder og et lite antall mulige retninger. Modellen beskriver heller ikke kollisjoner eller krefter eksplisitt.
Oppgave 4.9
En analyse gir absorbansverdiene [0.08, 0.42, 0.39, 1.18, 0.44, 0.41]. I denne oppgaven regner vi verdier fra og med 0,10 til og med 1,00 som gyldige.
Lag et program som
teller antall gyldige og ugyldige målinger
beregner gjennomsnittet av de gyldige målingene
gir en egen beskjed dersom ingen målinger er gyldige.
Løsningsforslag
absorbansverdier = [0.08, 0.42, 0.39, 1.18, 0.44, 0.41]
total = 0
antall_gyldige = 0
antall_ugyldige = 0
for absorbans in absorbansverdier:
if 0.10 <= absorbans <= 1.00:
total = total + absorbans
antall_gyldige = antall_gyldige + 1
else:
antall_ugyldige = antall_ugyldige + 1
if antall_gyldige > 0:
gjennomsnitt = total / antall_gyldige
print(f"Gjennomsnitt: {gjennomsnitt:.3f}")
else:
print("Ingen gyldige målinger.")
print(f"Gyldige målinger: {antall_gyldige}")
print(f"Ugyldige målinger: {antall_ugyldige}")
Oppgave 4.10
En løsning har startkonsentrasjon 1,00 mol/L. Den fortynnes gjentatte ganger 1:2. Lag et program som fortsetter til konsentrasjonen er lavere enn 0,010 mol/L. Programmet skal skrive ut antall fortynninger og sluttkonsentrasjonen.
Løsningsforslag
konsentrasjon = 1.00
grense = 0.010
antall_fortynninger = 0
while konsentrasjon >= grense:
konsentrasjon = konsentrasjon / 2
antall_fortynninger = antall_fortynninger + 1
print(f"Antall fortynninger: {antall_fortynninger}")
print(f"Sluttkonsentrasjon: {konsentrasjon:.5f} mol/L")
Oppgave 4.11
Utvid den kinetiske modellen for \(A \rightarrow B\) slik at programmet lagrer tid og konsentrasjonen av A i hver sine lister. Plott deretter \([A]\) som funksjon av tid. Kjør modellen med minst to ulike tidssteg og sammenlikn kurvene.
Løsningsforslag
import matplotlib.pyplot as plt
k = 0.15
A = 1.00
dt = 0.10
tid_slutt = 10
antall_steg = int(tid_slutt / dt)
tid = [0]
A_verdier = [A]
for steg in range(antall_steg):
endring = k * A * dt
A = A - endring
t = (steg + 1) * dt
tid.append(t)
A_verdier.append(A)
plt.plot(tid, A_verdier)
plt.xlabel("Tid (s)")
plt.ylabel("[A] (mol/L)")
plt.show()
For å sammenlikne tidssteg kan programmet kjøres på nytt med for eksempel dt = 1, dt = 0.1 og dt = 0.01. Husk at antall_steg må beregnes på nytt hver gang.
Oppgave 4.12
Når et elektron i et hydrogenatom deeksiterer fra energinivå \(n\) til et lavere nivå \(m\), er energien til det emitterte fotonet
der \(B=2{,}18\cdot10^{-18}\) J. Vi bruker også sammenhengene \(\Delta E=hf\) og \(\lambda=c/f\).
a) Beregn bølgelengden ved overgangen fra \(n=5\) til \(m=2\). Dette skal gi omtrent 434 nm.
b) Bruk ei løkke til å beregne alle mulige bølgelengder fra ett valgt startnivå n.
c) Bruk nøstede løkker til å beregne alle mulige overganger fra startnivåene 2–6.
Løsningsforslag
B = 2.18E-18 # J
h = 6.626E-34 # J s
c = 3.00E8 # m/s
# a)
n = 5
m = 2
energi = B * (1 / m**2 - 1 / n**2)
frekvens = energi / h
bølgelengde_nm = c / frekvens * 1E9
print(f"n = {n} til m = {m}: {bølgelengde_nm:.2f} nm")
# b)
n = 5
for m in range(1, n):
energi = B * (1 / m**2 - 1 / n**2)
frekvens = energi / h
bølgelengde_nm = c / frekvens * 1E9
print(f"n = {n} til m = {m}: {bølgelengde_nm:.2f} nm")
# c)
for n in range(2, 7):
for m in range(1, n):
energi = B * (1 / m**2 - 1 / n**2)
frekvens = energi / h
bølgelengde_nm = c / frekvens * 1E9
print(f"n = {n} til m = {m}: {bølgelengde_nm:.2f} nm")
Oppgave 4.13
Mengden av en radioaktiv isotop etter tida \(t\) kan modelleres som
der \(a=\ln(2)/T\) og \(T\) er halveringstida. Plutonium-239 har en halveringstid på omtrent 24 000 år.
Skriv et program som skriver ut hvor mye som gjenstår for hvert 5000. år over en periode på 50 000 år dersom startmengden er 4,0 kg.
Løsningsforslag
from math import log, exp
startmengde = 4.0 # kg
halveringstid = 24000 # år
a = log(2) / halveringstid
for tid in range(0, 50001, 5000):
mengde = startmengde * exp(-a * tid)
print(f"Etter {tid:5d} år gjenstår {mengde:.3f} kg.")
Her importerer vi funksjonene log og exp fra standardbiblioteket math. De beregner henholdsvis den naturlige logaritmen og eksponentialfunksjonen.
Oppgave 4.14
Ideelle gasser kan beskrives med likningen
Lag ei liste med trykk fra 100 til og med 400 kPa med steg på 25 kPa. Bruk ei løkke til å beregne volumet i liter for 1,00 mol ideell gass ved 298 K. Lagre volumene i ei liste og plott volum som funksjon av trykk.
Pass på enhetene: Gasskonstanten \(R=8{,}314\) Pa m\(^3\) K\(^{-1}\) mol\(^{-1}\) krever trykk i pascal og gir volum i m\(^3\).
Løsningsforslag
import matplotlib.pyplot as plt
T = 298 # K
n = 1.00 # mol
R = 8.314 # Pa m^3 K^-1 mol^-1
trykk_kPa = []
volum_L = []
for trykk in range(100, 401, 25):
trykk_kPa.append(trykk)
trykk_Pa = trykk * 1000
volum_m3 = n * R * T / trykk_Pa
volum_L.append(volum_m3 * 1000)
plt.plot(trykk_kPa, volum_L, marker="o")
plt.xlabel("Trykk (kPa)")
plt.ylabel("Volum (L)")
plt.show()
Ved 100 kPa får vi omtrent 24,8 L. Dette er i samsvar med det molare volumet til en tilnærmet ideell gass ved romtemperatur og omtrent atmosfæretrykk.
Oppgave 4.15
Vi vil undersøke hvordan startkonsentrasjonen og ratekonstanten påvirker den kinetiske modellen for \(A \rightarrow B\). Bruk nøstede løkker til å simulere alle kombinasjonene av
startkonsentrasjoner = [0.50, 1.00]mol/Lratekonstanter = [0.05, 0.10, 0.20]s\(^{-1}\).
Bruk dt = 0.01 s og tid_slutt = 10 s. Skriv ut sluttkonsentrasjonen av A for hver kombinasjon.
Løsningsforslag
startkonsentrasjoner = [0.50, 1.00]
ratekonstanter = [0.05, 0.10, 0.20]
dt = 0.01
tid_slutt = 10
antall_steg = int(tid_slutt / dt)
for startkonsentrasjon in startkonsentrasjoner:
for k in ratekonstanter:
A = startkonsentrasjon
for _ in range(antall_steg):
endring = k * A * dt
A = A - endring
print(f"[A]0 = {startkonsentrasjon:.2f} mol/L, "
f"k = {k:.2f} s^-1: [A] = {A:.3f} mol/L")
Her har vi egentlig tre løkker inni hverandre. De to ytterste løkkene går gjennom parameterkombinasjonene, mens den innerste utfører selve tidssimuleringen.
Oppgave 4.16
Lag et program som regner ut summen
Løs oppgaven både med en for-løkke og en while-løkke.
Løsningsforslag
# For-løkke
summen = 0
for n in range(51):
summen = summen + 1 / 2**n
print(summen)
# While-løkke
summen = 0
n = 0
while n <= 50:
summen = summen + 1 / 2**n
n = n + 1
print(summen)
Oppgave 4.17
Fibonaccifølgen er en kjent tallfølge der hvert tall etter de to første er summen av de to foregående:
Lag et program som skriver ut de første n tallene i følgen.
Løsningsforslag
x0 = 0
x1 = 1
n = 20
for nummer in range(1, n + 1):
print(f"Fibonaccitall nr. {nummer}: {x1}")
x0, x1 = x1, x0 + x1
Den siste linja oppdaterer begge variablene samtidig. Høyresida beregnes før de nye verdiene lagres.
Oppgave 4.18
I kombinatorikk er \(n\)-fakultet definert som produktet av alle heltall fra og med 1 til og med \(n\):
Lag et program som regner ut fakultetet til et positivt heltall.
Løsningsforslag
n = int(input("Skriv inn et positivt heltall: "))
fakultet = 1
for k in range(1, n + 1):
fakultet = fakultet * k
print(f"{n}! = {fakultet}")
Oppgave 4.19
Eksponentialfunksjonen kan tilnærmes med et Taylorpolynom:
Lag en funksjon exp_taylor(x, n) som beregner tilnærmingen med n ledd. Bruk deretter ei løkke til å sammenlikne tilnærmingen med math.exp(x) for heltallene 1–10.
Løsningsforslag
from math import factorial, exp
def exp_taylor(x, n):
tilnærming = 0
for i in range(n):
tilnærming = tilnærming + x**i / factorial(i)
return tilnærming
antall_ledd = 20
for x in range(1, 11):
numerisk = exp_taylor(x, antall_ledd)
eksakt = exp(x)
feil = abs(numerisk - eksakt)
print(f"x = {x:2d}: tilnærming = {numerisk:.6f}, feil = {feil:.2E}")
Videoer#
I videoene nedenfor kan du få en innføring eller repetisjon i den grunnleggende teorien bak løkker:
Når du har sett videoen, kan du gjøre følgende oppgave for å sjekke om du forstår innholdet:
Underveisoppgave
Skriv et program som regner ut summen av alle heltallene fra og med 1 til og med 449 ved hjelp av en for-løkke.
Løsningsforslag
summen = 0
for tall in range(1, 450):
summen = summen + tall
print("Summen er:", summen)
Når du har sett videoen, kan du gjøre følgende oppgave for å sjekke om du forstår innholdet:
Underveisoppgave
Skriv et program som regner ut summen av tallene fra og med 1 til og med 449 ved hjelp av en while-løkke.
Løsningsforslag
summen = 0
tall = 1
while tall <= 449:
summen = summen + tall
tall = tall + 1
print("Summen er:", summen)