Trajectree 0.0.0__py3-none-any.whl → 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trajectree/__init__.py +3 -0
- trajectree/fock_optics/devices.py +1 -1
- trajectree/fock_optics/light_sources.py +2 -2
- trajectree/fock_optics/measurement.py +3 -3
- trajectree/fock_optics/utils.py +6 -6
- trajectree/quimb/docs/_pygments/_pygments_dark.py +118 -0
- trajectree/quimb/docs/_pygments/_pygments_light.py +118 -0
- trajectree/quimb/docs/conf.py +158 -0
- trajectree/quimb/docs/examples/ex_mpi_expm_evo.py +62 -0
- trajectree/quimb/quimb/__init__.py +507 -0
- trajectree/quimb/quimb/calc.py +1491 -0
- trajectree/quimb/quimb/core.py +2279 -0
- trajectree/quimb/quimb/evo.py +712 -0
- trajectree/quimb/quimb/experimental/__init__.py +0 -0
- trajectree/quimb/quimb/experimental/autojittn.py +129 -0
- trajectree/quimb/quimb/experimental/belief_propagation/__init__.py +109 -0
- trajectree/quimb/quimb/experimental/belief_propagation/bp_common.py +397 -0
- trajectree/quimb/quimb/experimental/belief_propagation/d1bp.py +316 -0
- trajectree/quimb/quimb/experimental/belief_propagation/d2bp.py +653 -0
- trajectree/quimb/quimb/experimental/belief_propagation/hd1bp.py +571 -0
- trajectree/quimb/quimb/experimental/belief_propagation/hv1bp.py +775 -0
- trajectree/quimb/quimb/experimental/belief_propagation/l1bp.py +316 -0
- trajectree/quimb/quimb/experimental/belief_propagation/l2bp.py +537 -0
- trajectree/quimb/quimb/experimental/belief_propagation/regions.py +194 -0
- trajectree/quimb/quimb/experimental/cluster_update.py +286 -0
- trajectree/quimb/quimb/experimental/merabuilder.py +865 -0
- trajectree/quimb/quimb/experimental/operatorbuilder/__init__.py +15 -0
- trajectree/quimb/quimb/experimental/operatorbuilder/operatorbuilder.py +1631 -0
- trajectree/quimb/quimb/experimental/schematic.py +7 -0
- trajectree/quimb/quimb/experimental/tn_marginals.py +130 -0
- trajectree/quimb/quimb/experimental/tnvmc.py +1483 -0
- trajectree/quimb/quimb/gates.py +36 -0
- trajectree/quimb/quimb/gen/__init__.py +2 -0
- trajectree/quimb/quimb/gen/operators.py +1167 -0
- trajectree/quimb/quimb/gen/rand.py +713 -0
- trajectree/quimb/quimb/gen/states.py +479 -0
- trajectree/quimb/quimb/linalg/__init__.py +6 -0
- trajectree/quimb/quimb/linalg/approx_spectral.py +1109 -0
- trajectree/quimb/quimb/linalg/autoblock.py +258 -0
- trajectree/quimb/quimb/linalg/base_linalg.py +719 -0
- trajectree/quimb/quimb/linalg/mpi_launcher.py +397 -0
- trajectree/quimb/quimb/linalg/numpy_linalg.py +244 -0
- trajectree/quimb/quimb/linalg/rand_linalg.py +514 -0
- trajectree/quimb/quimb/linalg/scipy_linalg.py +293 -0
- trajectree/quimb/quimb/linalg/slepc_linalg.py +892 -0
- trajectree/quimb/quimb/schematic.py +1518 -0
- trajectree/quimb/quimb/tensor/__init__.py +401 -0
- trajectree/quimb/quimb/tensor/array_ops.py +610 -0
- trajectree/quimb/quimb/tensor/circuit.py +4824 -0
- trajectree/quimb/quimb/tensor/circuit_gen.py +411 -0
- trajectree/quimb/quimb/tensor/contraction.py +336 -0
- trajectree/quimb/quimb/tensor/decomp.py +1255 -0
- trajectree/quimb/quimb/tensor/drawing.py +1646 -0
- trajectree/quimb/quimb/tensor/fitting.py +385 -0
- trajectree/quimb/quimb/tensor/geometry.py +583 -0
- trajectree/quimb/quimb/tensor/interface.py +114 -0
- trajectree/quimb/quimb/tensor/networking.py +1058 -0
- trajectree/quimb/quimb/tensor/optimize.py +1818 -0
- trajectree/quimb/quimb/tensor/tensor_1d.py +4778 -0
- trajectree/quimb/quimb/tensor/tensor_1d_compress.py +1854 -0
- trajectree/quimb/quimb/tensor/tensor_1d_tebd.py +662 -0
- trajectree/quimb/quimb/tensor/tensor_2d.py +5954 -0
- trajectree/quimb/quimb/tensor/tensor_2d_compress.py +96 -0
- trajectree/quimb/quimb/tensor/tensor_2d_tebd.py +1230 -0
- trajectree/quimb/quimb/tensor/tensor_3d.py +2869 -0
- trajectree/quimb/quimb/tensor/tensor_3d_tebd.py +46 -0
- trajectree/quimb/quimb/tensor/tensor_approx_spectral.py +60 -0
- trajectree/quimb/quimb/tensor/tensor_arbgeom.py +3237 -0
- trajectree/quimb/quimb/tensor/tensor_arbgeom_compress.py +565 -0
- trajectree/quimb/quimb/tensor/tensor_arbgeom_tebd.py +1138 -0
- trajectree/quimb/quimb/tensor/tensor_builder.py +5411 -0
- trajectree/quimb/quimb/tensor/tensor_core.py +11179 -0
- trajectree/quimb/quimb/tensor/tensor_dmrg.py +1472 -0
- trajectree/quimb/quimb/tensor/tensor_mera.py +204 -0
- trajectree/quimb/quimb/utils.py +892 -0
- trajectree/quimb/tests/__init__.py +0 -0
- trajectree/quimb/tests/test_accel.py +501 -0
- trajectree/quimb/tests/test_calc.py +788 -0
- trajectree/quimb/tests/test_core.py +847 -0
- trajectree/quimb/tests/test_evo.py +565 -0
- trajectree/quimb/tests/test_gen/__init__.py +0 -0
- trajectree/quimb/tests/test_gen/test_operators.py +361 -0
- trajectree/quimb/tests/test_gen/test_rand.py +296 -0
- trajectree/quimb/tests/test_gen/test_states.py +261 -0
- trajectree/quimb/tests/test_linalg/__init__.py +0 -0
- trajectree/quimb/tests/test_linalg/test_approx_spectral.py +368 -0
- trajectree/quimb/tests/test_linalg/test_base_linalg.py +351 -0
- trajectree/quimb/tests/test_linalg/test_mpi_linalg.py +127 -0
- trajectree/quimb/tests/test_linalg/test_numpy_linalg.py +84 -0
- trajectree/quimb/tests/test_linalg/test_rand_linalg.py +134 -0
- trajectree/quimb/tests/test_linalg/test_slepc_linalg.py +283 -0
- trajectree/quimb/tests/test_tensor/__init__.py +0 -0
- trajectree/quimb/tests/test_tensor/test_belief_propagation/__init__.py +0 -0
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d1bp.py +39 -0
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d2bp.py +67 -0
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hd1bp.py +64 -0
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hv1bp.py +51 -0
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l1bp.py +142 -0
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l2bp.py +101 -0
- trajectree/quimb/tests/test_tensor/test_circuit.py +816 -0
- trajectree/quimb/tests/test_tensor/test_contract.py +67 -0
- trajectree/quimb/tests/test_tensor/test_decomp.py +40 -0
- trajectree/quimb/tests/test_tensor/test_mera.py +52 -0
- trajectree/quimb/tests/test_tensor/test_optimizers.py +488 -0
- trajectree/quimb/tests/test_tensor/test_tensor_1d.py +1171 -0
- trajectree/quimb/tests/test_tensor/test_tensor_2d.py +606 -0
- trajectree/quimb/tests/test_tensor/test_tensor_2d_tebd.py +144 -0
- trajectree/quimb/tests/test_tensor/test_tensor_3d.py +123 -0
- trajectree/quimb/tests/test_tensor/test_tensor_arbgeom.py +226 -0
- trajectree/quimb/tests/test_tensor/test_tensor_builder.py +441 -0
- trajectree/quimb/tests/test_tensor/test_tensor_core.py +2066 -0
- trajectree/quimb/tests/test_tensor/test_tensor_dmrg.py +388 -0
- trajectree/quimb/tests/test_tensor/test_tensor_spectral_approx.py +63 -0
- trajectree/quimb/tests/test_tensor/test_tensor_tebd.py +270 -0
- trajectree/quimb/tests/test_utils.py +85 -0
- trajectree/trajectory.py +2 -2
- {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/METADATA +2 -2
- trajectree-0.0.1.dist-info/RECORD +126 -0
- trajectree-0.0.0.dist-info/RECORD +0 -16
- {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/WHEEL +0 -0
- {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/licenses/LICENSE +0 -0
- {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1472 @@
|
|
|
1
|
+
"""DMRG-like variational algorithms, but in tensor network language.
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import itertools
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from ..utils import progbar
|
|
10
|
+
from ..core import prod
|
|
11
|
+
from ..linalg.base_linalg import eigh, IdentityLinearOperator
|
|
12
|
+
from .tensor_core import (
|
|
13
|
+
Tensor,
|
|
14
|
+
tensor_contract,
|
|
15
|
+
TNLinearOperator,
|
|
16
|
+
asarray,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_default_opts(cyclic=False):
|
|
21
|
+
"""Get the default advanced settings for DMRG.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
default_sweep_sequence : str
|
|
26
|
+
How to sweep. Will be repeated, e.g. "RRL" -> RRLRRLRRL..., default: R.
|
|
27
|
+
bond_compress_method : {'svd', 'eig', ...}
|
|
28
|
+
Method used to compress sites after update.
|
|
29
|
+
bond_compress_cutoff_mode : {'sum2', 'abs', 'rel'}
|
|
30
|
+
How to perform compression truncation.
|
|
31
|
+
bond_expand_rand_strength : float
|
|
32
|
+
In DMRG1, strength of randomness to expand bonds with. Needed to avoid
|
|
33
|
+
singular matrices after expansion.
|
|
34
|
+
local_eig_tol : float
|
|
35
|
+
Relative tolerance to solve inner eigenproblem to, larger = quicker but
|
|
36
|
+
more unstable, default: 1e-3. Note this can be much looser than the
|
|
37
|
+
overall tolerance, the starting point for each local solve is the
|
|
38
|
+
previous state, and the overall accuracy comes from multiple sweeps.
|
|
39
|
+
local_eig_ncv : int
|
|
40
|
+
Number of inner eigenproblem lanczos vectors. Smaller can mean quicker.
|
|
41
|
+
local_eig_backend : {None, 'AUTO', 'SCIPY', 'SLEPC'}
|
|
42
|
+
Which to backend to use for the inner eigenproblem. None or 'AUTO' to
|
|
43
|
+
choose best. Generally ``'SLEPC'`` best if available for large
|
|
44
|
+
problems, but it can't currently handle ``LinearOperator`` Neff as well
|
|
45
|
+
as ``'lobpcg'``.
|
|
46
|
+
local_eig_maxiter : int
|
|
47
|
+
Maximum number of inner eigenproblem iterations.
|
|
48
|
+
local_eig_ham_dense : bool
|
|
49
|
+
Force dense representation of the effective hamiltonian.
|
|
50
|
+
local_eig_EPSType : {'krylovschur', 'gd', 'jd', ...}
|
|
51
|
+
Eigensovler tpye if ``local_eig_backend='slepc'``.
|
|
52
|
+
local_eig_norm_dense : bool
|
|
53
|
+
Force dense representation of the effective norm.
|
|
54
|
+
periodic_segment_size : float or int
|
|
55
|
+
How large (as a proportion if float) to make the 'segments' in periodic
|
|
56
|
+
DMRG. During a sweep everything outside this (the 'long way round') is
|
|
57
|
+
compressed so the effective energy and norm can be efficiently formed.
|
|
58
|
+
Tradeoff: longer segments means having to compress less, but also
|
|
59
|
+
having a shorter 'long way round', meaning that it needs a larger bond
|
|
60
|
+
to represent it and can be 'pseudo-orthogonalized' less effectively.
|
|
61
|
+
0.5 is the largest fraction that makes sense. Set to >= 1.0 to not
|
|
62
|
+
use segmentation at all, which is better for small systems.
|
|
63
|
+
periodic_compress_method : {'isvd', 'svds'}
|
|
64
|
+
Which method to perform the transfer matrix compression with.
|
|
65
|
+
periodic_compress_norm_eps : float
|
|
66
|
+
Precision to compress the norm transfer matrix in periodic systems.
|
|
67
|
+
periodic_compress_ham_eps : float
|
|
68
|
+
Precision to compress the energy transfer matrix in periodic systems.
|
|
69
|
+
periodic_compress_max_bond : int
|
|
70
|
+
The maximum bond to use when compressing transfer matrices.
|
|
71
|
+
periodic_nullspace_fudge_factor : float
|
|
72
|
+
Factor to add to ``Heff`` and ``Neff`` to remove nullspace.
|
|
73
|
+
periodic_canonize_inv_tol : float
|
|
74
|
+
When psuedo-orthogonalizing, an inverse gauge is generated that can be
|
|
75
|
+
very ill-conditioned. This factor controls cutting off the small
|
|
76
|
+
singular values of the gauge to stop this.
|
|
77
|
+
periodic_orthog_tol : float
|
|
78
|
+
When psuedo-orthogonalizing, if the local norm is within this
|
|
79
|
+
distance to 1 (pseudo-orthogonoalized), then the generalized eigen
|
|
80
|
+
decomposition is *not* used, which is much more efficient. If set too
|
|
81
|
+
large the total normalization can become unstable.
|
|
82
|
+
"""
|
|
83
|
+
return {
|
|
84
|
+
"default_sweep_sequence": "R",
|
|
85
|
+
"bond_compress_method": "svd",
|
|
86
|
+
"bond_compress_cutoff_mode": "rel" if cyclic else "sum2",
|
|
87
|
+
"bond_expand_rand_strength": 1e-6,
|
|
88
|
+
"local_eig_tol": 1e-3,
|
|
89
|
+
"local_eig_ncv": 4,
|
|
90
|
+
"local_eig_backend": None,
|
|
91
|
+
"local_eig_maxiter": None,
|
|
92
|
+
"local_eig_EPSType": None,
|
|
93
|
+
"local_eig_ham_dense": None,
|
|
94
|
+
"local_eig_norm_dense": None,
|
|
95
|
+
"periodic_segment_size": 1 / 2,
|
|
96
|
+
"periodic_compress_method": "isvd",
|
|
97
|
+
"periodic_compress_norm_eps": 1e-6,
|
|
98
|
+
"periodic_compress_ham_eps": 1e-6,
|
|
99
|
+
"periodic_compress_max_bond": -1,
|
|
100
|
+
"periodic_nullspace_fudge_factor": 1e-12,
|
|
101
|
+
"periodic_canonize_inv_tol": 1e-10,
|
|
102
|
+
"periodic_orthog_tol": 1e-6,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class MovingEnvironment:
|
|
107
|
+
r"""Helper class for efficiently moving the effective 'environment' of a
|
|
108
|
+
few sites in a 1D tensor network. E.g. for ``begin='left', bsz=2``, this
|
|
109
|
+
initializes the right environments like so::
|
|
110
|
+
|
|
111
|
+
n - 1: ●─●─●─ ─●─●─●
|
|
112
|
+
│ │ │ │ │ │
|
|
113
|
+
H─H─H─ ... ─H─H─H
|
|
114
|
+
│ │ │ │ │ │
|
|
115
|
+
●─●─●─ ─●─●─●
|
|
116
|
+
|
|
117
|
+
n - 2: ●─●─●─ ─●─●─╮
|
|
118
|
+
│ │ │ │ │ ●
|
|
119
|
+
H─H─H─ ... ─H─H─H
|
|
120
|
+
│ │ │ │ │ ●
|
|
121
|
+
●─●─●─ ─●─●─╯
|
|
122
|
+
|
|
123
|
+
n - 3: ●─●─●─ ─●─╮
|
|
124
|
+
│ │ │ │ ●●
|
|
125
|
+
H─H─H─ ... ─H─HH
|
|
126
|
+
│ │ │ │ ●●
|
|
127
|
+
●─●─●─ ─●─╯
|
|
128
|
+
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
0 : ●─●─╮
|
|
132
|
+
│ │ ●● ●●●
|
|
133
|
+
H─H─HH...HHH
|
|
134
|
+
│ │ ●● ●●●
|
|
135
|
+
●─●─╯
|
|
136
|
+
|
|
137
|
+
which can then be used to efficiently generate the left environments as
|
|
138
|
+
each site is updated. For example if ``bsz=2`` and the environements have
|
|
139
|
+
been shifted many sites into the middle, then ``MovingEnvironment()``
|
|
140
|
+
returns something like::
|
|
141
|
+
|
|
142
|
+
<---> bsz sites
|
|
143
|
+
╭─●─●─╮
|
|
144
|
+
●●●●● │ │ ●●●●●●●
|
|
145
|
+
HHHHH─H─H─HHHHHHH
|
|
146
|
+
●●●●● │ │ ●●●●●●●
|
|
147
|
+
╰─●─●─╯
|
|
148
|
+
0 ... i i+1 ... n-1
|
|
149
|
+
|
|
150
|
+
For periodic systems ``MovingEnvironment`` approximates the 'long
|
|
151
|
+
way round' transfer matrices. E.g consider replacing segment B
|
|
152
|
+
(to arbitrary precision) with an SVD::
|
|
153
|
+
|
|
154
|
+
╭───────────────────────────────────────────────╮
|
|
155
|
+
╰─A─A─A─A─A─A─A─A─A─A─A─A─B─B─B─B─B─B─B─B─B─B─B─╯
|
|
156
|
+
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ==>
|
|
157
|
+
╭─A─A─A─A─A─A─A─A─A─A─A─A─B─B─B─B─B─B─B─B─B─B─B─╮
|
|
158
|
+
╰───────────────────────────────────────────────╯
|
|
159
|
+
|
|
160
|
+
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
|
161
|
+
┊ ╭─A─A─A─A─A─A─A─A─A─A─A─A─╮ ┊ ==>
|
|
162
|
+
╰┄<BL │ │ │ │ │ │ │ │ │ │ │ │ BR>┄╯
|
|
163
|
+
╰─A─A─A─A─A─A─A─A─A─A─A─A─╯
|
|
164
|
+
^ ^
|
|
165
|
+
segment_start segment_stop - 1
|
|
166
|
+
|
|
167
|
+
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
|
168
|
+
┊ ╭─A─A─╮ ┊ ==>
|
|
169
|
+
╰┄<BL │ │ AAAAAAAAAAAAAAAAAAAAABR>┄╯
|
|
170
|
+
╰─A─A─╯
|
|
171
|
+
...
|
|
172
|
+
<-bsz->
|
|
173
|
+
|
|
174
|
+
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
|
|
175
|
+
┊ ╭─A─A─╮ ┊ ==>
|
|
176
|
+
╰~<BLAAAAAAAAAAA │ │ AAAAAAAABR>~╯
|
|
177
|
+
╰─A─A─╯
|
|
178
|
+
i i+1
|
|
179
|
+
-----sweep--------->
|
|
180
|
+
|
|
181
|
+
Can then contract and store left and right environments for efficient
|
|
182
|
+
sweeping just as in non-periodic case. If the segment is long enough (50+)
|
|
183
|
+
sites, often only 1 singular value is needed, and thus the efficiency is
|
|
184
|
+
the same as for OBC.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
tn : TensorNetwork
|
|
189
|
+
The 1D tensor network, should be closed, i.e. an overlap of some sort.
|
|
190
|
+
begin : {'left', 'right'}
|
|
191
|
+
Which side to start at and sweep from.
|
|
192
|
+
bsz : int
|
|
193
|
+
The number of sites that form the the 'non-environment',
|
|
194
|
+
e.g. 2 for DMRG2.
|
|
195
|
+
ssz : float or int, optional
|
|
196
|
+
The size of the segment to use, if float, the proportion. Default: 1/2.
|
|
197
|
+
eps : float, optional
|
|
198
|
+
The tolerance to approximate the transfer matrix with. See
|
|
199
|
+
:meth:`~quimb.tensor.TensorNetwork.replace_with_svd`.
|
|
200
|
+
cyclic : bool, optional
|
|
201
|
+
Whether this is a periodic ``MovingEnvironment``.
|
|
202
|
+
segment_callbacks : sequence of callable, optional
|
|
203
|
+
Functions with signature ``callback(start, stop, self.begin)``, to be
|
|
204
|
+
called every time a new segment is initialized.
|
|
205
|
+
method : {'isvd', 'svds', ...}, optional
|
|
206
|
+
How to perform the transfer matrix compression if PBC. See
|
|
207
|
+
:meth:`~quimb.tensor.TensorNetwork.replace_with_svd`.
|
|
208
|
+
max_bond : , optional
|
|
209
|
+
If > 0, the maximum bond of the compressed transfer matrix.
|
|
210
|
+
norm : bool, optional
|
|
211
|
+
If True, treat this ``MovingEnvironment`` as the state overlap, which
|
|
212
|
+
enables a few extra checks.
|
|
213
|
+
|
|
214
|
+
Notes
|
|
215
|
+
-----
|
|
216
|
+
Does not necessarily need to be an operator overlap tensor network. Useful
|
|
217
|
+
for any kind of sweep where only local tensor updates are being made. Note
|
|
218
|
+
that *only* the current site is completely up-to-date and can be modified
|
|
219
|
+
with changes meant to propagate.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
def __init__(
|
|
223
|
+
self,
|
|
224
|
+
tn,
|
|
225
|
+
begin,
|
|
226
|
+
bsz,
|
|
227
|
+
*,
|
|
228
|
+
cyclic=False,
|
|
229
|
+
segment_callbacks=None,
|
|
230
|
+
ssz=0.5,
|
|
231
|
+
eps=1e-8,
|
|
232
|
+
method="isvd",
|
|
233
|
+
max_bond=-1,
|
|
234
|
+
norm=False,
|
|
235
|
+
):
|
|
236
|
+
self.tn = tn.copy(virtual=True)
|
|
237
|
+
self.begin = begin
|
|
238
|
+
self.bsz = bsz
|
|
239
|
+
self.cyclic = cyclic
|
|
240
|
+
|
|
241
|
+
if callable(segment_callbacks):
|
|
242
|
+
self.segment_callbacks = (segment_callbacks,)
|
|
243
|
+
else:
|
|
244
|
+
self.segment_callbacks = segment_callbacks
|
|
245
|
+
|
|
246
|
+
self.L = tn.L
|
|
247
|
+
self._site_tag_id = tn.site_tag_id
|
|
248
|
+
|
|
249
|
+
if self.cyclic:
|
|
250
|
+
self.eps = eps
|
|
251
|
+
self.method = method
|
|
252
|
+
self.max_bond = max_bond
|
|
253
|
+
self.norm = norm
|
|
254
|
+
self.bond_sizes = []
|
|
255
|
+
|
|
256
|
+
if isinstance(ssz, float):
|
|
257
|
+
# this logic essentially makes sure that segments prefer
|
|
258
|
+
# overshooting e.g ssz=1/3 with n=100 produces segments of
|
|
259
|
+
# length 34, to avoid a final segement of length 1.
|
|
260
|
+
self._ssz = int(self.L * ssz + self.L % int(1 / ssz))
|
|
261
|
+
else:
|
|
262
|
+
self._ssz = ssz
|
|
263
|
+
|
|
264
|
+
self.segmented = self._ssz < self.L
|
|
265
|
+
# will still split system in half but no compression or callbacks
|
|
266
|
+
if not self.segmented:
|
|
267
|
+
self._ssz = int(self.L / 2 + self.L % 2)
|
|
268
|
+
|
|
269
|
+
start, stop = {
|
|
270
|
+
"left": (0, self._ssz),
|
|
271
|
+
"right": (self.L - self._ssz, self.L),
|
|
272
|
+
}[begin]
|
|
273
|
+
else:
|
|
274
|
+
self.segmented = False
|
|
275
|
+
start, stop = (0, self.L - self.bsz + 1)
|
|
276
|
+
|
|
277
|
+
self.init_segment(begin, start, stop)
|
|
278
|
+
|
|
279
|
+
def site_tag(self, i):
|
|
280
|
+
return self._site_tag_id.format(i % self.L)
|
|
281
|
+
|
|
282
|
+
def init_segment(self, begin, start, stop):
|
|
283
|
+
"""Initialize the environments in ``range(start, stop)`` so that one
|
|
284
|
+
can start sweeping from the side defined by ``begin``.
|
|
285
|
+
"""
|
|
286
|
+
if (start >= self.L) or (stop < 0):
|
|
287
|
+
start, stop = start % self.L, stop % self.L
|
|
288
|
+
|
|
289
|
+
self.segment = range(start, stop)
|
|
290
|
+
self.init_non_segment(start, stop + self.bsz // 2)
|
|
291
|
+
|
|
292
|
+
if begin == "left":
|
|
293
|
+
tags_initital = ["_RIGHT"] + [
|
|
294
|
+
self.site_tag(stop - 1 + b) for b in range(self.bsz)
|
|
295
|
+
]
|
|
296
|
+
self.envs = {stop - 1: self.tnc.select_any(tags_initital)}
|
|
297
|
+
|
|
298
|
+
for i in reversed(range(start, stop - 1)):
|
|
299
|
+
# add a new site to previous env, and contract one site
|
|
300
|
+
self.envs[i] = self.envs[i + 1].copy(virtual=True)
|
|
301
|
+
self.envs[i] |= self.tnc.select(i)
|
|
302
|
+
self.envs[i] ^= ("_RIGHT", self.site_tag(i + self.bsz))
|
|
303
|
+
|
|
304
|
+
self.envs[start] |= self.tnc["_LEFT"]
|
|
305
|
+
self.pos = start
|
|
306
|
+
|
|
307
|
+
elif begin == "right":
|
|
308
|
+
tags_initital = ["_LEFT"] + [
|
|
309
|
+
self.site_tag(start + b) for b in range(self.bsz)
|
|
310
|
+
]
|
|
311
|
+
self.envs = {start: self.tnc.select_any(tags_initital)}
|
|
312
|
+
|
|
313
|
+
for i in range(start + 1, stop):
|
|
314
|
+
# add a new site to previous env, and contract one site
|
|
315
|
+
self.envs[i] = self.envs[i - 1].copy(virtual=True)
|
|
316
|
+
self.envs[i] |= self.tnc.select(i + self.bsz - 1)
|
|
317
|
+
self.envs[i] ^= ("_LEFT", self.site_tag(i - 1))
|
|
318
|
+
|
|
319
|
+
self.envs[i] |= self.tnc["_RIGHT"]
|
|
320
|
+
self.pos = stop - 1
|
|
321
|
+
|
|
322
|
+
else:
|
|
323
|
+
raise ValueError("``begin`` must be 'left' or 'right'.")
|
|
324
|
+
|
|
325
|
+
def init_non_segment(self, start, stop):
|
|
326
|
+
"""Compress and label the effective env not in ``range(start, stop)``
|
|
327
|
+
if cyclic, else just add some dummy left and right end pieces.
|
|
328
|
+
"""
|
|
329
|
+
self.tnc = self.tn.copy(virtual=True)
|
|
330
|
+
|
|
331
|
+
if not self.segmented:
|
|
332
|
+
if not self.cyclic:
|
|
333
|
+
# generate dummy left and right envs
|
|
334
|
+
self.tnc |= Tensor(tags="_LEFT").astype(self.tn.dtype)
|
|
335
|
+
self.tnc |= Tensor(tags="_RIGHT").astype(self.tn.dtype)
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
# if cyclic just contract other section and tag
|
|
339
|
+
self.tnc |= Tensor(tags="_LEFT").astype(self.tn.dtype)
|
|
340
|
+
self.tnc.contract(slice(stop, start + self.L), inplace=True)
|
|
341
|
+
self.tnc.add_tag("_RIGHT", where=stop + 1)
|
|
342
|
+
return
|
|
343
|
+
|
|
344
|
+
# replicate all tags on end pieces apart from site number
|
|
345
|
+
ltags = self.tnc.select(start - 1).tags
|
|
346
|
+
ltags.add("_LEFT")
|
|
347
|
+
ltags.discard(self.site_tag(start - 1))
|
|
348
|
+
rtags = self.tnc.select(stop).tags
|
|
349
|
+
rtags.add("_RIGHT")
|
|
350
|
+
rtags.discard(self.site_tag(stop))
|
|
351
|
+
|
|
352
|
+
# for example, pseudo orthogonalization if cyclic
|
|
353
|
+
if self.segment_callbacks is not None:
|
|
354
|
+
for callback in self.segment_callbacks:
|
|
355
|
+
callback(start, stop, self.begin)
|
|
356
|
+
|
|
357
|
+
opts = {
|
|
358
|
+
"keep_tags": False,
|
|
359
|
+
"ltags": ltags,
|
|
360
|
+
"rtags": rtags,
|
|
361
|
+
"eps": self.eps,
|
|
362
|
+
"method": self.method,
|
|
363
|
+
"max_bond": self.max_bond,
|
|
364
|
+
"inplace": True,
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
self.tnc.replace_section_with_svd(start, stop, which="!any", **opts)
|
|
368
|
+
|
|
369
|
+
self.bond_sizes.append(
|
|
370
|
+
self.tnc["_LEFT"].shared_bond_size(self.tnc["_RIGHT"])
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if self.norm:
|
|
374
|
+
# ensure that expectation still = 1 after approximation
|
|
375
|
+
# section left can still be pretty long so do structured contract
|
|
376
|
+
tnn = self.tnc.copy()
|
|
377
|
+
tnn ^= ["_LEFT", self.site_tag(start)]
|
|
378
|
+
tnn ^= ["_RIGHT", self.site_tag(stop - 1)]
|
|
379
|
+
norm = (tnn ^ slice(start, stop)) ** 0.5
|
|
380
|
+
|
|
381
|
+
self.tnc["_LEFT"] /= norm
|
|
382
|
+
self.tnc["_RIGHT"] /= norm
|
|
383
|
+
|
|
384
|
+
def move_right(self):
|
|
385
|
+
if (not self.cyclic) and (self.pos + 1 not in self.segment):
|
|
386
|
+
raise ValueError("For OBC, ``0 <= position <= n - bsz``.")
|
|
387
|
+
|
|
388
|
+
i = (self.pos + 1) % self.L
|
|
389
|
+
|
|
390
|
+
# generate a new segment if we go over the border
|
|
391
|
+
if i not in self.segment:
|
|
392
|
+
self.init_segment("left", i, i + self._ssz)
|
|
393
|
+
else:
|
|
394
|
+
self.pos = i
|
|
395
|
+
|
|
396
|
+
i0 = self.segment.start
|
|
397
|
+
|
|
398
|
+
if i >= i0 + 1:
|
|
399
|
+
# insert the updated left env from previous step
|
|
400
|
+
# contract left env with updated site just to left
|
|
401
|
+
new_left = self.envs[i - 1].select(
|
|
402
|
+
["_LEFT", self.site_tag(i - 1)], which="any"
|
|
403
|
+
)
|
|
404
|
+
self.envs[i] |= new_left ^ all
|
|
405
|
+
|
|
406
|
+
def move_left(self):
|
|
407
|
+
if (not self.cyclic) and (self.pos - 1 not in self.segment):
|
|
408
|
+
raise ValueError("For OBC, ``0 <= position <= n - bsz``.")
|
|
409
|
+
|
|
410
|
+
i = (self.pos - 1) % self.L
|
|
411
|
+
|
|
412
|
+
# generate a new segment if we go over the border
|
|
413
|
+
if i not in self.segment:
|
|
414
|
+
self.init_segment("right", i - self._ssz + 1, i + 1)
|
|
415
|
+
else:
|
|
416
|
+
self.pos = i
|
|
417
|
+
|
|
418
|
+
iN = self.segment.stop
|
|
419
|
+
|
|
420
|
+
if i <= iN - 2:
|
|
421
|
+
# insert the updated right env from previous step
|
|
422
|
+
# contract right env with updated site just to right
|
|
423
|
+
new_right = self.envs[i + 1].select(
|
|
424
|
+
["_RIGHT", self.site_tag(i + self.bsz)], which="any"
|
|
425
|
+
)
|
|
426
|
+
self.envs[i] |= new_right ^ all
|
|
427
|
+
|
|
428
|
+
def move_to(self, i):
|
|
429
|
+
"""Move this effective environment to site ``i``."""
|
|
430
|
+
|
|
431
|
+
if self.cyclic:
|
|
432
|
+
# to take account of PBC, rescale so that current pos == n // 2,
|
|
433
|
+
# then work out if desired i is lower or higher
|
|
434
|
+
ri = (i + (self.L // 2 - self.pos)) % self.L
|
|
435
|
+
direction = "left" if ri <= self.L // 2 else "right"
|
|
436
|
+
else:
|
|
437
|
+
direction = "left" if i < self.pos else "right"
|
|
438
|
+
|
|
439
|
+
while self.pos != i % self.L:
|
|
440
|
+
{"left": self.move_left, "right": self.move_right}[direction]()
|
|
441
|
+
|
|
442
|
+
def __call__(self):
|
|
443
|
+
"""Get the current environment."""
|
|
444
|
+
return self.envs[self.pos]
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def get_cyclic_canonizer(k, b, inv_tol=1e-10):
|
|
448
|
+
"""Get a function to use as a callback for ``MovingEnvironment`` that
|
|
449
|
+
approximately orthogonalizes the segments of periodic MPS.
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
def cyclic_canonizer(start, stop, begin):
|
|
453
|
+
k.canonize_cyclic(slice(start, stop), bra=b, inv_tol=inv_tol)
|
|
454
|
+
if begin == "left":
|
|
455
|
+
k.right_canonize(start=stop - 1, stop=start, bra=b)
|
|
456
|
+
else:
|
|
457
|
+
k.left_canonize(start=start, stop=stop - 1, bra=b)
|
|
458
|
+
|
|
459
|
+
return cyclic_canonizer
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
# --------------------------------------------------------------------------- #
|
|
463
|
+
# DMRG Base #
|
|
464
|
+
# --------------------------------------------------------------------------- #
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def parse_2site_inds_dims(k, b, i):
|
|
468
|
+
r"""Sort out the dims and inds of::
|
|
469
|
+
|
|
470
|
+
---O---O---
|
|
471
|
+
| |
|
|
472
|
+
|
|
473
|
+
For use in 2 site algorithms.
|
|
474
|
+
"""
|
|
475
|
+
u_bond_ind = k.bond(i, i + 1)
|
|
476
|
+
dims_L, uix_L = zip(
|
|
477
|
+
*((d, ix) for d, ix in zip(k[i].shape, k[i].inds) if ix != u_bond_ind)
|
|
478
|
+
)
|
|
479
|
+
dims_R, uix_R = zip(
|
|
480
|
+
*(
|
|
481
|
+
(d, ix)
|
|
482
|
+
for d, ix in zip(k[i + 1].shape, k[i + 1].inds)
|
|
483
|
+
if ix != u_bond_ind
|
|
484
|
+
)
|
|
485
|
+
)
|
|
486
|
+
uix = uix_L + uix_R
|
|
487
|
+
|
|
488
|
+
l_bond_ind = b.bond(i, i + 1)
|
|
489
|
+
lix_L = tuple(i for i in b[i].inds if i != l_bond_ind)
|
|
490
|
+
lix_R = tuple(i for i in b[i + 1].inds if i != l_bond_ind)
|
|
491
|
+
lix = lix_L + lix_R
|
|
492
|
+
|
|
493
|
+
dims = dims_L + dims_R
|
|
494
|
+
|
|
495
|
+
return dims, lix_L, lix_R, lix, uix_L, uix_R, uix, l_bond_ind, u_bond_ind
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class DMRGError(Exception):
|
|
499
|
+
pass
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class DMRG:
|
|
503
|
+
r"""Density Matrix Renormalization Group variational groundstate search.
|
|
504
|
+
Some initialising arguments act as defaults, but can be overidden with
|
|
505
|
+
each solve or sweep. See :func:`~quimb.tensor.tensor_dmrg.get_default_opts`
|
|
506
|
+
for the list of advanced options initialized in the ``opts`` attribute.
|
|
507
|
+
|
|
508
|
+
Parameters
|
|
509
|
+
----------
|
|
510
|
+
ham : MatrixProductOperator
|
|
511
|
+
The hamiltonian in MPO form.
|
|
512
|
+
bond_dims : int or sequence of ints.
|
|
513
|
+
The bond-dimension of the MPS to optimize. If ``bsz > 1``, then this
|
|
514
|
+
corresponds to the maximum bond dimension when splitting the effective
|
|
515
|
+
local groundstate. If a sequence is supplied then successive sweeps
|
|
516
|
+
iterate through, then repeate the final value. E.g.
|
|
517
|
+
``[16, 32, 64] -> (16, 32, 64, 64, 64, ...)``.
|
|
518
|
+
cutoffs : dict-like
|
|
519
|
+
The cutoff threshold(s) to use when compressing. If a sequence is
|
|
520
|
+
supplied then successive sweeps iterate through, then repeate the final
|
|
521
|
+
value. E.g. ``[1e-5, 1e-7, 1e-9] -> (1e-5, 1e-7, 1e-9, 1e-9, ...)``.
|
|
522
|
+
bsz : {1, 2}
|
|
523
|
+
Number of sites to optimize for locally i.e. DMRG1 or DMRG2.
|
|
524
|
+
which : {'SA', 'LA'}, optional
|
|
525
|
+
Whether to search for smallest or largest real part eigenvectors.
|
|
526
|
+
p0 : MatrixProductState, optional
|
|
527
|
+
If given, use as the initial state.
|
|
528
|
+
|
|
529
|
+
Attributes
|
|
530
|
+
----------
|
|
531
|
+
state : MatrixProductState
|
|
532
|
+
The current, optimized state.
|
|
533
|
+
energy : float
|
|
534
|
+
The current most optimized energy.
|
|
535
|
+
energies : list of float
|
|
536
|
+
The total energy after each sweep.
|
|
537
|
+
local_energies : list of list of float
|
|
538
|
+
The local energies per sweep: ``local_energies[i, j]`` contains the
|
|
539
|
+
local energy found at the jth step of the (i+1)th sweep.
|
|
540
|
+
total_energies : list of list of float
|
|
541
|
+
The total energies per sweep: ``local_energies[i, j]`` contains the
|
|
542
|
+
total energy after the jth step of the (i+1)th sweep.
|
|
543
|
+
opts : dict
|
|
544
|
+
Advanced options e.g. relating to the inner eigensolve or compression,
|
|
545
|
+
see :func:`~quimb.tensor.tensor_dmrg.get_default_opts`.
|
|
546
|
+
(bond_sizes_ham) : list[list[int]]
|
|
547
|
+
If cyclic, the sizes of the energy environement transfer matrix bonds,
|
|
548
|
+
per segment, per sweep.
|
|
549
|
+
(bond_sizes_norm) : list[list[int]]
|
|
550
|
+
If cyclic, the sizes of the norm environement transfer matrix bonds,
|
|
551
|
+
per segment, per sweep.
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
def __init__(
|
|
555
|
+
self, ham, bond_dims, cutoffs=1e-9, bsz=2, which="SA", p0=None
|
|
556
|
+
):
|
|
557
|
+
self.L = ham.L
|
|
558
|
+
self.phys_dim = ham.phys_dim()
|
|
559
|
+
self.bsz = bsz
|
|
560
|
+
self.which = which
|
|
561
|
+
self.cyclic = ham.cyclic
|
|
562
|
+
self._set_bond_dim_seq(bond_dims)
|
|
563
|
+
self._set_cutoff_seq(cutoffs)
|
|
564
|
+
|
|
565
|
+
# create internal states and ham
|
|
566
|
+
if p0 is not None:
|
|
567
|
+
self._k = p0.copy()
|
|
568
|
+
else:
|
|
569
|
+
self._k = ham.rand_state(self._bond_dim0)
|
|
570
|
+
self._b = self._k.H
|
|
571
|
+
self.ham = ham.copy()
|
|
572
|
+
self._k.add_tag("_KET")
|
|
573
|
+
self._b.add_tag("_BRA")
|
|
574
|
+
self.ham.add_tag("_HAM")
|
|
575
|
+
|
|
576
|
+
# Line up and overlap for energy calc
|
|
577
|
+
self._k.align_(self.ham, self._b)
|
|
578
|
+
|
|
579
|
+
# want to contract this multiple times while
|
|
580
|
+
# manipulating k/b -> make virtual
|
|
581
|
+
self.TN_energy = self._b | self.ham | self._k
|
|
582
|
+
self.energies = []
|
|
583
|
+
self.local_energies = []
|
|
584
|
+
self.total_energies = []
|
|
585
|
+
|
|
586
|
+
# if cyclic need to keep track of normalization
|
|
587
|
+
if self.cyclic:
|
|
588
|
+
eye = self.ham.identity()
|
|
589
|
+
eye.add_tag("_EYE")
|
|
590
|
+
self.TN_norm = self._b | eye | self._k
|
|
591
|
+
|
|
592
|
+
self.bond_sizes_ham = []
|
|
593
|
+
self.bond_sizes_norm = []
|
|
594
|
+
|
|
595
|
+
self.opts = get_default_opts(self.cyclic)
|
|
596
|
+
|
|
597
|
+
def _set_bond_dim_seq(self, bond_dims):
|
|
598
|
+
bds = (bond_dims,) if isinstance(bond_dims, int) else tuple(bond_dims)
|
|
599
|
+
self._bond_dim0 = bds[0]
|
|
600
|
+
self._bond_dims = itertools.chain(bds, itertools.repeat(bds[-1]))
|
|
601
|
+
|
|
602
|
+
def _set_cutoff_seq(self, cutoffs):
|
|
603
|
+
bds = (cutoffs,) if isinstance(cutoffs, float) else tuple(cutoffs)
|
|
604
|
+
self._cutoffs = itertools.chain(bds, itertools.repeat(bds[-1]))
|
|
605
|
+
|
|
606
|
+
@property
|
|
607
|
+
def energy(self):
|
|
608
|
+
return self.energies[-1]
|
|
609
|
+
|
|
610
|
+
@property
|
|
611
|
+
def state(self):
|
|
612
|
+
copy = self._k.copy()
|
|
613
|
+
copy.drop_tags("_KET")
|
|
614
|
+
return copy
|
|
615
|
+
|
|
616
|
+
# -------------------- standard DMRG update methods --------------------- #
|
|
617
|
+
|
|
618
|
+
def _canonize_after_1site_update(self, direction, i):
|
|
619
|
+
"""Compress a site having updated it. Also serves to move the
|
|
620
|
+
orthogonality center along.
|
|
621
|
+
"""
|
|
622
|
+
if (direction == "right") and ((i < self.L - 1) or self.cyclic):
|
|
623
|
+
self._k.left_canonize_site(i, bra=self._b)
|
|
624
|
+
elif (direction == "left") and ((i > 0) or self.cyclic):
|
|
625
|
+
self._k.right_canonize_site(i, bra=self._b)
|
|
626
|
+
|
|
627
|
+
def _eigs(self, A, B=None, v0=None):
|
|
628
|
+
"""Find single eigenpair, using all the internal settings."""
|
|
629
|
+
# intercept generalized eigen
|
|
630
|
+
backend = self.opts["local_eig_backend"]
|
|
631
|
+
if (backend is None) and (B is not None):
|
|
632
|
+
backend = "LOBPCG"
|
|
633
|
+
|
|
634
|
+
return eigh(
|
|
635
|
+
A,
|
|
636
|
+
k=1,
|
|
637
|
+
B=B,
|
|
638
|
+
which=self.which,
|
|
639
|
+
v0=v0,
|
|
640
|
+
backend=backend,
|
|
641
|
+
EPSType=self.opts["local_eig_EPSType"],
|
|
642
|
+
ncv=self.opts["local_eig_ncv"],
|
|
643
|
+
tol=self.opts["local_eig_tol"],
|
|
644
|
+
maxiter=self.opts["local_eig_maxiter"],
|
|
645
|
+
fallback_to_scipy=True,
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
def print_energy_info(self, Heff=None, loc_gs=None):
|
|
649
|
+
sweep_num = len(self.energies) + 1
|
|
650
|
+
full_en = self.TN_energy ^ ...
|
|
651
|
+
effv_en = self._eff_ham ^ all
|
|
652
|
+
|
|
653
|
+
if Heff is None:
|
|
654
|
+
site_en = "N/A"
|
|
655
|
+
else:
|
|
656
|
+
site_en = (loc_gs.H @ (Heff @ loc_gs)).item()
|
|
657
|
+
|
|
658
|
+
print(
|
|
659
|
+
f"Sweep {sweep_num} -- fullE={full_en} "
|
|
660
|
+
f"effcE={effv_en} siteE={site_en}"
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
def print_norm_info(self, i=None):
|
|
664
|
+
sweep_num = len(self.energies) + 1
|
|
665
|
+
full_n = self._k.H @ self._k
|
|
666
|
+
|
|
667
|
+
if self.cyclic:
|
|
668
|
+
effv_n = self._eff_norm ^ all
|
|
669
|
+
else:
|
|
670
|
+
effv_n = "OBC"
|
|
671
|
+
|
|
672
|
+
if i is None:
|
|
673
|
+
site_norm = [self._k[i].H @ self._k[i] for i in range(self.L)]
|
|
674
|
+
else:
|
|
675
|
+
site_norm = self._k[i].H @ self._k[i]
|
|
676
|
+
|
|
677
|
+
print(
|
|
678
|
+
f"Sweep {sweep_num} -- fullN={full_n} "
|
|
679
|
+
f"effvN={effv_n} siteN={site_norm}"
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
def form_local_ops(self, i, dims, lix, uix):
|
|
683
|
+
"""Construct the effective Hamiltonian, and if needed, norm."""
|
|
684
|
+
if self.cyclic:
|
|
685
|
+
self._eff_norm = self.ME_eff_norm()
|
|
686
|
+
self._eff_ham = self.ME_eff_ham()
|
|
687
|
+
|
|
688
|
+
# choose a rough value at which dense effective ham should not be used
|
|
689
|
+
dense = self.opts["local_eig_ham_dense"]
|
|
690
|
+
if dense is None:
|
|
691
|
+
dense = prod(dims) < 800
|
|
692
|
+
|
|
693
|
+
dims_inds = {
|
|
694
|
+
"ldims": dims,
|
|
695
|
+
"rdims": dims,
|
|
696
|
+
"left_inds": lix,
|
|
697
|
+
"right_inds": uix,
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
# form effective hamiltonian
|
|
701
|
+
if dense:
|
|
702
|
+
# contract remaining hamiltonian and get its dense representation
|
|
703
|
+
Heff = (self._eff_ham ^ "_HAM")["_HAM"].to_dense(lix, uix)
|
|
704
|
+
else:
|
|
705
|
+
Heff = TNLinearOperator(self._eff_ham["_HAM"], **dims_inds)
|
|
706
|
+
|
|
707
|
+
# form effective norm
|
|
708
|
+
if self.cyclic:
|
|
709
|
+
fudge = self.opts["periodic_nullspace_fudge_factor"]
|
|
710
|
+
|
|
711
|
+
neff_dense = self.opts["local_eig_norm_dense"]
|
|
712
|
+
if neff_dense is None:
|
|
713
|
+
neff_dense = dense
|
|
714
|
+
|
|
715
|
+
# Check if site already pseudo-orthonogal
|
|
716
|
+
site_norm = self._k[i : i + self.bsz].H @ self._k[i : i + self.bsz]
|
|
717
|
+
if abs(site_norm - 1) < self.opts["periodic_orthog_tol"]:
|
|
718
|
+
Neff = None
|
|
719
|
+
|
|
720
|
+
# else contruct RHS normalization operator
|
|
721
|
+
elif neff_dense:
|
|
722
|
+
Neff = (self._eff_norm ^ "_EYE")["_EYE"].to_dense(lix, uix)
|
|
723
|
+
np.fill_diagonal(Neff, Neff.diagonal() + fudge)
|
|
724
|
+
np.fill_diagonal(Heff, Heff.diagonal() + fudge**0.5)
|
|
725
|
+
else:
|
|
726
|
+
Neff = TNLinearOperator(self._eff_norm["_EYE"], **dims_inds)
|
|
727
|
+
Neff += IdentityLinearOperator(Neff.shape[0], fudge)
|
|
728
|
+
Heff += IdentityLinearOperator(Heff.shape[0], fudge**0.5)
|
|
729
|
+
|
|
730
|
+
else:
|
|
731
|
+
Neff = None
|
|
732
|
+
|
|
733
|
+
return Heff, Neff
|
|
734
|
+
|
|
735
|
+
def post_check(self, i, Neff, loc_gs, loc_en, loc_gs_old):
|
|
736
|
+
"""Perform some checks on the output of the local eigensolve."""
|
|
737
|
+
if self.cyclic:
|
|
738
|
+
# pseudo-orthogonal
|
|
739
|
+
if Neff is None:
|
|
740
|
+
# just perform leading correction to norm from site_norm
|
|
741
|
+
site_norm = (
|
|
742
|
+
self._k[i : i + self.bsz].H @ self._k[i : i + self.bsz]
|
|
743
|
+
)
|
|
744
|
+
loc_gs *= site_norm**0.5
|
|
745
|
+
loc_en *= site_norm
|
|
746
|
+
return loc_en, loc_gs
|
|
747
|
+
|
|
748
|
+
loc_en -= self.opts["periodic_nullspace_fudge_factor"] ** 0.5
|
|
749
|
+
|
|
750
|
+
# this is helpful for identifying badly behaved numerics
|
|
751
|
+
Neffnorm = (loc_gs.H @ (Neff @ loc_gs)).item()
|
|
752
|
+
if abs(Neffnorm - 1) > 10 * self.opts["local_eig_tol"]:
|
|
753
|
+
raise DMRGError(
|
|
754
|
+
f"Effective norm diverged to {Neffnorm}, "
|
|
755
|
+
"check that Neff is positive?"
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
return loc_en, loc_gs
|
|
759
|
+
|
|
760
|
+
def _update_local_state_1site(self, i, direction, **compress_opts):
|
|
761
|
+
r"""Find the single site effective tensor groundstate of::
|
|
762
|
+
|
|
763
|
+
>->->->->-/|\-<-<-<-<-<-<-<-< /|\ <-- uix
|
|
764
|
+
| | | | | | | | | | | | | | / | \
|
|
765
|
+
H-H-H-H-H--H--H-H-H-H-H-H-H-H = L--H--R
|
|
766
|
+
| | | | | i| | | | | | | | | \i| /
|
|
767
|
+
>->->->->-\|/-<-<-<-<-<-<-<-< \|/ <-- lix
|
|
768
|
+
|
|
769
|
+
And insert it back into the states ``k`` and ``b``, and thus
|
|
770
|
+
``TN_energy``.
|
|
771
|
+
"""
|
|
772
|
+
uix, lix = self._k[i].inds, self._b[i].inds
|
|
773
|
+
dims = self._k[i].shape
|
|
774
|
+
|
|
775
|
+
# get local operators
|
|
776
|
+
Heff, Neff = self.form_local_ops(i, dims, lix, uix)
|
|
777
|
+
|
|
778
|
+
# get the old local groundstate to use as initial guess
|
|
779
|
+
loc_gs_old = self._k[i].data.ravel()
|
|
780
|
+
|
|
781
|
+
# find the local energy and groundstate
|
|
782
|
+
loc_en, loc_gs = self._eigs(Heff, B=Neff, v0=loc_gs_old)
|
|
783
|
+
|
|
784
|
+
# perform some minor checks and corrections
|
|
785
|
+
loc_en, loc_gs = self.post_check(i, Neff, loc_gs, loc_en, loc_gs_old)
|
|
786
|
+
|
|
787
|
+
# insert back into state and all tensor networks viewing it
|
|
788
|
+
loc_gs = loc_gs.toarray().reshape(dims)
|
|
789
|
+
self._k[i].modify(data=loc_gs)
|
|
790
|
+
self._b[i].modify(data=loc_gs.conj())
|
|
791
|
+
|
|
792
|
+
# normalize - necessary due to loose tolerance eigensolve
|
|
793
|
+
if self.cyclic:
|
|
794
|
+
norm = (self._eff_norm ^ all) ** 0.5
|
|
795
|
+
self._k[i].modify(data=self._k[i].data / norm)
|
|
796
|
+
self._b[i].modify(data=self._b[i].data / norm)
|
|
797
|
+
|
|
798
|
+
tot_en = self._eff_ham ^ all
|
|
799
|
+
|
|
800
|
+
self._canonize_after_1site_update(direction, i)
|
|
801
|
+
|
|
802
|
+
return loc_en.item(), tot_en
|
|
803
|
+
|
|
804
|
+
def _update_local_state_2site(self, i, direction, **compress_opts):
|
|
805
|
+
r"""Find the 2-site effective tensor groundstate of::
|
|
806
|
+
|
|
807
|
+
>->->->->-/| |\-<-<-<-<-<-<-<-< /| |\
|
|
808
|
+
| | | | | | | | | | | | | | | / | | \
|
|
809
|
+
H-H-H-H-H--H-H--H-H-H-H-H-H-H-H = L--H-H--R
|
|
810
|
+
| | | | | i i+1| | | | | | | | \ | | /
|
|
811
|
+
>->->->->-\| |/-<-<-<-<-<-<-<-< \| |/
|
|
812
|
+
i i+1
|
|
813
|
+
|
|
814
|
+
And insert it back into the states ``k`` and ``b``, and thus
|
|
815
|
+
``TN_energy``.
|
|
816
|
+
"""
|
|
817
|
+
(
|
|
818
|
+
dims,
|
|
819
|
+
lix_L,
|
|
820
|
+
lix_R,
|
|
821
|
+
lix,
|
|
822
|
+
uix_L,
|
|
823
|
+
uix_R,
|
|
824
|
+
uix,
|
|
825
|
+
l_bond_ind,
|
|
826
|
+
u_bond_ind,
|
|
827
|
+
) = parse_2site_inds_dims(self._k, self._b, i)
|
|
828
|
+
|
|
829
|
+
# get local operators
|
|
830
|
+
Heff, Neff = self.form_local_ops(i, dims, lix, uix)
|
|
831
|
+
|
|
832
|
+
# get the old 2-site local groundstate to use as initial guess
|
|
833
|
+
loc_gs_old = self._k[i].contract(self._k[i + 1]).to_dense(uix)
|
|
834
|
+
|
|
835
|
+
# find the 2-site local groundstate and energy
|
|
836
|
+
loc_en, loc_gs = self._eigs(Heff, B=Neff, v0=loc_gs_old)
|
|
837
|
+
|
|
838
|
+
# perform some minor checks and corrections
|
|
839
|
+
loc_en, loc_gs = self.post_check(i, Neff, loc_gs, loc_en, loc_gs_old)
|
|
840
|
+
|
|
841
|
+
# split the two site local groundstate
|
|
842
|
+
T_AB = Tensor(loc_gs.toarray().reshape(dims), uix)
|
|
843
|
+
L, R = T_AB.split(
|
|
844
|
+
left_inds=uix_L,
|
|
845
|
+
get="arrays",
|
|
846
|
+
absorb=direction,
|
|
847
|
+
right_inds=uix_R,
|
|
848
|
+
**compress_opts,
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
# insert back into state and all tensor networks viewing it
|
|
852
|
+
self._k[i].modify(data=L, inds=(*uix_L, u_bond_ind))
|
|
853
|
+
self._b[i].modify(data=L.conj(), inds=(*lix_L, l_bond_ind))
|
|
854
|
+
self._k[i + 1].modify(data=R, inds=(u_bond_ind, *uix_R))
|
|
855
|
+
self._b[i + 1].modify(data=R.conj(), inds=(l_bond_ind, *lix_R))
|
|
856
|
+
|
|
857
|
+
# normalize due to compression and insert factor at the correct site
|
|
858
|
+
if self.cyclic:
|
|
859
|
+
# Right Left
|
|
860
|
+
# i i+1 i i+1
|
|
861
|
+
# -->~~o-- or --o~~<--
|
|
862
|
+
# | | | |
|
|
863
|
+
norm = (self.ME_eff_norm() ^ all) ** 0.5
|
|
864
|
+
next_site = {"right": i + 1, "left": i}[direction]
|
|
865
|
+
|
|
866
|
+
self._k[next_site].modify(data=self._k[next_site].data / norm)
|
|
867
|
+
self._b[next_site].modify(data=self._b[next_site].data / norm)
|
|
868
|
+
|
|
869
|
+
tot_en = self._eff_ham ^ all
|
|
870
|
+
|
|
871
|
+
return loc_en.item(), tot_en
|
|
872
|
+
|
|
873
|
+
def _update_local_state(self, i, **update_opts):
|
|
874
|
+
"""Move envs to site ``i`` and dispatch to the correct local updater."""
|
|
875
|
+
if self.cyclic:
|
|
876
|
+
# move effective norm first as it can trigger canonize_cyclic etc.
|
|
877
|
+
self.ME_eff_norm.move_to(i)
|
|
878
|
+
|
|
879
|
+
self.ME_eff_ham.move_to(i)
|
|
880
|
+
|
|
881
|
+
return {
|
|
882
|
+
1: self._update_local_state_1site,
|
|
883
|
+
2: self._update_local_state_2site,
|
|
884
|
+
}[self.bsz](i, **update_opts)
|
|
885
|
+
|
|
886
|
+
def sweep(self, direction, canonize=True, verbosity=0, **update_opts):
|
|
887
|
+
r"""Perform a sweep of optimizations, either rightwards::
|
|
888
|
+
|
|
889
|
+
optimize -->
|
|
890
|
+
...
|
|
891
|
+
>->-o-<-<-<-<-<-<-<-<-<-<-<-<-<
|
|
892
|
+
| | | | | | | | | | | | | | | |
|
|
893
|
+
H-H-H-H-H-H-H-H-H-H-H-H-H-H-H-H
|
|
894
|
+
| | | | | | | | | | | | | | | |
|
|
895
|
+
>->-o-<-<-<-<-<-<-<-<-<-<-<-<-<
|
|
896
|
+
|
|
897
|
+
or leftwards (`direction='L'`)::
|
|
898
|
+
|
|
899
|
+
<-- optimize
|
|
900
|
+
...
|
|
901
|
+
>->->->->->->->->->->->->-o-<-<
|
|
902
|
+
| | | | | | | | | | | | | | | |
|
|
903
|
+
H-H-H-H-H-H-H-H-H-H-H-H-H-H-H-H
|
|
904
|
+
| | | | | | | | | | | | | | | |
|
|
905
|
+
>->->->->->->->->->->->->-o-<-<
|
|
906
|
+
|
|
907
|
+
After the sweep the state is left or right canonized respectively.
|
|
908
|
+
|
|
909
|
+
Parameters
|
|
910
|
+
----------
|
|
911
|
+
direction : {'R', 'L'}
|
|
912
|
+
Sweep from left to right (->) or right to left (<-) respectively.
|
|
913
|
+
canonize : bool, optional
|
|
914
|
+
Canonize the state first, not needed if doing alternate sweeps.
|
|
915
|
+
verbosity : {0, 1, 2}, optional
|
|
916
|
+
Show a progress bar for the sweep.
|
|
917
|
+
update_opts :
|
|
918
|
+
Supplied to ``self._update_local_state``.
|
|
919
|
+
"""
|
|
920
|
+
if canonize:
|
|
921
|
+
{"R": self._k.right_canonize, "L": self._k.left_canonize}[
|
|
922
|
+
direction
|
|
923
|
+
](bra=self._b)
|
|
924
|
+
|
|
925
|
+
n, bsz = self.L, self.bsz
|
|
926
|
+
|
|
927
|
+
direction, begin, sweep = {
|
|
928
|
+
("R", False): ("right", "left", range(0, n - bsz + 1)),
|
|
929
|
+
("L", False): ("left", "right", range(n - bsz, -1, -1)),
|
|
930
|
+
("R", True): ("right", "left", range(0, n)),
|
|
931
|
+
("L", True): ("left", "right", range(n - 1, -1, -1)),
|
|
932
|
+
}[direction, self.cyclic]
|
|
933
|
+
|
|
934
|
+
if verbosity:
|
|
935
|
+
sweep = progbar(sweep, ncols=80, total=len(sweep))
|
|
936
|
+
|
|
937
|
+
env_opts = {
|
|
938
|
+
"begin": begin,
|
|
939
|
+
"bsz": bsz,
|
|
940
|
+
"cyclic": self.cyclic,
|
|
941
|
+
"ssz": self.opts["periodic_segment_size"],
|
|
942
|
+
"method": self.opts["periodic_compress_method"],
|
|
943
|
+
"max_bond": self.opts["periodic_compress_max_bond"],
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if self.cyclic:
|
|
947
|
+
# setup moving norm environment
|
|
948
|
+
nm_opts = {
|
|
949
|
+
**env_opts,
|
|
950
|
+
"norm": True,
|
|
951
|
+
"eps": self.opts["periodic_compress_norm_eps"],
|
|
952
|
+
"segment_callbacks": get_cyclic_canonizer(
|
|
953
|
+
self._k,
|
|
954
|
+
self._b,
|
|
955
|
+
inv_tol=self.opts["periodic_canonize_inv_tol"],
|
|
956
|
+
),
|
|
957
|
+
}
|
|
958
|
+
self.ME_eff_norm = MovingEnvironment(self.TN_norm, **nm_opts)
|
|
959
|
+
|
|
960
|
+
# setup moving energy environment
|
|
961
|
+
en_opts = {**env_opts, "eps": self.opts["periodic_compress_ham_eps"]}
|
|
962
|
+
self.ME_eff_ham = MovingEnvironment(self.TN_energy, **en_opts)
|
|
963
|
+
|
|
964
|
+
# perform the sweep, collecting local and total energies
|
|
965
|
+
local_ens, tot_ens = zip(
|
|
966
|
+
*[
|
|
967
|
+
self._update_local_state(i, direction=direction, **update_opts)
|
|
968
|
+
for i in sweep
|
|
969
|
+
]
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
if verbosity:
|
|
973
|
+
sweep.close()
|
|
974
|
+
|
|
975
|
+
self.local_energies.append(local_ens)
|
|
976
|
+
self.total_energies.append(tot_ens)
|
|
977
|
+
|
|
978
|
+
if self.cyclic:
|
|
979
|
+
self.bond_sizes_ham.append(self.ME_eff_ham.bond_sizes)
|
|
980
|
+
self.bond_sizes_norm.append(self.ME_eff_norm.bond_sizes)
|
|
981
|
+
|
|
982
|
+
return tot_ens[-1]
|
|
983
|
+
|
|
984
|
+
def sweep_right(self, canonize=True, verbosity=0, **update_opts):
|
|
985
|
+
return self.sweep(
|
|
986
|
+
direction="R",
|
|
987
|
+
canonize=canonize,
|
|
988
|
+
verbosity=verbosity,
|
|
989
|
+
**update_opts,
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
def sweep_left(self, canonize=True, verbosity=0, **update_opts):
|
|
993
|
+
return self.sweep(
|
|
994
|
+
direction="L",
|
|
995
|
+
canonize=canonize,
|
|
996
|
+
verbosity=verbosity,
|
|
997
|
+
**update_opts,
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
# ----------------- overloadable 'plugin' style methods ----------------- #
|
|
1001
|
+
|
|
1002
|
+
def _print_pre_sweep(self, i, direction, max_bond, cutoff, verbosity=0):
|
|
1003
|
+
"""Print this before each sweep."""
|
|
1004
|
+
if verbosity > 0:
|
|
1005
|
+
print(
|
|
1006
|
+
f"{i + 1}, {direction}, "
|
|
1007
|
+
f"max_bond=({self._k.max_bond()}/{max_bond}), "
|
|
1008
|
+
f"cutoff:{cutoff}",
|
|
1009
|
+
flush=True,
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
def _compute_post_sweep(self):
|
|
1013
|
+
"""Compute this after each sweep."""
|
|
1014
|
+
pass
|
|
1015
|
+
|
|
1016
|
+
def _print_post_sweep(self, converged, verbosity=0):
|
|
1017
|
+
"""Print this after each sweep."""
|
|
1018
|
+
if verbosity > 1:
|
|
1019
|
+
self._k.show()
|
|
1020
|
+
if verbosity > 0:
|
|
1021
|
+
msg = "Energy: {} ... {}".format(
|
|
1022
|
+
self.energy, "converged!" if converged else "not converged."
|
|
1023
|
+
)
|
|
1024
|
+
print(msg, flush=True)
|
|
1025
|
+
|
|
1026
|
+
def _check_convergence(self, tol):
|
|
1027
|
+
"""By default check the absolute change in energy."""
|
|
1028
|
+
if len(self.energies) < 2:
|
|
1029
|
+
return False
|
|
1030
|
+
return abs(self.energies[-2] - self.energies[-1]) < tol
|
|
1031
|
+
|
|
1032
|
+
# -------------------------- main solve driver -------------------------- #
|
|
1033
|
+
|
|
1034
|
+
def solve(
|
|
1035
|
+
self,
|
|
1036
|
+
tol=1e-4,
|
|
1037
|
+
bond_dims=None,
|
|
1038
|
+
cutoffs=None,
|
|
1039
|
+
sweep_sequence=None,
|
|
1040
|
+
max_sweeps=10,
|
|
1041
|
+
verbosity=0,
|
|
1042
|
+
suppress_warnings=True,
|
|
1043
|
+
):
|
|
1044
|
+
"""Solve the system with a sequence of sweeps, up to a certain
|
|
1045
|
+
absolute tolerance in the energy or maximum number of sweeps.
|
|
1046
|
+
|
|
1047
|
+
Parameters
|
|
1048
|
+
----------
|
|
1049
|
+
tol : float, optional
|
|
1050
|
+
The absolute tolerance to converge energy to.
|
|
1051
|
+
bond_dims : int or sequence of int
|
|
1052
|
+
Overide the initial/current bond_dim sequence.
|
|
1053
|
+
cutoffs : float of sequence of float
|
|
1054
|
+
Overide the initial/current cutoff sequence.
|
|
1055
|
+
sweep_sequence : str, optional
|
|
1056
|
+
String made of 'L' and 'R' defining the sweep sequence, e.g 'RRL'.
|
|
1057
|
+
The sequence will be repeated until ``max_sweeps`` is reached.
|
|
1058
|
+
max_sweeps : int, optional
|
|
1059
|
+
The maximum number of sweeps to perform.
|
|
1060
|
+
verbosity : {0, 1, 2}, optional
|
|
1061
|
+
How much information to print about progress.
|
|
1062
|
+
suppress_warnings : bool, optional
|
|
1063
|
+
Whether to suppress warnings about non-convergence, usually due to
|
|
1064
|
+
the intentional low accuracy of the inner eigensolve.
|
|
1065
|
+
|
|
1066
|
+
Returns
|
|
1067
|
+
-------
|
|
1068
|
+
converged : bool
|
|
1069
|
+
Whether the algorithm has converged to ``tol`` yet.
|
|
1070
|
+
"""
|
|
1071
|
+
verbosity = int(verbosity)
|
|
1072
|
+
|
|
1073
|
+
# Possibly overide the default bond dimension, cutoff, LR sequences.
|
|
1074
|
+
if bond_dims is not None:
|
|
1075
|
+
self._set_bond_dim_seq(bond_dims)
|
|
1076
|
+
if cutoffs is not None:
|
|
1077
|
+
self._set_cutoff_seq(cutoffs)
|
|
1078
|
+
if sweep_sequence is None:
|
|
1079
|
+
sweep_sequence = self.opts["default_sweep_sequence"]
|
|
1080
|
+
|
|
1081
|
+
directions = itertools.cycle(sweep_sequence)
|
|
1082
|
+
previous_direction = "0"
|
|
1083
|
+
|
|
1084
|
+
for _ in range(max_sweeps):
|
|
1085
|
+
# Get the next direction, bond dimension and cutoff
|
|
1086
|
+
direction, max_bond, cutoff = (
|
|
1087
|
+
next(directions),
|
|
1088
|
+
next(self._bond_dims),
|
|
1089
|
+
next(self._cutoffs),
|
|
1090
|
+
)
|
|
1091
|
+
self._print_pre_sweep(
|
|
1092
|
+
len(self.energies),
|
|
1093
|
+
direction,
|
|
1094
|
+
max_bond,
|
|
1095
|
+
cutoff,
|
|
1096
|
+
verbosity=verbosity,
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
# if last sweep was in opposite direction no need to canonize
|
|
1100
|
+
canonize = (
|
|
1101
|
+
False
|
|
1102
|
+
if direction + previous_direction in {"LR", "RL"}
|
|
1103
|
+
else True
|
|
1104
|
+
)
|
|
1105
|
+
|
|
1106
|
+
# need to manually expand bond dimension for DMRG1
|
|
1107
|
+
if self.bsz == 1:
|
|
1108
|
+
self._k.expand_bond_dimension(
|
|
1109
|
+
max_bond,
|
|
1110
|
+
bra=self._b,
|
|
1111
|
+
rand_strength=self.opts["bond_expand_rand_strength"],
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
# inject all options and defaults
|
|
1115
|
+
sweep_opts = {
|
|
1116
|
+
"canonize": canonize,
|
|
1117
|
+
"max_bond": max_bond,
|
|
1118
|
+
"cutoff": cutoff,
|
|
1119
|
+
"cutoff_mode": self.opts["bond_compress_cutoff_mode"],
|
|
1120
|
+
"method": self.opts["bond_compress_method"],
|
|
1121
|
+
"verbosity": verbosity,
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
# perform the sweep
|
|
1125
|
+
if suppress_warnings:
|
|
1126
|
+
with warnings.catch_warnings():
|
|
1127
|
+
warnings.simplefilter("ignore")
|
|
1128
|
+
energy = self.sweep(direction=direction, **sweep_opts)
|
|
1129
|
+
else:
|
|
1130
|
+
energy = self.sweep(direction=direction, **sweep_opts)
|
|
1131
|
+
|
|
1132
|
+
self.energies.append(energy)
|
|
1133
|
+
|
|
1134
|
+
# any plugin computations
|
|
1135
|
+
self._compute_post_sweep()
|
|
1136
|
+
|
|
1137
|
+
# check convergence
|
|
1138
|
+
converged = self._check_convergence(tol)
|
|
1139
|
+
self._print_post_sweep(converged, verbosity=verbosity)
|
|
1140
|
+
if converged:
|
|
1141
|
+
break
|
|
1142
|
+
|
|
1143
|
+
previous_direction = direction
|
|
1144
|
+
|
|
1145
|
+
return converged
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
class DMRG1(DMRG):
|
|
1149
|
+
"""Simple alias of one site ``DMRG``."""
|
|
1150
|
+
|
|
1151
|
+
__doc__ += DMRG.__doc__
|
|
1152
|
+
|
|
1153
|
+
def __init__(self, ham, which="SA", bond_dims=None, cutoffs=1e-8, p0=None):
|
|
1154
|
+
if bond_dims is None:
|
|
1155
|
+
bond_dims = range(10, 1001, 10)
|
|
1156
|
+
|
|
1157
|
+
super().__init__(
|
|
1158
|
+
ham,
|
|
1159
|
+
bond_dims=bond_dims,
|
|
1160
|
+
cutoffs=cutoffs,
|
|
1161
|
+
which=which,
|
|
1162
|
+
p0=p0,
|
|
1163
|
+
bsz=1,
|
|
1164
|
+
)
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
class DMRG2(DMRG):
|
|
1168
|
+
"""Simple alias of two site ``DMRG``."""
|
|
1169
|
+
|
|
1170
|
+
__doc__ += DMRG.__doc__
|
|
1171
|
+
|
|
1172
|
+
def __init__(self, ham, which="SA", bond_dims=None, cutoffs=1e-8, p0=None):
|
|
1173
|
+
if bond_dims is None:
|
|
1174
|
+
bond_dims = [8, 16, 32, 64, 128, 256, 512, 1024]
|
|
1175
|
+
|
|
1176
|
+
super().__init__(
|
|
1177
|
+
ham,
|
|
1178
|
+
bond_dims=bond_dims,
|
|
1179
|
+
cutoffs=cutoffs,
|
|
1180
|
+
which=which,
|
|
1181
|
+
p0=p0,
|
|
1182
|
+
bsz=2,
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
# --------------------------------------------------------------------------- #
|
|
1187
|
+
# DMRGX #
|
|
1188
|
+
# --------------------------------------------------------------------------- #
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
class DMRGX(DMRG):
|
|
1192
|
+
"""Class implmenting DMRG-X [1], whereby local effective energy eigenstates
|
|
1193
|
+
are chosen to maximise overlap with the previous step's state, leading to
|
|
1194
|
+
convergence on an mid-spectrum eigenstate of the full hamiltonian, as long
|
|
1195
|
+
as it is perturbatively close to the original state.
|
|
1196
|
+
|
|
1197
|
+
[1] Khemani, V., Pollmann, F. & Sondhi, S. L. Obtaining Highly Excited
|
|
1198
|
+
Eigenstates of Many-Body Localized Hamiltonians by the Density Matrix
|
|
1199
|
+
Renormalization Group Approach. Phys. Rev. Lett. 116, 247204 (2016).
|
|
1200
|
+
|
|
1201
|
+
Parameters
|
|
1202
|
+
----------
|
|
1203
|
+
ham : MatrixProductOperator
|
|
1204
|
+
The hamiltonian in MPO form, should have ~area-law eigenstates.
|
|
1205
|
+
p0 : MatrixProductState
|
|
1206
|
+
The initial MPS guess, e.g. a computation basis state.
|
|
1207
|
+
bond_dims : int or sequence of int
|
|
1208
|
+
See :class:`DMRG`.
|
|
1209
|
+
cutoffs : float or sequence of float
|
|
1210
|
+
See :class:`DMRG`.
|
|
1211
|
+
|
|
1212
|
+
Attributes
|
|
1213
|
+
----------
|
|
1214
|
+
k : MatrixProductState
|
|
1215
|
+
The current, optimized state.
|
|
1216
|
+
energies : list of float
|
|
1217
|
+
The list of energies after each sweep.
|
|
1218
|
+
"""
|
|
1219
|
+
|
|
1220
|
+
def __init__(self, ham, p0, bond_dims, cutoffs=1e-8, bsz=1):
|
|
1221
|
+
super().__init__(
|
|
1222
|
+
ham, bond_dims=bond_dims, p0=p0, bsz=bsz, cutoffs=cutoffs
|
|
1223
|
+
)
|
|
1224
|
+
# Want to keep track of energy variance as well
|
|
1225
|
+
var_ham1 = self.ham.copy()
|
|
1226
|
+
var_ham2 = self.ham.copy()
|
|
1227
|
+
var_ham1.upper_ind_id = self._k.site_ind_id
|
|
1228
|
+
var_ham1.lower_ind_id = "__ham2{}__"
|
|
1229
|
+
var_ham2.upper_ind_id = "__ham2{}__"
|
|
1230
|
+
var_ham2.lower_ind_id = self._b.site_ind_id
|
|
1231
|
+
self.TN_energy2 = self._k | var_ham1 | var_ham2 | self._b
|
|
1232
|
+
self.energies.append(self.TN_energy ^ ...)
|
|
1233
|
+
self.variances = [(self.TN_energy2 ^ ...) - self.energies[-1] ** 2]
|
|
1234
|
+
self._target_energy = self.energies[-1]
|
|
1235
|
+
|
|
1236
|
+
self.opts = {
|
|
1237
|
+
"local_eig_partial_cutoff": 2**11,
|
|
1238
|
+
"local_eig_partial_k": 0.02,
|
|
1239
|
+
"local_eig_tol": 1e-1,
|
|
1240
|
+
"overlap_thresh": 2 / 3,
|
|
1241
|
+
"bond_compress_method": "svd",
|
|
1242
|
+
"bond_compress_cutoff_mode": "sum2",
|
|
1243
|
+
"default_sweep_sequence": "RRLL",
|
|
1244
|
+
"bond_expand_rand_strength": 1e-9,
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
@property
|
|
1248
|
+
def variance(self):
|
|
1249
|
+
return self.variances[-1]
|
|
1250
|
+
|
|
1251
|
+
def form_local_ops(self, i, dims, lix, uix):
|
|
1252
|
+
self._eff_ham = self.ME_eff_ham()
|
|
1253
|
+
self._eff_ovlp = self.ME_eff_ovlp()
|
|
1254
|
+
self._eff_ham2 = self.ME_eff_ham2()
|
|
1255
|
+
|
|
1256
|
+
Heff = (self._eff_ham ^ "_HAM")["_HAM"].to_dense(lix, uix)
|
|
1257
|
+
|
|
1258
|
+
return Heff
|
|
1259
|
+
|
|
1260
|
+
def _update_local_state_1site_dmrgx(self, i, direction, **compress_opts):
|
|
1261
|
+
"""Like ``_update_local_state``, but re-insert all eigenvectors, then
|
|
1262
|
+
choose the one with best overlap with ``eff_ovlp``.
|
|
1263
|
+
"""
|
|
1264
|
+
uix, lix = self._k[i].inds, self._b[i].inds
|
|
1265
|
+
dims = self._k[i].shape
|
|
1266
|
+
|
|
1267
|
+
# contract remaining hamiltonian and get its dense representation
|
|
1268
|
+
Heff = self.form_local_ops(i, dims, lix, uix)
|
|
1269
|
+
|
|
1270
|
+
# eigen-decompose and reshape eigenvectors thus::
|
|
1271
|
+
#
|
|
1272
|
+
# |'__ev_ind__'
|
|
1273
|
+
# E
|
|
1274
|
+
# /|\
|
|
1275
|
+
#
|
|
1276
|
+
D = prod(dims)
|
|
1277
|
+
if D <= self.opts["local_eig_partial_cutoff"]:
|
|
1278
|
+
evals, evecs = eigh(Heff)
|
|
1279
|
+
else:
|
|
1280
|
+
if isinstance(self.opts["local_eig_partial_k"], float):
|
|
1281
|
+
k = int(self.opts["local_eig_partial_k"] * D)
|
|
1282
|
+
else:
|
|
1283
|
+
k = self.opts["local_eig_partial_k"]
|
|
1284
|
+
|
|
1285
|
+
evals, evecs = eigh(
|
|
1286
|
+
Heff,
|
|
1287
|
+
sigma=self._target_energy,
|
|
1288
|
+
v0=self._k[i].data,
|
|
1289
|
+
k=k,
|
|
1290
|
+
tol=self.opts["local_eig_tol"],
|
|
1291
|
+
backend="scipy",
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
evecs = asarray(evecs).reshape(*dims, -1)
|
|
1295
|
+
evecs_c = evecs.conj()
|
|
1296
|
+
|
|
1297
|
+
# update tensor at site i with all evecs -> need dummy index
|
|
1298
|
+
ki = self._k[i]
|
|
1299
|
+
bi = self._b[i]
|
|
1300
|
+
ki.modify(data=evecs, inds=(*uix, "__ev_ind__"))
|
|
1301
|
+
|
|
1302
|
+
# find the index of the highest overlap eigenvector, by contracting::
|
|
1303
|
+
#
|
|
1304
|
+
# |'__ev_ind__'
|
|
1305
|
+
# o-o-o-E-o-o-o-o-o-o-o
|
|
1306
|
+
# | | | | | | | | | | |
|
|
1307
|
+
# 0-0-0-0-0-0-0-0-0-0-0 <- state from previous step
|
|
1308
|
+
#
|
|
1309
|
+
# choose the eigenvectors with best overlap
|
|
1310
|
+
overlaps = np.abs((self._eff_ovlp ^ all).data)
|
|
1311
|
+
|
|
1312
|
+
if self.opts["overlap_thresh"] == 1:
|
|
1313
|
+
# just choose the maximum overlap state
|
|
1314
|
+
best = np.argmax(overlaps)
|
|
1315
|
+
else:
|
|
1316
|
+
# else simulteneously reduce energy variance as well
|
|
1317
|
+
(best_overlaps,) = np.where(
|
|
1318
|
+
overlaps > np.max(overlaps) * self.opts["overlap_thresh"]
|
|
1319
|
+
)
|
|
1320
|
+
|
|
1321
|
+
if len(best_overlaps) == 1:
|
|
1322
|
+
# still only one good overlapping eigenvector -> choose that
|
|
1323
|
+
(best,) = best_overlaps
|
|
1324
|
+
else:
|
|
1325
|
+
# reduce down to the candidate eigenpairs
|
|
1326
|
+
evals = evals[best_overlaps]
|
|
1327
|
+
evecs = evecs[..., best_overlaps]
|
|
1328
|
+
evecs_c = evecs_c[..., best_overlaps]
|
|
1329
|
+
|
|
1330
|
+
# need bra site in place with extra dimension to calc variance
|
|
1331
|
+
ki.modify(data=evecs)
|
|
1332
|
+
bi.modify(data=evecs_c, inds=(*lix, "__ev_ind__"))
|
|
1333
|
+
|
|
1334
|
+
# now find the variances of the best::
|
|
1335
|
+
#
|
|
1336
|
+
# |'__ev_ind__'
|
|
1337
|
+
# o-o-o-E-o-o-o |'__ev_ind__' ^2
|
|
1338
|
+
# | | | | | | | o-o-o-E-o-o-o
|
|
1339
|
+
# H-H-H-H-H-H-H | | | | | | |
|
|
1340
|
+
# | | | | | | | - H-H-H-H-H-H-H
|
|
1341
|
+
# H-H-H-H-H-H-H | | | | | | |
|
|
1342
|
+
# | | | | | | | o-o-o-E-o-o-o
|
|
1343
|
+
# o-o-o-E-o-o-o |'__ev_ind__'
|
|
1344
|
+
# |'__ev_ind__'
|
|
1345
|
+
#
|
|
1346
|
+
# use einsum notation to get diagonal of left hand term
|
|
1347
|
+
en2 = tensor_contract(
|
|
1348
|
+
*self._eff_ham2.tensors, output_inds=["__ev_ind__"]
|
|
1349
|
+
).data
|
|
1350
|
+
|
|
1351
|
+
# then find minimum variance
|
|
1352
|
+
best = np.argmin(en2 - evals**2)
|
|
1353
|
+
|
|
1354
|
+
# update site i with the data and drop dummy index too
|
|
1355
|
+
ki.modify(data=evecs[..., best], inds=uix)
|
|
1356
|
+
bi.modify(data=evecs_c[..., best], inds=lix)
|
|
1357
|
+
# store the current effective energy for possibly targeted eigh
|
|
1358
|
+
self._target_energy = evals[best]
|
|
1359
|
+
|
|
1360
|
+
tot_en = self._eff_ham ^ all
|
|
1361
|
+
|
|
1362
|
+
self._canonize_after_1site_update(direction, i)
|
|
1363
|
+
|
|
1364
|
+
return evals[best], tot_en
|
|
1365
|
+
|
|
1366
|
+
# def _update_local_state_2site_dmrgx(self, i, direction, **compress_opts):
|
|
1367
|
+
# raise NotImplementedError("2-site DMRGX not implemented yet.")
|
|
1368
|
+
# dims, lix_L, lix_R, lix, uix_L, uix_R, uix, l_bond_ind, u_bond_ind =\
|
|
1369
|
+
# parse_2site_inds_dims(self._k, self._b, i)
|
|
1370
|
+
|
|
1371
|
+
# # contract remaining hamiltonian and get its dense representation
|
|
1372
|
+
# eff_ham = (self._eff_ham ^ '_HAM')['_HAM']
|
|
1373
|
+
# eff_ham.fuse_((('lower', lix), ('upper', uix)))
|
|
1374
|
+
# A = eff_ham.data
|
|
1375
|
+
|
|
1376
|
+
# # eigen-decompose and reshape eigenvectors thus::
|
|
1377
|
+
# #
|
|
1378
|
+
# # ||'__ev_ind__'
|
|
1379
|
+
# # EE
|
|
1380
|
+
# # /||\
|
|
1381
|
+
# #
|
|
1382
|
+
# D = prod(dims)
|
|
1383
|
+
# if D <= self.opts['local_eig_partial_cutoff']:
|
|
1384
|
+
# evals, evecs = eigh(A)
|
|
1385
|
+
# else:
|
|
1386
|
+
# if isinstance(self.opts['local_eig_partial_k'], float):
|
|
1387
|
+
# k = int(self.opts['local_eig_partial_k'] * D)
|
|
1388
|
+
# else:
|
|
1389
|
+
# k = self.opts['local_eig_partial_k']
|
|
1390
|
+
|
|
1391
|
+
# # find the 2-site local state using previous as initial guess
|
|
1392
|
+
# v0 = self._k[i].contract(self._k[i + 1], output_inds=uix).data
|
|
1393
|
+
|
|
1394
|
+
# evals, evecs = eigh(
|
|
1395
|
+
# A, sigma=self.energies[-1], v0=v0,
|
|
1396
|
+
# k=k, tol=self.opts['local_eig_tol'], backend='scipy')
|
|
1397
|
+
|
|
1398
|
+
def _update_local_state(self, i, **update_opts):
|
|
1399
|
+
self.ME_eff_ham.move_to(i)
|
|
1400
|
+
self.ME_eff_ham2.move_to(i)
|
|
1401
|
+
self.ME_eff_ovlp.move_to(i)
|
|
1402
|
+
|
|
1403
|
+
return {
|
|
1404
|
+
1: self._update_local_state_1site_dmrgx,
|
|
1405
|
+
# 2: self._update_local_state_2site_dmrgx,
|
|
1406
|
+
}[self.bsz](i, **update_opts)
|
|
1407
|
+
|
|
1408
|
+
def sweep(self, direction, canonize=True, verbosity=0, **update_opts):
|
|
1409
|
+
"""Perform a sweep of the algorithm.
|
|
1410
|
+
|
|
1411
|
+
Parameters
|
|
1412
|
+
----------
|
|
1413
|
+
direction : {'R', 'L'}
|
|
1414
|
+
Sweep from left to right (->) or right to left (<-) respectively.
|
|
1415
|
+
canonize : bool, optional
|
|
1416
|
+
Canonize the state first, not needed if doing alternate sweeps.
|
|
1417
|
+
verbosity : {0, 1, 2}, optional
|
|
1418
|
+
Show a progress bar for the sweep.
|
|
1419
|
+
update_opts :
|
|
1420
|
+
Supplied to ``self._update_local_state``.
|
|
1421
|
+
"""
|
|
1422
|
+
old_k = self._k.copy().H
|
|
1423
|
+
TN_overlap = self._k | old_k
|
|
1424
|
+
|
|
1425
|
+
if canonize:
|
|
1426
|
+
{"R": self._k.right_canonize, "L": self._k.left_canonize}[
|
|
1427
|
+
direction
|
|
1428
|
+
](bra=self._b)
|
|
1429
|
+
|
|
1430
|
+
direction, begin, sweep = {
|
|
1431
|
+
"R": ("right", "left", range(0, self.L - self.bsz + 1)),
|
|
1432
|
+
"L": ("left", "right", reversed(range(0, self.L - self.bsz + 1))),
|
|
1433
|
+
}[direction]
|
|
1434
|
+
|
|
1435
|
+
eff_opts = {"begin": begin, "bsz": self.bsz, "cyclic": self.cyclic}
|
|
1436
|
+
self.ME_eff_ham = MovingEnvironment(self.TN_energy, **eff_opts)
|
|
1437
|
+
self.ME_eff_ham2 = MovingEnvironment(self.TN_energy2, **eff_opts)
|
|
1438
|
+
self.ME_eff_ovlp = MovingEnvironment(TN_overlap, **eff_opts)
|
|
1439
|
+
|
|
1440
|
+
if verbosity:
|
|
1441
|
+
sweep = progbar(sweep, ncols=80, total=self.L - self.bsz + 1)
|
|
1442
|
+
|
|
1443
|
+
local_ens, tot_ens = zip(
|
|
1444
|
+
*[
|
|
1445
|
+
self._update_local_state(i, direction=direction, **update_opts)
|
|
1446
|
+
for i in sweep
|
|
1447
|
+
]
|
|
1448
|
+
)
|
|
1449
|
+
|
|
1450
|
+
self.local_energies.append(local_ens)
|
|
1451
|
+
self.total_energies.append(tot_ens)
|
|
1452
|
+
|
|
1453
|
+
return tot_ens[-1]
|
|
1454
|
+
|
|
1455
|
+
def _compute_post_sweep(self):
|
|
1456
|
+
en_var = (self.TN_energy2 ^ ...) - self.energies[-1] ** 2
|
|
1457
|
+
self.variances.append(en_var)
|
|
1458
|
+
|
|
1459
|
+
def _print_post_sweep(self, converged, verbosity=0):
|
|
1460
|
+
if verbosity > 1:
|
|
1461
|
+
self._k.show()
|
|
1462
|
+
if verbosity > 0:
|
|
1463
|
+
msg = "Energy={}, Variance={} ... {}"
|
|
1464
|
+
msg = msg.format(
|
|
1465
|
+
self.energy,
|
|
1466
|
+
self.variance,
|
|
1467
|
+
"converged!" if converged else "not converged.",
|
|
1468
|
+
)
|
|
1469
|
+
print(msg, flush=True)
|
|
1470
|
+
|
|
1471
|
+
def _check_convergence(self, tol):
|
|
1472
|
+
return self.variance < tol
|