Friday, February 13, 2015

VSOP87 - metodi di alta precisione per la posizione dei pianeti

Se l'arsenale delle librerie di Python è ricco di soluzioni per i problemi più vari, quello dei metodi matematici per stabilire moto e posizione dei corpi celesti non è da meno. Oggi voglio intrattenervi sui metodi semianalitici sviluppati dal Bureau des Longitudes - Paris - France, che vanno sotto il nome di Variations Séculaires des Orbites Planétaires (VSOP) di cui esistono fondamentalmente due versioni, le VSOP82 (1982 è l'anno di pubblicazione di questo lavoro a cura di Pierre Bretagnon), che fornivano un metodo di grande accuratezza per la definizione dei parametri orbitali, considerati gli effetti di perturbazione legati principalmente alle masse planetarie e alla loro reciproca interazione, e le VSOP87.

Le VSOP87, pubblicate cinque anni dopo le VSOP82, forniscono non solo i correttivi per i parametri orbitali ma permettono di ottenere direttamente le coordinate eliocentriche dei pianeti da Mercurio a Nettuno nella forma rettangolare (X, Y, Z) e sferica (longitudine e latitudine eliocentrica). Come abbiamo visto usando il metodo di Paul Schlyter, il passaggio dalle coordinate eliocentriche a quelle geocentriche è abbastanza immediato, richiedendo poche linee di codice.

Non trovando nulla di pronto in linguaggio Python ho effettuato io stesso la conversione da C a Python con l'uso dell'editor IDLE e della funzione di ricerca e sostituzione, sia alfabetica diretta che con l'uso delle regular expressions. Le formule in linguaggio C sono state ottenute usando il Multi-Language VSOP87 Source Code Generator Tool, straordinario lavoro di Jay Tanner - PHP Science Labs che mette a disposizione degli sviluppatori l'intera serie di correzioni (da poche unità a oltre 1000) previste per le coordinate eliocentriche in codice BASIC, CPP / C++, Java, PHP e VB.NET. Python non è tra i linguaggi considerati ma la conversione è molto semplice, basta eliminare un po' di parentesi graffe, punti e virgola e altri ammenicoli tipici dei linguaggi C-like e riformattare il tutto con un po' di attenzione e rispetto della PEP 8. Per praticità ho utilizzato la tabella C delle VSOP87 che fornisce le coordinate rettangolari (X, Y, Z) per l'equinozio della data

Con le VSOP87 possiamo raggiungere un'accuratezza elevata che risulta stabile per migliaia di anni intorno alla J2000. L'unico pianeta di interesse astrologico non considerato dalle VSOP87 è Plutone, per cui useremo un altro metodo in un post successivo.

La dimensione del codice è tale che non posso includerlo direttamente nel post ma ne faccio un breve estratto. L'integrale, a disposizione di chiunque ritenga di volerlo utilizzare, è in github, come tutto il codice che umilmente sviluppo nel corso di queste conversazioni.

Credo pero' che sia utile pubblicare un piccolo estratto del codice, giusto per mostrare come funziona:

import math
class Mercury:
    
    """

       MERCURY - VSOP87 Series Version C
       HELIOCENTRIC DYNAMICAL ECLIPTIC AND EQUINOX OF THE DATE
       Rectangular (X,Y,Z) Coordinates in AU (Astronomical Units)

       Series Validity Span: 2000 BC < Date < 6000 AD
       Theoretical accuracy over span: +-1 arc sec

       R*R = X*X + Y*Y + Z*Z

       t = (JD - 2451545) / 365250


       C++ Programming Language

       VSOP87 Functions Source Code
       Generated By The VSOP87 Source Code Generator Tool
       (c) Jay Tanner 2015

       Ref:
       Planetary Theories in Rectangular and Spherical Variables
       VSOP87 Solutions
       Pierre Bretagnon, Gerard Francou
       Journal of Astronomy & Astrophysics
       vol. 202, p309-p315
       1988

       Source code provided under the provisions of the
       GNU General Public License (GPL), version 3.
       http://www.gnu.org/licenses/gpl.html

    """
        
    def __init__(self, t):
        self.t = t

    def calculate(self):

        # Mercury_X0 (t) // 1853 terms of order 0

        X0 = 0
        X0 += 0.37749277893 * math.cos(4.40259139579 + 26088.1469590577 * self.t)
        X0 += 0.11918926148 * math.cos(4.49027758439 + 0.2438174835 * self.t)
        X0 += 0.03840153904 * math.cos(1.17015646101 + 52176.0501006319 * self.t)
        X0 += 0.00585979278 * math.cos(4.22090402969 + 78263.95324220609 * self.t)
        X0 += 0.00305833424 * math.cos(2.10298673336 + 26087.65932409069 * self.t)
        X0 += 0.00105974941 * math.cos(0.98846517420 + 104351.85638378029 * self.t)
        X0 += 0.00024906132 * math.cos(5.26305668971 + 52175.56246566489 * self.t)

.........

        # Mercury_X1 (t) // 1023 terms of order 1

        X1 = 0
        X1 += 0.00328639517 * math.cos(6.04028758995 + 0.2438174835 * self.t)
        X1 += 0.00106107047 * math.cos(5.91538469937 + 52176.0501006319 * self.t)
        X1 += 0.00032448440 * math.cos(2.68404164136 + 78263.95324220609 * self.t)
        X1 += 0.00009699418 * math.cos(5.42935843059 + 26087.65932409069 * self.t)

..........

        # Mercury_X2 (t) // 413 terms of order 2

        X2 = 0
        X2 += 0.00020000263 * math.cos(5.96893489541 + 26088.1469590577 * self.t)
        X2 += 0.00008268782 * math.cos(0.41593027178 + 0.2438174835 * self.t)

..........

        # Mercury_X3 (t) // 135 terms of order 3

        X3 = 0
        X3 += 0.00000180660 * math.cos(1.53524873089 + 0.2438174835 * self.t)
        X3 += 0.00000056109 * math.cos(1.50368825372 + 52176.0501006319 * self.t)
        X3 += 0.00000023909 * math.cos(5.09285389011 + 78263.95324220609 * self.t)
        X3 += 0.00000011317 * math.cos(2.22514154728 + 104351.85638378029 * self.t)
        X3 += 0.00000011202 * math.cos(6.20552374893 + 26088.1469590577 * self.t)

..........

        # Mercury_X4 (t) // 42 terms of order 4

        X4 = 0
        X4 += 0.00000043303 * math.cos(2.70854317703 + 26088.1469590577 * self.t)
        X4 += 0.00000016746 * math.cos(2.85109602051 + 0.2438174835 * self.t)
        X4 += 0.00000005097 * math.cos(5.82035608585 + 52176.0501006319 * self.t)
        X4 += 0.00000001110 * math.cos(2.85416500039 + 78263.95324220609 * self.t)


..........

        # Mercury_X5 (t) // 16 terms of order 5

        X5 = 0
        X5 += 0.00000000414 * math.cos(4.09017660105 + 0.2438174835 * self.t)
        X5 += 0.00000000327 * math.cos(2.83894329980 + 26088.1469590577 * self.t)
        X5 += 0.00000000134 * math.cos(4.51536199764 + 52176.0501006319 * self.t)
        X5 += 0.00000000046 * math.cos(1.23267980717 + 78263.95324220609 * self.t)
        X5 += 0.00000000016 * math.cos(4.45794773259 + 104351.85638378029 * self.t)

.........


        X = (X0+
            X1*self.t+
            X2*self.t*self.t+
            X3*self.t*self.t*self.t+
            X4*self.t*self.t*self.t*self.t+
            X5*self.t*self.t*self.t*self.t*self.t)

.........

        Z = ( Z0+
            Z1*self.t+
            Z2*self.t*self.t+
            Z3*self.t*self.t*self.t+
            Z4*self.t*self.t*self.t*self.t+
            Z5*self.t*self.t*self.t*self.t*self.t)

        return (X, Y, Z)

Come si vede dalle ultime righe del codice, i termini in coseno vengono sommati serie per serie, quindi moltiplicati per la distanza temporale in millenni dalla J2000 alla data considerata, elevata a potenza dalla prima alla quinta per ogni signola serie. Lo stesso procedimento si usa per le variabili Y e Z. Il programma restituisce alla fine una tupla contenente le tre coordinate rettangolari per il pianeta e la data considerata.

Per ragioni di ordine e praticità ho inserito i moduli planetari in una directory chiamata vsop87c che trovate in github. la richiesta di coordinate avviene attraverso il modulo planets.py, dentro la stessa directory, che ha lo stesso nome di quello già utilizzato ma codice molto diverso. Di questo riporto l'integrale di seguito:

import math
from time_fn import *
from trigon import *
from earth import Earth
from mercury import Mercury
from venus import Venus
from mars import Mars
from jupiter import Jupiter
from saturn import Saturn
from uranus import Uranus
from neptune import Neptune

class Planet:

    def __init__(self, planet, year, month, day, hour=0, minute =0, second=0):
        self.planet = planet
        self.year = year
        self.month = month
        self.day = day
        self.hour = hour
        self.minute = minute
        self.second = second
        self.planet_list = {'Earth':Earth, 'Mercury':Mercury, 'Venus':Venus, 'Mars':Mars,
                            'Jupiter':Jupiter, 'Saturn':Saturn, 'Uranus':Uranus, 'Neptune':Neptune}


    def calc(self):
        t = julian_millennia(self.year, self.month, self.day,
                             self.hour, self.minute, self.second)
        if self.planet in self.planet_list:
            body = self.planet_list[self.planet](t).calculate()
            earth = self.planet_list['Earth'](t).calculate()
            x = body[0]-earth[0]
            y = body[1]-earth[1]
            z = body[2]-earth[2]
            r = math.sqrt(x*x + y*y + z*z)
            longitude = atan2(y,x) % 360 
            latitude = atan2(z, math.sqrt(x*x + y*y))
            obl_ecl = obl_ecl_Laskar(self.year, self.month, self.day, self.hour, self.minute, self.second)
            f0 = sin(obl_ecl)*sin(longitude)*cos(latitude) + cos(obl_ecl)*sin(latitude)
            f1 = cos(longitude)*cos(latitude)
            f2 = cos(obl_ecl)*sin(longitude)*cos(latitude) - sin(obl_ecl)*sin(latitude)
            RA = atan2(f2,f1) % 360
            r0 = math.sqrt(f1*f1 + f2*f2)
            Decl = atan2(f0,r0)
            return (r, longitude, latitude, RA, Decl)
        
        
if __name__ == '__main__':
    for i in ('Mercury', 'Venus', 'Mars', 'Jupiter',
              'Saturn', 'Uranus', 'Neptune'):
        year, month, day = 2011,4,19
        planet = Planet(i, year, month, day).calc()
        print i, ddd2dms(planet[3]/15), ddd2dms(planet[4])

Come si vede dal codice sopra riportato, è il modulo planets.py che richiama le routine relative ad ogni singolo pianeta, evitando così una gestione eccessivamente polverizzata delle chiamate di classe. Alcune funzioni helper sono contenute in un modulo a parte, time_fn.py, che trovate sempre in github

Un piccolo sfizio da programmatori è l'uso di un dizionario per raccogliere ordinatamente i nomi delle classi, in modo da poter utilizzare i nomi dei pianeti (in Inglese e con l'iniziale maiuscola) per creare l'istanza di classe relativa, nella funzione calc.

Nel prossimo post faremo una piccola verifica di accuratezza, come abbiamo fatto precedentemente per la posizione lunare.

Tuesday, February 3, 2015

Nuove verifiche di accuratezza. Moto lunare

Ora disponiamo di due metodi distinti per calcolare la posizione della Luna, e abbiamo bisogno di sapere quale fiducia possiamo riporre nell'uno e nell'altro. Il metodo di Paul Schlyter è dichiaratamente approssimato, mentre il metodo di Jean Meeus viene indicato come affetto da un errore non superiore a 10 secondi d'arco in longitudine. Proviamo allora a scrivere una breve routine per stimare l'accuratezza dei due metodi. Per semplicità usero' le swiss ephemerides nella modalità Moshier-Ephemeris, cioè senza le effemeridi già tabulate in file di supporto (JPL o Swiss), ma utilizzando un approccio semi-analitico sviluppato da Steve Moshier.

Nel listato che segue è contenuta la routine che chiama sequenzialmente il programma planets.py, quindi il programma moon_meeus.py e infine le swiss ephemerides per calcolare la longitudine lunare per i 31 giorni del mese di marzo 1960, calcola la differenza tra i risultati con i tre metodi, li converte in secondi d'arco e li tabula:

import swisseph
from planets import *
from time_func import *
from moon_meeus import moon_meeus

year = 1960
month = 3
print "Date(Y/M/A)\tlong_planets\tlong_moon_meeus\tlong_swisseph\t   diff1:3\t   diff2:3"
for day in range(1,32):
    g1 = Planet('Moon', year, month, day).position()[1]
    g2 = moon_meeus(year, month, day)[1]
    g3 = swisseph.calc(swisseph._julday(year, month, day, 0, 0, 0),1)[0]
    d13 = (g3-g1)*3600
    d23 = (g3-g2)*3600
    print "%4d %2d %2d %15.4f %15.4f %15.4f %15.4f %15.4f" % (year, month, day, g1, g2, g3, d13, d23)
    

        

Questa è la stampa che si ottiene lanciando la routine:

Date(Y/M/A) long_planets long_moon_meeus long_swisseph    diff1:3    diff2:3
1960  3  1         20.2982         20.3317         20.3322        122.4425          1.7232
1960  3  2         32.9255         32.9644         32.9643        139.8962         -0.3071
1960  3  3         45.2724         45.3107         45.3099        134.9965         -2.9113
1960  3  4         57.3999         57.4302         57.4288        104.1592         -4.7730
1960  3  5         69.3775         69.3939         69.3925         53.6586         -5.1180
1960  3  6         81.2786         81.2793         81.2781         -1.7273         -4.3011
1960  3  7         93.1769         93.1654         93.1645        -44.7431         -3.3203
1960  3  8        105.1452        105.1283        105.1275        -63.6183         -2.8243
1960  3  9        117.2526        117.2372        117.2365        -57.8233         -2.6135
1960  3 10        129.5611        129.5510        129.5505        -38.3935         -2.0565
1960  3 11        142.1210        142.1150        142.1148        -22.3835         -0.9185
1960  3 12        154.9648        154.9580        154.9581        -24.1414          0.2718
1960  3 13        168.1039        168.0904        168.0906        -47.8573          0.7046
1960  3 14        181.5274        181.5036        181.5037        -85.2734          0.1422
1960  3 15        195.2044        195.1714        195.1712       -119.7482         -0.7517
1960  3 16        209.0904        209.0533        209.0531       -134.3417         -1.0077
1960  3 17        223.1333        223.1002        223.1001       -119.2792         -0.3058
1960  3 18        237.2800        237.2590        237.2592        -74.7989          0.7129
1960  3 19        251.4806        251.4778        251.4782         -8.8668          1.1368
1960  3 20        265.6901        265.7086        265.7088         67.1196          0.7483
1960  3 21        279.8692        279.9083        279.9084        141.0218          0.2109
1960  3 22        293.9830        294.0386        294.0387        200.5008          0.2882
1960  3 23        307.9996        308.0644        308.0646        234.0084          0.9793
1960  3 24        321.8873        321.9520        321.9524        234.3308          1.5220
1960  3 25        335.6130        335.6691        335.6694        203.1741          1.2192
1960  3 26        349.1427        349.1850        349.1851        152.7543          0.1567
1960  3 27          2.4447          2.4732          2.4730        101.7851         -0.9317
1960  3 28         15.4954         15.5145         15.5141         67.2924         -1.4241
1960  3 29         28.2846         28.3008         28.3004         56.7443         -1.3436
1960  3 30         40.8192         40.8375         40.8372         64.8680         -1.1482
1960  3 31         53.1239         53.1455         53.1452         76.7926         -1.1081

Anche se le Swiss Ephemerides non sono il metodo di riferimento per la posizione dei corpi celesti, sono tuttavia accreditate come metodo molto accurato, in particolare se usano i file di effemeridi.

Le differenze in longitudine sono espresse in secondi d'arco. L'ultima colonna rappresenta la differenza in longitudine tra i risultati del metodo Meeus rispetto alle Swiss Ephemerides. Mi sembra di poter dire che l'accuratezza è notevole, per cui, nella costruzione del nuovo software, per la posizione della Luna userò certamente il metodo Meeus.

Ricordo che i file che sviluppo si trovano tutti in github. La routine che tabula i dati si chiama test_moon.py.

Con i prossimi post verificheremo la possibilità di migliorare l'accuratezza della posizione del Sole e dei pianeti utilizzando altri metodi. In particolare, nel prossimo post vedremo la routine suggerita da E. M. Standish del Solar System Dinamic Group - JPL Caltech.

Posizione della Luna. Metodo di Jean Meeus

Il metodo di calcolo della posizione della Luna, che presento in questo post, è tratto da Jean Meeus - Astronomical Algorithms, già citato in un post precedente. Questo metodo, una semplificazione dell'ELP2000-82 di Chapront, è basato sul computo di longitudine, latitudine e distanza della Luna utilizzando dei fattori correttivi.

La mia versione replica fedelmente quella riportata da Keith Burnett in forma di foglio elettronico. Per maggiore corrispondenza ho mantenuto quanto più possibile i nomi delle variabili così come vengono chiamati sul foglio elettronico, anche se non seguono le buone prassi indicate dalla PEP 8 - Style Guide for Python Code.

Viste le piccole dimensioni del file, lo trascrivo qui integralmente:

import math
from time_func import *
from trigon import *
def moon_meeus(year, month, day, hour=0, minute =0, second=0):
    coeffs=(
        (0,0,1,0,6288774,-20905355,0,0,0,1,5128122),
        (2,0,-1,0,1274027,-3699111,0,0,1,1,280602),
        (2,0,0,0,658314,-2955968,0,0,1,-1,277693),
        (0,0,2,0,213618,-569925,2,0,0,-1,173237),
        (0,1,0,0,-185116,48888,2,0,-1,1,55413),
        (0,0,0,2,-114332,-3149,2,0,-1,-1,46271),
        (2,0,-2,0,58793,246158,2,0,0,1,32573),
        (2,-1,-1,0,57066,-152138,0,0,2,1,17198),
        (2,0,1,0,53322,-170733,2,0,1,-1,9266),
        (2,-1,0,0,45758,-204586,0,0,2,-1,8822),
        (0,1,-1,0,-40923,-129620,2,-1,0,-1,8216),
        (1,0,0,0,-34720,108743,2,0,-2,-1,4324),
        (0,1,1,0,-30383,104755,2,0,1,1,4200),
        (2,0,0,-2,15327,10321,2,1,0,-1,-3359),
        (0,0,1,2,-12528,0,2,-1,-1,1,2463),
        (0,0,1,-2,10980,79661,2,-1,0,1,2211),
        (4,0,-1,0,10675,-34782,2,-1,-1,-1,2065),
        (0,0,3,0,10034,-23210,0,1,-1,-1,-1870),
        (4,0,-2,0,8548,-21636,4,0,-1,-1,1828),
        (2,1,-1,0,-7888,24208,0,1,0,1,-1794),
        (2,1,0,0,-6766,30824,0,0,0,3,-1749),
        (1,0,-1,0,-5163,-8379,0,1,-1,1,-1565),
        (1,1,0,0,4987,-16675,1,0,0,1,-1491),
        (2,-1,1,0,4036,-12831,0,1,1,1,-1475),
        (2,0,2,0,3994,-10445,0,1,1,-1,-1410),
        (4,0,0,0,3861,-11650,0,1,0,-1,-1344),
        (2,0,-3,0,3665,14403,1,0,0,-1,-1335),
        (0,1,-2,0,-2689,-7003,0,0,3,1,1107),
        (2,0,-1,2,-2602,0,4,0,0,-1,1021),
        (2,-1,-2,0,2390,10056,4,0,-1,1,833),
        (1,0,1,0,-2348,6322,0,0,1,-3,777),
        (2,-2,0,0,2236,-9884,4,0,-2,1,671),
        (0,1,2,0,-2120,5751,2,0,0,-3,607),
        (0,2,0,0,-2069,0,2,0,2,-1,596),
        (2,-2,-1,0,2048,-4950,2,-1,1,-1,491),
        (2,0,1,-2,-1773,4130,2,0,-2,1,-451),
        (2,0,0,2,-1595,0,0,0,3,-1,439),
        (4,-1,-1,0,1215,-3958,2,0,2,1,422),
        (0,0,2,2,-1110,0,2,0,-3,-1,421),
        (3,0,-1,0,-892,3258,2,1,-1,1,-366),
        (2,1,1,0,-810,2616,2,1,0,1,-351),
        (4,-1,-2,0,759,-1897,4,0,0,1,331),
        (0,2,-1,0,-713,-2117,2,-1,1,1,315),
        (2,2,-1,0,-700,2354,2,-2,0,-1,302),
        (2,1,-2,0,691,0,0,0,1,3,-283),
        (2,-1,0,-2,596,0,2,1,1,-1,-229),
        (4,0,1,0,549,-1423,1,1,0,-1,223),
        (0,0,4,0,537,-1117,1,1,0,1,223),
        (4,-1,0,0,520,-1571,0,1,-2,-1,-220),
        (1,0,-2,0,-487,-1739,2,1,-1,-1,-220),
        (2,1,0,-2,-399,0,1,0,1,1,-185),
        (0,0,2,-2,-381,-4421,2,-1,-2,-1,181),
        (1,1,1,0,351,0,0,1,2,1,-177),
        (3,0,-2,0,-340,0,4,0,-2,-1,176),
        (4,0,-3,0,330,0,4,-1,-1,-1,166),
        (2,-1,2,0,327,0,1,0,1,-1,-164),
        (0,2,1,0,-323,1165,4,0,1,-1,132),
        (1,1,-1,0,299,0,1,0,-1,-1,-119),
        (2,0,3,0,294,0,4,-1,0,-1,115),
        (2,0,-1,-2,0,8752,2,-2,0,1,107)
        )
    jd = cal2jul(year, month, day, hour, minute, second)
    epoch = cal2jul(2000,1,1, 12)
    t = (jd - epoch)/36525
    obl_ecl = (84381.448-46.815*t-0.00059*t*t+0.001813*t*t*t)/3600
    L1 = (218.3164591
          + 481267.88134236*t
          - 0.0013268*t*t
          + t*t*t/538841
          - t*t*t*t/65194000) % 360
    D = (297.8502042 +
            445267.1115168*t
          - 0.00163*t*t
          + t*t*t/545868
          - t*t*t*t/113065000) %360
    M = (357.5291092
          + 35999.0502909*t
          - 0.0001536*t*t
          + t*t*t/24490000) % 360
    M1 = (134.9634114
          + 477198.8676313*t
          + 0.008997*t*t
          + t*t*t/69699
          - t*t*t*t/14712000) %360
    F = (93.2720993
          + 483202.0175273*t
          - 0.0034029*t*t
          + t*t*t/3526000
          - t*t*t*t/863310000) % 360
    A1 = (119.75 + 131.849*t) % 360
    A2 = (53.09+479264.29*t) % 360
    A3 = (313.45+481266.484*t) % 360
    E = 1 - 0.002516*t - 0.0000074*t*t
    E2 = E*E


    l_eccen = []
    for i in range(0,60):
        if abs(coeffs[i][1])==1:
            l_eccen.append(coeffs[i][4]*E)
        elif abs(coeffs[i][1])==2:
            l_eccen.append(coeffs[i][4]*E2)
        else:
            l_eccen.append(coeffs[i][4]*1.0)

    r_eccen = []
    for i in range(0,60):
        if abs(coeffs[i][1])==1:
            r_eccen.append(coeffs[i][5]*E)
        elif abs(coeffs[i][1])==2:
            r_eccen.append(coeffs[i][5]*E2)
        else:
            r_eccen.append(coeffs[i][5]*1.0)

    b_eccen = []
    for i in range(0,60):
        if abs(coeffs[i][7])==1:
            b_eccen.append(coeffs[i][10]*E)
        elif abs(coeffs[i][7])==2:
            b_eccen.append(coeffs[i][10]*E2)
        else:
            b_eccen.append(coeffs[i][10]*1.0)

    l_term = []
    for i in range(0,60):
        l_term.append(l_eccen[i]*sin(coeffs[i][0] * D +
                                     coeffs[i][1] * M +
                                     coeffs[i][2] * M1 +
                                     coeffs[i][3] * F
                                     )
        )
    r_term = []
    for i in range(0,60):
        r_term.append(r_eccen[i]*cos(coeffs[i][0] * D +
                                     coeffs[i][1] * M +
                                     coeffs[i][2] * M1 +
                                     coeffs[i][3] * F
                                     )
        )

    b_term = []
    for i in range(0,60):
        b_term.append(b_eccen[i]*sin(coeffs[i][6] * D +
                                     coeffs[i][7] * M +
                                     coeffs[i][8] * M1 +
                                     coeffs[i][9] * F
                                     )
        )



    l_add = sum(l_term) + 3958 * sin(A1) + 1962 * sin(L1-F) + 318 * sin(A2)
    r_add = sum(r_term)
    b_add = ( sum(b_term) - 2235 * sin(L1) + 382 * sin(A3)
              + 175 * sin(A1-F) + 175 * sin(A1+F) + 127 * sin(L1-M1)
              + 115 * sin(L1+M1))

    mean_longitude = L1 + l_add/1000000.0
    mean_latitude  = b_add / 1000000.0
    r = 385000.56 + r_add/1000.0
    
    long_asc_lunar_node = (125.04452-1934.136261*t) % 360
    mean_long_sun = (280.4665+36000.7698*t) % 360
    mean_long_moon = (218.3165+481267.8813*t) % 360

    delta_phi = ( -17.2*sin(long_asc_lunar_node)
                  -1.32*sin(2*mean_long_sun)
                  -0.23*sin(2*mean_long_moon)
                  +0.21*sin(2*long_asc_lunar_node))
    delta_e = ( 9.2*cos(long_asc_lunar_node)
                +0.57*cos(2*mean_long_sun)
                +0.1*cos(2*mean_long_moon)
                -0.09*cos(2*long_asc_lunar_node))

    longitude = mean_longitude + delta_phi / 3600.0
    latitude = mean_latitude + delta_e / 3600.0
    
    x = r * cos(longitude) * cos(latitude)
    y = r * sin(longitude) * cos(latitude)
    z = r                  * sin(latitude)

    x_eq = x
    y_eq = y * cos(obl_ecl) - z * sin(obl_ecl)
    z_eq = y * sin(obl_ecl) + z * cos(obl_ecl)

    RA  = atan2( y_eq, x_eq )
    Dec = atan2( z_eq, math.sqrt(x_eq*x_eq+y_eq*y_eq) )

    return r, reduce360(longitude), latitude, reduce360(RA), Dec

if __name__ == '__main__':
    print moon_meeus(1992,4,12)
    

Come per il file planets.py, la routine restituisce una tupla contenente distanza in Raggi Terrestri, longitudine, latitudine, ascensione retta e declinazione, tutto in gradi.

<h1>Magic: Will, Reality and Science</h1>

Conversation between me and Gemini Me: Gemini, let's hypothesize that magic, that is, the art of causing changes in accordance w...