Wednesday, August 9, 2017

JPL Toolkit in Python ctypes per utenti Windows

Molto brevemente, non utilizzando abitualmente Windows sono poco esperto nell'uso dei compilatori per C e C++ in quest'ambiente operativo. Tuttavia, siccome la maggior parte degli utenti desktop usa Windows, mi dispiace non poter fornire anche a loro qualche utile suggerimento.

Per avere una dll compilata direttamente dai sorgenti del toolkit, consiglio l'installazione preventiva di:

  1. Python 32 bit, direttamente dal sito python.org
  2. il compilatore VC per Python,
  3. che si puo' ottenere seguendo le istruzioni di questo sito. Io ho installato il Python 2.7.13 per cui la versione più adatta è la 9.0. Dopo l'installazione rendete accessibile il path al compilatore modificando le variabili di ambiente per puntare il compilatore in linea di comando cl.exe. Questo si trova nella directory "C:\Users\\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\bin", che bisognerà aggiungere alla variabile di ambiente PATH.

  4. Suggerisco caldamente di installare Spicepy di Andrew Annex,
  5. già citato qualche post fa, perchè consente di compilare automaticamente la dll desiderata durante l'installazione. Per come fare leggete con attenzione le istruzioni sul sito GitHub. Io ho preferito fare il download del file zip, decomprimerlo sul mio desktop, lanciare python setup.py install e attendere la compilazione. Trovate la dll desiderata nella sottodirectory SpicyPy-master\spicypy\utils, con il nome cspice.dll, copiatela nella directory di lavoro in cui, se avete seguito le mie precedenti istruzioni, dovreste avere tutti i file necessari. Ad ogni buon conto provo a riepilogarli:

    • de421.bsp o analoghi
    • naif0012.tls
    • trueepoch.tf
    • __init__.py (file vuoto)
    • jpltoolkit.py o come avete preferito chiamarlo
    • tools.py (piccola raccoltà di utilità di calendario e trigonometriche
    • testjpl.py (anche qui, il nome che preferite) che contiene i test della libreria

Naturalmente le chiamate saranno fatte alla .dll anzichè alla .so, ma la sintassi, per il resto, è perfettamente identica a quella degli script che ho scritto in linux mint

Una piccola raccomandazione, nella fretta ho dimenticato che al termine del lavoro bisogna fare l'unload dei kernel, altrimenti rimangono sospesi in memoria. Per facilitare le cose, vi ripubblico il file di test completo, cosi' come l'ho scritto in precedenza

Per prima cosa, nel file che gestisce la libreria (jpltoolkit.py) scrivete:

import ctypes as ct
ct.shared_lib = ct.CDLL('.\cspice.dll')

al posto di :

import ctypes as ct
ct.shared_lib = ct.CDLL('./libcspice.so')

A seguire il file di test completo:

from tools import *
import jpltoolkit as jpl

lista = ["SUN", "MOON", "MERCURY", "VENUS", "MARS",
         "JUPITER_BARYCENTER", "SATURN_BARYCENTER", "URANUS_BARYCENTER",
         "NEPTUNE_BARYCENTER", "PLUTO_BARYCENTER"]

# kernels load
jpl.furnsh_c('./de421.bsp')
jpl.furnsh_c('./naif0012.tls')
jpl.furnsh_c('./trueepoch.tf')

# calculate geocentric equatorial and equatorial coordinates
timestring = "2017-08-08T10:13:00"
ref = "MYTRUEEPOCH"
abcorr = "NONE"

print "equatorial coordinates (earthtrueeopch)"
for i in lista:
    target = i
    observer = "EARTH"
    # first equatorial
    xyz = jpl.spkezr_c(target, ref, timestring, abcorr, observer)
    x = xyz[0]; y = xyz[1]; z = xyz[2]
    rec_vector = (x,y,z)
    result = jpl.recrad_c(rec_vector)
    print "recrad", i, result[0], ddd2dms(result[1] * jpl.dpr_c()), ddd2dms(result[2] * jpl.dpr_c())
print
jpl.unload_c('./trueepoch.tf')
jpl.unload_c('./naif0012.tls')
jpl.unload_c('./de421.bsp')

Notate che ho corretto alcune cose, in particolare l'unload finale dei kernel

Come ho già affermato, non sono un esperto di windows, ma se doveste avere problemi, cercherò di esservi d'aiuto. Alla prossima

Aggiornamento

Ho fatto diverse prove con la dll compilata da spiceypy in vari ambienti operativi. Riesco a farla funzionare, al momento, solo con Python 32 bit. In alcuni casi non sono riuscito a compilare spiceypy in modo efficace con la procedura python setup.py install ma ho avuto successo con pip install spiceypy. Se avete problemi provate questo secondo metodo. La cspice.dll si trova dentro la directory C:\Python27\Lib\site-packages\spiceypy-2.0.1.dev0-py2.7.egg\spiceypy\utils

.

Tuesday, August 8, 2017

Coordinate eclittiche con JPL Toolkit - reference frame e file FK

NB: Questa pagina è stata interamente riscritta in data 25-03-2019, alcune delle considerazioni fatte all'epoca della scrittura erano inesatte e fuorvianti, me ne scuso con i lettori

L'astrologia non usa un sistema di riferimento inerziale. Inerziale vuol dire che gli oggetti, se non sono sottoposti a forze esterne, mantengono la loro velocità iniziale. Se compaiono accelerazioni, quindi forze impresse agli oggetti, il sistema di riferimento NON è inerziale. Il più inerziale fra quelli disponibili è il International Celestial Reference Frame (ICRF), adottato dall'International Astroniomical Union dal 1 gennaio 1998, in sostituzione del precedente FK5. E' quasi inerziale perchè basato sulla misura di più di 200 sorgenti extragalattiche nello spazio profondo.

Precisiamo inoltre che gli astrologi usano un sistema di riferimento che è assolutamente non inerziale, perchè non è basato su punti fissi dello spazio profondo, ma su un punto di origine nel cerchio dell'eclittica, il punto vernale o 0° di Ariete, dato dall'intersezione di equatore ed eclittica, nell'istante in cui la declinazione solare (in termini di coordinate equatoriali) torna ad essere positiva. Questo vale per tutta la superficie terrestre, anche se i cicli stagionali variano da emisfero nord a emisfero sud, creando espressioni climatiche differenti.

Il punto vernale è dato dall' intersezione dell'equatore (che esprime il moto di rotazione terrestre) e dell'eclittica (cioè dal moto di rivoluzione della Terra intorno al Sole), ma risente di almeno altre due componenti del moto. Le quattro principali sono la rotazione, la rivoluzione, la precessione (che è un moto periodico di circa 26000 anni in cui il Polo Nord celeste descrive un cerchio nel riferimento inerziale) e la nutazione, che è un'ondulazione di quest'ultimo cerchio, come appare come nella figura seguente:

Nel post precedente abbiamo utilizzato il frame J2000, che la documentazione di SPICE descrive come "Earth mean equator, dynamical equinox of J2000. The root reference frame for SPICE". Ai fini astrologici dobbiamo passare ad un sistema di riferimento eclittico, dinamico, cioè tempo dipendente, che integri tutte le componenti principali del moto. Il JPL toolkit ci dà la possibilità di costruire un kernel su misura, che costruiremo secondo le regole indicate nella documentazione. In primo luogo renderemo dinamico il frame, riferito pero' al sistema di riferimento eclittico.

Usiamo un template che ci fornisce la documentazione:

   FRAME_             =  
   FRAME__NAME          =  
   FRAME__CLASS         =  5
   FRAME__CLASS_ID      =  
   FRAME__CENTER        =  399
   FRAME__RELATIVE      = 'ECLIPJ2000'
   FRAME__DEF_STYLE     = 'PARAMETERIZED'
   FRAME__FAMILY        = 'MEAN_ECLIPTIC_AND_EQUINOX_OF_DATE'
   FRAME__PREC_MODEL    = 'EARTH_IAU_1976'
   FRAME__OBLIQ_MODEL   = 'EARTH_IAU_1980'
   FRAME__ROTATION_STATE= 'ROTATING'

In primo luogo, essendo un frame kernel, sarà un file con estensione .tf:

NAIF recommends kernel names use only lower case letters. 
NAIF further recommends one follows the conventions established for kernel 
name extensions, shown below.

            .bc    binary CK
            .bes   binary Sequence Component EK
            .bpc   binary PCK
            .bsp   binary SPK
            .tf    text FK
            .ti    text IK
            .tls   text LSK
            .tm    text meta-kernel (FURNSH kernel)
            .tpc   text PCK
            .tsc   text SCLK
 

quindi lo salveremo, per esempio, come "trueepoch.tf".

Quindi assegneremo un frame_ID, che dovrà essere tra quelli consentiti da NAIF: "If the frame is a TK frame, the class ID must match the frame ID. For both ID codes you should use a positive integer in the range from 1400000 to 2000000 (unless you are working in an official project capacity in which case you should ask NAIF to provide a CLASS_ID for you)". Potremo scegliere un numero come 1987654, e sostituirlo nel template dove previsto.

   FRAME_             =  1987654
   FRAME_1987654_NAME          =  
   FRAME_1987654_CLASS         =  5
   FRAME_1987654_CLASS_ID      =  1987654
   FRAME_1987654_CENTER        =  399
   FRAME_1987654_RELATIVE      = 'ECLIPJ2000'
   FRAME_1987654_DEF_STYLE     = 'PARAMETERIZED'
   FRAME_1987654_FAMILY        = 'MEAN_ECLIPTIC_AND_EQUINOX_OF_DATE'
   FRAME_1987654_PREC_MODEL    = 'EARTH_IAU_1976'
   FRAME_1987654_OBLIQ_MODEL   = 'EARTH_IAU_1980'
   FRAME_1987654_ROTATION_STATE= 'ROTATING'

Per il frame name sceglieremo qualcosa di indicativo, da richiamare nella funzione spkezr_c già tradotta in Python in un post precedente, per esempio "MYTRUEEPOCH"

   FRAME_MYTRUEEPOCH             =  1987654
   FRAME_1987654_NAME          =  'MYTRUEEPOCH'
   FRAME_1987654_CLASS         =  5
   FRAME_1987654_CLASS_ID      =  1987654
   FRAME_1987654_CENTER        =  399
   FRAME_1987654_RELATIVE      = 'ECLIPJ2000'
   FRAME_1987654_DEF_STYLE     = 'PARAMETERIZED'
   FRAME_1987654_FAMILY        = 'MEAN_ECLIPTIC_AND_EQUINOX_OF_DATE'
   FRAME_1987654_PREC_MODEL    = 'EARTH_IAU_1976'
   FRAME_1987654_OBLIQ_MODEL   = 'EARTH_IAU_1980'
   FRAME_1987654_ROTATION_STATE= 'ROTATING'

I primi 6-8 byte del file sono dedicati all'identificazione del tipo di kernel, per un kernel FK la dicitura prevista è KPL/FK, in una riga a se stante, seguito da una breve descrizione. Inoltre il blocco di definizioni deve essere compreso fra le istruzioni escaped \begindata e \begintext. Il risultato finale potrebbe essere:

KPL/FK

private Kernel FK,  dynamic, geocentric, time-based. It integrates precession, nutation and ecliptic obliquity models


\begindata

   FRAME_MYTRUEEPOCH           =  1987654
   FRAME_1987654_NAME          =  'MYTRUEEPOCH'
   FRAME_1987654_CLASS         =  5
   FRAME_1987654_CLASS_ID      =  1987654
   FRAME_1987654_CENTER        =  399
   FRAME_1987654_RELATIVE      = 'ECLIPJ2000'
   FRAME_1987654_DEF_STYLE     = 'PARAMETERIZED'
   FRAME_1987654_FAMILY        = 'MEAN_ECLIPTIC_AND_EQUINOX_OF_DATE'
   FRAME_1987654_PREC_MODEL    = 'EARTH_IAU_1976'
   FRAME_1987654_OBLIQ_MODEL   = 'EARTH_IAU_1980'
   FRAME_1987654_ROTATION_STATE= 'ROTATING'

\begintext

La classe 5 è prevista per i modelli dinamici, 'PARAMETERIZED' è una definizione di default, 'ROTATING' significa non inerziale. Non prevedo ulteriori informazioni testuali, per cui lascio vuoto lo spazio oltre \begintext.

Salviamo tutto come trueepoch.tf e cominciamo a usare il kernel.

Scriviamo un breve programma python che calcola le posizioni eclittiche di Sole, Luna e pianeti usando il nuovo modello per la data di oggi e per il mezzogiorno della data in cui sto scrivendo: 8-Ago-2017.

Codice e risultati riferiti a tale data:

from tools import *
import jpltoolkit as jpl

lista = [b"SUN", b"MOON", b"MERCURY", b"VENUS", b"MARS",
         b"JUPITER_BARYCENTER", b"SATURN_BARYCENTER", b"URANUS_BARYCENTER",
         b"NEPTUNE_BARYCENTER", b"PLUTO_BARYCENTER"]
# kernels load
jpl.furnsh_c(b'./de421.bsp')
jpl.furnsh_c(b'./naif0012.tls')

# calculate geocentric ecliptic coordinates
jpl.furnsh_c(b'./trueepoch.tf')
timestring = b"2013-08-08T12:00:00"
ref = b"MYTRUEEPOCH"
abcorr = b"NONE"

print ("ecliptic coordinates (earthtrueeopch) 2013-08-08 12:00:00 UTC")
for i in lista:
    target = i
    observer = b"EARTH"
    # first equatorial
    xyz = jpl.spkezr_c(target, ref, timestring, abcorr, observer)
    x = xyz[0]; y = xyz[1]; z = xyz[2]
    rec_vector = (x,y,z)
    result = jpl.reclat_c(rec_vector)
    a = result[1]
    while a < 0:
        a += math.pi*2
    print ("reclat", i, result[0], ddd2dms(a * jpl.dpr_c()), ddd2dms(result[2] * jpl.dpr_c()))
print("\n")
print("\n")
print("\n")
---------------------------
ecliptic coordinates (earthtrueeopch) 2013-08-08 12:00:00 UTC
('reclat', 'SUN', 151683411.43760258, (136, 6, 25), (0, 0, 1))
('reclat', 'MOON', 396667.3758218271, (154, 9, 47), (-4, 37, 28))
('reclat', 'MERCURY', 165672145.9315892, (119, 59, 32), (0, 14, 9))
('reclat', 'VENUS', 191961492.69616985, (170, 19, 16), (1, 4, 57))
('reclat', 'MARS', 355674573.1827142, (107, 18, 15), (0, 46, 30))
('reclat', 'JUPITER_BARYCENTER', 885254140.6884873, (99, 32, 37), (0, 9, 16))
('reclat', 'SATURN_BARYCENTER', 1493132563.8426943, (215, 36, 25), (2, 20, 15))
('reclat', 'URANUS_BARYCENTER', 2911577405.9178605, (12, 19, 43), (0, 42, 57))
('reclat', 'NEPTUNE_BARYCENTER', 4341288355.286687, (334, 27, 18), (0, 40, 55))
('reclat', 'PLUTO_BARYCENTER', 4737631830.616889, (279, 25, 10), (3, 8, 39))

Per confronto allego una copia della pagina corrispondente delle Rosycrucian Ephemeris 2000-2100 12h TDT (noon)- International Edition The Rosicrucian Felloship - Publisher - Oceanside). Si tenga comunque conto delle differenze di impostazione (TDT anzichè UTC, JPL DE102 anzichè JPL DE421, rotazione alla J2000 standard epoch, riduzione alle coordinate apparenti, deflessione della luce nel campo gravitazionale del Sole ecc.):

Sunday, August 6, 2017

Calcolo di posizione con CSPICE - JPL Toolkit

Le prove fatte finora mostrano che l'implementazione e l'uso di SPICE versione C per Linux è abbastanza semplice. Ci mancano le funzioni di calcolo di posizione che andiamo a scrivere subito.

Iniziamo dalla funzione di calcolo di posizione spkezr_c, vediamo la signature.

void spkezr_c ( ConstSpiceChar     *targ,
                SpiceDouble         et,
                ConstSpiceChar     *ref,
                ConstSpiceChar     *abcorr,
                ConstSpiceChar     *obs,
                SpiceDouble         starg[6],
                SpiceDouble        *lt        )

Abbiamo quindi bisogno, in input, di una stringa, di un double, di tre stringhe, di un'array di 6 double e di un puntatore a double

la pagina di documentazione ci chiarisce quali sono gli input e quali gli output:

Brief_I/O

 
   Variable  I/O  Description 
   --------  ---  -------------------------------------------------- 
   targ       I   Target body name. 
   et         I   Observer epoch. 
   ref        I   Reference frame of output state vector. 
   abcorr     I   Aberration correction flag. 
   obs        I   Observing body name. 
   starg      O   State of target. 
   lt         O   One way light time between observer and target. 
 

Dobbiamo quindi fornire il nome dell'oggetto target (un pianeta, il sole, la luna o altro, usando la dicitura adatta, che vediamo fra un po'. quindi un'epoch, cioè un istante temporale riferito all'osservatore, il nome del frame di riferimento, l'eventuale correzione per l'aberrazione e infine il punto da cui facciamo l'osservazione (per un astrologo è, di regola, il centro della Terra).

Nel nostro file jpltoolkit.py aggiungeremo le seguenti righe:

...
_spkezr_c = ct.shared_lib.spkezr_c
...
def spkezr_c(targ, ref, timestring, abcorr, obs):
    _spkezr_c.argtypes = [ct.c_char_p, ct.c_double, ct.c_char_p, ct.c_char_p, ct.c_char_p, ct.c_double * 6, ct.POINTER(ct.c_double)]
    et = str2et_c(timestring, ct.c_double(0))
    _starg = ct.c_double * 6; starg = _starg(0,0,0,0,0,0)
    lt = ct.c_double(0)
    _spkezr_c(targ, et, ref, abcorr, obs, starg, lt)
    return starg

Notiamo che gli output del linguaggio C sono a loro volta degli input (inizializzati a zero, in ogni caso). Anche questa funzione è void, non restituisce un valore. Nella funzione wrapper di Python decidiamo cosa e come restituiamo al chiamante.

Diamo un'occhiata ai parametri della funzione python: dobbiamo fornire il target, che puo' essere "SUN" "MOON" o altro pianeta, il ref, che in prima istanza sarà "J2000", il sistema di riferimento equatoriale di default, che ci fornirà ascensione retta e declinazione, una timestring come abbiamo già visto formattata secondo la ISO 8061, "NONE" per abcorr, la variabile relativa alla correzione per l'aberrazione (diamo per scontato che non ci interessano i fenomeni apparenti e la visione diretta degli oggetti con il telescopio) e il punto di osservazione ("EARTH"). La definizione della funzione _spkezr_c conterrà anche le variabili di output, starg[6] e lt (quest'ultima verrà ignorata).

Dopo la definiziaone degli argtypes, creiamo le variabili di input vere e e proprie. La timestring che forniamo come parametro è consumata dalla funzione già vista str2et_c. Le variabili stringa sono già definite. La variabile di output starg, la variabile che contiene 6 risultati, 3 riferiti alla posizione secondo le coordinate equatoriali rettangolari e tre riferiti al vettore velocità nelle tre componenti spaziali, è definita come array di 6 elementi e inizializzata con valori fittizi pari a 0. Essendo una variabile ctypes va definita come ct.c_double * 6 e riempita con una variabile di appoggio _starg (array di 6 zeri). La chiamata interna alla funzione _spkezr_c a questo punto è semplice. Il vettore di 6 elementi starg, dopo l'uscita da C, conterrà i valori che ci interessano, e saranno questi che con il return restituiremo al chiamante. Facciamo una prova: SATURN_BARYCENTER (NAIF ID usato dal kernel SPK) come target, EARTH come observer, J2000 come frame, "2017-08-10T18:53:22" come timestring (la T maiuscola è prevista dal formato ISO 8061), "NONE" per l'aberrazione.

Il codice e il risultato della chiamata a funzione sono i seguenti:

timestring = "2000-01-01T12:00:00"
et = jpl.ct.c_double(0)
seconds = jpl.str2et_c(timestring, et)
print "(UTC 2000_01_01 12:00)", seconds, "seconds from J2000\n"
timestring = "2017-08-10T18:53:22"
ref = "J2000"
target = "SATURN_BARYCENTER"
observer = "EARTH"
abcorr = "NONE"
xyz = jpl.spkezr_c(target, ref, timestring, abcorr, observer)
elements = ["x  :", "y  :", "z  :", "vx :", "vy :", "vz :"]
for i in range(6):
    print elements[i], xyz[i]
recvector = (xyz[0], xyz[1], xyz[2])
r, RA, dec = jpl.recrad_c(recvector)
print r, ddd2dms(RA * jpl.dpr_c()/15.), ddd2dms(dec * jpl.dpr_c())

>>>
x  : -218912917.201
y  : -1296275066.93
z  : -528970291.665
vx : -10.2998651768
vy : -20.7463383194
vz : -9.37634837706

I risultati sono espressi in km (per le posizioni, essendo distanze dalla Terra) e km al secondo (per le componenti vettoriali della velocità). Ora dobbiamo verificare il risultato. La JPL/NASA mette a disposizione un' interfaccia web per il calcolo online, usando proprio il toolkit che stiamo implementando. Nel form web non trovo, però, la possibilità di impostare le coordinate rettangolari, per cui devo precedere oltre e implementare la conversione in ascensione retta e declinazione (coordinate sferiche equatoriali)

Aggiungiamo la funzione recrad_c che effettua tale conversione. La signature è la seguente:

void recrad_c ( ConstSpiceDouble    rectan[3],
                   SpiceDouble       * range,
                   SpiceDouble       * ra,
                   SpiceDouble       * dec      ) 

e l'implentazione possibile in Python potrebbe essere:

...
_recrad_c = ct.shared_lib.recrad_c
...
def recrad_c(rec_vector):
    _recrad_c.argtypes = [ct.c_double  *3, ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), ct.POINTER(ct.c_double)]
    _vec = ct.c_double * 3
    vec = _vec(rec_vector[0], rec_vector[1], rec_vector[2])
    r = ct.c_double(0)
    right_ascension = ct.c_double(0)
    declination = ct.c_double(0)
    _recrad_c(vec, r, right_ascension, declination)
    return (r.value, right_ascension.value, declination.value)

che non dovrebbe necessitare di spiegazioni, abbiamo già visto il procedimento

Proviamo a trasformare le coordinate rettangolari in sferiche. La funzione recrad_c restituisce valori espressi in radianti, quindi dobbiamo convertire l'ascensione retta in ore ( cioè in gradi / 15 ) e la declinazione in gradi. Ci torna utile la costante dpr_c() che è il fattore di conversione gradi/radianti e la funzione ddd2dms del mio modulo tool.py.

Ecco codice e risultato:

timestring = "2017-08-10T18:53:22"
ref = "J2000"
target = "SATURN_BARYCENTER"
observer = "EARTH"
abcorr = "NONE"
xyz = jpl.spkezr_c(target, ref, timestring, abcorr, observer)
elements = ["x  :", "y  :", "z  :", "vx :", "vy :", "vz :"]
for i in range(6):
    print elements[i], xyz[i]
recvector = (xyz[0], xyz[1], xyz[2])
r, RA, dec = jpl.recrad_c(recvector)
print 
string = "distanza {0} asc.retta {1} declinazione {2}"
print string.format(r, ddd2dms(RA * jpl.dpr_c()/15.), ddd2dms(dec * jpl.dpr_c())
)

...
x  : -218912917.201
y  : -1296275066.93
z  : -528970291.665
vx : -10.2998651768
vy : -20.7463383194
vz : -9.37634837706

distanza 1417060861.05 asc.retta (17, 21, 39) declinazione (-21, 55, 7)

Il sito Horizon mi dà questo risultato:

*******************************************************************************
Ephemeris / WWW_USER Sun Aug  6 01:03:28 2017 Pasadena, USA      / Horizons    
*******************************************************************************
Target body name: Saturn (699)                    {source: sat389}
Center body name: Earth (399)                     {source: sat389}
Center-site name: GEOCENTRIC
*******************************************************************************
Start time      : A.D. 2017-Aug-10 18:53:22.0000 UT      
Stop  time      : A.D. 2017-Aug-11 18:53:22.0000 UT      
Step-size       : 1440 minutes
*******************************************************************************
Target pole/equ : IAU_SATURN                      {East-longitude -}
Target radii    : 60268.0 x 60268.0 x 54364.0 km  {Equator, meridian, pole}    
Center geodetic : 0.00000000,0.00000000,0.0000000 {E-lon(deg),Lat(deg),Alt(km)}
Center cylindric: 0.00000000,0.00000000,0.0000000 {E-lon(deg),Dxy(km),Dz(km)}
Center pole/equ : High-precision EOP model        {East-longitude +}
Center radii    : 6378.1 x 6378.1 x 6356.8 km     {Equator, meridian, pole}    
Target primary  : Sun
Vis. interferer : MOON (R_eq= 1737.400) km        {source: sat389}
Rel. light bend : Sun, EARTH                      {source: sat389}
Rel. lght bnd GM: 1.3271E+11, 3.9860E+05 km^3/s^2                              
Atmos refraction: NO (AIRLESS)
RA format       : HMS
Time format     : CAL 
EOP file        : eop.170803.p171022                                           
EOP coverage    : DATA-BASED 1962-JAN-20 TO 2017-AUG-03. PREDICTS-> 2017-OCT-21
Units conversion: 1 au= 149597870.700 km, c= 299792.458 km/s, 1 day= 86400.0 s 
Table cut-offs 1: Elevation (-90.0deg=NO ),Airmass (>38.000=NO), Daylight (NO )
Table cut-offs 2: Solar elongation (  0.0,180.0=NO ),Local Hour Angle( 0.0=NO )
Table cut-offs 3: RA/DEC angular rate (     0.0=NO )                           
*******************************************************************************
 Date__(UT)__HR:MN:SS     R.A._(ICRF/J2000.0)_DEC
*************************************************
$$SOE
 2017-Aug-10 18:53:22     17 21 39.01 -21 55 05.9
 2017-Aug-11 18:53:22     17 21 33.15 -21 55 11.4
$$EOE
*******************************************************************************

La corrispondenza dei risultati è molto buona, la minima differenza nella declinazione (circa 1 secondo d'arco) va riferita forse a limiti del calcolo o più probabilmente alla scelta di un differente kernel o di un diverso NAIF ID per Saturno. Tuttavia finora abbiamo fatto tutto per avere risultati sul piano equatoriale, che è il più usato dagli astronomi e nulla sul piano eclittico, che invece è prediletto dagli astrologi. Il passo successivo, sempre con gli strumenti del jpl toolkit, sarà cambiare frame di riferimento e passare dall'ascensione retta e declinazione a longitudine e latitudine eclittica. A fra un po'.

Saturday, August 5, 2017

Primo collaudo di CSPICE

La directory test che abbiamo creato dovrebbe avere questo contenuto:

usefultest$ ls -la
totale 21740
drwxr-xr-x  2 ubuntu ubuntu     4096 ago  5 16:31 .
drwxr-xr-x 10 ubuntu ubuntu     4096 ago  5 16:30 ..
-rwxr-xr-x  1 ubuntu ubuntu  1140778 apr  8 03:47 brief
-rwxrwxrwx  1 ubuntu ubuntu 16788480 gen 10  2016 de421.bsp
-rw-r--r--  1 ubuntu ubuntu        0 ago  5 08:19 __init__.py
-rw-r--r--  1 ubuntu ubuntu     6780 ago  4 19:04 jpltoolkit.py
-rwxr-xr-x  1 ubuntu ubuntu  4286040 ago  3 08:41 libcspice.so
-rw-r--r--  1 ubuntu ubuntu     5257 lug 15  2016 naif0012.tls
-rw-r--r--  1 ubuntu ubuntu     1732 ago  5 12:36 testjpl.py
-rwxrwxrwx  1 ubuntu ubuntu    10146 giu 26 09:55 tools.py

In sostanza:

  • un file con estensione bsp (file DAF/SPK, contenente i coefficienti del polinomio interpolatore per il calcolo delle effemeridi in un certo un arco temporale)
  • l'utilità brief, che ci fornisce le informazioni relative al file di cui sopra
  • il kernel generico naif0012.tls, che contiene i leapsecond stabiliti dall'IERS (va sostituito ogni volta che ne viene emesso uno nuovo)
  • Il file di libreria shared libcspice.so
  • il file che sto costruendo jpltoolkit.py (chiamatelo come volete) che contiene le funzioni ctypes
  • il file tools.py (funzioni di calendario, trigonometriche e di supporto alla conversione radianti - gradi)
  • il file __init__.py che serve esclusivamente a dichiarare che tools.py e jpltoolkit.py sono moduli
  • il file testjpl.py che conterrà il programma di collaudo vero e proprio

cominciamo a scrivere il file di collaudo (testjpl.py, nel mio caso). Importiamo i moduli e lanciamo in esecuzione.

from tools import *
import jpltoolkit as jpl

print "valore di pi greco pi_c :", jpl.pi_c()
print "coefficiente di conversione radianti->gradi dpr_c :", jpl.dpr_c()
print "secondi in un giorno spd_c :", jpl.spd_c()

Python 2.7.12 (default, Nov 19 2016, 06:48:10) 
[GCC 5.4.0 20160609] on linux2
Type "copyright", "credits" or "license()" for more information.
>>> 
 RESTART: /home/ubuntu/Scrivania/ASTRONOMY/JPL toolkit/cspice/usefultest/prova.py 
valore di pi greco pi_c : 3.14159265359
coefficiente di conversione radianti->gradi dpr_c : 57.2957795131
secondi in un giorno spd_c : 86400.0
>>> 

Fin qui tutto bene. Le funzioni relative alle costanti restituiscono effettivamente i valori attesi. Ora carichiamo i nostri kernel usando la funzione furnsh_c di CSPICE.

import ctypes as ct

ct.shared_lib = ct.CDLL('./libcspice.so')

# definitions
_spd_c = ct.shared_lib.spd_c
_pi_c = ct.shared_lib.pi_c
_dpr_c = ct.shared_lib.dpr_c
_furnsh_c = ct.shared_lib.furnsh_c
_unload_c = ct.shared_lib.unload_c

def spd_c():
    _spd_c.restype = ct.c_double
    return _spd_c()

def pi_c():
    _pi_c.restype = ct.c_double
    return _pi_c()

def dpr_c():
    _dpr_c.restype = ct.c_double
    return _dpr_c()

def furnsh_c(k):
    _furnsh_c.argtypes=[ct.c_char_p]
    _furnsh_c(k)

def unload_c(k):
    _unload_c.argtype=[ct.c_char_p]
    _unload_c(k)

Come ho fatto per le costanti, creo una funzione ctypes per linkare la funzione CSPICE furnsh_c (che carica un kernel) e la funzione unload_c (che invece lo rimuove dalla memoria, quindi liberando spazio).

Molte istruzioni d'uso sono nella documentazione in particolare nella pagina delle funzioni più frequentemente utilizzate che è una vera miniera.

In particolare per l'uso di furnsh_c e unload_c ci guida alla signature delle stesse:

void furnsh_c ( ConstSpiceChar  * file ) 
void unload_c ( ConstSpiceChar  * file ) 

che significa che queste funzioni accettano un parametro di input (il nome del file nel formato ConstantSpiceChar, che corrisponde al char* di C, puntatore all'inizio della stringa)

In ctype basta banalmente dichiarare la variabile stringa come ctypes.c_char_p, o, nel mio caso, ct.c_char_p, visto che ho usato un'alias. Queste funzioni non restituiscono valore (sono void). La gestione di eventuali errori va gestita separatamente, ma ne parliamo più avanti.

Siamo pronti per inserire una funzione importante, str2et_c, che assume una stringa rappresentante una data e ora precise e le converte in secondi trascorsi dal J2000 (ore 12 del 1 gennaio 2000) secondo la convenzione TDB - Barycentric Dynamic Time, istituita nel 1976 a sostituzione del precedente Ephemeris Time. La conversione è automatica.

Integro la nuova funzione nel file jpltoolkit.py, evito di riprodurre le funzioni precedenti, spero che sia abbastanza chiaro il procedimento.

...
_str2et_c = ct.shared_lib.str2et_c
....
def str2et_c(s, e):
    _str2et_c.argtypes=[ct.c_char_p, ct.POINTER(ct.c_double)]
    _str2et_c(s, e)
    return e.value

Stavolta la signature è la seguente:

void str2et_c ( ConstSpiceChar * str, SpiceDouble * et ) 

Questo significa che la funzione accetta una stringa (che in C viene dichiarata come char *) e un puntatore a variabile double. Il che spiega perchè negli argomenti della funzione python equivalente _str2et uso un ct.c_char_p come prima e un ct.POINTER(ct.c_double). La funzione mi restituirà void, cioè None in linguaggio Python, ma io prevedo di restituire al chiamante il value della variabile, cioè il contenuto puntato dal puntatore a double. Sembra complicato, ma vi assicuro che ci si fa presto l'abitudine.

Una curiosità: tra UTC e TDB c'è una differenza temporale significativa. Se lanciamo la chiamata alla funzione str2et_c per il J2000 espresso in UTC, otteniamo questa risposta:

timestring = "2000-01-01T12:00:00"
et = jpl.ct.c_double(0)
seconds = jpl.str2et_c(timestring, et)
print "(UTC 2000_01_01 12:00)", seconds, "seconds from J2000\n"

(UTC 2000_01_01 12:00) 64.1839272847 seconds from J2000

che è esattamente quanto atteso, vedi questa tabella

Il formato della stringa è un formato standard secondo ISO. La pagina che documenta la funzione str2et_c ne propone diversi formati nel paragrafo Examples

Chiudo qui per il momento, continuero' nel prossimo post.

Implementare CSPICE - Alcune costanti e funzioni comuni

Ora che abbiamo creato una libreria dinamica, è necessario creare l'interfaccia per il linguaggio Python. Il modulo ctypes della libreria standard è il metodo che prediligo, ma ce ne sono altri:

  • SWIG, per esempio, utilizzabile con più linguaggi di programmazione
  • le Python/C API, che consentono di scrivere codice python dentro un sorgente C (le trovo piuttosto scomode da usare)
  • Cython, che è un compilatore statico per Python e per un linguaggio proprio derivato da Pyrex, molto più user friendly delle Python/C API

Io perferisco ctypes perchè diretto e relativamente facile da usare, come vi dimostrero' a breve.

Naturalmente qualcuno potrebbe obiettare che, avendo delle librerie SPICE scritte in C, tanto vale usare il C. Il problema è che C va bene per scrivere routine e libreria, ma è molto complesso da utilizzare in ambiente grafico e molto meno produttivo di Python. Inoltre il debug di C può essere molto più complicato.

Iniziamo dai collegamenti fondamentali.

Linking della libreria dinamica cspice

La volta scorsa abbiamo creato una shared library che abbiamo chiamato libcspice.so, equivalente in ambiente windows di una dll. Creiamo una directory testSPICE, che useremo per lo sviluppo del codice sorgente.

Oltre al file libcspice.so, copiamo nella directory uno o più file de.bsp che recuperiamo dal sito ftp della NASA, sono i cosiddetti DAF/SPK Kernel, che contengono i coefficienti dei polinomi interpolatori usati per calcolare le posizioni di oggetti nello spazio, in particolare i pianeti. Durante la compilazione della libreria abbiamo creato anche una serie di file eseguibili che troviamo nella directory cspice/exe. Uno di questi è brief, che possiamo usare per trovare le specifiche di ognuno di questi file. Per esempio, se abbiamo scaricato de421.bsp e vogliamo sapere cosa contiene, copiamo brief nella directory test e lo lanciamo in esecuzione:

./brief de421.bsp

L'output del programma sarà il seguente:

BRIEF -- Version 4.0.0, September 8, 2010 -- Toolkit Version N0066
 
 
Summary for: de421.bsp
 
Bodies: MERCURY BARYCENTER (1)  SATURN BARYCENTER (6)   MERCURY (199)
        VENUS BARYCENTER (2)    URANUS BARYCENTER (7)   VENUS (299)
        EARTH BARYCENTER (3)    NEPTUNE BARYCENTER (8)  MOON (301)
        MARS BARYCENTER (4)     PLUTO BARYCENTER (9)    EARTH (399)
        JUPITER BARYCENTER (5)  SUN (10)                MARS (499)
        Start of Interval (ET)              End of Interval (ET)
        -----------------------------       -----------------------------
        1899 JUL 29 00:00:00.000            2053 OCT 09 00:00:00.000

I corpi celesti di cui è possibile avere un'effemeride sono i pianeti o i loro baricentri, il sole, la terra e la luna. Tra parentesi sono indicati i codici NAIF corrispondenti. L'ultima riga contiene gli estremi temporali, in questo caso si va dal 29 luglio 1899 00:00:00 Ephemeris Time al 9 ottobre 2053 00:00:00. Se vogliamo fare ricerche storiche più lontane nel tempo dobbiamo caricare un file effemeride più grande e quindi necessariamente più voluminoso. Il sito della NASA contiene molte informazioni, tutorial e letture tecniche per capire come si usano, come si costruiscono, come si importano ed esportano i file SPK e come è possibile modificarli, per esempio per creane un estratto e salvarlo in un nuovo file. Le utilità della sottodirectory exe sono utili anche a questo scopo

La prima cosa che dobbiamo fare è creare un modulo che useremo nel seguito per creare i nostri sorgenti python.

Possiamo aprire il nostro editor Python (idle, Wingware, Geany, PyCharm sono i più diffusi in ambiente Ubuntu/Mint), e cominciare a buttare giù qualcosa.

#!/usr/bin/env python
import ctypes as ct
from tools import *

Il file tools.py è quello che ho creato due post addietro, contiene un po' di funzioni per lavorare con gradi e radianti, funzioni di calendario ecc. Se tornate indietro nel blog trovate tutto fatto. Ricordatevi di mette nella cartella un file vuoto chiamato __init__.py, che serve a considerare i file contenenti le funzioni di utilità come moduli.

Adesso generiamo il link alla libreria libcspice.so, che abbiamo opportunamente messo nella directory.

ct.shared_lib = ct.CDLL('./libcspice.so')

Da questo momento in poi ct.shared_lib è l'anello di congiunzione tra Python e C. A questa funzione collegheremo tutte le funzioni della libreria cspice, man mano che ci serviranno.

Inziamo da alcune costanti:

SpiceDouble pi_c ( void )
SpiceDouble dpr_c ( void )
SpiceDouble spd_c ( void )

la prima è semplicemente il valore di pi greco, la seconda è il coefficiente di conversione radianti->gradi, la terza è il numero di secondi trascorsi dall'epoca J2000 (1° gennaio 2000, tempo delle effemeridi) in un giorno. Per usare queste costanti dobbiamo creare l'equivalente in python.

_spd_c = ct.shared_lib.spd_c
_pi_c = ct.shared_lib.pi_c
_dpr_c = ct.shared_lib.dpr_c

Ho usato l'underscore iniziale perchè riservo il nome in piano alla funzione python che scrivero' successivamente.

Creiamo le funzioni proprie in Python:

def spd_c():
    _spd_c.restype = ct.c_double
    return _spd_c()

def pi_c():
    _pi_c.restype = ct.c_double
    return _pi_c()

def dpr_c():
    _dpr_c.restype = ct.c_double
    return _dpr_c()

Fin qui è tutto molto semplice: dalla definizione C delle costanti desumo che non ricevono parametri di input(per forza, sono costanti) e restituiscono invece un valore SpiceDouble, che corrisponde ad un double di python. Per cui ognuna di queste costanti è chiamata dal codice Python come funzionePython, che gestisce all'interno la funzioneC con underscore e la restituisce con un return _funzioneC.

Mi fermo qui con questo post, nel prossimo continuiamo l'implentazione e facciamo qualche prova di collaudo. A presto.

Wednesday, August 2, 2017

Don't reinvent the wheel - non reinventiamo la ruota, CSPICE toolkit è perfetto anche per un astrologo

Nei post passati ho posto spesso l'enfasi sull'autoproduzione di software. Se avete cominciato a programmare, come me, alla fine degli anni '80, ricorderete che i compilatori dell'epoca non erano sempre facilmente disponibili e, almeno in ambiente Windows, nemmeno gratuiti. Il primo software di astrologia che ho usato per il DOS di allora era copiato da un numero della rivista del CIDA, scritto in basic, che avevo dotato di una misera interfaccia grafica, completato da alcune routine scritte da me e compilato con Quick Basic. Ne ho ancora la copia, che gira sotto DosBox, e questa a fianco è la sua immagine in esecuzione.

Sono andato sempre un po' di corsa, per cui il programma non ha un titolo decente, l'avevo chiamato Prova70 in base al numero di release, e tale è rimasto.

Da metà anni 80 in avanti ho iniziato a usare visual basic versione 5 poi 6, che è rimasto un po' l'unica piattaforma fino all'avvento di .NET e Visual Basic 2005, 2008 e successivi. Oggi uso soprattutto linux Debian, Ubuntu e derivate, che mettono a disposizione una quantità di software di elevata qualità che fa buona concorrenza a sistemi operativi più diffusi presso gli utenti desktop (mi riferisco a Windows e Mac, ovviamente), linguaggio di programmazione python per quasi tutto (ma pasticcio volentieri con Tcl/Tk, l'ho scoperto da poco meno di un anno ma me ne sono innamorato subito), Android Studio per i sistemi mobile e Debian ARMhf per la raspberry pi come ambiente di sperimentazione (ve ne parlo tra poco, a proposito).

Non c'è maggiore soddisfazione che farsi le cose da sè ma, come scrivo nel titolo, non è che dobbiamo sempre reinventare la ruota, proprio oggi che internet ci dà accesso alle migliori librerie software disponibili. Per questo motivo oggi vi mostro come sono riuscito a installare il toolkit CSPICE, che è lo standard per il calcolo astronomico e non solo, visto che ci mette in grado, volendo, di calcolare il movimento della nostra astronave e farcela parcheggiare, con discreta approssimazione, nel giardino di casa.

CSPICE è la versione in linguaggio C del toolkit elaborato dalla NASA/JPL come "an ancillary information system that provides scientists and engineers the capability to include space geometry and event data into mission design, science observation planning, and science data analysis software."

E' molto esteso e complesso, il solo core contiene più di 1500 funzioni, che spaziano dalla gestione e conversione di data e ora, al calcolo di posizione e movimento di oggetti quali pianeti, asteroidi, satelliti e astronavi, anche nelle relazioni geometriche tra loro e altri oggetti vicini e lontani, alla conversione tra sistemi di coordinate celesti e altro ancora. Per un'esposizione dettagliata delle funzioni disponibili vi invito a leggere la pagina della documentazione di CSPICE.

A questo punto pero' devo fare un

DISCLAIMER - AVVERTENZA

Non so quanto l'astrologia sia considerata conforme alla dignità scientifica delle istituzioni che hanno creato il software. Alcune importanti effemeridi basate su kernel JPL sono utilizzate da software open source e commerciali (vedi Swiss Ephemeris, di cui vi ho parlato in precedenza), per cui mi sento relativamente sicuro di non abusare dei termini della licenza. In ogni caso non fornisco software basato su librerie NASA/JPL ma mi limito a dare qualche suggerimento per un libero e disinteressato utilizzo (volendo anche in campo astronomico, l'astrologo si accontenta di poco, qualcuno dei metodi che descrivo puo' essere benefico anche per astronomi dilettanti e professionisti). Se le mie procedure risultassero in qualche modo difettose, comunque, non me ne faccio carico oltre i limiti ragionevoli della sperimentazione. Ognuno di voi è libero di prendere quello che faccio, copiarlo, modificarlo, venderlo (ammesso che abbia un valore commerciale), sono anche disponibile a dare qualche suggerimento in privato, ma non assumo la responsabilità che deriva dall'uso di queste procedure, ripeto, sono anch'io uno sperimentatore e un dilettante.

Passiamo alle cose divertenti.

Premessa

Non so perchè la NASA/JPL mette a disposizione toolkit in C e FORTRAN per ambienti x86 e x64, per PC, MAC Solaris e SPARC e si dimentica dei processori ARM, se volete compilare il toolkit in una raspberry PI 2 dovete fare qualche piccolo ritocco alla procedura di compilazione. Per prendere il caso più generale (se siete fanatici come me e volete installare il toolkit in uno smartphone cinese da 100 euro, come ho fatto io, siete i benvenuti) dovete in primo luogo trovare il software e compilarlo nel vostro ambiente operativo. Facciamo qualche caso, guardando la tabella delle versioni disponibili per il linguaggio C:

Download e decompressione

  • Mac/Intel, OSX, Apple C, 32bit
  • Mac/Intel, OSX, Apple C, 64bit
  • PC, CYGWIN, gCC, 32bit
  • PC, CYGWIN, gCC, 64bit
  • PC, Linux, gCC, 32bit
  • PC, Linux, gCC, 64bit
  • PC, Windows, MS Visual C, 32bit
  • PC, Windows, MS Visual C, 64bit
  • Sun/Intel, Solaris, Sun C, 32bit
  • Sun/Intel, Solaris, Sun C, 64bit
  • Sun/SPARC, Solaris, gCC, 32bit
  • Sun/SPARC, Solaris, gCC, 64bit
  • Sun/SPARC, Solaris, Sun C, 32bit
  • Sun/SPARC, Solaris, Sun C, 64bit

Io uso una Linux Mint 18.2 64bit, basata su Ubuntu 16.04, per cui la versione PC; Linux, gCC, 64bit mi va benissimo. Cliccando il link si apre una nuova pagina, dove vediamo, in prima posizione nell'elenco, la voce cspice.tar.Z. Facciamo il download dove preferiamo, magari sul desktop.

Apriamo una finestra terminale nella directory di salvataggio e lanciamo il comando:

tar xvf cspice.tar.Z

Diamo invio e aspettiamo che venga costruita una nuova directory contenente tutti i file del toolkit.

Compilazione statica

Nella directory cspice appena creata troviamo un file di installazione makeall.csh. Vi prego di notare l'estensione csh, si tratta di uno script per la shell csh, che è un po' diversa dalla bash che usiamo abitualmente in ambiente linux. Procediamo al suo download, la useremo forse solo per questa procedura, ma ne vale la pena:

sudo apt install csh

Ora possiamo lanciare la compilazione, con il comando

sudo csh ./makeall.csh

Ci vorrà un po', abbiate pazienza.

Al termine del processo, nella sottodirectory lib troviamo il file cspice.a, che è la libreria statica per il linker di C. Non è quella che ci serve per Python, dobbiamo compilare diversamente. Il lancio dello script ha però consentito di decomprimere ulteriormente i file, creandone di nuovi. Di questi abbiamo bisogno per il prossimo passaggio procedurale.

Variante per processori ARM

In una raspberry pi è installato, di solito, il sistema operativo linux Debian jessie sotto distribuzione Raspbian. Non è affatto diverso dalla Debian per x86 nel funzionamento, ma lo è in relazione all'architettura del sistema. Se lanciamo lo script di cui al paragrafo precedente su raspberry pi va subito in crash. Lo script dovrebbe consentire di rimuovere i riferimenti a tale tipo di architettura, ma dopo alcuni tentativi ho scelto una via più semplice e funzionale. Entrate da teminale nella directory cspice e lanciate il seguente comando:

grep -rl "m64" . --include "*.csh" | sudo xargs sed -i "s/\-m64//g"

oppure, se avete scaricato la versione 32 bit del toolkit

grep -rl "m32" . --include "*.csh" | sudo xargs sed -i "s/\-m32//g"

Il comando rimuoverà in modo ricorsivo tutti i riferimenti del compilatore alle architetture 64 o 32bit, per cui abiliterà il funzionamento dello script makeall.csh che abbiamo già visto in precedenza. In questo modo avremo effettuato una compilazione statica del toolkit anche in ambiente ARM, con grande gioia di tutti gli astrofili possessori di raspberry pi (o di smartphone cinesi).

Compilazione dinamica

Per usare python nel calcolo astronomico ci sono diverse interessanti possibilità:

  • La libreria AstroPy è una community based library, complessa e articolata, decisamente orientata all'osservazione astronomica e al relativo imaging. Per un astrologo è eccessiva e, a mio parere, piuttosto difficile da capire e usare

  • La libreria Skyfield di Brandon Rhodes, elegante e pitonica, utilizza i kernel (ne parliamo presto) della JPL e quindi assicura un'elevata precisione di calcolo. Fa seguito alla libreria pyephem dello stesso autore, prevalentemente basata sul wrapping di funzioni c, mentre l'attuale è basata su pure python e utilizza la libreria numerica numpy per l'elevata efficienza nel calcolo numerico.

  • Utile segnalare anche SpiceyPy di Andrew Annex, che è un python wrapper per lo SPICE toolkit.

Ho parlato, in un precedente post, del modulo ctypes di Python (incluso nella standard library) e ho fatto vedere come si puo' compilare dinamicamente le Swiss Ephemeris e utilizzare ctypes per scrivere delle funzioni wrapper. Il metodo che illustrero', applicato al JPL toolkit, è proprio questo. Pur compilando l'intera libreria, avremo una shared library di basso peso (meno di cinque Mb nella mia raspberry, con tutte le 1500 e oltre funzioni del toolkit, ma potremo artigianalmente scrivere solo i wrapper che ci interessano per il calcolo di posizione, lasciando agli astronomi funzioni più elaborate e complesse.

Per fare la compilazione dinamica della libreria CSPICE entriamo, tramite terminale, nella directory src, quindi nella sottodirectory cspice, dove troviamo molti file sorgenti in c (estensione .c) e file header (estensione .h). Per creare una libreria dinamica non dobbiamo fare altro che usare il seguente comando:

gcc -Wall -fPIC -shared -o libcspice.so *.c -lm -ldl

Vedrete un sacco di warnings ma nulla che blocchi la compilazione, probabilmente ci sono piccole anomalie non corrette nei sorgenti. Anche stavolta, portate pazienza, ci vorrà un po' di tempo. In questo caso sia che lavoriate su PC sia che usiate una raspberry, il comando è identico.

Una volta creata la libreria libcspice.so, copiatela in una nuova directory a cui darete il nome che preferite, relativo al vostro progetto python.

Per iniziare, ci servono altri due file, che metterete nella stessa directory:

  • uno dei file kernel planetari di JPL, suggerisco il de421.bsp, che è abbastanza leggero (16 mega) e che trovate a questo indirizzo.

  • un kernel che contiene informazioni relative al deltat, lo scarto temporale fra tempo degli orologi atomici e il tempo terrestre, che è fissato periodicamente da una autorità (IERS), ve ne ho anticipato in un post precedente, attualmente vale 37 secondi (leapseconds). Lo trovate a quest'altro indirizzo.

Con questi strumenti nella directory abbiamo quanto ci serve per cominciare a sviluppare codice python usando il JPL toolkit. Il prossimo post (credo più d'uno) sarà proprio dedicato a questo. Al prossimo post.

Saturday, June 17, 2017

Un po' di tools di base

La libreria standard di Python è fornitissima di funzioni pronte all'uso, ma per il nostro modesto lavoro di programmatori di software astrologico è bene che ci dotiamo di strumenti fatti apposta per lo scopo.

Per prima cosa apriremo un file tools.py con un editor a nostra scelta, in cui raccoglieremo tutte le funzioni che andremo via via creando in modo da poterle richiamare facilmente.

Per prime definiremo delle funzioni goniometriche di supporto alle operazioni sulle coordinate.

La libreria standard ha già queste funzioni, pero' si applicano ai gradi misurati in radianti (in cui l'angolo piatto vale 2 pigreco, per intenderci), mentre molte delle formule disponibili usano la misura in gradi.

Per prime le funzioni seno, coseno, tangente e arctangente2 (è la funzione inversa della tangente, ma a differenza della funzione arctangente normale effettua alcuni controlli sul segno degli operandi, risolvendo il dubbio sul corretto posizionamento dell'angolo nei quadranti, come si vede nellla tabella seguente):

Python 2.7.13 (default, Jan 19 2017, 14:48:08) 
[GCC 6.3.0 20170118] on linux2
Type "copyright", "credits" or "license()" for more information.
>>> 
======== RESTART: /home/ubuntu/Scrivania/proveStroBlog/provaatan2.py ========
arctan(x) for x varying from 0° to 360° step 30°

x              atan(sin(x)/cos(x))     atan2(sin(x),cos(x))
 30.000               30.000               30.000
 60.000               60.000               60.000
 90.000               90.000               90.000
120.000              -60.000              120.000
150.000              -30.000              150.000
180.000               -0.000              180.000
210.000               30.000             -150.000
240.000               60.000             -120.000
270.000               90.000              -90.000
300.000              -60.000              -60.000
330.000              -30.000              -30.000
360.000               -0.000               -0.000
>>> 

Ecco le prime funzioni goniometriche in gradi e una funzione per riportare un angolo all'angolo giro fondamentale che toglie il segno meno se presente:

import math

def sin(x):
        return math.sin(math.radians(x))

def cos(x):
 return math.cos(math.radians(x))

def tan(x):
        return sin(x)/cos(x)

def atan(x):
        return math.degrees(math.atan(x))

def atan2(y , x):
 return math.degrees(math.atan2(y, x))

def reduce360(x):
 return x % 360.0

Ora ci servono due funzioni per convertire un angolo dalla notazione sessagesimale alla decimale e viceversa. Decido che il segno che compare davanti alle ore darà il segno a tutta la variable decimale, mentre, al contrario, nella conversione da decimale a sessagesimale solo le ore riceveranno un eventuale segno negativo. La funzione, oltre che alle ore, puo' applicarsi immodificata anche agli angoli, sempre in notazione sessagesimale.

def dms2ddd(hour, minute, second):
    """ from sexagesimal to decimal
        the sign of hour variable is automatically applied to minutes and seconds
    """
    if hour < 0:
        sign = -1
        hour *= sign
    else:
        sign = 1
    return (hour+minute/60.0+second/3600.0)*sign

def ddd2dms(dec_hour):
    """ from decimal to sexagesimal representation of hours and angles.
        the sign of dec_hour variable is applied only to hours variable
        see the dms2ddd function for comparison
    """
    if dec_hour < 0:
        sign = -1
        dec_hour *= sign
    else:
        sign = 1
    total_seconds = int(dec_hour * 3600.0+.5)
    seconds = total_seconds % 60 
    total_minutes = int((total_seconds - seconds)/60.0)
    minutes = total_minutes % 60 
    hours = int((total_minutes - minutes)/60.0)
    return (hours * sign, minutes, seconds)

Ci serve inoltre una funzione che converta una data dal formato giorno, mese, anno, ora minuto e secondo, scomodissimo per il calcolo, in quella comoda notazione che corrisponde alla data juliana.

def cal2jul(year, month, day, hour=0, minute=0, second=0):
    """ converts calendar date to julian date
        this routine and the following are built following Duffet Smith /Zwart instructions
        as given in Peter Duffett-Smith-Zwart Practical Astronomy with your Calculator or Spreadsheet
        Fourth Edition, Cambridge University Press, Fourth Ed. 2011
        For an easier use of the function, hours minutes and seconds are defaulted to 0, so it's
        not necessary to give them as parameters when the hour is 00:00:00 
    """
    month2 = month
    year2 = year
    if month2 <= 2:
        year2 -= 1
        month2 += 12
    else:
        pass
    if (year*10000 + month*100 + day) >= 15821015:
        a = math.trunc(year2/100.0)
        b = 2 - a + math.trunc(a/4.0)
    else:
        a = 0
        b = 0
    if year < 0:
        c = math.trunc((365.25 * year2)-0.75)
    else:
        c = math.trunc(365.25 * year2)
    d = math.trunc(30.6001 *(month2 + 1))
    return b + c + d + day + hour / 24.0 + minute / 1440.0 + second / 86400.0 + 1720994.5

Infine ecco due funzioni che servono per il calcolo del tempo siderale di Greenwich e del tempo siderale locale, necessari ad allineare il tempo locale con un riferimento alle cosiddette "stelle fisse" (che tanto fisse non sono, ma per noi astrologi il riferimento statico è l'eclittica e la sua divisione in segni zodiacali, che, per inciso, non c'entrano niente con le costellazioni).

def ut2gst(year, month, day, hour, minute, second):
    """ Sidereal time is a time-keeping system astronomers use to keep track of the direction to point
        their telescopes to view a given star in the night sky.
        Briefly, sidereal time is a "time scale that is based on the Earth's rate of rotation measured
        relative to the fixed stars." (source Wikipedia)
        This routine converts Universal Time to Sidereal Time for Greenwich (Greenwich Sidereal Time)
    """
    jd = cal2jul(year, month, day)
    S = jd - 2451545.0
    T = S/36525.0
    T0 = (6.697374558 + 2400.051336 * T+ 0.000025862 *T*T) % 24
    UT = dms2ddd(hour, minute, second)*1.002737909
    GST = (UT + T0) % 24
    return GST

e

def gst2lst( gst, long_degree, long_minute, long_second=0):
    """ Corrects GST for a different location on the Earth
    """
    lg = dms2ddd(long_degree, long_minute, long_second)/15.
    lst = (gst + lg) % 24.
    return lst

Nel prossimo post vedremo qual è il significato di queste due ultime funzioni.

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