Example 1: 1D Bose-Hubbard Model
This is an example calculation finding the ground state of a 1D Bose-Hubbard chain with 6 particles in 6 lattice sites.
A runnable script for this example is located here. Run it with julia BHM-example.jl
.
Firstly, we load all needed modules. Rimu
for FCIQMC calculation;
using Rimu
using Plots # for plotting
Setting up the model
Now we define the physical problem: Generating a configuration where 6 particles are evenly distributed in 6 lattice sites:
aIni = near_uniform(BoseFS{6,6})
BoseFS{6,6}(1, 1, 1, 1, 1, 1)
where BoseFS
is used to create a bosonic Fock state. The Hamiltonian is defined by specifying the model and the parameters. Here we use the Bose Hubbard model in one dimension and real space:
Ĥ = HubbardReal1D(aIni; u = 6.0, t = 1.0)
HubbardReal1D(BoseFS{6,6}(1, 1, 1, 1, 1, 1); u=6.0, t=1.0)
Parameters of the calculation
Now let's setup the Monte Carlo calculation. We need to decide the number of walkers to use in this Monte Carlo run, which is equivalent to the average one-norm of the coefficient vector:
targetwalkers = 1_000;
It is good practice to equilibrate the time series before taking statistics.
steps_equilibrate = 1_000;
steps_measure = 2_000;
The appropriate size of the time step is problem dependent.
dτ = 0.001;
Defining an observable
Now let's set up an observable to measure. Here we will measure the projected energy. In additon to the shift
, the projected energy is a second estimator for the energy.
We first need to define a projector. Here we use the function default_starting_vector
to generate a vector with only a single occupied configuration, the same vector that we will use as starting vector for the FCIQMC calculation.
svec = default_starting_vector(aIni; style=IsStochasticInteger())
DVec{BoseFS{6, 6, BitString{11, 1, UInt16}},Int64} with 1 entry, style = IsStochasticInteger{Int64}()
fs"|1 1 1 1 1 1⟩" => 10
The choice of the style
argument already determines the FCIQMC algorithm to use (while it is irrelevant for the projected energy).
Observables are passed into the lomc!
function with the post_step
keyword argument.
post_step = ProjectedEnergy(Ĥ, svec)
ProjectedEnergy{HubbardReal1D{Float64, BoseFS{6, 6, BitString{11, 1, UInt16}}, 6.0, 1.0}, Rimu.DictVectors.FrozenDVec{BoseFS{6, 6, BitString{11, 1, UInt16}}, Int64}, Rimu.DictVectors.FrozenDVec{BoseFS{6, 6, BitString{11, 1, UInt16}}, Float64}}(:vproj, :hproj, HubbardReal1D(BoseFS{6,6}(1, 1, 1, 1, 1, 1); u=6.0, t=1.0), Rimu.DictVectors.FrozenDVec{BoseFS{6, 6, BitString{11, 1, UInt16}}, Int64}(Pair{BoseFS{6, 6, BitString{11, 1, UInt16}}, Int64}[fs"|1 1 1 1 1 1⟩" => 10]), Rimu.DictVectors.FrozenDVec{BoseFS{6, 6, BitString{11, 1, UInt16}}, Float64}(Pair{BoseFS{6, 6, BitString{11, 1, UInt16}}, Float64}[fs"|1 1 1 1 2 0⟩" => -14.142135623730951, fs"|0 2 1 1 1 1⟩" => -14.142135623730951, fs"|1 1 1 1 0 2⟩" => -14.142135623730951, fs"|1 2 0 1 1 1⟩" => -14.142135623730951, fs"|2 0 1 1 1 1⟩" => -14.142135623730951, fs"|1 1 1 2 0 1⟩" => -14.142135623730951, fs"|1 1 2 0 1 1⟩" => -14.142135623730951, fs"|1 1 0 2 1 1⟩" => -14.142135623730951, fs"|1 1 1 0 2 1⟩" => -14.142135623730951, fs"|1 0 2 1 1 1⟩" => -14.142135623730951, fs"|2 1 1 1 1 0⟩" => -14.142135623730951, fs"|0 1 1 1 1 2⟩" => -14.142135623730951]))
Running the calculation
Seeding the random number generator is sometimes useful in order to get reproducible results
using Random
Random.seed!(17);
Finally, we can start the main FCIQMC loop:
df, state = lomc!(Ĥ, svec;
laststep = steps_equilibrate + steps_measure, # total number of steps
dτ,
targetwalkers,
post_step,
);
df
is a DataFrame
containing the time series data.
Analysing the results
We can plot the norm of the coefficient vector as a function of the number of steps:
hline([targetwalkers], label="targetwalkers", color=:red, linestyle=:dash)
plot!(df.steps, df.norm, label="norm", ylabel="norm", xlabel="steps")
After some equilibriation steps, the norm fluctuates around the target number of walkers.
Now let's look at estimating the energy from the shift. The mean of the shift is a useful estimator of the shift. Calculating the error bars is a bit more involved as correlations have to be removed from the time series. The following code does that with blocking transformations:
se = shift_estimator(df; skip=steps_equilibrate)
BlockingResult{Float64}
mean = -3.815 ± 0.099
with uncertainty of ± 0.008970614342568576
from 62 blocks after 5 transformations (k = 6).
For the projected energy, it a bit more complicated as it's a ratio of fluctuationg quantities:
pe = projected_energy(df; skip=steps_equilibrate)
RatioBlockingResult{Float64,MonteCarloMeasurements.Particles{Float64, 2000}}
ratio = -4.1774 ± (0.0474639, 0.051944) (MC)
95% confidence interval: [-4.28066, -4.07921]) (MC)
linear error propagation: -4.1726 ± 0.049528
|δ_y| = |0.00975764| (≤ 0.1 for normal approx)
Blocking successful with 31 blocks after 6 transformations (k = 7).
The result is a ratio distribution. Let's get its median and lower and upper error bars for a 95% confidence interval
v = val_and_errs(pe; p=0.95)
(val = -4.177400207765482, val_l = 0.1032568959464415, val_u = 0.09819112324625667)
Let's visualise these estimators together with the time series of the shift
plot(df.steps, df.shift, ylabel="energy", xlabel="steps", label="shift")
plot!(x->se.mean, df.steps[steps_equilibrate+1:end], ribbon=se.err, label="shift mean")
plot!(
x -> v.val, df.steps[steps_equilibrate+1:end], ribbon=(v.val_l,v.val_u),
label="projected_energy",
)
In this case the projected energy and the shift are close to each other an the error bars are hard to see on this scale.
The problem was just a toy example, as the dimension of the Hamiltonian is rather small:
dimension(Ĥ)
462
In this case is easy (and more efficient) to calculate the exact ground state energy using standard linear algebra:
using LinearAlgebra
exact_energy = eigvals(Matrix(Ĥ))[1]
-4.021502406906392
Read more about Rimu.jl
s capabilities for exact diagonalisation in the example "Exact diagonalisation".
Comparing our results for the energy:
println("Energy from $steps_measure steps with $targetwalkers walkers:
Shift: $(se.mean) ± $(se.err)
Projected Energy: $(v.val) ± ($(v.val_l), $(v.val_u))
Exact Energy: $exact_energy")
Energy from 2000 steps with 1000 walkers:
Shift: -3.814556830153461 ± 0.09908367395962789
Projected Energy: -4.177400207765482 ± (0.1032568959464415, 0.09819112324625667)
Exact Energy: -4.021502406906392
This page was generated using Literate.jl.