Source code for montecarlo.mc
"""Metropolis Monte Carlo sampling for Ising models."""
import numpy as np
import random
from .bitstring import BitString
from .ising import IsingHamiltonian
[docs]
class MonteCarlo:
"""Metropolis Monte Carlo sampler for an Ising Hamiltonian.
Parameters
----------
ham : IsingHamiltonian
The Hamiltonian to sample
"""
def __init__(self, ham: IsingHamiltonian):
self.ham = ham
self.N = ham.N
[docs]
def run(self, T: float, n_samples: int = 1000, n_burn: int = 100):
"""Run Metropolis Monte Carlo sampling.
For each MC step, sweep over all sites proposing single spin flips.
Accept or reject based on the Metropolis criterion:
- If dE <= 0: accept
- If dE > 0: accept with probability exp(-dE/T)
Parameters
----------
T : float
Temperature
n_samples : int
Number of samples to collect after burn-in
n_burn : int
Number of burn-in sweeps to discard
Returns
-------
tuple of (energies, magnetizations)
energies : np.ndarray of length n_samples
magnetizations : np.ndarray of length n_samples
"""
conf = BitString(self.N)
# Start from a random configuration
for i in range(self.N):
if random.random() < 0.5:
conf.flip_site(i)
current_e = self.ham.energy(conf)
energies = np.zeros(n_samples)
magnetizations = np.zeros(n_samples)
total_sweeps = n_burn + n_samples
sites = list(range(self.N))
sample_idx = 0
for sweep in range(total_sweeps):
# Sweep over all sites in random order
random.shuffle(sites)
for site in sites:
# Propose a flip
conf.flip_site(site)
new_e = self.ham.energy(conf)
dE = new_e - current_e
if dE <= 0:
# Accept
current_e = new_e
elif random.random() < np.exp(-dE / T):
# Accept with Boltzmann probability
current_e = new_e
else:
# Reject: flip back
conf.flip_site(site)
# Record after burn-in
if sweep >= n_burn:
energies[sample_idx] = current_e
magnetizations[sample_idx] = conf.on() - conf.off()
sample_idx += 1
return energies, magnetizations