From 83fa6e2a3d857e95ec1d0f671714162eaf5239be Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 30 Jul 2024 12:56:48 -0700 Subject: [PATCH 1/4] feat: Add ahs_validator emulation pass and create Emulators for devices that support the AhsProgram DeviceActionType --- add_ahs_old | 1 + setup.py | 3 +- src/braket/aws/aws_device.py | 21 +- src/braket/aws/aws_emulation.py | 91 +++++ .../emulation_passes/ahs_passes/__init__.py | 1 + .../ahs_passes/ahs_validator.py | 30 ++ .../device_capabilities_constants.py | 36 ++ .../ahs_passes/device_ir_validator.py | 68 ++++ .../ahs_passes/device_validators/__init__.py | 12 + .../device_atom_arrangement.py | 159 ++++++++ .../device_validators/device_driving_field.py | 346 ++++++++++++++++ .../device_validators/device_hamiltonian.py | 35 ++ .../device_local_detuning.py | 176 +++++++++ .../braket/aws/test_aws_emulation.py | 370 +++++++++++++++++- .../ahs_passes/device_validators/conftest.py | 114 ++++++ .../test_device_atom_arrangement.py | 172 ++++++++ .../test_device_driving_field.py | 321 +++++++++++++++ .../test_device_hamiltonian.py | 72 ++++ .../test_device_ir_validator.py | 11 + .../test_device_local_detuning.py | 169 ++++++++ tox.ini | 3 +- 21 files changed, 2203 insertions(+), 8 deletions(-) create mode 160000 add_ahs_old create mode 100644 src/braket/emulation/emulation_passes/ahs_passes/__init__.py create mode 100644 src/braket/emulation/emulation_passes/ahs_passes/ahs_validator.py create mode 100644 src/braket/emulation/emulation_passes/ahs_passes/device_capabilities_constants.py create mode 100644 src/braket/emulation/emulation_passes/ahs_passes/device_ir_validator.py create mode 100644 src/braket/emulation/emulation_passes/ahs_passes/device_validators/__init__.py create mode 100644 src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_atom_arrangement.py create mode 100644 src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_driving_field.py create mode 100644 src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_hamiltonian.py create mode 100644 src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_local_detuning.py create mode 100644 test/unit_tests/braket/emulation/ahs_passes/device_validators/conftest.py create mode 100644 test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_atom_arrangement.py create mode 100644 test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_driving_field.py create mode 100644 test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_hamiltonian.py create mode 100644 test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_ir_validator.py create mode 100644 test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_local_detuning.py diff --git a/add_ahs_old b/add_ahs_old new file mode 160000 index 000000000..b269aa22a --- /dev/null +++ b/add_ahs_old @@ -0,0 +1 @@ +Subproject commit b269aa22a2da4c0c1797bf1cd402158a5790f13d diff --git a/setup.py b/setup.py index bf429b266..071d78aef 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,8 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.3", - "amazon-braket-default-simulator>=1.26.0", + # "amazon-braket-default-simulator>=1.26.0", + "amazon-braket-default-simulator@git+https://github.com/Altanali/amazon-braket-default-simulator-python.git@optional_net_detuning", # noqa: E501 "oqpy~=0.3.5", "backoff", "boltons", diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 8603f648d..7abadfaf1 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -28,6 +28,7 @@ from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem from braket.aws.aws_emulation import ( + ahs_criterion, connectivity_validator, gate_connectivity_validator, gate_validator, @@ -41,7 +42,12 @@ from braket.circuits import Circuit, Gate, QubitSet from braket.circuits.gate_calibrations import GateCalibrations from braket.circuits.noise_model import NoiseModel -from braket.device_schema import DeviceCapabilities, ExecutionDay, GateModelQpuParadigmProperties +from braket.device_schema import ( + DeviceActionType, + DeviceCapabilities, + ExecutionDay, + GateModelQpuParadigmProperties, +) from braket.device_schema.dwave import DwaveProviderProperties # TODO: Remove device_action module once this is added to init in the schemas repo @@ -895,6 +901,14 @@ def emulator(self) -> Emulator: return self._emulator def _setup_emulator(self) -> Emulator: + if DeviceActionType.OPENQASM in self.properties.action.keys(): + return self._setup_gate_device_emulator() + elif DeviceActionType.AHS in self.properties.action.keys(): + return self._setup_ahs_device_emulator() + else: + raise ValueError(f"Emulators for device {self._name} are not supported.") + + def _setup_gate_device_emulator(self) -> Emulator: """ Sets up an Emulator object whose properties mimic that of this AwsDevice, if the device is a real QPU (not a simulator). @@ -914,6 +928,11 @@ def _setup_emulator(self) -> Emulator: self._emulator.add_pass(gate_connectivity_validator(self.properties, self.topology_graph)) return self._emulator + def _setup_ahs_device_emulator(self) -> Emulator: + self._emulator = Emulator(backend="braket_ahs", name=self._name) + self._emulator.add_pass(ahs_criterion(self.properties)) + return self._emulator + def validate( self, task_specification: ProgramType, diff --git a/src/braket/aws/aws_emulation.py b/src/braket/aws/aws_emulation.py index 75ff9ad58..4570d7d28 100644 --- a/src/braket/aws/aws_emulation.py +++ b/src/braket/aws/aws_emulation.py @@ -7,7 +7,12 @@ from braket.device_schema import DeviceActionType, DeviceCapabilities from braket.device_schema.ionq import IonqDeviceCapabilities from braket.device_schema.iqm import IqmDeviceCapabilities +from braket.device_schema.quera import QueraDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities +from braket.emulation.emulation_passes.ahs_passes import AhsValidator +from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( + DeviceCapabilitiesConstants, +) from braket.emulation.emulation_passes.gate_device_passes import ( ConnectivityValidator, GateConnectivityValidator, @@ -212,3 +217,89 @@ def _(properties: RigettiDeviceCapabilities, gate_name: str) -> str: def _(properties: IonqDeviceCapabilities, gate_name: str) -> str: translations = {"GPI": "GPi", "GPI2": "GPi2"} return translations.get(gate_name, gate_name) + + +def ahs_criterion(properties: DeviceCapabilities) -> AhsValidator: + """ + Creates an AHS program validation pass using the input QPU device capabilities. + + Args: + properties (DeviceCapabilities): Device capabilities of a QPU device that supports + Analog Hamiltonian Simulation Programs. + + Returns: + AhsValidator: An emulator pass that validates whether or not an AHS program is able to run + on the target device. + """ + device_capabilities_constants = _get_ahs_device_capabilities(properties) + return AhsValidator(device_capabilities_constants) + + +@singledispatch +def _get_ahs_device_capabilities(properties: DeviceCapabilities) -> DeviceCapabilitiesConstants: + raise NotImplementedError( + "AHS Device Capabilities Constants cannot be created" + f"using capabilities type ({type(properties)})" + ) + + +@_get_ahs_device_capabilities.register(QueraDeviceCapabilities) +def _(properties: QueraDeviceCapabilities) -> DeviceCapabilitiesConstants: + properties = properties.dict() + capabilities = dict() + lattice = properties["paradigm"]["lattice"] + capabilities["MAX_SITES"] = lattice["geometry"]["numberSitesMax"] + capabilities["MIN_DISTANCE"] = lattice["geometry"]["spacingRadialMin"] + capabilities["MIN_ROW_DISTANCE"] = lattice["geometry"]["spacingVerticalMin"] + capabilities["SITE_PRECISION"] = lattice["geometry"]["positionResolution"] + capabilities["BOUNDING_BOX_SIZE_X"] = lattice["area"]["width"] + capabilities["BOUNDING_BOX_SIZE_Y"] = lattice["area"]["height"] + capabilities["MAX_FILLED_SITES"] = properties["paradigm"]["qubitCount"] + + rydberg = properties["paradigm"]["rydberg"] + rydberg_global = rydberg["rydbergGlobal"] + capabilities["MIN_TIME"] = rydberg_global["timeMin"] + capabilities["MAX_TIME"] = rydberg_global["timeMax"] + capabilities["GLOBAL_TIME_PRECISION"] = rydberg_global["timeResolution"] + capabilities["GLOBAL_MIN_TIME_SEPARATION"] = rydberg_global["timeDeltaMin"] + + ( + capabilities["GLOBAL_AMPLITUDE_VALUE_MIN"], + capabilities["GLOBAL_AMPLITUDE_VALUE_MAX"], + ) = rydberg_global["rabiFrequencyRange"] + capabilities["GLOBAL_AMPLITUDE_VALUE_PRECISION"] = rydberg_global["rabiFrequencyResolution"] + capabilities["GLOBAL_AMPLITUDE_SLOPE_MAX"] = rydberg_global["rabiFrequencySlewRateMax"] + + ( + capabilities["GLOBAL_PHASE_VALUE_MIN"], + capabilities["GLOBAL_PHASE_VALUE_MAX"], + ) = rydberg_global["phaseRange"] + capabilities["GLOBAL_PHASE_VALUE_PRECISION"] = rydberg_global["phaseResolution"] + + ( + capabilities["GLOBAL_DETUNING_VALUE_MIN"], + capabilities["GLOBAL_DETUNING_VALUE_MAX"], + ) = rydberg_global["detuningRange"] + capabilities["GLOBAL_DETUNING_VALUE_PRECISION"] = rydberg_global["detuningResolution"] + capabilities["GLOBAL_DETUNING_SLOPE_MAX"] = rydberg_global["detuningSlewRateMax"] + + rydberg_local = rydberg.get("rydbergLocal") + if rydberg_local: + capabilities["LOCAL_RYDBERG_CAPABILITIES"] = True + capabilities["LOCAL_MIN_DISTANCE_BETWEEN_SHIFTED_SITES"] = rydberg_local["spacingRadialMin"] + capabilities["LOCAL_TIME_PRECISION"] = rydberg_local["timeResolution"] + capabilities["LOCAL_MIN_TIME_SEPARATION"] = rydberg_local["timeDeltaMin"] + ( + capabilities["LOCAL_MAGNITUDE_SEQUENCE_VALUE_MIN"], + capabilities["LOCAL_MAGNITUDE_SEQUENCE_VALUE_MAX"], + ) = rydberg_local["detuningRange"] + capabilities["LOCAL_MAGNITUDE_SLOPE_MAX"] = rydberg_local["detuningSlewRateMax"] + capabilities["LOCAL_MAX_NONZERO_PATTERN_VALUES"] = rydberg_local[ + "numberLocalDetuningSitesMax" + ] + ( + capabilities["MAGNITUDE_PATTERN_VALUE_MIN"], + capabilities["MAGNITUDE_PATTERN_VALUE_MAX"], + ) = rydberg_local["siteCoefficientRange"] + + return DeviceCapabilitiesConstants.parse_obj(capabilities) diff --git a/src/braket/emulation/emulation_passes/ahs_passes/__init__.py b/src/braket/emulation/emulation_passes/ahs_passes/__init__.py new file mode 100644 index 000000000..5c9c6f86c --- /dev/null +++ b/src/braket/emulation/emulation_passes/ahs_passes/__init__.py @@ -0,0 +1 @@ +from braket.emulation.emulation_passes.ahs_passes.ahs_validator import AhsValidator # noqa: F40 diff --git a/src/braket/emulation/emulation_passes/ahs_passes/ahs_validator.py b/src/braket/emulation/emulation_passes/ahs_passes/ahs_validator.py new file mode 100644 index 000000000..35b43c7f2 --- /dev/null +++ b/src/braket/emulation/emulation_passes/ahs_passes/ahs_validator.py @@ -0,0 +1,30 @@ +from typing import Union + +from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation +from braket.emulation.emulation_passes import ValidationPass +from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( + DeviceCapabilitiesConstants, +) +from braket.emulation.emulation_passes.ahs_passes.device_ir_validator import validate_program +from braket.ir.ahs import Program as AHSProgram + + +class AhsValidator(ValidationPass): + def __init__(self, device_capabilities_constants: DeviceCapabilitiesConstants): + self._capabilities = device_capabilities_constants + + def validate(self, program: Union[AnalogHamiltonianSimulation, AHSProgram]) -> None: + """ + Validates whether or not a AHS program is runnable on the AHS device properties defined + in device_capabilities_constants. + + Args: + program (Union[AnalogHamiltonianSimulation, AHSProgram]): The input AHS program + to validate. + """ + if isinstance(program, AnalogHamiltonianSimulation): + program = program.to_ir() + validate_program(program, self._capabilities) + + def __eq__(self, other: ValidationPass) -> bool: + return isinstance(other, AhsValidator) and other._capabilities == self._capabilities diff --git a/src/braket/emulation/emulation_passes/ahs_passes/device_capabilities_constants.py b/src/braket/emulation/emulation_passes/ahs_passes/device_capabilities_constants.py new file mode 100644 index 000000000..eb342b2b9 --- /dev/null +++ b/src/braket/emulation/emulation_passes/ahs_passes/device_capabilities_constants.py @@ -0,0 +1,36 @@ +from decimal import Decimal +from typing import Optional + +from braket.analog_hamiltonian_simulator.rydberg.validators.capabilities_constants import ( + CapabilitiesConstants, +) + + +class DeviceCapabilitiesConstants(CapabilitiesConstants): + MAX_SITES: int + SITE_PRECISION: Decimal + MAX_FILLED_SITES: int + MIN_ROW_DISTANCE: Decimal + + GLOBAL_TIME_PRECISION: Decimal + GLOBAL_AMPLITUDE_VALUE_PRECISION: Decimal + GLOBAL_AMPLITUDE_SLOPE_MAX: Decimal + GLOBAL_MIN_TIME_SEPARATION: Decimal + GLOBAL_DETUNING_VALUE_PRECISION: Decimal + GLOBAL_DETUNING_SLOPE_MAX: Decimal + GLOBAL_PHASE_VALUE_MIN: Decimal + GLOBAL_PHASE_VALUE_MAX: Decimal + GLOBAL_PHASE_VALUE_PRECISION: Decimal + + LOCAL_RYDBERG_CAPABILITIES: bool = False + LOCAL_MAGNITUDE_SLOPE_MAX: Optional[Decimal] + LOCAL_MIN_DISTANCE_BETWEEN_SHIFTED_SITES: Optional[Decimal] + LOCAL_TIME_PRECISION: Optional[Decimal] + LOCAL_MIN_TIME_SEPARATION: Optional[Decimal] + LOCAL_MAGNITUDE_SEQUENCE_VALUE_MIN: Optional[Decimal] + LOCAL_MAGNITUDE_SEQUENCE_VALUE_MAX: Optional[Decimal] + LOCAL_MAX_NONZERO_PATTERN_VALUES: Optional[Decimal] + + MAGNITUDE_PATTERN_VALUE_MIN: Optional[Decimal] + MAGNITUDE_PATTERN_VALUE_MAX: Optional[Decimal] + MAX_NET_DETUNING: Optional[Decimal] diff --git a/src/braket/emulation/emulation_passes/ahs_passes/device_ir_validator.py b/src/braket/emulation/emulation_passes/ahs_passes/device_ir_validator.py new file mode 100644 index 000000000..2a93905eb --- /dev/null +++ b/src/braket/emulation/emulation_passes/ahs_passes/device_ir_validator.py @@ -0,0 +1,68 @@ +# 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 braket.analog_hamiltonian_simulator.rydberg.validators.physical_field import ( + PhysicalFieldValidator, +) +from braket.analog_hamiltonian_simulator.rydberg.validators.program import ProgramValidator +from braket.analog_hamiltonian_simulator.rydberg.validators.times_series import TimeSeriesValidator +from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( + DeviceCapabilitiesConstants, +) +from braket.emulation.emulation_passes.ahs_passes.device_validators import ( + DeviceAtomArrangementValidator, + DeviceDrivingFieldValidator, + DeviceHamiltonianValidator, + DeviceLocalDetuningValidator, +) +from braket.ir.ahs.program_v1 import Program + + +def validate_program(program: Program, device_capabilities: DeviceCapabilitiesConstants) -> None: + """ + Validate the analog Hamiltonian simulation program has only one driving and shifting field, + and all the sequences have the same last time point. + + Args: + program (Program): An analog Hamiltonian simulation program. + device_capabilities (DeviceCapabilitiesConstants): The capability constants for the + simulator. + """ + + ProgramValidator(capabilities=device_capabilities, **program.dict()) + DeviceAtomArrangementValidator( + capabilities=device_capabilities, **program.setup.ahs_register.dict() + ) + DeviceHamiltonianValidator( + LOCAL_RYDBERG_CAPABILITIES=device_capabilities.LOCAL_RYDBERG_CAPABILITIES, + **program.hamiltonian.dict() + ) + for d_fields in program.hamiltonian.drivingFields: + DeviceDrivingFieldValidator(capabilities=device_capabilities, **d_fields.dict()) + amplitude = d_fields.amplitude + phase = d_fields.phase + detuning = d_fields.detuning + + PhysicalFieldValidator(**amplitude.dict()) + TimeSeriesValidator(capabilities=device_capabilities, **amplitude.time_series.dict()) + + PhysicalFieldValidator(**phase.dict()) + TimeSeriesValidator(capabilities=device_capabilities, **phase.time_series.dict()) + + PhysicalFieldValidator(**detuning.dict()) + TimeSeriesValidator(capabilities=device_capabilities, **detuning.time_series.dict()) + for s_fields in program.hamiltonian.localDetuning: + DeviceLocalDetuningValidator(capabilities=device_capabilities, **s_fields.dict()) + magnitude = s_fields.magnitude + PhysicalFieldValidator(**magnitude.dict()) + TimeSeriesValidator(capabilities=device_capabilities, **magnitude.time_series.dict()) diff --git a/src/braket/emulation/emulation_passes/ahs_passes/device_validators/__init__.py b/src/braket/emulation/emulation_passes/ahs_passes/device_validators/__init__.py new file mode 100644 index 000000000..acbb4ac57 --- /dev/null +++ b/src/braket/emulation/emulation_passes/ahs_passes/device_validators/__init__.py @@ -0,0 +1,12 @@ +from braket.emulation.emulation_passes.ahs_passes.device_validators.device_atom_arrangement import ( # noqa: E501 F401 + DeviceAtomArrangementValidator, +) +from braket.emulation.emulation_passes.ahs_passes.device_validators.device_driving_field import ( # noqa: E501 F401 + DeviceDrivingFieldValidator, +) +from braket.emulation.emulation_passes.ahs_passes.device_validators.device_hamiltonian import ( # noqa: E501 F401 + DeviceHamiltonianValidator, +) +from braket.emulation.emulation_passes.ahs_passes.device_validators.device_local_detuning import ( # noqa: E501 F401 + DeviceLocalDetuningValidator, +) diff --git a/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_atom_arrangement.py b/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_atom_arrangement.py new file mode 100644 index 000000000..89dd3b78f --- /dev/null +++ b/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_atom_arrangement.py @@ -0,0 +1,159 @@ +from decimal import Decimal +from typing import Dict, Tuple + +from pydantic.v1.class_validators import root_validator + +from braket.analog_hamiltonian_simulator.rydberg.validators.atom_arrangement import ( + AtomArrangementValidator, +) +from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( + DeviceCapabilitiesConstants, +) + + +def _y_distance(site_1: Tuple[Decimal, Decimal], site_2: Tuple[Decimal, Decimal]) -> Decimal: + # Compute the y-separation between two sets of 2-D points, (x1, y1) and (x2, y2) + + return Decimal(abs(site_1[1] - site_2[1])) + + +class DeviceAtomArrangementValidator(AtomArrangementValidator): + capabilities: DeviceCapabilitiesConstants + + @root_validator(pre=True, skip_on_failure=True) + def sites_not_empty(cls, values: Dict) -> Dict: + """ + Checks that the program atom arrangement uses at least one site. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the atom arrangement data to + validate. + + Returns: + Dict: Unmodified atom arrangement data and DeviceCapabilitiesConstants + + Raises: + ValueError: If the AtomArrangement sites array do not contain any points. + """ + sites = values["sites"] + if not sites: + raise ValueError("Sites can not be empty.") + return values + + # The maximum allowable precision in the coordinates is SITE_PRECISION + @root_validator(pre=True, skip_on_failure=True) + def sites_defined_with_right_precision(cls, values: Dict) -> Dict: + """ + Checks that the precision of the site coordinates are within the SITE_PRECISION of the + device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the atom arrangement data + to validate. + + Returns: + Dict: Unmodified AtomArrangment data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any site coordinate's precision exceeds that supported by the device + capabilities. + """ + sites = values["sites"] + capabilities = values["capabilities"] + for idx, s in enumerate(sites): + if not all( + [Decimal(str(coordinate)) % capabilities.SITE_PRECISION == 0 for coordinate in s] + ): + raise ValueError( + f"Coordinates {idx}({s}) is defined with too high precision;" + f"they must be multiples of {capabilities.SITE_PRECISION} meters" + ) + return values + + # Number of sites must not exceeds MAX_SITES + @root_validator(pre=True, skip_on_failure=True) + def sites_not_too_many(cls, values: Dict) -> Dict: + """ + Checks that the number of sites in the atom arrangement do not exceed the limit MAX_SITES in + the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the atom arrangement data + to validate. + + Returns: + Dict: Unmodified atom arrangement data and DeviceCapabilitiesConstants + + Raises: + ValueError: If the number of sites in the atom arrangement exceeds the amount allowed in + the device capabilities. + """ + sites = values["sites"] + capabilities = values["capabilities"] + num_sites = len(sites) + if num_sites > capabilities.MAX_SITES: + raise ValueError( + f"There are too many sites ({num_sites}); there must be at most " + f"{capabilities.MAX_SITES} sites" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def sites_in_rows(cls, values: Dict) -> Dict: + """ + Checks that the y-distance between sites in the atom arrangment are either identical + or differ by at least the MIN_ROW_DISTANCE in the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the atom arrangement data + to validate. + + Returns: + Dict: Unmodified atom arrangement data and DeviceCapabilitiesConstants + + Raises: + ValueError: If there are sites in the atom arrangement with differing y-positions that + do not differ by more than the MIN_ROW_DISTANCE in the device capabilities. + """ + sites = values["sites"] + capabilities = values["capabilities"] + sorted_sites = sorted(sites, key=lambda xy: xy[1]) + min_allowed_distance = capabilities.MIN_ROW_DISTANCE + if capabilities.LOCAL_RYDBERG_CAPABILITIES: + min_allowed_distance = Decimal("0.000002") + for s1, s2 in zip(sorted_sites[:-1], sorted_sites[1:]): + row_distance = _y_distance(s1, s2) + if row_distance == 0: + continue + if row_distance < min_allowed_distance: + raise ValueError( + f"Sites {s1} and site {s2} have y-separation ({row_distance}). It must " + f"either be exactly zero or not smaller than {min_allowed_distance} meters" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def atom_number_limit(cls, values: Dict) -> Dict: + """ + Checks that the number of filled sites in the atom arrangement does not exceed the + MAX_FILLED_SITES limit in the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the atom arrangement data + to validate. + Returns: + Dict: Unmodified atom arrangement data and DeviceCapabilitiesConstants + + Raises: + ValueError: If the number of filled sites in the atom arrangement exceeds the + MAX_FILLED_SITES limit in the device capabilities. + """ + filling = values["filling"] + capabilities = values["capabilities"] + qubits = sum(filling) + if qubits > capabilities.MAX_FILLED_SITES: + raise ValueError( + f"Filling has {qubits} '1' entries; it must have not " + f"more than {capabilities.MAX_FILLED_SITES}" + ) + return values diff --git a/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_driving_field.py b/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_driving_field.py new file mode 100644 index 000000000..01b921028 --- /dev/null +++ b/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_driving_field.py @@ -0,0 +1,346 @@ +from typing import Dict + +from pydantic.v1.class_validators import root_validator + +from braket.analog_hamiltonian_simulator.rydberg.validators.driving_field import ( + DrivingFieldValidator, +) +from braket.analog_hamiltonian_simulator.rydberg.validators.field_validator_util import ( + validate_max_absolute_slope, + validate_time_precision, + validate_time_separation, + validate_value_precision, + validate_value_range_with_warning, +) +from braket.analog_hamiltonian_simulator.rydberg.validators.physical_field import PhysicalField +from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( + DeviceCapabilitiesConstants, +) + + +class DeviceDrivingFieldValidator(DrivingFieldValidator): + capabilities: DeviceCapabilitiesConstants + + @root_validator(pre=True, skip_on_failure=True) + def amplitude_start_and_end_values(cls, values: Dict) -> Dict: + """ + Checks that the amplitudes in the driving field time series begin and end at 0.0. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If either the starting or ending amplitude are nonzero. + """ + amplitude = values["amplitude"] + time_series = amplitude["time_series"] + time_series_values = time_series["values"] + if time_series_values: + start_value, end_value = time_series_values[0], time_series_values[-1] + if start_value != 0 or end_value != 0: + raise ValueError( + f"The values of the Rabi frequency at the first and last time points are " + f"{start_value}, {end_value}; they both must be nonzero." + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def amplitude_time_precision_is_correct(cls, values: Dict) -> Dict: + """ + Checks that the precision of the amplitude times points are within the GLOBAL_TIME_PRECISION + of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any of the amplitude times points in the driving field have precision + greater than that supported in the device capabilities. + """ + amplitude = values["amplitude"] + capabilities = values["capabilities"] + amplitude_obj = PhysicalField.parse_obj(amplitude) + validate_time_precision( + amplitude_obj.time_series.times, capabilities.GLOBAL_TIME_PRECISION, "amplitude" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def amplitude_timepoint_not_too_close(cls, values: Dict) -> Dict: + """ + Checks that data points in the time series are separated in time by at least the + GLOBAL_MIN_TIME_SEPARATION of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any of the data points in the driving field time series do not differ + in time by at least the GLOBAL_MIN_TIME_SEPARATION of the device capabilities. + """ + amplitude = values["amplitude"] + capabilities = values["capabilities"] + validate_time_separation( + amplitude["time_series"]["times"], capabilities.GLOBAL_MIN_TIME_SEPARATION, "amplitude" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def amplitude_value_precision_is_correct(cls, values: Dict) -> Dict: + """ + Checks that the precision of the amplitude values are within the GLOBAL_VALUE_PRECISION + of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any of the amplitude values in the driving field have precision + greater than that supported in the device capabilities. + """ + amplitude = values["amplitude"] + capabilities = values["capabilities"] + amplitude_obj = PhysicalField.parse_obj(amplitude) + validate_value_precision( + amplitude_obj.time_series.values, + capabilities.GLOBAL_AMPLITUDE_VALUE_PRECISION, + "amplitude", + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def amplitude_slopes_not_too_steep(cls, values: Dict) -> Dict: + """ + Checks that the max absolute slope in the amplitude time series does not exceed the + GLOBAL_AMPLITUDE_SLOPE_MAX of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If the slope between any points in the amplitude time series exceeds the + GLOBAL_AMPLITUDE_SLOPE_MAX of the device capabilities. + """ + amplitude = values["amplitude"] + capabilities = values["capabilities"] + amplitude_times = amplitude["time_series"]["times"] + amplitude_values = amplitude["time_series"]["values"] + if amplitude_times and amplitude_values: + validate_max_absolute_slope( + amplitude_times, + amplitude_values, + capabilities.GLOBAL_AMPLITUDE_SLOPE_MAX, + "amplitude", + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def phase_time_precision_is_correct(cls, values: Dict) -> Dict: + """ + Checks that the precision of the phase times points are within the GLOBAL_TIME_PRECISION + of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any of the phase times points in the driving field have precision + greater than that supported in the device capabilities. + """ + phase = values["phase"] + capabilities = values["capabilities"] + phase_obj = PhysicalField.parse_obj(phase) + validate_time_precision( + phase_obj.time_series.times, capabilities.GLOBAL_TIME_PRECISION, "phase" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def phase_timepoint_not_too_close(cls, values: Dict) -> Dict: + """ + Checks that the phase time points of the driving field differ by at least the + GLOBAL_MIN_SEPARATION of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any pair of phase time points do not differ by more than the + GLOBAL_MIN_SEPARATION of the device capabilities. + """ + phase = values["phase"] + capabilities = values["capabilities"] + validate_time_separation( + phase["time_series"]["times"], capabilities.GLOBAL_MIN_TIME_SEPARATION, "phase" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def phase_values_start_with_0(cls, values: Dict) -> Dict: + """ + Checks that the phase values of the driving field start with 0. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If the phase values of the driving field do not start with 0. + """ + phase = values["phase"] + phase_values = phase["time_series"]["values"] + if phase_values: + if phase_values[0] != 0: + raise ValueError( + f"The first value of of driving field phase is {phase_values[0]}; it must be 0." + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def phase_values_within_range(cls, values: Dict) -> Dict: + """ + Checks that the phase values of the driving field are in the range + [GLOBAL_PHASE_VALUE_MIN, GLOBAL_PHASE_VALUE_MAX] of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any of the phase values are outside the range + [GLOBAL_PHASE_VALUE_MIN, GLOBAL_PHASE_VALUE_MAX] of the device capabilities. + """ + phase = values["phase"] + capabilities = values["capabilities"] + validate_value_range_with_warning( + phase["time_series"]["values"], + capabilities.GLOBAL_PHASE_VALUE_MIN, + capabilities.GLOBAL_PHASE_VALUE_MAX, + "phase", + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def phase_value_precision_is_correct(cls, values: Dict) -> Dict: + """ + Checks that the precision of the phase values are within the GLOBAL_VALUE_PRECISION + of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any of the phase values in the driving field have precision + greater than that supported in the device capabilities. + """ + phase = values["phase"] + capabilities = values["capabilities"] + phase_obj = PhysicalField.parse_obj(phase) + validate_value_precision( + phase_obj.time_series.values, capabilities.GLOBAL_PHASE_VALUE_PRECISION, "phase" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def detuning_time_precision_is_correct(cls, values: Dict) -> Dict: + """ + Checks that the precision of the detuning times points are within the GLOBAL_TIME_PRECISION + of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any of the detuning times points in the driving field have precision + greater than that supported in the device capabilities. + """ + detuning = values["detuning"] + capabilities = values["capabilities"] + detuning_obj = PhysicalField.parse_obj(detuning) + validate_time_precision( + detuning_obj.time_series.times, capabilities.GLOBAL_TIME_PRECISION, "detuning" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def detuning_timepoint_not_too_close(cls, values: Dict) -> Dict: + """ + Checks that the detuning time points of the driving field differ by at least the + GLOBAL_MIN_SEPARATION of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any pair of detuning time points do not differ by more than the + GLOBAL_MIN_SEPARATION of the device capabilities. + """ + detuning = values["detuning"] + capabilities = values["capabilities"] + validate_time_separation( + detuning["time_series"]["times"], capabilities.GLOBAL_MIN_TIME_SEPARATION, "detuning" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def detuning_slopes_not_too_steep(cls, values: Dict) -> Dict: + """ + Checks that the max absolute slope in the detuning time series does not exceed the + GLOBAL_AMPLITUDE_SLOPE_MAX of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the driving field data + to validate. + Returns: + Dict: Unmodified driving field data and DeviceCapabilitiesConstants + + Raises: + ValueError: If the slope between any points in the detuning time series exceeds the + GLOBAL_AMPLITUDE_SLOPE_MAX of the device capabilities. + """ + detuning = values["detuning"] + capabilities = values["capabilities"] + detuning_times = detuning["time_series"]["times"] + detuning_values = detuning["time_series"]["values"] + if detuning_times and detuning_values: + validate_max_absolute_slope( + detuning_times, + detuning_values, + capabilities.GLOBAL_DETUNING_SLOPE_MAX, + "detuning", + ) + return values diff --git a/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_hamiltonian.py b/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_hamiltonian.py new file mode 100644 index 000000000..c89154785 --- /dev/null +++ b/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_hamiltonian.py @@ -0,0 +1,35 @@ +from typing import Dict + +from pydantic.v1.class_validators import root_validator + +from braket.analog_hamiltonian_simulator.rydberg.validators.hamiltonian import HamiltonianValidator + + +class DeviceHamiltonianValidator(HamiltonianValidator): + LOCAL_RYDBERG_CAPABILITIES: bool = False + + @root_validator(pre=True, skip_on_failure=True) + def max_zero_local_detuning(cls, values: Dict) -> Dict: + """ + Checks if local detuning is supported in this Hamiltonian definition based on the device + capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the Hamiltonian data + to validate. + Returns: + Dict: Unmodified Hamiltonian data and DeviceCapabilitiesConstants + + Raises: + ValueError: If local detuning is non empty in the Hamiltonian definition, but local + rydberg capabilities are not provded or supported in the device capabilities. + """ + LOCAL_RYDBERG_CAPABILITIES = values["LOCAL_RYDBERG_CAPABILITIES"] + local_detuning = values.get("localDetuning", []) + if not LOCAL_RYDBERG_CAPABILITIES and len(local_detuning): + raise ValueError( + f"Local detuning cannot be specified; \ +{len(local_detuning)} are given. Specifying local \ +detuning is an experimental capability, use Braket Direct to request access." + ) + return values diff --git a/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_local_detuning.py b/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_local_detuning.py new file mode 100644 index 000000000..56235f63d --- /dev/null +++ b/src/braket/emulation/emulation_passes/ahs_passes/device_validators/device_local_detuning.py @@ -0,0 +1,176 @@ +from typing import Dict + +from pydantic.v1.class_validators import root_validator + +from braket.analog_hamiltonian_simulator.rydberg.validators.field_validator_util import ( + validate_max_absolute_slope, + validate_time_precision, + validate_time_separation, +) +from braket.analog_hamiltonian_simulator.rydberg.validators.local_detuning import ( + LocalDetuningValidator, +) +from braket.analog_hamiltonian_simulator.rydberg.validators.physical_field import PhysicalField +from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( + DeviceCapabilitiesConstants, +) + + +class DeviceLocalDetuningValidator(LocalDetuningValidator): + capabilities: DeviceCapabilitiesConstants + + @root_validator(pre=True, skip_on_failure=True) + def check_local_rydberg_capabilities(cls, values: Dict) -> Dict: + """ + Checks that this device supports and provides local rydberg capabilities data for local + detuning. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the local detuning data + to validate. + Returns: + Dict: Unmodified local detuning data and DeviceCapabilitiesConstants + + Raises: + ValueError: If the device capabilities LOCAL_RYDBERG_CAPABILITIES is marked as false, + i.e. local rydberg capabilities data for local detuning has not been provided. + """ + capabilities = values["capabilities"] + if not capabilities.LOCAL_RYDBERG_CAPABILITIES: + raise ValueError( + "Local Rydberg capabilities information has not been " + "provided for local detuning." + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def magnitude_pattern_have_not_too_many_nonzeros(cls, values: Dict) -> Dict: + """ + Checks that the number of nonzero magnitude pattern values does not exceed the + LOCAL_MAX_NONZERO_PATTERN_VALUES of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the local detuning data + to validate. + Returns: + Dict: Unmodified local detuning data and DeviceCapabilitiesConstants + + Raises: + ValueError: If the number of nonzero magnitude pattern values exceeds the + LOCAL_MAX_NONZERO_PATTERN_VALUES of the device capabilities. + """ + magnitude = values["magnitude"] + capabilities = values["capabilities"] + pattern = magnitude["pattern"] + num_nonzeros = sum([p != 0.0 for p in pattern]) + if num_nonzeros > capabilities.LOCAL_MAX_NONZERO_PATTERN_VALUES: + raise ValueError( + f"Number of nonzero magnitude pattern values is {num_nonzeros}; " + f"it must not be more than {capabilities.LOCAL_MAX_NONZERO_PATTERN_VALUES}" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def magnitude_time_precision_is_correct(cls, values: Dict) -> Dict: + """ + Checks that the precision of the magnitude times series times are within the + LOCAL_TIME_PRECISION of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the local detuning data + to validate. + Returns: + Dict: Unmodified local detuning data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any of the magnitude time points have precision greater than that + supported in the device capabilities. + """ + magnitude = values["magnitude"] + capabilities = values["capabilities"] + magnitude_obj = PhysicalField.parse_obj(magnitude) + validate_time_precision( + magnitude_obj.time_series.times, capabilities.LOCAL_TIME_PRECISION, "magnitude" + ) + return values + + @root_validator(pre=True, skip_on_failure=True) + def magnitude_timepoint_not_too_close(cls, values: Dict) -> Dict: + """ + Checks that the precision of the magnitude times series times are within the + LOCAL_TIME_PRECISION of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the local detuning data + to validate. + Returns: + Dict: Unmodified local detuning data and DeviceCapabilitiesConstants + + Raises: + ValueError: If any of the magnitude time points have precision greater than that + supported in the device capabilities. + """ + magnitude = values["magnitude"] + capabilities = values["capabilities"] + times = magnitude["time_series"]["times"] + validate_time_separation(times, capabilities.LOCAL_MIN_TIME_SEPARATION, "magnitude") + return values + + @root_validator(pre=True, skip_on_failure=True) + def magnitude_slopes_not_too_steep(cls, values: Dict) -> Dict: + """ + Checks that the max absolute slope in the magnitude time series does not exceed the + LOCAL_MAGNITUDE_SLOPE_MAX of the device capabilities. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the local detuning data + to validate. + Returns: + Dict: Unmodified local detuning data and DeviceCapabilitiesConstants + + Raises: + ValueError: If the slope between any points in the local detuning magnitude + time series exceeds the LOCAL_MAGNITUDE_SLOPE_MAX of the device capabilities. + """ + magnitude = values["magnitude"] + capabilities = values["capabilities"] + magnitude_times = magnitude["time_series"]["times"] + magnitude_values = magnitude["time_series"]["values"] + if magnitude_times and magnitude_values: + validate_max_absolute_slope( + magnitude_times, + magnitude_values, + capabilities.LOCAL_MAGNITUDE_SLOPE_MAX, + "magnitude", + ) + return values + + # Rule: The Rydberg local detuning must start and end at 0. + @root_validator(pre=True, skip_on_failure=True) + def local_detuning_start_and_end_values(cls, values: Dict) -> Dict: + """ + Checks that the values in the magnitude time series of the local detuning data start and + end at 0.0. + + Args: + values (Dict): Contains DeviceCapabilitiesConstants and the local detuning data + to validate. + Returns: + Dict: Unmodified local detuning data and DeviceCapabilitiesConstants + + Raises: + ValueError: If either the start or the end values of the magnitude time series of the + local detuning data are nonzero. + """ + magnitude = values["magnitude"] + time_series = magnitude["time_series"] + time_series_values = time_series["values"] + if time_series_values: + start_value, end_value = time_series_values[0], time_series_values[-1] + if start_value != 0 or end_value != 0: + raise ValueError( + f"The values of the shifting field magnitude time series at the first " + f"and last time points are {start_value}, {end_value}; " + f"they both must be nonzero." + ) + return values diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py index 6d0728326..d7a8b903f 100644 --- a/test/unit_tests/braket/aws/test_aws_emulation.py +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -4,16 +4,18 @@ import networkx as nx import numpy as np import pytest +from decimal import Decimal from common_test_utils import RIGETTI_ARN, RIGETTI_REGION from braket.aws import AwsDevice -from braket.aws.aws_emulation import _get_qpu_gate_translations +from braket.aws.aws_emulation import _get_qpu_gate_translations, ahs_criterion from braket.aws.aws_noise_models import ( GateDeviceCalibrationData, GateFidelity, _setup_calibration_specs, device_noise_model, ) +from braket.ahs import AnalogHamiltonianSimulation from braket.circuits import Circuit, Gate from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria from braket.circuits.noises import ( @@ -28,6 +30,7 @@ from braket.device_schema.ionq import IonqDeviceCapabilities, IonqDeviceParameters from braket.device_schema.iqm import IqmDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities +from braket.device_schema.quera import QueraDeviceCapabilities from braket.devices import Devices from braket.devices.local_simulator import LocalSimulator from braket.emulation import Emulator @@ -37,11 +40,18 @@ GateValidator, QubitCountValidator, ) +from braket.emulation.emulation_passes.ahs_passes import AhsValidator +from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( + DeviceCapabilitiesConstants +) +from braket.ir.ahs.program_v1 import Program as AhsProgram + REGION = "us-west-1" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/Forte1" IQM_ARN = "arn:aws:braket:::device/qpu/iqm/Garent" +QUERA_ARN = "arn:aws:braket:::device/qpu/quera/Aquila" MOCK_QPU_GATE_DURATIONS = { @@ -252,13 +262,192 @@ def basic_device_capabilities(): @pytest.fixture -def rigetti_device_capabilities(): - return RigettiDeviceCapabilities.parse_obj(MOCK_RIGETTI_QPU_CAPABILITIES_1) +def rigetti_capabilities_unsupported_actions(rigetti_device_capabilities): + rigetti_capabilities_obj = rigetti_device_capabilities.dict() + rigetti_capabilities_obj["action"] = { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["x", "y"], + "supportedResultTypes": [ + { + "name": "resultType1", + "observables": ["observable1"], + "minShots": 0, + "maxShots": 4, + } + ], + } + } + return RigettiDeviceCapabilities(**rigetti_capabilities_obj) @pytest.fixture -def iqm_device_capabilities(): - return IqmDeviceCapabilities.parse_obj(MOCK_IQM_QPU_CAPABILITIES_1) +def quera_device_capabilities(): + device_properties = { + "service": { + "braketSchemaHeader": { + "name": "braket.device_schema.device_service_properties", + "version": "1", + }, + "executionWindows": [ + { + "executionDay": "Weekdays", + "windowStartHour": "09:00:00", + "windowEndHour": "10:00:00", + } + ], + "shotsRange": [1, 10000], + "deviceCost": {"price": 0.25, "unit": "minute"}, + "deviceDocumentation": { + "imageUrl": "", + "summary": "", + "externalDocumentationUrl": "", + }, + "deviceLocation": "Boston, USA", + "updatedAt": "2022-05-15T19:28:02.869136", + }, + "action": { + "braket.ir.ahs.program": {"version": ["1"], "actionType": "braket.ir.ahs.program"} + }, + "deviceParameters": {}, + "braketSchemaHeader": { + "name": "braket.device_schema.quera.quera_device_capabilities", + "version": "1", + }, + "paradigm": { + "braketSchemaHeader": { + "name": "braket.device_schema.quera.quera_ahs_paradigm_properties", + "version": "1", + }, + "qubitCount": 256, + "lattice": { + "area": {"width": 0.0001, "height": 0.0001}, + "geometry": { + "spacingRadialMin": 4e-06, + "spacingVerticalMin": 2.5e-06, + "positionResolution": 1e-07, + "numberSitesMax": 256, + }, + }, + "rydberg": { + "c6Coefficient": 5.42e-24, + "rydbergGlobal": { + "rabiFrequencyRange": [0, 25000000], + "rabiFrequencyResolution": 400, + "rabiFrequencySlewRateMax": 250000000000000, + "detuningRange": [-125000000, 125000000], + "detuningResolution": 0.2, + "detuningSlewRateMax": 2500000000000000, + "phaseRange": [-99, 99], + "phaseResolution": 5e-07, + "timeResolution": 1e-09, + "timeDeltaMin": 1e-08, + "timeMin": 0, + "timeMax": 4e-06, + }, + "rydbergLocal": { + "detuningRange": [0, 125000000.0], + "detuningSlewRateMax": 1256600000000000.0, + "siteCoefficientRange": [0.0, 1.0], + "numberLocalDetuningSitesMax": 200, + "spacingRadialMin": 5e-06, + "timeResolution": 1e-9, + "timeDeltaMin": 1e-8, + }, + }, + "performance": { + "lattice": { + "atomCaptureProbabilityTypical": Decimal("0.001"), + "atomCaptureProbabilityWorst": Decimal("0.002"), + "atomDetectionErrorFalseNegativeTypical": Decimal("0.001"), + "atomDetectionErrorFalseNegativeWorst": Decimal("0.005"), + "atomDetectionErrorFalsePositiveTypical": Decimal("0.001"), + "atomDetectionErrorFalsePositiveWorst": Decimal("0.005"), + "atomLossProbabilityTypical": Decimal("0.005"), + "atomLossProbabilityWorst": Decimal("0.01"), + "atomPositionError": Decimal("2E-7"), + "fillingErrorTypical": Decimal("0.008"), + "fillingErrorWorst": Decimal("0.05"), + "positionErrorAbs": Decimal("2.25E-7"), + "sitePositionError": Decimal("1E-7"), + "vacancyErrorTypical": Decimal("0.001"), + "vacancyErrorWorst": Decimal("0.005"), + }, + "rydberg": { + "rydbergGlobal": { + "T1Ensemble": Decimal("0.000075"), + "T1Single": Decimal("0.000075"), + "T2BlockadedRabiEnsemble": Decimal("0.000007"), + "T2BlockadedRabiSingle": Decimal("0.000008"), + "T2EchoEnsemble": Decimal("0.000007"), + "T2EchoSingle": Decimal("0.000008"), + "T2RabiEnsemble": Decimal("0.000007"), + "T2RabiSingle": Decimal("0.000008"), + "T2StarEnsemble": Decimal("0.00000475"), + "T2StarSingle": Decimal("0.000005"), + "detuningError": Decimal("1000000.0"), + "detuningInhomogeneity": Decimal("1000000.0"), + "groundDetectionError": Decimal("0.05"), + "groundPrepError": Decimal("0.01"), + "rabiAmplitudeRampCorrection": [ + {"rabiCorrection": Decimal("0.92"), "rampTime": Decimal("5E-8")}, + {"rabiCorrection": Decimal("0.97"), "rampTime": Decimal("7.5E-8")}, + {"rabiCorrection": Decimal("1.0"), "rampTime": Decimal("1E-7")}, + ], + "rabiFrequencyErrorRel": Decimal("0.03"), + "rabiFrequencyGlobalErrorRel": Decimal("0.02"), + "rabiFrequencyInhomogeneityRel": Decimal("0.02"), + "rydbergDetectionError": Decimal("0.1"), + "rydbergPrepErrorBest": Decimal("0.05"), + "rydbergPrepErrorWorst": Decimal("0.07"), + }, + "rydbergLocal": None, + }, + }, + }, + } + return QueraDeviceCapabilities(**device_properties) + + +@pytest.fixture +def ahs_device_capabilities_constants(): + capabilities = { + "MAX_SITES": 256, + "MIN_DISTANCE": 4e-06, + "MIN_ROW_DISTANCE": 2.5e-06, + "SITE_PRECISION": 1e-07, + "BOUNDING_BOX_SIZE_X": 0.0001, + "BOUNDING_BOX_SIZE_Y": 0.0001, + "MAX_FILLED_SITES": 256, + "MIN_TIME": 0, + "MAX_TIME": 4e-06, + "GLOBAL_TIME_PRECISION": 1e-09, + "GLOBAL_MIN_TIME_PRECISION": 1e-08, + "GLOBAL_MIN_TIME_SEPARATION": 1e-08, + "GLOBAL_AMPLITUDE_VALUE_MIN": 0, + "GLOBAL_AMPLITUDE_VALUE_MAX": 25000000, + "GLOBAL_AMPLITUDE_VALUE_PRECISION": 400, + "GLOBAL_AMPLITUDE_SLOPE_MAX": 250000000000000, + "GLOBAL_PHASE_VALUE_MIN": -99, + "GLOBAL_PHASE_VALUE_MAX": 99, + "GLOBAL_PHASE_VALUE_PRECISION": 5e-07, + "GLOBAL_DETUNING_VALUE_MIN": -125000000, + "GLOBAL_DETUNING_VALUE_MAX": 125000000, + "GLOBAL_DETUNING_VALUE_PRECISION": 0.2, + "GLOBAL_DETUNING_SLOPE_MAX": 2500000000000000, + "LOCAL_RYDBERG_CAPABILITIES": True, + "LOCAL_MIN_DISTANCE_BETWEEN_SHIFTED_SITES": 5e-06, + "LOCAL_TIME_PRECISION": 1e-09, + "LOCAL_MIN_TIME_SEPARATION": 1e-08, + "LOCAL_MAGNITUDE_SEQUENCE_VALUE_MIN": 0, + "LOCAL_MAGNITUDE_SEQUENCE_VALUE_MAX": 125000000.0, + "LOCAL_MAGNITUDE_SLOPE_MAX": 1256600000000000.0, + "LOCAL_MAX_NONZERO_PATTERN_VALUES": 200, + "MAGNITUDE_PATTERN_VALUE_MIN": 0.0, + "MAGNITUDE_PATTERN_VALUE_MAX": 1.0, + } + return DeviceCapabilitiesConstants(**capabilities) MOCK_IONQ_GATE_MODEL_CAPABILITIES_JSON_1 = { @@ -308,6 +497,18 @@ def iqm_device_capabilities(): } + +@pytest.fixture +def rigetti_device_capabilities(): + return RigettiDeviceCapabilities.parse_obj(MOCK_RIGETTI_QPU_CAPABILITIES_1) + + +@pytest.fixture +def iqm_device_capabilities(): + return IqmDeviceCapabilities.parse_obj(MOCK_IQM_QPU_CAPABILITIES_1) + + + @pytest.fixture def ionq_device_capabilities(): return IonqDeviceCapabilities.parse_obj(MOCK_IONQ_GATE_MODEL_CAPABILITIES_JSON_1) @@ -501,6 +702,37 @@ def mock_ionq_qpu_device(ionq_device_capabilities): {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, ], } + +@pytest.fixture +def mock_quera_qpu_device(quera_device_capabilities): + return { + "deviceName": "Aquila", + "deviceType": "QPU", + "providerName": "QuEra", + "deviceStatus": "OFFLINE", + "deviceCapabilities": quera_device_capabilities.json(), + "deviceQueueInfo": [ + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "19", "queuePriority": "Normal"}, + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "3", "queuePriority": "Priority"}, + {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, + ], + } + + +@pytest.fixture +def mock_device_with_unsupported_actions(rigetti_capabilities_unsupported_actions): + return { + "deviceName": "FakeDevice", + "deviceType": "QPU", + "providerName": "Rigetti", + "deviceStatus": "OFFLINE", + "deviceCapabilities": rigetti_capabilities_unsupported_actions.json(), + "deviceQueueInfo": [ + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "19", "queuePriority": "Normal"}, + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "3", "queuePriority": "Priority"}, + {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, + ], + } @pytest.fixture @@ -563,6 +795,25 @@ def _device(): return _device() +@pytest.fixture +def quera_device(aws_session, mock_quera_qpu_device): + def _device(): + aws_session.get_device.return_value = mock_quera_qpu_device + aws_session.search_devices.return_value = [mock_quera_qpu_device] + return AwsDevice(QUERA_ARN, aws_session) + + return _device() + + +@pytest.fixture +def device_with_unsupported_actions(aws_session, mock_device_with_unsupported_actions): + def _device(): + aws_session.get_device.return_value = mock_device_with_unsupported_actions + aws_session.search_devices.return_value = [mock_device_with_unsupported_actions] + return AwsDevice(RIGETTI_ARN, aws_session) + + return _device() + def test_ionq_emulator(ionq_device): emulator = ionq_device.emulator target_emulator_passes = [ @@ -712,3 +963,112 @@ def test_get_emulator_multiple(mock_setup, rigetti_device): assert emulator._emulator_passes == [] emulator = rigetti_device.emulator mock_setup.assert_called_once() + + + +def test_fail_create_emulator_unsupported_actions(device_with_unsupported_actions): + error_message = "Emulators for device FakeDevice are not supported." + with pytest.raises(ValueError, match=error_message): + device_with_unsupported_actions.emulator + + +def test_create_ahs_criterion_with_no_local_rydberg( + quera_device_capabilities, ahs_device_capabilities_constants +): + quera_device_capabilities.paradigm.rydberg.rydbergLocal = None + ahs_device_capabilities_constants_dict = ahs_device_capabilities_constants.dict() + ahs_device_capabilities_constants_dict.update( + { + "LOCAL_RYDBERG_CAPABILITIES": False, + "LOCAL_MIN_DISTANCE_BETWEEN_SHIFTED_SITES": None, + "LOCAL_TIME_PRECISION": None, + "LOCAL_MIN_TIME_SEPARATION": None, + "LOCAL_MAGNITUDE_SEQUENCE_VALUE_MIN": None, + "LOCAL_MAGNITUDE_SEQUENCE_VALUE_MAX": None, + "LOCAL_MAGNITUDE_SLOPE_MAX": None, + "LOCAL_MAX_NONZERO_PATTERN_VALUES": None, + "MAGNITUDE_PATTERN_VALUE_MIN": None, + "MAGNITUDE_PATTERN_VALUE_MAX": None, + } + ) + ahs_device_capabilities_constants = DeviceCapabilitiesConstants( + **ahs_device_capabilities_constants_dict + ) + assert ahs_criterion(quera_device_capabilities) == AhsValidator( + ahs_device_capabilities_constants + ) + + +@pytest.fixture +def ahs_program_data(): + data = { + "setup": { + "ahs_register": { + "sites": [ + [Decimal("0.0"), Decimal("0.0")], + [Decimal("0.0"), Decimal("4e-6")], + [Decimal("5e-6"), Decimal("0.0")], + [Decimal("5e-6"), Decimal("4e-6")], + ], + "filling": [1, 0, 1, 0], + } + }, + "hamiltonian": { + "drivingFields": [ + { + "amplitude": { + "pattern": "uniform", + "time_series": { + "times": [0.0, 1e-07, 3.9e-06, 4e-06], + "values": [0.0, 12566400.0, 12566400.0, 0], + }, + }, + "phase": { + "pattern": "uniform", + "time_series": { + "times": [0.0, 1e-07, 3.9e-06, 4e-06], + "values": [0.0, 0, -16.0832, -16.0832], + }, + }, + "detuning": { + "pattern": "uniform", + "time_series": { + "times": [0.0, 1e-07, 3.9e-06, 4e-06], + "values": [-125000000, -125000000, 125000000, 125000000], + }, + }, + } + ], + "localDetuning": [ + { + "magnitude": { + "time_series": { + "times": [0.0, 1e-07, 3.9e-06, 4e-06], + "values": [0.0, 12566400.0, 12566400.0, 0], + }, + "pattern": [0.0, 0.1, 0.2, 0.0], + } + } + ], + }, + } + return AhsProgram.parse_obj(data) + + +@pytest.fixture +def ahs_program(ahs_program_data): + return AnalogHamiltonianSimulation.from_ir(ahs_program_data) + + +def test_ahs_emulator_validate_program(quera_device, ahs_program_data): + try: + quera_device.validate(ahs_program_data) + except Exception as e: + pytest.fail("Validate test failed: " + str(e)) + + +def test_ahs_emulate_program(quera_device, ahs_program): + try: + quera_device.emulate(ahs_program, shots=1) + except Exception as e: + pytest.fail("Validate test failed: " + str(e)) diff --git a/test/unit_tests/braket/emulation/ahs_passes/device_validators/conftest.py b/test/unit_tests/braket/emulation/ahs_passes/device_validators/conftest.py new file mode 100644 index 000000000..96e2e553f --- /dev/null +++ b/test/unit_tests/braket/emulation/ahs_passes/device_validators/conftest.py @@ -0,0 +1,114 @@ +from decimal import Decimal + +import pytest + +from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( + DeviceCapabilitiesConstants, +) +from braket.ir.ahs.program_v1 import Program + + +@pytest.fixture +def non_local_capabilities_constants(): + capabilities_dict = { + "BOUNDING_BOX_SIZE_X": Decimal("0.000075"), + "BOUNDING_BOX_SIZE_Y": Decimal("0.000076"), + "DIMENSIONS": 2, + "GLOBAL_AMPLITUDE_SLOPE_MAX": Decimal("250000000000000"), + "GLOBAL_AMPLITUDE_VALUE_MAX": Decimal("15800000.0"), + "GLOBAL_AMPLITUDE_VALUE_MIN": Decimal("0.0"), + "GLOBAL_AMPLITUDE_VALUE_PRECISION": Decimal("400.0"), + "GLOBAL_DETUNING_SLOPE_MAX": Decimal("250000000000000"), + "GLOBAL_DETUNING_VALUE_MAX": Decimal("125000000.0"), + "GLOBAL_DETUNING_VALUE_MIN": Decimal("-125000000.0"), + "GLOBAL_DETUNING_VALUE_PRECISION": Decimal("0.2"), + "GLOBAL_MIN_TIME_SEPARATION": Decimal("1E-8"), + "GLOBAL_PHASE_VALUE_MAX": Decimal("99.0"), + "GLOBAL_PHASE_VALUE_MIN": Decimal("-99.0"), + "GLOBAL_PHASE_VALUE_PRECISION": Decimal("5E-7"), + "GLOBAL_TIME_PRECISION": Decimal("1E-9"), + "LOCAL_MAGNITUDE_SEQUENCE_VALUE_MAX": None, + "LOCAL_MAGNITUDE_SEQUENCE_VALUE_MIN": None, + "LOCAL_MAGNITUDE_SLOPE_MAX": None, + "LOCAL_MIN_DISTANCE_BETWEEN_SHIFTED_SITES": None, + "LOCAL_MIN_TIME_SEPARATION": None, + "LOCAL_RYDBERG_CAPABILITIES": False, + "LOCAL_TIME_PRECISION": None, + "MAGNITUDE_PATTERN_VALUE_MAX": None, + "MAGNITUDE_PATTERN_VALUE_MIN": None, + "MAX_FILLED_SITES": 4, + "MAX_NET_DETUNING": None, + "MAX_SITES": 8, + "MAX_TIME": Decimal("0.000004"), + "MIN_DISTANCE": Decimal("0.000004"), + "MIN_ROW_DISTANCE": Decimal("0.000004"), + "SITE_PRECISION": Decimal("1E-7"), + } + return DeviceCapabilitiesConstants(**capabilities_dict) + + +@pytest.fixture +def capabilities_with_local_rydberg(non_local_capabilities_constants): + local_rydberg_constants = { + "LOCAL_RYDBERG_CAPABILITIES": True, + "LOCAL_TIME_PRECISION": Decimal("1e-9"), + "LOCAL_MAGNITUDE_SEQUENCE_VALUE_MAX": Decimal("125000000.0"), + "LOCAL_MAGNITUDE_SEQUENCE_VALUE_MIN": Decimal("0.0"), + "LOCAL_MAGNITUDE_SLOPE_MAX": Decimal("1256600000000000.0"), + "LOCAL_MIN_DISTANCE_BETWEEN_SHIFTED_SITES": Decimal("5e-06"), + "LOCAL_MIN_TIME_SEPARATION": Decimal("1E-8"), + "LOCAL_MAX_NONZERO_PATTERN_VALUES": 200, + "MAGNITUDE_PATTERN_VALUE_MAX": Decimal("1.0"), + "MAGNITUDE_PATTERN_VALUE_MIN": Decimal("0.0"), + } + capabilities_dict = non_local_capabilities_constants.dict() + capabilities_dict.update(local_rydberg_constants) + return DeviceCapabilitiesConstants(**capabilities_dict) + + +@pytest.fixture +def program_data(): + data = { + "setup": { + "ahs_register": { + "sites": [[0, 0], [0, 4e-6], [5e-6, 0], [5e-6, 4e-6]], + "filling": [1, 0, 1, 0], + } + }, + "hamiltonian": { + "drivingFields": [ + { + "amplitude": { + "pattern": "uniform", + "time_series": { + "times": [0, 1e-07, 3.9e-06, 4e-06], + "values": [0, 12566400.0, 12566400.0, 0], + }, + }, + "phase": { + "pattern": "uniform", + "time_series": { + "times": [0, 1e-07, 3.9e-06, 4e-06], + "values": [0, 0, -16.0832, -16.0832], + }, + }, + "detuning": { + "pattern": "uniform", + "time_series": { + "times": [0, 1e-07, 3.9e-06, 4e-06], + "values": [-125000000, -125000000, 125000000, 125000000], + }, + }, + } + ], + "localDetuning": [ + { + "magnitude": { + "time_series": {"times": [0, 4e-6], "values": [0, 0]}, + "pattern": [0.0, 1.0, 0.5, 0.0], + } + } + ], + }, + } + return Program.parse_obj(data) diff --git a/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_atom_arrangement.py b/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_atom_arrangement.py new file mode 100644 index 000000000..9f6e6bfad --- /dev/null +++ b/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_atom_arrangement.py @@ -0,0 +1,172 @@ +from decimal import Decimal + +import pytest +from pydantic.v1.error_wrappers import ValidationError + +from braket.analog_hamiltonian_simulator.rydberg.validators.atom_arrangement import AtomArrangement +from braket.emulation.emulation_passes.ahs_passes.device_validators import ( + DeviceAtomArrangementValidator, +) + + +@pytest.fixture +def atom_arrangement_data(): + return { + "sites": [[0, 0], [0, 5e-6], [5e-6, 0], [5e-6, 10e-6]], + "filling": [1, 0, 1, 0], + } + + +@pytest.fixture +def mock_atom_arrangement_data(): + data = { + "sites": [], + "filling": [], + } + return AtomArrangement.parse_obj(data).dict() + + +@pytest.mark.parametrize( + "sites, filling, error_message", + [ + ( + [], + [], + "Sites can not be empty.", + ), + ( + [], + [1, 0, 1, 0], + "Filling length (4) does not match sites length (0)", + ), + ( + [ + [Decimal("0.0"), Decimal("0.0")], + [Decimal("0.0"), Decimal("4e-6")], + [Decimal("5e-6"), Decimal("0.0")], + [Decimal("5e-6"), Decimal("4e-6")], + ], + [], + "Filling length (0) does not match sites length (4)", + ), + ], +) +def test_atom_arrangement_sites_or_fillings_empty( + sites, filling, error_message, mock_atom_arrangement_data, capabilities_with_local_rydberg +): + mock_atom_arrangement_data["sites"] = sites + mock_atom_arrangement_data["filling"] = filling + _assert_atom_arrangement( + mock_atom_arrangement_data, error_message, capabilities_with_local_rydberg + ) + + +def test_valid_atom_array(atom_arrangement_data, capabilities_with_local_rydberg): + try: + DeviceAtomArrangementValidator( + capabilities=capabilities_with_local_rydberg, **atom_arrangement_data + ) + except ValidationError as e: + pytest.fail(f"Validate test is failing : {str(e)}") + + +@pytest.mark.parametrize( + "sites, filling ,error_message", + [ + ( + [[1.1e-6, 1.1e-6], [2.2e-6, 7.31e-6]], + [1, 0], + "Coordinates 1([2.2e-06, 7.31e-06]) is defined with too high precision;" + "they must be multiples of 1E-7 meters", + ), + ( + [[3.201e-6, 0.0], [0.00, 4.1e-6], [-1.101e-6, 8.11e-6]], + [1, 0, 1], + "Coordinates 0([3.201e-06, 0.0]) is defined with too high precision;" + "they must be multiples of 1E-7 meters", + ), + ], +) +# Rule: Lattice coordinates have a maximum precision of lattice.geometry.position_resolution +def test_atom_arrangement_sites_defined_with_right_precision( + sites, filling, error_message, mock_atom_arrangement_data, non_local_capabilities_constants +): + mock_atom_arrangement_data["sites"] = sites + mock_atom_arrangement_data["filling"] = filling + _assert_atom_arrangement( + mock_atom_arrangement_data, error_message, non_local_capabilities_constants + ) + + +def _assert_atom_arrangement(data, error_message, device_capabilities_constants): + with pytest.raises(ValidationError) as e: + DeviceAtomArrangementValidator(capabilities=device_capabilities_constants, **data) + assert error_message in str(e.value) + + +def test_atom_arrangement_sites_not_too_many( + mock_atom_arrangement_data, non_local_capabilities_constants +): + sites = [[0.0, 0.0]] * (non_local_capabilities_constants.MAX_SITES + 1) + filling = [0] * (non_local_capabilities_constants.MAX_SITES + 1) + mock_atom_arrangement_data["sites"] = sites + mock_atom_arrangement_data["filling"] = filling + error_message = "There are too many sites (9); there must be at most 8 sites" + _assert_atom_arrangement( + mock_atom_arrangement_data, error_message, non_local_capabilities_constants + ) + + +@pytest.mark.parametrize( + "sites, filling,error_message", + [ + ( + [[Decimal("0.0"), Decimal("0.0")], [Decimal("5e-6"), Decimal("2.4e-6")]], + [0, 1], + "Sites [Decimal('0.0'), Decimal('0.0')] and site [Decimal('0.000005'), " + "Decimal('0.0000024')] have y-separation (0.0000024). It must either be " + "exactly zero or not smaller than 0.000004 meters", + ), + ( + [[Decimal("0.0"), Decimal("0.0")], [Decimal("5e-6"), Decimal("1.4e-6")]], + [0, 1], + "Sites [Decimal('0.0'), Decimal('0.0')] and site [Decimal('0.000005'), " + "Decimal('0.0000014')] have y-separation (0.0000014). It must either " + "be exactly zero or not smaller than 0.000004 meters", + ), + ], +) +# Rule: All sites in the lattice must be separated vertically +# by at least lattice.geometry.spacing_vertical_min +def test_atom_arrangement_sites_in_rows( + sites, + filling, + error_message, + mock_atom_arrangement_data, + non_local_capabilities_constants, +): + mock_atom_arrangement_data["sites"] = sites + mock_atom_arrangement_data["filling"] = filling + _assert_atom_arrangement( + mock_atom_arrangement_data, + error_message, + non_local_capabilities_constants, + ) + + +def test_atom_arrangement_filling_atom_number_limit( + mock_atom_arrangement_data, non_local_capabilities_constants +): + filling = [1] * (non_local_capabilities_constants.MAX_FILLED_SITES + 1) + mock_atom_arrangement_data["filling"] = filling + mock_atom_arrangement_data["sites"] = [ + [0, 0], + [0, 0.00004], + [0, 0.00005], + [0.00005, 0.00004], + [0.00005, 0], + ] + error_message = "Filling has 5 '1' entries; it must have not more than 4" + _assert_atom_arrangement( + mock_atom_arrangement_data, error_message, non_local_capabilities_constants + ) diff --git a/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_driving_field.py b/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_driving_field.py new file mode 100644 index 000000000..79bc402ce --- /dev/null +++ b/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_driving_field.py @@ -0,0 +1,321 @@ +import pytest +from pydantic.v1.error_wrappers import ValidationError + +from braket.analog_hamiltonian_simulator.rydberg.validators.driving_field import DrivingField +from braket.emulation.emulation_passes.ahs_passes.device_validators import ( + DeviceDrivingFieldValidator, +) + + +@pytest.fixture +def driving_field_data(): + return { + "amplitude": { + "pattern": "uniform", + "time_series": { + "times": [0, 1e-07, 3.9e-06, 4e-06], + "values": [0, 12566400.0, 12566400.0, 0], + }, + }, + "phase": { + "pattern": "uniform", + "time_series": { + "times": [0, 1e-07, 3.9e-06, 4e-06], + "values": [0, 0, -16.0832, -16.0832], + }, + }, + "detuning": { + "pattern": "uniform", + "time_series": { + "times": [0, 1e-07, 3.9e-06, 4e-06], + "values": [-125000000, -125000000, 125000000, 125000000], + }, + }, + } + + +@pytest.fixture +def mock_driving_field_data(): + data = { + "amplitude": { + "pattern": "uniform", + "time_series": { + "times": [], + "values": [], + }, + }, + "phase": { + "pattern": "uniform", + "time_series": { + "times": [], + "values": [], + }, + }, + "detuning": { + "pattern": "uniform", + "time_series": { + "times": [], + "values": [], + }, + }, + } + return DrivingField.parse_obj(data).dict() + + +def test_valid_device_driving_field(driving_field_data, non_local_capabilities_constants): + try: + DeviceDrivingFieldValidator( + capabilities=non_local_capabilities_constants, **driving_field_data + ) + except ValidationError as e: + pytest.fail(f"Validate test is failing : {str(e)}") + + +@pytest.mark.parametrize( + "times, field_name, error_message", + [ + ( + [0.0, 12.1e-9], + "amplitude", + "time point 1 (1.21E-8) of amplitude time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 12.1e-9], + "phase", + "time point 1 (1.21E-8) of phase time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 12.1e-9], + "detuning", + "time point 1 (1.21E-8) of detuning time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 12.1e-9, 4e-6], + "amplitude", + "time point 1 (1.21E-8) of amplitude time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 12.1e-9, 4e-6], + "phase", + "time point 1 (1.21E-8) of phase time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 12.1e-9, 4e-6], + "detuning", + "time point 1 (1.21E-8) of detuning time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 12.1e-9, 22.1e-9], + "amplitude", + "time point 1 (1.21E-8) of amplitude time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 12.1e-9, 22.1e-9], + "phase", + "time point 1 (1.21E-8) of phase time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 12.1e-9, 22.1e-9], + "detuning", + "time point 1 (1.21E-8) of detuning time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 22.1e-9, 12.1e-9], + "amplitude", + "time point 1 (2.21E-8) of amplitude time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 22.1e-9, 12.1e-9], + "phase", + "time point 1 (2.21E-8) of phase time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 22.1e-9, 12.1e-9], + "detuning", + "time point 1 (2.21E-8) of detuning time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ], +) +# Rule: The times for any component of the effective Hamiltonian +# have a maximum precision of rydberg.global.time_resolution +def test_driving_field_time_precision_is_correct( + times, field_name, error_message, mock_driving_field_data, non_local_capabilities_constants +): + mock_driving_field_data[field_name]["time_series"]["times"] = times + _assert_driving_field(mock_driving_field_data, error_message, non_local_capabilities_constants) + + +def test_driving_field_no_detuning(mock_driving_field_data, non_local_capabilities_constants): + mock_driving_field_data["detuning"]["time_series"]["times"].clear() + mock_driving_field_data["detuning"]["time_series"]["values"].clear() + try: + DeviceDrivingFieldValidator( + capabilities=non_local_capabilities_constants, **mock_driving_field_data + ) + except ValidationError as e: + pytest.fail(f"Validate test is failing : {str(e)}") + + +@pytest.mark.parametrize( + "values, field_name, error_message", + [ + ( + [0.0, 22.1e-9, 12, 1e-9, 0.0], + "amplitude", + "Value 1 (2.21E-8) in amplitude time_series is " + "defined with too many digits; it must be an integer multiple of 400.0", + ) + ], +) +def test_driving_field_value_precision_is_correct( + values, field_name, error_message, driving_field_data, non_local_capabilities_constants +): + driving_field_data[field_name]["time_series"]["values"] = values + _assert_driving_field(driving_field_data, error_message, non_local_capabilities_constants) + + +@pytest.mark.parametrize( + "times, field_name, error_message", + [ + ( + [0.0, 9e-9, 25e-9], + "amplitude", + "Time points of amplitude time_series, 0 (0.0) and 1 (9e-09), are too close; " + "they are separated by 9e-09 seconds. It must be at least 1E-8 seconds", + ), + ( + [0.0, 9e-9, 25e-9], + "phase", + "Time points of phase time_series, 0 (0.0) and 1 (9e-09), are too close; " + "they are separated by 9e-09 seconds. It must be at least 1E-8 seconds", + ), + ( + [0.0, 9e-9, 25e-9], + "detuning", + "Time points of detuning time_series, 0 (0.0) and 1 (9e-09), are too close; " + "they are separated by 9e-09 seconds. It must be at least 1E-8 seconds", + ), + ( + [0.0, 9e-9, 25e-9, 30e-9], + "amplitude", + "Time points of amplitude time_series, 0 (0.0) and 1 (9e-09), are too close; " + "they are separated by 9e-09 seconds. It must be at least 1E-8 seconds", + ), + ( + [0.0, 9e-9, 25e-9, 30e-9], + "phase", + "Time points of phase time_series, 0 (0.0) and 1 (9e-09), are too close; " + "they are separated by 9e-09 seconds. It must be at least 1E-8 seconds", + ), + ( + [0.0, 9e-9, 25e-9, 30e-9], + "detuning", + "Time points of detuning time_series, 0 (0.0) and 1 (9e-09), are too close; " + "they are separated by 9e-09 seconds. It must be at least 1E-8 seconds", + ), + ], +) +# Rule: The times for any component of the effective Hamiltonian +# must be spaced by at least rydberg.global.time_delta_min +def test_driving_field_timepoint_not_too_close( + times, field_name, error_message, mock_driving_field_data, non_local_capabilities_constants +): + mock_driving_field_data[field_name]["time_series"]["times"] = times + _assert_driving_field(mock_driving_field_data, error_message, non_local_capabilities_constants) + + +@pytest.mark.parametrize( + "values, field_name, error_message", + [ + ( + [2.5e7, 2.5e7, 2.5e7, 0.0], + "amplitude", + "The values of the Rabi frequency at the first and last time points " + "are 25000000.0, 0.0; they both must be nonzero.", + ), + ( + [0.0, 2.5e7, 2.5e7, 2.5e7], + "amplitude", + "The values of the Rabi frequency at the first and last time points " + "are 0.0, 25000000.0; they both must be nonzero.", + ), + ( + [2.5e7, 2.5e7, 2.5e7, 2.5e7], + "amplitude", + "The values of the Rabi frequency at the first and last time points " + "are 25000000.0, 25000000.0; they both must be nonzero.", + ), + ], +) +# Rule: The global Rydberg Rabi frequency amplitude must start and end at 0. +def test_driving_field_amplitude_start_and_end_values( + values, field_name, error_message, mock_driving_field_data, non_local_capabilities_constants +): + mock_driving_field_data[field_name]["time_series"]["values"] = values + _assert_driving_field(mock_driving_field_data, error_message, non_local_capabilities_constants) + + +@pytest.mark.parametrize( + "values, times, field_name, error_message", + [ + ( + [0.0, 2.5e7, 0.0], + [0.0, 0.01e-6, 2.0e-6], + "amplitude", + "For the amplitude field, rate of change of values " + "(between the 0-th and the 1-th times) " + "is 2500000000000000.0, more than 250000000000000", + ), + ( + [0.0, 2.5e7, 2.5e7, 0.0], + [0.0, 0.01e-6, 3.2e-6, 3.21e-6], + "amplitude", + "For the amplitude field, rate of change of values " + "(between the 0-th and the 1-th times) " + "is 2500000000000000.0, more than 250000000000000", + ), + ( + [0.0, 2.5e7, 2.5e7, 0.0], + [0.0, 0.01e-6, 3.2e-6, 3.21e-6], + "detuning", + "For the detuning field, rate of change of values " + "(between the 0-th and the 1-th times) " + "is 2500000000000000.0, more than 250000000000000", + ), + ], +) +def test_driving_field_slopes_not_too_steep( + values, + times, + field_name, + error_message, + mock_driving_field_data, + non_local_capabilities_constants, +): + mock_driving_field_data[field_name]["time_series"]["values"] = values + mock_driving_field_data[field_name]["time_series"]["times"] = times + _assert_driving_field(mock_driving_field_data, error_message, non_local_capabilities_constants) + + +def test_phase_values_start_with_0(mock_driving_field_data, non_local_capabilities_constants): + mock_driving_field_data["phase"]["time_series"]["values"] = [0.1, 2.5e7] + error_message = "The first value of of driving field phase is 0.1; it must be 0." + _assert_driving_field(mock_driving_field_data, error_message, non_local_capabilities_constants) + + +def _assert_driving_field(data, error_message, device_capabilities_constants): + with pytest.raises(ValidationError) as e: + DeviceDrivingFieldValidator(capabilities=device_capabilities_constants, **data) + assert error_message in str(e.value) diff --git a/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_hamiltonian.py b/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_hamiltonian.py new file mode 100644 index 000000000..0bf783a89 --- /dev/null +++ b/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_hamiltonian.py @@ -0,0 +1,72 @@ +import pytest +from pydantic.v1.error_wrappers import ValidationError + +from braket.emulation.emulation_passes.ahs_passes.device_validators import ( + DeviceHamiltonianValidator, +) + + +@pytest.fixture +def hamiltonian_data(): + return { + "drivingFields": [ + { + "amplitude": { + "pattern": "uniform", + "time_series": { + "times": [0, 1e-07, 3.9e-06, 4e-06], + "values": [0, 12566400.0, 12566400.0, 0], + }, + }, + "phase": { + "pattern": "uniform", + "time_series": { + "times": [0, 1e-07, 3.9e-06, 4e-06], + "values": [0, 0, -16.0832, -16.0832], + }, + }, + "detuning": { + "pattern": "uniform", + "time_series": { + "times": [0, 1e-07, 3.9e-06, 4e-06], + "values": [-125000000, -125000000, 125000000, 125000000], + }, + }, + } + ], + "localDetuning": [ + { + "magnitude": { + "time_series": {"times": [0, 4e-6], "values": [0, 0]}, + "pattern": [0.0, 1.0, 0.5, 0.0, 1.0], + } + } + ], + } + + +def test_hamiltonian(hamiltonian_data): + try: + DeviceHamiltonianValidator(**hamiltonian_data, LOCAL_RYDBERG_CAPABILITIES=True) + except ValidationError as e: + pytest.fail(f"Validate test is failing : {str(e)}") + + +@pytest.mark.parametrize("local_rydberg_exists", [(True), (False)]) +def test_hamiltonian_no_detuning(local_rydberg_exists, hamiltonian_data): + hamiltonian_data["localDetuning"].clear() + try: + DeviceHamiltonianValidator( + **hamiltonian_data, LOCAL_RYDBERG_CAPABILITIES=local_rydberg_exists + ) + except ValidationError as e: + pytest.fail(f"Validate test is failing : {str(e)}") + + +def test_no_local_rydberg_capabilities(hamiltonian_data): + error_message = "Local detuning cannot be specified; \ +1 are given. Specifying local \ +detuning is an experimental capability, use Braket Direct to request access." + with pytest.raises(ValidationError) as e: + DeviceHamiltonianValidator(**hamiltonian_data, LOCAL_RYDBERG_CAPABILITIES=False) + assert error_message in str(e.value) diff --git a/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_ir_validator.py b/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_ir_validator.py new file mode 100644 index 000000000..fffd372ee --- /dev/null +++ b/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_ir_validator.py @@ -0,0 +1,11 @@ +import pytest +from pydantic.v1.error_wrappers import ValidationError + +from braket.emulation.emulation_passes.ahs_passes.device_ir_validator import validate_program + + +def test_validate_program(program_data, capabilities_with_local_rydberg): + try: + validate_program(program=program_data, device_capabilities=capabilities_with_local_rydberg) + except ValidationError as e: + pytest.fail(f"Validate program is failing : {str(e)}") diff --git a/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_local_detuning.py b/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_local_detuning.py new file mode 100644 index 000000000..6c8bcff84 --- /dev/null +++ b/test/unit_tests/braket/emulation/ahs_passes/device_validators/test_device_local_detuning.py @@ -0,0 +1,169 @@ +import pytest +from pydantic.v1.error_wrappers import ValidationError + +from braket.emulation.emulation_passes.ahs_passes.device_validators import ( + DeviceLocalDetuningValidator, +) +from braket.ir.ahs.local_detuning import LocalDetuning + + +@pytest.fixture +def local_detuning_data(): + return { + "magnitude": { + "time_series": {"times": [0, 1e-07], "values": [0, 0]}, + "pattern": [0.0, 0.1, 0.1, 0.0, 0.1], + } + } + + +@pytest.fixture +def mock_local_detuning_data(): + data = { + "magnitude": { + "pattern": [], + "time_series": { + "times": [], + "values": [], + }, + } + } + return LocalDetuning.parse_obj(data).dict() + + +def test_valid_detuning(local_detuning_data, capabilities_with_local_rydberg): + try: + DeviceLocalDetuningValidator( + capabilities=capabilities_with_local_rydberg, **local_detuning_data + ) + except ValidationError as e: + pytest.fail(f"Validate test is failing: {str(e.value)}") + + +def test_validation_no_time_series(mock_local_detuning_data, capabilities_with_local_rydberg): + mock_local_detuning_data["magnitude"]["time_series"]["times"].clear() + mock_local_detuning_data["magnitude"]["time_series"]["values"].clear() + + try: + DeviceLocalDetuningValidator( + capabilities=capabilities_with_local_rydberg, **mock_local_detuning_data + ) + except ValidationError as e: + pytest.fail(f"Validate test is failing: {str(e.value)}") + + +def test_validation_no_detuning_data(mock_local_detuning_data, non_local_capabilities_constants): + error_message = ( + "Local Rydberg capabilities information has not been " "provided for local detuning." + ) + non_local_capabilities_constants.LOCAL_RYDBERG_CAPABILITIES = False + with pytest.raises(ValueError) as e: + DeviceLocalDetuningValidator( + capabilities=non_local_capabilities_constants, **mock_local_detuning_data + ) + assert error_message in str(e.value) + + +def test_shifting_field_magnitude_pattern_have_not_too_many_nonzeros( + mock_local_detuning_data, capabilities_with_local_rydberg +): + pattern = [0.1] * 257 + mock_local_detuning_data["magnitude"]["pattern"] = pattern + error_message = ( + "Number of nonzero magnitude pattern values is 257; it must not be more than 200" + ) + _assert_local_detuning(mock_local_detuning_data, error_message, capabilities_with_local_rydberg) + + +@pytest.mark.parametrize( + "values, times, error_message", + [ + ( + [0.0, 2.5e7, 0.0], + [0.0, 0.01e-6, 2.0e-6], + "For the magnitude field, rate of change of values " + "(between the 0-th and the 1-th times) " + "is 2500000000000000.0, more than 1256600000000000.0", + ), + ( + [0.0, 2.5e7, 2.5e7, 0.0], + [0.0, 0.01e-6, 3.2e-6, 3.21e-6], + "For the magnitude field, rate of change of values " + "(between the 0-th and the 1-th times) " + "is 2500000000000000.0, more than 1256600000000000.0", + ), + ], +) +def test_local_detuning_slopes_not_too_steep( + values, times, error_message, mock_local_detuning_data, capabilities_with_local_rydberg +): + mock_local_detuning_data["magnitude"]["time_series"]["values"] = values + mock_local_detuning_data["magnitude"]["time_series"]["times"] = times + _assert_local_detuning(mock_local_detuning_data, error_message, capabilities_with_local_rydberg) + + +@pytest.mark.parametrize( + "times, error_message", + [ + ( + [0.0, 12.1e-9], + "time point 1 (1.21E-8) of magnitude time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 12.1e-9, 4e-6], + "time point 1 (1.21E-8) of magnitude time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 12.1e-9, 22.1e-9], + "time point 1 (1.21E-8) of magnitude time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ( + [0.0, 22.1e-9, 12.1e-9], + "time point 1 (2.21E-8) of magnitude time_series is " + "defined with too many digits; it must be an integer multiple of 1E-9", + ), + ], +) +# Rule: The times for any component of the effective Hamiltonian have +# a maximum precision of rydberg.global.time_resolution +def test_shifting_field_magnitude_time_precision_is_correct( + times, error_message, mock_local_detuning_data, capabilities_with_local_rydberg +): + mock_local_detuning_data["magnitude"]["time_series"]["times"] = times + _assert_local_detuning(mock_local_detuning_data, error_message, capabilities_with_local_rydberg) + + +@pytest.mark.parametrize( + "times, error_message", + [ + ( + [0.0, 1.0, 2.0], + "The values of the shifting field magnitude time series at " + "the first and last time points are 0.0, 2.0; they both must be nonzero.", + ), + ( + [0.2, 1.0, 0], + "The values of the shifting field magnitude time series at " + "the first and last time points are 0.2, 0; they both must be nonzero.", + ), + ( + [0.0, 0.0, 1e-5], + "The values of the shifting field magnitude time series at " + "the first and last time points are 0.0, 1e-05; they both must be nonzero.", + ), + ], +) +def test_shifting_start_and_end_are_zero( + times, error_message, mock_local_detuning_data, capabilities_with_local_rydberg +): + mock_local_detuning_data["magnitude"]["time_series"]["values"] = times + _assert_local_detuning(mock_local_detuning_data, error_message, capabilities_with_local_rydberg) + + +def _assert_local_detuning(data, error_message, device_capabilities_constants): + with pytest.raises(ValidationError) as e: + DeviceLocalDetuningValidator(capabilities=device_capabilities_constants, **data) + assert error_message in str(e.value) diff --git a/tox.ini b/tox.ini index 95c862106..464c4f90c 100644 --- a/tox.ini +++ b/tox.ini @@ -130,4 +130,5 @@ commands = deps = # If you need to test on a certain branch, add @ after .git git+https://github.com/amazon-braket/amazon-braket-schemas-python.git - git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git + # git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git + git+https://github.com/Altanali/amazon-braket-default-simulator-python.git@optional_net_detuning From 6a0244d3ef4ee7e55fe16333dada3314eeb0ad75 Mon Sep 17 00:00:00 2001 From: Nagji Date: Tue, 30 Jul 2024 13:32:05 -0700 Subject: [PATCH 2/4] fix: Add missing imports to test_aws_emulation.py --- .../braket/aws/test_aws_emulation.py | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/test/unit_tests/braket/aws/test_aws_emulation.py b/test/unit_tests/braket/aws/test_aws_emulation.py index d7a8b903f..1498caaea 100644 --- a/test/unit_tests/braket/aws/test_aws_emulation.py +++ b/test/unit_tests/braket/aws/test_aws_emulation.py @@ -1,12 +1,13 @@ import json +from decimal import Decimal from unittest.mock import Mock, patch import networkx as nx import numpy as np import pytest -from decimal import Decimal from common_test_utils import RIGETTI_ARN, RIGETTI_REGION +from braket.ahs import AnalogHamiltonianSimulation from braket.aws import AwsDevice from braket.aws.aws_emulation import _get_qpu_gate_translations, ahs_criterion from braket.aws.aws_noise_models import ( @@ -15,7 +16,6 @@ _setup_calibration_specs, device_noise_model, ) -from braket.ahs import AnalogHamiltonianSimulation from braket.circuits import Circuit, Gate from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria from braket.circuits.noises import ( @@ -29,24 +29,23 @@ from braket.device_schema.error_mitigation.debias import Debias from braket.device_schema.ionq import IonqDeviceCapabilities, IonqDeviceParameters from braket.device_schema.iqm import IqmDeviceCapabilities -from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.device_schema.quera import QueraDeviceCapabilities +from braket.device_schema.rigetti import RigettiDeviceCapabilities from braket.devices import Devices from braket.devices.local_simulator import LocalSimulator from braket.emulation import Emulator +from braket.emulation.emulation_passes.ahs_passes import AhsValidator +from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( + DeviceCapabilitiesConstants, +) from braket.emulation.emulation_passes.gate_device_passes import ( ConnectivityValidator, GateConnectivityValidator, GateValidator, QubitCountValidator, ) -from braket.emulation.emulation_passes.ahs_passes import AhsValidator -from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( - DeviceCapabilitiesConstants -) from braket.ir.ahs.program_v1 import Program as AhsProgram - REGION = "us-west-1" IONQ_ARN = "arn:aws:braket:::device/qpu/ionq/Forte1" @@ -497,7 +496,6 @@ def ahs_device_capabilities_constants(): } - @pytest.fixture def rigetti_device_capabilities(): return RigettiDeviceCapabilities.parse_obj(MOCK_RIGETTI_QPU_CAPABILITIES_1) @@ -508,7 +506,6 @@ def iqm_device_capabilities(): return IqmDeviceCapabilities.parse_obj(MOCK_IQM_QPU_CAPABILITIES_1) - @pytest.fixture def ionq_device_capabilities(): return IonqDeviceCapabilities.parse_obj(MOCK_IONQ_GATE_MODEL_CAPABILITIES_JSON_1) @@ -702,7 +699,8 @@ def mock_ionq_qpu_device(ionq_device_capabilities): {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, ], } - + + @pytest.fixture def mock_quera_qpu_device(quera_device_capabilities): return { @@ -814,6 +812,7 @@ def _device(): return _device() + def test_ionq_emulator(ionq_device): emulator = ionq_device.emulator target_emulator_passes = [ @@ -965,7 +964,6 @@ def test_get_emulator_multiple(mock_setup, rigetti_device): mock_setup.assert_called_once() - def test_fail_create_emulator_unsupported_actions(device_with_unsupported_actions): error_message = "Emulators for device FakeDevice are not supported." with pytest.raises(ValueError, match=error_message): From a379f85ad69441fce6e9e33e6ee079030a97e5a8 Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 31 Jul 2024 14:39:04 -0700 Subject: [PATCH 3/4] feat: Introduce Basic AHS noise model --- src/braket/aws/aws_device.py | 2 + src/braket/aws/aws_emulation.py | 33 ++- .../emulation_passes/ahs_passes/__init__.py | 4 + .../ahs_passes/ahs_noisy_pass.py | 238 ++++++++++++++++++ 4 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 src/braket/emulation/emulation_passes/ahs_passes/ahs_noisy_pass.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 51bdc932a..f78b6930f 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -30,6 +30,7 @@ from braket.annealing.problem import Problem from braket.aws.aws_emulation import ( ahs_criterion, + ahs_noise_model, connectivity_validator, gate_connectivity_validator, gate_validator, @@ -932,6 +933,7 @@ def _setup_gate_device_emulator(self) -> Emulator: def _setup_ahs_device_emulator(self) -> Emulator: self._emulator = Emulator(backend="braket_ahs", name=self._name) self._emulator.add_pass(ahs_criterion(self.properties)) + self._emulator.add_pass(ahs_noise_model(self.properties)) return self._emulator def validate( diff --git a/src/braket/aws/aws_emulation.py b/src/braket/aws/aws_emulation.py index 4570d7d28..c0a8de832 100644 --- a/src/braket/aws/aws_emulation.py +++ b/src/braket/aws/aws_emulation.py @@ -9,7 +9,7 @@ from braket.device_schema.iqm import IqmDeviceCapabilities from braket.device_schema.quera import QueraDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities -from braket.emulation.emulation_passes.ahs_passes import AhsValidator +from braket.emulation.emulation_passes.ahs_passes import AhsValidator, AhsNoise, AhsNoiseData from braket.emulation.emulation_passes.ahs_passes.device_capabilities_constants import ( DeviceCapabilitiesConstants, ) @@ -21,6 +21,7 @@ ) + def qubit_count_validator(properties: DeviceCapabilities) -> QubitCountValidator: """ Create a QubitCountValidator pass which checks that the number of qubits used in a program does @@ -303,3 +304,33 @@ def _(properties: QueraDeviceCapabilities) -> DeviceCapabilitiesConstants: ) = rydberg_local["siteCoefficientRange"] return DeviceCapabilitiesConstants.parse_obj(capabilities) + + + +def ahs_noise_model(properties: DeviceCapabilities) -> AhsNoise: + return _ahs_noise_model(properties) + +@singledispatch +def _ahs_noise_model(properties: DeviceCapabilities) -> AhsNoise: + raise NotImplementedError("An AHS noise model cannot be created from device capabilities of " + f"type {type(properties)}.") + +@_ahs_noise_model.register(QueraDeviceCapabilities) +def _(properties: QueraDeviceCapabilities): + + capabilities = properties.paradigm + performance = capabilities.performance + noise_data = AhsNoiseData( + site_position_error= float(performance.lattice.sitePositionError), + filling_error = float(performance.lattice.vacancyErrorTypical), + vacancy_error = float(performance.lattice.vacancyErrorTypical), + ground_prep_error = float(performance.rydberg.rydbergGlobal.groundPrepError), + rabi_amplitude_ramp_correction = performance.rydberg.rydbergGlobal.rabiAmplitudeRampCorrection, + rabi_frequency_error_rel = float(performance.rydberg.rydbergGlobal.rabiFrequencyGlobalErrorRel), + detuning_error = float(performance.rydberg.rydbergGlobal.detuningError), + detuning_inhomogeneity = float(performance.rydberg.rydbergGlobal.detuningInhomogeneity), + atom_detection_error_false_positive = float(performance.lattice.atomDetectionErrorFalsePositiveTypical), + atom_detection_error_false_negative = float(performance.lattice.atomDetectionErrorFalseNegativeTypical), + rabi_amplitude_max = float(capabilities.rydberg.rydbergGlobal.rabiFrequencyRange[-1]) + ) + return AhsNoise(noise_data) diff --git a/src/braket/emulation/emulation_passes/ahs_passes/__init__.py b/src/braket/emulation/emulation_passes/ahs_passes/__init__.py index 5c9c6f86c..1fe6a3836 100644 --- a/src/braket/emulation/emulation_passes/ahs_passes/__init__.py +++ b/src/braket/emulation/emulation_passes/ahs_passes/__init__.py @@ -1 +1,5 @@ from braket.emulation.emulation_passes.ahs_passes.ahs_validator import AhsValidator # noqa: F40 +from braket.emulation.emulation_passes.ahs_passes.ahs_noisy_pass import( #noqa: F40 + AhsNoise, + AhsNoiseData +) \ No newline at end of file diff --git a/src/braket/emulation/emulation_passes/ahs_passes/ahs_noisy_pass.py b/src/braket/emulation/emulation_passes/ahs_passes/ahs_noisy_pass.py new file mode 100644 index 000000000..1ed40c363 --- /dev/null +++ b/src/braket/emulation/emulation_passes/ahs_passes/ahs_noisy_pass.py @@ -0,0 +1,238 @@ +from braket.emulation.emulation_passes import EmulationPass +from braket.ahs import AnalogHamiltonianSimulation +from braket.timings.time_series import TimeSeries +from braket.ahs.pattern import Pattern +from braket.ahs.field import Field +from braket.ahs.atom_arrangement import AtomArrangement, SiteType +from braket.ahs.driving_field import DrivingField +from braket.ahs.local_detuning import LocalDetuning +from braket.ir.ahs import Program as AHSProgram +from typing import Union, List, TypeVar, Tuple +from dataclasses import dataclass +from functools import singledispatchmethod +import scipy +import numpy as np +from decimal import Decimal + +@dataclass +class AhsNoiseData: + site_position_error: float + filling_error: float + vacancy_error: float + ground_prep_error: float + rabi_amplitude_ramp_correction: List[float] + rabi_frequency_error_rel: float + rabi_amplitude_max: float + detuning_error: float + detuning_inhomogeneity: float + atom_detection_error_false_positive: float + atom_detection_error_false_negative: float + + +AhsProgramType = TypeVar('AhsProgramType', bound = AHSProgram | AnalogHamiltonianSimulation) + + +class AhsNoise(EmulationPass[AhsProgramType]): + def __init__(self, ahs_noise_data: AhsNoiseData): + self._noise_data = ahs_noise_data + + + def run(self, program: AhsProgramType, steps: int = 100) -> AhsProgramType: + return self._apply_noise_model(program, steps) + + @singledispatchmethod + def _apply_noise_model(self, program: AhsProgramType, steps: int) -> AhsProgramType: + raise NotImplementedError + + @_apply_noise_model.register(AnalogHamiltonianSimulation) + def _(self, program: AnalogHamiltonianSimulation, steps: int) -> AnalogHamiltonianSimulation: + sites, fillings, preseq = self._apply_lattice_initialization_errors(program) + drive, local_detuning = self._apply_rydberg_noise(program, steps) + + register = AtomArrangement() + for (site, filling) in zip(sites, fillings): + if filling == 1: + register.add(site) + else: + register.add(site, site_type=SiteType.VACANT) + + + return AnalogHamiltonianSimulation( + register=register, + hamiltonian=drive+local_detuning + ) + + @_apply_noise_model.register(AHSProgram) + def _(self, program: AHSProgram) -> AHSProgram: + raise NotImplementedError + + def _apply_lattice_initialization_errors(self, program: AnalogHamiltonianSimulation) \ + -> Tuple[List[List[float]], List[int], List[int]]: + #Default to using the typical position error for now instead of the worst case error. + sites = [[float(x), float(y)] for (x, y) in zip(program.register.coordinate_list(0), program.register.coordinate_list(1))] + filling = program.to_ir().setup.ahs_register.filling + + erroneous_sites = self._apply_site_position_error(sites) + erroneous_filling = self._apply_binomial_noise(filling, \ + self._noise_data.filling_error, self._noise_data.vacancy_error) + + pre_seq = self._apply_binomial_noise(erroneous_filling, \ + self._noise_data.atom_detection_error_false_negative, \ + self._noise_data.atom_detection_error_false_positive) + + erroneous_filling = self._apply_binomial_noise(erroneous_filling, \ + 0, self._noise_data.ground_prep_error) + + return erroneous_sites, erroneous_filling, pre_seq + + + def _apply_site_position_error(self, + sites: List[List[float]]) -> List[List[float]]: + erroneous_sites = [] + for site in sites: + erroneous_sites.append( + site + self._noise_data.site_position_error * np.random.normal(size=2) + ) + + return erroneous_sites + + def _apply_binomial_noise(self, + arr: List[int], + binomial_probability_p01: float, + binomial_probability_p10: float) -> List[int]: + noisy_arr = [] + for val in arr: + if val == 1: + # Apply the error of switching 1 as 0 + noisy_arr.append(1 - np.random.binomial(1, binomial_probability_p10)) + else: + # Apply the error of switching 0 as 1 + noisy_arr.append(np.random.binomial(1, binomial_probability_p01)) + + return noisy_arr + + + def _apply_rydberg_noise(self, program: AnalogHamiltonianSimulation, steps: int) \ + -> Tuple[TimeSeries, LocalDetuning]: + noisy_detuning, local_detuning = self._apply_detuning_errors( + program.hamiltonian.detuning, + program.to_ir().setup.ahs_register.filling, + steps + ) + + noisy_amplitude = self._apply_amplitude_errors( + program.hamiltonian.amplitude, + steps, + ) + noisy_drive = DrivingField(amplitude = noisy_amplitude, + detuning = noisy_detuning, + phase = program.hamiltonian.phase) + + return noisy_drive, local_detuning + + + def _apply_detuning_errors(self, + detuning: TimeSeries, + fillings: List[int], + steps: int + ) -> Tuple[TimeSeries, LocalDetuning]: + + detuning_times = np.array(detuning.time_series.times(), dtype='float64') + detuning_values = np.array(detuning.time_series.values(), dtype='float64') + + noisy_detuning_times = np.linspace(0, detuning_times[-1], steps) + noisy_detuning_values = np.interp(noisy_detuning_times, detuning_times, detuning_values) + + # Apply the detuning error + noisy_detuning_values += \ + self._noise_data.detuning_error * np.random.normal(size=len(noisy_detuning_values)) + + noisy_detuning = TimeSeries.from_lists(noisy_detuning_times, noisy_detuning_values) + + # Apply detuning inhomogeneity + h = Pattern([np.random.rand() for _ in fillings]) + detuning_local = TimeSeries.from_lists(noisy_detuning_times, + self._noise_data.detuning_inhomogeneity * np.ones( + len(noisy_detuning_times) + ) + ) + + # Assemble the local detuning + local_detuning = LocalDetuning( + magnitude=Field( + time_series=detuning_local, + pattern=h + ) + ) + + return noisy_detuning, local_detuning + + + + def _apply_amplitude_errors(self, amplitude: TimeSeries, steps: int) \ + -> TimeSeries: + amplitude_times = np.array(amplitude.time_series.times(), dtype='float64') + amplitude_values = np.array(amplitude.time_series.values(), dtype='float64') + + # Rewrite the rabi_ramp_correction as a function of slopes + rabi_ramp_correction_slopes = [self._noise_data.rabi_amplitude_max / float(corr.rampTime) + for corr in self._noise_data.rabi_amplitude_ramp_correction + ] + rabi_ramp_correction_fracs = [float(corr.rabiCorrection) + for corr in self._noise_data.rabi_amplitude_ramp_correction + ] + rabi_ramp_correction_slopes = rabi_ramp_correction_slopes[::-1] + rabi_ramp_correction_fracs = rabi_ramp_correction_fracs[::-1] + + # Helper function to find the correction factor for a given slope + get_frac = scipy.interpolate.interp1d(rabi_ramp_correction_slopes, + rabi_ramp_correction_fracs, + bounds_error=False, + fill_value="extrapolate" + ) + + noisy_amplitude_times = np.linspace(0, amplitude_times[-1], steps) + noisy_amplitude_values = [] + + # First apply the rabi ramp correction + for ind in range(len(amplitude_times)): + if ind == 0: + continue + + # First determine the correction factor from the slope + t1, t2 = amplitude_times[ind-1], amplitude_times[ind] + v1, v2 = amplitude_values[ind-1], amplitude_values[ind] + slope = (v2 - v1) / (t2 - t1) + if np.abs(slope) > 0: + frac = get_frac(np.abs(slope)) * np.sign(slope) + else: + frac = 1.0 + + # Next, determine the coefficients for the quadratic correction + if frac >= 1.0: + a, b, c = 0, 0, v2 + else: + # Determine the coefficients for the quadratic correction + # of the form f(t) = a*t^2 + b * t + c + # such that f(t1) = v1 and f(t2) = v2 and + # a/3*(t2^3-t1^3) + b/2*(t2^2-t1^2) + c(t2-t1) = frac * (t2-t1) * (v2-v1)/2 + + a = 3 * (v1 + frac * v1 + v2 - frac * v2)/(t1 - t2)**2 + c = (t2 * v1 * ((2 + 3 * frac) * t1 + t2) + t1 * v2 * (t1 + (2 - 3 * frac) * t2))/(t1 - t2)**2 + b = (v2 - c - a * t2**2) / t2 + + # Finally, put values into noisy_amplitude_values + for t in noisy_amplitude_times: + if t1 <= t and t <= t2: + noisy_amplitude_values.append(a * t**2 + b * t + c) + + + # Next apply amplitude error + rabi_errors = 1 + self._noise_data.rabi_frequency_error_rel * np.random.normal(size=len(noisy_amplitude_values)) + noisy_amplitude_values = np.multiply(noisy_amplitude_values, rabi_errors) + noisy_amplitude_values = [max(0, value) for value in noisy_amplitude_values] # amplitude has to be non-negative + + noisy_amplitude = TimeSeries.from_lists(noisy_amplitude_times, noisy_amplitude_values) + + return noisy_amplitude + \ No newline at end of file From c00813156ed4235568d37728d1a9d0b94a1731fd Mon Sep 17 00:00:00 2001 From: Nagji Date: Wed, 31 Jul 2024 14:39:31 -0700 Subject: [PATCH 4/4] change: Remove extraneous directory --- add_ahs_old | 1 - 1 file changed, 1 deletion(-) delete mode 160000 add_ahs_old diff --git a/add_ahs_old b/add_ahs_old deleted file mode 160000 index b269aa22a..000000000 --- a/add_ahs_old +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b269aa22a2da4c0c1797bf1cd402158a5790f13d