Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce Device AHS validator for additional device information. #271

Closed
wants to merge 10 commits into from
1 change: 1 addition & 0 deletions a.txt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed!

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<ExceptionInfo ValidationError(model='DeviceHamiltonianValidator', errors=[{'loc': ('__root__',), 'msg': 'Local detuning cannot be sp...pecifying local detuning is an experimental capability, use Braket Direct to request access.', 'type': 'value_error'}]) tblen=2>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from braket.analog_hamiltonian_simulator.rydberg.validators.device_validators.device_atom_arrangement import ( # noqa: E501 F401
DeviceAtomArrangementValidator,
)
from braket.analog_hamiltonian_simulator.rydberg.validators.device_validators.device_capabilities_constants import ( # noqa: E501 F401
DeviceCapabilitiesConstants,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two imports are going to cause circular import issues. One fix would be to swap the ordering of L1 and L4 but I would encourage you to see what other options there would be to resolve the circular import issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 746ab2c I opt to move device_capabilities_constants.py to the parent directory, so it lives alongside the capabilities_constants.py; this does break the separation of device_validation and validation constructs/methods but separates the DeviceCapabilitiesConstants from the constructs that use it without having to enforce an import order in __init__.py.

Please let me know if this approach is appropriate!

)
from braket.analog_hamiltonian_simulator.rydberg.validators.device_validators.device_driving_field import ( # noqa: E501 F401
DeviceDrivingFieldValidator,
)
from braket.analog_hamiltonian_simulator.rydberg.validators.device_validators.device_hamiltonian import ( # noqa: E501 F401
DeviceHamiltonianValidator,
)
from braket.analog_hamiltonian_simulator.rydberg.validators.device_validators.device_local_detuning import ( # noqa: E501 F401
DeviceLocalDetuningValidator,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from decimal import Decimal
from typing import Tuple

from pydantic.v1.class_validators import root_validator

from braket.analog_hamiltonian_simulator.rydberg.validators.atom_arrangement import (
AtomArrangementValidator,
)
from braket.analog_hamiltonian_simulator.rydberg.validators.device_validators 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):
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):
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;\
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):
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\
{capabilities.MAX_SITES} sites"
)
return values

# The y coordinates of any two lattice sites must either be equal
# or differ by at least MIN_ROW_DISTANCE.
@root_validator(pre=True, skip_on_failure=True)
def sites_in_rows(cls, values):
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\
either be exactly zero or not smaller than {min_allowed_distance} meters"
)
return values

# The number of filled lattice sites must not exceed MAX_FILLED_SITES.
@root_validator(pre=True, skip_on_failure=True)
def atom_number_limit(cls, values):
filling = values["filling"]
capabilities = values["capabilities"]
qubits = sum(filling)
if qubits > capabilities.MAX_FILLED_SITES:
raise ValueError(
f"Filling has {qubits} '1' entries; is must have not\
Altanali marked this conversation as resolved.
Show resolved Hide resolved
more than {capabilities.MAX_FILLED_SITES}"
)
return values
Original file line number Diff line number Diff line change
@@ -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]
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
from pydantic.v1.class_validators import root_validator

from braket.analog_hamiltonian_simulator.rydberg.validators.device_validators import (
DeviceCapabilitiesConstants,
)
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


class DeviceDrivingFieldValidator(DrivingFieldValidator):
capabilities: DeviceCapabilitiesConstants

# Amplitude must start and end at 0.0
@root_validator(pre=True, skip_on_failure=True)
def amplitude_start_and_end_values(cls, values):
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 \
{start_value}, {end_value}; they both must be both 0."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{start_value}, {end_value}; they both must be both 0."
{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):
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):
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):
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):
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):
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):
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):
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):
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):
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):
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):
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):
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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):
LOCAL_RYDBERG_CAPABILITIES = values["LOCAL_RYDBERG_CAPABILITIES"]
local_detuning = values.get("localDetuning", [])
if not LOCAL_RYDBERG_CAPABILITIES:
if len(local_detuning) > 0:
Altanali marked this conversation as resolved.
Show resolved Hide resolved
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
Loading
Loading