# Ising model simulation

Posted on February 5, 2018 by Dimitri Lozeve

The Ising model is a model used to represent magnetic dipole moments in statistical physics. Physical details are on the Wikipedia page, but what is interesting is that it follows a complex probability distribution on a lattice, where each site can take the value +1 or -1.

# Mathematical definition

We have a lattice \(\Lambda\) consisting of sites \(k\). For each site, there is a moment \(\sigma_k \in \{ -1, +1 \}\). \(\sigma = (\sigma_k)_{k\in\Lambda}\) is called the configuration of the lattice.

The total energy of the configuration is given by the Hamiltonian \[ H(\sigma) = -\sum_{i\sim j} J_{ij}\, \sigma_i\, \sigma_j, \] where \(i\sim j\) denotes neighbours, and \(J\) is the interaction matrix.

The configuration probability is given by: \[ \pi_\beta(\sigma) = \frac{e^{-\beta H(\sigma)}}{Z_\beta} \] where \(\beta = (k_B T)^{-1}\) is the inverse temperature, and \(Z_\beta\) the normalisation constant.

For our simulation, we will use a constant interaction term \(J > 0\). If \(\sigma_i = \sigma_j\), the probability will be proportional to \(\exp(\beta J)\), otherwise it would be \(\exp(\beta J)\). Thus, adjacent spins will try to align themselves.

# Simulation

The Ising model is generally simulated using Markov Chain Monte Carlo (MCMC), with the Metropolis-Hastings algorithm.

The algorithm starts from a random configuration and runs as follows:

1. Select a site \(i\) at random and reverse its spin: \(\sigma'_i = -\sigma_i\)
2. Compute the variation in energy (hamiltonian) \(\Delta E = H(\sigma') - H(\sigma)\)
3. If the energy is lower, accept the new configuration
4. Otherwise, draw a uniform random number \(u \in ]0,1[\) and accept the new configuration if \(u < \min(1, e^{-\beta \Delta E})\).

# Implementation

The simulation is in Clojure, using the Quil library (a Processing library for Clojure) to display the state of the system.

This post is “literate Clojure”, and contains `core.clj`. The complete project can be found on GitHub.

    (ns ising-model.core
      (:require [quil.core :as q]
            [quil.middleware :as m]))

The application works with Quil’s functional mode, with each function taking a state and returning an updated state at each time step.

The `setup` function generates the initial state, with random initial spins. It also sets the frame rate. The matrix is a single vector in row-major mode. The state also holds relevant parameters for the simulation: \(\beta\), \(J\), and the iteration step.

    (defn setup [size]
      "Setup the display parameters and the initial state"
      (q/frame-rate 300)
      (q/color-mode :hsb)
      (let [matrix (vec (repeatedly (* size size) #(- (* 2 (rand-int 2)) 1)))]
        {:grid-size size
         :matrix matrix
         :beta 10
         :intensity 10
         :iteration 0}))

Given a site \(i\), we reverse its spin to generate a new configuration state.

    (defn toggle-state [state i]
      "Compute the new state when we toggle a cell's value"
      (let [matrix (:matrix state)]
        (assoc state :matrix (assoc matrix i (* -1 (matrix i))))))

In order to decide whether to accept this new state, we compute the difference in energy introduced by reversing site \(i\): \[ \Delta E = J\sigma_i \sum_{j\sim i} \sigma_j. \]

The `filter some?` is required to eliminate sites outside of the boundaries of the lattice.

    (defn get-neighbours [state idx]
      "Return the values of a cell's neighbours"
      [(get (:matrix state) (- idx (:grid-size state)))
       (get (:matrix state) (dec idx))
       (get (:matrix state) (inc idx))
       (get (:matrix state) (+ (:grid-size state) idx))])

    (defn delta-e [state i]
      "Compute the energy difference introduced by a particular cell"
      (* (:intensity state) ((:matrix state) i)
         (reduce + (filter some? (get-neighbours state i)))))

We also add a function to compute directly the hamiltonian for the entire configuration state. We can use it later to log its values across iterations.

    (defn hamiltonian [state]
      "Compute the Hamiltonian of a configuration state"
      (- (reduce + (for [i (range (count (:matrix state)))
                 j (filter some? (get-neighbours state i))]
             (* (:intensity state) ((:matrix state) i) j)))))

Finally, we put everything together in the `update-state` function, which will decide whether to accept or reject the new configuration.

    (defn update-state [state]
      "Accept or reject a new state based on energy
      difference (Metropolis-Hastings)"
      (let [i (rand-int (count (:matrix state)))
        new-state (toggle-state state i)
        alpha (q/exp (- (* (:beta state) (delta-e state i))))]
        ;;(println (hamiltonian new-state))
        (update (if (< (rand) alpha) new-state state)
            :iteration inc)))

The last thing to do is to draw the new configuration:

    (defn draw-state [state]
      "Draw a configuration state as a grid"
      (q/background 255)
      (let [cell-size (quot (q/width) (:grid-size state))]
        (doseq [[i v] (map-indexed vector (:matrix state))]
      (let [x (* cell-size (rem i (:grid-size state)))
            y (* cell-size  (quot i (:grid-size state)))]
        (q/no-stroke)
        (q/fill
         (if (= 1 v) 0 255))
        (q/rect x y cell-size cell-size))))
      ;;(when (zero? (mod (:iteration state) 50)) (q/save-frame "img/ising-######.jpg"))
      )

And to reset the simulation when the user clicks anywhere on the screen:

    (defn mouse-clicked [state event]
      "When the mouse is clicked, reset the configuration to a random one"
      (setup 100))

    (q/defsketch ising-model
      :title "Ising model"
      :size [300 300]
      :setup #(setup 100)
      :update update-state
      :draw draw-state
      :mouse-clicked mouse-clicked
      :features [:keep-on-top :no-bind-output]
      :middleware [m/fun-mode])

# Conclusion

The Ising model is a really easy (and common) example use of MCMC and Metropolis-Hastings. It allows to easily and intuitively understand how the algorithm works, and to make nice visualizations!