Das Modul Math::Algebra::Symbols
löst Gleichungen mittels
klassischer Mathematik mit Symbolen. Imager::Plot
erzeugt
professionell aussehende Graphen.
Millionen von Schülern graust's vor dem realitätsfernen Algebraunterricht. Dabei zieht sich das Rechnen mit der Variablen x durch alle Lebenslagen: Ob Benzinverbrauch oder eine Fuchs- und Hasenjagd, überall kommen die Grundsätze der Algebra gut zur Geltung. Heute übernimmt einmal Perl das Lösen und Umformen von Gleichungen.
Nehmen wir als Beispiel eine einfache Formel: In Amerika geben Autohersteller nicht an, wieviel Liter Benzin ihre Produkte auf 100 Kilometer verbrauchen, sondern wieviele Meilen diese mit einer Gallone Benzin fahren. Ein hoher Wert indiziert also niedrigen Benzinverbrauch.
Wie lässt sich nun ein Miles/Gallon-Wert in Liter pro 100 km umrechnen?
Listing mpgal
definiert mit Math::Algebra::Symbols
zwei Symbole $gallons
und $miles
und
baut die Formel Schritt für Schritt auf. Der Ausdruck
my $liters = $gallons * 37854118/10000000;
gibt an, dass eine Gallone 3.7854118 Litern entspricht. Zwei Dinge
sind zu beachten: Erstens mag Math::Algebra::Symbols
in der
bei Drucklegung vorhandenen Version 1.16 (noch)
keine langen Fließkommawerte, also gibt man sie als Brüche an. Und
da $liters
die Anzahl der Liter im Beispiel angibt, muss
man die Anzahl der Gallonen mit 3.7854118 multiplizieren:
Zwei Gallonen (eine 2 für $gallons
) entspricht so
7.5708236 Litern in $liters
. Ähnliches gilt für Kilometer und
Meilen, eine Meile entspricht 1.609344 Kilometern.
Den Benzinverbrauch pro 100km gibt die Formel
my $usage = $liters / $kilometers * 100;
an. Da vorher schon Formeln für $liters
und $kilometers
angegeben
wurden,
ersetzt Math::Algebra::Symbols
die Symbole und generiert
eine Gleichung, die nur noch von $gallons
und $miles
abhängt. Zeile 18 in mpgal
gibt sie aus:
Formula: 94635295/402336*$gallons/$miles
Math::Algebra::Symbols
hat dazu mit ein wenig Bruchrechnung die
Konstanten gekürzt. Um konkrete Werte in die Formel einzusetzen,
weist mpgal
den Variablen $gallons
und $miles
Werte
zu und ruft in Zeile 25 jeweils eval $usage
auf, um die Formel
anzuwenden:
20 m/gal: 11.8 l/100km 30 m/gal: 7.8 l/100km 40 m/gal: 5.9 l/100km
Das war nun trivial, eine einfache Funktion in Perl hätte es auch getan.
Math::Algebra::Symbols
kann aber noch mehr: Gleichungen, auch
gerne höheren Grades, nach Variablen auflösen, zum Beispiel.
01 #!/usr/bin/perl 02 ########################################### 03 # mpgal - miles/gallon => liters/100km 04 # Mike Schilli, 2004 (m@perlmeister.com) 05 ########################################### 06 use warnings; 07 use strict; 08 09 use Math::Algebra::Symbols; 10 11 my ($gallons, $miles) = 12 symbols(qw(gallons miles)); 13 14 my $liters = $gallons * 37854118/10000000; 15 my $kilometers = $miles * 1609344/1000000; 16 my $usage = $liters / $kilometers * 100; 17 18 print "Formula: $usage\n"; 19 20 for $miles (qw(20 30 40)) { 21 22 $gallons = 1; 23 24 printf "$miles m/gal: " . 25 "%4.1f l/100km\n", eval $usage; 26 }
Wie wär's mit folgender Textaufgabe: Ein Fuchs sieht einen in 10 Meter Entfernung und konstanten 5 Metern pro Sekunde davonzischenden Hasen und beginnt die Hatz, während der er mit 7 Metern pro Sekundenquadrat beschleunigt. Wie lange dauert es, bis er den Hasen schnappt?
Listing race
definiert hierzu ein Symbol $t
für die verstrichene
Zeit in Sekunden und gibt die von Hase und Fuchs gelaufene Strecke,
abhängig von der Zeit an:
my $rabbit = 10 + 5 * $t; my $fox = 7 * $t * $t;
Der Hase hat mit seinen 10 Metern Vorsprung und gemäß der Formel für
konstante Geschwindigkeit (s = v * t), zum Zeitpunkt t
die Strecke
10 + 5 * t
zurückgelegt, während der Fuchs gemäß der Formel für
konstante Beschleunigung (s = a * t**2) genau 7 * $t**2 Meter
aufholt.
Er schnappt den Hasen, wenn beide Streckenangaben übereinstimmen, also die Gleichung
my $schnapp = ($rabbit - $fox);
den Wert 0 liefert. Von Hand ausgerechnet liefe dies auf eine quadratische
Gleichung hinaus, und ich müsste mein Formelbuch aus der 7. Klasse
aus dem Keller holen, aber dank Math::Algebra::Symbols
löst man
$schnapp
einfach mittels
$schnapp->solve("t")
nach $t
auf und erhält (wegen der quadratischen Gleichung) eine
Liste mit zwei symbolischen Gleichungslösungen zurück:
Solution: 1/14*sqrt(305)+5/14 Solution: -1/14*sqrt(305)+5/14
Da negative Zeiten für praktische Belange wie das Leben des Hasen
irrelevant sind, wird die zweite Lösung ab Zeile 23 verworfen. Den
Lösungswert in Sekunden erhält man durch Einsetzen, was wie
im vorigen Beispiel Perls eval
erledigt:
my $val = eval $solution;
Nach etwa 1.60 Sekunden ist's also aus für den Hasen, und nachdem Zeile
27 die symbolische Variable $t
auf dieses Ergebnis gesetzt hat,
steht auch die vom Fuchs zurückgelegte Strecke fest: eval $fox
gibt
sie mit etwa 18.02 Metern an.
01 #!/usr/bin/perl 02 ########################################### 03 # race - Fox chasing a Rabbit 04 # Mike Schilli, 2004 (m@perlmeister.com) 05 ########################################### 06 use warnings; 07 use strict; 08 09 use Math::Algebra::Symbols; 10 11 my ($t) = symbols(qw(t)); 12 13 my $rabbit = 10 + 5 * $t; 14 my $fox = 14/2 * $t * $t; 15 16 my $schnapp = ($rabbit - $fox); 17 18 for my $solution 19 (@{$schnapp->solve("t")}) { 20 print "Solution: $solution\n"; 21 my $val = eval $solution; 22 if($val < 0) { 23 print "Discarded\n"; 24 next; 25 } else { 26 printf "%.2f seconds\n", $val; 27 $t = $val; 28 printf "%.2f meters\n", eval $fox; 29 } 30 }
Listing graph
illustriert das Ganze graphisch, wie Abbildung
1 zeigt. Mit dem
Modul Imager::Plot
vom lassen sich mittels ein paar Zeilen Perlcode
professionelle Plots in verschiedenen Bildformaten zeichnen.
Abbildung 1: Nach etwa 1.6 Sekunden schnappt der konstant beschleunigende Fuchs den mit 10 Meter Vorsprung und konstanter Geschwindigkeit rennenden Hasen. |
Zeile 12 erzeugt ein neues Imager::Plot
-Objekt und nutzt als Font
für die Graphenbeschriftung den unter dem angegebenen Pfad
stehenden Truetype-Font tahoma.ttf
.
Die for
-Schleife ab Zeile 21 iteriert in Hundertstel-Schritten über
X-Werte von 0.0 bis 2.0 und legt drei Arrays an: @t
für die
X-Achsenwerte und @rabbit
bzw. @fox
für die den jeweiligen
Zeitwerten gemäß den Bewegungsformeln zugeordneten Ortswerte
von Hase und Fuchs.
Zeile 28 fügt den Hasenplot in Grün in das Koordinatensystem ein, Zeile
37 und folgende fügt den Graphen des Fuchses in rot hinzu. Die
Render
-Funktion in Zeile 55 übernimmt das Zeichnen und die write
-Methode
in Zeile 58 schreibt das Ganze in eine PNG-Datei.
Abbildung 2 zeigt den Kurvenverlauf des Polynoms y = t^3 - 3t^2 - 3t + 1, dessen zwei Bäuche jeweils ein lokales Maximum und Minimum darstellen. In der Schule lernt man, dass die Steigung der Kurve an diesen Stellen gleich Null ist. Um sie zu bestimmen, differenziert man die Funktion, setzt das Ergebnis gleich Null und bestimmt die Lösungen der entstehenden Gleichung.
Glücklicherweise kann Math::Algebra::Symbols
einfache Funktionen
differenzieren:
my ($x) = symbols('x');
my $y = $x**3 - 3*$x**2 - 3*$x + 1; my $diff = $y->d('x');
my $extrema = $diff->solve('x'); print eval($_), "\n" for @$extrema;
Die Methode $y->d('x')
differenziert die in $y
definierte Funktion
nach $x
, was das angegebene Polynom dritten Grades in eines zweiten
Grades überführt. Die nachfolgend aufgerufene solve()
-Methode löst
letzteres und gibt
wie vorher eine Referenz auf ein Array mit zwei Elementen zurück:
-1/6*sqrt(72)+1 1/6*sqrt(72)+1
Diese rationalen Zahlen konvertiert die eval
-Funktion in die
Fließkommawerte
-0.414213562373095 2.41421356237309
Das sind die x-Werte der Bäuche. Die y-Werte erhält man, wenn man
die Variable $x
gleich dem evaluierten Wert für das jeweilige
Extremum setzt und anschließend $y
auswertet:
for(@$extrema) { $x = eval $_; print eval($y), "\n"; }
Das Ergebnis:
1.65685424949238 -9.65685424949238
Oder wie wär's mit dem Wendepunkt des Graphen zwischen den zwei Bäuchen? An dieser Stelle ist die Grenze zwischen abnehmender und zunehmender Steigung. Wie ich von meinem genialen Mathelehrer Hauptner am Gymnasium Neusäß auch nach zwanzig Jahren noch weiss, ist die zweite Ableitung dort gleich Null:
$y->d('x')->d('x')->solve('x');
gibt exakt den Wert 1
zurück -- da staunt der Fachmann und der Laie
wundert sich!
Math::Algebra::Symbols
kann auch mit allerlei trigonomischen Funktionen
umgehen, allerdings kommt es bei komplizierteren Strukturen noch
ins Schleudern.
Abbildung 2: Der Funktionsverlauf des Polynoms t^3 - 3t^2 - 3t + 1 |
Math::Algebra::Symbols
und Imager::Plot
sind vom CPAN erhältlich
und eignen sich hervorragend zum Herumspielen mit allerlei mathematischen
Rätseln.
Math::Algebra::Symbols
ist allerdings noch Alpha-Qualität, wird aber
von seinem Autor, Philip Brenan,
stetig weiterentwickelt und könnte im Laufe der Zeit immer mehr
Funktionen, ähnlich wie Mathematica, anbieten. Paukt fleißig Mathe,
lernt für's Leben!
01 #!/usr/bin/perl 02 ########################################### 03 # graph -- Graph of fox/rabbit chase 04 # Mike Schilli, 2004 (m@perlmeister.com) 05 ########################################### 06 use strict; 07 use warnings; 08 09 use Imager; 10 use Imager::Plot; 11 12 my $plot = Imager::Plot->new( 13 Width => 550, 14 Height => 350, 15 GlobalFont => 16 '/usr/share/fonts/truetype/tahoma.ttf'); 17 18 my (@t, @rabbit, @fox); 19 20 # Generate function data 21 for(my $i = 0.0; $i < 2.0; $i += 0.01) { 22 push @t, $i; 23 push @rabbit, 10 + 5 * $i; 24 push @fox, 7 * $i * $i; 25 } 26 27 # Add rabbit plot 28 $plot->AddDataSet(X => \@t, Y => \@rabbit, 29 style => { 30 marker => { 31 size => 2, 32 symbol => 'circle', 33 color => Imager::Color->new('green') 34 }}); 35 36 # Add fox plot 37 $plot->AddDataSet(X => \@t, Y => \@fox, 38 style => { 39 marker => { 40 size => 2, 41 symbol => 'circle', 42 color => Imager::Color->new('red') 43 }}); 44 45 my $img = Imager->new(xsize => 600, 46 ysize => 400); 47 48 $img->box(filled => 1, color => 'white'); 49 50 # Add text 51 $plot->{'Ylabel'} = 'Distance'; 52 $plot->{'Xlabel'} = 'Time'; 53 $plot->{'Title'} = 'Fox vs. Rabbit'; 54 55 $plot->Render(Image => $img, 56 Xoff => 40, Yoff => 370); 57 58 $img->write(file => "graph.png");
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |