programmering för fysiker - Åbo akademi
TRANSCRIPT
Programmering för fysikerHöstterminen 2008
Institutionen för fysik
Innehåll
Lektion 1
for-loop 2
Funktionen printf 2
Division av heltal 2
Programmets struktur 2
Kompilering 2
Kompilering i Linux 2
Kompilering i Windows 2
Förkortad notation 3
if-satser 3
Jämförelseoperatorer 3
Mer information 3
Lektion 2
Funktioner 4
Variablers synlighet (scope) 4
Call by value 4
Globala variabler 4
Matematik 4
Lista över matematikbibliotekets funktioner 4
Datatyper 5
Kommandot make 5
Introduktion till pekare 5
Lektion 3
Filhantering (Input/Output) 6
Input med scanf 6
Arrayer 6
Numerisk integrering 7
Gnuplot 7
Komplexa tal i C 8
Arrayer och pekare 8
Listor
Funktioner 9
Datatyper 9
Nyckelord 9
Argument för printf 9
Bibliotek 9
Kompileringsflaggor 9
Lektion 4
Fraktaler 10
Grafik med biblioteket SDL 10
Kompilering 11
Strukturer 11
Ett grafiskt mandelbrotprogram 12
Färger och pixlar i SDL 12
Fler färger 12
Lektion 5
Ordinära differentialekvationer 13
Differentialekvationer av högre ordning 13
Exempel: Rörelse i en dimension 13
Kopplade differentialekvationer 13
En pendel 14
Mer information 14
Lektion 6
Diskretisering 15
Diskretisering av derivator 15
Schrödingerekvationen 15
Numerisk lösning av Schrödingerekvationen 15
Lektion 7
Slumptal 18
Slumpvandring 18
Olika fördelningar 18
Definitioner 18
Likformig fördelning i ett intervall 18
Exponentiell fördelning 18
Gaussisk fördelning / normalfördelning 19
Diffusion-Limited Aggregation 19
Kvaliteten på slumptal 19
Lektion 8
Linjär algebra med LAPACK 20
Egenvärden och egenvektorer 20
Att anropa FORTRAN-funktioner från C 20
Egenfunktioner för Schrödingerekvationen 20
Referenser 22
Programmering för fysiker. Lektion 1.
Lektion 1
Exempelprogram 1. Ett program som räknar ihop en summa.#include <stdio.h>#include <math.h>
int main (){ int n; double resultat; resultat = 0;
for (n = 1; n < 10000; n = n + 1) { resultat = resultat + 1.0/(n*n); }
printf ("Svaret är: %f\n", resultat); printf ("pi^2/6 = %f\n", M_PI*M_PI / 6.0); return 0;}
Kommandot #include <stdio.h> läser in en fil som heter "stdio.h". Filen
innehåller bl.a. printf-funktionen. Filen math.h innehåller matematiska
funktioner och konstanter.
Märk att så gott som alla rader avslutas med semikolon ;
Variablerna i exempelprogrammetint n;double resultat;
n är en variabel av typ int (står för integer), ett positivt eller negativt
heltal.
resultat är en variabel av typ double, ett reellt tal (s.k. floating point,
ca 15 decimalers noggrannhet. double står för "double precision").
Variabler får inte något startvärde automatiskt, de måste initialiseras.
Variabeln resultat tilldelas värdet 0 med kommandot resultat = 0;
for-loop
En for-loop används för att upprepa ett block så länge ett villkor är
uppfyllt. En vanlig användning är att upprepa något ett visst antal
gånger.
OBS! inget semikolon efter for(...) !
for (n = 1; n < 10000; n = n + 1) { ... }
Uttrycket i parentesen har tre delar: initialisering "n=1", villkor
"n<10000" och inkrementering "n=n+1". Initialiseringen (n ges värdet 1)
utförs en gång. Blocket (koden mellan klamrarna) utförs så länge vill-
koret är sant. Inkrementeringen (värdet på n ökas med ett) utförs
varje gång blocket har utförts.
Funktionen printf
Funktionen printf kan användas för att skriva ut text och variablers
värden på skärmen.printf ("Svaret är: %f\n", resultat);
kommer att skriva ut texten Svaret är: 1.644834
Funktionen ges i det här fallet två argument, separerade med
kommatecken. Texten i det första argumentet skrivs ut, och %f byts ut
mot det andra argumentet. \n betyder radbyte. För att skriva ut heltal
(int) används %d istället för %f.
Exempel.
En printf med bara ett argument.printf("Här är en text. Den byter inte rad i slutet.");
Kommer att skriva ut:Här är en text. Den byter inte rad i slutet.
Exempel.
Flera värden kan skrivas ut med samma printf.int a = 3;double b = 4.0;printf ("a har värdet %d och b har värdet %f", a, b);
Programmet kommer att skriva:a har värdet 3 och b har värdet 4.000000
Exempel.printf ("%d %d %d %f %f %f", a, a*a, 2*a*a, b, b*b, a*b*b);
Kommer att skriva ut:3 9 18 4.000000 16.00000 48.00000
Division av heltal
Beräkningar som innehåller endast heltal ger heltal som resultat. Vid
division leder det här till avrundning, t.ex. 2/3 ger resultatet 0. Om
man vill ha ett resultat av typ double, måste en av operanderna vara av
typ double, t.ex. 2.0/3 ger 0.66666666666667. "2" är alltså av typen int,
"2.0" av typen double.
M_PI är en konstant som deklareras i math.h och är en approximation
av ¼.
Programmets struktur
int main (){ ... return 0;}
Det här deklarerar funktionen main. Varje C-program har en main-
funktion som körs då programmet startas. I vårt exempel ger man inga
argument åt main, så den har tomma argumentparenteser efter sig.
Mer om funktioner senare.
Kompilering
Programkoden skrivs i en textfil, som oftast ges suffixet ".c". För att få
en fil som kan köras måste .c-filen kompileras, detta görs med en
kompilator. Här använder vi kompilatorn gcc.
Kompilering i LinuxI en terminal, kör gcc exempel1.c (i den katalog där filen exempel1.c
finns). Om allt fungerar skapas den körbara filen a.out, som kan köras
med kommandot ./a.out I linux finns gcc ofta färdigt installerat, om
inte kan det installeras med distributionens pakethanterare (t.ex. yum i
Fedora). För att skriva koden behövs någon texteditor, t.ex. kedit, kate
eller emacs.
Kompilering i WindowsKör gcc exempel1.c i ett kommandofönster (i den katalog där filen
exempel1.c finns). Det körbara programmet heter a.exe. Kör program-
met med kommandot "a". gcc för windows kallas MinGW, det finns på
www.mingw.org. Koden kan skrivas i någon texteditor, Notepad duger,
2
Programmering för fysiker. Lektion 1.
Word rekommenderas inte. Se också kurshemsidan för mer diskussion
om editorer och om hur man installerar MinGW.
Man kan ge den körbara filen ett annat namn än a.out / a.exe med
flaggan -o på gcc:s kommandorad:gcc exempel1.c -o exempel.exe
En annan nyttig flagga är -Wall, som gör att gcc skriver ut varnings-
meddelanden om programmet verkar suspekt.gcc exempel1.c -Wall
Förkortad notation
För ofta förekommande matematiska operationer finns en förkortad
notation:
n = n + 1; n++; öka med 1
n = n - 1; n--; minska med 1
n = n + a; n += a; öka med a
b = b * 1.34; b *= 1.34; multiplicera med 1.34
Om ett block består av endast en sats, kan klamrarna lämnas bort.
if-satser
if-satser används då något ska utföras om ett villkor är uppfyllt.
Exempel:int a = 8;if (a > 5) { printf ("a är större än fem\n"); }
Om villkoret innanför parentesen är sant utförs blocket efter if.
Med else kan ett annat block specificeras, som utförs om vilkoret är
falskt. else-blocket behövs inte.
Exempel.int a = 3;if (a > 5) { printf ("a är större än fem\n"); }else { printf ("a är inte större än fem\n"); }
Märk! Likhet testas med operatorn == . Enkelt likhetstecken = används
endast för tilldelning.if (a == 5) ...
Jämförelseoperatorer
== lika med
!= inte lika med
> större än
>= större än eller lika med
< mindre än
<= mindre än eller lika med
Exempelprogram 2.//Skriver ut alla primtal < 1000.#include <stdio.h>
int main (){ int t; //talet som testas int f; //en faktor i talet? int prime;
for (t = 2; t < 1000; t++)
{ prime = 1; for (f = 2; f < t; f++) { if (t % f == 0) { prime = 0; } } if (prime == 1) printf ("%d ", t); } printf ("\n"); return 0;}
% är modulo-operatorn. Den ger resten vid en heltalsdivision, t.ex 11 %
3 är 2. t är delbart med f om resten vid division av t med f är 0, dvs
om t % f är 0.
Programmets funktionsprincip: Gå igenom heltalen från 2 till 1000.
För varje tal t, prova om talet är delbart med något mindre heltal f.
Om ett sådant f hittas är t ett sammansatt tal. Om inget f hittades är
t ett primtal.
Mer information
En C-tutorial:
http://www.cprogramming.com/tutorial.html (Se en bit ner på sidan)
http://www.cppreference.com/
Länkarna finns också på kurshemsidan,
www.abo.fi/~fjansson/progkurs/
En bok (finns på insten): Brian W. Kernighan och Dennis M. Ritchie:
The C programming Language (Prentice Hall, 1988)
Linux har ett manualprogram, man, som innnehåller dokumentation för
kommandon och c-funktioner.
t.ex. man 3 printf
Programmet avslutas genom att trycka på q.
3 anger sektionen som man vill söka i, sektion 3 innehåller dokumen-
tation om funktioner som kan anropas från ett C-program. Sektionen
måste anges i det här fallet, eftersom det också finns ett program med
namnet printf. Ett alternativ är att använda flaggan -a, som gör att
alla manualsidor som hittas med ett visst namn visas.
info libc visar dokumentation för c-standardbiblioteket.
info gcc visar dokumentation för c-kompilatorn
Exempelprogram 3.
Ett minimalt C-program, som bara skriver ut en rad text.
Lämpligt för att prova om man installerat kompilatorn rätt.
#include <stdio.h>int main () { printf ("Hallå!\n"); return 0; }
3
Programmering för fysiker. Lektion 2.
Lektion 2
Funktioner
main och printf är exempel på funktioner.
Man kan också definiera egna funktioner, för att dela upp ett program
i hanterliga delar. Om någonting ska göras på flera ställen i ett
program, är det ofta en god idé att definiera en funktion för det.
Exempelprogram 1. En enkel funktion#include <stdio.h>int cube (int x);
int main (){ int i, V; for (i = 0; i < 4; i++) { V = cube(i); printf ("cube(%d) är %d\n", i, V); } return 0;}
int cube (int x){ int volume; volume = x * x * x; return volume;}
int cube (int x); i början av programmet är en funktionsprototyp,
som anger vilken datatyp funktionen returnerar, hur många para-
metrar den ska anropas med, och vilka datatyper parametrarna har.
Funktionens definition finns sist i programmet. return avslutar funk-
tionen, och anger funktionens resultat, dess returvärde. Observera att
funktionsprototypen avslutas med ett semikolon, medan det inte finns
ett semikolon i definitionen av funktionen.
En funktion måste inte returnera något värde. En funktion som inte
returnerar ett värde bör definieras med returtypen void:void funk (int x){ ... return;}
En void-funktion kan (men måste inte) avslutas med return, utan
returvärde.
En funktion behöver inte heller ta emot några parametrar, det kan
specificeras med void i parameterlistan:int f (void){ ...}
Exempelprogram 2. En funktion som beräknar fakultet. Den är av
typen double, eftersom svaret kan bli stort och kanske inte ryms i en
int.#include <stdio.h>double fakultet (int n);
int main (){ int i; printf ("20! = %f\n\n", fakultet (20)); for (i = 0; i <= 10; i++) printf ("%d! = %f\n", i, fakultet(i));
return 0;
}
//Beräkna n! = 1 * 2 * 3 * ... * ndouble fakultet (int n){ double f = 1; int i; for (i = 1; i <= n; i++) f = f * i;
return f;}
Variablers synlighet (scope)I exempelprogrammet ovan finns det en variabel som heter i både i
main och i fakultet. De är helt orelaterade, en variabel som deklareras
inne i en funktion är synlig bara innanför funktionen. Det här är en
bra sak, eftersom man kan skriva en funktion, och vara säker på att
ingen annan del av programmet ändrar på funktionens variabler.
Call by valueDå man skickar parametrar till en funktion, skickas parametrarnas
värden. Om parametern ändras inne i funktionen, syns ändringen inte
utanför. (Om man vill att ändringen ska synas också utanför, kan man
skicka en referens i stället, tas upp senare).
Globala variablerOm man vill att en variabel ska "synas" i alla fuktioner i ett program,
kan den deklareras utanför alla funktioner, typiskt någonstans i början
av programmet. Sådana globala variabler kan göra programmet svår-
are att förstå och förändra, eftersom det är svårt att hålla reda på alla
ställen där de används.
Matematik
C har många matematiska funktioner inbyggda. För att använda dem
ska man ha #include <math.h> i början av programmet. Programmet
ska sedan kompileras med flaggan -lm, för att funktionerna ska länkas
med i programmet. (-l är en flagga för att länka till ett bibliotek och m
är namnet på matematikbiblioteket.)
Lista över matematikbibliotekets funktioner (ej fullständig)
Trigonometriska och hyperboliska funktioner, inversercos sinh tan sin cosh atan, atan2(x, y); asin asinh tanh acos acosh atanh
pow(x, y) xy
sqrt kvadratrotcbrt kubikrothypot(x,y) sqrt(x2 + y2)exp(x) ex
exp2(x) 2x
log() logaritm (bas e)log10() logaritm (bas 10)log2() logaritm (bas 2)
remainder(x, y) rest vid divisionfabs(x) absolutvärde |x|
Avrundingceil avrunda uppåt till ett heltalfloor avrunda nedåt till ett heltalround avrunda till närmaste heltalnearbyint avrunda till närmaste heltaltrunc avrunda mot 0, till närmaste heltal
4
Programmering för fysiker. Lektion 2.
Besselfunktioner j0(x) j1(x) jn(n, x)
y0(x) y1(x) yn(n, x)
tgamma gammafunktionenerf errorfunction
Exempel. Användning av matematikfunktioner.double x = 0.32;double a = pow(sin(x), 2) + pow(cos(x), 2);
Datatyper
Hittills har vi använt datatyperna int och double.
Det finns fler:
char ett tecken / en bokstav
int ett heltal
float ett decimaltal (floating point, flyttal)
double double precision floating point
Dessutom finns det "modifiers": short, long, signed, unsigned. short och
long ändrar på hur mycket plats (i bytes) en variabel tar och därmed
på hur stora tal som kan uttryckas. signed anger att negativa värden är
tillåtna, unsigned att de inte är det. Om ingendera anges är signed
standard. Det finns inga unsigned flyttal.
typ bytes bits värdeområde (signed)
char 1 8 -128..127
short int 2 16 -32768..32767
int 4 32 -2147483648..+2147483647
long long int 8 64 -263..263-1
float 4 32
double 8 64
Kommandot make
make är ett program som underlättar kompileringen. Det är speciellt
nyttigt för större program som är delade i flera filer. make läser en
textfil (ofta med namnet Makefile) som beskriver hur programmet ska
kompileras.
Exempel: Vad det kan stå i filen Makefilefakultet : fakultet.c
gcc fakultet.c -o fakultet -Wall -lm
På den första raden står namnet på den fil som ska skapas (fakultet),
ett kolon och en lista på de filer som behövs. På den andra raden finns
kommandot som ska köras om någon av filerna till höger om kolon är
nyare (har en senare modifierad-tid) än den till vänstra. Märk! Före
kommandot (här gcc), ska det finnas exakt ett TAB-tecken, mellan-
rum/space duger inte. Då makefilen är gjord kan man kompilera
programmet genom att ge kommandot make.
Mer information om make finns i manualen:
http://www.gnu.org/software/make/manual/make.html
eller info make i en linuxterminal.
Exempel: Makefileprogrammet : del1.o del2.o
gcc del1.o del2.o -Wall -lm
del1.o : del1.cgcc del1.c -c -o del1.o -Wall
del2.o : del2.cgcc del2.c -c -o del2.o -Wall
Flaggan -c gör att gcc inte skapar ett körbart program, utan endast
producerar ett mellanresultat, en s.k. objektfil, som oftast ges ett
namn som slutar med ".o". Flera objektfiler kan sedan länkas ihop till
ett körbart program, den första raden är kommandot för det.
Introduktion till pekare
Variablerna som används i ett program lagras i datorns minne (RAM).
Minnet kan ses som en sekvens av bytes, där varje byte har en egen
adress. C kan använda de här adresserna för att hantera data, med
hjälp av s.k. pekare, pointers.
Pekare deklareras på samma sätt som variabler, men med * framför
namnet:int *p1; //En pekare till en intdouble *p2; //En pekare till en double
Operatorer:
& "address of"
* "object at"
Exempel. Användning av pekare.int a; int *p; //en pekare till en integer
a = 5;p = &a; //p pekar nu till aprintf ("%d\n", *p);
*p = 3; //sätt värdet 3 i den integer som p pekar påprintf ("%d\n", a);
Resultat:53
Precis som variabler måste pekare ges ett värde innan de används,
annars pekar de "vart som helst", och det är fel att använda dem.
5
Programmering för fysiker. Lektion 3.
Lektion 3
Filhantering (Input/Output)
Om man har stora mängder data kan det vara opraktiskt att skriva ut
dem på skärmen. Då kan man skriva i en fil i stället.
Exempel. Skriver text i en fil kallad "filen.txt"#include <stdio.h>#include <stdlib.h>#include <math.h>
int main (){ FILE *out; int i;
out = fopen ("filen.txt", "w"); if (out == NULL) { perror ("Cannot open file"); exit (1); }
fprintf (out, "Test! "); for (i = 1; i <= 3; i++) fprintf (out, "%d ", i); fprintf (out, "\n");
fclose (out); return 0;}
fopen öppnar en fil, så att man kan skriva till och/eller läsa från
den. Funktionen tar två argument: filens namn och en
annan sträng som beskriver vad filen ska användas till.
"r" read
"w" write (om filen finns, skrivs den över)
"a" append (lägger till ny data i slutet av filen)
"rb" "wb" read/write binary. Behövs i windows om filen
innehåller data (dvs. inte läsbar text) för att förhindra att
ny-rad-tecknen konverteras.
fopen returnerar en FILE pointer, eller NULL vid fel.
fclose stänger en fil. Öppna filer stängs automatiskt då
programmet avslutas, men det är bättre att stänga filer då
de inte längre behövs.
fprintf Skriver till en fil. Fungerar som printf, men det första
argumentet är en FILE * till filen.
perror Skriver ut ett felmeddelande. Texten i argumentet skrivs ut,
följt av ett felmeddelande från den senaste biblioteksfunk
tionen som misslyckats.
exit avslutar programmet. Return kunde ha använts här (i
main), men för att avsluta programmet ifrån någon annan
funktion kan exit användas. Parametern blir programmets
"exit code". Typiskt retureras 1 eller -1 om programmet
avslutas pga. ett fel och 0 om programmet avslutas
normalt.
Input med scanf
scanf används för att läsa in input från terminalen eller från en fil.
Hurdana värden som ska behandlas anges med en formatsträng, på
liknande sätt som för printf. Efter formatsträngen finns en lista med
adresser där de inlästa värdena ska sparas. Om scanf inte kan läsa in
ett värde (t.ex. om den väntar sig ett tal men läser en bokstav) lämnar
den resten av värdena obehandlade. scanf returnerar antalet värden
som lästes.
Exempelprogram. Läser två heltalsvariabler från terminalen med
scanf.#include <stdio.h>int main (){ int a, b, n; a = -1; b = -1; n = scanf ("%d%d", &a, &b); printf ("%d values read.\n", n); printf ("a = %d b = %d\n", a, b); return 0;}
För att läsa in doubles används %lf. Om man vill läsa från en fil i
stället för från terminalen kan man använda fscanf. fscanf fungerar
precis som scanf, men tar en FILE * som sitt första argument.
Arrayer
(kallas också räckor på svenska)
En array är en samling av variabler med samma typ och namn, som
skiljs åt genom ett index. Matematiskt betecknas det här xi, i C anges
index innanför hakparenteser: x[i]. Det första elementet i en array har
alltid index 0.
Arrayer är nyttiga då man ska behandla data eller skicka mycket data
till en funktion. Textsträngar i C är arrayer av typen char, det finns
alltså ingen egen datatyp för textsträngar som i många andra prog-
rammeringsspråk. char är en datatyp som innehåller ett tecken, en
char tar 1 byte minne.
Exempel 1. Användning av en arrayint main (){ int i; int x[10]; //10 element, index går från 0 till 9 for (i = 0; i < 10; i++) x[i] = 0; x[1] = 3; x[8] = 2; for (i = 0; i < 10; i++) printf ("%d ", x[i]); printf ("\n"); return 0;}
Man kan också definiera flerdimensionella arrayer, t.ex. 2-dimension-
ella matriser:
int row, col;double x;double m [10][5];m[row][col] = x;
6
Programmering för fysiker. Lektion 3.
Numerisk integrering
Integralen av en funktion f från a till b kan approximeras genom att
dela intervallet i N lika stora bitar, evaluera f i mitten av varje bit och
summera:
mittpunkten
för det i:te intervallet.
W. H. Press et al.:
Numerical Recipes in C - The art of scientific computing, 2:nd edition
(Cambridge university press 1992)
(Elektronisk version på www.nr.com)
Exempelprogram. Numerisk integrering.
#include <stdio.h>#include <math.h>double integrate (double xmin, double xmax, int steps);double f(double x);
int main (){ double I, R; R = sqrt(M_PI)/2; I = integrate (0, 10, 500); printf ("I =%.15f fel:%e\n", I, I-R); printf ("R =%.15f (exakt svar)\n", R); return 0;}
double integrate (double xmin, double xmax, int steps){ double A = 0; double x; double dx = (xmax - xmin)/steps; int i; x = xmin + dx/2;
for (i = 0; i < steps; i++) { A += dx * f(x); x += dx; } return A;}
double f(double x){ return exp(-x*x);}
Gnuplot
Gnuplot är ett program för att rita grafer av funktioner och data. Det
finns till både linux och windows. Graferna kan visas på skärmen eller
sparas, som bilder eller postscript, för att användas t.ex. i publika-
tioner. Gnuplot kan också anpassa funktioner till data.
http://www.gnuplot.info/
http://t16web.lanl.gov/Kawano/gnuplot/index-e.html
Gnuplot läser kommandon i en terminal eller från en fil. För kompli-
cerade figurer med flera kurvor, med namn på koordinataxlarna etc,
lönar det sig att skriva plottningskommandon i en fil så att man
slipper upprepa allt för varje liten förändring som görs.
Exempel. Kommandon i Gnuplot.
Plotta en funktion:plot 0.02*x**2 + cos(x)
Plotta data från en fil:plot "data.dat" using 2:3 with linespoints
using anger vilka kolumner i filen som ska användas. Den första kol-
umnen är nummer 1.
Man kan ange någon beräkning som ska utföras på datat innan det
plottas:plot "data.dat" using ($2**2):($3/$1) with linespoints
Uttrycken efter using är nu innanför parenteser, och kolumnnummer
anges med $.
Behåll den tidigare bilden och lägg till en ny kurva:replot 2*x+1
Ställ in intervall för koordinataxlarna:set xrange [-2:1]set yrange [-1:1]
I graf-fönstret kan man zooma mad högra musknappen. 'a' återställer
axlarna så att all data syns. 'h' ger en lista på andra knappkom-
mandon. Dessa kommandon fungerar då plot-fönstret (och inte ter-
minalen) är aktivt.
Exempelprogram. Numerisk integrering, skriver resultatet i en fil för
plottning med gnuplot.
#include <stdio.h>#include <stdlib.h>#include <math.h>
double integr (double xmin, double xmax, int steps, FILE *out);double f(double x);
int main (){ FILE *out; double I, R;
out = fopen ("int.dat", "w"); if (out == NULL) { perror("Cannot open file"); exit (1); }
I = integrate (0, 10, 500, out); fclose (out);
R = sqrt(M_PI)/2; printf ("I =%.15f fel:%e\n", I, I-R); printf ("R =%.15f (exakt svar)\n", R); return 0;}
7
Programmering för fysiker. Lektion 3.
double integr (double xmin, double xmax, int steps, FILE *out){ double A = 0; double x; double dx = (xmax - xmin)/steps; int i; x = xmin + dx/2;
for (i = 0; i < steps; i++) { A += dx * f(x); fprintf (out, "%.15f %.15f %.15f %.15f\n", x+dx/2, f(x+dx/2), A, erf(x+dx/2)*sqrt(M_PI)/2); x += dx; } return A;}
double f(double x){ return exp(-x*x);}
Komplexa tal i C
Komplexa tal finns med i den nya C-standarden C99.
Exempel. Användning av komplexa tal.#include <math.h>#include <complex.h>#include <stdio.h>
int main (){ _Complex double x, y; x = 1 + 2 * I; printf ("x = (%f, %f)\n", creal(x), cimag(x)); y = 1 - 2 * I; printf ("y = (%f, %f)\n", creal(y), cimag(y)); printf ("x*y = (%f, %f)\n", creal(x*y), cimag(x*y)); printf ("\n");
_Complex double z = cexp(I * M_PI); printf("z = e^(pi*I) = %f + %f * i\n", creal(z), cimag(z));
return 0;}
De flesta matematiska funktionerna i c-biblioteket har en komplex
motsvarighet:
cabs(z) absolutbelopp |z|
carg(z) argument (vinkel)
cexp(z) ez
cpow(z,w) zw
creal(z) realdel
cimag(z) imaginärdel
Arrayer och pekare
Pekare och arrayer i C har ett starkt samband. Det här är viktigt då
man har data i en array och vill anropa en funktion som behandlar
datan, eftersom man inte direkt kan ge en array som parameter till en
funktion. I stället ger man adressen där arrayen börjar, dvs. en pekare
till dess första element, och ofta också antalet element.
Exempel. Att skicka en array till en funktion.#include <stdio.h>#include <math.h>void skriv (int *a, int n);
int main (){ int i; int x[10];
for (i = 0; i < 10; i++) x[i] = 0;
x[1] = 3; x[8] = 2; skriv (x, 10); //"x" ger adressen av det första // elementet return 0;}
void skriv (int *a, int n){ int i; for (i = 0; i < n; i++) printf ("%d ", a[i]);
printf ("\n");}
Två viktiga egenskaper hos arrayer används i exemplet.
1) Namnet på en array, utan [], är adressen av dess första ele-
ment. Funktionen skriv ska ges adressen av det första elementet som
argument, här "x".
2) En pekare följd av [i] är det i:te elementet räknat från pekaren.
I funktionen skriv är a en pekare till det första elementet av arrayen.
a[i] är arrayens i:te element.
Ännu en egenskap:
*(a+i) betyder detsamma som a[i]. Alltså, en pekare a plus ett heltal
i ger en pekare till det i:te följande elementet.
8
Programmering för fysiker. Listor.
ListorNärmare beskrivningar finns i den löpande texten.
Funktioner
exit avslutar programmetfclose stänger en filfopen öppnar en fil
fprintf skriver till en filfscanf läser från en filperror skriver ut ett felmeddelandeprintf skriver till terminalenscanf läser från terminalen
Datatyper
char teckendouble decimaltal
_Complex double komplext decimaltal, a + I*bfloat decimaltalint heltallong
short
signed
unsigned
void "ingenting"
Nyckelord
break avbryter den innersta loopenelse
for
if
return
while
Argument för printf
\n ny rad\t tab%d en int%f en double
%.3f en double med 3 decimaler%e en double i exponentiell form
Bibliotek
#include <complex.h> komplexa tal#include <math.h> matematikfuntioner#include <stdio.h> input och output#include <stdlib.h> standardbiblioteket (innehåller exit)
Kompileringsflaggor
-c endast kompilering (länkar inte)-o .exe-filens namn efter -o-lm länka till matematikbiblioteket
-Wall visa alla varningar-O3 optimera räknehastigeten
9
Programmering för fysiker. Lektion 4.
Lektion 4
Fraktaler
Låt och vara komplexa tal. Definiera
För vissa är följden begränsad, för andra är den obegränsad.
Mandelbrotmängden är mängden av de , för vilka följden är be-
gränsad.
Exempelprogrammet nedan beräknar följden för olika genom att an-
ropa funktionen iterate, och visar om sekvensen är begränsad eller
inte. Programmet skriver ut en bild av det komplexa planet, där x mot-
svarar realdelen och y imaginärdelen av . Bilden visar området Rmin
Re(c) Rmax, Imin Im(c) Imax, som en ruta av tecken med
måtten X*Y. Ett "X" skrivs ut om följden är begränsad för det som
motsvarar tecknets position, annars skrivs ett mellanrum ut.
Funktionen iterate (z0, c, maxIter) returnerar det minsta n sådant att
, eller maxIter, ifall hållits innanför gränsen i maxIter iter-
ationer. Om för något , är serien obegränsad, och beräk-
ningen kan avbrytas. Om inte gått utanför gränsen efter maxIter
iterationer, antar man att följden är begränsad och beräkningen av-
bryts.
Exempelprogram. Ritar Mandelbrotmängden på skärmen.#include <math.h>#include <complex.h>#include <stdio.h>
int iterate (_Complex double z0, _Complex double c, int maxIter);
int main (){ double r = -.7; //mittpunktens realdel double i = 0; //mittpunktens imaginärdel double s = 3; //sidlängd double Rmin = r - s/2; //det minsta värdet på Re(c) double Rmax = r + s/2; double Imin = i - s/2; //det minsta värdet på Im(c) double Imax = i + s/2;
int X = 80; //antal kolumner i bilden int Y = 40; //antal rader int x, y; _Complex double c; for (y = 0; y < Y; y++) { for (x = 0; x < X; x++)
{ c = (double)x/X * (Rmax-Rmin) + Rmin; c += ((double)y/Y * (Imax-Imin) + Imin) * I;
int k = iterate(0, c, 1000); if (k == 1000) printf ("X"); else printf (" ");}
printf ("\n"); } return 0;}
int iterate (_Complex double z0, _Complex double c, int maxIter){ double R = 2; int i; _Complex double z = z0; for (i = 0; i < maxIter; i++) { z = z*z + c; if (cabs(z) > R)
break; } return i;}
Grafik med biblioteket SDL
Med biblioteket SDL kan man relativt enkelt skriva program som visar
grafik på skärmen. SDL (Simple DirectMedia Layer) är planerat för
främst för spelprogrammering. Målet är att spel som använder biblio-
teket ska vara portabla till alla system som SDL finns till. SDL fungerar
bl.a. på Linux, MacOS och Windows. För dokumentation och installa-
tionsinstruktioner, se länkarna på kurshemsidan.
Exempelprogram. Grafik med SDL.#include <SDL/SDL.h> #include <stdio.h>#include <stdlib.h>#include <math.h>
void dieSDL (char *reason);void putPixel (SDL_Surface *s, int x, int y, int c);SDL_Surface * makeWindow (int width, int height);void waitAndClose ();
int main(int argc, char *argv[]) { int width = 500; // fönstrets bredd i pixlar int height = 500; SDL_Surface * screen; screen = makeWindow (width, height); SDL_LockSurface (screen); //Nu får vi rita... putPixel (screen, 10, 10, 255); putPixel (screen, 11, 10, 255); putPixel (screen, 10, 11, 255); putPixel (screen, 11, 11, 255); int x, y; for (y = 0; y < 256; y++) for (x = 0; x < 256; x++) putPixel (screen, x+10, y+100, y*256 + x); for (y = -10; y < 10; y++) for (x = -10; x < 10; x++) if (hypot(x, y) < 10)
putPixel (screen, x+50, y+200, 255*256*256);
SDL_UnlockSurface (screen); //Visa den nya bilden: SDL_Flip(screen); waitAndClose (); return 0;}
10
Programmering för fysiker. Lektion 4.
//Endast för 32 bits per pixelvoid putPixel (SDL_Surface *s, int x, int y, int c){ int bpp = s->format->BytesPerPixel; /* Here p is the address to the pixel we want to set */ int *p = (int *)(s->pixels + y * s->pitch + x * bpp); *p = c;}
void dieSDL (char *reason){ fprintf(stderr, reason, SDL_GetError()); exit (1);}
SDL_Surface * makeWindow(int width, int height){ SDL_Surface *screen; //Skapa ett fönster att rita i //SetVideoMode tar parametrarna: bredd, höjd, bitar/pixel, flaggor if( SDL_Init(SDL_INIT_VIDEO) < 0 ) dieSDL ("SDL init failed: %s\n"); screen = SDL_SetVideoMode(width, height, 32, SDL_HWSURFACE|
SDL_DOUBLEBUF); if (screen == NULL) dieSDL ("SDL_SetVideoMode failed: %s\n"); return screen;}
void waitAndClose (){ //Väntar på att man stänger fönstret SDL_Event event; int quit = 0; while (!quit) { SDL_WaitEvent(&event); if (event.type == SDL_QUIT)
{ quit = 1; break;}
} SDL_Quit();}
KompileringI linux kan programmet kompileras med flaggorna: -Wall -O3 -lm -lSDL
I windows kan man använda -Wall -O3 -lm -lmingw32 -lSDLmain -lSDL
Observera att -l flaggorna måste vara i denna ordning.
Funktionen main är definierad såhär, int main(int argc, char *argv[]),
eftersom kompileringen inte fungerar i Windows med en tom para-
meterlista. Parametrarna till main är antalet argument på kommando-
raden och en array av pekare till dem, men de används inte här.
Strukturer
SDL_Surface är ett exempel på en struktur. Den är definierad få man
har inkluderat SDL.h. typedef definierar en ny datatyp, här med nam-
net SDL_Surface. struct definierar en struktur, dvs. en sammansatt
datatyp som innehåller flera variabler. Strukturer används för att
gruppera data som hör ihop.
typedef struct SDL_Surface { Uint32 flags; /* Read-only */ SDL_PixelFormat *format; /* Read-only */ int w, h; /* Read-only */ Uint16 pitch; /* Read-only */ void *pixels; /* Read-write */ SDL_Rect clip_rect; /* Read-only */ int refcount; /* Read-mostly */ /* This structure also contains private fields not shown here */} SDL_Surface;
Här är SDL_PixelFormat och SDL_Rect andra strukturer som också defi-
nieras i SDL. Uint16 och Uint32 är definierade i SDL som 16 och 32
bitars unsigned int. Deklarationen void* pixels definierar en pekare
utan typ. Den måste explicit konverteras till rätt typ innan den kan
användas. Det här behövs, eftersom pixeldatat kan lagras på olika sätt,
med olika antal bitar per pixel (se nedan).
Exempel. Operatorerna "." och "->"SDL_surface s; //s är av typen SDL_SurfaceSDL_surface *p; //en pekare till en SDL_Surfacep = &s;s.w // "." används för att komma åt ett element i sp->w // "->" används då man har en pekare till en struktur
11
Programmering för fysiker. Lektion 4.
Ett grafiskt mandelbrotprogram
Exempelprogram. Ritar mandelbrotfraktalen i ett fönster med SDL.#include <SDL/SDL.h> #include <stdio.h>#include <stdlib.h>#include <math.h>#include <complex.h>
void dieSDL (char *reason);int iterate (_Complex double z0, _Complex double c, int maxIter);void putPixel (SDL_Surface *s, int x, int y, int c);SDL_Surface * makeWindow (int width, int height);void waitAndClose ();
int main(int argc, char *argv[]) { int width = 800; // fönstrets bredd i pixlar int height = 800; SDL_Surface * screen; screen = makeWindow (width, height);
double r = -.7; //mittpunkt double i = 0; double s = 3; //storlek double Rmin = r - s/2; double Rmax = r + s/2; double Imin = i - s/2; double Imax = i + s/2; int x, y, iterations, color; int MAXiter = 1000; _Complex double c;
for (y = 0; y < height; y++) { SDL_LockSurface (screen); for (x = 0; x < width; x++)
{ c = (double)x/width * (Rmax-Rmin) + Rmin; c += ((double)y/height * (Imax-Imin) + Imin) * I;
iterations = iterate(0, c, MAXiter); if (iterations == MAXiter) color = 0; else color = 255*log(iterations)/log(MAXiter); putPixel (screen, x, y, color);}
SDL_UnlockSurface (screen); SDL_Flip(screen); } if (SDL_SaveBMP(screen, "mandel.bmp") < 0) dieSDL ("SDL_SaveBMP failed: %s\n");
waitAndClose (); return 0;}
Funktionerna iterate, dieSDL, putPixel, makeWindow och waitAndCloseär definierade som tidigare.
Färger och pixlar i SDL
I anropet av SDL_SetVideoMode ska man ange i vilket format bilderna
behandlas. Här använder vi 32 bits per pixel, dvs. färgen för varje pixel
lagras i ett 32 bits tal. Också 8, 16 eller 24 bitar per pixel används
ibland. Färgerna beskrivs med tre komponenter: röd, grön och blå.
Hur mycket av varje färg som ingår anges med ett heltal mellan 0 och
255 (8 bitar per färg). De 32 bitarna för en pixel används såhär: 00000000 rrrrrrrr gggggggg bbbbbbbb
Det här lagringssättet slösar 8 bitar för varje pixel, men det lönar sig
eftersom 32 bitar är en mer praktisk storlek än 24. För att få ett
färgvärde c då man har värden på komponenterna r, g, och b kan man
använda c = r*256*256 + g*256 + b;
eller c = (r << 16) + (g << 8) + b;
"<<" är en vänster-shift-operation.
Pixlarna i en bild lagras radvis, efter varandra, börjande från det övre
vänstra hörnet av bilden. Den första pixelns adress anges i elementet
pixels. För varje rad i bilden reserveras det antal bytes som anges i
pitch. pitch är minst lika stor som antalet byte per pixel * bildens
bredd, men kan vara större av tekniska orsaker. För att komma åt
pixeln på koordinaterna (x, y) kan man använda: int *p = (int *)(s->pixels + y * s->pitch + x * bpp);
*p = c;
Beräkningar med void-pointers görs i bytes, därför multipliceras y med
antalet bytes per rad och x med antalet bytes per pixel. Då adressen är
beräknad konverteras den till en int pointer med (int *), eftersom vi i
det här fallet vill använda adressen för att skriva en 32 bits pixel.
Fler färgerFör att få fler färger i bilden av mandelbrotfraktalen, kan man färg-
lägga det yttre området, där följden är obegränsad, enligt hur många
iterationer som behövdes innan gick utanför gränsen. Ett förslag:color = 255 * log(k) / log(MAX);
12
Programmering för fysiker. Lektion 5.
Lektion 5
Ordinära differentialekvationer
En ordinär differentialekvation av första ordningen kan skrivas i
formen , där är någon given funktion. Lösningen till
ekvationen är funktionen , som kan bestämmas entydigt, om man
har ett randvillkor, t.ex. .
En ekvation av den här typen kan lösas med Eulers metod:
Man startar alltså i med (randvillkoret). Sedan
beräknar man approximativa värden på i punkterna ,
osv. Då steglängden blir det här en allt
noggrannare approximation av .
Exempelprogram. Löser x(t) med Eulers metod, då
#include <stdio.h>#include <math.h>
double time_derivative (double x, double t);double step (double x, double t, double dt);
int main (){ double t, dt = 0.1, tmax = 8; double x, x0 = 0; x = x0; for (t = 0; t <= tmax; t += dt) { printf ("%f %f\n", t, x); x = step (x, t, dt); } return 0;}
double time_derivative (double x, double t){ return cos(t);}
double step (double x, double t, double dt){ return x + dt * time_derivative (x, t);}
Differentialekvationer av högre ordning
Differentialekvationer av högre ordning är viktiga i fysiken, t.ex.
Newtons lagar kan skrivas som andra ordningens differentialekva-
tioner. Differentialekvationer av högre ordning kan skrivas om som ett
system av kopplade första ordningens ekvationer.
Exempel: Rörelse i en dimension
En partikel med massan m som påverkas av en kraft F accelererar med
accelerationen a, enligt Newtons andra lag . Accelerationen
är positionens andra derivata:
Det här kan skrivas om som två första ordningens ekvationer:
, ,
där v är partikelns hastighet.
Ekvationerna skrivs alltså som derivatan av en funktion uttryckt med
funktionerna utan derivator. Ett ekvationssystem av den här typen kan
lösas med Eulers metod, se nedan.
Kopplade differentialekvationer
Betrakta ett system av kopplade första ordningens differentialekva-
tioner:
, , osv,
där , , ... är givna funktioner, och kan bero av och alla .
Det här systemet kan lösas med Eulers metod på samma sätt som en
ensam ekvation:
I ett program beräknar man alltså derivatan för alla och adderar
sedan till varje dess derivata gånger .
Ekvationerna för ett tidssteg för rörelseekvationen i exemplet ovan
blir:
där kan bero av t, x och v, beroende på problemet som studeras.
13
Programmering för fysiker. Lektion 5.
En pendel
Differentialekvationen för pendelns rörelse är
.
För att kunna lösa ekvationen analytiskt, antar man att vinkeln är
liten, och att . Lösningen blir då , där
. Exempelprogrammet nedan löser numeriskt, utan
approximationen att vinkeln är liten.
Exempelprogram. En pendel.#include <stdio.h>#include <math.h>void time_derivative (int N, double *x, double *dx, double t);void step (int N, double *x, double t, double dt);
double g = 9.81; // m/s^2 gravitationeaccelerationendouble l = 1; // m pendelns längddouble A = .1; // rad. utgångsvinkel int main (){ double t, dt = 0.001, tmax = 10; double x[2]; x[0] = A; //theta x[1] = 0; //theta'
for (t = 0; t <= tmax; t+= dt) { printf ("%f %f %f\n", t, x[0], x[1]); step (2, x, t, dt); } return 0;}
void time_derivative (int N, double *x, double *dx, double t){ dx[0] = x[1]; dx[1] = -g/l*sin(x[0]);}
void step (int N, double *x, double t, double dt){ int i; double dx [N];
time_derivative (N, x, dx, t);
for (i = 0; i < N; i ++) x[i] += dx[i] * dt;}
Differentialekvationen skrivs om till två kopplade första ordningens
ekvationer. Variablerna lagras i arrayen x, så att vinkeln finns i x[0]
och vinkelhastigheten i x[1]. Ekvationerna har nu formen
, .
Derivatorna beräknas i funktionen time_derivative. Tidssteget utförs
i funktionen step. Variablerna är placerade i en array på det här sättet
för att programmet ska vara mer allmänt. Om man vill lösa ett annat
problem måste man skriva in ekvationerna för det nya problemet i
funktionen time_derivative och möjligen ändra längden på arrayen x i
main, men funktionen step kan användas som den är. En array är också
praktisk om man har många likadana variabler, t.ex. ett system av
kopplade pendlar. I det här exemplet kunde man istället ha använt två
skilda variabler för vinkeln och dess derivata, men då hade också
funktionen för tidssteget blivit specifik för just det här problemet.
För varje tidssteg skriver programmet ut t, och . Värdena skrivs
till skärmen, men kan fås i en fil om man kör programmet såhär:./pendel > filnamn.dat
Resultatet kan plottas med gnuplot:
plot "filnamn.dat" using 1:2 with lines
Fler exempel finns på kurshemsidan.
Mer information om lösning av differentialekvationer finns i
W. H. Press et al.: Numerical Recipes in C - The art of scientific com-
puting, 2:nd edition (Cambridge university press 1992)
Gnu Scientific Library (GSL) innehåller funktioner för lösning av
ODE:r: http://www.gnu.org/software/gsl/
14
Programmering för fysiker. Lektion 6.
Lektion 6
Diskretisering
Om man vill behandla en kontinuerlig funktion av , , kan man
diskretisera den, så att man håller reda på funktionens värden vid
vissa diskreta -värden. Typiskt väljer man dem med jämna mellan-
rum i något intervall. Om man är intresserad av funktionen mellan
och , och vill beräkna i N punkter , kan
man välja
där är avståndet mellan två punkter. Med det här valet är
och , dvs. ändpunkterna av intervallet
finns med som diskretiseringspunkter.
Funktionsvärdena kan nu sparas i en array. Det kan löna
sig att ha en array också för -värdena, så att man slipper beräkna
dem varje gång de behövs.
Exempel. Diskretisering i ett intervall. double Xmax = 2; //meter double Xmin = 1; //meter int N = 20; int i; double dx = (Xmax - Xmin) / (N-1);
//Gör en array för x-värden for (i = 0; i < N; i++) x[i] = Xmin + i * dx;
Diskretisering av derivator
Om man har en diskretisering av en funktion , kan man använda
den för att approximera funktionens derivata.
Derivatan definieras som gränsvärdet
,
men kan approximeras så att man använder ett ändligt
Det lönar sig i allmänhet att använda ett symmetriskt uttryck, även om
man kunde ha använt skillnaden i stället. På motsvarande
sätt kan den andra derivatan beräknas:
Exempel. Funktioner för att beräkna derivatan och andra derivatan
för en diskretiserad funktion fdouble derivata (int i, double deltax, double *f){ return (f[i+1] - f[i-1]) / (2*deltax);}double andraderivata (int i, double deltax, double *f){ return (f[i+1] + f[i-1] - 2*f[i]) / (deltax*deltax);}
Schrödingerekvationen
I kvantmekaniken beskrivs en partikel med en vågfunktion, ofta
beteknad med (psi). Vågfunktionen har ett komplext värde i varje
punkt av rymden, och är i allmänhet tidsberoende. Sannolikhetstät-
heten för att hitta partikeln i punkten x vid tiden t är:
Hur vågfunktionen utvecklas i tiden beskrivs av Schrödingerekvationen,
här given för en dimension:
,
där V(x) är potentialen. Schrödingerekvationen är en partiell differen-
tialekvation, eftersom den innehåller derivator både med avseende på
x och t.
Den första termen på höger sida beskriver partikelns kinetiska energi,
och får partiklarna att röra sig. Utan den här termen kommer
sannolikhetstätheten P(x,t) inte att ändras i tiden.
Numerisk lösning av SchrödingerekvationenVi ska nu lösa Schrödingerekvationen för en partikel som rör sig i en
endimensionell potential. Vi diskretiserar x-koordinaten, och byter ut
andraderivatan med avseende på x mot den diskretiserade versionen
som gavs ovan. Nu har vi ett system av kopplade första ordningens
ordinära differentialekvationer, som kan lösas med Eulers metod från
förra föreläsningen. (Ekvationerna är ordinära nu, eftersom det endast
finns en derivata med avseende på tiden.)
Exempelprogram. Löser Schrödingerekvationen, ritar med SDL. #include <stdio.h>#include <math.h>#include <complex.h>#include <SDL/SDL.h>#include <string.h>
_Complex double E_kin_psi (int i, double deltax, _Complex double *psi);void time_derivative (int N, _Complex double *psi, _Complex double *dpsi, double *V, double dx, double t);void step (int N, _Complex double *psi, double *V,
double t, double dx, double dt);void discretize (int N, double *x);void initialize (int N, double *x, _Complex double *psi);void normalize (int N, double deltax, _Complex double *psi);void potential (int N, double *x, double *potential);
void rita (SDL_Surface *screen, int N, double ymax, double *x, _Complex double *psi, double *V);void graph (SDL_Surface *screen, double xmin, double xmax, double ymin, double ymax, int N, double *x, double *y, int col);double max_sannolikhet(int N, _Complex double *psi);void skriv (int N, double *x, _Complex double *psi);void putPixel (SDL_Surface *s, int x, int y, int c);int RGBtoInt (int r, int g, int b);void clearScreen (SDL_Surface *s);SDL_Surface * makeWindow (int width, int height);void dieSDL (char *reason);int testIfQuit ();
#define Hbar 1.054e-34 //Js#define Me 9.109e-31 //kg
double Xmin = -1e-7; //mdouble Xmax = 1e-7; //mdouble m = Me;
int main (){ int width = 500, height = 500, frame = 0; SDL_Surface * screen; screen = makeWindow (width, height); double ymax;
double t, dt = .02e-15, tframe = 1e-14;
15
Programmering för fysiker. Lektion 6.
int N = 80; //Antal punkter i x-led double dx = (Xmax - Xmin) / (N-1); double x[N], V[N]; _Complex double psi[N];
discretize (N, x); initialize (N, x, psi); normalize (N, dx, psi); potential(N, x, V); ymax = max_sannolikhet(N, psi); t = 0; while (1) { frame++; for (; t <= tframe*frame; t+= dt)
step (N, psi, V, t, dx, dt); rita (screen, N, ymax, x, psi, V); if (testIfQuit())
break; }
SDL_Quit(); return 0;}
void discretize (int N, double *x){ int i; double dx = (Xmax - Xmin) / (N-1);
for (i = 0; i < N; i++) x[i] = Xmin + i * dx;}
void initialize (int N, double *x, _Complex double *psi){ int i; double sigma = .3e-7; //initialbredd för gaussisk vågfunktion double x0 = Xmax/7; double k = 0; // 1/m
for (i = 0; i < N; i++) { psi[i] = cexp (- pow((x[i]-x0)/sigma, 2) + I*k*x[i]) ; }}
void normalize (int N, double deltax, _Complex double *psi){ int i; double summa = 0; for (i = 0; i < N; i++) summa += deltax*cabs(psi[i] * psi[i]);
for (i = 0; i < N; i++) psi[i] /= sqrt(summa);}
void potential (int N, double *x, double *V){ int i; for (i = 0; i < N; i++) { V[i] = sin(x[i]/2/Xmax*M_PI)*sin(x[i]/2/Xmax*M_PI)*1e-22; }}
_Complex double E_kin_psi (int i, double deltax, _Complex double *psi){ return -Hbar*Hbar/(2*m)*(psi[i+1] + psi[i-1] - 2*psi[i]) / (deltax*deltax);}
void time_derivative (int N, _Complex double *psi, _Complex double *dpsi, double *V, double dx, double t)
{ int i; for (i = 1; i < N-1; i++) dpsi[i] = -I/Hbar * ( E_kin_psi(i, dx, psi) + V[i]*psi[i] );
dpsi [0] = 0; //specialfall för kanterna. dpsi[N-1] = 0;}
void step (int N, _Complex double *psi, double *V, double t, double dx, double dt){
int i; _Complex double dpsi [N];
time_derivative (N, psi, dpsi, V, dx, t);
for (i = 0; i < N; i ++) psi[i] += dpsi[i] * dt;}
void rita (SDL_Surface *screen, int N, double ymax, double *x, _Complex double *psi, double *V){ double p[N]; int c; int j; for (j = 0; j < N; j++) { p[j]= cabs(psi[j]*psi[j]); } SDL_LockSurface (screen); clearScreen (screen); c = RGBtoInt (0, 255, 128); graph (screen, Xmin, Xmax, 0, 3*ymax, N, x, p, c); //ritar sannolikhetstätheten
c = RGBtoInt (255, 128, 64); graph (screen, Xmin, Xmax, 0, 2*V[0], N, x, V, c); //ritar potentialen
SDL_UnlockSurface (screen); SDL_Flip(screen);}
void graph (SDL_Surface *screen, double xmin, double xmax, double ymin, double ymax, int N, double *x, double *y, int col){ int i; int width = screen->w; int height = screen->h; for (i = 0; i < N; i++) { int xs, ys; //Koordinater på skärmen xs = (x[i] - xmin)/(xmax - xmin) * (width-1); ys = height - 1 - (y[i] - ymin)/(ymax - ymin) * (height-1); if (xs >= 0 && ys >= 0 && xs < width && ys < height)
putPixel (screen, xs, ys, col); }}
double max_sannolikhet(int N, _Complex double *psi){ int i; double ymax = 0; for (i = 0; i < N; i++) { if (cabs(psi[i]*psi[i]) > ymax)
ymax = cabs(psi[i]*psi[i]); } return ymax;}
int RGBtoInt (int r, int g, int b){ return (r << 16) + (g << 8) + b;}
void skriv (int N, double *x, _Complex double *psi){ int i; for (i = 0; i < N; i++) printf ("%.10g %.10g\n", x[i], cabs(psi[i]*psi[i])); printf ("\n\n");}
SDL_Surface * makeWindow(int width, int height){ SDL_Surface *screen; if( SDL_Init(SDL_INIT_VIDEO) < 0 ) dieSDL ("SDL init failed: %s\n"); screen = SDL_SetVideoMode(width, height, 32, SDL_SWSURFACE|SDL_DOUBLEBUF); if (screen == NULL) dieSDL ("SDL_SetVideoMode failed: %s\n"); return screen;}
void dieSDL (char *reason){ fprintf(stderr, reason, SDL_GetError()); exit (1);}
16
Programmering för fysiker. Lektion 6.
void putPixel (SDL_Surface *s, int x, int y, int c){ int bpp = s->format->BytesPerPixel; int *p = (int *)(s->pixels + y * s->pitch + x * bpp); *p = c;}
void clearScreen (SDL_Surface *s){ int bpp = s->format->BytesPerPixel; int x, y; int *p;
for (y = 0; y < s->h; y++) for (x = 0; x < s->w; x++) {
p = (int *)(s->pixels + y * s->pitch + x * bpp);*p = 0;
}}
int testIfQuit (){ SDL_Event event; while (SDL_PollEvent(&event)) if (event.type == SDL_QUIT) return 1; return 0;}
17
Programmering för fysiker. Lektion 7.
Lektion 7
Slumptal
Funktionen random() i stdlib.h returnerar ett "slumpmässigt" heltal
mellan 0 och konstanten RAND_MAX. Om man vill ha ett slumpmässigt
tal mellan 0 och R kan man skala resultatet från random().r = random ();s = (double) R * r / RAND_MAX;
Konverteringen till double görs för att undvika fel pga. avrundning.
Exempelprogram. Genererar några slumptal.#include <stdio.h>#include <stdlib.h>#include <math.h>
int main (){ int i, r, s; printf ("RAND_MAX är %d\n", RAND_MAX); printf ("2^31 = %.0f\n", pow(2, 31));
for (i = 0; i < 20; i++) { r = random (); s = 10.0 * r / RAND_MAX; printf ("r = %d, s = %d\n", r, s); } return 0;}
Varje gång programmet körs genereras samma sekvens av tal. Om man
vill ha en ny sekvens varje gång kan man använda funktionen srandom,
som sätter slumptalsgeneratorn i ett givet tillstånd. Startvärdet kan tas
t.ex. från klocktiden, med kommandot srandom(time(NULL)).
Slumpvandring
Med slumpvandring avses en process där en partikel förflyttar sig med
slumpmässigt valda steg. Slumvandring kan användas som modell för
Brownsk rörelse och diffusion. Exempelprogrammet nedan simulerar
slumpvandring i ett tvådimensionellt gitter, dvs. alla steg har längden
1 och görs i en av de fyra riktningarna upp, ner, vänster eller höger.
Exempelprogram. Slumpvandring i ett 2D-gitter#include <stdio.h>#include <stdlib.h>#include <math.h>#include <time.h>
int main (){ srandom (time(NULL));
int x = 0, y = 0; int r; int i; double d;
for (i = 0; i < 10000; i++) { r = 4.0 * random () / RAND_MAX; switch (r)
{case 0: x++; //höger break;case 1: x--; //vänster
break;case 2: y++; //upp break;case 3: y--; //ner break;default: printf ("Omöjligt!\n"); break;}
d = sqrt(x*x + y*y); printf ("%d %d %d %f\n", i, x, y, d); } return 0;}
Här introducerades structuren switch - case, som kan användas för att
välja ett av flera alternativ, beroende på värdet av en variabel. Varje
fall avslutas med break. Utan kommandot break skulle programmet
fortsätta med koden för nästa fall, vilket är tillåtet men oftast inte vad
som önskas. Fallet default utförs om inget av de givna alternativen
utfördes. Observera att varje case-rad avslutas med ett kolon.
Olika fördelningar
Funktionen random returnerar ett heltal mellan 0 och RAND_MAX, med en
likformig fördelning. Ofta behöver man slumptal med andra egen-
skaper, t.ex. ett decimaltal i ett givet intervall eller med någon annan
fördelning än den likformiga.
DefinitionerOm är en stokastisk variabel med någon fördelning, definieras dess
fördelningsfunktion
Om fördelningen är kontinuerlig, definieras sannolikhetstätheten
så att
Likformig fördelning i ett intervall
Funktionen uniform (a, b) returnerar en slumpmässig double mellan a
och b, med likformig fördelning.
double uniform (double a, double b){ return (b-a) * random() / RAND_MAX + a;}
Exponentiell fördelningEn exponentiell fördelning har fördelningsfunktionen
och sannolikhetstätheten
.
18
Programmering för fysiker. Lektion 7.
Den exponentiella fördelningen förekommer till exempel vid
radioaktivt sönderfall. Tiden mellan två händelser ("klick från
Geigerräknaren") är exponentiellt fördelad.
Om stokastisk variabel som är likformigt fördelad mellan 0 och 1,
kan man få en exponentiellt fördelad variabel genom att beräkna
.
Motivering:
,
dvs. har en exponentiell fördelning med . Om man vill ha ett
annat , kan man skala genom att multiplicera med en konstant.
//Slumptal med exponentiell fördelning. Lambda = 1. double exponential () { return -log(uniform(0,1)); }
Samma metod kan användas för att generera andra fördelningar, om
man känner fördelningsfunktionen och om den inversa funktionen går
att beräkna.
Gaussisk fördelning / normalfördelning
För en Gaussisk fördelning ges sannolikhetstätheten av
.
Slumptal med en Gaussisk fördelning kan genereras med Box-Müller-
metoden, som är enklare och effektivare än metoden med invers
funktion som presenterades ovan. Metoden använder två likformigt
fördelade slumptal och producerar två oberoende slumptal med en
Gaussisk fördelning. I ett polärt koordinatsystem väljs vinkeln lik-
formigt i intervallet och radien väljs så att har en
exponentiell fördelning (med metoden ovan). x- och y-koordinaterna
för den valda punkten har nu en Gaussisk fördelning, med väntevärdet
och standardavvikelsen .
double gaussian () { static double next; static int gotOne = 0; double fi, x;
if (gotOne) { gotOne = 0; return next; }
fi = uniform(0, 1) * 2 * PI; x = sqrt(-2*log(uniform(0,1))); next = x * sin (fi); //Two independent numbers are generated gotOne = 1; return x*cos(fi); }
Versionen nedan undviker de trigonometriska funktionerna, genom att
välja x- och y-koordinater för punkten. Koordinaterna väljs i intervallet
, med kravet att punkten ligger innanför enhetscirkeln. Om
punkten ligger utanför enhetscirkeln genereras koordinaterna pånytt. // Gaussian random number, average=0, variance=1 double gaussian() { static double next; static int gotOne = 0; double x, y, r;
if (gotOne) { gotOne = 0; return next; } do { x = uniform(-1,1); y = uniform(-1,1); r = x*x + y*y; } while (r > 1); r = sqrt(-2 * log (r) / r);
gotOne = 1; next = y * r; return x * r; }
Diffusion-Limited Aggregation
Vi ska se på en slumpvandringsprocess med flera partiklar, där
partiklarna kan fastna. En partikel i gången får slumpvandra omkring i
ett gitter (här i två dimensioner). Partikeln fastnar om den kommer
till en gitterpunkt bredvid en partikel som redan fastnat. Beräkningen
börjar från ett läge där det endast finns en fixerad partikel, den kan
placeras i origo.
Kvaliteten på slumptal
Hur man genererar en sekvens av ”slumpmässiga” tal på en
deterministisk dator är ett komplext ämne. I allmänhet vill man ha en
sekvens av tal där varje tal förekommer lika ofta och där korrelationen
mellan talen är möjligast liten. Standardbibliotekets random()-funktion
är inte den bästa, och kvaliteten kan variera beroende på vilken C-
kompilator och vilket bibliotek man använder. Se Knuth, The Art of
Computer Programming vol 2 för en grundlig behandling av slumptal.
En bra slumptalsgenerator är Mersenne Twister, se Wikipedia och
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html .
19
Programmering för fysiker. Lektion 8.
Lektion 8
Linjär algebra med LAPACK
LAPACK är ett programbibliotek för linjär algebra. Det innehåller t.ex.
funktioner för att lösa ekvationer i formen
,
där är en matris och och är vektorer. LAPACK innehåller också
funktioner för att hitta egenvärden och egenvektorer för en matris.
Programpaketen Matlab och Octave bygger på LAPACK-biblioteket, så
om man har ett specifikt problem som man vill lösa en enda gång kan
det vara enklare att använda något av dem i stället för att själv skriva
ett program som anropar funktionerna i LAPACK.
Egenvärden och egenvektorer
En egenvektor för matrisen är någon vektor som uppfyller
ekvationen
,
där är ett tal. kallas egen-
värde till matrisen (som
motsvarar egenvektorn ). T.ex.
har en tre-gånger-tre-matris
(oftast) tre olika egenvektorer, och
dessa har varsitt egenvärde.
Egenvärdena och -vektorena ger en
massa nyttig information om matrisens egenskaper. Mer om sådant
här kommer det t.ex. i kursen Linjär algebra på matematikinsten.
Att anropa FORTRAN-funktioner från C
LAPACK är skrivet i FORTRAN. Funktionerna i biblioteket kan ändå
anropas från C, bara man följer reglerna nedan.
- Byt alla parametrar till pointers. FORTRAN använder call-by-
reference.
- Skriv en funktionsprototyp.
- Lägg till ett understreck efter funktionsnamnet.
- Transponera matriserna. FORTRAN sparar matriser så att elementen
i en kolumn lagras efter varandra i minnet. I C lagras elementen
radvis.
Exempel. Funktionsprototyp för dgeev, som löser egenvärdesproblem.void dgeev_ (char *jobvl, char *jobvr, int *n, double *A, int *lda,
double *wr, double *wi, double *vl, int *ldvl, double *vr, int *ldvr, double *work, int *lwork, int *info ); //Prototyp
Egenfunktioner för
Schrödingerekvationen
Schrödingerekvationen, för vilket som helst:
Om är ett s.k. egentillstånd gäller dessutom:
där är ett energiegenvärde. För att ränkna ut vilka egenenergier
och egentillstånd som är möjliga i något givet system (dvs. för någon
potential ), löser man ekvationen
för och . Det här kan göras t.ex. numeriskt - då diskretiserar man
x-rymden på samma sätt som tidigare. blir en vektor, psi[i], och
högra sidan av ekvationen ovan kan skrivas som en matris gånger
vektorn psi. I matematisk vektor- och matrisnotation kan man skriva
ekvationen på det här sättet:
Det här är en egenvärdesekvation, där matrisen som kallades mot-
svaras av matrisen innanför de stora hakparenteserna. motsvaras av
. Ettorna och tvåorna i den första termen i det högra ledet kommer
från diskretiseringen av den andra derivatan i x, som beskrevs i
Lektion 6.
Exempelprogram
Ett program som löser alla egenvärden och -funktioner för en
potentialbrunn och för en parabolisk potential.
Egenfunktionerna beräknas genom att anropa LAPACK-funktionen
dgeev. Egenvärdena och -vektorerna sorteras, så att egenvärdena är i
stigande ordning.
#include <stdio.h>#include <stdlib.h>#include <math.h>#include <complex.h>
#define Hbar 1.054e-34 //Js#define Me 9.109e-31 //kg
double Xmin = -1e-7; //mdouble Xmax = 1e-7; //mdouble m = Me;#define k (1e-21/(Xmax*Xmax))//konstanten i den paraboliska potentialen
void dgeev_ (char *jobvl, char *jobvr, int *n, double *A, int *lda, double *wr, double *wi, double *vl, int *ldvl, double
*vr, int *ldvr, double *work, int *lwork, int *info );
void solve (double *M, int N, double *E, double *V);
void discretize (int N, double *x);void potential (int N, double *x, double *potential);void hamiltonian (int N, double *H, double *V, double dx);void analytisktE (int N, double *x);
20
Programmering för fysiker. Lektion 8.
double *energies; //Används för att sortera egenärdena
int main (){ int i, j; int N = 200; //Antal punkter i x-led double dx = (Xmax - Xmin) / (N-1); double x[N], V[N]; double H[N*N]; double E[N]; double vectors [N*N]; discretize (N, x); potential(N, x, V); hamiltonian (N, H, V, dx); solve(H, N, E, vectors);
FILE *out; //Skriv egenvärdena (endast realdelen) out = fopen("E.dat", "w"); if (out == NULL) { perror ("E.dat"); exit (1); } for (i = 0; i < N; i++) { fprintf (out, "%d %g\n", i, E[i]); } fclose (out);
//Skriv egenvektorerna out = fopen("psi.dat", "w"); if (out == NULL) { perror ("psi.dat"); exit (1); } for (j = 0; j < N; j++) { for (i = 0; i < N; i++)
fprintf (out, "%g %g\n", x[i], vectors[i+N*j]); fprintf (out, "\n\n"); }
fclose (out);
analytisktE (N, x); return 0;}
/*skriv ut de teoretiska energinivåernaför en potentialbrunn och för en parabolisk potentialsom jämförelse.*/void analytisktE (int N, double *x){ int i; double Ewell, Eparable; double omega = sqrt(k/m); double a = Xmax - Xmin;
FILE *out; out = fopen ("E2.dat", "w"); if (out == NULL) { perror ("E2.dat"); exit (1); } for (i = 0; i < N; i++) { Ewell = Hbar*Hbar * M_PI*M_PI * i*i / (2*m*a*a); Eparable = Hbar*omega*(i+.5); fprintf (out, "%d %g %g\n", i, Ewell, Eparable); } fclose (out);}
void hamiltonian (int N, double *H, double *V, double dx){ int i, j; for (j = 0; j < N; j++) for (i = 0; i < N; i++) H[i + N*j] = 0;
//first row H[0 + N*0] = V[0] + 2 * Hbar * Hbar / (2 * m * dx * dx); H[N*(0+1) + 0] = - Hbar * Hbar / (2 * m * dx * dx);
//last row int n = N-1;
H[n + N*n] = V[n] + 2 * Hbar * Hbar / (2 * m * dx * dx); H[N*(n-1) + n] = - Hbar * Hbar / (2 * m * dx * dx); for (i = 1; i < N-1; i++) { H[i + N*i] = V[i] + 2 * Hbar * Hbar / (2 * m * dx * dx); H[N*(i-1) + i] =
H[N*(i+1) + i] = - Hbar * Hbar / (2 * m * dx * dx); }}
//jämförelsefunktion för qsortint comp (const void *p1, const void *p2){ int *a, *b; a = (int*) p1; b = (int*) p2; if (energies[*a] > energies[*b]) return 1; return -1;}
//Find eigenvalues and eigenvectors for the matrix M.//The eigenvectors are real. The eigenvalues might be complex//but ONLY THE REAL PART IS RETURNED//Sort the eigenvalues in increasing order,//and order the vectors accordingly.void solve (double *M, int N, double *E, double *V){ char jobvl, jobvr; //left,right eigenvectors? //'N'=no, 'V'=yes int lda, ldvl, ldvr, lwork, info;
double *vl; //left eigenvectors, not used double *vr; //right eigenvectors double wr[N], wi[N]; //real and imaginary part of eigenvalues. lwork = 100000+4*N; double work[lwork]; //Temporary workspace, 4*N is the minimum
jobvl ='N'; //No left eigenvectors jobvr ='V'; //Calculate right eigenvectors lda = N; ldvr = ldvl = N;
vl = NULL; vr = malloc (N*N*sizeof(double)); if (vr == NULL) { perror ("Cannot allocate memory"); exit (1); }
dgeev_ (&jobvl, &jobvr, &N, M, &lda, wr, wi, vl, &ldvl, vr, &ldvr,work, &lwork, &info);
//The matrix is overwritten!
if (info == 0) printf ("Success!\n"); else { printf ("ERROR: info =%d\n", info); exit (2); } printf ("Optimal lwork: %5.0f, used lwork: %d\n\n", work[0], lwork);
//Sort the eigenvalues in increasing order. //Permute the eigenvectors accordingly. int index [N]; energies = wr; //till jämförelsefunktionen int i, j;
for (i = 0; i < N; i++) index[i] = i; qsort (index, N, sizeof(index[0]), comp); for (i = 0; i < N; i++) { E[i] = wr[index[i]]; for (j = 0; j < N; j++)
V[j+N*i] = vr[j+N*index[i]]; }
free (vr);}
21
Programmering för fysiker. Lektion 8.
void discretize (int N, double *x){ int i; double dx = (Xmax - Xmin) / (N-1);
for (i = 0; i < N; i++) x[i] = Xmin + i * dx;}
void potential (int N, double *x, double *V){ int i; for (i = 0; i < N; i++) { //V[i] = 0; //Potentialbrunn
//Parabolisk potential V[i] = .5 * k * (x[i] * x[i]); }}
Referenser
LAPACK:
http://www.netlib.org/lapack/
Funktionen dgeev
http://www.netlib.org/lapack/double/dgeev.f
Om att anropa LAPACK från C
http://www.physics.orst.edu/~rubin/nacphy/lapack/cprogp.html
22