Emergenza
Una fenomeno può essere definito emergente quando qualcosa
diventa più della somma delle sue parti oppure quando esso ad
alto livello deriva dallinterazione a livelli più bassi.
Ad esempio la temperatura e la pressione sono fenomeni emergenti.
Infatti essi derivano dallinterazione di miliardi di
molecole (livello microscopico), ma prese singolarmente ciascuna
di esse non ha né pressione né temperatura, che sono a un
livello più alto (livello macroscopico). Anche un essere vivente
può essere considerato emergente: infatti noi siamo
più della somma delle nostre parti e mezzo uomo non può
esistere.
I sistemi di Vita Artificiale consistono di una grande collezione
di semplici unità di base che producono delle proprietà
interessanti a livelli più alti. Un esempio di sistema di AL che
presenta emergenza sono i Boids inventati nel 1989 da Craig
Reynolds. Il suo obiettivo era quello di realizzare delle
creature artificiali che volassero assieme in stormi come gli
uccelli e senza avere un leader.
Ogni boid ha un vettore velocità V=s*U dove s
è lo speed (cioè il modulo della velocità) e U un vettore
unitario chiamato vettore tangente. Generalmente un boid varia
questi due vettori gradualmente, il che corrisponde ad ammettere
che esso ha una certa inerzia ed è anche in accordo con quello
che avviene in natura dato che durante il volo non si hanno
cambiamenti bruschi. Per ogni creatura sarà necessario ad ogni
passo prima aggiornare i valori di s e U e poi calcolare la nuova
posizione. Per questultima operazione sia (x,y,z) il
vettore posizione, (ux,uy,uz) il vettore tangente, allora la
posizione al passo successiva sarà (x+s*ux, y+s*uy, z+s*uz).
Reynolds ha definito questo comportamento volo geometrico. Dopo
aver definito il comportamento di ciascun boid vediamo lalgoritmo
di Reynolds che regola linterazione tra di essi e
produce le proprietà emergenti. Esso consiste di tre regole:
1. Evitare le collisioni: evitare le collisioni con i vicini
2. Adeguare la velocità: cercare di adeguare la velocità a
quella dei vicini
3. Centrare lo stormo: cercare di star vicino ai compagni
circostanti.
(1) Ogni boid cerca di mantenere una distanza di crociera dai
suoi vicini per evitare collisioni. Così se è troppo vicino al
compagni che gli sta davanti rallenta, se è troppo vicino a
quello che gli sta dietro accelera. Allo stesso modo eviterà di
stare troppo distante dai compagni.
(2) Ciascun boid cerca di volare parallelo a quello più vicino.
Questo è ottenuto adeguando il vettore tangente a quello del
vicino, senza variare lo speed.
(3) Ogni boid cerca di essere circondato da altri compagni. Per
fare ciò viene calcolata la posizione media degli altri boids
detta centroid e un boid calcolerà il vettore che punta al
centroid e cercherà di adeguare ad esso il suo vettore tangente.
Anche in questo caso non cambia lo speed.
Il risultato di questo algoritmo è che i boids si comportano
in modo del tutto analogo a quello degli stormi degli uccelli.
Essi volano in un gruppo coesivo e quando incontrano un ostacolo
si dividono in due gruppi per poi riunirsi dopo averlo superato.
Se mettiamo invece un singolo boid in un mondo vuoto esso volerà
in direzione rettilinea e a velocità costante. Questa tecnica è
stata usata per realizzare alcune realistiche scene di stormi di
pipistrelli nei film Batman il Ritorno e Cliffanger e mostra
inoltre larchitettura base della Vita Artificiale: un gran
numero di elementi base, relativamente semplici, ognuno dei quali
interagisce solo coi suoi vicini. Riportiamo la seguente
implementazione in Java.
LAlgoritmo in Java
public class BoidThatFlocks extends Boid {
// ...
// The three rules to simulate flocking, most
important first, are:
// 1. collision avoidance,
// 2. velocity matching, and
// 3. flock centering.
// Collision Avoidance: Avoid collisions
with nearby boids.
// Steer away from imminent impact. This is
based on the
// relative position of each boid and ignores
velocity.
private TwoDVector avoidCollisions() {
// Not yet implemented.
return new TwoDVector();
}
// Match Velocity: Velocity is a vector
quantity consisting of
// heading and speed.
private TwoDVector matchVelocity() {
TwoDVector newVelocity
= new TwoDVector();
Vector nearbyBoids =
obsField.nearbyFlockmates(this);
float magnitudeSum = 0;
float directionSum = 0;
float
deltaDirectionSum = 0;
float myDirection =
TwoDVector.normalizedDirection(this.prevVelocity.direction);
int nearbyBoidCount =
nearbyBoids.size();
for (int i = 0; i <
nearbyBoidCount; i++) {
Boid nearbyBoid = (Boid) nearbyBoids.elementAt(i);
magnitudeSum += nearbyBoid.prevVelocity.magnitude;
float neighborDirection = nearbyBoid.prevVelocity.direction;
float deltaDirection = neighborDirection - myDirection;
deltaDirectionSum += deltaDirection;
if (nearbyBoidCount != 0) {
float deltaDirection = deltaDirectionSum / nearbyBoidCount;
newVelocity.direction =
TwoDVector.normalizedDirection(deltaDirection);
} else {
TwoDVector randomVel = randomlyAltered(this.velocity);
newVelocity.direction = this.velocity.direction;
newVelocity.magnitude = randomVel.magnitude / 2;
}
}
return newVelocity;
}
// Flock Centering: Stay close to nearby
flockmates.
private TwoDVector centerInFlock() {
TwoDVector newVelocity
= new TwoDVector();
FloatPair
nearbyBoidsCentroid = new FloatPair();
Vector nearbyBoids =
obsField.nearbyFlockmates(this);
int nearbyBoidCount =
nearbyBoids.size();
for (int i = 0; i <
nearbyBoidCount; i++) {
Boid nearbyBoid = (Boid) nearbyBoids.elementAt(i);
nearbyBoidsCentroid.x += nearbyBoid.prevLocation.x;
nearbyBoidsCentroid.y += nearbyBoid.prevLocation.y;
}
if (nearbyBoidCount !=
0) {
float nearbyBoidsCentroidX = nearbyBoidsCentroid.x /
nearbyBoidCount;
float nearbyBoidsCentroidY = nearbyBoidsCentroid.y /
nearbyBoidCount;
float deltaX = this.prevLocation.x - nearbyBoidsCentroidX;
float deltaY = this.prevLocation.y - nearbyBoidsCentroidY;
newVelocity.direction = (float) Math.atan(deltaY/deltaX);
newVelocity.magnitude = nearbyBoidCount * randomNumber();
} else {
TwoDVector randomVel = randomlyAltered(this.velocity);
newVelocity.direction = this.velocity.direction;
newVelocity.magnitude = 1;
}
return newVelocity.normalized();
}
public void moveNext() {
super.moveNext();
TwoDVector
acceleration = new TwoDVector();
if (useVelocityMatching)
acceleration = matchVelocity();
else acceleration =
randomlyAltered(acceleration);
if (useFlockCentering)
acceleration = acceleration.plus(centerInFlock());
//if (useCollisionAvoidance)
//
newVelocity = newVelocity.plus(avoidCollisions());
float
directionChange =
TwoDVector.normalizedDirection(acceleration.direction);
if (directionChange
> PI) directionChange -= (float) 2.0*PI;
if (directionChange
> maxDirectionChangePerMove)
directionChange = maxDirectionChangePerMove;
else if (directionChange
< -maxDirectionChangePerMove)
directionChange = -maxDirectionChangePerMove;
velocity.direction
=
TwoDVector.normalizedDirection(velocity.direction
+= directionChange);
float magnitude =
acceleration.magnitude;
if (magnitude >
maxDistancePerMove)
magnitude = maxDistancePerMove;
else if (magnitude
< minDistancePerMove)
magnitude = minDistancePerMove;
velocity.magnitude =
magnitude;
float xDistance =
velocity.magnitude *
(float) Math.cos(velocity.direction + PI/2);
float yDistance =
velocity.magnitude *
(float) Math.sin(velocity.direction + PI/2);
location.x +=
xDistance;
location.y +=
yDistance;
}
}