User Guide#
This guide covers the physics implemented by montecarlo and how to apply
it to your own graphs.
The Ising model#
For a graph \(G = (V, E)\) with edge weights \(J_{ij}\), montecarlo
represents an Ising model with Hamiltonian
where \(s_i = +1\) if site \(i\) is “up” (bit value 1) and
\(s_i = -1\) if it is “down” (bit value 0). Each configuration
\(\alpha\) is represented by a BitString.
Representing configurations#
The BitString class wraps a NumPy array of 0/1 entries
and exposes utilities for flipping bits, counting up/down spins, and
converting to and from a decimal integer index:
import montecarlo
bs = montecarlo.BitString(N=4)
bs.set_integer_config(11) # binary 1011
print(bs) # "1011"
print(bs.on(), bs.off()) # 3, 1
bs.flip_site(0) # now "0011"
Building a Hamiltonian#
Hamiltonians are constructed from a NetworkX graph with weight attributes
on each edge. Optional local fields can be set with set_mu:
import networkx as nx
import montecarlo
N = 6
G = nx.Graph()
G.add_nodes_from(range(N))
G.add_edges_from([(i, (i + 1) % N) for i in range(N)])
for e in G.edges:
G.edges[e]['weight'] = 2.0
ham = montecarlo.IsingHamiltonian(G)
ham.set_mu([0.1] * N)
bs = montecarlo.BitString(N)
bs.set_config([1, 0, 1, 0, 1, 0])
print(ham.energy(bs))
Exact thermodynamic averages#
Given the Boltzmann distribution
an expectation value of an observable \(A\) is
The compute_average_values() method
enumerates all \(2^N\) configurations and returns
i.e. the average energy, magnetization, heat capacity, and magnetic susceptibility:
E, M, HC, MS = ham.compute_average_values(T=1.0)
This is exact, but its cost grows as \(2^N\) and becomes infeasible beyond roughly 20 sites.
Metropolis sampling#
For larger systems, MonteCarlo performs Metropolis
sampling. From the current configuration \(\alpha\), a single-site flip
proposes a new configuration \(\beta\), accepted with probability
The resulting Markov chain converges to the Boltzmann distribution, so expectation values reduce to a simple arithmetic average over collected samples:
import numpy as np
mc = montecarlo.MonteCarlo(ham)
E_samples, M_samples = mc.run(T=2.0, n_samples=10_000, n_burn=200)
Eavg, Estd = np.mean(E_samples), np.std(E_samples)
Mavg, Mstd = np.mean(M_samples), np.std(M_samples)
HC = Estd**2 / 2.0**2
MS = Mstd**2 / 2.0
The n_burn argument discards an initial number of sweeps to allow the
chain to equilibrate before recording samples. Each “sweep” attempts a flip
on every site once.
Temperature scans#
A common workflow is to compute observables across a range of temperatures and locate the critical temperature, where the heat capacity and magnetic susceptibility peak:
import numpy as np
import matplotlib.pyplot as plt
Ts = np.linspace(0.1, 10.0, 100)
Es, Ms, HCs, MSs = [], [], [], []
for T in Ts:
E, M, HC, MS = ham.compute_average_values(T)
Es.append(E); Ms.append(M); HCs.append(HC); MSs.append(MS)
plt.plot(Ts, Es, label="Energy")
plt.plot(Ts, Ms, label="Magnetization")
plt.plot(Ts, HCs, label="Heat capacity")
plt.plot(Ts, MSs, label="Susceptibility")
plt.xlabel("Temperature"); plt.legend(); plt.show()
Tc = Ts[int(np.argmax(MSs))]
print(f"Critical temperature ~ {Tc:.2f}")