Path Integral Formulation¶

da PowerShell -- jupyter nbconvert --to html notebook.ipynb

Cammino di Feynman -- Random path sampling¶

Generi cammini completamente casuali. Calcoli l’azione totale. Fai la media di $e^{iS}$

Caratteristiche:

  • ❌ Nessun aggiornamento locale
  • ❌ Nessun criterio di accettazione
  • ❌ Non è un Markov Chain Monte Carlo
  • ✔ Didattico, ma non è Metropolis
In [9]:
import numpy as np

def lagrangian(q, q_dot, t):
    """
    L = T - V per oscillatore armonico
    """
    T = 0.5 * q_dot**2
    V = 0.5 * q**2
    return T - V

def classical_action(q, t, L):
    """
    Calcola l'azione classica S = ∫ L dt (discretizzata)
    """
    dt = t[1] - t[0]

    # velocità discretizzata
    q_dot = np.diff(q) / dt

    # usiamo q e t fino a N-1 per coerenza con q_dot
    L_vals = L(q[:-1], q_dot, t[:-1])

    return np.sum(L_vals) * dt

def monte_carlo_path_integral(N, num_steps, t0, t1, q0, q1):
    """
    Monte Carlo per il path integral
    """
    t = np.linspace(t0, t1, num_steps + 1)
    dt = t[1] - t[0]

    S_values = np.zeros(N)

    for i in range(N):
        # path casuale con estremi fissati
        q = np.zeros(num_steps + 1)
        q[0] = q0
        q[-1] = q1

        # punti intermedi random (Gaussiani)
        q[1:-1] = np.random.normal(0.0, 0.5, size=num_steps - 1)

        # azione
        S_values[i] = classical_action(q, t, lagrangian)

    # ampiezza di transizione (fattore di fase)
    amplitude = np.mean(np.exp(1j * S_values))

    return amplitude

# =========================
# Esempio di utilizzo
# =========================

N = 2000
num_steps = 100
t0, t1 = 0.0, 1.0
q0, q1 = 0.0, 1.0

transition_amplitude = monte_carlo_path_integral(
    N, num_steps, t0, t1, q0, q1
)

print("Estimated Transition Amplitude:", transition_amplitude)
Estimated Transition Amplitude: (-0.00614661219865214+0.012142531164836852j)

Cammino di Feynman -- Metropolis Monte Carlo su lattice (Path Integral lattice)¶

Qui l’oggetto non è “un cammino indipendente”, ma una configurazione di campo che viene aggiornata localmente.¶

  • Inizializza una configurazione $q(t_n)$
  • Proponi una modifica locale $q_n\, \rightarrow \, q_n′$
  • Calcoli solo la variazione di azione $\Delta S$
  • Accetti o rifiuti con Metropolis
  • Ripeti $\rightarrow$ catena di Markov
  • Media sugli osservabil

Azione euclidea discretizzata (oscillatore armonico, 1D) ¶

$$\large S_E[q] =\sum_{n=0}^{N-2} \left[ \frac{(q_{n+1} - q_n)^2}{2,\Delta\tau}\frac{\Delta\tau}{2}, q_n^2\right] $$

In [37]:
import numpy as np

# -------------------------------
# Parametri del problema
# -------------------------------
Nt = 100                 # punti del reticolo temporale
tau0, tau1 = 0.0, 1.0
dt = (tau1 - tau0) / (Nt - 1)

n_thermal = 5000         # passi di termalizzazione
n_steps = 20000          # passi Monte Carlo
delta = 0.5              # ampiezza proposta

q0 = 0.0                 # condizioni al bordo
q1 = 0.0

# -------------------------------
# Inizializzazione configurazione
# -------------------------------
q = np.zeros(Nt)
q[0] = q0
q[-1] = q1

# -------------------------------
# Contributo locale all'azione
# -------------------------------
def local_action(q, n):
    """
    Contributo all'azione che dipende da q[n]
    Azione Euclidea discretizzata
    """
    s = 0.0

    if n > 0:
        s += (q[n] - q[n-1])**2 / (2 * dt)

    if n < Nt - 1:
        s += (q[n+1] - q[n])**2 / (2 * dt)

    s += 0.5 * dt * q[n]**2

    return s

# -------------------------------
# Metropolis update
# -------------------------------
def metropolis_step(q):
    # scegli punto interno del reticolo
    n = np.random.randint(1, Nt - 1)

    old_qn = q[n]
    old_S = local_action(q, n)

    # proposta casuale
    q[n] += delta * np.random.normal()

    new_S = local_action(q, n)
    dS = new_S - old_S

    # criterio di Metropolis
    if dS > 0 and np.random.rand() > np.exp(-dS):
        q[n] = old_qn  # rifiuta

# -------------------------------
# Termalizzazione
# -------------------------------
for _ in range(n_thermal):
    metropolis_step(q)

# -------------------------------
# Monte Carlo + misura osservabili
# -------------------------------
q2_samples = []

for _ in range(n_steps):
    metropolis_step(q)
    q2_samples.append(np.mean(q**2))

# -------------------------------
# Risultato
# -------------------------------
print("<q^2> =", np.mean(q2_samples))
<q^2> = 0.035637215845587274

Interpretazione corretta del “massimo” che osservi al variate di $tau1$
Quello che cresce e poi decresce non è una probabilità nello spazio, ma:
la capacità del sistema di fluttuare su una finestra temporale

  • per $\tau 1$ piccolo → il campo è “rigido”
  • per $\tau 1$ intermedio $\rightarrow$ fluttuazioni massime
  • per $\tau 1$ grande $\rightarrow$ domina il ground state, che sopprime tutto
  • il massimo identifica la scala di correlazione del sistema:
    $ξ=\frac{1}{E_1−E_0}$
  • per oscillatore armonico $ξ=1$

Cammino di Feynman -- Metropolis Monte Carlo su lattice (Path Integral lattice)¶

In [1]:
import numpy as np
import matplotlib.pyplot as plt

# -----------------------------
# Parametri fisici
# -----------------------------
m = 1.0
omega = 1.0
hbar = 1.0

T = 10.0          # tempo euclideo totale
N = 200           # punti del path
dt = T / N

# -----------------------------
# Azione euclidea discretizzata
# -----------------------------
def euclidean_action(x):
    kinetic = 0.5 * m * np.sum((np.roll(x, -1) - x)**2) / dt
    potential = 0.5 * m * omega**2 * np.sum(x**2) * dt
    return kinetic + potential

# -----------------------------
# Metropolis update
# -----------------------------
def metropolis_step(x, delta=0.5):
    i = np.random.randint(N)
    x_new = x.copy()
    x_new[i] += np.random.uniform(-delta, delta)

    dS = euclidean_action(x_new) - euclidean_action(x)
    if dS < 0 or np.random.rand() < np.exp(-dS / hbar):
        return x_new
    return x

# -----------------------------
# Simulazione
# -----------------------------
np.random.seed(0)
x = np.zeros(N)

thermalization = 5000
samples = 20000
paths = []

for step in range(thermalization + samples):
    x = metropolis_step(x)
    if step >= thermalization:
        paths.append(x.copy())

paths = np.array(paths)

# -----------------------------
# Osservabili
# -----------------------------
x2_mean = np.mean(paths[:, N//2]**2)

print("⟨x²⟩ (Monte Carlo) =", x2_mean)
print("⟨x²⟩ esatto =", hbar / (2 * m * omega))

# -----------------------------
# Grafico di alcuni path
# -----------------------------
plt.figure()
for i in range(10):
    plt.plot(paths[i], alpha=0.6)

plt.xlabel("τ (indice discreto)")
plt.ylabel("x(τ)")
plt.title("Cammini quantistici (path integral)")
plt.show()
⟨x²⟩ (Monte Carlo) = 0.22635490413899484
⟨x²⟩ esatto = 0.5
No description has been provided for this image

Cammino di Feynman applicato alla rifrazione¶

Questo esercizio mostra, in modo semplice ma profondo, come la legge di Snell dell’ottica geometrica emerga naturalmente da una descrizione ondulatoria, ispirata al formalismo degli integrali sui cammini.

L’obiettivo non è ``risolvere un problema di rifrazione'' nel modo tradizionale, ma capire $\textit{perché}$ esiste un cammino privilegiato per la luce.

Geometria del problema¶

Consideriamo due punti fissi:

  • $A$: sorgente luminosa, situata nell’aria;
  • $B$: osservatore, situato nel vetro.

Tra i due mezzi esiste un’interfaccia piana orizzontale, posta a $y=0$. La luce parte da $A$, attraversa l’interfaccia in un punto $$\large P = (x,0), $$ e poi raggiunge $B$.

Il punto di attraversamento $x$ $\textit{non è noto a priori}$.

Indici di rifrazione¶

I due mezzi sono caratterizzati da indici di rifrazione diversi: $$\large n_1 = 1 \quad \text{(aria)}, \qquad n_2 = 1.5 \quad \text{(vetro)}. $$

In ottica, l’indice di rifrazione misura quanto la luce ``rallenta'' in un mezzo.

Il cammino ottico¶

Per ogni possibile punto $x$ sull’interfaccia, la luce percorre due tratti:

  • da $A$ a $P$ nel mezzo 1;
  • da $P$ a $B$ nel mezzo 2.

Il cammino ottico totale è definito come $$\large S(x) = n_1 |AP| + n_2 |PB|. $$

Questa quantità gioca lo stesso ruolo dell’azione in meccanica classica e quantistica.

Tutti i cammini sono ammessi¶

Invece di cercare direttamente il cammino ``giusto'', si considerano $\textit{tutti}$ i possibili punti $x$ sull’interfaccia.

In altre parole:

  • "la luce può attraversare l’interfaccia in qualunque punto".
  • Questo è il punto di vista fondamentale del path integral.

Fase associata a ogni cammino¶

A ogni cammino viene associata un’ampiezza complessa: $$\large \exp\!\bigl(i k S(x)\bigr), $$ dove:

  • $k$ è il numero d’onda;
  • $S(x)$ è il cammino ottico.

Se $k$ è grande (lunghezza d’onda piccola), la fase oscilla rapidamente quando $S(x)$ cambia.

Somma dei contributi¶

L’ampiezza totale osservata in $B$ è ottenuta sommando (integrando) i contributi di tutti i cammini possibili: $$\large \mathcal{A} \sim \int dx \, e^{i k S(x)}. $$

Non si sceglie quindi un singolo cammino: tutti contribuiscono, ma con fasi diverse.

In realtà dentro l'integrale ci dovrebbe stare il cammino ottico generico. Ma sapendo che la luce si propaga in modo rettilineo, si può ottimizzare rispetto al punto di attraversamento $dx$ (non può essere un punto, ma un infinitesimo).

Principio di fase stazionaria¶

Quando $k$ è grande, la somma è dominata dai punti in cui la fase varia poco, cioè dai punti in cui: $$\large \frac{dS}{dx} = 0. $$

In questi punti:

  • le fasi dei cammini vicini sono simili;
  • i contributi si sommano costruttivamente.

Lontano da questi punti, invece, le fasi oscillano rapidamente e si cancellano a vicenda.

Emergenza della legge di Snell¶

Il punto $x^\*$ che soddisfa $$\large \frac{dS}{dx} = 0 $$ coincide esattamente con il punto di attraversamento previsto dalla legge di Snell: $$\large n_1 \sin\theta_1 = n_2 \sin\theta_2. $$

La legge dell’ottica geometrica emerge quindi come $\textit{risultato collettivo}$ dell’interferenza dei cammini.

Interpretazione fisica¶

Il messaggio concettuale dell’esercizio è il seguente:

  • La luce esplora tutti i cammini possibili,
  • ma solo il cammino classico sopravvive
  • per interferenza costruttiva.

Il cammino classico non è imposto, ma emerge naturalmente nel limite di lunghezza d’onda piccola.

Conclusione¶

Questo esercizio mostra come:

  • l’ottica geometrica sia un limite dell’ottica ondulatoria;
  • il principio di Fermat sia analogo al principio di minima azione;
  • il linguaggio degli integrali sui cammini chiarisca l’origine delle leggi classiche.

Si tratta di un esempio semplice ma estremamente istruttivo di come concetti ``quantistici'' producano risultati classici.

In [4]:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import simpson

# -----------------------------
# Parametri fisici
# -----------------------------
n1, n2 = 1.0, 1.5    # indice di rifrazione aria / vetro
k = 200.0             # numero d'onda (grande → limite classico)
A = np.array([0.0, 1.0])    # sorgente
B = np.array([1.0, -1.0])   # osservatore

# griglia di punti sull'interfaccia
xs = np.linspace(-1, 2, 5000)

# -----------------------------
# Funzione cammino ottico
# -----------------------------
def optical_path(x):
    P = np.array([x, 0.0])
    L1 = np.linalg.norm(P - A)
    L2 = np.linalg.norm(B - P)
    return n1*L1 + n2*L2

# calcolo azione per tutti i punti
S = np.array([optical_path(x) for x in xs])
phase = np.exp(1j * k * S)

# -----------------------------
# Trova punto di fase stazionaria
# -----------------------------
dSdx = np.gradient(S, xs)
idx_stationary = np.argmin(np.abs(dSdx))
x_star = xs[idx_stationary]
print("Punto di attraversamento più probabile x* =", x_star)

# -----------------------------
# Integrazione locale attorno al massimo
# -----------------------------
window = 50  # numero di punti da includere attorno al massimo
x_window = xs[idx_stationary - window : idx_stationary + window]
phase_window = phase[idx_stationary - window : idx_stationary + window]

amp_local = simpson(phase_window, x_window)
intensity_local = np.abs(amp_local)**2
print("Intensità locale attorno al massimo =", intensity_local)

# -----------------------------
# Visualizzazione
# -----------------------------
# calcolo intensità locale finestra mobile per grafico
local_intensity = np.array([
    np.abs(simpson(phase[i-window:i+window], xs[i-window:i+window]))**2
    for i in range(window, len(xs)-window)
])
xs_local = xs[window:-window]

plt.figure(figsize=(8,4))
plt.plot(xs_local, local_intensity)
plt.axvline(x_star, color='red', linestyle='--', label="Cammino classico")
plt.xlabel("x (punto di attraversamento)")
plt.ylabel("|Ampiezza locale|^2")
plt.title("Path integral ottico → cammino di Snell")
plt.legend()
plt.show()
Punto di attraversamento più probabile x* = 0.6233246649329867
Intensità locale attorno al massimo = 0.0035215019537106406
No description has been provided for this image
In [ ]: