# 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 collections.abc import Iterable
import numpy as np
from braket.default_simulator.openqasm.interpreter import VerbatimBoxDelimiter
from braket.default_simulator.openqasm.program_context import AbstractProgramContext
from braket.ir.jaqcd.program_v1 import Results
from sympy import Expr, Number
from braket.circuits import Circuit, Instruction
from braket.circuits.compiler_directives import Barrier
from braket.circuits.gates import Unitary
from braket.circuits.measure import Measure
from braket.circuits.noises import Kraus
from braket.circuits.translations import (
BRAKET_GATES,
COMPILER_DIRECTIVES,
braket_result_to_result_type,
one_prob_noise_map,
)
from braket.parametric import FreeParameterExpression
from braket.registers.qubit_set import QubitSet, QubitSetInput
[docs]
class BraketProgramContext(AbstractProgramContext):
def __init__(self, circuit: Circuit | None = None):
"""Inits a `BraketProgramContext`.
Args:
circuit (Circuit | None): A partially-built circuit to continue building with this
context. Default: None.
"""
super().__init__()
self._circuit = circuit or Circuit()
@property
def circuit(self) -> Circuit:
"""The circuit being built in this context."""
return self._circuit
[docs]
def is_builtin_gate(self, name: str) -> bool:
"""Whether the gate is currently in scope as a built-in Braket gate.
Args:
name (str): name of the built-in Braket gate
Returns:
bool: return TRUE if it is a built-in gate else FALSE.
"""
user_defined_gate = self.is_user_defined_gate(name)
return name in BRAKET_GATES and not user_defined_gate
[docs]
def add_phase_instruction(self, target: tuple[int], phase_value: float) -> None:
"""Add a global phase to the circuit.
Args:
target (tuple[int]): Unused
phase_value (float): The phase value to be applied
"""
self._circuit.gphase(phase_value)
[docs]
def add_gate_instruction(
self, gate_name: str, target: tuple[int], *params, ctrl_modifiers: list[int], power: float
) -> None:
"""Add Braket gate to the circuit.
Args:
gate_name (str): name of the built-in Braket gate.
target (tuple[int]): control_qubits + target_qubits.
ctrl_modifiers (list[int]): Quantum state on which to control the
operation. Must be a binary sequence of same length as number of qubits in
`control-qubits` in target. 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.
power(float): Integer or fractional power to raise the gate to.
"""
target_qubits = target[len(ctrl_modifiers) :]
control_qubits = target[: len(ctrl_modifiers)]
instruction = Instruction(
BRAKET_GATES[gate_name](*params[0]),
target=target_qubits,
control=control_qubits,
control_state=ctrl_modifiers,
power=power,
)
self._circuit.add_instruction(instruction)
[docs]
def add_custom_unitary(
self,
unitary: np.ndarray,
target: tuple[int],
) -> None:
"""Add a custom Unitary instruction to the circuit
Args:
unitary (np.ndarray): unitary matrix
target (tuple[int]): control_qubits + target_qubits
"""
instruction = Instruction(Unitary(unitary), target)
self._circuit.add_instruction(instruction)
[docs]
def add_noise_instruction(
self, noise_instruction: str, target: list[int], probabilities: list[float]
) -> None:
"""Method to add a noise instruction to the circuit
Args:
noise_instruction (str): The name of the noise operation
target (list[int]): The target qubit or qubits to which the noise operation is applied.
probabilities (list[float]): The probabilities associated with each possible outcome
of the noise operation.
"""
instruction = Instruction(
one_prob_noise_map[noise_instruction](*probabilities), target=target
)
self._circuit.add_instruction(instruction)
[docs]
def add_kraus_instruction(self, matrices: list[np.ndarray], target: list[int]) -> None:
"""Method to add a Kraus instruction to the circuit
Args:
matrices (list[ndarray]): The matrices defining the Kraus operation
target (list[int]): The target qubit or qubits to which the Kraus operation is applied.
"""
instruction = Instruction(Kraus(matrices), target)
self._circuit.add_instruction(instruction)
[docs]
def add_result(self, result: Results) -> None:
"""Abstract method to add result type to the circuit
Args:
result (Results): The result object representing the measurement results
"""
self._circuit.add_result_type(braket_result_to_result_type(result))
[docs]
def handle_parameter_value(self, value: float | Expr) -> float | FreeParameterExpression:
"""Convert parameter value to required format.
Args:
value (float | Expr): Value of the parameter
Returns:
float | FreeParameterExpression]: Returns the value directly if numeric,
otherwise wraps the symbolic expression as a `FreeParameterExpression`.
"""
if isinstance(value, Expr):
evaluated_value = value.evalf()
if isinstance(evaluated_value, Number):
return evaluated_value
return FreeParameterExpression(evaluated_value)
return value
[docs]
def add_measure(
self, target: tuple[int], classical_targets: Iterable[int] | None = None
) -> None:
"""Add a measure instruction to the circuit
Args:
target (tuple[int]): the target qubits to be measured.
classical_targets (Iterable[int] | None): the classical registers
to use in the qubit measurement.
"""
for iter, qubit in enumerate(target):
index = classical_targets[iter] if classical_targets else iter
instruction = Instruction(Measure(index=index), qubit)
self._circuit.add_instruction(instruction)
[docs]
def add_verbatim_marker(self, marker: VerbatimBoxDelimiter) -> None:
self._circuit.add_instruction(Instruction(COMPILER_DIRECTIVES[marker](), target=[]))
[docs]
def add_barrier(self, target: QubitSetInput | None = None) -> None:
"""Add a barrier instruction to the circuit.
Args:
target (QubitSetInput | None): Target qubits for the barrier. If None or empty,
applies barrier to all qubits in the circuit.
"""
target_qubits = QubitSet() if not target else QubitSet(target)
self._circuit.add_instruction(Instruction(Barrier(list(target_qubits)), target_qubits))