Ein kleiner Programmierkurs - Teil 1.5 Integration durch Trapezfunktion

Im letzten Teil dieser Lektion wollen wir eine numerische Intergration der Funktion durch Annäherung mit Trapezflächen visualisieren. Dazu lernen wir eine neue Zeichenfunktion kennen und schauen uns an, wie Variablen verwendet werden können um Rechenergebnisse zu speichern und als Zwischenergebnisse weiter zu verwenden.
Das Riemann-Integral einer Funktion kann näherungsweise durch die Ober- und Untersummen der Flächen von Treppenfunktionen bestimmt werden, die den Funktionsgraphen von oben bzw. von unten annähern. Eine weitere Möglichkeit, dieses Integral approximativ zu berechnen besteht darin, den Funktionsgraphen durch einen Kantenzug zu ersetzen und dann die Trapezflächen unterhalb des Kantenzugs aufzuaddieren. Dies entspricht der Mittelwertbildung aus der Ober- und Untersumme bei gleicher Unterteilung der x-Achse. Dieses Vorgehen wollen wir nun für unsere Funktion implementieren.

Dazu wollen wir zunächst die entsprechenden Trapezflächen zeichnen lassen. Zur besseren Unterscheidung sollen benachbarte Trapeze unterschiedliche Farben erhalten. Der entsprechende Befehl lautet fillpoly. fillpoly zeichnet ein ausgemaltes Polygon und erhält dafür zwei Parameter. Der erste Parameter ist eine geordnete Liste aller Eckpunkte des Polygons. der zweite Parameter Parameter ist ein optionaler, sogenanter Modifyer. Dieser wird mit color-> eingeleitet, gefolgt von dem Farbvektor im RGB-Farbmodell (vgl. Teil 1). Wird dieser weggelassen, so wird das Polygon in der Standardfarbe von Cinderella für Polygone gezeichnet.

Als Ecken wählen wir die aktuelle Stützstelle (vi,0), (vi,f(vi)) sowie die darauf folgende. Diese kennen wir in der Schleife noch nicht. Deshalb führen wir den Nachfolger von i ein: j=i+1. Auch j muss mit dem Vorzeichen multipliziert werden: vj=vorzeichen*j. Jetzt muss die Liste der Trapezecken noch in die richtige Reihenfolge gebracht werden:


[[vi,0],[vi,f(vi)],[vj,f(vj)],[vj,0]]

Zur Festlegung der Farbe verwenden wir wieder unsere Funktion farbe.

Neben der Funktion fillpoly gibt es noch die analoge Funktion drawpoly, die nur den (unausgefüllten) Polygonzug, also die Kanten, zeichnet.


f(x):=x/2+sin(x);
plot(f(x));A.y=f(A.x);
 
farbe(c):=[sin(c),sin(c+2*pi/3),sin(c-2*pi/3)];
A.color=farbe(A.x); 
vorzeichen=if(A.x>=0,1,-1); 
forall(0..abs(A.x),i,
    vi=vorzeichen*i;
    j=i+1;
    vj=vorzeichen*j;
 
    draw([vi,f(vi)]);
    draw([vi,0],[vi,f(vi)]);
    fillpoly([[vi,0],[vi,f(vi)],[vj,f(vj)],[vj,0]],color->farbe(vi));
);

Bitte schalten Sie Java ein, um eine Cinderella-Konstruktion zu sehen.
Wenn man den Punkt A bewegt, erscheint nun stets ein neues Flächenstück, sobald A die nächste ganze Zahl auf der x-Achse überschreitet. Die Fläche eilt dem A also etwas voraus. Diesen Fehler wollen wir nun beheben.
Das Problem dabei ist, dass die Flächenstücke jeweils von einem ganzzahligen x-Wert bis zum nächsten ganzzahligen Wert gezeichnet werden. Ist A.x aber nicht ganzzahlig, so geht die Fläche des letzten Stückes zu weit. Wir wollen nun also in der Schleife überprüfen, ob wir schon das letzte Stück erreicht haben und wenn dies der Fall ist, zeichnen wir nicht bis vj sondern nur bis A.x.


f(x):=x/2+sin(x);
plot(f(x));
A.y=f(A.x); 

farbe(c):=[sin(c),sin(c+2*pi/3),sin(c-2*pi/3)];
A.color=farbe(A.x); 
vorzeichen=if(A.x>=0,1,-1); 
forall(0..abs(A.x),i,
    vi=vorzeichen*i;
    j=i+1;
    vj=vorzeichen*j; 
    draw([vi,f(vi)]);
    draw([vi,0],[vi,f(vi)]); 
    if(i<floor(abs(A.x)),
        fillpoly([[vi,0],[vi,f(vi)],[vj,f(vj)],[vj,0]],color->farbe(vi))
    ,
        fillpoly([[vi,0],[vi,f(vi)],[A.x,f(A.x)],[A.x,0]],color->farbe(vi))
    );  
);

Bitte schalten Sie Java ein, um eine Cinderella-Konstruktion zu sehen.
Jetzt wollen wir die Flächenmaße der einzelnen Trapeze aufaddieren um die Gesamtfläche unter der Kurve zu berechnen. Dazu führen wir eine neue Variable namens flaeche ein, deren Wert wir am Anfang vor der Schleife auf 0 setzen, da wir hier noch keine Flächen aufaddiert haben.
In der Schleife addieren wir jeweils das Maß der neuen Trapezfläche hinzu. Dies erfolgt mit der Flächenmaßformel für Trapeze: Breite*(Minimale Höhe + Maximale Höhe)/2. Die Breite ist dabei jeweils die Differenz aus vj und vi. Den hinteren Teil nähern wir durch den Funktionswert an der mittleren x-Position an. (Dies ist zwar nicht ganz korrekt, aber es liefert eine gute Näherung, die uns hoer erst einmal genügen soll.
Um in einer Variablen nun mehrere Werte zu addieren, können wir schreiben


variable = variable + summand;

Dies würde mathematisch nur Sinn machen, wenn summand=0 ist, aber in der Programmierung ist dies eine erlaubte Zuweisung. Hier wird an den Speicherplatz der Variablen ein neuer Wert gespeichert, der sich aus dem alten Wert an dem Speicherplatz und dem Wert von summand zusammensetzt.
Unser Programm sieht jetzt wie folgt aus:


f(x):=x/2+sin(x);
plot(f(x));
A.y=f(A.x); 

farbe(c):=[sin(c),sin(c+2*pi/3),sin(c-2*pi/3)];
A.color=farbe(A.x); 
vorzeichen=if(A.x>=0,1,-1); 
flaeche=0; 

forall(0..abs(A.x), i,
    vi=vorzeichen*i;
    j=i+1; vj=vorzeichen*j; 
    draw([vi,f(vi)]);
    draw([vi,0],[vi,f(vi)]); 
    if(i<floor(abs(A.x)),
        fillpoly([[vi,0],[vi,f(vi)],[vj,f(vj)],[vj,0]],color->farbe(vi));
        flaeche = flaeche + (vj-vi)*f((vi+vj)/2)
    , 
        fillpoly([[vi,0],[vi,f(vi)],[A.x,f(A.x)],[A.x,0]],color->farbe(vi));
        flaeche = flaeche + (A.x-vi)*f((vi+A.x)/2);
    );
); 
drawtext([2,-2],"Der Flächeninhalt ist "+flaeche);

Bitte schalten Sie Java ein, um eine Cinderella-Konstruktion zu sehen.

Schließlich wollen wir - ebenfalls wieder nur näherungsweise - den Verlauf der Flächenmaßkurve in Abhängigkeit von A.x als Streckenzug zeichnen lassen und diese mit der korrekten Kurve der - zuvor per Hand - berechneten Stammfunktion als exakter Lösung vergleichen. Leider kann Cinderella keine symbolische Integration durchführen.
Es ist jedoch durchaus möglich, eine solche Intergration aus Cinderella heraus durch das Computer-Algebra-System Mathematica ausführen zu lassen und mit dem Ergebnis weiterzuarbeiten. Doch da nicht jeder eine Mathematica-Lizenz zur Verfügung hat, verzichten wir hier darauf.
Für unsere Zwecke soll es genügen, dass wir die Stammfunktion als g(x) selbst in dem Programmcode definieren.


f(x):=x/2+sin(x);
plot(f(x));
A.y=f(A.x); 

farbe(c):=[sin(c),sin(c+2*pi/3),sin(c-2*pi/3)];
A.color=farbe(A.x); 
vorzeichen=if(A.x>=0,1,-1); 
flaeche=0; 

forall(0..abs(A.x), i,
    vi=vorzeichen*i;
    j=i+1;
    vj=vorzeichen*j; 
    draw([vi,f(vi)]);
    draw([vi,0],[vi,f(vi)]); 
    if(i<floor(abs(A.x)),
        fillpoly([[vi,0],[vi,f(vi)],[vj,f(vj)],[vj,0]],color->farbe(vi));
        draw([vi,flaeche],[vj,flaeche+(vj-vi)*f((vi+vj)/2)],color->[1,1,1],size->2);
        flaeche = flaeche + (vj-vi)*f((vi+vj)/2)
    , 
        fillpoly([[vi,0],[vi,f(vi)],[A.x,f(A.x)],[A.x,0]],color->farbe(vi));
        draw([vi,flaeche],[A.x,flaeche + (A.x-vi)*f((vi+A.x)/2)],color->[1,1,1],size->2);
        flaeche = flaeche + (A.x-vi)*f((vi+A.x)/2);
    );
); 
drawtext([2,-2],"Der Flächeninhalt unter der Kurve ist "+flaeche); 
g(x):=(x^2)/4-cos(x)+1;
plot(g(x)); 
drawtext([2,-3],"Das Integral ist "+g(A.x)); 

Bitte schalten Sie Java ein, um eine Cinderella-Konstruktion zu sehen.
Wir sehen, dass unsere näherungsweise Berechnung eine recht gute Approximation an die tatsächliche Stammfunktion liefert. Durch Verfeinerung der Intervallbreiten kann hier noch eine bessere Berechnung erfolgen.

Grundsätzlich funktioniert unser Programm auch für jede andere Funktion. Es muss de Definition von f(x) (und g(x), damit auch die Stammfunktion korrekt gezeichnet wird( geändert werden. Und das ist einer der wichtigen Vorteile von Programmiereung: die Wiederverwendbarkeit! Ein Lösungsweg, den wir programmiert haben, kann der Computer für uns beliebig oft auf verschiedene Objekte oder Situationen anwenden.

Abschließen wollen wir diese Lektion mit einem zusammenfassenden Überblick.