Correlation times#
# Load packages
using Plots
using OffsetArrays
using Random
using ProgressMeter
using Statistics
using Plots.PlotMeasures
using LinearAlgebra
using LaTeXStrings
using HDF5
using CSV
using CSVFiles
using Tables, DataFrames
Review#
Last day we began discussing the 2D Ising Model with Energy:
We used units where \(J=1\), and wrote
function Energy(s #=spin configuration=#,h)
m, n = size(s)
E=0.
@inbounds for i in 1:m
@inbounds for j in 1:n
E+=-s[i,j]*(h+s[mod1(i+1,m),j]+s[i,mod1(j+1,n)])
end
end
return E/(m*n)
end
Energy (generic function with 1 method)
We also claimed that many of the features of this model could be understood from the properties of the magnetization:
Magnetization(s#=spin configuration=#)=mean(s)
Magnetization (generic function with 1 method)
We made code to give us a random spin pattern
rand_ising2d(m=128, n=m) = rand(Int8[-1, 1], m, n)
heatmap(rand_ising2d(),aspect_ratio=1,
c=cgrad([:black, :white]),legend=:none,
xlabel="x",ylabel="y")
We then made a code to use a Markov Chain to generate samples from the equilibrium distribution of this model at temperature \(T=1/\beta\).
function randlocalupdate!(s,probflip,numsweeps)
m, n = size(s)
@inbounds for sweep in 1:numsweeps*m*n
# Choose random spin
i=rand(1:m)
j=rand(1:n)
# Find environment
τ₁=s[mod1(i-1,m),j]
τ₂=s[mod1(i+1,m),j]
τ₃=s[i,mod1(j-1,n)]
τ₄=s[i,mod1(j+1,n)]
τ=τ₁+τ₂+τ₃+τ₄
σ=s[i,j]
# Propose flip
if rand()<probflip[σ,τ]
s[i,j]=-σ
end
end
return s
end
struct isingparams
β ::Float64
h ::Float64
end
function randlocalupdate!(s,numsweeps,p::isingparams)
probflip=isingprobs(p.β,p.h)
randlocalupdate!(s,probflip,numsweeps)
end
"""
isingprobs(β,h,numneighbors)
returns a 2D array of probabilities, `probflip[σ,τ]` which is the
probability of flipping a spin if site `i` is in state `σ`, and `τ` is the sum of the
neighbor spins. Here `sigma` is `1` or `-1` and `τ` goes from `-numneighbors` to `numneighbors`.
``P_{\\rm flip}(\\sigma,\\tau)= e^{-2 \\beta \\sigma \\tau -2 \\beta h \\sigma}``
"""
isingprobs(β,h=0,numneighbors=4)=
OffsetArray([exp(-2*β*(τ+h)*σ) for σ in -1:1, τ in -numneighbors:numneighbors],
-1:1,-numneighbors:numneighbors)
isingprobs
# sample usage
m1=2^8
β1=10.
h1=0.
s1=rand_ising2d(m1)
p1=isingprobs(β1,h1)
@time randlocalupdate!(s1,p1,10^3)
heatmap(s1,
aspect_ratio=1,c=cgrad([:black, :white]),legend=:none,
xlabel="x",ylabel="y")
0.791543 seconds (65.01 k allocations: 4.277 MiB, 1.92% compilation time)
That code is super-simple, and works great.
With it we can uncover the basic physics: At low temperatures the system is ordered – all the spins point in the same direction. At high temperature it is random.
Correlation and Relaxation Times#
Today we are not going to talk about the physics of the Ising Model. Instead we are going to talk about properties of the algorithm – viewing the Markov Chain as a dynamical system. This will be essential to understand how to use it.
## Mini Lab 1
We want to make a function, which takes an initial configuration, and updates it with our algorithm, recording the energy and magnetization at some regular intervals. To do this we need several parameters:
β
,h
,m
,n
– the inverse temperature, magnetic field, and the geometryoptional:
s
– the initial configuration. If we gives
, then you don’t needm
andn
innersweeps
– how many sweeps to do between every measurement ofEnergy
andMagnetization
. We might want to keep this equal to1
if things converge quickly, or up this number if it is taking a long time to converge. I like to think of it as our “stepsize”outersweeps
– how many total measurements we want to do
It should then return a named tuple with several entries: (Elist=??,maglist=??,dims=??,β=??, h=??,innersweeps=??)
. Here dims is the dimension of the lattice. We will need it later
Here is an outline function – you just need to fill in the blanks. If you want to be able to call by keyword, add the appropriate convenience methods.
function simulate(m::Int64,n::Int64,β::Float64,h::Float64,
innersweeps::Int64,outersweeps::Int64)
s=rand_ising2d(m,n)
simulate!(s,β,h,innersweeps,outersweeps)
end
function simulate!(s,β::Float64,h::Float64,
innersweeps::Int64,outersweeps::Int64)
# First generate the probability distribution
# Create empty lists for storing the energies and magnetizations
# next do the outer loop
for outer in 1:outersweeps
#
# call randlocalupdate! -- passing the number of innersweeps
# then calculate/store E and m in the lists
end
return (Elist= ,maglist=, m= , dims= ,h= ,innersweeps=innersweeps)
end
ParseError:
# Error @ ]8;;file:///Users/emueller/Documents/Work Laptop 2011-2012/PHYS 4480-7680 Computational Physics/git/phys4480/In[7]#23:20\In[7]:23:20]8;;\
return (Elist= ,maglist=, m= , dims= ,h= ,innersweeps=innersweeps)
# ╙ ── unexpected `,`
Stacktrace:
[1] top-level scope
@ In[7]:23
Once you have the function working, see if you can run it for a sequence of temperatures, and plot the average of the absolute value of the magnetization, as a function of temperature.
How does this algorithm behave?#
To understand how the algorithm works, lets just run it for a couple parameters.
# My version of the function
function simulate(m::Int64,n::Int64,β::Float64,h::Float64,
innersweeps::Int64,outersweeps::Int64)
s=rand_ising2d(m,n)
simulate!(s,β,h,innersweeps,outersweeps)
end
function simulate!(s,β::Float64,h::Float64,
innersweeps::Int64,outersweeps::Int64)
# First generate the probability distribution
pflip=isingprobs(β,h)
# Create empty lists for storing the energies and magnetizations
Elist=Float64[]
maglist=Float64[]
# next do the outer loop
for outer in 1:outersweeps
#
randlocalupdate!(s,pflip,innersweeps)
E=Energy(s,h)
mag=Magnetization(s)
push!(Elist,E)
push!(maglist,mag)
# call randlocalupdate!
# then calcualte/store E and m in the lists
end
return (Elist=Elist ,maglist=maglist, dims=size(s) ,β=β ,h=h ,innersweeps=innersweeps)
end
simulate!(;s,β,h,innersweeps,outersweeps)=simulate!(s,β,h,innersweeps,outersweeps)
simulate! (generic function with 2 methods)
Lets take a moderately sized system – say 64x64, start with a random configuration, and run it a bunch of times. It is a stochastic algorithm, so one expects different answers each time – but the average should be robust
s=rand_ising2d(2^6,2^6)
β=0.4
efig=plot()
mfig=plot()
edata=Array[]
mdata=Array[]
@time for realization in 1:10
s=rand_ising2d(2^6,2^6)
run=simulate!(s,β,0.,1,200)
plot!(efig,run.Elist,widen=false,label="",title="Energy, 2^6 x 2^6",xlabel="iteration",ylabel="E")
plot!(mfig,run.maglist,widen=false,label="",title="Magnetization, 2^6 x 2^6",xlabel="iteration",ylabel="M")
push!(edata,run.Elist)
push!(edata,run.maglist)
end
plot(efig,mfig,layout=[1,1])
0.245005 seconds (146.64 k allocations: 10.183 MiB, 33.79% compilation time)
It appears that we started in a very rare state. The probability that we would find that state at that temperature is exponentially small. Thus if we simply averaged these values, the mean would be skewed by those initial values.
When we run our Markov Chain, we will therefore have to discard some number of values. Based on the energy plot, it looks like we would need to discard about 50 entries. Looking at the magnetization, it is probably closer to 100. Apparently the energy relaxes to its equilibrium distribution faster than the magnetization.
Lets see if we can extract the relaxation time. Note, we often use the term time for Monte-Carlo steps. I should emphasize however that it bears no relation to actual time. It is a property of the algorithm, not a property of the physical system or the model.
Lets average a bunch of runs to get good statistics.
s=rand_ising2d(2^6,2^6)
β=0.4
numiter=100
run=simulate!(s,β,0.,1,300)
totalE=run.Elist
totalEsq=(run.Elist).^2
totalm=run.maglist
totalabsm=abs.(run.maglist)
totalmsq=(run.maglist).^2
@time for iter in 2:numiter
s=rand_ising2d(2^6,2^6)
run=simulate!(s,β,0.,1,300)
totalE .+= run.Elist
totalEsq .+= (run.Elist).^2
totalm .+= run.maglist
totalabsm .+= abs.(run.maglist)
totalmsq .+= (run.maglist).^2
end
meanE=totalE./numiter
meanEsq=totalEsq./numiter
meanm=totalm./numiter
meanabsm=totalabsm./numiter
meanmsq=totalmsq./numiter;
2.411604 seconds (181.25 k allocations: 13.929 MiB, 0.21% gc time, 2.73% compilation time)
Given that each run is independent, we can use the fluctuations to estimate our uncertainty in the means.
uncertaintyE=@. sqrt((meanEsq-meanE^2)/numiter);
uncertaintym=@. sqrt((meanmsq-meanm^2)/numiter);
uncertaintyabsm=@. sqrt((meanmsq-meanabsm^2)/numiter);
plot(meanE)
scatter!(1:10:length(meanE),meanE[1:10:end],
markersize=1.,yerr=uncertaintyE[1:10:end])
Those error bars are consistent with the scatter of the points, so we should feel good about them. Lets try the magnetization
plot(meanm,xlabel="iteration",ylabel="magnetization",label="m")
plot!(meanabsm,label="|m|")
scatter!(1:10:length(meanm),meanm[1:10:end],
markersize=1.,yerr=uncertaintym[1:10:end],label="")
scatter!(1:10:length(meanabsm),meanabsm[1:10:end],
markersize=1.,yerr=uncertaintyabsm[1:10:end],label="")
Again, the error bars look reasonable. They visually appear larger than the energy error bars, and are consistent with the observed jitter in the curve.
The magnetization is always consistent with zero – but the absolute value of the magnetization has structure.
We can now define a relaxation time by taking the moment of the distribution. Later we will define some other characteristic times.
aveE=mean(meanE[100:end])
τErelax=sum(abs(meanE[j]-aveE)*j for j in 1:100)/
sum(abs(meanE[j]-aveE) for j in 1:100)
plot(abs.(meanE.-aveE)[1:100],label="E",xlabel="iteration",
ylabel="Energy")
plot!([τErelax,τErelax],[0,abs(meanE[1]-aveE)],label="τErelax")
aveabsm=mean(meanabsm[200:end])
τabsmrelax=sum(abs(meanabsm[j]-aveabsm)*j for j in 1:200)/
sum(abs(meanabsm[j]-aveabsm) for j in 1:200)
plot(abs.(meanabsm.-aveabsm)[1:200],
label="|m|",xlabel="iteration",
ylabel="Magnetization")
scatter!(1:10:200,abs.(meanabsm[1:10:200].-aveabsm),
markersize=1.,yerr=uncertaintyabsm[1:10:200],label="")
plot!([τabsmrelax,τabsmrelax],[0,abs(meanabsm[1]-aveabsm)],label="τₘ relaxation")
We could have done the same analysis starting from a different initial state, for example the uniform spin configuration:
fill!(s,1)
β=0.4
efig=plot()
mfig=plot()
edata=Array[]
mdata=Array[]
@time for realization in 1:10
fill!(s,1)
run=simulate!(s,β,0.,1,200)
plot!(efig,run.Elist,widen=false,label="",title="Energy, 2^6 x 2^6",xlabel="iteration",ylabel="E")
plot!(mfig,run.maglist,widen=false,label="",title="Magnetization, 2^6 x 2^6",xlabel="iteration",ylabel="M")
push!(edata,run.Elist)
push!(mdata,run.maglist)
end
plot(efig,mfig,layout=[1,1])
0.161623 seconds (7.61 k allocations: 694.469 KiB)
It looks like the relaxation here was even longer: The energy looks like it took twice as long to converge, and the magnetization never converged on this timescale.
Lessons#
When using Markov chain Monte Carlo, you need to discard a the first few data points – as you are sampling rare configurations.
Starting with good configurations will reduce the relaxation time.
Often we will want to sweep parameters. For example, we may want to know the magnetization as a function of temperature. A good strategy is to use a configuration from the last temperature as a “seed” for the Markov Chain at the next temperature. If the temperature step is small, then this will be a plausible configuration for the new temperature. Thus we have fewer data points we need to throw away.
Workflow comments#
An important thing to pay attention to here was our workflow. To create those cells that I ran, I did a lot of copy and pasting. That is great for exploration – but is really a poor way to work with data.
One big issue with how we did things is that I used the same global variable name for different data – which means I can no longer ask questions about the old data. For example I can look at one of the energy traces in the relaxation-from-uniform data, but I would need to rerun the code if I wanted to look again at the data from the relaxation-from-random runs.
plot(edata[1],xlabel="iteration",ylabel="E",label="from uniform")
The fix for this is once we decide on what we want to do, we turn it into a function
function relax(starts,β,numsteps,numrealizations)
edata=Array[]
mdata=Array[]
for i in 1:numrealizations
s=copy(starts)
run=simulate!(s,β,0.,1,numsteps)
push!(edata,run.Elist)
push!(mdata,run.maglist)
end
return (edata=edata,mdata=mdata)
end
relax (generic function with 1 method)
Now we can quickly run three different temperatures:
@time rdat04=relax(rand_ising2d(2^6,2^6),0.4,300,10);
@time rdat08=relax(rand_ising2d(2^6,2^6),0.8,300,10);
@time rdat02=relax(rand_ising2d(2^6,2^6),0.2,300,10);
0.241762 seconds (129 allocations: 201.344 KiB)
0.169209 seconds (129 allocations: 201.344 KiB)
0.231074 seconds (129 allocations: 201.344 KiB)
plot(rdat04.edata,label="",xlabel="iteration",ylabel="E")
plot!(rdat08.edata,label="",xlabel="iteration",ylabel="E")
plot!(rdat02.edata,label="",xlabel="iteration",ylabel="E")
If the data took a long time to collect, then it is also good practice to save it to disk. A good generic data format is hdf5
. That is a heirarchical format which stores things in a nice compressed binary format. It is a broadly used file format, so it is portable (can be opened in Python, or any other language.)
In the current context the only tricky thing is that we need to convert our array of arrays into matrices, using hcat
using HDF5
h5open("relax.h5", "w") do file
write(file, "edat04", hcat(rdat04.edata...))
write(file, "mdat04", hcat(rdat04.mdata...))
end
datastream=h5open("relax.h5", "r")
🗂️ HDF5.File: (read-only) relax.h5
├─ 🔢 edat04
└─ 🔢 mdat04
read(datastream["edat04"])
300×10 Matrix{Float64}:
-0.556641 -0.592773 -0.587891 … -0.585938 -0.599609 -0.584961
-0.754883 -0.762695 -0.776367 -0.788086 -0.768555 -0.761719
-0.854492 -0.863281 -0.827148 -0.842773 -0.838867 -0.836914
-0.90918 -0.946289 -0.883789 -0.928711 -0.90625 -0.924805
-0.930664 -0.978516 -0.899414 -0.929688 -0.943359 -0.954102
-0.981445 -0.99707 -0.949219 … -0.932617 -0.99707 -0.963867
-1.00195 -1.02441 -0.998047 -0.967773 -1.01953 -0.919922
-1.00684 -0.969727 -1.03125 -1.00781 -1.05176 -0.972656
-1.01562 -1.02441 -1.02148 -1.05273 -0.999023 -0.974609
-1.02637 -1.0166 -1.0332 -1.06738 -1.02148 -0.948242
-1.04785 -1.00293 -1.06152 … -1.05957 -1.04395 -1.00293
-1.09375 -1.05176 -1.09668 -1.02832 -1.06641 -0.987305
-1.08398 -1.0459 -1.07227 -1.00586 -1.02539 -1.00098
⋮ ⋱
-1.0957 -1.13672 -1.12402 -1.06543 -1.15332 -1.08789
-1.07129 -1.11719 -1.11328 -1.07812 -1.08203 -1.09082
-1.06934 -1.11621 -1.10352 … -1.12988 -1.10352 -1.0791
-1.0332 -1.0957 -1.09668 -1.11523 -1.14648 -1.12012
-1.05957 -1.10254 -1.10547 -1.09863 -1.14941 -1.10645
-1.06738 -1.15039 -1.06641 -1.0752 -1.12988 -1.11719
-1.12402 -1.14258 -1.06738 -1.1377 -1.16504 -1.09863
-1.08301 -1.17676 -1.05762 … -1.17676 -1.14844 -1.11523
-1.13281 -1.11523 -1.06836 -1.19434 -1.09375 -1.09668
-1.12695 -1.16406 -1.10254 -1.18457 -1.13184 -1.04688
-1.15234 -1.13379 -1.07617 -1.16016 -1.14551 -1.0498
-1.09863 -1.14941 -1.04199 -1.11719 -1.15039 -1.09863
read(datastream)
Dict{String, Any} with 2 entries:
"mdat04" => [0.0141602 -0.00537109 … 0.0385742 -0.0131836; 0.027832 0.0234375…
"edat04" => [-0.556641 -0.592773 … -0.599609 -0.584961; -0.754883 -0.762695 ……
close(datastream)
Another option is to use a csv
file to store data. Then in-principle you could look at it in a spreadsheet.
using CSV,Tables, DataFrames
CSV.write("tst.csv",Tables.table(hcat(rdat04.edata...)))
"tst.csv"
ld=CSV.read("tst.csv",DataFrame)
300 rows × 10 columns (omitted printing of 2 columns)
Column1 | Column2 | Column3 | Column4 | Column5 | Column6 | Column7 | Column8 | |
---|---|---|---|---|---|---|---|---|
Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
1 | -0.556641 | -0.592773 | -0.587891 | -0.59375 | -0.588867 | -0.573242 | -0.583984 | -0.585938 |
2 | -0.754883 | -0.762695 | -0.776367 | -0.759766 | -0.78418 | -0.799805 | -0.768555 | -0.788086 |
3 | -0.854492 | -0.863281 | -0.827148 | -0.876953 | -0.838867 | -0.897461 | -0.890625 | -0.842773 |
4 | -0.90918 | -0.946289 | -0.883789 | -0.925781 | -0.90625 | -0.957031 | -0.914062 | -0.928711 |
5 | -0.930664 | -0.978516 | -0.899414 | -0.963867 | -0.964844 | -0.990234 | -0.944336 | -0.929688 |
6 | -0.981445 | -0.99707 | -0.949219 | -0.988281 | -0.990234 | -1.0 | -0.954102 | -0.932617 |
7 | -1.00195 | -1.02441 | -0.998047 | -1.01758 | -0.983398 | -0.973633 | -1.03516 | -0.967773 |
8 | -1.00684 | -0.969727 | -1.03125 | -1.00977 | -1.04297 | -0.975586 | -1.02344 | -1.00781 |
9 | -1.01562 | -1.02441 | -1.02148 | -1.03125 | -1.03516 | -0.979492 | -1.04688 | -1.05273 |
10 | -1.02637 | -1.0166 | -1.0332 | -1.06641 | -1.01465 | -1.00293 | -1.04492 | -1.06738 |
11 | -1.04785 | -1.00293 | -1.06152 | -1.06738 | -1.03613 | -1.02832 | -1.07324 | -1.05957 |
12 | -1.09375 | -1.05176 | -1.09668 | -1.00781 | -1.00684 | -1.02539 | -1.08398 | -1.02832 |
13 | -1.08398 | -1.0459 | -1.07227 | -1.06836 | -1.03125 | -1.0 | -1.12207 | -1.00586 |
14 | -1.11621 | -1.04395 | -1.09766 | -1.06934 | -1.05273 | -0.980469 | -1.07031 | -1.00977 |
15 | -1.06738 | -1.09473 | -1.1084 | -1.06738 | -1.08203 | -0.995117 | -1.04785 | -1.03906 |
16 | -1.08691 | -1.07031 | -1.12207 | -1.09473 | -1.08301 | -1.03125 | -1.02051 | -1.0625 |
17 | -1.12891 | -1.08301 | -1.12012 | -1.08496 | -1.12402 | -1.04199 | -1.02344 | -1.02832 |
18 | -1.12891 | -1.05469 | -1.09961 | -1.0957 | -1.125 | -1.03906 | -1.03711 | -1.04492 |
19 | -1.10645 | -1.07617 | -1.1084 | -1.06738 | -1.09473 | -1.03027 | -1.06445 | -1.04395 |
20 | -1.11816 | -1.08887 | -1.09961 | -1.09375 | -1.09961 | -1.04688 | -1.04688 | -1.05859 |
21 | -1.16016 | -1.06738 | -1.11035 | -1.12305 | -1.10254 | -1.06934 | -1.04004 | -1.07324 |
22 | -1.1377 | -1.08203 | -1.07324 | -1.09961 | -1.08203 | -1.06641 | -1.0332 | -1.0293 |
23 | -1.16113 | -1.12988 | -1.11719 | -1.09082 | -1.05859 | -1.07324 | -1.0498 | -1.10547 |
24 | -1.20801 | -1.10254 | -1.11719 | -1.06543 | -1.0332 | -1.06836 | -1.03418 | -1.09473 |
25 | -1.21289 | -1.08691 | -1.08691 | -1.07812 | -1.10059 | -1.08008 | -1.04199 | -1.1377 |
26 | -1.1543 | -1.10645 | -1.08887 | -1.12305 | -1.13477 | -1.05957 | -1.04492 | -1.11133 |
27 | -1.1543 | -1.07422 | -1.04688 | -1.12793 | -1.13477 | -1.0752 | -1.05859 | -1.08398 |
28 | -1.15625 | -1.06934 | -1.08398 | -1.10547 | -1.13184 | -1.11133 | -1.06641 | -1.08398 |
29 | -1.13574 | -1.11914 | -1.06055 | -1.06543 | -1.08691 | -1.08789 | -1.09473 | -1.11328 |
30 | -1.1748 | -1.11426 | -1.09961 | -1.07324 | -1.13281 | -1.11328 | -1.08984 | -1.13281 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
Matrix(ld)
300×10 Matrix{Float64}:
-0.556641 -0.592773 -0.587891 … -0.585938 -0.599609 -0.584961
-0.754883 -0.762695 -0.776367 -0.788086 -0.768555 -0.761719
-0.854492 -0.863281 -0.827148 -0.842773 -0.838867 -0.836914
-0.90918 -0.946289 -0.883789 -0.928711 -0.90625 -0.924805
-0.930664 -0.978516 -0.899414 -0.929688 -0.943359 -0.954102
-0.981445 -0.99707 -0.949219 … -0.932617 -0.99707 -0.963867
-1.00195 -1.02441 -0.998047 -0.967773 -1.01953 -0.919922
-1.00684 -0.969727 -1.03125 -1.00781 -1.05176 -0.972656
-1.01562 -1.02441 -1.02148 -1.05273 -0.999023 -0.974609
-1.02637 -1.0166 -1.0332 -1.06738 -1.02148 -0.948242
-1.04785 -1.00293 -1.06152 … -1.05957 -1.04395 -1.00293
-1.09375 -1.05176 -1.09668 -1.02832 -1.06641 -0.987305
-1.08398 -1.0459 -1.07227 -1.00586 -1.02539 -1.00098
⋮ ⋱
-1.0957 -1.13672 -1.12402 -1.06543 -1.15332 -1.08789
-1.07129 -1.11719 -1.11328 -1.07812 -1.08203 -1.09082
-1.06934 -1.11621 -1.10352 … -1.12988 -1.10352 -1.0791
-1.0332 -1.0957 -1.09668 -1.11523 -1.14648 -1.12012
-1.05957 -1.10254 -1.10547 -1.09863 -1.14941 -1.10645
-1.06738 -1.15039 -1.06641 -1.0752 -1.12988 -1.11719
-1.12402 -1.14258 -1.06738 -1.1377 -1.16504 -1.09863
-1.08301 -1.17676 -1.05762 … -1.17676 -1.14844 -1.11523
-1.13281 -1.11523 -1.06836 -1.19434 -1.09375 -1.09668
-1.12695 -1.16406 -1.10254 -1.18457 -1.13184 -1.04688
-1.15234 -1.13379 -1.07617 -1.16016 -1.14551 -1.0498
-1.09863 -1.14941 -1.04199 -1.11719 -1.15039 -1.09863
Correlation Time#
Our goal with the Markov Chain is to sample independent configurations from the equilibrium (Boltzmann) distribution. The chain, however, produces correlated samples. The configuration at one timestep will be close to the one at the previous timestep. Understanding the correlation time is important for knowing how long we need to run our code, and for estimating error bars.
s=rand_ising2d(2^6,2^6)
β=0.4
# Run for 300 timesteps to get past transients
# Use the "innersweeps" so that we don't bother measuring
simulate!(s,β,0.,300,1)
# Take data for 10000 timesteps
@time run=simulate!(s,β,0.,1,40000)
efig=plot(run.Elist,label="",
title="Energy, 2^6 x 2^6",xlabel="iteration",ylabel="E")
mfig=plot(run.maglist,label="",
title="Magnetization, 2^6 x 2^6",xlabel="iteration",ylabel="M")
plot(efig,mfig,layout=[1,1],format=:png,dpi=400)
3.130972 seconds (22 allocations: 1.588 MiB)
One Jupyter trick is that I made that plot with format=:png
. That makes a bitmapped figure instead of a svg
. The figure is lower quality, but is a lot smaller. This helps keep the notebook from slowing down. We can also make a svg
with
#plot(efig,mfig,layout=[1,1],format=:svg)
Clearly the magnetization has a longer correlation time than the energy. Lets look at its distribution:
histogram(run.maglist,normed=true,xlabel="m",ylabel="P(m)",label="")
At first glance that looks similar to a Gaussian, but it clearly has smaller tails and is more “triangular” shaped.
To get the autocorrelation function, we can use our function from an earlier lecture:
where \(N_j\) is the number of points that are separated by \(j\) timesteps
function autocorrelation(values,largestsep)
m=mean(values)
timesteps=length(values)
[(values[1+j:end]⋅values[1:end-j])/(timesteps-j)-m^2 for j in 0:largestsep]
end
autocorrelation (generic function with 1 method)
magcor=autocorrelation(run.maglist,1000);
plot(magcor,xlabel="time difference,j",
ylabel=L"$C_j^{(m)}=\langle m_i m_{i+j}\rangle$",label="",
title="Magnetization Correlations")
As we see the correlation time is of order a hundred steps. We can quantify things by noting that this curve is roughly triangular shaped. If we take its area, and divide by its height we get a charactistic time
tau1=sum(magcor[j] for j in 2:500)/magcor[1]
107.3929273274867
This expression comes from thinking about exponentials: Suppose our sequence is \(m_j=A e^{-j/\tau}\), then
cortime(corlist)=sum(corlist)/corlist[1]-1
cortime (generic function with 1 method)
cortime(magcor)
129.77258800857243
Lets now look at the energy:
Ecor=autocorrelation(run.Elist,100);
plot(Ecor,xlabel="time difference,j",
ylabel=L"$C_j^{(E)}=\langle E_i E_{i+j}\rangle$",label="",
title="Energy Correlations")
Clearly the energy correlation time is just a few steps.
cortime(Ecor)
9.073045273275667
## Mini Lab 2
The correlation times (and relaxation times) depend on parameters. Redo this analysis for \(\beta=0.43\). It turns out the autocorrelation time is quite long there – so do more iterations – say 100,000 timesteps
s=rand_ising2d(2^6,2^6)
β=0.43
# Run for 300 timesteps to get past transients
# Use the "innersweeps" so that we don't bother measureing
simulate!(s,β,0.,300,1)
# Take data for 10000 timesteps
@time run43=simulate!(s,β,0.,1,100000)
efig43=plot(run43.Elist,label="",
title="Energy, 2^6 x 2^6",xlabel="iteration",ylabel="E")
mfig43=plot(run43.maglist,label="",
title="Magnetization, 2^6 x 2^6",xlabel="iteration",ylabel="M")
plot(efig43,mfig43,layout=[1,1],format=:png,dpi=400)
7.198014 seconds (24 allocations: 3.656 MiB)
ParseError:
# Error @ ]8;;file:///Users/emueller/Documents/Work Laptop 2011-2012/PHYS 4480-7680 Computational Physics/lectures/In[29]#12:33\In[29]:12:33]8;;\
title="Magnetization, 2^6 x 2^6",xlabel="iteration",ylabel="M")
plot(efig43,mfig43,layout=[1,1],,format=:png,dpi=400)
# └──────────────────┘ ── Expected `)`
Stacktrace:
[1] top-level scope
@ In[29]:12
We see that there appears to be multiple timescales. There magnetization appears to spend some time rapidly oscillating around either 0.5 or -0.5. On a longer time-scale it randomly jumps between those values.
histogram(run43.maglist,bins=500,normed=true,
xlabel="m",ylabel="P(m)",label="")
If we sampled for infinitely long this should be symmetric, but for a finite run like this, we ofen find that it spends more time around one location than the other. This indicates that we may want to get more samples.
Note that the energy and the magnetization are correlated, so the energy also has oscillations on both these scales:
histogram2d(run43.maglist,run43.Elist,xlabel="m",ylabel="E")
We can calculate the autocorrelation function, and see that it has a long time constant:
magcor43=autocorrelation(run43.maglist,20000);
scatter(1:100:length(magcor43),magcor43[1:100:end],
format=:png,dpi=400,
markersize=1.,
xlabel="time difference,j",
ylabel=L"$C_j^{(m)}=\langle m_i m_{i+j}\rangle$",label="",
title="Magnetization Correlations")
We clearly see a very long correlation time. We also see some structure where the correlations become negative for some intermediate time. We would probably need more realizations to quantify this behavior. This correlation function looks very noisy – which tells me that we need to do more averaging if we want to nicely quantify the correlations.
Most importantly, if we want to accurately sample \(m\) – we will need to get samples separated by many correlation times.
A final caution. In this case, where we have very noisy data, our estimate of the correlation time may not be reliable. Nonetheless:
cortime(magcor43)
1097.978023085172
Clearly that is shorter than we would visually expect. That is because the negative correlations cancel out some of the positive correlations. We can even end up with negative values, especially if we do not have enough statistics.
Correlations of \(|m|\)#
One important trick is that if we look at the correlation time for \(|m|\), it is much shorter than \(m\): The occasional sign flips do not effect \(|m|\) much
absmagcor43=autocorrelation(abs.(run43.maglist),20000);
plot(1:100:length(magcor43),magcor43[1:100:end]/magcor43[1],label="m")
plot!(1:100:length(absmagcor43),absmagcor43[1:100:end]/absmagcor43[1],label="|m|")
Again, things are noisy, so our estimate of the correlation time may not be useful. The solution would be to run for more time.
cortime(magcor43)
1097.978023085172
cortime(absmagcor43)
1086.361512503022