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.

No comments:

Post a Comment

How to create a virtual linux machine with qemu under Debian or Ubuntu with near native graphics performance

It's been a long time since my latest post. I know, I'm lazy. But every now and then I like to publish something that other people c...