from math import log2
import itertools
import numpy as np
import quimb as qu
from .tensor_core import rand_uuid, IsoTensor, TensorNetwork
from .tensor_1d import TensorNetwork1D, TensorNetwork1DVector
def is_power_of_2(x):
return ((x & (x - 1)) == 0) and x > 0
[docs]class MERA(TensorNetwork1DVector,
TensorNetwork1D,
TensorNetwork):
r"""The Multi-scale Entanglement Renormalization Ansatz (MERA) state::
... ... ... ... ... ...
| | | | | | |
ISO ISO ISO ISO ISO ISO ISO :
\ / \ / \ / \ / \ / \ / : '_LAYER1'
UNI UNI UNI UNI UNI UNI :
/ \ / \ / \ / \ / \ / \
O ISO ISO ISO ISO ISO ISO ISO ISO ISO ISO ISO ISO I :
| | | | | | | | | | | | | | | | | | | | | | | | | | : '_LAYER0'
UNI UNI UNI UNI UNI UNI UNI UNI UNI UNI UNI UNI UNI :
| | | | | | | | | | | | | | | | | | | | | | | | | | <-- phys_dim
0 1 2 3 4 .... ... L-2 L-1
Parameters
----------
L : int
The number of phyiscal sites. Shoule be a power of 2.
uni : array or sequence of arrays of shape (d, d, d, d).
The unitary operator(s). These will be cycled over and placed from
bottom left to top right in diagram above.
iso : array or sequence of arrays of shape (d, d, d)
The isometry operator(s). These will be cycled over and placed from
bottom left to top right in diagram above.
phys_dim : int, optional
The dimension of the local hilbert space.
dangle : bool, optional
Whether to leave a dangling index on the final isometry, in order to
maintain perfect scale invariance, else join the final unitaries just
with an indentity.
"""
_EXTRA_PROPS = ('_site_ind_id', '_site_tag_id', 'cyclic', '_L')
_CONTRACT_STRUCTURED = False
def __init__(self, L, uni=None, iso=None, phys_dim=2, dangle=False,
site_ind_id="k{}", site_tag_id="I{}", **tn_opts):
# short-circuit for copying MERA
if isinstance(L, MERA):
super().__init__(L)
for ep in MERA._EXTRA_PROPS:
setattr(self, ep, getattr(L, ep))
return
self._site_ind_id = site_ind_id
self._site_tag_id = site_tag_id
self.cyclic = True
self._L = L
if not is_power_of_2(L):
raise ValueError("``L`` should be a power of 2.")
nlayers = round(log2(L))
if hasattr(uni, 'shape'):
uni = (uni,)
if hasattr(iso, 'shape'):
iso = (iso,)
unis = itertools.cycle(uni)
isos = itertools.cycle(iso)
def gen_mera_tensors():
u_ind_id = site_ind_id
for i in range(nlayers):
# index id connecting to layer below
l_ind_id = u_ind_id
# index id connecting to isos to unis
m_ind_id = rand_uuid() + "_{}"
# index id connecting to layer above
u_ind_id = rand_uuid() + "_{}"
# number of tensor sites in this layer
eff_L = L // 2**i
for j in range(0, eff_L, 2):
# generate the unitary:
# ul | | ur
# UNI
# ll | | lr
# j j+1
ll, lr = map(l_ind_id.format, (j, (j + 1) % eff_L))
ul, ur = map(m_ind_id.format, (j, (j + 1) % eff_L))
inds = (ll, lr, ul, ur)
tags = ("_UNI", f"_LAYER{i}")
if i == 0:
tags += (site_tag_id.format(j),
site_tag_id.format(j + 1))
yield IsoTensor(next(unis), inds=inds,
tags=tags, left_inds=(ll, lr))
# generate the isometry (offset by one effective site):
# | ui
# ISO
# ll | | lr
# j+1 j+2
ll, lr = map(m_ind_id.format, (j + 1, (j + 2) % eff_L))
ui = u_ind_id.format(j // 2)
inds = (ll, lr, ui)
tags = ("_ISO", f"_LAYER{i}")
if i < nlayers - 1 or dangle:
yield IsoTensor(next(isos), inds=inds,
tags=tags, left_inds=(ll, lr))
else:
# don't leave dangling index at top
iso_f = next(isos)
yield IsoTensor(
np.eye(iso_f.shape[0], dtype=iso_f.dtype) / 2**0.5,
inds=inds[:-1], tags=tags, left_inds=(ll, lr)
)
super().__init__(gen_mera_tensors(), virtual=True)
# tag the MERA with the 'causal-cone' of each site
for i in range(nlayers):
for j in range(L):
# get isometries in the same layer
for t in self.select_neighbors(j):
if f'_LAYER{i}' in t.tags:
t.add_tag(f'I{j}')
# get unitaries in layer above
for t in self.select_neighbors(j):
if f'_LAYER{i + 1}' in t.tags:
t.add_tag(f'I{j}')
@classmethod
def rand(cls, L, max_bond=None, phys_dim=2, dtype=float, **mera_opts):
d = phys_dim
if max_bond is None:
max_bond = d
def gen_unis():
D = d
m = L // 2
while True:
for _ in range(m):
uni = qu.rand_iso(D**2, D**2, dtype=dtype)
uni.shape = (D, D, D, D)
yield uni
D = min(D**2, max_bond)
m //= 2
def gen_isos():
Dl = d
Du = min(Dl**2, max_bond)
m = L // 2
while True:
for _ in range(m):
iso = qu.rand_iso(Dl**2, Du, dtype=dtype)
iso.shape = (Dl, Dl, Du)
yield iso
Dl = Du
Du = min(Dl**2, max_bond)
m //= 2
return cls(L, gen_unis(), gen_isos(), phys_dim=d, **mera_opts)
[docs] @classmethod
def rand_invar(cls, L, phys_dim=2, dtype=float, **mera_opts):
"""Generate a random translational and scale invariant MERA.
"""
d = phys_dim
iso = qu.rand_iso(d**2, d, dtype=dtype)
iso.shape = (d, d, d)
uni = qu.rand_iso(d**2, d**2, dtype=dtype)
uni.shape = (d, d, d, d)
return cls(L, uni, iso, phys_dim=d, **mera_opts)