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.
Files changed (122) hide show
  1. trajectree/__init__.py +3 -0
  2. trajectree/fock_optics/devices.py +1 -1
  3. trajectree/fock_optics/light_sources.py +2 -2
  4. trajectree/fock_optics/measurement.py +3 -3
  5. trajectree/fock_optics/utils.py +6 -6
  6. trajectree/quimb/docs/_pygments/_pygments_dark.py +118 -0
  7. trajectree/quimb/docs/_pygments/_pygments_light.py +118 -0
  8. trajectree/quimb/docs/conf.py +158 -0
  9. trajectree/quimb/docs/examples/ex_mpi_expm_evo.py +62 -0
  10. trajectree/quimb/quimb/__init__.py +507 -0
  11. trajectree/quimb/quimb/calc.py +1491 -0
  12. trajectree/quimb/quimb/core.py +2279 -0
  13. trajectree/quimb/quimb/evo.py +712 -0
  14. trajectree/quimb/quimb/experimental/__init__.py +0 -0
  15. trajectree/quimb/quimb/experimental/autojittn.py +129 -0
  16. trajectree/quimb/quimb/experimental/belief_propagation/__init__.py +109 -0
  17. trajectree/quimb/quimb/experimental/belief_propagation/bp_common.py +397 -0
  18. trajectree/quimb/quimb/experimental/belief_propagation/d1bp.py +316 -0
  19. trajectree/quimb/quimb/experimental/belief_propagation/d2bp.py +653 -0
  20. trajectree/quimb/quimb/experimental/belief_propagation/hd1bp.py +571 -0
  21. trajectree/quimb/quimb/experimental/belief_propagation/hv1bp.py +775 -0
  22. trajectree/quimb/quimb/experimental/belief_propagation/l1bp.py +316 -0
  23. trajectree/quimb/quimb/experimental/belief_propagation/l2bp.py +537 -0
  24. trajectree/quimb/quimb/experimental/belief_propagation/regions.py +194 -0
  25. trajectree/quimb/quimb/experimental/cluster_update.py +286 -0
  26. trajectree/quimb/quimb/experimental/merabuilder.py +865 -0
  27. trajectree/quimb/quimb/experimental/operatorbuilder/__init__.py +15 -0
  28. trajectree/quimb/quimb/experimental/operatorbuilder/operatorbuilder.py +1631 -0
  29. trajectree/quimb/quimb/experimental/schematic.py +7 -0
  30. trajectree/quimb/quimb/experimental/tn_marginals.py +130 -0
  31. trajectree/quimb/quimb/experimental/tnvmc.py +1483 -0
  32. trajectree/quimb/quimb/gates.py +36 -0
  33. trajectree/quimb/quimb/gen/__init__.py +2 -0
  34. trajectree/quimb/quimb/gen/operators.py +1167 -0
  35. trajectree/quimb/quimb/gen/rand.py +713 -0
  36. trajectree/quimb/quimb/gen/states.py +479 -0
  37. trajectree/quimb/quimb/linalg/__init__.py +6 -0
  38. trajectree/quimb/quimb/linalg/approx_spectral.py +1109 -0
  39. trajectree/quimb/quimb/linalg/autoblock.py +258 -0
  40. trajectree/quimb/quimb/linalg/base_linalg.py +719 -0
  41. trajectree/quimb/quimb/linalg/mpi_launcher.py +397 -0
  42. trajectree/quimb/quimb/linalg/numpy_linalg.py +244 -0
  43. trajectree/quimb/quimb/linalg/rand_linalg.py +514 -0
  44. trajectree/quimb/quimb/linalg/scipy_linalg.py +293 -0
  45. trajectree/quimb/quimb/linalg/slepc_linalg.py +892 -0
  46. trajectree/quimb/quimb/schematic.py +1518 -0
  47. trajectree/quimb/quimb/tensor/__init__.py +401 -0
  48. trajectree/quimb/quimb/tensor/array_ops.py +610 -0
  49. trajectree/quimb/quimb/tensor/circuit.py +4824 -0
  50. trajectree/quimb/quimb/tensor/circuit_gen.py +411 -0
  51. trajectree/quimb/quimb/tensor/contraction.py +336 -0
  52. trajectree/quimb/quimb/tensor/decomp.py +1255 -0
  53. trajectree/quimb/quimb/tensor/drawing.py +1646 -0
  54. trajectree/quimb/quimb/tensor/fitting.py +385 -0
  55. trajectree/quimb/quimb/tensor/geometry.py +583 -0
  56. trajectree/quimb/quimb/tensor/interface.py +114 -0
  57. trajectree/quimb/quimb/tensor/networking.py +1058 -0
  58. trajectree/quimb/quimb/tensor/optimize.py +1818 -0
  59. trajectree/quimb/quimb/tensor/tensor_1d.py +4778 -0
  60. trajectree/quimb/quimb/tensor/tensor_1d_compress.py +1854 -0
  61. trajectree/quimb/quimb/tensor/tensor_1d_tebd.py +662 -0
  62. trajectree/quimb/quimb/tensor/tensor_2d.py +5954 -0
  63. trajectree/quimb/quimb/tensor/tensor_2d_compress.py +96 -0
  64. trajectree/quimb/quimb/tensor/tensor_2d_tebd.py +1230 -0
  65. trajectree/quimb/quimb/tensor/tensor_3d.py +2869 -0
  66. trajectree/quimb/quimb/tensor/tensor_3d_tebd.py +46 -0
  67. trajectree/quimb/quimb/tensor/tensor_approx_spectral.py +60 -0
  68. trajectree/quimb/quimb/tensor/tensor_arbgeom.py +3237 -0
  69. trajectree/quimb/quimb/tensor/tensor_arbgeom_compress.py +565 -0
  70. trajectree/quimb/quimb/tensor/tensor_arbgeom_tebd.py +1138 -0
  71. trajectree/quimb/quimb/tensor/tensor_builder.py +5411 -0
  72. trajectree/quimb/quimb/tensor/tensor_core.py +11179 -0
  73. trajectree/quimb/quimb/tensor/tensor_dmrg.py +1472 -0
  74. trajectree/quimb/quimb/tensor/tensor_mera.py +204 -0
  75. trajectree/quimb/quimb/utils.py +892 -0
  76. trajectree/quimb/tests/__init__.py +0 -0
  77. trajectree/quimb/tests/test_accel.py +501 -0
  78. trajectree/quimb/tests/test_calc.py +788 -0
  79. trajectree/quimb/tests/test_core.py +847 -0
  80. trajectree/quimb/tests/test_evo.py +565 -0
  81. trajectree/quimb/tests/test_gen/__init__.py +0 -0
  82. trajectree/quimb/tests/test_gen/test_operators.py +361 -0
  83. trajectree/quimb/tests/test_gen/test_rand.py +296 -0
  84. trajectree/quimb/tests/test_gen/test_states.py +261 -0
  85. trajectree/quimb/tests/test_linalg/__init__.py +0 -0
  86. trajectree/quimb/tests/test_linalg/test_approx_spectral.py +368 -0
  87. trajectree/quimb/tests/test_linalg/test_base_linalg.py +351 -0
  88. trajectree/quimb/tests/test_linalg/test_mpi_linalg.py +127 -0
  89. trajectree/quimb/tests/test_linalg/test_numpy_linalg.py +84 -0
  90. trajectree/quimb/tests/test_linalg/test_rand_linalg.py +134 -0
  91. trajectree/quimb/tests/test_linalg/test_slepc_linalg.py +283 -0
  92. trajectree/quimb/tests/test_tensor/__init__.py +0 -0
  93. trajectree/quimb/tests/test_tensor/test_belief_propagation/__init__.py +0 -0
  94. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d1bp.py +39 -0
  95. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d2bp.py +67 -0
  96. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hd1bp.py +64 -0
  97. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hv1bp.py +51 -0
  98. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l1bp.py +142 -0
  99. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l2bp.py +101 -0
  100. trajectree/quimb/tests/test_tensor/test_circuit.py +816 -0
  101. trajectree/quimb/tests/test_tensor/test_contract.py +67 -0
  102. trajectree/quimb/tests/test_tensor/test_decomp.py +40 -0
  103. trajectree/quimb/tests/test_tensor/test_mera.py +52 -0
  104. trajectree/quimb/tests/test_tensor/test_optimizers.py +488 -0
  105. trajectree/quimb/tests/test_tensor/test_tensor_1d.py +1171 -0
  106. trajectree/quimb/tests/test_tensor/test_tensor_2d.py +606 -0
  107. trajectree/quimb/tests/test_tensor/test_tensor_2d_tebd.py +144 -0
  108. trajectree/quimb/tests/test_tensor/test_tensor_3d.py +123 -0
  109. trajectree/quimb/tests/test_tensor/test_tensor_arbgeom.py +226 -0
  110. trajectree/quimb/tests/test_tensor/test_tensor_builder.py +441 -0
  111. trajectree/quimb/tests/test_tensor/test_tensor_core.py +2066 -0
  112. trajectree/quimb/tests/test_tensor/test_tensor_dmrg.py +388 -0
  113. trajectree/quimb/tests/test_tensor/test_tensor_spectral_approx.py +63 -0
  114. trajectree/quimb/tests/test_tensor/test_tensor_tebd.py +270 -0
  115. trajectree/quimb/tests/test_utils.py +85 -0
  116. trajectree/trajectory.py +2 -2
  117. {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/METADATA +2 -2
  118. trajectree-0.0.1.dist-info/RECORD +126 -0
  119. trajectree-0.0.0.dist-info/RECORD +0 -16
  120. {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/WHEEL +0 -0
  121. {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/licenses/LICENSE +0 -0
  122. {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