def __init__(
        self,
        symmetries: List[Pauli],
        sq_paulis: List[Pauli],
        sq_list: List[int],
        tapering_values: Optional[List[int]] = None,
    ):
        """
        Args:
            symmetries: the list of Pauli objects representing the Z_2 symmetries
            sq_paulis: the list of single - qubit Pauli objects to construct the
                                     Clifford operators
            sq_list: the list of support of the single-qubit Pauli objects used to build
                                 the Clifford operators
            tapering_values: values determines the sector.
        Raises:
            OpflowError: Invalid paulis
        """
        if len(symmetries) != len(sq_paulis):
            raise OpflowError(
                "Number of Z2 symmetries has to be the same as number " "of single-qubit pauli x."
            )

        if len(sq_paulis) != len(sq_list):
            raise OpflowError(
                "Number of single-qubit pauli x has to be the same "
                "as length of single-qubit list."
            )

        if tapering_values is not None:
            if len(sq_list) != len(tapering_values):
                raise OpflowError(
                    "The length of single-qubit list has "
                    "to be the same as length of tapering values."
                )

        self._symmetries = symmetries
        self._sq_paulis = sq_paulis
        self._sq_list = sq_list
        self._tapering_values = tapering_values
Ejemplo n.º 2
0
    def permute(self, permutation: List[int]) -> 'OperatorBase':
        """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:
            OpflowError: 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 OpflowError("New index must be defined for each qubit of the operator.")
        except ValueError:
            raise OpflowError("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=cyclic-import
        from ..primitive_ops.circuit_op import CircuitOp

        return CircuitOp(qc.reverse_ops()) @ new_self @ CircuitOp(qc)
Ejemplo n.º 3
0
    def permute(self, permutation: List[int]) -> 'DictStateFn':
        new_num_qubits = max(permutation) + 1
        if self.num_qubits != len(permutation):
            raise OpflowError("New index must be defined for each qubit of the operator.")

        # helper function to permute the key
        def perm(key):
            list_key = ['0'] * new_num_qubits
            for i, k in enumerate(permutation):
                list_key[k] = key[i]
            return ''.join(list_key)

        new_dict = {perm(key): value for key, value in self.primitive.items()}
        return DictStateFn(new_dict, coeff=self.coeff, is_measurement=self.is_measurement)
Ejemplo n.º 4
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:
            OpflowError: if indices do not define a new index for each qubit.
        """
        set_perm = set(permutation)
        if len(set_perm) != len(permutation) or any(index < 0
                                                    for index in set_perm):
            raise OpflowError(f"List {permutation} is not a permutation.")

        if len(permutation) != self.num_qubits:
            raise OpflowError(
                "List of indices to permute must have the same size as Pauli Operator"
            )
        length = max(permutation) + 1

        if length > self.num_qubits:
            spop = self.primitive.tensor(
                SparsePauliOp(Pauli("I" * (length - self.num_qubits))))
        else:
            spop = self.primitive.copy()

        permutation = [i for i in range(length) if i not in permutation
                       ] + permutation
        permu_arr = np.arange(length)[np.argsort(permutation)]
        spop.paulis.x = spop.paulis.x[:, permu_arr]
        spop.paulis.z = spop.paulis.z[:, permu_arr]
        return PauliSumOp(spop, self.coeff)
Ejemplo n.º 5
0
    def group_subops(cls, list_op: Union[ListOp, PauliSumOp]) -> ListOp:
        """Given a ListOp, attempt to group into Abelian ListOps of the same type.

        Args:
            list_op: The Operator to group into Abelian groups

        Returns:
            The grouped Operator.

        Raises:
            OpflowError: If any of list_op's sub-ops is not ``PauliOp``.
        """
        if isinstance(list_op, ListOp):
            for op in list_op.oplist:
                if not isinstance(op, PauliOp):
                    raise OpflowError(
                        "Cannot determine Abelian groups if any Operator in list_op is not "
                        f"`PauliOp`. E.g., {op} ({type(op)})")

        edges = cls._anti_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 = defaultdict(list)
        for idx, color in coloring_dict.items():
            groups[color].append(idx)

        if isinstance(list_op, PauliSumOp):
            primitive = list_op.primitive
            return SummedOp(
                [
                    PauliSumOp(primitive[group], grouping_type="TPB")
                    for group in groups.values()
                ],
                coeff=list_op.coeff,
            )

        group_ops: List[ListOp] = [
            list_op.__class__([list_op[idx] for idx in group], abelian=True)
            for group in groups.values()
        ]
        if len(group_ops) == 1:
            return group_ops[0].mul(list_op.coeff)
        return list_op.__class__(group_ops, coeff=list_op.coeff)
Ejemplo n.º 6
0
    def to_circuit(self) -> QuantumCircuit:
        """Returns the quantum circuit, representing the tensored operator.

        Returns:
            The circuit representation of the tensored operator.

        Raises:
            OpflowError: for operators where a single underlying circuit can not be produced.
        """
        circuit_op = self.to_circuit_op()
        # pylint: disable=cyclic-import
        from ..state_fns.circuit_state_fn import CircuitStateFn
        from ..primitive_ops.primitive_op import PrimitiveOp
        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.')
Ejemplo n.º 7
0
    def consistent_tapering(self, operator: PauliSumOp) -> OperatorBase:
        """
        Tapering the `operator` with the same manner of how this tapered operator
        is created. i.e., using the same Cliffords and tapering values.

        Args:
            operator: the to-be-tapered operator

        Returns:
            The tapered operator

        Raises:
            OpflowError: The given operator does not commute with the symmetry
        """
        for symmetry in self._symmetries:
            commutator_op = cast(PauliSumOp, commutator(operator, PauliOp(symmetry)))
            if not commutator_op.is_zero():
                raise OpflowError(
                    "The given operator does not commute with " "the symmetry, can not taper it."
                )

        return self.taper(operator)
Ejemplo n.º 8
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 parameterized PrimitiveOps (in that case,
        OpflowError 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:
            OpflowError: if SummedOp can not be converted to MatrixOp (e.g. SummedOp is composed of
            parameterized PrimitiveOps).
        """
        # pylint: disable=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 OpflowError("The SummedOp can not be converted to circuit, because to_matrix_op did "
                          "not return a MatrixOp.")
Ejemplo n.º 9
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:
            OpflowError: 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 OpflowError("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(''.join(new_pauli_list)), self.coeff)
    def taper(self, operator: PauliSumOp) -> OperatorBase:
        """
        Taper an operator based on the z2_symmetries info and sector defined by `tapering_values`.
        The `tapering_values` will be stored into the resulted operator for a record.

        Args:
            operator: the to-be-tapered operator.

        Returns:
            If tapering_values is None: [:class`PauliSumOp`]; otherwise, :class:`PauliSumOp`
        Raises:
            OpflowError: Z2 symmetries, single qubit pauli and single qubit list cannot be empty
        """
        if not self._symmetries or not self._sq_paulis or not self._sq_list:
            raise OpflowError(
                "Z2 symmetries, single qubit pauli and single qubit list cannot be empty."
            )

        # If the operator is zero then we can skip the following. We still need to taper the
        # operator to reduce its size i.e. the number of qubits so for example 0*"IIII" could
        # taper to 0*"II" when symmetries remove two qubits.
        if not operator.is_zero():
            for clifford in self.cliffords:
                operator = cast(PauliSumOp, clifford @ operator @ clifford)
                operator = operator.reduce(atol=0)

        if self._tapering_values is None:
            tapered_ops_list = [
                self._taper(operator, list(coeff))
                for coeff in itertools.product([1, -1],
                                               repeat=len(self._sq_list))
            ]
            tapered_ops: OperatorBase = ListOp(tapered_ops_list)
        else:
            tapered_ops = self._taper(operator, self._tapering_values)

        return tapered_ops
    def taper(self, operator: PauliSumOp) -> OperatorBase:
        """
        Taper an operator based on the z2_symmetries info and sector defined by `tapering_values`.
        The `tapering_values` will be stored into the resulted operator for a record.

        Args:
            operator: the to-be-tapered operator.

        Returns:
            If tapering_values is None: [:class`PauliSumOp`]; otherwise, :class:`PauliSumOp`
        Raises:
            OpflowError: Z2 symmetries, single qubit pauli and single qubit list cannot be empty
        """
        if not self._symmetries or not self._sq_paulis or not self._sq_list:
            raise OpflowError(
                "Z2 symmetries, single qubit pauli and single qubit list cannot be empty."
            )

        if operator.is_zero():
            logger.warning(
                "The operator is empty, return the empty operator directly.")
            return operator

        for clifford in self.cliffords:
            operator = cast(PauliSumOp, clifford @ operator @ clifford)

        if self._tapering_values is None:
            tapered_ops_list = [
                self._taper(operator, list(coeff))
                for coeff in itertools.product([1, -1],
                                               repeat=len(self._sq_list))
            ]
            tapered_ops: OperatorBase = ListOp(tapered_ops_list)
        else:
            tapered_ops = self._taper(operator, self._tapering_values)

        return tapered_ops
Ejemplo n.º 12
0
    def __init__(
        self,
        primitive: Union[OperatorBase] = None,
        alpha: float = 1.0,
        coeff: Union[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].
            OpflowError: 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 OpflowError(
                "Input operator to CVaRMeasurement must be diagonal, but is not:",
                str(primitive))

        super().__init__(primitive, coeff=coeff, is_measurement=True)
Ejemplo n.º 13
0
 def to_instruction(self, massive: bool = False) -> Instruction:
     mat_op = self.to_matrix_op(massive=massive)
     if not isinstance(mat_op, MatrixOp):
         raise OpflowError("to_instruction is not allowed for ListOp.")
     return mat_op.to_instruction()
Ejemplo n.º 14
0
    def sample_circuits(
        self,
        circuit_sfns: Optional[List[CircuitStateFn]] = None,
        param_bindings: Optional[List[Dict[Parameter, float]]] = None,
    ) -> Dict[int, 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:
            OpflowError: if extracted circuits are empty.
        """
        if not circuit_sfns and not self._transpiled_circ_cache:
            raise OpflowError("CircuitStateFn is empty and there is no cache.")

        if circuit_sfns:
            self._transpiled_circ_templates = None
            if self._statevector or circuit_sfns[0].from_operator:
                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, pass_manager=self.quantum_instance.unbound_pass_manager
                )
            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

        # run transpiler passes on bound circuits
        if self._transpile_before_bind and self.quantum_instance.bound_pass_manager is not None:
            ready_circs = self.quantum_instance.transpile(
                ready_circs, pass_manager=self.quantum_instance.bound_pass_manager
            )

        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:
                    avg = circ_results["expval_measurement"]
                    # Will be replaced with just avg when eval is called later
                    num_qubits = circuit_sfns[0].num_qubits
                    result_sfn = DictStateFn(
                        "0" * num_qubits,
                        coeff=avg * op_c.coeff,
                        is_measurement=op_c.is_measurement,
                        from_operator=op_c.from_operator,
                    )
                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 = DictStateFn(
                        {
                            b: (v / shots) ** 0.5 * op_c.coeff
                            for (b, v) in results.get_counts(circ_index).items()
                        },
                        is_measurement=op_c.is_measurement,
                        from_operator=op_c.from_operator,
                    )
                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
Ejemplo n.º 15
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:
            OpflowError: if extracted circuits are empty.
        """
        # check if the operator should be cached
        op_id = operator.instance_id
        # op_id = id(operator)
        if op_id not in self._cached_ops.keys():
            # delete cache if we only want to cache one operator
            if self._caching == "last":
                self.clear_cache()

            # convert to circuit and reduce
            operator_dicts_replaced = operator.to_circuit_op()
            self._reduced_op_cache = operator_dicts_replaced.reduce()

            # extract circuits
            self._circuit_ops_cache = {}
            self._extract_circuitstatefns(self._reduced_op_cache)
            if not self._circuit_ops_cache:
                raise OpflowError(
                    "Circuits are empty. "
                    "Check that the operator is an instance of CircuitStateFn or its ListOp."
                )
            self._transpiled_circ_cache = None
            self._transpile_before_bind = True
        else:
            # load the cached circuits
            self._reduced_op_cache = self._cached_ops[op_id].reduced_op_cache
            self._circuit_ops_cache = self._cached_ops[op_id].circuit_ops_cache
            self._transpiled_circ_cache = self._cached_ops[op_id].transpiled_circ_cache
            self._transpile_before_bind = self._cached_ops[op_id].transpile_before_bind
            self._transpiled_circ_templates = self._cached_ops[op_id].transpiled_circ_templates

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

        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

        # store the operator we constructed, if it isn't stored already
        if op_id not in self._cached_ops.keys():
            op_cache = OperatorCache()
            op_cache.reduced_op_cache = self._reduced_op_cache
            op_cache.circuit_ops_cache = self._circuit_ops_cache
            op_cache.transpiled_circ_cache = self._transpiled_circ_cache
            op_cache.transpile_before_bind = self._transpile_before_bind
            op_cache.transpiled_circ_templates = self._transpiled_circ_templates
            self._cached_ops[op_id] = op_cache

        if return_as_list:
            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)