Skip to content

Commit d664212

Browse files
committed
file suggestions for grouping evth related to QMC datastructures
1 parent 6fb0f5f commit d664212

File tree

7 files changed

+622
-0
lines changed

7 files changed

+622
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
using Base.Iterators
2+
3+
# changed type tree s.t. AbstractRydberg is a direct subtype of Hamiltonian
4+
abstract type AbstractRydberg{O <: AbstractOperatorSampler} <: Hamiltonian{2,O} end
5+
6+
struct Rydberg{O,M <: UpperTriangular{Float64},UΩ <: AbstractVector{Float64}, Uδ <: AbstractVector{Float64}, L <: Lattice} <: AbstractRydberg{O}
7+
op_sampler::O
8+
V::M
9+
Ω::UΩ
10+
δ::Uδ
11+
lattice::L
12+
energy_shift::Float64
13+
end
14+
15+
nspins(H::Rydberg) = nspins(H.lattice)
16+
17+
18+
function make_prob_vector(H::Type{<:AbstractRydberg}, V::UpperTriangular{T}, Ω::AbstractVector{T}, δ::AbstractVector{T}; epsilon=0.0) where T
19+
@assert length(Ω) == length(δ) == size(V, 1) == size(V, 2)
20+
@assert (0.0 <= epsilon <= 1.0) "epsilon must be in the range [0, 1]!"
21+
22+
ops = Vector{NTuple{ISING_OP_SIZE, Int}}()
23+
p = Vector{T}()
24+
energy_shift = zero(T)
25+
26+
for i in eachindex(Ω)
27+
if !iszero(Ω[i])
28+
push!(ops, makediagonalsiteop(AbstractLTFIM, i))
29+
push!(p, Ω[i] / 2)
30+
energy_shift += Ω[i] / 2
31+
end
32+
end
33+
34+
Ns = length(Ω)
35+
bond_spins = Set{NTuple{2,Int}}()
36+
coordination_numbers = zeros(Int, Ns)
37+
for j in axes(V, 2), i in axes(V, 1)
38+
if i < j && !iszero(V[i, j])
39+
push!(bond_spins, (i, j))
40+
coordination_numbers[i] += 1
41+
coordination_numbers[j] += 1
42+
end
43+
end
44+
45+
n = diagonaloperator(H)
46+
I = Diagonal(LinearAlgebra.I, 2)
47+
48+
# TODO: add fictitious bonds if there's a z-field on an "unbonded" site
49+
for (site1, site2) in bond_spins
50+
# by this point we can assume site1 < site2
51+
δb1 = δ[site1] / coordination_numbers[site1]
52+
δb2 = δ[site2] / coordination_numbers[site2]
53+
local_H = V[site1, site2]*kron(n, n) - δb1*kron(n, I) - δb2*kron(I, n)
54+
55+
p_spins = -diag(local_H)
56+
C = abs(min(0, minimum(p_spins))) + epsilon*abs(minimum(p_spins[2:end]))
57+
#dont use the zero matrix element for the epsilon shift
58+
p_spins .+= C
59+
energy_shift += C
60+
61+
for (t, p_t) in enumerate(p_spins)
62+
push!(p, p_t)
63+
push!(ops, (2, t, length(p), site1, site2))
64+
end
65+
end
66+
67+
return ops, p, energy_shift
68+
end
69+
70+
71+
###############################################################################
72+
73+
# function Rydberg(dims::NTuple{D, Int}, R_b, Ω, δ; pbc=true, trunc::Int=0, epsilon::Float64=0) where D
74+
# if D == 1
75+
# lat = Chain(dims[1], 1.0, pbc)
76+
# elseif D == 2
77+
# lat = Rectangle(dims[1], dims[2], 1.0, 1.0, pbc)
78+
# else
79+
# error("Unsupported number of dimensions. 1- and 2-dimensional lattices are supported.")
80+
# end
81+
# return Rydberg(lat, R_b, Ω, δ; trunc=trunc, epsilon=epsilon)
82+
# end
83+
84+
85+
# We only want one Rydberg() interface, integrated into BloqadeExpr.
86+
# Probably, we'll want one function generate_interaction_matrix() that creates V and then feeds that matrix into Rydberg().
87+
# Check with Phil how that function is coming along.
88+
89+
function Rydberg(lat::Lattice, R_b::Float64, Ω::Float64, δ::Float64; trunc::Int=0, epsilon=0)
90+
Ns = nspins(lat)
91+
V = zeros(Float64, Ns, Ns)
92+
93+
if trunc > 0
94+
_dist = sort!(collect(Set(lat.distance_matrix)))
95+
uniq_dist = Vector{Float64}(undef, 0)
96+
for i in eachindex(_dist)
97+
if length(uniq_dist) == 0
98+
push!(uniq_dist, _dist[i])
99+
elseif !(last(uniq_dist) _dist[i])
100+
push!(uniq_dist, _dist[i])
101+
end
102+
end
103+
smallest_k = sort!(uniq_dist)[1:(trunc+1)]
104+
dist = copy(lat.distance_matrix)
105+
for i in eachindex(dist)
106+
if dist[i] > last(smallest_k) && !(dist[i] last(smallest_k))
107+
dist[i] = zero(dist[i])
108+
end
109+
end
110+
elseif lat isa Rectangle && all(lat.PBC)
111+
V = zeros(Ns, Ns)
112+
K = 3 # figure out an efficient way to set this dynamically
113+
114+
dist = zeros(Ns, Ns)
115+
for v2 in -K:K, v1 in -K:K
116+
dV = zeros(Ns, Ns)
117+
for x2 in axes(dV, 2), x1 in axes(dV, 1)
118+
i1, j1 = divrem(x1 - 1, lat.n1)
119+
i2, j2 = divrem(x2 - 1, lat.n1)
120+
r = [i2 - i1 + v1*lat.n1, j2 - j1 + v2*lat.n2]
121+
dV[x1, x2] += Ω * (R_b/norm(r, 2))^6
122+
end
123+
# @show v2, v1, maximum(abs, dV)
124+
125+
V += dV
126+
end
127+
128+
V = (V + V') / 2 # should already be symmetric but just in case
129+
130+
return Rydberg(UpperTriangular(triu!(V, 1)), Ω*ones(Ns), δ*ones(Ns), lat; epsilon=epsilon)
131+
else
132+
dist = lat.distance_matrix
133+
end
134+
135+
@inbounds for i in 1:(Ns-1)
136+
for j in (i+1):Ns
137+
# a zero entry in distance_matrix means there should be no bond
138+
V[i, j] = dist[i, j] != 0.0 ? Ω * (R_b / dist[i, j])^6 : 0.0
139+
end
140+
end
141+
V = UpperTriangular(triu!(V, 1))
142+
143+
return Rydberg(V, Ω*ones(Ns), δ*ones(Ns), lat; epsilon=epsilon)
144+
end
145+
146+
function Rydberg(V::AbstractMatrix{T}, Ω::AbstractVector{T}, δ::AbstractVector{T}, lattice::Lattice; epsilon=zero(T)) where T
147+
ops, p, energy_shift = make_prob_vector(AbstractRydberg, V, Ω, δ, epsilon=epsilon)
148+
op_sampler = ImprovedOperatorSampler(AbstractLTFIM, ops, p)
149+
return Rydberg{typeof(op_sampler), typeof(V), typeof(Ω), typeof(δ), typeof(lattice)}(op_sampler, V, Ω, δ, lattice, energy_shift)
150+
end
151+
152+
# Check whether the two functions below are needed in updates.
153+
154+
total_hx(H::Rydberg)::Float64 = sum(H.Ω) / 2
155+
haslongitudinalfield(H::AbstractRydberg) = !iszero(H.δ)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
###############################################################################
2+
# Walker's Alias Method: draw samples in O(1) time, initialize vector in O(n)
3+
# caveat: unsure if it's possible to build an efficient update scheme
4+
# for these types of probability vectors
5+
6+
struct ProbabilityAlias{T} <: AbstractProbabilityVector{T}
7+
normalization::T
8+
9+
cutoffs::Vector{T}
10+
alias::Vector{Int}
11+
12+
# initialize using Vose's algorithm
13+
function ProbabilityAlias{T}(weights::Vector{T}) where {T <: AbstractFloat}
14+
if length(weights) == 0
15+
throw(ArgumentError("weight vector must have non-zero length!"))
16+
end
17+
if any(x -> x < zero(T), weights)
18+
throw(ArgumentError("weights must be non-negative!"))
19+
end
20+
21+
weights_ = copy(weights)
22+
normalization = sum(weights)
23+
N = length(weights)
24+
avg = normalization / N
25+
26+
underfull = Stack{Int}()
27+
overfull = Stack{Int}()
28+
29+
for (i, w) in enumerate(weights_)
30+
if w >= avg
31+
push!(overfull, i)
32+
else
33+
push!(underfull, i)
34+
end
35+
end
36+
37+
cutoffs = zeros(float(T), N)
38+
alias = zeros(Int, N)
39+
40+
while !isempty(underfull) && !isempty(overfull)
41+
less = pop!(underfull)
42+
more = pop!(overfull)
43+
44+
cutoffs[less] = weights_[less] * N
45+
alias[less] = more
46+
47+
weights_[more] += weights_[less]
48+
weights_[more] -= avg
49+
50+
if weights_[more] >= avg
51+
push!(overfull, more)
52+
else
53+
push!(underfull, more)
54+
end
55+
end
56+
57+
while !isempty(underfull)
58+
cutoffs[pop!(underfull)] = normalization
59+
end
60+
61+
while !isempty(overfull)
62+
cutoffs[pop!(overfull)] = normalization
63+
end
64+
65+
cutoffs /= normalization
66+
67+
new{T}(sum(weights), cutoffs, alias)
68+
end
69+
end
70+
71+
ProbabilityAlias(p::Vector{T}) where T = ProbabilityAlias{T}(p)
72+
@inline length(pvec::ProbabilityAlias) = length(pvec.cutoffs)
73+
@inline normalization(pvec::ProbabilityAlias) = pvec.normalization
74+
75+
function show(io::IO, p::ProbabilityAlias{T}) where T
76+
r = repr(p.normalization; context=IOContext(io, :limit=>true))
77+
print(io, "ProbabilityAlias{$T}($r)")
78+
end
79+
80+
# function Base.rand(rng::AbstractRNG, pvec::ProbabilityAlias{T}) where T
81+
# u, i::Int = modf(muladd(length(pvec), rand(rng), 1.0))
82+
# return @inbounds (u < pvec.cutoffs[i]) ? i : pvec.alias[i]
83+
# end
84+
85+
# xorshift prngs seem to be noticably faster if you just sample from them twice
86+
function Base.rand(rng::AbstractRNG, pvec::ProbabilityAlias{T}) where T
87+
u = rand(rng)
88+
i = rand(rng, 1:length(pvec))
89+
return @inbounds (u < pvec.cutoffs[i]) ? i : pvec.alias[i]
90+
end
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# To befirst merged with Rydberg (the only specific Hamiltonian we need) and eventually integrated with BloqadeExpr interfaces
2+
abstract type Hamiltonian{D,O<:AbstractOperatorSampler} end
3+
4+
localdim(::Hamiltonian{D}) where {D} = D
5+
6+
zero(H::Hamiltonian{2}) = zeros(Bool, nspins(H))
7+
zero(H::Hamiltonian) = zeros(Int, nspins(H))
8+
one(H::Hamiltonian{2}) = ones(Bool, nspins(H))
9+
one(H::Hamiltonian) = ones(Int, nspins(H))
10+
11+
# nspins(H::Hamiltonian) = H.Ns # Rydberg has its own version
12+
nbonds(H::Hamiltonian) = H.Nb # Is this needed for Rydberg?
13+
14+
@inline isdiagonal(H) = op -> isdiagonal(H, op)
15+
@inline isidentity(H) = op -> isidentity(H, op)
16+
@inline issiteoperator(H) = op -> issiteoperator(H, op)
17+
@inline isbondoperator(H) = op -> isbondoperator(H, op)
18+
@inline getsite(H) = op -> getsite(H, op)
19+
@inline getbondsites(H) = op -> getbondsites(H, op)
20+
21+
@inline diag_update_normalization(H::Hamiltonian) = normalization(H.op_sampler)
22+
23+
###############################################################################
24+
25+
# These energy functions are only used in Rydberg ED not in LTFIM QMC
26+
27+
energy(::Type{<:BinaryThermalState}, H::Hamiltonian, β::Float64, n::Int) = H.energy_shift - (n / β)
28+
function energy(::Type{<:BinaryThermalState}, H::Hamiltonian, β::Float64, ns::Vector{T}) where {T <: Real}
29+
E = -mean_and_stderr(ns) / β
30+
return H.energy_shift + E
31+
end
32+
energy(qmc_state::BinaryQMCState, args...; kwargs...) = energy(typeof(qmc_state), args...; kwargs...)
33+
34+
energy_density(S::Type{<:BinaryQMCState}, H::Hamiltonian, args...; kwargs...) =
35+
energy(S, H, args...; kwargs...) / nspins(H)
36+
37+
energy_density(qmc_state::BinaryQMCState, args...; kwargs...) = energy_density(typeof(qmc_state), args...; kwargs...)
38+
39+
###############################################################################
40+
41+
# Move this stuff to state files?
42+
43+
function BinaryGroundState(H::Hamiltonian{2,O}, M::Int, trialstate::Union{Nothing, AbstractTrialState}=nothing) where {K, O <: AbstractOperatorSampler{K}}
44+
z = zero(H) # Here we are initializing state into all zeros
45+
BinaryGroundState(z, init_op_list(2*M, Val{K}()), trialstate)::BinaryGroundState{K, typeof(z)}
46+
end
47+
48+
49+
function BinaryThermalState(H::Hamiltonian{2,O}, cutoff::Int) where {K, O <: AbstractOperatorSampler{K}} # cutoff is essentially 2M but here we tune it via an algorithm
50+
z = zero(H)
51+
BinaryThermalState(z, init_op_list(cutoff, Val{K}()))::BinaryThermalState{K, typeof(z)}
52+
end
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Same file as previous improved_operator_sampler.jl just with Abstract type copied over from operatorsampler.jl
2+
3+
# Do we still need AbstractOperatorSampler at all? Probably we don't want a structure called "Improvedxxx" once we don't have the structure called "xxx" anymore :)
4+
5+
abstract type AbstractOperatorSampler{K, T, P <: AbstractProbabilityVector{T}} end
6+
firstindex(::AbstractOperatorSampler) = 1
7+
lastindex(os::AbstractOperatorSampler) = length(os)
8+
@inline normalization(os::AbstractOperatorSampler) = normalization(os.pvec)
9+
10+
abstract type AbstractImprovedOperatorSampler{K, T, P} <: AbstractOperatorSampler{K, T, P} end
11+
12+
struct ImprovedOperatorSampler{K, T, P} <: AbstractImprovedOperatorSampler{K, T, P}
13+
operators::Vector{NTuple{K, Int}}
14+
pvec::P
15+
op_log_weights::Vector{T}
16+
end
17+
18+
# only supports the LTFIM/Rydberg cases for now
19+
function ImprovedOperatorSampler(H::Type{<:Hamiltonian{2, <:AbstractOperatorSampler}}, operators::Vector{NTuple{K, Int}}, p::Vector{T}) where {T <: AbstractFloat, K}
20+
@assert length(operators) == length(p) "Given vectors must have the same length!"
21+
22+
op_log_weights = log.(p)
23+
24+
max_mel_ops = Vector{NTuple{K, Int}}()
25+
p_modified = Vector{T}()
26+
27+
# fill with all the site operators first
28+
for (i, op) in enumerate(operators)
29+
if issiteoperator(H, op) && isdiagonal(H, op)
30+
push!(max_mel_ops, op)
31+
push!(p_modified, @inbounds p[i])
32+
end
33+
end
34+
idx = findall(isbondoperator(H), operators)
35+
ops = operators[idx]
36+
p_mod = p[idx]
37+
38+
perm = sortperm(ops, by=getbondsites(H))
39+
ops = ops[perm]
40+
p_mod = p_mod[perm]
41+
42+
op_groups = Dict{NTuple{2, Int}, Vector{NTuple{K, Int}}}()
43+
p_groups = Dict{NTuple{2, Int}, Vector{T}}()
44+
45+
while !isempty(ops)
46+
op = pop!(ops)
47+
site1, site2 = getbondsites(H, op)
48+
p_ = pop!(p_mod)
49+
50+
if haskey(op_groups, (site1, site2))
51+
push!(op_groups[(site1, site2)], op)
52+
push!(p_groups[(site1, site2)], p_)
53+
else
54+
op_groups[(site1, site2)] = [op]
55+
p_groups[(site1, site2)] = [p_]
56+
end
57+
end
58+
59+
for (k, p_gr) in p_groups
60+
i = argmax(p_gr)
61+
push!(max_mel_ops, op_groups[k][i])
62+
push!(p_modified, p_gr[i])
63+
end
64+
65+
pvec = probability_vector(p_modified)
66+
return ImprovedOperatorSampler{K, T, typeof(pvec)}(max_mel_ops, pvec, op_log_weights)
67+
end
68+
69+
@inline rand(rng::AbstractRNG, os::ImprovedOperatorSampler) = @inbounds os.operators[rand(rng, os.pvec)]
70+
71+
Base.@propagate_inbounds getlogweight(os::ImprovedOperatorSampler, w::Int) = os.op_log_weights[w]
72+
73+
@inline length(os::ImprovedOperatorSampler) = length(os.operators)
74+
75+
##############################################################################

0 commit comments

Comments
 (0)