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.

Tuesday, June 13, 2017

Trovare le coordinate e la timezone di un luogo

Prima di entrare nel merito delle tecniche di calcolo della domificazione, dobbiamo risolvere un problema: come trovo, in modo programmatico, le coordinate di un luogo geografico? Posso usare un datebase di località, naturalmente, vi ho già mostrato come ricavare una semplice tabella con milioni di località e trasformarla in un database sqlite, qualche post addietro, abbiamo anche costruito un widget apposito. Il problema è che i database sono, in genere, molto pesanti da portarsi dietro, si arriva facilmente a qualche decina di mega e non è detto che siamo sempre in condizioni di installarli nel memoria di un dispositivo, se abbiamo qualche limitazione di risorse.

Considerato che il web è un enorme deposito di informazioni, comprese quelle di natura geografica, possiamo pensare, volendo, a utilizzare un servizio REST come quello che mette a disposizione Google: Google Maps Geocoding API, che è gratuito per usi limitati e richiede l'apertura di un account, una API key, e il pagamento oltre una certa soglia di utilizzo, oppure quello di geonames.org che è il sito che mette a disposizione quell'enorme database che ho usato per costruire il mio widget e sicuramente molti altri.

Oggi voglio invece provare a usare un metodo più ruvido e gratuito, basato su wikipedia e su alcune librerie python. Ve ne parlo succintamente, un trafiletto per ogni libreria.

Wikipedia

Per ogni luogo nel mondo, per quanto piccolo, Wikipedia fornisce una pagina in cui sono riportate le coordinate geografiche del luogo. Spulciando nel sorgente della pagina, non è difficile individuarle ed estrarle usando le tecniche delle espressioni regolari, presenti nella Python Standard Library. Preferisco, fra tutte, l'edizione inglese, cosa di cui dobbiamo tener conto quando cerchiamo un nome di città. In quel grande contenitore di librerie python che è Pypi ho trovato un'API per wikipedia pronta per l'uso, che dovremo quindi installare preliminarmente :

# Se non l'avete già fatto:
sudo pip install python-pip <Invio>
# quindi usate pip per installare wikipedia
pip install wikipedia <Invio>

Per la necessità o meno di usare i diritti di amministratore o per Windows o Mac vi prego di controllare la documentazione relativa all'uso di pip. La libreria python wikipedia è soggetta a licenza MIT.

Requests: HTTP for Humans

Se avete mai usato urllib e urlib2 apprezzerete questo piccolo gioiello di Kenneth Reitz, che consente di accedere al contenuto di una pagina web, anche in forma di xml o json dove previsto, in un soffio.

Per installare questa libreria si procede come prima:

pip install requests

La licenza d'uso è Apache 2.0

timezonefinder

Se Wikipedia ci aiuta moltissimo a trovare le coordinate geografiche, usa una rappresentazione della timezone del luogo basata sullo scarto orario da UTC, che diventa complicata da gestire. Con questa libreria posso facilmente ottenere, in base alle coordinate geografiche, la timezone del luogo espressa come "Europe/Rome" e simili, lasciando ad altre librerie il compito di gestire l'ora estiva

Installazione:

pip install timezonefinder

La libreria è di J. Michelfeit, ed è pubblicata su Pypi con licenza MIT

MyLittleTownFinder

Veniamo dunque al codice con cui gestire tutte le complesse operazioni di ricerca ed estrazione delle coordinate geografiche.

Wikipedia è abbastanza regolare nel catalogare i nomi di città, per cui di fronte a toponimi unici, di solito basta comporre l'url così: https://en.wikipedia.org/wiki/nomedelposto e siamo nella pagina giusta. Non sempre è così semplice, per esempio se un nome di città è riportato più volte, per esempio Paris è la capitale della Francia, ma anche una ridente località del Texas (che ha dato il titolo ad un bellissimo film di Wim Wenders).

In questo e simili casi, dobbiamo escogitare un trucco per individuare la pagina di interesse tra le molte possibili. La prima funzione che vi presento, debitamente commentata, fa proprio questo.

import sys
import re
import requests
import wikipedia as wk
from timezonefinder import TimezoneFinder as tfz


def find_a_place_wikipage():

    # first input the name of a place, in English, if possible 

    town = raw_input("city, town, or village: ")

    # the wikipedia search function gets a list of all the available pages

    urls = wk.search(town)

    # the list is printed to the screen, one element at a time,
    # each element preceded by its ordinal numbers. The user can choose an
    # element or type 99 to exit 

    for x,y in enumerate(urls):
        print x,y
    print "99 to quit"
    n = 9999
    while n not in range(len(urls)):
        n = int (raw_input("select a link : "))
        if (n==99):
            sys.exit()
    # the choosen page is returned to the caller
    return urls[n]

Questa funzione cerca di inviduare la pagina corrispondente alla città che cerchiamo. Nel caso non trovi una voce che ci sembra quella corretta, perchè per esempio cerchiamo Venice ma non quella italiana, magari quella in Florida o in Illinois, possiamo aggiungere queste specificazioni geografiche alla stringa di ricerca, il motore di ricerca punterà con maggiore facilità alla voce giusta.

La seconda funzione è quella che gestisce il recupero dei dati dalla pagina che abbiamo individuato. E' abbastanza semplice e commentata, per cui ve la lascio esaminare senza ulteriori discussioni.

def get_coordinates(location):

    # location is given by the caller
    base_url = 'https://en.wikipedia.org/wiki/'

    # a complete url is formed
    url = base_url + location

    # a page request is sent, if HTTP request is successful
    # then the procedure goes on, else is stopped
    page = requests.get(url)
    if page.status_code == requests.codes.ok:
        # the html text is retrieved
        b = page.text
        pattern = '(wgCoordinates":{"lat":)([+-]?\d*\.?\d*)(,"lon":)([+-]?\d*\.?\d*)'
        m = re.search(pattern, b) 
        # a regular expression is compared to the text
        # if successful, the coordinates are extracted and converted to float
        if m:
            latitudine = float(m.group(2))
            longitudine = float(m.group(4))
            # timezonefinder is used to precisely locate the related timezone
            tf = tfz()
            timezone = tf.timezone_at(lat=latitudine, lng=longitudine)
            # all three results are returned back to the caller as a tuple
            return (latitudine, longitudine, timezone)
        else:
            # unsuccessful operation  
            print"not found, sorry"    
            return None
    else:
        # error in HTTP request 
        print "requests error"
        return None

Per finire qualche riga di codice per eseguire in serie le due funzioni.

if __name__ == '__main__':
    location = find_a_place_wikipage()
    result = get_coordinates(location)
    if result:
        lat, lon, timezone = result
        print "latitude {0} longitude {1} timezone {2} ".format(lat, lon, timezone) 

Bene, facciamo qualche prova.

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/cityurls.py =========
città: Roma
0 Roma
1 Representation oligonucleotide microarray analysis
2 A.S. Roma
3 Roma (mythology)
4 Ruska Roma
5 Roma, Queensland
6 Servitka Roma
7 Atletico Roma F.C.
8 Colonia Roma
9 Town of Roma
99 to quit
select a link : 0
not found, sorry
>>> 
========= RESTART: /home/ubuntu/Scrivania/proveStroBlog/cityurls.py =========
città: Rome
0 Rome
1 Rome, Georgia
2 Founding of Rome
3 History of Rome
4 Rome (TV series)
5 Rome Prize
6 Third Rome
7 Diocese of Rome
8 Sack of Rome
9 Province of Rome
99 to quit
select a link : 0
latitude 41.9 longitude 12.5 timezone Europe/Rome 
>>> 

Come potete vedere, ho inserito il nome di città nella dicitura italiana, ma sulla wikipedia in lingua inglese non trova niente. Una volta inserito il nome inglese, tutto fila liscio.

Un'altra prova:

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/cityurls.py =========
città: moscow
0 Moscow
1 MoSCoW method
2 Moscow, Idaho
3 Moscow Metro
4 FC Moscow
5 Administrative divisions of Moscow
6 Moscow Conservatory
7 Mayor of Moscow
8 Grand Duchy of Moscow
9 Moscow Time
99 to quit
select a link : 0
latitude 55.75 longitude 37.6166666667 timezone Europe/Moscow 
>>> 

Notate che c'è una Moscow anche nell'Idaho, USA. Se selezionassi quel link avrei questo risultato:

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/cityurls.py =========
città: moscow
0 Moscow
1 MoSCoW method
2 Moscow, Idaho
3 Moscow Metro
4 FC Moscow
5 Administrative divisions of Moscow
6 Moscow Conservatory
7 Mayor of Moscow
8 Moscow Time
9 Grand Duchy of Moscow
99 to quit
select a link : 2
latitude 46.73 longitude -117.0 timezone America/Los_Angeles 
>>> 

Facciamo direttamente la ricerca di Paris, Texas per vedere come se la cava.

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/cityurls.py =========
città: paris texas
0 Paris, Texas
1 Paris, Texas (film)
2 Paris, Texas (band)
3 Lamar County, Texas
4 Paris Independent School District
5 Paris, Texas (disambiguation)
6 The Paris News
7 Paris (disambiguation)
8 Eiffel Tower (Paris, Texas)
9 First Church of Christ, Scientist (Paris, Texas)
99 to quit
select a link : 0
latitude 33.662508 longitude -95.547692 timezone America/Chicago 
>>> 

Mi sembra che funzioni benino. A questo punto penso che abbiamo un po' di materiale per andare oltre e iniziare a parlare della domificazione. A fra un po'.

Sunday, June 11, 2017

Il qui e ora in astrologia

Ogni volta che osserviamo l'ora nel nostro orologio, per qualunque necessità, astrologia compresa, dobbiamo essere consapevoli che l'ora non viene generata nè misurata dal nostro orologio. Tutto quello che l'orologio sa fare, con metodo meccanico o usando un oscillatore al quarzo, è generare eventi periodici.

Quando parliamo di ora civile facciamo riferimento ad accordi e convenzioni che discendono da fenomeni geografici e astronomici, ma anche da scelte amministrative e politiche.

Se osserviamo un orologio in Italia, l'ora che leggiamo è unica in tutto l'ambito territoriale nazionale italiano, ed è adottata anche da un certo numero di altri Paesi che, da Nord a Sud, dalla Norvegia in Nord Europa alla Namibia in Africa, condividono, all'incirca, la nostra posizione a est rispetto ad un meridiano di riferimento, come diciamo comunemente, hanno il nostro stesso fuso orario. I Peasi con estensione territoriale maggiore, come gli Stati Uniti, la Russia e la Cina, non possono che adottare più fusi orari e quindi ore diverse nei diversi territori che li compongono. Per poter viaggiare da un Paese all'altro, nello stesso fuso orario, dobbiamo inoltre tener conto delle regole associate all'ora estiva, che possono cambiare da Paese a Paese.

L'appartenenza ad un fuso orario piuttosto che all'altro genera strani effetti. Spagna e Norvegia adottano il fuso orario dell'Europa Centrale, con un disallineamento rispetto alla posizione del Sole, in ritardo e rispettivamente in anticipo, che ha ripercussioni sulla percezione della distribuzione della luce del giorno in questi paesi. Con un'escursione da 8° Ovest a 23° Est, anche se l'orologio indica la stessa ora, ci sono due ore di differenza nella percezione, per esempio, del sorgere e tramonto del Sole, anche alla stessa latitudine.

Per usare l'ora in Astrologia è pertanto necessario riportarla alla sua fondamentale natura di tempo locale, allineato con la reale posizione del Sole per un dato luogo. Quindi l'ora dell'alba, del mezzogiorno, del tramonto e della mezzanotte astrologici sono ricalcolati rispetto all'ora convenzionale per riportare la rappresentazione dell'ora alla sua fondamentale realtà geografica e astronomica. Per fare questo, tienamo conto della longitudine e della latitudine geografica. Vediamo in breve come fare, visto che siamo programmatori, partendo dall'ora segnata dal nostro PC.

Ora del computer

La scheda madre del vostro computer è dotata di circuiti elettronici oscillanti, che riproducono con buona precisione lo scorrere del tempo e, di conseguenza, della rappresentazione dell'ora nel computer. Si imposta nel BIOS, il sistema operativo di base del computer, e rimane stabile salvo che non ci sia la sincronizzazione con un server remoto, o server NTP. In tal caso l'orologio del PC, anche a livello di BIOS, viene sincronizzato con il server remoto.

Ora civile

Il vostro PC riconosce l'ora standard del Paese in cui vi trovate, il cosiddetto locale che è fondamentalmente riconducibile alla timezone della vostra località geografica. Se installate una qualsiasi distribuzione Linux, durante l'installazione vi viene chiesta la conferma della timezone individuata automaticamente (di solito per l'Italia compare Europe/Rome ma qualche volta mi è successo di leggere Europe/Vatican City. L'elenco completo delle timezone è contenuto nel PC, compilato da quello che, in onore del suo primo curatore, viene chiamato Olson database. Se ne volete un esempio e avete, come me, una Debian o una Ubuntu, aprite un terminale e provate a digitare tzselect . Se provo a seguire le istruzioni, viene fuori questo dialogo:

ubuntu@ubuntu:~$ tzselect
Please identify a location so that time zone rules can be set correctly.
Please select a continent, ocean, "coord", or "TZ".
 1) Africa
 2) Americas
 3) Antarctica
 4) Asia
 5) Atlantic Ocean
 6) Australia
 7) Europe
 8) Indian Ocean
 9) Pacific Ocean
10) coord - I want to use geographical coordinates.
11) TZ - I want to specify the time zone using the Posix TZ format.
#? 4
Please select a country whose clocks agree with yours.
 1) Afghanistan    18) Israel      35) Palestine
 2) Armenia    19) Japan      36) Philippines
 3) Azerbaijan    20) Jordan      37) Qatar
 4) Bahrain    21) Kazakhstan     38) Russia
 5) Bangladesh    22) Korea (North)     39) Saudi Arabia
 6) Bhutan    23) Korea (South)     40) Singapore
 7) Brunei    24) Kuwait      41) Sri Lanka
 8) Cambodia    25) Kyrgyzstan     42) Syria
 9) China    26) Laos      43) Taiwan
10) Cyprus    27) Lebanon      44) Tajikistan
11) East Timor    28) Macau      45) Thailand
12) Georgia    29) Malaysia      46) Turkmenistan
13) Hong Kong    30) Mongolia      47) United Arab Emirates
14) India    31) Myanmar (Burma)     48) Uzbekistan
15) Indonesia    32) Nepal      49) Vietnam
16) Iran    33) Oman      50) Yemen
17) Iraq    34) Pakistan
#? 9
Please select one of the following time zone regions.
1) Beijing Time
2) Xinjiang Time
#? 1

The following information has been given:

 China
 Beijing Time

Therefore TZ='Asia/Shanghai' will be used.
Local time is now: Sun Jun 11 17:03:24 CST 2017.
Universal Time is now: Sun Jun 11 09:03:24 UTC 2017.
Is the above information OK?
1) Yes
2) No
#? 

Come si vede dal dialogo, io posso benissimo fingere di trovarmi a Beijiing in Cina, il sistema operativo non discute. Quello che mi interessa di più, pero', non è l'ora cinese ma quella che viene riportata come Universal Time, che, guarda caso, oggi che siamo in piena ora estiva, è esattamente due ore prima di quella del mio ufficio. L'Universal Time è quello che ci serve, in prima istanza, per calcolare l'ora dell'oroscopo.

UTC o Universal Coordinated Time

UTC non è un vero acronimo, ma una sintesi fra definizioni in lingua inglese e francese. Rappresenta l'ora di riferimento e viene ottenuto correggendo il TAI (Tempo Atomico Universale), ottenuto dalla misura delle oscillazioni dell'atomo di Cesio 133 in diversi orologi atomici in giro per il mondo, mediante una correzione periodica.

Da metà Ottocento fino al 1 gennaio 1972 si è utilizzato il meridiano di Greenwich (Regno Unito) come l'origine dei meridiani (meridiano 0) e l'ora corrispondente, misurata come tempo solare medio, veniva chiamata Greenwich Medium Time (GMT). In pratica doveva rappresentare il momento della massima elevazione solare sull'orizzonte, che necessitava tuttavia di una correzione fino a 16 minuti (equazione del tempo). L'ora così definita veniva contata a partire dalla mezzanotte (è stata rinominata per accordo comune come UT1 o tempo universale). L'UTC che abbiamo citato prima è molto vicino al valore di UT1 ma necessita di una correzione prevedibile ma non calcolabile esattamente che si chiama DUT1 e viene dichiarata dall'IERS (International Earth Rotation and Reference Systems Service) in un bollettino periodico che si trova anche sul web.

In pratica, indipendentemente dalla complessità delle operazioni di rettifica e riallineamento dell'ora rispetto ai fenomeni terrestri e celesti, in astrologia usiamo, per le finalità di calcolo della domificazione, l'UTC che, come abbiamo visto, si ottiene facilmente dal nostro computer o dalle librerie dei principali linguaggi, compreso Python che è il linguaggio che ho assunto come riferimento.

Ora locale

Arriviamo quindi finalmente a calcolare un'ora del giorno che sia congruente con la nostra longitudine terrestre. L'operazione, semplicissima, consiste nell'ottenere la longitudine del luogo per cui redigiamo la carta natale, convertirla in ore e sommarla all'UTC se siamo a longitudine EST, sottrarla se siamo a OVEST.

Facciamo un esempio:

Ho sbagliato percorso e mi ritrovo a girare per le strade di Mumbay. Il mio orologio portato dall'Italia segna le 8.15, ma si schiatta del caldo e credo, data la posizione del Sole, che siamo più vicini a mezzogiorno di quanto il mio orologio possa pensare. So che Mumbay si trova a 72.825833333333 di longitudine est e voglio calcolare che ora sarebbe se non ci fosse un'ora standard e dovessi basarmi su un'ora calcolata per il luogo.

Presto fatto: in Italia è estate e ci sono due ore di differenza rispetto all'UTC, quindi posso calcolare un UTC=6.15. La longitudine di Mumbay, calcolata in ore, è 72.82 ecc / 15, considerando 15 gradi corrispondenti a un'ora di orologio. Bene, questo significa che devo aggiungere 4.8550 ore (4h 51' 18") all'UTC per avere l'ora locale di Mumbay.

Quindi:

8.15 ora estiva italiana - 2 ore 6.15 UTC + 4 ore 51 m 11.06 ora locale di Mumbay

In realtà Mumbay usa l'UTC+5:30 che mi darebbe un'ora civile di 11:45, estesa a tutto il fuso orario di riferimento.

Se fossi a Kolkata (Calcutta) l'orologio indicherebbe sempre le 11:45, ma l'ora locale sarebbe un po' diversa, considerata la longitudine (88°22' cioè +5 ore 53'), quindi sarebbero le 6.15+5.53 = 12.09. La differenza di circa 15 gradi in longitudine si esprime, come prevedibile, in circa un'ora di differenza nell'ora locale, a parità di ora civile

.

Spero che questa breve digressione sull'ora e il luogo sia stata di qualche utilità. Nel prossimo post parleremo di domificazione, cioè di rappresentazione di una rappresentazione della suddivisione del cerchio zodiacale alternativa allo zodiaco, e fondamentale per la centratura dell'oroscopo rispetto alla reale posizione del Sole sull'orizzonte locale. A presto.

Tuesday, June 6, 2017

E' tempo di ricominciare

Due anni di pausa per un blog sono tanti, e non sono passati per mancanza di interesse, quanto per la necessità di esplorare altri aspetti della programmazione e dei software disponibili per il calcolo astronomico che possono risultare utili anche ad un astrologo dilettante come me. Non sono un astrologo professionista, per cui mi limito a trattare di aspetti tecnici del calcolo, rinviando eventuali discussioni di merito sull'astrologia in sè come arte esplorativa del mondo ad un futuro non ancora precisato.

Per fare un po' il punto, prima di riprendere a trattare i temi del software astrologico, desidero riprendere da dove ero arrivato e, possibilmente, andare oltre.

Negli ultimi anni è esploso il mercato consumer degli smartphone e dei single board computer, di cui le schede Raspberry PI sono l'esempio più interessante, per il gran numero di elementi venduti. Rispetto ai classici computer desktop, questi dispositivi presentano dei vantaggi (gli smartphone e i tablet sono portabili ovunque, le SBC sono dispositivi di bassissimo consumo energetico, tanto da poter rimanere accese 24 ore senza gravare sulla bolletta elettrica) e dei potenziali inconvenienti (il linguaggio Python, che ho usato fino all'ultimo post, è scarsamente implementato sugli smartphone, in cui non gira nativamente e soprattutto non usa le librerie grafiche proprie dei sistemi operativi android e IOS, dovendosi quindi, per un buon risultato finale e una buona usabilità, usare i linguaggi propri di questi sistemi operativi (Java e Objective C), la Raspberry PI è invece basata su processore ARM, quindi la compilazione di codice deve considerare la particolarità della piattaforma). In margine alle mie sperimentazioni in Python su ambiente desktop, avro' ogni tanto occasione di parlare di come riesco a implementare funzioni e programmi anche in questi differenti ambienti operativi.

Ritorno un attimo sul tema delle librerie di alta precisione. Dopo aver tentato di implementare le VSOP2013 in ambiente Python e aver discusso delle difficoltà di usare Python per avere una efficienza elevata quando i termini da sommare sono migliaia, ho definitivamente rinunciato a quest'idea. In realtà ho visto ben poche implementazioni di queste librerie, mentre ho constatato che gran parte degli sviluppi del software vanno in altre direzioni. In particolare al posto dell'approccio analitico classico, basato sulle equazioni del moto dei corpi celesti, con cui ho elaborato alcuni software nei post più vecchi di questo blog, si è progressivamente affermata una metodologia basata sull'uso di effemeridi precompilate, la cui interpolazione con metodi numerici produce soluzioni della posizione dei pianeti e dei satelliti che non sono altrimenti risolvibili con le metodologie tradizionali. Non sono da considerare una novità, in quanto le prime effemeridi, elaborate per supportare le prime missioni spaziali, risalgono alla fine degli anni '60.

In questa pagina di Wikipedia è riassunta la storia delle JPL ephemeris. La versione de436t che è la più recente alla data attuale, copre un tempo che va dal 31 dicembre 1549 ald 25 gennaio 2650 e contiene le posizioni e le velocità del Sole, della Luna, dei pianeti da Mercurio a Plutone o dei loro baricentri. Versioni più estese nel tempo o che considerano più fattori astronomici sono presenti nel sito FTP della NASA all'indirizzo ftp://ssd.jpl.nasa.gov/pub/eph/planets/bsp/ da cui sono liberamente scaricabili. Un inconveniente serio dell'uso di queste effemeridi, rispetto all'approccio analitico classico, è la dimensione, per quelle che possono interessare un astrologo si va da un minimo di 15 mega fino ai 3.4 giga delle de431t. Di cio' dovremo tenere conto nel momento in cui decidiamo di usarle in dispositivi leggeri come smartphone e Single Board.

Oltre ai kernel elaborati dalla Caltech in California per la NASA, esistono altre autorevoli effemeridi prodotte da istituti accademici in Europa, cito quelle del progetto INPOP dell' Institut de Mecanique Celeste et des Calcul des Ephemerides (IMCCE)), che già conosciamo per le VSOP87 e 2013, e le EPM dell' Institute of Applied Astronomy of the Russian Academy of Sciences. A parte lievissime differenze, tutti questi sistemi di integrazione numerica sono sviluppati in modo coerente e presentati in formati compatibili fra loro, rendendo possibile l'utilizzo degli stessi software. Ne discuteremo ampiamente nei prossimi post.

Per riprendere in modo serio la discussione relativamente all'uso di metodologie astronomiche per la pratica astrologica, dobbiamo fermarci a considerare l'elemento tempo, che fin qui abbiamo dato per scontato. Il prossimo post sarà quindi dedicato al tema della misura del tempo in astronomia e quindi in astrologia, utilizzando fonti molto autorevoli. Alla prossima.

Tuesday, May 19, 2015

Intermezzo su list comprehensions e generators

Ho iniziato a usare Python circa 3 anni fa, dopo aver passato anni a scrivere codice in C++, Java e Visual Basic. All'inizio scrivevo come ero stato sempre abituato, con cicli for e while, scrivendo funzioni e un po' di programmazione per oggetti. Python mi ha fatto conoscere tipologie dai dati che prima non usavo, come le liste, le tuple, i dizionari e i set. C'è voluto altro tempo per capire che Python è un linguaggio con una ricca scelta di strumenti di programmazione che, grazie alla necessità di velocizzare l'esecuzione di alcune tipologie di software, mi ha costretto a considerare scelte differenti da quelle che caratterizzavano lo stile precedente. Se inizi a lavorare con le list comprehensions e i generatori ci vuole comunque un po' di tempo per capire che le puoi usare dappertutto, al posto di cicli for e minimizzando lo storage intermedio di dati. Oggi faccio una pausa nell'elaborazione del software per il calcolo astronomico e parlo un po' di questi strumenti.

Gli esempi che si trovano nei manuali sono utili per afferrare l'idea, ma sono troppo semplici per essere esaustivi delle possibilità che i metodi funzionali offrono, per cui ho pensato di scrivere un programmino d'esempio che complica un po' le cose ma spero faccia capire meglio come si puo' procedere.

Se aprite una console Python e scrivete:

import this

viene fuori un Easter Egg (Uovo di Pasqua), che è il seguente (per curiosità è la PEP 20):

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Nella comunità Python questa piccola poesia è nota e citata quanto i Monty Python (che hanno dato il nome al linguaggio), e trovate spesso piccole citazioni che vanno a braccetto con spam e eggs e altre divertenti citazioni dei comici inglesi. Mi interessa farvi notare l'enfasi che viene data alla leggibilità del codice, alla sua semplicità costruttiva, e alla ricerca dell'unica soluzione che risulta ovvia (potremmo dire pythonica).

Il piccolo esercizio che segue discende da un software che ho scritto per gestire la mia spesa domestica, visto che la mia banca online mi permette di esportare in formato Excel un batch di operazioni bancarie. Python ha, ovviamente, delle belle librerie di terze parti per leggere file nel formato xls di Excel, ma preferisco, per vecchia abitudine, usare i file csv, che hanno il pregio della leggibilità diretta come testi.

Dovendo assemblare vari pezzi (il sito della mia banca non mi permette di estrarre tutto l'archivio storico in un colpo solo, per cui sono costretto a unire degli estratti parziali), mi sono posto il problema di come eliminare tutte le linee di intestazione e commento e ridurmi alle sole linee che contengono le operazioni bancarie.

Per avere un codice più essenziale ho usato estesamente le list comprehension e i generatori, come faccio sempre allego il codice poche righe alla volta e le commento,

from datetime import date, timedelta
import re, string
from random import randrange, shuffle

# helper functions:
# rand_string() generates a random shuffled string from all ascii chars
# europ_date converts a date in a string conforming to european format

def rand_shuffled_string():
    all_letters = list(string.ascii_letters)
    shuffle(all_letters)
    return "".join(all_letters)

def european_date(y,m,d, diff):
    return (date(y,m,d) + timedelta(diff)).strftime("%d/%m/%Y")

In breve: importo alcuni moduli essenziali: date e timedelta da datetime per la gestione della data, re per le regular expressions che uso più avanti, string per generare dei set di caratteri ascii dalle funzioni built-in e randrange e shuffle dal modulo random, per generare delle stringhe pseudocasuali.

Le due funzioni helper, banalmente, servono a creare una stringa pseudocasuale e una stringa per la data in formato europeo.

# To create a text file on hard disk we use a list generator
# to generate a text file where each row contains a date and a string

file_out = open("prova_gen.dat","w")
[file_out.write(
    "#".join(
        (european_date(2000,1,1,t),
         rand_shuffled_string(),
         '\n')
        )
    )
    if randrange(0,100) < 90
    else file_out.write("<----------------------- header\n")
    for t in xrange(0,3650,15)
]
file_out.close()

La list comprehension è formata da una funzione applicata ad una o più variabili che entrano in un ciclo e puo' comprendere un filtro di tipo if ... else. Nel caso specifico, applico la funzione helper european_date() alla variabile t che rappresenta lo scarto temporale tra la data di inizio e l'estremo del ciclo for. In pratica creo una data in formato europeo ogni quindici giorni a partire dal 1 gennaio 2000, la associo ad una stringa pseudocasuale e salvo la riga su disco se un numero causale tra 0 e 100 rimane compreso tra 0 e 90, quindi per il 90% dei casi, altrimenti scrivo sul file una pseudo riga di intestazione.

E' interessante notare che l'inclusione di funzioni esterne da me create, quindi presenti nel namespace, mi permette di creare list comprehension molto elaborate ma ancora molto leggibili.

# reload the file just written, filtering rows not begininng with a date
pattern = '\d{2}\/\d{2}\/\d{4}'
less_ = (i for i in open("prova_gen.dat") if re.match(pattern, i) != None)
all_set = (i.split("#") for i in less_)

# generates a list of tuples with date and event 
events = [(i[0], i[1]) for i in all_set]
# prints the first 10 tuples
print events[:10]

Nelle ultimo righe faccio il lavoro di rilettura del file e di filtraggio delle righe. Vorrei farvi notare come la funzione open(<file>) funziona come un generatore, mettendo a disposizione, uno alla volta, i contenuti del file in forma di righe singole. Viene ciclato tutto il file per estrarre le sole righe che contengono una data (a questo scopo uso l'espressione regolare del pattern, che corriponde ad una definita sequenza di caratteri). Il tutto, anziché come in precedenza, in una lista comprehension, in un generatore di espressioni, che quindi non genera una lista e, di fatto, non impegna memoria. Se provo a stampare la variabile less_ l'interprete Python mi risponde <generator object at 0x7f2f39985b90>, quindi in questa fase non ho ancora nulla di leggibile. Idem per la variabile all_set, che splitta ogni singola riga usando il separatore # che ho definito in fase di creazione del file

Infine la variabile event è generata attraverso una list comprehension, quindi è una lista, contenente tuple di due elementi (data e stringa pseudocasuale), che posso stampare o utilizzare altrimenti come insieme di dati. Si noti che, in tutto questo procedimento, è solo alla fine che viene generata una variabile che impegna memoria. Non ho dovuto infatti usare storage intermedi, pur avendo scomposto il procedimento in più fasi. In questo modo spero di avervi mostrato un po' di programmazione funzionale e di lazy evaluation (tipico dei generatori). Si dice lazy (pigro) perchè non pretende di buttare fuori l'output tutto insieme, ma fornisce un risultato alla volta quando richiesto da un'altra funzione o istruzione.

Il prossimo post ritorna sulle VSOP2013 e sull'elaborazione delle matrici di dati che abbiamo reso disponibili. Sarà un'occasione per riprendere il discorso sull'uso dei generatori. Come vedremo, sono anche una delle possibilità tecniche offerte per la velocizzazione del codice.

Monday, May 11, 2015

VSOP2013 - profiling e scelta dei metodi - 2 parte: conversione di blocchi di stringhe in array

Ora che abbiamo una procedura per rileggere velocemente i file di parametri VSOP2013, dobbiamo individuare un metodo veloce per convertire le stringhe nei singoli elementi numerici costitutivi. Dalla guida dell' IMCCE e dalla lettura del file in Fortran che abbiamo tradotto in Python qualche post addietro, sappiamo che ogni riga di testo contiene 17 coefficienti numerici, che vanno moltiplicati per i corrispondenti elementi del vettore lambda, sommati insieme per formare l'argomento phi, il cui seno e coseno vengono moltiplicati per i coefficienti S e C (presenti in fondo a ogni riga), risommati insieme e moltiplicati per una potenza del tempo, espresso in millenni, trascorso dalla J2000.

Il primo problema quindi è tradurre una stringa contenente i coefficienti in singoli elementi numerici. Il file è facilmente leggibile in Fortran, molto più macchinoso in Python. Il linguaggio Python, sia nella libreria di base, sia in Numpy, ha delle funzioni di lettura da file che consentono di ricostruire una tabella dati e convertirla in array, pero' il file dell'IMCCE presenta un difetto: non è a campi delimitati, perchè i primi coefficienti interi di ogni riga sono appiccicati insieme. La lettura va quindi fatta mediante un procedimento a campi a lunghezza fissa, che in Numpy si chiama genfromtxt, che ho provato, funziona, ma mi limita nella possibilità di settare la precisione (in termini di byte utilizzati) dei termini float. Ho pertanto risolto con un approccio differente.

Dal post precedente abbiamo ottenuto un generatore tramite la funzione islice di itertools. Vorrei evitare di creare storage temporanei sotto forma di stringa, visto che alla fine dell'intero procedimento, per ogni pianeta e variabile mi aspetto un risultato singolo di tipo float (possibilmente ad alta precisione), che va sommato per tutte le potenze di 10 del tempo fino ad ottenere il parametro orbitale di interesse.

Ho pensato di procedere in questo modo:

  1. creo una funzione helper convert() che ha lo scopo di spezzettare la riga nei singoli elementi (17 coefficienti + S e C (base ed esponente) e di convertirli in float
  2. applico questa funzione al generatore che legge il blocco di righe nel file e ottengo, mediante una list comprehension, l'input per la funzione numpy asarray che crea una matrice di float ad alta precisione formata da tante righe quante sono le righe del blocco dati nel file e da 21 colonne.
  3. restituisco la matrice alla funzione chiamante

Allego di seguito un po' di codice:

Inizializzazione

import cPickle
import numpy as np
from itertools import islice
from fastnumbers import fast_int, fast_float
import struct
import cProfile, pstats 

limits = cPickle.load(open('VSOP2013_ranges.pickle','rb'))

fmt = struct.Struct("""6s 3s 3s 3s 3s x 3s 3s 3s 3s 3s x 4s 4s 4s 4s x
                       6s x 3s 3s 3s 20s x 3s 20s x 3s x""")

Parte di questi import è già stato visto nei post precedenti. Fastnumbers è una libreria, presente nel repository PyPI, che consente la creazione veloce di integer e float a partire dalle stringhe, sostituendo in modo più efficiente le funzioni di casting int() e float(). Si installa facilmente con pip (sudo pip install fastnumbers).

Di cPickle abbiamo già parlato a sufficienza.

Ho definito una stringa di formato fmt da utilizzare con il metodo struct per spezzettare la lista, con il metodo indicato di seguito.


Funzione helper convert()

def convert(i):
    
    a = fmt.unpack(i)
    return [fast_float(a[j]) for j in xrange(1,22)]

Il modulo di libreria struct serve a gestire la conversione tra valori Python e strutture C rappresentate come stringhe. E' molto raffinato, qui io lo uso in modo elementare per compilare una sequenza di delimitatori di stringa che mi servono per spezzare la riga in singoli elementi. Si traduce facilmente così: nella stringa fmt s sta per carattere e il numero che precede la s indica il numero di caratteri da estrarre, mentre la x rappresenta uno spazio.

Una volta fatto l'unpack della stringa passata alla funzione convert, ho una lista di 17 valori float distinti e 2 basi con esponenti separati, che restituisco alla funzione chiamante. Scarto il numeratore di riga, che non è più necessario. La precisione dei float è quella base di Python (64 bit), sufficiente per la fase di lettura.

La nuova funzione slice_file()

def slice_file(planet, var, power, precision):
    
    file_in = open("VSOP2013p{}.dat".format(planet))
    start, end = limits[planet][var][power][precision]
    data = np.asarray([convert(i) for i in islice(file_in, start, end+1)], np.longdouble)
    file_in.close()
    coeffs = data[:,:17]
    S = 10**data[:,18:19]*data[:,17:18]
    C = 10**data[:,20:21]*data[:,19:20]
    return (coeffs, S, C)

la funzione acquisisce come parametri il codice numerico del pianeta, la variabile corrispondente al parametro orbitale, la potenza di 10 e la precisione richiesta. Apertura e chiusura del file sono poco intensivi, per cui non li considero ai fini del timing. la variabile data corrisponde ad un array di numpy contenente i valori elementari delle variabili. Una volta creato l'array lo divido in una matrice di n per 17 elementi (coefficienti), un vettore colonna S e un vettore colonna C di n elementi, ottenuti con funzioni veloci di numpy, e li restituisco alla funzione chiamante, che in questo caso è il profiler.

Profiling

Per finire eseguo il timing della funzione islice con il metodo cProfile.run che abbiamo già usato in un post precedente:

if __name__ == '__main__':
    profile = cProfile.run("slice_file(1,1,0,12)","prof.prof")
    p = pstats.Stats("prof.prof")
    p.sort_stats("cumulative").print_stats(10)
Ed ecco i risultati con il mio computer:

Python 2.7.9 (default, Apr  2 2015, 15:33:21) 
[GCC 4.9.2] on linux2
Type "copyright", "credits" or "license()" for more information.
>>> ================================ RESTART ================================
>>> 
Mon May 11 16:47:37 2015    prof.prof

         36279 function calls in 0.023 seconds

   Ordered by: cumulative time
   List reduced from 11 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.023    0.023 <string>:1(<module>)
        1    0.004    0.004    0.023    0.023 /home/ubuntu/Scrivania/vsop2013/solution/VSOP2013_load_range7.py:18(slice_file)
     1577    0.009    0.000    0.017    0.000 /home/ubuntu/Scrivania/vsop2013/solution/VSOP2013_load_range7.py:13(convert)
    33117    0.007    0.000    0.007    0.000 {fastnumbers.fast_float}
        1    0.000    0.000    0.003    0.003 /usr/lib/python2.7/dist-packages/numpy/core/numeric.py:392(asarray)
        1    0.003    0.003    0.003    0.003 {numpy.core.multiarray.array}
     1577    0.001    0.000    0.001    0.000 {method 'unpack' of 'Struct' objects}
        1    0.000    0.000    0.000    0.000 {method 'close' of 'file' objects}
        1    0.000    0.000    0.000    0.000 {open}
        1    0.000    0.000    0.000    0.000 {method 'format' of 'str' objects}


>>> 

La routine, per una precisione richiesta di 1e-12, appare abbastanza veloce, considerato che genera le matrici in circa 2 centesimi di secondo. Per avere una reale stima dei tempi esecutivi, è necessario ora inserirla nel contesto più generale del programma di calcolo dei parametri per ogni pianeta. Di questo cominciamo ad occuparci nel prossimo post.

Wednesday, May 6, 2015

VSOP2013 - profiling e scelta dei metodi - 1 parte: rilettura dei file di parametri orbitali

La convinzione diffusa che Python sia un ambiente di sviluppo caratterizzato da lentezza esecutiva, almeno in rapporto ai compilatori tradizionalmente ritenuti più efficienti, come C, C++ e Fortran, è fondata solo in parte.

Giocano sicuramente a sfavore alcune caratteristiche intrinseche del linguaggio, che è fondamentamentalmente un linguaggio di scripting, a tipizzazione dinamica, con alcune strutture dati, quali le liste, che impongono all'interprete grandi spostamenti in memoria, una volta superate certe dimensioni. Per esempio, aggiornare una lista inserendo elementi al suo interno, anzichè accodarli in append, puo' risultare molto pesante quando la lista è costituita da migliaia di termini, dovendosi procedere, ad ogni nuovo inserimento, allo spostamento globale in memoria di tutta la lista. Non è detto pero' che la libreria di base non fornisca metodi utili a evitare sia overhead che massiccio impegno di memoria. Per ogni necessità, possiamo confrontare tra loro più metodi, utilizzando allo scopo degli appositi strumenti di misura, che vanno sotto il nome di profiler.

Oggi continuo il lavoro iniziato nel post precedente, in cui abbiamo ottenuto un dizionario contenente riferimenti ai limiti di inizio e fine di ogni blocco dati all'interno dei file di parametri VSOP2013.

La creazione del dizionario e il suo storage, anche se lenti, sono stati un'operazione da fare una volta sola, della durata di circa un minuto, per cui non mi sono preoccupato particolarmente della velocità esecutiva.

Diverso è il problema della rilettura ed elaborazione dei dati, al fine di ricavare i parametri da sottoporre poi a calcolo per ottenere, alla fine, le variazioni temporali da applicare ai parametri orbitali per gli otto pianeti più il baricentro terra-luna. Non è un compito semplice, voglio arrivare alla fine dell'estrazione ad avere una o più matrici numeriche con cui effettuare i calcoli con i metodi dell'algebra matriciale e, in particolare, con la libreria numpy, le cui routine critiche sono scritte in C e Fortran.

Ho scomposto il lavoro di definizione in più tempi e in più alternative. Oggi provo a scrivere la routine di lettura usando diversi metodi, per confrontarli poi con le funzioni del modulo di misura integrato nella libreria di Python cProfile, che naturalmente dovro' importare nel mio codice, se voglio effettuare le misure dall'interno del codice stesso. Per ovviare alla velocità (comunque elevata) di esecuzione, utilizzo il modulo timeit per ognuna delle tre funzioni

Il listato allegato di seguito, che contiene l'intero programma, usa tre metodi diversi per leggere i file dati (ho scelto quello relativo al pianeta Mercurio, alla sola prima variabile, al primo blocco e a una soglia di arresto pari a 1e-20, praticamente l'intera serie). In questa fase non faccio trattamento delle stringhe per ricavarne i dati numerici, mi accontento di verificare eventuali differenze nella velocità di recupero delle stesse. In coda al listato si trovano una def main() che riepiloga le tre routine e, infine, i metodi di profiling e il timing con il metodo timeit con 100 ripetizioni per ogni funzione.

import cPickle
from itertools import islice
import cProfile, pstats, timeit

limits = cPickle.load(open('VSOP2013_ranges.pickle','rb'))

def timing01():
    chunk = []
    file_in = open('VSOP2013p1.dat')
    all_lines = file_in.readlines()
    start, end = limits[1][1][0][20]
    chunk = all_lines[start:end+1]
    file_in.close()
    return chunk
    
def timing02():
    chunk=[]
    file_in = open('VSOP2013p1.dat')
    start, end = limits[1][1][0][20]
    chunk = [line for line in islice(file_in, start, end+1)]
    file_in.close()    
    return chunk
    
def timing03():
    chunk = []
    file_in = open('VSOP2013p1.dat')
    start, end = limits[1][1][0][20]
    chunk = list(islice(file_in, start, end+1))
    file_in.close()
    return chunk
        
def main():
    result1 = timing01()
    result2 = timing02()
    result3 = timing03()
    return (result1, result2, result3)

if __name__ == '__main__':
    
    profile = cProfile.run("main()","prof.prof")
    p = pstats.Stats("prof.prof")
    p.sort_stats("cumulative").print_stats(15)

    result = main()
    print "timing 100 repliche timing01():", timeit.timeit("timing01()", setup = "from __main__ import timing01", number=100)
    print "timing 100 repliche timing02():", timeit.timeit("timing02()", setup = "from __main__ import timing02", number=100)
    print "timing 100 repliche timing03():", timeit.timeit("timing03()", setup = "from __main__ import timing03", number=100)
    print "-" * 80
    print "primo elemento della lista 1:", result[0][0]
    print "primo elemento della lista 2:", result[1][0]
    print "primo elemento della lista 3:", result[2][0]
    
    print "ultimo elemento della lista 1:", result[0][-1]
    print "ultimo elemento della lista 2:", result[1][-1]
    print "ultimo elemento della lista 3:", result[2][-1]
    

Nel mio PC i risultati sono i seguenti (possono differire in modo significativo su altre macchine):

Python 2.7.9 (default, Apr  2 2015, 15:33:21) 
[GCC 4.9.2] on linux2
Type "copyright", "credits" or "license()" for more information.
>>> ================================ RESTART ================================
>>> 
Wed May  6 17:23:08 2015    prof.prof

         13 function calls in 0.072 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.004    0.004    0.072    0.072 <string>:1(<module>)
        1    0.010    0.010    0.068    0.068 /home/ubuntu/Scrivania/vsop2013/solution/timing.py:32(main)
        1    0.001    0.001    0.047    0.047 /home/ubuntu/Scrivania/vsop2013/solution/timing.py:7(timing01)
        1    0.046    0.046    0.046    0.046 {method 'readlines' of 'file' objects}
        1    0.006    0.006    0.006    0.006 /home/ubuntu/Scrivania/vsop2013/solution/timing.py:16(timing02)
        1    0.006    0.006    0.006    0.006 /home/ubuntu/Scrivania/vsop2013/solution/timing.py:24(timing03)
        3    0.000    0.000    0.000    0.000 {method 'close' of 'file' objects}
        3    0.000    0.000    0.000    0.000 {open}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


timing 100 repliche timing01(): 5.62393903732
timing 100 repliche timing02(): 0.71301984787
timing 100 repliche timing03(): 0.654221773148
--------------------------------------------------------------------------------
primo elemento della lista 1:     1   0  0  0  0   0  0  0  0  0    0   0   0   0      0   0  0  0  0.0000000000000000 +00  0.3870983098840000 +00

primo elemento della lista 2:     1   0  0  0  0   0  0  0  0  0    0   0   0   0      0   0  0  0  0.0000000000000000 +00  0.3870983098840000 +00

primo elemento della lista 3:     1   0  0  0  0   0  0  0  0  0    0   0   0   0      0   0  0  0  0.0000000000000000 +00  0.3870983098840000 +00

ultimo elemento della lista 1: 32240   3  0  0  0   0  0  0  0 -7    0   0   0   0      0   0  0  0 -0.7232436513837180 -16 -0.4617790227683706 -15

ultimo elemento della lista 2: 32240   3  0  0  0   0  0  0  0 -7    0   0   0   0      0   0  0  0 -0.7232436513837180 -16 -0.4617790227683706 -15

ultimo elemento della lista 3: 32240   3  0  0  0   0  0  0  0 -7    0   0   0   0      0   0  0  0 -0.7232436513837180 -16 -0.4617790227683706 -15

>>> 

E' abbastanza evidente il pessimo timing della prima funzione: 8 volte più lenta e inefficiente delle successive. Inoltre, la necessità di creare una lista per procedere al suo slicing, oltre a comportare un esagerato impegno di memoria, si rivela impegnativa anche sotto il profilo dei tempi di esecuzione, giustificando la quasi totalità del tempo impiegato dalla funzione. Le due funzioni successive sono decisamente più performanti, a dimostrare che una scelta accurata dei metodi puo' essere determinante, anche quando usiamo funzioni di libreria.

Ho previsto la stampa del primo e dell'ultimo elemento del blocco ottenuto da ogni funzione per mostrare che i risultati prodotti dalle funzioni sono identici. Nel post successivo utilizzeremo il metodo islice per l'estrazione delle righe del file dei parametri e inizieremo, sempre confrontando più metodi, a convertire le righe di testo in variabili numeriche.

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