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. |