import numpy
DTYPE = complex
def conjugate(t) -> 'TensorValued':
"""Return the complex conjugation of a `TensorValued` object.
:returns: :class:`TensorValued` -- the conjugation of a `TensorValued` object.
"""
from .tensor import Tensor
from .tensor_sum import TensorSum
from .tensor_network import TensorNetwork
from .tensor_view import TensorView
if not isinstance(t, TensorValued):
raise TypeError()
elif isinstance(t, Tensor):
if t._data is None:
return Tensor()
else:
return Tensor(numpy.conj(t._data))
elif isinstance(t, TensorSum):
ts = t.copy()
for term in ts.terms_by_name:
ts.terms_by_name[term] = conjugate(ts.terms_by_name[term])
return ts
elif isinstance(t, TensorNetwork):
tn = t.copy()
for node in tn.nodes:
tn.network.nodes[node]['tensor'] = conjugate(tn.network.nodes[node]['tensor'])
return tn
elif isinstance(t, TensorView):
return TensorView(t)
def transpose(t, axes: tuple) -> 'TensorValued':
"""Return the transposition of a `TensorValued` object.
:param axes: the transposition on the referred object.
:type axes: tuple.
:returns: :class:`TensorNetwork` -- the transposition of a `TensorValued` object can be readily expressed in terms of a
`TensorNetwork` object.
"""
from .tensor_network import TensorNetwork
tn = TensorNetwork()
tn.add_node(tensor=t, is_open=True)
tn.open_edges = [tn.open_edges[i] for i in axes]
return tn
def normalize(tv: 'TensorValued') -> 'TensorValued':
"""Return a copy with unit Frobenius norm.
:param tv: The :class:`TensorValued` object to normalize.
:type tv: :class:`TensorValued`.
:returns: :class:`TensorValued` -- the normalized :class:`TensorValued` object.
"""
return tv * (1 / numpy.sqrt(tv.norm_squared))
[docs]class TensorValued(object):
"""Interface for all :class:`TensorValued` objects, including :class:`Tensor`, :class:`TensorNetwork`,
:class:`TensorSum` and :class:`TensorView`.
:ivar identifier: unique identifier for each :class:`TensorValued` object.
:ivar dtype: A :class:`TensorValued` object is homogeneous, and contains elements described by a dtype object.
"""
id_count = -1
def __new__(cls, *args, **kwargs):
TensorValued.id_count += 1
instance = object.__new__(cls)
return instance
def __init__(self, dtype: type = DTYPE) -> None:
self.identifier = TensorValued.id_count
self.dtype = dtype
@property
def shape(self):
"""The common property of all :class:`TensorValued` classes, indicating whether the bond dimensions for a tensor
valued object. A tensor valued object is semantically a multi-dimensionally array, and its dimensions can be
expressed as a tuple of integers. The tuple is called the shape of the tensor, whereas the length of the tuple
is the rank of the tensor.
In ACQDP, undetermined tensors and undetermined dimensions are allowed. In the former case, the shape of the tensor
will return `None`; in the latter case, some of the bond_dimensions appearing in the tuple could be `None`.
:returns: :class:`tuple` or None
:raises: NotImplementedError, ValueError
"""
raise NotImplementedError()
@property
def is_valid(self) -> bool:
"""The common property of all :class:`TensorValued` classes, indicating whether the :class:`TensorValued` object
is valid or not. In every step of a program, all existing :class:`TensorValued` object must be valid, otherwise
an exception should be thrown out.
:returns: :class:`bool`
:raises: NotImplementedError
"""
raise NotImplementedError()
@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. A `TensorValued` object needs to be ready upon contraction, but needs not to be ready throught
the construction.
:returns: :class:`bool`
:raises: NotImplementedError()
"""
raise NotImplementedError()
def __str__(self) -> str:
return "Type: " + str(type(self))\
+ ", Shape: " + str(self.shape)
def __repr__(self) -> str:
return "Id: " + str(self.identifier) + self.__str__()
def __eq__(self, other) -> bool:
if self is other:
return True
elif isinstance(other, TensorValued):
if self.shape != other.shape:
return False
return numpy.allclose(self.contract(), other.contract())
else:
return False
def __mul__(self, other) -> 'TensorValued':
"""Tensor product of two tensors. For tensor multiplications, use `TensorNetwork` classes to specify how indices
are to be contracted. By default, addition creates a `TensorNetwork` object with two operands as two components.
:returns: :class:`TensorNetwork`
"""
from .tensor_network import TensorNetwork
tn = TensorNetwork()
tn.add_node(tensor=self, is_open=True)
tn.add_node(tensor=other, is_open=True)
return tn
__rmul__ = __mul__
def __add__(self, other: 'TensorValued') -> 'TensorValued':
"""Addition of two `TensorValued` objects.
By default, addition creates a `TensorSum` object with two operands as two components. The two operands of the
addition must have compatible shapes.
"""
from .tensor_sum import TensorSum
tl = TensorSum()
tl.add_term(tensor=self)
tl.add_term(tensor=other)
return tl
def __neg__(self) -> 'TensorValued':
"""Negation of a `TensorValued` object."""
return -1 * self
def __sub__(self, other: 'TensorValued') -> 'TensorValued':
"""Subtraction of `TensorValued` objects."""
return self + (-other)
def __invert__(self) -> 'TensorValued':
"""Syntactic sugar for conjugation."""
return conjugate(self)
def __mod__(self, axes: tuple):
"""Syntactic sugar for transposition."""
return transpose(self, axes)
@property
def norm_squared(self):
"""Square of Frobenius norm of the :class:`TensorValued` object."""
from .tensor_network import TensorNetwork
tn = TensorNetwork()
tn.add_node(tensor=self)
tn.add_node(tensor=~self)
return tn.contract().real
[docs] def fix_index(self, index, fix_to=0) -> 'TensorValued':
"""Fix the given index to the given value. The result :class:`TensorValued` object would have the same type 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:`bool`
:returns: :class:`TensorValued` -- The :class:`TensorValued` object after fixing the given index.
:raises: NotImplementedError
"""
raise NotImplementedError()
def expand(self):
"""Expand nested tensor network structures in the :class:`TensorValued` object if there is any.
:returns: :class:`TensorValued`.
"""
return self
[docs] def contract(self, **kwargs) -> numpy.ndarray:
"""Evaluate the :class:`TensorValued` object to a :class:`numpy.ndarray`.
:returns: :class:`numpy.ndarray`
:raises: NotImplementedError
"""
raise NotImplementedError()
[docs] def cast(self, dtype):
"""Cast the tensor valued object to a new underlying dtype."""
self.dtype = dtype
def copy(self) -> 'TensorValued':
"""Make a copy of the current object.
Data is duplicated only when necessary.
"""
raise NotImplementedError()
def __deepcopy__(self, memo) -> 'TensorValued':
"""Make a deepcopy of the current object with all data duplicated."""
raise NotImplementedError()