Source code for tn4ml.models.tn

import copy
from typing import Any

import autoray as a
import jax.numpy as jnp
import numpy as np
import quimb.tensor as qtn
from jax.nn.initializers import Initializer
from quimb import *

from ..initializers import *
from .model import Model


[docs] class TensorNetwork(Model, qtn.tensor_1d.TensorNetwork1DFlat): """A Trainable TensorNetwork class. See :class:`quimb.tensor.tensor_core.TensorNetwork` for explanation of other attributes and methods. """ _EXTRA_PROPS = ("_L", "_site_tag_id", "cyclic")
[docs] def __init__( self, tensors, site_tag_id: str = "I{}", cyclic: bool = False, **kwargs ): """Initialize :class:`tn4ml.models.tn.ParametrizedTensorNetwork`. Parameters ---------- tensors : list or TensorNetwork List of tensors of :class:`quimb.tensor.tensor_core.Tensor` or :class:quimb.tensor.tensor_core.TensorNetwork. kwargs : dict Additional arguments. """ if isinstance(tensors, TensorNetwork): Model.__init__(self) return Model.__init__(self) qtn.tensor_1d.TensorNetwork1DFlat.__init__(self, tensors, **kwargs) self._L = len(self.tensors) self.cyclic = cyclic self._site_tag_id = site_tag_id
[docs] def canonize(self, where, cur_orthog="calc", info=None, bra=None, inplace=False): """Canonizes the tensor network.""" self.canonicalize( where, cur_orthog=cur_orthog, info=info, bra=bra, inplace=inplace )
[docs] def copy(self, virtual: bool = False, deep: bool = False): """Copy the model. Returns ------- Model of the same type. """ if deep: return copy.deepcopy(self) model = self.__class__(self, virtual=virtual) for key in self.__dict__: model.__dict__[key] = self.__dict__[key] return model
[docs] def norm(self, **contract_opts) -> float: """Calculate norm of :class:`tn4ml.models.tn.TensorNetwork`. Parameters ---------- contract_opts : Optional Arguments passed to ``contract()``. Returns ------- float Norm of :class:`tn4ml.models.smpo.SpacedMatrixProductOperator` """ norm = self.conj() & self return norm.contract(**contract_opts) ** 0.5
[docs] def normalize(self, insert=None) -> None: """Normalize tensors of :class:`tn4ml.models.tn.TensorNetwork`. Parameters ---------- insert : int Index of tensor divided by norm. *Default = None*. When `None` the norm division is distributed across all tensors. """ if not self.tensors: raise ValueError("The tensor network is empty.") if self.L > 200: # for large systems for i, tensor in enumerate(self.tensors): if i == 0: self.left_canonize_site(i) elif i == self.L - 1: tensor.modify(data=tensor.data / jnp.linalg.norm(tensor.data)) else: tensor.modify(data=tensor.data / jnp.linalg.norm(tensor.data)) self.left_canonize_site(i) else: norm = self.norm() if insert is None: for tensor in self.tensors: tensor.modify(data=tensor.data / a.do("power", norm, 1 / self.L)) else: if not (0 <= insert < len(self.tensors)): raise IndexError( f"Insert index {insert} is out of bounds for the tensor list." ) self.tensors[insert].modify(data=self.tensors[insert].data / norm)
def trainable_wrapper( tn: qtn.tensor_1d.TensorNetwork1DFlat, **kwargs ) -> qtn.tensor_1d.TensorNetwork1DFlat: """Create a trainable wrapper around qtn.tensor_1d.TensorNetwork1DFlat. Parameters ---------- tn : :class:`quimb.tensor.TensorNetwork` Tensor Network to be trained. Returns ------- :class:`tn4ml.models.tn.TensorNetwork` """ tensors = tn.tensors return TensorNetwork(tensors, **kwargs) def TN_initialize( # noqa: N802 arrays: list | None = None, shapes: list | None = None, key: Any = None, initializer: Initializer = None, inds: list | None = None, tags_id: str = "I{}", cyclic: bool = False, dtype: Any = jnp.float_, **kwargs, ) -> TensorNetwork: """Initialize a TensorNetwork. Parameters ---------- arrays : list List of arrays to be used as tensors. *Default = None*. If None, shapes must be provided. shapes : list List of shapes of tensors. Each shape should be in LRP(P) format : (left, right, physical) *Default = None*. If None, arrays must be provided. key : Any Random key for initialization. *Default = None*. initializer : from `tn4ml.initializers` or `jax.nn.initializers` Initializer for tensors. *Default = None*. If None, tensors are initialized with random values. Only provided if arrays is None. inds : sequence of arrays of str List of indices for tensors. *Default = None*. Neeeds to be provided because its showing connectivity between tensors. Example for TN with 3 tensors: >>> inds = [['bond0', 'k0'], ['bond0', 'bond1', 'k2'], ['bond1', 'k3']] tags_id : str Tag identifier for tensors. *Default = 'I{}'*. The tag identifier should have a single placeholder for tag number. dtype : Any Data type for tensors. *Default = jnp.float_*. kwargs : dict Additional arguments. Returns ------- :class:`tn4ml.models.tn.TensorNetwork` """ if arrays is None and shapes is None: raise ValueError("Provide either arrays or shapes to create Tensor Network.") L = len(arrays) if arrays is not None else len(shapes) if inds is None: raise ValueError( "Provide indices for tensors - connectivity map between tensors." ) tensors = [] if arrays is not None: if len(arrays) != len(inds): raise ValueError("Number of tensors and indices should be same.") for i, array in enumerate(arrays): tensors.append(qtn.Tensor(array, inds=inds[i], tags=tags_id.format(i))) elif shapes is not None: if len(shapes) != len(inds): raise ValueError("Number of tensors and indices should be same.") for i, shape in zip(range(1, L + 1), shapes, strict=False): if initializer is not None: array = initializer(key, shape, dtype) else: rng = np.random.default_rng() array = np.asarray(rng.normal(0.0, 1.0, shape), dtype) tensors.append( qtn.Tensor(array, inds=inds[i - 1], tags=tags_id.format(i - 1)) ) tn = TensorNetwork(tensors, cyclic=cyclic, site_tag_id=tags_id, **kwargs) # normalize tn.normalize() return tn