Emersione: pensa localmente, agisci localmente

Un sistema complesso è costituito da una pluralità di attori che interagiscono tra di loro mediante semplici regole. Si ha il fenomeno dell'emersione quando le interazioni a livello locale producono una complessità organizzata a livello globale. Quando il tutto è più della somma delle parti.

Spesso gli animali (incluso l'uomo) si muovono in gruppo generando un comportamento emergente. Un tipico esempio è lo stormo di uccelli. Ogni volatile segue tre semplici regole locali:

  1. Separazione. Non avvicinarti troppo ai tuoi vicini. E' una repulsione a breve distanza.
  2. Allineamento. Segui la direzione media dei tuoi vicini.
  3. Coesione. Non allontanarti troppo dai tuoi vicini. E' una attrazione a lunga distanza.

Il risultato, inaspettato, è quello di un'unica entità che si muove nell'aria, cambiando sovente forma. Questo comportamento di gruppo è tipico anche di altri animali come formiche, api, termiti, pesci e uomini (folle). Qui una simulazione.

Robert Hodgin

Semplici regole producono un risultato complesso. La biologia può essere vista come una proprietà emergente delle leggi della chimica che, a loro volta, possono essere visti come una proprietà emergente della fisica delle particelle. Allo stesso modo, la psicologia potrebbe essere intesa come una proprietà emergente della neurobiologia, e l'economia come una caratteristica emergente della psicologia.

L'aspetto interessante dei sistemi complessi è che non esiste un centro che regola il comportamento del sistema; al contrario, gli attori si muovono in autonomia e hanno una conoscenza locale del solo contesto che li circonda. Spesso le interazioni tra gli attori vengono modellate attraverso una struttura a grafo detto rete complessa. Esempi di reti complesse sono: Internet, Web, reti sociali, reti biochimiche e reti neurali.

Per esempio, nella rete complessa di Twitter i nodi sono gli utenti della rete sociale e esiste un arco orientato dal nodo A al nodo B se l'utente A è un seguace di B. Gli utenti di Twitter operano con regole di una semplicità disarmante: cinguettano in autonomia, al più ispirati dal ristretto numero di utenti che seguono. I 140 caratteri di un cinguettio, moltiplicati per la moltitudine di utenti e veicolati attraverso la rete complessa sottostante, possono trasformarsi in fenomeni globali, quali opinioni, mode, rivoluzioni.

Sharon Molloy

"My quest is to reveal how everything is interconnected. From the atom to the cell, to the body and beyond into society and the cosmos, there are underlying processes, structures and rhythms that are mirrored all around and permeate reality. I attempt to visualize 'the molecular process of revolution'; how one small thing leads to another and larger patterns emerge. (...) This work embraces the multiple, the network, the paradoxical and the idea that even the smallest gesture or event has significance, and the power to change everything." (Sharon Molloy)



Come può l'arte generativa servirsi dei sistemi complessi? L'artista generativo mira ad una bellezza inaspettata, investigando processi tipici del mondo organico attraverso l'uso di semplici regole provenienti dal mondo della logica e della matematica. Sistemi complessi abbondano in natura (oltre che nel mondo della tecnologia, dell'informazione, e della società umana). I sistemi complessi, inoltre, sono in grado di produrre un risultato complesso a partire da semplici regole di interazione. E' tale complessità che desta la sorpresa, e può produrre un risultato esteticamente interessante.

La tecnica della casualità organica (rumore di Perlin) si basa su aspetti non deterministici legati al caso. Un sistema complesso, invece, è perfettamente deterministico: ogni stato del sistema è ottenuto a partire dallo stato precedente applicando delle regole logiche alle componenti. Il comportamento del sistema è però talmente intricato che il risultato è imprevedibile, quasi fosse casuale (in realtà in filosofia si parla di caso anche quando le cause esistono ma sono sconosciute).

"The distinction that's new, and that we didn't used to make, is that something can be deterministic but not predictable. We tend to think they are synonyms but they're not. Something can be obeying some law of nature but it's not predictable because what it's doing is so complicated that the time it would take you to calculate what it was going to do would take longer than the thing actually doing it. So you could compute it but you can't compute the world any faster than it is happening." (Rudy Rucker)

Il contributo artistico della macchina sta nel gestire al meglio delle proprie capacità la complessità computazionale del sistema. L'aspetto delle complessità computazionale di un sistema complesso è critico e l'artista programmatore deve fare la sua parte. Vi sono due fonti di complessità: (i) il numero di attori del sistema, generalmente elevato; (ii) la necessità di gestire efficientemente l'interazione tra ogni attore con tutti gli altri (in un tempo inferiore a 1/60 di secondo se si tratta di animazioni al fine di mantenere fluido il movimento). Dunque l'artista generativo deve essere un buon programmatore che conosce e sceglie strutture di dati e metodi efficienti (!).

Un sistema complesso, infine, si presta bene ad essere realizzato usando la programmazione orientata agli oggetti: ogni attore viene rappresentato da un oggetto appartenente ad una classe che ne descrive le caratteristiche (campi) e i comportamenti (metodi).

Vediamo un esempio di sistema complesso con comportamento emergente: Net. Gli attori sono dei cerchi caratterizzati da una posizione, una dimensione, un colore, una direzione e velocità di movimento. Ogni cerchio si muove, disegna sé stesso, e si connette mediante una linea con altri cerchi che si sovrappongono. Ciò che emerge dal sistema complesso è la rete delle connessioni tra cerchi sovrapposti, frutto delle interazioni degli attori. E' possibile isolare il solo sistema senza interazioni oppure il solo comportamento emergente.

int nCircles = 10;
Circle[] circles = {};   

void setup() {
  size(800, 500); 
  background(255);
  smooth();
  strokeWeight(1);
  fill(150, 50);
  newCircles();
}

void draw() {
  background(255);
  for (int i=0; i < circles.length; i++) {
    if (!keyPressed || (key != 'n')) {
      circles[i].drawMe();
    }  
    if (!keyPressed || (key != 'b')) {
      circles[i].moveMe();
    }  
    if (!keyPressed || (key != 'm')) {
      circles[i].linkMe();
    }
  }
}

void newCircles() {
  for (int i=0; i < nCircles; i++) { 
    circles = (Circle[]) append(circles, new Circle(circles.length));
  }
}

void mouseReleased() {
  newCircles();
}


class Circle {

  int id; // identificatore univoco
  float x, y; // coordinate del centro
  float radius; // raggio
  color linecol, fillcol; // colori del contorno e del contenuto 
  float alpha; // trasparenza
  float xmove, ymove; // vettore di movimento (direzione e velocità)
  
  Circle(int _id) {
    id = _id;
    x = random(width);
    y = random(height);
    radius = random(10, 100); 
    linecol = color(random(255), random(255), random(255));
    fillcol = color(random(255), random(255), random(255));
    alpha = random(255);
    xmove = random(-2, 2);   
    ymove = random(-2, 2);  
  }
  
  void drawMe() { 
    noStroke(); 
    fill(fillcol, alpha);
    ellipse(x, y, radius*2, radius*2);
    stroke(linecol, 150);
    noFill();
    ellipse(x, y, 10, 10);
  }
  
  void moveMe() {
    x += xmove;
    y += ymove;
    if (x > (width + radius))   x = 0 - radius; 
    if (x < (0 - radius))       x = width + radius; 
    if (y > (height + radius))  y = 0 - radius; 
    if (y < (0 - radius))       y = height + radius; 
  }
  
  void linkMe() {
    for (int i = id + 1; i < circles.length; i++) {
      float dis = dist(x, y, circles[i].x, circles[i].y); 
      float overlap = dis - radius - circles[i].radius; 
      if (overlap < 0) { 
        stroke(0);
        line(x, y, circles[i].x, circles[i].y);
      }
    } 
  }
  
}

Vediamo un esempio famoso di sistema complesso. Il Gioco della Vita (Game of Life in inglese) è un automa cellulare sviluppato dal matematico inglese John Conway sul finire degli anni sessanta. Il suo scopo è quello di mostrare come comportamenti complessi simili alla vita possano emergere da regole semplici di interazione tra molti corpi.

Il gioco si svolge su una griglia infinita di caselle quadrate (celle) detta mondo. Ogni cella ha 8 vicini, che sono le celle ad essa adiacenti, includendo quelle in senso diagonale. Ogni cella può trovarsi in due stati: viva o morta. Lo stato della griglia evolve in intervalli di tempo discreti. Gli stati di tutte le celle in un dato istante sono usati per calcolare lo stato delle celle all'istante successivo. Tutte le celle del mondo vengono quindi aggiornate simultaneamente nel passaggio da un istante a quello successivo: passa così una generazione. Le regole di transizione di stato dipendono unicamente dal numero di vicini vivi:

Dal punto di vista teorico il Gioco della Vita è interessante perché ha le potenzialità di una macchina di Turing universale: in altre parole, ogni cosa che può essere elaborata algoritmicamente può essere elaborata dal Gioco della Vita.

Gol è una realizzazione del Gioco della Vita.

Cell[][] cells;     
int cellSize = 10;  
int numX, numY;     

void setup() { 
  frameRate(10);
  size(500, 300);
  numX = floor(width/cellSize);
  numY = floor(height/cellSize);
  startup();
} 


void draw() {
  background(200);
            
  for (int x = 0; x < numX; x++) {
    for (int y = 0; y < numY; y++) {
     cells[x][y].compNextState();
    }
  }             
            
  for (int x = 0; x < numX; x++) {
    for (int y = 0; y < numY; y++) {
     cells[x][y].drawMe();
    }
  }
}

void startup() {
  // creo la griglia di celle
  cells = new Cell[numX][numY];  
  for (int x = 0; x < numX; x++) {
    for (int y = 0; y < numY; y++) {  
      cells[x][y] = new Cell(x, y);     
    }        
  }          
  // assegno i vicini ad ogni cella 
  for (int x = 0; x < numX; x++) {
    for (int y = 0; y < numY; y++) {  
      int above = y-1;    
      int below = y+1;    
      int left = x-1;      
      int right = x+1;      
      if (above < 0) above = numY-1;   
      if (below == numY) below = 0;  
      if (left < 0) left = numX-1;  
      if (right == numX) right = 0;  

      cells[x][y].addNeighbour(cells[left][above]);  
      cells[x][y].addNeighbour(cells[left][y]);    
      cells[x][y].addNeighbour(cells[left][below]);  
      cells[x][y].addNeighbour(cells[x][below]);  
      cells[x][y].addNeighbour(cells[right][below]);  
      cells[x][y].addNeighbour(cells[right][y]);  
      cells[x][y].addNeighbour(cells[right][above]);  
      cells[x][y].addNeighbour(cells[x][above]);    
    }
  }
}


void mousePressed() {
  startup();
}

class Cell {
  float x, y;
  boolean state;  
  boolean nextState;  
  Cell[] neighbours;
  
  Cell(float _x, float _y) {
    x = _x * cellSize;
    y = _y * cellSize;
    if (random(2) > 1) {  
      nextState = true;
    } else {
      nextState = false; 
    }
    state = nextState;
    neighbours = new Cell[0];
  }
  
  void addNeighbour(Cell cell) {
    neighbours = (Cell[]) append(neighbours, cell); 
  }
  
  void compNextState() { 
    // conto i vicini vivi
    int liveCount = 0;    
    for (int i=0; i < neighbours.length; i++) {
       if (neighbours[i].state == true) {  
         liveCount++;      
       }        
     }
    // se sono vivo e ho non troppi vicini vivi (2 o 3) rimango vivo, altrimenti muoio 
    if (state == true) {          
        if ((liveCount == 2) || (liveCount == 3)) {  
          nextState = true;          
        } else {            
          nextState = false;        
        } 
     // se sono morto e ho 3 vicini vivi, ritorno vivo         
     } else {            
        if (liveCount == 3) {        
          nextState = true;      
        } else {        
          nextState = false;    
        }        
     }       
  }

  void drawMe() {
    state = nextState;
    noStroke();
    if (state == true) {
      fill(0);
    } else {
      fill(255);
    }
    rect(x, y, cellSize, cellSize);
  }
  
}

Una paio di libere interpretazioni: Life (le celle vive corrispondono a sfere di dimensione proporzionale al numero di vicini vivi) e Crystallization (le celle vive corrispondono a stelle a otto punte verso tutti i vicini).