Example #1
0
    def convert(
        self,
        operator: OperatorBase,
        params: Optional[Union[ParameterExpression, ParameterVector,
                               List[ParameterExpression],
                               Tuple[ParameterExpression, ParameterExpression],
                               List[Tuple[ParameterExpression,
                                          ParameterExpression]]]] = None
    ) -> OperatorBase:
        """
        Args:
            operator: The operator corresponding to our quantum state we are taking the
                      gradient of: |ψ(ω)〉
            params: The parameters we are taking the gradient wrt: ω
                    If a ParameterExpression, ParameterVector or List[ParameterExpression] is given,
                    then the 1st order derivative of the operator is calculated.
                    If a Tuple[ParameterExpression, ParameterExpression] or
                    List[Tuple[ParameterExpression, ParameterExpression]]
                    is given, then the 2nd order derivative of the operator is calculated.

        Returns:
            An operator corresponding to the gradient resp. Hessian. The order is in accordance with
            the order of the given parameters.
        Raises:
            AquaError: If the parameters are given in an invalid format.

        """
        if isinstance(params, (ParameterExpression, ParameterVector)):
            return self._parameter_shift(operator, params)
        elif isinstance(params, tuple):
            return self._parameter_shift(
                self._parameter_shift(operator, params[0]), params[1])
        elif isinstance(params, Iterable):
            if all(isinstance(param, ParameterExpression) for param in params):
                return self._parameter_shift(operator, params)
            elif all(isinstance(param, tuple) for param in params):
                return ListOp([
                    self._parameter_shift(
                        self._parameter_shift(operator, pair[0]), pair[1])
                    for pair in params
                ])
            else:
                raise AquaError(
                    'The linear combination gradient does only support the computation '
                    'of 1st gradients and 2nd order gradients.')
        else:
            raise AquaError(
                'The linear combination gradient does only support the computation '
                'of 1st gradients and 2nd order gradients.')
Example #2
0
    def permute(self, permutation: List[int]) -> "PauliSumOp":
        """Permutes the sequence of ``PauliSumOp``.

        Args:
            permutation: A list defining where each Pauli should be permuted. The Pauli at index
                j of the primitive should be permuted to position permutation[j].

        Returns:
              A new PauliSumOp representing the permuted operator. For operator (X ^ Y ^ Z) and
              indices=[1,2,4], it returns (X ^ I ^ Y ^ Z ^ I).

        Raises:
            AquaError: if indices do not define a new index for each qubit.
        """
        if len(permutation) != self.num_qubits:
            raise AquaError(
                "List of indices to permute must have the same size as Pauli Operator"
            )
        length = max(permutation) + 1
        spop = self.primitive.tensor(  # type:ignore
            SparsePauliOp(Pauli(label="I" * (length - self.num_qubits))))
        permutation = [i for i in range(length) if i not in permutation
                       ] + permutation
        permutation = np.arange(length)[np.argsort(permutation)]
        permutation = np.hstack([permutation,
                                 permutation + length])  # type: ignore
        spop.table.array = spop.table.array[:, permutation]
        return PauliSumOp(spop, self.coeff)
Example #3
0
    def trim_circuit(circuit: QuantumCircuit,
                     reference_gate: Gate) -> QuantumCircuit:
        """Trim the given quantum circuit before the reference gate.

        Args:
            circuit: The circuit to be trimmed.
            reference_gate: The gate where the circuit is supposed to be trimmed.

        Returns:
            The trimmed circuit.

        Raises:
            AquaError: If the reference gate is not part of the given circuit.
        """
        parameterized_gates = []
        for _, elements in circuit._parameter_table.items():
            for element in elements:
                parameterized_gates.append(element[0])

        for i, op in enumerate(circuit.data):
            if op[0] == reference_gate:
                trimmed_circuit = QuantumCircuit(*circuit.qregs)
                trimmed_circuit.data = circuit.data[:i]
                return trimmed_circuit

        raise AquaError(
            'The reference gate is not in the given quantum circuit.')
Example #4
0
    def permute(self, permutation: List[int]) -> 'PauliOp':
        """Permutes the sequence of Pauli matrices.

        Args:
            permutation: A list defining where each Pauli should be permuted. The Pauli at index
                j of the primitive should be permuted to position permutation[j].

        Returns:
              A new PauliOp representing the permuted operator. For operator (X ^ Y ^ Z) and
              indices=[1,2,4], it returns (X ^ I ^ Y ^ Z ^ I).

        Raises:
            AquaError: if indices do not define a new index for each qubit.
        """
        pauli_string = self.primitive.__str__()
        length = max(permutation
                     ) + 1  # size of list must be +1 larger then its max index
        new_pauli_list = ['I'] * length
        if len(permutation) != self.num_qubits:
            raise AquaError(
                "List of indices to permute must have the same size as Pauli Operator"
            )
        for i, index in enumerate(permutation):
            new_pauli_list[-index - 1] = pauli_string[-i - 1]
        return PauliOp(Pauli(label=''.join(new_pauli_list)), self.coeff)
Example #5
0
    def evaluate_with_result(self,
                             result,
                             statevector_mode=True,
                             use_simulator_snapshot_mode=None,
                             circuit_name_prefix=''):
        """
        Use the executed result with operator to get the evaluated value.

        Args:
            result (qiskit.Result): the result from the backend
            statevector_mode (bool): mode
            use_simulator_snapshot_mode (bool): uses simulator operator mode
            circuit_name_prefix (str, optional): a prefix of circuit name

        Returns:
            float: the mean value
            float: the standard deviation
        Raises:
            AquaError: if Operator is empty
        """
        if self.is_empty():
            raise AquaError("Operator is empty, check the operator.")

        del use_simulator_snapshot_mode  # unused
        avg, std_dev = 0.0, 0.0
        quantum_state = np.asarray(
            result.get_statevector(circuit_name_prefix + 'psi'))
        avg = np.vdot(quantum_state, self._matrix.dot(quantum_state))

        return avg, std_dev
Example #6
0
 def _unroll_param_dict(
     value_dict: Dict[Union[ParameterExpression, ParameterVector],
                      Union[Number, List[Number]]]
 ) -> Union[Dict[ParameterExpression, Number], List[Dict[
         ParameterExpression, Number]]]:
     """ Unrolls the ParameterVectors in a param_dict into separate Parameters, and unrolls
     parameterization value lists into separate param_dicts without list nesting. """
     unrolled_value_dict = {}
     for (param, value) in value_dict.items():
         if isinstance(param, ParameterExpression):
             unrolled_value_dict[param] = value
         if isinstance(param, ParameterVector):
             if not len(param) == len(value):  # type: ignore
                 raise ValueError(
                     'ParameterVector {} has length {}, which differs from value list {} of '
                     'len {}'.format(param, len(param), value,
                                     len(value)))  # type: ignore
             unrolled_value_dict.update(zip(param, value))  # type: ignore
     if isinstance(list(unrolled_value_dict.values())[0], list):
         # check that all are same length
         unrolled_value_dict_list = []
         try:
             for i in range(len(list(
                     unrolled_value_dict.values())[0])):  # type: ignore
                 unrolled_value_dict_list.append(
                     OperatorBase._get_param_dict_for_index(
                         unrolled_value_dict,  # type: ignore
                         i))
             return unrolled_value_dict_list
         except IndexError as ex:
             raise AquaError(
                 'Parameter binding lists must all be the same length.'
             ) from ex
     return unrolled_value_dict  # type: ignore
Example #7
0
def to_tpb_grouped_weighted_pauli_operator(
        operator: Union[WeightedPauliOperator, TPBGroupedWeightedPauliOperator,
                        MatrixOperator], grouping_func: Callable,
        **kwargs: int) -> TPBGroupedWeightedPauliOperator:
    """

    Args:
        operator: one of supported operator type
        grouping_func: a callable function that grouped the paulis in the operator.
        kwargs: other setting for `grouping_func` function

    Returns:
        the converted tensor-product-basis grouped weighted pauli operator

    Raises:
        AquaError: Unsupported type to convert
    """
    if operator.__class__ == WeightedPauliOperator:
        return grouping_func(operator, **kwargs)
    elif operator.__class__ == TPBGroupedWeightedPauliOperator:
        # different tpb grouping approach is asked
        op_tpb = cast(TPBGroupedWeightedPauliOperator, operator)
        if grouping_func != op_tpb.grouping_func and kwargs != op_tpb.kwargs:
            return grouping_func(op_tpb, **kwargs)
        else:
            return op_tpb
    elif operator.__class__ == MatrixOperator:
        op = to_weighted_pauli_operator(operator)
        return grouping_func(op, **kwargs)
    else:
        raise AquaError(
            "Unsupported type to convert to TPBGroupedWeightedPauliOperator: "
            "{}".format(operator.__class__))
Example #8
0
    def permute(self, permutation: Optional[List[int]] = None) -> 'MatrixOp':
        """Creates a new MatrixOp that acts on the permuted qubits.

        Args:
            permutation: A list defining where each qubit should be permuted. The qubit at index
                j should be permuted to position permutation[j].

        Returns:
            A new MatrixOp representing the permuted operator.

        Raises:
            AquaError: if indices do not define a new index for each qubit.
        """
        new_self = self
        new_matrix_size = max(permutation) + 1

        if self.num_qubits != len(permutation):
            raise AquaError(
                "New index must be defined for each qubit of the operator.")
        if self.num_qubits < new_matrix_size:
            # pad the operator with identities
            new_self = self._expand_dim(new_matrix_size - self.num_qubits)
        qc = QuantumCircuit(new_matrix_size)

        # extend the indices to match the size of the new matrix
        permutation \
            = list(filter(lambda x: x not in permutation, range(new_matrix_size))) + permutation

        # decompose permutation into sequence of transpositions
        transpositions = arithmetic.transpositions(permutation)
        for trans in transpositions:
            qc.swap(trans[0], trans[1])
        matrix = CircuitOp(qc).to_matrix()
        return MatrixOp(matrix.transpose()) @ new_self @ MatrixOp(
            matrix)  # type: ignore
Example #9
0
def to_matrix_operator(
        operator: Union[WeightedPauliOperator, TPBGroupedWeightedPauliOperator, MatrixOperator])\
        -> MatrixOperator:
    """
    Converting a given operator to `MatrixOperator`

    Args:
        operator: one of supported operator type
    Returns:
        the converted matrix operator
    Raises:
        AquaError: Unsupported type to convert
    """
    if operator.__class__ == WeightedPauliOperator:
        op_w = cast(WeightedPauliOperator, operator)
        if op_w.is_empty():
            return MatrixOperator(None)
        hamiltonian = 0
        for weight, pauli in op_w.paulis:
            hamiltonian += weight * pauli.to_spmatrix()
        return MatrixOperator(matrix=hamiltonian,
                              z2_symmetries=op_w.z2_symmetries,
                              name=op_w.name)
    elif operator.__class__ == TPBGroupedWeightedPauliOperator:
        op_tpb = cast(TPBGroupedWeightedPauliOperator, operator)
        op = WeightedPauliOperator(paulis=op_tpb.paulis,
                                   z2_symmetries=op_tpb.z2_symmetries,
                                   name=op_tpb.name)
        return to_matrix_operator(op)
    elif operator.__class__ == MatrixOperator:
        return cast(MatrixOperator, operator)
    else:
        raise AquaError("Unsupported type to convert to MatrixOperator: "
                        "{}".format(operator.__class__))
    def adjoint(self) -> OperatorBase:
        """The adjoint of a CVaRMeasurement is not defined.

        Returns:
            Does not return anything, raises an error.

        Raises:
            AquaError: The adjoint of a CVaRMeasurement is not defined.
        """
        raise AquaError('Adjoint of a CVaR measurement not defined')
Example #11
0
    def permute(self, permutation: List[int]) -> 'ListOp':
        """Permute the qubits of the operator.

        Args:
            permutation: A list defining where each qubit should be permuted. The qubit at index
                j should be permuted to position permutation[j].

        Returns:
            A new ListOp representing the permuted operator.

        Raises:
            AquaError: if indices do not define a new index for each qubit.
        """
        new_self = self
        circuit_size = max(permutation) + 1

        try:
            if self.num_qubits != len(permutation):
                raise AquaError("New index must be defined for each qubit of the operator.")
        except ValueError:
            raise AquaError("Permute is only possible if all operators in the ListOp have the "
                            "same number of qubits.") from ValueError
        if self.num_qubits < circuit_size:
            # pad the operator with identities
            new_self = self._expand_dim(circuit_size - self.num_qubits)
        qc = QuantumCircuit(circuit_size)
        # extend the indices to match the size of the circuit
        permutation \
            = list(filter(lambda x: x not in permutation, range(circuit_size))) + permutation

        # decompose permutation into sequence of transpositions
        transpositions = arithmetic.transpositions(permutation)
        for trans in transpositions:
            qc.swap(trans[0], trans[1])

        # pylint: disable=import-outside-toplevel,cyclic-import
        from ..primitive_ops.circuit_op import CircuitOp

        return CircuitOp(qc.reverse_ops()) @ new_self @ CircuitOp(qc)
            def get_parameter_expression(circuit, param):
                if len(circuit._parameter_table[param]) > 1:
                    raise NotImplementedError(
                        "OverlapDiag does not yet support multiple "
                        "gates parameterized by a single parameter. For such "
                        "circuits use LinCombFull")
                gate = circuit._parameter_table[param][0][0]
                if len(gate.params) > 1:
                    raise AquaError(
                        "OverlapDiag cannot yet support gates with more than one "
                        "parameter.")

                param_value = gate.params[0]
                return param_value
Example #13
0
    def group_subops(cls, list_op: ListOp, fast: Optional[bool] = None,
                     use_nx: Optional[bool] = None) -> ListOp:
        """Given a ListOp, attempt to group into Abelian ListOps of the same type.

        Args:
            list_op: The Operator to group into Abelian groups
            fast: Ignored - parameter will be removed in future release
            use_nx: Ignored - parameter will be removed in future release

        Returns:
            The grouped Operator.

        Raises:
            AquaError: If any of list_op's sub-ops is not ``PauliOp``.
        """
        if fast is not None or use_nx is not None:
            warnings.warn('Options `fast` and `use_nx` of `AbelianGrouper.group_subops` are '
                          'no longer used and are now deprecated and will be removed no '
                          'sooner than 3 months following the 0.8.0 release.')

        # TODO: implement direct way
        if isinstance(list_op, PauliSumOp):
            list_op = list_op.to_pauli_op()

        for op in list_op.oplist:
            if not isinstance(op, PauliOp):
                raise AquaError(
                    'Cannot determine Abelian groups if any Operator in list_op is not '
                    '`PauliOp`. E.g., {} ({})'.format(op, type(op)))

        edges = cls._commutation_graph(list_op)
        nodes = range(len(list_op))

        graph = rx.PyGraph()
        graph.add_nodes_from(nodes)
        graph.add_edges_from_no_data(edges)
        # Keys in coloring_dict are nodes, values are colors
        coloring_dict = rx.graph_greedy_color(graph)

        groups = {}  # type: Dict
        # sort items so that the output is consistent with all options (fast and use_nx)
        for idx, color in sorted(coloring_dict.items()):
            groups.setdefault(color, []).append(list_op[idx])

        group_ops = [list_op.__class__(group, abelian=True) for group in groups.values()]
        if len(group_ops) == 1:
            return group_ops[0] * list_op.coeff  # type: ignore
        return list_op.__class__(group_ops, coeff=list_op.coeff)  # type: ignore
Example #14
0
    def to_circuit(self) -> QuantumCircuit:
        """Returns the quantum circuit, representing the tensored operator.

        Returns:
            The circuit representation of the tensored operator.

        Raises:
            AquaError: for operators where a single underlying circuit can not be produced.
        """
        # pylint: disable=import-outside-toplevel,cyclic-import
        from ..state_fns.circuit_state_fn import CircuitStateFn
        circuit_op = self.to_circuit_op()
        if isinstance(circuit_op, (PrimitiveOp, CircuitStateFn)):
            return circuit_op.to_circuit()
        raise AquaError(
            'Conversion to_circuit supported only for operators, where a single '
            'underlying circuit can be produced.')
Example #15
0
    def evaluate_with_statevector(self, quantum_state):
        """

        Args:
            quantum_state (numpy.ndarray): quantum state

        Returns:
            float: the mean value
            float: the standard deviation
        Raises:
            AquaError: if Operator is empty
        """
        if self.is_empty():
            raise AquaError("Operator is empty, check the operator.")

        avg = np.vdot(quantum_state, self._matrix.dot(quantum_state))
        return avg, 0.0
    def __init__(
            self,
            primitive: Union[OperatorBase] = None,
            alpha: float = 1.0,
            coeff: Union[int, float, complex,
                         ParameterExpression] = 1.0) -> None:
        """
        Args:
            primitive: The ``OperatorBase`` which defines the diagonal operator
                       measurement.
            coeff: A coefficient by which to multiply the state function
            alpha: A real-valued parameter between 0 and 1 which specifies the
                   fraction of observed samples to include when computing the
                   objective value. alpha = 1 corresponds to a standard observable
                   expectation value. alpha = 0 corresponds to only using the single
                   sample with the lowest energy. alpha = 0.5 corresponds to ranking each
                   observation by lowest energy and using the best

        Raises:
            ValueError: TODO remove that this raises an error
            ValueError: If alpha is not in [0, 1].
            AquaError: If the primitive is not diagonal.
        """
        if primitive is None:
            raise ValueError

        if not 0 <= alpha <= 1:
            raise ValueError('The parameter alpha must be in [0, 1].')
        self._alpha = alpha

        if not _check_is_diagonal(primitive):
            raise AquaError(
                'Input operator to CVaRMeasurement must be diagonal, but is not:',
                str(primitive))

        super().__init__(primitive, coeff=coeff, is_measurement=True)
Example #17
0
    def to_circuit(self) -> QuantumCircuit:
        """Returns the quantum circuit, representing the SummedOp. In the first step,
        the SummedOp is converted to MatrixOp. This is straightforward for most operators,
        but it is not supported for operators containing parametrized PrimitiveOps (in that case,
        AquaError is raised). In the next step, the MatrixOp representation of SummedOp is
        converted to circuit. In most cases, if the summands themselves are unitary operators,
        the SummedOp itself is non-unitary and can not be converted to circuit. In that case,
        ExtensionError is raised in the underlying modules.

        Returns:
            The circuit representation of the summed operator.

        Raises:
            AquaError: if SummedOp can not be converted to MatrixOp (e.g. SummedOp is composed of
            parametrized PrimitiveOps).
        """
        # pylint: disable=import-outside-toplevel,cyclic-import
        from ..primitive_ops.matrix_op import MatrixOp
        matrix_op = self.to_matrix_op()
        if isinstance(matrix_op, MatrixOp):
            return matrix_op.to_circuit()
        raise AquaError(
            "The SummedOp can not be converted to circuit, because to_matrix_op did "
            "not return a MatrixOp.")
Example #18
0
    def sample_circuits(self,
                        circuit_sfns: Optional[List[CircuitStateFn]] = None,
                        param_bindings: Optional[List[Dict[Parameter, float]]] = None
                        ) -> Dict[int, Union[StateFn, List[StateFn]]]:
        r"""
        Samples the CircuitStateFns and returns a dict associating their ``id()`` values to their
        replacement DictStateFn or VectorStateFn. If param_bindings is provided,
        the CircuitStateFns are broken into their parameterizations, and a list of StateFns is
        returned in the dict for each circuit ``id()``. Note that param_bindings is provided here
        in a different format than in ``convert``, and lists of parameters within the dict is not
        supported, and only binding dicts which are valid to be passed into Terra can be included
        in this list.

        Args:
            circuit_sfns: The list of CircuitStateFns to sample.
            param_bindings: The parameterizations to bind to each CircuitStateFn.

        Returns:
            The dictionary mapping ids of the CircuitStateFns to their replacement StateFns.
        Raises:
            AquaError: if extracted circuits are empty.
        """
        if not circuit_sfns and not self._transpiled_circ_cache:
            raise AquaError('CircuitStateFn is empty and there is no cache.')

        if circuit_sfns:
            self._transpiled_circ_templates = None
            if self._statevector:
                circuits = [op_c.to_circuit(meas=False) for op_c in circuit_sfns]
            else:
                circuits = [op_c.to_circuit(meas=True) for op_c in circuit_sfns]

            try:
                self._transpiled_circ_cache = self.quantum_instance.transpile(circuits)
            except QiskitError:
                logger.debug(r'CircuitSampler failed to transpile circuits with unbound '
                             r'parameters. Attempting to transpile only when circuits are bound '
                             r'now, but this can hurt performance due to repeated transpilation.')
                self._transpile_before_bind = False
                self._transpiled_circ_cache = circuits
        else:
            circuit_sfns = list(self._circuit_ops_cache.values())

        if param_bindings is not None:
            if self._param_qobj:
                start_time = time()
                ready_circs = self._prepare_parameterized_run_config(param_bindings)
                end_time = time()
                logger.debug('Parameter conversion %.5f (ms)', (end_time - start_time) * 1000)
            else:
                start_time = time()
                ready_circs = [circ.assign_parameters(_filter_params(circ, binding))
                               for circ in self._transpiled_circ_cache
                               for binding in param_bindings]
                end_time = time()
                logger.debug('Parameter binding %.5f (ms)', (end_time - start_time) * 1000)
        else:
            ready_circs = self._transpiled_circ_cache

        results = self.quantum_instance.execute(ready_circs,
                                                had_transpiled=self._transpile_before_bind)

        if param_bindings is not None and self._param_qobj:
            self._clean_parameterized_run_config()

        # Wipe parameterizations, if any
        # self.quantum_instance._run_config.parameterizations = None

        sampled_statefn_dicts = {}
        for i, op_c in enumerate(circuit_sfns):
            # Taking square root because we're replacing a statevector
            # representation of probabilities.
            reps = len(param_bindings) if param_bindings is not None else 1
            c_statefns = []
            for j in range(reps):
                circ_index = (i * reps) + j
                circ_results = results.data(circ_index)

                if 'expval_measurement' in circ_results.get('snapshots', {}).get(
                        'expectation_value', {}):
                    snapshot_data = results.data(circ_index)['snapshots']
                    avg = snapshot_data['expectation_value']['expval_measurement'][0]['value']
                    if isinstance(avg, (list, tuple)):
                        # Aer versions before 0.4 use a list snapshot format
                        # which must be converted to a complex value.
                        avg = avg[0] + 1j * avg[1]
                    # Will be replaced with just avg when eval is called later
                    num_qubits = circuit_sfns[0].num_qubits
                    result_sfn = DictStateFn('0' * num_qubits,
                                             is_measurement=op_c.is_measurement) * avg
                elif self._statevector:
                    result_sfn = StateFn(op_c.coeff * results.get_statevector(circ_index),
                                         is_measurement=op_c.is_measurement)
                else:
                    shots = self.quantum_instance._run_config.shots
                    result_sfn = StateFn({b: (v / shots) ** 0.5 * op_c.coeff
                                          for (b, v) in results.get_counts(circ_index).items()},
                                         is_measurement=op_c.is_measurement)
                if self._attach_results:
                    result_sfn.execution_results = circ_results
                c_statefns.append(result_sfn)
            sampled_statefn_dicts[id(op_c)] = c_statefns
        return sampled_statefn_dicts
Example #19
0
def evolution_instruction(pauli_list, evo_time, num_time_slices,
                          controlled=False, power=1,
                          use_basis_gates=True, shallow_slicing=False,
                          barrier=False):
    """
    Construct the evolution circuit according to the supplied specification.

    Args:
        pauli_list (list([[complex, Pauli]])): The list of pauli terms corresponding
                                               to a single time slice to be evolved
        evo_time (Union(complex, float, Parameter, ParameterExpression)): The evolution time
        num_time_slices (int): The number of time slices for the expansion
        controlled (bool, optional): Controlled circuit or not
        power (int, optional): The power to which the unitary operator is to be raised
        use_basis_gates (bool, optional): boolean flag for indicating only using basis
                                          gates when building circuit.
        shallow_slicing (bool, optional): boolean flag for indicating using shallow
                                          qc.data reference repetition for slicing
        barrier (bool, optional): whether or not add barrier for every slice

    Returns:
        Instruction: The Instruction corresponding to specified evolution.

    Raises:
        AquaError: power must be an integer and greater or equal to 1
        ValueError: Unrecognized pauli
    """

    if not isinstance(power, (int, np.int)) or power < 1:
        raise AquaError("power must be an integer and greater or equal to 1.")

    state_registers = QuantumRegister(pauli_list[0][1].num_qubits)
    if controlled:
        inst_name = 'Controlled-Evolution^{}'.format(power)
        ancillary_registers = QuantumRegister(1)
        qc_slice = QuantumCircuit(state_registers, ancillary_registers, name=inst_name)
    else:
        inst_name = 'Evolution^{}'.format(power)
        qc_slice = QuantumCircuit(state_registers, name=inst_name)

    # for each pauli [IXYZ]+, record the list of qubit pairs needing CX's
    cnot_qubit_pairs = [None] * len(pauli_list)
    # for each pauli [IXYZ]+, record the highest index of the nontrivial pauli gate (X,Y, or Z)
    top_xyz_pauli_indices = [-1] * len(pauli_list)

    for pauli_idx, pauli in enumerate(reversed(pauli_list)):
        n_qubits = pauli[1].num_qubits
        # changes bases if necessary
        nontrivial_pauli_indices = []
        for qubit_idx in range(n_qubits):
            # pauli I
            if not pauli[1].z[qubit_idx] and not pauli[1].x[qubit_idx]:
                continue

            if cnot_qubit_pairs[pauli_idx] is None:
                nontrivial_pauli_indices.append(qubit_idx)

            if pauli[1].x[qubit_idx]:
                # pauli X
                if not pauli[1].z[qubit_idx]:
                    if use_basis_gates:
                        qc_slice.h(state_registers[qubit_idx])
                    else:
                        qc_slice.h(state_registers[qubit_idx])
                # pauli Y
                elif pauli[1].z[qubit_idx]:
                    if use_basis_gates:
                        qc_slice.u(pi / 2, -pi / 2, pi / 2, state_registers[qubit_idx])
                    else:
                        qc_slice.rx(pi / 2, state_registers[qubit_idx])
            # pauli Z
            elif pauli[1].z[qubit_idx] and not pauli[1].x[qubit_idx]:
                pass
            else:
                raise ValueError('Unrecognized pauli: {}'.format(pauli[1]))

        if nontrivial_pauli_indices:
            top_xyz_pauli_indices[pauli_idx] = nontrivial_pauli_indices[-1]

        # insert lhs cnot gates
        if cnot_qubit_pairs[pauli_idx] is None:
            cnot_qubit_pairs[pauli_idx] = list(zip(
                sorted(nontrivial_pauli_indices)[:-1],
                sorted(nontrivial_pauli_indices)[1:]
            ))

        for pair in cnot_qubit_pairs[pauli_idx]:
            qc_slice.cx(state_registers[pair[0]], state_registers[pair[1]])

        # insert Rz gate
        if top_xyz_pauli_indices[pauli_idx] >= 0:

            # Because Parameter does not support complexity number operation; thus, we do
            # the following tricks to generate parameterized instruction.
            # We assume the coefficient in the pauli is always real. and can not do imaginary time
            # evolution
            if isinstance(evo_time, (Parameter, ParameterExpression)):
                lam = 2.0 * pauli[0] / num_time_slices
                lam = lam.real if lam.imag == 0 else lam
                lam = lam * evo_time
            else:
                lam = (2.0 * pauli[0] * evo_time / num_time_slices).real

            if not controlled:
                if use_basis_gates:
                    qc_slice.p(lam, state_registers[top_xyz_pauli_indices[pauli_idx]])
                else:
                    qc_slice.rz(lam, state_registers[top_xyz_pauli_indices[pauli_idx]])
            else:
                if use_basis_gates:
                    qc_slice.p(lam / 2, state_registers[top_xyz_pauli_indices[pauli_idx]])
                    qc_slice.cx(ancillary_registers[0],
                                state_registers[top_xyz_pauli_indices[pauli_idx]])
                    qc_slice.p(-lam / 2, state_registers[top_xyz_pauli_indices[pauli_idx]])
                    qc_slice.cx(ancillary_registers[0],
                                state_registers[top_xyz_pauli_indices[pauli_idx]])
                else:
                    qc_slice.crz(lam, ancillary_registers[0],
                                 state_registers[top_xyz_pauli_indices[pauli_idx]])

        # insert rhs cnot gates
        for pair in reversed(cnot_qubit_pairs[pauli_idx]):
            qc_slice.cx(state_registers[pair[0]], state_registers[pair[1]])

        # revert bases if necessary
        for qubit_idx in range(n_qubits):
            if pauli[1].x[qubit_idx]:
                # pauli X
                if not pauli[1].z[qubit_idx]:
                    if use_basis_gates:
                        qc_slice.h(state_registers[qubit_idx])
                    else:
                        qc_slice.h(state_registers[qubit_idx])
                # pauli Y
                elif pauli[1].z[qubit_idx]:
                    if use_basis_gates:
                        qc_slice.u(-pi / 2, -pi / 2, pi / 2, state_registers[qubit_idx])
                    else:
                        qc_slice.rx(-pi / 2, state_registers[qubit_idx])
    # repeat the slice
    if shallow_slicing:
        logger.info('Under shallow slicing mode, the qc.data reference is repeated shallowly. '
                    'Thus, changing gates of one slice of the output circuit might affect '
                    'other slices.')
        if barrier:
            qc_slice.barrier(state_registers)
        qc_slice.data *= (num_time_slices * power)
        qc = qc_slice
    else:
        qc = QuantumCircuit(name=inst_name)
        for _ in range(num_time_slices * power):
            qc += qc_slice
            if barrier:
                qc.barrier(state_registers)
    return qc.to_instruction()
Example #20
0
    def get_hessian(
        self,
        operator: OperatorBase,
        params: Optional[Union[Tuple[ParameterExpression, ParameterExpression],
                               List[Tuple[ParameterExpression,
                                          ParameterExpression]]]] = None
    ) -> OperatorBase:
        """Get the Hessian for the given operator w.r.t. the given parameters

        Args:
            operator: Operator w.r.t. which we take the Hessian.
            params: Parameters w.r.t. which we compute the Hessian.

        Returns:
            Operator which represents the gradient w.r.t. the given params.

        Raises:
            ValueError: If ``params`` contains a parameter not present in ``operator``.
            AquaError: If the coefficient of the operator could not be reduced to 1.
                        AquaError: If the differentiation of a combo_fn requires JAX but the package
                        is not installed.
            TypeError: If the operator does not include a StateFn given by a quantum circuit
            TypeError: If the parameters were given in an unsupported format.
            Exception: Unintended code is reached
        """
        def is_coeff_c(coeff, c):
            if isinstance(coeff, ParameterExpression):
                expr = coeff._symbol_expr
                return expr == c
            return coeff == c

        if isinstance(params, (ParameterVector, list)):
            # Case: a list of parameters were given, compute the Hessian for all param pairs
            if all(isinstance(param, ParameterExpression) for param in params):
                return ListOp([
                    ListOp([
                        self.get_hessian(operator, (p0, p1)) for p1 in params
                    ]) for p0 in params
                ])
            # Case: a list was given containing tuples of parameter pairs.
            # Compute the Hessian entries corresponding to these pairs of parameters.
            elif all(isinstance(param, tuple) for param in params):
                return ListOp([
                    self.get_hessian(operator, param_pair)
                    for param_pair in params
                ])

        # If a gradient is requested w.r.t a single parameter, then call the
        # Gradient().get_gradient method.
        if isinstance(params, ParameterExpression):
            return Gradient(grad_method=self._hess_method).get_gradient(
                operator, params)

        if (not isinstance(params, tuple)) or (not len(params) == 2):
            raise TypeError("Parameters supplied in unsupported format.")

        # By this point, it's only one parameter tuple
        p_0 = params[0]
        p_1 = params[1]

        # Handle Product Rules
        if not is_coeff_c(operator._coeff, 1.0):
            # Separate the operator from the coefficient
            coeff = operator._coeff
            op = operator / coeff
            # Get derivative of the operator (recursively)
            d0_op = self.get_hessian(op, p_0)
            d1_op = self.get_hessian(op, p_1)
            # ..get derivative of the coeff
            d0_coeff = self.parameter_expression_grad(coeff, p_0)
            d1_coeff = self.parameter_expression_grad(coeff, p_1)

            dd_op = self.get_hessian(op, params)
            dd_coeff = self.parameter_expression_grad(d0_coeff, p_1)

            grad_op = 0
            # Avoid creating operators that will evaluate to zero
            if dd_op != ~Zero @ One and not is_coeff_c(coeff, 0):
                grad_op += coeff * dd_op
            if d0_op != ~Zero @ One and not is_coeff_c(d1_coeff, 0):
                grad_op += d1_coeff * d0_op
            if d1_op != ~Zero @ One and not is_coeff_c(d0_coeff, 0):
                grad_op += d0_coeff * d1_op
            if not is_coeff_c(dd_coeff, 0):
                grad_op += dd_coeff * op

            if grad_op == 0:
                return ~Zero @ One

            return grad_op

        # Base Case, you've hit a ComposedOp!
        # Prior to execution, the composite operator was standardized and coefficients were
        # collected. Any operator measurements were converted to Pauli-Z measurements and rotation
        # circuits were applied. Additionally, all coefficients within ComposedOps were collected
        # and moved out front.
        if isinstance(operator, ComposedOp):

            if not is_coeff_c(operator.coeff, 1.):
                raise AquaError(
                    'Operator pre-processing failed. Coefficients were not properly '
                    'collected inside the ComposedOp.')

            # Do some checks to make sure operator is sensible
            # TODO enable compatibility with sum of CircuitStateFn operators
            if not isinstance(operator[-1], CircuitStateFn):
                raise TypeError(
                    'The gradient framework is compatible with states that are given as '
                    'CircuitStateFn')

            return self.hess_method.convert(operator, params)

        # This is the recursive case where the chain rule is handled
        elif isinstance(operator, ListOp):
            # These operators correspond to (d_op/d θ0,θ1) for op in operator.oplist
            # and params = (θ0,θ1)
            dd_ops = [self.get_hessian(op, params) for op in operator.oplist]

            # Note that this check to see if the ListOp has a default combo_fn
            # will fail if the user manually specifies the default combo_fn.
            # I.e operator = ListOp([...], combo_fn=lambda x:x) will not pass this check and
            # later on jax will try to differentiate it and fail.
            # An alternative is to check the byte code of the operator's combo_fn against the
            # default one.
            # This will work but look very ugly and may have other downsides I'm not aware of
            if operator.combo_fn == ListOp([]).combo_fn:
                return ListOp(oplist=dd_ops)
            elif isinstance(operator, SummedOp):
                return SummedOp(oplist=dd_ops)
            elif isinstance(operator, TensoredOp):
                return TensoredOp(oplist=dd_ops)

            # These operators correspond to (d g_i/d θ0)•(d g_i/d θ1) for op in operator.oplist
            # and params = (θ0,θ1)
            d1d0_ops = ListOp([
                ListOp([
                    Gradient(grad_method=self._hess_method).convert(op, param)
                    for param in params
                ],
                       combo_fn=np.prod) for op in operator.oplist
            ])

            if operator.grad_combo_fn:
                first_partial_combo_fn = operator.grad_combo_fn
                if _HAS_JAX:
                    second_partial_combo_fn = jit(
                        grad(lambda x: first_partial_combo_fn(x)[0],
                             holomorphic=True))
                else:
                    raise AquaError(
                        'This automatic differentiation function is based on JAX. Please '
                        'install jax and use `import jax.numpy as jnp` instead of '
                        '`import numpy as np` when defining a combo_fn.')
            else:
                if _HAS_JAX:
                    first_partial_combo_fn = jit(
                        grad(operator.combo_fn, holomorphic=True))
                    second_partial_combo_fn = jit(
                        grad(lambda x: first_partial_combo_fn(x)[0],
                             holomorphic=True))
                else:
                    raise AquaError(
                        'This automatic differentiation function is based on JAX. Please install '
                        'jax and use `import jax.numpy as jnp` instead of `import numpy as np` when'
                        'defining a combo_fn.')

            # For a general combo_fn F(g_0, g_1, ..., g_k)
            # dF/d θ0,θ1 = sum_i: (∂F/∂g_i)•(d g_i/ d θ0,θ1) + (∂F/∂^2 g_i)•(d g_i/d θ0)•(d g_i/d
            # θ1)

            # term1 = (∂F/∂g_i)•(d g_i/ d θ0,θ1)
            term1 = ListOp([
                ListOp(operator.oplist, combo_fn=first_partial_combo_fn),
                ListOp(dd_ops)
            ],
                           combo_fn=lambda x: np.dot(x[1], x[0]))
            # term2 = (∂F/∂^2 g_i)•(d g_i/d θ0)•(d g_i/d θ1)
            term2 = ListOp([
                ListOp(operator.oplist, combo_fn=second_partial_combo_fn),
                d1d0_ops
            ],
                           combo_fn=lambda x: np.dot(x[1], x[0]))

            return SummedOp([term1, term2])

        elif isinstance(operator, StateFn):
            if not operator.is_measurement:
                return self.hess_method.convert(operator, params)

            else:
                raise TypeError(
                    'The computation of Hessians is only supported for Operators which '
                    'represent expectation values or quantum states.')

        else:
            raise TypeError(
                'The computation of Hessians is only supported for Operators which '
                'represent expectation values.')
Example #21
0
    def convert(self,
                operator: OperatorBase,
                params: Optional[Dict[Parameter,
                                      Union[float, List[float], List[List[float]]]]] = None
                ) -> OperatorBase:
        r"""
        Converts the Operator to one in which the CircuitStateFns are replaced by
        DictStateFns or VectorStateFns. Extracts the CircuitStateFns out of the Operator,
        caches them, calls ``sample_circuits`` below to get their converted replacements,
        and replaces the CircuitStateFns in operator with the replacement StateFns.

        Args:
            operator: The Operator to convert
            params: A dictionary mapping parameters to either single binding values or lists of
                binding values.

        Returns:
            The converted Operator with CircuitStateFns replaced by DictStateFns or VectorStateFns.
        Raises:
            AquaError: if extracted circuits are empty.
        """
        if self._last_op is None or id(operator) != id(self._last_op):
            # Clear caches
            self._last_op = operator
            self._reduced_op_cache = None
            self._circuit_ops_cache = None
            self._transpiled_circ_cache = None
            self._transpile_before_bind = True

        if not self._reduced_op_cache:
            operator_dicts_replaced = operator.to_circuit_op()
            self._reduced_op_cache = operator_dicts_replaced.reduce()

        if not self._circuit_ops_cache:
            self._circuit_ops_cache = {}
            self._extract_circuitstatefns(self._reduced_op_cache)
            if not self._circuit_ops_cache:
                raise AquaError(
                    'Circuits are empty. '
                    'Check that the operator is an instance of CircuitStateFn or its ListOp.'
                )

        if params is not None and len(params.keys()) > 0:
            p_0 = list(params.values())[0]  # type: ignore
            if isinstance(p_0, (list, np.ndarray)):
                num_parameterizations = len(cast(List, p_0))
                param_bindings = [{param: value_list[i]  # type: ignore
                                   for (param, value_list) in params.items()}
                                  for i in range(num_parameterizations)]
            else:
                num_parameterizations = 1
                param_bindings = [params]  # type: ignore

        else:
            param_bindings = None
            num_parameterizations = 1

        # Don't pass circuits if we have in the cache, the sampling function knows to use the cache
        circs = list(self._circuit_ops_cache.values()) if not self._transpiled_circ_cache else None
        p_b = cast(List[Dict[Parameter, float]], param_bindings)
        sampled_statefn_dicts = self.sample_circuits(circuit_sfns=circs,
                                                     param_bindings=p_b)

        def replace_circuits_with_dicts(operator, param_index=0):
            if isinstance(operator, CircuitStateFn):
                return sampled_statefn_dicts[id(operator)][param_index]
            elif isinstance(operator, ListOp):
                return operator.traverse(partial(replace_circuits_with_dicts,
                                                 param_index=param_index))
            else:
                return operator

        if params:
            return ListOp([replace_circuits_with_dicts(self._reduced_op_cache, param_index=i)
                           for i in range(num_parameterizations)])
        else:
            return replace_circuits_with_dicts(self._reduced_op_cache, param_index=0)
Example #22
0
def to_weighted_pauli_operator(
        operator: Union[WeightedPauliOperator, TPBGroupedWeightedPauliOperator, MatrixOperator]) \
        -> WeightedPauliOperator:
    """
    Converting a given operator to `WeightedPauliOperator`

    Args:
        operator: one of supported operator type
    Returns:
        The converted weighted pauli operator
    Raises:
        AquaError: Unsupported type to convert

    Warnings:
        Converting time from a MatrixOperator to a Pauli-type Operator grows exponentially.
        If you are converting a system with large number of qubits, it will take time.
        You can turn on DEBUG logging to check the progress.
    """
    if operator.__class__ == WeightedPauliOperator:
        return cast(WeightedPauliOperator, operator)
    elif operator.__class__ == TPBGroupedWeightedPauliOperator:
        # destroy the grouping but keep z2 symmetries info
        op_tpb = cast(TPBGroupedWeightedPauliOperator, operator)
        return WeightedPauliOperator(paulis=op_tpb.paulis,
                                     z2_symmetries=op_tpb.z2_symmetries,
                                     name=op_tpb.name)
    elif operator.__class__ == MatrixOperator:
        op_m = cast(MatrixOperator, operator)
        if op_m.is_empty():
            return WeightedPauliOperator(paulis=[])
        if op_m.num_qubits > 10:
            logger.warning(
                "Converting time from a MatrixOperator to a Pauli-type Operator grows "
                "exponentially. If you are converting a system with large number of "
                "qubits, it will take time. And now you are converting a %s-qubit "
                "Hamiltonian. You can turn on DEBUG logging to check the progress."
                "", op_m.num_qubits)
        num_qubits = op_m.num_qubits
        coeff = 2**(-num_qubits)

        paulis = []
        possible_basis = 'IXYZ'
        if op_m.dia_matrix is not None:
            possible_basis = 'IZ'

        if logger.isEnabledFor(logging.DEBUG):
            logger.debug(
                "Converting a MatrixOperator to a Pauli-type Operator:")
            TextProgressBar(sys.stderr)
        results = parallel_map(_conversion,
                               list(
                                   itertools.product(possible_basis,
                                                     repeat=num_qubits)),
                               task_kwargs={"matrix": op_m._matrix},
                               num_processes=aqua_globals.num_processes)
        for trace_value, pauli in results:
            weight = trace_value * coeff
            if weight != 0.0 and np.abs(weight) > op_m.atol:
                paulis.append([weight, pauli])

        return WeightedPauliOperator(paulis,
                                     z2_symmetries=operator.z2_symmetries,
                                     name=operator.name)
    else:
        raise AquaError(
            "Unsupported type to convert to WeightedPauliOperator: "
            "{}".format(operator.__class__))
Example #23
0
    def evolve(self,
               state_in,
               evo_time=0,
               num_time_slices=0,
               expansion_mode='trotter',
               expansion_order=1):
        """
        Carry out the eoh evolution for the operator under supplied specifications.

        Args:
            state_in (Union(list,numpy.array)): A vector representing the initial state
                                            for the evolution
            evo_time (Union(complex, float)): The evolution time
            num_time_slices (int): The number of time slices for the expansion
            expansion_mode (str): The mode under which the expansion is to be done.
                Currently support 'trotter', which follows the expansion as discussed in
                http://science.sciencemag.org/content/273/5278/1073,
                and 'suzuki', which corresponds to the discussion in
                https://arxiv.org/pdf/quant-ph/0508139.pdf
            expansion_order (int): The order for suzuki expansion

        Returns:
            numpy.array: Return the matrix vector multiplication result.
        Raises:
            ValueError: Invalid arguments
            AquaError: if Operator is empty
        """
        # pylint: disable=import-outside-toplevel
        from .op_converter import to_weighted_pauli_operator

        if self.is_empty():
            raise AquaError("Operator is empty, check the operator.")

        # pylint: disable=no-member
        if num_time_slices < 0 or not isinstance(num_time_slices, int):
            raise ValueError(
                'Number of time slices should be a non-negative integer.')
        if expansion_mode not in ['trotter', 'suzuki']:
            raise ValueError(
                'Expansion mode {} not supported.'.format(expansion_mode))

        if num_time_slices == 0:
            return scila.expm(
                -1.j * evo_time * self._matrix.tocsc()) @ state_in
        else:

            pauli_op = to_weighted_pauli_operator(self)
            pauli_list = pauli_op.reorder_paulis()

            if len(pauli_list) == 1:
                approx_matrix_slice = scila.expm(
                    -1.j * evo_time / num_time_slices * pauli_list[0][0] *
                    pauli_list[0][1].to_spmatrix().tocsc())
            else:
                if expansion_mode == 'trotter':
                    approx_matrix_slice = reduce(lambda x, y: x @ y, [
                        scila.expm(-1.j * evo_time / num_time_slices * c *
                                   p.to_spmatrix().tocsc())
                        for c, p in pauli_list
                    ])
                # suzuki expansion
                elif expansion_mode == 'suzuki':
                    approx_matrix_slice = MatrixOperator._suzuki_expansion_slice_matrix(
                        pauli_list, -1.j * evo_time / num_time_slices,
                        expansion_order)
                else:
                    raise ValueError('Unrecognized expansion mode {}.'.format(
                        expansion_mode))
            return reduce(lambda x, y: x @ y,
                          [approx_matrix_slice] * num_time_slices) @ state_in
Example #24
0
    def get_gradient(
        self, operator: OperatorBase, params: Union[ParameterExpression,
                                                    ParameterVector,
                                                    List[ParameterExpression]]
    ) -> OperatorBase:
        """Get the gradient for the given operator w.r.t. the given parameters

        Args:
            operator: Operator w.r.t. which we take the gradient.
            params: Parameters w.r.t. which we compute the gradient.

        Returns:
            Operator which represents the gradient w.r.t. the given params.

        Raises:
            ValueError: If ``params`` contains a parameter not present in ``operator``.
            AquaError: If the coefficient of the operator could not be reduced to 1.
            AquaError: If the differentiation of a combo_fn requires JAX but the package is not
                       installed.
            TypeError: If the operator does not include a StateFn given by a quantum circuit
            Exception: Unintended code is reached
        """
        def is_coeff_c(coeff, c):
            if isinstance(coeff, ParameterExpression):
                expr = coeff._symbol_expr
                return expr == c
            return coeff == c

        if isinstance(params, (ParameterVector, list)):
            param_grads = [
                self.get_gradient(operator, param) for param in params
            ]
            # If get_gradient returns None, then the corresponding parameter was probably not
            # present in the operator. This needs to be looked at more carefully as other things can
            # probably trigger a return of None.
            absent_params = [
                params[i] for i, grad_ops in enumerate(param_grads)
                if grad_ops is None
            ]
            if len(absent_params) > 0:
                raise ValueError(
                    'The following parameters do not appear in the provided operator: ',
                    absent_params)
            return ListOp(param_grads)

        # By now params is a single parameter
        param = params
        # Handle Product Rules
        if not is_coeff_c(operator._coeff, 1.0):
            # Separate the operator from the coefficient
            coeff = operator._coeff
            op = operator / coeff
            # Get derivative of the operator (recursively)
            d_op = self.get_gradient(op, param)
            # ..get derivative of the coeff
            d_coeff = self.parameter_expression_grad(coeff, param)

            grad_op = 0
            if d_op != ~Zero @ One and not is_coeff_c(coeff, 0.0):
                grad_op += coeff * d_op
            if op != ~Zero @ One and not is_coeff_c(d_coeff, 0.0):
                grad_op += d_coeff * op
            if grad_op == 0:
                grad_op = ~Zero @ One
            return grad_op

        # Base Case, you've hit a ComposedOp!
        # Prior to execution, the composite operator was standardized and coefficients were
        # collected. Any operator measurements were converted to Pauli-Z measurements and rotation
        # circuits were applied. Additionally, all coefficients within ComposedOps were collected
        # and moved out front.
        if isinstance(operator, ComposedOp):

            # Gradient of an expectation value
            if not is_coeff_c(operator._coeff, 1.0):
                raise AquaError(
                    'Operator pre-processing failed. Coefficients were not properly '
                    'collected inside the ComposedOp.')

            # Do some checks to make sure operator is sensible
            # TODO add compatibility with sum of circuit state fns
            if not isinstance(operator[-1], CircuitStateFn):
                raise TypeError(
                    'The gradient framework is compatible with states that are given as '
                    'CircuitStateFn')

            return self.grad_method.convert(operator, param)

        elif isinstance(operator, CircuitStateFn):
            # Gradient of an a state's sampling probabilities
            if not is_coeff_c(operator._coeff, 1.0):
                raise AquaError(
                    'Operator pre-processing failed. Coefficients were not properly '
                    'collected inside the ComposedOp.')
            return self.grad_method.convert(operator, param)

        # Handle the chain rule
        elif isinstance(operator, ListOp):
            grad_ops = [self.get_gradient(op, param) for op in operator.oplist]

            # Note: this check to see if the ListOp has a default combo_fn
            # will fail if the user manually specifies the default combo_fn.
            # I.e operator = ListOp([...], combo_fn=lambda x:x) will not pass this check and
            # later on jax will try to differentiate it and raise an error.
            # An alternative is to check the byte code of the operator's combo_fn against the
            # default one.
            if operator._combo_fn == ListOp([])._combo_fn:
                return ListOp(oplist=grad_ops)
            elif isinstance(operator, SummedOp):
                return SummedOp(
                    oplist=[grad for grad in grad_ops
                            if grad != ~Zero @ One]).reduce()
            elif isinstance(operator, TensoredOp):
                return TensoredOp(oplist=grad_ops)

            if operator.grad_combo_fn:
                grad_combo_fn = operator.grad_combo_fn
            else:
                if _HAS_JAX:
                    grad_combo_fn = jit(
                        grad(operator._combo_fn, holomorphic=True))
                else:
                    raise AquaError(
                        'This automatic differentiation function is based on JAX. Please install '
                        'jax and use `import jax.numpy as jnp` instead of `import numpy as np` when'
                        'defining a combo_fn.')

            def chain_rule_combo_fn(x):
                result = np.dot(x[1], x[0])
                if isinstance(result, np.ndarray):
                    result = list(result)
                return result

            return ListOp([
                ListOp(operator.oplist, combo_fn=grad_combo_fn),
                ListOp(grad_ops)
            ],
                          combo_fn=chain_rule_combo_fn)