예제 #1
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__()
예제 #2
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__()