Trajectree 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trajectree/__init__.py +0 -3
- trajectree/fock_optics/devices.py +1 -1
- trajectree/fock_optics/light_sources.py +2 -2
- trajectree/fock_optics/measurement.py +3 -3
- trajectree/fock_optics/utils.py +6 -6
- trajectree/trajectory.py +2 -2
- {trajectree-0.0.1.dist-info → trajectree-0.0.2.dist-info}/METADATA +2 -3
- trajectree-0.0.2.dist-info/RECORD +16 -0
- trajectree/quimb/docs/_pygments/_pygments_dark.py +0 -118
- trajectree/quimb/docs/_pygments/_pygments_light.py +0 -118
- trajectree/quimb/docs/conf.py +0 -158
- trajectree/quimb/docs/examples/ex_mpi_expm_evo.py +0 -62
- trajectree/quimb/quimb/__init__.py +0 -507
- trajectree/quimb/quimb/calc.py +0 -1491
- trajectree/quimb/quimb/core.py +0 -2279
- trajectree/quimb/quimb/evo.py +0 -712
- trajectree/quimb/quimb/experimental/__init__.py +0 -0
- trajectree/quimb/quimb/experimental/autojittn.py +0 -129
- trajectree/quimb/quimb/experimental/belief_propagation/__init__.py +0 -109
- trajectree/quimb/quimb/experimental/belief_propagation/bp_common.py +0 -397
- trajectree/quimb/quimb/experimental/belief_propagation/d1bp.py +0 -316
- trajectree/quimb/quimb/experimental/belief_propagation/d2bp.py +0 -653
- trajectree/quimb/quimb/experimental/belief_propagation/hd1bp.py +0 -571
- trajectree/quimb/quimb/experimental/belief_propagation/hv1bp.py +0 -775
- trajectree/quimb/quimb/experimental/belief_propagation/l1bp.py +0 -316
- trajectree/quimb/quimb/experimental/belief_propagation/l2bp.py +0 -537
- trajectree/quimb/quimb/experimental/belief_propagation/regions.py +0 -194
- trajectree/quimb/quimb/experimental/cluster_update.py +0 -286
- trajectree/quimb/quimb/experimental/merabuilder.py +0 -865
- trajectree/quimb/quimb/experimental/operatorbuilder/__init__.py +0 -15
- trajectree/quimb/quimb/experimental/operatorbuilder/operatorbuilder.py +0 -1631
- trajectree/quimb/quimb/experimental/schematic.py +0 -7
- trajectree/quimb/quimb/experimental/tn_marginals.py +0 -130
- trajectree/quimb/quimb/experimental/tnvmc.py +0 -1483
- trajectree/quimb/quimb/gates.py +0 -36
- trajectree/quimb/quimb/gen/__init__.py +0 -2
- trajectree/quimb/quimb/gen/operators.py +0 -1167
- trajectree/quimb/quimb/gen/rand.py +0 -713
- trajectree/quimb/quimb/gen/states.py +0 -479
- trajectree/quimb/quimb/linalg/__init__.py +0 -6
- trajectree/quimb/quimb/linalg/approx_spectral.py +0 -1109
- trajectree/quimb/quimb/linalg/autoblock.py +0 -258
- trajectree/quimb/quimb/linalg/base_linalg.py +0 -719
- trajectree/quimb/quimb/linalg/mpi_launcher.py +0 -397
- trajectree/quimb/quimb/linalg/numpy_linalg.py +0 -244
- trajectree/quimb/quimb/linalg/rand_linalg.py +0 -514
- trajectree/quimb/quimb/linalg/scipy_linalg.py +0 -293
- trajectree/quimb/quimb/linalg/slepc_linalg.py +0 -892
- trajectree/quimb/quimb/schematic.py +0 -1518
- trajectree/quimb/quimb/tensor/__init__.py +0 -401
- trajectree/quimb/quimb/tensor/array_ops.py +0 -610
- trajectree/quimb/quimb/tensor/circuit.py +0 -4824
- trajectree/quimb/quimb/tensor/circuit_gen.py +0 -411
- trajectree/quimb/quimb/tensor/contraction.py +0 -336
- trajectree/quimb/quimb/tensor/decomp.py +0 -1255
- trajectree/quimb/quimb/tensor/drawing.py +0 -1646
- trajectree/quimb/quimb/tensor/fitting.py +0 -385
- trajectree/quimb/quimb/tensor/geometry.py +0 -583
- trajectree/quimb/quimb/tensor/interface.py +0 -114
- trajectree/quimb/quimb/tensor/networking.py +0 -1058
- trajectree/quimb/quimb/tensor/optimize.py +0 -1818
- trajectree/quimb/quimb/tensor/tensor_1d.py +0 -4778
- trajectree/quimb/quimb/tensor/tensor_1d_compress.py +0 -1854
- trajectree/quimb/quimb/tensor/tensor_1d_tebd.py +0 -662
- trajectree/quimb/quimb/tensor/tensor_2d.py +0 -5954
- trajectree/quimb/quimb/tensor/tensor_2d_compress.py +0 -96
- trajectree/quimb/quimb/tensor/tensor_2d_tebd.py +0 -1230
- trajectree/quimb/quimb/tensor/tensor_3d.py +0 -2869
- trajectree/quimb/quimb/tensor/tensor_3d_tebd.py +0 -46
- trajectree/quimb/quimb/tensor/tensor_approx_spectral.py +0 -60
- trajectree/quimb/quimb/tensor/tensor_arbgeom.py +0 -3237
- trajectree/quimb/quimb/tensor/tensor_arbgeom_compress.py +0 -565
- trajectree/quimb/quimb/tensor/tensor_arbgeom_tebd.py +0 -1138
- trajectree/quimb/quimb/tensor/tensor_builder.py +0 -5411
- trajectree/quimb/quimb/tensor/tensor_core.py +0 -11179
- trajectree/quimb/quimb/tensor/tensor_dmrg.py +0 -1472
- trajectree/quimb/quimb/tensor/tensor_mera.py +0 -204
- trajectree/quimb/quimb/utils.py +0 -892
- trajectree/quimb/tests/__init__.py +0 -0
- trajectree/quimb/tests/test_accel.py +0 -501
- trajectree/quimb/tests/test_calc.py +0 -788
- trajectree/quimb/tests/test_core.py +0 -847
- trajectree/quimb/tests/test_evo.py +0 -565
- trajectree/quimb/tests/test_gen/__init__.py +0 -0
- trajectree/quimb/tests/test_gen/test_operators.py +0 -361
- trajectree/quimb/tests/test_gen/test_rand.py +0 -296
- trajectree/quimb/tests/test_gen/test_states.py +0 -261
- trajectree/quimb/tests/test_linalg/__init__.py +0 -0
- trajectree/quimb/tests/test_linalg/test_approx_spectral.py +0 -368
- trajectree/quimb/tests/test_linalg/test_base_linalg.py +0 -351
- trajectree/quimb/tests/test_linalg/test_mpi_linalg.py +0 -127
- trajectree/quimb/tests/test_linalg/test_numpy_linalg.py +0 -84
- trajectree/quimb/tests/test_linalg/test_rand_linalg.py +0 -134
- trajectree/quimb/tests/test_linalg/test_slepc_linalg.py +0 -283
- trajectree/quimb/tests/test_tensor/__init__.py +0 -0
- trajectree/quimb/tests/test_tensor/test_belief_propagation/__init__.py +0 -0
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d1bp.py +0 -39
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d2bp.py +0 -67
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hd1bp.py +0 -64
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hv1bp.py +0 -51
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l1bp.py +0 -142
- trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l2bp.py +0 -101
- trajectree/quimb/tests/test_tensor/test_circuit.py +0 -816
- trajectree/quimb/tests/test_tensor/test_contract.py +0 -67
- trajectree/quimb/tests/test_tensor/test_decomp.py +0 -40
- trajectree/quimb/tests/test_tensor/test_mera.py +0 -52
- trajectree/quimb/tests/test_tensor/test_optimizers.py +0 -488
- trajectree/quimb/tests/test_tensor/test_tensor_1d.py +0 -1171
- trajectree/quimb/tests/test_tensor/test_tensor_2d.py +0 -606
- trajectree/quimb/tests/test_tensor/test_tensor_2d_tebd.py +0 -144
- trajectree/quimb/tests/test_tensor/test_tensor_3d.py +0 -123
- trajectree/quimb/tests/test_tensor/test_tensor_arbgeom.py +0 -226
- trajectree/quimb/tests/test_tensor/test_tensor_builder.py +0 -441
- trajectree/quimb/tests/test_tensor/test_tensor_core.py +0 -2066
- trajectree/quimb/tests/test_tensor/test_tensor_dmrg.py +0 -388
- trajectree/quimb/tests/test_tensor/test_tensor_spectral_approx.py +0 -63
- trajectree/quimb/tests/test_tensor/test_tensor_tebd.py +0 -270
- trajectree/quimb/tests/test_utils.py +0 -85
- trajectree-0.0.1.dist-info/RECORD +0 -126
- {trajectree-0.0.1.dist-info → trajectree-0.0.2.dist-info}/WHEEL +0 -0
- {trajectree-0.0.1.dist-info → trajectree-0.0.2.dist-info}/licenses/LICENSE +0 -0
- {trajectree-0.0.1.dist-info → trajectree-0.0.2.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()
|