Skip to content

Commit f9be81b

Browse files
authored
Merge pull request #32 from tensor4all/31-fix-open-boundary-condition-in-affine_transform
31 fix open boundary condition in affine transform
2 parents b08ba14 + 7088423 commit f9be81b

File tree

2 files changed

+123
-87
lines changed

2 files changed

+123
-87
lines changed

src/affine.jl

Lines changed: 108 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
"""
22
AbstractBoundaryConditions
33
4-
Boundary conditions for the QTT to use. Use `OpenBoundaryCondtions`` for open
5-
boundaries and `PeriodicBoundaryConditions` for periodic ones.
4+
Boundary conditions for the QTT to use. Use `OpenBoundaryCondtions`` for open boundaries and `PeriodicBoundaryConditions` for periodic ones.
65
"""
76
abstract type AbstractBoundaryConditions end
87

@@ -35,23 +34,22 @@ Construct and return ITensor matrix product state for the affine transformation
3534
| | | | | |
3635
x[1,1] ... x[1,N] x[2,1] ... x[2,N] x[R,1] ... x[R,N]
3736
37+
## Arguments
3838
39-
Arguments
40-
---------
41-
- `y`: An `R × M` matrix of ITensor indices, where `y[r,m]` corresponds to
42-
the `r`-th length scale of the `m`-th output variable.
43-
- `x`: An `R × N` matrix of ITensor indices, where `x[r,n]` corresponds to
44-
the `r`-th length scale of the `n`-th input variable.
45-
- `A`: An `M × N` rational matrix representing the linear transformation.
46-
- `b`: An `M` reational vector representing the translation.
47-
- `boundary`: boundary conditions (defaults to `PeriodicBoundaryConditions()`)
39+
- `y`: An `R × M` matrix of ITensor indices, where `y[r,m]` corresponds to
40+
the `r`-th length scale of the `m`-th output variable.
41+
- `x`: An `R × N` matrix of ITensor indices, where `x[r,n]` corresponds to
42+
the `r`-th length scale of the `n`-th input variable.
43+
- `A`: An `M × N` rational matrix representing the linear transformation.
44+
- `b`: An `M` reational vector representing the translation.
45+
- `boundary`: boundary conditions (defaults to `PeriodicBoundaryConditions()`)
4846
"""
4947
function affine_transform_mpo(
50-
y::AbstractMatrix{<:Index}, x::AbstractMatrix{<:Index},
51-
A::AbstractMatrix{<:Union{Integer,Rational}},
52-
b::AbstractVector{<:Union{Integer,Rational}},
53-
boundary::AbstractBoundaryConditions=PeriodicBoundaryConditions()
54-
)::MPO
48+
y::AbstractMatrix{<:Index}, x::AbstractMatrix{<:Index},
49+
A::AbstractMatrix{<:Union{Integer,Rational}},
50+
b::AbstractVector{<:Union{Integer,Rational}},
51+
boundary::AbstractBoundaryConditions=PeriodicBoundaryConditions()
52+
)::MPO
5553
R = size(y, 1)
5654
M, N = size(A)
5755
size(x) == (R, N) ||
@@ -65,24 +63,24 @@ function affine_transform_mpo(
6563
tensors = affine_transform_tensors(R, A, b, boundary)
6664

6765
# Create the links
68-
link = [Index(size(tensors[r], 2), tags="link $r") for r in 1:R-1]
66+
link = [Index(size(tensors[r], 2); tags="link $r") for r in 1:(R - 1)]
6967

7068
# Fill the MPO, taking care to not include auxiliary links at the edges
7169
mpo = MPO(R)
7270
spin_dims = ntuple(_ -> 2, M + N)
7371
if R == 1
7472
mpo[1] = ITensor(reshape(tensors[1], spin_dims...),
75-
(y[1,:]..., x[1,:]...))
73+
(y[1, :]..., x[1, :]...))
7674
elseif R > 1
7775
mpo[1] = ITensor(reshape(tensors[1], size(tensors[1], 2), spin_dims...),
78-
(link[1], y[1,:]..., x[1,:]...))
79-
for r in 2:R-1
76+
(link[1], y[1, :]..., x[1, :]...))
77+
for r in 2:(R - 1)
8078
newshape = size(tensors[r])[1:2]..., spin_dims...
8179
mpo[r] = ITensor(reshape(tensors[r], newshape),
82-
(link[r-1], link[r], y[r,:]..., x[r,:]...))
80+
(link[r - 1], link[r], y[r, :]..., x[r, :]...))
8381
end
8482
mpo[R] = ITensor(reshape(tensors[R], size(tensors[R], 1), spin_dims...),
85-
(link[R-1], y[R,:]..., x[R,:]...))
83+
(link[R - 1], y[R, :]..., x[R, :]...))
8684
end
8785
return mpo
8886
end
@@ -95,51 +93,79 @@ operator corresponding to one of affine transformation `y = A*x + b` with
9593
rational `A` and `b`
9694
"""
9795
function affine_transform_tensors(
98-
R::Integer, A::AbstractMatrix{<:Union{Integer,Rational}},
99-
b::AbstractVector{<:Union{Integer,Rational}},
100-
boundary::AbstractBoundaryConditions=PeriodicBoundaryConditions())
101-
return affine_transform_tensors(
102-
Int(R), _affine_static_args(A, b)..., boundary)
96+
R::Integer, A::AbstractMatrix{<:Union{Integer,Rational}},
97+
b::AbstractVector{<:Union{Integer,Rational}},
98+
boundary::AbstractBoundaryConditions=PeriodicBoundaryConditions())
99+
tensors, carry = affine_transform_tensors(
100+
Int(R), _affine_static_args(A, b)...; boundary)
101+
return tensors
103102
end
104103

105104
function affine_transform_tensors(
106-
R::Int, A::SMatrix{M, N, Int}, b::SVector{M, Int}, s::Int,
107-
boundary::AbstractBoundaryConditions) where {M, N}
105+
R::Int, A::SMatrix{M,N,Int}, b::SVector{M,Int}, s::Int;
106+
boundary::AbstractBoundaryConditions=PeriodicBoundaryConditions()) where {M,N}
108107
# Checks
109108
0 <= R * max(M, N) <= 8 * sizeof(Int) ||
110109
throw(DomainError(R, "invalid value of the length R"))
111110

112111
# The output tensors are a collection of matrices, but their first two
113112
# dimensions (links) vary
114-
tensors = Vector{Array{Bool, 4}}(undef, R)
113+
tensors = Vector{Array{Bool,4}}(undef, R)
115114

116115
# The initial carry is zero
117-
carry = [zero(SVector{M, Int})]
116+
carry = [zero(SVector{M,Int})]
118117
for r in R:-1:1
119118
# Figure out the current bit to add from the shift term and shift
120-
# it out from the array
121-
bcurr = @. Bool(b & 1)
119+
bcurr = SVector{M,Int}((copysign(b_, abs(b_)) & 1 for b_ in b))
122120

123121
# Get tensor.
124122
new_carry, data = affine_transform_core(A, bcurr, s, carry)
125123

126124
# XXX do pruning: cut away carries that are dead ends in further
127125
# tensors
128126

129-
if r == 1
130-
# For the first tensor, we examine the carry to see which elements
131-
# contribute with which weight
132-
weights = map(c -> carry_weight(c, boundary), new_carry)
133-
tensors[r] = sum(data .* weights, dims=1)
134-
else
135-
tensors[r] = data
136-
end
127+
#if r == 1
128+
# For the first tensor, we examine the carry to see which elements
129+
# contribute with which weight
130+
#weights = map(c -> carry_weight(c, boundary), new_carry)
131+
#tensors[r] = sum(data .* weights, dims=1)
132+
#else
133+
tensors[r] = data
134+
#end
137135

138136
# Set carry to the next value
139137
carry = new_carry
140138
b = @. b >> 1
141139
end
142-
return tensors
140+
141+
if boundary == OpenBoundaryConditions() && maximum(abs, b) > 0
142+
# Extend the tensors to the left until we have no more nonzero bits in b
143+
# This is equivalent to a larger domain.
144+
tensors_ext = Array{Bool,4}[]
145+
while maximum(abs, b) > 0
146+
bcurr = SVector{M,Int}((copysign(b_, abs(b_)) & 1 for b_ in b))
147+
new_carry, data = affine_transform_core(A, bcurr, s, carry; activebit=false)
148+
pushfirst!(tensors_ext, data)
149+
150+
carry = new_carry
151+
b = @. b >> 1
152+
end
153+
154+
weights = map(c -> carry_weight(c, boundary), carry)
155+
tensors_ext[1] = sum(tensors_ext[1] .* weights; dims=1)
156+
_matrix(x) = reshape(x, size(x, 1), size(x, 2))
157+
cap_matrix = reduce(*, _matrix.(tensors_ext))
158+
159+
tensors[1] = reshape(
160+
cap_matrix * reshape(tensors[1], size(tensors[1], 1), :),
161+
size(cap_matrix, 1), size(tensors[1])[2:end]...
162+
)
163+
else
164+
weights = map(c -> carry_weight(c, boundary), carry)
165+
tensors[1] = sum(tensors[1] .* weights; dims=1)
166+
end
167+
168+
return tensors, carry
143169
end
144170

145171
"""
@@ -159,9 +185,9 @@ are the binary input and output vectors, respectively, of the affine transform.
159185
They are indexed in a "little-endian" fashion.
160186
"""
161187
function affine_transform_core(
162-
A::SMatrix{M, N, Int}, b::SVector{M, Bool}, s::Int,
163-
carry::AbstractVector{SVector{M, Int}}
164-
) where {M, N}
188+
A::SMatrix{M,N,Int}, b::SVector{M,Int}, s::Int,
189+
carry::AbstractVector{SVector{M,Int}}; activebit=true
190+
) where {M,N}
165191

166192
# Otherwise we have to reverse the indexing of x and y
167193
M <= N ||
@@ -174,14 +200,16 @@ function affine_transform_core(
174200
# for all "incoming" carrys c and all possible bit vectors, x ∈ {0,1}^N.
175201
# and y ∈ {0,1}^M for some outgoing carry d, which may be negative.
176202
# We then store this as something like out[d, c, x, y].
177-
out = Dict{SVector{M, Int}, Array{Bool, 3}}()
203+
out = Dict{SVector{M,Int},Array{Bool,3}}()
178204
sizehint!(out, length(carry))
179205

180-
all_x = Iterators.product(ntuple(_ -> 0:1, N)...)
181-
all_y = Iterators.product(ntuple(_ -> 0:1, M)...)
206+
bitrange = activebit ? range(0, 1) : range(0, 0)
207+
all_x = Iterators.product(ntuple(_ -> bitrange, N)...)
208+
all_y = Iterators.product(ntuple(_ -> bitrange, M)...)
209+
182210
for (c_index, c) in enumerate(carry)
183211
for (x_index, x) in enumerate(all_x)
184-
z = A * SVector{N, Bool}(x) + b + SVector{M, Int}(c)
212+
z = A * SVector{N,Bool}(x) + b + SVector{M,Int}(c)
185213

186214
if isodd(s)
187215
# if s is odd, then there is a unique y which solves satisfies
@@ -194,7 +222,8 @@ function affine_transform_core(
194222

195223
# Store this
196224
d_mat = get!(out, d) do
197-
return zeros(Bool, length(carry), 1 << M, 1 << N)
225+
return zeros(
226+
Bool, length(carry), length(bitrange)^M, length(bitrange)^N)
198227
end
199228
@inbounds d_mat[c_index, y_index, x_index] = true
200229
else
@@ -214,7 +243,8 @@ function affine_transform_core(
214243

215244
# Store this
216245
d_mat = get!(out, d) do
217-
return zeros(Bool, length(carry), 1 << M, 1 << N)
246+
return zeros(
247+
Bool, length(carry), length(bitrange)^M, length(bitrange)^N)
218248
end
219249
@inbounds d_mat[c_index, y_index, x_index] = true
220250
end
@@ -224,8 +254,10 @@ function affine_transform_core(
224254

225255
# We translate the dictionary into a vector of carrys (which we can then
226256
# pass into the next iteration) and a 4-way tensor of output values.
227-
carry_out = Vector{SVector{M, Int}}(undef, length(out))
228-
value_out = Array{Bool, 4}(undef, length(out), length(carry), 1<<M, 1<<N)
257+
carry_out = Vector{SVector{M,Int}}(undef, length(out))
258+
#value_out = Array{Bool,4}(undef, length(out), length(carry), 1 << M, 1 << N)
259+
value_out = Array{Bool,4}(
260+
undef, length(out), length(carry), length(bitrange)^M, length(bitrange)^N)
229261
for (p_index, p) in enumerate(pairs(out))
230262
carry_out[p_index] = p.first
231263
value_out[p_index, :, :, :] .= p.second
@@ -251,16 +283,16 @@ mapped to `x` and `y` as follows:
251283
`boundary` specifies the type of boundary conditions.
252284
"""
253285
function affine_transform_matrix(
254-
R::Integer, A::AbstractMatrix{<:Union{Integer,Rational}},
255-
b::AbstractVector{<:Union{Integer,Rational}},
256-
boundary::AbstractBoundaryConditions=PeriodicBoundaryConditions()
257-
)
286+
R::Integer, A::AbstractMatrix{<:Union{Integer,Rational}},
287+
b::AbstractVector{<:Union{Integer,Rational}},
288+
boundary::AbstractBoundaryConditions=PeriodicBoundaryConditions()
289+
)
258290
return affine_transform_matrix(Int(R), _affine_static_args(A, b)..., boundary)
259291
end
260292

261293
function affine_transform_matrix(
262-
R::Int, A::SMatrix{M, N, Int}, b::SVector{M, Int},
263-
s::Int, boundary::AbstractBoundaryConditions) where {M, N}
294+
R::Int, A::SMatrix{M,N,Int}, b::SVector{M,Int},
295+
s::Int, boundary::AbstractBoundaryConditions) where {M,N}
264296
# Checks
265297
0 <= R * max(M, N) <= 8 * sizeof(Int) ||
266298
throw(DomainError(R, "invalid value of the length R"))
@@ -274,22 +306,22 @@ function affine_transform_matrix(
274306
all_x = Iterators.product(ntuple(_ -> 0:mask, N)...)
275307
all_y = Iterators.product(ntuple(_ -> 0:mask, M)...)
276308
for (ix, x) in enumerate(all_x)
277-
v = A * SVector{N, Int}(x) + b
309+
v = A * SVector{N,Int}(x) + b
278310
for (iy, y) in enumerate(all_y)
279-
if equiv(v, s * SVector{M, Int}(y), R, boundary)
311+
if equiv(v, s * SVector{M,Int}(y), R, boundary)
280312
#println("$y <- $x")
281313
push!(y_index, iy)
282314
push!(x_index, ix)
283315
end
284316
end
285317
end
286318
values = ones(Bool, size(x_index))
287-
return sparse(y_index, x_index, values, 1 << (R*M), 1 << (R*N))
319+
return sparse(y_index, x_index, values, 1 << (R * M), 1 << (R * N))
288320
end
289321

290322
function affine_mpo_to_matrix(
291-
outsite::AbstractMatrix{<:Index}, insite::AbstractMatrix{<:Index},
292-
mpo::MPO)
323+
outsite::AbstractMatrix{<:Index}, insite::AbstractMatrix{<:Index},
324+
mpo::MPO)
293325
prev_warn_order = ITensors.disable_warn_order()
294326
try
295327
mpo_contr = reduce(*, mpo)
@@ -298,31 +330,31 @@ function affine_mpo_to_matrix(
298330
# order (xR, ..., x1, yR, ..., y1) in order to have y = (y1 ... yR)_2
299331
# once we reshape a column-major array and match the order of the
300332
# variables in the full matrix.
301-
out_indices = vec(reverse(outsite, dims=1))
302-
in_indices = vec(reverse(insite, dims=1))
333+
out_indices = vec(reverse(outsite; dims=1))
334+
in_indices = vec(reverse(insite; dims=1))
303335
tensor = Array(mpo_contr, out_indices..., in_indices...)
304336
matrix = reshape(tensor,
305-
1 << length(out_indices), 1 << length(in_indices))
337+
1 << length(out_indices), 1 << length(in_indices))
306338
return matrix
307339
finally
308340
ITensors.set_warn_order(prev_warn_order)
309341
end
310342
end
311343

312344
function _affine_static_args(A::AbstractMatrix{<:Union{Integer,Rational}},
313-
b::AbstractVector{<:Union{Integer,Rational}})
345+
b::AbstractVector{<:Union{Integer,Rational}})
314346
M, N = size(A)
315347
size(b, 1) == M ||
316348
throw(ArgumentError("A and b have incompatible size"))
317349

318350
# Factor out common denominator and pass
319-
denom = lcm(mapreduce(denominator, lcm, A, init=1),
320-
mapreduce(denominator, lcm, b, init=1))
351+
denom = lcm(mapreduce(denominator, lcm, A; init=1),
352+
mapreduce(denominator, lcm, b; init=1))
321353
Ai = @. Int(denom * A)
322354
bi = @. Int(denom * b)
323355

324356
# Construct static matrix
325-
return SMatrix{M, N, Int}(Ai), SVector{M, Int}(bi), denom
357+
return SMatrix{M,N,Int}(Ai), SVector{M,Int}(bi), denom
326358
end
327359

328360
"""
@@ -337,20 +369,20 @@ even though A is purely integer. In this case, the inverse transformation only
337369
maps some of the points.
338370
"""
339371
function active_to_passive(A::AbstractMatrix{<:Union{Rational,Integer}},
340-
b::AbstractVector{<:Union{Rational,Integer}})
372+
b::AbstractVector{<:Union{Rational,Integer}})
341373
return active_to_passive(Rational.(A), Rational.(b))
342374
end
343375

344376
function active_to_passive(
345-
A::AbstractMatrix{<:Rational}, b::AbstractVector{<:Rational})
377+
A::AbstractMatrix{<:Rational}, b::AbstractVector{<:Rational})
346378
m, n = size(A)
347379
T = [A b; zero(b)' 1]
348380

349381
# XXX: we do not support pseudo-inverses (LinearAlgebra cannot do
350382
# this yet over the Rationals).
351383
Tinv = inv(T)
352384
Ainv = Tinv[1:m, 1:n]
353-
binv = Tinv[1:m, n+1]
385+
binv = Tinv[1:m, n + 1]
354386
return Ainv, binv
355387
end
356388

@@ -363,5 +395,5 @@ a number.
363395
digits_to_number(v::AbstractVector{Bool}) = _digits_to_number(Tuple(v))
364396

365397
@inline _digits_to_number(v::Tuple{}) = 0
366-
@inline _digits_to_number(v::Tuple{Bool, Vararg{Bool}}) =
367-
_digits_to_number(v[2:end]) << 1 | v[1]
398+
@inline _digits_to_number(v::Tuple{Bool,Vararg{Bool}}) = _digits_to_number(v[2:end]) << 1 |
399+
v[1]

0 commit comments

Comments
 (0)