automi cellulari come modelli di sistemi fisici bongiorno.pdf · fisica, orientati verso la...
TRANSCRIPT
UNIVERSITa DEGLI STUDI DI TRIESTE
DIPARTIMENTO DI FISICA
TESI DI LAUREA IN FISICA
AUTOMI CELLULARI COME
MODELLI DI SISTEMI FISICI
Samuele Bongiorno
Relatore
Prof. Giorgio Pastore
Anno Accademico 2014/2015
2
Contents
1. Introduzione 4
2. Automi Cellulari 5
3. Problemi fisici e loro modellizzazioni 7
4. Lattice Gas Automata (LGA) e Lattice Boltzmann Method (LBM) 9
4.1. LGA 9
4.2. LBM 10
5. Implementazione Algoritmi e Linguaggio di programmazione Python 14
6. Esempi di Implementazione e Risultati 16
6.1. LGA 16
6.2. LBM 21
6.3. Traslazione rigida 23
6.4. Tubo con ostacolo 24
6.5. Ostacolo esteso 28
7. Conclusioni 30
8. Appendice: Codici 32
References 45
4
1. Introduzione
In questo lavoro di Tesi, si e voluto andare ad analizzare nel dettaglio i modelli matem-
atici denominati Automi Cellulari. Si tratta di modelli fortemente ”coarse-grained”, cioe
tracciati su un finito numero di gradi di liberta microscopici, ed estesi automaticamente
fino a raggiungere grandi scale temporali e/o di taglia del sistema. Dopodiche, si e ap-
profondito come questo tipo di modelli possa essere utile per analizzare dei problemi di
Fisica, orientati verso la meccanica statistica. Per andare nello specifico, si sono studiati
due diversi approcci: il Lattice Gas Automata, e il suo erede Lattice Boltzmann Method,
ovvero due modelli adibiti alla simulazione del comportamento di gas e fluidi. Il vantag-
gio nell’usare questo tipo di modelli sta nel semplificare le equazioni caratteristiche di un
sistema appesantendone la computazione, che viene pero eseguita automaticamente da un
calcolatore. Con lo scopo di scrivere dei codici in grado di replicare questi modelli, si e
studiato il linguaggio di programmazione Python, comprese varie librerie e altri strumenti
per rendere graficamente i risultati degli algoritmi. Quindi, si sono scritti dei codici che
implementassero varie casistiche di LGA e di LBM, e ne si sono analizzati i risultati in
termini di ampiezza ed efficienza.
5
2. Automi Cellulari
Gli automi cellulari, a livello concettuale, non sono altro che dei modelli matematici
basati su dei reticoli che evolvono nel tempo, secondo regole deterministiche o anche sto-
castiche basate sullo stato del reticolo, e progredendo secondo intervalli discretizzati. In
particolare, si puo associare ad ogni istante di tempo un intero reticolo, corrispondente
allo stato di un determinato sistema in un preciso istante. Ogni reticolo e composto da
un numero fisso di caselle, il cui stato e caratterizzato da una o piu variabili in grado di
assumere un insieme di valori numerici.
Quindi, la filosofia che ci sta dietro consiste nel prendere una condizione iniziale, dipen-
dente dal problema che si vuole affrontare, impostare il reticolo di conseguenza, ed evolverlo
nel tempo andando a cambiare l’intero reticolo secondo delle selettive regole del gioco per
un certo numero di passi, fino a raggiungere la situazione desiderata.
Cio sottolinea alcuni punti fondamentali nell’utilizzo degli automi cellulari. In primis,
il fatto che ad ogni step il valore assunto da ciascuna casella dipende esclusivamente dai
valori della stessa casella, piu un fissato numero di vicine, assunti in una certa quantita
di step precedenti. Questo significa che, ad ogni iterazione del processo, bisogna avere a
disposizione il reticolo relativo all’istante “presente“, piu i necessari reticoli degli istanti
“passati“. Inoltre, apre la possibilita di distinguere i diversi automi cellulari in base a come
essi possano o meno raggiungere una condizione stazionaria dopo un determinato numero
di intervalli di tempo. Esistono 4 diverse classificazioni, evidenziate da Wolfram [1], di
automa cellulare in base a tale principio:
(1) dopo un numero finito di passi, l’automa converge a uno stato stabile o un ciclo
definito, dipendente dalle condizioni iniziali.
(2) il valore di equilibrio di una cellula dipende solo dai valori iniziali di un piccolo
intorno di celle. Sapere quindi la condizione iniziale di una piccola porzione di celle
e sufficiente a sapere come evolvera, conducendo a trame semplici e periodiche. Tali
tipologie sono utili come filtri per l’elaborazione delle immagini.
6
(3) l’evoluzione porta a configurazioni caotiche (ma non casuali), con proprieta statis-
tiche uguali per tutti i possibili stati iniziali.
(4) Poche regole di transizione rendono le evoluzioni totalmente imprevedibili anche
conoscendo la configurazione degli stati iniziali. Gli automi di questa classe sono
capaci di simulare una Macchina di Turing.
Gli automi cellulari sono quindi considerabili come dei sistemi dinamici discreti, e nelle loro
applicazioni spesso vengono scelti come mezzo alternativo a dei set di equazioni differenziali
parziali. Il motivo principe del loro utilizzo sta quindi proprio qui: utilizzando gli automi
cellulari, si cerca di trasferire la complessita di un sistema da un set di equazioni che
magari non ha nemmeno delle soluzioni analitiche esatte, nella complessita di un iterazione
molto pesante ma di soggetti elementari e regole semplici. Per questo si prestano bene
alla simulazione di sistemi e alla previsione di ipotetici comportamenti fatti mediante la
scrittura di codici macchina, sfruttando consistenti iterazioni di operazioni elementari e
ingenti quantita di memoria impiegata step by step per salvare i diversi reticoli.
7
3. Problemi fisici e loro modellizzazioni
L’origine degli automi di tipo meccanico e molto antica. Gia gli antichi greci costru-
irono degli automi meccanici, di tipo prettamente artistico per stupire le persone o per
dimostrare teorie e principi scientifici. Nel basso medioevo, e in particolare nel mondo
islamico, molti scienziati si sono dedicati al loro sviluppo in campo bellico e persino musi-
cale, con la costruzione di veri e propri robot musicanti. Qual e il legame tra gli automi in
senso stretto e gli automi cellulari, che ha dato il nome stesso a una simile idea di modelliz-
zazione? Il nesso sta proprio nel fatto che, come in automazione, e sufficiente preparare con
cura la struttura degli automi cellulari; dopodiche, una volta avviati, il loro funzionamento
e per l’appunto automatico.
Il formalismo degli automi cellulari si deve invece a John von Neumann e Stanislaw
Ulam, che negli anni ’40 ne introdussero le basi teoriche. In seguito, negli anni ’60, John
Horton Conway ne diede un applicazione pratica con il suo “game of life“[3]. Si tratta di
un automa cellulare in cui ogni cella rappresenta una vera e propria cellula, avente solo una
scelta binaria di valori: 1 o 0, corrispondente a cellula viva o morta. La condizione di ogni
cellula al tempo successivo sara percio dipendente (secondo alcune regole) dalle cellule che
la circondavano; l’evoluzione di ogni cellula sara quindi contemporanea a quella di tutte
le altre, ma cellule piu distanti non interagiranno, se non in step temporali piu distanti.
Con logiche analoghe, si sono sviluppati (e continuano ad essere) moltissime simulazioni
di carattere biologico/chimico: la riproduzione di colonie batteriche, l’evoluzione di una
specie in base alle risorse disponibili e al sovrappopolamento, l’autopoiesi o la creazione
delle prime molecole o barriere cellulari. Per chi volesse provarlo, esiste un software di
nome “Golly“ che gode di un discreto successo nel campo di questo tipo di analisi [4].
Come sfruttare un automa cellulare per studiare dei sistemi fisici? T. Toffoli, N. H.
Margolous e E. Fredkin furono tra i primi a notare quante analogie sono presenti tra la
teoria dell’informatica e le leggi della fisica, cimentandosi nello sviluppo dei “Block Cellular
Automaton“[5][6], come i semplici “palla da biliardo“. Si tratta di automi cellulari nei quali
i vari reticoli rappresentano il moto di un corpo su di una superficie, ricordando appunto
una sfera su un tavolo da biliardo, che aggiornano la posizione del corpo istante per istante
e ne tracciano la traiettoria secondo delle simulate leggi di cinematica. Per muoversi verso
8
cose piu interessanti, si puo associare un reticolo a una sorta di griglia spaziale cartesiana
bi-dimensionale con le sue coordinate, e si puo salvare in ogni casella una certa misura
come numero puro, presa in quella determinata coordinata, e creare tanti automi cellulari
quanti sono le grandezze che si vogliono esaminare, evolvendoli nel tempo parallelamente
oppure inglobandoli tutti in uno stesso automa cellulare generando una sorta di matrice
tridimensionale. In questo modo, tutte le misure saranno presenti in unita “reticolari“: lo
spazio e il tempo unitari saranno le caselle del reticolo e il time step, corrispondenti al mas-
simo di “risoluzione“ ottenibile. Con le opportune conversioni, si potra successivamente
scalare le grandezze a proprio piacimento.
Il ragionamento si puo estendere anche a casi con 3 o piu dimensioni. Una cosa che
ancora non si e discussa e la gestione delle celle al bordo. A seconda dei propri interessi, e
possibile impostare una periodicita delle caselle in modo da creare una superficie toroidale,
sferica, eccetera, oppure aggiungere delle regole specifiche per i bordi, come ad esempio il
bounceback di cui si discutera piu avanti.
Nella prossima sezione, si analizzeranno nel dettaglio due diversi modelli che si basano
sulla teoria degli automi cellulari per lo studio di problemi fisici: Lattice Gas Automata
(LGA) e Lattice Boltzmann Method (LBM). Se vogliamo, si possono considerare LGA e
LBM come, in senso lato, simili agli esperimenti mentali pensati da Galilei nel suo metodo
scientifico, la cui evoluzione e pero appunto automatizzata. Si tratta infatti di due modelli
mirati a simulare in maniera strutturale il comportamento di gas e fluidi, e a prevederne
lo sviluppo in particolare quando le soluzioni analitiche iniziano a non essere sufficienti.
Per questo, i loro risultati vengono spesso confrontati con quelli forniti dalle equazioni di
Navier-Stokes; un sistema di equazioni differenziali alle derivate parziali che descrivono il
comportamento di un fluido dal punto di vista macroscopico. Da notare che fu proprio a
partire da un LGA che vennero ricavate per la prima volta le corrette equazioni di Navier-
Stokes. Il primo modello a essere sviluppato fu LGA, ma venne rapidamente rimpiazzato
dal successore LBM. Tuttavia, per comprendere LBM e molto utile analizzare prima LGA,
sia come percorso logico sia come focalizzazione dei reciproci vantaggi e svantaggi.
9
4. Lattice Gas Automata (LGA) e Lattice Boltzmann Method (LBM)
4.1. LGA. -
Il lattice Gas automata, o LGA, e un modello cinetico con una logica abbastanza simile
alle “palle da biliardo“ citate nella sezione precedente. Sostanzialmente, e un automa
cellulare in cui il reticolo rappresenta delle coordinate spaziali n-dimensionali, la coordinata
temporale e espressa dal susseguirsi dei reticoli, e la presenza o meno di una particella in
una casella e espressa da un operatore booleano: la particella nel sito x e al tempo t c’e
(ni(x, t) = 1) o non c’e (ni(x, t) = 0). L’indice i rappresenta il vettore velocita assunto
dalla singola particella: supponendo di impostare a m (+1, se previsto il vettore nullo) il
numero di diversi vettori velocita disponibili, esso spaziera quindi da 1 (o 0) a m. La cosa
nuova e che, oltre a registrare la posizione di una particella, se ne vuole registrare anche
la velocita. In reticoli successivi quindi, il valore “1“ relativo alla posizione della particella
si spostera con delle regole in accordo alla propria velocita. Percio, assieme alla presenza
di essa, anche la velocita stessa dovra muoversi nel reticolo sulla base del proprio valore,
perche essa e idealmente collegata ala coordinata e non alla particella. Come gestire i vari
possibili versi e direzioni delle velocita? Il modo piu intuitivo e trattare le velocita non in
modo esplicito, ma anche esse come operatori booleani. Per fare cio, bisogna stabilire in
anticipo la quantita di diversi vettori velocita che le particelle potranno avere, alla quale
l’LGA sara completamente collegato. Con il formalismo DnQm si indicano le n dimensioni
del reticolo e i m vettori velocita. Nella figura 1 sono illustrati gli schemi di vari LGA
bi-dimensionali [7].
Figure 1. Schemi di reticoli bi-dimensionali. I pallini rappresentano i nodi
del reticolo, le frecce i caratteristici vettori velocita disponibili per le parti-
celle, le fi i relativi operatori booleani di presenza o meno della particella
con tale velocita. In ordine: D2Q4, D2Q5, D2Q7, D2Q9
10
Quindi, possiamo scrivere lo stato di una particella come:
ni(x + eiδt, t+ 1) = ni(x, t) + Ωi(n(x, t)), i = 0, ...m
con x vettore posizione, t il tempo, ei il vettore velocita, ni il singolo stato. L’operatore
di collisione Ω entra in gioco quando due o piu particelle in un determinato step vengono a
occupare una stessa casella e quindi a simulare un urto. All’interno di esso devono essere
previsti tutti i tipi di urti, sia come numero di particelle coinvolte sia come velocita in
entrata ed uscita; si tratta quindi del modello di scattering che dara le proprieta dinamiche
al sistema. Ogni step di evoluzione sara quindi divisibile in due sotto-step: lo streaming, nel
quale velocita e posizione si spostano nel reticolo secondo le velocita stesse, e la collision,
nella quale le particelle modificano i loro vettori velocita secondo lo scattering. A livello
di codice, questi due sotto-step vengono fatti in sequenza all’interno di ogni iterazione, ed
e fondamentale tenere conto del loro ordine a seconda di come viene gestito il salvataggio
dei reticoli. Allo stesso modo, bisogna decidere in quale punto di ogni iterazione far agire
le condizioni a contorno, che regolano il comportamento delle particelle al loro urto con le
pareti.
4.2. LBM. -
Per sua natura, il modello del LGA e un modello con ampio rumore statistico. Ovvero,
ad ogni utilizzo anche di uno stesso identico algoritmo, si verifica una poco prevedibile
variazione dello sviluppo dovuta alla casualita del campione di dati di partenza. Fu princi-
palmente questo il motivo per il quale si e pensato di rimpiazzare i numeri di occupazione
booleani con delle funzioni di distribuzione di singola particella. Percio, l’idea fu quella
di creare degli automi cellulari in cui le celle non potessero essere solo zeri o uni, ma un
qualsiasi numero reale compreso tra 0 e 1. Ci sara quindi, per ogni cella, uno di questi nu-
meri per ciascuno dei vettori velocita scelti, come accadeva nel LGA, per il singolo modello
in esame; essa non sara pero la effettiva “velocita macroscopica“, ma idealmente come se
fosse la porzione di particelle contenute in quella casella (x+dx) e aventi quella velocita
(e+de). Quindi, le quantita macroscopiche andranno calcolate mediando tutti gli stati
della singola cella. In termini di algoritmo, questo comporta in primis uno svantaggio di
efficienza perche, ad ogni step, ogni casella dovra portarsi dietro tutti i vettori velocita e
non ci saranno celle “inutili“ come accadeva in LGA; questo pero viene ampliamente mit-
igato dal fatto che, come vedremo, gli urti non sono trattati esplicitamente come in LGA.
11
La funzione di distribuzione dipendera quindi dalla posizione x, dal tempo t e dal vettore
velocita e:
f(x, e, t).
Dopo un tempo dt, i nuovi valori saranno:
x→ x + edt
e→ e +F
mdt.
Quindi, dopo uno sub-step di streaming, le funzioni diventeranno:
f(x + edt, e +F
mdt, t+ dt)dxde,
che corrisponde allo spostare di casella nel reticolo le varie funzioni in base al loro vettore
velocita. Gli urti non verranno trattati in maniera esplicita; il fatto che, in caso di collisione,
non tutte le particelle di una cella arrivino alla cella sperata, viene espresso dall’operatore
di collisione Ω:
f(x + edt, e +F
mdt, t+ dt)dxde− f(x, e, t)dxde = Ω(f)dxdedt
che, al limite, si esprime quindi come differenziale:
Df
dt= Ω(f).
Espandendo Df e dividendo per dt, diventa:
Df
dt=∂f
∂x
dx
dt+∂f
∂e
de
dt+∂f
∂t,
ovvero:
Ω(f) =∂f
∂t+∂f
∂xe +
∂f
∂e
F
mche permette di derivare l’equazione di Boltzmann per un caso privo di forze esterne:
∂f
∂t+ e · ∇f = Ω(f).
Il BGK, ovvero Bhatnagar, Gross, Krook, e un ottimo mezzo per implementare in un algo-
ritmo di questo genere l’operatore di collisione [8]. In effetti, si tratta di una linearizzazione
dell’operatore di collisione attorno alla sua locale soluzione di equilibrio. Percio,
Ωk = −1
τ(fk − fEQk ).
Ad ogni iterazione dell’automa cellulare, gli urti mascherati che avvengono nella porzione
di fluido compresa nella casella si riversano nell’avvicinarsi della funzione di distribuzione
12
al suo valore di equilibrio. Tale avvicinamento avviene con un ritmo dipendente da τ , una
sorta di tempo caratteristico di rilassamento collegato alla sua viscosita.
La funzione di distribuzione di equilibrio e sostanzialmente figlia della funzione di dis-
tribuzione di Maxwell normalizzata:
f =ρ2π3
e−32(e·e)e
32(2e·u−u·u)
con u la velocita macroscopica, e i vettori velocita del modello, e ρ la densita in massa.
Con un espansione per piccole velocita,
f =ρ2π3
e−32(e·e)[1 + 3(e · u)− 3
2(u · u) +
9
2(e · u)2],
passando cosı alle funzioni di distribuzione di equilibrio nelle varie direzioni:
fEQk = ρwk[1 + 3(e · u)− 3
2(u · u) +
9
2(e · u)2].
Gli wk non sono altro che dei pesi che si inseriscono per compensare il fatto che le
varie velocita, e quindi gli ipotetici nodi destinazione, non sono tutti equidistanti dal nodo
centrale di partenza. In seguito sono riportati i diversi wk da utilizzare in base al reticolo
studiato. Da notare che, per ciascun caso:∑wk = 1.
Ponendoci nel caso piu semplice, ogni nodo si rilassa con lo stesso tempo τ (SRT, single
time relaxation). Percio e stato considerato come costante per ogni k. Il suo rapporto con
la viscosita del fluido e:
ν = (τ − ∆t
2)c2s,
con cs velocita del suono nel reticolo. Le distanze e i tempi reticolari sono stati considerati
come unitari, e quindi:
cs =1√3
∆x
∆t.
La forma cinetica per direzione, implementabile nel codice, e analoga al caso di LGA:
fk(x + ek∆t, t+ ∆t) = fk(x, t) + Ω(f(x, t)),
13
con al solito k che va da 0 a m. Percio, il cuore dell’algoritmo ovvero la relazione che andra
modificare le celle, sara:
∂fk∂t
+ ek∇fk = −1
τ(fk − feqk )
in quanto, discretizzata, diventa:
fk(xk + ek∆t, t+ ∆t) = fk(xk, t)−∆t
τ(fk − feqk ).
Anche in LBM, lo step verra diviso in due sotto-step, di collisione e di streaming. A
livello di algoritmo, cio significa che si andra a calcolare separatamente:
collisione : fk(xk, t+ ∆t) = fk(xk, t)−∆t
τ(fk − feqk ),
streaming : fk(xk + ek∆t, t+ ∆t) = fk(xk, t+ ∆t).
Il reticolo si portera avanti ad ogni iterazione le varie funzioni di distribuzione delle
particelle. Da queste, si ricava cio che veramente interessa a livello di simulazione di
fluido: le sue grandezze macroscopiche. Nello specifico, la densita del fluido e il momento
si ottengono semplicemente integrando le distribuzioni:
ρ(x, t) = m
∫f(x, e, t)de,
ρ(x, t)u(x, t) = m
∫f(x, e, t)ede,
che, nello spazio discretizzato del reticolo, permettono di ricavare casella per casella la
densita e la velocita macroscopica:
ρ =∑
fk,
ρu =∑
ekfk,
considerando ancora una volta le masse delle particelle unitarie, come tutte le dimensioni
reticolari. Sfruttando la tecnica degli automi cellulari, le relazioni ottenute permettono di
generare un LBM iterandole in maniera opportuna.
14
5. Implementazione Algoritmi e Linguaggio di programmazione Python
In questo lavoro di tesi, si sono scritti dei codici secondo il modello degli Automi Cellu-
lari, in grado di riprodurre dei casi di LGA e LBM. Come linguaggio di programmazione
e stato scelto il Python [10]: questo perche con la sua dominante predisposizione alla pro-
grammazione ad oggetti, rende possibile creare in modo molto agile delle classi contenenti i
reticoli, che verranno implementati come matrici tridimensionali (due spaziali, e una terza
relativa alla singola grandezza in evoluzione), le regole per l’evoluzione delle caselle e le
eventuali stampe su file o video. Inoltre, la sua gestione dinamica delle liste e i metodi in
stile append rendono comodo il modificare anche in fase di esecuzione le dimensioni delle
matrici.
Il motivo principe dell’utilizzo di Python puo pero essere trovato nel suo elevatissimo
potenziale come elaborazione grafica, sicuramente superiore e piu maneggevole rispetto
ad altri linguaggi. Infatti, in letteratura sono disponibili moltissimi software open source
piu o meno amatoriali interamente scritti in Python e facili da inserire come pacchetti nei
propri lavori, e/o adattabili e migliorabili. In questo lavoro di tesi, ad esempio, si e utiliz-
zato il software open source “streamplot.py“ per il tracciamento delle streamline dell’LBM
[11][12]. Oltre a Python, si e utilizzato il software GNUPLOT per tracciare i grafici piu
immediati a partire da file di dati.
Esiste poi una libreria Python, chiamata numpy, completa di oggetti e di moltissime
funzioni matematiche e non, per la gestione di matrici e di operazioni tra di esse. Quindi,
tale libreria potrebbe essere utile per la scrittura di automi cellulari. Tuttavia, in questo
lavoro di tesi non e stata utilizzata numpy, ma matrici e operazioni sono state costruite da
zero utilizzando liste e cicli.
Utilizzi successivi della stessa classe serviranno a salvare il reticolo del momento presente
e del momento futuro. Le modifiche alle varie caselle andranno fatte secondo le regole del
gioco sfruttando metodi di controllo e condizioni logiche (if). Iterazioni ripetute dei moduli
per l’evoluzione fatte mediante cicli (for) andranno quindi a simulare la dimensione tem-
porale, portando avanti uno o al massimo due reticoli per ogni grandezza e sovrascrivendo
15
tutti i dati piu vecchi al fine di ottimizzare la memoria.
Come vedremo, l’esecuzione dei modelli di Automi Cellulari e molto impegnativa per
la macchina sia in termini di memoria che di numero di operazioni elementari. I calcoli
saranno percio spesso molto lunghi, soprattutto in funzione di quella che e la taglia del
sistema; e bene quindi ottimizzare il codice il piu possibile ed evitare calcoli e dati inutili.
Ovviamente, il tutto va calibrato in base alla potenza del calcolatore che si va ad utilizzare.
Altro motivo per cui puo essere utile lavorare in Python e la sua famosa libreria Time; in
modo molto agile, e possibile utilizzare tale libreria per ottenere dati utili allo studio del
tempo macchina impiegato nelle simulazioni.
Per chi non conoscesse il Python e venisse invece ad esempio dal linguaggio C/C++, e
volesse provare a cimentarsi nel codice, una cosa sicuramente da notare e la diversa gestione
degli spazi e della punteggiatura. In Python infatti l’“inscatolamento“ delle varie operazioni
e direttamente dipendente dal numero di spazi precedenti alla riga scritta. Operazioni alla
“stessa distanza dal bordo“ saranno quindi allo stesso livello, e righe che comincino con
un numero di spazi non corrispondente ne allo stesso di righe precedenti, ne allo stesso
di “inscatolamenti“ precedenti, causeranno errori in fase di compilazione. Se invece il nu-
mero di spazi sara compatibile col codice ma sbagliato logicamente, il codice funzionera
ma generera risultati completamente sbagliati. Questa struttura rende sicuramente ben
leggibile l’ordine delle operazioni e molto meno pesante l’utilizzo di punteggiatura e par-
entesi rispetto a come si fa in C/C++ (dove gli spazi sono peraltro “invisibili“), ma apre
la possibilita di tutta una serie di errori piu o meno gravi legata all’utilizzo degli spazi.
Per chi volesse approfondire, cito un progetto di nome sailfish [15], che raccoglie una vasta
collezione liberamente scaricabile e installabile di pacchetti e codici Python in grado di
simulare problemi di fluidodinamica, trattati con il metodo di LBM. I risultati vengono
espressi con vere e proprie animazioni.
16
6. Esempi di Implementazione e Risultati
6.1. LGA. -
In questo lavoro di tesi, sono stati scritti due codici riguardanti LGA: un D2Q4 e un D2Q9.
Le scelte di struttura sono state le seguenti:
L’automa e stato pensato come una matrice tri-dimensionale. Il vantaggio rispetto al
portare avanti in parallelo molti automi cellulari e prettamente di compattazione e chiarezza
del codice. In questo modo infatti si sfruttano meglio stessi cicli con stessi indici per fare
cose diverse senza andare a creare molte matrici con nomi diversi; in termini di allocazioni
di memoria, pero, creare matrici di nomi diversi non ne risentirebbe. Per risparmiare
memoria, anziche riservare un reticolo per ciascuno dei vettori velocita e dare a ciascuno
la sola scelta binaria 0 o 1 (la particella non ha o ha tale velocita), si e scelto di salvare un
solo reticolo per la componente orizzontale della velocita e uno per quella verticale. Nel
caso D2Q4, una sola tra le due celle dei due reticoli velocita (per ciascuna coordinata)
potra assumere il valore +1 o -1, mentre l’altra assumera uno 0. Le quattro combinazioni
possibili genereranno i 4 vettori velocita propri del reticolo. Nel caso D2Q9, ciascuna delle
due caselle potra assumere uno qualsiasi tra i valori +1, -1, e 0; le 9 combinazioni possibili
genereranno i 9 vettori velocita propri del reticolo. Questo ha forse appesantito il codice,
ma ha reso un notevole guadagno di efficienza.
Si e pensato di salvare tanti layer di velocita quante sono le particelle che possono essere
coinvolte in un urto. Questo e purtroppo un dispendio di memoria necessario al fine di non
perdere le varie singole velocita nel caso di urti. Per limitare questo problema, si e pensato
che ad ogni ciclo tutti i layer verranno azzerati, e che i vari controlli e conti sui vari piani
della matrice andranno fatti se e solo se in quei piani e “presente qualcosa“. Cio non toglie
il gran dispendio di memoria occupata e priva di significato step by step, ma ne minimizza
il tempo perso in termini di operazioni elementari su di essa.
il layer relativo alla presenza delle particelle e stato pensato invece come unico: anziche
essere un operatore booleano, e una sorta di contatore che aumenta di uno ogni volta che
in quella casella giunge una particella. Il numero di operazioni (urti) da fare in quella
casella sara quindi dipendente da tale numero. Quando una particella si deve spostare in
17
una casella che nello step successivo sara gia occupata, andra a salvare la sua velocita nel
primo layer vuoto disponibile, e nello step successivo ci sara un ulteriore sottociclo per la
fase di collisione e streaming delle particelle sovrapposte. In sintesi, il numero di dati da
salvare step by step usando questa struttura sara quindi:
DIM ·DIM · (2 ·Nvettori+ 1)
Le velocita sono state tutte impostate con valore unitario, in modo tale da sfruttare
al massimo la risoluzione offerta dalla discretizzazione del reticolo. Inoltre, gli urti sono
stati considerati elastici, dato che per i risultati ricercati non avrebbe avuto un diverso
esito impostare delle regole di skattering differenti. Comunque, e molto semplice andare a
cambiarle anche nel codice proposto, inserendo per esempio all’interno del sottociclo che
gestisce le particelle sovrapposte delle modifiche alle velocita in uscita sulla base delle ve-
locita entrate, usando delle condizioni di controllo (if).
Un problema da non sottovalutare sta nell’urto tra particelle con velocita opposte e che
si trovino a una casella di distanza. Non esistendo la “mezza casella“, esse non potranno
gestire l’urto come gia visto. Nel caso in esame, regolato da urti elastici, il problema
non si e posto: si e notato che il tempo (∆t)/2 fino allo scontro piu il tempo (∆t)/2 per
ritornare alla posizione iniziale coincidono con il tempo ∆t con il quale le particelle si
“attraversano“, portandosi comunque ad una condizione analoga all’urto a meta casella
per la conservazione della quantita di moto. Una soluzione piu generica potrebbe essere
impostare, ad ogni iterazione, il controllo sull’occupazione o meno della casella obiettivo
non solo rispetto al layer futuro, ma anche rispetto a quello presente.
Le condizioni iniziali sono state pensate come un numero variabile di taglia del sistema
(dim x dim x N particelle), modificabili immediatamente prima di ogni singolo utilizzo
dell’algoritmo tramite variabili. La posizione iniziale delle varie particelle e stata resa
pseudo-casuale sfruttando le librerie random, cosı come le loro velocita iniziali. Proprio in
questo punto si inserisce il vincolo del numero di vettori velocita: impostando le regole per
la randomizzazione delle velocita, si e passati dal D2Q4 al D2Q9 (dato che entrambi sono
dei reticoli quadrati). Oltre a questo, si e come detto prima cambiata anche la dimensione
18
dell’intera matrice.
Ad ogni chiamata di iterazione, si prende il reticolo corrente, si crea un nuovo reticolo
vuoto, e si scrive sul reticolo nuovo l’evoluzione del sistema, senza toccare il reticolo pre-
sente dato che e fondamentale conservarlo integro fino alla fine dello step temporale per
mantenere la simultaneita dell’evoluzione dell’intero reticolo. Solo alla fine di tutti i calcoli
della singola iterazione, si andra a sovrascrivere il layer presente con quello futuro, avan-
zando il tempo di uno scatto e ripetendo il tutto fino al numero desiderato di step.
Si e voluto scegliere come condizioni a contorno il modello di bounceback. Idealmente,
si tratta di inserire il sistema in una scatola con pareti rigide, sulle quali le particelle rim-
balzano con urto elastico. In letteratura esistono due modelli di bounceback: come se la
parete si trovasse a meta tra l’ultimo e il penultimo nodo, e come se si trovasse sull’ultimo.
Nel primo caso, i nodi esterni resteranno sempre “vuoti“ e serviranno solo a gestire il cam-
bio delle velocita ai bordi. In questo lavoro di tesi si e utilizzato il primo caso per l’LBM,
poiche e dimostrato che conduce ad una accuratezza numerica al secondo ordine anziche al
primo [13]. Per LGA, invece, non c’e nessun vantaggio ad utilizzare l’uno o l’altro schema;
percio, si e scelto di piazzare le pareti sull’ultimo nodo, al fine di risparmiare memoria.
I risultati del codice sono stati espressi in piu modi. Nel primo, si sono fatte delle
stampe a video (e/o su file) del reticolo step by step, attribuendo dei simboli ai vari val-
ori di presenza o meno di particelle. Questo serviva a rendere proprio graficamente cosa
l’LGA volesse simulare, e a controllare la correttezza dell’algoritmo andando di volta in
volta a correggere il codice in punti diversi in base a quali comportamenti sbagliati si no-
tavano. Tra l’altro, guardando durante l’esecuzione del programma le stampe a video che
si susseguono ad alta frequenza attendendo i risultati veri e propri, si osservera una vera
e propria animazione (grezza) delle particelle in movimento. Ovviamente, tali stampe del
reticolo hanno senso per taglie del sistema non troppo grandi, ovvero contenute all’interno
della finestra del Terminale senza sformarsi. Le stampe sono state fatte direttamente da
codice Python sfruttando delle dictionary; in figura 2 ne sono riportati alcuni esempi.
Nel secondo, si sono calcolate alcune quantita macroscopiche. A differenza del suo erede,
LBM, in LGA non sono molte le quantita che si possono ricavare. In questo lavoro, si sono
19
Figure 2. Visuale di un reticolo di LGA a vari istanti successivi. Il simbolo
“-” rappresenta una cella vuota, la “x” una cella occupata da una particella,
l’ “*” piu particelle coinvolte in un urto.
andati a evidenziare il numero di particelle step by step, che puo nella sua costanza andare
ad esprimere la conservazione della massa del fluido e l’efficienza dell’algoritmo che non
andava a perdere informazioni; il numero di urti, e il numero di particelle che step by step
andavano a fare un bounceback.
Come analisi, si e andati ad osservare come numero di urti, bounceback e tempo di
lavoro macchina dipendessero in modo molto evidente dalla taglia del sistema. Per fare
questo lavoro, e stato utile salvare i singoli algoritmi degli automi cellulari come pacchetti
Python a tutti gli effetti, e creare un altro programma “main“ che semplicemente impor-
tasse e iterasse il pacchetto con reticoli, evoluzione e stampe passandogli di volta in volta
valori differenti, e stampando le grandezze macroscopiche in file gestito come append. Nei
grafici sottostanti sono sintetizzate le analisi relative alla taglia del sistema del LGA. Questi
grafici sono stati tracciati utilizzando il software GNUPLOT, sulla base di un organizzata
divisione dei risultati ottenuti dalle opportune chiamate degli algoritmi (raccolti in file
di testo), e con la stesura di specifici script dediti a fornire come input a GNUPLOT le
20
(a) (b)
Figure 3. Numero di BounceBack (a) e Tempo Macchina (b) con volume
fissato. Il numero di Bounceback e proporzionale alla pressione.
(a) (b)
Figure 4. Numero di BounceBack (a) e Tempo Macchina (b) con massa
fissata. Il numero di Bounceback e proporzionale alla pressione.
desiderate chiamate delle funzioni.
Nella figura 3.a, si puo vedere come il numero di Bounceback cresce in maniera lin-
eare con il numero di particelle per entrambi i modelli. Mentre la curva e estremamente
pulita per D2Q4, in D2Q9 si vede una leggera fluttuazione statistica, dovuta probabilmente
all’aggiunta della velocita (0,0) che va a rompere la totale simmetria del modello. A livello
21
Figure 5. Radice quadrata del Tempo Macchina con massa fissata
intuitivo, si puo considerare come una evidenza della proporzionalita diretta tra pressione
di un gas e il numero di particelle, a fissi volume e temperatura. In 3.b, si nota che la
dipendenza tra il tempo macchina di esecuzione e il numero di particelle a parita di volume
e anche essa lineare, sebbene con ampie fluttuazioni. E invece quadratica la dipendenza
del Tempo macchina dalla Lunghezza del lato del reticolo; si puo notare dal grafico 4.b, o
piu chiaramente dal 5 che e sostanzialmente lo stesso con i valori in ascissa sotto radice.
Bisogna sottolineare che il valore assoluto del tempo macchina e rilevante solo in parte, dato
che dipende completamente dal calcolatore utilizzato; ad essere significativo e l’andamento
delle curve, essendo collegato all’efficienza del solo algoritmo. Il grafico 4.a fa invece notare
una proporzionalita inversa tra numero di Bounceback e dimensione del reticolo; evidenza
intuitiva, anche questa, della proporzionalita inversa tra volume e pressione di un gas a
parita di numero di particelle e temperatura.
6.2. LBM. -
Oltre ai precedenti, e stato scritto un algoritmo relativo a un LBM di tipo D2Q9. Come test,
si e scelto di trattare casi di traslazioni macroscopiche rigide a velocita costante dell’intera
“scatola“ di fluido. Le scelte di implementazione sono state le seguenti:
Come in LGA, la struttura e stata di un unico automa cellulare composto da una matrice
tridimensionale. Oltre alle due solite dimensioni spaziali, la divisione dei reticoli e stata:
22
• Un layer per ciascuno dei vettori velocita, salvando per ogni casella tutte le funzioni
di distribuzione;
• Due layer per il calcolo della velocita macroscopica, divisa in componente vx e vy,
fatta in ogni singola cella;
• Un layer per il calcolo della densita del fluido per ogni singola cella, da aggiornare
ad ogni iterazione.
Percio, e si vero che le operazioni fatte su ogni singola casella sono molte di piu che in
LGA, e che vanno ripetute indifferentemente per tutte le caselle della matrice senza la pos-
sibilita di saltare caselle inutili; pero, in termini di memoria occupata, la quantita di layer
della matrice dipende in modo meno marcato dal numero di vettori velocita, riducendo la
dimensione della classe automa cellulare. Per di piu, Il fatto che ogni singola casella non
rappresenti una singola particella ma una sorta di media, rende l’algoritmo molto meglio
scalabile per taglie piu grandi del sistema. Quindi, al netto, oltre a essere piu significativo
del LGA in termini di quantita macroscopiche ottenibili e assenza di rumore statistico,
l’LBM e anche piu efficiente per quanto riguarda i codici macchina.
Importantissima e la sequenza con la quale vengono posti bounceback, “urti“ e streaming
in ciascuna operazione. A seconda di come si struttura la matrice, ovvero se si mette su
uno stesso oggetto le quantita macroscopiche dello step precedente o di quello corrente, la
scelta e univoca. In questo lavoro di tesi, si e optato per la seconda, percio lo schema scelto
e il seguente:
• Collisione;
• Streaming;
• Boundary condition;
• Calcolo delle quantita macroscopiche;
• Ciclo.
Nell’impostare le condizioni iniziali, e importante fare molta attenzione a riempire bene
tutte le caselle necessarie per evitare errori nel miglior caso di compilazione (come ad es-
empio, dividere per una densita nulla), e nel peggiore logici (come ad esempio far arrivare
uno streaming da zone oltre il bordo). Nel caso in esame quindi, si e scelto di inizializzare
tutte le densita a 1 eccetto sui bordi, avendo usato il bounceback con parete tra due nodi.
23
Inoltre, i vari cicli sono stati fatti a meno dei bordi stessi; in questo modo si e esclusa la
possibilita di far uscire fluido dalle pareti, utilizzando cosı i bordi della matrice solo come
step temporaneo delle funzioni di distribuzione prima del bounceback.
Tutti i prodotti vettoriali sono stati fatti per componenti, sfruttando oggetti linearmente
indipendenti. Percio, la conversione delle velocita in componenti viene fatta subito sui due
layer predisposti del reticolo del tempo presente, cosı da dimenticarsi dei diversi pesi dei
vettori velocita all’interno dei prodotti scalari. Il diverso peso dei vettori velocita in fun-
zione della loro distanza dal centro del nodo, invece, viene inserito nel calcolo degli equilibri.
La presenza dei pesi wk e la non simmetria delle condizioni a contorno nel caso del moto
in esame, rende impossibile gestire i calcoli sulle singole funzioni di distribuzione mediante
un ciclo come veniva fatto in LGA. Qui il ciclo girera solo sulle coordinate spaziali, mentre
le istruzioni di calcolo dei diversi layer andranno scritte una per una.
6.3. Traslazione rigida. -
Come primi risultati, si riportano quelli relativi alla simulazione di una traslazione rigida
orizzontale del fluido. Per fare cio, si e impostata come fissa la velocita macroscopica del
fluido in tutte le celle: la componente x a -0.1, a 0 la componente y. In questa fase, si
e analizzato un fluido con dimensione spaziale 100x100 celle e con τ 10, e si e osservato
come le funzioni di distribuzione evolvessero per iterazioni successive del codice, ovvero
a diversi istanti di tempo. Le altre componenti sono state inizializzate come discusso nei
punti precedenti.
Nelle figure 6, sono riportate le streamline del fluido a istanti diversi, tracciate con
l’ausilio del pacchetto “streamplot.py“. Non si tratta altro che di grafici bi-dimensionali
rappresentanti il reticolo nel suo complesso, con in ascissa e ordinata le coordinate spaziali
della scatola (in celle). Le frecce rappresentano direzione e verso macroscopici delle traiet-
torie percorse dalle particelle del fluido, e i colori indicano l’intensita delle velocita. Come
si puo notare, lungo tutta la superficie le funzioni di distribuzione tendono a bilanciarsi
verso la loro funzione all’equilibrio, dettata dalla velocita macroscopica forzata; quindi,
24
(a) (b)
Figure 6. Streamline di traslazione orizzontale della scatola con v=(-
0.1,0), dopo 10 step (a) e dopo 100 step (b). Si noti l’andamento orizzontale
di tutte le traiettorie, il verso coerente con la velocita v, e le curvature am-
pliate dopo un numero maggiore di step.
assumono una direzione coerente con quella della scatola.
Lungo l’asse verticale, si nota come progressivamente queste traiettorie si spostino verso
la riga centrale; questo si verifica perche tra le particelle che assumono una componente
di velocita verso sinistra, quelle con anche componente verso il basso a un certo punto
rimbalzeranno verso l’alto sulla parete sud, e viceversa sulla parete nord.
Nelle figure 7, sono riportate le streamline di una situazione analoga, ma riguardante
una traslazione rigida obliqua; si puo notare quindi, a differenza del caso precedente, una
simmetria rispetto all’asse passante per il vettore velocita (1,1).
6.4. Tubo con ostacolo. -
Come caso ulteriore, si e modificato il programma del LBM del caso “scatola“ fino a pro-
durre una simulazione di diverso genere. In particolare, sfruttando degli spostamenti di
Bounceback si e passati da un sistema di fluido in pareti chiuse a una sorta di flusso di
25
(a) (b)
Figure 7. Streamline di traslazione obliqua della scatola con v=(0.1,0.1).
Si noti l’andamento obliquo di tutte le traiettorie, il verso coerente con la
velocita v, e le curvature ampliate dopo un numero maggiore di step.
fluido all’interno di un tubo, introducendo inoltre un ostacolo.
Per fare cio, come prima cosa si e creato un foro nella parete ovest del reticolo, andando
a rimuovere i bounceback per una porzione di parete dal basso verso l’alto. Dopodiche, si
e impostata una velocita macroscopica del fluido orizzontale in corrispondenza di questo
foro, creando una sorta di sorgente. Nella parete est, si e creato allo stesso modo un foro,
senza pero impostare alcuna velocita; quel foro rappresenta una foce. Entrambi i fori sono
stati fatti “dal basso verso l’alto“, e la loro lunghezza e modificabile all’interno del codice
tramite una variabile. La situazione finale ricorda quella di un tubo che percorre una curva
stretta ”a U”.
Con altre due variabili, si possono settare la lunghezza verso il basso e la posizione oriz-
zontale di una barriera. Infatti, in modo opposto a quanto fatto per i fori, si e inserita una
barriera verticale, di spessore un nodo e di lunghezza e posizione a piacere, che partendo
dalla parete nord si allunga verso il basso. Per crearla, a livello di codice, si e impostato
delle nuove condizioni di bounceback nella stessa sezione di quelle per le pareti, ma con
posizione diversa. Quindi, con dei cicli in funzione di lunghezza e posizione del muro, si
sono inseriti i bounceback “orizzontali“ anche sulla barriera; fare attenzione, oltre a quelli
orizzontali, a impostare anche i bounceback verticali per le singole caselle poste sotto la
barriera, che ha spessore uno. Dato che questa barriera va a simulare un ostacolo “solido“,
e fondamentale oltre ai bounceback andare a modificare lo streaming: infatti, non ci puo
26
Figure 8. Streamline per fluido in entrata e uscita da un tubo con ostacolo
verticale. Caso di fluido con tempo di rilassamento τ = 2s.
essere streaming dalla barriera come non ce ne puo essere dalle pareti. Per fare questo, si
sono escluse dai cicli per lo streaming tutte le operazioni relative agli indici contenuti nella
barriera, mediante dei controlli di tipo if.
A questo punto, avviando l’esecuzione, si andra a simulare una quantita di fluido che
entra in questa sorta di tubo, e aggirando la barriera si dirige verso la foce. Tutte le ve-
locita macroscopiche, a eccezione di quella di sorgente, partono da zero e si evolvono fino
alla situazione stazionaria solo sulla base delle equazioni di Boltzmann, quindi serviranno
un gran numero di cicli completare l’iterazione. L’uscita dal tubo sara rappresentata sfrut-
tando ancora una volta il modello di Bounceback utilizzato: l’ultimo nodo a destra, che in
precedenza restava sempre vuoto al solo fine di simulare il rimbalzo a meta tra due nodi,
verra invece riempito con il fluido in uscita, che non rimbalza, ma nemmeno genera prob-
lemi di gestione dalle matrici in quanto gia escluso dai cicli di streaming nei casi precedenti.
Nelle figure dalla 8 alla 11, sono riportate le streamline, ancora una volta rappresentate
con “streamplot.py“, relative a diverse situazioni. La barriera stessa non si nota in modo
esplicito nei grafici; si vedono solamente gli andamenti del fluido attorno ad essa. Le
diverse immagini sono relative a fluidi con diversi valori di tempo di rilassamento τ , che
corrispondono a fluidi dotati di diversa viscosita: da notare che, spesso, nel formalismo
degli automi cellulari si parla in termini non di τ , ma di Numero di Reynolds. Questo
perche gli algoritmi degli automi cellulari sono accurati solo per determinati intervalli di
27
Figure 9. Streamline per fluido in entrata e uscita da un tubo con ostacolo
verticale. Caso di fluido con tempo di rilassamento τ = 10s.
Figure 10. Streamline per fluido in entrata e uscita da un tubo con osta-
colo verticale. Caso di fluido con tempo di rilassamento τ = 20s.
Figure 11. Streamline per fluido in entrata e uscita da un tubo con osta-
colo verticale. Caso di fluido con tempo di rilassamento τ = 100s.
28
numeri di Reynolds. Esso, per quanto riguarda LBM, si puo esprimere come:
Re =ULBMN
νLBM
Con N lato del reticolo, ULBM velocita caratteristica del reticolo e νLBM viscosita del flu-
ido. Come si puo vedere, infatti, andando a cambiare τ , e quindi ν, per mantenere efficace
l’algoritmo e stato necessario variare le dimensioni dei reticoli, con conseguente variazione
dei tempi macchina di esecuzione.
Osservando le immagini, la traiettoria dei vari fluidi risulta avere lo stesso andamento
dalla sorgente alla foce, ma con curve piu strette via via che aumentano il tempo di rilassa-
mento e la viscosita. In tutte le immagini, si nota un principio di mulinello sul lato sinistro,
piu evidente per fluidi meno viscosi. Nei grafici non si vedono bene le scale di colore in
quanto le velocita sono molto basse, essendo tutte trainate dalla sola porzione di fluido
continuamente alimentata, cioe quella sulla sorgente a sinistra; si riesce pero a scorgere
un picco di velocita in prossimita della foce. Inoltre, si vede che globalmente le velocita
diventano piu basse per viscosita piu elevate. Per tutti i fluidi, poi, si puo osservare un
principio di moto turbolento nella zona sottostante alla barriera, dovuta agli urti contro
la parete inferiore del muro, dove tra l’altro si vede un ulteriore piccolo picco di velocita
(effetto ”rapide”).
6.5. Ostacolo esteso. -
Come ultimo caso, si e modificato il codice relativo al tubo con ostacolo fino a riprodurre
una situazione di ostacolo esteso su due dimensioni. Per realizzarlo, si e inserito sostanzial-
mente un nuovo ostacolo di tipo ”muro” come il precedente, ma in direzione orizzontale
anziche verticale. Tale ostacolo si estende dalla punta sud del muro che cade dalla parete
nord, fino alla parete ovest. La sorgente e stata inserita dall’estremo sud dell’ostacolo fino
alla parete sud del reticolo, mentre la foce e stata estesa a tutta la parete est. In questo
modo, si e simulato un ostacolo rettangolare inserito in un tubo rettilineo piu che, come
nel caso precedente, un tubo con una curva.
Per l’implementazione su codice si e seguito gli stessi ragionamenti del caso precedente
(Bounceback spostati, nessuno streaming dall’interno dell’ostacolo, eccetera), chiaramente
29
(a) (b)
Figure 12. Streamline per fluido in flusso orizzontale che incontra un os-
tacolo rettangolare. Lavoro di Tesi (a) e letteratura (b)
ripetuti e adattati nelle due dimensioni dell’ostacolo. Un esempio di risultato finale delle
simulazioni e riportato nella figura 12.a, che riporta le streamline del fluido in esame. In
seguito, e stato fatto un confronto con un problema simile trattato in letteratura [14],
vedesi figura 12.b, al fine di valutare la correttezza dei risultati.
Come nel caso a confronto, anche nel nostro si generano dei vortici nella zona adia-
cente all’ostacolo. I due casi, pero, differiscono in quanto quello in letteratura riguarda
un reticolo asimmetrico. Inoltre, la larghezza orizzontale dell’ostacolo nel secondo caso e
pari alla componente verticale del tubo, quindi la situazione non e riproducibile fedelmente
sugli algoritmi scritti in questo lavoro di tesi. Avendo trattato tutti reticoli quadrati, im-
postare un tubo longilineo richiederebbe una nuova struttura. Probabilmente, e per questi
motivi che le streamline che proseguono oltre l’ostacolo vanno verso le pareti opposte per
il nostro caso, mentre verso il centro del tubo per quello a confronto. Effettivamente,
osservando le streamline subito sotto la fine dell’ostacolo per 12.b (e quindi piu simili a
quelle nell’estrema destra per il nostro caso), sembrano anch’esse muoversi lievemente verso
la parete sud come accade in 12.a. Tuttavia, analizzare nello specifico se sono queste le
motivazioni delle differenze o scrivere anche codici per reticoli asimmetrici, richiederebbe
ulteriore lavoro e approfondimento.
30
7. Conclusioni
Per questo lavoro di tesi, ho studiato la modellizzazione degli automi cellulari, il Lattice
Gas Automata, il Lattice Boltzmann Method e il linguaggio di programmazione Python.
Lo scopo principale del lavoro e stato di riuscire a riprodurre il comportamento di un
automa cellulare che possa essere utilizzato per simulare sistemi fisici reali. Nello specifico,
il comportamento di gas e fluidi “creati“ da righe di codice e risultato coerente con effettivi
comportamenti reali.
Dai risultati del LGA, si puo notare come la dipendenza del numero di urti contro
le pareti dal numero di particelle presenti all’interno della “scatola“, a parita di volume
(dimensione del reticolo) e temperatura (velocita delle particelle in unita reticolari), sia ef-
fettivamente lineare. Inoltre, si e potuto constatare come l’andamento del tempo macchina
impiegato in base alle dimensioni spaziali del reticolo sia quadratico. Invece, la dipendenza
dal numero di particelle contenute nel reticolo e meno incisiva in quanto lineare. Questo
sottolinea che, nonostante tutto il corpo dell’algoritmo venga percorso solo quando si in-
contra una particella, il controllo stesso delle caselle e il riservare la memoria in preventivo
impieghino la maggior parte del lavoro della macchina ad ogni ciclo. Questo e confortante,
perche “toglie“ una dipendenza e permette di scalare tutti i ragionamenti a diverse densita
del gas senza avere ripercussioni eccessive. D’altra parte, si tratta di un ulteriore punto
di forza del LBM rispetto al LGA, in quanto tra le altre cose esso ottimizza la scalabilita
delle dimensioni spaziali del reticolo (casella/particella non e piu 1:1) a scapito del numero
di calcoli fatti per ogni casella e all’impiego e trasporto di tutti i dati in tutte le caselle.
Dai risultati di LBM, invece, e chiaro come le potenzialita rispetto al LGA sono su-
periori. Con una progressione logica, si e visto come e possibile trasformare di volta in
volta l’algoritmo creato, passando da un semplice gas a temperatura costante contenuto
in una scatola, a un fluido contenuto in una scatola in movimento, a un flusso di fluido in
movimento entro dei “tubi“. Procedendo in questo modo, e ampliando i codici si possono
creare simulazioni piu complesse e molto utili anche sul piano pratico, come il caso di tubi
di dimensioni asimmetriche, tri-dimensionali, con barriere diverse e/o periodiche, pareti
in movimento ecc. E anche possibile introdurre nuove equazioni e grandezze estendendo i
ragionamenti e inserendo diverse temperature, tenendo conto di conduzione o convezione,
curve adiabatiche, isoterme, eccetera.
32
8. Appendice: Codici
In questa appendice, sono riportate alcune sezioni di codice tra quelle scritte per questo
lavoro di tesi. Nello specifico, sono presenti solo due dei vari software scritti. Si tratta
di uno dei programmi per LGA (il D2Q4), e uno dei programmi per LBM (l’ostacolo
verticale). Si e scelto di riportare questi due esempi per dare una panoramica di come in
entrambi i casi sono stati pensati gli oggetti, le matrici tridimensionali e i moduli, e come
gestite le iterazioni, comprese le fasi di bounceback, urto, streaming, calcolo delle quantita
macroscopiche, eccetera. Inoltre, si puo vedere come vengono diversamente implementati
gli urti nei due modelli, come in LGA sono state randomizzate le condizioni iniziali, come
in LBM inseriti gli ostacoli, e un idea di come poter gestire le stampe su file e non.
def ca(coso): ''' Cellular automata with Python''' import time
tempo_iniziale = time.time() class layer:
def __init__(self):
self.mega = [[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]]] self.dim=100 self.n=coso self.press=[] self.part=0 import random
#inizializzo tutto a 0 con dimensione dimxdim for k in range (0,9): self.mega[k] = [[0 for j in range(self.dim)] for i in range(self.dim)] #matrice 3d, un layer contenente il numero di particelle per casella, piu 2 layer per le velocita (+-1,+-1) per il numero di particelle che si possono sovrapporre (=una per vettore velocita,4)
#imposto n particelle in dimxdim for i in range (0,self.n): setx=random.randint(0,self.dim-1) sety=random.randint(0,self.dim-1) if self.mega[0][setx][sety]==1: i-=1 self.mega[0][setx][sety]=1 for i in range (self.dim): for j in range (self.dim): if self.mega[0][i][j]==1: self.part+=1
#associo a ciascuna particella uno dei 4 vettori velocita casualmente for j in range (0,self.dim): for i in range (0,self.dim): if self.mega[0][i][j]==1: dado=random.random() if dado<0.25: self.mega[1][i][j]=1
33
elif (dado<0.5 and dado>=0.25): self.mega[2][i][j]=1 elif (dado<0.75 and dado>=0.5): self.mega[1][i][j]=-1 elif dado>0.75: self.mega[2][i][j]=-1 #stampo matrici di condizione iniziale print print " Matrici di pos, vx, vy iniziali: " print
for i in range(self.dim): print self.mega[0][i] print for i in range(self.dim): print self.mega[1][i] print for i in range(self.dim): print self.mega[2][i] print self.tempo = 0 #stampa dello stato iniziale def statoiniziale(self):
print " LGA alle condizioni iniziali: " print
dic = 0:'-', 1:'x', 2:'*', 3:'3', 4:'4'
for i in range (0,self.dim): print ''.join( [dic[e] for e in self.mega[0][i]]) print #evoluzione dell automa def evolvi(self):
#azzero totalmente il layer futuro newmega = [[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]]] for k in range (0,9): newmega[k] = [[0 for j in range(self.dim)] for i in range(self.dim)] conta=0 urto=0 pressione=0
34
for j in range (0,self.dim): for i in range (0,self.dim):
#vado a muovere solo le caselle che hanno particelle dal layer passato al layer futuro if self.mega[0][i][j]>=1:
conta+=self.mega[0][i][j]
#bounceback sulle pareti #il for per k serve a muoversi, in ogni casella, su tutte le particelle che hanno urtato for k in range (0,self.mega[0][i][j]):
if (j+self.mega[1+2*k][i][j])>(self.dim-1): self.mega[1+2*k][i][j]=-self.mega[1+2*k][i][j] pressione+=1 if (j+self.mega[1+2*k][i][j])<0: self.mega[1+2*k][i][j]=-self.mega[1+2*k][i][j] pressione+=1
if (i+self.mega[2+2*k][i][j])<0: self.mega[2+2*k][i][j]=-self.mega[2+2*k][i][j] pressione+=1 if (i+self.mega[2+2*k][i][j])>(self.dim-1): self.mega[2+2*k][i][j]=-self.mega[2+2*k][i][j] pressione+=1
#streaming della pos particella
for k in range (0,self.mega[0][i][j]): a=(i+self.mega[2+2*k][i][j])%self.dim b=(j+self.mega[1+2*k][i][j])%self.dim newmega[0][(i+self.mega[2+2*k][i][j])%self.dim][(j+self.mega[1+2*k][i][j])%self.dim]+=1 c=newmega[0][(i+self.mega[2+2*k][i][j])%self.dim][(j+self.mega[1+2*k][i][j])%self.dim] newmega[1+(c-1)*2][a][b]+=self.mega[1+2*k][i][j] newmega[2+(c-1)*2][a][b]+=self.mega[2+2*k][i]
35
[j]
urto+=c-1
self.press.append(pressione)
#stampa a video del layer dic = 0:'-', 1:'x', 2:'*', 3:'3', 4:'4'
self.tempo = self.tempo + 1 #aggiorno il layer self.mega=newmega
print " LGA al tempo: ", self.tempo print fo2.write('LGA al tempo: ') fo2.write("%d" % self.tempo) fo2.write('\n') fo2.write('\n') for i in range (0,self.dim): print ''.join( [dic[e] for e in self.mega[0][i]]) print " "
for i in range (0,self.dim): fo2.write(''.join( [dic[e] for e in self.mega[0][i]])) fo2.write('\n') fo2.write('') fo2.write('\n')
print " Particelle: ", conta print " Urti: ", urto print " Bounceback: ", pressione print
fo2.write('Particelle: ') fo2.write("%d" % conta) fo2.write('\n')
fo2.write('Urti: ') fo2.write("%d" % urto) fo2.write('\n')
fo2.write('Bounceback: ') fo2.write("%d" % pressione) fo2.write('\n')
36
fo2.write('\n') fo2.write('\n')
def media(self): return 1.0*sum(self.press) / len(self.press)
def errore(self): err=0 for k in range (len(self.press)): err=err + abs(self.press[k]-pmedia) return 1.0 * err / len(self.press) automa=layer() automa.statoiniziale() fo1 = open("d2q4fissodim.txt", "a") fo2 = open("ex.txt", "w") step = 0 while(step < 100):
automa.evolvi() step += 1
pmedia=automa.media() errr=automa.errore() print "pmedia", pmedia print "errr", errr
tempo_finale = time.time() print "Impiegati", str(tempo_finale - tempo_iniziale), "secondi."
fo1.write("%d %d %f %f %f \n" % (automa.dim,automa.part,pmedia,errr,tempo_finale - tempo_iniziale));
fo1.close() fo2.close()
if __name__ == '__main__': ca()
37
def ca(): ''' Cellular automata with Python'''
import random import math import matplotlib.pyplot as plt import numpy as np from streamplot import streamplot
class layer:
def __init__(self):
self.mega = [[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]]] self.dim=51
for k in range (0,12): self.mega[k] = [[0 for j in range(self.dim)] for i in range(self.dim)] for i in range (1,self.dim-1): for j in range (1,self.dim-1): self.mega[9][i][j]=1
self.tempo = 0 #evoluzione dell automa def evolvi(self):
#azzero totalmente il layer futuro newmega = [[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]]]
tau=0.01 sorgente=self.dim/4 posmuro=5*self.dim/6 lenmuro=7*self.dim/8 uscita=self.dim/4 for k in range (0,12): newmega[k] = [[0 for j in range(self.dim)] for i in range(self.dim)]
38
for j in range (1,self.dim-1): for i in range (1,self.dim-1):
if i<=sorgente:
self.mega[10][i][1]=1
u=self.mega[10][i][j] v=self.mega[11][i][j]
self.mega[0][i][j]-=tau*(self.mega[0][i][j]-self.mega[9][i][j]*(4/9.0))
self.mega[1][i][j]-=tau*(self.mega[1][i][j]-(1+3*u-1.5*(u*u+v*v)+4.5*(u*u))*self.mega[9][i][j]*(1/9.0))
self.mega[2][i][j]-=tau*(self.mega[2][i][j]-(1+3*v-1.5*(u*u+v*v)+4.5*(v*v))*self.mega[9][i][j]*(1/9.0))
self.mega[3][i][j]-=tau*(self.mega[3][i][j]-(1-3*u-1.5*(u*u+v*v)+4.5*(u*u))*self.mega[9][i][j]*(1/9.0))
self.mega[4][i][j]-=tau*(self.mega[4][i][j]-(1-3*v-1.5*(u*u+v*v)+4.5*(v*v))*self.mega[9][i][j]*(1/9.0))
self.mega[5][i][j]-=tau*(self.mega[5][i][j]-(1+3*u+3*v-1.5*(u*u+v*v)+4.5*(u+v)*(u+v))*self.mega[9][i][j]*(1/36.0))
self.mega[6][i][j]-=tau*(self.mega[6][i][j]-(1+3*v-3*u-1.5*(u*u+v*v)+4.5*(v-u)*(v-u))*self.mega[9][i][j]*(1/36.0))
self.mega[7][i][j]-=tau*(self.mega[7][i][j]-(1-3*v-3*u-1.5*(u*u+v*v)+4.5*(u+v)*(u+v))*self.mega[9][i][j]*(1/36.0))
self.mega[8][i][j]-=tau*(self.mega[8][i][j]-(1-3*v+3*u-1.5*(u*u+v*v)+4.5*(u-v)*(u-v))*self.mega[9][i][j]*(1/36.0))
for j in range (1,self.dim-1): for i in range (1,self.dim-1): if j!=posmuro or i>lenmuro:
#streaming della pos particella
newmega[4][i+1][j]=self.mega[4][i][j] newmega[2][i-1][j]=self.mega[2][i][j] newmega[1][i][j+1]=self.mega[1][i][j] newmega[3][i][j-1]=self.mega[3][i][j] newmega[8][i+1][j+1]=self.mega[8][i][j] newmega[7][i+1][j-1]=self.mega[7][i][j]
39
newmega[5][i-1][j+1]=self.mega[5][i][j] newmega[6][i-1][j-1]=self.mega[6][i][j] newmega[0][i][j]=self.mega[0][i][j]
#bounceback sulle pareti for i in range (uscita,self.dim-1): newmega[3][i][self.dim-2]=self.mega[1][i][self.dim-2] newmega[6][i][self.dim-2]=self.mega[8][i][self.dim-2] newmega[7][i][self.dim-2]=self.mega[5][i][self.dim-2] for i in range (1,self.dim-1): newmega[1][i][1]=self.mega[3][i][1] newmega[8][i][1]=self.mega[6][i][1] newmega[5][i][1]=self.mega[7][i][1]
for j in range (1,self.dim-1): newmega[2][self.dim-2][j]=self.mega[4][self.dim-2][j] newmega[5][self.dim-2][j]=self.mega[7][self.dim-2][j] newmega[6][self.dim-2][j]=self.mega[8][self.dim-2][j]
newmega[4][1][j]=self.mega[2][1][j] newmega[7][1][j]=self.mega[5][1][j] newmega[8][1][j]=self.mega[6][1][j]
for i in range (1,lenmuro):
newmega[1][i][1+posmuro]=self.mega[3][i][1+posmuro] newmega[8][i][1+posmuro]=self.mega[6][i][1+posmuro] newmega[5][i][1+posmuro]=self.mega[7][i][1+posmuro]
newmega[3][i][-1+posmuro]=self.mega[1][i][-1+posmuro] newmega[6][i][-1+posmuro]=self.mega[8][i][-1+posmuro] newmega[7][i][-1+posmuro]=self.mega[5][i][-1+posmuro]
newmega[4][lenmuro][posmuro]=self.mega[2][lenmuro][posmuro] newmega[7][lenmuro][-1+posmuro]=self.mega[5][lenmuro][-1+posmuro] newmega[8][lenmuro][1+posmuro]=self.mega[6][lenmuro][1+posmuro] for j in range (1,self.dim-1): for i in range (1,self.dim-1): #densita
40
for k in range (9): newmega[9][i][j]+=newmega[k][i][j]
#momento
#calcolo delle componenti del momento tenendo gia conto del diverso contributo delle varie fk alle componenenti vx e vy
newmega[10][i][j]=newmega[1][i][j]-newmega[3][i][j]+1.4142*0.5*(newmega[5][i][j]+newmega[8][i][j]-newmega[6][i][j]-newmega[7][i][j]) newmega[11][i][j]=newmega[2][i][j]-newmega[4][i][j]+1.4142*0.5*(newmega[5][i][j]+newmega[6][i][j]-newmega[7][i][j]-newmega[8][i][j])
newmega[10][i][j]=newmega[10][i][j]/newmega[9][i][j] newmega[11][i][j]=newmega[11][i][j]/newmega[9][i][j]
if i<=sorgente: newmega[10][i][1]=1
for i in range (lenmuro):
newmega[9][i][posmuro]=0 newmega[10][i][posmuro]=0 newmega[11][i][posmuro]=0 self.tempo = self.tempo + 1 #aggiorno il layer self.mega=newmega
automa=layer() step = 1 while(step < 1000):
automa.evolvi() step += 1 print step
fo1 = open("densita assi centrali.txt", "w") fo2 = open("velocita orizzontale riga centrale.txt", "w")
41
fo3 = open("velocita verticale colonna centrale.txt", "w") fo4 = open("vx sotto.txt", "w") fo5 = open("vx uscita.txt", "w") fo6 = open("vy sotto.txt", "w")
for j in range (0,automa.dim): fo1.write("%d %f %f \n" % (j,automa.mega[9][j][automa.dim/2],automa.mega[9][automa.dim/2][j]));
for j in range (0,automa.dim): fo2.write("%d %f \n" % (j,automa.mega[10][automa.dim/2][j]));
for i in range (0,automa.dim): fo3.write("%d %f \n" % (i,automa.mega[11][i][automa.dim/2]));
for j in range (0,automa.dim): fo4.write("%d %f \n" % (j,automa.mega[10][3*automa.dim/4][j]));
for i in range (0,automa.dim): fo5.write("%d %f \n" % (i,automa.mega[10][i][automa.dim-2])); for j in range (0,automa.dim): fo6.write("%d %f \n" % (j,automa.mega[11][3*automa.dim/4][j])); fo1.close() fo2.close() fo3.close() fo4.close() fo5.close() fo6.close()
x = np.linspace(1,automa.dim-2,automa.dim-2) y = np.linspace(1,automa.dim-2,automa.dim-2) u = np.zeros((automa.dim-2,automa.dim-2),float) v = np.zeros((automa.dim-2,automa.dim-2),float)
for i in range (0,automa.dim-2): for j in range (0,automa.dim-2): u[i][j]=automa.mega[10][(automa.dim-i)-2][j+1] v[i][j]=automa.mega[11][(automa.dim-i)-2][j+1]
42
speed = np.sqrt(u*u + v*v)
plt.figure() plt.subplot(121) streamplot(x, y, u, v, density=1, INTEGRATOR='RK4', color='b') plt.subplot(122) streamplot(x, y, u, v, density=(1,1), INTEGRATOR='RK4', color=u, linewidth=5*speed/speed.max()) plt.show()
if __name__ == '__main__': ca()
43
45
References
[1] Wolfram, Stephen. ”Universality and complexity in cellular automata.” Physica D: Nonlinear Phenom-
ena 10.1 (1984): 1-35.
[2] Chopard, Bastien, and Michel Droz. Cellular automata modeling of physical systems. Vol. 6. Cambridge
University Press, 2005.
[3] https://it.wikipedia.org/wiki/Gioco_della_vita
[4] http://golly.sourceforge.net
[5] https://it.wikipedia.org/wiki/Automa_cellulare
[6] https://en.wikipedia.org/wiki/Block_cellular_automaton
[7] Mele, Igor. ”Lattice Boltzmann method”. Seminar, Ljubljana, 2013.
[8] P. L. Bhatnagar, E. P. Gross, M. Krook: A Model for Collision Processes in Gases. I. Small Amplitude
Processes in Charged and Neutral One-Component Systems, Phys. Rev. 94, 511-525, (1954).
[9] http://en.wikipedia.org/wiki/Lattice_Boltzmann_methods
[10] http://www.tutorialspoint.com/python/
[11] http://www.atm.damtp.cam.ac.uk/people/tjf37/streamplot.py
[12] http://tonysyu.github.io/plotting-streamlines-with-matplotlib-and-sympy.html#
.Vt6j8rTyhrM
[13] D. P. Ziegler: Boundary conditions for lattice Boltzmann simulations, J. Stat. Phys. 71, 1171-1177,
(1993).
[14] Mohamad, Abdulmajeed A. Lattice Boltzmann method: fundamentals and engineering applications
with computer codes. Springer Science and Business Media, 2011.
[15] http://sailfish.us.edu.pl