Hemtentamen Kursen har tre moment, LAB1, laborationerna, TEN1, den teoretiska tentan och TEN2. TEN2 består av två delar, dels en kontrollskrivning och en hemtenta. Detta dokument beskriver hemtentan. Man ska lösa hemtentamen självständigt (den kommer att plagiatkontrolleras) och det man lämnar in ska vara en fil som heter display.c. Detta ska vara ett C-program som inkluderar ett par bibliotek som kommer att ges, dessa bibliotek är dynmathfunc och dispunit. Givetvis kommer fler bibliotek att behövas, bland annat grafikbiblioteket SDL. Biblioteket dynmathfunc ordnar en möjlighet att i C, på NewTinyDebian, skriva in ett uttryck som kan definiera en matematisk funktion som sedan skapas i C. Här används så kallade dynamiskt laddade bibliotek och de exakta tekniska detaljerna behöver ni inte förstå i denna kurs. Ni kan bara använda er av den möjligheten. Med biblioteket dispunit kan man hantera funktionsuttryck tillsammans med representationer av intervall där de tillhörande funktionerna ska ritas ut. Det övergripande målet med hemtentan är att skapa ett program som kan rita ut en graf hörande till en matematisk funktion över ett visst intervall. För högre betyg ska vi också beräkna ett ungefärligt värde på arean under funktionsgrafen, alltså integralen av funktionen över det givna intervallet. För högsta betyget ska vi också introducera en möjlighet att byta perspektiv genom att zooma och flytta blickpunkten till andra intervall detta kommer att innebära att ni på egen hand får sätta er in i så kallad händelsehantering i SDL. Detta är dock för högsta betyget. Biblioteken dispunit och dynmathfunc är givna på kursens webbsida. Biblioteket SDL är förinstallerat på NewTinyDebian. Vidare är också ett skelettprogram givet som visar hur man får igång grafiken på NewTinyDebian. Ni ska bygga på dessa program och skriva ett särskilt program som heter display som ska skrivas som ett program som man kör från kommandoprompten. Programmet ska ta ett argument som ska vara en binärfil baserad på följande struct: struct dispunit double a, b, height; double gridlength; char functext[100]; ; Här är a och b intervallets gränser, height är den höjd som grafen ritas ut på, gridlength är hur långt isär två rutnätslinjer ska vara och strängen functext innehåller uttrycket som definerar den funktion som ska ritas ut. Om vi vill rita ut funktionen f(x) = sin(x) i intervallet 0 till 1 får alltså a värdet 0, b värdet 1 och strängen functext innehåller då sin(x). Dessa data ska lagras i en binärfil av ett program som ni också får givet, det heter install_du och skapar en binär fil med det angivna formatet. Programmet som ni ska skriva (som heter display) ska sedan ta ett filnamn som argument och då förvänta sig att filen är en binärfil med det föreskrivna formatet. Ett anrop till display skulle då kunna se ut så här: $ display my_du, där my_du är filnamnet. Då ska ett nytt fönster öppnas som ger en bild av funktionen. Detta ger betyg E. För högre betyg (C) ska integralen beräknas och för högsta betyget (A) ska man kunna zooma och byta perspektiv i grafen och styra detta genom piltangenter, för att flytta sig åt höger och vänster i bilden respektive upp och ner. Med +-tangenten zoomar man in i bilden och med -tangenten zoomar man ut ur bilden. Redovisning av detta sker under tentamensveckan och då ska man ha lämnat in programmet i plagiatkontrollen och vara redo att köra programmet. Då kommer andra binärfiler att presenteras och de ska ge ett korrekt resultat vid körningen av programmet. Givetvis ska vi även diskutera igenom programmets upplägg och andra aspekter. Redovisning i par som vanligt. Dock ingen tidsbokning, tiderna bestäms av skolan och anslås på KTH-Social då kontrollskrivningen är rättad. johnnyp@kth.se Sidan 1 av 7
De olika biblioteken Det är tre nya bibliotek som ska användas, tre små som KTH tillhandahåller (dynmathfunc och dispunit och draw (tillsammans med sdl_demo)) och ett stort som är skrivet av Sam Lantinga m.fl. som heter SDL, Simple Direct Media Layer, som möjliggör tillgång till datorns grafik och händelsehantering. (Man behöver inte använda händelsehanteringen för betygen E och C.) dispunit Namnet på biblioteket dispunit kan utläsas display unit, alltså element som beskriver en display, alltså vad som ska visas. Det engelska ordet display betyder visa eller visningsenhet. Med display unit menar vi då alltså en sak som kan visas. Den består, som nämnt ovan av en struct som innehåller intervallgränser (a och b) och en text som beskriver en funktion och det som ska visas är en bild av grafen hörande till funktionen utritad i det intervall som bestäms av gränserna a och b. (Ovan gavs exemplet f(x) = sin(x) med a = 0 och b = 1.) Det enda som biblioteket egentligen gör är att förpacka dessa data i en struct och ger möjlighet att läsa/skriva dessa från/till binärfiler. Vi behöver alltså inte bry oss om detaljer i hur man skapar binärfiler med dessa data i, vi kan bara använda dessa färdiga funktioner. Det finns ett testprogram inbyggt i dispunit.c som demonstrerar alla funktioner som ges i biblioteket. Kompilera biblioteket med gcc -DTEST dispunit.c så skapas en körbar fil. (Man måste ange växeln -DTEST som ordnar så att ett main() inkluderas via villkorlig kompilering.) dynmathfunc Detta biblioteks namn ska utläsas dynamic mathematical functions, alltså dynamiska matematiska funktioner. Funktioner vet vi vad det är, men vad är då dynamiska funktioner? När vi skriver ett C- program med funktioner så har vi hittills angett det funktioner vi skrivit i källkoden. Då vi använder C under UNIX finns dock en mycket exalterande möjlighet: att låta ett program skapa sina egna funktioner då programmet kör det är det som är betydelsen i ordet dynamisk här. (Detta fungerar under andra operativsystem än UNIX också men det är inte på långt när så lätt att ordna detta som i det system och den konfiguration vi har här.) Detta bibliotek tar en text (en C-sträng) och skapar en fil som innehåller en källkod i C med följande utseende: #include <math.h> double f (double f) return sin(x); Detta blir resultatet om vi skickar in textsträngen sin(x) (som ovan) och styrkan i biblioteket ligger förstås i att vi kan skapa andra funktioner än sin(x), vi kan skapa vilka funktioner som helst som kan skrivas i C. I dynmathfunc har vi inskränkt oss till att skapa funktioner som har en mening matematiskt sett, men det finns ingen anledning till varför vi inte skulle kunna skapa andra funktioner. (Den intresserade programmeraren blir här oerhört exalterad och inspirerad!) Biblioteket dynmathfunc innehåller också hjälpfunktioner som kompilerar denna fil till ett dynamiskt laddningsbart bibliotek och även en funktion som laddar in den funktionen som är skapad i datorns minne under körning. Detta bibliotek förlitar sig på så kallade funktionspekare vilket vi ska beskriva lite av nedan. Biblioteket kan också (som ovan) kompileras med gcc -DTEST... som då ger ett körbart demonstrationsprogram. Det här är också en av anledningarna till varför man måste arbeta med den virtuella maskin som tillhandahålls i kursen alla bibliotek och konfigurations som behövs för att få detta att fungera är installerade i NewTinyDebian. johnnyp@kth.se Sidan 2 av 7
Funktionspekare och biblioteket dynmathfunc i detalj Vi har förlitat oss på pekare till variabler men i det här biblioteket (dynmathfunc) använder vi vid ett tillfälle en pekare till en funktion. Vi kommer inte att behöva förstå funktionspekare i detalj men vi kommer att behöva kunna använda dem i biblioteket som är givet. För att förenkla detta är en typdefinition införd i dynmathfunc.h (se nedan) som hjälper oss att hantera funktionspekare. Vi kommer endast att syssla med funktionspekare till funktioner som tar ett flyttal (double) och returnerar ett flyttal (double), som till exempel funktionen sin(x). Headerfilen till biblioteket dynmathfunc ser ut så här: #ifndef DYNMATHFUNC_H #define DYNMATHFUNC_H void create_function_source_file(char *expression, char *filename); int create_library (char *filename_src, char *filename_dst); typedef double (*pt2func)(double); pt2func GetFunc(char *lib_filename); #endif och.c-filen ser nästan ut så här (vi har inte skrivit ut alla detaljer i funktionerna utan bara återupprepat deras funktionsprototyper): #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <dlfcn.h> #include <wait.h> #include "dynmathfunc.h" void create_function_source_file(char *expression, char *filename); int create_library (char *filename_src, char *filename_dst); pt2func GetFunc(char *lib_filename); #ifdef TEST //Kompilera med gcc -DTEST dynmathfunc.c -ldl main() char funcstr[100]; pt2func myfunction; double x = 1.0; printf("f(x) = "); fgets(funcstr,sizeof(funcstr),stdin); printf("f(x) = %s", funcstr); create_function_source_file(funcstr,"func.c"); create_library("func.c", "func.so"); myfunction = GetFunc("./func.so"); while(x!=0.0) printf("x = "); scanf("%lf", &x); //Här ser vi ett anrop till en dynamiskt skapad funktion: printf("f(x) = %6.3lf.\n", (*myfunction)(x)); //man måste alltså skriva (*myfunction)(x) för att få ut värdet, //men annars är det precis som en vanlig funktion i C som vi //skriver i en vanlig källkodsfil. #endif johnnyp@kth.se Sidan 3 av 7
Kommentaren Kompilera med gcc -DTEST... indikerar att den följande koden bara inkluderas i skapandet av programmet om man vill att det ska skapas en körbart demonstrationsprogram, normalt sett ska vi endast inkludera detta bibliotek i de program vi vill skapa och då exkluderas main()-slingan från kompileringen så att vi inte skapar demonstrationsprogrammet. Då vi kör demonstrationsprogrammet kan det se ut så här: $./a.out f(x) = exp(cos(x)) f(x) = exp(cos(x)) x = 0.5 f(x) = 2.405. x = 1.0 f(x) = 1.717. x = 3.0 f(x) = 0.372. x = 0.0 f(x) = 2.718. $ I denna programkörning är det markerat i fetstil vad användaren matar in. Det första som användaren ska mata in är funktionsdefinitionen och här studerar vi funktionen e upphöjt till cos(x). Sedan frågar programmet gång på gång efter nya värden på x som stoppas in i den dynamiskt skapade funktionen. Vi testar här tre värden, 0.5, 1.0 och 3.0 innan vi matar in värdet 0.0 som är en signal till testprogrammet att avbryta. (Sista funktionsvärdet beräknas också och skrivs ut som är blir e själv.) Det intressanta här är inte vilken matematisk funktion som är skapad, det intressanta här är att det är användaren som har bestämt vilken funktion som ska hanteras genom att ange det i en text, möjligheten finns alltså här att skapa vilken matematisk funktion som helst och det är det vi ska utnyttja. Vi arbetar i UNIX (i Debian) för att detta ska vara möjligt att genomföra på ett sätt som är bra för er. draw Biblioteket draw innehåller endast två funktioner som heter drawpixel() och drawline(), den baserar sig på SDL, koden till draw är given tillsammans med sdl_demo och ni är fria att använda koden i sdl_demo och även förstås draw i er lösning av hemtentan. Biblioteket draw innehåller möjligheter att rita ut en enskild pixel eller en hel linje och var pixeln ska ritas ut, respektive vilka punkter på skärmen som linjen ska gå anges i parametrarna till dessa båda funktioner. Programmet sdl_demo visar hur anropen sker och en bra övningsuppgift kan kanske vara att rita ut lite linjer här och var på den rityta som sdl_demo just nu använder. När man kör det programmet så ritas ett rutnät ut och här kan ni alltså rita ut lite linjer kors och tvärs. Prova också att använda olika färger genom att ändra på RBG-parametrarna, tex röda, gröna eller blå linjer. Det finns en funktion i programmet sdl_demo.c som heter draw_coordinate_system() som tar en SDL_Screen som parameter och som ritar ut ett koordinatsystem. Om vi lägger in anropen DrawLine(screen, 100, 100, 350, 200, 255,0,0 ); DrawLine(screen, 150, 150, 370, 210, 0,255,0 ); DrawLine(screen, 120, 140, 360, 240, 0,0,255 ); längst ner i den funktionen (innan den avslutas) så får vi följande utseende vid körning av johnnyp@kth.se Sidan 4 av 7
programmet: Vi ser alltså tre linjer, en röd, en blå och en grön. Det kan vara en ypperlig övning att helt enkelt bara knappa in denna kod, och sen göra lite mönster av linjer med forloopar, kan ni, med forloop med anrop till DrawLine() ordna något som ser ut så här till exempel? Förtydligande kommentarer om innehållet i dispunit Några har frågat om betydelsen av attributet height i dispunit. Jag lämnar ett förtydligande här. Tanken med programmet är att vi ska rita ut grafer till matematiska funktioner som vi anger dynamiskt, dvs vi ska kunna skriva in vilken matematisk funktion som helst, bara vi använder korrekt C-syntax. Till exempel anges funktionen e upphöjt till sin x som exp(sin(x)), eller bara sin x anges som sin(x). Vi vill sedan rita ut detta över ett intervall vars gränser anges av attributen a (vänster intervallgräns) och b (höger intervallgräns). Dessa fastställs i install som skapar en dispunit som alltså lagrar dessa data. (Alltså vilken funktion det är och intervallgränser med mera.) Men vi har också ett attribut som heter height. Min tanke var att den vy som användaren ska få är en kvadratisk yta som ritar funktionsgrafen på en viss höjd, därav namnet height (som ju betyder höjd på engelska.) Eftersom ritytan är kvadratisk räcker det att ange vilken höjd som avses och height är alltså då tänkt att vara den höjd över x-axeln som vi observerar funktionsgrafen på. En punkt i diagrammet som har y-koordinaten height kommer då alltså att synas i mitten av vår johnnyp@kth.se Sidan 5 av 7
kvadratiska yta. Om vi till exempel sätter height = 0 så kommer hela x-axeln att ritas ut och synas mitt i bilden. Jag vill också framhålla en annan viktig aspekt när det gäller programmering. Om tolkningen av uppgiften är otydlig kan det vara lämpligt att införa egna tolkningar, det är inte bara otillåtet, det är faktiskt önskvärt att ni gör det vid behov. Eftersom vi har redovisningar i par så kan det också bli extra intrssant att se på två olika tolkningar av en genoförd programmeringsuppgift, det ger oss möjligheten till mycket intressanta diskussioner. Olika tolkningar är alltså önskvärt! Det kommer alltid att bli så vid utveckling av programvara att det finns olika tolkningar av behovet och det blir då extra intressant om programmeraren, dvs ni, inför egna tolkningar. Konsten är att tolka en uppgift på ett sådant sätt att man inte så att säga omintetgör kundens önskemål, det gäller att sätta sig in i vad kunden vill ha och i grund och botten uppfylla kundens behov. (Nu är skolan kund här.) Några ord om beräkning av integralen För de som vill ha ett högre betyg än E behöver ni beräkna ett närmevärde på integralen. Det är då tänkt att ni väljer en fin indelning av intervallet med gränser a och b och summerar rektangelareor under funktionsgrafen baserade på denna indelning. I skissen ser vi en funktion som antar positiva värden och integralen approximeras som summan av ett antal (gröna) rektangelareor. Dessa rektangelareor ligger då under, eller kanske över funktionsgrafen, men om vi väljer en tillräckligt fin indelning av intervallet mellan a och b så kan den totala rektangelarean komma ganska nära arean under funktionsgrafen, det vill säga integralen av funktionen y=f(x) från a till b. Ni får själv avgöra hur programmet ska välja hur fin indelningen ska bli, kanske det kan bli (b-a)/100, så att vi får 100 rektangelareor att beräkna eller kanske (b-a)/1000. Integralens värde kan då beräknas med något slags forloop: for(k=1;k<1000;k++) integral = integral + rektangelarea(k,f,dx,a,b); där alltså k är ett vilken rektangelarea som ska beräknas, f är en funktionspekare till funktionen, dx är indelningens finhet och a och b är intervallets gränser. johnnyp@kth.se Sidan 6 av 7
Förpackning av allt material till hemtentan Alla filer som ni behöver för att komma igång med hemtentan finns i de tre arkiven dynmathfunc_files.zip, install_du_files.zip och sdl_demo_files.zip. Dessa tre arkiv finns på KTH-Social och man laddar ner dem till NewTinyDebian och packar upp dem med hjälp av unzip-kommandot. Detta skapar tre kataloger och man kan gå ner i dessa kataloger och hitta källkodsfiler som kan kompileras i NewTinyDebian med kommandot make. Så kallade make-filer, alltså instruktioner om hur programmen ska byggas ihop, finns precis brevid i katalogerna så det är bara att skriva make så ska det fungera. I vissa fall behövs parametrar, läs README-filerna (som också finns i katalogerna) för mer information om detta. I sdl_demo_files.zip finns dessutom ett litet bibliotek som heter draw som kan användas för att rita ut punkter och linjer med SDL, detta används i demonstrationsprogrammet som också finns i sdl_demo_files.zip. I install_du_files.zip finns också biblioteket dispunit som också kommer att vara en del av lösningen på hemtentan, det program du ska skriva ska heta display och ta ett filnamn som kommandoradsparameter, filen ska innehålla en display unit som innehåller den funktion (med intervall etc.) som ska ritas ut och för att läsa in display uniten kommer dit program att behöva anropa en funktion i biblioteket dispunit. Det finns gott om exempel på make-filer överallt i zip-filerna. johnnyp@kth.se Sidan 7 av 7