Sei sulla pagina 1di 81

Scuola di

Scienze Matematiche
Fisiche e Naturali

Corso di Laurea Magistrale in


Scienze Fisiche e Astrofisiche

Laboratorio di Fisica Computazionale

Ottimizzazione della
Velocità di Esecuzione
in Simulazioni di
Dinamica Molecolare 2D

Docente: Studente:
Franco Bagnoli Pier Paolo Bonaccini

Anno Accademico 2015-2016


Indice

1 Introduzione 1

2 Modellizzazione del sistema fisico 3


2.1 Il potenziale di Lennard-Jones . . . . . . . . . . . . . . . . . . . . 3
2.2 Condizioni periodiche al contorno . . . . . . . . . . . . . . . . . . 5
2.2.1 Convenzione della minima immagine . . . . . . . . . . . . 6
2.3 Potenziale troncato . . . . . . . . . . . . . . . . . . . . . . . . . . 6

3 Algoritmi e implementazioni 8
3.1 Integrazione temporale . . . . . . . . . . . . . . . . . . . . . . . . 8
3.1.1 Velocity Verlet . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2 Implementazione del programma base . . . . . . . . . . . . . . . 10
3.3 Un programma più veloce . . . . . . . . . . . . . . . . . . . . . . 13
3.4 Un programma ancora più veloce . . . . . . . . . . . . . . . . . . 13
3.4.1 La funzione pp_sort . . . . . . . . . . . . . . . . . . . . . 19
3.5 Compilazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4 Simulazioni e risultati 23
4.1 Spostamento quadratico medio . . . . . . . . . . . . . . . . . . . 23
4.2 Misure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.2.1 L = 20 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.2.2 L = 100 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

5 Conclusioni 35

A 001base.c 36

B 002trunc.c 41

C 003fast.c 46

D box.c 57

E measure.c 78

Bibliografia 79
CAPITOLO 1. INTRODUZIONE 1

Capitolo 1

Introduzione

La dinamica molecolare (MD, molecular dynamics) è una tecnica di simulazione


computazionale, in cui l’evoluzione temporale di un insieme di atomi interagenti
viene seguita integrandone le equazioni del moto [2].
La MD segue le leggi della meccanica classica, e in particolare la legge di
Newton:
F~i = mi~ai . (1.1)
Dove, in un sistema composto da N atomi, mi è la massa dell’atomo i-esimo,
~ai = d2~ri /dt2 la sua accelerazione e F~i la forza che subisce a causa delle intera-
zioni con gli altri atomi. La MD è perciò una tecnica deterministica: dato un
insieme iniziale di posizioni e velocità, l’evoluzione temporale successiva è, in
linea di principio, completamente determinata1 .
Il computer calcola una traiettoria nello spazio delle fasi 6N -dimensionale
(3N posizioni e 3N momenti), solitamente però, questa traiettoria non è parti-
colarmente rilevante in sé per sé. Infatti, la MD è un metodo utilizzato in mec-
canica statistica per ottenere un numero statisticamente significativo di possi-
bili configurazioni nello spazio delle fasi (insieme statistico). Tali configurazioni
saranno distribuite secondo una qualche funzione di distribuzione di probabilità.
Un esempio di insieme statistico è quello microcanonico, che comprende tutte
quegli stati la cui energia totale E è costante. Nello spazio delle fasi, questo
corrisponde a una densità di probabilità pari a:
δ(H(Γ) − E), (1.2)
dove Γ rappresenta l’insieme di posizioni e momenti, H è l’Hamiltoniana e δ è
la distribuzione di Dirac, che va a selezionare solo quegli stati la cui energia è
pari a E.
Un altro esempio di insieme statistico è quello canonico, dove è la tempera-
tura T ad essere costante. Nello spazio delle fasi, la densità di probabilità di
questo insieme è rappresentata dalla distribuzione di Boltzmann:
exp(−H(Γ)/kB T ). (1.3)
La fisica statistica è la connessione tra il comportamento microscopico e
la termodinamica. Le osservabili fisiche sono rappresentate dalle medie sulle
1 Nella pratica, la finitezza degli intervalli temporali di integrazione e gli errori di

arrotondamento numerico portano la traiettoria calcolata a deviare rispetto a quella vera.


CAPITOLO 1. INTRODUZIONE 2

configurazioni, distribuite secondo un certo insieme statistico. Nel limite di


tempi di simulazione molto lunghi, ci possiamo aspettare che lo spazio delle fasi
venga campionato tutto e che questo processo di media porti come risultato le
proprietà termodinamiche.
Perciò, la misura di un’osservabile si ottiene semplicemente dalla media arit-
metica dei vari valori istantanei che assume durante l’esecuzione della simula-
zione di MD.
Trattandosi di medie, maggiore è il numero N degli atomi che compongono il
nostro sistema e minori risultano essere le fluttuazioni sulle osservabili misurate.
All’aumentare di N aumenta il numero di equazioni del moto e con esso il
tempo necessario ad integrarle. Ma con N aumenta anche il tempo necessario
a determinare le forze di interazione tra gli atomi.
In quest’ottica si inquadra l’obiettivo della relazione: realizzare un program-
ma di simulazione di MD per un sistema atomico 2D, soggetto a un potenziale di
interazione di Lennard-Jones con un tempo di esecuzione ragionevole se rappor-
tato al numero N di atomi che compongono il sistema. In particolare verranno
presentati tre approcci al problema via via sempre più performanti.
CAPITOLO 2. MODELLIZZAZIONE DEL SISTEMA FISICO 3

Capitolo 2

Modellizzazione del sistema


fisico

La modellizzazione del sistema fisico è l’ingrediente principale di ogni simulazio-


ne. In MD questo risiede nella scelta del potenziale V (~r1 , . . . , ~rN ), una funzione
delle posizioni dei nuclei che rappresenta l’energia potenziale del sistema quando
gli atomi che lo compongono sono disposti in quella specifica configurazione. Di
solito questa funzione, invariante per traslazione e per rotazione, viene costrui-
ta considerando le posizioni relative degli atomi piuttosto che le loro posizioni
assolute.
Le forze vengono poi ricavate come i gradienti del potenziale rispetto allo
spostamento atomico:
F~i = −∇
~ ~r V (~r1 , . . . , ~rN )
i
(2.1)
Questa operazione implica la conservazione dell’energia totale:
E = K + V,
dove K è l’energia cinetica istantanea.
La scelta più semplice del potenziale V è quella di scriverlo come somma di
interazioni a due corpi:
XX
V (~r1 , . . . , ~rN ) = φ(|~ri − ~rj |) (2.2)
i j>i

La condizione j > i nella seconda sommatoria serve a considerare ogni coppia


di atomi soltanto una volta.
Il modello di interazione a due corpi usato più comunemente è il potenziale
di Lennard-Jones.

2.1 Il potenziale di Lennard-Jones


Il potenziale di Lennard-Jones (LJ), chiamato anche legge 12-6 1 , è dato dall’e-
spressione:   
σ 12  σ 6
φLJ (r) = 4ε − , (2.3)
r r
1 Questa è una forma mnemonica, infatti “12-6” si riferisce agli esponenti nell’espressione

del potenziale di LJ.


CAPITOLO 2. MODELLIZZAZIONE DEL SISTEMA FISICO 4

ε/2

0
φLJ (r)

−ε/2

−ε

0 σ 2σ 3σ 4σ 5σ
r

Figura 2.1: Potenziale di Lennard-Jones.

dove r è la distanza fra la coppia di atomi. I parametri ε e σ vengono scelti


in base alle proprietà fisiche del materiale modellizzato. In una simulazione è
consuetudine lavorare in un sistema di unità dove σ = 1 e ε = 1.
Il potenziale totale di un sistema di N atomi, come detto precedentemente,
è dato dalla (2.2). Come si può osservare in figura 2.1, il potenziale di LJ ha una
coda attrattiva che tende asintoticamente a zero già dopo qualche σ, raggiunge
il minimo quando r = 21/6 σ e diventa fortemente repulsivo a corta distanza,
passando da zero quando r = σ.
Il termine 1/r12 , che domina a corta distanza, schematizza la repulsione tra
gli atomi quando questi vengono portati molto vicini tra loro. La sua origine
fisica è legata al principio di esclusione di Pauli : quando le nuvole elettro-
niche che avvolgono gli atomi cominciano a sovrapporsi, l’energia del sistema
aumenta bruscamente. Dal punto di vista fisico, un comportamento esponen-
ziale sarebbe più appropriato, ma il 12 come esponente rende l’equazione (2.3)
particolarmente semplice da calcolare.
Il termine 1/r6 , che invece domina a grande distanza, costituisce la com-
ponente attrattiva ed è quello che dà coesione al sistema. L’attrazione 1/r6 è
originata dalle così dette forze di van der Walls. Questo termine include tre
diversi tipi di interazione intermolecolare:

- interazione dipolo permanente-dipolo permanente (forze di Keesom);


- interazione dipolo permanente-dipolo indotto (forze di Debye);
- interazione dipolo istantaneo-dipolo indotto (forze di London).
CAPITOLO 2. MODELLIZZAZIONE DEL SISTEMA FISICO 5

Queste interazioni sono piuttosto deboli se comparate ai legami covalenti o


ionici, diventano però dominanti nei sistemi a shell chiusa, come i gas nobili.
Sono appunto l’argon (Ar) e il kripton (Kr) i materiali che il potenziale di LJ
riesce a descrivere abbastanza bene.
D’altra parte un potenziale di LJ non è per niente adeguato a modellizzare
sistemi a shell aperta, in cui si vengono a formare forti legami localizzati, come in
un sistema covalente, o dove gli ioni si trovano immersi in un “mare di elettroni”
delocalizzato, come nei metalli. A fallire pesantemente in questi sistemi è lo
schema stesso dell’interazione a due corpi.
Comunque, anche senza considerare quanto bene riesca a modellizzare i ma-
teriali reali, il potenziale di LJ costituisce un sistema di modellizzazione estre-
mamente importante. Si potrebbe dire che il potenziale di LJ è il potenziale
standard da utilizzare in tutte quelle indagini in cui l’attenzione è sui problemi
fondamentali, piuttosto che sulle proprietà di un materiale specifico. I lavori di
simulazione che utilizzano il potenziale di LJ hanno aiutato (e lo fanno ancora)
a comprendere i principi base in molte aree della fisica e per questa ragione
l’importanza del potenziale di LJ non deve essere sottovalutata.

2.2 Condizioni periodiche al contorno


Consideriamo il sistema di atomi racchiuso all’interno di una scatola di lato L. A
meno di non voler simulare un cluster di atomi, non importa quanto sia grande
il sistema simulato, il numero N di atomi considerato sarà sempre trascurabile
rispetto al numero di atomi contenuto in una porzione macroscopica di materia
(∼ 1023 ). Perciò il rapporto tra il numero di atomi vicino alle pareti e il loro
numero totale sarà molto più grande che nella realtà, rendendo gli effetti di
superficie molto più importanti di quello che dovrebbero essere. Una soluzione
a questo problema è usare delle condizioni periodiche al contorno (PBC, periodic
boundary condition).
Quando usiamo le PBC, possiamo immaginare che la scatola in cui sono
racchiuse le particelle venga replicata all’infinito tramite traslazioni rigide in
tutte e tre le direzioni cartesiane, andando a riempire completamente tutto lo
spazio. In altre parole, se una delle nostre particelle si trova nella posizione ~r
all’interno della scatola, assumiamo che questa particella rappresenti un insieme
infinito di particelle che si trovano in

~r + l~a + m~b + n~c, (l, m, n = −∞, ∞), (2.4)

dove l, m, n sono numeri interi e ~a, ~b, ~c sono i vettori corrispondenti agli spigoli
della scatola. Tutte queste particelle “immagine” si muovono insieme, e infatti
soltanto una di queste è rappresentata nel computer dal programma. Il punto
chiave è che adesso possiamo pensare ogni particella i all’interno della scatola
interagente non solo con le altre particelle j nella scatola, ma anche con le loro
immagini che si trovano nelle scatole vicine. Cioè, le interazioni possono “andare
oltre” le pareti della scatola.
Apparentemente, il numero di coppie interagenti cresce enormemente come
effetto delle PBC. Nella pratica, grazie alla convenzione della minima immagine,
le PBC impongono un cutoff naturale al raggio d’azione del potenziale.
CAPITOLO 2. MODELLIZZAZIONE DEL SISTEMA FISICO 6

2.2.1 Convenzione della minima immagine


Supponiamo di utilizzare un potenziale con un raggio d’azione finito: quando
sono separate da una distanza uguale o maggiore del raggio di cutoff Rc , due
particelle non interagiscono tra loro. Supponiamo anche di utillizzare una sca-
tola le cui dimensioni siano almeno pari a 2Rc lungo tutte e tre le direzioni
cartesiane. Quando vengono soddisfatte queste condizioni, è ovvio che al massi-
mo, fra tutte le coppie formate da una particella i nella scatola e l’insieme delle
immagini periodiche di un’altra particella j, solo una interagirà.
Per dimostrarlo, supponiamo che la particella i interagisca sia con j1 che con
j2 , due immagini della particella j. Per definizione, due immagini sono separate
dal vettore traslazione che porta una scatola su un’altra; la lunghezza di questo
vettore è, per ipotesi, almeno 2Rc . Per poter interagire sia con j1 che con j2 ,
i dovrebbe trovarsi entro una distanza Rc da entrambe, ma poiché le immagini
di j sono separate almeno da una distanza 2Rc , questo è impossibile.
Sotto queste condizioni possiamo tranquillamente applicare la convenzione
della minima immagine: fra tutte le possibili immagini di una particella j, si
seleziona la più vicina e si scartano tutte le altre. In effetti, soltanto la più
vicina è candidata a interagire; tutte le altre non lo faranno sicuramente.
Questa condizione di lavoro semplifica enormemente lo schema di un pro-
gramma di MD ed è comunemente usata.
Come detto, l’utilizzo delle PBC impone un cutoff naturale RcL al raggio
d’azione del potenziale:
L L
L ≥ 2Rc ⇒ Rc ≤ ⇒ RcL = .
2 2
Nelle PBC è intrinseco il problema del potenziale troncato.

2.3 Potenziale troncato


Il potenziale di LJ (2.3) ha un raggio d’azione infinito, che però, come mostrato
in figura 2.1, tende a zero subito dopo qualche σ. Nelle applicazioni pratiche,
anche quando non si utilizzano le PBC, è consuetudine stabilire un raggio di
cutoff Rc e scartare le interazioni tra gli atomi che distano tra loro più di quella
distanza. Questo ha come conseguenza programmi più semplici e un enorme
risparmio delle risorse del computer2 .
Un semplice troncamento del potenziale crea, però, un nuovo problema: ogni
volta che una particella oltrepassa la distanza di cutoff, l’energia fa un piccolo
“salto”. È probabile che un alto numero di questi eventi rovini la conservazione
dell’energia in una simulazione. Spesso, per evitare questo problema, il poten-
ziale viene traslato in modo da farlo svanire una volta raggiunto il raggio di
cutoff 3 : (
φLJ (r) − φLJ (Rc ) se r < Rc
V (r) = (2.5)
0 se r ≥ Rc
2 Il numero delle coppie di atomi separati da una distanza r diventa rapidamente molto

grande: cresce come r2 per un sistema 3D e come r per un sistema 2D.


3 La traslazione elimina la discontinuità dell’energia, ma non la discontinuità della forza.

Alcuni ricercatori alterano la forma del potenziale vicino a Rc così da ottenere un raccordo
più liscio, ma non c’è un metodo standard per farlo.
CAPITOLO 2. MODELLIZZAZIONE DEL SISTEMA FISICO 7

Le quantità fisiche, ovviamente, vengono affette dal potenziale troncato. In


molti casi, però, non c’è interesse nel valutare le variazioni dovute al tronca-
mento, perché è il modello troncato stesso il soggetto dell’indagine.
Raggi di cutoff comunemente usati per il potenziale di LJ sono 2.5σ e 3.2σ.
Questi valori sono così popolari da essere diventati particolarmente importanti
come modelli di riferimento per generici sistemi a due corpi.
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 8

Capitolo 3

Algoritmi e implementazioni

3.1 Integrazione temporale


Il motore di un programma di MD è il suo algoritmo di integrazione temporale.
Questo permette di risolvere l’equazione del moto delle particelle interagenti e
di seguirne le traiettorie.
Gli algoritmi di integrazione temporale si basano sul metodo delle differen-
ze finite 1 (FDM, finite difference method ), dove il tempo viene discretizzato e
l’intervallo temporale ∆t è la distanza tra due istanti consecutivi. Conoscendo
le posizioni e alcune delle loro derivate temporali al tempo t, lo schema di in-
tegrazione restituisce le stesse quantita al tempo successivo t + ∆t. Ripetendo
la procedura, l’evoluzione temporale del sistema può essere seguita per tempi
lunghi.
Ovviamente questi schemi sono approssimati e ci sono degli errori ad essi
associati. In particolare, si possono distiguere :

- Errori di troncamento, relativi all’accuratezza del FDM rispetto alla solu-


zione vera. Il FDM di solito si basa sullo sviluppo in serie di Taylor tron-
cato dopo qualche termine, da qui il nome. Questi errori non dipendono
dall’implementazione: sono intrinseci dell’algoritmo.
- Errori di arrotondamento, associati alla particolare implementazione del-
l’algoritmo. Per esempio, al numero finito di digit usati nell’aritmentica
del computer.

Entrambi gli errori possono essere ridotti riducendo ∆t.


Gli errori di troncamento dominano per ∆t grandi, ma decrescono rapida-
mente al decrescere dei ∆t.
Gli errori di arrotondamento decrescono più lentamente al decrescere dei ∆t,
e dominano nel limite di ∆t piccoli. Utilizzare una rappresentazione numerica in
virgola mobile a doppia precisione (double, 64 bit) aiuta a mantenere gli errori
di arrotondamento al minimo.
1 Da non confondere con il metodo agli elementi finiti (FEM, finite element method).
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 9

3.1.1 Velocity Verlet


In MD uno degli algoritmi di integrazione temporale maggiormente utilizzato
è probabilmente il cosidetto velocity Verlet. Il velocity Verlet è uno dei mol-
ti algoritmi simplettici 2 utilizzati per risolvere il problema della conservazione
dell’energia totale nel metodo di Eulero.
I valori delle posizioni, delle velocità e delle accelerazioni al tempo t + ∆t
vengono trovati attraverso il seguente schema:
∆t
~r(t + ∆t) = ~r(t) + ~v (t + )∆t (3.1)
2
∆t ∆t
~v (t + ) = ~v (t) + ~a(t) (3.2)
2 2
F~ (t + ∆t)
~a(t + ∆t) = (3.3)
m
∆t ∆t
~v (t + ∆t) = ~v (t + ) + ~a(t + ∆t) (3.4)
2 2
Considerare, nel calcolo di ~r(t + ∆t), la velocità ~v al tempo t + ∆t/2 invece che
al tempo t porta l’energia a non crescere all’infinito e a far sì che oscilli attorno
ad un valore fisso, mantenendo il sistema lungo una curva chiusa nello spazio
delle fasi.
Poiché stiamo integrando la legge di Newton, ~a è semplicemente la forza
diviso la massa, e la forza è a sua volta funzione della posizione ~r:

~ (~r(t))
∇V
~a(t) = − (3.5)
m
Quindi, utilizzando l’equazione (3.2) nell’equazione (3.1) e l’equazione (3.5)
nell’equazione (3.3), lo schema dell’algoritmo velocity Verlet diventa:

∆t2
~r(t + ∆t) = ~r(t) + ~v (t)∆t + ~a(t) (3.6)
2
∆t ∆t
~v (t + ) = ~v (t) + ~a(t) (3.7)
2 2
~ (~r(t + ∆t)
∇V
~a(t + ∆t) = − (3.8)
m
∆t ∆t
~v (t + ∆t) = ~v (t + ) + ~a(t + ∆t) (3.9)
2 2
È interessante notare la necessità di 9N locazioni di memoria per salvare le
3N posizioni, velocità e accelerazioni3 , senza però il bisogno di memorizza-
re contemporaneamente i valori di nessuna di questa quantità per due tempi
differenti.
Questo algoritmo è al tempo stesso semplice da implementare, preciso e
stabile, il che spiega il suo largo utilizzo nelle simulazioni di MD.
2 Un altro algoritmo simplettico molto utilizzato è il leap frog.
3 Questo nel caso 3D, in 2D le allocazioni di memoria sono 6N .
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 10

3.2 Implementazione del programma base


Dato il suo ampio utilizzo in campo scientifico, il linguaggio di programmazione
scelto è il linguaggio C.
In questa simulazione di MD ci limitiamo ad analizzare il caso 2D di una
scatola quadrata con le PBC.
Definiamo la struttura in cui vengono memorizzati i dati relativi a ciascuna
particella durante l’evoluzione temporale del sistema:
typedef struct _particle{
double x[2];// posizione
double v[2];// velocità
double f[2];// forza
int l[2];// indice del reticolo periodico
} particle;
I primi tre campi indicano la posizione, la velocità e la forza (e quindi l’accele-
razione) della particella, mentre il campo int l[2] indica in quale replica della
scatola si trova la particella, in modo da poterne calcolare la distanza percorsa.
La massa della particella, che non varia durante la simulazione ed è uguale
per tutte le particelle4 , viene memorizzata in un’altra struttura assieme ad altre
variabili definite all’inizio della simulazione. Tra queste ci sono il numero di
particelle, il raggio del potenziale troncato, il suo quadrato, le dimensioni del
lato della scatola, l’intervallo temporale di integrazione e la costante additiva
per il potenziale troncato.

typedef struct _startopt{


int N;// numero particelle
double R;// raggio potenziale troncato
double R2;// R^2
double L;// dimensione scatola quadrata
double m0;// massa della particella p[0]
double mi;// massa della particella p[i]
double dt;// step temporali
double Vt;// V(R) da aggiungere per il potenziale troncato
} startopt;
Il raggio del potenziale troncato, come detto nel capitolo 2.2.1, deriva natural-
mente dall’uso delle PBC ed è pari a Rc = L/2. La costante additiva viene
calcolata nella funzione load, mentre vengono caricati tutti i dati iniziali:
popt->R2 = (popt->L * popt->L)/4;
dm2 = 1./popt->R2;
dm6 = dm2 * dm2 * dm2;
popt->Vt = 4*dm6*(dm6-1);
Per semplicità, come detto nel capitolo 2.1, si adotta la consuetudine che pone
σ = 1 e ε = 1 nell’equazione (2.3).
Il fulcro di tutta la simulazione è la funzione evolve.
4 Questa è un’ulteriore semplificazione del problema. Ovviamente le particelle potrebbero

avere tutte masse diverse. Come opzione, non utilizzata però, è stato aggiunto il campo m0,
in modo da poter impostare una massa diversa per la sola particella 0.
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 11

Seguendo lo schema del velocity Verlet (capitolo 3.1.1), prima viene calcolata
la nuova posizione ~r(t + ∆t) della particella:

p[0].x[0] += (p[0].v[0] + p[0].f[0]/opt.m0*0.5*opt.dt)*opt.dt;


p[0].x[1] += (p[0].v[1] + p[0].f[1]/opt.m0*0.5*opt.dt)*opt.dt;
Si verificano le PBC aggiornando, nel caso, la posizione della particella e il
campo che indica la replica della scatola:
if(p[0].x[0] < 0){
p[0].l[0]--;
p[0].x[0] += opt.L;
}
else if(p[0].x[0] > opt.L){
p[0].l[0]++;
p[0].x[0] -= opt.L;
}
if(p[0].x[1] < 0){
p[0].l[1]--;
p[0].x[1] += opt.L;
}
else if(p[0].x[1] > opt.L){
p[0].l[1]++;
p[0].x[1] -= opt.L;
}
Si passa poi al calcolo di ~v (t + ∆t), azzerando al contempo le forze:

p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
p[0].f[0] = p[0].f[1] = 0;
Questi passaggi vengono ripetuti per tutte le altre particelle all’interno di un
ciclo for.
A questo punto si calcola la forza F~ (t + ∆t). Il ciclo è strutturato in modo
da considerare tutte le coppie che è possibile formare con le N particelle del
sistema:
for(a = 0; a < opt.N-1; a++){
for(b = a+1; b < opt.N; b++){

[. . .]
}
}
La sua complessità si trova calcolando C(N ; 2):
 
N N!
C(N ; 2) = =
2 2! (N − 2)!
(3.10)
N
= (N − 1) ∈ O(N 2 )
2
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 12

Questo è il “collo di bottiglia” che rallenta tutto il programma. Infatti la du-


rata di questo ciclo cresce come il quadrato del numero N di particelle che
costituiscono il sistema.
Al suo interno, i controlli condizionali if else tengono conto della conven-
zione della minima immagine (capitolo 2.2.1), in modo da determinare le giuste
distanze tra le particelle:
r[0] = p[b].x[0] - p[a].x[0];
if(r[0] > opt.L/2.)
r[0] = r[0] - opt.L;
else if(r[0] < -opt.L/2.)
r[0] = r[0] + opt.L;
r[1] = p[b].x[1] - p[a].x[1];
if(r[1] > opt.L/2.)
r[1] = r[1] - opt.L;
else if(r[1] < -opt.L/2.)
r[1] = r[1] + opt.L;
A questo punto, nel caso in cui la distanza tra le due particelle sia inferiore
al raggio di cutoff, si procede al calcolo della forza:
r2 = r[0]*r[0] + r[1]*r[1];
if(r2 < opt.R2){
r_2 = 1./r2;
r_6 = r_2*r_2*r_2;
ff = -24*r_6*(2*r_6-1)*r_2;
p[a].f[0] += ff*r[0];
p[a].f[1] += ff*r[1];
p[b].f[0] -= ff*r[0];
p[b].f[1] -= ff*r[1];
E[1] += (4*r_6*(r_6-1) - opt.Vt);
}
Sempre nello stesso ciclo viene calcolata anche l’energia potenziale V (t + ∆t) e
memorizzata in E[1].
L’ultimo passaggio del velocity Verlet prevede il calcolo di ~v (t + ∆t)
p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
E[2] += opt.m0*(p[0].v[0]*p[0].v[0] + p[0].v[1]*p[0].v[1])/2;
Contemporaneamente viene calcolata anche l’energia cinetica K(t + ∆t) e me-
morizzata in E[2]. Per semplicità, analogamente all’energia potenziale, si pone
la costante di Boltzmann kB = 1.
Questo passaggio viene ripetuto per le altre particelle all’interno di un ci-
clo for.
Prima di uscire dalla funzione evolve, vengono calcolate l’energia totale per
particella, l’energia potenziale per particella e l’energia cinetica per particella,
memorizzate rispettivamente in E[0], E[1] e E[2]:
E[0] = E[1] + E[2];
E[0] = E[0]/opt.N;
E[1] = E[1]/opt.N;
E[2] = E[2]/opt.N;
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 13

Nel caso 2D, avendo posto kB = 1, l’energia cinetica per particella coincide con
la temperatura. Infatti, per il teorema di equipartizione dell’energia:
D
K= N kB T (3.11)
2
Dove D è il numero di gradi di libertà di ogni particella. Quindi:
2 K(t) D=2 K(t)
T (t) = −−−−→ T (t) = (3.12)
D N kB kB =1 N
Il calcolo dell’energia totale e della temperatura permette di verificare che il
sistema sia in equilibrio termodinamico durante la misura.
Il programma completo 001base.c si trova in appendice A.

3.3 Un programma più veloce


Come visto nel capitolo precedente, il programma base ha un “collo di bottiglia”
nel punto in cui vengono calcolate le forze. È possibile renderlo più veloce
sfruttando un raggio di cutoff Rc più piccolo di quello che naturalmente deriva
dalle PBC.
Sostanzialmente il programma resta invariato, le uniche che cambiano sono
il valore di Rc e di conseguenza quello della costante additiva per il potenziale
troncato.
Anche se la complessità del ciclo delle forze resta sempre proporzionale a N 2 ,
un Rc piccolo riduce notevolmente le operazioni al suo interno. Infatti, grazie
al controllo condizionale if(r2 < opt.R2), se la distanza tra le particelle è
maggiore di Rc , nessuna di queste operazioni viene eseguita:
r_2 = 1./r2;
r_6 = r_2*r_2*r_2;
ff = -24*r_6*(2*r_6-1)*r_2;
p[a].f[0] += ff*r[0];
p[a].f[1] += ff*r[1];
p[b].f[0] -= ff*r[0];
p[b].f[1] -= ff*r[1];
E[1] += (4*r_6*(r_6-1) - opt.Vt);
Più piccolo sarà Rc e minore sarà il numero di operazioni eseguite all’interno
del ciclo delle forze. Per la nostra simulazione il raggio di cutoff scelto è Rc = 4σ.
Il programma completo 002trunc.c si trova in appendice B.

3.4 Un programma ancora più veloce


Una soluzione più incisiva al problema è quella di andare a modificare l’intero
ciclo delle forze all’interno della funzione evolve.
L’idea è quella di dividere la scatola in celle quadrate di lato Rc . Poiché Rc
è il raggio d’azione del potenziale troncato, solo le particelle che si trovano in
celle adiacenti saranno candidate a interagire tra loro.
Quindi, per calcolare le forze, invece di prendere ogni particella e determi-
narne la distanza da tutte le altre, si prendono in considerazione, per ogni cella,
solo le particelle al suo interno e quelle contenute nelle celle adiacenti.
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 14

3 0 1 2 3 0
2
0 9
2
0

15 12 13 14 15 12
7
5 1
7
5
4
11 8 9 10 11 8 3
6

7 4 5 6 7 4 8

3 0 1 2 3 0
2
0 9
2
0

15 12 13 14 15 12
7
5 1
7
5

Figura 3.1: Divisione in celle della scatola.

Prendiamo come esempio la figura 3.1, consideriamo la cella 10, le particelle


candidate a interagire sono quelle al suo interno e quelle contenute nelle celle
adiacenti, cioè all’interno delle celle 5, 6, 7, 9, 11, 13, 14 e 15. Andremo a
determinare le distanze solo tra le particelle 1, 3, 4, 6, 7 e 8. Tutte le altre
particelle (la 0, la 2, la 5 e la 9) sono sicuramente fuori dal raggio d’azione Rc
del potenziale.
Quindi quello che si vuole fare è individuare quali celle occupano le particelle,
ordinare il vettore delle particelle in base alla cella occupata e a quel punto
calcolare le forze, scorrendo sugli indici delle celle invece che su quelli delle
particelle.
Per considerare la suddivisione della scatola in celle , vengono aggiunti nella
struttura startopt i campi int C e int c, che indicano, rispettivamente, il
numero totale di celle e il numero di celle per lato, e nella struttura particle il
campo int nc, che indica la cella in cui si trova la particella. Per tenere traccia
di quali e quante particelle si trovano in ogni cella vengono introdotti il vettore
cumulata cum[C+1] (il cui nome sarà chiarito poco più avanti) e il vettore dei
puntatori alle particelle pp[N].
L’elemento cum[0] è uguale a zero per tutto il programma, mentre gli altri
elementi del vettore rappresentano, almeno in un primo stadio della funzio-
ne evolve, quante particelle si trovano in ogni cella. Per questo cum viene
“svuotato” all’inizio della funzione evolve:

for(i = 1; i <= opt.C; i++){


cum[i] = 0;
}
La cella in cui si trova la particella viene determinata durante il calcolo di
~r(t + ∆t) e il valore dell’elemento di cum ad essa associato viene incrementato
di un’unità:
p[0].nc = (int)(p[0].x[0]/opt.R) + (int)(p[0].x[1]/opt.R)*opt.c;
cum[p[0].nc+1]++;
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 15

Quindi, riprendo l’esempio della figura 3.1, il vettore cum risulta essere:

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
cum[i] 0 1 1 0 1 0 0 1 0 0 0 3 0 1 1 0 1

Dopo il calcolo di ~v (t + ∆t/2) si passa al calcolo della cumulata:

for(i = 1; i < opt.C + 1; i++){


cum[i] += cum[i-1];
}
Qui si capisce il perché del nome. Ogni elemento del vettore è la somma del
numero di particelle in quella cella sommato al numero delle particelle nelle celle
precedenti.
L’utilità della forma di questo vettore risulterà evidente dopo la prossi-
ma operazione. Grazie alla funzione pp_sort (descritta più avanti nel capi-
tolo 3.4.1), il vettore pp viene ordinanto in funzione della cella occupata dalla
particella.
In riferimento sempre alla figura 3.1, la situazione che si presenta a questo
punto è la seguente:

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
cum[i] 0 1 2 2 3 3 3 4 4 4 4 7 7 8 9 9 10

i 0 1 2 3 4 5 6 7 8 9
pp[i]

p[i].nc 0 13 3 10 10 12 10 15 6 1
i 0 1 2 3 4 5 6 7 8 9

Il valore dell’elemento cum[nc] indica l’elemento del vettore pp dal quale


inizia la cella nc. Per esempio, il valore di cum[7] è 4, vuol dire che pp[4]
punta l’inizio della cella 7.
Quante particelle sono contenute nella cella nc è dato dalla differenza tra
cum[nc+1] e cum[nc]. Di nuovo, il valore di cum[7] è 4 così come il valore di
cum[8], quindi la cella 7 è vuota, proprio come mostrato in figura 3.1. La stessa
cosa si ripete fino alla cella 9.
Per la cella nc=10 abbiamo cum[10]=4 e cum[11]=7. Significa che la cella 10
contiene 3 particelle e che queste sono puntate dagli elementi con indice 4, 5 e
6 del vettore pp. Infatti, i puntatori pp[4], pp[5] e pp[6] puntano rispettiva-
mente le particelle 3, 4 e 6 contenute all’interno della cella 10, come si vede in
figura 3.1.
Quindi per calcolare le forze si scorre sugli indici delle celle e non sugli indici
delle particelle.
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 16

Figura 3.2: Schema di interazione tra le celle.

Ovviamente, anche in questo caso, bisogna fare in modo di prendere ogni


coppia di particelle soltanto una volta. A tale scopo, per ogni cella, si consi-
derano le tre celle sopra di essa e la cella accanto a destra, come mostrato in
figura 3.2.
In questa operazione bisogna stare attenti alle celle che si trovano ai bordi
della scatola per le quali valgono le PBC. Per questo motivo le celle vengono
trattate in blocchi, come mostrato in figura 3.3.

j=3 4 5 6

j=2

j=1 1 2 3

j=0

i=0 i=1 i=2 i=3

Figura 3.3: Schema a blocchi per la trattazione delle celle.

Si comincia con un ciclo for che va dalla prima riga di celle fino alla
penultima.

for(j = 0; j < opt.c-1; j++){


[. . .]
}//chiude ciclo j

Al suo interno vengono inseriti i blocchi 1, 2 e 3.


Andiamo a vedere il blocco 1, cioè quello che comprende le celle sul bordo
sinistro della scatola, evidenziato in rosso nella figura 3.3. Il numero di celle in
questo blocco è pari a (c − 1), dove c è il numero di celle per lato.
Le particelle contenute in ogni cella del blocco vengono esaminate all’interno
di un altro ciclo for:
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 17

//i = 0;
l = j*opt.c;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
[. . .]
}//chiude ciclo a
La variabile cnt tiene il conto delle particelle in esame, cosicché, finito il
ciclo delle forze, si possa verificare che tutte le particelle nella scatola siano
state prese in considerazione.
Quindi, seguendo lo schema della figura 3.2, si calcolano prima le forze tra
una particella e quelle all’interno della stessa cella (se ce ne sono):
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
La macro 5 forces è utilizzata per snellire la scrittura e la lettura del file
sorgente. Al suo interno vengono calcolate le forze e l’energia potenziale:
#define forces(a,b) \
r2 = r[0]*r[0] + r[1]*r[1]; \
if(r2 < opt.R2){ \
r_2 = 1./r2; \
r_6 = r_2*r_2*r_2; \
ff = -24*r_6*(2*r_6-1)*r_2; \
pp[a]->f[0] += ff*r[0]; \
pp[a]->f[1] += ff*r[1]; \
pp[b]->f[0] -= ff*r[0]; \
pp[b]->f[1] -= ff*r[1]; \
E[1] += (4*r_6*(r_6-1) - opt.Vt); \
}
Si calcolano, poi, le forze tra la particella e quelle contenute nella cella
accanto a destra:
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
Si passa quindi a considerare le interazioni con le particelle nelle celle poste
sopra. Si parte da quella in alto a sinistra che, per le PBC, è la cella situata sul
bordo destro della scatola:
5 Una macro è un frammento di codice al quale viene dato un nome. Quando il preprocessore

incontra il nome di una macro all’interno del codice sorgente, lo sostituisce con il suo contenuto.
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 18

for(b = cum[l+2*opt.c-1]; b < cum[l+2*opt.c]; b++){


r[0] = (pp[b]->x[0] - opt.L) - pp[a]->x[0];//special
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
E si finisce con le due restanti:
for(k = 0; k < 2; k++){
for(b = cum[l+opt.c+k]; b < cum[l+opt.c+k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}

Con qualche piccola variazione a causa delle PBC, il codice è lo stesso anche
per gli altri blocchi. Il programma completo 003fast.c si trova in appendice C.
Il blocco 2 è il più grande e comprende le celle al centro della scatola (in
giallo nella figura 3.3). Il loro numero è pari a (c − 2) · (c − 1) = c2 − 3c + 2, dove
c indica sempre il numero di celle per lato. Queste sono tutte celle “standard”
per le quali non dobbiamo preoccuparci delle PBC.
Passiamo al blocco 3, cioè quello che comprende le celle sul bordo destro
della scatola, evidenziato in verde nella figura 3.3. Il numero di celle in questo
blocco è lo stesso del blocco 1, cioè (c − 1).
Lo schema è sempre lo stesso (quello della figura 3.2), con l’accortezza di
considerare le PBC per la cella accanto a destra e per la cella in alto a destra.
Quest’ultime vengono quindi a trovarsi sul bordo sinistro della scatola.
Si passa all’ultima riga di celle, che comprende i blocchi 4, 5 e 6:
//j = opt.c-1;
Il blocco 4 è formato da un’unica cella, quella in alto a sinistra e colorata di
blu in figura 3.2.
Qui le PBC vanno applicate alle tre celle che si trovano sopra. La cella in
alto a sinistra, allora, risulta essere quella nell’angolo in basso a destra della
scatola; le altre due, invece, diventano le prime due celle sul fondo.
Il blocco 5 (colorato di arancione in figura 3.2) comprende la parte centrale
delle celle sul bordo superiore della scatola. Il loro numero è pari a (c − 2).
L’ultimo blocco è il 6 e, proprio come il 4, è formato da un’unica cella, quella
in alto a destra di colore grigio nella figura 3.3.
Qui le PBC vanno applicate a tutte le celle dello schema di interazione in
figura 3.2. La cella accanto a destra risulta essere la cella del blocco 4, quelle
sopra diventano rispettivamente le ultime due celle del bordo inferiore della
scatola e la prima cella del bordo inferiore.
Scritto in questo modo il ciclo delle forze è chiaramente più lungo e com-
plicato rispetto a quello base. Evita però di calcolare inutilmente distanze tra
particelle che sicuramente non interagiscono.
Il “collo di bottiglia” diventa così un problema di ordinamento: se ordinare
il vettore pp in funzione della cella occupata dalle particelle richiedesse più
tempo del calcolare tutte le distanze tra particelle e confrontarle con il raggio di
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 19

cutoff del potenziale troncato, avremmo solo complicato il codice senza alcun
guadagno.
A tal proposito la funzione pp_sort risulta fondamentale.

3.4.1 La funzione pp_sort


Come detto, il problema del “collo di bottiglia” si riduce ad un problema di
ordinamento del vettore dei puntatori alle particelle pp.
L’introduzione di questo vettore trova adesso la sua spiegazione: sebbene ri-
chieda un’ulteriore allocazione di memoria, permette di risparmiare un notevole
ammontare di tempo. Infatti, scambiare tra loro due puntatori è estremamente
più veloce che scambiare due strutture particle.
Quindi nella funzione pp_sort vengono confrontate variabili di tipo int (al-
tra operazione molto veloce) e scambiate variabili di tipo puntatore a particella
(particle *).
L’algoritmo di ordinamento per antonomasia è il Quicksort, basato sul prin-
cipio del divide et impera: si sceglie un elemento del vettore, detto pivot; gli
elementi minori del pivot vengono posti alla sua sinistra, quelli maggiori alla
sua destra; tale operazione viene ripetuta ricorsivamente sui due sottoinsiemi
fino al completo ordinamento dell’intero vettore.
La funzione pp_sort è stata scritta prendendo come spunto la funzione
qsort di Bentley e McIlroy [1], una versione del Quicksort piuttosto chiara,
veloce e robusta.
Questa funzione ricorre a molte astuzie per rendere l’ordinamento più veloce.
Ad esempio, per vettori di piccole dimensioni (n < 7) viene utilizzato l’algoritmo
iterativo insertion sort, molto più efficiente del Quicksort in questi casi:
if(n<7){
for(pm=pp+1;pm<pp+n;pm++)
for(pl=pm;pl>pp && cmp_nc(pl-1,pl)>0;pl--)
swap(pl,pl-1,tmp);
return;
}
La macro cmp_nc compara i campi nc delle particelle puntate dagli elementi
del vettore pp restituendo come valore la loro differenza:
#define cmp_nc(a,b) ((*(a))->nc - (*(b))->nc)

La macro swap scambia gli elementi del vettore pp servendosi della variabile
temporanea tmp:
#define swap(a,b,c) (c = (*(a)), (*(a)) = (*(b)), (*(b)) = c)
Quando i vettori sono più grandi è invece fondamentale la scelta del pivot.
Quando n = 7 viene scelto l’elemento centrale del vettore. Quando il vettore è
di medie dimensioni (fino a n = 40) come pivot viene scelto il mediano tra il
primo elemento, quello centrale e l’ultimo. Quando n > 40 come pivot viene
scelto il ninther, cioè il mediano dei mediani di tre campioni composti da tre
elementi ciascuno.
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 20

pm=pp+(n/2);
if(n>7){
pl=pp;
pn=pp+(n-1);
if(n>40){
s=(n/8);
pl=med3(pl,pl+s,pl+2*s);
pm=med3(pm-s,pm,pm+s);
pn=med3(pn-2*s,pn-s,pn);
}
pm=med3(pl,pm,pn);
}
Il mediano di tre elementi si trova grazie alla macro med3:

#define med3(a,b,c) \
cmp_nc(a,b) < 0 ? \
(cmp_nc(b,c) < 0 ? b : cmp_nc(a,c) < 0 ? c : a) : \
(cmp_nc(b,c) > 0 ? b : cmp_nc(a,c) > 0 ? c : a)
Il disordine indotto scambiando il pivot con il primo elemento risulta esse-
re molto costoso quando il vettore è ordinato, o quasi ordinato, al contrario.
Quindi, invece di eseguire lo scambio, come viene fatto tipicamente, il valore del
pivot viene copiato nella variabile ausiliaria pv:
pv=&v;
v=*pm;

Si passa poi alla tripartizione del vettore. Tripartizione perché oltre a sposta-
re gli elementi minori del pivot a sinistra e quelli maggiori a destra, gli elementi
con lo stesso valore del pivot vengono a trovarsi ai due estremi del vettore.
pa=pb=pp;
pc=pd=pp+(n-1);
for(;;){
while(pb<=pc && (r=cmp_nc(pb,pv))<=0){
if(r==0){
swap(pa,pb,tmp);
pa++;
}
pb++;
}
while(pc>=pb && (r=cmp_nc(pc,pv))>=0){
if(r==0){
swap(pc,pd,tmp);
pd--;
}
pc--;
}
if(pb>pc)
break;
swap(pb,pc,tmp);
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 21

pb++;
pc--;
}
La situazione del vettore dopo la tripartizione risulta:
= < > =

Vengono allora riportati al centro del vettore gli elementi uguali al pivot:
pn=pp+n;
s=min(pa-pp,pb-pa);
pv=pp;
while(s>0){
swap(pv,pb-s,tmp);
pv++;
s--;
}
s=min(pd-pc,pn-pd-1);
while(s>0){
swap(pb,pn-s,tmp);
pb++;
s--;
}
La macro min ci assicura di effettuare il minor numero di scambi in questa
operazione:
#define min(a,b) \
(a)-(b) < 0 ? (a) : (b)
La tripartizione del vettore risulta allora evidente:
< = >

A questo punto si passa all’approccio ricorsivo, evocando la funzione pp_sort


per i due rami del vettore:
if((s=pb-pa) > 1)
pp_sort(pp,s);
if((s=pd-pc) > 1)
pp_sort(pn-s,s);
Il controllo condizionale if(s > 1) previene chiamate a funzione che spre-
cherebbero soltanto tempo.
La funzione completa si trova all’interno del programma 003fast.c in ap-
pendice C.

3.5 Compilazione
I tre file sorgente sono stati compilati utilizzando il programma GNU Compiler
Collection version 5.4.0 (GCC 5.4.0). GCC è il compilatore ufficiale dei sistemi
GNU/Linux6 .
6 gcc.gnu.org
CAPITOLO 3. ALGORITMI E IMPLEMENTAZIONI 22

Tabella 3.1: Dimensioni dei file.

filename file.c [byte] file.out [byte]


base 6076 14600
trunc 5860 14600
fast 13278 22936

Il compilatore traduce il codice sorgente (scritto in C) in codice macchina


eseguibile. Inoltre è possibile passare altri parametri al compilatore, così da
gestire vari aspetti e dettagli del processo di generazione del file eseguibile.
In questo caso tutto l’interesse è rivolto a ridurre i tempi di esecuzione,
pertanto si ricorre all’opzione -Ofast che ottimizza la velocità del programma
senza particolare cura per le dimensioni del codice, per il tempo di compilazione
e per l’eventualità di eseguire un debug.
In tabella 3.1 sono riportate le dimensioni dei file sorgente e degli eseguibili
dopo la compilazione. Come si vede, le dimensioni del programma 003fast.c
dopo la compilazione sono maggiori del 57% rispetto alle dimensioni degli altri
due eseguibili.
CAPITOLO 4. SIMULAZIONI E RISULTATI 23

Capitolo 4

Simulazioni e risultati

4.1 Spostamento quadratico medio


Il programma 003fast.c non deve soltanto essere più rapido del programma
base, ma deve anche essere in grado di portare agli stessi risultati. Per questo
si confrontano sia i tempi di esecuzione dei tre programmi che le misure di una
stessa osservabile per verificarne la consistenza.
L’osservabile scelta è lo spostamento quadratico medio (MSD, mean square
displacement), che può essere facilmente calcolato utilizzando la sua definizione:
1 X
MSD = |~ri (t) − ~ri (0)|2 . (4.1)
N i

La porzione di codice che si occupa del calcolo del MSD è uguale per tutti e
tre i programmi e si trova subito dopo la funzione evolve:

MSD = 0;
for(i=0;i<opt.N;i++){
z[0] = opt.L*p[i].l[0] + p[i].x[0] - x0[i][0];
z[1] = opt.L*p[i].l[1] + p[i].x[1] - x0[i][1];
MSD += z[0]*z[0] + z[1]*z[1];
}
MSD = MSD/opt.N;
Ad ogni intervallo temporale, i valori di tempo, MSD, energia totale, cinetica
e potenziale vengono trascritti su file utilizzando la funzione fwrite:
fwrite(&t,sizeof(double),1,fp);
fwrite(&MSD,sizeof(double),1,fp);
fwrite(E,sizeof(double),3,fp);
In questo modo il computer non deve effettuare la conversione dei dati da
binario ad ASCII, risultando notevolmente più veloce nel processo di salvataggio.
Il MSD contiene informazioni sulla diffusività atomica. Se il sistema è so-
lido, il MSD satura ad un valore finito. Se il sistema è liquido, il MSD cresce
linearmente nel tempo.
CAPITOLO 4. SIMULAZIONI E RISULTATI 24

Ricordiamo, per completezza, che in questi casi risulta conveniente caratte-


rizzare il comportamento del sistema in termini di pendenza, rappresentata dal
coefficiente di diffusione D:
(
1 6 per sistemi 3D
D = lim MSD, con K = (4.2)
t→∞ K t 4 per sistemi 2D

4.2 Misure
La macchina su cui sono stati fatti girare i programmi è dotata di un processore
Intel Core i5-4210U CPU @ 1.70 GHz × 4 e di una RAM da 5.7 GiB.
Le condizioni iniziali dei sistemi atomici studiati nelle simulazioni vengono
generate con l’utilizzo del programma box.c (appendice D). Questo è dotato
di un’interfaccia grafica che permette di osservare l’evoluzione del sistema di-
rettamente sullo schermo e di controllarne la temperatura. Una volta raggiunto
l’equilibrio termodinamico alla temperatura desiderata, le posizioni, le velocità
e tutti gli altri dati relativi agli atomi e al sistema vengono salvati in un file dal
quale caricheremo le condizioni iniziali per le nostre simulazioni.
Come unità di misura delle lunghezze si utilizza σ = 1. Invece, per mante-
nere la generalità delle simulazioni, le energie, la temperatura e la massa sono
adimensionali.
Per la scatola sono state scelte due grandezze, L = 20 e L = 100. In tutte le
simulazioni, la massa degli atomi è uguale a mi = 1 e il raggio di cutoff Rc = 4.
L’intervallo temporale di integrazione è uguale a ∆t = 0.01 s.
La durata delle simulazioni è stata misurata grazie al comando time di Unix,
evocato dalla funzione di sistema system all’interno del programma measure.c
(in appendice E la versione per L = 20).

4.2.1 L = 20
Sono stati simulati, per TMAX = 100000 s (∼ 28 ore), sistemi con densità nume-
rica1 pari a ρ = {0.25, 0.50, 0.75, 1.00}. Per ognuna di esse sono stati misurati
i MSD alle temperature T = {0.1, 0.2, 0.5, 1.0, 2.0}.
Come si vede nelle figure 4.1, 4.2, 4.3 e 4.4, per tutte le simulazioni a T = 2.0,
la temperatura non rimane costante. Questo è dovuto alla scelta di un intervallo
temporale di integrazione troppo grande che non permette un campionamento
adeguato dell’evoluzione del sistema. Gli atomi a quella temperatura sono ab-
bastanza veloci da percorrere, in un intervallo temporale ∆t = 0.01 s, spazi
sufficienti a oltrepassare la barriera del potenziale, trovandosi sottoposti a for-
ze di repulsione elevatissime. Di conseguenza, hanno accelerazioni elevatissime
che portano a velocità altrettanto elevate. Le variabili double non sono più
sufficienti a rappresentare valori numerici così grandi. Si innesca, quindi, un
effetto valanga che fa esplodere la temperatura del sistema. Per questa ragione
la misura del MSD per T = 2.0 è stata esclusa dal confronto.
1 Siamo N
nel caso 2D, quindi ρ = .
σ2
CAPITOLO 4. SIMULAZIONI E RISULTATI 25

base trunc fast


3 3 3

2 2 2

1 1 1

0.5 0.5 0.5

0.2 0.2 0.2


0.1 0.1 0.1

0 105 0 105 0 105


t (s) t (s) t (s)

Figura 4.1: Temperature per ρ = 0.25.

base trunc fast


3 3 3

2 2 2

1 1 1

0.5 0.5 0.5

0.2 0.2 0.2


0.1 0.1 0.1

0 105 0 105 0 105


t (s) t (s) t (s)

Figura 4.2: Temperature per ρ = 0.50.


CAPITOLO 4. SIMULAZIONI E RISULTATI 26

base trunc fast


3 3 3

2 2 2

1 1 1

0.5 0.5 0.5

0.2 0.2 0.2


0.1 0.1 0.1

0 105 0 105 0 105


t (s) t (s) t (s)

Figura 4.3: Temperature per ρ = 0.75.

base trunc fast


3 3 3

2 2 2

1 1 1

0.5 0.5 0.5

0.2 0.2 0.2


0.1 0.1 0.1

0 105 0 105 0 105


t (s) t (s) t (s)

Figura 4.4: Temperature per ρ = 1.00.


CAPITOLO 4. SIMULAZIONI E RISULTATI 27

T = 0.1 T = 0.2
7.0 10+3 1.0 10+4
base 6.0 10+3
base 9.0 10+3
trunc trunc 8.0 10+3
5.0 10+3 7.0 10+3
fast 4.0 10+3
fast 6.0 10+3
5.0 10+3
3.0 10+3 4.0 10+3
2.0 10+3 3.0 10+3
1.0 10+3 2.0 10+3
1.0 10+3
0.0 10+0 0.0 10+0
0 105 0 105
t (s) t (s)

T = 0.5 T = 1.0
2.5 10+5 6.0 10+5
base base
trunc 2.0 10+5 trunc 5.0 10+5
fast fast 4.0 10+5
1.5 10+5
3.0 10+5
1.0 10+5
2.0 10+5
5.0 10+4 1.0 10+5
0.0 10+0 0.0 10+0
0 105 0 105
t (s) t (s)

Figura 4.5: MSD per ρ = 0.25.

T = 0.1 T = 0.2
4.5 10+2 4.5 10+1
base 4.0 10+2 base 4.0 10+1
trunc 3.5 10+2 trunc 3.5 10+1
fast 3.0 10+2 fast 3.0 10+1
2.5 10+2 2.5 10+1
2.0 10+2 2.0 10+1
1.5 10+2 1.5 10+1
1.0 10+2 1.0 10+1
5.0 10+1 5.0 10+0
0.0 10+0 0.0 10+0
0 105 0 105
t (s) t (s)

T = 0.5 T = 1.0
8.0 10+4 1.6 10+5
base 7.0 10+4 base 1.4 10+5
trunc 6.0 10+4 trunc 1.2 10+5
fast 5.0 10+4 fast 1.0 10+5
4.0 10+4 8.0 10+4
3.0 10+4 6.0 10+4
2.0 10+4 4.0 10+4
1.0 10+4 2.0 10+4
0.0 10+0 0.0 10+0
0 105 0 105
t (s) t (s)

Figura 4.6: MSD per ρ = 0.50.


CAPITOLO 4. SIMULAZIONI E RISULTATI 28

T = 0.1 T = 0.2
1.8 10+0 3.0 10+1
base 1.6 10+0 base
trunc 1.4 10+0 trunc 2.5 10+1
fast 1.2 10+0 fast 2.0 10+1
1.0 10+0 1.5 10+1
8.0 10−1
6.0 10−1 1.0 10+1
4.0 10−1 5.0 10+0
2.0 10−1
0.0 10+0 0.0 10+0
0 105 0 105
t (s) t (s)

T = 0.5 T = 1.0
2.5 10+4 5.0 10+4
base base 4.5 10+4
trunc 2.0 10+4 trunc 4.0 10+4
fast fast 3.5 10+4
1.5 10+4 3.0 10+4
2.5 10+4
1.0 10+4 2.0 10+4
1.5 10+4
5.0 10+3 1.0 10+4
5.0 10+3
0.0 10+0 0.0 10+0
0 105 0 105
t (s) t (s)

Figura 4.7: MSD per ρ = 0.75.

T = 0.1 T = 0.2
6.0 10+0 2.0 10−1
base base 1.8 10−1
trunc 5.0 10+0 trunc 1.6 10−1
fast 4.0 10+0 fast 1.4 10−1
1.2 10−1
3.0 10+0 1.0 10−1
8.0 10−2
2.0 10+0 6.0 10−2
1.0 10+0 4.0 10−2
2.0 10−2
0.0 10+0 0.0 10+0
0 105 0 105
t (s) t (s)

T = 0.5 T = 1.0
1.2 10+2 7.0 10+1
base base 6.0 10+1
trunc 1.0 10+2 trunc
8.0 10+1 5.0 10+1
fast fast 4.0 10+1
6.0 10+1
3.0 10+1
4.0 10+1 2.0 10+1
2.0 10+1 1.0 10+1
0.0 10+0 0.0 10+0
0 105 0 105
t (s) t (s)

Figura 4.8: MSD per ρ = 1.00.


CAPITOLO 4. SIMULAZIONI E RISULTATI 29

Tabella 4.1: Tempi di esecuzione per L = 20.

N ρ T tbase ttrunc tf ast


0.1 09m 28.56s 07m 43.59s 05m 02.24s
0.2 09m 23.88s 07m 34.02s 05m 02.22s
100 0.25
0.5 10m 32.29s 07m 43.16s 03m 20.51s
1.0 11m 06.67s 07m 53.67s 02m 55.36s
0.1 43m 39.32s 30m 46.05s 12m 54.84s
0.2 49m 36.86s 38m 44.43s 13m 29.01s
200 0.50
0.5 48m 54.22s 34m 20.35s 11m 03.87s
1.0 49m 50.72s 34m 27.91s 10m 31.50s
0.1 1h 51m 27s 1h 16m 27s 22m 34.25s
0.2 1h 55m 17s 1h 20m 14s 22m 16.75s
300 0.75
0.5 1h 54m 19s 1h 19m 31s 22m 05.44s
1.0 1h 54m 35s 1h 20m 44s 22m 09.51s
0.1 3h 10m 31s 2h 08m 56s 39m 39.89s
0.2 3h 13m 47s 2h 10m 41s 39m 44.17s
400 1.00
0.5 3h 12m 21s 2h 09m 09s 39m 34.73s
1.0 3h 19m 00s 2h 15m 16s 39m 21.54s

Gli altri MSD sono mostrati nelle figure 4.5, 4.6, 4.7 e 4.8. Come si vede,
gli andamenti sono piuttosto consistenti tra loro. Per i programmi trunc.c
e fast.c ci aspetteremmo però andamenti identici, dopotutto hanno lo stesso
valore di Rc . Le discrepanze tra i due MSD sono dovute all’aritmetica in virgola
mobile che, a differenza dell’aritmetica reale, non gode delle proprietà associativa
e distributiva: l’ordine con cui vengono eseguite le operazioni algebriche influisce
sul risultato finale.
La tabella 4.1 mostra i tempi che impiegano i tre programmi a far evolvere
i diversi sistemi per la durata di simulazione TMAX = 100000 s.
Come si vede, il programma fast.c ha sempre prestazioni superiori agli altri
due e, all’aumentare del numero N degli atomi, il divario aumenta.

4.2.2 L = 100
Per TMAX = 1000 s (∼ 17 minuti), vengono simulati sistemi con densità numerica
pari a ρ = {0.25, 0.50, 0.75}. Per ognuna di esse sono stati misurati i MSD alle
temperature T = {0.1, 0.2, 0.5, 1.0, 2.0}.
Come si vede nelle figure 4.9, 4.10 e 4.11, la temperatura rimane costante
per tutti i valori di T .
I MSD sono mostrati nelle figure 4.12, 4.13 e 4.14. Anche in questo caso,
gli andamenti sono piuttosto consistenti tra loro. Le discrepanze sono proba-
bilmente dovute, oltre ai motivi discussi per L = 20, anche ad una durata delle
simulazione più piccola di 2 ordini di grandezza.
CAPITOLO 4. SIMULAZIONI E RISULTATI 30

base trunc fast


2 2 2

1 1 1

0.5 0.5 0.5

0.2 0.2 0.2


0.1 0.1 0.1

0 103 0 103 0 103


t (s) t (s) t (s)

Figura 4.9: Temperature per ρ = 0.25.

base trunc fast


2 2 2

1 1 1

0.5 0.5 0.5

0.2 0.2 0.2


0.1 0.1 0.1

0 103 0 103 0 103


t (s) t (s) t (s)

Figura 4.10: Temperature per ρ = 0.50.

base trunc fast


2 2 2

1 1 1

0.5 0.5 0.5

0.2 0.2 0.2


0.1 0.1 0.1

0 103 0 103 0 103


t (s) t (s) t (s)

Figura 4.11: Temperature per ρ = 0.75.


CAPITOLO 4. SIMULAZIONI E RISULTATI 31

T = 0.1 T = 0.2
7.0 10+0 6.0 10+1
Basic Basic
Trunc 6.0 10+0 Trunc 5.0 10+1
Fast Fast
5.0 10+0
4.0 10+1
4.0 10+0
3.0 10+1
3.0 10+0
2.0 10+1
2.0 10+0
1.0 10+0 1.0 10+1

0.0 10+0 0.0 10+0


0 103 0 103
t (s) t (s)

T = 0.5 T = 1.0
3.0 10+3 5.0 10+3
Basic Basic
Trunc Trunc 4.5 10+3
2.5 10+3 4.0 10+3
Fast Fast
2.0 10+3 3.5 10+3
3.0 10+3
1.5 10+3 2.5 10+3
2.0 10+3
1.0 10+3 1.5 10+3
5.0 10+2 1.0 10+3
5.0 10+2
0.0 10+0 0.0 10+0
0 103 0 103
t (s) t (s)

T = 2.0
7.0 10+3
Basic
Trunc 6.0 10+3
Fast
5.0 10+3
4.0 10+3
3.0 10+3
2.0 10+3
1.0 10+3
0.0 10+0
0 103
t (s)

Figura 4.12: MSD per ρ = 0.25.


CAPITOLO 4. SIMULAZIONI E RISULTATI 32

T = 0.1 T = 0.2
2.0 10−1 2.5 10+0
Basic Basic
Trunc 1.8 10−1 Trunc
Fast 1.6 10−1 Fast 2.0 10+0
1.4 10−1
1.2 10−1 1.5 10+0
1.0 10−1
8.0 10−2 1.0 10+0
6.0 10−2
4.0 10−2 5.0 10−1
2.0 10−2
0.0 10+0 0.0 10+0
0 103 0 103
t (s) t (s)

T = 0.5 T = 1.0
1.2 10+3 2.0 10+3
Basic Basic
Trunc Trunc 1.8 10+3
1.0 10+3 1.6 10+3
Fast Fast
8.0 10+2 1.4 10+3
1.2 10+3
6.0 10+2 1.0 10+3
8.0 10+2
4.0 10+2 6.0 10+2
2.0 10+2 4.0 10+2
2.0 10+2
0.0 10+0 0.0 10+0
0 103 0 103
t (s) t (s)

T = 2.0
3.5 10+3
Basic
Trunc 3.0 10+3
Fast
2.5 10+3
2.0 10+3
1.5 10+3
1.0 10+3
5.0 10+2
0.0 10+0
0 103
t (s)

Figura 4.13: MSD per ρ = 0.50.


CAPITOLO 4. SIMULAZIONI E RISULTATI 33

T = 0.1 T = 0.2
3.5 10−2 2.0 10−1
Basic Basic
Trunc Trunc 1.8 10−1
3.0 10−2
Fast Fast 1.6 10−1
2.5 10−2 1.4 10−1
2.0 10−2 1.2 10−1
1.0 10−1
1.5 10−2 8.0 10−2
1.0 10−2 6.0 10−2
4.0 10−2
5.0 10−3
2.0 10−2
0.0 10+0 0.0 10+0
0 103 0 103
t (s) t (s)

T = 0.5 T = 1.0
3.5 10+2 7.0 10+2
Basic Basic
Trunc 3.0 10+2 Trunc 6.0 10+2
Fast Fast
2.5 10+2 5.0 10+2
2.0 10+2 4.0 10+2
1.5 10+2 3.0 10+2
1.0 10+2 2.0 10+2
5.0 10+1 1.0 10+2
0.0 10+0 0.0 10+0
0 103 0 103
t (s) t (s)

T = 2.0
1.4 10+3
Basic
Trunc 1.2 10+3
Fast
1.0 10+3
8.0 10+2
6.0 10+2
4.0 10+2
2.0 10+2
0.0 10+0
0 103
t (s)

Figura 4.14: MSD per ρ = 0.75.


CAPITOLO 4. SIMULAZIONI E RISULTATI 34

Tabella 4.2: Tempi di esecuzione per L = 100.

N ρ T tbase ttrunc tf ast


0.1 58m 30.99s 21m 47.94s 02m 05.28s
0.2 58m 47.69s 22m 33.44s 02m 01.80s
2500 0.25 0.5 1h 17m 44s 46m 50.50s 01m 14.67s
1.0 1h 20m 46s 47m 01.23s 00m 55.20s
2.0 1h 20m 47s 46m 54.92s 00m 54.31s
0.1 5h 11m 30s 3h 04m 37s 04m 19.34s
0.2 5h 12m 10s 3h 05m 18s 04m 13.46s
5000 0.50 0.5 5h 29m 22s 3h 09m 41s 03m 10.36s
1.0 5h 23m 37s 3h 07m 54s 02m 54.01s
2.0 5h 23m 09s 3h 08m 02s 02m 53.86s
0.1 11h 15m 30s 6h 06m 06s 06m 33.28s
0.2 11h 18m 21s 6h 09m 30s 06m 26.69s
7500 0.75 0.5 11h 53m 26s 6h 49m 55s 05m 54.66s
1.0 11h 50m 29s 6h 44m 17s 05m 55.37s
2.0 11h 35m 05s 6h 30m 27s 05m 55.57s

La tabella 4.2 mostra i tempi che impiegano i tre programmi a far evolvere
i diversi sistemi per la durata di simulazione TMAX = 1000 s.
La superiorità del programma fast.c qui è ancora più evidente che nel caso
di L = 20. Il diavario tra le prestazioni dei tre programmi all’aumentare del
numero N degli atomi è impressionante.
Da questo si intuisce quanto sarebbe stato proibitivo, per L = 100, studiare
il comportamento del sistema con ρ = 1.00 (N = 10000) con i programmmi
base.c e trunc.c, ed anche impostare TMAX = 100000 s.
CAPITOLO 5. CONCLUSIONI 35

Capitolo 5

Conclusioni

Le simulazioni e le misure effettuate mostrano l’efficienza del programma fast.c


per quanto riguarda prestazioni e consistenza dei risultati.
L’aumento delle dimensioni del 57% dell’eseguibile rispetto agli altri due pro-
grammi è ampiamente trascurabile se confrontato con l’aumento della velocità
di esecuzione.
L’algoritmo sviluppato per il ciclo delle forze è applicabile a tutti i potenziali
con raggio d’azione finito e, con le dovute accortezze, può essere adattato anche
al caso 3D.
APPENDICE A. 001BASE.C 36

Appendice A

001base.c

#include <stdio.h>
#include <stdlib.h>

#define TMAX 100000

typedef struct _startopt{


int func;// funzione che evolve il sistema
int N;// numero particelle
double R;// raggio potenziale troncato
double R2;// R^2
int c;// numero di colonne/righe
int C;// numero di celle
double L;// dimensione scatola quadrata
double T;// temperatura iniziale
double m0;// massa della particella p[0]
double mi;// massa della particella p[i]
double dt;// step temporali
double Vt;// V(R) da aggiungere per il potenziale troncato
} startopt;

typedef struct _particle{


double x[2];//-----posizione
double v[2];//-----velocità
double f[2];//-----forza
int l[2];//--------indice del reticolo periodico
int nc;//----------numero di cella
} particle;

/* carica le posizioni e le velocità iniziali da file */


particle *load(char *,startopt *);

/* fa evolvere il sistema nel tempo con le


condizioni periodiche al contorno (pbc) */
void evolve(particle *,startopt,double *);
APPENDICE A. 001BASE.C 37

int main(int argc,char **argv){


FILE *fp;
double MSD = 0;
double z[2];
double *E;
E=calloc(3,sizeof(double));
double **x0;
int tmax = TMAX;
startopt opt;
particle *p;
int i;
double t;
int it;
p = load(argv[1],&opt);
//printf("%f\n",opt.dt);
fp = fopen(argv[2],"w");

x0 = calloc(opt.N,sizeof(double*));
for(i=0;i<opt.N;i++){
x0[i] = calloc(2,sizeof(double));
x0[i][0] = p[i].x[0];
x0[i][1] = p[i].x[1];
}
it = 0;// indice conteggio step temporali
t = 0;// variabile tempo
//fprintf(fp,"%f %f %f %f %f\n",t,MSD,E[0],E[1],E[2]);

//printf("%f\n",t);
fwrite(&t,sizeof(double),1,fp);
fwrite(&MSD,sizeof(double),1,fp);
fwrite(E,sizeof(double),3,fp);
while(t < tmax){
it++;
if(it%100 == 0)
t = it * opt.dt;
else
t += opt.dt;
evolve(p,opt,E);
MSD = 0;
for(i=0;i<opt.N;i++){
z[0] = opt.L*p[i].l[0] + p[i].x[0] - x0[i][0];
z[1] = opt.L*p[i].l[1] + p[i].x[1] - x0[i][1];
MSD += z[0]*z[0] + z[1]*z[1];
}
MSD = MSD/opt.N;
fwrite(&t,sizeof(double),1,fp);
fwrite(&MSD,sizeof(double),1,fp);
fwrite(E,sizeof(double),3,fp);
}
APPENDICE A. 001BASE.C 38

fclose(fp);
return 0;
}

void evolve(particle *p,startopt opt,double *E){


int i,a,b;
double r[2];
double r2,r_2,r_6,ff;

E[0]=E[1]=E[2]=0;

// calcolo di x(t + dt) = x(t) + v(t + dt/2) * dt


// con v(t + dt/2) = v(t) + F(t)/m * dt/2
// sì da considerare correttamente gli urti con le pareti della scatola
p[0].x[0] += (p[0].v[0] + p[0].f[0]/opt.m0*0.5*opt.dt)*opt.dt;
p[0].x[1] += (p[0].v[1] + p[0].f[1]/opt.m0*0.5*opt.dt)*opt.dt;

//reticolo periodico
if(p[0].x[0] < 0){
p[0].l[0]--;
p[0].x[0] += opt.L;
}
else if(p[0].x[0] > opt.L){
p[0].l[0]++;
p[0].x[0] -= opt.L;
}
if(p[0].x[1] < 0){
p[0].l[1]--;
p[0].x[1] += opt.L;
}
else if(p[0].x[1] > opt.L){
p[0].l[1]++;
p[0].x[1] -= opt.L;
}

// calcolo di v(t + dt/2) e azzeramento forze


p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
p[0].f[0] = p[0].f[1] = 0;

for (i = 1; i < opt.N; i++){


// calcolo di x(t + dt) = x(t) + v(t + dt/2) * dt
// con v(t + dt/2) = v(t) + F(t)/m * dt/2
// sì da considerare correttamente gli urti con le pareti della scatola
p[i].x[0] += (p[i].v[0] + p[i].f[0]/opt.mi*0.5*opt.dt)*opt.dt;
p[i].x[1] += (p[i].v[1] + p[i].f[1]/opt.mi*0.5*opt.dt)*opt.dt;

//reticolo periodico
if(p[i].x[0] < 0){
APPENDICE A. 001BASE.C 39

p[i].l[0]--;
p[i].x[0] += opt.L;
}
else if(p[i].x[0] > opt.L){
p[i].l[0]++;
p[i].x[0] -= opt.L;
}
if(p[i].x[1] < 0){
p[i].l[1]--;
p[i].x[1] += opt.L;
}
else if(p[i].x[1] > opt.L){
p[i].l[1]++;
p[i].x[1] -= opt.L;
}

// calcolo di v(t + dt/2) e azzeramento forze


p[i].v[0] += p[i].f[0]/opt.mi*0.5*opt.dt;
p[i].v[1] += p[i].f[1]/opt.mi*0.5*opt.dt;
p[i].f[0] = p[i].f[1] = 0;
}

//////////////////////////////

// calcolo di F(t+dt)
for(a = 0; a < opt.N-1; a++){
for(b = a+1; b < opt.N; b++){
r[0] = p[b].x[0] - p[a].x[0];
if(r[0] > opt.L/2.)
r[0] = r[0] - opt.L;
else if(r[0] < -opt.L/2.)
r[0] = r[0] + opt.L;
r[1] = p[b].x[1] - p[a].x[1];
if(r[1] > opt.L/2.)
r[1] = r[1] - opt.L;
else if(r[1] < -opt.L/2.)
r[1] = r[1] + opt.L;
r2 = r[0]*r[0] + r[1]*r[1];
if(r2 < opt.R2){
r_2 = 1./r2;
r_6 = r_2*r_2*r_2;
ff = -24*r_6*(2*r_6-1)*r_2;
p[a].f[0] += ff*r[0];
p[a].f[1] += ff*r[1];
p[b].f[0] -= ff*r[0];
p[b].f[1] -= ff*r[1];
E[1] += (4*r_6*(r_6-1) - opt.Vt);
}
}
}
APPENDICE A. 001BASE.C 40

// calcolo di v(t + dt)


p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
E[2] += opt.m0*(p[0].v[0]*p[0].v[0] + p[0].v[1]*p[0].v[1])/2;

for(i = 1; i < opt.N; i++){


p[i].v[0] += p[i].f[0]/opt.mi*0.5*opt.dt;
p[i].v[1] += p[i].f[1]/opt.mi*0.5*opt.dt;
E[2] += opt.mi*(p[i].v[0]*p[i].v[0] + p[i].v[1]*p[i].v[1])/2;
}
E[0] = E[1] + E[2];
E[0] = E[0]/opt.N;
E[1] = E[1]/opt.N;
E[2] = E[2]/opt.N;
}

particle *load(char *filename,startopt *popt){


FILE *sp;
particle *p;
double dm2;
double dm6;
int i;
sp = fopen(filename,"r");
if(fread(popt,sizeof(struct _startopt),1,sp) != 1){
fprintf(stdout,"fread error");
exit(-1);
}
p = calloc(popt->N,sizeof(struct _particle));
if(fread(p,sizeof(struct _particle),popt->N,sp) != popt->N){
fprintf(stdout,"fread error");
exit(-1);
}

for(i=0;i<popt->N;i++)
p[i].l[0] = p[i].l[1] = 0;

popt->R2 = (popt->L * popt->L)/4;

dm2 = 1./popt->R2;
dm6 = dm2 * dm2 * dm2;

popt->Vt = 4*dm6*(dm6-1);

fclose(sp);
return p;
}
APPENDICE B. 002TRUNC.C 41

Appendice B

002trunc.c

#include <stdio.h>
#include <stdlib.h>

#define TMAX 100000

typedef struct _startopt{


int func;// funzione che evolve il sistema
int N;// numero particelle
double R;// raggio potenziale troncato
double R2;// R^2
int c;// numero di colonne/righe
int C;// numero di celle
double L;// dimensione scatola quadrata
double T;// temperatura iniziale
double m0;// massa della particella p[0]
double mi;// massa della particella p[i]
double dt;// step temporali
double Vt;// V(R) da aggiungere per il potenziale troncato
} startopt;

typedef struct _particle{


double x[2];//-----posizione
double v[2];//-----velocità
double f[2];//-----forza
int l[2];//--------indice del reticolo periodico
int nc;//----------numero di cella
} particle;

/* carica le posizioni e le velocità iniziali da file */


particle *load(char *,startopt *);

/* fa evolvere il sistema nel tempo con le


condizioni periodiche al contorno (pbc) */
void evolve(particle *,startopt,double *);
APPENDICE B. 002TRUNC.C 42

int main(int argc,char **argv){


FILE *fp;
double MSD = 0;
double z[2];
double *E;
E=calloc(3,sizeof(double));
double **x0;
int tmax = TMAX;
startopt opt;
particle *p;
int i;
double t;
int it;
p = load(argv[1],&opt);
fp = fopen(argv[2],"w");

x0 = calloc(opt.N,sizeof(double*));
for(i=0;i<opt.N;i++){
x0[i] = calloc(2,sizeof(double));
x0[i][0] = p[i].x[0];
x0[i][1] = p[i].x[1];
}
it = 0;// indice conteggio step temporali
t = 0;// variabile tempo

fwrite(&t,sizeof(double),1,fp);
fwrite(&MSD,sizeof(double),1,fp);
fwrite(E,sizeof(double),3,fp);
while(t < tmax){
it++;
if(it%100 == 0)
t = it * opt.dt;
else
t += opt.dt;
evolve(p,opt,E);
MSD = 0;
for(i=0;i<opt.N;i++){
z[0] = opt.L*p[i].l[0] + p[i].x[0] - x0[i][0];
z[1] = opt.L*p[i].l[1] + p[i].x[1] - x0[i][1];
MSD += z[0]*z[0] + z[1]*z[1];
}
MSD = MSD/opt.N;

fwrite(&t,sizeof(double),1,fp);
fwrite(&MSD,sizeof(double),1,fp);
fwrite(E,sizeof(double),3,fp);

}
fclose(fp);
APPENDICE B. 002TRUNC.C 43

return 0;
}

void evolve(particle *p,startopt opt,double *E){


int i,a,b;
double r[2];
double r2,r_2,r_6,ff;

E[0]=E[1]=E[2]=0;

// calcolo di x(t + dt) = x(t) + v(t + dt/2) * dt


// con v(t + dt/2) = v(t) + F(t)/m * dt/2
// sì da considerare correttamente gli urti con le pareti della scatola
p[0].x[0] += (p[0].v[0] + p[0].f[0]/opt.m0*0.5*opt.dt)*opt.dt;
p[0].x[1] += (p[0].v[1] + p[0].f[1]/opt.m0*0.5*opt.dt)*opt.dt;

//reticolo periodico
if(p[0].x[0] < 0){
p[0].l[0]--;
p[0].x[0] += opt.L;
}
else if(p[0].x[0] > opt.L){
p[0].l[0]++;
p[0].x[0] -= opt.L;
}
if(p[0].x[1] < 0){
p[0].l[1]--;
p[0].x[1] += opt.L;
}
else if(p[0].x[1] > opt.L){
p[0].l[1]++;
p[0].x[1] -= opt.L;
}

// calcolo di v(t + dt/2) e azzeramento forze


p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
p[0].f[0] = p[0].f[1] = 0;

for (i = 1; i < opt.N; i++){


// calcolo di x(t + dt) = x(t) + v(t + dt/2) * dt
// con v(t + dt/2) = v(t) + F(t)/m * dt/2
// sì da considerare correttamente gli urti con le pareti della scatola
p[i].x[0] += (p[i].v[0] + p[i].f[0]/opt.mi*0.5*opt.dt)*opt.dt;
p[i].x[1] += (p[i].v[1] + p[i].f[1]/opt.mi*0.5*opt.dt)*opt.dt;

//reticolo periodico
if(p[i].x[0] < 0){
p[i].l[0]--;
APPENDICE B. 002TRUNC.C 44

p[i].x[0] += opt.L;
}
else if(p[i].x[0] > opt.L){
p[i].l[0]++;
p[i].x[0] -= opt.L;
}
if(p[i].x[1] < 0){
p[i].l[1]--;
p[i].x[1] += opt.L;
}
else if(p[i].x[1] > opt.L){
p[i].l[1]++;
p[i].x[1] -= opt.L;
}

// calcolo di v(t + dt/2) e azzeramento forze


p[i].v[0] += p[i].f[0]/opt.mi*0.5*opt.dt;
p[i].v[1] += p[i].f[1]/opt.mi*0.5*opt.dt;
p[i].f[0] = p[i].f[1] = 0;
}

//////////////////////////////

// calcolo di F(t+dt)
for(a = 0; a < opt.N-1; a++){
for(b = a+1; b < opt.N; b++){
r[0] = p[b].x[0] - p[a].x[0];
if(r[0] > opt.L/2.)
r[0] = r[0] - opt.L;
else if(r[0] < -opt.L/2.)
r[0] = r[0] + opt.L;
r[1] = p[b].x[1] - p[a].x[1];
if(r[1] > opt.L/2.)
r[1] = r[1] - opt.L;
else if(r[1] < -opt.L/2.)
r[1] = r[1] + opt.L;
r2 = r[0]*r[0] + r[1]*r[1];
if(r2 < opt.R2){r_2 = 1./r2;
r_6 = r_2*r_2*r_2;
ff = -24*r_6*(2*r_6-1)*r_2;
p[a].f[0] += ff*r[0];
p[a].f[1] += ff*r[1];
p[b].f[0] -= ff*r[0];
p[b].f[1] -= ff*r[1];
E[1] += (4*r_6*(r_6-1) - opt.Vt);
}
}
}

// calcolo di v(t + dt)


APPENDICE B. 002TRUNC.C 45

p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
E[2] += opt.m0*(p[0].v[0]*p[0].v[0] + p[0].v[1]*p[0].v[1])/2;

for(i = 1; i < opt.N; i++){


p[i].v[0] += p[i].f[0]/opt.mi*0.5*opt.dt;
p[i].v[1] += p[i].f[1]/opt.mi*0.5*opt.dt;
E[2] += opt.mi*(p[i].v[0]*p[i].v[0] + p[i].v[1]*p[i].v[1])/2;

}
E[0] = E[1] + E[2];
E[0] = E[0]/opt.N;
E[1] = E[1]/opt.N;
E[2] = E[2]/opt.N;
}

particle *load(char *filename,startopt *popt){


FILE *sp;
particle *p;
int i;
sp = fopen(filename,"r");
if(fread(popt,sizeof(struct _startopt),1,sp) != 1){
fprintf(stdout,"fread error");
exit(-1);
}
p = calloc(popt->N,sizeof(struct _particle));
if(fread(p,sizeof(struct _particle),popt->N,sp) != popt->N){
fprintf(stdout,"fread error");
exit(-1);
}

for(i=0;i<popt->N;i++)
p[i].l[0] = p[i].l[1] = 0;

fclose(sp);
return p;
}
APPENDICE C. 003FAST.C 46

Appendice C

003fast.c

#include <stdio.h>
#include <stdlib.h>

#define TMAX 100000

#define forces(a,b) \
r2 = r[0]*r[0] + r[1]*r[1]; \
if(r2 < opt.R2){ \
r_2 = 1./r2; \
r_6 = r_2*r_2*r_2; \
ff = -24*r_6*(2*r_6-1)*r_2; \
pp[a]->f[0] += ff*r[0]; \
pp[a]->f[1] += ff*r[1]; \
pp[b]->f[0] -= ff*r[0]; \
pp[b]->f[1] -= ff*r[1]; \
E[1] += (4*r_6*(r_6-1) - opt.Vt); \
}

/* cmp_nc confronta i campi puntati dagli elementi del vettore,


vuole come argomenti gli indirizzi degli elementi del vsettore
e restituisce la differenza dei campi. */
#define cmp_nc(a,b) ((*(a))->nc - (*(b))->nc)

/* swap scambia gli elementi del vettore,


vuole come argomenti gli indirizzi degli elementi del vettore. */
#define swap(a,b,c) (c = (*(a)), (*(a)) = (*(b)), (*(b)) = c)

/* med3 utilizza la macro cmp_nc, quindi vuole come argomenti


gli indirizzi degli elementi del vettore.
restituisce l’indirizzo dell’elemento mediano tra i tre. */
#define med3(a,b,c) \
cmp_nc(a,b) < 0 ? \
(cmp_nc(b,c) < 0 ? b : cmp_nc(a,c) < 0 ? c : a) : \
(cmp_nc(b,c) > 0 ? b : cmp_nc(a,c) > 0 ? c : a)
APPENDICE C. 003FAST.C 47

/* min trova il minimo di due argomenti. */


#define min(a,b) \
(a)-(b) < 0 ? (a) : (b)

typedef struct _startopt{


int func;// funzione che evolve il sistema
int N;// numero particelle
double R;// raggio potenziale troncato
double R2;// R^2
int c;// numero di colonne/righe
int C;// numero di celle
double L;// dimensione scatola quadrata
double T;// temperatura iniziale
double m0;// massa della particella p[0]
double mi;// massa della particella p[i]
double dt;// step temporali
double Vt;// V(R) da aggiungere per il potenziale troncato
} startopt;

typedef struct _particle{


double x[2];//-----posizione
double v[2];//-----velocità
double f[2];//-----forza
int l[2];//--------indice del reticolo periodico
int nc;//----------numero di cella
} particle;

/* ordina il vettore pp[] in funzione degli nc delle particelle */


void pp_sort(particle **,int);

/* carica le posizioni e le velocità iniziali da file */


particle *load(char *,startopt *);

/* fa evolvere il sistema nel tempo con le


condizioni periodiche al contorno (pbc) */
void evolve(particle *,particle **,startopt,int *,double *);

int main(int argc,char **argv){


FILE *fp;
double MSD = 0;
double z[2];
double *E;
E=calloc(3,sizeof(double));
double **x0;
int tmax = TMAX;
startopt opt;
particle *p;
particle **pp;
int *cum;
int i;
APPENDICE C. 003FAST.C 48

double t;
int it;
p = load(argv[1],&opt);
pp = calloc(opt.N,sizeof(struct _particle *));
for(i = 0; i < opt.N; i++){
pp[i] = p+i;
}
cum = calloc(opt.C+1,sizeof(int));
fp = fopen(argv[2],"w");

x0 = calloc(opt.N,sizeof(double*));
for(i=0;i<opt.N;i++){
x0[i] = calloc(2,sizeof(double));
x0[i][0] = p[i].x[0];
x0[i][1] = p[i].x[1];
}
it = 0;// indice conteggio step temporali
t = 0;// variabile tempo

fwrite(&t,sizeof(double),1,fp);
fwrite(&MSD,sizeof(double),1,fp);
fwrite(E,sizeof(double),3,fp);
while(t < tmax){
it++;
if(it%100 == 0)
t = it * opt.dt;
else
t += opt.dt;
evolve(p,pp,opt,cum,E);
MSD = 0;
for(i=0;i<opt.N;i++){
z[0] = opt.L*p[i].l[0] + p[i].x[0] - x0[i][0];
z[1] = opt.L*p[i].l[1] + p[i].x[1] - x0[i][1];
MSD += z[0]*z[0] + z[1]*z[1];
}
MSD = MSD/opt.N;
fwrite(&t,sizeof(double),1,fp);
fwrite(&MSD,sizeof(double),1,fp);
fwrite(E,sizeof(double),3,fp);

}
fclose(fp);
return 0;
}

void evolve(particle *p,particle **pp,startopt opt,int *cum,double *E){


int cnt = 0;
int i,j,l,k,a,b;
double r[2];
APPENDICE C. 003FAST.C 49

double r2,r_2,r_6,ff;

E[0]=E[1]=E[2]=0;

// azzeramento celle
for(i = 1; i <= opt.C; i++){
cum[i] = 0;
}
// calcolo di x(t + dt) = x(t) + v(t + dt/2) * dt
// con v(t + dt/2) = v(t) + F(t)/m * dt/2
// sì da considerare correttamente gli urti con le pareti della scatola
p[0].x[0] += (p[0].v[0] + p[0].f[0]/opt.m0*0.5*opt.dt)*opt.dt;
p[0].x[1] += (p[0].v[1] + p[0].f[1]/opt.m0*0.5*opt.dt)*opt.dt;

//reticolo periodico
if(p[0].x[0] < 0){
p[0].l[0]--;
p[0].x[0] += opt.L;
}
else if(p[0].x[0] > opt.L){
p[0].l[0]++;
p[0].x[0] -= opt.L;
}
if(p[0].x[1] < 0){
p[0].l[1]--;
p[0].x[1] += opt.L;
}
else if(p[0].x[1] > opt.L){
p[0].l[1]++;
p[0].x[1] -= opt.L;
}

p[0].nc = (int)(p[0].x[0]/opt.R) + \
(int)(p[0].x[1]/opt.R)*opt.c;
cum[p[0].nc+1]++;

// calcolo di v(t + dt/2) e azzeramento forze


p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
p[0].f[0] = p[0].f[1] = 0;

for (i = 1; i < opt.N; i++){


// calcolo di x(t + dt) = x(t) + v(t + dt/2) * dt
// con v(t + dt/2) = v(t) + F(t)/m * dt/2
// sì da considerare correttamente gli urti con le pareti della scatola
p[i].x[0] += (p[i].v[0] + p[i].f[0]/opt.mi*0.5*opt.dt)*opt.dt;
p[i].x[1] += (p[i].v[1] + p[i].f[1]/opt.mi*0.5*opt.dt)*opt.dt;

//reticolo periodico
if(p[i].x[0] < 0){
APPENDICE C. 003FAST.C 50

p[i].l[0]--;
p[i].x[0] += opt.L;
}
else if(p[i].x[0] > opt.L){
p[i].l[0]++;
p[i].x[0] -= opt.L;
}
if(p[i].x[1] < 0){
p[i].l[1]--;
p[i].x[1] += opt.L;
}
else if(p[i].x[1] > opt.L){
p[i].l[1]++;
p[i].x[1] -= opt.L;
}

p[i].nc = (int)(p[i].x[0]/opt.R) + \
(int)(p[i].x[1]/opt.R)*opt.c;
cum[p[i].nc+1]++;

// calcolo di v(t + dt/2) e azzeramento forze


p[i].v[0] += p[i].f[0]/opt.mi*0.5*opt.dt;
p[i].v[1] += p[i].f[1]/opt.mi*0.5*opt.dt;
p[i].f[0] = p[i].f[1] = 0;
}

// cumulata
for(i = 1; i < opt.C + 1; i++){
cum[i] += cum[i-1];
}
/*ordinamento del vettore pp in funzione
della cella occupata dalla particella*/
pp_sort(pp,opt.N);
//////////////////////////////

// calcolo di F(t+dt)
for(j = 0; j < opt.c-1; j++){
//i = 0;
l = j*opt.c;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
APPENDICE C. 003FAST.C 51

r[1] = pp[b]->x[1] - pp[a]->x[1];


forces(a,b);
}
for(b = cum[l+2*opt.c-1]; b < cum[l+2*opt.c]; b++){
r[0] = (pp[b]->x[0] - opt.L) - pp[a]->x[0];//special
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(k = 0; k < 2; k++){
for(b = cum[l+opt.c+k]; b < cum[l+opt.c+k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
}//chiude ciclo a
for(i = 1; i < opt.c-1; i++){
l = j*opt.c + i;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(k = -1; k < 2; k++){
for(b = cum[l+opt.c+k]; b < cum[l+opt.c+k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
}//chiude ciclo a
}//chiude ciclo i
//i = opt.c-1;
l = j*opt.c + i;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
APPENDICE C. 003FAST.C 52

}
}
for(b = cum[l-opt.c+1]; b < cum[l-opt.c+2]; b++){
r[0] = (pp[b]->x[0] + opt.L) - pp[a]->x[0];//special
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(k = -1; k < 1; k++){
for(b = cum[l+opt.c+k]; b < cum[l+opt.c+k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = (pp[b]->x[0] + opt.L) - pp[a]->x[0];//special
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}//chiude cicla a
}//chiude ciclo j
//j = opt.c-1;
//i = 0;
l = j*opt.c;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(b = cum[opt.c-1]; b < cum[opt.c]; b++){
r[0] = (pp[b]->x[0] - opt.L) - pp[a]->x[0];//special
r[1] = (pp[b]->x[1] + opt.L) - pp[a]->x[1];//special
forces(a,b);
}
for(k = 0; k < 2; k++){
for(b = cum[k]; b < cum[k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = (pp[b]->x[1] + opt.L) - pp[a]->x[1];//special
forces(a,b);
}
}
APPENDICE C. 003FAST.C 53

}//chiude ciclo a
for(i = 1; i < opt.c-1; i++){
l = j*opt.c + i;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(k = -1; k < 2; k++){
for(b = cum[i+k]; b < cum[i+k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = (pp[b]->x[1] + opt.L) - pp[a]->x[1];//special
forces(a,b);
}
}
}//chiude ciclo a
}//chiude ciclo i
//i = opt.c-1;
l = j*opt.c + i;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l-opt.c+1]; b < cum[l-opt.c+2]; b++){
r[0] = (pp[b]->x[0] + opt.L) - pp[a]->x[0];//special
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(k = -1; k < 1; k++){
for(b = cum[opt.c-1+k]; b < cum[opt.c+k]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = (pp[b]->x[1] + opt.L) - pp[a]->x[1];//special
forces(a,b);
}
}
for(b = cum[0]; b < cum[1]; b++){
APPENDICE C. 003FAST.C 54

r[0] = (pp[b]->x[0] + opt.L) - pp[a]->x[0];//special


r[1] = (pp[b]->x[1] + opt.L) - pp[a]->x[1];//special
forces(a,b);
}
}//chiude ciclo a

if(cnt != opt.N){
fprintf(stdout,"cnt = %3d\n",cnt);
exit(-1);
}

// calcolo di v(t + dt)


p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
E[2] += opt.m0*(p[0].v[0]*p[0].v[0] + p[0].v[1]*p[0].v[1])/2;

for(i = 1; i < opt.N; i++){


p[i].v[0] += p[i].f[0]/opt.mi*0.5*opt.dt;
p[i].v[1] += p[i].f[1]/opt.mi*0.5*opt.dt;
E[2] += opt.mi*(p[i].v[0]*p[i].v[0] + p[i].v[1]*p[i].v[1])/2;

}
E[0] = E[1] + E[2];
E[0] = E[0]/opt.N;
E[1] = E[1]/opt.N;
E[2] = E[2]/opt.N;
}

particle *load(char *filename,startopt *popt){


FILE *sp;
particle *p;
int i;
sp = fopen(filename,"r");
if(fread(popt,sizeof(struct _startopt),1,sp) != 1){
fprintf(stdout,"fread error");
exit(-1);
}
p = calloc(popt->N,sizeof(struct _particle));
if(fread(p,sizeof(struct _particle),popt->N,sp) != popt->N){
fprintf(stdout,"fread error");
exit(-1);
}

for(i=0;i<popt->N;i++)
p[i].l[0] = p[i].l[1] = 0;

fclose(sp);
return p;
}
APPENDICE C. 003FAST.C 55

void pp_sort(particle **pp,int n){


particle **pa,**pb,**pc,**pd,**pl,**pm,**pn,**pv;
particle *v,*tmp;
int s,r;
if(n<7){
for(pm=pp+1;pm<pp+n;pm++)
for(pl=pm;pl>pp && cmp_nc(pl-1,pl)>0;pl--)
swap(pl,pl-1,tmp);
return;
}
pm=pp+(n/2);
if(n>7){
pl=pp;
pn=pp+(n-1);
if(n>40){
s=(n/8);
pl=med3(pl,pl+s,pl+2*s);
pm=med3(pm-s,pm,pm+s);
pn=med3(pn-2*s,pn-s,pn);
}
pm=med3(pl,pm,pn);
}
pv=&v;
v=*pm;
pa=pb=pp;
pc=pd=pp+(n-1);
for(;;){
while(pb<=pc && (r=cmp_nc(pb,pv))<=0){
if(r==0){
swap(pa,pb,tmp);
pa++;
}
pb++;
}
while(pc>=pb && (r=cmp_nc(pc,pv))>=0){
if(r==0){
swap(pc,pd,tmp);
pd--;
}
pc--;
}
if(pb>pc)
break;
swap(pb,pc,tmp);
pb++;
pc--;
}
pn=pp+n;
APPENDICE C. 003FAST.C 56

s=min(pa-pp,pb-pa);
pv=pp;
while(s>0){
swap(pv,pb-s,tmp);
pv++;
s--;
}
s=min(pd-pc,pn-pd-1);
while(s>0){
swap(pb,pn-s,tmp);
pb++;
s--;
}
if((s=pb-pa) > 1)
pp_sort(pp,s);
if((s=pd-pc) > 1)
pp_sort(pn-s,s);
}
Appendice D

box.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <math.h>//-----to compile -lm
#include <ncurses.h>//--to compile -lncurses

#define forces(a,b) \
r2 = r[0]*r[0] + r[1]*r[1]; \
if(r2 < opt.R2){ \
r_2 = 1./r2; \
r_6 = r_2*r_2*r_2; \
ff = -24*r_6*(2*r_6-1)*r_2; \
pp[a]->f[0] += ff*r[0]; \
pp[a]->f[1] += ff*r[1]; \
pp[b]->f[0] -= ff*r[0]; \
pp[b]->f[1] -= ff*r[1]; \
E[1] += (4*r_6*(r_6-1) - opt.Vt); \
}
int kbhit(void);//-----------------detect keyboard hit

/* swap scambia gli elementi del vettore,


vuole come argomenti gli indirizzi degli elementi del vettore. */
#define swap(a,b,c) (c = (*(a)), (*(a)) = (*(b)), (*(b)) = c)
/* cmp_nc confronta i campi puntati dagli elementi del vettore,
vuole come argomenti gli indirizzi degli elementi del vsettore
e restituisce la differenza dei campi. */
#define cmp_nc(a,b) ((*(a))->nc - (*(b))->nc)
/* med3 utilizza la macro cmp_nc, quindi vuole come argomenti
gli indirizzi degli elementi del vettore.
restituisce l’indirizzo dell’elemento mediano tra i tre. */
#define med3(a,b,c) \
cmp_nc(a,b) < 0 ? \
(cmp_nc(b,c) < 0 ? b : cmp_nc(a,c) < 0 ? c : a) : \

57
APPENDICE D. BOX.C 58

(cmp_nc(b,c) > 0 ? b : cmp_nc(a,c) > 0 ? c : a)


/* min trova il minimo di due argomenti. */
#define min(a,b) \
(a)-(b) < 0 ? (a) : (b)

typedef struct _startopt{


int func;// funzione che evolve il sistema
int N;// numero particelle
double R;// raggio potenziale troncato
double R2;// R^2
int c;// numero di colonne/righe
int C;// numero di celle
double L;// dimensione scatola quadrata
double T;// temperatura iniziale
double m0;// massa della particella p[0]
double mi;// massa della particella p[i]
double dt;// step temporali
double Vt;// V(R) da aggiungere per il potenziale troncato
} startopt;

typedef struct _particle{


double x[2];//-----posizione
double v[2];//-----velocità
double f[2];//-----forza
int l[2];//--------indice del reticolo periodico
int nc;//----------numero di cella
} particle;

/* gestisce le opzioni da linea di comando */


startopt opt_manager(int,char **);
/* inizializza le posizioni e le velocità delle particelle */
void init(particle *,startopt);
/* carica le posizioni e le velocità iniziali da file */
particle *load(char *,startopt *);
/* salva i dati delle particelle e le impostazioni*/
void save(particle *,startopt,char *);
/* fa evolvere il sistema nel tempo con le
condizioni periodiche al contorno (pbc) */
double *fast_trunc_evolve(particle *,particle **,startopt,int *,double *,double);
/* fa evolvere il sistema nel tempo con le
condizioni periodiche al contorno (pbc) */
double *basic_evolve(particle *,particle **,startopt,int *,double *,double);
/* ordina il vettore pp[] in funzione degli nc delle particelle */
void pp_sort(particle **,int);

void quit(int sig){//----chiude ncurses ed esce


endwin();
fprintf(stdout,"Segmentation fault (core dumped) %d\n",sig);
exit(-2);
}
APPENDICE D. BOX.C 59

/////////////////////////////////////////////////////////////////////

int main(int argc,char **argv){


signal(SIGSEGV,quit);
signal(SIGPIPE,quit);
signal(SIGFPE,quit);
double A=1;
double **data;
double *(*evolve)(particle *,particle **,startopt,int *,double *,double);
double *E;
E=calloc(3,sizeof(double));
int tmax = 100000;
startopt opt;
particle *p;
particle **pp;
int i;
int *cum;
float sz0,szi;
FILE *gp,*fp;
double t;
int it;
int fr;
fr = 1;

opt = opt_manager(argc,argv);
int Ndata = (int)(tmax/opt.dt);
data = calloc(Ndata+1,sizeof(double *));
for(i=0;i<Ndata+1;i++){
data[i] = calloc(2,sizeof(double));
}
/////////////////////////////
// ncurses //
/////////////////////////////
int cc;
int bath = 0;
initscr();
cbreak();
noecho();
nodelay(stdscr,TRUE);

/////////////////////////////

if((argc-optind) == 0){
p = calloc(opt.N,sizeof(struct _particle));
pp = calloc(opt.N,sizeof(struct _particle *));
for(i = 0; i < opt.N; i++){
pp[i] = p+i;
}
cum = calloc(opt.C+1,sizeof(int));
APPENDICE D. BOX.C 60

init(p,opt);
}
else
if((argc-optind) == 1){
char string[2];
p = load(argv[optind],&opt);
pp = calloc(opt.N,sizeof(struct _particle *));
for(i = 0; i < opt.N; i++){
pp[i] = p+i;
}
cum = calloc(opt.C+1,sizeof(int));
nodelay(stdscr,FALSE);
refresh();
printw("evolution function: ");
echo();
getstr(string);
opt.func = atoi(string);
move(0,0);
noecho();
nodelay(stdscr,TRUE);
refresh();
}
else{
fprintf(stderr,"\nwrong command line\nto load file use: \
%s file_name\nto look at the options: %s -h\n",argv[0],argv[0]);
return -1;
}
printw("R = %.2f; L = %.2f; N = %d; rho = %.2f; \
",opt.R,opt.L,opt.N,opt.N/(opt.L*opt.L));
if(opt.func == 1)
printw("truncated %d\n",opt.func);
else if(opt.func == 2)
printw("basic %d\n",opt.func);
else
printw("fast_truncated %d\n",opt.func);
refresh();

sz0 = sqrt(opt.m0);
szi = sqrt(opt.mi);
fp = popen("gnuplot","w");
fprintf(fp,"set term x11 persist; unset key\n");
fprintf(fp,"set xrange[0:100]; set yrange[0:3]\n");
fprintf(fp,"set title ’Temperature’\n");
gp = popen("gnuplot","w");
fprintf(gp,"set term x11 persist; unset key; set size square\n");
fprintf(gp,"set xrange[0:%f]; set yrange[0:%f]\n",opt.L,opt.L);
//fprintf(gp,"set xtics 20\n");
//fprintf(gp,"set ytics 20\n");
//fprintf(gp,"set mxtics 5\n");
//fprintf(gp,"set mytics 5\n");
APPENDICE D. BOX.C 61

//fprintf(gp,"set grid xtics ytics mxtics mytics\n");

it = 0;// indice conteggio step temporali


t = 0;// variabile tempo
fprintf(fp,"plot ’-’ binary record=%d format=’%%double’ w l\n",it+1);
data[0][0] = t;
E[2] += opt.m0*(p[0].v[0]*p[0].v[0] + p[0].v[1]*p[0].v[1])/2;
for(i = 1; i < opt.N; i++){
E[2] += opt.mi*(p[i].v[0]*p[i].v[0] + p[i].v[1]*p[i].v[1])/2;
}
E[2] = E[2]/opt.N;
data[0][1] = E[2];
fwrite(data[0],sizeof(double),2,fp);
fprintf(gp,"set title ’e = %-8.4f; t = %-8.2f; u = %-8.4f; \
T = %-8.4f’\n",E[0],t,E[1],E[2]);
fprintf(gp,"plot ’-’ binary record=1 format=’%%double’ w p ls 6 \
lc ’blue’ ps %f, ’-’ binary record=%d format=’%%double’ w p ls 6 \
lc ’red’ ps %f\n",sz0,opt.N-1,szi);
fwrite(p[0].x,sizeof(double),2,gp);
for(i = 1; i < opt.N; i++){
fwrite(p[i].x,sizeof(double),2,gp);
}
fflush(gp);
fflush(fp);
fprintf(fp,"set autoscale\n");

evolve = basic_evolve;
if(opt.func == 2){
opt.R = opt.L/2.;
opt.R2 = opt.R * opt.R;

double dm2 = 1./opt.R2;


double dm6 = dm2 * dm2 * dm2;

opt.Vt = 4*dm6*(dm6-1);
}
else if(opt.func == 0)
evolve = fast_trunc_evolve;

while(t < tmax){


it++;
if(it%100 == 0)
t = it * opt.dt;
else
t += opt.dt;
E = evolve(p,pp,opt,cum,E,A);

data[it][0] = t;
data[it][1] = E[2];
APPENDICE D. BOX.C 62

if(it%fr == 0){
fprintf(fp,\
"plot ’-’ binary record=%d format=’%%double’ w l\n",(it/fr)+1);
for(i = 0; i < it+1; i++){
if(i%fr == 0)
fwrite(data[i],sizeof(double),2,fp);
}
fprintf(gp,"set title ’e = %-8.4f; t = %-8.2f; u = %-8.4f; \
T = %-8.4f’\n",E[0],t,E[1],E[2]);
fprintf(gp,"plot ’-’ binary record=1 format=’%%double’ w p \
ls 6 lc ’blue’ ps %f, ’-’ binary record=%d format=’%%double’ w p \
ls 6 lc ’red’ ps %f\n",sz0,opt.N-1,szi);
fwrite(p[0].x,sizeof(double),2,gp);
for(i = 1; i < opt.N; i++){
fwrite(p[i].x,sizeof(double),2,gp);
}
fflush(gp);
fflush(fp);
}
/*-- cooling/heating block --*/
if(bath != 0){
A = sqrt(opt.T/E[2]);
}
/*-- end cooling/heating block --*/
/*-- keyboard commands block --*/
cc=kbhit();
switch(cc){
int chr;
char string[100];
case 0:
break;
case ’p’:
move(2,0);
nodelay(stdscr,FALSE);
refresh();
chr = getchar();
switch(chr){
case ’g’:
move(3,0);
printw("gnuplot command: ");
echo();
getstr(string);
fprintf(fp,"%s\n",string);
fflush(fp);
break;
case ’f’:
move(3,0);
printw("frames: ");
echo();
getstr(string);
APPENDICE D. BOX.C 63

fr = atoi(string);
break;
case ’s’:
move(3,0);
printw("save as: ");
echo();
getstr(string);
save(p,opt,string);
break;
default:
break;
}
nodelay(stdscr,TRUE);
noecho();
move(2,0);
clrtobot();
move(2,0);
break;
case ’t’://-- termalization
if(bath==0){
bath=1;
move(2,0);
nodelay(stdscr,FALSE);
refresh();
move(3,0);
printw("temperature: ");
echo();
getstr(string);
opt.T = atof(string);
nodelay(stdscr,TRUE);
noecho();
move(1,0);
clrtobot();
move(1,0);
printw("termalization T = %.2f\n",opt.T);
}
else {
double vx,vy;
bath = 0;
A=1;
move(1,0);
clrtobot();
move(1,0);
for(i=0;i<opt.N;i++){
vx += p[i].v[0];
vy += p[i].v[1];
}
vx /= opt.N;
vy /= opt.N;
for(i=0;i<opt.N;i++){
APPENDICE D. BOX.C 64

p[i].v[0] -= vx;
p[i].v[1] -= vy;
p[i].l[0] = p[i].l[1] = 0;
}
}
break;
default:
move(1,0);
clrtoeol();
move(1,0);
break;
}
/*-- end keyboard commands block --*/
}
endwin();

return 0;
}

int kbhit(void){
int c;
c=getch();
if(c != ERR)
return c;
else
return 0;
}

/////////////////////////////////////////////////////////////////////

startopt opt_manager(int argc,char **argv){


char option[]="N:L:R:M:m:T:f:t:";
int c;
startopt opt;

double dm2; // r^{-2}


double dm6; // r^{-6}

opt.N = 200;
opt.R = 4;
opt.L = 100;
opt.m0 = 1;
opt.mi = 1;
opt.dt = 0.01;
opt.T = 1;
opt.func = 0;

while((c=getopt(argc,argv,option)) != -1){
switch(c){
case ’t’://intervallo di integrazione
APPENDICE D. BOX.C 65

opt.dt = atof(optarg);
break;
case ’L’://dimensioni scatola
opt.L = atof(optarg);
break;
case ’R’://raggio potenziale troncato
opt.R = atof(optarg);
break;
case ’f’://funzione di evoluzione
opt.func = atoi(optarg);
break;
case ’T’://temperatura iniziale
opt.T = atof(optarg);
break;
case ’N’://numero particelle
opt.N = atoi(optarg);
break;
case ’M’://massa particella 0
opt.m0 = atof(optarg);
break;
case ’m’://massa particelle
opt.mi = atof(optarg);
break;
default:
fprintf(stdout,"to look at the options: %s -h\n",argv[0]);
exit(-1);
}
}
opt.c = (int)(opt.L/opt.R);
if((opt.L - opt.c*opt.R) > opt.R)
opt.c++;
opt.C = opt.c * opt.c;
opt.L = opt.R * opt.c;
if(opt.func == 2)
opt.R = opt.L/2.;
opt.R2 = opt.R * opt.R;

dm2 = 1./opt.R2;
dm6 = dm2 * dm2 * dm2;

opt.Vt = 4*dm6*(dm6-1);

return opt;
}

void init(particle *p,startopt opt){


int i,j,n,b;
int a[2];
APPENDICE D. BOX.C 66

double d[2];
//double theta;
int m;
double vx,vy,rsq,vf;
srand48(time(NULL));

a[0] = sqrt(opt.N);
a[1] = opt.N/a[0];
b = opt.N%a[0];
d[0] = opt.L/(a[0]);
if(b)// se b=0 => if falso => else
d[1] = opt.L/(a[1]+1);
else
d[1] = opt.L/a[1];
n = 0;
for(i = 0; i < a[0] ; i++){
for(j = 0; j < a[1]; j++){
//theta = 2*M_PI*drand48();
p[n].x[0] = (0.5 + i /* + 0.25*cos(theta) */)*d[0];
p[n].x[1] = (0.5 + j /* + 0.25*sin(theta) */)*d[1];
n++;
}
}
if(b){// se b=0 => if falso => non fa nulla
d[0] = opt.L / b;
for(i = 0 ; i < b; i++){
//theta = 2*M_PI*drand48();
p[n].x[0] = (0.5 + i /* + 0.25*cos(theta) */)*d[0];
p[n].x[1] = (0.5 + a[1] /* + 0.25*sin(theta) */)*d[1];
n++;
}
}
// distribuzione di Maxwell-Boltzman delle velocità
// v[i,j] = sqrt(kB*T/m[i])*G(0,1), j = {x, y}
// G(0,1) distribuzione gaussiana centrata in 0 con varianza 1
m = opt.N - opt.N%2;
for(j=0;j<2;j++){
for(i=0;i<m;i+=2){
do{
vx=2*drand48()-1;
vy=2*drand48()-1;
rsq=vx*vx+vy*vy;
}while(rsq>=1 || rsq==0);
vf=sqrt(-opt.T/opt.mi*2*log(rsq)/rsq);// kB == 1
p[i].v[j]=vx*vf;
p[i+1].v[j]=vy*vf;
}// qui i = opt.N -1
if(m < opt.N){
do{
vx=2*drand48()-1;
APPENDICE D. BOX.C 67

vy=2*drand48()-1;
rsq=vx*vx+vy*vy;
}while(rsq>=1 || rsq==0);
vf=sqrt(-opt.T/opt.mi*2*log(rsq)/rsq);
p[i].v[j]=vx*vf;
}
}
p[0].v[0] = p[0].v[1] = vx = vy = 0;// p[0] è speciale
for(i=0;i<opt.N;i++){
vx += p[i].v[0];
vy += p[i].v[1];
}
vx /= opt.N;
vy /= opt.N;
/* fprintf(stderr,"vmx = %f\nvmy = %f\n",vx,vy); */
for(i=0;i<opt.N;i++){
p[i].v[0] -= vx;
p[i].v[1] -= vy;
p[i].l[0] = p[i].l[1] = 0;
}
}

void save(particle *p,startopt opt,char *filename){


FILE *sp;
//time_t tempo;
//time(&tempo);
sp = fopen(filename,"w");
//fprintf(sp,"# Date: %s",ctime(&tempo));
fwrite(&opt,sizeof(struct _startopt),1,sp);
fwrite(p,sizeof(struct _particle),opt.N,sp);
fclose(sp);
}

particle *load(char *filename,startopt *popt){


FILE *sp;
particle *p;
sp = fopen(filename,"r");
if(fread(popt,sizeof(struct _startopt),1,sp) != 1){
endwin();
fprintf(stdout,"fread error");
exit(-1);
}
p = calloc(popt->N,sizeof(struct _particle));
if(fread(p,sizeof(struct _particle),popt->N,sp) != popt->N){
endwin();
fprintf(stdout,"fread error");
exit(-1);
APPENDICE D. BOX.C 68

}
fclose(sp);
return p;
}

double *basic_evolve(particle *p,particle **pp,startopt opt,int *cum,double *E,double A){


int i,a,b;
double r[2];
double r2,r_2,r_6,ff;

E[0]=E[1]=E[2]=0;

// calcolo di x(t + dt) = x(t) + v(t + dt/2) * dt


// con v(t + dt/2) = v(t) + F(t)/m * dt/2
// sì da considerare correttamente gli urti con le pareti della scatola
p[0].x[0] += (p[0].v[0] + p[0].f[0]/opt.m0*0.5*opt.dt)*opt.dt;
p[0].x[1] += (p[0].v[1] + p[0].f[1]/opt.m0*0.5*opt.dt)*opt.dt;

//reticolo periodico
if(p[0].x[0] < 0){
p[0].l[0]--;
p[0].x[0] += opt.L;
}
else if(p[0].x[0] > opt.L){
p[0].l[0]++;
p[0].x[0] -= opt.L;
}
if(p[0].x[1] < 0){
p[0].l[1]--;
p[0].x[1] += opt.L;
}
else if(p[0].x[1] > opt.L){
p[0].l[1]++;
p[0].x[1] -= opt.L;
}

// calcolo di v(t + dt/2) e azzeramento forze


p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
p[0].f[0] = p[0].f[1] = 0;

for (i = 1; i < opt.N; i++){


// calcolo di x(t + dt) = x(t) + v(t + dt/2) * dt
// con v(t + dt/2) = v(t) + F(t)/m * dt/2
// sì da considerare correttamente gli urti con le pareti della scatola
p[i].x[0] += (p[i].v[0] + p[i].f[0]/opt.mi*0.5*opt.dt)*opt.dt;
p[i].x[1] += (p[i].v[1] + p[i].f[1]/opt.mi*0.5*opt.dt)*opt.dt;
APPENDICE D. BOX.C 69

//reticolo periodico
if(p[i].x[0] < 0){
p[i].l[0]--;
p[i].x[0] += opt.L;
}
else if(p[i].x[0] > opt.L){
p[i].l[0]++;
p[i].x[0] -= opt.L;
}
if(p[i].x[1] < 0){
p[i].l[1]--;
p[i].x[1] += opt.L;
}
else if(p[i].x[1] > opt.L){
p[i].l[1]++;
p[i].x[1] -= opt.L;
}

// calcolo di v(t + dt/2) e azzeramento forze


p[i].v[0] += p[i].f[0]/opt.mi*0.5*opt.dt;
p[i].v[1] += p[i].f[1]/opt.mi*0.5*opt.dt;
p[i].f[0] = p[i].f[1] = 0;
}

//////////////////////////////

// calcolo di F(t+dt)
for(a = 0; a < opt.N-1; a++){
for(b = a+1; b < opt.N; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
if(r[0] > opt.L/2.)
r[0] = r[0] - opt.L;
else if(r[0] < -opt.L/2.)
r[0] = r[0] + opt.L;
r[1] = pp[b]->x[1] - pp[a]->x[1];
if(r[1] > opt.L/2.)
r[1] = r[1] - opt.L;
else if(r[1] < -opt.L/2.)
r[1] = r[1] + opt.L;
r2 = r[0]*r[0] + r[1]*r[1];
if(r2 < opt.R2){
r_2 = 1./r2;
r_6 = r_2*r_2*r_2;
ff = -24*r_6*(2*r_6-1)*r_2;
pp[a]->f[0] += ff*r[0];
pp[a]->f[1] += ff*r[1];
pp[b]->f[0] -= ff*r[0];
pp[b]->f[1] -= ff*r[1];
E[1] += (4*r_6*(r_6-1) - opt.Vt);
}
APPENDICE D. BOX.C 70

}
}

// calcolo di v(t + dt)


p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
E[2] += opt.m0*(p[0].v[0]*p[0].v[0] + p[0].v[1]*p[0].v[1])/2;

for(i = 1; i < opt.N; i++){


p[i].v[0] += p[i].f[0]/opt.mi*0.5*opt.dt;
p[i].v[1] += p[i].f[1]/opt.mi*0.5*opt.dt;
E[2] += opt.mi*(p[i].v[0]*p[i].v[0] + p[i].v[1]*p[i].v[1])/2;

}
E[0] = E[1] + E[2];
E[0] = E[0]/opt.N;
E[1] = E[1]/opt.N;
E[2] = E[2]/opt.N;
return E;
}

double *fast_trunc_evolve(particle *p,particle **pp,startopt opt,int *cum,double *E,double


int cnt = 0;
int i,j,l,k,a,b;
double r[2];
double r2,r_2,r_6,ff;

E[0]=E[1]=E[2]=0;

// azzeramento celle
for(i = 1; i <= opt.C; i++){
cum[i] = 0;
}
// calcolo di x(t + dt) = x(t) + v(t + dt/2) * dt
// con v(t + dt/2) = v(t) + F(t)/m * dt/2
// sì da considerare correttamente gli urti con le pareti della scatola
p[0].x[0] += (p[0].v[0] + p[0].f[0]/opt.m0*0.5*opt.dt)*opt.dt;
p[0].x[1] += (p[0].v[1] + p[0].f[1]/opt.m0*0.5*opt.dt)*opt.dt;

//reticolo periodico
if(p[0].x[0] < 0){
p[0].l[0]--;
p[0].x[0] += opt.L;
}
else if(p[0].x[0] > opt.L){
p[0].l[0]++;
p[0].x[0] -= opt.L;
}
APPENDICE D. BOX.C 71

if(p[0].x[1] < 0){


p[0].l[1]--;
p[0].x[1] += opt.L;
}
else if(p[0].x[1] > opt.L){
p[0].l[1]++;
p[0].x[1] -= opt.L;
}

p[0].nc = (int)(p[0].x[0]/opt.R) + \
(int)(p[0].x[1]/opt.R)*opt.c;
cum[p[0].nc+1]++;

// calcolo di v(t + dt/2) e azzeramento forze


p[0].v[0] = A*p[0].v[0] + p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] = A*p[0].v[1] + p[0].f[1]/opt.m0*0.5*opt.dt;
p[0].f[0] = p[0].f[1] = 0;

for (i = 1; i < opt.N; i++){


// calcolo di x(t + dt) = x(t) + v(t + dt/2) * dt
// con v(t + dt/2) = v(t) + F(t)/m * dt/2
// sì da considerare correttamente gli urti con le pareti della scatola
p[i].x[0] += (p[i].v[0] + p[i].f[0]/opt.mi*0.5*opt.dt)*opt.dt;
p[i].x[1] += (p[i].v[1] + p[i].f[1]/opt.mi*0.5*opt.dt)*opt.dt;

//reticolo periodico
if(p[i].x[0] < 0){
p[i].l[0]--;
p[i].x[0] += opt.L;
}
else if(p[i].x[0] > opt.L){
p[i].l[0]++;
p[i].x[0] -= opt.L;
}
if(p[i].x[1] < 0){
p[i].l[1]--;
p[i].x[1] += opt.L;
}
else if(p[i].x[1] > opt.L){
p[i].l[1]++;
p[i].x[1] -= opt.L;
}

p[i].nc = (int)(p[i].x[0]/opt.R) + \
(int)(p[i].x[1]/opt.R)*opt.c;
cum[p[i].nc+1]++;

// calcolo di v(t + dt/2) e azzeramento forze


p[i].v[0] += p[i].f[0]/opt.mi*0.5*opt.dt;
p[i].v[1] += p[i].f[1]/opt.mi*0.5*opt.dt;
APPENDICE D. BOX.C 72

p[i].f[0] = p[i].f[1] = 0;
}

// cumulata
for(i = 1; i < opt.C + 1; i++){
cum[i] += cum[i-1];
}
//ordinamento del vettore pp in funzione della cella occupata dalla particella
pp_sort(pp,opt.N);
//////////////////////////////

// calcolo di F(t+dt)
for(j = 0; j < opt.c-1; j++){
//i = 0;
l = j*opt.c;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(b = cum[l+2*opt.c-1]; b < cum[l+2*opt.c]; b++){
r[0] = (pp[b]->x[0] - opt.L) - pp[a]->x[0];//special
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(k = 0; k < 2; k++){
for(b = cum[l+opt.c+k]; b < cum[l+opt.c+k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
}//chiude ciclo a
for(i = 1; i < opt.c-1; i++){
l = j*opt.c + i;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
APPENDICE D. BOX.C 73

forces(a,b);
}
}
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(k = -1; k < 2; k++){
for(b = cum[l+opt.c+k]; b < cum[l+opt.c+k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
}//chiude ciclo a
}//chiude ciclo i
//i = opt.c-1;
l = j*opt.c + i;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l-opt.c+1]; b < cum[l-opt.c+2]; b++){
r[0] = (pp[b]->x[0] + opt.L) - pp[a]->x[0];//special
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(k = -1; k < 1; k++){
for(b = cum[l+opt.c+k]; b < cum[l+opt.c+k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = (pp[b]->x[0] + opt.L) - pp[a]->x[0];//special
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}//chiude cicla a
}//chiude ciclo j
//j = opt.c-1;
//i = 0;
l = j*opt.c;
APPENDICE D. BOX.C 74

for(a = cum[l]; a < cum[l+1]; a++){


cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(b = cum[opt.c-1]; b < cum[opt.c]; b++){
r[0] = (pp[b]->x[0] - opt.L) - pp[a]->x[0];//special
r[1] = (pp[b]->x[1] + opt.L) - pp[a]->x[1];//special
forces(a,b);
}
for(k = 0; k < 2; k++){
for(b = cum[k]; b < cum[k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = (pp[b]->x[1] + opt.L) - pp[a]->x[1];//special
forces(a,b);
}
}
}//chiude ciclo a
for(i = 1; i < opt.c-1; i++){
l = j*opt.c + i;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l+1]; b < cum[l+2]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(k = -1; k < 2; k++){
for(b = cum[i+k]; b < cum[i+k+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = (pp[b]->x[1] + opt.L) - pp[a]->x[1];//special
forces(a,b);
}
}
APPENDICE D. BOX.C 75

}//chiude ciclo a
}//chiude ciclo i
//i = opt.c-1;
l = j*opt.c + i;
for(a = cum[l]; a < cum[l+1]; a++){
cnt++;
if(a < cum[l+1]-1){
for(b = a+1; b < cum[l+1]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
}
for(b = cum[l-opt.c+1]; b < cum[l-opt.c+2]; b++){
r[0] = (pp[b]->x[0] + opt.L) - pp[a]->x[0];//special
r[1] = pp[b]->x[1] - pp[a]->x[1];
forces(a,b);
}
for(k = -1; k < 1; k++){
for(b = cum[opt.c-1+k]; b < cum[opt.c+k]; b++){
r[0] = pp[b]->x[0] - pp[a]->x[0];
r[1] = (pp[b]->x[1] + opt.L) - pp[a]->x[1];//special
forces(a,b);
}
}
for(b = cum[0]; b < cum[1]; b++){
r[0] = (pp[b]->x[0] + opt.L) - pp[a]->x[0];//special
r[1] = (pp[b]->x[1] + opt.L) - pp[a]->x[1];//special
forces(a,b);
}
}//chiude ciclo a

if(cnt != opt.N){
fprintf(stderr,"cnt = %3d\n",cnt);
endwin();
exit(-1);
}

// calcolo di v(t + dt)


p[0].v[0] += p[0].f[0]/opt.m0*0.5*opt.dt;
p[0].v[1] += p[0].f[1]/opt.m0*0.5*opt.dt;
E[2] += opt.m0*(p[0].v[0]*p[0].v[0] + p[0].v[1]*p[0].v[1])/2;

for(i = 1; i < opt.N; i++){


p[i].v[0] += p[i].f[0]/opt.mi*0.5*opt.dt;
p[i].v[1] += p[i].f[1]/opt.mi*0.5*opt.dt;
E[2] += opt.mi*(p[i].v[0]*p[i].v[0] + p[i].v[1]*p[i].v[1])/2;

}
E[0] = E[1] + E[2];
APPENDICE D. BOX.C 76

E[0] = E[0]/opt.N;
E[1] = E[1]/opt.N;
E[2] = E[2]/opt.N;
return E;
}

void pp_sort(particle **pp,int n){


particle **pa,**pb,**pc,**pd,**pl,**pm,**pn,**pv;
particle *v,*tmp;
int s,r;
if(n<7){
for(pm=pp+1;pm<pp+n;pm++)
for(pl=pm;pl>pp && cmp_nc(pl-1,pl)>0;pl--)
swap(pl,pl-1,tmp);
return;
}
pm=pp+(n/2);
if(n>7){
pl=pp;
pn=pp+(n-1);
if(n>40){
s=(n/8);
pl=med3(pl,pl+s,pl+2*s);
pm=med3(pm-s,pm,pm+s);
pn=med3(pn-2*s,pn-s,pn);
}
pm=med3(pl,pm,pn);
}
pv=&v;
v=*pm;
pa=pb=pp;
pc=pd=pp+(n-1);
for(;;){
while(pb<=pc && (r=cmp_nc(pb,pv))<=0){
if(r==0){
swap(pa,pb,tmp);
pa++;
}
pb++;
}
while(pc>=pb && (r=cmp_nc(pc,pv))>=0){
if(r==0){
swap(pc,pd,tmp);
pd--;
}
pc--;
}
if(pb>pc)
APPENDICE D. BOX.C 77

break;
swap(pb,pc,tmp);
pb++;
pc--;
}
pn=pp+n;
s=min(pa-pp,pb-pa);
pv=pp;
while(s>0){
swap(pv,pb-s,tmp);
pv++;
s--;
}
s=min(pd-pc,pn-pd-1);
while(s>0){
swap(pb,pn-s,tmp);
pb++;
s--;
}
if((s=pb-pa) > 1)
pp_sort(pp,s);
if((s=pd-pc) > 1)
pp_sort(pn-s,s);
}
Appendice E

measure.c

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv){


FILE *fp;
char log[] = "100k_measure.log";
char command[100];
int i,j,k;
int r[4]={25,50,75,100};
int T[5]={1,2,5,10,20};
for(i=1;i<4;i++){
for(j=0;j<4;j++){
for(k=0;k<5;k++){
sprintf(command,\
"%s -vao %s ./%03d R4L20r%03dT%02d 100k_%03d_%03d_%02d.dat" \
,"/usr/bin/time",log,i,r[j],T[k],i,r[j],T[k]);
printf("%s\n",command);
system(command);
fp = fopen(log,"a");
fprintf(fp,"\n");
fclose(fp);
}
}
}
return 0;
}

78
BIBLIOGRAFIA 79

Bibliografia

[1] Jon L. Bentley e M. Douglas McIlroy. «Engineering a Sort Function». In:


Software–Practice and Experience 23.11 (nov. 1993), pp. 1249–1265.
[2] Furio Ercolessi. A molecular dynamics primer. Appunti. Spring College
in Computational Physics, ICTP. Trieste, Italia: International School for
Advanced Studies (SISSA-ISAS), giu. 1997.

Potrebbero piacerti anche