Auch in diesem Beitrag soll es um die Swiss Ephemeris gehen, die vom Astrodienst Zürich herausgebrachte Bibliothek von Ephemeridenfunktionen. Für eigene Studien habe ich mir eine Sammlung von sogenannten xsubs programmiert, mit denen ich in der Programmiersprache Perl auf die Bibliotheksfunktionen der Swiss Ephemeris zugreifen kann.

Swiss Ephemeris und Perl

Inhalt

Warum Perl?
Das Modul Win32::API
XSUBs (externe Subroutinen)
Das Modul SwissEph
:: Julianisches Datum und Kalenderdatum
:: Berechnung der Planeten und Häuser
:: Listen von Planetenständen
Das Astrologische Austauschformat (AAF)
Ein Anwendungsbeispiel
Anwendungen des Moduls

Warum Perl?

Die "Practical Extraction and Reporting Language" (PERL) ist eine vielseitig einsetzbare, freie Programmiersprache, die gerade ihr 20jähriges Jubiläum gefeiert hat. Schon aufgrund dieses Alters hat sie einen hohen Reifegrad - was die Perl-Entwickler nicht daran hindert, mit dem in Vorbereitung befindlichen Perl 6 Release die gesamte Sprache von Grund auf neu zu implementieren. Perl wird gerne von Systemadministratoren und Web-Entwicklern eingesetzt, ist aber als plattformübergreifende Hochsprache für alle möglichen Aufgaben der Informationsverarbeitung verwendbar. Das mächtige "Bestiarium" regulärer Ausdrücke macht vor allem die Stringverarbeitung zu einer grossen Stärke von Perl, denn der Leistungsumfang der in Perl eingebauten "Regex Engine" hat es in sich.

Darüberhinaus haben Perl-Programme häufig einen besonderen ästhetischen Reiz. Es ist möglich, sehr schöne und zugleich kurze Perl-Programme zu entwickeln. Larry Wall, der Erfinder von Perl, sagt es so: "Auf jeder Ebene hilft Perl Ihnen mit minimalem Getöse und maximalem Vergnügen auf Ihrem Weg von hier nach dort. Das ist der Grund, warum viele Perl-Programmierer mit einem Grinsen durch die Gegend laufen."

Hier ein kleines Beispiel für Kenner und Geniesser: Wenn Sie einen Perl-Interpreter besitzen, können Sie mit dem folgenden Einzeiler testen, ob eine Zahl prim ist, zum Beispiel die Zahl 19. Der Algorithmus (ich habe ihn im Internet gefunden) ist zwar nicht besonders effizient, aber kurz und schön:

perl -le 'print "prim" if (1 x shift) !~ /^(11+)\1+$/' 19

Die grosse Stärke der Sprache Perl liegt in der Extraktion von Daten. Das ist auch für Astrologen nützlich. Wenn Sie aus einer Datei oder aus allen Dateien eines Ordners Geburtsdaten extrahieren wollen, um irgendeine astrologische Rechnung auszuführen und die Ergebnisse in einer Statistikdatei zu sammeln, ist Perl das ideale Hilfsmittel.

Perl ist wie Java eine Interpretersprache. Sie benötigen also einen Perl-Interpreter, um Ihre Perl-Programme auszuführen. Ein bekannter und beliebter Perl-Interpreter für Windows wurde von ActiveState entwickelt und kann von deren Webseite kostenlos heruntergeladen werden. Ich verwende auf meinem Notebook das Perl-Release 5.8 mit dem Perl-Interpreter von ActiveState.

Die astrologischen Berechnungen von Häusern und Planeten liegen in der Swiss Ephemeris gekapselt vor. Für Windows-Rechner bedeutet das, Sie haben die DLL swedll32.dll auf Ihrem Rechner, und zwar in einem der Verzeichnisse, die in der Umgebungsvariablen $PATH aufgeführt sind (das sind die Verzeichnisse, die Windows auf der Suche nach Biblotheken heranzieht). Wie können Sie nun in Perl auf die Funktionen der DLL zugreifen?


Zum Anfang

Das Modul Win32::API

Das von Aldo Calpini entworfene Modul Win32::API zum Aufruf beliebiger Funktionen aus beliebigen DLLs ist an Einfachheit wohl nicht zu übertreffen. Allerdings kommt es bei Funktionen mit komplizierteren Schnittstellen schnell an seine Grenzen. Es ist für experimentelle Zwecke gut geeignet. "Produktive" Funktionen sollten jedoch nicht auf diesem Modul aufbauen.

Ein Beispielaufruf soll das Prinzip verdeutlichen. Wir schreiben heute Sonntag, den 16.3.2008. Das ist der Tag 2454541.5 in der fortlaufenden "julianischen" Tageszählung, wie sie die Astronomen verwenden. Der Zweizeiler

$DayOfWeek = new Win32::API('swedll32','_swe_day_of_week@8','D','I');
print $DayOfWeek->Call( 2454541.5 );
gibt die die Wochentagsnummer 6 (= Sonntag) für dieses Julianische Datum aus, wobei - und darum geht es in diesem Beispiel - die Bibliotheksfunktion swe_day_of_week() der Swiss Ephemeris zur Berechnung durchlaufen wurde.

Wie funktioniert das? Im Konstruktor der API-Calls gibt man als erstes Argument die DLL swedll32 und als zweites Argument den Namen der aufzurufenden Funktion an. Hier wurde der sogenannte "dekorierte" Name verwendet, der die Grösse des Stacks für die Parameter in Bytes enthält. (Der Grund, dass ich hier den dekorierten Namen verwende, ist schlicht der, dass es mit dem gewöhnlichen Namen nicht funktioniert.) Die folgenden beiden Argumente dienen dazu, die Signatur der gerufenen Methode zu kennzeichnen. In diesem Fall nimmt die Funktion swe_day_of_week() einen double entgegen und gibt einen Integerwert zurück. Das dritte Argument ist daher 'D' (ein Double), und das vierte Argument 'I', um den Typ des Rückgabewerts zu bezeichnen.

Einfacher ist ein DLL-Aufruf wohl nicht möglich. Zu beachten ist jedoch, dass die Implementierung den Aufrufkontext für die DLL anhand der Signatur ad hoc aufbaut. Das ist ineffizient. Um einen schnelleren Aufruf zu erhalten, ist es besser mit xsubs (externen Subroutinen), zu arbeiten, denen wir uns im nächsten Abschnitt zuwenden wollen.

Das Modul Win32::API kann von der Webseite http://dada.perl.it/ oder vom CPAN heruntergeladen werden.


Zum Anfang

Externe Subroutinen (xsubs)

Die elementare Modularisierungseinheit von Perl-Code ist die Subroutine, deren Deklaration mit dem Schlüsselwort sub eingeleitet wird. Beim Aufruf einer sub wird der in ihr enthaltene Perl-Code durchlaufen. Der Aufruf einer externen Subroutine, einer xsub, hingegen, erfolgt syntaktisch gleich wie ein sub-Aufruf, jedoch wird die Perl-Laufzeit verlassen und eine externe Bibliotheksfunktion aufgerufen.

Die externe Subroutine ist Teil eines gewöhnlichen Moduls, der vom Aufrufer mittels use in sein eigenes Programm eingebunden wird. Der Aufruf der externen Funktionen ist jedoch nicht direkt möglich: Die Datenstrukturen der Funktionsschnittstelle müssen an die in Perl verwendeten Datentypen angepasst werden. Für dieses Mapping wird eine eigene Syntax benutzt, die sich xs nennt. Mit dem Dienstprogramm h2xs, das Teil jeder Perl-Distribution ist, lässt sich ein Modul-Template generieren, das neben einer noch leeren xs-Datei auch ein Makefile enthält (genauer: einen "Make-Maker", ein Perl-Programm makefile.pl, dessen Ausführung ein Makefile für C-Compiler generiert). Diese leere xs-Datei gilt es mit dem Mapping der Schnittstellen zu füllen.

Eine xs-Datei ähnelt einer C-Header-Datei: Sie deklariert die zur Verfügung gestellten externen Funktionen. Neben der reinen Schnittstellendefinition kann der Aufruf im Detail festgelegt werden: Der Weg von den übergebenen Perl-Argumenten bis zum Aufruf der Funktion und danach zur Rückgabe von Perl-Daten an den Aufrufer. Eine xs-Datei kann neben xs-Sprachelementen auch C-Code enthalten.

Zur Illustration folgt hier eine einfache xs-Deklaration: Die Definition der Funktion sidtime zur Berechnung der Sternzeit. Sie hat nur ein Argument vom Typ double - das Julianische Datum - und gibt genau einen Wert zurück: Die Sternzeit.

double
sidtime(tjd_ut)
  double tjd_ut
CODE:
  RETVAL = swe_sidtime(tjd_ut);
OUTPUT:
  RETVAL      
Wenn man eine xs-Definition wie diese mit dem dafür vorgesehenen sogenannten Makefile verarbeitet - zum Beispiel unter Verwendung des Programms nmake, das mit dem Microsoft Visual C++ Compiler ausgeliefert wird - so wird daraus folgender C-Code generiert:
XS(XS_SwissEph_sidtime); /* prototype to pass -Wmissing-prototypes */
XS(XS_SwissEph_sidtime)
{
    dXSARGS;
    if (items != 1)
	Perl_croak(aTHX_ "Usage: SwissEph::sidtime(tjd_ut)");
    {
	double	tjd_ut = (double)SvNV(ST(0));
	double	RETVAL;
	dXSTARG;
#line 76 "SwissEph.xs"
  RETVAL = swe_sidtime(tjd_ut);
#line 124 "SwissEph.c"
	XSprePUSH; PUSHn((double)RETVAL);
    }
    XSRETURN(1);
}
Ohne dies im Detail verstehen zu müssen, können Sie dennoch nachvollziehen, was hier passiert: Nach einer Prüfung, ob die richtige Anzahl von Aufrufparametern übergeben wurde, wird der im Aufrufstack übergebene Perl-Skalar ST(0) mittels der Funktion SvNV() in einen double gewandelt. Dann erfolgt der Aufruf der externen Funktion, hier swe_sidtime(), und schliesslich wird der reservierte Parameter RETVAL zur Rückgabe des Ergebnisses verwendet. In diesem Fall enthält die Funktion PUSHn() die Logik zur Rückkonvertierung des double-Wertes in einen Perl-Skalar.

Im Endeffekt produziert nmake nicht nur diesen C-Zwischencode, sondern auch eine (kleine) DLL, die als Mittlerin zwischen der externen Bibliothek - hier der swedll32.dll - und der Perl-Laufzeit fungiert.


Zum Anfang

Das Modul SwissEph

Auf Grundlage dieser Technologie habe ich mir ein kleines Perl-Modul namens SwissEph erstellt, mit dem ich auf wichtige Funktionen der Swiss Ephemeris zugreife, nämlich auf alle Funktionen, die ich zur Berechnung eines Horoskops mit Planeten- und Häuserstellungen benötige. Meine Sammlung von xsubs erhebt also nicht den Anspruch, sämtliche Funktionen der Swiss Ephemeris abzubilden. Wenn Sie andere als die von mir realisierten Zugriffe benötigen, müssen Sie daher selbst in die Tasten greifen und das Modul entsprechend erweitern. Hierbei helfen Ihnen vielleicht einige Erläuterungen zu den von mir realisierten Funktionen.

Das Modul SwissEph können Sie wie folgt in Ihre ActivePerl-Installation einbinden:

Dabei müssen Sie ggf. nmake durch das von Ihnen bevorzugte make-Tool ersetzen. Die Befehle generieren aus den XS-Angaben den notwendigen Maschinencode (die Mapping-DLL), führen dann einige Funktionstests aus (zur Zeit sind es 49 Tests, mit denen ich die korrekte Funktionsweise der Subroutinenaufrufe überprüfe) und installieren das Modul schliesslich im allgemeinen Bibliotheksverzeichnis Ihrer Perl-Module. Dabei wird auch die Moduldokumentation als HTML-Dokument generiert, so dass Sie sie im Active Perl User Guide einsehen können.

Auf anderen Plattformen können Sie im Prinzip genauso verfahren. Jedoch werden Sie sich zuerst damit beschäftigen müssen, wie Sie die in der Datei swedll32.h notierten Funktionsdeklarationen auf Ihrer Plattform (für Ihren C-Compiler) als externe C-Bibliotheksfunktionen beschreiben.

Die Modul-Dokumentation SwissEph.html im HTML-Format zeigt einige Verwendungen.


Zum Anfang

Julianisches Datum und Kalenderdatum

Die Umrechnung eines bürgerlichen Kalenderdatums in eine Julianische Tageszahl, wie sie in der Astronomie üblich ist, stellt vergleichsweise geringe Anforderungen an das Mapping. Immerhin kann man sich die Tatsache zunutze machen, dass Perl (wie C) keine festen Schnittstellen vorsieht, sondern in der Schnittstelle auch optionale Parameter zulässt. Idealer Kandidat für einen optionalen Parameter ist das Flag für den Kalenderstil (gregorianisch oder julianisch). Wir wollen es mit einem Vorschlagswert versorgen: Wenn es nicht angegeben wird, gilt ab dem 15.10.1582 der gregorianische, davor der julianische Kalenderstil als Vorschlag. Die beiden folgenden Aufrufe liefern also dasselbe Ergebnis:
use SwissEph;
# Aufruf 1: JD = SwissEph::julday(Jahr, Monat, Tag, Weltzeit, Flag "Gregorianisch");
print SwissEph::julday(1582,1,1,0.,0)."\n";
# Aufruf 2: JD = SwissEph::julday(Jahr, Monat, Tag, Weltzeit);
print SwissEph::julday(1582,1,1,0.)."\n";
Warum liefern diese beiden Aufrufe dasselbe Ergebnis? Weil beim ersten Aufruf explizit der julianische Kalender gewünscht wird (an das Flag "Gregorianisch" wird der Wert 0 übergeben), während beim zweiten Aufruf ohne Kalenderflag anhand des Datums der julianische Kalender bevorzugt wird (denn der 1.1.1582 liegt vor dem 15.10.1582, ab welchem Datum erst der Vorschlagswert auf den gregorianischen Kalender wechselt).

Um in der xs-Syntax einen optionalen Parameter zu kennzeichnen, verwendet man drei Punkte ... am Ende der Deklaration. Im CODE-Block kann man dann C-Code schreiben, um den Parameter gegebenenfalls dem Aufrufstack zu entnehmen oder alternativ mit einem Vorschlagswert zu versorgen.

double
julday(year,month,day,hour,...) 
    int year
    int month
    int day
    double hour
CODE:
  int gregflag;
  if (items > 4) gregflag = (int)SvIV(ST(4));
  else gregflag = (day/370+month/12+year > 1582.87117) ? 1 : 0;
  RETVAL = swe_julday(year,month,day,hour,gregflag);
OUTPUT:
  RETVAL  
Im Codeblock wird die Variable items herangezogen, um zu entscheiden, ob der optionale Parameter übergeben wurde (dann ist items = 5) oder nicht (dann ist items = 4). Das Macro ST(4) liefert eine Referenz auf den fünften Aufrufparemeter. Das Macro SvIV() liefert den Inhalt, auf den diese Referenz zeigt - also den Perl-Skalar - als Integerwert.

Alternativ, beim Aufruf mit nur vier Parametern, wird das gregflag anhand des übergebenen Datums vorgeschlagen. Nun kann die externe Bibliotheksfunktion swe_julday der Swiss Ephemeris aufgerufen werden, und zwar immer mit allen fünf Parametern. Das Ergebnis kommt in die reservierte Variable RETVAL, die gemäss OUTPUT-Block an den Aufrufer zurückgegeben wird.

Das xs-Mapping für die umgekehrte Funktion revjul sollte nun nicht mehr für Überraschung sorgen:

SV *
revjul(tjd,...)
  double tjd 		
CODE:
  int year, month, day;
  int gregflag;
  double hour;
  char date[30];
  if (items > 1) gregflag = (int)SvIV(ST(1));
  else gregflag = ( tjd > 2299170.5 ) ? 1 : 0
  swe_revjul( tjd, gregflag, &year, &month, &day, &hour);
  sprintf(date,"%d.%d.%d %-.6f",day,month,year,hour);
  RETVAL = newSVpv(date,strlen(date));
OUTPUT:
  RETVAL  
Neu ist hier lediglich, dass die Rückgabevariable ein String ist, der als neuer Perl-Skalar im CODE-Block generiert wird - hierzu dient die Funktion newSVpv(const char*, STRLEN). Da Perl so leichtfüssig auf der Stringebene operiert, erschien es mir hier angemessen, das gesamte Datum mitsamt Zeit in einen String zu schreiben und diesen zurückzugeben. Die Alternative wäre ein Array mit vier Elementen gewesen, eines davon ein double. Das hätte den Aufruf dieser einfachen Funktion unnötig verkompliziert.
Zum Anfang

Berechnung der Planeten und Häuser

Für die Berechnung der Koordinaten eines Planeten zu einem gegeben Datum stellt die Swiss Ephemeris die Funktionen swe_calc() und swe_calc_ut() zur Verfügung. Letztere arbeitet mit Weltzeit, erstere mit Ephemeridenzeit. Für astrologische Anwendungen ist die Arbeit mit Weltzeit bequemer, denn die Umrechnung der bürgerlichen in die gleichförmig verlaufende Ephemeridenzeit erfordert den Aufruf einer weiteren Funktion: swe_deltat(). Dennoch ist es manchmal nützlich, über die auf Ephemeridenzeit basierte Funktion swe_calc() zu verfügen.

Während die Signatur von swe_deltat() (ein double-Wert als Input, ein double als Rückgabewert) keine neuen Hürden bereithält, muss man sich bei swe_calc() klarmachen, wie die Übergabe von Perl-Arrays an C-Arrays funktioniert und wie man mit einer Funktion vorgeht, die nicht nur einen impliziten Rückgabewert hat, sondern auch in der Parameterliste Rückgabewerte aufweist (hier neben dem eigentlichen Rückgabewert int iret den Array xx[] mit den Planetenpositionen und den char *serr mit der allfälligen Fehler- oder Informationsmeldung).

int
calc( tjd_et, ipl, iflag, xx, serr ) 
  double tjd_et
  int ipl
  int iflag
  SV* xx  
  SV* serr
CODE:  
  int i, iret;
  double __xx[6];
  char _serr[255];
  AV * _xx;
  _xx = (AV*) SvRV(xx);
  if (_xx != NULL) {
    iret = swe_calc( tjd_et, ipl, iflag, __xx, _serr);
    for (i=0;i<6;i++) av_push(_xx,(newSVnv(__xx[i])));
    sv_setpv(serr, _serr); 
    }
  else {
    iret = -1;
    sv_setpv(serr,"Bitte gültige Arrayreferenz in Argument 4 übergeben");    
    }  
  RETVAL = iret;
OUTPUT:
  RETVAL
  xx
  serr
Dem Code entnehmen Sie die Antworten auf die oben gestellten Fragen:
  1. Mehr als ein Rückgabewert: Alle Parameter, in denen Werte an Perl zurückgegeben werden, müssen im OUTPUT-Block aufgeführt werden. Für Parameter, an deren Inhalt nach Ausführung man interessiert ist, sieht man am besten eine Übergabe der Referenz auf ein initiales Datenobjekt vor. Referenzen sind immer Skalare, da sie ja einen Zeigerwert enthalten. Aus diesem Grund sind hier sowohl der Ergebnisarray xx wie auch der Fehlerstring serr in der Schnittstelle als SV* deklariert. SV bedeutet einen Perl-Skalar. Die Übergabe von Perl-Daten an C erfolgt immer per Referenz - daher SV*.
  2. Übergabe von Perl-Arrays: Der Perl-Skalar xx enthält die Referenz auf ein Perl-Array (AV). Im CODE-Block wird dieser Array in die Variable _xx derefenziert. Mit dem Perl-Array lassen sich nun in C die gängigen Operationen durchführen, die man auch in Perl mit Arrays macht: Zum Beispiel lässt sich ein aus einem double mit der Funktion newSVnv() erzeugter Perl-Skalar mittels av_push() an das Ende des Arrays anhängen - wie wir es oben mit dem Ergebnis-Array __xx[] (einem gewöhnlichen C-Array von double-Werten) der Funktion swe_calc() der Swiss Ephemeris machen.

Ein Beispielaufruf gestaltet sich folgendermassen:

my $xx = [];
my $serr = "";
my $iret = 0;
$iret = SwissEph::calc( 2451544.5, 0, 256, $xx, $serr);
Wichtig ist die Initialisierung des Skalars $xx mit der Referenz auf einen Perl-Array (deshalb wird mit $xx und [] gearbeitet, nicht mit @xx und ()).

Nachdem es nun klar ist, wie Arrays an Perl übergeben werden, ist es leicht, auch die Funktion swe_houses() der Swiss Ephemeris auf eine Perl-Subroutine abzubilden. Da das xs hier nichts Neues bietet, zeige ich nur einen Beispielaufruf:

# Häuserspitzen
my ($iret,@cusps,@ascmc);
$iret = SwissEph::houses( 2451544.5, 52., 7., 0, \@cusps, \@ascmc);
Nach dem Aufruf enthält der Array @cusps die Häuserspitzen, @ascmc die besonderen trigonometrischen Punkte wie Vertex, Ko-Aszendent, äquatorialer Aszendent, wie in der Dokumentation swephprog der Swiss Ephemeris beschrieben.
Zum Anfang

Listen von Planetenständen

Bei der Berechnung von Sekundärdirektionen, Transiten und Ephemeriden steht man vor der Aufgabe, Planetenpositionen in Tagesschritten zu berechnen. Es wäre nun möglich, die Schleife über die Tage in Perl hinzuschreiben und tageweise die Funktion SwissEph::calc aufzurufen. Das ist ein ineffizientes Hin und Her zwischen externem und Perl-eigenem Code. Besser schien es mir, auch die Schleife in C zu programmieren, so dass nur ein einziger externer Aufruf durchgeführt werden muss. Hierfür bietet sich wiederum der CODE-Block an.

Ich will zunächst die Aufrufsyntax beschreiben. Man gibt das Startdatum als Julianisches Datum, dann die Anzahl der zu berechnenden Tage und schliesslich einen optionalen Array mit den Nummern der Planeten, die berechnet werden sollen. Der Vorschlagswert für letzteren ist der Array 0..9. Das Ergebnis ist die Liste der Positionen je Tag und Planet, gruppiert als Array von Arrays. Hier ein Beispielaufruf:

my $list;
$list = SwissEph::get_long_list(2451544.50,5,[0,1,2]);
Es sollen also die Positionen von Sonne, Mond und Merkur für fünf aufeinanderfolgende Tage ab dem Julianischen Datum 2451544.50 berechnet werden. Die Variable $list zeigt nach dem Aufruf auf einen Array der folgenden Form:
[
          [
            '279.859214548742',
            '280.878647525955',
            '281.898168142991',
            '282.917754628557',
            '283.93738357459'
          ],
          [
            '217.293284118158',
            '229.31715475649',
            '241.221851863712',
            '253.060392323715',
            '264.877744829482'
          ],
          [
            '271.111806759922',
            '272.668068546155',
            '274.229693578235',
            '275.796854264239',
            '277.369741944308'
          ]
]

Das Besondere an dieser Funktion ist, dass nun Referenzen auf neue Perl-Arrays im C-Code erzeugt werden müssen. Dies geschieht sehr einfach mit der Funktion newAV(). Alle anderen Konstrukte sind bereits in vorangehenden Beispielen vorgekommen.

AV * 
get_long_list(jd_ut,n,...)
  double jd_ut
  int n
CODE: 
  SV* slon;
  SV* srlon_av;
  SV* sipl;
  SV** sripl;
  AV *ipl_av, *lon_av;
  double xx[6],xjd;
  int i=0,j,iflag = 0,ip=0,ipl_len,iret;
  char serr[255];
  RETVAL = newAV();
// Den neuen Array sterblich machen (GC erfasst keine Return Values)
  sv_2mortal((SV*)RETVAL);
  if( items > 2 ) ipl_av = (AV*)SvRV(ST(2));    
  else {
    ipl_av = newAV();
    for (i=0;i<10;i++) av_push(ipl_av,(newSViv(i)));
    }
  ipl_len = av_len( ipl_av );
  for (i=0;i<=ipl_len;i++) {
    sripl = av_fetch(ipl_av, i, 0);
    if (sripl == NULL) break;
    sipl = *sripl;
    ip = (int)(SvIV(sipl));
    lon_av = newAV();
    sv_2mortal((SV*)lon_av);  
    for (j=0;j<n;j++) {
      xjd = jd_ut + (double)j;
      iret = swe_calc_ut( xjd, ip, iflag, xx, serr);
      slon = newSVnv(xx[0]);
      av_push(lon_av,slon);
      }
      srlon_av = newRV_inc((SV*) lon_av);
      av_push((AV *)RETVAL, srlon_av);
    }
OUTPUT:          
RETVAL

Zum Anfang

Das Astrologische Austauschformat (AAF)

Das von Martin Garms entworfene Astrologische Austauschformat AAF ist ein gängiges textbasiertes Format für Horoskopdaten. Die Datenbank des Deutschen Astrologenverbandes (DAV) liegt in diesem Format vor. Darüberhinaus bieten einige deutschsprachige Astrologieprogramme Import- und Exportfunktionen für dieses Format. Ich will das Format hier nicht in den Details dokumentieren – hierzu gibt es die Webseiten der Sternwerkstatt. Ein Beispieldatensatz aus der DAV-Datenbank möge einen Eindruck davon geben, wie die Daten im Astrologischen Austauschformat strukturiert sind:
#A93:Aadland,Beverley,f,16.9.1942g,11h45,Los Angeles -CA-,USA
#B93:*,34n04,118w15,8hw,w
#ZNAM:PST/W
#ID:a1
#ATTRIB:DAV,m=FS,bem=704,src=RO,via=ROD&79
#LPOS:So23vi16,Mo11sg03,AC26sc36,MC6vi19
Der mit dem Präfix #A93 eingeleitete Datenblock spielt eine Sonderrolle: Er steht am Beginn eines Datensatzes und darf innerhalb desselben nicht noch einmal vorkommen.

Wie erwähnt, ist Perl ideal geeignet, um Information aus Texten zu extrahieren, die nach bestimmten Regeln organisiert sind. Um dies zu verdeutlichen, folgt hier ein vollständiges Perl-Programm, das aus einer gegebenen Datei mit AAF-Daten je Datensatz das Kalenderdatum extrahiert und ausgibt. Beachten Sie, mit wie wenigen Zeilen Programmcode man eine Datei öffnen, auswerten und die gewünschten Daten ausgeben kann. Eine sehr schöne Sache an Perl ist, dass Sie nicht viel sagen müssen, um das zu sagen, was Sie sagen wollen, formuliert es Larry Wall.

my ($FH,@a);
open(FH, "dav.aaf") 
  or die "Kann Datei dav.aaf nicht öffnen: $!";   

$/ = "#A93:";   # Satztrenner definieren
<FH>;           # "Ersten Satz" ignorieren, da er keiner ist
while ( <FH> ) {  
  @a    =  split /,\s*/, $_, 5;
  print "$a[3]\n";
  }
Auf die DAV-Datenbank angewandt, erhält man eine Programmausgabe, die wie folgt beginnt:
16.9.1942g
12.5.1914g
4.1.1933g
21.6.1921g
5.2.1934g
...

Zum Anfang

Ein Anwendungsbeispiel

Wir wollen das obige Progrämmchen nun noch um einige Zeilen anreichern, in denen wir die Swiss Ephemeris aufrufen. Hierzu denken wir uns die Aufgabe aus, für jedes der rund 29.000 Horoskope der DAV-Datenbank die Neptun-Position im Zeichen zu berechnen und danach eine Statistik auszugeben, in wie vielen Fällen Neptun in welchem Tierkreiszeichen stand. Da der Neptun so langsam läuft, genügt es uns, die Neptunposition zum Kalenderdatum zu verwenden. Wir wollen also der Einfachheit halber die Geburtszeit ignorieren und die Position stets für 12 Uhr Weltzeit berechnen.

Hier ist der Code. Wir verwenden die Funktion get_planet_long des SwissEph-Moduls, weil sie ein bürgerliches Kalenderdatum als Input entgegennimmt.

use SwissEph qw(:Planets get_planet_long);

# Beispielprogramm. Liest AAF-Horoskope ein und gibt die Verteilung 
# der Neptun-Stände in den Tierkreiszeichen aus.

my @sign = qw(Ari Tau Gem Can Leo Vir Lib Sco Sag Cap Aqu Pis);
my @neptun = (0) x 12;

my ($FH,@a,$l,$total);
open(FH, "dav.aaf") 
  or die "Kann Datei dav.aaf nicht öffnen: $!";   

$/ = "#A93:";   # Satztrenner definieren
<FH>;           # "Ersten Satz" ignorieren, da er keiner ist
$total = 0;
while ( <FH> ) {  
  $total++;
  @a    =  split /,\s*/, $_, 5;
  $a[3] =~ /(\d+)\.(\d+)\.(-?\d+)([jg]?)/;
  $l = get_planet_long( Neptune, $3,$2,$1,12.,$4 eq 'j'? 0 : 1 );
  $neptun[int($l/30)]++;
  }

printf "%s : %5d\n", ($sign[$_], $neptun[$_]) for 0..11;
print "\nSum : $total\n";
Mit der Anweisung use SwissEph wird angekündigt, dass das Programm auf die Zusatzfunktionen der Swiss Ephemeris zugreift. Mit dem Zusatz qw(:Planets get_planet_long) werden die Planetensymbole und die Funktion get_planet_long() in den Namensraum importiert, so dass man beim Aufruf nicht das Präfix SwissEph:: dem Namen voranstellen muss. Das Einlesen der DAV-Datenbank geschieht wie oben. Mit einem regulären Ausdruck werden die Bestandteile des Kalenderdatums separiert, um sie in der folgenden Zeile der Funktion get_planet_long vorzulegen. Das Ergebnis, der ekliptikale Längenwert $l, wird durch den Ausdruck int($l/30) auf die Tierkreiszeichenposition reduziert, die als Index eines Arrays von Zählern verwendet wird. Das Programm erzeugt die folgende Ausgabe:
Ari :  2715
Tau :  3547
Gem :  3831
Can :  4014
Leo :  4210
Vir :  2773
Lib :  1652
Sco :   853
Sag :   919
Cap :  1182
Aqu :  1515
Pis :  1814

Sum : 29025

Zum Anfang

Anwendungen des Moduls

Mit dem Modul SwissEph – bedarfsweise um weitere eigene Funktionen ergänzt – können Sie die Swiss Ephemeris überall dort einsetzen, wo Sie Perl sinnvoll verwenden:
Zum Anfang Zurück zur Homepage