forked from JoeyT1994/TensorNetworkQuantumSimulator.jl
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexpect.jl
More file actions
200 lines (169 loc) · 7.31 KB
/
Copy pathexpect.jl
File metadata and controls
200 lines (169 loc) · 7.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
function expect(
alg::Algorithm"exact",
ψ::TensorNetworkState,
observables::Vector{<:Tuple};
contraction_sequence_kwargs = (; alg = "omeinsum", optimizer = GreedyMethod())
)
ITensors.disable_warn_order()
denom = norm_sqr(alg, ψ; contraction_sequence_kwargs)
out = Number[]
for obs in observables
op_strings, vs, coeff = collectobservable(obs, graph(ψ))
if iszero(coeff)
push!(out, zero(coeff))
continue
end
op_string_f = op_string_function(op_strings, vs)
ψOψ_tensors = norm_factors(ψ, collect(vertices(ψ)); op_strings = op_string_f)
numer_seq = contraction_sequence(ψOψ_tensors; contraction_sequence_kwargs...)
numer = scalar(contract(ψOψ_tensors; sequence = numer_seq))
push!(out, coeff * (numer / denom))
end
return out
end
function expect(
alg::Algorithm"exact",
ψ::TensorNetworkState,
observable::Tuple;
kwargs...
)
return only(expect(alg, ψ, [observable]; kwargs...))
end
"""
expect(ψ, observable; alg="exact", kwargs...) -> Number or Vector{Number}
Compute the expectation value of one or more observables on a tensor network state.
# Arguments
- `ψ::Union{TensorNetworkState, BeliefPropagationCache, BoundaryMPSCache}`: The tensor network state or cache wrapping the state to measure the observable(s) on.
- `observable::Union{Tuple, Vector{<:Tuple}}`: The observable(s) to measure. Should be a tuple or vector of tuples of the form `(ops, vertices, coeff=1)`.
# Keyword Arguments
- `alg::Union{String, Nothing}`: The algorithm to use. Supported algorithms:
- `"exact"`: Exact contraction of the tensor network.
- `"bp"`: Belief propagation approximation.
- `"boundarymps"`: Boundary MPS approximation (requires `mps_bond_dimension`).
- `cache_update_kwargs...`: Keyword arguments passed to the `update` function when using `"bp"` or `"boundarymps"` algorithms.
# Returns
- A single number if measuring one observable, or a vector of numbers if measuring multiple observables.
"""
function expect(ψ::Union{TensorNetworkState, BeliefPropagationCache, BoundaryMPSCache}, observable; alg::Union{String, Nothing} = default_alg(ψ), kwargs...)
algorithm_check(ψ, "expect", alg)
return expect(Algorithm(alg), ψ, observable; kwargs...)
end
function expect(
alg::Algorithm"bp",
cache::BeliefPropagationCache,
obs::Tuple
)
op_strings, obs_vs, coeff = collectobservable(obs, graph(cache))
iszero(coeff) && return zero(coeff)
steiner_vs = length(obs_vs) == 1 ? obs_vs : collect(vertices(steiner_tree(network(cache), obs_vs)))
incoming_ms = incoming_messages(cache, steiner_vs)
#TODO: If there are a lot of tensors here, (more than 100 say), we need to think about defining a custom sequence as optimal may be too slow
function contract_region(op_string_f)
tensors = norm_factors(network(cache), steiner_vs; op_strings = op_string_f)
append!(tensors, incoming_ms)
seq = contraction_sequence(tensors; alg = "optimal")
return scalar(contract(tensors; sequence = seq))
end
denom = contract_region(v -> "I")
numer = contract_region(op_string_function(op_strings, obs_vs))
return coeff * numer / denom
end
function expect(
alg::Algorithm"boundarymps",
cache::BoundaryMPSCache,
obs::Tuple;
bmps_messages_up_to_date = false,
)
op_strings, obs_vs, coeff = collectobservable(obs, graph(cache))
iszero(coeff) && return zero(coeff)
op_string_f = op_string_function(op_strings, obs_vs)
numer, denom = path_contract(cache, obs_vs, op_string_f; bmps_messages_up_to_date)
return coeff * scalar(numer) / denom
end
function expect(
alg::Algorithm"boundarymps",
cache::BoundaryMPSCache,
observables::Vector{<:Tuple};
bmps_messages_up_to_date = false,
kwargs...,
)
obs_vs = observables_vertices(observables, graph(cache))
if !bmps_messages_up_to_date
cache = update_partitions(cache, obs_vs)
end
out = map(obs -> expect(alg, cache, obs; bmps_messages_up_to_date = true, kwargs...), observables)
return out
end
function expect(
alg::Algorithm"bp",
cache::BeliefPropagationCache,
observables::Vector{<:Tuple};
kwargs...,
)
return map(obs -> expect(alg, cache, obs; kwargs...), observables)
end
function expect(
alg::Algorithm"bp",
ψ::TensorNetworkState,
observable::Union{Tuple, Vector{<:Tuple}};
cache_update_kwargs = default_bp_update_kwargs(ψ),
kwargs...,
)
ψ_bpc = BeliefPropagationCache(ψ)
ψ_bpc = update(ψ_bpc; cache_update_kwargs...)
return expect(alg, ψ_bpc, observable; kwargs...)
end
function expect(
alg::Algorithm"boundarymps",
ψ::TensorNetworkState,
observable::Union{Tuple, Vector{<:Tuple}};
cache_update_kwargs = default_bmps_update_kwargs(ψ),
partition_by = boundarymps_partitioning(observable, graph(ψ)),
mps_bond_dimension::Integer,
gauge_state = true,
kwargs...,
)
ψ_bmps = BoundaryMPSCache(ψ, mps_bond_dimension; partition_by, gauge_state)
cache_update_kwargs = with_default_maxiter(cache_update_kwargs, ψ_bmps)
ψ_bmps = update(ψ_bmps; cache_update_kwargs...)
obs_vs = observables_vertices(observable, graph(ψ))
ψ_bmps = update_partitions(ψ_bmps, obs_vs)
return expect(alg, ψ_bmps, observable; bmps_messages_up_to_date = true, kwargs...)
end
#Process an observable into more readable form
function collectobservable(obs::Tuple, g::NamedGraph)
coeff = length(obs) == 2 ? 1 : last(obs)
verts = observables_vertices(obs, g)
op = obs[1]
length(op) != length(verts) && error("Invalid observable: need as many operators as vertices passed.")
if op isa String
op_strings = [string(o) for o in op]
elseif op isa Vector{<:String}
op_strings = [o for o in op]
else
error("Invalid observable, did not recognize operator specification. Either a single string (one pauli character per vertex) or a vector of strings (one string per vertex) expected.")
end
return op_strings, verts, coeff
end
# Map each vertex to its operator string, defaulting to the identity "I" off the observable's support.
function op_string_function(op_strings, vs)
op_dict = Dict(zip(vs, op_strings))
return v -> get(op_dict, v, "I")
end
observables_vertices(observable::Tuple, g::NamedGraph) = collect_vertices(observable[2], g)
observables_vertices(observables::Vector{<:Tuple}, g::NamedGraph) = unique(collect(Iterators.flatten(observables_vertices(obs, g) for obs in observables)))
function boundarymps_partitioning(observable::Union{Tuple, Vector{<:Tuple}}, g::NamedGraph)
observables = observable isa Tuple ? [observable] : observable
partitioning = nothing
for o in observables
vs = observables_vertices(o, g)
if allequal(first.(vs)) && (partitioning == "row" || partitioning == nothing)
partitioning = "row"
elseif allequal(last.(vs)) && (partitioning == "col" || partitioning == nothing)
partitioning = "col"
else
error("Observables must all be aligned in either the same column or the same row to do BoundaryMPS measurements.")
end
end
return partitioning
end