Beispiel #1
0
    def __init__(self, target: QubitSetInput = None):
        """
        Args:
            target (QubitSetInput): The target qubits
                of the reduced density matrix. Default is `None`, and the
                full density matrix is returned.

        Examples:
            >>> ResultType.DensityMatrix(target=[0, 1])
        """
        self._target = QubitSet(target)
        ascii_symbols = ["DensityMatrix"] * len(self._target) if self._target else ["DensityMatrix"]
        super().__init__(ascii_symbols=ascii_symbols)
Beispiel #2
0
    def __init__(self, target: QubitSetInput = None):
        """
        Args:
            target (int, Qubit, or iterable of int / Qubit, optional): The target qubits that the
                result type is requested for. Default is `None`, which means all qubits for the
                circuit.

        Examples:
            >>> ResultType.Probability(target=[0, 1])
        """
        self._target = QubitSet(target)
        ascii_symbols = ["Probability"] * len(self._target) if self._target else ["Probability"]
        super().__init__(ascii_symbols=ascii_symbols)
    def qubit_intersection(self, qubits: QubitSetInput) -> QubitSetInput:
        """
        Returns subset of passed qubits that match the criteria.

        Args:
            qubits (QubitSetInput): A qubit or set of qubits that may match the criteria.

        Returns:
            QubitSetInput: The subset of passed qubits that match the criteria.
        """
        target_qubit = QubitSet(qubits)
        if self._qubits is None:
            return target_qubit
        return self._qubits.intersection(target_qubit)
Beispiel #4
0
    def i(target: QubitSetInput) -> Iterable[Instruction]:
        """Registers this function into the circuit class.

        Args:
            target (Qubit, int, or iterable of Qubit / int): Target qubit(s)

        Returns:
            Iterable[Instruction]: `Iterable` of I instructions.

        Examples:
            >>> circ = Circuit().i(0)
            >>> circ = Circuit().i([0, 1, 2])
        """
        return [Instruction(Gate.I(), target=qubit) for qubit in QubitSet(target)]
Beispiel #5
0
    def phaseshift(target: QubitInput, angle: float) -> Instruction:
        """Registers this function into the circuit class.

        Args:
            target (Qubit or int): Target qubit index.
            angle (float): Angle in radians.

        Returns:
            Instruction: PhaseShift instruction.

        Examples:
            >>> circ = Circuit().phaseshift(0, 0.15)
        """
        return [Instruction(Gate.PhaseShift(angle), target=qubit) for qubit in QubitSet(target)]
Beispiel #6
0
    def rz(target: QubitInput, angle: float) -> Iterable[Instruction]:
        """Registers this function into the circuit class.

        Args:
            target (Qubit or int): Target qubit index.
            angle (float): Angle in radians.

        Returns:
            Iterable[Instruction]: Rz instruction.

        Examples:
            >>> circ = Circuit().rz(0, 0.15)
        """
        return [
            Instruction(Rz(angle), target=qubit) for qubit in QubitSet(target)
        ]
    def phase_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]:
        """Registers this function into the circuit class.

        Args:
            target (Qubit, int, or iterable of Qubit / int): Target qubit(s)
            gamma (float): Probability of phase damping.

        Returns:
            Iterable[Instruction]: `Iterable` of PhaseDamping instructions.

        Examples:
            >>> circ = Circuit().phase_damping(0, gamma=0.1)
        """
        return [
            Instruction(Noise.PhaseDamping(gamma=gamma), target=qubit) for qubit in QubitSet(target)
        ]
Beispiel #8
0
    def _ascii_group_items(
        circuit_qubits: QubitSet,
        items: List[Union[Instruction, ResultType]],
    ) -> List[Tuple[QubitSet, List[Instruction]]]:
        """
        Group instructions in a moment for ASCII diagram

        Args:
            circuit_qubits (QubitSet): set of qubits in circuit
            items (List[Union[Instruction, ResultType]]): list of instructions or result types

        Returns:
            List[(QubitSet, List[Union[Instruction, ResultType]])]: list of grouped instructions
            or result types
        """
        groupings = []
        for item in items:
            # Can only print Gate and Noise operators for instructions at the moment
            if isinstance(item, Instruction) and not isinstance(
                    item.operator, (Gate, Noise, CompilerDirective)):
                continue

            if (isinstance(item, ResultType) and not item.target) or (
                    isinstance(item, Instruction)
                    and isinstance(item.operator, CompilerDirective)):
                qubit_range = circuit_qubits
            else:
                qubit_range = QubitSet(
                    range(min(item.target),
                          max(item.target) + 1))

            found_grouping = False
            for group in groupings:
                qubits_added = group[0]
                instr_group = group[1]
                # Take into account overlapping multi-qubit gates
                if not qubits_added.intersection(set(qubit_range)):
                    instr_group.append(item)
                    qubits_added.update(qubit_range)
                    found_grouping = True
                    break

            if not found_grouping:
                groupings.append((qubit_range, [item]))

        return groupings
    def bit_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction]:
        """Registers this function into the circuit class.

        Args:
            target (Qubit, int, or iterable of Qubit / int): Target qubit(s)
            probability (float): Probability of bit flipping.

        Returns:
            Iterable[Instruction]: `Iterable` of BitFlip instructions.

        Examples:
            >>> circ = Circuit().bit_flip(0, probability=0.1)
        """
        return [
            Instruction(Noise.BitFlip(probability=probability), target=qubit)
            for qubit in QubitSet(target)
        ]
    def amplitude_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]:
        """Registers this function into the circuit class.

        Args:
            target (Qubit, int, or iterable of Qubit / int): Target qubit(s).
            gamma (float): decaying rate of the amplitude damping channel.

        Returns:
            Iterable[Instruction]: `Iterable` of AmplitudeDamping instructions.

        Examples:
            >>> circ = Circuit().amplitude_damping(0, gamma=0.1)
        """
        return [
            Instruction(Noise.AmplitudeDamping(gamma=gamma), target=qubit)
            for qubit in QubitSet(target)
        ]
def test_apply_noise_to_moments_initialization_2QubitNoise_1(
        circuit_2qubit, noise_2qubit):
    circ = apply_noise_to_moments(
        circuit_2qubit,
        [noise_2qubit],
        target_qubits=QubitSet([0, 1]),
        position="initialization",
    )

    expected = (Circuit().add_instruction(Instruction(
        noise_2qubit,
        [0, 1])).add_instruction(Instruction(Gate.X(), 0)).add_instruction(
            Instruction(Gate.Y(), 1)).add_instruction(Instruction(
                Gate.X(),
                0)).add_instruction(Instruction(Gate.X(), 1)).add_instruction(
                    Instruction(Gate.CNot(), [0, 1])))

    assert circ == expected
def test_apply_noise_to_gates_1QubitNoise_2(circuit_2qubit, noise_1qubit):
    circ = apply_noise_to_gates(
        circuit_2qubit,
        [noise_1qubit],
        target_gates=[Gate.X],
        target_qubits=QubitSet(0),
    )

    expected = (Circuit().add_instruction(Instruction(
        Gate.X(),
        0)).add_instruction(Instruction(noise_1qubit, 0)).add_instruction(
            Instruction(Gate.Y(), 1)).add_instruction(Instruction(
                Gate.X(), 0)).add_instruction(Instruction(
                    noise_1qubit, 0)).add_instruction(Instruction(
                        Gate.X(),
                        1)).add_instruction(Instruction(Gate.CNot(), [0, 1])))

    assert circ == expected
Beispiel #13
0
    def depolarizing(target: QubitSetInput,
                     probability: float) -> Iterable[Instruction]:
        """Registers this function into the circuit class.

        Args:
            target (QubitSetInput): Target qubit(s)
            probability (float): Probability of depolarizing.

        Returns:
            Iterable[Instruction]: `Iterable` of Depolarizing instructions.

        Examples:
            >>> circ = Circuit().depolarizing(0, probability=0.1)
        """
        return [
            Instruction(Noise.Depolarizing(probability=probability),
                        target=qubit) for qubit in QubitSet(target)
        ]
def test_apply_noise_to_moments_readout_1QubitNoise_2(circuit_2qubit,
                                                      noise_1qubit):
    circ = apply_noise_to_moments(
        circuit_2qubit,
        [noise_1qubit],
        target_qubits=QubitSet(1),
        position="readout",
    )

    expected = (Circuit().add_instruction(Instruction(
        Gate.X(),
        0)).add_instruction(Instruction(Gate.Y(), 1)).add_instruction(
            Instruction(Gate.X(), 0)).add_instruction(Instruction(
                Gate.X(), 1)).add_instruction(Instruction(
                    Gate.CNot(),
                    [0, 1])).add_instruction(Instruction(noise_1qubit, 1)))

    assert circ == expected
    def pauli_channel(
        target: QubitSetInput, probX: float, probY: float, probZ: float
    ) -> Iterable[Instruction]:
        """Registers this function into the circuit class.

        Args:
            target (Qubit, int, or iterable of Qubit / int): Target qubit(s)
                probability List[float]: Probabilities for the Pauli X, Y and Z noise
                happening in the Kraus channel.

        Returns:
            Iterable[Instruction]: `Iterable` of PauliChannel instructions.

        Examples:
            >>> circ = Circuit().pauli_channel(0,probX=0.1,probY=0.2,probZ=0.3)
        """
        return [
            Instruction(Noise.PauliChannel(probX=probX, probY=probY, probZ=probZ), target=qubit)
            for qubit in QubitSet(target)
        ]
Beispiel #16
0
def test_apply_noise_to_gates_1QubitNoise_not_dense(circuit_2qubit_not_dense, noise_1qubit):
    circ = apply_noise_to_gates(
        circuit_2qubit_not_dense,
        [noise_1qubit],
        target_qubits=QubitSet([0, 1]),
        target_gates=None,
    )

    expected_moments = Moments()
    expected_moments._add(Instruction(Gate.X(), 0), noise_index=1)
    expected_moments.add_noise(Instruction(noise_1qubit, 0), "gate_noise", 1)
    expected_moments._add(Instruction(Gate.Y(), 1), noise_index=1)
    expected_moments.add_noise(Instruction(noise_1qubit, 1), "gate_noise", 1)
    expected_moments._add(Instruction(Gate.X(), 0), noise_index=1)
    expected_moments.add_noise(Instruction(noise_1qubit, 0), "gate_noise", 1)
    expected_moments._add(Instruction(Gate.CNot(), [0, 1]), noise_index=2)
    expected_moments.add_noise(Instruction(noise_1qubit, 0), "gate_noise", 1)
    expected_moments.add_noise(Instruction(noise_1qubit, 1), "gate_noise", 2)

    assert circ.moments == expected_moments
def test_apply_noise_to_gates_2QubitNoise_2(circuit_3qubit, noise_2qubit,
                                            noise_1qubit, noise_1qubit_2):
    circ = apply_noise_to_gates(
        circuit_3qubit,
        [noise_1qubit, noise_2qubit, noise_1qubit_2],
        target_gates=[Gate.CZ],
        target_qubits=QubitSet([1, 2]),
    )

    expected = (Circuit().add_instruction(Instruction(
        Gate.X(),
        0)).add_instruction(Instruction(Gate.Y(), 1)).add_instruction(
            Instruction(Gate.CNot(), [0, 1])).add_instruction(
                Instruction(Gate.Z(), 2)).add_instruction(
                    Instruction(Gate.CZ(), [2, 1])).add_instruction(
                        Instruction(noise_1qubit, 1)).add_instruction(
                            Instruction(noise_1qubit, 2)).add_instruction(
                                Instruction(noise_2qubit,
                                            [2, 1])).add_instruction(
                                                Instruction(noise_1qubit_2,
                                                            1)).
                add_instruction(Instruction(
                    noise_1qubit_2,
                    2)).add_instruction(Instruction(
                        Gate.CNot(), [0, 2])).add_instruction(
                            Instruction(Gate.CZ(), [1, 2])).add_instruction(
                                Instruction(noise_1qubit, 1)).add_instruction(
                                    Instruction(
                                        noise_1qubit, 2)).add_instruction(
                                            Instruction(
                                                noise_2qubit,
                                                [1, 2])).add_instruction(
                                                    Instruction(
                                                        noise_1qubit_2,
                                                        1)).add_instruction(
                                                            Instruction(
                                                                noise_1qubit_2,
                                                                2)))

    assert circ == expected
Beispiel #18
0
def check_noise_target_qubits(
    circuit: Circuit, target_qubits: Optional[QubitSetInput] = None
) -> QubitSet:
    """
    Helper function to check whether all the target_qubits are positive integers.
    Args:
        target_qubits (Optional[QubitSetInput] = None): Index or indices of qubit(s).
    Returns:
        target_qubits: QubitSet
    """
    if target_qubits is None:
        target_qubits = circuit.qubits
    else:
        target_qubits = wrap_with_list(target_qubits)
        if not all(isinstance(q, int) for q in target_qubits):
            raise TypeError("target_qubits must be integer(s)")
        if not all(q >= 0 for q in target_qubits):
            raise ValueError("target_qubits must contain only non-negative integers.")

        target_qubits = QubitSet(target_qubits)

    return target_qubits
Beispiel #19
0
    def __init__(self,
                 operator: InstructionOperator,
                 target: QubitSetInput = None):
        """
        InstructionOperator includes objects of type `Gate` only.

        Args:
            operator (InstructionOperator): Operator for the instruction.
            target (int, Qubit, or iterable of int / Qubit): Target qubits that the operator is
                applied to.

        Raises:
            ValueError: If `operator` is empty or any integer in `target` does not meet the `Qubit`
                or `QubitSet` class requirements. Also, if operator qubit count does not equal
                the size of the target qubit set.
            TypeError: If a `Qubit` class can't be constructed from `target` due to an incorrect
                `typing`.

        Examples:
            >>> Instruction(Gate.CNot(), [0, 1])
            Instruction('operator': CNOT, 'target': QubitSet(Qubit(0), Qubit(1)))
            >>> instr = Instruction(Gate.CNot()), QubitSet([0, 1])])
            Instruction('operator': CNOT, 'target': QubitSet(Qubit(0), Qubit(1)))
            >>> instr = Instruction(Gate.H(), 0)
            Instruction('operator': H, 'target': QubitSet(Qubit(0),))
            >>> instr = Instruction(Gate.Rx(0.12), 0)
            Instruction('operator': Rx, 'target': QubitSet(Qubit(0),))
        """
        if not operator:
            raise ValueError("Operator cannot be empty")
        target_set = QubitSet(target)
        if isinstance(
                operator,
                QuantumOperator) and len(target_set) != operator.qubit_count:
            raise ValueError(
                f"Operator qubit count {operator.qubit_count} must be equal to"
                f" size of target qubit set {target_set}")
        self._operator = operator
        self._target = target_set
    def generalized_amplitude_damping(
        target: QubitSetInput, gamma: float, probability: float
    ) -> Iterable[Instruction]:
        """Registers this function into the circuit class.

        Args:
            target (Qubit, int, or iterable of Qubit / int): Target qubit(s).
            p(float): Probability of the system being excited by the environment.
            gamma (float): The damping rate of the amplitude damping channel.

        Returns:
            Iterable[Instruction]: `Iterable` of GeneralizedAmplitudeDamping instructions.

        Examples:
            >>> circ = Circuit().generalized_amplitude_damping(0, probability = 0.9, gamma=0.1)
        """
        return [
            Instruction(
                Noise.GeneralizedAmplitudeDamping(gamma=gamma, probability=probability),
                target=qubit,
            )
            for qubit in QubitSet(target)
        ]
    def __init__(
        self, ascii_symbols: List[str], observable: Observable, target: QubitSetInput = None
    ):
        """
        Args:
            ascii_symbols (List[str]): ASCII string symbols for the result type. This is used when
                printing a diagram of circuits.
            observable (Observable): the observable for the result type
            target (QubitSetInput): Target qubits that the
                result type is requested for. Default is `None`, which means the observable must
                only operate on 1 qubit and it will be applied to all qubits in parallel

        Raises:
            ValueError: if target=None and the observable's qubit count is not 1.
                Or, if `target!=None` and the observable's qubit count and the number of target
                qubits are not equal. Or, if `target!=None` and the observable's qubit count and
                the number of `ascii_symbols` are not equal.
        """
        super().__init__(ascii_symbols)
        self._observable = observable
        self._target = QubitSet(target)
        if not self._target:
            if self._observable.qubit_count != 1:
                raise ValueError(
                    f"Observable {self._observable} must only operate on 1 qubit for target=None"
                )
        else:
            if self._observable.qubit_count != len(self._target):
                raise ValueError(
                    f"Observable's qubit count {self._observable.qubit_count} and "
                    f"the size of the target qubit set {self._target} must be equal"
                )
            if self._observable.qubit_count != len(self.ascii_symbols):
                raise ValueError(
                    "Observable's qubit count and the number of ASCII symbols must be equal"
                )
Beispiel #22
0
    def _ascii_diagram_column(
            circuit_qubits: QubitSet, items: List[Union[Instruction,
                                                        ResultType]]) -> str:
        """
        Return a column in the ASCII string diagram of the circuit for a given list of items.

        Args:
            circuit_qubits (QubitSet): qubits in circuit
            items (List[Union[Instruction, ResultType]]): list of instructions or result types

        Returns:
            str: An ASCII string diagram for the specified moment in time for a column.
        """
        symbols = {qubit: "-" for qubit in circuit_qubits}
        margins = {qubit: " " for qubit in circuit_qubits}

        for item in items:
            if isinstance(item, ResultType) and not item.target:
                target_qubits = circuit_qubits
                qubits = circuit_qubits
                ascii_symbols = [item.ascii_symbols[0]] * len(circuit_qubits)
            elif isinstance(item, Instruction) and isinstance(
                    item.operator, CompilerDirective):
                target_qubits = circuit_qubits
                qubits = circuit_qubits
                ascii_symbol = item.ascii_symbols[0]
                marker = "*" * len(ascii_symbol)
                num_after = len(circuit_qubits) - 1
                after = ["|"] * (num_after - 1) + ([marker]
                                                   if num_after else [])
                ascii_symbols = [ascii_symbol] + after
            else:
                target_qubits = item.target
                qubits = circuit_qubits.intersection(
                    set(range(min(item.target),
                              max(item.target) + 1)))
                ascii_symbols = item.ascii_symbols

            for qubit in qubits:
                # Determine if the qubit is part of the item or in the middle of a
                # multi qubit item.
                if qubit in target_qubits:
                    item_qubit_index = [
                        index for index, q in enumerate(target_qubits)
                        if q == qubit
                    ][0]
                    symbols[qubit] = ascii_symbols[item_qubit_index]
                else:
                    symbols[qubit] = "|"

                # Set the margin to be a connector if not on the first qubit
                if qubit != min(target_qubits):
                    margins[qubit] = "|"

        symbols_width = max([len(symbol) for symbol in symbols.values()])

        output = ""
        for qubit in circuit_qubits:
            output += "{0:{width}}\n".format(margins[qubit],
                                             width=symbols_width + 1)
            output += "{0:{fill}{align}{width}}\n".format(symbols[qubit],
                                                          fill="-",
                                                          align="<",
                                                          width=symbols_width +
                                                          1)
        return output
 def qubits(self) -> QubitSet:
     """QubitSet: Get a copy of the qubits for this circuit."""
     return QubitSet(self._moments.qubits)
    def apply_readout_noise(
        self,
        noise: Union[Type[Noise], Iterable[Type[Noise]]],
        target_qubits: Optional[QubitSetInput] = None,
    ) -> Circuit:
        """Apply `noise` right before measurement in every qubit (default) or target_qubits`.

        Only when `target_qubits` is given can the noise be applied to an empty circuit.

        When `noise.qubit_count` > 1, the number of qubits in target_qubits must be equal
        to `noise.qubit_count`.

        Args:
            noise (Union[Type[Noise], Iterable[Type[Noise]]]): Noise channel(s) to be applied
            to the circuit.
            target_qubits (Union[QubitSetInput, optional]): Index or indices of qubit(s).
                Default=None.

        Returns:
            Circuit: self

        Raises:
            TypeError:
                If `noise` is not Noise type.
                If `target_qubits` has non-integers.
            IndexError:
                If applying noise to an empty circuit.
            ValueError:
                If `target_qubits` has negative integers.
                If `noise.qubit_count` > 1 and the number of qubits in target_qubits is
                not the same as `noise.qubit_count`.

        Examples:
            >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1)
            >>> print(circ)

            >>> noise = Noise.Depolarizing(probability=0.1)
            >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1)
            >>> print(circ.apply_initialization_noise(noise))

            >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1)
            >>> print(circ.apply_initialization_noise(noise, target_qubits = 1))

            >>> circ = Circuit()
            >>> print(circ.apply_initialization_noise(noise, target_qubits = [0, 1]))

        """
        if (len(self.qubits) == 0) and (target_qubits is None):
            raise IndexError(
                "target_qubits must be provided in order to apply the readout noise \
to an empty circuit.")

        if target_qubits is None:
            target_qubits = self.qubits
        else:
            if not isinstance(target_qubits, list):
                target_qubits = [target_qubits]
            if not all(isinstance(q, int) for q in target_qubits):
                raise TypeError("target_qubits must be integer(s)")
            if not all(q >= 0 for q in target_qubits):
                raise ValueError(
                    "target_qubits must contain only non-negative integers.")
            target_qubits = QubitSet(target_qubits)

        # make noise a list
        noise = wrap_with_list(noise)
        for noise_channel in noise:
            if not isinstance(noise_channel, Noise):
                raise TypeError("Noise must be an instance of the Noise class")
            if noise_channel.qubit_count > 1 and noise_channel.qubit_count != len(
                    target_qubits):
                raise ValueError(
                    "target_qubits needs to be provided for this multi-qubit noise channel, and \
the number of qubits in target_qubits must be the same as defined by the multi-qubit noise channel."
                )

        return apply_noise_to_moments(self, noise, target_qubits, "readout")
Beispiel #25
0
class Moments(Mapping[MomentsKey, Instruction]):
    """
    An ordered mapping of `MomentsKey` to `Instruction`. The core data structure that
    contains instructions, ordering they are inserted in, and time slices when they
    occur. `Moments` implements `Mapping` and functions the same as a read-only
    dictionary. It is mutable only through the `add()` method.

    This data structure is useful to determine a dependency of instructions, such as
    printing or optimizing circuit structure, before sending it to a quantum
    device. The original insertion order is preserved and can be retrieved via the `values()`
    method.

    Args:
        instructions (Iterable[Instruction], optional): Instructions to initialize self.
            Default = [].

    Examples:
        >>> moments = Moments()
        >>> moments.add([Instruction(Gate.H(), 0), Instruction(Gate.CNot(), [0, 1])])
        >>> moments.add([Instruction(Gate.H(), 0), Instruction(Gate.H(), 1)])
        >>> for i, item in enumerate(moments.items()):
        ...     print(f"Item {i}")
        ...     print(f"\\tKey: {item[0]}")
        ...     print(f"\\tValue: {item[1]}")
        ...
        Item 0
            Key: MomentsKey(time=0, qubits=QubitSet([Qubit(0)]))
            Value: Instruction('operator': H, 'target': QubitSet([Qubit(0)]))
        Item 1
            Key: MomentsKey(time=1, qubits=QubitSet([Qubit(0), Qubit(1)]))
            Value: Instruction('operator': CNOT, 'target': QubitSet([Qubit(0), Qubit(1)]))
        Item 2
            Key: MomentsKey(time=2, qubits=QubitSet([Qubit(0)]))
            Value: Instruction('operator': H, 'target': QubitSet([Qubit(0)]))
        Item 3
            Key: MomentsKey(time=2, qubits=QubitSet([Qubit(1)]))
            Value: Instruction('operator': H, 'target': QubitSet([Qubit(1)]))
    """
    def __init__(self, instructions: Iterable[Instruction] = []):
        self._moments: OrderedDict[MomentsKey, Instruction] = OrderedDict()
        self._max_times: Dict[Qubit, int] = {}
        self._qubits = QubitSet()
        self._depth = 0

        self.add(instructions)

    @property
    def depth(self) -> int:
        """int: Get the depth (number of slices) of self."""
        return self._depth

    @property
    def qubit_count(self) -> int:
        """int: Get the number of qubits used across all of the instructions."""
        return len(self._qubits)

    @property
    def qubits(self) -> QubitSet:
        """
        QubitSet: Get the qubits used across all of the instructions. The order of qubits is based
        on the order in which the instructions were added.

        Note:
            Don't mutate this object, any changes may impact the behavior of this class and / or
            consumers. If you need to mutate this, then copy it via `QubitSet(moments.qubits())`.
        """
        return self._qubits

    def time_slices(self) -> Dict[int, List[Instruction]]:
        """
        Get instructions keyed by time.

        Returns:
            Dict[int, List[Instruction]]: Key is the time and value is a list of instructions that
            occur at that moment in time. The order of instructions is in no particular order.

        Note:
            This is a computed result over self and can be freely mutated. This is re-computed with
            every call, with a computational runtime O(N) where N is the number
            of instructions in self.
        """

        time_slices = {}
        for key, instruction in self._moments.items():
            instructions = time_slices.get(key.time, [])
            instructions.append(instruction)
            time_slices[key.time] = instructions

        return time_slices

    def add(self, instructions: Iterable[Instruction]) -> None:
        """
        Add instructions to self.

        Args:
            instructions (Iterable[Instruction]): Instructions to add to self. The instruction
            is added to the max time slice in which the instruction fits.
        """
        for instruction in instructions:
            self._add(instruction)

    def _add(self, instruction: Instruction) -> None:
        qubit_range = instruction.target
        time = max([self._max_time_for_qubit(qubit)
                    for qubit in qubit_range]) + 1

        # Mark all qubits in qubit_range with max_time
        for qubit in qubit_range:
            self._max_times[qubit] = max(time, self._max_time_for_qubit(qubit))

        self._moments[MomentsKey(time, instruction.target)] = instruction
        self._qubits.update(instruction.target)
        self._depth = max(self._depth, time + 1)

    def _max_time_for_qubit(self, qubit: Qubit) -> int:
        return self._max_times.get(qubit, -1)

    #
    # Implement abstract methods, default to calling selfs underlying dictionary
    #

    def keys(self) -> KeysView[MomentsKey]:
        """Return a view of self's keys."""
        return self._moments.keys()

    def items(self) -> ItemsView[MomentsKey, Instruction]:
        """Return a view of self's (key, instruction)."""
        return self._moments.items()

    def values(self) -> ValuesView[Instruction]:
        """Return a view of self's instructions."""
        return self._moments.values()

    def get(self, key: MomentsKey, default=None) -> Instruction:
        """
        Get the instruction in self by key.

        Args:
            key (MomentsKey): Key of the instruction to fetch.
            default (Any, optional): Value to return if `key` is not in `moments`. Default = `None`.

        Returns:
            Instruction: `moments[key]` if `key` in `moments`, else `default` is returned.
        """
        return self._moments.get(key, default)

    def __getitem__(self, key):
        return self._moments.__getitem__(key)

    def __iter__(self):
        return self._moments.__iter__()

    def __len__(self):
        return self._moments.__len__()

    def __contains__(self, item):
        return self._moments.__contains__(item)

    def __eq__(self, other):
        if isinstance(other, Moments):
            return (self._moments) == (other._moments)
        return NotImplemented

    def __ne__(self, other):
        result = self.__eq__(other)
        if result is not NotImplemented:
            return not result
        return NotImplemented

    def __repr__(self):
        return self._moments.__repr__()

    def __str__(self):
        return self._moments.__str__()
Beispiel #26
0
class Moments(Mapping[MomentsKey, Instruction]):
    """
    An ordered mapping of `MomentsKey` or `NoiseMomentsKey` to `Instruction`. The
    core data structure that contains instructions, ordering they are inserted in, and
    time slices when they occur. `Moments` implements `Mapping` and functions the same as
    a read-only dictionary. It is mutable only through the `add()` method.

    This data structure is useful to determine a dependency of instructions, such as
    printing or optimizing circuit structure, before sending it to a quantum
    device. The original insertion order is preserved and can be retrieved via the `values()`
    method.

    Args:
        instructions (Iterable[Instruction], optional): Instructions to initialize self.
            Default = None.

    Examples:
        >>> moments = Moments()
        >>> moments.add([Instruction(Gate.H(), 0), Instruction(Gate.CNot(), [0, 1])])
        >>> moments.add([Instruction(Gate.H(), 0), Instruction(Gate.H(), 1)])
        >>> for i, item in enumerate(moments.items()):
        ...     print(f"Item {i}")
        ...     print(f"\\tKey: {item[0]}")
        ...     print(f"\\tValue: {item[1]}")
        ...
        Item 0
            Key: MomentsKey(time=0, qubits=QubitSet([Qubit(0)]))
            Value: Instruction('operator': H, 'target': QubitSet([Qubit(0)]))
        Item 1
            Key: MomentsKey(time=1, qubits=QubitSet([Qubit(0), Qubit(1)]))
            Value: Instruction('operator': CNOT, 'target': QubitSet([Qubit(0), Qubit(1)]))
        Item 2
            Key: MomentsKey(time=2, qubits=QubitSet([Qubit(0)]))
            Value: Instruction('operator': H, 'target': QubitSet([Qubit(0)]))
        Item 3
            Key: MomentsKey(time=2, qubits=QubitSet([Qubit(1)]))
            Value: Instruction('operator': H, 'target': QubitSet([Qubit(1)]))
    """

    def __init__(self, instructions: Iterable[Instruction] = None):
        self._moments: OrderedDict[MomentsKey, Instruction] = OrderedDict()
        self._max_times: Dict[Qubit, int] = {}
        self._qubits = QubitSet()
        self._depth = 0
        self._time_all_qubits = -1

        self.add(instructions or [])

    @property
    def depth(self) -> int:
        """int: Get the depth (number of slices) of self."""
        return self._depth

    @property
    def qubit_count(self) -> int:
        """int: Get the number of qubits used across all of the instructions."""
        return len(self._qubits)

    @property
    def qubits(self) -> QubitSet:
        """
        QubitSet: Get the qubits used across all of the instructions. The order of qubits is based
        on the order in which the instructions were added.

        Note:
            Don't mutate this object, any changes may impact the behavior of this class and / or
            consumers. If you need to mutate this, then copy it via `QubitSet(moments.qubits())`.
        """
        return self._qubits

    def time_slices(self) -> Dict[int, List[Instruction]]:
        """
        Get instructions keyed by time.

        Returns:
            Dict[int, List[Instruction]]: Key is the time and value is a list of instructions that
            occur at that moment in time. The order of instructions is in no particular order.

        Note:
            This is a computed result over self and can be freely mutated. This is re-computed with
            every call, with a computational runtime O(N) where N is the number
            of instructions in self.
        """

        time_slices = {}
        self.sort_moments()
        for key, instruction in self._moments.items():
            instructions = time_slices.get(key.time, [])
            instructions.append(instruction)
            time_slices[key.time] = instructions

        return time_slices

    def add(self, instructions: Iterable[Instruction], noise_index: int = 0) -> None:
        """
        Add instructions to self.

        Args:
            instructions (Iterable[Instruction]): Instructions to add to self. The instruction is
                added to the max time slice in which the instruction fits.
        """
        for instruction in instructions:
            self._add(instruction, noise_index)

    def _add(self, instruction: Instruction, noise_index: int = 0) -> None:
        operator = instruction.operator
        if isinstance(operator, CompilerDirective):
            time = self._update_qubit_times(self._qubits)
            self._moments[MomentsKey(time, None, MomentType.COMPILER_DIRECTIVE, 0)] = instruction
            self._depth = time + 1
            self._time_all_qubits = time
        elif isinstance(operator, Noise):
            self.add_noise(instruction)
        else:
            qubit_range = instruction.target
            time = self._update_qubit_times(qubit_range)
            self._moments[
                MomentsKey(time, instruction.target, MomentType.GATE, noise_index)
            ] = instruction
            self._qubits.update(instruction.target)
            self._depth = max(self._depth, time + 1)

    def _update_qubit_times(self, qubits):
        qubit_max_times = [self._max_time_for_qubit(qubit) for qubit in qubits] + [
            self._time_all_qubits
        ]
        time = max(qubit_max_times) + 1
        # Update time for all specified qubits
        for qubit in qubits:
            self._max_times[qubit] = time
        return time

    def add_noise(
        self, instruction: Instruction, input_type: str = "noise", noise_index: int = 0
    ) -> None:

        qubit_range = instruction.target
        time = max(0, *[self._max_time_for_qubit(qubit) for qubit in qubit_range])
        if input_type == MomentType.INITIALIZATION_NOISE:
            time = 0

        while MomentsKey(time, qubit_range, input_type, noise_index) in self._moments:
            noise_index = noise_index + 1

        self._moments[MomentsKey(time, qubit_range, input_type, noise_index)] = instruction
        self._qubits.update(qubit_range)

    def sort_moments(self) -> None:
        """
        Make the disordered moments in order.

        1. Make the readout noise in the end
        2. Make the initialization noise at the beginning
        """
        # key for NOISE, GATE and GATE_NOISE
        key_noise = []
        # key for INITIALIZATION_NOISE
        key_initialization_noise = []
        # key for READOUT_NOISE
        key_readout_noise = []
        moment_copy = OrderedDict()
        sorted_moment = OrderedDict()

        for key, instruction in self._moments.items():
            moment_copy[key] = instruction
            if key.moment_type == MomentType.READOUT_NOISE:
                key_readout_noise.append(key)
            elif key.moment_type == MomentType.INITIALIZATION_NOISE:
                key_initialization_noise.append(key)
            else:
                key_noise.append(key)

        for key in key_initialization_noise:
            sorted_moment[key] = moment_copy[key]
        for key in key_noise:
            sorted_moment[key] = moment_copy[key]
        # find the max time in the circuit and make it the time for readout noise
        max_time = max(self._depth - 1, 0)

        for key in key_readout_noise:
            sorted_moment[
                MomentsKey(max_time, key.qubits, MomentType.READOUT_NOISE, key.noise_index)
            ] = moment_copy[key]

        self._moments = sorted_moment

    def _max_time_for_qubit(self, qubit: Qubit) -> int:
        # -1 if qubit is unoccupied because the first instruction will have an index of 0
        return self._max_times.get(qubit, -1)

    #
    # Implement abstract methods, default to calling selfs underlying dictionary
    #

    def keys(self) -> KeysView[MomentsKey]:
        """Return a view of self's keys."""
        return self._moments.keys()

    def items(self) -> ItemsView[MomentsKey, Instruction]:
        """Return a view of self's (key, instruction)."""
        return self._moments.items()

    def values(self) -> ValuesView[Instruction]:
        """Return a view of self's instructions."""
        self.sort_moments()
        return self._moments.values()

    def get(self, key: MomentsKey, default=None) -> Instruction:
        """
        Get the instruction in self by key.

        Args:
            key (MomentsKey): Key of the instruction to fetch.
            default (Any, optional): Value to return if `key` is not in `moments`. Default = `None`.

        Returns:
            Instruction: `moments[key]` if `key` in `moments`, else `default` is returned.
        """
        return self._moments.get(key, default)

    def __getitem__(self, key):
        return self._moments.__getitem__(key)

    def __iter__(self):
        return self._moments.__iter__()

    def __len__(self):
        return self._moments.__len__()

    def __contains__(self, item):
        return self._moments.__contains__(item)

    def __eq__(self, other):
        if isinstance(other, Moments):
            return self._moments == other._moments
        return NotImplemented

    def __ne__(self, other):
        result = self.__eq__(other)
        if result is not NotImplemented:
            return not result
        return NotImplemented

    def __repr__(self):
        return self._moments.__repr__()

    def __str__(self):
        return self._moments.__str__()
Beispiel #27
0
 def qubits(self) -> QubitSet:
     """QubitSet: Get a copy of the qubits for this circuit."""
     return QubitSet(self._moments.qubits.union(self._qubit_observable_set))
 def target(self, target: QubitSetInput) -> None:
     """Sets the target.
     Args:
         target (QubitSetInput): The new target.
     """
     self._target = QubitSet(target)
Beispiel #29
0
 def target(self, target: QubitSetInput) -> None:
     self._target = QubitSet(target)
class Instruction:
    """
    An instruction is a quantum directive that describes the task to perform on a quantum device.
    """

    def __init__(self, operator: InstructionOperator, target: QubitSetInput):
        """
        InstructionOperator includes objects of type `Gate` only.

        Args:
            operator (InstructionOperator): Operator for the instruction.
            target (int, Qubit, or iterable of int / Qubit): Target qubits that the operator is
                applied to.

        Raises:
            ValueError: If `operator` is empty or any integer in `target` does not meet the `Qubit`
                or `QubitSet` class requirements.
            TypeError: If a `Qubit` class can't be constructed from `target` due to an incorrect
                `typing`.

        Examples:
            >>> Instruction(Gate.CNot(), [0, 1])
            Instruction('operator': CNOT, 'target': QubitSet(Qubit(0), Qubit(1)))
            >>> instr = Instruction(Gate.CNot()), QubitSet([0, 1])])
            Instruction('operator': CNOT, 'target': QubitSet(Qubit(0), Qubit(1)))
            >>> instr = Instruction(Gate.H(), 0)
            Instruction('operator': H, 'target': QubitSet(Qubit(0),))
            >>> instr = Instruction(Gate.Rx(0.12), 0)
            Instruction('operator': Rx, 'target': QubitSet(Qubit(0),))
        """
        if not operator:
            raise ValueError("Operator cannot be empty")
        self._operator = operator
        self._target = QubitSet(target)

    @property
    def operator(self) -> InstructionOperator:
        """Operator: The operator for the instruction, for example, `Gate`."""
        return self._operator

    @property
    def target(self) -> QubitSet:
        """
        QubitSet: Target qubits that the operator is applied to.

        Note:
            Don't mutate this property, any mutations can have unexpected consequences.
        """
        return self._target

    def to_ir(self):
        """
        Converts the operator into the canonical intermediate representation.
        If the operator is passed in a request, this method is called before it is passed.
        """
        return self._operator.to_ir([int(qubit) for qubit in self._target])

    def copy(
        self, target_mapping: Dict[QubitInput, QubitInput] = {}, target: QubitSetInput = None
    ) -> Instruction:
        """
        Return a shallow copy of the instruction.

        Note:
            If `target_mapping` is specified, then `self.target` is mapped to the specified
            qubits. This is useful apply an instruction to a circuit and change the target qubits.

        Args:
            target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of
                qubit mappings to apply to the target. Key is the qubit in this `target` and the
                value is what the key is changed to. Default = `{}`.
            target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the new
                instruction.

        Returns:
            Instruction: A shallow copy of the instruction.

        Raises:
            TypeError: If both `target_mapping` and `target` are supplied.

        Examples:
            >>> instr = Instruction(Gate.H(), 0)
            >>> new_instr = instr.copy()
            >>> new_instr.target
            QubitSet(Qubit(0))
            >>> new_instr = instr.copy(target_mapping={0: 5})
            >>> new_instr.target
            QubitSet(Qubit(5))
            >>> new_instr = instr.copy(target=[5])
            >>> new_instr.target
            QubitSet(Qubit(5))
        """
        if target_mapping and target is not None:
            raise TypeError("Only 'target_mapping' or 'target' can be supplied, but not both.")
        elif target is not None:
            return Instruction(self._operator, target)
        else:
            return Instruction(self._operator, self._target.map(target_mapping))

    def __repr__(self):
        return f"Instruction('operator': {self._operator}, 'target': {self._target})"

    def __eq__(self, other):
        if isinstance(other, Instruction):
            return (self._operator, self._target) == (other._operator, other._target)
        return NotImplemented