Questa è un'ultima carrellata di immagini. Perché alla fine, al di là di equazioni, algoritmi e tutto il resto, il fascino di questi argomenti è prevalentemente visivo.
Se vi ricordate l'insieme di Mandelbrot viene fuori da zn+1 =zn² + c. Se manteniamo c costante non otteniamo l'insieme di Mandelbrot, ma il Julia Set per quel particolare valore di c, cioè una particolare sezione/sottoinsieme del Mandelbrot Set.
![]() |
| Immagine creata con Python |
Questo è un particolare Julia Set (con c = 0.355 + 0.355i) che costituisce una sezione dei filamenti che si diramano dalle estremità del Mandelbrot Set.
Se notate, le spirali grandi centrali sono circondate da spirali più piccole, le quali a loro volta sono composte da spirali ancora più microscopiche, e così via all'infinito. Questo zoom mostra perfettamente il concetto di auto-similarità: ogni filamento secondario imita la struttura globale del frattale stesso.
Il Mandelbrot Set fa parte della famiglia delle mappe quadratiche, ma la famiglia delle mappe logistiche/densità-dipendenti si estende a equazioni alle differenze finite con multiplo feedback. Un esempio? Questo:
Dove:
Ht (Host density): La densità della popolazione di ospiti al tempo t.
Pt (Parasitoid density): La densità della popolazione di parassitoidi al tempo t.
r (Intrinsic growth rate): Il tasso intrinseco di crescita dell'ospite.
a (Parasitoid attack rate / Searching efficiency): Il tasso di attacco o efficienza di ricerca del parassitoide. Rappresenta la capacità del parassitoide di scovare e infettare l'ospite.
e-aPt: La probabilità che un ospite sfugga al parassitismo.
(1 − e-aPt): La probabilità che un ospite venga parassitato. Moltiplicato per Ht, indica il numero di ospiti infettati che daranno origine esattamente a 1 parassitoide adulto ciascuno nel tempo t+1.
Per un momento lasciate perdere tutto questo e date un'occhiata alla matematica. Questo sistema di equazioni alle differenze finite è vertiginoso. Ht+1 è proporzionale sia a Ht che a e-Pt dove Pt+1 è proporzionale sia ad Ht che a e-Pt: è un labirinto di feedback incrociati. E il suo diagramma di biforcazione è un merletto:
![]() |
| Grafico ottenuto con Python |
Ma di cosa stiamo parlando?
Il parassita vive a spese dell'ospite ma generalmente non lo uccide - almeno non direttamente e non immediatamente. Il parassitoide invece uccide l'ospite, ma con tempistica controllata. La femmina depone le uova dentro o sopra l'ospite (tipicamente un insetto). La larva si sviluppa nutrendosi dei tessuti dell'ospite dall'interno, tenendolo in vita il più a lungo possibile - perché un ospite vivo è cibo fresco. Quando la larva ha completato lo sviluppo l'ospite è consumato e muore. L'adulto è libero e autonomo.
Il parassitoide è quindi a metà strada tra parassita e predatore: come il predatore uccide, come il parassita lo fa lentamente e dall'interno. La distinzione è rilevante per i modelli perché ogni femmina parassitoide produce esattamente un nuovo parassitoide per ospite parassitizzato. Si tratta solitamente di insetti che predano/parassitano altri insetti, come le vespe braconidi.
Ma la cosa più importante è la struttura, prodotto delle iterazioni, che rispecchia la complessa dinamica del sistema: quando l'attack rate del parassitoide supera 4.5 la transizione verso il caos è molto veloce. E ancora una volta, per quanto la matematica sia meno semplice di quella di una mappa logistica o quadratica, la notevole struttura è un prodotto del processo iterativo che non può essere immediatamente derivato dalla matematica.
Ultima immagine, quella di un fiocco di neve:
![]() |
| Immagine generata da Grok |
Curiosamente un fiocco neve, con la sua geometria frattale (determinabile sperimentalmente) non sembra replicabile facilmente tal quale da un algoritmo. L'acqua cristallizza nel sistema esagonale, con i legami a idrogeno a 120° tra una molecola e l'altra. Quindi il processo di crescita è ricorrente nel senso fisico del termine - molecola per molecola - e le regole che lo governano vengono dalla meccanica quantistica, dalla geometria degli orbitali molecolari di H2O.
Questa immagine è stata generata dal seguente prompt:
Highly detailed macro photograph of a single perfect snowflake, intricate symmetrical hexagonal structure with delicate branches and ice crystals, sharp focus, captured under microscope, realistic photography style like Wilson Bentley or Nathan Myhrvold, high resolution, white background with subtle light reflections.
E in questo caso non si parla di algoritmi iterativi, ma di quello che viene dopo Photoshop, nonché di come non pagare il copyright sulle immagini. Ma non vi ingannate: i processi di cristallizzazione producono strutture del genere nella realtà, con simmetrie semplici come quelle del fiocco di neve o con simmetrie meno estese, a dare strutture dendritiche, anche esse frattali.
APPENDICE
Mathematica, oltre a una funzione MandelbrotSetPlot(), ha anche JuliaSetPlot(). In questo caso l'immagine del Julia Set è stata ottenuta con lo stesso tipo di codice Python usato per il Mandelbrot Set, impostando c come costante. Ma il modo più facile e gratuito per esplorare il Julia Set è usare Wolfram Alpha (free).
Per quanto riguarda la generazione del diagramma di biforcazione per il modello BFL lo schema generale del codice non è diverso da quello per la generazione di una mappa quadratica, ma non esiste un criterio generale di escape dal ciclo di generazione della matrice dei punti (|x|>2, nel caso della mappa quadratica). import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
r = 2.0
H0, P0 = 0.5, 0.1
a_min = 3.0
a_max = 5.0
n_a = 1500
n_transient = 2000
n_plot = 60 # basso: punti grandi coprono le bande rade
a_values = np.linspace(a_min, a_max, n_a)
xs, ys = [], []
for a in a_values:
H, P = H0, P0
for _ in range(n_transient):
Hn = H * np.exp(r*(1-H)) * np.exp(-a*P) #Modello BFL
Pn = H * (1 - np.exp(-a*P)) #Modello BFL
H, P = max(Hn,0.0), max(Pn,0.0) #Garantisce che H e P siano positivi
if not np.isfinite(H+P): H=P=0; break #condizione di fuga
for _ in range(n_plot):
Hn = H * np.exp(r*(1-H)) * np.exp(-a*P)
Pn = H * (1 - np.exp(-a*P))
H, P = max(Hn,0.0), max(Pn,0.0)
if not np.isfinite(H+P): break
xs.append(a); ys.append(H)
mpl.rcParams.update({
"font.family": "DejaVu Sans", "font.size": 11,
"axes.spines.top": False, "axes.spines.right": False,
"axes.linewidth": 0.7,
"xtick.direction": "out", "ytick.direction": "out",
})
fig, ax = plt.subplots(figsize=(9, 4.5), dpi=300)
ax.scatter(xs, ys, s=0.5, color="#4455aa", alpha=0.5,
linewidths=0, rasterized=True)
ax.set_xlabel("Parasitoid attack rate", labelpad=8)
ax.set_ylabel("Host density", labelpad=8)
ax.set_title(
"Beddington-Free-Lawton (1975) — diagramma di biforcazione\n"
r"$H_{t+1}=H_t\,e^{r(1-H_t)}\,e^{-aP_t}$ "
r"$P_{t+1}=H_t\!\left(1-e^{-aP_t}\right)$ "
f"$r={r}$",
fontsize=9, pad=10
)
ax.set_xlim(a_min, a_max)
ax.set_ylim(0, 2.0)
ax.set_xticks([3.0, 3.5, 4.0, 4.5, 5.0])
ax.tick_params(labelsize=9)
plt.tight_layout()
plt.show()
In breve, se qualcuno si ricorda cosa succede con la mappa quadratica, già in quel caso esponenziali negative e serie divergenti a + ∞ erano un problema da trattare. Ma |x|=2 era una soglia ricavabile analiticamente. Con il modello BFL non è possibile ricavare analiticamente la soglia. Quindi si evitano i valori negativi con (sarebbe a dire: se H o P calcolati sono negativi, il loro valore viene settato a 0); H, P = max(Hn,0.0), max(Pn,0.0)invece evita la divergenza all'infinito ancora una volta settando i valori di H e P a 0, uscendo dal ciclo interno e passando al valore successivo di if not np.isfinite(H+P): H=P=0; break . La soglia di fuga non è solo una necessità computazionale: non possono esistere densità infinite (o negative) dell'ospite o del parassitoide.a
Effettuando questi controlli ad ogni passo il programma è più machine intensive di quello per la mappa quadratica: 26,6 secondi contro i 6,5 secondi per una mappa quadratica - il Mandelbrot Set (Python 3, Windows 10 su un i5-7300 2.60 GHz).
`


























