Source code for braket.circuits.instruction

# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

from __future__ import annotations

from typing import Any, Optional

from braket.circuits.basis_state import BasisState, BasisStateInput
from braket.circuits.compiler_directive import CompilerDirective
from braket.circuits.gate import Gate
from braket.circuits.operator import Operator
from braket.circuits.quantum_operator import QuantumOperator
from braket.circuits.serialization import IRType, SerializationProperties
from braket.registers.qubit import QubitInput
from braket.registers.qubit_set import QubitSet, QubitSetInput

# InstructionOperator is a type alias, and it can be expanded to include other operators
InstructionOperator = Operator


[docs] class Instruction: """An instruction is a quantum directive that describes the quantum task to perform on a quantum device. """ def __init__( self, operator: InstructionOperator, target: Optional[QubitSetInput] = None, *, control: Optional[QubitSetInput] = None, control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: """InstructionOperator includes objects of type `Gate` and `Noise` only. Args: operator (InstructionOperator): Operator for the instruction. target (Optional[QubitSetInput]): Target qubits that the operator is applied to. Default is None. control (Optional[QubitSetInput]): Target qubits that the operator is controlled on. Default is None. control_state (Optional[BasisStateInput]): Quantum state on which to control the operation. Must be a binary sequence of same length as number of qubits in `control`. Will be ignored if `control` is not present. May be represented as a string, list, or int. For example "0101", [0, 1, 0, 1], 5 all represent controlling on qubits 0 and 2 being in the \\|0⟩ state and qubits 1 and 3 being in the \\|1⟩ state. Default "1" * len(control). power (float): Integer or fractional power to raise the gate to. Negative powers will be split into an inverse, accompanied by the positive power. Default 1. 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),)) >>> instr = Instruction(Gate.Rx(0.12, control=1), 0) Instruction( 'operator': Rx, 'target': QubitSet(Qubit(0),), 'control': QubitSet(Qubit(1),), ) """ if not operator: raise ValueError("Operator cannot be empty") target_set = QubitSet(target) control_set = QubitSet(control) 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 self._control = control_set self._control_state = BasisState( (1,) * len(control_set) if control_state is None else control_state, len(control_set), ) self._power = power @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.""" return self._target @property def control(self) -> QubitSet: """QubitSet: Target qubits that the operator is controlled on.""" return self._control @property def control_state(self) -> BasisState: """BasisState: Quantum state that the operator is controlled to.""" return self._control_state @property def power(self) -> float: """float: Power that the operator is raised to.""" return self._power
[docs] def adjoint(self) -> list[Instruction]: """Returns a list of Instructions implementing adjoint of this instruction's own operator This operation only works on Gate operators and compiler directives. Returns: list[Instruction]: A list of new instructions that comprise the adjoint of this operator Raises: NotImplementedError: If `operator` is not of type `Gate` or `CompilerDirective` """ operator = self._operator if isinstance(operator, Gate): return [ Instruction( gate, self._target, control=self._control, control_state=self._control_state, power=self._power, ) for gate in operator.adjoint() ] elif isinstance(operator, CompilerDirective): return [Instruction(operator.counterpart(), self._target)] raise NotImplementedError(f"Adjoint not supported for {operator}")
[docs] def to_ir( self, ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties | None = None, ) -> Any: """Converts the operator into the canonical intermediate representation. If the operator is passed in a request, this method is called before it is passed. Args: ir_type(IRType) : The IRType to use for converting the instruction object to its IR representation. serialization_properties (SerializationProperties | None): The serialization properties to use while serializing the object to the IR representation. The serialization properties supplied must correspond to the supplied `ir_type`. Defaults to None. Returns: Any: IR object of the instruction. """ kwargs = {} if self.control: kwargs["control"] = self.control kwargs["control_state"] = self.control_state if self.power != 1: kwargs["power"] = self.power return self._operator.to_ir( [int(qubit) for qubit in self._target], ir_type=ir_type, serialization_properties=serialization_properties, **kwargs, )
@property def ascii_symbols(self) -> tuple[str, ...]: """tuple[str, ...]: Returns the ascii symbols for the instruction's operator.""" return self._operator.ascii_symbols
[docs] def copy( self, target_mapping: Optional[dict[QubitInput, QubitInput]] = None, target: Optional[QubitSetInput] = None, control_mapping: Optional[dict[QubitInput, QubitInput]] = None, control: Optional[QubitSetInput] = None, control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> 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. Same relationship holds for `control_mapping`. Args: target_mapping (Optional[dict[QubitInput, QubitInput]]): 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 = `None`. target (Optional[QubitSetInput]): Target qubits for the new instruction. Default is None. control_mapping (Optional[dict[QubitInput, QubitInput]]): A dictionary of qubit mappings to apply to the control. Key is the qubit in this `control` and the value is what the key is changed to. Default = `None`. control (Optional[QubitSetInput]): Control qubits for the new instruction. Default is None. control_state (Optional[BasisStateInput]): Quantum state on which to control the operation. Must be a binary sequence of same length as number of qubits in `control`. Will be ignored if `control` is not present. May be represented as a string, list, or int. For example "0101", [0, 1, 0, 1], 5 all represent controlling on qubits 0 and 2 being in the \\|0⟩ state and qubits 1 and 3 being in the \\|1⟩ state. Default "1" * len(control). power (float): Integer or fractional power to raise the gate to. Negative powers will be split into an inverse, accompanied by the positive power. Default 1. 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.") if control_mapping and control is not None: raise TypeError("Only 'control_mapping' or 'control' can be supplied, but not both.") new_target = self._target.map(target_mapping or {}) if target is None else target new_control = self._control.map(control_mapping or {}) if control is None else control new_control_state = self._control_state if control_state is None else control_state return Instruction( self._operator, new_target, control=new_control, control_state=new_control_state, power=power, )
def __repr__(self): return ( f"Instruction('operator': {self._operator}, " f"'target': {self._target}, " f"'control': {self._control}, " f"'control_state': {self._control_state.as_tuple}, " f"'power': {self.power})" ) def __eq__(self, other: Instruction): if isinstance(other, Instruction): return ( self._operator, self._target, self._control, self._control_state, self._power, ) == ( other._operator, other._target, other._control, other._control_state, other._power, ) return NotImplemented def __pow__(self, power: float, modulo: float = None): new_power = self.power * power if modulo is not None: new_power %= modulo return self.copy(power=new_power)