The two dimensional Ising model possesses all of the features that we expect of a true ferromagnetic system: spontaneous magnetization, critical behavior, and hysteresis.
A Monte Carlo simulation illustrates all of this quite graphically. I use common lisp in this coding example, and the SDL graphics library (which is great for coding games and real-time graphics). The lispbuilder-sdl package can be installed on your computer system with quicklisp . I recommend using sbcl, which is the fastest lisp available.
Let’s get to the fun first. I am running the simulation on a Rockpro64, which is a 79.00 SBC computer with 4Gb of ram and a 64-bit ARM processor, so a very modest machine. It is run in real time, and I use vokoscreen to capture the running simulation as an mp4 stream, which we are able to embed below.
I will slowly ramp up the temperature, and you can see the scale-invariant macrostate with domains of all sizes form at around the critical temperature T=2.4 k_B J.
I will increase the temperature well past this, then very rapidly drop it back down to nearly zero. When the simulation began at very low-T, we had one large domain of spins-down, with a few fluctuations of spin-up sites. Now you will see hysteresis: we will rapidly drop to the original low temperature but the large domains that form will be of a totally different nature from the initial.
Notice that near the critical temperature the simulation slows down a lot, because we use the accept-reject algorithm for the Boltzmann distribution, and there are many rejects.
For those of you that may wish to try it out, the code is below.
#| Jeff Schmidt 2015,
First we create some classes for handling the lattice and its measurements
then we set up the graphics. This requires lispbuilder-sdl, and should be
run under sbcl, clisp or ccl ;
(load \"sdl-Ising2D.lisp\") remove the slashes!
(sdl-Ising2D) |#
(defclass lattice ()
((size :accessor size :initarg :size)
(spins :accessor spins :initarg :spins)
(energy :accessor energy :initarg :energy)
(magnetization :accessor magnetization :initarg :magnetization)))
(defun environ (m i j n)
"Local environment of a nxn matrix PBC"
(+ (aref m (mod (+ i 1) n) j) (aref m (mod (- i 1) n) j)
(aref m i (mod (+ j 1) n)) (aref m i (mod (- j 1) n))))
(defgeneric find-mag (obj))
(defmethod find-mag ((lat lattice))
"Compute by hand the average magnetic moment"
(let* ((n (size lat)))
(/ (loop for i from 0 to (- n 1) sum
(loop for j from 0 to (- n 1) sum (aref (spins lat) i j))) (* 1.0d0 n n))))
(defgeneric find-energy (obj))
(defmethod find-energy ((lat lattice))
"Compute by hand the total energy"
(let* ((n (size lat)))
(loop for i from 0 to (- n 1) sum
(loop for j from 0 to (- n 1) sum
(* (aref (spins lat) i j) (environ (spins lat) i j n) -0.5d0)))))
(defmethod initialize-instance :after
((lat lattice) &rest args)
"Initialize the energy/magnetization slots after the spin-state is loaded"
(setf (energy lat) (find-energy lat))
(setf (magnetization lat) (find-mag lat)))
(defgeneric update-lattice (obj tee))
(defmethod update-lattice ((lat lattice) (temp number))
"Update-washout of the lattice, run to erase its memory"
(let* ((n (size lat)) (i (random n)) (j (random n))
(de (* 2.0d0 (aref (spins lat) i j) (environ (spins lat) i j n))))
(if (< de 0.0d0)
(progn (setf (aref (spins lat) i j) (* -1 (aref (spins lat) i j)))
(setf (energy lat) (+ (energy lat) de))
(setf (magnetization lat) (+ (magnetization lat)
(* 2 (aref (spins lat) i j)))))
(progn
(if (<= (random 1.0d0) (exp (/ (* -1.0d0 de) temp)))
(progn (setf (aref (spins lat) i j) (* -1 (aref (spins lat) i j)))
(setf (energy lat) (+ (energy lat) de))
(setf (magnetization lat) (+ (magnetization lat)
(* 2 (aref (spins lat) i j))))))))
))
(defgeneric measure-lattice (obj tee en))
(defmethod measure-lattice ((lat lattice) (temp number) (M number))
"Data collection, finds average M,E at T=temp, averaging over M updates"
(loop for trials from 1 to M do (update-lattice lat temp)
summing (energy lat) into Esum
summing (magnetization lat) into Msum
finally (return (/ Esum M 10000))))
;;That's it for classes, let's get busy!
(require 'lispbuilder-sdl)
(defvar ell (make-instance 'lattice :size 100 :spins
(make-array (list 100 100) :initial-element -1)))
(defvar dT 0.05)
(defvar Temp 1.0)
(defun sdl-Ising2D ()
"Two-dimensional Ising model, the video game (Jeff Schmidt 2015)"
(sdl:with-init ()
(sdl:window 600 405 :title-caption "2D-Ising")
(sdl:initialise-default-font sdl:*font-10x20*)
(setf (sdl:frame-rate) 20)
(sdl:with-events (:poll)
(:quit-event () t)
(:key-down-event
(:key key)
(case key
(:sdl-key-up (incf Temp dT))
(:sdl-key-down (decf Temp dT))
(:sdl-key-p (sleep 2))
(:sdl-key-q (sdl:push-quit-event))))
(:idle ()
(sdl:clear-display sdl:*black*)
(loop for i from 0 to 10000 do (update-lattice ell Temp))
(loop for i from 0 to 99 do
(loop for j from 0 to 99 do (if (= 1 (aref (spins ell) i j))
(sdl:draw-filled-circle (sdl:point :x (+ (* i 4) 3)
:y (+ (* j 4) 3))
2
:color sdl:*yellow*
:stroke-color sdl:*white*))))
(sdl:draw-string-solid-* (format nil "10^4 spins" )
420 20
:color sdl:*red* :justify :left)
(sdl:draw-string-solid-* (format nil "Showing \"up\"" )
420 40
:color sdl:*red* :justify :left)
(sdl:draw-string-solid-* (format nil "spins only" )
420 60
:color sdl:*red* :justify :left)
(sdl:draw-string-solid-* (format nil "kT/J=~,3F" Temp)
420 100
:color sdl:*red* :justify :left)
(sdl:draw-string-solid-* (format nil "E/N=~,3F" (measure-lattice ell Temp 1000))
420 120
:color sdl:*red* :justify :left)
(sdl:draw-string-solid-* (format nil "up increases T")
420 200
:color sdl:*blue* :justify :left)
(sdl:draw-string-solid-* (format nil "down decreases T")
420 220
:color sdl:*blue* :justify :left)
(sdl:draw-string-solid-* (format nil "p pauses")
420 240
:color sdl:*blue* :justify :left)
(sdl:draw-string-solid-* (format nil "q quits")
420 260
:color sdl:*blue* :justify :left)
(sdl:update-display)
)
)
)
)