Emergenza

Una fenomeno può essere definito emergente quando qualcosa diventa più della somma delle sue parti oppure quando esso ad alto livello deriva dall’interazione a livelli più bassi. Ad esempio la temperatura e la pressione sono fenomeni emergenti. Infatti essi derivano dall’interazione 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 quest’ultima 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 l’algoritmo di Reynolds che regola l’interazione 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 l’architettura 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.
 

L’Algoritmo 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;
    }
}