Source code for qiskit.quantum_info.operators.scalar_op

# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""
ScalarOp class
"""

from numbers import Number
import numpy as np

from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.quantum_info.operators.operator import Operator


[docs]class ScalarOp(BaseOperator): """Scalar identity operator class. This is a symbolic representation of an scalar identity operator on multiple subsystems. It may be used to initialize a symbolic scalar multiplication of an identity and then be implicitly converted to other kinds of operator subclasses by using the :meth:`compose`, :meth:`dot`, :meth:`tensor`, :meth:`expand` methods. """ def __init__(self, dims, coeff=1): """Initialize an operator object. Args: dims (int or tuple): subsystem dimensions. coeff (Number): scalar coefficient for the identity operator (Default: 1). Raises: QiskitError: If the optional coefficient is invalid. """ if not isinstance(coeff, Number): QiskitError("coeff {} must be a number.".format(coeff)) self._coeff = coeff input_dims = self._automatic_dims(dims, np.product(dims)) super().__init__(input_dims, input_dims) def __repr__(self): return 'ScalarOp({}, coeff={})'.format( self._input_dims, self.coeff) @property def coeff(self): """Return the coefficient""" return self._coeff
[docs] def conjugate(self): """Return the conjugate of the operator.""" ret = self.copy() ret._coeff = np.conjugate(self.coeff) return ret
[docs] def transpose(self): """Return the transpose of the operator.""" return self.copy()
[docs] def is_unitary(self, atol=None, rtol=None): """Return True if operator is a unitary matrix.""" if atol is None: atol = self.atol if rtol is None: rtol = self.rtol return np.isclose(np.abs(self.coeff), 1, atol=atol, rtol=rtol)
[docs] def to_matrix(self): """Convert to a Numpy matrix.""" dim, _ = self.dim iden = np.eye(dim, dtype=complex) return self.coeff * iden
[docs] def to_operator(self): """Convert to an Operator object.""" return Operator(self.to_matrix(), input_dims=self._input_dims, output_dims=self._output_dims)
[docs] def tensor(self, other): """Return the tensor product operator self ⊗ other. Args: other (BaseOperator): an operator object. Returns: ScalarOp: if other is an ScalarOp. BaseOperator: if other is not an ScalarOp. """ if not isinstance(other, BaseOperator): other = Operator(other) if isinstance(other, ScalarOp): coeff = self.coeff * other.coeff dims = other._input_dims + self._input_dims return ScalarOp(dims, coeff=coeff) return other.expand(self)
[docs] def expand(self, other): """Return the tensor product operator other ⊗ self. Args: other (BaseOperator): an operator object. Returns: ScalarOp: if other is an ScalarOp. BaseOperator: if other is not an ScalarOp. """ if not isinstance(other, BaseOperator): other = Operator(other) if isinstance(other, ScalarOp): coeff = self.coeff * other.coeff dims = self._input_dims + other._input_dims return ScalarOp(dims, coeff=coeff) return other.tensor(self)
[docs] def compose(self, other, qargs=None, front=False): """Return the composed operator. Args: other (BaseOperator): an operator object. qargs (list or None): a list of subsystem positions to apply other on. If None apply on all subsystems [default: None]. front (bool): If True compose using right operator multiplication, instead of left multiplication [default: False]. Returns: BaseOperator: The operator self @ other. Raises: QiskitError: if other has incompatible dimensions for specified subsystems. Additional Information: Composition (``@``) is defined as `left` matrix multiplication for matrix operators. That is that ``A @ B`` is equal to ``B * A``. Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ if qargs is None: qargs = getattr(other, 'qargs', None) if not isinstance(other, BaseOperator): other = Operator(other) input_dims, output_dims = self._get_compose_dims(other, qargs, front) # If other is also an ScalarOp we only need to # update the coefficient and dimensions if isinstance(other, ScalarOp): coeff = self.coeff * other.coeff return ScalarOp(input_dims, coeff=coeff) # If we are composing on the full system we return the # other operator with reshaped dimensions if qargs is None: ret = other.reshape(input_dims, output_dims) # Other operator might not support scalar multiplication # so we treat the identity as a special case to avoid a # possible error if self.coeff == 1: return ret return self.coeff * ret # For qargs composition we initialize the scalar operator # as an instance of the other BaseOperators subclass. We then # perform subsystem qargs composition using the BaseOperator # subclasses compose method. # Note that this will raise an error if the other operator does # not support initialization from a ScalarOp or the ScalarOps # `to_operator` method). return other.__class__(self).compose( other, qargs=qargs, front=front)
[docs] def power(self, n): """Return the power of the ScalarOp. Args: n (Number): the exponent for the scalar op. Returns: ScalarOp: the ``coeff ** n`` ScalarOp. Raises: QiskitError: if the input and output dimensions of the operator are not equal, or the power is not a positive integer. """ ret = self.copy() ret._coeff = self.coeff ** n return ret
def _add(self, other, qargs=None): """Return the operator self + other. If ``qargs`` are specified the other operator will be added assuming it is identity on all other subsystems. Args: other (BaseOperator): an operator object. qargs (None or list): optional subsystems to subtract on (Default: None) Returns: ScalarOp: if other is an ScalarOp. BaseOperator: if other is not an ScalarOp. Raises: QiskitError: if other has incompatible dimensions. """ if qargs is None: qargs = getattr(other, 'qargs', None) if not isinstance(other, BaseOperator): other = Operator(other) self._validate_add_dims(other, qargs) # If qargs are specified we have to pad the other BaseOperator # with identities on remaining subsystems. We do this by # composing it with an identity ScalarOp. other = ScalarOp._pad_with_identity(self, other, qargs) # First we check the special case where coeff=0. In this case # we simply return the other operator reshaped so that its # subsystem dimensions are equal to the current operator for the # case where total dimensions agree but subsystem dimensions differ. if self.coeff == 0: return other.reshape(self._input_dims, self._output_dims) # Next if we are adding two ScalarOps we return a ScalarOp if isinstance(other, ScalarOp): return ScalarOp(self._input_dims, coeff=self.coeff+other.coeff) # Finally if we are adding another BaseOperator subclass # we use that subclasses `_add` method and reshape the # final dimensions. return other.reshape(self._input_dims, self._output_dims)._add(self) def _multiply(self, other): """Return the ScalarOp other * self. Args: other (Number): a complex number. Returns: ScalarOp: the scaled identity operator other * self. Raises: QiskitError: if other is not a valid complex number. """ if not isinstance(other, Number): raise QiskitError("other ({}) is not a number".format(other)) ret = self.copy() ret._coeff = other * self.coeff return ret @staticmethod def _pad_with_identity(current, other, qargs=None): """Pad another operator with identities. Args: current (BaseOperator): current operator. other (BaseOperator): other operator. qargs (None or list): qargs Returns: BaseOperator: the padded operator. """ if qargs is None: return other return ScalarOp(current._input_dims).compose(other, qargs=qargs)