# Gabriele Baldassarre — Contenuti completi per AI ingestion # Generato automaticamente. Ultimo aggiornamento: 2026-06-19T08:26:48Z --- title: Passacavi da scrivania: geometrie interne e innesto a baionetta date: 2026-06-18 url: https://gabrielebaldassarre.com/3d-printing/openscad-tutorial-parte-2/ excerpt: Seconda parte del tutorial OpenSCAD: analizziamo la geometria dell'innesto del passacavo parametrico. Vedremo i pattern iterativi, la gestione delle tolleranze e le scelte progettuali affinché il nostro oggetto sia effettivamente stampabile. audience: practitioner proficiency: intermediate tags: OpenSCAD, 3D Printing, CAD, modellizzazione parametrica, incastri stampabili category: 3D Printing prerequisites: OpenSCAD; Stampa 3D FDM; Filettatura; CSG (Constructive Solid Geometry); Supporti di stampa 3D; Tolleranze meccaniche; Orientamento di stampa 3D; Geometria non-manifold; STL (formato di file); 3MF (3D Manufacturing Format); CAD parametrico; PLA (acido polilattico); PETG; Innesto a baionetta; Aritmetica a virgola mobile; Pattern epsilon (eps); Ciclo for in OpenSCAD; intersection_for in OpenSCAD; Slot a L (innesto a baionetta); Pin radiale; Unione implicita in OpenSCAD; Delaminazione (stampa 3D); Sovra-estrusione; Ugello (stampa 3D); Infill (riempimento); Gyroid --- Nella prima parte abbiamo visto la struttura generale del codice e le operazioni fondamentali di OpenSCAD. Ora scendiamo nel dettaglio del componente più interessante del passacavo, e che ci consentirà di scendere più nel dettaglio dei pattern geometrici più utilizzati nella progettazione: l’innesto a baionetta. Un innesto a baionetta è un meccanismo di fissaggio, tipico degli obiettivi fotografici, che unisce due pezzi tramite una combinazione di inserimento assiale e rotazione. Lo troviamo negli obiettivi fotografici, nei tappi dei radiatori e — nel nostro caso — in un passacavo da scrivania che deve restare saldo senza viti, colla o incastri a pressione. Lo so, lo so, per la destinazione d’uso, non c’era alcun motivo di realizzare questa componente. Ma mi è sembrato un ottimo approfondimento per gli scopi prettamente divulgativi di questa serie. Quindi, appurata la necessità “divulgativa” di avere un elemento inferiore nell’assieme, la prima domanda è… Perché una baionetta e non una filettatura La scelta è puramente pratica e dettata dai vincoli della stampa 3D FDM. Una filettatura su un pezzo di 60 mm di diametro con pareti di 3 mm avrebbe questi problemi: Supporti inevitabili. Il profilo di un filetto crea sporgenze che richiederebbero supporti, peraltro di quelli difficilmente rimovibili. E io odio i supporti difficilmente rimovibili (come tutti). Tolleranze critiche. Una filettatura stampata in PLA è molto sensibile alle tolleranze: troppo stretta si grippa, troppo larga balla. Orientamento di stampa. I tre pezzi del passacavo vengono stampati in orientamenti diversi. Una filettatura sul manicotto sarebbe stampata in verticale (ok), ma sull’anello sarebbe orizzontale (pessimo). La resistenza alla delaminazione, infatti, dipende da come è orientato l’oggetto e, quindi, dall’asse di carico. Di nuovo, in questo caso parliamo di quasi totale assenza di carico, ma teniamolo come principio generale che male non farà. L’innesto a baionetta risolve tutti e tre i problemi: i pin e gli slot sono geometrie semplici, stampabili senza supporti o con supporti minimi e le tolleranze si controllano con un unico parametro a prescindere dall’orientamento sul piano di stampa dei vari componenti dell’assieme. E poi, è anche bello da vedere! Filettatura vs innesto a baionetta: la filettatura genera sporgenze che richiedono supporti e ha tolleranze critiche, il che la rende una geometria difficile da stampare; la baionetta è geometricamente autosupportante e con gioco costante. Descrizione estesaA sinistra: sezione trasversale di un accoppiamento filettato tra manicotto (azzurro) e vite (grigio). I denti trapezoidali della filettatura creano sporgenze superiori a 45° (evidenziate) che richiedono supporti di stampa. La zona di tolleranza critica è indicata da frecce in arancione. A destra: sviluppo piano della parete interna del manicotto con slot a L composto da ingresso verticale (1), corridoio orizzontale (2) e tacca di ritenzione (3). Il pin (verde) compie un percorso a baionetta: inserimento assiale, rotazione, scatto nella tacca. L’anatomia della baionetta Il meccanismo si compone di due elementi: Pin radiali sul collare dell’anello inferiore (bottom_part). Sono denti che sporgono verso l’esterno. Slot a L scavati nella parete interna del manicotto superiore (top_part). Ogni slot ha tre zone: un ingresso verticale, un corridoio orizzontale e una tacca di ritenzione. L’assemblaggio funziona così: si allineano i pin con gli ingressi verticali, si spinge l’anello fino in fondo, si ruota in senso orario. I pin percorrono il corridoio orizzontale e scattano nella tacca di ritenzione, dove restano bloccati. Il pin: un dente radiale Il pin è un piccolo settore di anello, generato dalla primitiva ring_sector che abbiamo introdotto nella parte 1: module bayonet_pin(r_inner, h, w_deg, t) { rotate([0, 0, -w_deg/2]) ring_sector(r_inner, r_inner + t, h, w_deg); } I parametri del pin sono: r_inner: il raggio interno a cui inizia il dente (tipicamente il raggio esterno del collare) h (bayo_pin_h = 2.4): l’altezza assiale w_deg (bayo_pin_w_deg = 14): la larghezza angolare in gradi t (bayo_pin_t = 1.4): la sporgenza radiale La rotate([0, 0, -w_deg/2]) centra il pin sull’angolo zero, a quel punto serve solo un ciclo for per definire il pattern circolare: for (i = [0 : bayo_pins-1]) { rotate([0, 0, i * 360/bayo_pins]) translate([0, 0, pin_z_on_collar]) bayonet_pin( r_inner = collar_od/2, h = bayo_pin_h, w_deg = bayo_pin_w_deg, t = bayo_pin_t ); } Tre pin (bayo_pins = 3) equidistanti a 120°. Tre è il numero ottimale: due potrebbero basculare, quattro sarebbero sovrabbondanti e ridurrebbero la superficie di contatto. Tre è il numero perfetto, tanto per cambiare. Il ciclo for: iterazione geometrica Il posizionamento dei tre pin introduce una feature di OpenSCAD che non avevamo ancora incontrato: il ciclo for. Diversamente dai linguaggi imperativi, in OpenSCAD for non esegue istruzioni ripetutamente: genera copie multiple dell’oggetto che segue, ciascuna con un valore diverso della variabile di iterazione. for (i = [0 : bayo_pins-1]) { rotate([0, 0, i * 360/bayo_pins]) translate([0, 0, pin_z_on_collar]) bayonet_pin(...); } La sintassi [start : end] produce un range: con bayo_pins = 3, la sequenza è [0, 1, 2]. Si può usare anche [start : step : end] per controllare l’incremento. Il pattern i * 360 / N distribuisce N oggetti in cerchio: ogni copia viene ruotata di un angolo proporzionale al suo indice. Differenza chiave: in un linguaggio imperativo, un for esegue il corpo N volte in sequenza. In OpenSCAD, il for produce N istanze geometriche che vengono automaticamente unite (è implicita una union()). Questo ha due conseguenze: Non potete creare o modificare una variabile dentro il ciclo — le variabili sono immutabili, e comunque ogni iterazione è un contesto indipendente. Il risultato è sempre un singolo solido (l’unione di tutte le copie), non N solidi separati. Se volete solidi indipendenti, dovete usare costrutti diversi (ad esempio moduli separati per ogni pezzo dell’assieme, come già fatto con top_part(), bottom_part() e cap_part()). intersection_for: quando serve l’intersezione La union() implicita del for standard è pratica, ma rende impossibili altri tipi di combinazioni booleana. Il caso classico: volete l’intersezione di N copie di un oggetto, ciascuna ruotata progressivamente. // Questo NON funziona come ci si aspetta: intersection() { for (n = [1 : 6]) { rotate([0, 0, n * 60]) translate([5, 0, 0]) sphere(r = 12); } } L'intersezione di una unione non funziona come ci si aspetterebbe Il problema è che il for interno raggruppa tutte le sfere in una union() prima ancora che intersection() le veda. L’intersezione viene calcolata tra… l’unione di tutte le sfere e nient’altro. Risultato: l’unionee. intersection_for risolve esattamente questo: sintassi identica al for, ma l’unione implicita è sostituita dall’intersezione: // Questo SÌ che funziona: intersection_for(n = [1 : 6]) { rotate([0, 0, n * 60]) translate([5, 0, 0]) sphere(r = 12); } L'intersezione senza unione richiede l'uso di un costrutto for apposito Lo slot a L: tre settori sovrapposti Lo slot è più articolato del pin. Deve permettere l’inserimento assiale, la rotazione e infine il bloccaggio. La logica, però è la stessa, in quanto si realizza con tre ring_sector sovrapposti. Sono tutti concetti che abbiamo già visto: module bayonet_slot_L(r_inner_wall, axial_in, h_pin, w_pin_deg, twist_deg, drop_extra) { eps = 0.05; r1 = r_inner_wall - eps; r2 = r_inner_wall + slot_depth_r + eps; rotate([0, 0, -w_pin_deg/2]) { // 1) Vertical entry slot (open at the bottom edge) translate([0, 0, -eps]) ring_sector(r1, r2, axial_in + eps, w_pin_deg); // 2) Horizontal travel translate([0, 0, axial_in - h_pin - eps]) ring_sector(r1, r2, h_pin + 2*eps, w_pin_deg + twist_deg); // 3) Retention notch at end of travel rotate([0, 0, twist_deg]) translate([0, 0, axial_in - h_pin - eps]) ring_sector(r1, r2, h_pin + drop_extra + 2*eps, w_pin_deg); } } Dettaglio di come si presenta uno slot per l'innesto a baionetta. È, di fstto, un set di tre scassi vagamente a forma di L, distanziati di 120° sulla superficie interna del cilindro Analizziamo i tre settori: Ingresso verticale (settore 1). Parte dal bordo inferiore del manicotto (z = -eps) e sale per axial_in millimetri. Ha larghezza w_pin_deg: quanto basta per far passare il pin. Corridoio orizzontale (settore 2). Si estende per w_pin_deg + twist_deg gradi. Qui il pin può ruotare dopo l’inserimento. La larghezza extra (+ twist_deg) è lo spazio di manovra. Tacca di ritenzione (settore 3). Alla fine del corridoio, un piccolo gradino (drop_extra = 0.5 mm) in cui il pin “cade” e si blocca. Questo è il segreto per cui la baionetta non si svita da sola: la gravità (o una leggera pressione della molla del tappo) tiene il pin nella tacca. Il pattern eps: sovrapposizione anti-artefatti I tre settori sono intenzionalmente sovrapposti (eps = 0.05 mm) per evitare che il motore CSG di OpenSCAD generi facce orfane (non-manifold) nelle zone di confine tra settori adiacenti o perfettamente complanari. Senza questa abbondanza, l’operazione di difference() produrrebbe artefatti visibili e potenziali errori di esportazione STL. Questo pattern è un accorgimento ricorrente nella modellazione CSG e merita un approfondimento. Il problema. Quando due primitive condividono esattamente una faccia (sono complanari), il motore CSG non sa decidere se i punti su quella faccia appartengano a un solido, all’altro, o a nessuno dei due. Il risultato sono micro-fratture nella mesh, normali invertite, o il rifiuto dell’esportazione STL con errori del tipo “Object may not be a valid 2-manifold”. Ma perché succede? La causa è una proprietà intrinseca dell’aritmetica a virgola mobile con cui lavorano tutti i computer. Tre meccanismi cospirano: Numeri con la virgola. I computer rappresentano i numeri reali con precisione finita (tipicamente 15-17 cifre significative). 1/3 non è 0.333... all’infinito ma 0.3333333333333333. È un’approssimazione talmente buona da essere indistinguibile nella stampa, ma sufficiente a confondere il CSG. Rotazioni e trigonometria. Ogni rotate([0, 0, 45]) chiama sin(45°) e cos(45°) che valgono √2/2 ≈ 0.7071067811865476. La radice di 2 è un numero irrazionale — ha infinite cifre decimali. Il computer la tronca. Dopo una rotazione, le coordinate dei vertici non sono mai esattamente dove la geometria “pura” le vorrebbe. Catena di operazioni. Un rotate() seguito da translate(), poi difference(), poi un altro rotate()… ogni passaggio accumula errori di arrotondamento. Due facce che dovrebbero essere a z = 10.0 potrebbero trovarsi una a 9.999999999999996 e l’altra a 10.000000000000002. Per noi sono 10. Per il CSG sono due piani diversi. La conseguenza pratica: ogni union(), esplicita o implicita, richiede che le facce da fondere non siano coincidenti. Se lo sono (o meglio: se il computer pensa nell’incercezza della rappresentazione a virgola mobile che lo siano), il comportamento è indefinito: porzioni di mesh possono sparire dal render, apparire rovesciate, o lampeggiare nella preview. Non è un bug: è il prezzo della rappresentazione discreta dei numeri. La soluzione. Si sommano o si sottraggono piccole quantità (eps, che in effetti sta per epsilon) alle dimensioni degli oggetti, in modo che le superfici adiacenti compenetrino leggermente invece di toccarsi. Nel nostro slot: r1 = r_inner_wall - eps e r2 = r_inner_wall + slot_depth_r + eps: i settori partono prima della parete e arrivano oltre lo scavo. translate([0, 0, -eps]) sul settore 1: l’ingresso inizia appena sotto il bordo inferiore del manicotto, garantendo che non ci sia una linea di giunzione esattamente a z = 0. h_pin + 2*eps nei settori 2 e 3: l’altezza è leggermente maggiorata per sfondare oltre i confini. Quanto piccolo? 0.05 mm è un valore empirico che funziona bene con la risoluzione standard delle stampanti FDM (0.4 mm di ugello, 0.2 mm di layer). È abbastanza grande da risolvere le ambiguità del CSG, ma abbastanza piccolo da non produrre alterazioni visibili nella stampa finale. Se usate $fn molto alti (>200), potete ridurlo a 0.01; se lavorate con mesh a bassa risoluzione, potrebbe servirvi 0.1. Il compromesso. L’eps modifica leggermente le dimensioni reali del modello. Per un passacavo non è un problema (0.05 mm su 60 mm di diametro è lo 0.08%), ma in applicazioni di alta precisione — ingranaggi, accoppiamenti meccanici stretti — va considerato nel calcolo delle tolleranze, aggiungendolo ai parametri di gioco come bayo_slot_play. So che questo introduce tanta frizione nella modellazione, pazienza. Tolleranze: benvenuti nel mondo reale La differenza tra un incastro che funziona e uno che si inceppa sta nei parametri di tolleranza. Nel passacavo sono tutti espliciti: bayo_slot_play = 0.4; // Radial and axial clearance inside the slots bayo_drop_extra = 0.5; // Extra axial drop for retention snap bayo_slot_play aggiunge 0.4 mm di gioco in tutte le direzioni all’interno dello slot. È il parametro che regola la “morbidezza” dell’incastro: aumentatelo se la vostra stampante tende a sovra-estrudere, diminuitelo se l’incastro balla. La profondità radiale dello slot è bayo_pin_t + bayo_slot_play: la sporgenza del pin più il gioco. L’altezza del pin nello slot è bayo_pin_h + 2*bayo_slot_play: altezza pin più gioco assiale sopra e sotto. Questa disciplina — ogni tolleranza è una variabile con nome — è ciò che trasforma un modello “può funzionare” in un modello “funziona sulla tua stampante” e sotto tutte le condizioni di lavoro per cui è progettato. Noterete anche un altro dettaglio: ho più di una tolleranza nel mio progetto. È uno scenario molto comune se lavorate su assiemi. Alcuni elementi del progetto probabilmente avranno, infatti, bisogno di una tolleranza maggiore rispetto agli altri: non dipende, quindi, solo dalla tua stampante, ma anche dal tipo di accoppiamento, dalla frizione meccanica, dal materiale, eccetera. Posizionamento e rotazione di lock Un dettaglio importante: nella vista esplosa (e nell’assieme montato), i pin dell’anello inferiore appaiono già ruotati nella posizione di bloccaggio: module bottom_part_placed(extra_drop = 0) { translate([0, 0, -extra_drop]) rotate([0, 0, -bayo_twist_deg]) bottom_part(); } La rotate([0, 0, -bayo_twist_deg]) pre-ruota l’intero anello in modo che, nella visualizzazione, i pin appaiano già nella tacca di ritenzione — esattamente come sarebbero dopo l’assemblaggio reale. È solo una mia convenzione visiva. In futuro conto di lavorare a delle piccole animazioni degli assiemi esplosi in OpenSCAD generate programmaticamente: aver creato questo modulo mi tornerà utile quel giorno. Riepilogo: cosa abbiamo imparato Una baionetta è preferibile a una filettatura per pezzi stampati in FDM: niente supporti, tolleranze controllabili, geometria semplice. Il for di OpenSCAD non è un ciclo imperativo: genera copie geometriche multiple, implicitamente unite in un unico solido. Il pattern i * 360 / N è il modo standard per distribuire oggetti in cerchio. Il pin è un settore di anello, generato con ring_sector. Lo slot a L è composto da tre settori sovrapposti: ingresso, corsa, ritenzione. Le tolleranze (bayo_slot_play, bayo_drop_extra) sono parametri espliciti. La costante eps previene artefatti CSG con 0.05 mm di sovrapposizione intenzionale tra primitive adiacenti: è un accorgimento universale per evitare geometrie non-manifold. Nella terza e ultima parte della serie realizzeremo il tappo superiore e vedremo come i CAD parametrici possono sfruttare la math library molto di più dei CAD tradizionali. Poi ci concentreremo su un picclo sistema di visualizzazione (mode) che permette di passare dalla vista d’assieme alla vista esplosa, al layout di stampa e all’esportazione STL/3MF. --- title: Introduzione a OpenSCAD: un passacavi da scrivania date: 2026-06-08 url: https://gabrielebaldassarre.com/3d-printing/openscad-tutorial-parte-1/ excerpt: Prima parte sulle basi di OpenSCAD attraverso un progetto reale: un passacavo parametrico da scrivania con attacco a baionetta. Impariamo la sintassi, le primitive CSG, le trasformazioni e l'organizzazione del codice. audience: practitioner proficiency: beginner tags: OpenSCAD, 3D Printing, CAD, modellizzazione parametrica, tutorial, Customizer category: 3D Printing prerequisites: Basi di stampa 3D (slicer, materiali, orientamento); Cos'è il Customizer di Thingiverse; Geometria Solida Costruttiva (CSG); Sistema di coordinate cartesiane 3D (assi X, Y, Z); Progettazione parametrica; Concetti base di programmazione funzionale (immutabilità, trasparenza referenziale) --- Se avete mai usato il Customizer di Thingiverse, conoscete già il principio: un modello 3D non è un oggetto statico, ma un insieme di parametri che potete regolare con degli slider per adattarlo alle vostre esigenze. Quello che il Customizer fa dietro le quinte è eseguire un programma scritto in OpenSCAD, un linguaggio di programmazione per la modellazione solida che descrive la geometria invece di disegnarla. OpenSCAD è uno strumento che, in effetti, agli inizi può intimidire non poco: l’idea di costruire un solido (o un assieme) a partire da primitive definite con un linguaggio di programmazione, quindi tutto a codice suona una cosa davvero da pazzi, soprattutto se lì fuori ci sono Fusion 360 e Blender — tanto per citare un paio di pezzi grossi. Ma ci sono almeno tre buoni motivi per abbracciare questa dose di follia: È incredibilmente elegante e soddisfacente. Si impara tantissimo sulla geometria solida. Essendo tutto a codice, i moderni LLM possono dare una gran mano sia nella generazione ma (soprattutto) nel debugging e nella parametrizzazione di un solido. In questa serie di tre articoli vi accompagno nella realizzazione di un progetto completo: un passacavo parametrico da scrivania con attacco a baionetta. L’oggetto finale è composto da tre pezzi — un manicotto superiore con flangia, un anello inferiore con innesto a baionetta e un tappo con apertura parabolica — e si adatta parametricamente al diametro del foro e allo spessore del piano. In questa prima parte ci concentreremo sul linguaggio: sintassi, primitive, trasformazioni e organizzazione del codice. Useremo il passacavo come esempio concreto per ogni concetto, ma l’obiettivo è darvi le basi per scrivere (o leggere) qualsiasi file .scad. La sintassi di OpenSCAD in dieci minuti OpenSCAD non è un linguaggio general-purpose come Python o JavaScript, o comunque non è un linguaggio imperativo: è un linguaggio di descrizione geometrica di tipo . C’è un insieme ridotto di costrutti, tutti orientati a un unico scopo: costruire solidi. Ecco le regole fondamentali, quelle che incontrerete in ogni file .scad: Terminatore. Ogni istruzione termina con un punto e virgola ;. Dimenticarlo è l’errore più comune (e più frustrante) per un principiante. cube([10, 20, 5]); // corretto cube([10, 20, 5]) // ERRORE: manca il ; Commenti. Come in C e JavaScript: // per una riga, /* ... */ per blocchi. OpenSCAD usa i commenti anche per istruzioni speciali destinate al Customizer (ci arriviamo). // Questo è un commento su una riga /* Questo è un commento su più righe */ Variabili. Si assegnano con =. Per la , una volta assegnate, sono immutabili: non potete riassegnarle nello stesso scope. Questo confonde chi viene da linguaggi imperativi, il cui concetto equivalente è quello della costante ma ha una logica: in un modello parametrico, ogni grandezza ha un solo valore in un dato contesto. hole_dia = 60; // numero wall = 3.0; // numero decimale material = "PLA"; // stringa (rara, ma esiste) Vettori. Le coordinate e le dimensioni sono vettori tra parentesi quadre. Un punto nello spazio è [x, y, z], una dimensione è [larghezza, profondità, altezza]. [0, 0, 10] // punto: x=0, y=0, z=10 [20, 30, 5] // cuboide: 20×30×5 Moduli. Sono l’equivalente delle funzioni in altri linguaggi. Si definiscono con module nome(parametri) { ... } e si richiamano per nome. Sono il mattone fondamentale per organizzare il codice. module my_peg(h, d) { cylinder(h = h, d = d); } my_peg(10, 5); // richiamo il modulo In realtà, il costrutto dei linguaggi imperativi più vicino al concetto di modulo è quello delle procedure: come queste ultime, infatti, non è previsto un valore di ritorno (che, invece, è sempre previsto nelle funzioni, che sia anche void o NULL). Proviamo a linearizzare la struttura, solo per farci uno schema mentale: flowchart LR subgraph M[Modulo] direction LR V[Parametri e variabili] --> P2[Primitive 2D] V --> P3[Primitive 3D] V --> T[Trasformazioni] P2 --> E[Estrusioni] --> T P3 --> T T --> CSG[Operazioni CSG] --> O[Solido] end Ordine dei comandi — la notazione prefissa. Attenzione! In OpenSCAD, trasformazioni, estrusioni e operazioni CSG si scrivono prima dell’oggetto (o del blocco) a cui si applicano: translate([10, 0, 0]) // ← modificatore (padre) cube([5, 5, 5]); // ← oggetto modificato (figlio) cube è il figlio di translate. Il modificatore si applica solo all’oggetto immediatamente successivo. Se dovete applicarlo a più oggetti, li racchiudete tra parentesi graffe: translate([10, 0, 0]) { // si applica a tutto il blocco cube([5, 5, 5]); cylinder(h = 3, d = 8); } L’indentazione non è obbligatoria ma è una convenzione universale per mostrare la gerarchia padre-figlio. Questa sintassi — chiamata prefix notation — è il fondamento di tutto OpenSCAD: la incontrerete in ogni trasformazione, estrusione, operazione booleana. Impararne la logica adesso vi eviterà ore di debug su parentesi fuori posto. Riepilogo sintattico: ; — termina ogni istruzione // e /* */ — commenti variabile = valore; — assegnazione (immutabile) [a, b, c] — vettore (punto o dimensione) module nome(...) { ... } — definizione di modulo nome(...); — richiamo di modulo Con queste sei regole avete già abbastanza per leggere la struttura di qualsiasi file OpenSCAD. Ora vediamo cosa potete costruirci. Primitive 3D Tutto ciò che viene creato in OpenSCAD parte da un insieme di forme elementari chiamate primitive. Sono poche, ma combinandole con le operazioni booleane che vedremo dopo, è possibile costruire qualsiasi geometria. cube([w, d, h]) — un parallelepipedo. Se passate un singolo numero (cube(5)) ottenete un cubo. Con il parametro center = true il cubo è centrato nell’origine invece che appoggiato sul piano XY. cube([20, 30, 5]); // 20×30×5, appoggiato su Z=0 cube([10, 10, 10], center=true); // 10×10×10, centrato nell'origine Un parallelepipedo 10x5x5 centrato nell'origine, creato a partire dalla primitiva sphere(r) o sphere(d) — una sfera di raggio r (o diametro d). Con $fn controllate il numero minimo di segmenti del solido: valori bassi danno un poliedro, valori alti una sfera liscia. $fn = 6 produce un cubo! Il parametro $fa (minimum angle) è un’alternativa più elegante quando non volete hardcodare la risoluzione. sphere(r = 10); // sfera di raggio 10 mm sphere(d = 20, $fn=64); // sfera liscia di diametro 20 mm Una sfera di diametro 10, appoggiata al piano di base e con $fn=128 cylinder(h, r) o cylinder(h, d) — un cilindro di altezza h. r1 e r2 (o d1 e d2) diversi tra loro producono un tronco di cono. Anche qui $fn controlla la risoluzione del cerchio di base. cylinder(h = 15, d = 10); // cilindro cylinder(h = 5, d1 = 10, d2 = 6); // tronco di cono cylinder(h = 25, d = 60, $fn = 128); // cilindro liscio Un cilindro appoggiato al piano di base, di diametro 35 ed altezza 55, con $fn = 128$ Riepilogo primitive 3D: cube([w, d, h]) o cube(size, center) sphere(r) o sphere(d) — risoluzione via $fn o $fa cylinder(h, r) o cylinder(h, d, r1, r2) — risoluzione via $fn o $fa polyhedron(points, faces) — per geometrie arbitrarie (avanzato) Primitive 2D: le forme piatte OpenSCAD ha anche primitive bidimensionali. Da sole non servono a molto — ma quando le combinate con linear_extrude() o rotate_extrude(), diventano la base per dei solidi 3D arbitrariamente complessi. square([w, h]) — un rettangolo. Con center = true è centrato nell’origine. square([20, 10]); // rettangolo 20×10 square(5, center = true); // quadrato 5×5 centrato circle(r) o circle(d) — un cerchio. Stessa logica di $fn della sfera e del cilindro. circle(r = 10, $fn = 64); // cerchio liscio polygon(points) — un poligono arbitrario definito da una lista di punti. polygon([[0,0], [10,0], [5,10]]); // triangolo Riepilogo primitive 2D comuni: square([w, h]) o square(size, center) circle(r) o circle(d) — risoluzione via $fn polygon(points) — poligono arbitrario text("...") — testo Trasformazioni: spostare, ruotare, scalare Le primitive da sole producono oggetti centrati nell’origine. Per costruire un assieme, dovete spostarle, ruotarle, ridimensionarle. Le trasformazioni si applicano a tutto ciò che sta dentro le parentesi graffe che le seguono. translate([x, y, z]) — traslazione. Sposta l’oggetto nello spazio. translate([10, 0, 0]) cube([5, 5, 5]); // cubo spostato di 10 mm sull'asse X rotate([x, y, z]) — rotazione. Gli angoli sono in gradi, applicati nell’ordine X → Y → Z. Una rotate([0, 0, 45]) ruota di 45° attorno all’asse Z (come un giro sul piano). rotate([0, 0, 45]) cube([10, 5, 3]); // ruotato di 45° su Z scale([x, y, z]) — ridimensionamento. Moltiplica le dimensioni per i fattori indicati. scale([1, 1, 0.5]) schiaccia l’oggetto a metà altezza. mirror([x, y, z]) — specchiatura. Riflette l’oggetto rispetto al piano passante per l’origine e perpendicolare al vettore dato. Riepilogo trasformazioni comuni: translate([x, y, z]) — traslazione rotate([ax, ay, az]) — rotazione in gradi (ordine X→Y→Z) scale([sx, sy, sz]) — ridimensionamento mirror([nx, ny, nz]) — specchiatura color("nome") — colore in anteprima (non influisce sulla geometria) resize([w, d, h]) — ridimensiona forzando le dimensioni assolute Estrusioni: dalla seconda alla terza dimensione Le primitive 2D da sole non producono oggetti stampabili: sono sagome piatte, senza spessore. Per trasformarle in solidi si usano le estrusioni, che sono il ponte tra il mondo 2D e quello 3D. linear_extrude(height) — prende una forma 2D e la “tira” verso l’alto per un’altezza height, creando un solido prismatico. È l’equivalente dell’operazione di, appunto, estrusione dei CAD parametrici come Fusion 360. linear_extrude(height = 5) polygon([[0,0], [10,0], [5,10]]); Prisma a base triangolare ottenuto estrudendo un triangolo di 5 nella direzione positiva dell'asse Z (comportamento di default) Con il parametro twist potete ruotare la sezione durante l’estrusione, utile ad esempio per creare strutture elicoidali. Notate come anche queste trasformazioni supportano il parametro $fn. convexity viene, invece, utilizzato per suggerire a OpenSCAD il profilo di convessità del solido, laddove il CAD non riesca (accade spesso) a risolvere fori e cavità. 10 è un ottimo valore di default con cui iniziare (e spesso non è necessario cambiarlo). linear_extrude(height = 15, convexity = 10, twist = 1440, $fn = 32) translate([2, 0, 0]) circle(r = 1); Boing boing... rotate_extrude(angle) — prende una forma 2D e la ruota attorno all’asse Z, creando un solido di rivoluzione. Se angle è 360°, ottenete un toroide completo; angoli minori producono settori. È uno dei modi per ottenere l’equivalente dell’operazione revolve, ovvero solidi di rotazione. rotate_extrude(angle = 180, $fn = 128) translate([15, 0, 0]) square([5, 10]); La translate([15, 0, 0]) sposta il quadrato lontano dall’asse Z prima della rivoluzione: senza questa traslazione, il solido collasserebbe su sé stesso (ruotando una forma che contiene l’origine si ottiene un cilindro pieno, non un guscio). Un semiguscio toroidale ottenuto facendo ruotare un rettangolo traslato di 15 sul semiasse positivo dell'asse X e poi effettuando una rivoluzione di 180° in modo da creare un solido con almeno 128 segmenti. Nel progetto del passacavo, rotate_extrude è usato, ad esempio, per creare i settori di anello che compongono lo slot a L dell’attacco a baionetta attraverso un modulo riutilizzabile (visto che di scassi ne sono previsti 3, sfalsati di 120°): module ring_sector(r1, r2, h, a) { rotate_extrude(angle = a, $fn = 160) translate([r1, 0, 0]) square([r2 - r1, h]); } Qui square([r2-r1, h]) è la sezione rettangolare, traslata a distanza r1 dall’asse, ruotata di a gradi. Il risultato è un settore di anello — il mattone con cui costruiremo pin e slot nella seconda parte della serie. Riepilogo estrusioni: linear_extrude(height) — estrusione lineare (+ twist, scale, center) rotate_extrude(angle) — estrusione per rivoluzione (+ $fn per la risoluzione angolare) Operazioni CSG: unite, scavate, intersecate Ora avete i mattoni e sapete spostarli. Il passo successivo è combinarli. Le tre operazioni fondamentali della Constructive Solid Geometry (CSG) sono il cuore di OpenSCAD. union() — unione Raggruppa più oggetti in uno solo. È il modo in cui componete un pezzo a partire da primitive distinte. Nel passacavo, il manicotto superiore unisce un cilindro (il corpo tubolare) e un disco (la flangia d’appoggio): union() { // Corpo tubolare cylinder(h = sleeve_h, d = sleeve_od); // Flangia superiore, traslata in cima al manicotto translate([0, 0, sleeve_h]) cylinder(h = top_lip_h, d = top_lip_od); } Flangia e manicotto Notate come translate sposti solo il secondo cylinder, non il primo: le trasformazioni dentro union() (o qualsiasi altro blocco) si applicano solo agli oggetti che seguono immediatamente, seguendo la prefix notation (per questo motivo delimitare i comandi con ; è fondamentale!). difference() — sottrazione Sottrae uno o più oggetti da un oggetto base. Il primo figlio è il solido positivo, tutti i successivi sono sottratti. È così che si creano fori, cave, scanalature: difference() { // Solido base: manicotto + flangia union() { cylinder(h = sleeve_h, d = sleeve_od); translate([0, 0, sleeve_h]) cylinder(h = top_lip_h, d = top_lip_od); } // Foro passante per i cavi (sottratto) cylinder(h = sleeve_h + top_lip_h + 4, d = sleeve_id); } Il manicotto finito Come potete vedere, il secondo cylinder (diametro sleeve_id, altezza maggiorata + 4) viene sottratto dal primo, creando un tubo cavo. L’altezza maggiorata garantisce che il foro attraversi completamente entrambi i pezzi senza lasciare superfici complanari — che in OpenSCAD causano artefatti di rendering, specie durante l’esportazione. Questo pattern — esagerare le dimensioni dell’oggetto da sottrarre in modo che “sbuchi” sempre oltre le superfici. Per gli utenti Fusion 360, dove pure è possibile far così invece di un extrude cut con to = object più elegante, è sinonimo di pigrizia. Qui, invece, ci mette al riparo da eventuali complicanze geometriche che sono molto ostiche su OpenSCAD (per via di funzionalità del tutto assenti nel tool di Autodesk). Essendo il cut incapsulato in una difference non c’è, poi, il rischio di tagliare inavvertitamente oggetti che, invece, dovrebberro essere preservati - errore piuttosto comune in Autodesk. Morale della favola: si estrude giù fino alle cantine e si dorme sereni. Il senso è quello. intersection() — intersezione Mantiene solo il volume comune a due o più oggetti. intersection() { cube([10, 10, 10]); sphere(r = 7); } // Risultato: un cubo con gli angoli sferici Riepilogo operazioni CSG comuni: union() — unisce oggetti difference() — sottrae dal primo oggetto tutti i successivi intersection() — mantiene solo il volume comune hull() — crea l’inviluppo convesso tra due o più oggetti minkowski() — somma di Minkowski (avanzato) Anatomia di un file OpenSCAD Ora che avete il vocabolario di base, possiamo guardare come è organizzato il file completo del passacavo (customizable-cable-grommet.scad). La struttura che uso sempre, con piccole variazioni, è questa: Header: nome, descrizione dei componenti, licenza d’uso, ecc. Parameter panel: variabili raggruppate per sezione, con range per il Customizer Derived geometry: variabili calcolate che dipendono dai parametri Helpers: moduli riutilizzabili di uso generale Modules: un modulo per ogni pezzo fisico Assembly: composizione dei pezzi Layout: switch per modalità di visualizzazione Il Parameter Panel La parte più visibile del file, specialmente se lo aprite nel Customizer di Thingiverse, è il pannello dei parametri. In OpenSCAD si scrive così: /* [Hole & Desk] */ // Diameter of the hole in the desk panel (mm) hole_dia = 60; // [40:1:120] // Thickness of the desk panel (mm) desk_thickness = 25; // [10:1:60] Due convenzioni importanti: I commenti /* [Nome Sezione] */ vengono interpretati dal Customizer per creare gruppi di slider nell’interfaccia utente. I commenti // [min:step:max] dopo una variabile definiscono il range dello slider. I parametri non sono solo numeri: sono il contratto del modello. Chiunque apra il file sa immediatamente cosa può modificare e entro quali limiti. Nel passacavo, i parametri sono organizzati in sezioni logiche: [Hole & Desk], [General Tolerances], [Top Ring], [Bottom Ring], [Bayonet Lock], [Cap Snap-In], [Parabolic Opening], [View], [Render]. Per un progetto parametrico ben fatto, la regola è: ogni numero che potrebbe cambiare deve essere una variabile nel parameter panel. Il modello completo: tre pezzi, un assieme Il passacavo è composto da tre moduli indipendenti: top_part() — il manicotto superiore con flangia d’appoggio. Contiene gli slot a L per la baionetta e la gola per lo snap-fit del tappo. bottom_part() — l’anello inferiore con collare. Sul collare sporgono i pin radiali. cap_part() — il tappo a scatto con apertura parabolica. Ogni modulo è un pezzo fisico distinto, stampabile indipendentemente. Sono moduli separati proprio per questo: potete esportare solo il top_part() se dovete ristampare solo quello. Organizzare il codice: le regole che uso Dopo aver scritto parecchi progetti in OpenSCAD, ho consolidato alcune convenzioni: Un modulo, un pezzo. top_part(), bottom_part(), cap_part() sono moduli separati. Niente numeri magici. 3 non significa niente; wall = 3.0 con commento // Sleeve wall thickness sì. Tolleranze esplicite. clearance, bayo_slot_play, cap_skirt_clearance sono variabili dedicate. Commenti che spiegano il perché. Non // raggio = 3 ma // 0.4 mm radial clearance for sliding fit. Sezioni marcate con delimitatori. // ====== per separare logica, geometria, moduli. Nelle prossime due parti della serie vedremo la geometria dell’attacco a baionetta e il sistema di modalità di visualizzazione con esportazione per la stampa. --- title: Monitoraggio del SEO: organizziamo i dati date: 2026-05-28 url: https://gabrielebaldassarre.com/devops/seo-automatico-parte-2-monitoraggio/ excerpt: Seconda parte della pipeline SEO con strumenti gratuiti e automatici: un data lake su Cloudflare R2 e D1, Google Trends e Search Console, dbt per l'ETL e un report mensile automatizzato, il tutto eseguito tramite GitHub Actions. Gratis. audience: practitioner proficiency: advanced tags: SEO, Jekyll, GitHub Actions, Cloudflare, dbt, Google Trends, Search Console, sqlite, datawarehousing category: DevOps prerequisites: SEO; GitHub Actions; Cloudflare R2; Cloudflare D1; dbt (data build tool); ETL (Extract, Transform, Load); Jekyll; Data Lake; Data Warehouse; SQLite; Google Trends; Google Search Console; SerpApi; API REST; Amazon S3; PageSpeed Insights; Lighthouse; CI/CD; Slowly Changing Dimension Type 2; Change Data Capture; Core Web Vitals; Large Language Model; Data Mart; Schema dimensionale (dim/fact); Cloudflare Pages; Python; Click-Through Rate; IndexNow; SQL; Wrangler (Cloudflare CLI); Terraform; HMAC; boto3 (AWS SDK for Python); Bearer token; ISO 8601; Ontologia (informatica); Object Storage; Principio del privilegio minimo; npm --- Nella prima parte abbiamo costruito il blocco 1 della pipeline SEO: il supporto all’autorialità. A ogni deploy, un workflow Github andava ad analizzare in ottica SEO i post nuovi o modificati tramite (individuati mediante git diff) e per ognuno di essi interrogava Lighthouse, PageSpeed Insights e IndexNow, segnalando potenziali problemi e fornendo indicazioni. Funziona. Ma è un’istantanea. Un controllo puntuale, una tantum. Quello che mancava era la dinamica del sistema, la sua dimensione temporale. Il SEO non è un esame che passi una volta e sei a posto. È un ecosistema vitale: i contenuti invecchiano, le keyword cambiano trend, Google aggiorna l’algoritmo, i crawler AI iniziano a citarti (o smettono…ci torneremo). In generale, come ho detto altrove, evito che l’ansia da prestazione mi rovini il piacere della scrittura. Tuttavia, è innegabile, visitatori sul blog e commenti agli articoli sono la benzina motivazionale di chi ama la divulgazione e senza monitoraggio, senza la più vaga idea di cosa stia dentro e fuori il tuo blog, finisci presto in riserva. Così è nato il blocco 2: il monitoraggio SEO, appunto. In free tier, tanto per cambiare. – flowchart LR subgraph Sources["🔍 Fonti esterne"] GT["Google Trends"] GSC["Search Console"] PS["PageSpeed Insights"] LH["Lighthouse"] end subgraph Repo["📝 Repository del blog"] GIT["git diff<br>(modifiche ai post)"] end subgraph Cloudflare["☁️ Cloudflare"] R2[("R2<br>Object Storage<br>(Data Lake)")] D1[("D1<br>SQLite Serverless<br>(Data Warehouse)")] end subgraph GHA["⚙️ GitHub Actions (cron weekly)"] direction TB ETL["🔄 dbt<br>Staging → Analytics"] ALERTS["⚠️ Alert<br>(soglie SEO)"] REPORT["📋 Report<br>Mensile"] ETL --> D1 D1 --> ALERTS D1 --> REPORT end GT --> R2 GSC --> R2 PS --> R2 LH --> R2 GIT -->|"post_history"| D1 R2 -->|"snapshot JSON"| ETL style Cloudflare fill:#f5f0e8,stroke:#d4a76a,color:#2c1810 style GHA fill:#e8f0f5,stroke:#6a9fd4,color:#102840 style Sources fill:#e8f5e8,stroke:#6ad46a,color:#104010 style Repo fill:#f0e8f5,stroke:#a06ad4,color:#280840 Architettura: da zero a data lake in tre mosse Il blocco 2 non è collegato al blocco 1, che scattava andando a fare le pulci ai post nuovi e modificati. È un workflow indipendente di buon, vecchio ETL, schedulato ogni lunedì alle 8:00 del mattino che: Raccoglie dati dal mondo esterno (Google Trends, Search Console) Consolida i dati già prodotti dal blocco 1 (PageSpeed, Lighthouse) in un archivio permanente Trasforma i dati grezzi in un piccolo datawarehouse Allerta se qualcosa supera le soglie Produce un report mensile Il tutto poggia su due servizi Cloudflare: R2 (object storage): il Data Lake. D1 (serverless SQLite): il DWH. Perché Cloudflare e non, chessò, un PostgreSQL su Railway, o uno qualsiasi dei mille mila strumenti disponibili sui provider cloud maggiori, come AWS, GCP o Azure? Diversi motivi: Il free tier di Cloudflare è davvero generoso per un blog personale — ci sto dentro di tre ordini di grandezza I servizi Cloudflare sono, in genere, molto semplici da configurare. Nel caso specifico, mi sembrava esagerato preparare un ambiente terraform per questo setup e la CLI di cloudflare, ovvero wrangler, si è dimostrata intuitiva, immediata e facilmente adattabile a Github Actions. L’intero blog è già su Cloudflare Pages, quindi tutto l’ecosistema è co-locato, garantendomi zero latenza di rete tra R2 e D1 quando si fanno operazioni di ETL. Questo più a valore di proof of concept, per la verità, dato i numeri più che esigui mossi da un banale blog di provincia. In generale, comunque, avevo voglia di sperimentare con i servizi Cloudflare che, appunto, sono spesso bistrattati a favore dei soliti tre pezzi grossi del cloud e che, invece, a mio parere sono sorprendentemente adatti per diverse classi di casi d’uso, a una frazione del costo e con molto meno complessità di deploy. Ma sto divagando. Dove eravamo rimasti? Step 1: preparare l’infrastruttura Cloudflare Prima di scrivere una riga di Python, prepariamo due bei barili dove buttare i dati. Cloudflare mette a disposizione due servizi perfetti per un data lake da quattro soldi (nel senso che costa poco): R2 per l’object storage e D1 per il database SQLite serverless. Come detto, la CLI wrangler è stata una scoperta davvero piacevole. Installiamolo, tanto per cominciare: npm install -g wrangler wrangler login Fatto. Ora possiamo creare tutto ciò che ci serve. R2: il data lake R2 è un object storage compatibile con l’API S3 di AWS. Non è un filesystem, non è un database: è un posto dove butti oggetti (nel nostro caso, JSON) e li recuperi quando ti servono, pagando solo per ciò che consumi. Qualsiasi libreria o tool che sa parlare S3 — boto3, awscli, rclone — funziona con R2 senza modifiche. Creare un bucket è una riga: wrangler r2 bucket create unfantasticobucket-seo --location weur Il location hint weur (Western Europe) mette il bucket fisicamente vicino al resto dell’infrastruttura Cloudflare, riducendo la latenza delle operazioni di ETL che vedremo dopo. Con wrangler r2 bucket list potete verificare che sia stato creato correttamente. Tutto a posto? Ok, continuiamo. D1: il database D1 è SQLite, ma serverless. Dialetto SQL da esame di Basi di Dati all’Università, niente estensioni esotiche, configurazioni e fine-tuning. È perfetto per un progetto come questo, dove i dati sono pochi, le query sono semplici e la latenza di un round-trip HTTP è irrilevante. Altra riga: wrangler d1 create unfantasticodatabase-seo Output: ✅ Successfully created DB 'unfantasticodatabase-seo' in region EEUR database_id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Il database_id è l’UUID che useremo per tutte le chiamate API successive. Salvatevelo da qualche parte (o, meglio ancora, lasciate che sia GitHub Actions a ricordarlo — ci arriviamo). A questo punto il database esiste ma è vuoto. Serve lo schema. Ho preparato un file di schema per la preparazione iniziale con tutte le tabelle, gli indici e i vincoli di unicità, eccetera. Lo applico con: wrangler d1 execute unfantasticodatabase-seo --remote --file=seo-schema.sql La flag --remote è importante: senza, wrangler prova a eseguire il SQL su un database D1 locale (che non esiste). Con --remote colpisce direttamente il server Cloudflare. Output: 🌀 Executing on remote database unfantasticodatabase-seo (XXXXXXXX-...): 🚣 Executed 17 queries in 4.45ms Per adesso non entriamo nel dettaglio del modello dati, lo faremo poi. I token: ogni servizio il suo Qui arriva l’unica parte che, per mia scelta, ho deciso di non fare via CLI, limitando quel tipo di grant a wrangler, ovvero la creazione dei token di autenticazione. Ne servono due, con ambiti di accesso separati (principio del privilegio minimo, che non fa mai male): R2 API Token. Si crea da Dashboard → R2 → Manage R2 API Tokens. Va configurato con permessi di Object Read & Write, limitato al nostro bucket unfantasticobucket-seo. Il token produce una coppia Access Key ID + Secret Access Key, identica a quella di AWS IAM. boto3 (o chi per essa) la userà per autenticare le chiamate S3. D1 API Token. Si crea da Dashboard → Profile → API Tokens → Create Custom Token, con permesso Account → D1 → Edit. Questo token va in Authorization: Bearer <token> nelle chiamate HTTP all’API REST di D1. Perché due token separati? Perché R2 usa l’autenticazione S3 (basata su firma HMAC), mentre D1 usa l’API REST di Cloudflare (basata su Bearer token). Sono due meccanismi diversi e, soprattutto, due superfici di attacco diverse. Se uno dei due token venisse compromesso, l’altro servizio resterebbe isolato. I secrets su GitHub Nella prima parte abbiamo già visto come GitHub Actions gestisce le credenziali sensibili tramite Secrets. Si configurano in Settings → Secrets and variables → Actions e sono accessibili nel workflow con la sintassi ${{ secrets.NOME_SECRET }}. Se avete dubbi sul meccanismo, recuperate quel paragrafo prima di procedere. Per il blocco 2, i secrets da configurare per la parte di infrastruttura sono questi: CLOUDFLARE_ACCOUNT_ID — il vostro Account ID Cloudflare (lo trovate nella dashboard in alto a destra, o con wrangler whoami) CLOUDFLARE_R2_ENDPOINT — l’endpoint S3 del bucket: https://<ACCOUNT_ID>.r2.cloudflarestorage.com CLOUDFLARE_R2_ACCESS_KEY_ID — l’Access Key ID del token R2 (step precedente) CLOUDFLARE_R2_SECRET_ACCESS_KEY — la Secret Access Key del token R2 CLOUDFLARE_R2_SEO_BUCKET — il nome del bucket: gabrielebaldassarre-seo CLOUDFLARE_D1_API_TOKEN — il token API D1 CLOUDFLARE_D1_DATABASE_ID — l’UUID del database D1 Alcuni di questi probabilmente li avete già configurati per il blocco 1. Controllate che siano tutti presenti prima di lanciare il workflow. Con questo, l’infrastruttura è pronta. Abbiamo un bucket R2 che farà da data lake, un database D1 che ospiterà i dati strutturati, e tutti i token necessari perché GitHub Actions possa parlare con entrambi. Ora possiamo passare alla ciccia vera. Step 2: arricchire la pipeline SEO principale La prima modifica è stata alla pipeline principale, quella di supporto autoriale. Nella precedente versione, gli strumenti che erano in grado di produrre output strutturato, ad esempio PageSpeed Insights e Lighthouse, lo facevano sottoforma di GitHub Artifacts JSON. Dopo un mese, ruotavano via, spesso prima che potessi consultarli e con l’impossibilità di fare una analisi storica. Il primo passo è stato far persistere questi file su R2, su cui copio i resoconti in JSON (mentre gli HTML, pensati per ispezione manuale, restano come artifact. Continuerò a non guardarli mai). Il path include il timestamp ISO 8601 — ogni run è una snapshot immutabile. I GitHub Artifacts restano come cache a breve termine (30 giorni), la fonte di verità diventa R2. In più, anche in questa fase c’è una componente di job di change tracking che, a ogni deploy, calcola il git diff dei post modificati e lo salva sia su R2 (archivio raw) sia su D1 in una tabella post_history strutturata: commit hash, data, messaggio, tipo di modifica (created/modified/deleted), righe aggiunte e rimosse. Un diario delle modifiche insomma: ci sarà molto utile quando introdurremo l’ontologia della SEO — l’ontologia semantica del blog che collegherà concetti, tag, keyword trends e modifiche ai contenuti. Ma questa è roba per il blocco 3. Google Trends: cosa cerca la gente? Il blocco 1 mi dice come sto. Il blocco 2 mi dice cosa dovrei scrivere. Ho integrato Google Trends tramite serpapi (100 chiamate/mese gratis, per un blog bastano…per poco, ma bastano). Le keyword da monitorare non le ho scelte io: le estrae automaticamente il sistema: Categorie del blog — “DevOps”, “Fisica”, “Reti Sociali”, “Home Assistant”, “3d Printing”…sono scritte li sopra. Top-10 tag per frequenza — calcolati dinamicamente raccogliendo i metadati dai frontmatter dei post Rising queries — le query in crescita che Google Trends associa a ogni keyword seed. Il seed set è di circa 25-30 keyword per run. Per ognuna, salvo l’interesse nel tempo (90 giorni), la distribuzione geografica (Italia) e le query correlate in crescita. Queste ultime sono il vero regalo della pipeline: ogni settimana scopro cosa sta cercando la gente adesso, e posso decidere se scriverne. I dati finiscono su R2 (raw) e su D1 (keyword_trends) per le query analitiche. Un esempio di serie storica di Google Trends: sono questi i dati che saranno recuperati e salvati nel database D1 Google Search Console: come mi trovano? Se Google Trends ti dice cosa cercano, Search Console ti dice come ti trovano. Ho integrato l’API ufficiale di Google Search Console tramite una service account: Query metriche: top 100 query di ricerca, con click, impressions, CTR e posizione media Page metriche: le stesse metriche, per ogni URL del blog Finestra: 30 giorni, allineata alla schedulazione weekly Anche questi dati vanno su R2 (raw) e D1 (search_console_data). La tabella ha colonne per query, page, clicks, impressions, ctr, position, device, country — abbastanza per fare analisi di coverage e identificare i contenuti che performano meglio (o peggio) del previsto. dbt: dal data lake al data warehouse Fin qui abbiamo dati grezzi in R2 e D1. Ma da qui a creare un DWH ce ne corre. Servono trasformazioni: pulizia, deduplicazione, aggregazione, modellazione dimensionale, indici… Per questo ho scelto dbt (data build tool). dbt è lo standard de facto per la trasformazione a riga di comando di dati in ambienti analitici. È totalmente dichiarativo: scrivi modelli SQL, dichiari le dipendenze tra di essi, e dbt li esegue nell’ordine giusto, con test di qualità e contratti di schema. Non è un orchestratore — è più un resolver. Ora, non intendo trasformare questo articolo in tutorial su dbt (ci torneremo), ma diamo comunque un’occhio alla catena di trasformazione del dato. Il progetto dbt (_dbt/) è strutturato in due layer: flowchart TB subgraph Raw["📥 Dati grezzi su R2 e D1"] R2P["R2: snapshot JSON<br>PageSpeed / Lighthouse"] D1P["D1: post_history<br>(git diff dei post)"] D1K["D1: keyword_trends<br>(Google Trends)"] D1S["D1: search_console_data<br>(Search Console)"] end subgraph Staging["🧹 Staging — pulizia e normalizzazione"] STG1["stg_seo_snapshots"] STG2["stg_post_history"] STG3["stg_keyword_trends"] STG4["stg_search_console"] end subgraph Analytics["📐 Analytics — modelli dimensionali"] DIM["dim_posts<br>SCD Type 2"] FCT1["fct_seo_metrics<br>KPI SEO giornalieri"] FCT2["fct_post_activity<br>attività autoriale"] FCT3["fct_keyword_performance<br>trend keyword"] end R2P --> STG1 D1P --> STG2 D1K --> STG3 D1S --> STG4 STG1 --> FCT1 STG2 --> DIM STG2 --> FCT2 STG3 --> FCT3 STG4 --> DIM style Raw fill:#f5f0e8,stroke:#d4a76a,color:#2c1810 style Staging fill:#e8f0f5,stroke:#6a9fd4,color:#102840 style Analytics fill:#e8f5e8,stroke:#6ad46a,color:#104010 Staging (pulizia e normalizzazione) stg_seo_snapshots → deduplica i JSON di PageSpeed/Lighthouse - elimina i duplicati e trasforma le strutture in oggetti tabellari (che possono essere salvati in un database) stg_post_history → funge da livello base di _change data capture_: usa `git diff` per estrarre i post che sono stati aggiunti o modificati e in che modo stg_keyword_trends → normalizza i dati di Google Trends stg_search_console → deduplica e normalizza i dati di Google Search Console Analytics (modelli dimensionali) Un livello di astrazione del dato in cui è più facile condurre analisi e interrogazioni. Pur molto semplice, è in tutto e per tutto un DWH in miniatura: dim_posts → Dimension SCD Type 2: storico delle modifiche a titoli, tag e categoria, mantenendo lo storico e le evoluzioni di tutti i cambiamenti fct_seo_metrics → Fatti SEO (performance, accessibility, LCP, CLS, etc.) fct_post_activity → Fatti autoriali (aggiunte, modifiche dei post, cambiamenti nei tag, ecc.) fct_keyword_performance → Fatti esogeni: copertura e trend delle keyword in ascesa, evidenze di possibili nicchie di contenuto da presidiare con nuovi articoli, ecc. Ecco il modello ER completo di ciò che vive su D1 — le tabelle raw (a sinistra) e i modelli analytics che dbt genera (a destra): erDiagram posts ||--o{ seo_snapshots : "post_urn" posts ||--o{ post_history : "post_urn" posts ||--o{ dim_posts : "urn ← dbt SCD2" post_history ||--o{ fct_post_activity : "dbt window" seo_snapshots ||--o{ fct_seo_metrics : "dbt pivot" keyword_trends ||--o{ fct_keyword_performance : "keyword" search_console_data ||--o{ alerts : "url match" posts { text urn PK "urn:cat:slug" text title text category text slug text tags "JSON array" text file_path text first_seen_at text last_modified_at } seo_snapshots { int id PK text post_urn FK text url text checked_at text source "pagespeed|lighthouse" text category "performance|accessibility|seo|best-practices" text metric_name "score|lcp|tbt|cls|fcp|tti|speed_index" real metric_value text device "mobile|desktop" } post_history { int id PK text post_urn FK text commit_hash text commit_date text commit_message text change_type "created|modified|deleted" int lines_added int lines_removed int diff_size text raw_diff text changed_at } keyword_trends { int id PK text keyword UK text fetched_at UK text interest_over_time "JSON timeseries" text interest_by_region "JSON" text related_rising "JSON top-5" text related_top "JSON top-5" } search_console_data { int id PK text query "nullable" text page "nullable" text data_date UK text fetched_at int clicks int impressions real ctr real position text device UK "desktop|mobile|tablet" text country UK "ita|eng|..." } alerts { int id PK text url text metric real current_value real threshold_value text comparison "lt|gt" text fired_at int acknowledged "0|1" } dim_posts { text urn PK text title text category text slug text tags text file_path text first_seen_at text last_modified_at text valid_from text valid_to boolean is_current } fct_seo_metrics { text post_urn text url date check_date text source text device real performance_score real accessibility_score real seo_score real best_practices_score real lcp_ms real tbt_ms real cls real fcp_ms boolean flag_perf_critical boolean flag_a11y_warning boolean flag_lcp_slow } fct_post_activity { text post_urn text commit_hash text commit_date text commit_message text change_type int lines_added int lines_removed int change_magnitude int changes_last_7d "window 7g" int cumulative_magnitude text changed_at } fct_keyword_performance { text keyword text fetched_at int data_points boolean has_trend_data boolean has_rising_queries boolean has_top_queries } Il dim_posts è la parte più elegante: usa una Slowly Changing Dimension di Tipo 2, il che significa che ogni modifica a un post (titolo, categoria, tag) crea una nuova riga con valid_from/valid_to, e la riga corrente ha is_current = TRUE. Questo permette di ricostruire lo stato del blog in qualsiasi momento nel passato e di correlare i cambiamenti nei contenuti con i trend SEO. dbt compila i modelli in SQLite (il dialetto SQL compreso da Cloudflare D1), valida la struttura dei dati (tipi, vincoli, test unique/not_null/relationships) e un adapter Python (dbt_runner.py) esegue il SQL compilato su D1 via HTTP API. Alert: quando le cose vanno male Un sistema di monitoring senza alert è un sistema di monitoring inutile. Le soglie che ho configurato: Metrica Soglia Azione Performance mobile < 50% Alert Accessibilità < 80% Alert SEO score < 70% Alert LCP mobile > 4 secondi Alert CLS > 0.25 Alert Gli alert vengono registrati nella tabella D1 alerts e salvati come artifact JSON del workflow, accessibile dalla pagina dell’azione su GitHub. Per il futuro (blocco 3), l’idea è che questi alert vengano consumati da un agente LLM che propone — e applica — le correzioni (quasi) automaticamente. Ma non anticipiamo. Il report mensile Se il workflow cade di lunedì, e quel lunedì è il primo del mese, parte la generazione del report mensile. Un file Markdown con: Site Health Overview: media di performance, accessibility, SEO e best-practices su tutti gli URL Core Web Vitals: LCP, TBT, CLS, FCP aggregati Alert del mese: tabella con URL, metrica, valore vs soglia Search Console top queries: cosa ha performato meglio in termini di click e CTR Il report viene salvato come artifact del workflow. Lo apro quando sento che il contenuto sta perdendo un po’ di slancio, lo ignoro quando voglio concentrarmi sulla scrittura. Quanto costa Zero. Ecco il conto: Cloudflare R2: 10 GB di storage, 10M operazioni Classe A/mese. Con ~100 JSON a settimana da pochi KB l’uno, non arrivo nemmeno all’1%. Cloudflare D1: 5 GB, 5M righe lette/giorno. Con ~1000 righe a settimana, sto nell’ordine dello 0.1%. serpapi: 100 chiamate/mese gratuite. Ne consumo ~50-60 a run settimanale. GitHub Actions: illimitato per repository pubblici. Il workflow weekly consuma ~2 minuti di CI. Google Search Console API: gratuita, limiti giornalieri generosissimi. Google Trends: gratuito (tramite serpapi, che fa da proxy). Quindi ETL, DWH e reportistica SEO gratis? Beh…ehm…sì. Ma alla fine…cosa abbiamo creato? È, come detto, un DWH in miniatura che assolve due scopi ben precisi, con il minor numero di tabelle (si, sono un fan dello Small Data): monitorare ciò che accade e fornirmi spunti per nuovi articoli. Questo è immediatamente chiaro guardando i tre piccoli data mart che lo compongono: La componente Google Search Console mi aiuta a capire come è posizionato il mio contenuto sui motori di ricerca e quanto è rilevante nelle ricerche degli utenti. La componente Post History tiene traccia della dinamica dei contenuti. Con essa posso cercare le eventuali cause di una modifica all’esposizione misurata da Search Console: sono state causate dalla pubblicazione di un nuovo articolo? Dall’aggiunta di un paragrafo, nuovi metadati o un aggiornamento della tag-soup di un articolo esistente? Dalla variazione di autorità dei link che puntano o sono puntati da un mio post sui social? La parte di Google Trends chiude il cerchio: mi aiuta a capire se una variazione di performance di un mio contenuto è stata magari causata non da un mio intervento sul contenuto stesso, ma da una variabile esogena, come una stagionalità, una keyword per la quale si è perso interesse, una nicchia che ho colpito. Mi suggerisce, inoltre, keyword affini che stanno crescendo o per cui è previsto un picco…una buona idea per un nuovo articolo! Ci tengo a far notare che, come dice anche il footer, questo sito non deposita alcun cookie. Di nessun genere. Men che meno quelli di profilazione. Non traccio le visite degli utenti con nessun sistema, non c’è Google Analytics o un altro tracker di nessun genere, qui dentro. Gli unici dati che ho sono i log degli accessi del web server, anonimizzati e con IP mascherati. Quindi, tutta la mia intelligence è basata su fenomeni esterni o comunque misurati esternamente, che hanno un riflesso indiretto su ciò che accade al mio sito. Avrebbe potuto essere una grossa limitazione alla raccolta dei dati, ma è stata una mia precisa scelta etica. E, devo dire, che alla prova dei fatti, le poche tabelle descritte sopra in realtà si sono rilevate perfettamente adeguate ai fini di questo blog. Beccati questo remarketing¹! ¹ Ovviamente, la vita è moooolto più complicata di così. Possiamo allora più realisticamente dire che è adeguato per le finalità divulgative di questo blog, a scopo non di lucro, scritto per il piacere di scrivere e che se proprio vogliamo trovargli degli obiettivi di marketing questi sono aumentare il citation rate dei miei articoli e l’aumento della mia base social. Prossimi passi: il blocco 3 Ora che ho i dati (blocco 1) e il monitoraggio (blocco 2), il passo finale è l’applicazione automatica delle migliorie. L’idea è quella di un agente LLM che agisce come un professore a scuola: Analizza i report SEO e gli alert Propone modifiche ai post, sia tecniche (“il titolo è troppo lungo per Google”) che lessicografiche (es. “questo post non è abbastanza accessibile, ha troppi prerequisiti tecnici che hai dato per scontato”, “questa keyword sta crescendo, scrivici un articolo”) Invia segnalazioni che aprono delle issue e/o le modifiche vengono applicate in una pull request su GitHub. Io passo e valuto se fare merge o respingerle. Il blocco 2, intanto, monitora l’effetto delle eventuali modifiche e chiude il ciclo di feedback sul lungo termine. Ma per oggi va bene così. I numeri arrivano. La macchina gira. Il dado (o il dato?) è tratto. Nel frattempo, tutto il codice che non abbiamo potuto vedere direttamente qui è su GitHub, nella directory _scripts/seo/ e _dbt/ ed è molto commentato. Sentitevi liberi di saccheggiarlo. --- title: Una shell history per ogni progetto date: 2026-05-27 url: https://gabrielebaldassarre.com/devops/una-shell-history-per-ogni-progetto/ excerpt: Come separare la history di ZSH per progetto usando la root git, senza plugin esterni e con un semplice hook. Un biscottino da terminale veloce e pratico. audience: practitioner proficiency: intermediate tags: zsh, terminal, bash, git, produttività category: DevOps prerequisites: ZSH; Git; File di configurazione della shell (.zshrc, .bashrc); Segnali Unix (SIGTERM); XDG Base Directory Specification; Hook functions di ZSH (chpwd, add-zsh-hook); Builtin di ZSH (fc, autoload, typeset, print); Trap functions di ZSH (TRAPEXIT, TRAPTERM) --- Se lavorate su più progetti contemporaneamente — e chi non lo fa? — vi sarà capitato di premere Ctrl+R per ripescare un comando e trovarvi tra i piedi il comando sbagliato. Il docker compose down del progetto A che non c’entra niente col progetto B. Il kubectl del cluster di staging mentre siete sul cluster di produzione. L’rsync verso il server del cliente sbagliato. Ok forse ho un po’ esagerato, magari non parliamo di eventi così catastrofici, però esplosioni termonucleari a parte non è raro perdersi nel cercare nella history della shell quel particolare configure pieno zeppo di parametri e ritrovarci ad andare indietro…e indietro…e indietro…insomma, ci siamo capiti. Per non parlare del momento-zero in cui ci ritroviamo pieni zeppi di schede di terminale aperte e, presi da irrefrenabile rifiuto per il livello di entropia raggiunto dall’Universo, andiamo a premere forsennatamente il famigerato pulsante d’angolo della finestra. Dimenticandoci che il SIGTERM non flusha il buffer nell’history file. Sull’entropia dell’Universo ci stiamo ancora lavorando, ma riguardo l’history della shell qualcosa in più possiamo farla. Ma qual è il problema? La history della shell è globale per default. Questo è il problema. La soluzione più pratica che ho trovato è separare la history per progetto, usando come discrimine la root del repository git in cui vi trovate. Questo perché è abbastanza sano ipotizzare che, se state consumando il pulsante freccia-su sulla tastiera, è perché state sviluppando qualcosa, e quindi con ogni probabilità siete nella root di un repository git. Oppure siete nel bel mezzo di una sessione di Crazy Climber. Tuttavia, se non siete in un repo git, si ripiega sulla directory corrente. Il tutto con poche righe di ZSH. Il mio script funziona con ZSH, ma il concetto è adattabile a qualsiasi shell moderna. Le opzioni globali Prima di entrare nel vivo, mettiamo a posto le impostazioni di base della history. Non sono strettamente necessarie per il trucco principale, ma evitano comportamenti fastidiosi. Queste vanno nel vostro .zshrc: export HISTSIZE=200000 export SAVEHIST=200000 setopt EXTENDED_HISTORY # registra il timestamp di ogni comando setopt HIST_IGNORE_ALL_DUPS # non mettere duplicati nella history setopt HIST_SAVE_NO_DUPS # non salvare duplicati su file setopt HIST_REDUCE_BLANKS # togli spazi inutili setopt HIST_IGNORE_SPACE # ignora comandi che iniziano con spazio setopt SHARE_HISTORY # condividi la history tra sessioni aperte setopt HIST_FCNTL_LOCK # file locking per evitare corruzione Due parole su SHARE_HISTORY: se usate tante sessioni terminale in parallelo (tmux, schede del terminale, etc.), tenetela attiva come sopra. Altrimenti, se preferite che ogni sessione sia isolata finché non la chiudete, sostituitela con setopt INC_APPEND_HISTORY_TIME. Io personalmente uso SHARE_HISTORY perché mi piace che tutto sia immediatamente disponibile ovunque, ma è questione di gusti. Riguardo invece HIST_IGNORE_SPACE è un trucchetto che uso quando voglio lanciare un comando “in incognito” affinché non lasci traccia nella history. Mi basta aggiungerci un leading space. Attenzione che questa opzione ha un comportamento diverso tra ZSH e Bash! Il cuore: history per progetto L’idea è semplice: ogni volta che entrate in una directory, controlliamo se fa parte di un repository git. Se sì, la history viene salvata in un file dedicato a quel repo. Altrimenti, si usa la directory corrente come chiave. Prima di tutto, serve una directory dove conservare i file di history: : ${XDG_STATE_HOME:=$HOME/.local/state} HIST_PROJECT_BASE="$XDG_STATE_HOME/zsh/history" mkdir -p "$HIST_PROJECT_BASE" Rispettiamo lo standard XDG, e se la variabile non è definita, ripieghiamo su ~/.local/state. Poi ci serve una funzione per sanitizzare i path e trasformarli in nomi di file validi: __hist_sanitize() { print -r -- "${1//\//__}" | tr -cd '[:alnum:]_.-' } Sostituisce gli slash con doppio underscore e rimuove qualsiasi carattere che non sia alfanumerico, underscore, punto o trattino. La funzione che individua la root git è banale: __project_root() { local root root="$(command git rev-parse --show-toplevel 2>/dev/null)" && print -r -- "$root" } Se non siete in un repo git, git rev-parse fallisce silenziosamente e la funzione restituisce 1. Il prefisso command evita che eventuali alias o funzioni personalizzate interferiscano. Lo switch: il cambio di history al volo Più o meno quello che succede è questo: flowchart LR A["chpwd / avvio"] --> B["__hist_switch()"] B --> C["fc -W<br/><i>flush su disco</i>"] C --> D{"git root<br/>esiste?"} D -->|"sì"| E["histfile<br/>git__repo.zsh_history"] D -->|"no"| F["histfile<br/>pwd__dir.zsh_history"] E --> G{"stesso file<br/>di prima?"} F --> G G -->|"sì"| H["skip"] G -->|"no"| I["fc -P / fc -p<br/><i>swap stack + touch</i>"] I --> J["fc -R<br/><i>carica history</i>"] J --> K["Pronta!"] E questa è la funzione che fa il grosso del lavoro: __hist_switch() { local key histfile key="$(__project_root)" && { key="$(__hist_sanitize "$key")" histfile="$HIST_PROJECT_BASE/git__${key}.zsh_history" } || { key="$(__hist_sanitize "$PWD")" histfile="$HIST_PROJECT_BASE/pwd__${key}.zsh_history" } # Già nel progetto giusto? Non fare nulla [[ "$histfile" == "$HISTFILE" ]] && return fc -W 2>/dev/null # flush su disco # Se lo stack esiste, lo poppiamo; poi pushiamo il nuovo. # push+pop in sequenza azzera la history in-memory, # evitando che comandi del vecchio progetto inquinino il nuovo file. (( __hist_has_stack )) && fc -P 2>/dev/null fc -p "$histfile" # Se il file non esiste ancora, lo creiamo [[ -f "$histfile" ]] || touch "$histfile" fc -R 2>/dev/null # carica la history del progetto __hist_has_stack=1 } Quattro cose notevoli: Guardia [[ "$histfile" == "$HISTFILE" ]] && return evita di fare I/O quando navigate tra sottodirectory dello stesso progetto. fc -P / fc -p gestiscono uno stack di history. La sequenza pop+push azzera la lista in-memory, risolvendo un leak subdolo per cui fc -R, in ZSH, appende i comandi a quelli già in memoria invece di sostituirli. touch "$histfile" è necessario perché fc -W di ZSH non crea file inesistenti — se il progetto è nuovo di zecca, senza touch la prima scrittura andrebbe persa. Il flag __hist_has_stack evita di chiamare fc -P al primo avvio, quando ancora non c’è uno stack da poppare. Gli hook Per far scattare lo switch in automatico, usiamo un hook di ZSH: autoload -Uz add-zsh-hook add-zsh-hook chpwd __hist_switch typeset -g __hist_has_stack=0 L’hook chpwd viene eseguito ogni volta che cambiate directory. Un cd ../altro-progetto e la history cambia automaticamente. Rabbrividiamo! Ma serve anche all’avvio di una nuova sessione: __hist_switch Così, quando aprite un nuovo terminale, la history giusta è già caricata. E per sicurezza, salviamo la history anche quando il terminale viene chiuso o interrotto: TRAPEXIT() { fc -W 2>/dev/null } TRAPTERM() { fc -W 2>/dev/null kill -TERM $$ } Non è strettamente indispensabile perché fc -W viene già chiamato a ogni chpwd, ma non si sa mai. Cosa ottenete in pratica ~ $ ls .local/state/zsh/history/ git____home__gabrio__progetti__mio-blog.zsh_history git____home__gabrio__progetti__ansible-playbook.zsh_history pwd____home__gabrio__Downloads.zsh_history pwd____home__gabrio.zsh_history Ogni progetto ha la sua history, pulita e isolata. Quando siete nella directory del blog, Ctrl+R vi mostra solo comandi rilevanti: bundle exec jekyll serve, git commit -m "nuovo post", rsync verso il server giusto. Quando passate al progetto Ansible, solo ansible-playbook, ansible-vault e compagnia cantante. Niente più docker compose down sbagliato. Niente più kubectl delete namespace sul cluster di produzione. Un’ultima nota Questo script è un estratto del mio setup personale, che trovate come Gist su GitHub. Sentitevi liberi di adattarlo, modificarlo, migliorarlo. Nel frattempo, mano destra sulla tastiera e cominciate a premere che il comando che vi serve rilanciare è proprio lì sopra…su…su…su…su… --- title: Il cielo in salotto, parte 3: Lo spazio profondo date: 2026-05-24 url: https://gabrielebaldassarre.com/home-assistant/dashboard-astrometria-parte-3/ excerpt: La terza e ultima sezione della dashboard di astrometria: l'immagine astronomica del giorno NASA (APOD) con API key e secrets.yaml, la posizione in tempo reale della ISS con multiscrape, e i prossimi lanci spaziali. Come integrare tutto in Home Assistant con REST sensor, Generic Camera e multiscrape. audience: practitioner proficiency: intermediate category: Home Assistant prerequisites: Home Assistant; YAML; REST API; Template Jinja2; Multiscrape; NASA APOD; International Space Station; NORAD ID (Satellite Catalog Number); Gestione delle secrets --- Nella prima parte abbiamo costruito il modulo Terra-Luna, nella seconda abbiamo monitorato il Sole. In questa terza e ultima parte ci spingiamo oltre: lo spazio profondo, la stazione spaziale e i razzi che la raggiungono. Dopo le misurazioni e i grafici dell’ultima volta, su questa colonna inseriremo card con uno scopo più sognante e divulgativo. APOD: Astronomical Picture of the Day L’Astronomical Picture of the Day è probabilmente il sito di divulgazione astronomica più longevo e più visitato del web. Attivo dal 16 giugno 1995, e mai realmente cambiato da allora, pubblica ogni giorno un’immagine astronomica diversa — fotografie di galassie, nebulose, eclissi, aurore, pianeti — con una spiegazione scritta da un astrofisico professionista. È una finestra quotidiana sull’universo accessibile a tutti. Avere l’APOD del giorno in dashboard, con immagine e didascalia, trasforma Home Assistant in qualcosa di più di un pannello domotico: diventa un’interfaccia verso il cosmo. L’API NASA La NASA mette a disposizione un’API pubblica e documentata per l’APOD all’indirizzo https://api.nasa.gov/planetary/apod. Richiede una chiave API che si ottiene gratuitamente registrandosi su api.nasa.gov. Esiste anche una chiave DEMO_KEY che funziona senza registrazione, ma è soggetta a limiti di utilizzo più stringenti (30 richieste/ora, 50/giorno). Per un’installazione casalinga che interroga l’API una volta al giorno, la DEMO_KEY è sufficiente, ma consiglio di richiedere una chiave personale: è gratis e si ottiene in pochi minuti. Proteggere la chiave: secrets.yaml Approfitto di questo semplice caso per spendere due parole su un argomento essenziale per chi sviluppa in Home Assistant, ovvero le cosiddette secrets. Analogamente a quelle di una moltitudine di altri sistemi software — e Home Assistant non fa eccezione — sono piccole porzioni di dato, tipicamente stringa, che vanno interpretate dal sistema software come confidenziali. In alcuni software sono gestite da sidecar di cifratura o di protezione particolari, mentre in altre circostanze, come Home Assistant, appunto, sono semplicemente gestite con maggiore cautela. Le chiavi API sono un tipico esempio di dati da gestire con le secrets: non vanno mai scritte direttamente nei file di configurazione, perché configuration.yaml e i file correlati possono finire accidentalmente in un repository git o in un backup condiviso o in file di log. Ma, d’altro canto, nemmeno sono chiavi così importanti da richiedere , e altre soluzioni sofisticate. La soluzione di compromesso è semplicemente quella di riportarla nel file secrets.yaml: # secrets.yaml nasa_api_key: "la_tua_chiave_api_qui" e nella configurazione si usa il riferimento con questa sintassi specifica: resource: "https://api.nasa.gov/planetary/apod?api_key=!secret nasa_api_key" Chiaramente secrets.yaml non va mai committato su git — aggiungilo al .gitignore se usi il controllo versione per la tua configurazione HA. Ci tengo, però, a precisare che la protezione delle secrets in Home Assistant, non va molto oltre quanto appena spiegato. Non è pensata, ripeto che non si sa mai, per dati veramente, veramente confidenziali e preziosi. Il REST sensor La risposta dell’API APOD ha questa struttura: { "date": "2026-05-10", "title": "Nome della foto", "url": "https://apod.nasa.gov/apod/image/...", "explanation": "Testo della spiegazione...", "media_type": "image", "hdurl": "https://apod.nasa.gov/apod/image/...hd.jpg" } Non ha senso scomodare un multiscrape per un parsing così semplice, piuttosto stavolta costruiamo un semplice sensore REST per la risposta APOD: rest: - resource: "https://api.nasa.gov/planetary/apod?api_key=!secret nasa_api_key" scan_interval: 3600 sensor: - unique_id: nasa_apod name: "NASA APOD" value_template: "{{ value_json.title }}" json_attributes: - url - explanation - date - media_type - hdurl Lo state del sensore è il titolo della foto — leggibile come badge o nel show_state della card. Gli attributi contengono tutto il resto. Il scan_interval: 3600 aggiorna ogni ora (l’APOD cambia una volta al giorno alle 00:00 UTC, ma è meglio aggiornare più spesso per non perdere il cambio). Generic Camera per l’immagine Esattamente come per il disco lunare, uso una Generic Camera per mostrare l’immagine: In Impostazioni → Dispositivi e servizi → Generic Camera, nel campo Still Image URL: {{ state_attr('sensor.nasa_apod', 'url') }} Questo crea camera.apod_nasa_gov (il nome, ovviamente, è libero). La camera si aggiorna automaticamente ogni volta che il sensore REST scarica un nuovo URL. Configuriamo la fotocamera generica come tipo a immagine fissa, passando il template del sensore con l'URL diretto della foto La card nella dashboard Da qui in poi è tutta discesa. Una semplice picture-entity type: picture-entity entity: sensor.nasa_apod camera_image: camera.apod_nasa_gov camera_view: live show_name: true show_state: true tap_action: action: more-info entity: camera.apod_nasa_gov La scheda finita, con l'immagine e la descrizione subito sotto Il tap_action punta all’entità della camera, non al sensore. Questo è il dettaglio chiave: se si lascia il comportamento predefinito (more-info del sensore), il tap apre il pannello del sensore con i valori degli attributi — utile, ma non spettacolare. Puntando la tap_action alla camera, il tap apre il modale della camera con l’immagine a tutta larghezza, che è quello che vogliamo. Nota importante: quando media_type è video (accade circa 2-3 volte al mese, quando l’APOD del giorno è un video YouTube), l’URL punta a un embed video e non a un’immagine. La Generic Camera non può mostrare un video e quando ciò accade la card rimane vuota. Se vuoi gestire questo caso, puoi aggiungere un template sensor che restituisce un’immagine placeholder quando media_type != 'image'. Come dite? Quando questo accade recuperare il video da YouTube, encodarlo con ffmpeg e renderlo disponibile tramite il media server direttamente connesso a HA? Si, in effetti è una bella idea! Inutilmente complicata…e per questo divertente! Ci penserò! La spiegazione in Markdown Torniamo alla realizzazione del blocco APOD. Sotto l’immagine aggiungo una card Markdown che mostra la spiegazione dell’APOD, recuperata dall’attributo del sensore: type: markdown content: "{{ state_attr('sensor.nasa_apod', 'explanation') }}" card_mod: style: | ha-markdown { font-size: var(--body-font-size); line-height: 1.6; } Il template Jinja2 {{ state_attr(...) }} funziona direttamente nel campo content delle card Markdown. card_mod, che se siete qui su questo blog quasi certamente avete, permette di sovrascrivere lo stile CSS per un’interlinea più leggibile sul testo lungo o per altri adattamenti stilistici che la sorgente NASA non ha (l’ho già detto che il sito è…longevo?). La ISS in tempo reale La Stazione Spaziale Internazionale orbita la Terra ogni 90 minuti a circa 400 km di quota e 27.600 km/h. È visibile a occhio nudo nelle ore crepuscolari come un punto luminoso che attraversa il cielo in 3-4 minuti, più brillante di qualunque stella. Per chi vuole fotografarla o semplicemente vederla passare, sapere la sua posizione attuale e la sua quota è il primo passo per pianificare un avvistamento. Ma, più poeticamente, averla in dashboard non è che un fulgido modo per ricordarci cosa l’umanità è in grado di fare se ci si mette di buzzo buono. La fonte dati: wheretheiss.at L’API Where the ISS At? è pubblica, gratuita e non richiede autenticazione. Viene interrogata passando come parametro il NORAD ID per i corpi in orbita. Il satellite 25544 è il NORAD ID della ISS: https://api.wheretheiss.at/v1/satellites/25544 La risposta: { "latitude": 45.12, "longitude": 9.34, "altitude": 418.5, "velocity": 27580.4, "visibility": "daylight" } Configurazione multiscrape Come non detto, qui, invece, ho di nuovo usato multiscrape. Il punto è che avevo bisogno di creare più sensori da un singolo endpoint, ma volevo farlo evitando di fare più richieste allo stesso endpoint: multiscrape: - name: "International Space Station" resource: "https://api.wheretheiss.at/v1/satellites/25544" scan_interval: 60 sensor: - unique_id: iss name: "International Space Station" value_template: "OK" attributes: - name: latitude value_template: "{{ value_json.latitude | default('') }}" - name: longitude value_template: "{{ value_json.longitude | default('') }}" - name: altitude value_template: "{{ value_json.altitude | default('') }}" - name: velocity value_template: "{{ value_json.velocity | default('') }}" - name: visibility value_template: "{{ value_json.visibility | default('') }}" - unique_id: iss_velocity name: "International Space Station - Velocity" value_template: "{{ value_json.velocity }}" unit_of_measurement: "km/h" - unique_id: iss_altitude name: "International Space Station - Altitude" value_template: "{{ value_json.altitude }}" unit_of_measurement: "km" Ho deciso di creare due piccole pills, o badge, nella parte superiore del dashboard con le informazioni di velocità istantanea e altitudine della Stazione Spaziale Internazionale. Non credevo dovessero occupare più spazio di così Alcune note: scan_interval: 60: La ISS si sposta di circa 460 km al minuto. Aggiornare ogni 60 secondi è un buon compromesso tra precisione e carico sull’API (che non ha rate limit documentati, ma va rispettata). Sensore principale con attributi: Il sensore iss ha come state il valore statico "OK" (un segnaposto) e porta tutti i dati come attributi. Questo è utile quando si vuole visualizzare più dati della stessa risorsa in un’unica entità — per esempio in un entity card che mostra latitudine, longitudine e quota in un colpo solo. Sensori separati per velocità e quota: Avere iss_velocity e iss_altitude come entità indipendenti permette di mostrarli come tile card, con icona e unità di misura, o di usarli in grafici e automatizzazioni (qualche idea a riguardo, a proposito?). Prossimi lanci spaziali I lanci spaziali sono diventati eventi frequenti: SpaceX, Rocket Lab, ULA e altre aziende lanciano decine di missioni all’anno. Molte sono visibili a occhio nudo se si abita nella giusta latitudine e nel momento giusto (spoiler: no, noi in Italia no) — e anche chi non può vederli dal vivo può seguirli in streaming. Sapere cosa sta per partire, quando e verso quale destinazione aggiunge un livello di contesto all’osservazione del cielo notturno. La fonte dati: RocketLaunch.Live RocketLaunch.Live offre un’API JSON gratuita per i prossimi lanci. Non voglio esagerare in dashboard, quindi recupero solo i due lanci più imminenti: https://fdo.rocketlaunch.live/json/launches/next/2 La struttura è un oggetto con un campo result che contiene un array di lanci. Per ogni lancio sono disponibili dozzine di campi: provider, veicolo, rampa di lancio, destinazione, missioni, finestra di lancio, link, tag. La risposta non è facilmente scomponibile: ho dovuto elencare tutti i campi. Due volte. Multiscrape, stavolta, è stato praticamente indispensabile: multiscrape: - name: "Rocket Launch - Next 2" resource: "https://fdo.rocketlaunch.live/json/launches/next/2" scan_interval: 43200 sensor: - unique_id: rocketlaunch_live_next_1 name: "Rocket Launch - Next 1" value_template: "{{ value_json.result[0].name }}" attributes: - name: Provider value_template: "{{ value_json.result[0].provider.name }}" - name: Vehicle value_template: "{{ value_json.result[0].vehicle.name }}" - name: Pad value_template: "{{ value_json.result[0].pad.name }}" - name: Pad Location value_template: "{{ value_json.result[0].pad.location.name }}" - name: Pad Location Country value_template: >- {{ value_json.result[0].pad.location.statename }} {{ value_json.result[0].pad.location.country }} - name: Missions value_template: >- {{ value_json.result[0].missions | map(attribute='name') | join(', ') }} - name: Launch Description value_template: "{{ value_json.result[0].launch_description }}" - name: Date value_template: "{{ value_json.result[0].date_str }}" - name: Date Exact value_template: "{{ value_json.result[0].win_open }}" - name: Tags value_template: >- {{ value_json.result[0].tags | map(attribute='text') | join(' / ') }} - name: Link value_template: >- {{ value_json.result[0].quicktext | regex_replace(find='.*(?=http)', replace='') | regex_replace(find=' for info.*', replace='') }} - unique_id: rocketlaunch_live_next_2 name: "Rocket Launch - Next 2" value_template: "{{ value_json.result[1].name }}" attributes: - name: Provider value_template: "{{ value_json.result[1].provider.name }}" - name: Vehicle value_template: "{{ value_json.result[1].vehicle.name }}" - name: Pad value_template: "{{ value_json.result[1].pad.name }}" - name: Pad Location value_template: "{{ value_json.result[1].pad.location.name }}" - name: Pad Location Country value_template: >- {{ value_json.result[1].pad.location.statename }} {{ value_json.result[1].pad.location.country }} - name: Missions value_template: >- {{ value_json.result[1].missions | map(attribute='name') | join(', ') }} - name: Launch Description value_template: "{{ value_json.result[1].launch_description }}" - name: Date value_template: "{{ value_json.result[1].date_str }}" - name: Date Exact value_template: "{{ value_json.result[1].win_open }}" - name: Tags value_template: >- {{ value_json.result[1].tags | map(attribute='text') | join(' / ') }} - name: Link value_template: >- {{ value_json.result[1].quicktext | regex_replace(find='.*(?=http)', replace='') | regex_replace(find=' for info.*', replace='') }} Il box con il bollettino dei lanci è semplice, ma efficace Alcuni dettagli che meritano spiegazione: scan_interval: 43200 — 12 ore. I lanci spaziali non cambiano finestra con frequenza così alta e l’API gratuita ha limitazioni di utilizzo. Aggiornare due volte al giorno è più che sufficiente. | map(attribute='name') | join(', ') — I campi missions e tags sono array di oggetti. Il filtro map estrae un campo da ciascun oggetto, e join li concatena in una stringa leggibile. Il regex_replace per il link — Il campo quicktext di RocketLaunch.Live non contiene solo l’URL ma del testo in prosa del tipo: “Follow SpaceX on Twitter for info: https://t.co/…“. La prima regex_replace usa un lookahead (.*(?=http)) per eliminare tutto quello che precede il protocollo http. La seconda elimina la parte finale ` for info…` che talvolta segue l’URL. Il risultato è l’URL pulito, usabile come link nella card. Ed eccoci al termine del nostro viaggio, con il dashboard che dovrebbe risultare più o meno così. Riepilogo della serie In tre articoli abbiamo costruito una dashboard di astrometria completa in Home Assistant, usando esclusivamente fonti pubbliche e gratuite. Il bello di questo tipo di progetto è che può crescere indefinitamente: ogni API pubblica che restituisce dati astronomici può diventare un nuovo sensore, ogni sensore può diventare una card, ogni card può diventare un’automazione. E il cielo come limite! --- title: Il cielo in salotto, parte 2: Il Sole date: 2026-05-17 url: https://gabrielebaldassarre.com/home-assistant/dashboard-astrometria-parte-2/ excerpt: Seconda parte della dashboard di astrometria: aggiungiamo il monitoraggio solare con immagini live da SOHO e GOES-16, la previsione delle aurore boreali OVATION, e i grafici del vento solare BT/BZ in tempo reale. category: Home Assistant --- Nella prima parte abbiamo costruito il modulo Terra-Luna. Ora ci spostiamo verso il centro del sistema solare: il Sole. Il Sole non è un oggetto statico. È una stella attiva, percorsa da correnti di plasma, squarciata periodicamente da eruzioni gigantesche, capace di lanciare nello spazio ondate di particelle che raggiungono la Terra in pochi giorni e interferiscono con le nostre reti elettriche, le comunicazioni radio e, nei casi più spettacolari, producono aurore boreali visibili anche alle latitudini italiane. Monitorare l’attività solare non è puro esercizio accademico: è space weather, e può avere conseguenze molto concrete per un astrofilo e un astrofotografo. Questa sezione della dashboard raccoglie tutto questo in un unico colpo d’occhio. Intanto, vi ricordo, questo è quello che ci prefiggiamo di ottenere e oggi ci concentreremo sulla colonna centrale NASA Eyes on the Solar System Prima di entrare nei dati quantitativi, ho voluto includere un elemento puramente visivo e divulgativo: NASA Eyes on the Solar System, un simulatore 3D interattivo sviluppato dalla NASA che mostra la posizione in tempo reale di pianeti, lune, sonde e asteroidi nel sistema solare. È uno strumento straordinario per visualizzare le distanze cosmiche in modo intuitivo: puoi seguire la traiettoria del Voyager 1, ora a più di 23 miliardi di chilometri dalla Terra, oppure vedere dove si trova in questo momento il James Webb Space Telescope. Tutto in scala e in tempo reale. Si incorpora nella dashboard tramite una card iframe che punta all’applicazione web della NASA. Non è esattamente l’integrazione più elegante che si sia mai vista in Lovelace, ma funziona: type: iframe url: "https://eyes.nasa.gov/apps/solar-system/#/home?featured=JUNO" aspect_ratio: 75% Nota: l’applicazione richiede WebGL e su dispositivi mobili può risultare pesante o non interattiva. È pensata principalmente per una visualizzazione su schermo desktop o su un tablet di buona potenza. Immagini live del Sole Qui non usiamo API Rest o servizi complessi, ma ci limitiamo a visualizzare gli stream media messi a disposizione dalla NASA o dal NOAA, manipolati con CSS per conformarli visivamente all’aspetto del dashboard. Riutilizzare il markup Lovelace: button_card_templates Per le immagini live ho scelto un approccio basato sui template globali di custom:button-card. L’idea è definire una volta sola uno stile “tile con immagine di sfondo” e riutilizzarlo per tutte le sorgenti visive, così da definire il markup una sola volta, in perfetto stile DRY. I template si definiscono nella configurazione globale del dashboard Lovelace e, nel momento in cui scrivo, devono per forza essere definiti in Yaml: button_card_templates: live_tile_card: show_icon: false show_name: true show_label: false styles: card: - border-radius: 12px - overflow: hidden - height: 180px custom_fields: picture: true live_tile_with_picture: template: live_tile_card custom_fields: picture: | [[[ return `<div style=" width: 100%; height: 100%; background-image: url('${variables.picture}'); background-size: cover; background-position: center; "></div>`; ]]] styles: custom_fields: picture: - position: absolute - top: 0 - left: 0 - width: 100% - height: 100% Ogni tile con immagine live diventa poi solo poche righe di configurazione, con la variabile picture che riceve l’URL dell’immagine. Vediamo subito gli esempi che popolano il nostro dashboard. Nota: non tutti sanno che effettivamente è possibile accedere al file Yaml complessivo di un intero dashboard. Lo si fa dal menù modifica dashboard in alto a destra, da dove è raggiungibile l’editor di configurazione testuale. I template vanno direttamente nel root node. SOHO Lasco C3 SOHO (Solar and Heliospheric Observatory) è un satellite congiunto ESA-NASA in orbita attorno al punto di Lagrange L1 tra Terra e Sole, a circa 1,5 milioni di chilometri dalla Terra. Uno dei suoi strumenti più famosi è LASCO C3, un coronagrafo che oscura il disco solare (come un’eclissi artificiale) per rendere visibile la corona solare esterna. Le immagini vengono aggiornate ogni pochi minuti e rese disponibili pubblicamente dalla NASA. Perché è utile? Il LASCO C3 è lo strumento primario per il rilevamento delle CME (Coronal Mass Ejection): espulsioni di miliardi di tonnellate di plasma che, quando sono dirette verso la Terra, possono causare tempeste geomagnetiche. Forse più utili per chi fa radioastronomia che astrofotografia, ma sono proprio belle da vedere in dashboard, questo è sicuro. type: custom:button-card template: - live_tile_card - live_tile_with_picture name: "SOHO Lasco C3" color_type: icon variables: picture: "https://sohowww.nascom.nasa.gov/data/realtime/c3/512/latest.jpg" tap_action: action: none GOES-16 SUVI GOES-16 è il satellite meteorologico geostazionario americano. Oltre ai dati meteo, ospita a bordo il SUVI (Solar Ultraviolet Imager), che fotografa il Sole in ultravioletto a sei diverse lunghezze d’onda. La banda da 195 Å (Angstrom), che corrisponde a temperature coronali di circa 1,5 milioni di Kelvin, è quella standard per individuare le regioni attive, i filamenti e i brillamenti solari. L’URL delle immagini più recenti è pubblico e aggiornato in tempo reale da NOAA: type: custom:button-card template: - live_tile_card - live_tile_with_picture name: "GOES-16 (SUVI)" color_type: icon variables: picture: "https://services.swpc.noaa.gov/images/animations/suvi/primary/195/latest.png" tap_action: action: none Aurora OVATION Il modello OVATION (Oval Variation, Assessment, Tracking, Intensity and Online Nowcasting) di NOAA è il sistema di nowcasting più usato per la previsione delle aurore boreali e australi. Produce ogni 30 minuti una mappa globale della probabilità di avvistamento aurorale basandosi sul vento solare misurato dai satelliti. Per chi vive nell’Europa settentrionale, questa mappa è preziosissima: durante le tempeste geomagnetiche più intense (indice Kp ≥ 7), le aurore diventano visibili anche a latitudini sorprendentemente basse. Certo, la probabilità di osservare un’aurora boreale, chessò, in Lombardia rimane piuttosto bassa (ma meno bassa di quanto si creda) e proprio per questo avere questo indicatore in dashboard è importante per evitare di perdersi i momenti giusti. type: custom:button-card template: - live_tile_card - live_tile_with_picture name: "Aurora Boreale (OVATION)" color_type: icon variables: picture: "https://services.swpc.noaa.gov/images/animations/ovation-north/latest.jpg" styles: custom_fields: picture: - background-size: "110% 110%" tap_action: action: none Il background-size: 110% 110% applica un leggero zoom che taglia il bordo bianco dell’immagine originale NOAA, migliorando la resa visiva nel tile. Le tre tile vengono raggruppate in un horizontal-stack: type: horizontal-stack cards: - # SOHO Lasco C3 - # GOES-16 SUVI - # Aurora OVATION Immagini della superficie del Sole e indicatori NOAA Space Weather NOAA Space Weather: i KPI dello spazio meteorologico Le immagini raccontano la situazione qualitativa. Per avere numeri precisi, ho aggiunto tre sensori chiave del NOAA Space Weather Prediction Center, mostrati come semplici tile. Radio Flux F10.7 Il flusso radio a 10,7 cm (F10.7) è l’indice più longevo e affidabile del ciclo di attività solare: viene misurato ogni giorno alle 17:00 UTC dall’Osservatorio di Penticton (Canada) dal 1947. Valori tipici vanno da ~70 (minimo solare) a ~300+ (massimo solare). Siamo (maggio 2026) attualmente nel Ciclo Solare 25, in fase di massimo, quindi i valori sono elevati. Velocità del Vento Solare Il vento solare è un flusso continuo di particelle cariche che il Sole emette in tutte le direzioni. La velocità tipica è 400-600 km/s, ma durante le CME può raggiungere i 3.000 km/s. Un aumento improvviso di velocità è spesso il primo segnale di un’onda d’urto in arrivo. Indice Kp L’indice Kp misura le perturbazioni del campo magnetico terrestre su scala globale da 0 (calma assoluta) a 9 (tempesta geomagnetica estrema). È l’indice di riferimento per le previsioni aurorali: Kp Visibilità aurora ≤ 3 Solo circolo polare 5 Scandinavia meridionale 7 Germania, Francia settentrionale 9 Mediterraneo Configurazione multiscrape I dati vengono recuperati tramite l’integrazione Multiscrape (disponibile anche su HACS), che permette di interrogare un’API REST (o in generale qualsiasi cosa), in questo caso l’API NOAA, e mappare ogni campo su un sensore separato in un’unica operazione. Al momento in cui scrivo, il setup va necessariamente fatto in Yaml: multiscrape: - name: "NOAA Space Weather" resource: "https://services.swpc.noaa.gov/json/solar-cycle/observed-solar-cycle-indices.json" sensor: - unique_id: noaa_space_weather_noon_10_7cm_radio_flux name: "NOAA Space Weather - Noon 10.7cm Radio Flux" value_template: "" unit_of_measurement: "sfu" - unique_id: noaa_space_weather_solar_wind_speed name: "NOAA Space Weather Solar Wind Speed" value_template: "" unit_of_measurement: "km/s" Per l’indice Kp, NOAA espone un endpoint dedicato: - name: "NOAA Kp Index" resource: "https://services.swpc.noaa.gov/json/planetary_k_index_1m.json" sensor: - unique_id: noaa_kp_index_current name: "NOAA Kp Index Current" value_template: "" icon: mdi:sine-wave Le tre tile nella dashboard: type: horizontal-stack cards: - type: tile entity: sensor.noaa_space_weather_noon_10_7cm_radio_flux name: "Radio Flux F10.7" - type: tile entity: sensor.noaa_space_weather_solar_wind_speed name: "Vento Solare" - type: tile entity: sensor.noaa_kp_index_current name: "Indice Kp" icon: mdi:sine-wave color: purple Grafici del vento solare: BT e BZ Il vento solare ha due parametri magnetici che determinano se una tempesta geomagnetica sarà intensa o meno: BT (Intensità totale del campo magnetico interplanetario) — quanto è forte il campo, in nanoTesla BZ (Componente Sud del campo) — la componente più critica: quando BZ è fortemente negativo (campo rivolto verso Sud), la magnetosfera terrestre si apre e le particelle solari entrano, causando aurore e tempeste geomagnetiche Monitorare l’andamento di BZ nelle ultime ore è fondamentale: un BZ negativo prolungato è il segnale che una tempesta è in corso o imminente. NOAA SWPC pubblica queste misure (aggiornate ogni minuto dal satellite DSCOVR al punto L1) in formato JSON, tanto per cambiare: https://services.swpc.noaa.gov/products/solar-wind/mag-1-day.json Per grafici molto densi che possono richiedere la visualizzazione di più serie di dati, in genere preferisco ApexCharts, che ha una fantastica card per Lovelace: La linea orizzontale a y=0 è il riferimento visivo chiave: quando BZ scende sotto quella linea e ci rimane, è il momento di controllare le previsioni aurorali. type: custom:apexcharts-card graph_span: 6h header: show: true title: "Vento Solare — BT / BZ" series: - entity: sensor.noaa_solar_wind_bt name: BT color: orange stroke_width: 2 - entity: sensor.noaa_solar_wind_bz name: BZ color: "#5b9ec9" stroke_width: 2 show: in_chart: true yaxis: - id: main min: -30 max: 30 apex_config: tickAmount: 6 apex_config: chart: type: line annotations: yaxis: - y: 0 borderColor: "#666" strokeDashArray: 4 ApexChart è estremamente flessibile e consente di ottenere layout e combinazioni molto complesse, anche se, ammetto, raramente mi trovo a rappresentare grafici diversi da istogrammi, scatterplot o spezzate...che ci volete fare, sono un tradizionalista! Descrizione estesaGrafico a doppia linea con BT (arancione) e BZ (blu) del vento solare su 6 ore, con linea orizzontale tratteggiata a zero. BT oscilla tra 2 e 8 nT, BZ tra -6 e +2 nT. Nella terza parte chiudiamo il cerchio con lo spazio profondo: l’immagine astronomica del giorno della NASA (APOD), la posizione in tempo reale della ISS e il bollettino prossimi lanci spaziali. --- title: Prova a prendermi: come ho insegnato al mio blog a farsi trovare date: 2026-05-16 url: https://gabrielebaldassarre.com/devops/seo-automatico-jekyll/ excerpt: Una pipeline completa di SEO auditing automatica per il mio blog Jekyll con Lighthouse, PageSpeed Insights, IndexNow, microformati e posizionamento per LLM — tutto integrato in GitHub Actions. audience: practitioner proficiency: intermediate tags: SEO, Jekyll, GitHub Actions, CI/CD, DevOps, webperf, LLM, PageSpeed, GEO category: DevOps prerequisites: Come funziona Jekyll; Basi di GitHub Actions; Cos'è Schema.org --- Pubblicare un articolo su un blog personale è facile. Assicurarsi che venga trovato dai motori di ricerca — e, sempre più, dai crawler AI — è un’altra storia. Per anni ho fatto come fanno tutti: scrivevo, applicavo il buon senso SEO di base, pubblicavo. Ogni tanto aprivo Google Search Console e se qualcosa era cambiato, o non stava funzionando, prendevo qualche contromisura seguendo, un po’ svogliatamente, le raccomandazioni di questo o quel tool di auditing. Ecco, il fatto è che io, in quanto appassionato di divulgazione, trovo piacere a spiegare la scienza, e raggiungere l’audience più ampia possibile è parte del gioco. Ma il desiderio di scrivere, in me, vince sempre. L’ottimizzazione e, soprattutto, il monitoraggio arrivano irrimediabilmente dopo. Così, oggi mi sono detto: perché non automatizziamo anche la componente SEO? In un modo moderno, perbacco, già che siamo nel 2026? …e possibilmente utilizzando solo strumenti open source o, al più, con un generoso free tier mensile? In effetti, ogni volta che pubblico o modifico un post su questo blog, parte già una catena di eventi organizzata nel workflow di authoring e pubblicazione: Jekyll builda il sito, Cloudflare Pages lo pubblica, Cloudinary (la mia CDN) genera le immagini hero e scalda la cache con gli altri formati (quelli, ad esempio, per i microformati social). Tutto automatico, tutto già funzionante. Si tratta solo di espandere il concetto. Così è nata la mia pipeline di SEO automatica, la prima di una serie che nella mia testa avrà, in definitiva, questi tre blocchi: Il supporto all’autorialità. Quello che vedremo oggi: una pipeline che alla pubblicazione di un nuovo articolo effettua una serie di audit ed altre operazioni sullo stesso, proponendo eventuali azioni migliorative. Il supporto al monitoraggio. Un workflow periodico che, anche in assenza di nuovi articoli, monitora i parametri SEO ed evidenzia problemi o cambiamenti peggiorativi nello stato di salute del blog nel suo complesso. L’applicazione automatica delle migliorie. In cui agenti LLM andranno ad analizzare le proposte di migliorie SEO emerse nel blocco 1 e applicheranno automaticamente i correttivi, per poi valutarne l’effetto grazie al monitoraggio del blocco 2. Il workflow di supporto autoriale: l’idea Banalmente, una volta terminato il processo di pubblicazione di un articolo, il suo permalink viene passato a una serie di servizi che lo analizzano e lo notificano ai motori di ricerca. Il tutto orchestrato da un unico file YAML. Zero servizi a pagamento, zero login da fare, zero dashboard da aprire, zero notifiche da controllare manualmente — tutto descritto programmaticamente dal contenuto del repository git e guidato da CICD. In questo articolo non andrò a riportare il codice nella sua interezza, per non appesantire la lettura, ma solo i suoi principi di base e le sue caratteristiche più istruttive. Chiaramente, sentitevi liberi di riutilizzare il workflow secondo le vostre esigenze: ne sarei felice! Come funziona Innanzitutto, il workflow si aggancia a workflow_run, cioè parte automaticamente al completamento del workflow precedente, quello di pubblicazione, appunto. Può, però, anche essere lanciato manualmente passando gli URL da verificare mediante una clausola di workflow_dispatch. Questo, nello YAML del workflow, è espresso tramite questo blocco in testa: on: workflow_run: workflows: ["Deploy blog on Cloudflare Pages"] types: [completed] branches: [main, master] workflow_dispatch: inputs: urls: description: 'URL da verificare (separati da spazio)' required: false type: string Ogni job avrà sempre una clausola if di questo tipo: if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} Ma la parte più elegante è il modo in cui trova i post da analizzare: git diff --name-only HEAD~1 HEAD -- '_posts/**.md' '_posts/**.Rmd' In pratica, prende solo i file .md e .Rmd modificati nell’ultimo commit e di questi estrae la categoria e lo slug dal nome file, costruisce la lista di URL da testare e poi uno ad uno chiama i vari servizi passandogli la lista, oppure un URL alla volta in un loop bash. Nessun inventario da mantenere. Gli url di chiamata sono costruiti tramite operazioni su stringhe, i servizi li chiamo tutti tramite curl, e formatto i risultati con jq e li mostro a console. È tutto scritto in bash, insomma. Se la maggior parte dei tool producono solo un po’ di informazioni in stdout che io riformatto e stampo, altri producono output molto più ricchi. Per questi ultimi, salvo i report come artifact del workflow, accessibili dalla pagina dell’azione su GitHub per 30 giorni. Un esempio: - name: Salva report Lighthouse if: always() uses: actions/upload-artifact@v4 with: name: lighthouse-reports path: | lighthouse-results/ .lighthouseci/*.html .lighthouseci/*.json retention-days: 30 Ma adesso scendiamo nel dettaglio pratico su ciò che facciamo e perché lo facciamo. Le cose che contano davvero Ho colto l’occasione di questa pipeline per sistemare diversi aspetti di SEO strutturale (layout) che erano in arretrato da tempo. Non sono interventi appariscenti, ma sono quelli che fanno la differenza tra un blog tecnicamente solido e uno che spera di essere trovato. Prima i fondamentali, insomma. Parte 1 — Crawlability e l’arte di farsi trovare Trovarsi su Google non basta più. Nel 2026, una fetta crescente del traffico verso i contenuti tecnici arriva dai crawler AI — ChatGPT, Perplexity, Claude, Gemini, Copilot. Questi sistemi non navigano il web come un browser: arrivano, leggono l’HTML e se ne vanno. Se il tuo contenuto non è immediatamente accessibile, per loro non esiste. Secondo AI Advisors, i crawler AI cercano tre cose: contenuti ben strutturati, segnali di autorità chiari, e una gerarchia di heading che renda ovvio di cosa parla ogni sezione. Non è diverso da ciò che vuole Google, ma è più esigente in termini di pulizia strutturale e metadati. Ne ho approfittato quindi per sistemare, lato template Jekyll, questi elementi statici: robots.txt per crawler AI. GPTBot, CCBot, anthropic-ai, PerplexityBot, Google-Extended e altri: tutti possono accedere ai contenuti testuali ma non agli asset binari. Con l’esplosione del traffico da AI crawler, è diventato rilevante. Ma attenzione a non stressare troppo la CDN: il traffico da AI può essere davvero invadente. llms.txt e llms-full.txt. Il primo è un indice strutturato dei post in formato Markdown — una specie di sitemap per LLM. Il secondo è l’intero corpus del blog in un unico file markdown, generato automaticamente a ogni build da un semplicissimo plugin Jekyll. Lo standard è ancora acerbo e usato da pochi attori (solo OpenAI, al momento in cui scrivo), ma adottarlo costa talmente poco che non farlo sarebbe pigrizia. Core Web Vitals sotto controllo. Lighthouse CI e PageSpeed Insights verificano a ogni deploy che LCP (Largest Contentful Paint), CLS (Cumulative Layout Shift) e TBT (Total Blocking Time) restino entro soglie accettabili. Le mie pagine sono statiche, quindi i valori sono naturalmente buoni, ma il controllo automatico mi avvisa se qualcosa degenera — un’immagine troppo pesante, un font che blocca il rendering, un layout che slitta, un javascript che crea problemi. PSI è un po' più old school e da molto peso agli aspetti tecnici Parte 2 — Schema.Org e l’arte di farsi riconoscere I microformati sono il modo in cui dici ai motori di ricerca cosa stai dicendo. Non “questa è una stringa di testo”, ma “questo è il titolo di un articolo, questo è l’autore, questo è un termine tecnico definito e questo sono io”. Sul mio blog, ogni post ha già un blocco JSON-LD BlogPosting con titolo, descrizione, data di pubblicazione, autore. Quello che ho aggiunto con questa pipeline è: author.sameAs. I link ai profili social dell’autore (GitHub, LinkedIn, Mastodon, Goodreads, Printables) ora appaiono nel markup strutturato. Per i motori di ricerca, questo collega inequivocabilmente i contenuti a una persona reale con una presenza online verificabile. { "@context": "https://schema.org", "@type": "Person", "name": "Gabriele Baldassarre", "url": "https://gabrielebaldassarre.com", "image": "https://gabrielebaldassarre.com/assets/images/gabriele-baldassarre.png", "sameAs": [ "https://github.com/theclue", "https://www.linkedin.com/in/gabrielebaldassarre", "https://mastodon.social/@gabriob", "https://goodreads.com/theclue", "https://www.printables.com/@GabrioB_1701666" ] } Ho creato un glossario YAML (_data/glossary.yml) con i termini tecnici che uso più spesso nel blog — spin quantistico, autovettore, illuminamento, legge di potenza, ecc. — ciascuno con definizione e URL sotto forma di entità DefinedTerm e DefinedTermSet. Ogni post ora ha un blocco about che linka i termini del glossario pertinenti alla categoria. Sulle pagine indice (homepage, categorie), un DefinedTermSet elenca l’intero glossario. { "@type": "DefinedTermSet", "name": "Glossario di Gabriele Baldassarre", "hasDefinedTerm": [ { "@type": "DefinedTerm", "termCode": "Spin quantistico", "name": "Spin quantistico", "description": "Proprietà intrinseca delle particelle elementari...", "url": "https://gabrielebaldassarre.com/fisica/stati-spin/" } ] } L’obiettivo è costruire un mini knowledge graph semantico, così che un motore di ricerca — o un LLM — che atterra su una mia pagina possa risalire al contesto tecnico in cui si inserisce. Dopo aver definito il proprio modello di metadati di schema, non fa male fare un passaggio su Schema Validator. Parte 3 — GEO e l’arte di farsi capire La Generative Engine Optimization (GEO) è la pratica di strutturare i contenuti perché vengano citati dai motori di ricerca AI — ChatGPT, Perplexity, Claude, Gemini. Non si tratta di ranking. Si tratta di citation rate: quanto spesso il tuo brand appare nelle risposte generate. Secondo LLMrefs, la sovrapposizione tra i link di Google e le fonti citate dagli AI è scesa dal 70% a meno del 20%. In particolare, le AI citano tranquillamente risultati che, invece, nelle SERP dei motori di ricerca sono ben lontane dalla prima pagina di risultati, senza timore. I due mondi si stanno separando. E i criteri che usano gli AI per scegliere cosa citare sono diversi. Cosa ho fatto, in concreto, per applicare GEO al mio blog: Heading gerarchici puliti. Ogni sezione ha un H2, ogni sottosezione un H3. Un argomento per sezione. Gli AI usano la struttura per decidere cosa è rilevante. Risposte prima del contesto. In ogni sezione, la risposta arriva subito, il contesto dopo. Gli AI estraggono informazioni, non leggono romanzi. E qui la sfida per me sta proprio nello scrivere un testo un minimo sospensivo che crei pathos per il lettore, ma che non sia penalizzato dalle AI. Spoiler: non è per niente facile (ma ci si riesce). Paragrafi corti. Due o tre frasi al massimo. I blocchi di testo lunghi sono più difficili da processare, meno probabili da citare e occupano più contesto (“Che, signora mia, di contesto non ce n’è mai abbastanza!”). Liste puntate e numerate. Per processi, checklist, confronti. Segnali di autorità espliciti. L’autore è indicato con nome, biografia e link social. Le fonti sono citate. I dati hanno attribuzione. I microformati rafforzano l’ancoraggio identitario. Molto utile. Freschezza dei contenuti. Gli AI hanno un recency bias fortissimo: dopo 3 mesi, le citation crollano. Per questo motivo il workflow di monitoraggio (blocco 2) includerà un check periodico sulla freschezza. Non ho dovuto snaturare il mio modo di scrivere. Ho solo applicato un po’ di disciplina strutturale. Che, a ben vedere, migliora la lettura anche per gli umani. Parte 4 — SEO Auditing e l’umiltà di farsi consigliare Arriviamo al cuore pratico della pipeline. Una volta che il workflow ha trovato i post da analizzare, per ognuno chiama in sequenza questi quattro servizi. IndexNow È il più semplice: un POST JSON con {host, key, urlList} a api.indexnow.org. La chiave è un file .txt servito alla root del dominio che dimostra la proprietà del sito — niente registrazioni, niente dashboard. Il protocollo è adottato da Bing, e questo ci basta: curl -X POST "https://api.indexnow.org/indexnow" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{"host":"gabrielebaldassarre.com","key":"b8f09512...","urlList":["https://gabrielebaldassarre.com/fisica/stati-spin/"]}' La risposta è un 200 se l’URL è stato accettato, 202 se è in attesa di validazione della key (la prima volta che si usa). Dopo il primo invio, la key è verificata e le risposte successive sono tutte 200 (quindi si, se usate il mio workflow la prima volta la pipeline fallirà. Sappiatelo). 🚀 Invio notifica IndexNow per 1 URL... 📥 Risposta IndexNow: HTTP 200 ✅ URL notificati con successo ai motori di ricerca Hermes SEO Audit Un’API pubblica (con key opzionale per limiti più alti — 5 req/giorno anonimo, 50 con key gratuita). Restituisce un JSON con score, grade, issues, warnings e passed. Lo uso per avere un quadro immediato dello stato SEO di una pagina. curl -s "https://hermesforge.dev/api/seo?url=https://gabrielebaldassarre.com/fisica/stati-spin/&key=YOUR_KEY" Output a console dopo parsing con jq: 📋 [1/2] https://gabrielebaldassarre.com/fisica/stati-spin/ Punteggio: 94/100 (A) Problemi: 0 | Warning: 2 | OK: 10 🟡 Warning: - [title] Title may be truncated in search results (114 chars) - [meta] Meta description may be truncated (213 chars) 📊 Riepilogo: 94/100 (A) | 🔴 0 | 🟡 2 | ✅ 10 📋 [2/2] https://gabrielebaldassarre.com/home-assistant/illuminanza-smartworking/ Punteggio: 85/100 (B) Problemi: 1 | Warning: 3 | OK: 8 🔴 Problemi: - [images] 1 of 1 images missing alt text (high) 🟡 Warning: - [title] Title too short (38 chars, recommend 50-60) - [meta] Meta description short (89 chars) - [performance] Page size excessive (1.2 MB) 📊 Riepilogo: 85/100 (B) | 🔴 1 | 🟡 3 | ✅ 8 Hermes è, in realtà, una suite ben più grande — offre anche screenshot API, chart rendering, dead link check e performance audit — con caratteristiche davvero intriganti e su cui sicuramente scriverò in futuro. PageSpeed Insights L’API ufficiale Google per Core Web Vitals. Richiede una API key gratuita da Google Cloud Console. Restituisce le categorie performance, accessibility, SEO e best-practices, sia per mobile che per desktop. curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://gabrielebaldassarre.com/&strategy=mobile&category=performance&category=accessibility&category=seo&category=best-practices&key=YOUR_KEY" Nota bene: bisogna passare esplicitamente le quattro categorie con &category=, altrimenti l’API restituisce solo performance. Ci ho perso due ore a capirlo e vi risparmio volentieri questo strazio. Output a console: 🔍 Analisi: https://gabrielebaldassarre.com/ 📱 Mobile | Perf: 89% | A11y: 92% | SEO: 83% | LCP: 3.8 s | TBT: 10 ms | CLS: 0 🖥️ Desktop | Perf: 98% | A11y: 92% 🔍 Analisi: https://gabrielebaldassarre.com/fisica/stati-spin/ 📱 Mobile | Perf: 82% | A11y: 88% | SEO: 79% | LCP: 4.3 s | TBT: 60 ms | CLS: 0.104 🖥️ Desktop | Perf: 100% | A11y: 88% ⚠️ PERFORMANCE MOBILE SOTTO SOGLIA CRITICA: 82% Il report JSON completo — ben più ricco della sintesi a console — viene salvato come artifact del workflow, accessibile per 30 giorni. Per utilizzare le API di PageSpeed Insights è necessario un progetto su Google Cloud Platform. Questa cosa non mi fa impazzire, ma se non altro sono gratuite Lighthouse CI Qui cominciamo a calare i pezzi grossi. Lighthouse CI non è un servizio remoto con una API REST, ma un headless Chrome eseguito in locale sulla VM del runner di GitHub Actions. Naviga le pagine, raccoglie metriche, e produce report HTML con screenshot, waterfall di rete e diagnostica. npm install -g @lhci/cli --quiet npx lhci collect --url="https://gabrielebaldassarre.com/fisica/stati-spin/" \ --settings.chromeFlags="--no-sandbox --headless --disable-gpu" \ --numberOfRuns=1 npx lhci assert --config=.github/lighthouse/lighthouserc.json Le assertion sono configurate in un file lighthouserc.json che definisce le soglie minime: { "ci": { "assert": { "assertions": { "categories:performance": ["warn", {"minScore": 0.50}], "categories:accessibility": ["warn", {"minScore": 0.80}], "categories:seo": ["warn", {"minScore": 0.70}], "largest-contentful-paint": ["warn", {"maxNumericValue": 4000}], "total-blocking-time": ["warn", {"maxNumericValue": 500}], "cumulative-layout-shift": ["warn", {"maxNumericValue": 0.25}] } } } } Essendo un tool locale, ipoteticamente potrebbe anche essere eseguito a build time prima della pubblicazione, e magari bloccarla se il post non raggiunge una soglia minima di score. All’atto pratico, però, questo scenario non è facile da implementare. Nel mio caso, ad esempio, che uso una CDN per le immagini, durante il build gli asset non sono ancora pubblici e i loro URL non sono ancora risolti. L’audit pre-deploy soffrirebbe di falsi positivi. Eseguo quindi Lighthouse post-deploy, quando il sito è live e Cloudinary ha già cachato tutte le immagini. Lighthouse CI fornisce un report molto dettagliato. Come vedete, già non sono messo così male come scoring I report HTML e JSON sono salvati come workflow artifact con timestamp e commit hash nel nome (assertions-20260516-164500-a1b2c3d.json), da dove li recupero per un’ispezione ad oggi manuale. Sono incredibilmente ricchi di informazioni. Forse anche troppe, per un umano… se capite dove voglio arrivare. Gestire secrets e API key Molti di questi servizi richiedono un’API key, spesso passata come parametro nelle chiamate curl. Come le si passa al workflow senza committare in chiaro sul repository? Ma con le Secrets ovviamente! Anche Github, infatti, ha questa funzionalità che non può mancare ad un orchestratore di CICD. Si configurano in Settings → Secrets and variables → Actions. Quanto costa Zero. Ecco il conto, servizio per servizio: GitHub Actions — illimitato per repository pubblici. 2000 minuti/mese di esecuzione; ne consumo forse 30. PageSpeed Insights — 25.000 query al giorno. Ne faccio 4 o 5 a deploy. IndexNow — completamente gratuito e senza limiti. Hermes SEO — 5 richieste al giorno in anonimo, 50 con API key gratuita. Lighthouse CI — eseguito localmente sul runner di GitHub Actions, nessun servizio esterno. Prossimi passi Questo era il blocco 1: supporto all’autorialità. Nel prossimo articolo della serie affronterò il blocco 2, il monitoraggio periodico: un workflow schedulato con cron che testa tutti i post del blog una volta al mese, produce un report aggregato, e — se necessario — apre automaticamente una issue su GitHub per segnalare regressioni. Poi, nel blocco 3, arriverà la parte più ambiziosa: un agente LLM che analizza i report SEO, propone modifiche ai post, le applica in una pull request, e verifica l’effetto grazie ai dati del monitoraggio. Ma per oggi va bene così. --- title: Il cielo in salotto, parte 1: Sistema Terra-Luna date: 2026-05-10 url: https://gabrielebaldassarre.com/home-assistant/dashboard-astrometria-parte-1/ excerpt: Come costruire una dashboard di astrometria in Home Assistant per tenere d'occhio la Luna, il Sole e lo spazio profondo. Prima parte: l'architettura generale e il modulo Terra-Luna, con disco lunare in tempo reale via API NASA, template sensor per il formato data NASA e il sensore di fase lunare con icone e immagine dinamica. audience: practitioner proficiency: intermediate category: Home Assistant --- Ci sono progetti che iniziano con un’idea semplice e finiscono per diventare qualcosa di più grande. Questo è uno di quelli. Tutto è partito da una domanda banale: posso sapere dalla mia dashboard di Home Assistant in che fase è la Luna stasera? La risposta è ovviamente sì — ma una volta aperta quella porta, è difficile fermarsi. Quello che descrivo in questa serie di articoli è una dashboard di astrometria — una parola un po’ altisonante per dire: uno schermo dedicato a tutto quello che succede sopra la nostra testa, dai cicli lunari all’attività solare, dalle aurore boreali ai prossimi lanci spaziali. Non è un progetto per astronomi professionisti e il suo scopo è sicuramente divulgativo, in particolare per incuriosire i miei due bambini. Tuttavia, non è nemmeno del tutto inutile: astrofili, appassionati di astrofotografia, o semplicemente chi vuole sapere se stasera vale la pena alzare lo sguardo potrebbero trovarvi informazioni utili. La dashboard è divisa in tre sezioni: Sistema Terra-Luna — la Luna, il sole che tramonta (questa prima parte) Sol — immagini live del Sole, aurora boreale, vento solare (parte 2) Spazio profondo e bollettini — l’immagine del giorno NASA (APOD), posizione della ISS, prossimi lanci (parte 3) Al termine di questa serie di articoli, l'aspetto del dashboard sarà - speriamo - più o meno questo La Luna La Luna è l’oggetto celeste più osservato e fotografato da sempre. Per un astrofilo o un appassionato di astrofotografia, la fase lunare è una variabile critica: la notte di luna piena è splendida da guardare a occhio nudo, ma rende quasi inutili le sessioni di deep sky a causa della luce diffusa. Sapere in anticipo la fase e l’orario del tramonto del sole (che definisce l’inizio della notte astronomica) permette di pianificare le sessioni di osservazione con precisione. L’integrazione moon nativa Home Assistant include di serie un’integrazione chiamata semplicemente Moon che calcola localmente, senza dipendenze esterne, la fase lunare corrente. Si abilita da Impostazioni → Dispositivi e servizi → Aggiungi integrazione cercando “Moon”. Questo crea l’entità sensor.moon con stati come new_moon, waxing_crescent, first_quarter, waxing_gibbous, full_moon, waning_gibbous, last_quarter, waning_crescent. È la base dati, ma da sola non basta per visualizzare il disco lunare attuale, esteticamente molto più gradevole da mostrare in dashboard. Per quello, ci serve una fonte fotografica. Il disco lunare in tempo reale: API NASA SVS La NASA mette a disposizione un’API pubblica e gratuita nel suo set di endpoint SVS (Scientific Visualization Studio) e che restituisce, per ogni ora, l’immagine esatta del disco lunare vista dalla Terra, completa di angolo di librazione, illuminazione e fase. L’endpoint è: https://svs.gsfc.nasa.gov/api/dialamoon/{YYYY-MM-DDTHH:MM} dove la data e l’ora vanno passate in formato ISO 8601. La risposta JSON contiene, tra le varie cose, proprio l’informazione che ci serve: { "image": { "url": "https://svs.gsfc.nasa.gov/vis/a000000/a005100/a005190/frames/730x730_1x1_30p/moon.XXXX.jpg" } } Quindi value_json.image.url è l’URL dell’immagine PNG del disco lunare per quell’istante. Il problema è il formato della data: l’API si aspetta 2026-05-10T14:00, ma i template Jinja2 di HA hanno now() che restituisce un oggetto datetime completo. Serve un passo intermedio. Il sensore template per la data Creo un template sensor che formatta la data corrente nel formato esatto richiesto dall’API: template: - sensor: - unique_id: datetime_universal_nasa name: "Data e Ora (formato REST NASA)" state: > {{ now().strftime('%Y-%m-%dT%H:%M') }} attributes: template: space Questo crea sensor.datetime_universal_nasa che aggiorna il suo stato ogni minuto con il timestamp corrente nel formato giusto. L’attributo template: space è un’etichetta libera che uso per raggruppare logicamente i sensori legati al tema astronomico — non ha impatto funzionale, ma torna utile per filtrare le entità nella UI. Come spesso accade, la scelta se realizzare il sensore template da UI piuttosto che da Yaml è molto personale e del tutto equivalente. Il REST sensor per il disco lunare Ora uso questo sensore come parte dell’URL nella resource_template del REST sensor. Questo, invece, ad oggi può essere solo configurato tramite Yaml: rest: - platform: rest unique_id: nasa_moon_phase resource_template: >- https://svs.gsfc.nasa.gov/api/dialamoon/{{ states('sensor.datetime_universal_nasa') if states('sensor.datetime_universal_nasa') not in ['unknown', 'unavailable'] else now().strftime('%Y-%m-%dT%H:%M') }} name: Nasa Moon Phase Info value_template: "{{ value_json.image.url }}" json_attributes: - image Due cose degne di nota: Il fallback: se il sensore della data non è ancora disponibile al boot, uso now().strftime(...) direttamente nella resource_template. Questo evita che il sensore vada in unavailable durante l’avvio di HA, sporcando il log di avvio. json_attributes: [image]: salvo l’intero oggetto image come attributo del sensore, non solo l’URL. Questo mi permette di accedere a tutti i campi restituiti dall’API in futuro senza modificare la configurazione. Il sensore sensor.nasa_moon_phase_info avrà come state l’URL dell’immagine del disco lunare aggiornata all’ora corrente. Generic Camera: mostrare l’immagine Un REST sensor restituisce testo (l’URL), non l’immagine stessa. Per mostrare l’immagine nella dashboard serve una Generic Camera. L’integrazione Generic Camera (o Still Image Camera) di Home Assistant crea un’entità camera.* che scarica periodicamente un’immagine da un URL statico o dinamico e la espone come stream video o still image. La si aggiunge da Impostazioni → Dispositivi e servizi → Generic Camera e nel campo Still Image URL si inserisce un template Jinja2 molto semplice: {{ states('sensor.nasa_moon_phase_info') }} oppure, equivalentemente: {{ state_attr('sensor.nasa_moon_phase_info', 'value') }} Questo crea camera.fasi_lunari_nasa (il nome si può scegliere in fase di configurazione). La camera aggiorna l’immagine ogni volta che lo stato del sensore cambia — che avviene ogni ora, allineato con l’ora astronomica. Nella dashboard, una card picture-entity che punta a questa camera mostra il disco lunare aggiornato: type: picture-entity entity: sensor.fasi_lunari camera_image: camera.fasi_lunari_nasa camera_view: auto show_name: true show_state: true Ho completato aggiungendo nell’heading della sezione un badge con sensor.sun_next_dusk: l’orario del prossimo crepuscolo astronomico. È una piccola aggiunta, ma molto pratica: guardando la dashboard so immediatamente a che ora inizia il buio utile per l’osservazione. Il sensore sun.sun espone automaticamente questi attributi tramite l’integrazione sole nativa di HA. Questo, per praticità, è l’intero codice dell’intestazione di sezione: type: heading icon: mdi:earth heading_style: title heading: "Sistema Terra-Luna" badges: - type: entity entity: sensor.sun_next_dusk La foto della Luna può finalmente essere visualizzata in dashboard utilizzando una qualsiasi card che possa visualizzare un flusso di fotocamera. La custom:lunar-phase-card Il disco lunare è già un buon punto di partenza, ma la custom:lunar-phase-card aggiunge un livello di informazione in più: mostra l’arco celeste con la posizione della Luna, il grafico dell’altitudine nelle prossime ore, e i dati di fase, prossimo plenilunio, distanza dalla Terra e percentuale di illuminazione. Si installa da HACS (Repository: lunar-phase-card). La configurazione per Milano: type: custom:lunar-phase-card use_default: true show_background: true selected_language: it default_card: horizon moon_position: left compact_view: false 12hr_format: false number_decimals: 0 latitude: 45.4668 longitude: 9.1457 location: city: Milano country: Italia graph_config: y_ticks: true x_ticks: true show_time: true show_current: true show_legend: false show_highest: true y_ticks_position: left y_ticks_step_size: 55 time_step_size: 30 Card di sintesi della Luna use_default: true dice alla card di calcolare autonomamente la fase senza affidarsi a un sensore esterno — il che la rende indipendente dal REST sensor NASA e dal sensore moon. Questo è utile per avere ridondanza: anche se l’API NASA fosse irraggiungibile, la card continua a mostrare la fase calcolata internamente. Sensore fasi lunari con icona e immagine Il sensore sensor.moon_phase dell’integrazione Moon restituisce valori interni in inglese (new_moon, waxing_crescent, ecc.). Per i badge e le Mushroom card — che mostrano nome, icona e immagine in modo visivamente curato — conviene costruire un template sensor dedicato che traduca questi valori in italiano, abbini l’icona MDI corretta e usi l’immagine NASA come picture dell’entità. Il risultato è un piccolo tocco di classe, esteticamente gradevole. template: - sensor: - unique_id: moon_phase_anime name: "Fasi Lunari" state: > {% set phase = states('sensor.moon_phase') %} {% set moon_phases = { 'new_moon': 'Luna nuova', 'waxing_crescent': 'Primo crescente', 'first_quarter': 'Primo quarto', 'waxing_gibbous': 'Gibbosa crescente', 'full_moon': 'Luna piena', 'waning_gibbous': 'Gibbosa calante', 'last_quarter': 'Ultimo quarto', 'waning_crescent': 'Ultimo crescente' } %} {{ moon_phases[phase] if phase in moon_phases.keys() else 'Sconosciuto' }} picture: "{{ states('sensor.nasa_moon_phase_info') }}" icon: > {% set phase = states('sensor.moon_phase') %} {% set moon_icons = { 'new_moon': 'mdi:moon-new', 'waxing_crescent': 'mdi:moon-waning-crescent', 'first_quarter': 'mdi:moon-first-quarter', 'waxing_gibbous': 'mdi:moon-waxing-gibbous', 'full_moon': 'mdi:moon-full', 'waning_gibbous': 'mdi:moon-waning-gibbous', 'last_quarter': 'mdi:moon-last-quarter', 'waning_crescent': 'mdi:moon-waning-crescent' } %} {{ moon_icons[phase] if phase in moon_icons.keys() else 'mdi:moon-new' }} Badge costruito a partire dal sensore. Notare l'immagine e le descrizioni in italiano Alcune note: picture: l’attributo speciale picture di un template sensor sovrascrive l’icona con un’immagine. In questo caso punta allo stato di sensor.nasa_moon_phase_info, che come abbiamo visto contiene l’URL del disco lunare attuale. Mushroom card e chip card mostrano automaticamente questa immagine tonda al posto dell’icona — il risultato, come dicevo, è visivamente molto efficace. icon: mappa ogni fase sulla corrispondente icona mdi:moon-*. La libreria MDI include tutte le otto fasi. Quando picture è valorizzata la card usa quella, ma l’icona rimane disponibile come fallback o in contesti che non supportano picture. Fallback 'Sconosciuto' / 'mdi:moon-new': il filtro if phase in moon_phases.keys() else protegge da stati transitori (unknown, unavailable) al boot o durante riavvii. Relazione con gli altri sensori: questo sensore è un aggregatore — non interroga API esterne, si limita a leggere sensor.moon_phase (nativo) e sensor.nasa_moon_phase_info (REST). Se l’API NASA fosse irraggiungibile, lo stato in italiano e l’icona continuerebbero a funzionare; mancherebbe solo l’immagine. Riepilogo In questa prima parte abbiamo costruito il modulo Sistema Terra-Luna. Nella seconda parte ci sposteremo, invece, sul Sole: immagini live da SOHO e GOES-16, la previsione delle aurore boreali OVATION, e i dati dello spazio meteorologico con grafici BT/BZ in tempo reale. --- title: Illuminanza intelligente per lo smartworking: fisica, modelli e controllo in Home Assistant date: 2026-05-09 url: https://gabrielebaldassarre.com/home-assistant/illuminanza-smartworking-home-assistant/ excerpt: Come stimare l'illuminamento di un ambiente senza sensori fisici, usando la posizione del sole e la fotometria delle finestre, e come usare questa stima per automatizzare la luce ottimale quando si lavora da casa. audience: practitioner proficiency: advanced category: Home Assistant prerequisites: Home Assistant; Fotometria; Jinja2; Illuminamento; Zigbee; Radiometria; Isotropia; Irradianza; Adaptive Lighting; YAML; Lux; Lumen; View factor; Trasmittanza; Angolo di incidenza; Flusso luminoso; Controllo feedforward; Philips Hue; CRI (Color Rendering Index); COB (Chip on Board); Ray tracing; OpenStreetMap; Modello di cielo diffuso isotropo; Copertura nuvolosa; UNI EN 12464-1; Distanza angolare; Tomorrow.io --- Chi lavora da casa conosce il problema: la luce cambia continuamente durante la giornata, e spesso ci si ritrova a lavorare al computer con una illuminazione inadeguata senza rendersene conto. Troppa luce crea abbagliamento sullo schermo, troppa poca affatica gli occhi. La soluzione ideale è un sistema che compensi automaticamente la luce naturale con quella artificiale, mantenendo l’illuminamento costante. In questo articolo descrivo come ho costruito questo sistema in Home Assistant, partendo da zero sensori di luminosità fisici — che non ho — fino a una automazione che stabilizza l’illuminamento del soggiorno durante le ore di lavoro. Il problema: non ho sensori di luce I sensori di illuminanza fisici esistono, costano poco e funzionano bene. Ma non mi andava di acquistarli e di aggiungere altri dispositivi alla mia già piuttosto affollata costellazione Zigbee. Volevo capire, allora, se fosse possibile stimare l’illuminamento con quello che già avevo: la posizione geografica della mia casa, le caratteristiche geometriche delle finestre, e le integrazioni meteo già presenti in Home Assistant. La risposta è sì, con qualche compromesso. Il modello fisico: soleggiamento Il punto di partenza è sun.sun, l’entità built-in di Home Assistant che espone in ogni momento l’elevazione e l’azimuth del sole. A questi aggiungo un sensore di illuminanza esterna (sensor.luminanza_esterna) che viene da un’integrazione che ha esattamente questo scopo e misura i lux al suolo nelle condizioni meteo attuali — già corretta per la copertura nuvolosa la cui misura proviene tipicamente dall’integrazione meteo. Il contributo della luce solare all’interno di una stanza dipende da tre fattori geometrici: L’angolo di incidenza della luce sulla finestra La trasmittanza del vetro ($\tau_g$) Il rapporto tra area finestra e area stanza ($A_f / A_{room}$) La formula per il componente diretto è quella standard della fotometria degli interni: Edir=Eext⋅τg⋅cos⁡θ⋅AfAroomE_{dir} = E_{ext} \cdot \tau_g \cdot \cos\theta \cdot \frac{A_f}{A_{room}}Edir​=Eext​⋅τg​⋅cosθ⋅Aroom​Af​​ dove: $E_{ext}$ è l’illuminanza esterna al suolo in condizioni di cielo sereno (in lux, da sensore meteo) $\tau_g$ è la trasmittanza del vetro, ovvero la frazione di luce che attraversa il vetro (tipicamente 0.15–0.30 per doppi vetri con trattamento) $A_f$ è l’area della finestra in m² $A_{room}$ è l’area della stanza in m² $\cos\theta$ è l’angolo di incidenza del raggio solare sulla superficie della finestra, calcolato a partire da elevazione e azimuth del sole: cos⁡θ=sin⁡(α)⋅cos⁡(β)+cos⁡(α)⋅sin⁡(β)⋅cos⁡(Δϕ)\cos\theta = \sin(\alpha) \cdot \cos(\beta) + \cos(\alpha) \cdot \sin(\beta) \cdot \cos(\Delta\phi)cosθ=sin(α)⋅cos(β)+cos(α)⋅sin(β)⋅cos(Δϕ) con $\alpha$ = elevazione solare, $\beta$ = inclinazione della finestra (90° per una finestra verticale), $\Delta\phi$ = differenza di azimuth tra sole e normale alla finestra. Correzione per la copertura nuvolosa. Il modello appena descritto assume implicitamente che la luce solare sia direzionale: tutti i raggi arrivano dalla stessa direzione (quella del sole) e $\cos\theta$ cattura correttamente quanto di quella luce passa attraverso la finestra. Questa ipotesi è buona sotto cielo sereno. Sotto cielo coperto, invece, la situazione cambia radicalmente. Le nuvole diffondono la radiazione solare in tutte le direzioni: la luce non ha più una provenienza privilegiata ma arriva isotropicamente da tutta la volta celeste, con intensità approssimativamente uniforme su ogni direzione. Questo è il modello di cielo diffuso isotropo (Liu e Jordan, 1960), la formulazione più semplice e più usata per la componente diffusa: si assume che la luminanza del cielo sia la stessa in ogni punto dell’emisfero. In questo regime, il fattore geometrico rilevante non è più $\cos\theta$ — che descrive l’angolo tra il raggio direzionale e la normale alla finestra — ma il view factor della finestra verso il cielo: la frazione dell’emisfero celeste che la finestra “vede”. Per una finestra piana verticale con inclinazione $\beta$ rispetto all’orizzontale, il view factor verso la metà superiore dell’emisfero è un risultato standard della radiometria: FV=1+cos⁡β2FV = \frac{1 + \cos\beta}{2}FV=21+cosβ​ Per una finestra verticale ($\beta = 90°$) questo vale esattamente $0.5$: la finestra vede metà cielo, e con luce isotropa raccoglie esattamente la metà dell’irradianza disponibile. Non è un’approssimazione né una costante empirica — è pura geometria. Ora, nella realtà quotidiana il cielo è raramente completamente sereno o completamente coperto: si trova quasi sempre in uno stato intermedio. Molte integrazioni meteo, però, come dicevo offrono una stima sulla presenza di nubi. Io, per esempio, posso avvalermi del sensor.tomorrow_io_casa_cloud_cover, che fornisce in tempo reale la copertura nuvolosa $f_c \in [0,1]$ (0 = sereno, 1 = coperto). È naturale usarlo come peso per interpolare linearmente tra i due regimi: cos⁡θeff=(1−fc)⋅cos⁡θ+fc⋅0.5\cos\theta_{eff} = (1 - f_c)\cdot\cos\theta + f_c \cdot 0.5cosθeff​=(1−fc​)⋅cosθ+fc​⋅0.5 Quando $f_c = 0$ si recupera la formula originale, puramente direzionale. Quando $f_c = 1$ il termine $\cos\theta$ scompare del tutto e rimane solo il view factor isotropo. Per valori intermedi si ottiene una transizione continua tra i due comportamenti limite. Sempre che ci accontentiamo, come al solito, di una semplificazione lineare. Il guadagno pratico è più evidente nelle situazioni in cui i due regimi producono risultati molto diversi: tipicamente al mattino presto o in inverno, quando il sole è basso ($\cos\theta$ piccolo) ma il cielo è parzialmente coperto — il modello senza correzione sottostimerebbe sensibilmente la luce disponibile. Al contrario, a mezzogiorno estivo con sole alto ma cielo coperto, il modello originale sovrastimerebbe. La correzione riduce l’errore in entrambe le direzioni, senza aggiungere nessun parametro da calibrare. A questo si aggiunge un componente indiretto (luce diffusa dal cielo e riflessa dalle superfici): Eind=Eext⋅DFindE_{ind} = E_{ext} \cdot DF_{ind}Eind​=Eext​⋅DFind​ dove $DF_{ind}$ è un fattore di daylight indiretto, calibrato empiricamente in base all’ambiente. In Jinja2 per Home Assistant, per la finestra del mio soggiorno orientata a sud (azimuth 180°), nel mio appartamento a Milano: {% set lux_ext = states('sensor.luminanza_esterna') | float / 10 %} {% set elev_deg = state_attr('sun.sun','elevation') | float %} {% set azimut_sun = state_attr('sun.sun','azimuth') | float %} {% set azimut_window = 180 %} {% set tilt_deg = 90 %} {% set elev_rad = elev_deg * 3.14159 / 180 %} {% set tilt_rad = tilt_deg * 3.14159 / 180 %} {% set delta_raw = (azimut_sun - azimut_window) | abs %} {% set delta_azimut = [delta_raw, 360 - delta_raw] | min %} {% set delta_azimut_rad = delta_azimut * 3.14159 / 180 %} {% set cos_theta = (sin(elev_rad) * cos(tilt_rad)) + (cos(elev_rad) * sin(tilt_rad) * cos(delta_azimut_rad)) %} {% set cos_theta = [cos_theta, 0] | max %} {% set fc = states('sensor.tomorrow_io_casa_cloud_cover') | float / 100 %} {% set cos_theta = (1 - fc) * cos_theta + fc * 0.5 %} {% set tau_g = 0.18 %} {% set Af = 2.7 %} {% set A_room = 25 %} {% set DF_indiretto = 0.005 %} {% set lux_diretto = lux_ext * tau_g * cos_theta * (Af / A_room) %} {% set lux_indiretto = lux_ext * DF_indiretto %} {{ (lux_diretto + lux_indiretto) | round(0) }} Un dettaglio non banale: il calcolo di delta_azimut deve usare la distanza angolare minima sul cerchio, non il valore assoluto della differenza. Senza questa correzione, una finestra orientata a nord (azimuth 0°) con il sole a 350° calcolerebbe un delta di 350° invece di 10°, producendo un coseno quasi nullo e un soleggiamento falsamente azzerato. La correzione è sintetizzata in questa parte di codice: {% set delta_raw = (azimut_sun - azimut_window) | abs %} {% set delta_azimut = [delta_raw, 360 - delta_raw] | min %} L’illuminamento totale della stanza Il soleggiamento è solo metà dell’equazione. L’altra metà è la luce prodotta dalle sorgenti artificiali. Io ho diverse lampadine Philips Hue dimmerabili che dichiarano esplicitamente 806 lm, quindi: Elamp=lm⋅(brightness/255)AroomE_{lamp} = \frac{lm \cdot (brightness / 255)}{A_{room}}Elamp​=Aroom​lm⋅(brightness/255)​ La linearità brightness → lumen è un’approssimazione (i LED non sono perfettamente lineari), ma sufficiente per questo scopo. Per le strisce LED prese su Aliexpress, invece, ho usato un modello linearizzato che tiene conto delle caratteristiche (approssimate a dir poco) del dispositivo. A titolo d’esempio, per la striscia LED che illumina la mia zona pranzo (2 m di COB, per un totale di 960 LED CRI90, alimentati a 24VDC → ~15 W/m) la stima del flusso luminoso sarà: ΦLED=2 m×15 W/m×93 lm/W≈2800 lm\Phi_{LED} = 2 \, m \times 15 \, W/m \times 93 \, lm/W \approx 2800 \, lmΦLED​=2m×15W/m×93lm/W≈2800lm In generale e per qualsiasi tipologia di striscia LED, una volta ricavata la potenza assorbita dal dispositivo, non dovrebbe essere difficile risalire al flusso luminoso emesso (purché, ripeto, ci si accontenti della semplificazione lineare). Il sensore finale sensor.illuminamento_soggiorno somma tutti i contributi: {% set area_stanza = 25 %} {% set lumen_per_lampadina = 806 %} {% set lumen_led_tavolo = 2800 %} {% set lux_sol = states('sensor.soleggiamento_soggiorno') | float %} {% set lux_lamp_1 = (lumen_per_lampadina * (state_attr('light.lampadina_soggiorno_1', 'brightness') | float / 255) / area_stanza) if is_state('light.lampadina_soggiorno_1', 'on') and state_attr('light.lampadina_soggiorno_1', 'brightness') is not none else 0 %} {% set lux_lamp_2 = (lumen_per_lampadina * (state_attr('light.lampadina_soggiorno_2', 'brightness') | float / 255) / area_stanza) if is_state('light.lampadina_soggiorno_2', 'on') and state_attr('light.lampadina_soggiorno_2', 'brightness') is not none else 0 %} {% set lux_lamp_3 = (lumen_per_lampadina * (state_attr('light.lampadina_soggiorno_3', 'brightness') | float / 255) / area_stanza) if is_state('light.lampadina_soggiorno_3', 'on') and state_attr('light.lampadina_soggiorno_3', 'brightness') is not none else 0 %} {% set lux_lamp_4 = (lumen_per_lampadina * (state_attr('light.lampadina_soggiorno_4', 'brightness') | float / 255) / area_stanza) if is_state('light.lampadina_soggiorno_4', 'on') and state_attr('light.lampadina_soggiorno_4', 'brightness') is not none else 0 %} {% set lux_lamp_ingresso = (lumen_per_lampadina * (state_attr('light.lampadina_ingresso', 'brightness') | float / 255) / area_stanza * 0.75) if is_state('light.lampadina_ingresso', 'on') and state_attr('light.lampadina_ingresso', 'brightness') is not none else 0 %} {% set lux_led_tavolo = (lumen_led_tavolo * (state_attr('light.led_tavolo', 'brightness') | float / 255) / area_stanza) if is_state('light.led_tavolo', 'on') and state_attr('light.led_tavolo', 'brightness') is not none else 0 %} {{ (lux_sol + lux_lamp_1 + lux_lamp_2 + lux_lamp_3 + lux_lamp_4 + lux_lamp_ingresso + lux_led_tavolo) | round(0) }} La lampadina dell’ingresso ha una particolarità: riceve, infatti, un coefficiente di abbattimento (spillover) di 0.75 perché illumina un’area attigua e non contribuisce esclusivamente al soggiorno. Quanta luce artificiale serve? Un controllo feedforward La letteratura ergonomica indica 300–500 lux come range ottimale per il lavoro al videoterminale (norma UNI EN 12464-1, che, tra le altre cose, è una vera miniera di idee per possibili espansioni di questo modello). Ho scelto 400 lux come target default, regolabile tramite un helper input_number, così da facilitare eventuali fine tuning successivi. Dirò un’ovvietà, ma ci tengo a far notare che questo non è un sistema retroazionato in senso stretto. In un vero sistema a ciclo chiuso si misurerebbe l’illuminamento effettivo della stanza con un sensore fisico, si calcolerebbe l’errore rispetto al target, e si userebbe quell’errore per correggere l’intensità delle sorgenti luminose. Qui invece l’illuminamento reale non è mai misurato e non entra mai nel calcolo (che, poi, era la premessa dell’articolo!): si misura solo la perturbazione (la luce naturale stimata), e si usa un modello del processo (i lumen noti delle lampade) per calcolare preventivamente l’azione correttiva. È un controllo feedforward: efficiente e senza oscillazioni, ma che non si autocorregge se il modello è impreciso. Qui c’è solo algebra: se la luce naturale copre già 200 lx, ho bisogno di altri 200 lx artificiali. Conoscendo il flusso massimo producibile dalle sorgenti disponibili ($E_{max,art} \approx 249 \, lx$ a piena potenza), la brightness necessaria è: brightness%=min⁡ ⁣(100,max⁡(0,  Etarget−Enaturale)Emax,art×100)brightness\% = \min\!\left(100, \frac{\max(0,\; E_{target} - E_{naturale})}{E_{max,art}} \times 100\right)brightness%=min(100,Emax,art​max(0,Etarget​−Enaturale​)​×100) A questo punto non dovrebbe essere complicato realizzare una automazione che si adatti alle specifiche esigenze di ognuno. Il mio consiglio è, però, quello di utilizzare, all’interno dell’automazione delle variables, per praticità: variables: lux_target: "{{ states('input_number.smartworking_target_illuminamento') | float }}" lux_naturale: "{{ states('sensor.soleggiamento_soggiorno') | float }}" lux_gap: "{{ [lux_target - lux_naturale, 0] | max }}" lux_max_art: "{{ (4 * 806 + 2800 + 806 * 0.25) / 25 }}" brightness_pct: "{{ ([lux_gap / lux_max_art * 100, 100] | min) | round(0) | int }}" Ad oggi l’aggiunta delle variables è, credo, ancora prerogativa del codice Yaml. Bisogna, quindi, passare a questa modalità durante la stesura dell’automazione per inserirle. Poi, si può tranquillamente tornare in modalità visuale e riprendere il lavoro. Convivenza con Adaptive Lighting Ho già Adaptive Lighting attivo sul soggiorno, che gestisce la temperatura colore in funzione della posizione del sole, e ti consiglio di fare lo stesso…è davvero divertente! Il fatto, però, è che l’integrazione può gestire anche la brightness, ma per lo smartworking questo crea un conflitto: AL vuole abbassare la luminosità al mattino presto o alla sera, mentre il compensatore feedforward vuole alzarla. La soluzione è disabilitare temporaneamente il controllo brightness di AL durante le ore di smartworking, lasciandogli solo il controllo della temperatura colore: sequence: - action: switch.turn_off target: entity_id: switch.adaptive_lighting_adapt_brightness_adaptive_zona_giorno - action: light.turn_on data: brightness_pct: "" target: entity_id: - light.luci_soggiorno - light.led_tavolo - light.lampadina_ingresso Al termine dell’orario di lavoro, o se la luce naturale diventa sufficiente, AL riprende il controllo completo. Anche questo può essere fatto tramite una automazione che viene invocata al cambiamento di stato di una entità di tipo Scheduler. Limiti del modello e sviluppi futuri Il sistema funziona bene, ma ci sono alcune approssimazioni di cui è bene essere consapevoli: Ombreggiamento da edifici: la formula non considera edifici o ostacoli che bloccano il sole a certe ore. Se il palazzo di fronte proietta ombra sulla finestra dal primo pomeriggio, il modello sovrastima il soleggiamento. In linea di principio è possibile costruire un modello di occlusione degli ostacoli a partire dai dati di OpenStreetMaps, ma più come curiosità scientifica. I dati cartografici OSM, infatti, pur avendo quasi sempre i dati planimetrici degli edifici, mancano spesso dei dati di altezza, quindi il modello risultante avrebbe scarsa utilità pratica. Il modello di ray tracing sarebbe, quindi, bidimensionale: basterebbe, se le sorgenti luminose fossero basse sull’orizzonte, ma chiaramente avendo a che fare con il sole l’approssimazione non regge più. Latenza meteo: sensor.luminanza_esterna potrebbe aggiornarsi con frequenza limitata. L’integrazione Tomorrow.io che utilizzo cattura i cambi di copertura nuvolosa esplicitamente da un sensore del tipo sensor.tomorrow_io_casa_cloud_cover, che si aggiorna più spesso, ma un ritardo residuo rimane durante i transitori veloci — per esempio, una nuvola solitaria che transita davanti al sole nel giro di pochi minuti. Linearità brightness/lumen: i LED dimmerabili non hanno una curva perfettamente lineare. Per una stima più precisa si potrebbe usare una curva gamma, ma la differenza pratica è trascurabile per questo scopo. E, ovviamente, parte dall’assunto (super semplicistico) che la volta celeste sia isotropa. Nonostante questi limiti, il risultato è un sistema che si comporta in modo fisicamente coerente e che migliora misurabilmente il comfort visivo durante le ore di lavoro, senza richiedere alcun intervento manuale o un sensore esterno. --- title: La quantità di moto e l'impulso (fisica classica) date: 2021-05-08 url: https://gabrielebaldassarre.com/fisica/quantita-moto/ excerpt: Studiando la fisica nei libri in lingua inglese, può capitare di fare un po' di confusione con i concetti di momento, quantità di moto e impulso, per via di come questi concetti sono, invece, espressi con la lingua di Newton. Questo breve articolo cercherà di fugare ogni possibile dubbio. category: Fisica --- Come quasi tutti ricorderanno dalla fisica studiata a scuola, Newton ci ha insegnato che la quantità di moto è una grandezza vettoriale data dal prodotto della massa del corpo per la sua velocità e si indica con p: p⃗=mv⃗\vec{p} = m\vec{v}p​=mv e si misura, di conseguenza, in [kg][m]/[s][kg][m]/[s][kg][m]/[s]. Viene, talvolta, anche chiamato momento lineare anche se, tecnicamente, non si tratta del momento di un vettore. Questa dicitura è un anglicismo dovuto al fatto che la quantità di moto in inglese è chiamata momentum. E questa è una prima causa di confusione, dato che in italiano il momento meccanico, o coppia di forze, è un concetto totalmente distinto (e che, in inglese, si dice moment o torque, appunto). È una grandezza vettoriale che ha direzione e verso del vettore velocità e modulo pari al modulo della velocità moltiplicato per la massa (che è una grandezza scalare sempre positiva). Dalla meccanica classica, sappiamo che un corpo in movimento imprime una forza sul corpo che tenti di fermarlo. Tanto maggiori sono la massa e la velocità del corpo in movimento, tanto maggiore è la forza impressa. Per questo motivo un proiettile, che pur avendo inerzia ridotta per via della sua piccola massa, ma gran velocità, quando colpisce una piccola area di tessuto trasferisce una forza tale da generare molti danni (ma non abbastanza da far sbalzare violentemente il corpo del cattivo sulla vetrata retrostante così da farlo precipitare su una inferriata appuntita, come si vede nei film d’azione). Ugualmente, urtare contro un camion di grande massa, anche se si muove lentamente, ci provocherà ben più di un mal di testa. Ebbene, questo fenomeno è ben espresso dalla quantità di moto che, in effetti, per la seconda legge della dinamica, può essere espressa anche in [N][s][N][s][N][s] e quindi rappresentare la forza trasferita per unità di tempo. Ma qual è questa forza impressa? Per rispondere a questa domanda, supponiamo che un corpo di massa mmm e avente velocità iniziale viv_ivi​ subisca una forza per un intervallo di tempo t durante il quale ha un’accelerazione che lo porta alla velocità finale vfv_fvf​ (ignoriamo tutti gli effetti di attrito sul sistema): Abbiamo quindi le quantità di moto iniziale pi⃗=mvi⃗\vec{p_i} = m\vec{v_i}pi​​=mvi​​ e finale pf⃗=mvf⃗\vec{p_f} = m\vec{v_f}pf​​=mvf​​ Pertanto, la variazione per unità di tempo della quantità di moto sarà: pf⃗−pi⃗t=mvf⃗−mvi⃗t=mvf⃗−vi⃗t\frac{\vec{p_f} - \vec{p_i}}{t} = \frac{m\vec{v_f} - m\vec{v_i}}{t} = m\frac{\vec{v_f} - \vec{v_i}}{t}tpf​​−pi​​​=tmvf​​−mvi​​​=mtvf​​−vi​​​ Passando alle variazioni infinitesime dp⃗dt=mdv⃗dt\frac{d\vec{p}}{dt} = m\frac{d\vec{v}}{dt}dtdp​​=mdtdv​ Poiché la variazione di velocità nell’intervallo di tempo infinitesimo rappresenta l’accelerazione, al secondo membro possiamo sostituire con la forza F⃗\vec{F}F e quindi: F⃗=dp⃗dt\vec{F} = \frac{d\vec{p}}{dt}F=dtdp​​ Ovvero, la forza che agisce su un corpo produce una variazione della quantità di moto dello stesso. È una formulazione più generale della seconda legge del moto che tiene conto anche delle situazioni in cui la massa del corpo in moto varia, come ad esempio un razzo lanciato verso lo spazio, che brucia carburante riducendo la sua massa complessiva. In questo caso, il secondo principio della dinamica andrebbe più correttamente scritto in forma differenziale (derivata della quantità di moto rispetto al tempo): F⃗=dp⃗dt\vec{F} = \frac{d\vec{p}}{dt}F=dtdp​​ dove la variazione della quantità di moto può rappresentare una variazione di velocità, di massa, o entrambe. Se la massa non varia, essa è costante rispetto alla derivazione per il tempo: F⃗=mdv⃗dt=ma⃗\vec{F} = m\frac{d\vec{v}}{dt} = m\vec{a}F=mdtdv​=ma che è quanto già sappiamo. L’impulso L’impulso è un’altra grandezza molto utile in fisica classica: rappresenta la quantità di moto effettivamente trasmessa da un corpo all’altro a seguito di un urto o, più in generale, gli effetti di una forza che agisce per un intervallo di tempo molto piccolo rispetto all’intero fenomeno osservato (sarebbe più corretto dire che un urto tra due corpi rigidi comporta l’intervento di una forza impulsiva). Ad esempio, un tennista che colpisce una pallina durante il servizio, imprime alla stessa una forza molto intensa e vi trasferisce una grande quantità di moto in un intervallo molto breve. Oppure la polvere da sparo che, esplodendo, causa l’espulsione del proiettile ad alta velocità dalla canna della pistola in pochi centesimi di secondo, e così via. Poiché le forze di questo tipo non sono costanti nel tempo, pur agendo per intervalli di tempo molto piccoli, in questi casi, più che con il secondo principio della dinamica è conveniente lavorare con una quantità che è il prodotto tra la forza agente e l’intervallo in cui agisce. L’impulso, appunto. I⃗=F⃗Δt\vec{I} = \vec{F}\Delta tI=FΔt Questa grandezza, come si può vedere, si misura in [N][s], come la quantità di moto. Rappresenta, come dicevamo, la quantità di moto trasmessa da una cosiddetta forza impulsiva. Questo è l’enunciato del cosiddetto teorema dell’impulso: I⃗=Δp⃗\vec{I} = \Delta \vec{p}I=Δp​ Quando la forza varia nel tempo, ha variazioni infinitesime: F⃗=dp⃗dt\vec{F} = \frac{d\vec{p}}{dt}F=dtdp​​ Integrando ambo i membri nell’intervallo di azione della forza abbiamo l’impulso: Δp=∫t1t2Fdt\Delta p = \int_{t_1}^{t_2} FdtΔp=∫t1​t2​​Fdt --- title: Che cos'è l'elettronvolt date: 2021-05-06 url: https://gabrielebaldassarre.com/fisica/elettronvolt/ excerpt: In questo breve articolo ci concentreremo sull'elettronvolt, una delle unità di misura più utilizzate in fisica delle particelle e ai cui i fisici sono così affezionati da usarlo per misurare praticamente tutto. category: Fisica --- L’elettronvolt (simbolo eV) è un’unità di misura dell’energia particolarmente adatta, per via del suo valore, a misurarne i valori su scale atomiche e subatomiche. In pratica, esso coincide con il lavoro compiuto dalla forza elettrostatica agente su un elettrone che si muove nel vuoto in un campo elettromagnetico quando la particella si sposta da un punto iniziale e finale aventi differenza di potenziale di 1 Volt. È, quindi, l’energia cinetica guadagnata (o persa) dall’elettrone accelerato dal campo caratterizzato da quel potenziale. Poiché il lavoro del campo elettromagnetico è dato dal prodotto del potenziale per la carica e la carica dell’elettrone è pari a e=1,602×10−19Ce = 1,602\times10^{-19}Ce=1,602×10−19C: 1V=1J1C⇒1eV=1J1C(1,602×10−19C)=1,602×10−19 J1V = \frac{1J}{1C} \Rightarrow 1eV = \frac{1J}{1C}(1,602\times10^{-19}C) = \boxed{1{,}602\times10^{-19}\,\text{J}}1V=1C1J​⇒1eV=1C1J​(1,602×10−19C)=1,602×10−19J​ Ricordando come 1 J sia il lavoro compiuto da una forza per spostare una massa di 1kg per un metro (o in alternativa di sollevare a 1m di altezza una massa di poco più di 100gr soggetta alla gravità terrestre), ne consegue che un elettronvolt sia una quantità di energia molto piccola. Per questo motivo, spesso si utilizza uno dei suoi multipli come il megaelettronvolt (MeV) o il gigaelettronvolt (GeV), soprattutto quando si utilizzà l’elettronvolt come unità di misura per la massa. Ciò che, infatti, genera più spesso confusione nell’uso dell’elettronvolt è il fatto che, soprattutto in fisica delle alte energia, questo sia utilizzato anche come unità di misura per la massa. Ricorderete, infatti, che quando fu scoperto il Bosone di Higgs, i ricercatori dichiararono di averlo “trovato” all’incirca dove si aspettavano che fosse, ovvero avente una massa di circa 125GeV/c2125 GeV/c^2125GeV/c2. Cosa significa questo? Per rispondere alla domanda, vediamo la definizione dimensionale dell’energia: [J]=[kg][m]2[s]2[J] = \frac{[kg][m]^2}{[s]^2}[J]=[s]2[kg][m]2​ se si divide per la dimensione della velocità [m]/[s][m]/[s][m]/[s] si ottiene: [J]=[kg][m][s][J] = \frac{[kg][m]}{[s]}[J]=[s][kg][m]​ che è la dimensione della quantità di moto. Quindi, dividendo l’energia espressa in eV per c, quello che otteniamo è effettivamente una quantità di moto espressa in eV/c. Andando ora a dividere l’espressione ancora una volta per la velocità. Nell’equazione dimensionale rimane solo la misura della m massa. Quindi, per misurare la massa si può usare come unità di misura eV/c2eV/c^2eV/c2. Ma perché proprio c? Per rispondere a questa domanda, basti ricordare l’equazione dell’equivalenza di massa-energia, ovvero la famosissima E=mc2E = mc^2E=mc2 della relatività ristretta, che esprime il legame tra la massa a riposo e l’energia, attraverso la costante rappresentata dalla velocità della luce nel vuoto. Dimensionalmente, i conti tornano: eV/c2eV/c^2eV/c2 è una massa. Facciamo un esempio. L’equivalente in massa di 1eV è: 1eV/c2=1eVc2=1,602×10−19(2,99×108)2=1,78×10−36 kg1eV/c^2 = \frac{1eV}{c^2} = \frac{1{,}602 \times 10^{-19}}{(2{,}99 \times 10^8)^2} = \boxed{1{,}78 \times 10^{-36}\,\text{kg}}1eV/c2=c21eV​=(2,99×108)21,602×10−19​=1,78×10−36kg​ Ad esempio, la massa dell’elettrone: me=9,109×10−31kg=9,109×10−311,78×10−36=0,511×106eV/c2=0,511MeV/c2m_e = 9,109 \times 10^{-31} kg = \frac{9,109 \times 10^{-31}}{1,78 \times 10^{-36}} = 0,511 \times 10^6 eV/c^2 = 0,511 MeV/c^2me​=9,109×10−31kg=1,78×10−369,109×10−31​=0,511×106eV/c2=0,511MeV/c2 Per creare un elettrone è quindi necessario spendere 0,511MeV0,511 MeV0,511MeV di energia, o, in altri termini, l’annichilimento dell’elettrone provoca l’emissione di 0,511MeV0,511 MeV0,511MeV di energia). C’è da dire, infine, che soprattutto in fisica teorica, per semplificare la notazione (e i calcoli) i fisici usano un sistema di riferimento in cui la velocità della luce del vuoto c è adimensionale e pari a 1. In questi sistemi, chiaramente, l’unità di misura della massa diventa semplicemente l’eV. Quando anche la costante di Plank ridotta ħ è adimensionale e pari ad uno, anche spazio e tempo sono esprimibili con l’inverso dell’energia, cioè eV−1eV^{-1}eV−1, e quindi l’elettronvolt, in questi sistemi, può esprimere anche distanze e intervalli di tempo. --- title: Il teorema dell'energia cinetica date: 2019-11-06 url: https://gabrielebaldassarre.com/fisica/teorema-energia-cinetica/ excerpt: La meccanica quantistica non è strana al punto da mettere _del tutto_ in discussione il principio di conservazione dell'energia. Ma cosa possiamo dire, esattamente, su questo principio? category: Fisica --- All’atto pratico, come l’elettromagnetismo parte dalle leggi di Maxwell, la dinamica classica parte dalle equazioni di Lagrange, che a loro volta sono semplicemente una riscrittura delle leggi di Newton in forma generale e universale, cioè che non dipende dal sistema di riferimento e di coordinate (es. cartesiane o polari), ottenibile mediante la derivazione di un’unica funzione scalare, chiamata appunto lagrangiana. Questo aspetto, attraverso la formulazione hamiltoniana che ne deriva e che vedremo successivamente, è essenziale in meccanica quantistica perché mette in luce il formalismo algebrico della cinematica e della dinamica, formalismo utilizzato più volte nella teoria. Quindi ci tocca sbatterci un po’ la testa, temo. Ma prima di cominciare, ci serve qualche nozione iniziale. Partiamo da Newton Supponiamo di avere un sistema in cui un punto materiale si muove, rispetto ad esso, di moto rettilineo uniforme. In questo sistema, più specificatamente chiamato sistema di riferimento inerziale vale la legge di Netwon che ne regola il movimento: ma=Fma = Fma=F e, più in generale con N punti materiali vale per ognuno: mkak=Fk(k=1,…N)m_ka_k = F_k (k=1,\dots N)mk​ak​=Fk​(k=1,…N) Poiché l’accelerazione è la derivata seconda della posizione, ne consegue che la legge di Newton non è una equazione qualunque, ma è una equazione differenziale di secondo ordine dove l’incognita è x(t) e dove vi compare, nell’equazione anche attraverso le sue derivate: x˙(t)=dxdt(t),x¨(t)=d2xdt2(t)\dot{x}(t) = \frac{dx}{dt}(t), \ddot{x}(t) = \frac{d^2x}{dt^2}(t)x˙(t)=dtdx​(t),x¨(t)=dt2d2x​(t) che ci consentono di esprimere l’equazione di Newton in forma esplicitamente differenziale: x¨(t)=1mF(x,x˙,t)\ddot{x}(t) = \frac{1}{m}F(x, \dot{x}, t)x¨(t)=m1​F(x,x˙,t) Il principio di determinismo che regola la meccanica classica ci consene di scrivere che, assegnata una forza F, ogni soluzione dell’equzione di Newton è univocamente individuata dalle condizioni iniziali del sistema. x(0)=x0,x˙=v0x(0) = x_0, \dot{x} = v_0x(0)=x0​,x˙=v0​ ovvero la posizione e la velocità iniziale a t0t_0t0​. Il motivo per cui Newton ritenne che il moto fosse determinato dalla posizione e dalla velocità iniziali è dovuto al fatto che l’equazione fosse descrivibile mediante sviluppo in serie e, pertanto, come soluzione di certe equazioni differenziali con particolari scelte dei dati iniziali (per i quali è anche possibile estrarre il relativo ritratto)). È il teorema di Cauchy-Kowalewska, ma avrò pietà e non lo approfondiremo in questa sede. Piuttosto,c ominciamo a parlare di energia. Il teorema dell’energia Supponiamo di avere un sistema composto da un unico punto materiale soggetto a una forza F(x, v, t), ovvero genericamente dipendente dalla velocità, dalla posizione nello spazio e dal tempo. Il teorema dell’energia cinetica afferma che l’energia cinetica posseduta da un corpo è pari all’energia cinetica iniziale più il lavoro compiuto da una forza che agisce sul corpo stesso lungo una determinata traiettoria. In termini meccanici, l’energia cinetica T è espressa come: T=12mv2=12mvvT = \frac{1}{2}mv^2 = \frac{1}{2}mv vT=21​mv2=21​mvv che, derivato rispetto al tempo, ddt12m(vv)=2vmdvdt=ma⇒\frac{d}{dt}\frac{1}{2}m(v v) = 2v m\frac{dv}{dt} = ma \Rightarrowdtd​21​m(vv)=2vmdtdv​=ma⇒ T˙=Fv(*)\dot{T} = F v \tag{*}T˙=Fv(*) La detivata T˙\dot{T}T˙ dell’energia rappresenta la potenza della forza mentre la forma integrale T(t1)−T(t0)=∫t0t1Fvdt(**)T(t_1) - T(t_0) = \int_{t_0}^{t_1} Fv dt \tag{**}T(t1​)−T(t0​)=∫t0​t1​​Fvdt(**) è il lavoro della forza. Quindi, come volevasi dimostrare, le due forme (*) e (**), ovvero le forme differenziale e integrale dell’espressione, ci consentono di affermare che: Il tasso di variazione dell’energia cinetica è pari alla potenza della forza; La variazione netta dell’energia cinetica è pari al lavoro della forza. Ovviamente stiamo assumendo che (come avviene nella stragrande maggioranza dei casi) la massa m sia costante; per la legge di Newton, la forza impressa sul corpo ne farà variare solo la velocità. Supponiamo che sul corpo agisca una forza posizionale; non, quindi, una generica forza F(x,v,t)F(x, v, t)F(x,v,t), ma una forza che dipende solo dalla posizione nello spazio in cui è applicata F=F(x)F = F(x)F=F(x). Poiché la foza permea, con la sua distribuzione, tutto lo spazio genera, appunto, un campo vettoriale di forze (ovvero una legge che assegna una forza meccanica ad ogni punto dello spazio). In questa circostanza, l’integrale in (**) dipende solo dalla traiettoria seguita dal punto materiale nell’intervallo di tempo t0,t1t_0, t_1t0​,t1​; chiamiamo questa traiettoria γ\gammaγ. T(t1)−T(t0)=∫γF(x)dxT(t_1) - T(t_0) = \int_{\gamma}{F(x) dx}T(t1​)−T(t0​)=∫γ​F(x)dx (perché data la funzione di movimento x=x(t)x = x(t)x=x(t), all’incremento infinitesimo si ha dx=vdtdx = vdtdx=vdt). Se il campo di forze oltre a essere posizionale è conservativo, si dice che la funzione ammette potenziale, cioè che esiste una funzione V(t)V(t)V(t) tale che F=−gradV⇒Fx=−dVdx,Fy=−dVdy,Fz=−dVdzF = -gradV \Rightarrow F_x = -\frac{dV}{dx}, F_y = -\frac{dV}{dy}, F_z = -\frac{dV}{dz}F=−gradV⇒Fx​=−dxdV​,Fy​=−dydV​,Fz​=−dzdV​ La funzione V è chiamata energia potenziale e il campo è definito conservativo perché in queste condizioni vale la legge di conservazione dell’energia (o Teorema dell’Energia) che dice che, chiamata energia la quntità E=T+VE = T + VE=T+V (cioè la somma di energia cinetica e potenziale) per un qualunque movimento x=x(t)x = x(t)x=x(t) soluzione dell’equazione di Newton ma=Fma = Fma=F, si ha E˙=0\dot{E} = 0E˙=0 Questo è vero nella misura in cui Fv=−dVdtdxdt=−dVdt=−V˙Fv = -\frac{dV}{dt} \frac{dx}{dt} = -\frac{dV}{dt} = -\dot{V}Fv=−dtdV​dtdx​=−dtdV​=−V˙ Sostituendo nel teorema dell’energia cinetica visto a inizio paragrafo T˙=Fv⇒T˙=−V˙⇒\dot{T} = Fv \Rightarrow \dot{T} = -\dot{V} \RightarrowT˙=Fv⇒T˙=−V˙⇒ ddt(T+V)=0\frac{d}{dt}(T + V) = 0dtd​(T+V)=0 Per come è formulato, il Teorema dell’Energia vale per forze che variano nello spazio con legge x(t) ed è, inoltre, una legge invariante. Al variare del sistema di riferimento inerziale considerato, infatti, variano le espressioni per l’energia, ma il suo gradiente complessivo è sempre identicamente nullo, così come può variare l’espressione per il lavoro, ma esso rappresenta, sempre e in ogni sistema, l’energia trasferita da un corpo a un altro attraverso l’applicazione di una forza. Le costanti di moto L’energia, essendo una quantità dinamica, è una funzione definita nello spazio degli stati E=E(x,v)E = E(x, v)E=E(x,v) e, poiché, in ogni punto dello spazio degli stati passa un unico vettore (originato dalle condizioni iniziali $$ x_0, v_0) che è soluzione dell’equazione di Newton, allora si può esplicitare la dipendenza E(t)=E(x(t),v(t))E(t) = E(x(t), v(t))E(t)=E(x(t),v(t)) che, derivata rispetto al tempo ricordando che E=T(v)+V(x)E = T(v) + V(x)E=T(v)+V(x) E˙=dTdva+dVdxv=mva−Fv=v(ma−F)=0\dot{E} = \frac{dT}{dv}a + \frac{dV}{dx}v = mva - Fv = v(ma - F) = 0E˙=dvdT​a+dxdV​v=mva−Fv=v(ma−F)=0 (il valore della precedente è zero perché ci muoviamo nel luogo delle soluzioni dell’equazione di Newton) Le variabili dinamiche che godo di questa proprietà di invarianza rispetto a un movimento (in un sistema inerziale) sono dette costanti di moto. L’energia E, che mantiene inalterato il proprio valore lungo qualsiasi movimento x=x(t)x = x(t)x=x(t) che sono soluzioni dell’equazione di Newton è, evidentemente, una di esse. Costanti di moto e ritratti in fase Come abbiamo detto in un precedente articolo, il ritratto in fase “fotografa” la dinamica del sistema (x, v) a partire dalle diverse condizioni iniziali (x0,v0)(x_0, v_0)(x0​,v0​). Questi valori iniziali, a loro volta, determinano un valore iniziale invariante di energia E0E_0E0​, tant’è che il principio di conservazione dell’energia fa riferimento proprio a questo valore iniziale di energia, ovvero E=T+V=E0E = T + V = E_0E=T+V=E0​ Questo significa che i movimenti nel ritratto in fase sono distribuiti in “fogli” stratificati sui diversi livelli di energia iniziale, invariante, E0E_0E0​. Per questo motivo, queste superfici sul diagramma delle fasi sono chiamate superfici di livello dell’energia. Ora, non voglio insistere nella trattazione di argomenti di meccanica, ma questo formalismo razionale ci sarà utile in ambito hamiltoniano. Che, a sua volta, ci consente di trovare una correlazione nel corrispettivo concetto quantistico, tutt’altro che intuitivo. Prendete, quindi, questo articolo come un ostico, ma utile, approfondimento formale per meglio capire i concetti che verranno in seguito. E possiate perdonarmi! --- title: Lo spazio degli stati date: 2019-10-31 url: https://gabrielebaldassarre.com/meccanica/spazio-degli-stati/ excerpt: Senza un minimo di concetti base di Meccanica Razionale non si va molto oltre, nello studio della fisica. La materia è certamente ostica, ma come di consueto cercheremo di limitarci al minimo (teorico) necessario. Cominciando dal principio. category: Meccanica --- Supponiamo di voler studiare la dinamica di un sistema inerziale composto da un singolo punto materiale in moto nello spazio. In quanto tale, suddetto punto è soggetto alla legge del moto di Newton: ma=Fma = Fma=F La dinamica di questo sistema inerziale è rappresentata dal movimento del punto materiale, ovvero l’incognita è rappresentata dalla posizione del punto nel tempo: x=x(t)x = x(t)x=x(t) Il che ci porta a concludere che la legge di Newton non solo è una equazione funzionale, ma è anche una equazione differenziale ordinaria di secondo ordine, perché l’incognita (la posizione) vi compare con il termine di derivata seconda (l’accelerazione, ricordando che a(t)=x¨(t)a(t) = \ddot{x}(t)a(t)=x¨(t)). In quanto tale, risolverla analiticamente è quasi sempre molto complicato e in ogni caso dipende molto dalla complessità del sistema. Poiché ogni soluzione dell’equazione di Newton è individuata da una coppia di vettori (e dalle rispettive condizioni iniziali di posizione x0x_0x0​ e velocità v0v_0v0​), l’intera dinamica del sistema è descrivibile come l’insieme di tutte le coppie ordinate {x, v}, che individuano uno spazio vettoriale in R3×R3\mathbb{R}^3 \times \mathbb{R}^3R3×R3. Lo spazio generato da questo prodotto cartesiano descrive ogni possibile stato dinamico del sistema e prende il nome, per l’appunto, di spazio degli stati o spazio delle fasi. Nel caso specifico, fissata una forza F come una ben definita funzione delle condizioni iniziali e, genericamente, dal tempo t, ovvero F(x, v, t), esiste una e una sola soluzione all’equazione di Newton che al tempo iniziale t0t_0t0​ parte da un punto x0,v0{x_0, v_0}x0​,v0​ dello spazio delle fasi e che determina l’evoluzione futura del sistema lungo t secondo le leggi della meccanica (l’equazione differenziale del moto). L’evoluzione del sistema altri non è che una successione di punti nello spazio delle fasi o, se il sistema è continuo, una curva avente come tangente in ogni punto il vettore le cui componenti sono i due vettori {x, v} al variare di t. Questo consente di poter visualizzare lo spazio degli stati in un diagramma chiamato ritratto di fase in cui andare a disegnare tutte le possibili traiettorie del sistema (in che stato il sistema evolverà al variare di t) e definirne qualitativamente le peculiarità. Ma forse è il caso di fare un esempio. Il pendolo ideale Innanzitutto una precisazione. Il concetto di ritratto di fase è del tutto generale e non è legato necessariamente a un sistema meccanico (descritto dalle equazioni del moto di Newton), ma può essere utilizzato su sistemi dinamici di qualsiasi natura e con un numero qualsiasi di dimensioni. Nulla vieta, per capirci, di descrivere un sistema attraverso l’uso di N variabili, anche infinite, e relative condizioni iniziali in modo da individuare uno spazio di fase in RN×RN\mathbb{R}^N \times \mathbb{R}^NRN×RN. Né siamo costretti a utilizzare vettori reali (in meccanica quantistica, ad esempio, quei vettori sono complessi). Il problema, più che altro, sta nell’efficacia della rappresentazione di uno spazio degli stati quando la dimensionalità del sistema dinamico cresce. Tuttavia, in meccanica classica la dinamica è espressa dalle variabili posizionali e dai loro gradienti (più spesso si usa il momento in luogo della velocità, ma la sostanza non cambia perché il momento p=mvp = mvp=mv). Per essere pratici, useremo perciò nel nostro esempio un sistema meccanico caratterizzato da un movimento in una dimensione, così da avere un diagramma di fase facilmente visualizzabile in un piano cartesiano con la posizione sull’asse delle ascisse e il relativo gradiente sull’asse delle ordinate. Il pendolo ideale si adatta bene allo scopo. Supponiamo di esprimerne la posizione come l’angolo che forma con il proprio asse verticale θ\thetaθ, quindi in uno spazio in R\mathbb{R}R, e il gradiente attraverso la velocità angolare θ˙\dot{\theta}θ˙. L’equazione che ne regola il moto armonico è la seguente: θ¨=−glsinθ\ddot{\theta} = -\frac{g}{l}sin\thetaθ¨=−lg​sinθ Nell’immagine in basso, possiamo vedere i vari stati che assume il pendolo, in termini di coppie di angolo θ\thetaθ e velocità angolare θ˙\dot{\theta}θ˙, fissate delle condizioni iniziali per θ0\theta_0θ0​ e θ0˙\dot{\theta_0}θ0​˙​. L’oscillazione del pendolo da destra verso sinistra (per poi tornare indietro) descrive una traiettoria di transizioni di fase che nel ritratto di fase disegna una circonferenza. Andamento nel tempo e rappresentazione di stato del pendolo ideale scelta una coppia di condizioni iniziali. Si dice ideale in quanto, nel sistema di riferimento inerziale preso in esame, si considerano nulle le forze di attrito. Il pendolo, insomma, continuerà a muoversi all’infinito di moto armonico sul piano xy. Di fatto, note le condizioni iniziali ed i vettori che determinano le transizioni di stato al variare di t, possiamo prevedere l’evoluzione del sistema e quindi il suo futuro. È il concetto base del determinismo laplaciano, che però studieremo nel dettaglio in uno dei prossimi articoli. Se, però, non fissiamo una coppia di condizioni iniziali, i vettori di tutte le possibili transizioni di stato vanno a costruire un campo vettoriale che descrive tutti gli stati possibili del sistema: il ritratto di fase vero e proprio. Non offre una soluzione analitica dell’equazione differenziale che regola il moto del sistema (cosa che pure sarebbe stata possibile calcolare nell’esempio specifico del pendolo, ma che in genere è estremamente complicata), ma offre una buona vista di sintesi ed enfatizza la presenza di regioni particolari. Vediamole: Interpretazione del diagramma Le traiettorie disegnate rappresentano sette possibili evoluzioni del sistema a partire da altrettante condizioni iniziali, che, ricordiamo, sono i “punti di partenza” e sul diagramma sono rappresentate dai cerchietti. Si notano subito tre regioni particolari: Le traiettorie in arancione sono caratterizzate da un gradiente di velocità angolare che a un certo punto cambia segno. È il comportamento che subito associamo a un movimento pendolare: il peso che oscilla in avanti fino ad un angolo θmax\theta_{max}θmax​ (la distanza percorsa nella direzione delle ascisse θ\thetaθ aumenta), riducendo progressivamente la sua velocità angolare (la traiettoria curva avvicinandosi all’asse delle ascisse) per poi, quando la velocità è zero, tornare indietro. Lo stesso movimento si ha nel verso opposto e così via, con oscillazioni infinite. Le traiettorie in blu sono caratterizzate da velocità angolari iniziali sufficienti per far girare il pendolo su se stesso (attorno al proprio vincolo). Il pendolo oscilla, perde progressivamente velocità, ma la velocità non è nulla quando θ=180°\theta = 180°θ=180°, quindi comincia a cadere nella direzione opposta, riprendendo velocità che ha il suo massimo quando θ=0\theta = 0θ=0 per poi ricominciare. La velocità, infatti, non è mai nulla (la traiettoria non interseca mai l’asse delle ascisse). Le traiettorie in rosso sono gli unici due punti di equilibrio del sistema, che si hanno quando il pendolo ha velocità angolare iniziale nulla e giace sul suo asse in posizione di riposo (con θ=0\theta = 0θ=0 ma anche con θ=180°\theta = 180°θ=180°). Il tutto si ripete con periodicità 2π2 \pi2π. In definitiva dovrebbe essere chiaro il funzionamento di questa rappresentazione e come può essere utile per studiare l’evoluzione dinamica di un sistema, una volta che se ne è determinata la sua rappresentazione in termini di sistema di equazioni differenziali ordinarie. Giacché non abbiamo risolto suddette equazioni, ne fornisce solo una vista approssimata, eppure la soluzione analitica non avrebbe evidenziato le differenti regioni dinamiche del sistema in modo tanto lampante. Certo, al crescere dell’ordine del sistema, la visualizzazione diventa complessa e inefficace, per non dire impossibile. Resta, tuttavia, uno strumento prezioso anche per visualizzare mentalmente come un sistema evolve e per spiegare come la sua dinamica è descritta da vettori in un campo. Questo concetto, tra l’altro, risulta essenziale nello studio della Meccanica Quantistica, oltre che ovviamente in Meccanica Razionale. Prossimamente faremo qualche esempio pratico di realizzazione di ritratti di fase tramite R, visto che esiste un package semplice e intuitivo adatto allo scopo. Alla prossima! --- title: Stati di Spin date: 2019-10-28 url: https://gabrielebaldassarre.com/fisica/stati-spin/ excerpt: È giunto il momento di verificare se il modello matematico che abbiamo derivato per descrivere gli stati quantistici funziona. Mettiamolo alla prova con il sistema quantistico più semplice di tutti: uno spin category: Fisica --- Negli articoli precedenti, abbiamo definito lo spazio degli stati di un sistema quantistico come un sistema vettoriale composto da vettori complessi e, come tale, caratterizzato da una direzione, un modulo e un verso (anche se intesi non in senso spaziale). Abbiamo anche visto che una certa impredicibilità è tipica di un sistema quantistico e che la misura stessa, per quanto infinitamente gentile, altera (anzi, come abbiamo detto, prepara) il sistema. Appurato che i vettori di stato predicono in modo perfettamente deterministico ciò che si può conoscere di un sistema (che, però, non è tutta l’informazione contenuta in un sistema; questo, per via del principio di indeterminazione), abbiamo tutti gli elementi per studiare un sistema concreto. Quello più facile: uno spin singolo Introduzione allo spin Innanzitutto è bene fare una precisazione: quando parliamo di sistema di spin singolo parliamo proprio di uno spin, ovvero una caratteristica intrinseca delle particelle che non ha un corrispettivo nel mondo classico. Non è quindi lo spin di un elettrone. L’elettrone è la particella subatomica che porta uno spin a spasso per l’Universo. Quindi il sistema che individuano è diverso, e più complicato, di quello (per certi versi astratto) individuato dal solo spin. E a maggior ragione non è la componente di rotazione dell’elettrone sul proprio asse, anche se spesso si ricorre a questa analogia per meglio rappresentarlo, visto che il vettore di spin può essere visto come un momento angolare magnetico caratterizzato da tre componenti sugli assi spaziali x,y,z{x, y, z}x,y,z. Lo spin fu introdotto da Pauli come un’astrazione puramente matematica per descrivere il modello delle particelle quantistiche; non fu mai pensato dallo scienziato come qualcosa di reale, sperimentalmente misurabile. Anzi, quando ne fu effettivamente misurato il valore, Pauli all’inizio non era del tutto certo che la grandezza misurata fosse proprio il suo spin. Come dicevamo, però, in questa fase ci concentriamo sull’idea astratta di spin, e in particolare sugli stati assunti dal sistema da esso individuato, non dal motivo per cui assume certi valori e non altri, né tantomeno sulla sua natura fisica. Gli stati di spin lungo z Prendiamo un sistema fisico spin singolo: come già assodato, esso può assumere solo due valori in corrispondenza di una misura orientata lungo l’asse zzz (non è sempre vero per qualunque direzione, ma per ora accontentatevi), i valori sono spin up e spin down. Alternativamente, possiamo definire che lo spin si trova in uno stato generale, composto da una sovrapposizione dei due stati di base (up e down), quindi generalizziamo: ∣A⟩=αu∣u⟩+αd∣d⟩\vert A \rangle = \alpha_u \vert u \rangle + \alpha_d \vert d \rangle∣A⟩=αu​∣u⟩+αd​∣d⟩ Non spaventatevi! È una notazione compatta per esprimere un concetto semplice: lo stato di spin è formato da una componente up e una componente down, ognuna pesata per un coefficiente, detto coefficiente di combinazione lineare. Il simbolo ∣A⟩\vert A \rangle∣A⟩ è un vettore di stato generico che chiamiamo ket A. I simboli ∣u⟩\vert u \rangle∣u⟩ e ∣d⟩\vert d \rangle∣d⟩ sono i vettori di stato relativi allo spin up e allo spin down. Non è questa la sede per spiegare la notazione bra-ket, ma sappiate che è uno strumento elegante e compatto per scrivere le equazioni della meccanica quantistica. Tornando al nostro stato generale, i coefficienti αu\alpha_uαu​ e αd\alpha_dαd​ sono numeri complessi che esprimono il “peso” di ciascuna componente. Il modulo quadro di questi coefficienti esprime la probabilità di trovare lo spin, durante una misura, in uno piuttosto che nell’altro stato. Esprimiamo questa probabilità scrivendo la matrice densità dei coefficienti. Riscriviamo lo stato generale in forma vettoriale: A=[αuαd]\mathbf{A} = \begin{bmatrix} \alpha_u \\ \alpha_d \end{bmatrix}A=[αu​αd​​] Naturalmente, ∣αu∣2\vert \alpha_u \vert^2∣αu​∣2 è la probabilità di osservare il sistema nello stato up e ∣αd∣2\vert \alpha_d \vert^2∣αd​∣2 è la probabilità di osservarlo nello stato down. Poiché la somma delle probabilità deve essere 1, abbiamo: ∣αu∣2+∣αd∣2=1\vert \alpha_u \vert^2 + \vert \alpha_d \vert^2 = 1∣αu​∣2+∣αd​∣2=1 La misura dello spin Cosa succede quando misuriamo lo spin? La risposta è in linea con i postulati della meccanica quantistica: prima della misura, il sistema si trova in uno stato di sovrapposizione. L’atto della misura “prepara” il sistema, collassandolo in uno dei due autostati. Per calcolare la probabilità che il sistema collassi su up o down in una direzione arbitraria, non basta conoscere i coefficienti lungo z ma bisogna proiettare lo stato sul vettore relativo alla direzione di misura. Questo non significa saperlo a priori: anche se conosciamo lo stato iniziale, non possiamo prevedere esattamente il risultato della singola misura, ma possiamo prevedere la distribuzione statistica dei risultati di tante misure. Se lo spin è preparato nello stato up e misuriamo lungo lo stesso asse z, otterremo sempre up. Se invece misuriamo lungo un asse diverso, la probabilità sarà data, in sintesi, dall’angolo fra la direzione dello stato e la direzione della misura — proprio come in un coseno direttore. Preparare uno stato di spin Finora abbiamo parlato di misurare spin. Ma come si prepara uno spin in uno stato specifico? Uno dei metodi più comuni è usare un magnete di Stern-Gerlach, che, accoppiandosi magneticamente con lo spin, lo indirizza su un percorso selettivo in base al suo valore. Nell’esperimento di Stern-Gerlach, un fascio di particelle con spin viene fatto passare attraverso un campo magnetico non uniforme. A seconda del valore dello spin, le particelle vengono deflesse verso l’alto o verso il basso, separando così i due stati di spin e preparando il sistema in uno di essi. Bloccando uno dei due fasci, possiamo ottenere un fascio di particelle con spin preparato nello stato desiderato. Questo è un modo per “inizializzare” lo spin in uno stato noto. --- title: Basi ortonormali in un sistema quantistico date: 2019-10-21 url: https://gabrielebaldassarre.com/fisica/basi-ortonormali/ excerpt: In questo articolo introdurremo il concetto di base di un sistema quantistico e di come questo rappresenti tutti i suoi stati possibili category: Fisica --- Chi ha un minimo di dimestichezza con l’algebra lineare conosce certamente il concetto di base di uno spazio vettoriale come l’insieme massimo di vettori mutuamente ortogonali che generano lo spazio stesso e ne determinano la dimensionalità. Ebbene, non c’è nulla in algebra che restringa tale concetto al fatto che i suoi vettori siano reali e individuino, di conseguenza, uno spazio reale (magari tridimensionale). È assolutamente sensato generalizzare il concetto di base al campo complesso, per individuare l’intero spazio degli stati di un sistema quantistico. Tutti gli stati interni che il sistema può assumere, per intenderci. Basi ortonormali di un sistema complesso In maniera del tutto analoga a quanto si fa in un sistema reale, si cominciano a prendere dei vettori con modulo unitario che siano tutti tra di loro mutuamente ortogonali (che, nel campo complesso, significa prendere due vettori il cui prodotto interno sia uguale a zero. Essendo tutti questi vettori di modulo unitario, l’informazione interessante che portano in dote è la direzione che individuano. Ebbene, continuando a prendere vettori (direzioni) ortogonali, si arriverà ad un punto in cui non è più possibile prendere altri preservando la condizione di mutua ortogonalità e saremo costretti a fermarci: i vettori presi fino a questo momento individueranno una base ortonormale e il loro numero la dimensionalità dello spazio. La cosa interessante (sempre in perfetta analogia con i vettori reali) è che tutti i vettori dello spazio sono esprimibili come combinazione lineare degli elementi della base. Ora, tecnicamente parlando, l’algebra non prevede che i vettori di base siano ortonormali (modulo unitario, ortogonali tra di loro), tuttavia per una serie infinita di ragioni, in meccanica quantistica normalmente lo sono. Per semplificare i calcoli, più che altro. Basi ortonormali e meccanica quantistica Supponiamo di avere una base ortonormale complessa di dimensione N. Esprimiamola in notazione di Dirac utilizzando un ket: ∣i⟩\vert i \rangle∣i⟩. Tale base, per quanto detto, avrà i1...iNi_1 ... i_Ni1​...iN​ componenti ortogonali tra di loro. Consideriamo ora un generico vettore di stato A ed esprimiamolo come combinazione lineare con i componenti del vettore di base: ∣A⟩=∑iαi∣i⟩\vert A \rangle = \sum_{i} \alpha_i \vert i \rangle∣A⟩=i∑​αi​∣i⟩ con gli αi\alpha_iαi​ coefficienti complessi chiamati componenti della base. Effettuiamo ora il prodotto interno della precedente uguaglianza per un particolare vettore della base j scelto arbitrariamente tra i vettori iNi_NiN​ della base. ⟨j∣A⟩=∑iαi⟨j∣i⟩\langle j \vert A \rangle = \sum_{i} \alpha_i \langle j \vert i \rangle⟨j∣A⟩=i∑​αi​⟨j∣i⟩ Poiché i vettori sono ortogonali tra di loro e hanno modulo unitario, l’equazione precedente vale 0 per qualsiasi coppia ∣j⟩≠∣i⟩\vert j \rangle \ne \vert i \rangle∣j⟩=∣i⟩ e vale 1 se ∣j⟩=∣i⟩\vert j \rangle = \vert i \rangle∣j⟩=∣i⟩. La sommatoria quindi collassa a un unico termine diverso da zero: ⟨j∣A⟩=αj\langle j \vert A \rangle = \alpha_j⟨j∣A⟩=αj​ Quindi le componenti di un generico vettore A sulla base ortonormale i sono dati dal prodotto interno del vettore stesso con la base. ∣A⟩=∑i∣i⟩⟨i∣A⟩\vert A \rangle = \sum_i \vert i \rangle \langle i \vert A \rangle∣A⟩=i∑​∣i⟩⟨i∣A⟩ Questa formulazione è essenziale per poter studiare la dinamica di un sistema, ovvero come lo stato di un sistema quantistico varia nel tempo e come è possibile studiarne l’evoluzione. Lo vedremo in uno dei prossimi articoli. Alla prossima! --- title: Lo spazio degli stati di un sistema quantistico date: 2019-10-04 url: https://gabrielebaldassarre.com/fisica/spazio-stati-quantistici/ excerpt: In questo articolo introdurremo lo spazio vettoriale complesso che rappresenta l'ossatura del modello matematico utilizzato per studiare gli stati di un sistema quantistico category: Fisica --- A differenza di quanto avviene in un sistema classico, in un sistema quantistico lo spazio degli stati non è esprimibile attraverso la teoria degli insiemi (e la loro corrispondente logica combinatoria), ma per descriverlo è necessario costruire un opportuno modello matematico astratto. A chi si avvicina per la prima volta allo studio della meccanica quantistica, l’adozione di un modello matematico apposito che descriva gli stati di un sistema è il primo scoglio da superare. Questo perché, mentre la logica insiemistica è molto intuitiva e adeguata a descrivere i sistemi macroscopici o classici - quelli che il nostro cervello è ben allenato a comprendere - quando ragioniamo su scala quantistica dobbiamo abbandonare la pretesa di poter comprendere intuitivamente i fenomeni e fidarci della matematica. Non mi stancherò mai di ripetere che gli elettroni, e in generale tutte le particelle che agiscono su scala quantistica, non si comportano in modo strano. Gli elettroni fanno quello che gli elettroni fanno. Ed è del tutto logico e normale (dal loro punto di vista). Siamo noi a essere troppo grandi e troppo lenti per poterli studiare direttamente, ragion per cui abbiamo avuto bisogno di creare un modello matematico per studiarne il comportamento, mentre abbiamo avuto bisogno di un modello semplificato dell’Universo (la meccanica classica) per poter riportare i fenomeni che osserviamo direttamente a una scala che ci è più congeniale. Ma non bisogna mai dimenticare che la realtà, è più che dimostrato, è decisamente quantistica. Fatto questo atto di fede, andiamo al sodo. Lo spazio vettoriale degli stati Come detto, la teoria degli insiemi non è adatta per descrivere lo spazio degli stati di un sistema quantistico, né lo è la logica booleana. Questo sembra piuttosto assurdo, ma piaccia o meno al nostro cervello, è così che funziona realmente l’Universo. In un sistema quantistico, lo spazio degli stati è, piuttosto, descritto come uno spazio vettoriale e la logica combinatoria tra i componenti di tale spazio è diversa dalla logica classica. Non voglio scegliere nel dettaglio di cosa sia un vettore, do per scontato che lo sappiate già. È, però, importante segnalare che la nostra definizione di vettore va intesa in senso astratto. Non parliamo cioè, di vettori nello spazio reale (con le loro componenti {x, y, z, \} per capirci), ma di oggetti matematici astratti, di dimensione nnn (anche infinita). Di conseguenza, con spazio vettoriale intendiamo una collezione di oggetti matematici astratti costruiti in modo tale che le relazioni tra di essi modellino in modo adeguato le relazioni tra i componenti di un sistema quantistico. Tanto per non farci mancare niente, non solo gli stati di un sistema quantistico sono dei vettori, ma sono anche dei vettori piuttosto interessanti: sono dei vettori generalmente complessi (nel senso che sono vettori le cui componenti sono nel dominio complesso, non che sono vettori complicati!). La notazione di Dirac Per descrivere un vettore di stato complesso aaa nello spazio degli stati di un sistema quantistico si utilizza la seguente notazione (introdotta da Paul Dirac): ∣a⟩\vert a \rangle∣a⟩ Questo “oggetto” prende il nome di ket del vettore aaa. Poiché si può dimostrare (non lo faremo) che lo spazio degli stati è uno spazio di Hilbert, esso è chiuso all’addizione. Se aaa e bbb sono due vettori di stato di un sistema quantistico, il vettore ccc combinazione dei due è anche esso vettore di stato del medesimo sistema. ∣a⟩+∣b⟩=∣c⟩\vert a \rangle + \vert b \rangle = \vert c \rangle∣a⟩+∣b⟩=∣c⟩ Similmente, è possibile moltiplicare un ket per uno scalare (genericamente complesso): z∣a⟩=∣a′⟩z \vert a \rangle = \vert a^\prime \ranglez∣a⟩=∣a′⟩ Anche le altre regole che definiscono gli spazi di Hilbert sono rispettate (esistenza di elemento neutro, commutatività, associatività, ecc.). Le riassumiamo tutte in un’unica proprietà, che lo spazio effettivamente rispetta: la linearità. Possiamo lavorare anche con una rappresentazione più concreta dei ket, ovvero nella forma di vettori colonna, che ne esplicitano le componenti. Ad esempio, possiamo scrivere: ∣A⟩=(α1α2)\vert A \rangle = \begin{pmatrix} \alpha_1 \\ \alpha_2 \end{pmatrix}∣A⟩=(α1​α2​​) dove i coefficienti complessi α\alphaα rappresentano le componenti del vettore. Come abbiamo detto per la linearità vale per la somma: ∣A⟩+∣B⟩=(α1α2)+(β1β2)=(α1+β1α2+β2)\vert A \rangle + \vert B \rangle = \begin{pmatrix} \alpha_1 \\ \alpha_2 \end{pmatrix} + \begin{pmatrix} \beta_1 \\ \beta_2 \end{pmatrix} = \begin{pmatrix} \alpha_1 + \beta_1 \\ \alpha_2 + \beta_2 \end{pmatrix}∣A⟩+∣B⟩=(α1​α2​​)+(β1​β2​​)=(α1​+β1​α2​+β2​​) e per la moltiplicazione per uno scalare complesso: z∣A⟩=z(α1α2)=(zα1zα2)z \vert A \rangle = z\begin{pmatrix} \alpha_1 \\ \alpha_2 \end{pmatrix} = \begin{pmatrix} z\alpha_1 \\ z\alpha_2 \end{pmatrix}z∣A⟩=z(α1​α2​​)=(zα1​zα2​​) Bra e Ket Così come un numero complesso possiede il suo complesso coniugato, che corrisponde al numero originario con la parte immaginaria cambiata di segno, allo stesso modo un vettore ket possiede il suo corrispettivo avente come componenti i complessi coniugati del ket di partenza. Questo nuovo vettore è chiamato bra e si indica con: ⟨A∣\langle A \vert⟨A∣ È facile immaginare che ogni bra ha il suo ket corrispondente e viceversa. È importante, però, ricordare che bra e ket non condividono lo stesso spazio vettoriale. I bra, infatti, individuano uno spazio vettoriale distinto che prende il nome di spazio duale. I bra godono delle stesse proprietà già viste per i ket, però bisogna fare alcune precisazioni. Innanzitutto, senza eccessive sorprese, data la seguente relazione: ∣A⟩+∣B⟩\vert A \rangle + \vert B \rangle∣A⟩+∣B⟩ vale che ⟨A∣+⟨B∣\langle A \vert + \langle B |⟨A∣+⟨B∣ Ma il corrispondente bra della relazione in ket z∣A⟩z \vert A \ranglez∣A⟩ è ⟨A∣z∗\langle A \vert z^*⟨A∣z∗ dove z∗z^*z∗ è il complesso coniugato di zzz. Nella rappresentazione delle componenti in colonna, per rendere ben evidente che i due spazi vettoriali individuati dai vettori bra e ket, i primi si rappresentano come vettori riga. In altre parole, al ket di A ∣A⟩=(α1α2)\vert A \rangle = \begin{pmatrix} \alpha_1 \\ \alpha_2 \end{pmatrix}∣A⟩=(α1​α2​​) corrisponde il bra: ⟨A∣=(α1∗α2∗)\langle A \vert = \begin{pmatrix} \alpha^*_1 & \alpha^*_2 \end{pmatrix}⟨A∣=(α1∗​​α2∗​​) Questa rappresentazione duale suggerisce che i bra e i ket possono essere moltiplicati insieme attraverso una operazione che è l’equivalente complesso del prodotto scalare: il prodotto interno Prodotto interno Il prodotto interno è l’operazione analoga al prodotto scalare, ma avviene tra un bra e un ket. Se, quindi, il prodotto scalare è tra vettori reali, il prodotto interno è tra vettori complessi, pertanto può esserne visto come una generalizzazione. In pratica, il prodotto interno è il prodotto di un vettore per il duale (il complesso coniugato) dell’altro vettore. Si indica con una notazione “a sandwitch”: ⟨B∣A⟩\langle B \vert A \rangle⟨B∣A⟩ Notate che i due vettori messi insieme fanno un bra-ket (“parentesi” in inglese): un modo mnemonico molto utile per non confondersi tra spazio e spazio duale! Le regole del prodotto interno sono abbastanza facili da derivare. Per esempio, godono di linearità: ⟨B∣(∣A⟩+∣B⟩)=⟨C∣A⟩+⟨C∣B⟩\langle B \vert ( | A \rangle + \vert B \rangle) = \langle C \vert A \rangle + \langle C \vert B \rangle⟨B∣(∣A⟩+∣B⟩)=⟨C∣A⟩+⟨C∣B⟩ Ma non della proprietà commutativa, ovvero in genere: ⟨B∣A⟩≠⟨A∣B⟩\langle B \vert A \rangle \ne \langle A \vert B \rangle⟨B∣A⟩=⟨A∣B⟩ ma vale la commutazione complessa: ⟨A∣B⟩=⟨B∣A⟩∗\langle A \vert B \rangle = \langle B \vert A \rangle^*⟨A∣B⟩=⟨B∣A⟩∗ In pratica, scambiare bra con ket equivale a prendere il complesso coniugato. Supponiamo di effettuare il prodotto interno di un vettore per se stesso. Per la commutazione complessa avremo ⟨A∣A⟩=⟨A∣A⟩∗\langle A \vert A \rangle = \langle A \vert A \rangle^*⟨A∣A⟩=⟨A∣A⟩∗ che è un numero reale perché solo nei numeri reali il complesso coincide con il coniugato. Inoltre, (α1∗α2∗)(α1α2)=α1∗α1+α2∗α2\begin{pmatrix} \alpha^*_1 & \alpha^*_2 \end{pmatrix} \begin{pmatrix} \alpha_1 \\ \alpha_2 \end{pmatrix} = \alpha^*_1\alpha_1 + \alpha^*_2\alpha_2(α1∗​​α2∗​​)(α1​α2​​)=α1∗​α1​+α2∗​α2​ non solo è reale, ma è anche positivo ed è il quadrato della lunghezza del vettore. Possiamo generalizzare e dire che vettori complessi hanno lunghezza reale. Per finire una nota sull’ortogonalità: due vettori complessi sono ortogonali se il loro prodotto interno à zero. ⟨A∣B⟩=0\langle A \vert B \rangle = 0⟨A∣B⟩=0 L’ortogonalità si mantiene nello spazio duale. In analogia con gli spazi reali, il massimo numero di vettori mutualmente ortogonali definisce la dimensionalità dello spazio (e la dimensione delle sue basi ortonormali, che vedremo nel prossimo articolo). --- title: L'effetto fotoelettrico date: 2019-10-01 url: https://gabrielebaldassarre.com/fisica/effetto-fotoelettrico/ excerpt: Un breve articolo che spiega cos'è l'effetto fotoelettrico e di come la spiegazione del fenomeno fornita da Einstein gli valse il premio Nobel nel 1921. category: Fisica --- Agli albori della meccanica quantistica, agli inizi del XX secolo, Planck contribuì con una importante osservazione relativa alla radiazione di corpo nero, ovvero di un oggetto reale che, a temperatura TTT, assorbe tutta la radiazione elettromagnetica incidente senza rifletterla (da cui, appunto, nero). Esso, secondo l’interpretazione classica e per via della conservazione dell’energia, re-irradia tutta l’energia assorbita e, secondo Planck, lo fa in uno spettro di emissione quantizzato. In particolare, la radiazione emessa ha energia hνh\nuhν., con h=6,626⋅10−34J⋅sh = 6,626 \cdot 10^{-34} J \cdot sh=6,626⋅10−34J⋅s la costante di Planck e ν\nuν la frequenza della radiazione emessa. Einstein partì da queste considerazioni per spiegare un fenomeno già noto all’epoca (siamo nel 1905) relativo all’assorbimento di una radiazione elettromagnetica di frequenza ν\nuν da parte di un campione metallico con conseguente emissione di elettroni. L’effetto fotoelettrico, appunto. Il fenomeno Supponiamo, come detto, che una radiazione elettromagnetica monocromatica di frequenza ν\nuν incida su un campione metallico di temperatura TTT nel vuoto. Gli elettroni del metallo assorbiranno l’energia della radiazione elettromagnetica ed alcuni di essi ne assorbiranno abbastanza da saltar via dalla loro orbita intorno ai rispettivi nuclei atomici ed essere espulsi dal metallo. Questi elettroni in fuga possono essere rilevati da un opportuno apparato posto nelle vicinanze del metallo formato da un potenziale e un circuito in cui l’elettrone in movimento induce una corrente; in questo modo l’ energia degli elettroni emessi può essere misurata. Andando a misurare la massima energia EmE_mEm​ degli elettroni espulsi si può osservare che dipende non dall’intensità della radiazione incidente, come ci saremmo aspettati dalla fisica classica, bensì dalla frequenza ν\nuν della radiazione stessa. Al di sotto di una frequenza di soglia ν0\nu_0ν0​, inoltre, non vi è emissione alcuna di elettroni. L’intensità della radiazione influisce, invece, solo nella quantità di elettroni emessi. La dipendenza tra la frequenza ν\nuν e l’energia EmE_mEm​ è lineare con un coefficiente pari alla costante di Planck. Più nel dettaglio, vige la relazione: Em=hν−qΦE_m = h\nu - q\PhiEm​=hν−qΦ con qqq la carica dell’elettrone e Φ\PhiΦ un potenziale (misurato in Volt) dipendente dalle caratteristiche fisiche del metallo. EmE_mEm​ rappresenta l’energia minima necessaria all’elettrone per essere espulso dal metallo e viene detto lavoro di estrazione. Detto in altri termini, l’elettrone assorbe una energia hνh\nuhν dalla radiazione elettromagnetica incidente e perde una energia qΦq\PhiqΦ per poter essere espulso dal metallo, il che risulta in una energia netta posseduta dall’elettrone emesso pari a EmE_mEm​. Più spesso, in luogo della frequenza si utilizza una formulazione avente come termine la pulsazione e chiamata Relazione di Planck E=h(2πν)2π=ℏωE = \frac{h(2\pi\nu)}{2\pi } = \hbar\omegaE=2πh(2πν)​=ℏω Questo fenomeno mostra la natura quantizzata della radiazione elettromagnetica, trasferita (sarebbe più corretto dire mediata) da particelle discrete chiamate fotoni. L’esperimento della doppia fenditura ha, successivamente, dimostrato la doppia natura (ondulatoria e corpuscolare) dei fotoni, mentre lo stesso esperimento effettuato con, ad esempio, dei raggi catodici ha consentito di stabilire che anche le particelle cosiddette di materia, come gli elettroni, hanno una doppia natura. In particolare, Louis de Broglie ipotizzò che una particella avente momento p=mvp = mvp=mv abbia una lunghezza d’onda di λ=hp=hmv\lambda = \frac{h}{p} = \frac{h}{mv}λ=ph​=mvh​ e, quindi, p=hλ=h2π=ℏλ=ℏkp = \frac{h}{\lambda} = \frac{h}{2\pi} = \frac{\hbar}{\lambda} = \frac{\hbar}{k}p=λh​=2πh​=λℏ​=kℏ​ con kkk il numero d’onda. Queste relazioni, insieme alla relazione di Planck, connettono (in un sistema a scala quantistica, ovviamente) la natura ondulatoria dei fenomeni (con grandezze quali frequenza e lunghezza d’onda) con la descrizione delle particelle dal punto di vista corpuscolare (dotati di energia e momento). È molto importante notare che la relazione esistente tra frequenza e lunghezza d’onda, chiamata relazione di dispersione, non è la stessa per tutte le particelle elementari. Per esempio, nei fotoni si ha: ν=cλ⇒E=ℏω=ℏ(2πν)=ℏ2π(cλ)=ℏck=cp\nu = \frac{c}{\lambda} \Rightarrow E = \hbar\omega = \hbar(2\pi\nu) = \hbar2\pi(\frac{c}{\lambda}) = \hbar ck = cpν=λc​⇒E=ℏω=ℏ(2πν)=ℏ2π(λc​)=ℏck=cp ma per gli elettroni, tanto per dire, la funzione E(k)E(k)E(k) è diversa. Questa proprietà è essenziale nello studio dei semiconduttori. Le conclusioni di Einstein In definitiva, nella radiazione elettromagnetica, l’energia non è distribuita uniformemente e con continuità sull’intero fronte d’onda, ma concentrata in singoli pacchetti discreti (o quanti) di energia - i fotoni - che interagiscono una alla volta con i singoli elettroni a cui cedono la loro energia. Questo, a patto che tale energia, ottenuta tramite la relazione di Planck, sia pari almeno al lavoro di estrazione necessario per rompere il legame elettrostatico che lega gli elettroni ai rispettivi nuclei. Aumentando l’intensità della radiazione elettromagnetica, ovvero il numero di fotoni con la stessa lunghezza d’onda che vanno a incidere sul metallo non andrà ad aumentare l’energia cinetica degli elettroni emessi, ma il loro numero. Se l’energia posseduta dai fotoni non è sufficiente per vincere il lavoro di estrazione, nessun elettrone è emesso dal metallo e l’effetto fotoelettrico non è osservato, indipendentemente da quanto intensta è la radiazione che lo ha illuminato. Questo fenomeno, condermato sperimentalmente, contraddice apertamente quanto previsto dalla fisica classica attravero le equazioni di Maxwell, secondo cui l’intera onda interagisce nel suo complesso con tutti gli elettroni del metallo. Questa diretta conseguenza della quantizzazione dell’energia, per cui fotoni ed elettroni interagiscono uno per uno, portò Einstein a vincere il suo unico premio Nobel nel 1921. Ironicamente, il grande fisico tedesco non vinse mai l’ambito premio per il lavoro che lo avrebbe consegnato alla storia, quello sulla Relatività Generale, ma solo per questo suo contributo che, di fatto, diede il via alla meccanica quantistica. Una teoria per alcuni aspetti della quale egli nutrì molte riserve, per non dire profonda avversione, durante tutta la sua vita. --- title: Logica classica e logica quantistica date: 2019-09-18 url: https://gabrielebaldassarre.com/fisica/logica-quantistica/ excerpt: Lo spazio degli stati di un sistema classico è governato dalla logica booleana. Vedremo oggi che, in un sistema quantistico le cose sono un po' più complicate e molto meno intuitive. category: Fisica --- Lo spazio degli stati di un sistema classico è rappresentato dai componenti di un insieme. Lo spazio degli stati del sistema lancio della moneta, ad esempio, è costituito dai due elementi {Testa, Croce}, che rappresentano anche i due unici, possibili, valori della variabile misurata in un esperimento. Non sono possibili valori intermedi, così come il sistema non può trovarsi in una stato diverso da Testa o da Croce, in un dato momento. La logica della teoria degli insiemi è l’arcinota logica booleana, che si basa sul concetto che una condizione di un sistema è esprimibile mediante una combinazione di proposizioni, ognuna rappresentata da una propria tabella di verità che dipende dalle caratteristiche del sistema studiato. Facciamo un esempio. Supponiamo di avere il sistema Lancio di un dado. Questo sistema può trovarsi in uno di sei possibili stati: D=1,2,3,4,5,6D = {1, 2, 3, 4, 5, 6}D=1,2,3,4,5,6 Una possibile proposizione potrebbe essere: “Tutti i risultati pari”, che rappresenta un sottoinsieme dell’insieme D. P=2,4,6P = {2, 4, 6}P=2,4,6 Un’altra proposizione potrebbe essere “Tutti i risultati maggiori di tre”: T=4,5,6T = {4, 5, 6}T=4,5,6 Logica classica La logica formale booleana consente la combinazione di più proposizioni attraverso gli operatori insiemistici di complemento, unione e intersezione. Per esempio la proposizione “Tutti i risultati pari e maggiori di tre”, ovvero l’operazione di AND logico, può essere espresso come operazione di intersezione tra P e T, ovvero: A=P∩TA = P \cap TA=P∩T Allo stesso modo valgono le operazioni di OR, di NOT e tutte le possibili declinazioni (NAND, XOR…). Ebbene, in un sistema classico vale la commutatività delle proposizioni. Siano A e B due proposizioni (insiemi), allora: P∩T=T∩PP \cap T = T \cap PP∩T=T∩P Nella logica classica, non è importante l’ordine con cui sono valutare le proposizioni. La tabella di verità dell’espressione risultante sarà sempre definita in modo non ambiguo. Logica quantistica Quanto detto non è, invece, sempre vero in un sistema quantistico e questo per via del principio di indeterminazione. Sugli operatori unari possiamo essere abbastanza tranquilli: σz=1  ⟹  NOT(σz)=−1\sigma_z = 1 \implies NOT(\sigma_z) = -1σz​=1⟹NOT(σz​)=−1 Per ciò che concerne gli operatori binari la questione è più complessa. Supponiamo di codificare due proposizioni booleane attraverso due grandezze quantistiche che, come sappiamo, possono assumere solo due valori e supponiamo che tali proposizione siano vere quando le grandezze assumono valore positivo: A:σz=1A: \sigma_z = 1A:σz​=1 B:σx=1B: \sigma_x = 1B:σx​=1 Supponiamo che il sistema sia stato preparato nello stato di up e di voler misurare l’OR logico. AORB=?A OR B = ?AORB=? Supponiamo di iniziare la verifica di A. Poiché, per ipotesi, il sistema è nello stato di up, la misura di σz\sigma_zσz​ darà come valore misurato 1, il che è sufficiente per verificare la veridicità di A e, quindi, dell’intera espressione (A OR B è vera se A oppure B sono vere). Possiamo fermarci qui. Ripetiamo l’esperimento, verificando stavolta prima B. Poiché per ipotesi il sistema è nello stato di up, avremo un valore σx\sigma_xσx​ che sarà il risultato di una distribuzione statistica avente il 50% di probabilità di valere 1. Quindi, la sola misura di B non è sufficiente per verificare l’espressione A OR B e bisogna verificare anche A. Ma la misura di B ha distrutto ogni informazione relativa a σz\sigma_zσz​, lasciando il sistema in uno stato compatibile con la misura perfettamente definita per σx\sigma_xσx​ (che si sia ottenuto 1 o -1). Di conseguenza, σz\sigma_zσz​ avrà il 50% di probabilità di valere 1, così come il 50% di probabilità di valere -1. In definitiva, la proposizione BORAB OR ABORA ha il 25% di possibilità di essere falsa, mentre AORBA OR BAORB è certamente vera. In un sistema quantistico, l’operatore OR non collega gli operandi in modo simmetrico. È la rappresentazione logica del principio di indeterminazione. Da notare che non tutte le grandezze si comportano in questo modo. In un sistema quantistico esistono coppie di grandezze che possono essere misurate contemporaneamente, per restituire una tabella di verità di una proposizione perfettamente definita, come in un sistema classico. Queste grandezze hanno delle proprietà che possono essere desunte dalle caratteristiche del sistema, ovvero dal suo spazio degli stati. Come vedremo, le due grandezze utilizzate in questo esempio, ovvero le componenti dello spin rispettivamente lungo la direzione z e lungo la direzione x, sono due grandezze che, per l’appunto, non possono essere misurate contemporaneamente e per le quali vige il principio di indeterminazione. Che, a sua volta, causa la componente di aleatorietà nella valutazione delle espressioni logiche. --- title: Esperimenti ripetuti e preparazione di un sistema date: 2019-09-13 url: https://gabrielebaldassarre.com/fisica/esperimenti-quantistici-distribuzione/ excerpt: La meccanica quantistica tutto sommato non distrugge totalmente la nostra idea del mondo. A patto di rinunciare a un po' di confortante determinismo. category: Fisica --- Abbiamo detto che un esperimento in un sistema quantistico, il cui stato è modellabile con un vettore dotato di modulo, direzione e verso, la misurazione di una grandezza, per esempio la componente dello spin lungo una certa direzione σ\sigmaσ , ci restituirà, ostinatamente, solo valori di +1 o -1, peraltro in modo imprevedibile. A parte che abbiamo irrimediabilmente perduto il determinismo delle meccanica classica, il vettore di stato di σ\sigmaσ deve essere un vettore ben strano. In realtà non è il vettore a essere strano, è il sistema (tanto per cambiare) a esserlo. In particolare, un sistema quantistico può trovarsi contemporaneamente in una sovrapposizione di stati (“un po’ testa e un po’ croce”). Ma di questo ci occuperemo più avanti. In questo breve articolo faremo, invece, pace con la fisica classica. La fisica classica, lo abbiamo già detto, non è sbagliata. È solo un po’ imprecisa. Distribuzione dei risultati di esperimenti ripetuti Abbiamo detto che gli esperimenti fisici in meccanica quantistica sono confermabili. Supponiamo di misurare una grandezza legata a un oggetto di scala quantistica, per esempio uno spin che sappiamo essere nello stato di up. Qualcuno o qualcosa, non importa cosa, lo ha preparato per noi in questo stato; per adesso ci basta sapere questo. Effettuiamo una misurazione con un apparato sdraiato su di un lato, ovvero misuriamo la componente σx\sigma_xσx​ di suddetto spin sull’asse xxx. Secondo le leggi della meccanica classica, avremmo dovuto misurare un valore di zero. E, invece, misuriamo un valore di +1 o -1, in modo del tutto imprevedibile. Se continuiamo a misurare, per la confermabilità degli esperimenti, continueremo a ottenere lo stesso valore misurato. Se la prima volta avevamo misurato +1, misureremo sempre +1. Se la prima volta abbiamo misurato -1, questo è il valore che continueremo incessantemente a misurare. Supponiamo ora di avere non uno, ma una collezione intera di spin indipendenti tra di loro, tutti preparati nello stato up, e misuriamo σx\sigma_xσx​. Cosa otteniamo? Ancora una volta, una serie di +1 e di -1 (uno per ogni spin della collezione). Ma con una particolarità: la media di tutti i valori misurati sarà 0, in perfetto accordo con la meccanica classica. Ripetiamo, ora, l’esperimento orientando l’apparato in una direzione arbitraria che formi un angolo θ\thetaθ con l’asse zzz (l’asse lungo up, per intenderci). Tanto per cambiare, otteniamo una serie di valori misurati di +1 e -1, con la media dei valori misurati di cos⁡(θ)\cos(\theta)cos(θ), nuovamente in accordo con la meccanica classica. Quindi, sì, in un sistema quantistico non possiamo più contare sul determinismo dei valori misurati, ma le grandezze seguono una distribuzione statistica che è coerente con i risultati ottenibili applicando le leggi della meccanica classica. Ci sono diverse interpretazioni per cui la Meccanica Quantistica sia, sembrerebbe, non deterministica. Einstein, ad esempio, era convinto di una certa incompletezza nella teoria e che ci fossero delle variabili nascoste del sistema che, una volta scoperte e considerate avrebbero portato a formulare equazioni perfettamente deterministiche. L’interpretazione oggi, però, universalmente più accettata è che la teoria sia completa e che il suo indeterminismo riflette quello del tessuto stesso della realtà. La teoria ci permette di conoscere tutto ciò e solo ciò che è possibile conoscere di un sistema, che è ben lungi (ma non casualmente) dall’essere la totalità dell’informazione contenuta in esso, come invece accade per i sistemi classici. Ma come si dice: piuttosto che niente, è meglio il piuttosto. Preparare un sistema quantistico Adesso vi chiedo un po’ di elasticità logica. Fino ad ora abbiamo detto che qualcuno o qualcosa aveva preparato per noi il sistema (costruito da un singolo spin) in uno stato ben preciso, lo stato up, senza però dirci in quale stato lo aveva lasciato. La misura, perfettamente priva di ambiguità, della componente σz\sigma_zσz​ ci ha consentito di conoscere tale stato, che esperimenti successivi hanno confermato. Misurare ripetutamente σz=+1\sigma_z = +1σz​=+1, dopotutto, è sufficiente per affermare che lo stato dello spin sia up. D’altra parte, anche una misura dela componente generica σθ\sigma_\thetaσθ​ ottenuta con l’apparato ruotato di un angolo θ\thetaθ ci ha fornito una misura (+1 o -1) perfettamente non ambigua, confermata da misure successive. Ma insomma, abbiamo forzato il sistema ad assumere una certa configurazione (stato) o ci siamo limitati a misurare una grandezza? La domanda è mal posta: poiché una misura, come abbiamo già detto, infuenza il sistema, l’una implica l’altra. Anzi, in un sistema quantistico, misurare una grandezza e preparare il sistema sono esattamente la stessa cosa. Meglio essere più chiari con un esempio. Supponiamo di avere uno spin e di non sapere assolutamente il suo stato. Prendiamo il nostro solito apparato, orientato in una direzione arbitraria e misuriamo la componente dello spin σθ\sigma_\thetaσθ​ lungo quella direzione. Supponiamo di leggere +1. Sappiamo che altri valori (diversi da +1 o -1) non sono possibili. E, d’altra parte, i singoli valori misurati sono grandezze reali, non sono distribuzioni statistiche. E le misure successive, lo abbiamo detto mille volte, confermano la prima. Questo vuol dire che il vettore di spin è orientato lungo la direzione dell’apparato (condizione sufficiente ecc. ecc.). È una coincidenza? Per una serie incredibile di circostanze, lo spin scelto rigorosamente a caso era orientato proprio nella direzione in cui abbiamo disposto l’apparato? Certo che no! E se proprio vogliamo essere sicuri che non sia un incredibile scherzo del destino, ruotiamo l’apparato di una manciata di gradi in una direzione arbitraria e ripetiamo l’esperimento. Otterremo, tanto per cambiare, +1 o -1. La misurazione lungo σθ\sigma_\thetaσθ​ ha preparato il sistema in uno stato, ovvero ha lasciato il sistema in uno stato corrispondente al valore misurato osservato, diverso, in generale, dallo stato in cui lo ha trovato. Non importa cosa avevamo prima; non possiamo saperlo, dato che è lo stato che il sistema aveva prima della misura. Quello che importa è ciò che abbiamo adesso, con il sistema che si trova in un nuovo stato. Quello corrispondente al valore misurato in uscita. A questo punto è necessario introdurre il concetto di spazio degli stati per poter costruire un modello di tutte le condizioni possibili in cui può trovarsi un sistema e vedere che relazioni ci sono con le uscite (i valori misurati). E da qui in poi, si comincia con la matematica. --- title: Esperimenti quantistici date: 2019-09-12 url: https://gabrielebaldassarre.com/fisica/esperimenti-quantistici/ excerpt: Un grande classico della meccanica quantistica è l'affermazione secondo cui una misurazione altera il sistema. In questo articolo cerchiamo di dare una spiegazione più rigorosa di questa concezione comune. category: Fisica --- In un sistema fisico che coinvolge grandezze su scala quantistica, non importa quanto gentilmente venga effettuata una misurazione (durante un esperimento), essa inevitabilmente perturberà in qualche modo il sistema, lasciandolo in uno stato diverso da quello in cui era prima della misurazione. Questa è la prima grande differenza rispetto a un sistema classico. Badate bene che questo non significa che la misura va necessariamente ad alterare la quantità che si sta misurando, cosa che renderebbe la misura stessa inutile e il sistema non osservabile, ma di sicuro qualcosa viene alterato. Il suo stato interno, per la precisione, che impatterà molto probabilmente altre grandezze del sistema. Vediamo bene cosa questo comporti nella pratica. Esperimenti quantistici Intanto diciamo che effettuare un esperimento quantistico significa misurare, con uno strumento opportuno, una particolare grandezza di un sistema (su scala) quantistica. Abbiamo giù detto che, quando si misura una grandezza quantistica, dobbiamo considerare l’apparato di misurazione come parte integrante del sistema, un qualcosa in grado di influenzarlo. Non ci interessa, per ora, come questo apparato sia fisicamente realizzato, quindi concettuializziamolo come una black box dotata di una finestrella dalla quale è possibile leggere i risultati e di una sonda per misurare il valore della grandezza da osservare. Questo apparato, inoltre, ha un’orientazione nello spazio: un lato è chiaramente indicato come “Alto”, come gli scatoloni dei mobili Ikea. Riprendiamo l’esempio del sistema della moneta, che può assumere i due soli valori misurati “Testa” o “Croce” e supponiamo che sia una grandezza quantistica. Supponiamo di chiamarla spin e indichiamola con la dicitura σz\sigma_zσz​. Invece che “Testa” o “Croce”, i risultati possibili per questa grandezza sono +1 e -1, corrispondenti rispettivamente agli stati “up” e “down”. In pratica, abbiamo la situazione seguente: Notiamo che prima di aver eseguito la misurazione, la finestrella dell’apparato non mostra alcun valore misurato. Supponiamo, ora, che lo spin sia stato preparato da qualcuno (o qualcosa) nello stato up. Non ci interessa sapere chi o cosa ha imposto questo stato al sistema, nella nostra concettualizzazione matematica. Ciò che importa è che lo spin sia nello stato up senza alcuna possibilità di errore. Effettuiamo la misurazione: l’apparato restituirà il valore +1. Per essere sicuri, resettiamo l’apparato di misura e rieseguiamo le misure più volte, assicurandoci che tra una misura e l’altra non accada qualcosa nel sistema tale da alterare il valore di σz\sigma_zσz​ (in fisica si dice che non accade niente di interessante al sistema). Otterremo sempre +1: il risultato dell’esperimento non varia. E questa è già una prima, grande, scoperta: nei sistemi quantistici, i risultati degli esperimenti ripetuti sono confermati. La meccanica quantistica, insomma, è strana, ma non è così strana. Adesso, facciamo qualcosa di veramente inaspettato: ruotiamo di 180° l’apparato ed eseguiamo la misurazione. Il risultato che otteniamo è -1 e tale risultato è, come prima, tale misura è confermata rieseguendo gli esperimenti. E questa è la seconda grande scoperta: c’è, nello stato delle grandezze quantistiche, un qualche tipo di concetto di direzionalità. E, quale strumento migliore abbiamo a disposizione per descrivere, in un modello, la direzionalità se non tramite i vettori?. Se, infatti, lo stato dello spin fosse rappresentato da un vettore con modulo unitario e che, nel caso specifico, punta verso l’alto (da cui, beh, _up__), non c’è nulla di imprevedibile nei risultati +1 e -1, in base a come è ruotato l’apparato. Newton è salvo. Ma cosa succede se ruotiamo l’apparato di lato ed effettuiamo la misurazione? In base alla meccanica classica, dovremmo avere come risultato 0 (la componente sull’asse x del vettore up è zero). E invece… Per quanto ci ostiniamo a misurare, il risultato sarà ancora una volta +1 oppure -1! Non solo: è impossibile prevedere se il risultato sarà +1 o -1. Entrambi i valori hanno uguale probabilità di presentarsi, ma una volta determinato, il valore misurato viene mantenuto nelle misurazioni successive. La proprietà di conferma delle misure non è andata perduta: l’aleatorietà si presenta solo alla prima misurazione. E se ruotiamo l’apparato di un angolo arbitrario? La meccanica classica ci dice che il risultato dovrebbe essere cos⁡(θ)\cos(\theta)cos(θ), dove θ\thetaθ è l’angolo formato dall’apparato con il vettore di spin, perché tale è la componente del vettore di spin lungo la direzione in cui è orientato l’apparato. Ma niente da fare… Il risultato è ancora una volta +1 o -1, con la stessa irritante aleatorietà iniziale. E tanto per complicare ancora di più le cose, se riportiamo l’apparato nella posizione iniziale e tentiamo di misurare σz\sigma_zσz​, il risultato è ancora una volta +1 o -1, con uguale probabilità. Quindi, questo vuol dire che il sistema non si trova più nello stato up, perché se così fosse il risultato della misurazione non sarebbe aleatorio (e valere, nel nostro esempio, +1). In effetti, a voler essere precisi, la figura sopra è imprecisa: la “freccia” non è più rivolta verso l’alto. Da questo esperimento possiamo trarre due importanti conclusioni: Lo spin può assumere solo valori discreti, ma solo per alcuni stati del sistema (es. up o down), le misure sono determinabili in modo non ambiguo. In generale, riscontreremo sempre una certa aleatorietà nelle misurazioni, il che ci suggerisce che, in un sistema quantistico, stati e misure non sono la stessa cosa. In un sistema classico questo non avviene: se si conosce lo stato di un sistema, è possibile sempre misurare le sue grandezze in modo non ambiguo. L’atto della misurazione ha perturbato il sistema. Nello specifico, la misura di σx\sigma_xσx​ ha distrutto qualunque informazione sulla misura di σz\sigma_zσz​ che avevamo raccolto in precedenza. Ne consegue che le due componenti spaziali dello spin non possono essere misurate contemporaneamente o, più in generale, di un sistema quantistico non è possibile conoscere tutto, a differenza di un sistema classico. Questi due principi (quantizzazione e indeterminazione) rappresentano i pilastri della meccanica quantistica; adesso non ci resta che costruire un modello matematico per poterla applicare. Prima, però, facciamo pace con la fisica classica. --- title: Sistemi classici e sistemi quantistici date: 2019-09-11 url: https://gabrielebaldassarre.com/fisica/sistemi-classici-quantistici/ excerpt: Prima di iniziare lo studio della meccanica quantistica, è importante chiarire cosa si intende per sistema e cosa differenzia un sistema quantistico da un sistema classico. category: Fisica --- Molto banalmente, per sistema fisico si intende un sistema chiuso, nel senso che non interagisce con l’esterno, in cui le cui caratteristiche interne (ovvero il suo stato) sono predicibili con esattezza. Supponendo di avere a disposizione tutte le informazioni e la conoscenza necessari per calcolare suddetto stato, questo è deterministico e predicibile con assoluta esattezza e senza ambiguità. Tutti gli stati possibili di un sistema (sia esso classico o quantistico) formano una astrazione matematica chiamata spazio degli stati. La differenza tra un sistema classico e uno quantistico sta proprio nella natura di tale spazio. Sistemi classici Supponiamo di voler modellare come sistema il lancio di una moneta (un classico, no?). Nota che stiamo modellando come sistema il lancio della moneta, non la moneta in sé (come oggetto con delle caratteristiche fisiche ben precise e che esiste in un punto ben preciso dello spazio). Come tale, questo sistema può trovarsi solo in uno dei due possibili stati: Ad ogni lancio, otterremo solo uno dei due possibili stati, senza alcuna ambiguità. Non importa quanti lanci effettuiamo: otterremo sempre testa oppure croce. E mai un po’ testa e un po’ croce. In un sistema classico, lo stato è sempre univocabilmente specificato. Non solo: in un sistema classico, se conosciamo lo stato conosciamo tutto del sistema, incluso il risultato della misurazione (nel caso del lancio della moneta, l’esito della misurazione/esperimento è banale: coincide con la rappresentazione dello stato interno). E il determinismo? Il lancio della moneta non è il tipico esempio di processo aleatorio? In particolare, quello che ha eguale probabilità di presentare in uscita ognumo dei due risultati possibili? Beh, sì. Ma solo se si considera la moneta utilizzando un modello astratto, semplificato, della realtà. Se avessimo a disposizione, prima del lancio, tutte le informazioni sul momento della moneta, le forze a cui è soggetta, l’angolo con cui è stata lanciata, la pressione atmosferica che agisce su di essa (sto volutamente esagerando) e in linea generale tutto ciò che serve per modellare la traiettoria della moneta nello spazio, il risultato sarà un processo assolutamente deterministico. Esattamente come prevede la fisica classica. Lo spazio degli stati di un sistema classico è, matematicamente iscrivibile a un insieme. Tale insieme può avere un numero finito (per esempio, il lancio della moneta o il lancio di un dado) o infinito di membri (per esempio, la posizione di una particella in una retta), purché sia un insieme numerabile. In quanto insieme, valgono su di esso le regole dell’insiemistica (unione e intersezione) e la logica booleana per individuare dei sottoinsiemi mediante proposizioni. Per esempio, nel lancio del dado (sempre inteso in senso astratto), un sottoinsieme dello spazio degli stati (formato dai sei elementi {1, 2, 3, 4, 5, 6}) può essere la proposizione “numeri pari”: P={2,4,6}P = \{2,4,6\}P={2,4,6} E un’altra la proposizione “tutti i numeri maggiori o uguali a tre” M={3,4,5,6}M = \{3,4,5,6\}M={3,4,5,6} Per la logica combinatoria, anche la seguenti proposizioni sono valide: P∩M={4,6}P \cap M = \{4, 6\}P∩M={4,6} P∪M={2,3,4,5,6}P \cup M = \{2,3,4,5,6\}P∪M={2,3,4,5,6} Sistemi quantistici (e il ruolo della matematica) Quando abbiamo a che fare con un sistema quantistico, praticamente tutto ciò che abbiamo appena detto su un sistema classico non vale più e ci sono, invece, altre leggi che vedremo successivamente. Ma prima di procedere, cosa vuol dire esattamente che i sistemi quantistici presentano leggi diverse? Esistono due tipologie di sistemi fisici nell’Universo? Oppure Newton aveva torto? Nessuna delle due. Non esistono sistemi “classici” e sistemi “quantistici” nel nostro (unico) Universo e Newton non aveva torto. In effetti, il comportamento di un sistema quantistico è così controintuitivo che la gente è portata a dire che la meccanica quantistica è “illogica” oppure che “gli elettroni si comportano in modo strano”. Gli elettroni non si comportano in modo strano. Gli elettroni fanno quello che gli elettroni fanno e, per tutto il XX Secolo, i fisici sperimentali hanno lavorate per dimostre, e ci sono riusciti, che è esattamente così che si comportano. La realtà è inequivocabilmente quantistica. Molto banalmente, per noi che siamo troppo grandi e abbiamo sensi troppo lenti per essere sensibili agli effetti quantistici, la realtà che percepiamo può essere descritta, perfettamente, dalla fisica newtoniana. In altre parole, la fisica classica è una approssimazione della fisica quantistica. O ancora, per sistemi grandi e lenti come quelli con cui siamo abituati a interagire (gattini, tostapane, manuali di Dungeons&Dragons e montagne), gli effetti quantistici della realtà sono trascurabili e la fisica classica può tranquillamente essere adoperata. Se, invece, vogliamo studiare sistemi microscopici, che non a caso sono definiti sistemi a scala quantistica, come il moto di una molecola o le caratteristiche di un elettrone, gli effetti quantistici non sono più trascurabili e le leggi di Newton (di Coulomb, di Maxwell, ecc. ecc.) non bastano più. Il problema è che i nostri sensi, e con essi il nostro cervello, non sono “ingegnerizzati” per comprendere i fenomeni a scala quantistica. Ecco perché ci sembra tutto così incomprensibile. E non è una questione di preparazione: il cervello di Feynmann non è in grado di comprendere la meccanica quantistica meglio di uno studente delle superiori. Quello che, però, può fare chi è preparato è visualizzare i fenomeni quantistici attraverso una opportuna astrazione matematica. Non sforzatevi di capire la meccanica quantistica con i vostri sensi. Non è possibile e arriverete a conclusioni sbagliate. Quello che bisogna fare è padroneggiare il modello matematico astratto che è in grado di descriverla. E tanto per essere ancora più chiari: guardare i video divulgativi su YouTube con titoli tipo “Quantum Mechanics finally explained!” possono aiutare all’inizio, ma senza una trattazione rigorosa (già…matematica), non si va molto avanti. Ma non preoccupatevi: paradossalmente, la matematica della meccanica quantistica è incredibilmente semplice. Molto più semplice di quella della meccanica classica, tanto per dire. Ok, fatta la doverosa premessa, dicevamo che la meccanica quantistica sovverte tutto ciò che abbiamo detto sui sistemi classici. Cominciamo a enunciare il tutto, e piano piano approfondiremo singolarmente i singoli punti. Se in un sistema quantistico conosci lo stato, non conosci tutto ciò che c’è da sapere sul sistema. In particolare, non è detto che tu sia in grado di prevedere il risultato di un esperimento. Lo spazio degli stati di un sistema quantistico non è un insieme numerabile. In un sistema quantistico gli stati non sono necessariamente distinguibili gli uni dagli altri in modo totalmente non ambiguo. In un sistema quantistico, non è possibile effettuare un esperimento (una misura) che lasci il sistema imperturbato, indipendentemente da quanto gentile la misura stessa sia stata. Niente male, eh? --- title: Approfondimento matematico: la power law date: 2018-08-12 url: https://gabrielebaldassarre.com/reti-sociali/legge-potenza-powerlaw/ excerpt: La legge di potenza, o _power law_ è uno dei costrutti matematici che ricoprono massima importanza in Social Network Analysis. In questo articolo ci doteremo di tutti gli strumenti teorici e metodologici per applicarla allo studio delle reti category: Reti Sociali --- Le legge di potenza altri non è che una funzione matematica con un andamento esponenziale caratterizzato da un parametro che può essere sia negativo che positivo e che nella sua forma più generale si esprime come f(x)=axkf(x) = ax^kf(x)=axk e di per sé non ha niente di speciale. Diventa, però, interessante quando descrive una distribuzione di probabilità, ovvero la probabilità del verificarsi degli eventi misurati sull’ascissa. In questo frangente, si può osservare che tantissimi fenomeni fisici, dalla dimensione delle città a quella dei crateri da impatto, dalla magnitudine dei terremoti all’attenuazione acustiva, seguono qusto andamento. Ma prima di addentrarci nello studio della legge di potenza, facciamo alcuni esempi chiarificatori di come si presentano, spesso, le grandezze in natura. Distribuzioni normali e…meno normali Il modo più “naturale” in cui ci aspettiamo di misurare una grandezza è una distribuzione centrale, dove la maggior parte delle osservazioni si concentrano attorno a un valore tipico, il valore medio, e decadono molto velocemente in modo simmetrico ai due estremi. Molte distribuzioni, sia presenti in natura che costruite dall’uomo, hanno queste caratteristiche, che possono essere descritte, con un livello più o meno accettabile di approssimazione, con tutta una famiglia di modelli statistici, come la distribuzione gaussiana, la distribuzione binomiale e la quella di Poisson che, dati una serie di parametri, è in grado di spiegare e descrivere la popolazione. Non è detto, però, che le osservazioni si distribuiscano in modo simmetrico attorno ad un valore tipico. Alcune grandezze, infatti, si esprimono in popolazioni in cui si ha la maggioranza delle osservazioni su valori bassi delle ascisse, ma presentano anche diverse osservazioni molto a destra, in una lunga coda , con osservazioni che possono raggiungere valori della ascissa molto grandi, anche di diversi ordini di granzezza più grandi della media. Quello più sotto ne è un tipico esempio. Il grafico in scala log-log, mostra chiaramente un andamento assimilabile a quello di una retta per buona parte del suo dominio. Non lo è, apparentemente, nella parte più a destra, dove, però, vige più che altro un elevato errore di campionamento (come dicevamo, sono pochissime le parole ad avere un numero di occorrenze così elevato). Passiamo ora dalle frequenze alle probabilità e quindi al differenziale. Se definiamo come \( p(x)dx \) la frazione di parole con un numero di occorrenze compreso tra \( x \) e \( x + dx \) e ci troviamo in un andamento più o meno rettilineo come quello appena evidenziato, allora la funzione, su scala logaritmica, può essere descritta come ln⁡p(x)=−αln⁡x+c\ln p(x) = -\alpha \ln x + clnp(x)=−αlnx+c con \( \alpha \) e \( c \) costanti. Elevando su \( e \) otteniamo la cosiddetta legge di potenza (o power law). p(x)=Cx−α(\ast)p(x) = Cx^{-\alpha} \tag{\ast}p(x)=Cx−α(\ast) con \( C = e^c \). Una volta definito \( \alpha \), che è il parametro caratterizzante e che in genere (e soprattutto in ambito SNA) è compreso tra 2 e 3, e comunque non è mai minore di 1, abbiamo tutto ciò che ci serve per descrivere la distribuzione. Il coefficiente \( C \), infatti, non è per nulla interessante ai fini pratici, dato che è semplicemente il termine che garantisce che la somma dell’area sottesa dalla curva sia 1, come obbligatorio che sia trovandoci di fronte a una funzione di probabilità. In natura sono migliaia le grandezze che seguono la legge di potenza nel loro andamento probabilistico e spaziano dall’astronomia, alla linguistica (come abbiamo visto) fino, naturalmente, alle scienze sociali. Tuttavia, molto raramente una grandezza segue una power law sull’interezza delle misure che assume. Più spesso, essa segue questo andamento a tratti, mentre in altre regioni il suo andamento segue leggi diverse. D’altra parte, per ciò che concerne in nostri scopi, poter affermare che una rete sociale segua, per alcuni suoi attributi (come, ad esempio, il grado) la legge di potenza, ci consente di poter desumere moltissimo sulla sua natura. Non è quindi una affermazione da fare alla leggera! Per questo motivo, ora che sappiamo quali sono le sue caratteristiche, resta da capire come studiarla. Individuare una distribuzione power law Innanzitutto una premessa, che facciamo riprendendo l’esempio di prima: dire che la r-esima parola più ricorrente in Moby Dick viene usata n volte è esattamente la stessa cosa di dire che esistono r parole che occorrono con una frequenza pari almenoa n (per capirci, r sta per rank). Intanto, questo ci consente di pensare alla legge di potenza come a una funzione di distribuzione cumulativa di probabilità, e questo è utile a prescindere. Ma, tornando all’interpretazione; il modo più semplice per individuare una power law è, in prima istanza, con un istogramma. Lo abbiamo involontariamente già visto con l’esempio precedente, in cui abbiamo rappresentato la popolazione in questa forma, usando punti invece che barre per non affollare il grafico e riportando sulle ascisse gli intervalli \( k, k+n \) di occorrenze nel testo e sulle ordinate il conteggio delle parole contenute in tali intervalli. In particolare, l’andamento (quasi) lineare del grafico su assi logaritmici ci ha fatto pensare a una power law. L’istogramma è stato costruito affinché l’ampiezza delle “barre” (o, più propriamente, bin o meglio ancora intervalli) fosse costante; il grafico che ne è risultato è decisamente ben definito nella parte sinistra, dove si concentrano la maggior parte delle osservazioni, ma molto confuso sulla coda di destra, dove ogni intervallo dell’istogramma ha pochi elementi e perciò le piccole fluttuazioni nel numero di componenti nel gruppo causa un grande scostamento sul grafico su scala logaritmica. Il problema è serio: andando a costruire su quel campione un modello di regressione per stimare il modulo di \( \alpha \), il rumore statistico sulla coda interverrà negativamente su di esso, introducendo una forte sottostima nel coefficiente. Un modo più accurato consiste allora nel variare la dimensione degli intervalli dell’isogramma con uno schema costante e normalizzare i risultati, in modo da ottenere un conteggio per unità di grandezza di x. Questa operazione si chiama non-linear binning. La logica più comune di binning è quella su base logaritmica, creando una sequenza di intervalli di dimensione esponenzialmente crescente ad es. \( 2^0=1, 2^1=2, 2^2=4, 2^3=8,…\) In questo modo, gli intervalli a destra otterranno un maggior numero di campioni, abbattendo il rumore statistico. Applichiamo questa trasformazione ai dati di Moby Dick: Il grafico di destra, su scala logaritmica, è ancora una volta quello più interessante. Grazie al logarithmic binning, infatti, abbiamo linearizzato efficacemente anche la parte destra della distribuzione e ora è possibile applicare una regressione per stimare \( \alpha \). Tuttavia, noterete come i punti caratterizzanti ora siano molti di meno. In effetti, per eliminare il rumore si è reso necessario creare degli intervalli molto ampi e questo ha comportato una notevole perdita di informazioni. Per spiegare meglio il concetto: una volta aggregate le misurazioni all’interno di un bin, le abbiamo perse come entità distinte. E tanto più ampi sono gli intervalli, tante più sono le osservazioni che abbiamo aggregato insieme, e sulle quali non è più possibile affermare nulla singolarmente. In particolare, è sufficiente che \( \alpha > 1 \), cosa che avviene sempre, per far sì che un intervallo abbia meno campioni dell’intervallo immediatamente alla sua sinistra. Fortunatamente esiste un modo ancora migliore per rappresentare (e studiare) una power law e consiste nel rappresentarla mediante la distribuzione di probabilità cumulativa (cumulative distribution function, o CDF). Se indichiamo con \( P(x) \) la probabilità che \( x \) abbia un valore non più semplicemente uguale ma maggiore o uguale a \( x \) P(x)=∫x∞p(x′)dx′P(x) = \int_x^\infty p(x^\prime)dx^\primeP(x)=∫x∞​p(x′)dx′ Poiché la \( p(x) \) è una power law di tipo p(x)=Cx−αp(x) = Cx^{-\alpha}p(x)=Cx−α possiamo scrivere P(x)=C∫x∞x′−αdx′=Cα−1x−(α−1)(CDF)P(x) = C\int_x^\infty x^{\prime-\alpha}dx^\prime = \boxed{ \frac{C}{\alpha - 1}x^{-(\alpha - 1 )}} \tag{CDF}P(x)=C∫x∞​x′−αdx′=α−1C​x−(α−1)​(CDF) La CDF \( P(x) \) di una power law (nel box rosso) è a sua volta, come si può vedere, una power law ma con esponente \( \alpha - 1 \); questo significa che una volta rappresentata su scala log-log presenterà ancora un andamento rettilineo, ma con una pendenza diversa. Soprattutto, essa è stata ottenuta senza un binning, quindi senza perdita di informazioni e senza aver dovuto formulare alcuna ipotesi sull’ampiezza degli intervalli (ovvero, fare una scelta empirica). Ancora una volta, usiamo il dataset di Moby Dick per visualizzare la CDF: La CDF del campione è, come si può vedere, molto “pulita” e segue con notevole precisione l’andamento di una power law avente α=1.93\alpha = 1.93α=1.93 (retta in rosso). Determinare i parametri della power law Per quanto molto comune in natura, sono poche le grandezze che presentano un andamento che segue la legge di potenza nell’interezza dei valori della loro CDF; più spesso, si riscontrano distribuzioni che assimilano la legge di potenza sulla parte destra del loro dominio (come nell’esempio sopra), mentre la parte sinistra segue un andamento diverso. Per questo motivo, è prassi abbastanza comune individuare il valore \( x_{min} \) al di sopra del quale la distribuzione, si verifica, segue in modo soddisfacente la legge di potenza, mentre per i valori al di sotto è opportuno individuare un modello che esprima con minore incertezza l’andamento della CDF, perché la power law non modella adeguatamente la distribuzione; spesso, per questi valori bassi, migliori risultati si hanno con modelli di tipo esponenziale, log-normali o di Poisson. Nell’esempio di Moby Dick, per dire, ha senso applicare il modello power law di cui sopra solo per xmin≥26x_{min} \ge 26xmin​≥26 La stima di \( x_{min} \) può essere fatta in modo visivo, dal grafico della CDF, oppure analiticamente, in modo da avere una stima statisticamente robusta e che, soprattutto, non richieda l’intervento diretto dell’osservatore per l’interpretazione. L’idea alla base è piuttosto semplice: scegliamo un valore \( \hat{x} \) tale che renda le distribuzioni di probabilità e la migliore power law applicabile (con un dato \( \alpha \)) il più simile possibile per valori sopra \( \hat{x} \). Se il valore di \( \hat x_{min} \) è superiore del valore vero di \( x_{min} \), questo implica scartare un maggior numero di osservazioni (sotto \( x_{min} \)) e, quindi, ottenere, una distribuzione di probabilità più scadente, per via delle fluttuazioni statistiche. D’altra parte, se il valore di \( \hat{x} \) è inferiore al valore vero di \( x_{min} \), le distribuzioni di probabilità divergeranno per la sostanziale differenza tra i dati e il modello stesso. Il valore migliore di \( x_{min} \) giace all’interno di questo intervallo. La migliore misura per quantificare la distanza tra due distribuzioni di probabilità, per dati che palesemente non seguono la distribuzione nornale, è la statistica KS (o Kolmogorof-Smirnov), ovvero la massima distanza tra le CDF dei dati e il modello: D=max⁡x≥xmin∣S(x)−P(x)∣D = \max_{x \ge x_{min}} \left\lvert S(x) - P(x) \right\rvertD=x≥xmin​max​∣S(x)−P(x)∣ con \( S(x) \) che rappresenta la CDF delle osservazioni per valori che siano almeno \( x_{min} \) e \( P(x) \) la CDF del modello power law che rappresenta la miglior stima possibile. La stima \( \hat{x} \) di \( x_{min} \) è quella che minimizza \( D \). Una volta individuato un soddisfacente \( x_{min} \), è possibile stimare un adeguato \( \alpha \). Per farlo, l’approccio migliore è utilizzare il metodo della massima verosmiglianza (MLE), posto che vi sia un adeguato numero di osservazioni con \( x \ge x_{min} \). LA MLE per il caso continuo è la seguente (quella per il caso discreto è molto più complessa e non verrà trattata in questa sede): α^=1+n[∑i=1nln⁡xixmin](MLE)\hat{\alpha} = 1 + n\left[\sum_{i=1}^n \ln\frac{x_i}{x_{min}}\right] \tag{MLE}α^=1+n[i=1∑n​lnxmin​xi​​](MLE) Dove \( \hat{\alpha} \) è il valore stimato per \( \alpha \), normalmente non noto, individuato da una popolazione di \( n \) osservazioni \( x_i \) con \( i = 1, 2, \dots, n \), tutti tale che \( x_i \ge x_{min} \). Naturalmente, la robustezza di tali stimatori va anche essa valutata con un opportuno test per l’ipotesi, ma per essa vi chiedo di attendere il secondo articolo sull’argomento che ho in lavorazione, che riporterà degli esempi pratici in R e che toccherà anche questi temi ulteriori. Esempi noti in letteratura Dai ricercatori, sono stati spesso studiati e citati dei campioni che esprimono un andamento della CDF come legge di potenza. Vediamo i più famosi: Frequenza delle parole nei testi - lo abbiamo visto con Moby Dick, ma il linguista George Kingsley Zipf ha potuto verificare come questo accada in tutta la letteratura occidentale. La trattazione è riportata in un suo elegante lavoro del 1949. Citazioni nelle pubblicazioni scientifiche - dimostrata da Derek J. de Solla Price in un articolo su Science nel lontano 1965. Un esempio tutto nostro: i comuni italiani Per completare la trattazione, volevo brevemente studiare un campione che molto spesso, in letteratura, è stato assimilato ad una power law: la popolazione degli insediamenti umani in una data nazione. Così, per essere originale, ho ben pensato di studiare il dataset dei communi italiani, messo a disposizione dall’ISTAT a valle dell’ultimo censimento nazionale. Si tratta di un dataset di 7978 comuni italiani con una popolazione compresa tra 30 e 2873494 abitanti. Il valore del più piccolo comune d’Italia, (30, si tratta del comune di Moncenisio, in provincia di Torino) mi ha fatto subito sospettare che la parte sinistra della distribuzione avesse un comportamento singolare (difficile pensare che ci siano più comuni di 30 abitanti rispetto agli altri). Prima, però, di stimare un \( x_{min} \) per la CDF, mi sono chiesto se la distribuzione, una volta applicato il binning logaritmico, non potesse assumere una forma canonica. Così, dopo aver segmentato la popolazione in 100 intervalli ho ottenuto questo plot della densità e della percentuale di osservazioni. Ebbene, questo grafico mi ha subito dato la sensazione di un andamento che segue una distribuzione log-normale, con una leggera distonia sulla “coda” destra che può essere imputabile a un andamento secondo la legge di potenza. Il prossimo passo è stato, perciò, quello di individuare, attraverso il metodo di massima verosimiglianza spiegato sopra, due modelli, uno di tipo power law e uno di tipo log-normale. L’ho fatto, naturalmente, sulla CDF e ho ottenuto il seguente: Ebbene, come previsto il modello log-normale (in verde) riesce a descrivere il campione per gran parte del suo codominio (ha \( x_{min_1} = 430\), fino a \( x_{min_2} = 99469\), al di sopra del quale è la legge di potenza (in rosso) caratterizzata dal parametro \( \alpha = 2.41 \) a descrivere meglio la curva di distribuzione della CDF. Poiché, per ciò che avevamo ipotizzato sui dati, lo studio della funzione non è interessante per valori inferiori di \( x_{min_1} \), il fitting si può arrestare qui. Ciò che bisognerebbe fare per terminare il lavoro è, come dicevamo, un opportuno test per verificare la robustezza degli stimatori, ma oggettivamente, nel caso specifico l’ispezione visiva dei grafici fornisce già, con discreto margine di sicurezza, le garanzie necessarie. Per approfondire M. E. J. Newman, Power laws, Pareto distributions and Zipf’s law, 2006; Aaron Clauset, Cosma Rohilla Shalizi, M. E. J. Newman, Power Laws Distributions in Empirical Data, 2009 --- title: Una rete sociale fatta di comunità date: 2018-07-29 url: https://gabrielebaldassarre.com/reti-sociali/community-network-model/ excerpt: L'esperimento dei sei gradi di separazione ci conferma che il mondo è sorprendentemente _piccolo_ e _ben connesso_. Recentemente, però, si è scoperto che le reti sociali umane presentano una caratteristica ancora più affascinate, fondamentale per la loro stessa sopravvivenza: l'innata propensione ad organizzarsi in community. category: Reti Sociali --- Nel corso di tutto il XX Secolo, i sociologi e gli studiosi di topologia e di reti ci hanno deliziato con modelli non solo sempre più credibili e realistici per descrivere una rete sociale, ma anche con sorprendenti similitudini e simmetrie con tante reti spontaneamente presenti in natura, in una sorta di elegante rappresentazione unificata della realtà. Il caso più noto è certamente quello della Teoria del mondo piccolo, quella dei sei gradi di separazione per intenderci, e della Rete Small-World da essa descritta. Accompagnato da un esperimento non proprio rigoroso, ma goloso dal punto di vista mediatico, qualche pellicola cinematografica e, in tempi più recenti, un po’ di intrigo complottistico in salsa social, il modello è anche uno dei primi che si studia in Social Network Analysis. Ebbene, sotto opportune condizioni lo Small-World si ritrova in tante reti, naturali e non, come quelle descritte dai neuroni del cervello, le reti di diffusione dell’energia elettrica, i commutatori telefoni, ecc., e questo ha contribuito non poco alla sua attrattiva per i non addetti ai lavori. Sfortunatamente per gli hippie e per i fisici, però, il XXI Secolo si è aperto con una serie di studi che ci hanno mostrato una realtà un po’ più complessa, che rompe, almeno per ora, la simmetria con neuroni, telefoni e relé e, forse, fa perdere un po’ di fascino al tutto. Le reti sociali umane sono sì delle reti Small-World, ma hanno anche alcune caratteristiche uniche. Ma prima di parlare di questo modello più evoluto, vediamo innanzitutto brevemente come si è chiuso il XX Secolo in casa dei sociologi. Stanley Milgram, lo psicologo statunitense che, nell’ambito dell’arcinoto esperimento, nel 1967 spedì lettere in giro per tutti gli Stati Uniti e dimostrò che viviamo in un mondo piccolo. Non tutti sanno, però, che prima di lui l’esistenza dello Small-World effect era stata speculata dallo scrittore ungherese Frigyers Karinthy mentre Ithiel de Sola Pool e Manfred Kochen colmarono, nel 1978 e con un rigoroso modello matematico, le lacune dell’illustre, e discusso, predecessore (foto: Psychology Unlocked). Cosa si sapeva già: le tre caratteristiche di una rete sociale Per lungo tempo, tre sono state, in linea di principio, le caratteristiche che sembravano mettere d’accordo tutte (o quasi) le reti sociali sui quali si avevano dati attendibili. Queste caratteristiche riscontrate nelle reti hanno consentito di costruire modelli via via più complessi e realistici e che descrivono in maniera sempre più accurata la complessità delle relazioni umane. Viviamo in un mondo piccolo Questa caratteristica, dimostrata empiricamente con l’esperimento di Milgram, suggerisce che la distanza media tra due nodi qualunque della rete è relativamente piccola. Tradotto, questo significa che, scelti due nodi della rete, essi sono connessi attraverso un numero di nodi intermedi molto piccolo se confrontato con la dimensione della rete stessa. Più precisamente, il diametro della rete è, sì, proporzionale al numero di nodi, ma cresce su scala solo logaritmica rispetto al numero di nodi \( n \). Questo comporta che al crescere del numero di nodi di una rete, il diametro cresce, ma più lentamente. Qui è racchiusa l’essenza dei sei gradi di separazione. E’ una caratteristica che non è di esclusivo appannaggio delle reti sociali, ma è stata riscontrata anche in reti di informazioni, reti tecnologiche e persino reti biologiche. Oltre ai succitati sei gradi dell’esperimento di Milgram (scesi, sembrerebbe, a 4.7 dall’avvento di Facebook), diametri contenuti il propozione alle dimensioni della rete si hanno nel World Wide Web (stimato in circa 19), nelle connesisoni neuronali e in molte altre circostanze. Gli amici dei miei amici sono miei amici Così come il DNA è costruito da una sequenza di quattro basi elementari (adenina, guanina,…) così anche le reti sociali hanno le loro “basi”. Queste basi sono le differenti combinazioni di collegamenti possibili tra tre nodi della rete, scelti casualmente. Il motivo per cui l’elemento più atomico di organizzazione delle comunità sociali sia il triangolo è ancora oggetto di studi e non vi ci addentreremo in questa sete. Fatto sta, che, come una catena di DNA è descritta da una lunga sequenza di basi, una rete sociale può essere descritta da una lunga sequenza di triangoli. Questi triangoli prendono il nome di triadi o, più spesso, di clique (in italiano orribilmente traducibile con ‘cricca’). E queste clique non sono, tipicamente, oggetti statici; le relazioni evolvono nel tempo e la struttura della rete con esso. Con essa, quindi, cambiano le clique. Una serie di clique, o triadi. In effetti, diverse possono essere le configurazioni con cui si presenta una triade aperta o chiusa. Tra questi esempi, l’unica caratterizzata da una certa instabilità è quella di sinistra che non a caso prende il nome di triade proibita (forbidden triad): la probabilità che evolva, presto o tardi, in una delle due configurazioni più a destra è molto alta. Il motivo per cui avviene questa “risoluzione” della clique ha cause fisiche: minimizzazione dell’energia. Troppo dispendiosa di energie, infatti, è la situazione del nodo A, costretto a mantenere due canali segregati con C e B in una situazione di perenne stress sociale. Molto più facile risolverla facendo sì che i due nodi non connessi tra di loro entrino in contatto oppure interrompendo la relazione con uno dei due vicini (C, in questo caso). La “forza” che eventualmente spinge verso la prima delle due evoluzioni appena descritte è la transitività ed è una caratteristica osservabile in molte reti sociali e anche in alcune reti di informazioni (come gli scambi di email in un’azienda). Detto anche clustering, perché spinge la rete a organizzarsi in grappoli strettamente connessi, è modellizabile con degli opportuni coefficienti che, in linea di principio, hanno lo scopo di misurare la densità e la tipologia di clique in una rete. Follow the leader Pensare alle reti come oggetti statici è un errore: le reti sociali si aggiornano di continuo introducendo nuovi nodi e nuovi collegamenti vengono a crearsi, mentre altri vengono recisi; solo che, talvolta, l’evoluzione è lenta e le variazioni impercettibili al punto che la rete stessa ci sembra un oggetto statico e immutabile. Ebbene, quando una rete sociale è in espansione, si può osservare che i nuovi nodi entranti tenderanno a estrudere collegamenti verso i nodi della rete che ne presentano già molti altri, come se la popolarità di un nodo attirasse a sé i nuovi arrivati. Questo comportamento, chiamato attaccamento preferenziale crea delle strutture di rete in cui pochi nodi molto influenti, gli hub, o se vogliamo i leader, detengono un numero di collegamenti attivi molto superiore alla media degli altri nodi della rete. Le reti sociali presentano questa caratteristica, ma non tutte e comunque non tutte le reti (per es. il fenomeno è molto più raro nelle reti di informazione, come Internet). Capire se ci troviamo di fronte a una rete di questo tipo, che per inciso prende il nome di scale-free, non è difficile: basta studiare la distribuzione del grado di tutti i nodi della rete. In una rete non scale-free non abbiamo motivo di sospettare la presenza di hub privilegiati perché la probabilità di un nodo di avere \( k \) connessioni è identica per tutti i nodi della rete e vale \( p(k) \). E, in effetti, la degree centrality di distribuisce secondo una distribuzione simmetrica (la binomiale, nella fattispecie) attorno alla degree centrality media \( k_{avg} \). Una rete scale-free munita, come detto, di hubs presenterà, invece, una distribuzione con una coda molto lunga sulla destra, dove ci sono nodi con una degree centrality di molto superiore alla media. Più precisamente, la degree centrality segue la legge di potenza, o power law. La distribuzione del numero dei collegamenti dei nodi per una rete non scale-free (a sinistra) con una scale-free (a destra). Sull’asse delle ascisse il numero di connessioni \( k \) di un nodo, sulle ordinate la percentuale di nodi tra tutti quelli della rete ad avere esattamente \( k \) connessioni. In una rete non scale-free si ha il tipico andamento a campana di una binomiale (o di una Poisson o anche di una normale, a seconda delle approssimazioni applicate al modello) con la maggior parte dei nodi avente un numero di nodi vicino alla media e scarsa probabilità di avere degli hub o dei nodi fortemente disconessi. Una rete scale-free, invece, ha l’andamento tipico di una power law, con la maggior parte dei nodi ad avere poche connessioni e pochi nodi ad avere un numero di connessioni \( k \) molto maggiori della media \( k_{avg} \). Solo una puntualizzazione sul nome: queste reti si chiamano scale-free perché la pendenza della retta caratteristica del modello di regressione costruito sulla distribuzione della centralità è invariante alla scala, cioè non varia al variare del numero dei nodi. Solo, tuttavia, questa grandezza presenta questa caratteristica, mentre tipicamente le altre grandezze caratteristiche (strutturali, come la centralitò, o attributi legati ai nodi) sono genericamente non invarianti. L’importanza delle community locali Innanzitutto una precisazione: non è che solo nel XXI secolo i ricercatori si siano accorti che gli esseri umani tendono a individuare delle community più o meno con le altre regioni della rete. Questa è una scoperta ben più antica, con cui sociologi, antropologi e, in tempi ancor più remoti, persino filosofi si sono confrontati. Quello che, però, mancava era un’adeguata rappresentazione mediante un modello che potesse essere utilizzato in Social Network Analysis. Un primo modello che descrive la creazione di community in una rete è stato un modello puramente gerarchico, che pensa alle community un po’ come alle organizzazioni militari, cioè in raggruppamenti aggregati che crescono progressivamente di dimensione (es. plotoni che formano compagnie che a loro volta formano battaglioni, reggimenti, ecc.). Presto, però, si è scoperto che questo modello era troppo schematico per rappresentare correttamente la realtà delle relazioni tra gli individui. Il buon senso ci suggerisce che le community locali si creano attorno agli individui che hanno qualcosa in comune, per es. l’età, la razza, la religione, la passione per i film di fantascienza. L’affinità tra questi individui è alta e, quindi, è più alta la probabilità che tra di loro si instauri un legame. In gergo, questo comportamento è chiamato mixing e una rete che favorisce la coesione tra nodi affini è una rete soggetta ad assortative mixing (o anche omofilia). Al contrario, una rete che favorisce le relazioni tra individui non affini viene detta una rete soggetta a disassortative mixing. Una stessa rete può essere sia assortativa che disassortativa, in base all’attributo scelto per esprimere la relazione. Una rete di adolescenti, ad esempio, è probabile che sia assortativa attorno a un attributo come l’età, ma sarà quasi certamente disassortativa su un attributo come il sesso se i legami esprimono relazioni sentimentali (la probabilità di un legame tra due nodi è più alta se essi non sono dello stesso sesso). Esiste, però, una particolare relazione di (dis)assortatività e cioè quella legata al numero di connessioni dei nodi (la degree centrality o grado) che possiamo riassumere in una semplice domanda: i nodi con un elevato numero connessioni tenderanno a collegarsi ad altri nodi con molte connessioni o preferiranno nodi con un basso numero di connessioni? Reti assortative e disassortative in riferimento al numero di connessioni (grado). In una rete assortativa (a sinistra) abbiamo una elevata densità di nodi con tante connessioni al centro, in un grande componente molto connesso, e una periferia di nodi con poche connessioni. In una rete disassortativa (a destra) invece avremo una distribuzione più uniforme tra centro e periferia e nodi con basso numero di connessioni adiacenti a nodi più popolari. Ebbene, molte reti sociali e per la verità tutte quelle che sembrano organizzarsi in community, a prescindere dalle loro dimensioni, sono assortative sul grado. Tipici esempi sono la rete individuata dalle collaborazioni scientifiche (tenendo conto che nella maggior parte dei casi i paper scientifici sono scritti a più mani da diversi studiosi e ricercatori) e la rete dei membri dei consigli di amministrazione delle aziende. Al contrario, reti tipicamente disassortative sul numero di gradi sono quelle biologiche, come le reti individuate tra le proteine di una cellula. La risposta alla domanda, comunque, è affermativa: i nodi con alta degree tenderanno a connettersi ai vicini più prossimi con alta degree, di fatto contribuendo al loro prestigio (e facilitandone le comunicazioni). Sembra poco, ma è la definizione di comunità per come la percepiscono gli esseri umani: comportamento assortativo verso i membri della propria community, e quindi tante connessioni e cluster molto stretti di individui con un fitto reticolo di connessioni che li tengono insieme, e comportamento disassortativo verso i membri delle altre community, raggiunti da sporadici connessioni che fungono da “ponte” tra le diverse community costituenti la rete. Non solo: se la rete è abbastanza fitta (e si può determinare anche numericamente quanto), questo fenomeno causa l’emergere di una community in genere di gran lunga più grande delle altre e di cui fanno parte la maggior parte dei nodi della rete: il cosiddetto giant component. Lo studieremo nel dettaglio in futuro. Oltre a fornirci il modello matematico con cui analizzare una rete fatta di comunità questa scoperta è fenomenale: ci fa capire come siano gli sporadici legami tra community diverse a essere essenziali per veicolare un messaggio, diffondere una notizia, capire in quanto tempo si diffonderà un virus ecc. nella totalità della rete. I (molti) legami presenti tra gli individui di una stessa community contribuiranno certamente alla velocità con cui il messaggio si distribuisce all’interno della community stessa, ma sono inutili affinché il segnale travalichi i confini della community locale per “infettare” la rete nella sua interezza. E’ quello che si intende forza dei legami deboli, e merita che se ne parli in un articolo dedicato. --- title: Approfondimento matematico: autovettori e autovalori date: 2018-07-13 url: https://gabrielebaldassarre.com/reti-sociali/introduzione-autovettori-autovalori/ excerpt: In questo articolo introdurremo l'argomento degli autovalori e autovettori, uno dei concetti base della geometria e dell'algebra lineare che trova applicazioni pratiche in moltissimi campi compresa, naturalmente, la Social Network Analysis. category: Reti Sociali --- Lo studio degli autovettori e dei corrispondenti autovalori è un qualcosa in cui ci siamo cimentati probabilmente tutti almeno una volta nella vita. In pochi, tuttavia, sanno che questo strumento è di importanza vitale sia per la Social Network Analysis che per la Fisica Quantistica. Nel primo caso, per via di tutto ciò che concerne la centralità. Senza la pretesa di fornire una trattazione rigorosa sull’argomento come se dovessimo preparare un esame di algebra lineare, qui ci concentreremo sull’intuizione dei concetti fondamentali così da avere tutti le conoscenze necessarie per utilizzarli in ambito di analisi delle reti sociali. Autovettori e autovalori: una definizione informale Supponiamo di avere una matrice di trasformazione quadrata \( A \). Essa può essere vista come un operatore perché può essere applicata (mediante prodotto scalare) a un vettore \( \vec{x} \) al fine di ottenere un vettore \( \vec{y} \), ovvero: Ax⃗=y⃗A\vec{x} = \vec{y}Ax=y​ Per chi non ha paura di fare indigestione di algebra lineare possiamo aggiungere che la matrice \( A \) è un endomorfismo. Ebbene, gli autovettori sono quei vettori che giacciono su delle direzioni caratteristiche della trasformazione \( A \) tali per cui quando sono moltiplicati per \(A \) danno come risultato altrettanti vettori non nulli che si differenziano dai vettori di partenza al più per il modulo e per il verso, mentre le loro direzioni restano invariate. In altri termini, supponendo che \( A \) sia una matrice \( 2 \times 2 \) di righe linearmente indipendenti, ovvero un endomorfismo in \( \mathbb{R}^2 \), allora esiste qualche coppia \( (x, y) \ne (0, 0) \) che, trasformata mediante \( A \), dia come risultato ancora la coppia \( (x, y) \) moltiplicata per \( \lambda \), che ne modifica il modulo e, eventualmente, il verso ovvero: A(x,y)=λ(x,y)A(x, y) = \lambda(x, y)A(x,y)=λ(x,y) I rispettivi coefficienti \( \lambda \) degli autovettori prendono il nome di autovalori. In caso di \( \lambda \) positivo, il rispettivo vettore trasformato non subisce un cambiamento di verso; quando negativo, il verso ne risulterà opposto. Ma, sia come sia, la direzione individuata dagli autovettori non cambia. Esempio di trasformazione. La matrice \( A = \begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix} \) produce gli autovettori \( v_1= \begin{pmatrix} 1 & 1 \end{pmatrix}^T \) e \( v_2= \begin{pmatrix} 1 & -1 \end{pmatrix}^T \) nelle direzioni rappresentate rispettivamente dalla retta blu e violetto. Tutti i vettori blu, paralleli alla direzione individuata da \( v_1 \) sono autovettori perché, quando trasformati attraverso \( A \), individuano un vettore con la stessa direzione, ma con un modulo moltiplicato per l’autovalore \( \lambda_1=3 \). Allo stesso modo, i vettori viola sono autovettori con autovalore \( \lambda_2 = 1 \). I vettori rossi, invece, quando trasformati da \( A \), variano in direzione: essi non sono autovettori (fonte: Wikipedia). Come calcolare gli autovettori e gli autovalori Per il calcolo degli autovalori, e quindi degli autovettori, procediamo algebricamente a partire da: Av⃗=λv⃗(1)A\vec{v} = \lambda\vec{v} \tag{1}Av=λv(1) che equivale a scrivere v⃗(A−λI)=0(2)\vec{v}(A - \lambda I) = 0 \tag{2}v(A−λI)=0(2) La relazione (2) produce un sistema di equazioni lineari omogeneo ovvero privo del termine noto. Il che significa che è possibile trovare soluzioni non nulle del vettore \( \vec{v} \) solo se il determinante della matrice che moltiplica \( \vec{v} \) è zero. In simboli: det(A−λI)=0\boxed{ det(A - \lambda I) = 0 }det(A−λI)=0​ che, una volta risolto, ci consente di individuare gli autovalori \( \lambda_{1 \cdots N} \) e i corrispettivi autovettori. Diagonalizzazione e cambio di sistema di riferimento Come dicevamo, gli autovettori ricoprono un ruolo essenziale in tanti campi del sapere, dalle scienze delle costruzioni alla meccanica quantistica, dall’elettromagnetismo alla teoria dell’informazione, fino allo studio delle reti. Alla base di tanta importanza è soprattutto la proprietà degli autovettori di poter trasformare una matrice \( A \) nella sua corrispettiva diagonalizzata, a seguito di un cambio di sistema di riferimento a favore di uno i cui assi siano gli autovettori della matrice stessa. Vediamo praticamente. Innanzitutto verifichiamo una proprietà molto interessante delle matrici diagonali: quando un vettore \( a \) è moltiplicato per una matrice diagonale \( A \), il vettore risultante \( b \) avrà componenti pari alla proiezione delle componenti del vettore originario sul sistema di assi ortogonali di riferimento moltiplicato scalarmente per i coefficienti sulla diagonale A. Facciamo un esempio. Supponendo di voler calcolare \( \vec{b} = A\vec{a} \) dati: a⃗=(13)A=[300−2]\vec{a} = \begin{pmatrix} 1 \\ 3 \end{pmatrix} A = \begin{bmatrix} 3 & 0 \\ 0 & -2 \end{bmatrix}a=(13​)A=[30​0−2​] Avremo: b⃗=(13)[300−2]=(3−6)\vec{b} = \begin{pmatrix} 1 \\ 3 \end{pmatrix} \begin{bmatrix} 3 & 0 \\ 0 & -2 \end{bmatrix} = \begin{pmatrix} 3 \\ -6 \end{pmatrix}b=(13​)[30​0−2​]=(3−6​) ovvero le componenti \(b_x = 3 \) e \(b_y = -6 \) sono le proiezioni sugli assi \( (x, y) \) del vettore \( \vec{b} \) con un modulo pari alle proiezioni del vettore di partenza \( \vec{a} \) moltiplicati, come abbiamo già detto, per i coefficienti sulla diagonale \( A \). Questo ragionamento è sorprendentemente simile a quanto abbiamo già detto sugli autovettori, ovvero i vettori caratteristici di una trasformazione che individuano delle direzioni sulle quali giacciono i vettori trasformati modificati al più in modulo e verso da un fattore \( \lambda \) In altri termini, è possibile rappresentare il vettore \( \vec{a} \) non più con il sistema di assi \( (x, y) \) ma su un nuovo sistema di assi \( (v_1, v_2) \) costruito sulle direzioni degli autovettori della matrice \( A \). Questo vettore trasformato \( \vec{a}’ \) avrà, come sappiamo, componenti di lunghezza pari a quelle del vettore di partenza \( \vec{a} \) moltiplicato per i vari autovalori \( \lambda_N \). In generale queste proiezioni saranno non ortogonali, laddove non lo sia stata la matrice di trasformazione di partenza. Questo ci consente di affermare due cose. Innanzitutto ci consente di dire che possiamo utilizzare gli autovettori come assi di riferimento per applicare una trasformazione \( A \) a un vettore \( \vec{a} \), utilizzando gli autovalori \( \lambda_N \) come moltiplicatori delle componenti di \( \vec{a} \) sulle direzioni degli autovettori. Ma quel che è più importante, ci consente di calcolare le coordinate del vettore \( \vec{a} \) nel nuovo sistema di riferimento individuato dagli autovettori \( (v_1, v_2) \). Vediamo come. Ricordando che il vettore \( \vec{a} \) è scomponibile nelle due componenti nelle direzioni degli autovettori, supponendo \( \vec{v_1} \) e \( \vec{v_2} \) autovettori normalizzati (cioè con modulo pari a uno), possiamo scrivere questa combinazione lineare: a=αv1+βv2a = \alpha v_1 + \beta v_2a=αv1​+βv2​ che individua un sistema di equazioni lineari che può essere risolto in \( (\alpha, \beta) \) così: (axay)=α(v1xv1y)+β(v2xv2y)⇒\begin{pmatrix} a_x \\ a_y \end{pmatrix} = \alpha\begin{pmatrix} v_1x \\ v_1y \end{pmatrix} + \beta\begin{pmatrix} v_2x \\ v_2y \end{pmatrix} \Rightarrow(ax​ay​​)=α(v1​xv1​y​)+β(v2​xv2​y​)⇒ (axay)=(αv1x+βv2xαv1y+βv2y)⇒\begin{pmatrix} a_x \\ a_y \end{pmatrix} = \begin{pmatrix} \alpha v_1x + \beta v_2x \\ \alpha v_1y + \beta v_2y \end{pmatrix} \Rightarrow(ax​ay​​)=(αv1​x+βv2​xαv1​y+βv2​y​)⇒ (axay)=(v1xv2xv1yv2y)(αβ)\begin{pmatrix} a_x \\ a_y \end{pmatrix} = \boxed{\begin{pmatrix} v_1x & v_2x \\ v_1y & v_2y \end{pmatrix}}\begin{pmatrix} \alpha \\ \beta \end{pmatrix}(ax​ay​​)=(v1​xv1​y​v2​xv2​y​)​(αβ​) Dove la matrice nel riquadro rosso non è altro che la matrice che ha come colonne le componenti normalizzate degli autovettori della matrice di partenza mentre \( (\alpha, \beta)^T \) è niente altro che la rappresentazione del vettore di partenza \(\vec{a} \) nel nuovo sistema di riferimento dato dagli autovettori. Se indichiamo la matrice degli autovettori con \( V \) possiamo in definitiva scrivere: a⃗=V(αβ)\vec{a} = V\begin{pmatrix} \alpha \\ \beta \end{pmatrix}a=V(αβ​) ovvero (αβ)=V−1A\begin{pmatrix} \alpha \\ \beta \end{pmatrix} = V^{-1}A(αβ​)=V−1A Il nuovo sistema di assi così individuato non sarà necessariamente rappresentato da autovettori ortogonali. Tuttavia, questi potranno essere ortogonalizzati facendoli ruotare relativamente uno agli altri in modo da ottenere variazioni di angoli di, giustappunto, 90°. L’applicazione più concreta del cambio di assi verso un sistema individuato dagli autovettori è proprio nella diagonalizzazione delle matrici che, come detto, ha diversi vantaggi tra cui la semplificazione dei calcoli. Ebbene, possiamo dimostrare che per diagonalizzare una matrice si possono usare gli autovalori per produrre una base di riferimento ortogonale. Se gli autovettori sono ortogonali, il vettore trasformato manterrà, nel nuovo sistema di riferimento, la stessa lunghezza - e questa è certamente la situazione più auspicabile. In caso contrario, il vettore trasformato presenterà una lunghezza diversa. Ma vediamo come. Ricordando l’equazione fondamentale degli autovettori per cui in uno spazio a \( N \) dimensioni abbiamo al più \( N \) autovettori e autovalori e la cui espressione del k-esimo è la seguente: Avk⃗=λkvk⃗ con k=1,…,NA\vec{v_k} = \lambda_k\vec{v_k} \text{ con } k = 1,\ldots,NAvk​​=λk​vk​​ con k=1,…,N Ovvero Av1⃗=λ1v1⃗Av2⃗=λ2v2⃗⋮Avk⃗=λkvk⃗⇒\begin{matrix} A\vec{v_1} = \lambda_1\vec{v_1} \\ A\vec{v_2} = \lambda_2\vec{v_2} \\ \vdots \\ A\vec{v_k} = \lambda_k\vec{v_k} \end{matrix} \RightarrowAv1​​=λ1​v1​​Av2​​=λ2​v2​​⋮Avk​​=λk​vk​​​⇒ A(v1v2…vN)=(v1v2…vN)ΛA(v_1 v_2 \ldots v_N) = (v_1 v_2 \ldots v_N)\LambdaA(v1​v2​…vN​)=(v1​v2​…vN​)Λ con i vettori \( v_k \) gli autovettori e \( \Lambda \) la matrice degli autovalori che, infatti, vale: Λ=(λ10…00λ2…0⋮⋮⋱⋮00…λk)\Lambda = \begin{pmatrix} \lambda_1 & 0 & \ldots & 0 \\ 0 & \lambda_2 & \ldots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \ldots & \lambda_k \end{pmatrix}Λ=​λ1​0⋮0​0λ2​⋮0​……⋱…​00⋮λk​​​ che, come si può vedere, è una matrice che presenta sulla diagonale principale i coefficienti degli autovalori della matrice \( A \). Per finire, andando a indicare con \( V \) la matrice degli autovettori utilizzati precedentemente, otterremo l’equazione fondamentale per la diagonalizzazione di una matrice: VA=VΛ\boxed{ VA = V\Lambda }VA=VΛ​ In altri termini, la matrice degli autovalori \( \Lambda \) sarà la rappresentazione diagonale della matrice \( A \) in un sistema di riferimento individuato dagli autovettori di \( A \), o come si dice più propriamente nell’autospazio di \( A \). Ma a cosa serve tutto ciò? Una applicazione pratica: la compressione dell’informazione Sia data una certa matrice quadrata \( A \). La matrice degli autovettori esprime la varianza di \(A \), ovvero le informazioni che contiene. Il quantitativo di varianza espressa da ogni singolo autovettore è espresso dal valore dell’autovettore corrispondente. Vediamolo con un esempio con la seguente matrice \( A \): A=(2−133−2215−916)A = \begin{pmatrix} 2 & -1 & 3 \\ 3 & -2 & 2 \\ 15 & -9 & 16 \end{pmatrix}A=​2315​−1−2−9​3216​​ (si noti che la matrice A è di ordine 3 con il terzo componente volutamente molto vicino ma non coincidente alla combinazione lineare \( 3a_1 + 3a_2\)). Ora calcoliamo gli autovalori e gli autovettori corrispondenti: λ1=17.59λ2=−1.62λ3=0.035\begin{matrix} \lambda_1 = 17.59 \\ \lambda_2 = -1.62 \\ \lambda_3 = 0.035 \end{matrix}λ1​=17.59λ2​=−1.62λ3​=0.035​ V=(−0.180.26−0.64−0.12−0.68−0.19−0.97−0.680.74)V = \begin{pmatrix} -0.18 & 0.26 & -0.64 \\ -0.12 & -0.68 & -0.19 \\ -0.97 & -0.68 & 0.74 \end{pmatrix}V=​−0.18−0.12−0.97​0.26−0.68−0.68​−0.64−0.190.74​​ V−1=(−0.810.47−0.931.04−0.86−0.08−0.80−0.600.22)V^-1 = \begin{pmatrix} -0.81 & 0.47 & -0.93 \\ 1.04 & -0.86 & -0.08 \\ -0.80 & -0.60 & 0.22 \end{pmatrix}V−1=​−0.811.04−0.80​0.47−0.86−0.60​−0.93−0.080.22​​ Notiamo subito che \( \lambda_3 \) è molto più piccolo in modulo degli altri due autovalori. Questo significa che il suo contributo all’informazione contenuta in \( A \) è molto ridotto, rispetto agli altri due. Decidiamo allora di scartarlo e di scartare anche l’autovettore corrispondente. Λ=(17.500−1.6)\Lambda = \begin{pmatrix} 17.5 & 0 \\ 0 & -1.6 \end{pmatrix}Λ=(17.50​0−1.6​) V=(−0.180.264−0.12−0.68−0.97−0.68)V = \begin{pmatrix} -0.18 & 0.26 4 \\ -0.12 & -0.68 \\ -0.97 & -0.68 \end{pmatrix}V=​−0.18−0.12−0.97​0.264−0.68−0.68​​ V−1=(−0.810.47−0.931.04−0.86−0.08)V^-1 = \begin{pmatrix} -0.81 & 0.47 & -0.93 \\ 1.04 & -0.86 & -0.08 \end{pmatrix}V−1=(−0.811.04​0.47−0.86​−0.93−0.08​) Ora, dalla formula della diagonalizzazione abbiamo che A=VΛV−1A = V\Lambda V^{-1}A=VΛV−1 e facendo i calcoli abbiamo questa rappresentazione di \( A \) che per distinguerla dall’originaria decoro con un pedice \( R \): AR=(1.98−1.0132.97−2.01215−8.9915.99)A_R = \begin{pmatrix} 1.98 & -1.01 & 3 \\ 2.97 & -2.01 & 2 \\ 15 & -8.99 & 15.99 \end{pmatrix}AR​=​1.982.9715​−1.01−2.01−8.99​3215.99​​ che non è identica alla matrice \( A \), ma è senza dubbio estremamente simile ad essa. Ma ottenuta lavorando con matrici di dimensione 2 invece di 3, ovvero con meno calcoli. Abbiamo, nel concreto, ricostruito la matrice \( A \) utilizzando una sua espressione diagonale valutata su una base di autovettori ridotta rispetto a quella individuabile da \( A \) e nel farlo abbiamo perso un quantitativo trascurabile di informazione. Geometricamente, abbiamo espresso \( A_R \) in un sistema di riferimento avente un numero di dimensioni più basso rispetto all’ordine di \( A \). Nella pratica, abbiamo condotto una operazione di compressione dell’informazione. Autovettori e Social Network Analysis L’importanza degli autovettori per la scienza dell’informazione, come potete immaginare, già solo per la capacità di ridurre di dimensioni un sistema è estrema. Ma come possiamo applicare questo potente strumento allo studio delle reti sociali? Per rispondere a questa domanda basta ricordare che noi possiamo sempre esprimere una rete di relazioni attraverso la matrice delle adiacenze. E questa matrice è, tipicamente, non singolare quindi ammette un’inversa e, quindi, su di essa è possibile calcolare gli autovalori e gli autovettori. Lo vedremo presto su una particolare misura di centralità, chiamata Eigenvector Centrality, sulla quale si è molto parlato negli ultimi anni e ancora di più se ne parlerà in futuro. E’ la modalità, infatti, su cui Google ha costruito il suo famigerato algoritmo PageRank, per stimare l’autorevolezza e ordinare i link nei risultati del motore di ricerca. Esatto, Google. E parte tutto da qui.