diff --git a/docs/changelog.md b/docs/changelog.md index 999d43b5..040825c1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -21,6 +21,9 @@ Release notes for `quimb`. [`is_cyclic_z`](quimb.tensor.TensorNetwork3D.is_cyclic_z) to [TensorNetwork2D](quimb.tensor.TensorNetwork2D) and [TensorNetwork3D](quimb.tensor.TensorNetwork3D). +- add [TensorNetwork.compress_all_1d](quimb.tensor.TensorNetwork.compress_all_1d) + for compressing generic tensor networks that you promise have a 1D topology, + without casting as a [TensorNetwork1D](quimb.tensor.TensorNetwork1D). (whats-new-1-6-0)= diff --git a/quimb/tensor/tensor_core.py b/quimb/tensor/tensor_core.py index 0598c856..8055bdd1 100644 --- a/quimb/tensor/tensor_core.py +++ b/quimb/tensor/tensor_core.py @@ -5807,6 +5807,61 @@ def sorter(t, tn, distances, connectivity): compress_all_tree, inplace=True ) + def compress_all_1d( + self, + max_bond=None, + cutoff=1e-10, + canonize=True, + inplace=False, + **compress_opts, + ): + """Compress a tensor network that you know has a 1D topology, this + proceeds by generating a spanning 'tree' from around the least central + tensor, then optionally canonicalizing all bonds outwards and + compressing inwards. + + Parameters + ---------- + max_bond : int, optional + The maximum bond dimension to compress to. + cutoff : float, optional + The singular value cutoff to use. + canonize : bool, optional + Whether to canonize all bonds outwards first. + inplace : bool, optional + Whether to perform the compression inplace. + compress_opts + Supplied to :func:`~quimb.tensor.tensor_core.tensor_compress_bond`. + + Returns + ------- + TensorNetwork + """ + tn = self if inplace else self.copy() + + tid0 = tn.least_central_tid() + span = tn.get_tree_span([tid0]) + + if canonize: + for tida, tidb, _ in span: + tn._canonize_between_tids(tida, tidb, absorb='right') + compress_opts.setdefault('absorb', 'right') + else: + compress_opts.setdefault('absorb', 'both') + + for tida, tidb, _ in reversed(span): + tn._compress_between_tids( + tidb, + tida, + max_bond=max_bond, + cutoff=cutoff, + **compress_opts, + ) + + return tn + + compress_all_1d_ = functools.partialmethod(compress_all_1d, inplace=True) + def compress_all_simple( self, max_bond=None, diff --git a/tests/test_tensor/test_tensor_core.py b/tests/test_tensor/test_tensor_core.py index c3eb77e2..d2bf97e4 100644 --- a/tests/test_tensor/test_tensor_core.py +++ b/tests/test_tensor/test_tensor_core.py @@ -1332,6 +1332,15 @@ def test_compress_all(self, method): assert k.max_bond() == 7 assert_allclose(k.H @ k, 1.0) + def test_compress_all_1d(self): + mpo = qtn.MPO_rand(10, 7) + mpo1 = mpo.copy() + mpo1.compress(max_bond=4, renorm=False) + mpo2 = mpo.compress_all_1d(max_bond=4, renorm=False) + assert mpo1.max_bond() == mpo2.max_bond() == 4 + assert mpo2 is not mpo + assert_allclose(mpo1.H @ mpo, mpo2.H @ mpo) + def test_canonize_between(self): k = MPS_rand_state(4, 3) k.canonize_between("I1", "I2")