Source code for acqdp.tensor_network.tensor
import copy
import numpy
from .tensor_valued import TensorValued, DTYPE
[docs]class Tensor(TensorValued):
"""A :class:`Tensor` is an array of numbers with multiple dimensions. The most basic examples of a :class:`Tensor`
are a vector (1-dimensional arrays of numbers) and a matrix (2-dimensional arrays).
In our implementation, :class:`Tensor` is a subclass of :class:`TensorValued`, where the value is stored in an
`numpy.ndarray`. All other `TensorValued` represent operations over the :class:`Tensor` objects.
:ivar _data: `numpy.ndarray` object representing the data corresponding to the tensor.
"""
[docs] def __init__(self,
data: numpy.ndarray = None,
dtype: type = DTYPE) -> None:
"""Constructor of a :class:`Tensor` object."""
if data is None:
self._data = None
elif isinstance(data, TensorValued):
self._data = data.contract()
elif isinstance(data, numpy.ndarray):
self._data = data
if dtype is None:
dtype = self._data.dtype
else:
self._data = numpy.array(data)
if dtype is None:
dtype = self._data.dtype
super().__init__(dtype)
@property
def shape(self):
"""
The common property of all :class:`TensorValued` classes.
The shape of a `TensorValued` object is the bond dimension for each of its indices.
:class:`TensorValued` objects must have compatible shapes in order to be connected together in
a :class:`TensorNetwork`,or summed over in a :class:`TensorSum`.
For :class:`Tensor` objects, it refers to the shape of the underlying :class:`numpy.ndarray` object.
"""
if self._data is None:
return None
elif isinstance(self._data, numpy.ndarray):
return self._data.shape
else:
raise ValueError
def __str__(self) -> str:
s = None
if isinstance(self._data, numpy.ndarray):
s = numpy.around(self._data, decimals=3)
data_str = "Data: \n" + str(s)
return super().__str__() + "\n" + data_str
def __repr__(self) -> str:
return "Id: " + str(self.identifier) + self.__str__()
def __iadd__(self, t):
self._data += t.contract()
return self
@property
def is_valid(self) -> bool:
"""For :class:`Tensor` objects, it is to indicate whether the underlying :class:`numpy.ndarray` object where the
unary operation is performed onto, is valid or not."""
return True
@property
def is_ready(self) -> bool:
"""The common property of all :class:`TensorValued` classes, indicating whether the current
:class:`TensorValued` object is ready for contraction, i.e. whether it semantically represents a tensor with a
definite value. In the process of a program, not all :class:`TensorValued` objects need to be ready; however
once the `data` property of a certain object is queried, such object must be ready in order to successfully
yield an :class:`numpy.ndarray` object.
For :class:`Tensor` objects, it is to indicate whether the underlying :class:`numpy.ndarray` object where the
unary operation is performed onto, is ready for contraction.
"""
return self._data is not None
@property
def norm_squared(self):
"""Square of Frobenius norm of the underlying :class:`numpy.ndarray` object."""
return numpy.linalg.norm(self._data.flatten()) ** 2
def fix_index(self, index, fix_to=0):
"""Fix the given index to the given value. The object would have the same dtype as the original one, with rank 1
smaller than the original.
:param index: The index to fix.
:type index: :class:`int`.
:param fix_to: the value to assign to the given index.
:type fix_to: :class:`int`
"""
if self._data is not None:
self._data = numpy.moveaxis(self._data, index, 0)[fix_to]
return self
def contract(self, **kwargs):
"""
:returns: :class:`numpy.ndarray` -- the value of the tensor whose value is stored in an :class:`numpy.ndarray` object.
"""
return self._data
def cast(self, dtype):
"""Return a copy of the `Tensor` object with updated underlying dtype."""
return Tensor(numpy.array(self._data, dtype), dtype)
def copy(self):
"""Return a copy of the `Tensor` object."""
return Tensor(self._data, self.dtype)
def __deepcopy__(self, memo):
tn = Tensor(copy.deepcopy(self._data), dtype=self.dtype)
return tn