diff --git a/pdm.lock b/pdm.lock index 56552ad54..cd1229283 100644 --- a/pdm.lock +++ b/pdm.lock @@ -6,7 +6,7 @@ groups = ["default", "dev", "doc"] cross_platform = true static_urls = false lock_version = "4.3" -content_hash = "sha256:c2995ea995ac3393ea3e9e41bfe2ecbb41d84836e2f0c6aaa3dc81d93c727adc" +content_hash = "sha256:e46096370f188b9383b4e9e8c42fa15ba66ff934140d0fb301a6372e5dc7b56e" [[package]] name = "amazon-braket-default-simulator" @@ -853,7 +853,7 @@ files = [ [[package]] name = "ipython" -version = "8.15.0" +version = "8.16.0" requires_python = ">=3.9" summary = "IPython: Productive Interactive Computing" dependencies = [ @@ -873,8 +873,8 @@ dependencies = [ "typing-extensions; python_version < \"3.10\"", ] files = [ - {file = "ipython-8.15.0-py3-none-any.whl", hash = "sha256:45a2c3a529296870a97b7de34eda4a31bee16bc7bf954e07d39abe49caf8f887"}, - {file = "ipython-8.15.0.tar.gz", hash = "sha256:2baeb5be6949eeebf532150f81746f8333e2ccce02de1c7eedde3f23ed5e9f1e"}, + {file = "ipython-8.16.0-py3-none-any.whl", hash = "sha256:dba644376826a532e362da945a672865be7efda76ecf999219e6021bda85d702"}, + {file = "ipython-8.16.0.tar.gz", hash = "sha256:7a1b2e1e6a3ec5baa466163c451335081f31859883889ff0289c6b21f7a095e2"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 347473eb2..a8c581039 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "bloqade" -version = "0.5.1" +version = "0.6.0" description = "Neutral atom software development kit" authors = [ {name = "QuEra Computing Inc.", email = "info@quera.com"}, @@ -17,7 +17,7 @@ classifiers = [ dependencies = [ "juliacall>=0.9.14", "numpy>=1.25.2", - "pydantic>=1.10.12", + "pydantic>=1.10.13", "scipy>=1.9.3", "pandas>=2.1.0", "bokeh>=3.2.2", diff --git a/src/bloqade/__init__.py b/src/bloqade/__init__.py index 7be52e800..e9629bbd7 100644 --- a/src/bloqade/__init__.py +++ b/src/bloqade/__init__.py @@ -1,15 +1,21 @@ from bloqade.ir import var, cast, Variable, Literal, start +from bloqade.ir import to_waveform as waveform from bloqade.serialize import load, save, loads, dumps -from bloqade.builder.factory import ( +from bloqade.factory import ( piecewise_linear, piecewise_constant, linear, constant, + rydberg_h, ) import bloqade.ir as _ir from bloqade.constants import RB_C6 +import importlib.metadata + +__version__ = importlib.metadata.version("bloqade") + def tree_depth(depth: int = None): """Setting globally maximum depth for tree printing @@ -44,4 +50,6 @@ def tree_depth(depth: int = None): "save", "loads", "dumps", + "rydberg_h", + "waveform", ] diff --git a/src/bloqade/builder/assign.py b/src/bloqade/builder/assign.py index af43e6945..879fdb122 100644 --- a/src/bloqade/builder/assign.py +++ b/src/bloqade/builder/assign.py @@ -93,6 +93,10 @@ def __init__( super().__init__(parent) + if len(assignments) == 0: + self._batch_params = [] + return + circuit = self.parse_circuit() variables = ScanVariablesAnalogCircuit().emit(circuit) diff --git a/src/bloqade/builder/backend/quera.py b/src/bloqade/builder/backend/quera.py index 574154b1c..208789797 100644 --- a/src/bloqade/builder/backend/quera.py +++ b/src/bloqade/builder/backend/quera.py @@ -100,7 +100,7 @@ def cloud_mock(self): """ return self.parse().quera.cloud_mock() - def mock(self, state_file: str = ".mock_state.txt"): + def mock(self, state_file: str = ".mock_state.txt", submission_error: bool = False): """ Specify mock, testing locally. @@ -123,4 +123,6 @@ def mock(self, state_file: str = ".mock_state.txt"): """ - return self.parse().quera.mock(state_file) + return self.parse().quera.mock( + state_file=state_file, submission_error=submission_error + ) diff --git a/src/bloqade/builder/factory.py b/src/bloqade/builder/factory.py deleted file mode 100644 index 91843aeee..000000000 --- a/src/bloqade/builder/factory.py +++ /dev/null @@ -1,42 +0,0 @@ -from bloqade.ir.control.waveform import Waveform, Linear, Constant -from bloqade.builder.typing import ScalarType -from beartype import beartype -from beartype.typing import List - -# this part only for manually build sequences. - - -@beartype -def linear(duration: ScalarType, start: ScalarType, stop: ScalarType): - return Linear(start, stop, duration) - - -@beartype -def constant(duration: ScalarType, value: ScalarType): - return Constant(value, duration) - - -@beartype -def piecewise_linear(durations: List[ScalarType], values: List[ScalarType]) -> Waveform: - pwl_wf = None - for duration, start, stop in zip(durations, values[:-1], values[1:]): - if pwl_wf is None: - pwl_wf = Linear(start, stop, duration) - else: - pwl_wf = pwl_wf.append(Linear(start, stop, duration)) - - return pwl_wf - - -@beartype -def piecewise_constant( - durations: List[ScalarType], values: List[ScalarType] -) -> Waveform: - pwc_wf = None - for duration, value in zip(durations, values): - if pwc_wf is None: - pwc_wf = Constant(value, duration) - else: - pwc_wf = pwc_wf.append(Constant(value, duration)) - - return pwc_wf diff --git a/src/bloqade/factory.py b/src/bloqade/factory.py new file mode 100644 index 000000000..f79115a76 --- /dev/null +++ b/src/bloqade/factory.py @@ -0,0 +1,166 @@ +from bloqade.ir.routine.base import Routine +from bloqade.ir.control.waveform import Waveform, Linear, Constant +from bloqade.builder.typing import ScalarType +from beartype import beartype +from beartype.typing import List, Optional, Union, Dict, Any + + +@beartype +def linear(duration: ScalarType, start: ScalarType, stop: ScalarType) -> Linear: + """Create a Linear waveform. + + Args: + duration (ScalarType): duration of linear waveform + start (ScalarType): starting value of linear waveform + stop (ScalarType): ending value of linear waveform + + Returns: + Linear: Linear waveform + """ + return Linear(start, stop, duration) + + +@beartype +def constant(duration: ScalarType, value: ScalarType) -> Constant: + """Create a Constant waveform. + + Args: + duration (ScalarType): _description_ + value (ScalarType): _description_ + + Returns: + Constant: A Constant waveform. + """ + return Constant(value, duration) + + +@beartype +def piecewise_linear(durations: List[ScalarType], values: List[ScalarType]) -> Waveform: + """Create a piecewise linear waveform. + + Create a piecewise linear waveform from a list of durations and values. The + value `duration[i]` is of the linear segment between `values[i]` and `values[i+1]`. + + Args: + durations (List[ScalarType]): The duration of each segment + values (List[ScalarType]): The values for each segment + + Raises: + ValueError: If the length of `values` is not one greater than the length of + `durations`. + + Returns: + Waveform: The piecewise linear waveform. + """ + + if len(durations) + 1 != len(values): + raise ValueError( + "The length of values must be one greater than the length of durations" + ) + + pwl_wf = None + for duration, start, stop in zip(durations, values[:-1], values[1:]): + if pwl_wf is None: + pwl_wf = Linear(start, stop, duration) + else: + pwl_wf = pwl_wf.append(Linear(start, stop, duration)) + + return pwl_wf + + +@beartype +def piecewise_constant( + durations: List[ScalarType], values: List[ScalarType] +) -> Waveform: + """Create a piecewise linear waveform. + + Create a piecewise constant waveform from a list of durations and values. The + value `duration[i]` corresponds to the length of time for the i'th segment + with a value of `values[i]`. + + Args: + durations (List[ScalarType]): The duration of each segment + values (List[ScalarType]): The values for each segment + + Raises: + ValueError: If the length of `values` is not the same as the length of + `durations`. + + Returns: + Waveform: The piecewise linear waveform. + """ + if len(durations) != len(values): + raise ValueError( + "The length of values must be the same as the length of durations" + ) + + pwc_wf = None + for duration, value in zip(durations, values): + if pwc_wf is None: + pwc_wf = Constant(value, duration) + else: + pwc_wf = pwc_wf.append(Constant(value, duration)) + + return pwc_wf + + +@beartype +def rydberg_h( + atoms_positions: Any, + detuning: Optional[Waveform] = None, + amplitude: Optional[Waveform] = None, + phase: Optional[Waveform] = None, + static_params: Dict[str, Any] = {}, + batch_params: Union[List[Dict[str, Any]], Dict[str, Any]] = [], + args: List[str] = [], +) -> Routine: + """Create a rydberg program with uniform detuning, amplitude, and phase. + + Args: + atoms_positions (Any): Description of geometry of atoms in system. + detuning (Optional[Waveform], optional): Waveform for detuning. + Defaults to None. + amplitude (Optional[Waveform], optional): Waveform describing the amplitude of + the rabi term. Defaults to None. + phase (Optional[Waveform], optional): Waveform describing the phase of rabi + term. Defaults to None. + static_params (Dict[str, Any], optional): Define static parameters of your + program. Defaults to {}. + batch_params (Union[List[Dict[str, Any]], Dict[str, Any]], optional): + Parmaters for a batch of tasks. Defaults to []. + args (List[str], optional): List of arguments to leave till runtime. + Defaults to []. + + Returns: + Routine: An object that can be used to dispatch a rydberg program to + multiple backends. + """ + from bloqade import start + from bloqade.atom_arrangement import AtomArrangement + + print(type(atoms_positions)) + + if isinstance(atoms_positions, AtomArrangement): + prog = atoms_positions + else: + prog = start.add_position(atoms_positions) + + if detuning is not None: + prog = prog.rydberg.detuning.uniform.apply(detuning) + + if amplitude is not None: + prog = prog.amplitude.uniform.apply(amplitude) + + if phase is not None: + prog = prog.phase.uniform.apply(phase) + + prog = prog.assign(**static_params) + + if isinstance(batch_params, dict): + prog = prog.batch_assign(**batch_params) + else: + prog = prog.batch_assign(batch_params) + + prog = prog.args(args) + + return prog.parse() diff --git a/src/bloqade/ir/__init__.py b/src/bloqade/ir/__init__.py index 7ce494ec8..3a5db739b 100644 --- a/src/bloqade/ir/__init__.py +++ b/src/bloqade/ir/__init__.py @@ -11,7 +11,7 @@ Interpolation, Sample, PythonFn, - instruction, + to_waveform, GaussianKernel, LogisticKernel, SigmoidKernel, @@ -68,7 +68,7 @@ "Sample", "Interpolation", "PythonFn", - "instruction", + "to_waveform", "GaussianKernel", "LogisticKernel", "SigmoidKernel", diff --git a/src/bloqade/ir/control/waveform.py b/src/bloqade/ir/control/waveform.py index 7525694f5..244aef3f2 100644 --- a/src/bloqade/ir/control/waveform.py +++ b/src/bloqade/ir/control/waveform.py @@ -1,4 +1,5 @@ from numbers import Real +from bloqade.builder.typing import ScalarType from bloqade.ir.tree_print import Printer from bloqade.ir.scalar import ( Scalar, @@ -14,6 +15,7 @@ from decimal import Decimal from pydantic.dataclasses import dataclass from beartype.typing import Any, Tuple, Union, List, Callable, Dict +from beartype import beartype from enum import Enum import numpy as np @@ -23,7 +25,8 @@ from bloqade.visualization import display_ir -def instruction(duration: Any) -> "PythonFn": +@beartype +def to_waveform(duration: ScalarType) -> Callable[[Callable], "PythonFn"]: # turn python function into a waveform instruction.""" def waveform_wrapper(fn: Callable) -> "PythonFn": @@ -486,6 +489,11 @@ def print_node(self): def children(self): return {"duration": self.duration, **{p.name: p for p in self.parameters}} + def sample( + self, dt: ScalarType, interpolation: Union[str, "Interpolation"] + ) -> "Sample": + return Sample(self, interpolation, cast(dt)) + @dataclass class SmoothingKernel: diff --git a/src/bloqade/ir/routine/base.py b/src/bloqade/ir/routine/base.py index 56338a889..ff0bde798 100644 --- a/src/bloqade/ir/routine/base.py +++ b/src/bloqade/ir/routine/base.py @@ -5,8 +5,8 @@ from bloqade.builder.base import Builder from bloqade.ir.routine.params import Params - -from dataclasses import dataclass +from pydantic import ConfigDict +from pydantic.dataclasses import dataclass from typing import TYPE_CHECKING, Union if TYPE_CHECKING: @@ -29,7 +29,10 @@ def parse(self: "RoutineBase") -> "Routine": return self -@dataclass(frozen=True) +__pydantic_dataclass_config__ = ConfigDict(arbitrary_types_allowed=True) + + +@dataclass(frozen=True, config=__pydantic_dataclass_config__) class RoutineBase(RoutineParse): source: Builder circuit: AnalogCircuit @@ -43,7 +46,7 @@ def __str__(self): return out -@dataclass(frozen=True) +@dataclass(frozen=True, config=__pydantic_dataclass_config__) class Routine(RoutineBase): """Result of parsing a completed Builder string.""" diff --git a/src/bloqade/ir/routine/bloqade.py b/src/bloqade/ir/routine/bloqade.py index 51a09a258..da87a5ec1 100644 --- a/src/bloqade/ir/routine/bloqade.py +++ b/src/bloqade/ir/routine/bloqade.py @@ -1,19 +1,19 @@ from collections import OrderedDict -from bloqade.ir.routine.base import RoutineBase +from bloqade.ir.routine.base import RoutineBase, __pydantic_dataclass_config__ from bloqade.builder.typing import LiteralType from bloqade.task.batch import LocalBatch from beartype import beartype from beartype.typing import Optional, Tuple -from dataclasses import dataclass +from pydantic.dataclasses import dataclass -@dataclass(frozen=True) +@dataclass(frozen=True, config=__pydantic_dataclass_config__) class BloqadeServiceOptions(RoutineBase): def python(self): return BloqadePythonRoutine(self.source, self.circuit, self.params) -@dataclass(frozen=True) +@dataclass(frozen=True, config=__pydantic_dataclass_config__) class BloqadePythonRoutine(RoutineBase): def _compile( self, diff --git a/src/bloqade/ir/routine/braket.py b/src/bloqade/ir/routine/braket.py index c2c718b4b..271d5c8ff 100644 --- a/src/bloqade/ir/routine/braket.py +++ b/src/bloqade/ir/routine/braket.py @@ -1,17 +1,17 @@ from collections import OrderedDict -from dataclasses import dataclass +from pydantic.dataclasses import dataclass from beartype import beartype from beartype.typing import Optional, Tuple from bloqade.builder.typing import LiteralType -from bloqade.ir.routine.base import RoutineBase +from bloqade.ir.routine.base import RoutineBase, __pydantic_dataclass_config__ from bloqade.submission.braket import BraketBackend from bloqade.task.batch import LocalBatch, RemoteBatch from bloqade.task.braket_simulator import BraketEmulatorTask from bloqade.task.braket import BraketTask -@dataclass(frozen=True) +@dataclass(frozen=True, config=__pydantic_dataclass_config__) class BraketServiceOptions(RoutineBase): def aquila(self) -> "BraketHardwareRoutine": backend = BraketBackend( @@ -23,7 +23,7 @@ def local_emulator(self): return BraketLocalEmulatorRoutine(self.source, self.circuit, self.params) -@dataclass(frozen=True) +@dataclass(frozen=True, config=__pydantic_dataclass_config__) class BraketHardwareRoutine(RoutineBase): backend: BraketBackend @@ -163,7 +163,7 @@ def __call__( return self.run(shots, args, name, shuffle, **kwargs) -@dataclass(frozen=True) +@dataclass(frozen=True, config=__pydantic_dataclass_config__) class BraketLocalEmulatorRoutine(RoutineBase): def _compile( self, shots: int, args: Tuple[LiteralType, ...] = (), name: Optional[str] = None diff --git a/src/bloqade/ir/routine/quera.py b/src/bloqade/ir/routine/quera.py index 5f6c9fa23..4d1ad6076 100644 --- a/src/bloqade/ir/routine/quera.py +++ b/src/bloqade/ir/routine/quera.py @@ -1,9 +1,9 @@ from collections import OrderedDict -from dataclasses import dataclass +from pydantic.dataclasses import dataclass import json from bloqade.builder.typing import LiteralType -from bloqade.ir.routine.base import RoutineBase +from bloqade.ir.routine.base import RoutineBase, __pydantic_dataclass_config__ from bloqade.submission.quera import QuEraBackend from bloqade.submission.mock import MockBackend from bloqade.submission.quera_api_client.load_config import load_config @@ -14,7 +14,7 @@ from beartype import beartype -@dataclass(frozen=True) +@dataclass(frozen=True, config=__pydantic_dataclass_config__) class QuEraServiceOptions(RoutineBase): @beartype def device(self, config_file: Optional[str], **api_config): @@ -35,12 +35,14 @@ def cloud_mock(self) -> "QuEraHardwareRoutine": return QuEraHardwareRoutine(self.source, self.circuit, self.params, backend) @beartype - def mock(self, state_file: str = ".mock_state.txt") -> "QuEraHardwareRoutine": - backend = MockBackend(state_file=state_file) + def mock( + self, state_file: str = ".mock_state.txt", submission_error: bool = False + ) -> "QuEraHardwareRoutine": + backend = MockBackend(state_file=state_file, submission_error=submission_error) return QuEraHardwareRoutine(self.source, self.circuit, self.params, backend) -@dataclass(frozen=True) +@dataclass(frozen=True, config=__pydantic_dataclass_config__) class QuEraHardwareRoutine(RoutineBase): backend: Union[QuEraBackend, MockBackend] diff --git a/src/bloqade/submission/mock.py b/src/bloqade/submission/mock.py index a638a1d4f..017a35b87 100644 --- a/src/bloqade/submission/mock.py +++ b/src/bloqade/submission/mock.py @@ -46,8 +46,12 @@ def simulate_task_results(task: QuEraTaskSpecification, p_full=0.99, p_empty=0.0 class MockBackend(SubmissionBackend): state_file: str = ".mock_state.txt" + submission_error: bool = False def submit_task(self, task: QuEraTaskSpecification) -> str: + if self.submission_error: + raise ValueError("mock submission error") + task_id = str(uuid.uuid4()) task_results = simulate_task_results(task) with open(self.state_file, "a") as IO: diff --git a/src/bloqade/task/batch.py b/src/bloqade/task/batch.py index d28b38f47..4842697ef 100644 --- a/src/bloqade/task/batch.py +++ b/src/bloqade/task/batch.py @@ -1,4 +1,4 @@ -from bloqade.serialize import Serializer, dumps +from bloqade.serialize import Serializer from bloqade.task.base import Report from bloqade.task.quera import QuEraTask from bloqade.task.braket import BraketTask @@ -15,11 +15,10 @@ # from bloqade.submission.base import ValidationError -from beartype.typing import Union, Optional, Dict, Any +from beartype.typing import Union, Optional, Dict, Any, List from beartype import beartype from collections import OrderedDict from itertools import product -import json import traceback import datetime import sys @@ -27,7 +26,7 @@ import warnings import pandas as pd import numpy as np -from dataclasses import dataclass +from dataclasses import dataclass, field class Serializable: @@ -39,6 +38,8 @@ def json(self, **options) -> str: JSON string """ + from bloqade import dumps + return dumps(self, **options) @@ -184,6 +185,63 @@ def _deserializer(d: Dict[str, Any]) -> LocalBatch: return LocalBatch(**d) +@dataclass +@Serializer.register +class TaskError(Serializable): + exception_type: str + stack_trace: str + + +@dataclass +@Serializer.register +class BatchErrors(Serializable): + task_errors: OrderedDict[int, TaskError] = field( + default_factory=lambda: OrderedDict([]) + ) + + @beartype + def print_errors(self, task_indices: Union[List[int], int]) -> str: + return str(self.get_errors(task_indices)) + + @beartype + def get_errors(self, task_indices: Union[List[int], int]): + return BatchErrors( + task_errors=OrderedDict( + [ + (task_index, self.task_errors[task_index]) + for task_index in task_indices + if task_index in self.task_errors + ] + ) + ) + + def __str__(self) -> str: + output = "" + for task_index, task_error in self.task_errors.items(): + output += ( + f"Task {task_index} failed to submit with error: " + f"{task_error.exception_type}\n" + f"{task_error.stack_trace}" + ) + + return output + + +@BatchErrors.set_serializer +def _serialize(self: BatchErrors) -> Dict[str, List]: + return { + "task_errors": [ + (task_number, task_error) + for task_number, task_error in self.task_errors.items() + ] + } + + +@BatchErrors.set_deserializer +def _deserialize(obj: dict) -> BatchErrors: + return BatchErrors(task_errors=OrderedDict(obj["task_errors"])) + + # this class get collection of tasks # basically behaves as a psudo queuing system # the user only need to store this objecet @@ -321,6 +379,8 @@ def resubmit(self, shuffle_submit_order: bool = True) -> "RemoteBatch": def _submit( self, shuffle_submit_order: bool = True, ignore_submission_error=False, **kwargs ) -> "RemoteBatch": + from bloqade import save + # online, non-blocking if shuffle_submit_order: submission_order = np.random.permutation(list(self.tasks.keys())) @@ -333,7 +393,7 @@ def _submit( ## upon submit() should validate for Both backends ## and throw errors when fail. - errors = OrderedDict() + errors = BatchErrors() shuffled_tasks = OrderedDict() for task_index in submission_order: task = self.tasks[task_index] @@ -342,17 +402,18 @@ def _submit( task.submit(**kwargs) except BaseException as error: # record the error in the error dict - errors[int(task_index)] = { - "exception_type": error.__class__.__name__, - "stack trace": traceback.format_exc(), - } + errors.task_errors[int(task_index)] = TaskError( + exception_type=error.__class__.__name__, + stack_trace=traceback.format_exc(), + ) + task.task_result_ir = QuEraTaskResults( task_status=QuEraTaskStatusCode.Unaccepted ) self.tasks = shuffled_tasks # permute order using dump way - if errors: + if len(errors.task_errors) > 0: time_stamp = datetime.datetime.now() if "win" in sys.platform: @@ -369,8 +430,8 @@ def _submit( # cloud_batch_result.save_json(future_file, indent=2) # saving ? - with open(error_file, "w") as f: - json.dump(errors, f, indent=2) + save(errors, error_file) + save(self, future_file) if ignore_submission_error: warnings.warn( @@ -382,7 +443,9 @@ def _submit( ) else: raise RemoteBatch.SubmissionException( - "One or more error(s) occured during submission, please see " + str(errors) + + "\n" + + "One or more error(s) occured during submission, please see " "the following files for more information:\n" f" - {os.path.join(cwd, future_file)}\n" f" - {os.path.join(cwd, error_file)}\n" diff --git a/tests/test_builder.py b/tests/test_builder.py index d133cac8f..f78f0b935 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -15,7 +15,7 @@ import bloqade.ir.routine.braket as braket from plum import NotFoundLookupError -from bloqade.ir.control.waveform import instruction +from bloqade.ir.control.waveform import to_waveform from bloqade.ir import rydberg, detuning, hyperfine, rabi from bloqade import start, cast, var @@ -33,7 +33,7 @@ def test_assign_checks(): delta = var("delta") / (2 * np.pi) omega_max = var("omega_max") * 2 * np.pi - @instruction(t_2) + @to_waveform(t_2) def detuning(t, u): return np.abs(t) * u diff --git a/tests/test_builder_factory.py b/tests/test_builder_factory.py deleted file mode 100644 index ae24fad47..000000000 --- a/tests/test_builder_factory.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -from bloqade.builder.factory import ( - piecewise_linear, - piecewise_constant, - constant, - linear, -) -from bloqade import cast - - -def test_ir_piecewise_linear(): - A = piecewise_linear([0.1, 3.8, 0.2], [-10, -7, "a", "b"]) - - ## Append type ir node - assert len(A.waveforms) == 3 - assert A.waveforms[0].duration == cast(0.1) - assert A.waveforms[0].start == cast(-10) - assert A.waveforms[0].stop == cast(-7) - - assert A.waveforms[1].duration == cast(3.8) - assert A.waveforms[1].start == cast(-7) - assert A.waveforms[1].stop == cast("a") - - assert A.waveforms[2].duration == cast(0.2) - assert A.waveforms[2].start == cast("a") - assert A.waveforms[2].stop == cast("b") - - -def test_ir_const(): - A = constant(value=3.415, duration=0.55) - - ## Constant type ir node: - assert A.value == cast(3.415) - assert A.duration == cast(0.55) - - -def test_ir_linear(): - A = linear(start=0.5, stop=3.2, duration=0.76) - - ## Linear type ir node: - assert A.start == cast(0.5) - assert A.stop == cast(3.2) - assert A.duration == cast(0.76) - - -def test_ir_piecewise_constant(): - A = piecewise_constant(durations=[0.1, 3.8, 0.2], values=[-10, "a", "b"]) - - assert A.waveforms[0].duration == cast(0.1) - assert A.waveforms[0].value == cast(-10) - - assert A.waveforms[1].duration == cast(3.8) - assert A.waveforms[1].value == cast("a") - - assert A.waveforms[2].duration == cast(0.2) - assert A.waveforms[2].value == cast("b") -""" diff --git a/tests/test_factory.py b/tests/test_factory.py new file mode 100644 index 000000000..165f20b28 --- /dev/null +++ b/tests/test_factory.py @@ -0,0 +1,165 @@ +import pytest +from bloqade import ( + waveform, + rydberg_h, + piecewise_linear, + piecewise_constant, + constant, + linear, + var, + cast, + start, +) +from bloqade.atom_arrangement import Chain +from bloqade.ir import ( + AnalogCircuit, + Sequence, + rydberg, + Pulse, + rabi, + detuning, + Field, + Uniform, +) +from bloqade.ir.routine.base import Routine +from bloqade.ir.routine.params import Params +import numpy as np + + +def test_ir_piecewise_linear(): + A = piecewise_linear([0.1, 3.8, 0.2], [-10, -7, "a", "b"]) + + ## Append type ir node + assert len(A.waveforms) == 3 + assert A.waveforms[0].duration == cast(0.1) + assert A.waveforms[0].start == cast(-10) + assert A.waveforms[0].stop == cast(-7) + + assert A.waveforms[1].duration == cast(3.8) + assert A.waveforms[1].start == cast(-7) + assert A.waveforms[1].stop == cast("a") + + assert A.waveforms[2].duration == cast(0.2) + assert A.waveforms[2].start == cast("a") + assert A.waveforms[2].stop == cast("b") + + with pytest.raises(ValueError): + piecewise_linear([0.1, 3.8, 0.2], [-10, -7, "a", "b", "c"]) + + +def test_ir_const(): + A = constant(value=3.415, duration=0.55) + + ## Constant type ir node: + assert A.value == cast(3.415) + assert A.duration == cast(0.55) + + +def test_ir_linear(): + A = linear(start=0.5, stop=3.2, duration=0.76) + + ## Linear type ir node: + assert A.start == cast(0.5) + assert A.stop == cast(3.2) + assert A.duration == cast(0.76) + + +def test_ir_piecewise_constant(): + A = piecewise_constant(durations=[0.1, 3.8, 0.2], values=[-10, "a", "b"]) + + assert A.waveforms[0].duration == cast(0.1) + assert A.waveforms[0].value == cast(-10) + + assert A.waveforms[1].duration == cast(3.8) + assert A.waveforms[1].value == cast("a") + + assert A.waveforms[2].duration == cast(0.2) + assert A.waveforms[2].value == cast("b") + + with pytest.raises(ValueError): + piecewise_constant([0.1, 3.8, 0.2], [-10, -7, "a", "b"]) + + +def test_rydberg_h(): + run_time = var("run_time") + + @waveform(run_time + 0.2) + def delta(t, amp, omega): + return np.sin(omega * t) * amp + + delta = delta.sample(0.05, "linear") + ampl = piecewise_linear([0.1, run_time, 0.1], [0, 10, 10, 0]) + phase = piecewise_constant([2, 2], [0, np.pi]) + register = Chain(11, lattice_spacing=6.1) + + static_params = {"amp": 1.0} + batch_params = [{"omega": omega} for omega in [1, 2, 4, 8]] + args = ["run_time"] + + routine = rydberg_h( + register, + detuning=delta, + amplitude=ampl, + phase=phase, + batch_params=batch_params, + static_params=static_params, + args=args, + ) + + detuning_field = Field({Uniform: delta}) + ampl_field = Field({Uniform: ampl}) + phase_field = Field({Uniform: phase}) + + pulse = Pulse( + {detuning: detuning_field, rabi.amplitude: ampl_field, rabi.phase: phase_field} + ) + sequence = Sequence({rydberg: pulse}) + + source = ( + register.rydberg.detuning.uniform.apply(delta) + .amplitude.uniform.apply(ampl) + .phase.uniform.apply(phase) + .assign(**static_params) + .batch_assign(batch_params) + .args(args) + ) + + circuit = AnalogCircuit(register, sequence) + params = Params(static_params, batch_params, args) + expected_routine = Routine(source, circuit, params) + + # ignore because no equality implemented + # assert routine.source == expected_routine.source + assert routine.circuit == expected_routine.circuit + assert routine.params == expected_routine.params + + +def test_rydberg_h_2(): + run_time = var("run_time") + + @waveform(run_time + 0.2) + def delta(t, amp, omega): + return np.sin(omega * t) * amp + + delta = delta.sample(0.05, "linear") + ampl = piecewise_linear([0.1, run_time, 0.1], [0, 10, 10, 0]) + phase = piecewise_constant([2, 2], [0, np.pi]) + register = start.add_position((0, 0)) + + prog = rydberg_h( + (0, 0), detuning=delta, amplitude=ampl, phase=phase, batch_params={} + ) + + detuning_field = Field({Uniform: delta}) + ampl_field = Field({Uniform: ampl}) + phase_field = Field({Uniform: phase}) + + pulse = Pulse( + {detuning: detuning_field, rabi.amplitude: ampl_field, rabi.phase: phase_field} + ) + sequence = Sequence({rydberg: pulse}) + + print(prog.parse_circuit()) + print(AnalogCircuit(register, sequence)) + + assert prog.parse_circuit() == AnalogCircuit(register, sequence) diff --git a/tests/test_task.py b/tests/test_task.py index 903e01b65..d78b9eb2d 100644 --- a/tests/test_task.py +++ b/tests/test_task.py @@ -1,121 +1,45 @@ -from bloqade.ir import Sequence, rydberg, detuning, Uniform, Linear, ScaledLocations -from bloqade.ir.location import Square - -# import bloqade.lattice as lattice - -n_atoms = 11 -lattice_const = 5.9 - -rabi_amplitude_values = [0.0, 15.8, 15.8, 0.0] -rabi_detuning_values = [-16.33, -16.33, "delta_end", "delta_end"] -durations = [0.8, "sweep_time", 0.8] - -ordered_state_2D_prog = ( - Square(n_atoms, lattice_const) - .rydberg.rabi.amplitude.uniform.piecewise_linear(durations, rabi_amplitude_values) - .detuning.uniform.piecewise_linear(durations, rabi_detuning_values) -) - -ordered_state_2D_job = ordered_state_2D_prog.assign(delta_end=42.66, sweep_time=2.4) - -pbin = ordered_state_2D_job.quera.aquila() - -pbin = pbin.parse_circuit() - -# pbin.circuit.sequence - - -# dict interface -seq = Sequence( - { - rydberg: { - detuning: { - Uniform: Linear(start=1.0, stop="x", duration=3.0), - ScaledLocations({1: 1.0, 2: 2.0}): Linear( - start=1.0, stop="x", duration=3.0 - ), - }, - } - } -) - - -# job = HardwareBatchResult.load_json("example-3-2d-ordered-state-job.json") - -# res = job.report() - - -# print(lattice.Square(3).apply(seq).__lattice__) -# print(lattice.Square(3).apply(seq).braket(nshots=1000).run_async().report().dataframe) -# print("bitstring") -# print(lattice.Square(3).apply(seq).braket(nshots=1000).run_async().report().bitstring) - -# # pipe interface -# report = ( -# lattice.Square(3) -# .rydberg.detuning.uniform.apply(Linear(start=1.0, stop="x", duration=3.0)) -# .location(2) -# .scale(3.0) -# .apply(Linear(start=1.0, stop="x", duration=3.0)) -# .rydberg.rabi.amplitude.uniform -# .apply(Linear(start=1.0, stop="x", duration=3.0)) -# .assign(x=10) -# .braket(nshots=1000) -# .run_async() -# .report() -# ) - -# print(report) -# print(report.bitstring) -# print(report.dataframe) - -# lattice.Square(3).rydberg.detuning.location(2).location(3).apply( -# Linear(start=1.0, stop="x", duration=3.0) -# ).location(3).location(4).apply(Linear(start=1.0, stop="x", duration=3.0)).braket( -# nshots=1000 -# ).run_async() - -# # start.rydberg.detuning.location(2).location(3) - - -# prog = ( -# lattice.Square(3) -# .rydberg.detuning.uniform.apply(Linear(start=1.0, stop="x", duration=3.0)) -# .location(2) -# .scale(3.0) -# .apply(Linear(start=1.0, stop="x", duration=3.0)) -# .hyperfine.rabi.amplitude.location(2) -# .apply(Linear(start=1.0, stop="x", duration=3.0)) -# .assign(x=1.0) -# .multiplex(10.0).braket(nshots=1000) -# .run_async() -# .report() -# .dataframe.groupby(by=["x"]) -# .count() -# ) - -# ( -# lattice.Square(3) -# .rydberg.detuning.uniform.apply(Linear(start=1.0, stop="x", duration=3.0)) -# .multiplex.quera -# ) - - -# wf = ( -# Linear(start=1.0, stop=2.0, duration=2.0) -# .scale(2.0) -# .append(Linear(start=1.0, stop=2.0, duration=2.0)) -# ) - - -# prog = ( -# lattice.Square(3) -# .hyperfine.detuning.location(1) -# .scale(2.0) -# .piecewise_linear(coeffs=[1.0, 2.0, 3.0]) -# .location(2) -# .constant(value=2.0, duration="x") -# ) - -# prog.seq -# prog.lattice +from bloqade.atom_arrangement import Chain +from bloqade.task.batch import RemoteBatch +import glob +import os +import pytest + + +def test_batch_error(*args): + with pytest.raises(RemoteBatch.SubmissionException): + ( + Chain(5, 6.1) + .rydberg.detuning.uniform.linear(-10, 10, 3.0) + .quera.mock(submission_error=True) + .run_async(100) + ) + + error_files = glob.glob("partial-batch-errors-*") + batch_files = glob.glob("partial-batch-future-*") + + for error_file, batch_file in zip(error_files, batch_files): + os.remove(error_file) + os.remove(batch_file) + + assert len(error_files) == 1 + assert len(batch_files) == 1 + + +def test_batch_warn(): + with pytest.warns(): + ( + Chain(5, 6.1) + .rydberg.detuning.uniform.linear(-10, 10, 3.0) + .quera.mock(submission_error=True) + .run_async(100, ignore_submission_error=True) + ) + + error_files = glob.glob("partial-batch-errors-*") + batch_files = glob.glob("partial-batch-future-*") + + for error_file, batch_file in zip(error_files, batch_files): + os.remove(error_file) + os.remove(batch_file) + + assert len(error_files) == 1 + assert len(batch_files) == 1 diff --git a/tests/test_variable_scan.py b/tests/test_variable_scan.py index fe8f05799..d250e4d07 100644 --- a/tests/test_variable_scan.py +++ b/tests/test_variable_scan.py @@ -6,7 +6,7 @@ ) import numpy as np -from bloqade.ir.control.waveform import instruction +from bloqade.ir.control.waveform import to_waveform def test_1(): @@ -42,7 +42,7 @@ def test_2(): delta = var("delta") / (2 * np.pi) omega_max = var("omega_max") * 2 * np.pi - @instruction(t_2) + @to_waveform(t_2) def detuning(t, u): return np.abs(t) * u diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 000000000..1982e5eb6 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,6 @@ +import bloqade + + +def test_version(): + print(bloqade.__version__) + assert bloqade.__version__ diff --git a/tests/test_waveform.py b/tests/test_waveform.py index 711bee54b..746b95244 100644 --- a/tests/test_waveform.py +++ b/tests/test_waveform.py @@ -6,7 +6,7 @@ AlignedWaveform, Alignment, AlignedValue, - instruction, + to_waveform, Interpolation, GaussianKernel, LogisticKernel, @@ -81,7 +81,7 @@ def my_func(time, *, omega, phi=0, amplitude): return amplitude * np.cos(omega * time + phi) - @instruction(duration=1.0) + @to_waveform(duration=1.0) def annot_my_func(time, *, omega, phi=0, amplitude): import numpy as np