Trajectree 0.0.1__py3-none-any.whl → 0.0.3__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 (124) hide show
  1. trajectree/__init__.py +0 -3
  2. trajectree/fock_optics/devices.py +1 -1
  3. trajectree/fock_optics/light_sources.py +2 -2
  4. trajectree/fock_optics/measurement.py +9 -9
  5. trajectree/fock_optics/outputs.py +10 -6
  6. trajectree/fock_optics/utils.py +9 -6
  7. trajectree/sequence/swap.py +5 -4
  8. trajectree/trajectory.py +5 -4
  9. {trajectree-0.0.1.dist-info → trajectree-0.0.3.dist-info}/METADATA +2 -3
  10. trajectree-0.0.3.dist-info/RECORD +16 -0
  11. trajectree/quimb/docs/_pygments/_pygments_dark.py +0 -118
  12. trajectree/quimb/docs/_pygments/_pygments_light.py +0 -118
  13. trajectree/quimb/docs/conf.py +0 -158
  14. trajectree/quimb/docs/examples/ex_mpi_expm_evo.py +0 -62
  15. trajectree/quimb/quimb/__init__.py +0 -507
  16. trajectree/quimb/quimb/calc.py +0 -1491
  17. trajectree/quimb/quimb/core.py +0 -2279
  18. trajectree/quimb/quimb/evo.py +0 -712
  19. trajectree/quimb/quimb/experimental/__init__.py +0 -0
  20. trajectree/quimb/quimb/experimental/autojittn.py +0 -129
  21. trajectree/quimb/quimb/experimental/belief_propagation/__init__.py +0 -109
  22. trajectree/quimb/quimb/experimental/belief_propagation/bp_common.py +0 -397
  23. trajectree/quimb/quimb/experimental/belief_propagation/d1bp.py +0 -316
  24. trajectree/quimb/quimb/experimental/belief_propagation/d2bp.py +0 -653
  25. trajectree/quimb/quimb/experimental/belief_propagation/hd1bp.py +0 -571
  26. trajectree/quimb/quimb/experimental/belief_propagation/hv1bp.py +0 -775
  27. trajectree/quimb/quimb/experimental/belief_propagation/l1bp.py +0 -316
  28. trajectree/quimb/quimb/experimental/belief_propagation/l2bp.py +0 -537
  29. trajectree/quimb/quimb/experimental/belief_propagation/regions.py +0 -194
  30. trajectree/quimb/quimb/experimental/cluster_update.py +0 -286
  31. trajectree/quimb/quimb/experimental/merabuilder.py +0 -865
  32. trajectree/quimb/quimb/experimental/operatorbuilder/__init__.py +0 -15
  33. trajectree/quimb/quimb/experimental/operatorbuilder/operatorbuilder.py +0 -1631
  34. trajectree/quimb/quimb/experimental/schematic.py +0 -7
  35. trajectree/quimb/quimb/experimental/tn_marginals.py +0 -130
  36. trajectree/quimb/quimb/experimental/tnvmc.py +0 -1483
  37. trajectree/quimb/quimb/gates.py +0 -36
  38. trajectree/quimb/quimb/gen/__init__.py +0 -2
  39. trajectree/quimb/quimb/gen/operators.py +0 -1167
  40. trajectree/quimb/quimb/gen/rand.py +0 -713
  41. trajectree/quimb/quimb/gen/states.py +0 -479
  42. trajectree/quimb/quimb/linalg/__init__.py +0 -6
  43. trajectree/quimb/quimb/linalg/approx_spectral.py +0 -1109
  44. trajectree/quimb/quimb/linalg/autoblock.py +0 -258
  45. trajectree/quimb/quimb/linalg/base_linalg.py +0 -719
  46. trajectree/quimb/quimb/linalg/mpi_launcher.py +0 -397
  47. trajectree/quimb/quimb/linalg/numpy_linalg.py +0 -244
  48. trajectree/quimb/quimb/linalg/rand_linalg.py +0 -514
  49. trajectree/quimb/quimb/linalg/scipy_linalg.py +0 -293
  50. trajectree/quimb/quimb/linalg/slepc_linalg.py +0 -892
  51. trajectree/quimb/quimb/schematic.py +0 -1518
  52. trajectree/quimb/quimb/tensor/__init__.py +0 -401
  53. trajectree/quimb/quimb/tensor/array_ops.py +0 -610
  54. trajectree/quimb/quimb/tensor/circuit.py +0 -4824
  55. trajectree/quimb/quimb/tensor/circuit_gen.py +0 -411
  56. trajectree/quimb/quimb/tensor/contraction.py +0 -336
  57. trajectree/quimb/quimb/tensor/decomp.py +0 -1255
  58. trajectree/quimb/quimb/tensor/drawing.py +0 -1646
  59. trajectree/quimb/quimb/tensor/fitting.py +0 -385
  60. trajectree/quimb/quimb/tensor/geometry.py +0 -583
  61. trajectree/quimb/quimb/tensor/interface.py +0 -114
  62. trajectree/quimb/quimb/tensor/networking.py +0 -1058
  63. trajectree/quimb/quimb/tensor/optimize.py +0 -1818
  64. trajectree/quimb/quimb/tensor/tensor_1d.py +0 -4778
  65. trajectree/quimb/quimb/tensor/tensor_1d_compress.py +0 -1854
  66. trajectree/quimb/quimb/tensor/tensor_1d_tebd.py +0 -662
  67. trajectree/quimb/quimb/tensor/tensor_2d.py +0 -5954
  68. trajectree/quimb/quimb/tensor/tensor_2d_compress.py +0 -96
  69. trajectree/quimb/quimb/tensor/tensor_2d_tebd.py +0 -1230
  70. trajectree/quimb/quimb/tensor/tensor_3d.py +0 -2869
  71. trajectree/quimb/quimb/tensor/tensor_3d_tebd.py +0 -46
  72. trajectree/quimb/quimb/tensor/tensor_approx_spectral.py +0 -60
  73. trajectree/quimb/quimb/tensor/tensor_arbgeom.py +0 -3237
  74. trajectree/quimb/quimb/tensor/tensor_arbgeom_compress.py +0 -565
  75. trajectree/quimb/quimb/tensor/tensor_arbgeom_tebd.py +0 -1138
  76. trajectree/quimb/quimb/tensor/tensor_builder.py +0 -5411
  77. trajectree/quimb/quimb/tensor/tensor_core.py +0 -11179
  78. trajectree/quimb/quimb/tensor/tensor_dmrg.py +0 -1472
  79. trajectree/quimb/quimb/tensor/tensor_mera.py +0 -204
  80. trajectree/quimb/quimb/utils.py +0 -892
  81. trajectree/quimb/tests/__init__.py +0 -0
  82. trajectree/quimb/tests/test_accel.py +0 -501
  83. trajectree/quimb/tests/test_calc.py +0 -788
  84. trajectree/quimb/tests/test_core.py +0 -847
  85. trajectree/quimb/tests/test_evo.py +0 -565
  86. trajectree/quimb/tests/test_gen/__init__.py +0 -0
  87. trajectree/quimb/tests/test_gen/test_operators.py +0 -361
  88. trajectree/quimb/tests/test_gen/test_rand.py +0 -296
  89. trajectree/quimb/tests/test_gen/test_states.py +0 -261
  90. trajectree/quimb/tests/test_linalg/__init__.py +0 -0
  91. trajectree/quimb/tests/test_linalg/test_approx_spectral.py +0 -368
  92. trajectree/quimb/tests/test_linalg/test_base_linalg.py +0 -351
  93. trajectree/quimb/tests/test_linalg/test_mpi_linalg.py +0 -127
  94. trajectree/quimb/tests/test_linalg/test_numpy_linalg.py +0 -84
  95. trajectree/quimb/tests/test_linalg/test_rand_linalg.py +0 -134
  96. trajectree/quimb/tests/test_linalg/test_slepc_linalg.py +0 -283
  97. trajectree/quimb/tests/test_tensor/__init__.py +0 -0
  98. trajectree/quimb/tests/test_tensor/test_belief_propagation/__init__.py +0 -0
  99. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d1bp.py +0 -39
  100. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d2bp.py +0 -67
  101. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hd1bp.py +0 -64
  102. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hv1bp.py +0 -51
  103. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l1bp.py +0 -142
  104. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l2bp.py +0 -101
  105. trajectree/quimb/tests/test_tensor/test_circuit.py +0 -816
  106. trajectree/quimb/tests/test_tensor/test_contract.py +0 -67
  107. trajectree/quimb/tests/test_tensor/test_decomp.py +0 -40
  108. trajectree/quimb/tests/test_tensor/test_mera.py +0 -52
  109. trajectree/quimb/tests/test_tensor/test_optimizers.py +0 -488
  110. trajectree/quimb/tests/test_tensor/test_tensor_1d.py +0 -1171
  111. trajectree/quimb/tests/test_tensor/test_tensor_2d.py +0 -606
  112. trajectree/quimb/tests/test_tensor/test_tensor_2d_tebd.py +0 -144
  113. trajectree/quimb/tests/test_tensor/test_tensor_3d.py +0 -123
  114. trajectree/quimb/tests/test_tensor/test_tensor_arbgeom.py +0 -226
  115. trajectree/quimb/tests/test_tensor/test_tensor_builder.py +0 -441
  116. trajectree/quimb/tests/test_tensor/test_tensor_core.py +0 -2066
  117. trajectree/quimb/tests/test_tensor/test_tensor_dmrg.py +0 -388
  118. trajectree/quimb/tests/test_tensor/test_tensor_spectral_approx.py +0 -63
  119. trajectree/quimb/tests/test_tensor/test_tensor_tebd.py +0 -270
  120. trajectree/quimb/tests/test_utils.py +0 -85
  121. trajectree-0.0.1.dist-info/RECORD +0 -126
  122. {trajectree-0.0.1.dist-info → trajectree-0.0.3.dist-info}/WHEEL +0 -0
  123. {trajectree-0.0.1.dist-info → trajectree-0.0.3.dist-info}/licenses/LICENSE +0 -0
  124. {trajectree-0.0.1.dist-info → trajectree-0.0.3.dist-info}/top_level.txt +0 -0
@@ -1,4778 +0,0 @@
1
- """Classes and algorithms related to 1D tensor networks."""
2
- """Modified by Ansh Singal to work with qudit simulations"""
3
-
4
- import functools
5
- import itertools
6
- import operator
7
- from math import log, log2
8
- from numbers import Integral
9
-
10
- import numpy as np
11
- import scipy.sparse.linalg as spla
12
- from autoray import conj, dag, do, get_dtype_name, reshape, size, transpose
13
-
14
- import quimb as qu
15
-
16
- from ..linalg.base_linalg import norm_trace_dense
17
- from ..utils import (
18
- deprecated,
19
- ensure_dict,
20
- pairwise,
21
- partition_all,
22
- print_multi_line,
23
- )
24
- from . import array_ops as ops
25
- from .tensor_arbgeom import (
26
- TensorNetworkGen,
27
- TensorNetworkGenOperator,
28
- TensorNetworkGenVector,
29
- tensor_network_ag_sum,
30
- tensor_network_align,
31
- tensor_network_apply_op_op,
32
- tensor_network_apply_op_vec,
33
- )
34
- from .tensor_core import (
35
- Tensor,
36
- bonds,
37
- new_bond,
38
- oset,
39
- rand_uuid,
40
- tags_to_oset,
41
- tensor_canonize_bond,
42
- tensor_compress_bond,
43
- )
44
-
45
- align_TN_1D = deprecated(
46
- tensor_network_align, "align_TN_1D", "tensor_network_align"
47
- )
48
-
49
-
50
- def expec_TN_1D(*tns, compress=None, eps=1e-15):
51
- """Compute the expectation of several 1D TNs, using transfer matrix
52
- compression if any are periodic.
53
-
54
- Parameters
55
- ----------
56
- tns : sequence of TensorNetwork1D
57
- The MPS and MPO to find expectation of. Should start and begin with
58
- an MPS e.g. ``(MPS, MPO, ..., MPS)``.
59
- compress : {None, False, True}, optional
60
- Whether to perform transfer matrix compression on cyclic systems. If
61
- set to ``None`` (the default), decide heuristically.
62
- eps : float, optional
63
- The accuracy of the transfer matrix compression.
64
-
65
- Returns
66
- -------
67
- x : float
68
- The expectation value.
69
- """
70
- expec_tn = functools.reduce(operator.or_, tensor_network_align(*tns))
71
-
72
- # if OBC or <= 0.0 specified use exact contraction
73
- cyclic = any(tn.cyclic for tn in tns)
74
- if not cyclic:
75
- compress = False
76
-
77
- n = expec_tn.L
78
- isflat = all(isinstance(tn, TensorNetwork1DFlat) for tn in tns)
79
-
80
- # work out whether to compress, could definitely be improved ...
81
- if compress is None and isflat:
82
- # compression only worth it for long, high bond dimension TNs.
83
- total_bd = qu.prod(tn.bond_size(0, 1) for tn in tns)
84
- compress = (n >= 100) and (total_bd >= 1000)
85
-
86
- if compress:
87
- expec_tn.replace_section_with_svd(1, n, eps=eps, inplace=True)
88
- return expec_tn ^ all
89
-
90
- return expec_tn ^ ...
91
-
92
-
93
- def maybe_factor_gate_into_tensor(G, phys_dim, nsites, where):
94
- # allow gate to be a matrix as long as it factorizes into tensor
95
- shape_matches_2d = (ops.ndim(G) == 2) and (G.shape[1] == phys_dim**nsites)
96
- shape_matches_nd = all(d == phys_dim for d in G.shape)
97
-
98
- if shape_matches_2d:
99
- G = ops.asarray(G)
100
- if nsites >= 2:
101
- G = reshape(G, [phys_dim] * 2 * nsites)
102
-
103
- elif not shape_matches_nd:
104
- raise ValueError(
105
- f"Gate with shape {G.shape} doesn't match sites {where}."
106
- )
107
-
108
- return G
109
-
110
-
111
- def gate_TN_1D(
112
- tn,
113
- G,
114
- where,
115
- contract=False,
116
- tags=None,
117
- propagate_tags="sites",
118
- info=None,
119
- inplace=False,
120
- cur_orthog=None,
121
- **compress_opts,
122
- ):
123
- r"""Act with the gate ``G`` on sites ``where``, maintaining the outer
124
- indices of the 1D tensor network::
125
-
126
-
127
- contract=False contract=True
128
- . . . . <- where
129
- o-o-o-o-o-o-o o-o-o-GGG-o-o-o
130
- | | | | | | | | | | / \ | | |
131
- GGG
132
- | |
133
-
134
-
135
- contract='split-gate' contract='swap-split-gate'
136
- . . . . <- where
137
- o-o-o-o-o-o-o o-o-o-o-o-o-o
138
- | | | | | | | | | | | | | |
139
- G~G G~G
140
- | | \ /
141
- X
142
- / \
143
-
144
- contract='swap+split'
145
- . . <- where
146
- o-o-o-G=G-o-o-o
147
- | | | | | | | |
148
-
149
- Note that the sites in ``where`` do not have to be contiguous. By default,
150
- site tags will be propagated to the gate tensors, identifying a
151
- 'light cone'.
152
-
153
- Parameters
154
- ----------
155
- tn : TensorNetwork1DVector
156
- The 1D vector-like tensor network, for example, and MPS.
157
- G : array
158
- A square array to act with on sites ``where``. It should have twice the
159
- number of dimensions as the number of sites. The second half of these
160
- will be contracted with the MPS, and the first half indexed with the
161
- correct ``site_ind_id``. Sites are read left to right from the shape.
162
- A two-dimensional array is permissible if each dimension factorizes
163
- correctly.
164
- where : int or sequence of int
165
- Where the gate should act.
166
- contract : {False, 'split-gate', 'swap-split-gate',
167
- 'auto-split-gate', True, 'swap+split'}, optional
168
- Whether to contract the gate into the 1D tensor network. If,
169
-
170
- - False: leave the gate uncontracted, the default
171
- - 'split-gate': like False, but split the gate if it is two-site.
172
- - 'swap-split-gate': like 'split-gate', but decompose the gate as
173
- if a swap had first been applied
174
- - 'auto-split-gate': automatically select between the above three
175
- options, based on the rank of the gate.
176
- - True: contract the gate into the tensor network, if the gate acts
177
- on more than one site, this will produce an ever larger tensor.
178
- - 'swap+split': Swap sites until they are adjacent, then contract
179
- the gate and split the resulting tensor, then swap the sites back
180
- to their original position. In this way an MPS structure can be
181
- explicitly maintained at the cost of rising bond-dimension.
182
-
183
- tags : str or sequence of str, optional
184
- Tag the new gate tensor with these tags.
185
- propagate_tags : {'sites', 'register', False, True}, optional
186
- Add any tags from the sites to the new gate tensor (only matters if
187
- ``contract=False`` else tags are merged anyway):
188
-
189
- - If ``'sites'``, then only propagate tags matching e.g. 'I{}' and
190
- ignore all others. I.e. just propagate the lightcone.
191
- - If ``'register'``, then only propagate tags matching the sites of
192
- where this gate was actually applied. I.e. ignore the lightcone,
193
- just keep track of which 'registers' the gate was applied to.
194
- - If ``False``, propagate nothing.
195
- - If ``True``, propagate all tags.
196
-
197
- inplace, bool, optional
198
- Perform the gate in place.
199
- compress_opts
200
- Supplied to :meth:`~quimb.tensor.tensor_core.Tensor.split`
201
- if ``contract='swap+split'`` or
202
- :meth:`~quimb.tensor.tensor_1d.MatrixProductState.gate_with_auto_swap`
203
- if ``contract='swap+split'``.
204
-
205
- Returns
206
- -------
207
- TensorNetwork1DVector
208
-
209
- See Also
210
- --------
211
- MatrixProductState.gate_split
212
-
213
- Examples
214
- --------
215
- >>> p = MPS_rand_state(3, 7)
216
- >>> p.gate_(spin_operator('X'), where=1, tags=['GX'])
217
- >>> p
218
- <MatrixProductState(tensors=4, L=3, max_bond=7)>
219
-
220
- >>> p.outer_inds()
221
- ('k0', 'k1', 'k2')
222
- """
223
- if isinstance(where, Integral):
224
- where = (where,)
225
- ng = len(where) # number of sites the gate acts on
226
-
227
- if contract == "auto-mps":
228
- # automatically choose a contract mode based on maintaining MPS form
229
- if ng == 1:
230
- contract = True
231
- elif ng == 2:
232
- contract = "swap+split"
233
- else:
234
- contract = "nonlocal"
235
-
236
- # check special MPS methods
237
- if contract == "swap+split":
238
- if ng == 1:
239
- # no swapping or splitting needed
240
- contract = True
241
- else:
242
- return tn.gate_with_auto_swap(
243
- G,
244
- where,
245
- cur_orthog=cur_orthog,
246
- info=info,
247
- inplace=inplace,
248
- **compress_opts,
249
- )
250
-
251
- elif contract == "nonlocal":
252
- if ng == 1:
253
- # no MPO needed
254
- contract = True
255
- else:
256
- return tn.gate_nonlocal(
257
- G,
258
- where,
259
- cur_orthog=cur_orthog,
260
- info=info,
261
- inplace=inplace,
262
- **compress_opts,
263
- )
264
-
265
- # can use generic gate method
266
- return TensorNetworkGenVector.gate(
267
- tn,
268
- G,
269
- where,
270
- contract=contract,
271
- tags=tags,
272
- propagate_tags=propagate_tags,
273
- info=info,
274
- inplace=inplace,
275
- **compress_opts,
276
- )
277
-
278
-
279
- def superop_TN_1D(
280
- tn_super,
281
- tn_op,
282
- upper_ind_id="k{}",
283
- lower_ind_id="b{}",
284
- so_outer_upper_ind_id=None,
285
- so_inner_upper_ind_id=None,
286
- so_inner_lower_ind_id=None,
287
- so_outer_lower_ind_id=None,
288
- ):
289
- r"""Take a tensor network superoperator and act with it on a
290
- tensor network operator, maintaining the original upper and lower
291
- indices of the operator::
292
-
293
-
294
- outer_upper_ind_id upper_ind_id
295
- | | | ... | | | | ... |
296
- +----------+ +----------+
297
- | tn_super +---+ | tn_super +---+
298
- +----------+ | upper_ind_id +----------+ |
299
- | | | ... | | | | | ... | | | | ... | |
300
- inner_upper_ind_id| +-----------+ +-----------+ |
301
- | + | tn_op | = | tn_op | |
302
- inner_lower_ind_id| +-----------+ +-----------+ |
303
- | | | ... | | | | | ... | | | | ... | |
304
- +----------+ | lower_ind_id +----------+ |
305
- | tn_super +---+ | tn_super +---+
306
- +----------+ +----------+
307
- | | | ... | <-- | | | ... |
308
- outer_lower_ind_id lower_ind_id
309
-
310
-
311
- Parameters
312
- ----------
313
- tn_super : TensorNetwork
314
- The superoperator in the form of a 1D-like tensor network.
315
- tn_op : TensorNetwork
316
- The operator to be acted on in the form of a 1D-like tensor network.
317
- upper_ind_id : str, optional
318
- Current id of the upper operator indices, e.g. usually ``'k{}'``.
319
- lower_ind_id : str, optional
320
- Current id of the lower operator indices, e.g. usually ``'b{}'``.
321
- so_outer_upper_ind_id : str, optional
322
- Current id of the superoperator's upper outer indices, these will be
323
- reindexed to form the new effective operators upper indices.
324
- so_inner_upper_ind_id : str, optional
325
- Current id of the superoperator's upper inner indices, these will be
326
- joined with those described by ``upper_ind_id``.
327
- so_inner_lower_ind_id : str, optional
328
- Current id of the superoperator's lower inner indices, these will be
329
- joined with those described by ``lower_ind_id``.
330
- so_outer_lower_ind_id : str, optional
331
- Current id of the superoperator's lower outer indices, these will be
332
- reindexed to form the new effective operators lower indices.
333
-
334
- Returns
335
- -------
336
- KAK : TensorNetwork
337
- The tensornetwork of the superoperator acting on the operator.
338
- """
339
- n = tn_op.L
340
-
341
- if so_outer_upper_ind_id is None:
342
- so_outer_upper_ind_id = getattr(tn_super, "outer_upper_ind_id", "kn{}")
343
- if so_inner_upper_ind_id is None:
344
- so_inner_upper_ind_id = getattr(tn_super, "inner_upper_ind_id", "k{}")
345
- if so_inner_lower_ind_id is None:
346
- so_inner_lower_ind_id = getattr(tn_super, "inner_lower_ind_id", "b{}")
347
- if so_outer_lower_ind_id is None:
348
- so_outer_lower_ind_id = getattr(tn_super, "outer_lower_ind_id", "bn{}")
349
-
350
- reindex_map = {}
351
- for i in range(n):
352
- upper_bnd = rand_uuid()
353
- lower_bnd = rand_uuid()
354
- reindex_map[upper_ind_id.format(i)] = upper_bnd
355
- reindex_map[lower_ind_id.format(i)] = lower_bnd
356
- reindex_map[so_inner_upper_ind_id.format(i)] = upper_bnd
357
- reindex_map[so_inner_lower_ind_id.format(i)] = lower_bnd
358
- reindex_map[so_outer_upper_ind_id.format(i)] = upper_ind_id.format(i)
359
- reindex_map[so_outer_lower_ind_id.format(i)] = lower_ind_id.format(i)
360
-
361
- return tn_super.reindex(reindex_map) & tn_op.reindex(reindex_map)
362
-
363
-
364
- def parse_cur_orthog(cur_orthog="calc", info=None):
365
- if info is None:
366
- info = {}
367
-
368
- if isinstance(cur_orthog, Integral):
369
- info.setdefault("cur_orthog", (cur_orthog, cur_orthog))
370
- else:
371
- info.setdefault("cur_orthog", cur_orthog)
372
-
373
- return info
374
-
375
-
376
- def convert_cur_orthog(fn):
377
- @functools.wraps(fn)
378
- def wrapped(self, *args, cur_orthog=None, info=None, **kwargs):
379
- info = parse_cur_orthog(cur_orthog, info)
380
- return fn(self, *args, info=info, **kwargs)
381
-
382
- return wrapped
383
-
384
-
385
- class TensorNetwork1D(TensorNetworkGen):
386
- """Base class for tensor networks with a one-dimensional structure."""
387
-
388
- _NDIMS = 1
389
- _EXTRA_PROPS = ("_site_tag_id", "_L")
390
- _CONTRACT_STRUCTURED = True
391
-
392
- def _compatible_1d(self, other):
393
- """Check whether ``self`` and ``other`` are compatible 2D tensor
394
- networks such that they can remain a 2D tensor network when combined.
395
- """
396
- return isinstance(other, TensorNetwork1D) and all(
397
- getattr(self, e) == getattr(other, e)
398
- for e in TensorNetwork1D._EXTRA_PROPS
399
- )
400
-
401
- def combine(self, other, *, virtual=False, check_collisions=True):
402
- """Combine this tensor network with another, returning a new tensor
403
- network. If the two are compatible, cast the resulting tensor network
404
- to a :class:`TensorNetwork1D` instance.
405
-
406
- Parameters
407
- ----------
408
- other : TensorNetwork1D or TensorNetwork
409
- The other tensor network to combine with.
410
- virtual : bool, optional
411
- Whether the new tensor network should copy all the incoming tensors
412
- (``False``, the default), or view them as virtual (``True``).
413
- check_collisions : bool, optional
414
- Whether to check for index collisions between the two tensor
415
- networks before combining them. If ``True`` (the default), any
416
- inner indices that clash will be mangled.
417
-
418
- Returns
419
- -------
420
- TensorNetwork1D or TensorNetwork
421
- """
422
- new = super().combine(
423
- other, virtual=virtual, check_collisions=check_collisions
424
- )
425
- if self._compatible_1d(other):
426
- new.view_as_(TensorNetwork1D, like=self)
427
- return new
428
-
429
- @property
430
- def L(self):
431
- """The number of sites, i.e. length."""
432
- return self._L
433
-
434
- @property
435
- def nsites(self):
436
- """The number of sites."""
437
- return self._L
438
-
439
- def gen_site_coos(self):
440
- """Generate the coordinates of all possible sites."""
441
- return range(self._L)
442
-
443
- def site_tag(self, i):
444
- """The name of the tag specifiying the tensor at site ``i``."""
445
- if not isinstance(i, str):
446
- i = i % self.L
447
- return self._site_tag_id.format(i)
448
-
449
- def slice2sites(self, tag_slice):
450
- """Take a slice object, and work out its implied start, stop and step,
451
- taking into account cyclic boundary conditions.
452
-
453
- Examples
454
- --------
455
- Normal slicing:
456
-
457
- >>> p = MPS_rand_state(10, bond_dim=7)
458
- >>> p.slice2sites(slice(5))
459
- (0, 1, 2, 3, 4)
460
-
461
- >>> p.slice2sites(slice(4, 8))
462
- (4, 5, 6, 7)
463
-
464
- Slicing from end backwards:
465
-
466
- >>> p.slice2sites(slice(..., -3, -1))
467
- (9, 8)
468
-
469
- Slicing round the end:
470
-
471
- >>> p.slice2sites(slice(7, 12))
472
- (7, 8, 9, 0, 1)
473
-
474
- >>> p.slice2sites(slice(-3, 2))
475
- (7, 8, 9, 0, 1)
476
-
477
- If the start point is > end point (*before* modulo n), then step needs
478
- to be negative to return anything.
479
- """
480
- if tag_slice.start is None:
481
- start = 0
482
- elif tag_slice.start is ...:
483
- if tag_slice.step == -1:
484
- start = self.L - 1
485
- else:
486
- start = -1
487
- else:
488
- start = tag_slice.start
489
-
490
- if tag_slice.stop in (..., None):
491
- stop = self.L
492
- else:
493
- stop = tag_slice.stop
494
-
495
- step = 1 if tag_slice.step is None else tag_slice.step
496
-
497
- return tuple(s % self.L for s in range(start, stop, step))
498
-
499
- def maybe_convert_coo(self, x):
500
- """Check if ``x`` is an integer and convert to the
501
- corresponding site tag if so.
502
- """
503
- if isinstance(x, Integral):
504
- return self.site_tag(x)
505
-
506
- if isinstance(x, slice):
507
- return tuple(map(self.site_tag, self.slice2sites(x)))
508
-
509
- return x
510
-
511
- def contract_structured(
512
- self, tag_slice, structure_bsz=5, inplace=False, **opts
513
- ):
514
- """Perform a structured contraction, translating ``tag_slice`` from a
515
- ``slice`` or `...` to a cumulative sequence of tags.
516
-
517
- Parameters
518
- ----------
519
- tag_slice : slice or ...
520
- The range of sites, or `...` for all.
521
- inplace : bool, optional
522
- Whether to perform the contraction inplace.
523
-
524
- Returns
525
- -------
526
- TensorNetwork, Tensor or scalar
527
- The result of the contraction, still a ``TensorNetwork`` if the
528
- contraction was only partial.
529
-
530
- See Also
531
- --------
532
- contract, contract_tags, contract_cumulative
533
- """
534
- # check for all sites
535
- if tag_slice is ...:
536
- # else slice over all sites
537
- tag_slice = slice(0, self.L)
538
-
539
- # filter sites by the slice, but also which sites are present at all
540
- tags_seq = filter(
541
- self.tag_map.__contains__,
542
- map(self.site_tag, self.slice2sites(tag_slice)),
543
- )
544
-
545
- # partition sites into `structure_bsz` groups
546
- if structure_bsz > 1:
547
- tags_seq = partition_all(structure_bsz, tags_seq)
548
-
549
- # contract each block of sites cumulatively
550
- return self.contract_cumulative(tags_seq, inplace=inplace, **opts)
551
-
552
- def compute_left_environments(self, **contract_opts):
553
- """Compute the left environments of this 1D tensor network.
554
-
555
- Parameters
556
- ----------
557
- contract_opts
558
- Supplied to
559
- :meth:`~quimb.tensor.tensor_core.TensorNetwork.contract`.
560
-
561
- Returns
562
- -------
563
- dict[int, Tensor]
564
- Environments indexed by the site they are to the left of, so keys
565
- run from (1, ... L - 1).
566
- """
567
- left_envs = {1: self.select(0).contract(all, **contract_opts)}
568
- for i in range(2, self.L):
569
- tll = left_envs[i - 1]
570
- tll.drop_tags()
571
- tnl = self.select(i - 1) | tll
572
- left_envs[i] = tnl.contract(all, **contract_opts)
573
-
574
- return left_envs
575
-
576
- def compute_right_environments(self, **contract_opts):
577
- """Compute the right environments of this 1D tensor network.
578
-
579
- Parameters
580
- ----------
581
- contract_opts
582
- Supplied to
583
- :meth:`~quimb.tensor.tensor_core.TensorNetwork.contract`.
584
-
585
- Returns
586
- -------
587
- dict[int, Tensor]
588
- Environments indexed by the site they are to the right of, so keys
589
- run from (0, ... L - 2).
590
- """
591
- right_envs = {
592
- self.L - 2: self.select(-1).contract(all, **contract_opts)
593
- }
594
- for i in range(self.L - 3, -1, -1):
595
- trr = right_envs[i + 1]
596
- trr.drop_tags()
597
- tnr = self.select(i + 1) | trr
598
- right_envs[i] = tnr.contract()
599
-
600
- return right_envs
601
-
602
- def _repr_info(self):
603
- info = super()._repr_info()
604
- info["L"] = self.L
605
- info["max_bond"] = self.max_bond()
606
- return info
607
-
608
-
609
- class TensorNetwork1DVector(TensorNetwork1D, TensorNetworkGenVector):
610
- """1D Tensor network which overall is like a vector with a single type of
611
- site ind.
612
- """
613
-
614
- _EXTRA_PROPS = (
615
- "_site_tag_id",
616
- "_site_ind_id",
617
- "_L",
618
- )
619
-
620
- def reindex_sites(self, new_id, where=None, inplace=False):
621
- """Update the physical site index labels to a new string specifier.
622
- Note that this doesn't change the stored id string with the TN.
623
-
624
- Parameters
625
- ----------
626
- new_id : str
627
- A string with a format placeholder to accept an int, e.g. "ket{}".
628
- where : None or slice
629
- Which sites to update the index labels on. If ``None`` (default)
630
- all sites.
631
- inplace : bool
632
- Whether to reindex in place.
633
- """
634
- if where is None:
635
- where = self.gen_sites_present()
636
- elif isinstance(where, slice):
637
- where = self.slice2sites(where)
638
- else:
639
- where = where
640
-
641
- return super().reindex_sites(new_id, where, inplace=inplace)
642
-
643
- reindex_sites_ = functools.partialmethod(reindex_sites, inplace=True)
644
-
645
- def site_ind(self, i):
646
- """Get the physical index name of site ``i``."""
647
- if not isinstance(i, str):
648
- i = i % self.L
649
- return self.site_ind_id.format(i)
650
-
651
- @functools.wraps(gate_TN_1D)
652
- def gate(self, *args, inplace=False, **kwargs):
653
- return gate_TN_1D(self, *args, inplace=inplace, **kwargs)
654
-
655
- gate_ = functools.partialmethod(gate, inplace=True)
656
-
657
- @functools.wraps(expec_TN_1D)
658
- def expec(self, *args, **kwargs):
659
- return expec_TN_1D(self, *args, **kwargs)
660
-
661
- def correlation(self, A, i, j, B=None, **expec_opts):
662
- """Correlation of operator ``A`` between ``i`` and ``j``.
663
-
664
- Parameters
665
- ----------
666
- A : array
667
- The operator to act with, can be multi site.
668
- i : int or sequence of int
669
- The first site(s).
670
- j : int or sequence of int
671
- The second site(s).
672
- expec_opts
673
- Supplied to :func:`~quimb.tensor.tensor_1d.expec_TN_1D`.
674
-
675
- Returns
676
- -------
677
- C : float
678
- The correlation ``<A(i)> + <A(j)> - <A(ij)>``.
679
-
680
- Examples
681
- --------
682
- >>> ghz = (MPS_computational_state('0000') +
683
- ... MPS_computational_state('1111')) / 2**0.5
684
- >>> ghz.correlation(pauli('Z'), 0, 1)
685
- 1.0
686
- >>> ghz.correlation(pauli('Z'), 0, 1, B=pauli('X'))
687
- 0.0
688
- """
689
- if B is None:
690
- B = A
691
-
692
- bra = self.H
693
-
694
- pA = self.gate(A, i, contract=True)
695
- cA = expec_TN_1D(bra, pA, **expec_opts)
696
-
697
- pB = self.gate(B, j, contract=True)
698
- cB = expec_TN_1D(bra, pB, **expec_opts)
699
-
700
- pAB = pA.gate_(B, j, contract=True)
701
- cAB = expec_TN_1D(bra, pAB, **expec_opts)
702
-
703
- return cAB - cA * cB
704
-
705
-
706
- class TensorNetwork1DOperator(TensorNetwork1D, TensorNetworkGenOperator):
707
- _EXTRA_PROPS = (
708
- "_site_tag_id",
709
- "_upper_ind_id",
710
- "_lower_ind_id",
711
- "_L",
712
- )
713
-
714
- def reindex_lower_sites(self, new_id, where=None, inplace=False):
715
- """Update the lower site index labels to a new string specifier.
716
-
717
- Parameters
718
- ----------
719
- new_id : str
720
- A string with a format placeholder to accept an int, e.g.
721
- ``"ket{}"``.
722
- where : None or slice
723
- Which sites to update the index labels on. If ``None`` (default)
724
- all sites.
725
- inplace : bool
726
- Whether to reindex in place.
727
- """
728
- if where is None:
729
- start = 0
730
- stop = self.L
731
- else:
732
- start = 0 if where.start is None else where.start
733
- stop = self.L if where.stop is ... else where.stop
734
-
735
- return self.reindex(
736
- {self.lower_ind(i): new_id.format(i) for i in range(start, stop)},
737
- inplace=inplace,
738
- )
739
-
740
- reindex_lower_sites_ = functools.partialmethod(
741
- reindex_lower_sites, inplace=True
742
- )
743
-
744
- def reindex_upper_sites(self, new_id, where=None, inplace=False):
745
- """Update the upper site index labels to a new string specifier.
746
-
747
- Parameters
748
- ----------
749
- new_id : str
750
- A string with a format placeholder to accept an int, e.g. "ket{}".
751
- where : None or slice
752
- Which sites to update the index labels on. If ``None`` (default)
753
- all sites.
754
- inplace : bool
755
- Whether to reindex in place.
756
- """
757
- if where is None:
758
- start = 0
759
- stop = self.L
760
- else:
761
- start = 0 if where.start is None else where.start
762
- stop = self.L if where.stop is ... else where.stop
763
-
764
- return self.reindex(
765
- {self.upper_ind(i): new_id.format(i) for i in range(start, stop)},
766
- inplace=inplace,
767
- )
768
-
769
- reindex_upper_sites_ = functools.partialmethod(
770
- reindex_upper_sites, inplace=True
771
- )
772
-
773
-
774
- def set_default_compress_mode(opts, cyclic=False):
775
- opts.setdefault("cutoff_mode", "rel" if cyclic else "rsum2")
776
-
777
-
778
- class TensorNetwork1DFlat(TensorNetwork1D):
779
- """1D Tensor network which has a flat structure."""
780
-
781
- _EXTRA_PROPS = ("_site_tag_id", "_L")
782
-
783
- def left_canonize_site(self, i, bra=None):
784
- r"""Left canonize this TN's ith site, inplace::
785
-
786
- i i
787
- -o-o- ->-s-
788
- ... | | ... ==> ... | | ...
789
-
790
- Parameters
791
- ----------
792
- i : int
793
- Which site to canonize. The site at i + 1 also absorbs the
794
- non-isometric part of the decomposition of site i.
795
- bra : None or matching TensorNetwork to self, optional
796
- If set, also update this TN's data with the conjugate canonization.
797
- """
798
- tl, tr = self[i], self[i + 1]
799
- tensor_canonize_bond(tl, tr)
800
- if bra is not None:
801
- # TODO: handle left inds
802
- bra[i].modify(data=conj(tl.data))
803
- bra[i + 1].modify(data=conj(tr.data))
804
-
805
- def right_canonize_site(self, i, bra=None):
806
- r"""Right canonize this TN's ith site, inplace::
807
-
808
- i i
809
- -o-o- -s-<-
810
- ... | | ... ==> ... | | ...
811
-
812
- Parameters
813
- ----------
814
- i : int
815
- Which site to canonize. The site at i - 1 also absorbs the
816
- non-isometric part of the decomposition of site i.
817
- bra : None or matching TensorNetwork to self, optional
818
- If set, also update this TN's data with the conjugate canonization.
819
- """
820
- tl, tr = self[i - 1], self[i]
821
- tensor_canonize_bond(tr, tl)
822
- if bra is not None:
823
- # TODO: handle left inds
824
- bra[i].modify(data=conj(tr.data))
825
- bra[i - 1].modify(data=conj(tl.data))
826
-
827
- def left_canonicalize(
828
- self,
829
- stop=None,
830
- start=None,
831
- normalize=False,
832
- bra=None,
833
- inplace=False,
834
- ):
835
- r"""Left canonicalize all or a portion of this TN (i.e. sweep the
836
- orthogonality center to the right). If this is a MPS,
837
- this implies that::
838
-
839
- i i
840
- >->->->->->->-o-o- +-o-o-
841
- | | | | | | | | | ... => | | | ...
842
- >->->->->->->-o-o- +-o-o-
843
-
844
- Parameters
845
- ----------
846
- start : int, optional
847
- If given, the site to start left canonicalizing at.
848
- stop : int, optional
849
- If given, the site to stop left canonicalizing at.
850
- normalize : bool, optional
851
- Whether to normalize the state, only works for OBC.
852
- bra : MatrixProductState, optional
853
- If supplied, simultaneously left canonicalize this MPS too,
854
- assuming it to be the conjugate state.
855
- inplace : bool, optional
856
- Whether to perform the operation inplace. If ``bra`` is supplied
857
- then it is always modifed inplace.
858
-
859
- Returns
860
- -------
861
- TensorNetwork1DFlat
862
- """
863
- mps = self if inplace else self.copy()
864
-
865
- if start is None:
866
- start = -1 if mps.cyclic else 0
867
- if stop is None:
868
- stop = mps.L - 1
869
-
870
- for i in range(start, stop):
871
- mps.left_canonize_site(i, bra=bra)
872
-
873
- if normalize:
874
- factor = mps[-1].norm()
875
- mps[-1] /= factor
876
- if bra is not None:
877
- bra[-1] /= factor
878
-
879
- return mps
880
-
881
- left_canonicalize_ = functools.partialmethod(
882
- left_canonicalize, inplace=True
883
- )
884
- left_canonize = left_canonicalize_
885
-
886
- def right_canonicalize(
887
- self, stop=None, start=None, normalize=False, bra=None, inplace=False
888
- ):
889
- r"""Right canonicalize all or a portion of this TN (i.e. sweep the
890
- orthogonality center to the left). If this is a MPS,
891
-
892
- i i
893
- -o-o-<-<-<-<-<-<-< -o-o-+
894
- ... | | | | | | | | | -> ... | | |
895
- -o-o-<-<-<-<-<-<-< -o-o-+
896
-
897
-
898
- Parameters
899
- ----------
900
- start : int, optional
901
- If given, the site to start right canonizing at.
902
- stop : int, optional
903
- If given, the site to stop right canonizing at.
904
- normalize : bool, optional
905
- Whether to normalize the state.
906
- bra : MatrixProductState, optional
907
- If supplied, simultaneously right canonicalize this MPS too,
908
- assuming it to be the conjugate state.
909
- inplace : bool, optional
910
- Whether to perform the operation inplace. If ``bra`` is supplied
911
- then it is always modifed inplace.
912
-
913
- Returns
914
- -------
915
- TensorNetwork1DFlat
916
- """
917
- mps = self if inplace else self.copy()
918
-
919
- if start is None:
920
- start = mps.L - (0 if mps.cyclic else 1)
921
- if stop is None:
922
- stop = 0
923
-
924
- for i in range(start, stop, -1):
925
- mps.right_canonize_site(i, bra=bra)
926
-
927
- if normalize:
928
- factor = mps[0].norm()
929
- mps[0] /= factor
930
- if bra is not None:
931
- bra[0] /= factor
932
-
933
- right_canonicalize_ = functools.partialmethod(
934
- right_canonicalize, inplace=True
935
- )
936
- right_canonize = right_canonicalize_
937
-
938
- def canonize_cyclic(self, i, bra=None, method="isvd", inv_tol=1e-10):
939
- """Bring this MatrixProductState into (possibly only approximate)
940
- canonical form at site(s) ``i``.
941
-
942
- Parameters
943
- ----------
944
- i : int or slice
945
- The site or range of sites to make canonical.
946
- bra : MatrixProductState, optional
947
- Simultaneously canonize this state as well, assuming it to be the
948
- co-vector.
949
- method : {'isvd', 'svds', ...}, optional
950
- How to perform the lateral compression.
951
- inv_tol : float, optional
952
- Tolerance with which to invert the gauge.
953
- """
954
- if isinstance(i, Integral):
955
- start, stop = i, i + 1
956
- elif isinstance(i, slice):
957
- start, stop = i.start, i.stop
958
- else:
959
- start, stop = min(i), max(i) + 1
960
- if tuple(i) != tuple(range(start, stop)):
961
- raise ValueError(
962
- "Parameter ``i`` should be an integer or "
963
- f"contiguous block of integers, got {i}."
964
- )
965
-
966
- k = self.copy()
967
- b = k.H
968
- k.add_tag("_KET")
969
- b.add_tag("_BRA")
970
- kb = k & b
971
-
972
- # approximate the rest of the chain with a separable transfer operator
973
- kbc = kb.replace_section_with_svd(
974
- start,
975
- stop,
976
- eps=0.0,
977
- which="!any",
978
- method=method,
979
- max_bond=1,
980
- ltags="_LEFT",
981
- rtags="_RIGHT",
982
- )
983
-
984
- EL = kbc["_LEFT"].squeeze()
985
- # explicitly symmetrize to hermitian
986
- EL.modify(data=(EL.data + dag(EL.data)) / 2)
987
- # split into upper 'ket' part and lower 'bra' part, symmetric
988
- (EL_lix,) = EL.bonds(kbc[k.site_tag(start), "_BRA"])
989
- _, x = EL.split(EL_lix, method="eigh", cutoff=-1, get="arrays")
990
-
991
- ER = kbc["_RIGHT"].squeeze()
992
- # explicitly symmetrize to hermitian
993
- ER.modify(data=(ER.data + dag(ER.data)) / 2)
994
- # split into upper 'ket' part and lower 'bra' part, symmetric
995
- (ER_lix,) = ER.bonds(kbc[k.site_tag(stop - 1), "_BRA"])
996
- _, y = ER.split(ER_lix, method="eigh", cutoff=-1, get="arrays")
997
-
998
- self.insert_gauge(x, start - 1, start, tol=inv_tol)
999
- self.insert_gauge(y, stop, stop - 1, tol=inv_tol)
1000
-
1001
- if bra is not None:
1002
- for i in (start - 1, start, stop, stop - 1):
1003
- bra[i].modify(data=self[i].data.conj())
1004
-
1005
- def shift_orthogonality_center(self, current, new, bra=None):
1006
- """Move the orthogonality center of this MPS.
1007
-
1008
- Parameters
1009
- ----------
1010
- current : int
1011
- The current orthogonality center.
1012
- new : int
1013
- The target orthogonality center.
1014
- bra : MatrixProductState, optional
1015
- If supplied, simultaneously move the orthogonality center of this
1016
- MPS too, assuming it to be the conjugate state.
1017
- """
1018
- if new > current:
1019
- for i in range(current, new):
1020
- self.left_canonize_site(i, bra=bra)
1021
- else:
1022
- for i in range(current, new, -1):
1023
- self.right_canonize_site(i, bra=bra)
1024
-
1025
- def canonicalize(
1026
- self,
1027
- where,
1028
- cur_orthog="calc",
1029
- info=None,
1030
- bra=None,
1031
- inplace=False,
1032
- ):
1033
- r"""Gauge this MPS into mixed canonical form, implying::
1034
-
1035
- i i
1036
- >->->->->- ->-o-<- -<-<-<-<-< +-o-+
1037
- | | | | |...| | |...| | | | | -> | | |
1038
- >->->->->- ->-o-<- -<-<-<-<-< +-o-+
1039
-
1040
- You can also supply a min/max of sites to orthogonalize around, and a
1041
- current location of the orthogonality center for efficiency::
1042
-
1043
- current where
1044
- ....... .....
1045
- >->->-c-c-c-c-<-<-<-<-<-< >->->->->->-w-<-<-<-<-<-<
1046
- | | | | | | | | | | | | | -> | | | | | | | | | | | | |
1047
- >->->-c-c-c-c-<-<-<-<-<-< >->->->->->-w-<-<-<-<-<-<
1048
- cmin cmax i j
1049
-
1050
- This would only move ``cmin`` to ``i`` and ``cmax`` to ``j`` if
1051
- necessary.
1052
-
1053
- Parameters
1054
- ----------
1055
- where : int or sequence of int
1056
- Which site(s) to orthogonalize around. If a sequence of int then
1057
- make sure that section from min(where) to max(where) is orthog.
1058
- info : dict, optional
1059
- If supplied, will be used to infer and store various extra
1060
- information. Currently, the key "cur_orthog" is used to store the
1061
- current orthogonality center. Its input value can be ``"calc"``, a
1062
- single site, or a pair of sites representing the min/max range,
1063
- inclusive. It will be updated to the actual range after.
1064
- bra : MatrixProductState, optional
1065
- If supplied, simultaneously mixed canonicalize this MPS too,
1066
- assuming it to be the conjugate state.
1067
- inplace : bool, optional
1068
- Whether to perform the operation inplace. If ``bra`` is supplied
1069
- then it is always modifed inplace.
1070
-
1071
- Returns
1072
- -------
1073
- MatrixProductState
1074
- The mixed canonical form MPS.
1075
- """
1076
- mps = self if inplace else self.copy()
1077
-
1078
- if isinstance(where, Integral):
1079
- i = j = where
1080
- else:
1081
- i, j = min(where), max(where)
1082
-
1083
- info = parse_cur_orthog(cur_orthog, info)
1084
- cur_orthog = info["cur_orthog"]
1085
- if cur_orthog == "calc":
1086
- cur_orthog = mps.calc_current_orthog_center()
1087
-
1088
- if cur_orthog is not None:
1089
- if isinstance(cur_orthog, int):
1090
- cmin = cmax = cur_orthog
1091
- else:
1092
- cmin, cmax = min(cur_orthog), max(cur_orthog)
1093
-
1094
- if i > cmin:
1095
- mps.shift_orthogonality_center(cmin, i, bra=bra)
1096
- else:
1097
- i = min(j, cmin)
1098
-
1099
- if j < cmax:
1100
- mps.shift_orthogonality_center(cmax, j, bra=bra)
1101
- else:
1102
- j = max(i, cmax)
1103
-
1104
- else:
1105
- mps.left_canonicalize_(i, bra=bra)
1106
- mps.right_canonicalize_(j, bra=bra)
1107
-
1108
- info["cur_orthog"] = (i, j)
1109
-
1110
- return mps
1111
-
1112
- canonicalize_ = functools.partialmethod(canonicalize, inplace=True)
1113
- canonize = canonicalize_
1114
-
1115
- def left_compress_site(self, i, bra=None, **compress_opts):
1116
- """Left compress this 1D TN's ith site, such that the site is then
1117
- left unitary with its right bond (possibly) reduced in dimension.
1118
-
1119
- Parameters
1120
- ----------
1121
- i : int
1122
- Which site to compress.
1123
- bra : None or matching TensorNetwork to self, optional
1124
- If set, also update this TN's data with the conjugate compression.
1125
- compress_opts
1126
- Supplied to :meth:`Tensor.split`.
1127
- """
1128
- set_default_compress_mode(compress_opts, self.cyclic)
1129
- compress_opts.setdefault("absorb", "right")
1130
- compress_opts.setdefault("reduced", "left")
1131
-
1132
- tl, tr = self[i], self[i + 1]
1133
- tensor_compress_bond(tl, tr, **compress_opts)
1134
-
1135
- if bra is not None:
1136
- # TODO: handle left inds
1137
- bra[i].modify(data=conj(tl.data))
1138
- bra[i + 1].modify(data=conj(tr.data))
1139
-
1140
- def right_compress_site(self, i, bra=None, **compress_opts):
1141
- """Right compress this 1D TN's ith site, such that the site is then
1142
- right unitary with its left bond (possibly) reduced in dimension.
1143
-
1144
- Parameters
1145
- ----------
1146
- i : int
1147
- Which site to compress.
1148
- bra : None or matching TensorNetwork to self, optional
1149
- If set, update this TN's data with the conjugate compression.
1150
- compress_opts
1151
- Supplied to :meth:`Tensor.split`.
1152
- """
1153
- set_default_compress_mode(compress_opts, self.cyclic)
1154
- compress_opts.setdefault("absorb", "left")
1155
- compress_opts.setdefault("reduced", "right")
1156
-
1157
- tl, tr = self[i - 1], self[i]
1158
- tensor_compress_bond(tl, tr, **compress_opts)
1159
-
1160
- if bra is not None:
1161
- # TODO: handle left inds
1162
- bra[i].modify(data=conj(tr.data))
1163
- bra[i - 1].modify(data=conj(tl.data))
1164
-
1165
- def left_compress(self, start=None, stop=None, bra=None, **compress_opts):
1166
- """Compress this 1D TN, from left to right, such that it becomes
1167
- left-canonical (unless ``absorb != 'right'``).
1168
-
1169
- Parameters
1170
- ----------
1171
- start : int, optional
1172
- Site to begin compressing on.
1173
- stop : int, optional
1174
- Site to stop compressing at (won't itself be an isometry).
1175
- bra : None or TensorNetwork like this one, optional
1176
- If given, update this TN as well, assuming it to be the conjugate.
1177
- compress_opts
1178
- Supplied to :meth:`Tensor.split`.
1179
- """
1180
- if start is None:
1181
- start = -1 if self.cyclic else 0
1182
- if stop is None:
1183
- stop = self.L - 1
1184
-
1185
- for i in range(start, stop):
1186
- self.left_compress_site(i, bra=bra, **compress_opts)
1187
-
1188
- def right_compress(self, start=None, stop=None, bra=None, **compress_opts):
1189
- """Compress this 1D TN, from right to left, such that it becomes
1190
- right-canonical (unless ``absorb != 'left'``).
1191
-
1192
- Parameters
1193
- ----------
1194
- start : int, optional
1195
- Site to begin compressing on.
1196
- stop : int, optional
1197
- Site to stop compressing at (won't itself be an isometry).
1198
- bra : None or TensorNetwork like this one, optional
1199
- If given, update this TN as well, assuming it to be the conjugate.
1200
- compress_opts
1201
- Supplied to :meth:`Tensor.split`.
1202
- """
1203
- if start is None:
1204
- start = self.L - (0 if self.cyclic else 1)
1205
- if stop is None:
1206
- stop = 0
1207
-
1208
- for i in range(start, stop, -1):
1209
- self.right_compress_site(i, bra=bra, **compress_opts)
1210
-
1211
- def compress(self, form=None, **compress_opts):
1212
- """Compress this 1D Tensor Network, possibly into canonical form.
1213
-
1214
- Parameters
1215
- ----------
1216
- form : {None, 'flat', 'left', 'right'} or int
1217
- Output form of the TN. ``None`` left canonizes the state first for
1218
- stability reasons, then right_compresses (default). ``'flat'``
1219
- tries to distribute the singular values evenly -- state will not
1220
- be canonical. ``'left'`` and ``'right'`` put the state into left
1221
- and right canonical form respectively with a prior opposite sweep,
1222
- or an int will put the state into mixed canonical form at that
1223
- site.
1224
- compress_opts
1225
- Supplied to :meth:`Tensor.split`.
1226
- """
1227
- if form is None:
1228
- form = "right"
1229
-
1230
- if isinstance(form, Integral):
1231
- if form < self.L // 2:
1232
- self.left_canonize()
1233
- self.right_compress(**compress_opts)
1234
- self.left_canonize(stop=form)
1235
- else:
1236
- self.right_canonize()
1237
- self.left_compress(**compress_opts)
1238
- self.right_canonize(stop=form)
1239
-
1240
- elif form == "left":
1241
- self.right_canonize(bra=compress_opts.get("bra", None))
1242
- self.left_compress(**compress_opts)
1243
-
1244
- elif form == "right":
1245
- self.left_canonize(bra=compress_opts.get("bra", None))
1246
- self.right_compress(**compress_opts)
1247
-
1248
- elif form == "flat":
1249
- compress_opts["absorb"] = "both"
1250
- self.right_compress(stop=self.L // 2, **compress_opts)
1251
- self.left_compress(stop=self.L // 2, **compress_opts)
1252
-
1253
- else:
1254
- raise ValueError(
1255
- f"Form specifier {form} not understood, should be either "
1256
- "'left', 'right', 'flat' or an int specifiying a new orthog "
1257
- "center."
1258
- )
1259
-
1260
- @convert_cur_orthog
1261
- def compress_site(
1262
- self,
1263
- i,
1264
- canonize=True,
1265
- info=None,
1266
- bra=None,
1267
- **compress_opts,
1268
- ):
1269
- r"""Compress the bonds adjacent to site ``i``, by default first setting
1270
- the orthogonality center to that site::
1271
-
1272
- i i
1273
- -o-o-o-o-o- --> ->->~o~<-<-
1274
- | | | | | | | | | |
1275
-
1276
- Parameters
1277
- ----------
1278
- i : int
1279
- Which site to compress around
1280
- canonize : bool, optional
1281
- Whether to first set the orthogonality center to site ``i``.
1282
- info : dict, optional
1283
- If supplied, will be used to infer and store various extra
1284
- information. Currently, the key "cur_orthog" is used to store the
1285
- current orthogonality center. Its input value can be ``"calc"``, a
1286
- single site, or a pair of sites representing the min/max range,
1287
- inclusive. It will be updated to the actual range after.
1288
- bra : MatrixProductState, optional
1289
- The conjugate state to also apply the compression to.
1290
- compress_opts
1291
- Supplied to :func:`~quimb.tensor.tensor_core.tensor_split`.
1292
- """
1293
- if canonize:
1294
- self.canonicalize_(i, info=info, bra=bra)
1295
-
1296
- if self.cyclic or i > 0:
1297
- self.left_compress_site(i - 1, bra=bra, **compress_opts)
1298
-
1299
- if self.cyclic or i < self.L - 1:
1300
- self.right_compress_site(i + 1, bra=bra, **compress_opts)
1301
-
1302
- def bond(self, i, j):
1303
- """Get the name of the index defining the bond between sites i and j."""
1304
- (bond,) = self[i].bonds(self[j])
1305
- return bond
1306
-
1307
- def bond_size(self, i, j):
1308
- """Return the size of the bond between site ``i`` and ``j``."""
1309
- b_ix = self.bond(i, j)
1310
- return self[i].ind_size(b_ix)
1311
-
1312
- def bond_sizes(self):
1313
- bnd_szs = [self.bond_size(i, i + 1) for i in range(self.L - 1)]
1314
- if self.cyclic:
1315
- bnd_szs.append(self.bond_size(-1, 0))
1316
- return bnd_szs
1317
-
1318
- def amplitude(self, b):
1319
- """Compute the amplitude of configuration ``b``.
1320
-
1321
- Parameters
1322
- ----------
1323
- b : sequence of int
1324
- The configuration to compute the amplitude of.
1325
-
1326
- Returns
1327
- -------
1328
- c_b : scalar
1329
- """
1330
- if len(b) != self.nsites:
1331
- raise ValueError(
1332
- f"Bit-string {b} length does not "
1333
- f"match MPS length {self.nsites}."
1334
- )
1335
-
1336
- selector = {self.site_ind(i): int(xi) for i, xi in enumerate(b)}
1337
- mps_b = self.isel(selector)
1338
- return mps_b ^ ...
1339
-
1340
- @convert_cur_orthog
1341
- def singular_values(self, i, info=None, method="svd"):
1342
- r"""Find the singular values associated with the ith bond::
1343
-
1344
- ....L.... i
1345
- o-o-o-o-o-l-o-o-o-o-o-o-o-o-o-o-o
1346
- | | | | | | | | | | | | | | | |
1347
- i-1 ..........R..........
1348
-
1349
- Leaves the 1D TN in mixed canoncial form at bond ``i``.
1350
-
1351
- Parameters
1352
- ----------
1353
- i : int
1354
- Which bond, or equivalently, the number of sites in the
1355
- left partition.
1356
- info : dict, optional
1357
- If supplied, will be used to infer and store various extra
1358
- information. Currently, the key "cur_orthog" is used to store the
1359
- current orthogonality center. Its input value can be ``"calc"``, a
1360
- single site, or a pair of sites representing the min/max range,
1361
- inclusive. It will be updated to the actual range after.
1362
-
1363
- Returns
1364
- -------
1365
- svals : 1d-array
1366
- The singular values.
1367
- """
1368
- if not (0 < i < self.L):
1369
- raise ValueError(f"Need 0 < i < {self.L}, got i={i}.")
1370
-
1371
- self.canonicalize_(i, info=info)
1372
-
1373
- Tm1 = self[i]
1374
- left_inds = Tm1.bonds(self[i - 1])
1375
- return Tm1.singular_values(left_inds, method=method)
1376
-
1377
- def expand_bond_dimension(
1378
- self,
1379
- new_bond_dim,
1380
- rand_strength=0.0,
1381
- bra=None,
1382
- inplace=True,
1383
- ):
1384
- """Expand the bond dimensions of this 1D tensor network to at least
1385
- ``new_bond_dim``.
1386
-
1387
- Parameters
1388
- ----------
1389
- new_bond_dim : int
1390
- Minimum bond dimension to expand to.
1391
- inplace : bool, optional
1392
- Whether to perform the expansion in place.
1393
- bra : MatrixProductState, optional
1394
- Mirror the changes to ``bra`` inplace, treating it as the conjugate
1395
- state.
1396
- rand_strength : float, optional
1397
- If ``rand_strength > 0``, fill the new tensor entries with gaussian
1398
- noise of strength ``rand_strength``.
1399
-
1400
- Returns
1401
- -------
1402
- MatrixProductState
1403
- """
1404
- tn = super().expand_bond_dimension(
1405
- new_bond_dim=new_bond_dim,
1406
- rand_strength=rand_strength,
1407
- inplace=inplace,
1408
- )
1409
-
1410
- if bra is not None:
1411
- for coo in tn.gen_sites_present():
1412
- bra[coo].modify(data=tn[coo].data.conj())
1413
-
1414
- return tn
1415
-
1416
- def count_canonized(self):
1417
- if self.cyclic:
1418
- return 0, 0
1419
-
1420
- ov = self.H & self
1421
- num_can_l = 0
1422
- num_can_r = 0
1423
-
1424
- def isidentity(x):
1425
- d = x.shape[0]
1426
- if get_dtype_name(x) in ("float32", "complex64"):
1427
- rtol, atol = 1e-5, 1e-6
1428
- else:
1429
- rtol, atol = 1e-9, 1e-11
1430
-
1431
- idtty = do("eye", d, like=x)
1432
- return do("allclose", x, idtty, rtol=rtol, atol=atol)
1433
-
1434
- for i in range(self.L - 1):
1435
- ov ^= slice(max(0, i - 1), i + 1)
1436
- x = ov[i].data
1437
- if isidentity(x):
1438
- num_can_l += 1
1439
- else:
1440
- break
1441
-
1442
- for j in reversed(range(num_can_l + 1, self.L)):
1443
- ov ^= slice(j, min(self.L, j + 2))
1444
- x = ov[j].data
1445
- if isidentity(x):
1446
- num_can_r += 1
1447
- else:
1448
- break
1449
-
1450
- return num_can_l, num_can_r
1451
-
1452
- def calc_current_orthog_center(self):
1453
- """Calculate the site(s) of the current orthogonality center.
1454
-
1455
- Returns
1456
- -------
1457
- (int, int)
1458
- The min/max of sites around which the TN is currently orthogonal.
1459
- """
1460
- lo, ro = self.count_canonized()
1461
- return lo, self.L - ro - 1
1462
-
1463
- def as_cyclic(self, inplace=False):
1464
- """Convert this flat, 1D, TN into cyclic form by adding a dummy bond
1465
- between the first and last sites.
1466
- """
1467
- tn = self if inplace else self.copy()
1468
-
1469
- # nothing to do
1470
- if tn.cyclic:
1471
- return tn
1472
-
1473
- tn.new_bond(0, -1)
1474
- tn.cyclic = True
1475
- return tn
1476
-
1477
- def show(self, max_width=None):
1478
- l1 = ""
1479
- l2 = ""
1480
- l3 = ""
1481
- num_can_l, num_can_r = self.count_canonized()
1482
- for i in range(self.L - 1):
1483
- bdim = self.bond_size(i, i + 1)
1484
- strl = len(str(bdim))
1485
- l1 += f" {bdim}"
1486
- l2 += (
1487
- ">"
1488
- if i < num_can_l
1489
- else "<"
1490
- if i >= self.L - num_can_r
1491
- else "●"
1492
- ) + ("─" if bdim < 100 else "━") * strl
1493
- l3 += "│" + " " * strl
1494
- strl = len(str(bdim))
1495
-
1496
- l1 += " "
1497
- l2 += "<" if num_can_r > 0 else "●"
1498
- l3 += "│"
1499
-
1500
- if self.cyclic:
1501
- bdim = self.bond_size(0, self.L - 1)
1502
- bnd_str = ("─" if bdim < 100 else "━") * strl
1503
- l1 = f" {bdim}{l1}{bdim} "
1504
- l2 = f"+{bnd_str}{l2}{bnd_str}+"
1505
- l3 = f" {' ' * strl}{l3}{' ' * strl} "
1506
-
1507
- print_multi_line(l1, l2, l3, max_width=max_width)
1508
-
1509
-
1510
- class MatrixProductState(TensorNetwork1DVector, TensorNetwork1DFlat):
1511
- """Initialise a matrix product state, with auto labelling and tagging.
1512
-
1513
- Parameters
1514
- ----------
1515
- arrays : sequence of arrays
1516
- The tensor arrays to form into a MPS.
1517
- sites : sequence of int, optional
1518
- Construct the MPO on these sites only. If not given, enumerate from
1519
- zero. Should be monotonically increasing and match ``arrays``.
1520
- L : int, optional
1521
- The number of sites the MPO should be defined on. If not given, this is
1522
- taken as the max ``sites`` value plus one (i.e.g the number of arrays
1523
- if ``sites`` is not given).
1524
- shape : str, optional
1525
- String specifying layout of the tensors. E.g. 'lrp' (the default)
1526
- indicates the shape corresponds left-bond, right-bond, physical index.
1527
- End tensors have either 'l' or 'r' dropped from the string.
1528
- tags : str or sequence of str, optional
1529
- Global tags to attach to all tensors.
1530
- site_ind_id : str
1531
- A string specifiying how to label the physical site indices. Should
1532
- contain a ``'{}'`` placeholder. It is used to generate the actual
1533
- indices like: ``map(site_ind_id.format, range(len(arrays)))``.
1534
- site_tag_id : str
1535
- A string specifiying how to tag the tensors at each site. Should
1536
- contain a ``'{}'`` placeholder. It is used to generate the actual tags
1537
- like: ``map(site_tag_id.format, range(len(arrays)))``.
1538
- """
1539
-
1540
- _EXTRA_PROPS = (
1541
- "_site_tag_id",
1542
- "_site_ind_id",
1543
- "cyclic",
1544
- "_L",
1545
- )
1546
-
1547
- def __init__(
1548
- self,
1549
- arrays,
1550
- *,
1551
- sites=None,
1552
- L=None,
1553
- shape="lrp",
1554
- tags=None,
1555
- site_ind_id="k{}",
1556
- site_tag_id="I{}",
1557
- **tn_opts,
1558
- ):
1559
- # short-circuit for copying MPSs
1560
- if isinstance(arrays, MatrixProductState):
1561
- super().__init__(arrays)
1562
- return
1563
-
1564
- arrays = tuple(arrays)
1565
-
1566
- if sites is None:
1567
- # assume dense
1568
- sites = range(len(arrays))
1569
- if L is None:
1570
- L = len(arrays)
1571
- num_sites = L
1572
- else:
1573
- sites = tuple(sites)
1574
- if L is None:
1575
- L = max(sites) + 1
1576
- num_sites = len(sites)
1577
-
1578
- self._L = len(arrays)
1579
- self._site_ind_id = site_ind_id
1580
- self._site_tag_id = site_tag_id
1581
- self.cyclic = ops.ndim(arrays[0]) == 3
1582
-
1583
- # this is the perm needed to bring the arrays from
1584
- # their current `shape`, to the desired 'lrud' order
1585
- lrp_ord = tuple(map(shape.find, "lrp"))
1586
-
1587
- tensors = []
1588
- tags = tags_to_oset(tags)
1589
- bonds = [rand_uuid() for _ in range(num_sites)]
1590
- bonds.append(bonds[0])
1591
-
1592
- for i, (site, array) in enumerate(zip(sites, arrays)):
1593
- inds = []
1594
-
1595
- if (i == 0) and not self.cyclic:
1596
- # only right bond
1597
- order = tuple(shape.replace("l", "").find(x) for x in "rp")
1598
- inds.append(bonds[i + 1])
1599
- elif (i == num_sites - 1) and not self.cyclic:
1600
- # only left bond
1601
- order = tuple(shape.replace("r", "").find(x) for x in "lp")
1602
- inds.append(bonds[i])
1603
- else:
1604
- order = lrp_ord
1605
- # both bonds
1606
- inds.append(bonds[i])
1607
- inds.append(bonds[i + 1])
1608
-
1609
- # physical index
1610
- inds.append(site_ind_id.format(site))
1611
-
1612
- tensors.append(
1613
- Tensor(
1614
- data=transpose(array, order),
1615
- inds=inds,
1616
- tags=tags | oset([site_tag_id.format(site)]),
1617
- )
1618
- )
1619
-
1620
- super().__init__(tensors, virtual=True, **tn_opts)
1621
-
1622
- @classmethod
1623
- def from_fill_fn(
1624
- cls,
1625
- fill_fn,
1626
- L,
1627
- bond_dim,
1628
- phys_dim=2,
1629
- sites=None,
1630
- cyclic=False,
1631
- shape="lrp",
1632
- site_ind_id="k{}",
1633
- site_tag_id="I{}",
1634
- tags=None,
1635
- ):
1636
- """Create an MPS by supplying a 'filling' function to generate the data
1637
- for each site.
1638
-
1639
- Parameters
1640
- ----------
1641
- fill_fn : callable
1642
- A function with signature
1643
- ``fill_fn(shape : tuple[int]) -> array_like``.
1644
- L : int
1645
- The number of sites.
1646
- bond_dim : int
1647
- The bond dimension.
1648
- phys_dim : int or Sequence[int], optional
1649
- The physical dimension(s) of each site, if a sequence it will be
1650
- cycled over.
1651
- sites : None or sequence of int, optional
1652
- Construct the MPS on these sites only. If not given, enumerate from
1653
- zero.
1654
- cyclic : bool, optional
1655
- Whether the MPS should be cyclic (periodic).
1656
- shape : str, optional
1657
- What specific order to layout the indices in, should be a sequence
1658
- of ``'l'``, ``'r'``, and ``'p'``, corresponding to left, right, and
1659
- physical indices respectively.
1660
- site_ind_id : str, optional
1661
- How to label the physical site indices.
1662
- site_tag_id : str, optional
1663
- How to tag the physical sites.
1664
- tags : str or sequence of str, optional
1665
- Global tags to attach to all tensors.
1666
-
1667
- Returns
1668
- -------
1669
- MatrixProductState
1670
- """
1671
- if set(shape) - set("lrp"):
1672
- raise ValueError("Invalid shape string: {}".format(shape))
1673
-
1674
- # check for site varying physical dimensions
1675
- if isinstance(phys_dim, Integral):
1676
- phys_dims = itertools.repeat(phys_dim)
1677
- else:
1678
- phys_dims = itertools.cycle(phys_dim)
1679
-
1680
- mps = cls.new(
1681
- L=L,
1682
- cyclic=cyclic,
1683
- site_ind_id=site_ind_id,
1684
- site_tag_id=site_tag_id,
1685
- )
1686
-
1687
- # which sites are actually present
1688
- if sites is None:
1689
- sites = range(L)
1690
- else:
1691
- sites = tuple(sites)
1692
- num_sites = len(sites)
1693
-
1694
- global_tags = tags_to_oset(tags)
1695
- bonds = [rand_uuid() for _ in range(num_sites)]
1696
- bonds.append(bonds[0])
1697
-
1698
- for i, site in enumerate(sites):
1699
- inds = []
1700
- data_shape = []
1701
- for c in shape:
1702
- if c == "l":
1703
- if (i - 1) >= 0 or cyclic:
1704
- inds.append(bonds[i])
1705
- data_shape.append(bond_dim)
1706
- elif c == "r":
1707
- if (i + 1) < num_sites or cyclic:
1708
- inds.append(bonds[i + 1])
1709
- data_shape.append(bond_dim)
1710
- else: # c == 'p':
1711
- inds.append(site_ind_id.format(site))
1712
- data_shape.append(next(phys_dims))
1713
- data = fill_fn(data_shape)
1714
- tags = global_tags | oset((site_tag_id.format(site),))
1715
- mps |= Tensor(data, inds=inds, tags=tags)
1716
-
1717
- return mps
1718
-
1719
- @classmethod
1720
- def from_dense(
1721
- cls,
1722
- psi,
1723
- dims=2,
1724
- tags=None,
1725
- site_ind_id="k{}",
1726
- site_tag_id="I{}",
1727
- **split_opts,
1728
- ):
1729
- """Create a ``MatrixProductState`` directly from a dense vector
1730
-
1731
- Parameters
1732
- ----------
1733
- psi : array_like
1734
- The dense state to convert to MPS from.
1735
- dims : int or sequence of int
1736
- Physical subsystem dimensions of each site. If a single int, all
1737
- sites have this same dimension, by default, 2.
1738
- tags : str or sequence of str, optional
1739
- Global tags to attach to all tensors.
1740
- site_ind_id : str, optional
1741
- How to index the physical sites, see
1742
- :class:`~quimb.tensor.tensor_1d.MatrixProductState`.
1743
- site_tag_id : str, optional
1744
- How to tag the physical sites, see
1745
- :class:`~quimb.tensor.tensor_1d.MatrixProductState`.
1746
- split_opts
1747
- Supplied to :func:`~quimb.tensor.tensor_core.tensor_split` to
1748
- in order to partition the dense vector into tensors.
1749
- ``absorb='left'`` is set by default, to ensure the compression
1750
- is canonical / optimal.
1751
-
1752
- Returns
1753
- -------
1754
- MatrixProductState
1755
-
1756
- Examples
1757
- --------
1758
-
1759
- >>> dims = [2, 2, 2, 2, 2, 2]
1760
- >>> psi = rand_ket(prod(dims))
1761
- >>> mps = MatrixProductState.from_dense(psi, dims)
1762
- >>> mps.show()
1763
- 2 4 8 4 2
1764
- o-o-o-o-o-o
1765
- | | | | | |
1766
- """
1767
- set_default_compress_mode(split_opts)
1768
- # ensure compression is canonical / optimal
1769
- split_opts.setdefault("absorb", "right")
1770
-
1771
- # make sure array_like
1772
- psi = ops.asarray(psi)
1773
-
1774
- if isinstance(dims, Integral):
1775
- # assume all sites have the same dimension
1776
- L = round(log(size(psi), dims))
1777
- dims = (dims,) * L
1778
- else:
1779
- dims = tuple(dims)
1780
- L = len(dims)
1781
-
1782
- # create a bare MPS TN object
1783
- mps = cls.new(
1784
- L=L,
1785
- cyclic=False,
1786
- site_ind_id=site_ind_id,
1787
- site_tag_id=site_tag_id,
1788
- )
1789
-
1790
- inds = [mps.site_ind(i) for i in range(L)]
1791
-
1792
- tm = Tensor(data=reshape(psi, dims), inds=inds)
1793
- for i in range(L - 1):
1794
- # progressively split off one more physical index
1795
- tl, tm = tm.split(
1796
- left_inds=None,
1797
- right_inds=inds[i + 1 :],
1798
- ltags=mps.site_tag(i),
1799
- get="tensors",
1800
- **split_opts,
1801
- )
1802
- # add left tensor
1803
- mps |= tl
1804
-
1805
- # add final right tensor
1806
- tm.add_tag(mps.site_tag(L - 1))
1807
- mps |= tm
1808
-
1809
- # add global tags
1810
- if tags is not None:
1811
- mps.add_tag(tags)
1812
-
1813
- return mps
1814
-
1815
- def add_MPS(self, other, inplace=False, **kwargs):
1816
- """Add another MatrixProductState to this one."""
1817
- return tensor_network_ag_sum(self, other, inplace=inplace, **kwargs)
1818
-
1819
- add_MPS_ = functools.partialmethod(add_MPS, inplace=True)
1820
-
1821
- def permute_arrays(self, shape="lrp"):
1822
- """Permute the indices of each tensor in this MPS to match ``shape``.
1823
- This doesn't change how the overall object interacts with other tensor
1824
- networks but may be useful for extracting the underlying arrays
1825
- consistently. This is an inplace operation.
1826
-
1827
- Parameters
1828
- ----------
1829
- shape : str, optional
1830
- A permutation of ``'lrp'`` specifying the desired order of the
1831
- left, right, and physical indices respectively.
1832
- """
1833
- for i in self.gen_sites_present():
1834
- inds = {"p": self.site_ind(i)}
1835
- if self.cyclic or i > 0:
1836
- inds["l"] = self.bond(i, (i - 1) % self.L)
1837
- if self.cyclic or i < self.L - 1:
1838
- inds["r"] = self.bond(i, (i + 1) % self.L)
1839
- inds = [inds[s] for s in shape if s in inds]
1840
- self[i].transpose_(*inds)
1841
-
1842
- def normalize(self, bra=None, eps=1e-15, insert=None):
1843
- """Normalize this MPS, optional with co-vector ``bra``. For periodic
1844
- MPS this uses transfer matrix SVD approximation with precision ``eps``
1845
- in order to be efficient. Inplace.
1846
-
1847
- Parameters
1848
- ----------
1849
- bra : MatrixProductState, optional
1850
- If given, normalize this MPS with the same factor.
1851
- eps : float, optional
1852
- If cyclic, precision to approximation transfer matrix with.
1853
- Default: 1e-14.
1854
- insert : int, optional
1855
- Insert the corrective normalization on this site, random if
1856
- not given.
1857
-
1858
- Returns
1859
- -------
1860
- old_norm : float
1861
- The old norm ``self.H @ self``.
1862
- """
1863
- norm = expec_TN_1D(self.H, self, eps=eps)
1864
-
1865
- if insert is None:
1866
- insert = -1
1867
-
1868
- self[insert].modify(data=self[insert].data / norm**0.5)
1869
- if bra is not None:
1870
- bra[insert].modify(data=bra[insert].data / norm**0.5)
1871
-
1872
- return norm
1873
-
1874
- def gate_split(self, G, where, inplace=False, **compress_opts):
1875
- r"""Apply a two-site gate and then split resulting tensor to retrieve a
1876
- MPS form::
1877
-
1878
- -o-o-A-B-o-o-
1879
- | | | | | | -o-o-GGG-o-o- -o-o-X~Y-o-o-
1880
- | | GGG | | ==> | | | | | | ==> | | | | | |
1881
- | | | | | | i j i j
1882
- i j
1883
-
1884
- As might be found in TEBD.
1885
-
1886
- Parameters
1887
- ----------
1888
- G : array
1889
- The gate, with shape ``(d**2, d**2)`` for physical dimension ``d``.
1890
- where : (int, int)
1891
- Indices of the sites to apply the gate to.
1892
- compress_opts
1893
- Supplied to :func:`~quimb.tensor.tensor_split`.
1894
-
1895
- See Also
1896
- --------
1897
- gate, gate_with_auto_swap
1898
- """
1899
- set_default_compress_mode(compress_opts, self.cyclic)
1900
- ix_i, ix_j = map(self.site_ind, where)
1901
- # note that 'reduce-split' is unecessary: tensors have ndim<=3
1902
- return self.gate_inds(
1903
- G, (ix_i, ix_j), contract="split", inplace=inplace, **compress_opts
1904
- )
1905
-
1906
- gate_split_ = functools.partialmethod(gate_split, inplace=True)
1907
-
1908
- @convert_cur_orthog
1909
- def swap_sites_with_compress(
1910
- self, i, j, info=None, inplace=False, **compress_opts
1911
- ):
1912
- """Swap sites ``i`` and ``j`` by contracting, then splitting with the
1913
- physical indices swapped. If the sites are not adjacent, this will
1914
- happen multiple times.
1915
-
1916
- Parameters
1917
- ----------
1918
- i : int
1919
- The first site to swap.
1920
- j : int
1921
- The second site to swap.
1922
- cur_orthog : int, sequence of int, or 'calc'
1923
- If known, the current orthogonality center.
1924
- info : dict, optional
1925
- If supplied, will be used to infer and store various extra
1926
- information. Currently, the key "cur_orthog" is used to store the
1927
- current orthogonality center. Its input value can be ``"calc"``, a
1928
- single site, or a pair of sites representing the min/max range,
1929
- inclusive. It will be updated to the actual range after.
1930
- inplace : bond, optional
1931
- Perform the swaps inplace.
1932
- compress_opts
1933
- Supplied to :func:`~quimb.tensor.tensor_core.tensor_split`.
1934
- """
1935
- mps = self if inplace else self.copy()
1936
-
1937
- i, j = sorted((i, j))
1938
- if i + 1 != j:
1939
- mps.swap_site_to_(j, i, info=info)
1940
- # first site is now at j + 1, move back up
1941
- mps.swap_site_to_(i + 1, j, info=info)
1942
- return mps
1943
-
1944
- mps.canonicalize_((i, j), info=info)
1945
-
1946
- # get site tensors and indices
1947
- ix_i, ix_j = map(mps.site_ind, (i, j))
1948
- Ti, Tj = mps[i], mps[j]
1949
- _, unshared = Ti.filter_bonds(Tj)
1950
-
1951
- # split the contracted tensor, swapping the site indices
1952
- Tij = Ti @ Tj
1953
- lix = [i for i in unshared if i != ix_i] + [ix_j]
1954
- set_default_compress_mode(compress_opts, self.cyclic)
1955
- sTi, sTj = Tij.split(lix, get="tensors", **compress_opts)
1956
-
1957
- # reindex and transpose the tensors to directly update original tensors
1958
- sTi.reindex_({ix_j: ix_i}).transpose_like_(Ti)
1959
- Ti.modify(data=sTi.data)
1960
- sTj.reindex_({ix_i: ix_j}).transpose_like_(Tj)
1961
- Tj.modify(data=sTj.data)
1962
-
1963
- absorb = compress_opts.get("absorb", None)
1964
- if absorb == "left":
1965
- info["cur_orthog"] = (i, i)
1966
- elif absorb == "right":
1967
- info["cur_orthog"] = (j, j)
1968
-
1969
- return mps
1970
-
1971
- swap_sites_with_compress_ = functools.partialmethod(
1972
- swap_sites_with_compress,
1973
- inplace=True,
1974
- )
1975
-
1976
- @convert_cur_orthog
1977
- def swap_site_to(
1978
- self,
1979
- i,
1980
- f,
1981
- info=None,
1982
- inplace=False,
1983
- **compress_opts,
1984
- ):
1985
- r"""Swap site ``i`` to site ``f``, compressing the bond after each
1986
- swap::
1987
-
1988
- i f
1989
- 0 1 2 3 4 5 6 7 8 9 0 1 2 4 5 6 7 3 8 9
1990
- o-o-o-x-o-o-o-o-o-o >->->->->->->-x-<-<
1991
- | | | | | | | | | | -> | | | | | | | | | |
1992
-
1993
-
1994
- Parameters
1995
- ----------
1996
- i : int
1997
- The site to move.
1998
- f : int
1999
- The new location for site ``i``.
2000
- info : dict, optional
2001
- If supplied, will be used to infer and store various extra
2002
- information. Currently, the key "cur_orthog" is used to store the
2003
- current orthogonality center. Its input value can be ``"calc"``, a
2004
- single site, or a pair of sites representing the min/max range,
2005
- inclusive. It will be updated to the actual range after.
2006
- inplace : bond, optional
2007
- Perform the swaps inplace.
2008
- compress_opts
2009
- Supplied to :func:`~quimb.tensor.tensor_core.tensor_split`.
2010
- """
2011
- mps = self if inplace else self.copy()
2012
-
2013
- if i == f:
2014
- return mps
2015
- if i < f:
2016
- compress_opts.setdefault("absorb", "right")
2017
- js = range(i, f)
2018
- if f < i:
2019
- compress_opts.setdefault("absorb", "left")
2020
- js = range(i - 1, f - 1, -1)
2021
-
2022
- for j in js:
2023
- mps.swap_sites_with_compress(
2024
- j, j + 1, info=info, inplace=True, **compress_opts
2025
- )
2026
-
2027
- return mps
2028
-
2029
- swap_site_to_ = functools.partialmethod(swap_site_to, inplace=True)
2030
-
2031
- @convert_cur_orthog
2032
- def gate_with_auto_swap(
2033
- self,
2034
- G,
2035
- where,
2036
- info=None,
2037
- swap_back=True,
2038
- inplace=False,
2039
- **compress_opts,
2040
- ):
2041
- """Perform a two site gate on this MPS by, if necessary, swapping and
2042
- compressing the sites until they are adjacent, using ``gate_split``,
2043
- then unswapping the sites back to their original position.
2044
-
2045
- Parameters
2046
- ----------
2047
- G : array
2048
- The gate, with shape ``(d**2, d**2)`` for physical dimension ``d``.
2049
- where : (int, int)
2050
- Indices of the sites to apply the gate to.
2051
- info : dict, optional
2052
- If supplied, will be used to infer and store various extra
2053
- information. Currently, the key "cur_orthog" is used to store the
2054
- current orthogonality center. Its input value can be ``"calc"``, a
2055
- single site, or a pair of sites representing the min/max range,
2056
- inclusive. It will be updated to the actual range after.
2057
- swap_back : bool, optional
2058
- Whether to swap the sites back to their original position after
2059
- applying the gate. If not, for sites ``i < j``, the site ``j`` will
2060
- remain swapped to ``i + 1``, and sites between ``i + 1`` and ``j``
2061
- will be shifted one place up.
2062
- inplace : bond, optional
2063
- Perform the swaps inplace.
2064
- compress_opts
2065
- Supplied to :func:`~quimb.tensor.tensor_core.tensor_split`.
2066
-
2067
- See Also
2068
- --------
2069
- gate, gate_split
2070
- """
2071
- mps = self if inplace else self.copy()
2072
-
2073
- i, j = where
2074
-
2075
- if i > j:
2076
- # work with i < j but flip application of gate when necessary
2077
- i, j = j, i
2078
- final_gate_where = (i + 1, i)
2079
- absorb = "left"
2080
- else:
2081
- final_gate_where = (i, i + 1)
2082
- absorb = "right"
2083
-
2084
- need_to_swap = i + 1 != j
2085
-
2086
- # move j site adjacent to i site
2087
- if need_to_swap:
2088
- mps.swap_site_to(
2089
- j, i + 1, info=info, inplace=True, **compress_opts
2090
- )
2091
-
2092
- # make sure sites are orthog center
2093
- mps.canonicalize_((i, i + 1), info=info)
2094
-
2095
- # apply gate and split back into MPS form
2096
- mps.gate_split_(
2097
- G, where=final_gate_where, absorb=absorb, **compress_opts
2098
- )
2099
-
2100
- # absorb setting guarantees this
2101
- info["cur_orthog"] = (i + 1, i + 1)
2102
-
2103
- if need_to_swap and swap_back:
2104
- # move j site back to original position
2105
- mps.swap_site_to(
2106
- i + 1, j, info=info, inplace=True, **compress_opts
2107
- )
2108
-
2109
- return mps
2110
-
2111
- gate_with_auto_swap_ = functools.partialmethod(
2112
- gate_with_auto_swap,
2113
- inplace=True,
2114
- )
2115
-
2116
- @convert_cur_orthog
2117
- def gate_with_submpo(
2118
- self,
2119
- submpo,
2120
- where=None,
2121
- method="direct",
2122
- transpose=False,
2123
- info=None,
2124
- inplace=False,
2125
- inplace_mpo=False,
2126
- **compress_opts,
2127
- ):
2128
- """Apply an MPO, which only acts on a subset of sites, to this MPS,
2129
- compressing the MPS with the MPO only on the minimal set of sites
2130
- covering `where`, keeping the MPS form::
2131
-
2132
- │ │ │
2133
- A───A─A
2134
- │ │ │ -> │ │ │ │ │ │ │ │
2135
- >─>─O━O━O━O─<─<
2136
- │ │ │ │ │ │ │ │
2137
- o─o─o─o─o─o─o─o
2138
-
2139
- Parameters
2140
- ----------
2141
- submpo : MatrixProductOperator
2142
- The MPO to apply.
2143
- where : sequence of int, optional
2144
- The range of sites the MPO acts on, will be inferred from the
2145
- support of the MPO if not given.
2146
- method : {'direct", 'dm', 'zipup', 'zipup-first', 'fit'}, optional
2147
- The compression method to use.
2148
- transpose : bool, optional
2149
- Whether to transpose the MPO before applying it. By default the
2150
- lower inds of the MPO are contracted with the MPS, if transposed
2151
- the upper inds are contracted.
2152
- info : dict, optional
2153
- If supplied, will be used to infer and store various extra
2154
- information. Currently, the key "cur_orthog" is used to store the
2155
- current orthogonality center. Its input value can be ``"calc"``, a
2156
- single site, or a pair of sites representing the min/max range,
2157
- inclusive. It will be updated to the actual range after.
2158
- inplace : bool, optional
2159
- Whether to perform the application and compression inplace.
2160
- compress_opts
2161
- Supplied to
2162
- :func:`~quimb.tensor.tensor_1d_compress.tensor_network_1d_compress`.
2163
-
2164
- Returns
2165
- -------
2166
- MatrixProductState
2167
- """
2168
- from .tensor_1d_compress import tensor_network_1d_compress
2169
-
2170
- psi = self if inplace else self.copy()
2171
-
2172
- # get the span of sites the sub-MPO acts on
2173
- if where is None:
2174
- where = tuple(submpo.gen_sites_present())
2175
- si, sf = min(where), max(where)
2176
-
2177
- # make the psi canonical around the sub-MPO region
2178
- psi.canonicalize_((si, sf), info=info)
2179
-
2180
- # lazily combine the sub-MPO with the MPS
2181
- psi.gate_with_op_lazy_(
2182
- submpo,
2183
- transpose=transpose,
2184
- inplace_op=inplace_mpo,
2185
- )
2186
-
2187
- # split off the sub MPS-MPO TN section
2188
- sub_site_tags = [psi.site_tag(s) for s in range(si, sf + 1)]
2189
- _, subpsi = psi.partition(sub_site_tags, which="any", inplace=True)
2190
-
2191
- # compress it!
2192
- tensor_network_1d_compress(
2193
- subpsi,
2194
- site_tags=sub_site_tags,
2195
- method=method,
2196
- # the sub TN can't be automatically permuted when missing sites
2197
- permute_arrays=False,
2198
- inplace=True,
2199
- **compress_opts,
2200
- )
2201
-
2202
- if compress_opts.get("sweep_reverse", False):
2203
- info["cur_orthog"] = (sf, sf)
2204
- else:
2205
- info["cur_orthog"] = (si, si)
2206
-
2207
- # recombine the compressed sub region TN
2208
- psi |= subpsi
2209
-
2210
- return psi
2211
-
2212
- gate_with_submpo_ = functools.partialmethod(gate_with_submpo, inplace=True)
2213
-
2214
- def gate_with_mpo(
2215
- self,
2216
- mpo,
2217
- method="direct",
2218
- transpose=False,
2219
- inplace=False,
2220
- inplace_mpo=False,
2221
- **compress_opts,
2222
- ):
2223
- """Gate this MPS with an MPO and compress the result with one of
2224
- various methods back to MPS form::
2225
-
2226
- │ │ │ │ │ │ │ │
2227
- A─A─A─A─A─A─A─A
2228
- │ │ │ │ │ │ │ │ -> │ │ │ │ │ │ │ │
2229
- O━O━O━O━O━O━O━O
2230
- │ │ │ │ │ │ │ │
2231
- o─o─o─o─o─o─o─o
2232
-
2233
- Parameters
2234
- ----------
2235
- mpo : MatrixProductOperator
2236
- The MPO to apply.
2237
- max_bond : int, optional
2238
- A maximum bond dimension to keep when compressing.
2239
- cutoff : float, optional
2240
- A singular value cutoff to use when compressing.
2241
- method : {'direct", 'dm', 'zipup', 'zipup-first', 'fit', ...}, optional
2242
- The compression method to use.
2243
- transpose : bool, optional
2244
- Whether to transpose the MPO before applying it. By default the
2245
- lower inds of the MPO are contracted with the MPS, if transposed
2246
- the upper inds are contracted.
2247
- inplace : bool, optional
2248
- Whether to perform the compression inplace.
2249
- inplace_mpo : bool, optional
2250
- Whether the modify the MPO in place, a minor performance gain.
2251
- compress_opts
2252
- Other options supplied to
2253
- :func:`~quimb.tensor.tensor_1d_compress.tensor_network_1d_compress`.
2254
-
2255
- Returns
2256
- -------
2257
- MatrixProductState
2258
- """
2259
- from .tensor_1d_compress import tensor_network_1d_compress
2260
-
2261
- psi = self if inplace else self.copy()
2262
-
2263
- # lazily combine the MPO with the MPS
2264
- psi.gate_with_op_lazy_(
2265
- mpo,
2266
- transpose=transpose,
2267
- inplace=inplace_mpo,
2268
- )
2269
-
2270
- # compress it!
2271
- return tensor_network_1d_compress(
2272
- psi,
2273
- method=method,
2274
- inplace=True,
2275
- **compress_opts,
2276
- )
2277
-
2278
- gate_with_mpo_ = functools.partialmethod(gate_with_mpo, inplace=True)
2279
-
2280
- @convert_cur_orthog
2281
- def gate_nonlocal(
2282
- self,
2283
- G,
2284
- where,
2285
- dims=None,
2286
- method="direct",
2287
- info=None,
2288
- inplace=False,
2289
- **compress_opts,
2290
- ):
2291
- """Apply a potentially non-local gate to this MPS by first decomposing
2292
- it into an MPO, then compressing the MPS with MPO only on the minimal
2293
- set of sites covering `where`.
2294
-
2295
- Parameters
2296
- ----------
2297
- G : array_like
2298
- The gate to apply.
2299
- where : sequence of int
2300
- The sites to apply the gate to.
2301
- max_bond : int, optional
2302
- A maximum bond dimension to keep when compressing.
2303
- cutoff : float, optional
2304
- A singular value cutoff to use when compressing.
2305
- dims : sequence of int, optional
2306
- The factorized dimensions of the gate ``G``, which should match the
2307
- physical dimensions of the sites it acts on. Calculated if not
2308
- supplied. If a single int, all sites are assumed to have this same
2309
- dimension.
2310
- method : {'direct", 'dm', 'zipup', 'zipup-first', 'fit', ...}, optional
2311
- The compression method to use.
2312
- info : dict, optional
2313
- If supplied, will be used to infer and store various extra
2314
- information. Currently, the key "cur_orthog" is used to store the
2315
- current orthogonality center. Its input value can be ``"calc"``, a
2316
- single site, or a pair of sites representing the min/max range,
2317
- inclusive. It will be updated to the actual range after.
2318
- inplace : bool, optional
2319
- Whether to perform the compression inplace.
2320
- compress_opts
2321
- Supplied to
2322
- :func:`~quimb.tensor.tensor_1d_compress.tensor_network_1d_compress`.
2323
-
2324
- Returns
2325
- -------
2326
- MatrixProductState
2327
- """
2328
- if dims is None:
2329
- dims = tuple(self.phys_dim(i) for i in where)
2330
-
2331
- # create a sub-MPO and lazily combine it with the MPS
2332
- mpo = MatrixProductOperator.from_dense(
2333
- G, dims=dims, sites=where, L=self.L
2334
- )
2335
-
2336
- return self.gate_with_submpo_(
2337
- mpo,
2338
- where=where,
2339
- method=method,
2340
- info=info,
2341
- inplace=inplace,
2342
- inplace_mpo=True,
2343
- **compress_opts,
2344
- )
2345
-
2346
- gate_nonlocal_ = functools.partialmethod(gate_nonlocal, inplace=True)
2347
-
2348
- def flip(self, inplace=False):
2349
- """Reverse the order of the sites in the MPS, such that site ``i`` is
2350
- now at site ``L - i - 1``.
2351
- """
2352
- flipped = self if inplace else self.copy()
2353
-
2354
- retag_map = {
2355
- self.site_tag(i): self.site_tag(self.L - i - 1) for i in self.sites
2356
- }
2357
- reindex_map = {
2358
- self.site_ind(i): self.site_ind(self.L - i - 1) for i in self.sites
2359
- }
2360
-
2361
- return flipped.retag_(retag_map).reindex_(reindex_map)
2362
-
2363
- @convert_cur_orthog
2364
- def magnetization(
2365
- self,
2366
- i,
2367
- direction="Z",
2368
- info=None,
2369
- ):
2370
- """Compute the magnetization at site ``i``."""
2371
- if self.cyclic:
2372
- msg = (
2373
- "``magnetization`` currently makes use of orthogonality for"
2374
- " efficiencies sake, for cyclic systems is it still "
2375
- "possible to compute as a normal expectation."
2376
- )
2377
- raise NotImplementedError(msg)
2378
-
2379
- self.canonicalize_(i, info=info)
2380
-
2381
- # +-k-+
2382
- # | O |
2383
- # +-b-+
2384
-
2385
- Tk = self[i]
2386
- ind1, ind2 = self.site_ind(i), "__tmp__"
2387
- Tb = Tk.H.reindex({ind1: ind2})
2388
-
2389
- O_data = qu.spin_operator(direction, S=(self.phys_dim(i) - 1) / 2)
2390
- TO = Tensor(O_data, inds=(ind1, ind2))
2391
-
2392
- return Tk.contract(TO, Tb)
2393
-
2394
- @convert_cur_orthog
2395
- def schmidt_values(self, i, info=None, method="svd"):
2396
- r"""Find the schmidt values associated with the bipartition of this
2397
- MPS between sites on either site of ``i``. In other words, ``i`` is the
2398
- number of sites in the left hand partition::
2399
-
2400
- ....L.... i
2401
- o-o-o-o-o-S-o-o-o-o-o-o-o-o-o-o-o
2402
- | | | | | | | | | | | | | | | |
2403
- i-1 ..........R..........
2404
-
2405
- The schmidt values, ``S``, are the singular values associated with the
2406
- ``(i - 1, i)`` bond, squared, provided the MPS is mixed canonized at
2407
- one of those sites.
2408
-
2409
- Parameters
2410
- ----------
2411
- i : int
2412
- The number of sites in the left partition.
2413
- info : dict, optional
2414
- If given, will be used to infer and store various extra
2415
- information. Currently the key "cur_orthog" is used to store the
2416
- current orthogonality center.
2417
-
2418
- Returns
2419
- -------
2420
- S : 1d-array
2421
- The schmidt values.
2422
- """
2423
- if self.cyclic:
2424
- raise NotImplementedError
2425
-
2426
- return self.singular_values(i, info=info, method=method) ** 2
2427
-
2428
- @convert_cur_orthog
2429
- def entropy(self, i, info=None, method="svd"):
2430
- """The entropy of bipartition between the left block of ``i`` sites and
2431
- the rest.
2432
-
2433
- Parameters
2434
- ----------
2435
- i : int
2436
- The number of sites in the left partition.
2437
- info : dict, optional
2438
- If given, will be used to infer and store various extra
2439
- information. Currently the key "cur_orthog" is used to store the
2440
- current orthogonality center.
2441
-
2442
- Returns
2443
- -------
2444
- float
2445
- """
2446
- if self.cyclic:
2447
- msg = (
2448
- "For cyclic systems, try explicitly computing the entropy "
2449
- "of the (compressed) reduced density matrix."
2450
- )
2451
- raise NotImplementedError(msg)
2452
-
2453
- S = self.schmidt_values(i, info=info, method=method)
2454
- S = S[S > 0.0]
2455
- return do("sum", -S * do("log2", S))
2456
-
2457
- @convert_cur_orthog
2458
- def schmidt_gap(self, i, info=None, method="svd"):
2459
- """The schmidt gap of bipartition between the left block of ``i`` sites
2460
- and the rest.
2461
-
2462
- Parameters
2463
- ----------
2464
- i : int
2465
- The number of sites in the left partition.
2466
- info : dict, optional
2467
- If given, will be used to infer and store various extra
2468
- information. Currently the key "cur_orthog" is used to store the
2469
- current orthogonality center.
2470
-
2471
- Returns
2472
- -------
2473
- float
2474
- """
2475
- if self.cyclic:
2476
- raise NotImplementedError
2477
-
2478
- S = self.schmidt_values(i, info=info, method=method)
2479
-
2480
- if len(S) == 1:
2481
- return S[0]
2482
-
2483
- return S[0] - S[1]
2484
-
2485
- def partial_trace_to_mpo(
2486
- self, keep, upper_ind_id="b{}", rescale_sites=True
2487
- ):
2488
- r"""Partially trace this matrix product state, producing a matrix
2489
- product operator.
2490
-
2491
- Parameters
2492
- ----------
2493
- keep : sequence of int or slice
2494
- Indicies of the sites to keep.
2495
- upper_ind_id : str, optional
2496
- The ind id of the (new) 'upper' inds, i.e. the 'bra' inds.
2497
- rescale_sites : bool, optional
2498
- If ``True`` (the default), then the kept sites will be rescaled to
2499
- ``(0, 1, 2, ...)`` etc. rather than keeping their original site
2500
- numbers.
2501
-
2502
- Returns
2503
- -------
2504
- rho : MatrixProductOperator
2505
- The density operator in MPO form.
2506
- """
2507
- p_bra = self.copy()
2508
- p_bra.reindex_sites_(upper_ind_id, where=keep)
2509
- rho = self.H & p_bra
2510
- # now have e.g:
2511
- # | | | |
2512
- # o-o-o-o-o-o-o-o-o
2513
- # | | | | |
2514
- # o-o-o-o-o-o-o-o-o
2515
- # | | | |
2516
-
2517
- if isinstance(keep, slice):
2518
- keep = self.slice2sites(keep)
2519
-
2520
- keep = sorted(keep)
2521
-
2522
- for i in self.gen_sites_present():
2523
- if i in keep:
2524
- # |
2525
- # -o- |
2526
- # ... -o- ... -> ... -O- ...
2527
- # i| i|
2528
- rho ^= self.site_tag(i)
2529
- else:
2530
- # |
2531
- # -o-o- |
2532
- # ... | ... -> ... -OO- ...
2533
- # -o-o- |i+1
2534
- # i |i+1
2535
- if i < self.L - 1:
2536
- rho >>= [self.site_tag(i), self.site_tag(i + 1)]
2537
- else:
2538
- rho >>= [self.site_tag(i), self.site_tag(max(keep))]
2539
-
2540
- rho.drop_tags(self.site_tag(i))
2541
-
2542
- # if single site a single tensor is produced
2543
- if isinstance(rho, Tensor):
2544
- rho = rho.as_network()
2545
-
2546
- if rescale_sites:
2547
- # e.g. [3, 4, 5, 7, 9] -> [0, 1, 2, 3, 4]
2548
- retag, reind = {}, {}
2549
- for new, old in enumerate(keep):
2550
- retag[self.site_tag(old)] = self.site_tag(new)
2551
- reind[self.site_ind(old)] = self.site_ind(new)
2552
- reind[upper_ind_id.format(old)] = upper_ind_id.format(new)
2553
-
2554
- rho.retag_(retag)
2555
- rho.reindex_(reind)
2556
- L = len(keep)
2557
- else:
2558
- L = self.L
2559
-
2560
- # transpose upper and lower tags to match other MPOs
2561
- rho.view_as_(
2562
- MatrixProductOperator,
2563
- cyclic=self.cyclic,
2564
- L=L,
2565
- site_tag_id=self.site_tag_id,
2566
- lower_ind_id=upper_ind_id,
2567
- upper_ind_id=self.site_ind_id,
2568
- )
2569
- rho.fuse_multibonds_()
2570
- return rho
2571
-
2572
- def partial_trace(self, *_, **__):
2573
- raise AttributeError(
2574
- "`mps.partial_trace` has been renamed to "
2575
- "`mps.partial_trace_to_mpo`. Soon `mps.partial_trace` "
2576
- "will produce (dense) local reduced density matrices to match "
2577
- "methods elsewhere in quimb."
2578
- )
2579
-
2580
- def ptr(self, *_, **__):
2581
- raise AttributeError(
2582
- "`mps.ptr` has been renamed " "to `mps.partial_trace_to_mpo`."
2583
- )
2584
-
2585
- def partial_trace_to_dense_canonical(
2586
- self, where, normalized=True, info=None, **contract_opts
2587
- ):
2588
- """Compute the dense local reduced density matrix by canonicalizing
2589
- around the target sites and then contracting the local tensors. Note
2590
- this moves the orthogonality around inplace, and records it in `info`.
2591
-
2592
- Parameters
2593
- ----------
2594
- where : int or tuple[int]
2595
- The site or sites to compute the reduced density matrix for.
2596
- normalized : bool, optional
2597
- Explicitly normalize the local reduced density matrix.
2598
- info : dict, optional
2599
- If supplied, will be used to infer and store various extra
2600
- information. Currently the key "cur_orthog" is used to store the
2601
- current orthogonality center. Its input value can be ``"calc"``, a
2602
- single site, or a pair of sites representing the min/max range,
2603
- inclusive. It will be updated to the actual range after.
2604
- contract_opts
2605
- Passed to `tensor_contract` when computing the reduced local
2606
- density matrix.
2607
-
2608
- Returns
2609
- -------
2610
- array_like
2611
- """
2612
- if self.cyclic:
2613
- raise NotImplementedError("Only supports OBC.")
2614
-
2615
- if isinstance(where, Integral):
2616
- where = (where,)
2617
-
2618
- # canonicalize around our sites
2619
- self.canonicalize_(where, info=info)
2620
-
2621
- # form the local reduced density matrix tn
2622
- kix = [self.site_ind(i) for i in where]
2623
- bix = [f"__b{i}__" for i in where]
2624
- k = self[min(where) : max(where) + 1]
2625
- b = k.reindex(dict(zip(kix, bix))).conj_()
2626
- rho_tn = k | b
2627
-
2628
- # contract down to a matrix
2629
- rho = rho_tn.to_dense(kix, bix, **contract_opts)
2630
-
2631
- if normalized:
2632
- # locally normalize, usually unnecessary for an MPS but cheap
2633
- rho = rho / do("trace", rho)
2634
-
2635
- return rho
2636
-
2637
- def local_expectation_canonical(
2638
- self, G, where, normalized=True, info=None, **contract_opts
2639
- ):
2640
- """Compute a local expectation value (via forming the reduced density
2641
- matrix). Note this moves the orthogonality around inplace, and records
2642
- it in `info`.
2643
-
2644
- Parameters
2645
- ----------
2646
- G : array_like
2647
- The local operator to compute the expectation of.
2648
- where : int or tuple[int]
2649
- The site or sites to compute the expectation at.
2650
- normalized : bool, optional
2651
- Explicitly normalize the local reduced density matrix.
2652
- info : dict, optional
2653
- If supplied, will be used to infer and store various extra
2654
- information. Currently the key "cur_orthog" is used to store the
2655
- current orthogonality center. Its input value can be ``"calc"``, a
2656
- single site, or a pair of sites representing the min/max range,
2657
- inclusive. It will be updated to the actual range after.
2658
- contract_opts
2659
- Passed to `tensor_contract` when computing the reduced local
2660
- density matrix.
2661
-
2662
- Returns
2663
- -------
2664
- float
2665
- """
2666
- rho = self.partial_trace_to_dense_canonical(
2667
- where, normalized=normalized, info=info, **contract_opts
2668
- )
2669
- return do("trace", G @ rho)
2670
-
2671
- def compute_local_expectation_canonical(
2672
- self,
2673
- terms,
2674
- normalized=True,
2675
- return_all=False,
2676
- info=None,
2677
- inplace=False,
2678
- **contract_opts,
2679
- ):
2680
- """Compute many local expectations at once, via forming the relevant
2681
- reduced density matrices via canonicalization. This moves the
2682
- orthogonality around inplace, and records it in `info`.
2683
-
2684
- Parameters
2685
- ----------
2686
- terms : dict[int or tuple[int], array_like]
2687
- The local terms to compute values for.
2688
- normalized : bool, optional
2689
- Explicitly normalize each local reduced density matrix.
2690
- return_all : bool, optional
2691
- Whether to return each expectation in `terms` separately
2692
- or sum them all together (the default).
2693
- info : dict, optional
2694
- If supplied, will be used to infer and store various extra
2695
- information. Currently, the key "cur_orthog" is used to store the
2696
- current orthogonality center. Its input value can be ``"calc"``, a
2697
- single site, or a pair of sites representing the min/max range,
2698
- inclusive. It will be updated to the actual range after.
2699
- inplace : bool, optional
2700
- Whether to perform the required canonicalizations inplace.
2701
- contract_opts
2702
- Supplied to
2703
- :meth:`~quimb.tensor.tensor_core.TensorNetwork.contract`
2704
- when contracting the local density matrices.
2705
-
2706
- Returns
2707
- -------
2708
- float or dict[in or tuple[int], float]
2709
- The expecetation value(s), either summed or for each term if
2710
- `return_all=True`.
2711
-
2712
- See Also
2713
- --------
2714
- compute_local_expectation_via_envs, local_expectation_canonical
2715
- partial_trace_to_dense_canonical
2716
- """
2717
- if self.cyclic:
2718
- raise NotImplementedError("Only supports OBC.")
2719
-
2720
- if info is None:
2721
- # this is used to keep track of canonical center
2722
- info = {}
2723
-
2724
- if inplace:
2725
- mps = self
2726
- else:
2727
- mps = self.copy()
2728
- info = info.copy()
2729
-
2730
- cur_orthog = info.get("cur_orthog", "calc")
2731
- if isinstance(cur_orthog, tuple):
2732
- # have a canonical center already -> start close to it
2733
- terms = sorted(
2734
- terms.items(), key=lambda kv: abs(min(kv[0]) - cur_orthog[0])
2735
- )
2736
- else:
2737
- # sort by the smallest site so we sweep in one direction
2738
- terms = sorted(terms.items(), key=lambda kv: min(kv[0]))
2739
-
2740
- expecs = {
2741
- where: mps.local_expectation_canonical(
2742
- G,
2743
- where,
2744
- normalized=normalized,
2745
- info=info,
2746
- **contract_opts,
2747
- )
2748
- for where, G in terms
2749
- }
2750
-
2751
- if return_all:
2752
- return expecs
2753
-
2754
- return functools.reduce(operator.add, expecs.values())
2755
-
2756
- def compute_local_expectation_via_envs(
2757
- self,
2758
- terms,
2759
- normalized=True,
2760
- return_all=False,
2761
- **contract_opts,
2762
- ):
2763
- """Compute many local expectations at once, via forming the relevant
2764
- local overlaps using left and right environments formed via
2765
- contraction. This does not require any canonicalization and can be
2766
- quicker if the canonical center is not already aligned.
2767
-
2768
- Parameters
2769
- ----------
2770
- terms : dict[int or tuple[int], array_like]
2771
- The local terms to compute values for.
2772
- normalized : bool, optional
2773
- Explicitly normalize each local reduced density matrix.
2774
- return_all : bool, optional
2775
- Whether to return each expectation in `terms` separately
2776
- or sum them all together (the default).
2777
- contract_opts
2778
- Supplied to
2779
- :meth:`~quimb.tensor.tensor_core.TensorNetwork.contract`
2780
- when contracting the local overlaps.
2781
-
2782
- Returns
2783
- -------
2784
- float or dict[int or tuple[int], float]
2785
- The expecetation value(s), either summed or for each term if
2786
- `return_all=True`.
2787
-
2788
- See Also
2789
- --------
2790
- compute_local_expectation_canonical, compute_left_environments,
2791
- compute_right_environments
2792
- """
2793
- norm, ket, bra = self.make_norm(return_all=True)
2794
-
2795
- left_envs = norm.compute_left_environments(**contract_opts)
2796
- right_envs = norm.compute_right_environments(**contract_opts)
2797
-
2798
- expecs = {}
2799
-
2800
- if normalized:
2801
- nfactor = (norm.select(0) | right_envs[0]).contract(
2802
- all, **contract_opts
2803
- )
2804
- else:
2805
- nfactor = None
2806
-
2807
- for where, G in terms.items():
2808
- sitemin = min(where)
2809
- sitemax = max(where)
2810
- tags = [ket.site_tag(i) for i in range(sitemin, sitemax + 1)]
2811
- # form:
2812
- # sitemin sitemax
2813
- # : :
2814
- # ┌─┐ ┌─┐
2815
- # ┌──┤k├─┤k├──┐
2816
- # │ └┬┘ └┬┘ │
2817
- # │ │ │ │
2818
- # ┌┴┐ ┌┴───┴┐ ┌┴┐
2819
- # │l│ │ G │ │r│
2820
- # └┬┘ └┬───┬┘ └┬┘
2821
- # │ │ │ │
2822
- # │ ┌┴┐ ┌┴┐ │
2823
- # └──┤b├─┤b├──┘
2824
- # └─┘ └─┘
2825
- # (n.b. might be non-gated sites in between as well)
2826
- k = ket.select_any(tags, virtual=False)
2827
- b = bra.select_any(tags, virtual=False)
2828
- k.gate_(G, where, contract=False)
2829
-
2830
- tn_local_overlap = k | b
2831
- if sitemin in left_envs:
2832
- tn_local_overlap |= left_envs[sitemin]
2833
- if sitemax in right_envs:
2834
- tn_local_overlap |= right_envs[sitemax]
2835
-
2836
- x = tn_local_overlap.contract(all, **contract_opts)
2837
- if normalized:
2838
- x = x / nfactor
2839
-
2840
- expecs[where] = x
2841
-
2842
- if return_all:
2843
- return expecs
2844
-
2845
- return functools.reduce(operator.add, expecs.values())
2846
-
2847
- def compute_local_expectation(
2848
- self,
2849
- terms,
2850
- normalized=True,
2851
- return_all=False,
2852
- method="canonical",
2853
- info=None,
2854
- inplace=False,
2855
- **contract_opts,
2856
- ):
2857
- """Compute many local expectations at once.
2858
-
2859
- Parameters
2860
- ----------
2861
- terms : dict[int or tuple[int], array_like]
2862
- The local terms to compute values for.
2863
- normalized : bool, optional
2864
- Explicitly normalize each local term.
2865
- return_all : bool, optional
2866
- Whether to return each expectation in `terms` separately
2867
- or sum them all together (the default).
2868
- method : {'canonical', 'envs'}, optional
2869
- The method to use to compute the local expectations.
2870
-
2871
- - 'canonical': canonicalize around the sites of interest and
2872
- contract the local reduced density matrices, moving the canonical
2873
- center around as needed.
2874
- - 'envs': form the local overlaps using left and right environments
2875
- and contract these directly. This can be quicker if the canonical
2876
- center is not already aligned.
2877
-
2878
- info : dict, optional
2879
- If supplied, and `method=="canonical"`, will be used to infer and
2880
- store various extra information. Currently the key "cur_orthog" is
2881
- used to store the current orthogonality center. Its input value can
2882
- be ``"calc"``, a single site, or a pair of sites representing the
2883
- min/max range, inclusive. It will be updated to the actual range
2884
- after.
2885
- inplace : bool, optional
2886
- If `method=="canonical"`, whether to perform the required
2887
- canonicalizations inplace or on a copy of the state.
2888
- contract_opts
2889
- Supplied to
2890
- :meth:`~quimb.tensor.tensor_core.TensorNetwork.contract`
2891
- when contracting the local overlaps or density matrices.
2892
-
2893
- Returns
2894
- -------
2895
- float or dict[int or tuple[int], float]
2896
- The expecetation value(s), either summed or for each term if
2897
- `return_all=True`.
2898
-
2899
- See Also
2900
- --------
2901
- compute_local_expectation_canonical, compute_local_expectation_via_envs
2902
- """
2903
- if method == "canonical":
2904
- return self.compute_local_expectation_canonical(
2905
- terms,
2906
- normalized=normalized,
2907
- return_all=return_all,
2908
- info=info,
2909
- inplace=inplace,
2910
- **contract_opts,
2911
- )
2912
- elif method == "envs":
2913
- return self.compute_local_expectation_via_envs(
2914
- terms,
2915
- normalized=normalized,
2916
- return_all=return_all,
2917
- **contract_opts,
2918
- )
2919
- else:
2920
- raise ValueError(
2921
- f"Unrecognized method: {method}, should be one of: "
2922
- "'canonical', 'envs'."
2923
- )
2924
-
2925
- @convert_cur_orthog
2926
- def bipartite_schmidt_state(self, sz_a, get="ket", info=None):
2927
- r"""Compute the reduced state for a bipartition of an OBC MPS, in terms
2928
- of the minimal left/right schmidt basis::
2929
-
2930
- A B
2931
- ......... ...........
2932
- >->->->->--s--<-<-<-<-<-< -> +-s-+
2933
- | | | | | | | | | | | | |
2934
- k0 k1... kA kB
2935
-
2936
- Parameters
2937
- ----------
2938
- sz_a : int
2939
- The number of sites in subsystem A, must be ``0 < sz_a < N``.
2940
- get : {'ket', 'rho', 'ket-dense', 'rho-dense'}, optional
2941
- Get the:
2942
-
2943
- - 'ket': vector form as tensor.
2944
- - 'rho': density operator form, i.e. vector outer product
2945
- - 'ket-dense': like 'ket' but return ``qarray``.
2946
- - 'rho-dense': like 'rho' but return ``qarray``.
2947
-
2948
- info : dict, optional
2949
- If given, will be used to infer and store various extra
2950
- information. Currently the key "cur_orthog" is used to store the
2951
- current orthogonality center.
2952
- """
2953
- if self.cyclic:
2954
- raise NotImplementedError("MPS must have OBC.")
2955
-
2956
- s = do("diag", self.singular_values(sz_a, info=info))
2957
-
2958
- if "dense" in get:
2959
- kd = qu.qarray(s.reshape(-1, 1))
2960
- if "ket" in get:
2961
- return kd
2962
- elif "rho" in get:
2963
- return kd @ kd.H
2964
-
2965
- else:
2966
- k = Tensor(s, (self.site_ind("A"), self.site_ind("B")))
2967
- if "ket" in get:
2968
- return k
2969
- elif "rho" in get:
2970
- return k & k.reindex({"kA": "bA", "kB": "bB"})
2971
-
2972
- @staticmethod
2973
- def _do_lateral_compress(
2974
- mps,
2975
- kb,
2976
- section,
2977
- leave_short,
2978
- ul,
2979
- ll,
2980
- heps,
2981
- hmethod,
2982
- hmax_bond,
2983
- verbosity,
2984
- compressed,
2985
- **compress_opts,
2986
- ):
2987
- # section
2988
- # ul -o-o-o-o-o-o-o-o-o- ul -\ /-
2989
- # | | | | | | | | | ==> 0~~~~~0
2990
- # ll -o-o-o-o-o-o-o-o-o- ll -/ : \-
2991
- # hmax_bond
2992
-
2993
- if leave_short:
2994
- # if section is short doesn't make sense to lateral compress
2995
- # work out roughly when this occurs by comparing bond size
2996
- left_sz = mps.bond_size(section[0] - 1, section[0])
2997
- right_sz = mps.bond_size(section[-1], section[-1] + 1)
2998
-
2999
- if mps.phys_dim() ** len(section) <= left_sz * right_sz:
3000
- if verbosity >= 1:
3001
- print(
3002
- f"Leaving lateral compress of section '{section}' as"
3003
- f" it is too short: length={len(section)}, eff "
3004
- f"size={left_sz * right_sz}."
3005
- )
3006
- return
3007
-
3008
- if verbosity >= 1:
3009
- print(
3010
- f"Laterally compressing section {section}. Using options: "
3011
- f"eps={heps}, method={hmethod}, max_bond={hmax_bond}"
3012
- )
3013
-
3014
- section_tags = map(mps.site_tag, section)
3015
- kb.replace_with_svd(
3016
- section_tags,
3017
- (ul, ll),
3018
- heps,
3019
- inplace=True,
3020
- ltags="_LEFT",
3021
- rtags="_RIGHT",
3022
- method=hmethod,
3023
- max_bond=hmax_bond,
3024
- **compress_opts,
3025
- )
3026
-
3027
- compressed.append(section)
3028
-
3029
- @staticmethod
3030
- def _do_vertical_decomp(
3031
- mps,
3032
- kb,
3033
- section,
3034
- sysa,
3035
- sysb,
3036
- compressed,
3037
- ul,
3038
- ur,
3039
- ll,
3040
- lr,
3041
- vmethod,
3042
- vmax_bond,
3043
- veps,
3044
- verbosity,
3045
- **compress_opts,
3046
- ):
3047
- if section == sysa:
3048
- label = "A"
3049
- elif section == sysb:
3050
- label = "B"
3051
- else:
3052
- return
3053
-
3054
- section_tags = [mps.site_tag(i) for i in section]
3055
-
3056
- if section in compressed:
3057
- # ----U---- | <- vmax_bond
3058
- # -\ /- / ----U----
3059
- # L~~~~R ==> \ ==>
3060
- # -/ \- / ----D----
3061
- # ----D---- | <- vmax_bond
3062
-
3063
- # try and choose a sensible method
3064
- if vmethod is None:
3065
- left_sz = mps.bond_size(section[0] - 1, section[0])
3066
- right_sz = mps.bond_size(section[-1], section[-1] + 1)
3067
- if left_sz * right_sz <= 2**13:
3068
- # cholesky is not rank revealing
3069
- vmethod = "eigh" if vmax_bond else "cholesky"
3070
- else:
3071
- vmethod = "isvd"
3072
-
3073
- if verbosity >= 1:
3074
- print(
3075
- f"Performing vertical decomposition of section {label}, "
3076
- f"using options: eps={veps}, method={vmethod}, "
3077
- f"max_bond={vmax_bond}."
3078
- )
3079
-
3080
- # do vertical SVD
3081
- kb.replace_with_svd(
3082
- section_tags,
3083
- (ul, ur),
3084
- right_inds=(ll, lr),
3085
- eps=veps,
3086
- ltags="_UP",
3087
- rtags="_DOWN",
3088
- method=vmethod,
3089
- inplace=True,
3090
- max_bond=vmax_bond,
3091
- **compress_opts,
3092
- )
3093
-
3094
- # cut joined bond by reindexing to upper- and lower- ind_id.
3095
- kb.cut_between(
3096
- (mps.site_tag(section[0]), "_UP"),
3097
- (mps.site_tag(section[0]), "_DOWN"),
3098
- f"_tmp_ind_u{label}",
3099
- f"_tmp_ind_l{label}",
3100
- )
3101
-
3102
- else:
3103
- # just unfold and fuse physical indices:
3104
- # |
3105
- # -A-A-A-A-A-A-A- -AAAAAAA-
3106
- # | | | | | | | ===>
3107
- # -A-A-A-A-A-A-A- -AAAAAAA-
3108
- # |
3109
-
3110
- if verbosity >= 1:
3111
- print(f"Just vertical unfolding section {label}.")
3112
-
3113
- kb, sec = kb.partition(section_tags, inplace=True)
3114
- sec_l, sec_u = sec.partition("_KET", inplace=True)
3115
- T_UP = sec_u ^ all
3116
- T_UP.add_tag("_UP")
3117
- T_UP.fuse_(
3118
- {f"_tmp_ind_u{label}": [mps.site_ind(i) for i in section]}
3119
- )
3120
- T_DN = sec_l ^ all
3121
- T_DN.add_tag("_DOWN")
3122
- T_DN.fuse_(
3123
- {f"_tmp_ind_l{label}": [mps.site_ind(i) for i in section]}
3124
- )
3125
- kb |= T_UP
3126
- kb |= T_DN
3127
-
3128
- def partial_trace_compress(
3129
- self,
3130
- sysa,
3131
- sysb,
3132
- eps=1e-8,
3133
- method=("isvd", None),
3134
- max_bond=(None, 1024),
3135
- leave_short=True,
3136
- renorm=True,
3137
- lower_ind_id="b{}",
3138
- verbosity=0,
3139
- **compress_opts,
3140
- ):
3141
- r"""Perform a compressed partial trace using singular value
3142
- lateral then vertical decompositions of transfer matrix products::
3143
-
3144
-
3145
- .....sysa...... ...sysb....
3146
- o-o-o-o-A-A-A-A-A-A-A-A-o-o-B-B-B-B-B-B-o-o-o-o-o-o-o-o-o
3147
- | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
3148
-
3149
- ==> form inner product
3150
-
3151
- ............... ...........
3152
- o-o-o-o-A-A-A-A-A-A-A-A-o-o-B-B-B-B-B-B-o-o-o-o-o-o-o-o-o
3153
- | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
3154
- o-o-o-o-A-A-A-A-A-A-A-A-o-o-B-B-B-B-B-B-o-o-o-o-o-o-o-o-o
3155
-
3156
- ==> lateral SVD on each section
3157
-
3158
- .....sysa...... ...sysb....
3159
- /\ /\ /\ /\
3160
- ... ~~~E A~~~~~~~~~~~A E~E B~~~~~~~B E~~~ ...
3161
- \/ \/ \/ \/
3162
-
3163
- ==> vertical SVD and unfold on A & B
3164
-
3165
- | |
3166
- /-------A-------\ /-----B-----\
3167
- ... ~~~E E~E E~~~ ...
3168
- \-------A-------/ \-----B-----/
3169
- | |
3170
-
3171
- With various special cases including OBC or end spins included in
3172
- subsytems.
3173
-
3174
-
3175
- Parameters
3176
- ----------
3177
- sysa : sequence of int
3178
- The sites, which should be contiguous, defining subsystem A.
3179
- sysb : sequence of int
3180
- The sites, which should be contiguous, defining subsystem B.
3181
- eps : float or (float, float), optional
3182
- Tolerance(s) to use when compressing the subsystem transfer
3183
- matrices and vertically decomposing.
3184
- method : str or (str, str), optional
3185
- Method(s) to use for laterally compressing the state then
3186
- vertially compressing subsytems.
3187
- max_bond : int or (int, int), optional
3188
- The maximum bond to keep for laterally compressing the state then
3189
- vertially compressing subsytems.
3190
- leave_short : bool, optional
3191
- If True (the default), don't try to compress short sections.
3192
- renorm : bool, optional
3193
- If True (the default), renomalize the state so that ``tr(rho)==1``.
3194
- lower_ind_id : str, optional
3195
- The index id to create for the new density matrix, the upper_ind_id
3196
- is automatically taken as the current site_ind_id.
3197
- compress_opts : dict, optional
3198
- If given, supplied to ``partial_trace_compress`` to govern how
3199
- singular values are treated. See ``tensor_split``.
3200
- verbosity : {0, 1}, optional
3201
- How much information to print while performing the compressed
3202
- partial trace.
3203
-
3204
- Returns
3205
- -------
3206
- rho_ab : TensorNetwork
3207
- Density matrix tensor network with
3208
- ``outer_inds = ('k0', 'k1', 'b0', 'b1')`` for example.
3209
- """
3210
- N = self.L
3211
-
3212
- if (len(sysa) + len(sysb) == N) and not self.cyclic:
3213
- return self.bipartite_schmidt_state(len(sysa), get="rho")
3214
-
3215
- # parse horizontal and vertical svd tolerances and methods
3216
- try:
3217
- heps, veps = eps
3218
- except (ValueError, TypeError):
3219
- heps = veps = eps
3220
- try:
3221
- hmethod, vmethod = method
3222
- except (ValueError, TypeError):
3223
- hmethod = vmethod = method
3224
- try:
3225
- hmax_bond, vmax_bond = max_bond
3226
- except (ValueError, TypeError):
3227
- hmax_bond = vmax_bond = max_bond
3228
-
3229
- # the sequence of sites in each of the 'environment' sections
3230
- envm = range(max(sysa) + 1, min(sysb))
3231
- envl = range(0, min(sysa))
3232
- envr = range(max(sysb) + 1, N)
3233
-
3234
- # spread norm, and if not cyclic put in mixed canonical form, taking
3235
- # care that the orthogonality centre is in right place to use identity
3236
- k = self.copy()
3237
- k.left_canonize()
3238
- k.right_canonize(max(sysa) + (bool(envm) or bool(envr)))
3239
-
3240
- # form the inner product
3241
- b = k.conj()
3242
- k.add_tag("_KET")
3243
- b.add_tag("_BRA")
3244
- kb = k | b
3245
-
3246
- # label the various partitions
3247
- names = ("_ENVL", "_SYSA", "_ENVM", "_SYSB", "_ENVR")
3248
- for name, where in zip(names, (envl, sysa, envm, sysb, envr)):
3249
- if where:
3250
- kb.add_tag(name, where=map(self.site_tag, where), which="any")
3251
-
3252
- if self.cyclic:
3253
- # can combine right and left envs
3254
- sections = [envm, sysa, sysb, (*envr, *envl)]
3255
- else:
3256
- sections = [envm]
3257
- # if either system includes end, can ignore and use identity
3258
- if 0 not in sysa:
3259
- sections.append(sysa)
3260
- if N - 1 not in sysb:
3261
- sections.append(sysb)
3262
-
3263
- # ignore empty sections
3264
- sections = list(filter(len, sections))
3265
-
3266
- # figure out the various indices
3267
- ul_ur_ll_lrs = []
3268
- for section in sections:
3269
- # ...section[i]....
3270
- # ul[i] -o-o-o-o-o-o-o-o-o- ur[i]
3271
- # | | | | | | | | |
3272
- # ll[i] -o-o-o-o-o-o-o-o-o- lr[i]
3273
-
3274
- st_left = self.site_tag(section[0] - 1)
3275
- st_right = self.site_tag(section[0])
3276
- (ul,) = bonds(kb["_KET", st_left], kb["_KET", st_right])
3277
- (ll,) = bonds(kb["_BRA", st_left], kb["_BRA", st_right])
3278
-
3279
- st_left = self.site_tag(section[-1])
3280
- st_right = self.site_tag(section[-1] + 1)
3281
- (ur,) = bonds(kb["_KET", st_left], kb["_KET", st_right])
3282
- (lr,) = bonds(kb["_BRA", st_left], kb["_BRA", st_right])
3283
-
3284
- ul_ur_ll_lrs.append((ul, ur, ll, lr))
3285
-
3286
- # lateral compress sections if long
3287
- compressed = []
3288
- for section, (ul, _, ll, _) in zip(sections, ul_ur_ll_lrs):
3289
- self._do_lateral_compress(
3290
- self,
3291
- kb,
3292
- section,
3293
- leave_short,
3294
- ul,
3295
- ll,
3296
- heps,
3297
- hmethod,
3298
- hmax_bond,
3299
- verbosity,
3300
- compressed,
3301
- **compress_opts,
3302
- )
3303
-
3304
- # vertical compress and unfold system sections only
3305
- for section, (ul, ur, ll, lr) in zip(sections, ul_ur_ll_lrs):
3306
- self._do_vertical_decomp(
3307
- self,
3308
- kb,
3309
- section,
3310
- sysa,
3311
- sysb,
3312
- compressed,
3313
- ul,
3314
- ur,
3315
- ll,
3316
- lr,
3317
- vmethod,
3318
- vmax_bond,
3319
- veps,
3320
- verbosity,
3321
- **compress_opts,
3322
- )
3323
-
3324
- if not self.cyclic:
3325
- # check if either system is at end, and thus reduces to identities
3326
- #
3327
- # A-A-A-A-A-A-A-m-m-m- \-m-m-m-
3328
- # | | | | | | | | | | ... ==> | | | ...
3329
- # A-A-A-A-A-A-A-m-m-m- /-m-m-m-
3330
- #
3331
- if 0 in sysa:
3332
- # get neighbouring tensor
3333
- if envm:
3334
- try:
3335
- TU = TD = kb["_ENVM", "_LEFT"]
3336
- except KeyError:
3337
- # didn't lateral compress
3338
- TU = kb["_ENVM", "_KET", self.site_tag(envm[0])]
3339
- TD = kb["_ENVM", "_BRA", self.site_tag(envm[0])]
3340
- else:
3341
- TU = kb["_SYSB", "_UP"]
3342
- TD = kb["_SYSB", "_DOWN"]
3343
- (ubnd,) = kb["_KET", self.site_tag(sysa[-1])].bonds(TU)
3344
- (lbnd,) = kb["_BRA", self.site_tag(sysa[-1])].bonds(TD)
3345
-
3346
- # delete the A system
3347
- kb.delete("_SYSA")
3348
- kb.reindex_({ubnd: "_tmp_ind_uA", lbnd: "_tmp_ind_lA"})
3349
- else:
3350
- # or else replace the left or right envs with identites since
3351
- #
3352
- # >->->->-A-A-A-A- +-A-A-A-A-
3353
- # | | | | | | | | ... ==> | | | | |
3354
- # >->->->-A-A-A-A- +-A-A-A-A-
3355
- #
3356
- kb.replace_with_identity("_ENVL", inplace=True)
3357
-
3358
- if N - 1 in sysb:
3359
- # get neighbouring tensor
3360
- if envm:
3361
- try:
3362
- TU = TD = kb["_ENVM", "_RIGHT"]
3363
- except KeyError:
3364
- # didn't lateral compress
3365
- TU = kb["_ENVM", "_KET", self.site_tag(envm[-1])]
3366
- TD = kb["_ENVM", "_BRA", self.site_tag(envm[-1])]
3367
- else:
3368
- TU = kb["_SYSA", "_UP"]
3369
- TD = kb["_SYSA", "_DOWN"]
3370
- (ubnd,) = kb["_KET", self.site_tag(sysb[0])].bonds(TU)
3371
- (lbnd,) = kb["_BRA", self.site_tag(sysb[0])].bonds(TD)
3372
-
3373
- # delete the B system
3374
- kb.delete("_SYSB")
3375
- kb.reindex_({ubnd: "_tmp_ind_uB", lbnd: "_tmp_ind_lB"})
3376
- else:
3377
- kb.replace_with_identity("_ENVR", inplace=True)
3378
-
3379
- kb.reindex_(
3380
- {
3381
- "_tmp_ind_uA": self.site_ind("A"),
3382
- "_tmp_ind_lA": lower_ind_id.format("A"),
3383
- "_tmp_ind_uB": self.site_ind("B"),
3384
- "_tmp_ind_lB": lower_ind_id.format("B"),
3385
- }
3386
- )
3387
-
3388
- if renorm:
3389
- # normalize
3390
- norm = kb.trace(["kA", "kB"], ["bA", "bB"])
3391
-
3392
- ts = []
3393
- tags = kb.tags
3394
-
3395
- # check if we have system A
3396
- if "_SYSA" in tags:
3397
- ts.extend(kb[sysa[0]])
3398
-
3399
- # check if we have system B
3400
- if "_SYSB" in tags:
3401
- ts.extend(kb[sysb[0]])
3402
-
3403
- # If we dont' have either (OBC with both at ends) use middle envm
3404
- if len(ts) == 0:
3405
- ts.extend(kb[envm[0]])
3406
-
3407
- nt = len(ts)
3408
-
3409
- if verbosity > 0:
3410
- print(f"Renormalizing for norm {norm} among {nt} tensors.")
3411
-
3412
- # now spread the norm out among tensors
3413
- for t in ts:
3414
- t.modify(data=t.data / norm ** (1 / nt))
3415
-
3416
- return kb
3417
-
3418
- def logneg_subsys(
3419
- self,
3420
- sysa,
3421
- sysb,
3422
- compress_opts=None,
3423
- approx_spectral_opts=None,
3424
- verbosity=0,
3425
- approx_thresh=2**12,
3426
- ):
3427
- r"""Compute the logarithmic negativity between subsytem blocks, e.g.::
3428
-
3429
- sysa sysb
3430
- ......... .....
3431
- ... -o-o-o-o-o-o-A-A-A-A-A-o-o-o-B-B-B-o-o-o-o-o-o-o- ...
3432
- | | | | | | | | | | | | | | | | | | | | | | | |
3433
-
3434
- Parameters
3435
- ----------
3436
- sysa : sequence of int
3437
- The sites, which should be contiguous, defining subsystem A.
3438
- sysb : sequence of int
3439
- The sites, which should be contiguous, defining subsystem B.
3440
- eps : float, optional
3441
- Tolerance to use when compressing the subsystem transfer matrices.
3442
- method : str or (str, str), optional
3443
- Method(s) to use for laterally compressing the state then
3444
- vertially compressing subsytems.
3445
- compress_opts : dict, optional
3446
- If given, supplied to ``partial_trace_compress`` to govern how
3447
- singular values are treated. See ``tensor_split``.
3448
- approx_spectral_opts
3449
- Supplied to :func:`~quimb.approx_spectral_function`.
3450
-
3451
- Returns
3452
- -------
3453
- ln : float
3454
- The logarithmic negativity.
3455
-
3456
- See Also
3457
- --------
3458
- MatrixProductState.partial_trace_compress, approx_spectral_function
3459
- """
3460
- if not self.cyclic and (len(sysa) + len(sysb) == self.L):
3461
- # pure bipartition with OBC
3462
- psi = self.bipartite_schmidt_state(len(sysa), get="ket-dense")
3463
- d = round(psi.shape[0] ** 0.5)
3464
- return qu.logneg(psi, [d, d])
3465
-
3466
- compress_opts = ensure_dict(compress_opts)
3467
- approx_spectral_opts = ensure_dict(approx_spectral_opts)
3468
-
3469
- # set the default verbosity for each method
3470
- compress_opts.setdefault("verbosity", verbosity)
3471
- approx_spectral_opts.setdefault("verbosity", verbosity)
3472
-
3473
- # form the compressed density matrix representation
3474
- rho_ab = self.partial_trace_compress(sysa, sysb, **compress_opts)
3475
-
3476
- # view it as an operator
3477
- rho_ab_pt_lo = rho_ab.aslinearoperator(["kA", "bB"], ["bA", "kB"])
3478
-
3479
- if rho_ab_pt_lo.shape[0] <= approx_thresh:
3480
- tr_norm = norm_trace_dense(rho_ab_pt_lo.to_dense(), isherm=True)
3481
- else:
3482
- # estimate its spectrum and sum the abs(eigenvalues)
3483
- tr_norm = qu.approx_spectral_function(
3484
- rho_ab_pt_lo, abs, **approx_spectral_opts
3485
- )
3486
-
3487
- # clip below 0
3488
- return max(0, log2(tr_norm))
3489
-
3490
- @convert_cur_orthog
3491
- def measure(
3492
- self,
3493
- site,
3494
- remove=False,
3495
- outcome=None,
3496
- renorm=True,
3497
- info=None,
3498
- get=None,
3499
- inplace=False,
3500
- ):
3501
- r"""Measure this MPS at ``site``, including projecting the state.
3502
- Optionally remove the site afterwards, yielding an MPS with one less
3503
- site. In either case the orthogonality center of the returned MPS is
3504
- ``min(site, new_L - 1)``.
3505
-
3506
- Parameters
3507
- ----------
3508
- site : int
3509
- The site to measure.
3510
- remove : bool, optional
3511
- Whether to remove the site completely after projecting the
3512
- measurement. If ``True``, sites greater than ``site`` will be
3513
- retagged and reindex one down, and the MPS will have one less site.
3514
- E.g::
3515
-
3516
- 0-1-2-3-4-5-6
3517
- / / / - measure and remove site 3
3518
- 0-1-2-4-5-6
3519
- - reindex sites (4, 5, 6) to (3, 4, 5)
3520
- 0-1-2-3-4-5
3521
-
3522
- outcome : None or int, optional
3523
- Specify the desired outcome of the measurement. If ``None``, it
3524
- will be randomly sampled according to the local density matrix.
3525
- renorm : bool, optional
3526
- Whether to renormalize the state post measurement.
3527
- info : dict, optional
3528
- If given, will be used to infer and store various extra
3529
- information. Currently the key "cur_orthog" is used to store the
3530
- current orthogonality center.
3531
- get : {None, 'outcome'}, optional
3532
- If ``'outcome'``, simply return the outcome, and don't perform any
3533
- projection.
3534
- inplace : bool, optional
3535
- Whether to perform the measurement in place or not.
3536
-
3537
- Returns
3538
- -------
3539
- outcome : int
3540
- The measurement outcome, drawn from ``range(phys_dim)``.
3541
- psi : MatrixProductState
3542
- The measured state, if ``get != 'outcome'``.
3543
- """
3544
- if self.cyclic:
3545
- raise ValueError("Not supported on cyclic MPS yet.")
3546
-
3547
- tn = self if inplace else self.copy()
3548
- L = tn.L
3549
- d = self.phys_dim(site)
3550
-
3551
- # make sure MPS is canonicalized
3552
- tn.canonicalize_(site, info=info)
3553
-
3554
- # local tensor and physical dim
3555
- t = tn[site]
3556
- ind = tn.site_ind(site)
3557
-
3558
- # diagonal of reduced density matrix = probs
3559
- tii = t.contract(t.H, output_inds=(ind,))
3560
- p = do("real", tii.data)
3561
-
3562
- if outcome is None:
3563
- # sample an outcome
3564
- outcome = do("random.choice", do("arange", d, like=p), p=p)
3565
-
3566
- if get == "outcome":
3567
- return outcome
3568
-
3569
- # project the outcome and renormalize
3570
- t.isel_({ind: outcome})
3571
-
3572
- if renorm:
3573
- t.modify(data=t.data / p[outcome] ** 0.5)
3574
-
3575
- if remove:
3576
- # contract the projected tensor into neighbor
3577
- if site == L - 1:
3578
- tn ^= slice(site - 1, site + 1)
3579
- else:
3580
- tn ^= slice(site, site + 2)
3581
-
3582
- # adjust structure for one less spin
3583
- for i in range(site + 1, L):
3584
- tn[i].reindex_({tn.site_ind(i): tn.site_ind(i - 1)})
3585
- tn[i].retag_({tn.site_tag(i): tn.site_tag(i - 1)})
3586
- tn._L = L - 1
3587
- else:
3588
- # simply re-expand tensor dimensions (with zeros)
3589
- t.new_ind(ind, size=d, axis=-1)
3590
-
3591
- return outcome, tn
3592
-
3593
- measure_ = functools.partialmethod(measure, inplace=True)
3594
-
3595
- def sample_configuration(self, seed=None, info=None):
3596
- """Sample a configuration from this MPS.
3597
-
3598
- Parameters
3599
- ----------
3600
- seed : None, int, or np.random.Generator, optional
3601
- A random seed or generator to use.
3602
- info : dict, optional
3603
- If given, will be used to infer and store various extra
3604
- information. Currently the key "cur_orthog" is used to store the
3605
- current orthogonality center.
3606
- """
3607
- import numpy as np
3608
-
3609
- # if seed is already a generator this simply returns it
3610
- rng = np.random.default_rng(seed)
3611
-
3612
- # right canonicalize
3613
- psi = self.canonicalize(0, info=info)
3614
-
3615
- config = []
3616
- omega = 1.0
3617
- for i in range(psi.L):
3618
- # form local density matrix
3619
- ki = psi[i]
3620
- bi = ki.H
3621
- ix = psi.site_ind(i)
3622
- # contract diagonal to get probabilities
3623
- pi = (ki & bi).contract(output_inds=[ix]).data
3624
-
3625
- # sample outcome using numpy
3626
- pi = do("to_numpy", pi).real
3627
- pi /= pi.sum()
3628
- xi = rng.choice(pi.size, p=pi)
3629
- config.append(xi)
3630
- # track local probability
3631
- omega *= pi[xi]
3632
-
3633
- # project outcome
3634
- psi.isel_({ix: xi})
3635
- if i < psi.L - 1:
3636
- # and absorb projected site into next site
3637
- psi.contract_tags_([psi.site_tag(i), psi.site_tag(i + 1)])
3638
-
3639
- return config, omega
3640
-
3641
- def sample(self, C, seed=None, info=None):
3642
- """Generate ``C`` samples rom this MPS, along with their probabilities.
3643
-
3644
- Parameters
3645
- ----------
3646
- C : int
3647
- The number of samples to generate.
3648
- seed : None, int, or np.random.Generator, optional
3649
- A random seed or generator to use.
3650
- info : dict, optional
3651
- If given, will be used to infer and store various extra
3652
- information. Currently the key "cur_orthog" is used to store the
3653
- current orthogonality center.
3654
-
3655
- Yields
3656
- ------
3657
- config : sequence of int
3658
- The sample configuration.
3659
- omega : float
3660
- The probability of this configuration.
3661
- """
3662
-
3663
- if info is None:
3664
- info = {}
3665
-
3666
- # do right canonicalization once (supplying info avoids re-performing)
3667
- psi0 = self.canonicalize(0, info=info)
3668
-
3669
- rng = np.random.default_rng(seed)
3670
- for _ in range(C):
3671
- yield psi0.sample_configuration(seed=rng, info=info)
3672
-
3673
-
3674
- class MatrixProductOperator(TensorNetwork1DOperator, TensorNetwork1DFlat):
3675
- """Initialise a matrix product operator, with auto labelling and tagging.
3676
-
3677
- Parameters
3678
- ----------
3679
- arrays : sequence of arrays
3680
- The tensor arrays to form into a MPO.
3681
- sites : sequence of int, optional
3682
- Construct the MPO on these sites only. If not given, enumerate from
3683
- zero. Should be monotonically increasing and match ``arrays``.
3684
- L : int, optional
3685
- The number of sites the MPO should be defined on. If not given, this is
3686
- taken as the max ``sites`` value plus one (i.e.g the number of arrays
3687
- if ``sites`` is not given).
3688
- shape : str, optional
3689
- String specifying layout of the tensors. E.g. 'lrud' (the default)
3690
- indicates the shape corresponds left-bond, right-bond, 'up' physical
3691
- index, 'down' physical index.
3692
- End tensors have either 'l' or 'r' dropped from the string.
3693
- tags : str or sequence of str, optional
3694
- Global tags to attach to all tensors.
3695
- upper_ind_id : str
3696
- A string specifiying how to label the upper physical site indices.
3697
- Should contain a ``'{}'`` placeholder. It is used to generate the
3698
- actual indices like: ``map(upper_ind_id.format, range(len(arrays)))``.
3699
- lower_ind_id : str
3700
- A string specifiying how to label the lower physical site indices.
3701
- Should contain a ``'{}'`` placeholder. It is used to generate the
3702
- actual indices like: ``map(lower_ind_id.format, range(len(arrays)))``.
3703
- site_tag_id : str
3704
- A string specifiying how to tag the tensors at each site. Should
3705
- contain a ``'{}'`` placeholder. It is used to generate the actual tags
3706
- like: ``map(site_tag_id.format, range(len(arrays)))``.
3707
- """
3708
-
3709
- _EXTRA_PROPS = (
3710
- "_site_tag_id",
3711
- "_upper_ind_id",
3712
- "_lower_ind_id",
3713
- "cyclic",
3714
- "_L",
3715
- )
3716
-
3717
- def __init__(
3718
- self,
3719
- arrays,
3720
- *,
3721
- sites=None,
3722
- L=None,
3723
- shape="lrud",
3724
- tags=None,
3725
- upper_ind_id="k{}",
3726
- lower_ind_id="b{}",
3727
- site_tag_id="I{}",
3728
- **tn_opts,
3729
- ):
3730
- # short-circuit for copying
3731
- if isinstance(arrays, MatrixProductOperator):
3732
- super().__init__(arrays)
3733
- return
3734
-
3735
- arrays = tuple(arrays)
3736
-
3737
- if sites is None:
3738
- # assume dense
3739
- sites = range(len(arrays))
3740
- if L is None:
3741
- L = len(arrays)
3742
- num_sites = L
3743
- else:
3744
- sites = tuple(sites)
3745
- if L is None:
3746
- L = max(sites) + 1
3747
- num_sites = len(sites)
3748
-
3749
- self._L = L
3750
- self._upper_ind_id = upper_ind_id
3751
- self._lower_ind_id = lower_ind_id
3752
- self._site_tag_id = site_tag_id
3753
- self.cyclic = ops.ndim(arrays[0]) == 4
3754
-
3755
- # this is the perm needed to bring the arrays from
3756
- # their current `shape`, to the desired 'lrud' order
3757
- lrud_order = tuple(map(shape.find, "lrud"))
3758
-
3759
- tensors = []
3760
- tags = tags_to_oset(tags)
3761
- bonds = [rand_uuid() for _ in range(num_sites)]
3762
- bonds.append(bonds[0])
3763
-
3764
- for i, (site, array) in enumerate(zip(sites, arrays)):
3765
- inds = []
3766
-
3767
- if (i == 0) and not self.cyclic:
3768
- # only right bond
3769
- order = tuple(shape.replace("l", "").find(x) for x in "rud")
3770
- inds.append(bonds[i + 1])
3771
- elif (i == num_sites - 1) and not self.cyclic:
3772
- # only left bond
3773
- order = tuple(shape.replace("r", "").find(x) for x in "lud")
3774
- inds.append(bonds[i])
3775
- else:
3776
- order = lrud_order
3777
- # both bonds
3778
- inds.append(bonds[i])
3779
- inds.append(bonds[i + 1])
3780
-
3781
- # physical indices
3782
- inds.append(upper_ind_id.format(site))
3783
- inds.append(lower_ind_id.format(site))
3784
-
3785
- tensors.append(
3786
- Tensor(
3787
- data=transpose(array, order),
3788
- inds=inds,
3789
- tags=tags | oset([site_tag_id.format(site)]),
3790
- )
3791
- )
3792
-
3793
- super().__init__(tensors, virtual=True, **tn_opts)
3794
-
3795
- @classmethod
3796
- def from_fill_fn(
3797
- cls,
3798
- fill_fn,
3799
- L,
3800
- bond_dim,
3801
- phys_dim=2,
3802
- sites=None,
3803
- cyclic=False,
3804
- shape="lrud",
3805
- tags=None,
3806
- upper_ind_id="k{}",
3807
- lower_ind_id="b{}",
3808
- site_tag_id="I{}",
3809
- ):
3810
- """Create an MPO by supplying a 'filling' function to generate the data
3811
- for each site.
3812
-
3813
- Parameters
3814
- ----------
3815
- fill_fn : callable
3816
- A function with signature
3817
- ``fill_fn(shape : tuple[int]) -> array_like``.
3818
- L : int
3819
- The number of sites.
3820
- bond_dim : int
3821
- The bond dimension.
3822
- phys_dim : int or Sequence[int], optional
3823
- The physical dimension(s) of each site, if a sequence it will be
3824
- cycled over.
3825
- sites : None or sequence of int, optional
3826
- Construct the MPO on these sites only. If not given, enumerate from
3827
- zero.
3828
- cyclic : bool, optional
3829
- Whether the MPO should be cyclic (periodic).
3830
- shape : str, optional
3831
- String specifying layout of the tensors. E.g. 'lrud' (the default)
3832
- indicates the shape corresponds left-bond, right-bond, 'up'
3833
- physical index, 'down' physical index. End tensors have either
3834
- 'l' or 'r' dropped from the string.
3835
- tags : str or sequence of str, optional
3836
- Global tags to attach to all tensors.
3837
- upper_ind_id : str
3838
- A string specifiying how to label the upper physical site indices.
3839
- Should contain a ``'{}'`` placeholder.
3840
- lower_ind_id : str
3841
- A string specifiying how to label the lower physical site indices.
3842
- Should contain a ``'{}'`` placeholder.
3843
- site_tag_id : str, optional
3844
- How to tag the physical sites. Should contain a ``'{}'``
3845
- placeholder.
3846
-
3847
- Returns
3848
- -------
3849
- MatrixProductState
3850
- """
3851
- if set(shape) - {"l", "r", "u", "d"}:
3852
- raise ValueError(f"Invalid shape string: {shape}.")
3853
-
3854
- # check for site varying physical dimensions
3855
- if isinstance(phys_dim, Integral):
3856
- phys_dims = itertools.repeat(phys_dim)
3857
- else:
3858
- phys_dims = itertools.cycle(phys_dim)
3859
-
3860
- mpo = cls.new(
3861
- L=L,
3862
- cyclic=cyclic,
3863
- site_tag_id=site_tag_id,
3864
- upper_ind_id=upper_ind_id,
3865
- lower_ind_id=lower_ind_id,
3866
- )
3867
-
3868
- # which sites are actually present
3869
- if sites is None:
3870
- sites = range(L)
3871
- else:
3872
- sites = tuple(sites)
3873
- num_sites = len(sites)
3874
-
3875
- global_tags = tags_to_oset(tags)
3876
- bonds = [rand_uuid() for _ in range(num_sites)]
3877
- bonds.append(bonds[0])
3878
-
3879
- for i, site in enumerate(sites):
3880
- p = next(phys_dims)
3881
- inds = []
3882
- data_shape = []
3883
- for c in shape:
3884
- if c == "l":
3885
- if (i - 1) >= 0 or cyclic:
3886
- inds.append(bonds[i])
3887
- data_shape.append(bond_dim)
3888
- elif c == "r":
3889
- if (i + 1) < L or cyclic:
3890
- inds.append(bonds[i + 1])
3891
- data_shape.append(bond_dim)
3892
- elif c == "u":
3893
- inds.append(upper_ind_id.format(site))
3894
- data_shape.append(p)
3895
- else: # c == "d"
3896
- inds.append(lower_ind_id.format(site))
3897
- data_shape.append(p)
3898
- data = fill_fn(data_shape)
3899
- tags = global_tags | oset((site_tag_id.format(site),))
3900
- mpo |= Tensor(data, inds=inds, tags=tags)
3901
-
3902
- return mpo
3903
-
3904
- @classmethod
3905
- def from_dense(
3906
- cls,
3907
- A,
3908
- dims=2,
3909
- sites=None,
3910
- L=None,
3911
- tags=None,
3912
- site_tag_id="I{}",
3913
- upper_ind_id="k{}",
3914
- lower_ind_id="b{}",
3915
- **split_opts,
3916
- ):
3917
- """Build an MPO from a raw dense matrix.
3918
-
3919
- Parameters
3920
- ----------
3921
- A : array
3922
- The dense operator, it should be reshapeable to ``(*dims, *dims)``.
3923
- dims : int, sequence of int, optional
3924
- The physical subdimensions of the operator. If any integer, assume
3925
- all sites have the same dimension. If a sequence, the dimension of
3926
- each site. Default is 2.
3927
- sites : sequence of int, optional
3928
- The sites to place the operator on. If None, will place it on
3929
- first `len(dims)` sites.
3930
- L : int, optional
3931
- The total number of sites in the MPO, if the operator represents
3932
- only a subset.
3933
- tags : str or sequence of str, optional
3934
- Global tags to attach to all tensors.
3935
- site_tag_id : str, optional
3936
- The string to use to label the site tags.
3937
- upper_ind_id : str, optional
3938
- The string to use to label the upper physical indices.
3939
- lower_ind_id : str, optional
3940
- The string to use to label the lower physical indices.
3941
- split_opts
3942
- Supplied to :func:`~quimb.tensor.tensor_core.tensor_split`.
3943
-
3944
- Returns
3945
- -------
3946
- MatrixProductOperator
3947
- """
3948
- set_default_compress_mode(split_opts)
3949
- # ensure compression is canonical / optimal
3950
- split_opts.setdefault("absorb", "right")
3951
-
3952
- # make sure array_like
3953
- A = ops.asarray(A)
3954
-
3955
- if isinstance(dims, Integral):
3956
- # assume all sites have the same dimension
3957
- ng = round(log(size(A), dims) / 2)
3958
- dims = (dims,) * ng
3959
- else:
3960
- dims = tuple(dims)
3961
- ng = len(dims)
3962
-
3963
- if sites is None:
3964
- sorted_sites = sites = range(ng)
3965
- else:
3966
- sorted_sites = sorted(sites)
3967
-
3968
- if L is None:
3969
- L = max(sites) + 1
3970
-
3971
- # create a bare MPO TN object
3972
- mpo = cls.new(
3973
- L=L,
3974
- cyclic=False,
3975
- upper_ind_id=upper_ind_id,
3976
- lower_ind_id=lower_ind_id,
3977
- site_tag_id=site_tag_id,
3978
- )
3979
-
3980
- # initial inds and tensor contains desired site order ...
3981
- uix = [mpo.upper_ind(i) for i in sites]
3982
- lix = [mpo.lower_ind(i) for i in sites]
3983
- tm = Tensor(data=reshape(A, (*dims, *dims)), inds=uix + lix)
3984
-
3985
- # ... but want to create MPO in sorted site order
3986
- uix = [mpo.upper_ind(i) for i in sorted_sites]
3987
- lix = [mpo.lower_ind(i) for i in sorted_sites]
3988
-
3989
- for i, site in enumerate(sorted_sites[:-1]):
3990
- # progressively split off one more pair of physical indices
3991
- tl, tm = tm.split(
3992
- left_inds=None,
3993
- right_inds=uix[i + 1 :] + lix[i + 1 :],
3994
- ltags=mpo.site_tag(site),
3995
- get="tensors",
3996
- **split_opts,
3997
- )
3998
- # add left tensor
3999
- mpo |= tl
4000
-
4001
- # add final right tensor
4002
- tm.add_tag(mpo.site_tag(sorted_sites[-1]))
4003
- mpo |= tm
4004
-
4005
- # add global tags
4006
- if tags is not None:
4007
- mpo.add_tag(tags)
4008
-
4009
- return mpo
4010
-
4011
- def fill_empty_sites(
4012
- self, mode="full", phys_dim=None, fill_array=None, inplace=False
4013
- ):
4014
- """Fill any empty sites of this MPO with identity tensors, adding
4015
- size 1 bonds or draping existing bonds where necessary such that the
4016
- resulting tensor has nearest neighbor bonds only.
4017
-
4018
- Parameters
4019
- ----------
4020
- mode : {'full', 'minimal'}, optional
4021
- Whether to fill in all sites, including at either end, or simply
4022
- the minimal range covering the min to max current sites present.
4023
- phys_dim : int, optional
4024
- The physical dimension of the identity tensors to add. If not
4025
- specified, will use the upper physical dimension of the first
4026
- present site.
4027
- fill_array : array, optional
4028
- The array to use for the identity tensors. If not specified, will
4029
- use the identity array of the same dtype as the first present site.
4030
- inplace : bool, optional
4031
- Whether to perform the operation inplace.
4032
-
4033
- Returns
4034
- -------
4035
- MatrixProductOperator
4036
- The modified MPO.
4037
- """
4038
- mpo = self if inplace else self.copy()
4039
-
4040
- sites_present = tuple(mpo.gen_sites_present())
4041
- sites_present_set = set(sites_present)
4042
- sitei = sites_present[0]
4043
- sitef = sites_present[-1]
4044
-
4045
- if fill_array is None:
4046
- t0 = mpo[sitei]
4047
- if phys_dim is None:
4048
- d = mpo.phys_dim(sitei)
4049
- fill_array = do("eye", d, dtype=t0.dtype, like=t0.data)
4050
-
4051
- if mode == "full":
4052
- sites_to_add = [
4053
- site for site in range(mpo.L) if site not in sites_present_set
4054
- ]
4055
- elif mode == "minimal":
4056
- sites_to_add = [
4057
- site
4058
- for site in range(sitei, sitef + 1)
4059
- if site not in sites_present_set
4060
- ]
4061
- else:
4062
- sites_to_add = list(mode)
4063
- sites_to_add_set = set(sites_to_add)
4064
-
4065
- new_sites = list(sites_present)
4066
- new_sites.extend(sites_to_add)
4067
- new_sites.sort()
4068
-
4069
- # add desired identites
4070
- for site in sites_to_add:
4071
- mpo |= Tensor(
4072
- data=fill_array,
4073
- inds=(mpo.upper_ind(site), mpo.lower_ind(site)),
4074
- tags=mpo.site_tag(site),
4075
- )
4076
-
4077
- # connect up between existing tensors
4078
- for si, sj in pairwise(sites_present):
4079
- if bonds(mpo[si], mpo[sj]):
4080
- # existing bond -> drape it thru
4081
- sl = si
4082
- for k in range(si + 1, sj):
4083
- if k in sites_to_add_set:
4084
- mpo.drape_bond_between_(sl, sj, k)
4085
- sl = k
4086
-
4087
- else:
4088
- # no bond -> just add bond dim 1
4089
- sl = si
4090
- for k in range(si, sj - 1):
4091
- if k in sites_to_add_set:
4092
- new_bond(mpo[sl], mpo[k])
4093
- sl = k
4094
- new_bond(mpo[sl], mpo[sj])
4095
-
4096
- # connect up on either side of existing patch
4097
- for si, sj in pairwise(new_sites):
4098
- if (sj <= sitei) or (si >= sitef):
4099
- new_bond(mpo[si], mpo[sj])
4100
-
4101
- return mpo
4102
-
4103
- fill_empty_sites_ = functools.partialmethod(fill_empty_sites, inplace=True)
4104
-
4105
- def add_MPO(self, other, inplace=False, **kwargs):
4106
- return tensor_network_ag_sum(self, other, inplace=inplace, **kwargs)
4107
-
4108
- add_MPO_ = functools.partialmethod(add_MPO, inplace=True)
4109
-
4110
- def _apply_mps(
4111
- self, other, compress=False, contract=True, **compress_opts
4112
- ):
4113
- return tensor_network_apply_op_vec(
4114
- A=self,
4115
- x=other,
4116
- compress=compress,
4117
- contract=contract,
4118
- **compress_opts,
4119
- )
4120
-
4121
- def _apply_mpo(
4122
- self, other, compress=False, contract=True, **compress_opts
4123
- ):
4124
- return tensor_network_apply_op_op(
4125
- A=self,
4126
- B=other,
4127
- contract=contract,
4128
- compress=compress,
4129
- **compress_opts,
4130
- )
4131
-
4132
- def apply(self, other, compress=False, **compress_opts):
4133
- r"""Act with this MPO on another MPO or MPS, such that the resulting
4134
- object has the same tensor network structure/indices as ``other``.
4135
-
4136
- For an MPS::
4137
-
4138
- | | | | | | | | | | | | | | | | | |
4139
- self: A-A-A-A-A-A-A-A-A-A-A-A-A-A-A-A-A-A
4140
- | | | | | | | | | | | | | | | | | |
4141
- other: x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x
4142
-
4143
- -->
4144
-
4145
- | | | | | | | | | | | | | | | | | | <- other.site_ind_id
4146
- out: y=y=y=y=y=y=y=y=y=y=y=y=y=y=y=y=y=y
4147
-
4148
- For an MPO::
4149
-
4150
- | | | | | | | | | | | | | | | | | |
4151
- self: A-A-A-A-A-A-A-A-A-A-A-A-A-A-A-A-A-A
4152
- | | | | | | | | | | | | | | | | | |
4153
- other: B-B-B-B-B-B-B-B-B-B-B-B-B-B-B-B-B-B
4154
- | | | | | | | | | | | | | | | | | |
4155
-
4156
- -->
4157
-
4158
- | | | | | | | | | | | | | | | | | | <- other.upper_ind_id
4159
- out: C=C=C=C=C=C=C=C=C=C=C=C=C=C=C=C=C=C
4160
- | | | | | | | | | | | | | | | | | | <- other.lower_ind_id
4161
-
4162
- The resulting TN will have the same structure/indices as ``other``, but
4163
- probably with larger bonds (depending on compression).
4164
-
4165
-
4166
- Parameters
4167
- ----------
4168
- other : MatrixProductOperator or MatrixProductState
4169
- The object to act on.
4170
- compress : bool, optional
4171
- Whether to compress the resulting object.
4172
- compress_opts
4173
- Supplied to :meth:`TensorNetwork1DFlat.compress`.
4174
-
4175
- Returns
4176
- -------
4177
- MatrixProductOperator or MatrixProductState
4178
- """
4179
- if isinstance(other, MatrixProductState):
4180
- return self._apply_mps(other, compress=compress, **compress_opts)
4181
- elif isinstance(other, MatrixProductOperator):
4182
- return self._apply_mpo(other, compress=compress, **compress_opts)
4183
- else:
4184
- raise TypeError(
4185
- "Can only Dot with a MatrixProductOperator or a "
4186
- f"MatrixProductState, got {type(other)}"
4187
- )
4188
-
4189
- dot = apply
4190
-
4191
- def permute_arrays(self, shape="lrud"):
4192
- """Permute the indices of each tensor in this MPO to match ``shape``.
4193
- This doesn't change how the overall object interacts with other tensor
4194
- networks but may be useful for extracting the underlying arrays
4195
- consistently. This is an inplace operation.
4196
-
4197
- Parameters
4198
- ----------
4199
- shape : str, optional
4200
- A permutation of ``'lrud'`` specifying the desired order of the
4201
- left, right, upper and lower (down) indices respectively.
4202
- """
4203
- for i in self.gen_sites_present():
4204
- temp = self[i]
4205
- if len(temp._inds) == 2:
4206
- inds = {}
4207
- else:
4208
- inds = {"u": self.upper_ind(i), "d": self.lower_ind(i)}
4209
- if self.cyclic or i > 0:
4210
- inds["l"] = self.bond(i, (i - 1) % self.L)
4211
- if self.cyclic or i < self.L - 1:
4212
- inds["r"] = self.bond(i, (i + 1) % self.L)
4213
- inds = [inds[s] for s in shape if s in inds]
4214
- self[i].transpose_(*inds)
4215
-
4216
- def trace(self, left_inds=None, right_inds=None):
4217
- """Take the trace of this MPO."""
4218
- if left_inds is None:
4219
- left_inds = map(self.upper_ind, self.gen_sites_present())
4220
- if right_inds is None:
4221
- right_inds = map(self.lower_ind, self.gen_sites_present())
4222
-
4223
- return super().trace(left_inds, right_inds)
4224
-
4225
- def partial_transpose(self, sysa, inplace=False):
4226
- """Perform the partial transpose on this MPO by swapping the bra and
4227
- ket indices on sites in ``sysa``.
4228
-
4229
- Parameters
4230
- ----------
4231
- sysa : sequence of int or int
4232
- The sites to transpose indices on.
4233
- inplace : bool, optional
4234
- Whether to perform the partial transposition inplace.
4235
-
4236
- Returns
4237
- -------
4238
- MatrixProductOperator
4239
- """
4240
- tn = self if inplace else self.copy()
4241
-
4242
- if isinstance(sysa, Integral):
4243
- sysa = (sysa,)
4244
-
4245
- tmp_ind_id = "__tmp_{}__"
4246
-
4247
- tn.reindex_({tn.upper_ind(i): tmp_ind_id.format(i) for i in sysa})
4248
- tn.reindex_({tn.lower_ind(i): tn.upper_ind(i) for i in sysa})
4249
- tn.reindex_({tmp_ind_id.format(i): tn.lower_ind(i) for i in sysa})
4250
- return tn
4251
-
4252
- def rand_state(self, bond_dim, **mps_opts):
4253
- """Get a random vector matching this MPO."""
4254
- return qu.tensor.MPS_rand_state(
4255
- self.L,
4256
- bond_dim=bond_dim,
4257
- phys_dim=[self.phys_dim(i) for i in self.gen_sites_present()],
4258
- dtype=self.dtype,
4259
- cyclic=self.cyclic,
4260
- **mps_opts,
4261
- )
4262
-
4263
- def identity(self, **mpo_opts):
4264
- """Get a identity matching this MPO."""
4265
- return qu.tensor.MPO_identity_like(self, **mpo_opts)
4266
-
4267
- def show(self, max_width=None):
4268
- l1 = ""
4269
- l2 = ""
4270
- l3 = ""
4271
- num_can_l, num_can_r = self.count_canonized()
4272
- for i in range(self.L - 1):
4273
- bdim = self.bond_size(i, i + 1)
4274
- strl = len(str(bdim))
4275
- l1 += f"│{bdim}"
4276
- l2 += (
4277
- ">"
4278
- if i < num_can_l
4279
- else "<"
4280
- if i >= self.L - num_can_r
4281
- else "●"
4282
- ) + ("─" if bdim < 100 else "━") * strl
4283
- l3 += "│" + " " * strl
4284
-
4285
- l1 += "│"
4286
- l2 += "<" if num_can_r > 0 else "●"
4287
- l3 += "│"
4288
-
4289
- if self.cyclic:
4290
- bdim = self.bond_size(0, self.L - 1)
4291
- bnd_str = ("─" if bdim < 100 else "━") * strl
4292
- l1 = f" {bdim}{l1}{bdim} "
4293
- l2 = f"+{bnd_str}{l2}{bnd_str}+"
4294
- l3 = f" {' ' * strl}{l3}{' ' * strl} "
4295
-
4296
- print_multi_line(l1, l2, l3, max_width=max_width)
4297
-
4298
-
4299
- class Dense1D(TensorNetwork1DVector):
4300
- """Mimics other 1D tensor network structures, but really just keeps the
4301
- full state in a single tensor. This allows e.g. applying gates in the same
4302
- way for quantum circuit simulation as lazily represented hilbert spaces.
4303
-
4304
- Parameters
4305
- ----------
4306
- array : array_like
4307
- The full hilbert space vector - assumed to be made of equal hilbert
4308
- spaces each of size ``phys_dim`` and will be reshaped as such.
4309
- phys_dim : int, optional
4310
- The hilbert space size of each site, default: 2.
4311
- tags : sequence of str, optional
4312
- Extra tags to add to the tensor network.
4313
- site_ind_id : str, optional
4314
- String formatter describing how to label the site indices.
4315
- site_tag_id : str, optional
4316
- String formatter describing how to label the site tags.
4317
- tn_opts
4318
- Supplied to :class:`~quimb.tensor.tensor_core.TensorNetwork`.
4319
- """
4320
-
4321
- _EXTRA_PROPS = (
4322
- "_site_ind_id",
4323
- "_site_tag_id",
4324
- "_L",
4325
- )
4326
-
4327
- def __init__(
4328
- self,
4329
- array,
4330
- phys_dim=2,
4331
- tags=None,
4332
- site_ind_id="k{}",
4333
- site_tag_id="I{}",
4334
- **tn_opts,
4335
- ):
4336
- # copy short-circuit
4337
- if isinstance(array, Dense1D):
4338
- super().__init__(array)
4339
- return
4340
-
4341
- # work out number of sites and sub-dimensions etc.
4342
- self._L = qu.infer_size(array, base=phys_dim)
4343
- dims = [phys_dim] * self.L
4344
- data = ops.asarray(array).reshape(*dims)
4345
-
4346
- # process site indices
4347
- self._site_ind_id = site_ind_id
4348
- site_inds = [self.site_ind(i) for i in range(self.L)]
4349
-
4350
- # process site tags
4351
- self._site_tag_id = site_tag_id
4352
- site_tags = oset(self.site_tag(i) for i in range(self.L))
4353
-
4354
- if tags is not None:
4355
- # mix in global tags
4356
- site_tags = tags_to_oset(tags) | site_tags
4357
-
4358
- T = Tensor(data=data, inds=site_inds, tags=site_tags)
4359
-
4360
- super().__init__([T], virtual=True, **tn_opts)
4361
-
4362
- @classmethod
4363
- def rand(cls, n, phys_dim=2, dtype=float, **dense1d_opts):
4364
- """Create a random dense vector 'tensor network'."""
4365
- array = qu.randn(phys_dim**n, dtype=dtype)
4366
- array /= qu.norm(array, "fro")
4367
- return cls(array, **dense1d_opts)
4368
-
4369
-
4370
- class SuperOperator1D(TensorNetwork1D):
4371
- r"""A 1D tensor network super-operator class::
4372
-
4373
- 0 1 2 n-1
4374
- | | | | <-- outer_upper_ind_id
4375
- O===O===O== =O
4376
- |\ |\ |\ |\ <-- inner_upper_ind_id
4377
- ) ) ) ... ) <-- K (size of local Kraus sum)
4378
- |/ |/ |/ |/ <-- inner_lower_ind_id
4379
- O===O===O== =O
4380
- | | : | | <-- outer_lower_ind_id
4381
- :
4382
- chi (size of entangling bond dim)
4383
-
4384
- Parameters
4385
- ----------
4386
- arrays : sequence of arrays
4387
- The data arrays defining the superoperator, this should be a sequence
4388
- of 2n arrays, such that the first two correspond to the upper and lower
4389
- operators acting on site 0 etc. The arrays should be 5 dimensional
4390
- unless OBC conditions are desired, in which case the first two and last
4391
- two should be 4-dimensional. The dimensions of array can be should
4392
- match the ``shape`` option.
4393
-
4394
- """
4395
-
4396
- _EXTRA_PROPS = (
4397
- "_site_tag_id",
4398
- "_outer_upper_ind_id",
4399
- "_inner_upper_ind_id",
4400
- "_inner_lower_ind_id",
4401
- "_outer_lower_ind_id",
4402
- "cyclic",
4403
- "_L",
4404
- )
4405
-
4406
- def __init__(
4407
- self,
4408
- arrays,
4409
- shape="lrkud",
4410
- site_tag_id="I{}",
4411
- outer_upper_ind_id="kn{}",
4412
- inner_upper_ind_id="k{}",
4413
- inner_lower_ind_id="b{}",
4414
- outer_lower_ind_id="bn{}",
4415
- tags=None,
4416
- tags_upper=None,
4417
- tags_lower=None,
4418
- **tn_opts,
4419
- ):
4420
- # short-circuit for copying
4421
- if isinstance(arrays, SuperOperator1D):
4422
- super().__init__(arrays)
4423
- return
4424
-
4425
- arrays = tuple(arrays)
4426
- self._L = len(arrays) // 2
4427
-
4428
- # process indices
4429
- self._outer_upper_ind_id = outer_upper_ind_id
4430
- self._inner_upper_ind_id = inner_upper_ind_id
4431
- self._inner_lower_ind_id = inner_lower_ind_id
4432
- self._outer_lower_ind_id = outer_lower_ind_id
4433
-
4434
- sites_present = tuple(self.gen_sites_present())
4435
- outer_upper_inds = map(outer_upper_ind_id.format, sites_present)
4436
- inner_upper_inds = map(inner_upper_ind_id.format, sites_present)
4437
- inner_lower_inds = map(inner_lower_ind_id.format, sites_present)
4438
- outer_lower_inds = map(outer_lower_ind_id.format, sites_present)
4439
-
4440
- # process tags
4441
- self._site_tag_id = site_tag_id
4442
- tags = tags_to_oset(tags)
4443
- tags_upper = tags_to_oset(tags_upper)
4444
- tags_lower = tags_to_oset(tags_lower)
4445
-
4446
- def gen_tags():
4447
- for site_tag in self.site_tags:
4448
- yield (site_tag,) + tags + tags_upper
4449
- yield (site_tag,) + tags + tags_lower
4450
-
4451
- self.cyclic = ops.ndim(arrays[0]) == 5
4452
-
4453
- # transpose arrays to 'lrkud' order
4454
- # u
4455
- # |
4456
- # l--O--r
4457
- # |\
4458
- # d k
4459
- def gen_orders():
4460
- lkud_ord = tuple(shape.replace("r", "").find(x) for x in "lkud")
4461
- rkud_ord = tuple(shape.replace("l", "").find(x) for x in "rkud")
4462
- lrkud_ord = tuple(map(shape.find, "lrkud"))
4463
- yield rkud_ord if not self.cyclic else lrkud_ord
4464
- yield rkud_ord if not self.cyclic else lrkud_ord
4465
- for _ in range(self.L - 2):
4466
- yield lrkud_ord
4467
- yield lrkud_ord
4468
- yield lkud_ord if not self.cyclic else lrkud_ord
4469
- yield lkud_ord if not self.cyclic else lrkud_ord
4470
-
4471
- def gen_inds():
4472
- # |<- outer_upper_ind
4473
- # cycU_ix or pU_ix --O-- nU_ix
4474
- # /|<- inner_upper_ind
4475
- # k_ix ->(
4476
- # \|<- inner_lower_ind
4477
- # cycL_ix or pL_ix --O-- nL_ix
4478
- # |<- outer_lower_ind
4479
- if self.cyclic:
4480
- cycU_ix, cycL_ix = (rand_uuid(),), (rand_uuid(),)
4481
- else:
4482
- cycU_ix, cycL_ix = (), ()
4483
- nU_ix, nL_ix, k_ix = rand_uuid(), rand_uuid(), rand_uuid()
4484
- yield (
4485
- *cycU_ix,
4486
- nU_ix,
4487
- k_ix,
4488
- next(outer_upper_inds),
4489
- next(inner_upper_inds),
4490
- )
4491
- yield (
4492
- *cycL_ix,
4493
- nL_ix,
4494
- k_ix,
4495
- next(outer_lower_inds),
4496
- next(inner_lower_inds),
4497
- )
4498
- pU_ix, pL_ix = nU_ix, nL_ix
4499
- for _ in range(self.L - 2):
4500
- nU_ix, nL_ix, k_ix = rand_uuid(), rand_uuid(), rand_uuid()
4501
- yield (
4502
- pU_ix,
4503
- nU_ix,
4504
- k_ix,
4505
- next(outer_upper_inds),
4506
- next(inner_upper_inds),
4507
- )
4508
- yield (
4509
- pL_ix,
4510
- nL_ix,
4511
- k_ix,
4512
- next(outer_lower_inds),
4513
- next(inner_lower_inds),
4514
- )
4515
- pU_ix, pL_ix = nU_ix, nL_ix
4516
- k_ix = rand_uuid()
4517
- yield (
4518
- pU_ix,
4519
- *cycU_ix,
4520
- k_ix,
4521
- next(outer_upper_inds),
4522
- next(inner_upper_inds),
4523
- )
4524
- yield (
4525
- pL_ix,
4526
- *cycL_ix,
4527
- k_ix,
4528
- next(outer_lower_inds),
4529
- next(inner_lower_inds),
4530
- )
4531
-
4532
- def gen_tensors():
4533
- for array, tags, inds, order in zip(
4534
- arrays, gen_tags(), gen_inds(), gen_orders()
4535
- ):
4536
- yield Tensor(transpose(array, order), inds=inds, tags=tags)
4537
-
4538
- super().__init__(gen_tensors(), virtual=True, **tn_opts)
4539
-
4540
- @classmethod
4541
- def rand(
4542
- cls,
4543
- n,
4544
- K,
4545
- chi,
4546
- phys_dim=2,
4547
- herm=True,
4548
- cyclic=False,
4549
- dtype=complex,
4550
- **superop_opts,
4551
- ):
4552
- def gen_arrays():
4553
- for i in range(n):
4554
- shape = []
4555
- if cyclic or (i != 0):
4556
- shape += [chi]
4557
- if cyclic or (i != n - 1):
4558
- shape += [chi]
4559
- shape += [K, phys_dim, phys_dim]
4560
- data = qu.randn(shape=shape, dtype=dtype)
4561
- yield data
4562
- if herm:
4563
- yield data.conj()
4564
- else:
4565
- yield qu.randn(shape=shape, dtype=dtype)
4566
-
4567
- arrays = map(ops.sensibly_scale, gen_arrays())
4568
-
4569
- return cls(arrays, **superop_opts)
4570
-
4571
- @property
4572
- def outer_upper_ind_id(self):
4573
- return self._outer_upper_ind_id
4574
-
4575
- @property
4576
- def inner_upper_ind_id(self):
4577
- return self._inner_upper_ind_id
4578
-
4579
- @property
4580
- def inner_lower_ind_id(self):
4581
- return self._inner_lower_ind_id
4582
-
4583
- @property
4584
- def outer_lower_ind_id(self):
4585
- return self._outer_lower_ind_id
4586
-
4587
-
4588
- class TNLinearOperator1D(spla.LinearOperator):
4589
- r"""A 1D tensor network linear operator like::
4590
-
4591
- start stop - 1
4592
- . .
4593
- :-O-O-O-O-O-O-O-O-O-O-O-O-: --+
4594
- : | | | | | | | | | | | | : |
4595
- :-H-H-H-H-H-H-H-H-H-H-H-H-: acting on --V
4596
- : | | | | | | | | | | | | : |
4597
- :-O-O-O-O-O-O-O-O-O-O-O-O-: --+
4598
- left_inds^ ^right_inds
4599
-
4600
- Like :class:`~quimb.tensor.tensor_core.TNLinearOperator`, but performs a
4601
- structured contract from one end to the other than can handle very long
4602
- chains possibly more efficiently by contracting in blocks from one end.
4603
-
4604
-
4605
- Parameters
4606
- ----------
4607
- tn : TensorNetwork
4608
- The tensor network to turn into a ``LinearOperator``.
4609
- left_inds : sequence of str
4610
- The left indicies.
4611
- right_inds : sequence of str
4612
- The right indicies.
4613
- start : int
4614
- Index of starting site.
4615
- stop : int
4616
- Index of stopping site (does not include this site).
4617
- ldims : tuple of int, optional
4618
- If known, the dimensions corresponding to ``left_inds``.
4619
- rdims : tuple of int, optional
4620
- If known, the dimensions corresponding to ``right_inds``.
4621
-
4622
- See Also
4623
- --------
4624
- TNLinearOperator
4625
- """
4626
-
4627
- def __init__(
4628
- self,
4629
- tn,
4630
- left_inds,
4631
- right_inds,
4632
- start,
4633
- stop,
4634
- ldims=None,
4635
- rdims=None,
4636
- is_conj=False,
4637
- is_trans=False,
4638
- ):
4639
- self.tn = tn
4640
- self.start, self.stop = start, stop
4641
-
4642
- if ldims is None or rdims is None:
4643
- ind_sizes = tn.ind_sizes()
4644
- ldims = tuple(ind_sizes[i] for i in left_inds)
4645
- rdims = tuple(ind_sizes[i] for i in right_inds)
4646
-
4647
- self.left_inds, self.right_inds = left_inds, right_inds
4648
- self.ldims, ld = ldims, qu.prod(ldims)
4649
- self.rdims, rd = rdims, qu.prod(rdims)
4650
- self.tags = self.tn.tags
4651
-
4652
- # conjugate inputs/ouputs rather all tensors if necessary
4653
- self.is_conj = is_conj
4654
- self.is_trans = is_trans
4655
- self._conj_linop = None
4656
- self._adjoint_linop = None
4657
- self._transpose_linop = None
4658
-
4659
- super().__init__(dtype=self.tn.dtype, shape=(ld, rd))
4660
-
4661
- def _matvec(self, vec):
4662
- in_data = reshape(vec, self.rdims)
4663
-
4664
- if self.is_conj:
4665
- in_data = conj(in_data)
4666
-
4667
- if self.is_trans:
4668
- i, f, s = self.start, self.stop, 1
4669
- else:
4670
- i, f, s = self.stop - 1, self.start - 1, -1
4671
-
4672
- # add the vector to the right of the chain
4673
- tnc = self.tn | Tensor(in_data, self.right_inds, tags=["_VEC"])
4674
- tnc.view_like_(self.tn)
4675
- # tnc = self.tn.copy()
4676
- # tnc |= Tensor(in_data, self.right_inds, tags=['_VEC'])
4677
-
4678
- # absorb it into the rightmost site
4679
- tnc ^= ["_VEC", self.tn.site_tag(i)]
4680
-
4681
- # then do a structured contract along the whole chain
4682
- out_T = tnc ^ slice(i, f, s)
4683
-
4684
- out_data = out_T.transpose_(*self.left_inds).data.ravel()
4685
- if self.is_conj:
4686
- out_data = conj(out_data)
4687
-
4688
- return out_data
4689
-
4690
- def _matmat(self, mat):
4691
- d = mat.shape[-1]
4692
- in_data = reshape(mat, (*self.rdims, d))
4693
-
4694
- if self.is_conj:
4695
- in_data = conj(in_data)
4696
-
4697
- if self.is_trans:
4698
- i, f, s = self.start, self.stop, 1
4699
- else:
4700
- i, f, s = self.stop - 1, self.start - 1, -1
4701
-
4702
- # add the vector to the right of the chain
4703
- in_ix = (*self.right_inds, "_mat_ix")
4704
-
4705
- tnc = self.tn | Tensor(in_data, inds=in_ix, tags=["_VEC"])
4706
- tnc.view_like_(self.tn)
4707
- # tnc = self.tn.copy()
4708
- # tnc |= Tensor(in_data, inds=in_ix, tags=['_VEC'])
4709
-
4710
- # absorb it into the rightmost site
4711
- tnc ^= ["_VEC", self.tn.site_tag(i)]
4712
-
4713
- # then do a structured contract along the whole chain
4714
- out_T = tnc ^ slice(i, f, s)
4715
-
4716
- out_ix = (*self.left_inds, "_mat_ix")
4717
- out_data = reshape(out_T.transpose_(*out_ix).data, (-1, d))
4718
- if self.is_conj:
4719
- out_data = conj(out_data)
4720
-
4721
- return out_data
4722
-
4723
- def copy(self, conj=False, transpose=False):
4724
- if transpose:
4725
- inds = (self.right_inds, self.left_inds)
4726
- dims = (self.rdims, self.ldims)
4727
- is_trans = not self.is_trans
4728
- else:
4729
- inds = (self.left_inds, self.right_inds)
4730
- dims = (self.ldims, self.rdims)
4731
- is_trans = self.is_trans
4732
-
4733
- if conj:
4734
- is_conj = not self.is_conj
4735
- else:
4736
- is_conj = self.is_conj
4737
-
4738
- return TNLinearOperator1D(
4739
- self.tn,
4740
- *inds,
4741
- self.start,
4742
- self.stop,
4743
- *dims,
4744
- is_conj=is_conj,
4745
- is_trans=is_trans,
4746
- )
4747
-
4748
- def conj(self):
4749
- if self._conj_linop is None:
4750
- self._conj_linop = self.copy(conj=True)
4751
- return self._conj_linop
4752
-
4753
- def _transpose(self):
4754
- if self._transpose_linop is None:
4755
- self._transpose_linop = self.copy(transpose=True)
4756
- return self._transpose_linop
4757
-
4758
- def _adjoint(self):
4759
- """Hermitian conjugate of this TNLO."""
4760
- # cache the adjoint
4761
- if self._adjoint_linop is None:
4762
- self._adjoint_linop = self.copy(conj=True, transpose=True)
4763
- return self._adjoint_linop
4764
-
4765
- def to_dense(self):
4766
- T = self.tn ^ slice(self.start, self.stop)
4767
-
4768
- if self.is_conj:
4769
- T = T.conj()
4770
-
4771
- return T.to_dense(self.left_inds, self.right_inds)
4772
-
4773
- def toarray(self):
4774
- return self.to_dense()
4775
-
4776
- @property
4777
- def A(self):
4778
- return self.to_dense()