|
| 1 | +""" |
| 2 | +$(TYPEDEF) |
| 3 | +
|
| 4 | +Algorithm for constructing the `N`th order time evolution MPO using the Taylor cluster expansion. |
| 5 | +
|
| 6 | +## Fields |
| 7 | +
|
| 8 | +$(TYPEDFIELDS) |
| 9 | +
|
| 10 | +## References |
| 11 | +
|
| 12 | +* [Van Damme et al. SciPost Phys. 17 (2024)](@cite vandamme2024) |
| 13 | +""" |
| 14 | +@kwdef struct TaylorCluster <: Algorithm |
| 15 | + "order of the Taylor expansion" |
| 16 | + N::Int = 2 |
| 17 | + "include higher-order corrections" |
| 18 | + extension::Bool = true |
| 19 | + "approximate compression of corrections, accurate up to order `N`" |
| 20 | + compression::Bool = false |
| 21 | +end |
| 22 | + |
| 23 | +""" |
| 24 | + const WI = TaylorCluster(; N=1, extension=false, compression=false) |
| 25 | +
|
| 26 | +First order Taylor expansion for a time-evolution MPO. |
| 27 | +""" |
| 28 | +const WI = TaylorCluster(; N=1, extension=false, compression=false) |
| 29 | + |
| 30 | +function make_time_mpo(H::MPOHamiltonian, dt::Number, alg::TaylorCluster; |
| 31 | + tol=eps(real(scalartype(H)))) |
| 32 | + N = alg.N |
| 33 | + τ = -1im * dt |
| 34 | + |
| 35 | + # start with H^N |
| 36 | + H_n = H^N |
| 37 | + virtual_sz = map(0:length(H)) do i |
| 38 | + return i == 0 ? size(H[1], 1) : size(H[i], 4) |
| 39 | + end |
| 40 | + linds = map(virtual_sz) do sz |
| 41 | + return LinearIndices(ntuple(Returns(sz), N)) |
| 42 | + end |
| 43 | + |
| 44 | + # extension step: Algorithm 3 |
| 45 | + # incorporate higher order terms |
| 46 | + # TODO: don't need to fully construct H_next... |
| 47 | + if alg.extension |
| 48 | + H_next = H_n * H |
| 49 | + for (i, slice) in enumerate(parent(H_n)) |
| 50 | + linds_left = linds[i] |
| 51 | + linds_right = linds[i + 1] |
| 52 | + V_left = virtual_sz[i] |
| 53 | + V_right = virtual_sz[i + 1] |
| 54 | + linds_next_left = LinearIndices(ntuple(Returns(V_left), N + 1)) |
| 55 | + linds_next_right = LinearIndices(ntuple(Returns(V_right), N + 1)) |
| 56 | + |
| 57 | + for a in CartesianIndices(linds_left), b in CartesianIndices(linds_right) |
| 58 | + all(>(1), b.I) || continue |
| 59 | + all(in((1, V_left)), a.I) && any(==(V_left), a.I) && continue |
| 60 | + |
| 61 | + n1 = count(==(1), a.I) + 1 |
| 62 | + n3 = count(==(V_right), b.I) + 1 |
| 63 | + factor = τ * factorial(N) / (factorial(N + 1) * n1 * n3) |
| 64 | + |
| 65 | + for c in 1:(N + 1), d in 1:(N + 1) |
| 66 | + aₑ = insert!([a.I...], c, 1) |
| 67 | + bₑ = insert!([b.I...], d, V_right) |
| 68 | + |
| 69 | + # TODO: use VectorInterface for memory efficiency |
| 70 | + slice[linds_left[a], 1, 1, linds_right[b]] += factor * |
| 71 | + H_next[i][linds_next_left[aₑ...], |
| 72 | + 1, 1, |
| 73 | + linds_next_right[bₑ...]] |
| 74 | + end |
| 75 | + end |
| 76 | + end |
| 77 | + end |
| 78 | + |
| 79 | + # loopback step: Algorithm 1 |
| 80 | + # constructing the Nth order time evolution MPO |
| 81 | + mpo = MPO(parent(H_n)) |
| 82 | + for (i, slice) in enumerate(parent(mpo)) |
| 83 | + V_right = virtual_sz[i + 1] |
| 84 | + linds_right = linds[i + 1] |
| 85 | + cinds_right = CartesianIndices(linds_right) |
| 86 | + for b in cinds_right[2:end] |
| 87 | + all(in((1, V_right)), b.I) || continue |
| 88 | + |
| 89 | + b_lin = linds_right[b] |
| 90 | + a = count(==(V_right), b.I) |
| 91 | + factor = τ^a * factorial(N - a) / factorial(N) |
| 92 | + slice[:, 1, 1, 1] = slice[:, 1, 1, 1] + factor * slice[:, 1, 1, b_lin] |
| 93 | + for I in nonzero_keys(slice) |
| 94 | + (I[1] == b_lin || I[4] == b_lin) && delete!(slice, I) |
| 95 | + end |
| 96 | + end |
| 97 | + end |
| 98 | + |
| 99 | + # Remove equivalent rows and columns: Algorithm 2 |
| 100 | + for (i, slice) in enumerate(parent(mpo)) |
| 101 | + V_left = virtual_sz[i] |
| 102 | + linds_left = linds[i] |
| 103 | + for c in CartesianIndices(linds_left) |
| 104 | + c_lin = linds_left[c] |
| 105 | + s_c = CartesianIndex(sort(collect(c.I); by=(!=(1)))...) |
| 106 | + |
| 107 | + n1 = count(==(1), c.I) |
| 108 | + n3 = count(==(V_left), c.I) |
| 109 | + |
| 110 | + if n3 <= n1 && s_c != c |
| 111 | + for k in 1:size(slice, 4) |
| 112 | + I = CartesianIndex(c_lin, 1, 1, k) |
| 113 | + if I in nonzero_keys(slice) |
| 114 | + slice[linds_left[s_c], 1, 1, k] += slice[I] |
| 115 | + delete!(slice, I) |
| 116 | + end |
| 117 | + end |
| 118 | + end |
| 119 | + end |
| 120 | + |
| 121 | + V_right = virtual_sz[i + 1] |
| 122 | + linds_right = linds[i + 1] |
| 123 | + for c in CartesianIndices(linds_right) |
| 124 | + c_lin = linds_right[c] |
| 125 | + s_r = CartesianIndex(sort(collect(c.I); by=(!=(V_right)))...) |
| 126 | + |
| 127 | + n1 = count(==(1), c.I) |
| 128 | + n3 = count(==(V_right), c.I) |
| 129 | + |
| 130 | + if n3 > n1 && s_r != c |
| 131 | + for k in 1:size(slice, 1) |
| 132 | + I = CartesianIndex(k, 1, 1, c_lin) |
| 133 | + if I in nonzero_keys(slice) |
| 134 | + slice[k, 1, 1, linds_right[s_r]] += slice[I] |
| 135 | + delete!(slice, I) |
| 136 | + end |
| 137 | + end |
| 138 | + end |
| 139 | + end |
| 140 | + end |
| 141 | + |
| 142 | + # Approximate compression step: Algorithm 4 |
| 143 | + if alg.compression |
| 144 | + for (i, slice) in enumerate(parent(mpo)) |
| 145 | + V_right = virtual_sz[i + 1] |
| 146 | + linds_right = linds[i + 1] |
| 147 | + for a in CartesianIndices(linds_right) |
| 148 | + all(>(1), a.I) || continue |
| 149 | + a_lin = linds_right[a] |
| 150 | + n1 = count(==(V_right), a.I) |
| 151 | + n1 == 0 && continue |
| 152 | + b = CartesianIndex(replace(a.I, V_right => 1)) |
| 153 | + b_lin = linds_right[b] |
| 154 | + factor = τ^n1 * factorial(N - n1) / factorial(N) |
| 155 | + for k in 1:size(slice, 1) |
| 156 | + I = CartesianIndex(k, 1, 1, a_lin) |
| 157 | + if I in nonzero_keys(slice) |
| 158 | + slice[k, 1, 1, b_lin] += factor * slice[I] |
| 159 | + delete!(slice, I) |
| 160 | + end |
| 161 | + end |
| 162 | + end |
| 163 | + V_left = virtual_sz[i] |
| 164 | + linds_left = linds[i] |
| 165 | + for a in CartesianIndices(linds_left) |
| 166 | + all(>(1), a.I) || continue |
| 167 | + a_lin = linds_left[a] |
| 168 | + n1 = count(==(V_left), a.I) |
| 169 | + n1 == 0 && continue |
| 170 | + for k in 1:size(slice, 4) |
| 171 | + I = CartesianIndex(a_lin, 1, 1, k) |
| 172 | + delete!(slice, I) |
| 173 | + end |
| 174 | + end |
| 175 | + end |
| 176 | + end |
| 177 | + |
| 178 | + return remove_orphans!(mpo; tol) |
| 179 | +end |
| 180 | + |
| 181 | +# Hack to treat FiniteMPOhamiltonians as Infinite |
| 182 | +function make_time_mpo(H::FiniteMPOHamiltonian, dt::Number, alg::TaylorCluster; |
| 183 | + tol=eps(real(scalartype(H)))) |
| 184 | + H′ = copy(parent(H)) |
| 185 | + |
| 186 | + V_left = left_virtualspace(H[1]) |
| 187 | + V_left′ = BlockTensorKit.oplus(V_left, oneunit(V_left), oneunit(V_left)) |
| 188 | + H′[1] = similar(H[1], V_left′ ⊗ space(H[1], 2) ← domain(H[1])) |
| 189 | + for (I, v) in nonzero_pairs(H[1]) |
| 190 | + H′[1][I] = v |
| 191 | + end |
| 192 | + |
| 193 | + V_right = right_virtualspace(H[end]) |
| 194 | + V_right′ = BlockTensorKit.oplus(oneunit(V_right), oneunit(V_right), V_right) |
| 195 | + H′[end] = similar(H[end], codomain(H[end]) ← space(H[end], 3)' ⊗ V_right′) |
| 196 | + for (I, v) in nonzero_pairs(H[end]) |
| 197 | + H′[end][I[1], 1, 1, end] = v |
| 198 | + end |
| 199 | + |
| 200 | + H′[1][end, 1, 1, end] = H′[1][1, 1, 1, 1] |
| 201 | + H′[end][1, 1, 1, 1] = H′[end][end, 1, 1, end] |
| 202 | + |
| 203 | + mpo = make_time_mpo(InfiniteMPOHamiltonian(H′), dt, alg; tol) |
| 204 | + |
| 205 | + # Impose boundary conditions |
| 206 | + mpo_fin = open_boundary_conditions(mpo, length(H)) |
| 207 | + return remove_orphans!(mpo_fin; tol) |
| 208 | +end |
0 commit comments