File size: 8,231 Bytes
b7d9967
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020, 2023.
#
# 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.

"""ComposedOp Class"""

from functools import partial, reduce
from typing import List, Optional, Union, cast, Dict

from numbers import Number
import numpy as np

from qiskit import QuantumCircuit
from qiskit.circuit import ParameterExpression
from qiskit.opflow.exceptions import OpflowError
from qiskit.opflow.list_ops.list_op import ListOp
from qiskit.opflow.operator_base import OperatorBase
from qiskit.quantum_info import Statevector
from qiskit.utils.deprecation import deprecate_func


class ComposedOp(ListOp):
    """Deprecated: A class for lazily representing compositions of Operators. Often Operators cannot be

    efficiently composed with one another, but may be manipulated further so that they can be

    composed later. This class holds logic to indicate that the Operators in ``oplist`` are meant to

    be composed, and therefore if they reach a point in which they can be, such as after

    conversion to QuantumCircuits or matrices, they can be reduced by composition."""

    @deprecate_func(

        since="0.24.0",

        additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.",

    )
    def __init__(

        self,

        oplist: List[OperatorBase],

        coeff: Union[complex, ParameterExpression] = 1.0,

        abelian: bool = False,

    ) -> None:
        """

        Args:

            oplist: The Operators being composed.

            coeff: A coefficient multiplying the operator

            abelian: Indicates whether the Operators in ``oplist`` are known to mutually commute.

        """
        super().__init__(oplist, combo_fn=partial(reduce, np.dot), coeff=coeff, abelian=abelian)

    @property
    def num_qubits(self) -> int:
        return self.oplist[0].num_qubits

    @property
    def distributive(self) -> bool:
        return False

    @property
    def settings(self) -> Dict:
        """Return settings."""
        return {"oplist": self._oplist, "coeff": self._coeff, "abelian": self._abelian}

    # TODO take advantage of the mixed product property, tensorpower each element in the composition
    # def tensorpower(self, other):
    #     """ Tensor product with Self Multiple Times """
    #     raise NotImplementedError

    def to_matrix(self, massive: bool = False) -> np.ndarray:
        OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive)

        mat = self.coeff * reduce(
            np.dot, [np.asarray(op.to_matrix(massive=massive)) for op in self.oplist]
        )

        # Note: As ComposedOp has a combo function of inner product we can end up here not with
        # a matrix (array) but a scalar. In which case we make a single element array of it.
        if isinstance(mat, Number):
            mat = [mat]

        return np.asarray(mat, dtype=complex)

    def to_circuit(self) -> QuantumCircuit:
        """Returns the quantum circuit, representing the composed operator.



        Returns:

            The circuit representation of the composed operator.



        Raises:

            OpflowError: for operators where a single underlying circuit can not be obtained.

        """
        # pylint: disable=cyclic-import
        from ..state_fns.circuit_state_fn import CircuitStateFn
        from ..primitive_ops.primitive_op import PrimitiveOp

        circuit_op = self.to_circuit_op()
        if isinstance(circuit_op, (PrimitiveOp, CircuitStateFn)):
            return circuit_op.to_circuit()
        raise OpflowError(
            "Conversion to_circuit supported only for operators, where a single "
            "underlying circuit can be produced."
        )

    def adjoint(self) -> "ComposedOp":
        return ComposedOp([op.adjoint() for op in reversed(self.oplist)], coeff=self.coeff)

    def compose(

        self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False

    ) -> OperatorBase:

        new_self, other = self._expand_shorter_operator_and_permute(other, permutation)
        new_self = cast(ComposedOp, new_self)

        if front:
            return other.compose(new_self)
        # Try composing with last element in list
        if isinstance(other, ComposedOp):
            return ComposedOp(new_self.oplist + other.oplist, coeff=new_self.coeff * other.coeff)

        # Try composing with last element of oplist. We only try
        # this if that last element isn't itself an
        # ComposedOp, so we can tell whether composing the
        # two elements directly worked. If it doesn't,
        # continue to the final return statement below, appending other to the oplist.
        if not isinstance(new_self.oplist[-1], ComposedOp):
            comp_with_last = new_self.oplist[-1].compose(other)
            # Attempt successful
            if not isinstance(comp_with_last, ComposedOp):
                new_oplist = new_self.oplist[0:-1] + [comp_with_last]
                return ComposedOp(new_oplist, coeff=new_self.coeff)

        return ComposedOp(new_self.oplist + [other], coeff=new_self.coeff)

    def eval(

        self, front: Optional[Union[str, dict, np.ndarray, OperatorBase, Statevector]] = None

    ) -> Union[OperatorBase, complex]:
        if self._is_empty():
            return 0.0

        # pylint: disable=cyclic-import
        from ..state_fns.state_fn import StateFn

        def tree_recursive_eval(r, l_arg):
            if isinstance(r, list):
                return [tree_recursive_eval(r_op, l_arg) for r_op in r]
            else:
                return l_arg.eval(r)

        eval_list = self.oplist.copy()
        # Only one op needs to be multiplied, so just multiply the first.
        eval_list[0] = eval_list[0] * self.coeff  # type: ignore
        if front and isinstance(front, OperatorBase):
            eval_list = eval_list + [front]
        elif front:
            eval_list = [StateFn(front, is_measurement=True)] + eval_list  # type: ignore

        return reduce(tree_recursive_eval, reversed(eval_list))

    # Try collapsing list or trees of compositions into a single <Measurement | Op | State>.
    def non_distributive_reduce(self) -> OperatorBase:
        """Reduce without attempting to expand all distributive compositions.



        Returns:

            The reduced Operator.

        """
        reduced_ops = [op.reduce() for op in self.oplist]
        reduced_ops = reduce(lambda x, y: x.compose(y), reduced_ops) * self.coeff
        if isinstance(reduced_ops, ComposedOp) and len(reduced_ops.oplist) > 1:
            return reduced_ops
        else:
            return reduced_ops[0]

    def reduce(self) -> OperatorBase:
        reduced_ops = [op.reduce() for op in self.oplist]
        if len(reduced_ops) == 0:
            return self.__class__([], coeff=self.coeff, abelian=self.abelian)

        def distribute_compose(l_arg, r):
            if isinstance(l_arg, ListOp) and l_arg.distributive:
                # Either ListOp or SummedOp, returns correct type
                return l_arg.__class__(
                    [distribute_compose(l_op * l_arg.coeff, r) for l_op in l_arg.oplist]
                )
            if isinstance(r, ListOp) and r.distributive:
                return r.__class__([distribute_compose(l_arg, r_op * r.coeff) for r_op in r.oplist])
            else:
                return l_arg.compose(r)

        reduced_ops = reduce(distribute_compose, reduced_ops) * self.coeff
        if isinstance(reduced_ops, ListOp) and len(reduced_ops.oplist) == 1:
            return reduced_ops.oplist[0]
        else:
            return cast(OperatorBase, reduced_ops)