# -*- coding: utf-8 -*-
# This code is part of Qiskit.
#
# (C) Copyright IBM 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.
""" MatrixOp Class """
from typing import Union, Optional, Set
import logging
import numpy as np
from scipy.sparse import spmatrix
from qiskit.quantum_info import Operator
from qiskit.circuit import ParameterExpression, Instruction
from qiskit.extensions.hamiltonian_gate import HamiltonianGate
from ..operator_base import OperatorBase
from ..primitive_ops.circuit_op import CircuitOp
from ..list_ops.summed_op import SummedOp
from ..list_ops.composed_op import ComposedOp
from ..list_ops.tensored_op import TensoredOp
from .primitive_op import PrimitiveOp
from ..legacy.matrix_operator import MatrixOperator
logger = logging.getLogger(__name__)
[docs]class MatrixOp(PrimitiveOp):
""" Class for Operators represented by matrices, backed by Terra's ``Operator`` module.
"""
def __init__(self,
primitive: Union[list, np.ndarray, spmatrix, Operator] = None,
coeff: Optional[Union[int, float, complex, ParameterExpression]] = 1.0) -> None:
"""
Args:
primitive: The matrix-like object which defines the behavior of the underlying function.
coeff: A coefficient multiplying the primitive
Raises:
TypeError: invalid parameters.
ValueError: invalid parameters.
"""
if isinstance(primitive, spmatrix):
primitive = primitive.toarray()
if isinstance(primitive, (list, np.ndarray)):
primitive = Operator(primitive)
if not isinstance(primitive, Operator):
raise TypeError(
'MatrixOp can only be instantiated with MatrixOperator, '
'not {}'.format(type(primitive)))
if not primitive.input_dims() == primitive.output_dims():
raise ValueError('Cannot handle non-square matrices yet.')
super().__init__(primitive, coeff=coeff)
[docs] def primitive_strings(self) -> Set[str]:
return {'Matrix'}
@property
def num_qubits(self) -> int:
return len(self.primitive.input_dims())
[docs] def add(self, other: OperatorBase) -> OperatorBase:
if not self.num_qubits == other.num_qubits:
raise ValueError(
'Sum over operators with different numbers of qubits, {} and {}, is not well '
'defined'.format(self.num_qubits, other.num_qubits))
# Terra's Operator cannot handle ParameterExpressions
if isinstance(other, MatrixOp) and \
not isinstance(self.coeff, ParameterExpression) and \
not isinstance(other.coeff, ParameterExpression):
return MatrixOp((self.coeff * self.primitive) + (other.coeff * other.primitive))
# Covers Paulis, Circuits, and all else.
return SummedOp([self, other])
[docs] def adjoint(self) -> OperatorBase:
return MatrixOp(self.primitive.conjugate().transpose(), coeff=np.conj(self.coeff))
[docs] def equals(self, other: OperatorBase) -> bool:
if not isinstance(other, MatrixOp) or not self.coeff == other.coeff:
return False
return self.primitive == other.primitive
[docs] def tensor(self, other: OperatorBase) -> OperatorBase:
if isinstance(other.primitive, Operator):
return MatrixOp(self.primitive.tensor(other.primitive), coeff=self.coeff * other.coeff)
return TensoredOp([self, other])
[docs] def compose(self, other: OperatorBase) -> OperatorBase:
other = self._check_zero_for_composition_and_expand(other)
if isinstance(other, MatrixOp):
return MatrixOp(self.primitive.compose(other.primitive, front=True),
coeff=self.coeff * other.coeff)
return ComposedOp([self, other])
[docs] def to_matrix(self, massive: bool = False) -> np.ndarray:
return self.primitive.data * self.coeff
def __str__(self) -> str:
prim_str = str(self.primitive)
if self.coeff == 1.0:
return prim_str
else:
return "{} * {}".format(self.coeff, prim_str)
[docs] def eval(self,
front: Union[str, dict, np.ndarray,
OperatorBase] = None) -> Union[OperatorBase, float, complex]:
# For other ops' eval we return self.to_matrix_op() here, but that's unnecessary here.
if front is None:
return self
# pylint: disable=cyclic-import,import-outside-toplevel
from ..list_ops import ListOp
from ..state_fns import StateFn, OperatorStateFn
new_front = None
# For now, always do this. If it's not performant, we can be more granular.
if not isinstance(front, OperatorBase):
front = StateFn(front, is_measurement=False)
if isinstance(front, ListOp) and front.distributive:
new_front = front.combo_fn([self.eval(front.coeff * front_elem)
for front_elem in front.oplist])
elif isinstance(front, OperatorStateFn):
new_front = OperatorStateFn(self.adjoint().compose(front.to_matrix_op()).compose(self))
elif isinstance(front, OperatorBase):
new_front = StateFn(self.to_matrix() @ front.to_matrix())
return new_front
[docs] def exp_i(self) -> OperatorBase:
"""Return a ``CircuitOp`` equivalent to e^-iH for this operator H"""
return CircuitOp(HamiltonianGate(self.primitive, time=self.coeff))
# Op Conversions
[docs] def to_matrix_op(self, massive: bool = False) -> OperatorBase:
return self
[docs] def to_instruction(self) -> Instruction:
return (self.coeff * self.primitive).to_instruction() # type: ignore
[docs] def to_legacy_op(self, massive: bool = False) -> MatrixOperator:
return MatrixOperator(self.to_matrix(massive=massive))