From 7f876001be516aa0575be9befb9e8911760ea5b8 Mon Sep 17 00:00:00 2001 From: John Long Date: Mon, 25 Sep 2023 16:03:04 -0400 Subject: [PATCH 01/20] refactor docstrings for start, rydberg/hyperfine, rabi/detuning --- src/bloqade/builder/coupling.py | 2 +- src/bloqade/builder/drive.py | 80 +++++++++++++-------------------- src/bloqade/ir/location/list.py | 33 +++++++------- 3 files changed, 50 insertions(+), 65 deletions(-) diff --git a/src/bloqade/builder/coupling.py b/src/bloqade/builder/coupling.py index 661d5f644..d7c6a09e0 100644 --- a/src/bloqade/builder/coupling.py +++ b/src/bloqade/builder/coupling.py @@ -5,7 +5,7 @@ class LevelCoupling(Builder): @property def detuning(self): """ - - Specify the Detuning field + - Specify the Detuning field of your program. - Next-step: - Possible Next: diff --git a/src/bloqade/builder/drive.py b/src/bloqade/builder/drive.py index 21ceb7882..100fc05d0 100644 --- a/src/bloqade/builder/drive.py +++ b/src/bloqade/builder/drive.py @@ -5,59 +5,43 @@ class Drive: @property def rydberg(self) -> Rydberg: """ - - Specify the Rydberg level coupling. - - Possible Next: - - -> `...rydberg.rabi` - :: address rabi term - - -> `...rydberg.detuning` - :: address detuning field - - Examples: - >>> node = bloqade.start.rydberg - >>> type(node) - - - - Rydberg level coupling have two reachable field nodes: - - - detuning term (See also [`Detuning`][bloqade.builder.field.Detuning]) - - rabi term (See also [`Rabi`][bloqade.builder.field.Rabi]) - - >>> ryd_detune = bloqade.start.rydberg.detuning - >>> ryd_rabi = bloqade.start.rydberg.rabi - - See [`Rydberg`][bloqade.builder.coupling.Rydberg] for more details. + - Address the Rydberg level coupling in your program. + - Next possible steps to build your program are specifying the [`Rabi`][bloqade.builder.field.Rabi] field or [`Detuning`][bloqade.builder.field.Detuning] field. + - |_ `...rydberg.rabi`: for Rabi field + - |_ `...rydberg.detuning`: for Detuning field + - In the absence of a field you the value is set to zero by default. + + Example Usage: + ``` + >>> target_rydberg_rabi = start.rydberg.rabi + >>> type(target_rydberg_rabi) + bloqade.builder.field.Rabi + + >>> targe_hyperfine_rabi = start.rydberg.detuning + >>> type(target_rydberg_detuning) + bloqade.builder.field.Detuning + ``` """ return Rydberg(self) @property def hyperfine(self) -> Hyperfine: """ - - Specify the Hyperfile level coupling. - - Possible Next: - - -> `...hyperfine.rabi` - :: address rabi term - - -> `...hyperfine.detuning` - :: address detuning field - - - Examples: - >>> node = bloqade.start.hyperfine - >>> type(node) - - - - Hyperfine level coupling have two reachable field nodes: - - - detuning term (See also [`Detuning`][bloqade.builder.field.Detuning]) - - rabi term (See also [`Rabi`][bloqade.builder.field.Rabi]) - - >>> hyp_detune = bloqade.start.hyperfine.detuning - >>> hyp_rabi = bloqade.start.hyperfine.rabi - - - See [Hyperfine][bloqade.builder.coupling.Hyperfine] for more details. + - Address the Hyperfine level coupling in your program. + - Next possible steps to build your program are specifying the [`Rabi`][bloqade.builder.field.Rabi] field or [`Detuning`][bloqade.builder.field.Detuning] field. + - |_ `...hyperfine.rabi`: for Rabi field + - |_ `...hyperfine.detuning`: for Detuning field + - In the absence of a field you the value is set to zero by default. + + Example Usage: + ``` + >>> target_hyperfine_rabi = start.hyperfine.rabi + >>> type(target_rydberg_rabi) + bloqade.builder.field.Rabi + + >>> targe_hyperfine_detuning = start.hyperfine.detuning + >>> type(target_hyperfine_detuning) + bloqade.builder.field.Detuning + ``` """ return Hyperfine(self) diff --git a/src/bloqade/ir/location/list.py b/src/bloqade/ir/location/list.py index 405ab6c52..56fc8106e 100644 --- a/src/bloqade/ir/location/list.py +++ b/src/bloqade/ir/location/list.py @@ -57,20 +57,21 @@ def __iter__(self): start = ListOfLocations() """ - - Program starting node - - Possible Next - - -> `start.rydberg` - :: address rydberg level coupling - - -> `start.hyperfine` - :: address hyperfine level coupling - - - Possible Next - - -> `start.add_locations(List[Tuple[int]])` - :: add multiple atoms to current register - - -> `start.add_location(Tuple[int])` - :: add atom to current register +- A Program starting point, alias of empty [`ListOfLocations`][bloqade.ir.location.list.ListOfLocations]. +- Next possible steps to build your program are adding atom positions and addressing level couplings. +- Specify which level coupling to address with: + - |_ `start.rydberg`: for [`Rydberg`][bloqade.builder.coupling.Rydberg] Level coupling + - |_ `start.hyperfine`: for [`Hyperfine`][bloqade.builder.coupling.Hyperfine] Level coupling + - LOCKOUT: You cannot add atoms to your geometry after specifying level coupling. +- continue/start building your geometry with: + - |_ `start.add_position()`: to add atom(s) to current register. It will accept: + - A single coordinate, represented as a tuple (e.g. `(5,6)`) with a value that + can either be: + - integers: `(5,6)` + - floats: `(5.1, 2.5)` + - strings (for later variable assignment): `("x", "y")` + - [`Scalar`][bloqade.ir.scalar.Scalar] objects: `(2*cast("x"), 5+cast("y"))` + - A list of coordinates, represented as a list of types mentioned previously. + - A numpy array with shape (n, 2) where n is the total number of atoms + - `.add_position()` will return another `ListOfLocations` you can build on. """ From 116144a4e2c4ab0c68f7d16e7c87ee70d1d5b7ad Mon Sep 17 00:00:00 2001 From: John Long Date: Mon, 25 Sep 2023 17:59:14 -0400 Subject: [PATCH 02/20] refactor detuning, rabi docstrings --- src/bloqade/builder/coupling.py | 56 +++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/bloqade/builder/coupling.py b/src/bloqade/builder/coupling.py index 7126bb40c..bdcf80ac3 100644 --- a/src/bloqade/builder/coupling.py +++ b/src/bloqade/builder/coupling.py @@ -6,19 +6,24 @@ class LevelCoupling(Builder): @property def detuning(self) -> Detuning: """ - - Specify the Detuning field of your program. - - Next-step: - - Possible Next: - - -> `...detuning.location(int)` - :: Address atom at specific location - - -> `...detuning.uniform` - :: Address all atoms in register - - -> `...detuning.var(str)` - :: Address atom at location labeled by variable - + - Specify the [`Detuning`][bloqade.builder.field.Detuning] field of your program. + - Next possible steps to build your program are allowing the field to target specific atoms: + - |_ `...detuning.uniform`: To address all atoms in the field + - |_ `...detuning.location(int)`: To address an atom at a specific location via index + - |_ `...detuning.var(str)`: To address an atom at a specific location via variable name + + Usage Examples: + ``` + >>> prog = start.add_position(([(0,0), (1,2)]).rydberg.detuning + # target all atoms with waveforms specified later + >>> prog.uniform + # target individual atoms via index in the list of coordinates you passed in earlier + # (This is chainable) + >>> prog.location(0).location(1) + # target individual atoms via index represented as a variable + # (This is also chainable) + >>> prog.var("atom_1").var("atom_2") + ``` """ return Detuning(self) @@ -26,16 +31,21 @@ def detuning(self) -> Detuning: @property def rabi(self) -> Rabi: """ - - Specify the Rabi term/field. - - Possible Next: - - -> `...rabi.amplitude` - :: address rabi amplitude - - -> `...rabi.phase` - :: address rabi phase - - + - Specify the [`Rabi`][bloqade.builder.field.Rabi] field of your program. + - Next possible steps to build your program are + addressing the [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude] and [`RabiPhase`][] of the field: + - |_ `...rabi.amplitude`: To address the Rabi amplitude + - |_ `...rabi.phase`: To address the Rabi phase + + Usage Examples + ``` + >>> target_rabi_amplitude = start.rydberg.rabi.amplitude + >>> type(target_rabi_amplitude) + bloqade.builder.field.RabiAmplitude + >>> target_rabi_phase = start.rydberg.rabi.phase + >>> type(target_rabi_phase) + bloqade.builder.field.RabiPhase + ``` """ return Rabi(self) From 631dd0be2756642cfb4f7c9eba6932cfd2553ff7 Mon Sep 17 00:00:00 2001 From: John Long Date: Wed, 27 Sep 2023 17:15:20 -0400 Subject: [PATCH 03/20] WIP drive, field, waveform docstrings --- src/bloqade/builder/coupling.py | 40 +- src/bloqade/builder/drive.py | 18 - src/bloqade/builder/field.py | 187 +++---- src/bloqade/builder/waveform.py | 902 +++++++++++++------------------- 4 files changed, 455 insertions(+), 692 deletions(-) diff --git a/src/bloqade/builder/coupling.py b/src/bloqade/builder/coupling.py index bdcf80ac3..4eb9bbafd 100644 --- a/src/bloqade/builder/coupling.py +++ b/src/bloqade/builder/coupling.py @@ -4,25 +4,33 @@ class LevelCoupling(Builder): @property - def detuning(self) -> Detuning: + def detuning(self) -> Detuning: # field is summation of one or more drives, waveform + spatial modulation = drive """ - - Specify the [`Detuning`][bloqade.builder.field.Detuning] field of your program. - - Next possible steps to build your program are allowing the field to target specific atoms: + - Specify the [`Detuning`][bloqade.builder.field.Detuning] [`Field`][bloqade.builder.Field] of your program. + - A "field" is a summation of one or more "drives", with a drive being the sum of a waveform and spatial modulation. + - You are currently building the spatial modulation component and will be able to specify a waveform + - You can do this by: - |_ `...detuning.uniform`: To address all atoms in the field - |_ `...detuning.location(int)`: To address an atom at a specific location via index - - |_ `...detuning.var(str)`: To address an atom at a specific location via variable name + - |_ `...detuning.var(str)` + - |_ To address an atom at a specific location via variable + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates Usage Examples: ``` >>> prog = start.add_position(([(0,0), (1,2)]).rydberg.detuning - # target all atoms with waveforms specified later + # subsequent specified waveform will target all atoms equally >>> prog.uniform # target individual atoms via index in the list of coordinates you passed in earlier # (This is chainable) >>> prog.location(0).location(1) # target individual atoms via index represented as a variable - # (This is also chainable) - >>> prog.var("atom_1").var("atom_2") + # (This is NOT chainable) + >>> prog.var("atom_1") + # target MULTIPLE atoms via indices represented as a single variable + # where ... is the rest of your program as you cannot perform variable + # assignment afterwards + >>> prog.var("locations")...assign(locations = [0, 1]) ``` """ @@ -31,21 +39,13 @@ def detuning(self) -> Detuning: @property def rabi(self) -> Rabi: """ - - Specify the [`Rabi`][bloqade.builder.field.Rabi] field of your program. + - Specify the complex-valued [`Rabi`][bloqade.builder.field.Rabi] field of your program. + - The Rabi field is composed of a real-valued Amplitude and Phase field - Next possible steps to build your program are - addressing the [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude] and [`RabiPhase`][] of the field: - - |_ `...rabi.amplitude`: To address the Rabi amplitude - - |_ `...rabi.phase`: To address the Rabi phase + creating the [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude] field and [`RabiPhase`][bloqade.builder.field.RabiAmplitude] field of the field: + - |_ `...rabi.amplitude`: To create the Rabi amplitude field + - |_ `...rabi.phase`: To create the Rabi phase field - Usage Examples - ``` - >>> target_rabi_amplitude = start.rydberg.rabi.amplitude - >>> type(target_rabi_amplitude) - bloqade.builder.field.RabiAmplitude - >>> target_rabi_phase = start.rydberg.rabi.phase - >>> type(target_rabi_phase) - bloqade.builder.field.RabiPhase - ``` """ return Rabi(self) diff --git a/src/bloqade/builder/drive.py b/src/bloqade/builder/drive.py index 100fc05d0..b774bca4b 100644 --- a/src/bloqade/builder/drive.py +++ b/src/bloqade/builder/drive.py @@ -11,15 +11,6 @@ def rydberg(self) -> Rydberg: - |_ `...rydberg.detuning`: for Detuning field - In the absence of a field you the value is set to zero by default. - Example Usage: - ``` - >>> target_rydberg_rabi = start.rydberg.rabi - >>> type(target_rydberg_rabi) - bloqade.builder.field.Rabi - - >>> targe_hyperfine_rabi = start.rydberg.detuning - >>> type(target_rydberg_detuning) - bloqade.builder.field.Detuning ``` """ return Rydberg(self) @@ -33,15 +24,6 @@ def hyperfine(self) -> Hyperfine: - |_ `...hyperfine.detuning`: for Detuning field - In the absence of a field you the value is set to zero by default. - Example Usage: - ``` - >>> target_hyperfine_rabi = start.hyperfine.rabi - >>> type(target_rydberg_rabi) - bloqade.builder.field.Rabi - - >>> targe_hyperfine_detuning = start.hyperfine.detuning - >>> type(target_hyperfine_detuning) - bloqade.builder.field.Detuning ``` """ return Hyperfine(self) diff --git a/src/bloqade/builder/field.py b/src/bloqade/builder/field.py index 281b7341a..458f13c0f 100644 --- a/src/bloqade/builder/field.py +++ b/src/bloqade/builder/field.py @@ -6,39 +6,18 @@ class Field(Builder): @property def uniform(self): """ - - Addressing all atom locations for preceeding waveform - - Next-step: - - Possible Next: - - -> `...uniform.linear()` - :: apply linear waveform - - -> `...uniform.constant()` - :: apply constant waveform - - -> `...uniform.ploy()` - :: apply polynomial waveform - - -> `...uniform.apply()` - :: apply pre-constructed waveform - - -> `...uniform.piecewise_linear()` - :: apply piecewise linear waveform - - -> `...uniform.piecewise_constant()` - :: apply piecewise constant waveform - - -> `...uniform.fn()` - :: apply callable as waveform. - - - Examples: - - - Addressing rydberg detuning to all atoms in the system with - 4 sites - - >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> loc = reg.rydberg.detuning.uniform + - Address all atoms as part of defining the spatial modulation component of a drive. + - Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive + - The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field + (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) + - You can do: + - |_ `...uniform.linear(start, stop, duration)` : to apply a linear waveform + - |_ `...uniform.constant(value, duration)` : to apply a constant waveform + - |_ `...uniform.poly([coefficients], duration)` : to apply a polynomial waveform + - |_ `...uniform.apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform + - |_ `...uniform.piecewise_linear([durations], [values])`: to apply a piecewise linear waveform + - |_ `...uniform.piecewise_constant([durations], [values])`: to apply a piecewise constant waveform + - |_ `...uniform.fn(f(t,...))`: to apply a function as a waveform """ from bloqade.builder.spatial import Uniform @@ -48,61 +27,33 @@ def uniform(self): @beartype def location(self, label: int): """ - Addressing one or multiple specific location(s) for preceeding waveform. - - (See [`Location`][bloqade.builder.location.Location] for more details]) - - Args: - label (int): The label of the location to apply the following waveform to. - - Examples: - - - Addressing rydberg detuning to location 1 on a system with 4 sites. - - >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> loc = reg.rydberg.detuning.location(1) - - - Addressing rydberg detuning on both location - 0 and 2 on a system with 4 sites. - - >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> loc = reg.rydberg.detuning.location(1).location(2) - - Note: - label index start with 0, and should be positive. - - - Possible Next : - - -> `...location(int).location(int)` - :: adding location into current list - - -> `...location(int).scale(float)` - :: specify scaling factor to current location - for the preceeding waveform - - - Possible Next : - - -> `...location(int).linear()` - :: apply linear waveform - - -> `...location(int).constant()` - :: apply constant waveform - - -> `...location(int).ploy()` - :: apply polynomial waveform - - -> `...location(int).apply()` - :: apply pre-constructed waveform - - -> `...location(int).piecewise_linear()` - :: apply piecewise linear waveform - - -> `...location(int).piecewise_constant()` - :: apply piecewise constant waveform - - -> `...location(int).fn()` - :: apply callable as waveform. - + - Address a single atom (or multiple via chaining calls, see below) as part of defining the spatial modulation component of a drive. + - Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive + - The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field + (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) + - |_ `...location(int).linear(start, stop, duration)` : to apply a linear waveform + - |_ `...location(int).constant(value, duration)` : to apply a constant waveform + - |_ `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform + - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform + - |_ `...location(int).piecewise_linear([durations], [values])`: to apply a piecewise linear waveform + - |_ `...location(int).piecewise_constant([durations], [values])`: to apply a piecewise constant waveform + - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform + - You can also address multiple atoms by chaining: + - |_ `...location(int).location(int)` + - The waveform you specify after the last `location` in the chain will be applied to all atoms in the chain + - And you can scale any waveform by a multiplicative factor on a specific atom via: + - |_ `...location(int).scale(float)` + - You cannot define a scaling across multiple atoms with one method call! They must be specified atom-by-atom. + + + Usage Example: + ``` + >>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi + # to target a single atom with a waveform + >>> one_location_prog = prog.location(0) + # to target multiple atoms with same waveform + >>> multi_location_prog = prog.location(0).location(2) + ``` """ from bloqade.builder.spatial import Location @@ -112,38 +63,30 @@ def location(self, label: int): @beartype def var(self, name: str): """ - - Addressing atom location associate with given variable for preceeding waveform - - Possible Next : - - -> `...location(int).linear()` - :: apply linear waveform - - -> `...location(int).constant()` - :: apply constant waveform - - -> `...location(int).ploy()` - :: apply polynomial waveform - - -> `...location(int).apply()` - :: apply pre-constructed waveform - - -> `...location(int).piecewise_linear()` - :: apply piecewise linear waveform - - -> `...location(int).piecewise_constant()` - :: apply piecewise constant waveform - - -> `...location(int).fn()` - :: apply callable as waveform. - - - Examples: - - - Addressing rydberg detuning to atom location `myatom` in the system with - 4 sites - - >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> loc = reg.rydberg.detuning.var('myatom') + - Address a single atom (or multiple via assigning a list of values) as part of defining the spatial modulation component of a drive. + - Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive + - The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field + (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) + - |_ `...location(int).linear(start, stop, duration)` : to apply a linear waveform + - |_ `...location(int).constant(value, duration)` : to apply a constant waveform + - |_ `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform + - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform + - |_ `...location(int).piecewise_linear(durations, values)`: to apply a piecewise linear waveform + - |_ `...location(int).piecewise_constant(durations, values)`: to apply a piecewise constant waveform + - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform + + + Usage Example: + ``` + >>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi + >>> one_location_prog = prog.var("a") + # "a" can be assigned in the END of the program during variable assignment + # indicating only a single atom should be targeted OR + # a list of values, indicating a set of atoms should be targeted. + >>> target_one_atom = ...assign(a = 0) + >>> target_multiple_atoms = ...assign(a = [0, 2]) + # Note that `assign` is used, you cannot batch_assign variables used in .var() calls + ``` """ from bloqade.builder.spatial import Var @@ -211,6 +154,10 @@ class Rabi(Builder): @property def amplitude(self) -> "RabiAmplitude": + """ + - Specify the real-valued Rabi Amplitude field. + - Next steps + """ """ - Specify the amplitude of the rabi field. - Next-step: diff --git a/src/bloqade/builder/waveform.py b/src/bloqade/builder/waveform.py index b340b40bc..f95589958 100644 --- a/src/bloqade/builder/waveform.py +++ b/src/bloqade/builder/waveform.py @@ -15,313 +15,229 @@ def linear( self, start: ScalarType, stop: ScalarType, duration: ScalarType ) -> "Linear": """ - Append/assign a linear waveform to the current location. - - Args: - start (ScalarType Union[float, str]): The start value of the waveform - stop (ScalarType Union[float, str]): The stop value of the waveform - duration (ScalarType Union[float, str]): The duration of the waveform - - Examples: - - specify a linear waveform for (spatial) uniform rydberg detuning - from 0 to 1 in 0.5 us. - - >>> node = bloqade.start.rydberg.detuning.uniform - >>> node = node.linear(start=0,stop=1,duration=0.5) - - Possible Next: - - - Possible Next : - - -> `.location(int)` - :: creating new channel to address another location(s) - - - Possible Next : - - -> `.slice()` - :: slice current waveform - - -> `.record(str)` - :: record the value of waveform at current time - - - Possible Next : - - :: Append waveform into current channel - - -> `.linear()` - - -> `.constant()` - - -> `.ploy()` - - -> `.apply()` - - -> `.piecewise_linear()` - - -> `.piecewise_constant()` - - -> `.fn()` - - - Possible Next : - - -> `.rydberg` - :: Create/Switch to new rydberg level coupling channel - - -> `.hyperfine` - :: Create/Switch to new hyperfine level coupling channel - - - Possible Next : - - -> `.assign()` - :: assign varialbe an actual value/number - - -> `.batch_assign()` - :: create batch job with different - sets of values assign to each variable. - - - Possible Next : - - -> `.quera` - :: specify QuEra backend - - -> `.braket` - :: specify QuEra backend - + - Append or assign a linear waveform to the current location(s) + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: + - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you have already specified a waveform previously: + - You will now be appending this waveform to that previous waveform or other options below: + - You can: + - Continue building your waveform via: + - |_ `...linear(start, stop, duration).linear(start, stop, duration)`: to append another linear waveform + - |_ `...linear(start, stop, duration).constant(value, duration)`: to append a constant waveform + - |_ `...linear(start, stop, duration).piecewise_linear()`: to append a piecewise linear waveform + - |_ `...linear(start, stop, duration).piecewise_constant()`: to append a piecewise constant waveform + - |_ `...linear(start, stop, duration).poly([coefficients], duration)`: to append a polynomial waveform + - |_ `...linear(start, stop, duration).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform + - |_ `...linear(start, stop, duration).fn(f(t,...))`: to append a waveform defined by a python function + - Slice a portion of the waveform to be used: + - |_ `...linear(start, stop, duration).slice(start, stop, duration)` + - Save the ending value of your waveform to be reused elsewhere + - |_ `...linear(start, stop, duration).record("you_variable_here")` + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): + -|_ `...linear(start, stop, duration).uniform`: To address all atoms in the field + -|_ `...linear(start, stop, duration).var`: To address an atom at a specific location via index + -|_ `...linear(start, stop, duration).detuning` + - |_ To address an atom at a specific location via variable + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - Assign values to pre-existing variables via: + - |_ `...linear(start, stop, duration).assign(variable_name = value)`: to assign a single value to a variable + - |_ `...linear(start, stop, duration).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable + - |_ `...linear(start, stop, duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - Select the backend you want your program to run on via: + - |_ `...linear(start, stop, duration).braket`: to run on Braket local emulator or QuEra hardware remotely + - |_ `...linear(start, stop, duration).bloqade`: to run on the Bloqade local emulator + - |_ `...linear(start, stop, duration).device`: to specify the backend via string + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...linear(start, stop, duration).parallelize(spacing)` + - Start targeting another level coupling + - |_ `...linear(start, stop, duration).rydberg`: to target the Rydberg level coupling + - |_ `...linear(start, stop, duration).hyperfine`: to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): + - |_ `...linear(start, stop, duration).amplitude`: to target the real-valued Rabi Amplitude field + - |_ `...linear(start, stop, duration).phase`: to target the real-valued Rabi Phase field + - |_ `...linear(start, stop, duration).detuning`: to target the Detuning field + - |_ `...linear(start, stop, duration).rabi`: to target the complex-valued Rabi field + + + Usage Example: + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + # apply a linear waveform that goes from 0 to 1 radians/us in 0.5 us + >>> prog.linear(start=0,stop=1,duration=0.5) """ + return Linear(start, stop, duration, self) @beartype def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": """ - Append/assign a constant waveform to the current location. - - Args: - value (ScalarType Union[float, str]): The value of the waveform - duration (ScalarType Union[float, str]): The duration of the waveform - - Examples: - - specify a constant waveform of value 1 with duration 0.5 - for (spatial) uniform rydberg detuning - - >>> node = bloqade.start.rydberg.detuning.uniform - >>> node = node.constant(value=1,duration=0.5) - - Possible Next: - - - Possible Next : - - -> `.location(int)` - :: creating new channel to address another location(s) - - - Possible Next : - - -> `.slice()` - :: slice current waveform - - -> `.record(str)` - :: record the value of waveform at current time - - - Possible Next : - - :: Append waveform into current channel - - -> `.linear()` - - -> `.constant()` - - -> `.ploy()` - - -> `.apply()` - - -> `.piecewise_linear()` - - -> `.piecewise_constant()` - - -> `.fn()` - - - Possible Next : - - -> `.rydberg` - :: Create/Switch to new rydberg level coupling channel - - -> `.hyperfine` - :: Create/Switch to new hyperfine level coupling channel - - - Possible Next : - - -> `.assign()` - :: assign varialbe an actual value/number - - -> `.batch_assign()` - :: create batch job with different sets - of values assign to each variable. - - - Possible Next : - - -> `.quera` - :: specify QuEra backend - - -> `.braket` - :: specify QuEra backend - + - Append or assign a constant waveform to the current location(s) + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: + - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you have already specified a waveform previously: + - You will now be appending this waveform to that previous waveform or other options below: + - You can: + - Continue building your waveform via: + - |_ `...constant(value, duration).linear(start, stop, duration)`: to append another linear waveform + - |_ `...constant(value, duration).constant(value, duration)`: to append a constant waveform + - |_ `...constant(value, duration).piecewise_linear()`: to append a piecewise linear waveform + - |_ `...constant(value, duration).piecewise_constant()`: to append a piecewise constant waveform + - |_ `...constant(value, duration).poly([coefficients], duration)`: to append a polynomial waveform + - |_ `...constant(value, duration).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform + - |_ `...constant(value, duration).fn(f(t,...))`: to append a waveform defined by a python function + - Slice a portion of the waveform to be used: + - |_ `...constant(value, duration).slice(start, stop, duration)` + - Save the ending value of your waveform to be reused elsewhere + - |_ `...constant(value, duration).record("you_variable_here")` + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): + -|_ `...constant(value, duration).uniform`: To address all atoms in the field + -|_ `...constant(value, duration).var`: To address an atom at a specific location via index + -|_ `...constant(value, duration).detuning` + - |_ To address an atom at a specific location via variable + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - Assign values to pre-existing variables via: + - |_ `...constant(value, duration).assign(variable_name = value)`: to assign a single value to a variable + - |_ `...constant(value, duration).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable + - |_ `...constant(value, duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - Select the backend you want your program to run on via: + - |_ `...constant(value, duration).braket`: to run on Braket local emulator or QuEra hardware remotely + - |_ `...constant(value, duration).bloqade`: to run on the Bloqade local emulator + - |_ `...constant(value, duration).device`: to specify the backend via string + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...constant(start, stop, duration).parallelize(spacing)` + - Start targeting another level coupling + - |_ `...constant(value, duration).rydberg`: to target the Rydberg level coupling + - |_ `...constant(value, duration).hyperfine`: to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): + - |_ `...constant(value, duration).amplitude`: to target the real-valued Rabi Amplitude field + - |_ `...constant(value, duration).phase`: to target the real-valued Rabi Phase field + - |_ `...constant(value, duration).detuning`: to target the Detuning field + - |_ `...constant(value, duration).rabi`: to target the complex-valued Rabi field + + + Usage Example: + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + # apply a constant waveform of 1.9 radians/us for 0.5 us + >>> prog.constant(value=1.9,duration=0.5) """ return Constant(value, duration, self) @beartype def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": """ - Append/assign a waveform with polynomial profile to the current location. - with form: - + - Append or assign a waveform with a polynomial profile to current location(s) + - You pass in a list of coefficients and a duration to this method which obeys the following expression: + ` wv(t) = coeffs[0] + coeffs[1]*t + coeffs[2]*t^2 + ... + coeffs[n]*t^n - - Args: - coeffs (ScalarType Union[float, str]): The coefficients of the polynomial - duration (ScalarType Union[float, str]): The duration of the waveform - - Examples: - - specify a second order polynomial with duration 0.5 - for (spatial) uniform rydberg detuning - - >>> node = bloqade.start.rydberg.detuning.uniform - >>> node = node.poly(coeffs=[1,2,3],duration=0.5) - - Possible Next: - - - Possible Next : - - -> `.location(int)` - :: creating new channel to address another location(s) - - - Possible Next : - - -> `.slice()` - :: slice current waveform - - -> `.record(str)` - :: record the value of waveform at current time - - - Possible Next : - - :: Append waveform into current channel - - -> `.linear()` - - -> `.constant()` - - -> `.ploy()` - - -> `.apply()` - - -> `.piecewise_linear()` - - -> `.piecewise_constant()` - - -> `.fn()` - - - Possible Next : - - -> `.rydberg` - :: Create/Switch to new rydberg level coupling channel - - -> `.hyperfine` - :: Create/Switch to new hyperfine level coupling channel - - - - Possible Next : - - -> `.assign()` - :: assign varialbe an actual value/number - - -> `.batch_assign()` - :: create batch job with different sets - of values assign to each variable. - - - Possible Next : - - -> `.quera` - :: specify QuEra backend - - -> `.braket` - :: specify QuEra backend - + ` + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: + - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you have already specified a waveform previously: + - You will now be appending this waveform to that previous waveform or other options below: + - You can: + - Continue building your waveform via: + - |_ `...poly([coeffs], duration).linear(start, stop, duration)`: to append another linear waveform + - |_ `...poly([coeffs], duration).constant(value, duration)`: to append a constant waveform + - |_ `...poly([coeffs], duration).piecewise_linear()`: to append a piecewise linear waveform + - |_ `...poly([coeffs], duration).piecewise_constant()`: to append a piecewise constant waveform + - |_ `...poly([coeffs], duration).poly([coefficients], duration)`: to append a polynomial waveform + - |_ `...poly([coeffs], duration).apply(waveform)`: to append a pre-defined waveform + - |_ `...poly([coeffs], duration).fn(f(t,...))`: to append a waveform defined by a python function + - Slice a portion of the waveform to be used: + - |_ `...poly([coeffs], duration).slice(start, stop, duration)` + - Save the ending value of your waveform to be reused elsewhere + - |_ `...poly([coeffs], duration).record("you_variable_here")` + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): + -|_ `...poly([coeffs], duration).uniform`: To address all atoms in the field + -|_ `...poly([coeffs], duration).var`: To address an atom at a specific location via index + -|_ `...poly([coeffs], duration).detuning` + - |_ To address an atom at a specific location via variable + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - Assign values to pre-existing variables via: + - |_ `...poly([coeffs], duration).assign(variable_name = value)`: to assign a single value to a variable + - |_ `...poly([coeffs], duration).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable + - |_ `...poly([coeffs], duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - Select the backend you want your program to run on via: + - |_ `...poly([coeffs], duration).braket`: to run on Braket local emulator or QuEra hardware remotely + - |_ `...poly([coeffs], duration).bloqade`: to run on the Bloqade local emulator + - |_ `...poly([coeffs], duration).device`: to specify the backend via string + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...poly([coeffs], duration).parallelize(spacing)` + - Start targeting another level coupling + - |_ `...poly([coeffs], duration).rydberg`: to target the Rydberg level coupling + - |_ `...poly([coeffs], duration).hyperfine`: to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): + - |_ `...poly([coeffs], duration).amplitude`: to target the real-valued Rabi Amplitude field + - |_ `...poly([coeffs], duration).phase`: to target the real-valued Rabi Phase field + - |_ `...poly([coeffs], duration).detuning`: to target the Detuning field + - |_ `...poly([coeffs], duration).rabi`: to target the complex-valued Rabi field + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + >>> coeffs = [-1, 0.5, 1.2] + # resulting polynomial is: + # f(t) = -1 + 0.5*t + 1.2*t^2 with duration of + # 0.5 us + >>> prog.poly(coeffs, duration=0.5) + ``` """ return Poly(coeffs, duration, self) @beartype def apply(self, wf: ir.Waveform) -> "Apply": """ - Apply a pre-defined waveform to the current location. - - Args: - wf (ir.Waveform): the waveform - - Examples: - - apply a pre-defined waveform object to current sequence. - - >>> node = bloqade.start.rydberg.detuning.uniform - >>> wv = bloqade.ir.Linear(0,10,0.5) - >>> node = node.apply(wv) - - Possible Next: - - - Possible Next : - - -> `.location(int)` - :: creating new channel to address another location(s) - - - Possible Next : - - -> `.slice()` - :: slice current waveform - - -> `.record(str)` - :: record the value of waveform at current time - - - Possible Next : - - :: Append waveform into current channel - - -> `.linear()` - - -> `.constant()` - - -> `.ploy()` - - -> `.apply()` - - -> `.piecewise_linear()` - - -> `.piecewise_constant()` - - -> `.fn()` - - - Possible Next : - - -> `.rydberg` - :: Create/Switch to new rydberg level coupling channel - - -> `.hyperfine` - :: Create/Switch to new hyperfine level coupling channel - - - - Possible Next : - - -> `.assign()` - :: assign varialbe an actual value/number - - -> `.batch_assign()` - :: create batch job with different sets - of values assign to each variable. - - - Possible Next : - - -> `.quera` - :: specify QuEra backend - - -> `.braket` - :: specify QuEra backend - + - Apply a [bloqade.ir.control.Waveform] built previously to current location(s) + - You can build waveforms outside of the main program with + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: + - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you have already specified a waveform previously: + - You will now be appending this waveform to that previous waveform or other options below: + - You can: + - Continue building your waveform via: + - |_ `...apply(waveform).linear(start, stop, duration)`: to append another linear waveform + - |_ `...apply(waveform).constant(value, duration)`: to append a constant waveform + - |_ `...apply(waveform).piecewise_linear()`: to append a piecewise linear waveform + - |_ `...apply(waveform).piecewise_constant()`: to append a piecewise constant waveform + - |_ `...apply(waveform).poly([coefficients], duration)`: to append a polynomial waveform + - |_ `...apply(waveform).apply(waveform)`: to append a pre-defined waveform + - |_ `...apply(waveform).fn(f(t,...))`: to append a waveform defined by a python function + - Slice a portion of the waveform to be used: + - |_ `...apply(waveform).slice(start, stop, duration)` + - Save the ending value of your waveform to be reused elsewhere + - |_ `...apply(waveform).record("you_variable_here")` + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): + -|_ `...apply(waveform).uniform`: To address all atoms in the field + -|_ `...apply(waveform).var`: To address an atom at a specific location via index + -|_ `...apply(waveform).detuning` + - |_ To address an atom at a specific location via variable + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - Assign values to pre-existing variables via: + - |_ `...apply(waveform).assign(variable_name = value)`: to assign a single value to a variable + - |_ `...apply(waveform).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable + - |_ `...apply(waveform).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - Select the backend you want your program to run on via: + - |_ `...apply(waveform).braket`: to run on Braket local emulator or QuEra hardware remotely + - |_ `...apply(waveform).bloqade`: to run on the Bloqade local emulator + - |_ `...apply(waveform).device`: to specify the backend via string + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...apply(waveform).parallelize(spacing)` + - Start targeting another level coupling + - |_ `...apply(waveform).rydberg`: to target the Rydberg level coupling + - |_ `...apply(waveform).hyperfine`: to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): + - |_ `...apply(waveform).amplitude`: to target the real-valued Rabi Amplitude field + - |_ `...apply(waveform).phase`: to target the real-valued Rabi Phase field + - |_ `...apply(waveform).detuning`: to target the Detuning field + - |_ `...apply(waveform).rabi`: to target the complex-valued Rabi field + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + # build our waveform independently of the main program + >>> from bloqade import piecewise_linear + >>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3], values=[0.0, 2.0, 2.0, 0.0]) + >>> prog.apply(wf) + ``` """ return Apply(wf, self) @@ -330,85 +246,60 @@ def piecewise_linear( self, durations: List[ScalarType], values: List[ScalarType] ) -> "PiecewiseLinear": """ - Append/assign a piecewise linear waveform to the current location. - The durations should have number of elements = len(values) - 1. - - This function create a waveform by connecting `values[i], values[i+1]` - with linear segments. - - Args: - durations (List[ScalarType]): The durations of each linear segment - values (List[ScalarType]): The values of each linear segment - - Examples: - - specify a piecewise linear of [0,1,1,0] with duration [0.1,3.8,0.1] - for (spatial) uniform rydberg detuning. - - >>> node = bloqade.start.rydberg.detuning.uniform - >>> node = node.piecewise_linear(values=[0,1,1,0],durations=[0.1,3.8,0.1]) - - Note: - ScalarType can be either float or str. - - Possible Next: - - - Possible Next : - - -> `.location(int)` - :: creating new channel to address another location(s) - - - Possible Next : - - -> `.slice()` - :: slice current waveform - - -> `.record(str)` - :: record the value of waveform at current time - - - Possible Next : - - :: Append waveform into current channel - - -> `.linear()` - - -> `.constant()` - - -> `.ploy()` - - -> `.apply()` - - -> `.piecewise_linear()` - - -> `.piecewise_constant()` - - -> `.fn()` - - - Possible Next : - - -> `.rydberg` - :: Create/Switch to new rydberg level coupling channel - - -> `.hyperfine` - :: Create/Switch to new hyperfine level coupling channel - - - - Possible Next : - - -> `.assign()` - :: assign varialbe an actual value/number - - -> `.batch_assign()` - :: create batch job with different sets - of values assign to each variable. - - - Possible Next : - - -> `.quera` - :: specify QuEra backend - - -> `.braket` - :: specify QuEra backend - + - Append or assign a piecewise linear waveform to current location(s), where the waveform is + formed by connecting `values[i], values[i+1]` with linear segments. + - The `durations` argument should have number of elements = len(values) - 1. + - `durations` should be the duration PER section of the waveform, NON-CUMULATIVE. + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: + - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you have already specified a waveform previously: + - You will now be appending this waveform to that previous waveform or other options below: + - You can now: + - Continue building your waveform via: + - |_ `...piecewise_linear(durations, values).linear(start, stop, duration)`: to append another linear waveform + - |_ `...piecewise_linear(durations, values).constant(value, duration)`: to append a constant waveform + - |_ `...piecewise_linear(durations, values).piecewise_linear(durations, values)`: to append a piecewise linear waveform + - |_ `...piecewise_linear(durations, values).piecewise_constant(durations, values)`: to append a piecewise constant waveform + - |_ `...piecewise_linear(durations, values).poly([coefficients], duration)`: to append a polynomial waveform + - |_ `...piecewise_linear(durations, values).apply(waveform)`: to append a pre-defined waveform + - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: to append a waveform defined by a python function + - Slice a portion of the waveform to be used: + - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` + - Save the ending value of your waveform to be reused elsewhere + - |_ `...piecewise_linear(durations, values).record("you_variable_here")` + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): + -|_ `...piecewise_linear(durations, values).uniform`: To address all atoms in the field + -|_ `...piecewise_linear(durations, values).var`: To address an atom at a specific location via index + -|_ `...piecewise_linear(durations, values).detuning` + - |_ To address an atom at a specific location via variable + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - Assign values to pre-existing variables via: + - |_ `...piecewise_linear(durations, values).assign(variable_name = value)`: to assign a single value to a variable + - |_ `...piecewise_linear(durations, values).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable + - |_ `...piecewise_linear(durations, values).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - Select the backend you want your program to run on via: + - |_ `...piecewise_linear(durations, values).braket`: to run on Braket local emulator or QuEra hardware remotely + - |_ `...piecewise_linear(durations, values).bloqade`: to run on the Bloqade local emulator + - |_ `...piecewise_linear(durations, values).device`: to specify the backend via string + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...piecewise_linear(durations, values).parallelize(spacing)` + - Start targeting another level coupling + - |_ `...piecewise_linear(durations, values).rydberg`: to target the Rydberg level coupling + - |_ `...piecewise_linear(durations, values).hyperfine`: to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): + - |_ `...piecewise_linear(durations, values).amplitude`: to target the real-valued Rabi Amplitude field + - |_ `...piecewise_linear(durations, values).phase`: to target the real-valued Rabi Phase field + - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field + - |_ `....rabi`: to target the complex-valued Rabi field + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + # ramp our waveform up to a certain value, hold it + # then ramp down. In this case, we ramp up to 2.0 rad/us in 0.3 us, + # then hold it for 1.5 us before ramping down in 0.3 us back to 0.0 rad/us. + >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[0.0, 2.0, 2.0, 0.0]) + ``` """ return PiecewiseLinear(durations, values, self) @@ -417,179 +308,122 @@ def piecewise_constant( self, durations: List[ScalarType], values: List[ScalarType] ) -> "PiecewiseConstant": """ - Append/assign a piecewise constant waveform to the current location. - The durations should have number of elements = len(values). - - This function create a waveform of piecewise_constant of - `values[i]` with duration `durations[i]`. - - Args: - durations (List[ScalarType]): The durations of each constant segment - values (List[ScalarType]): The values of each constant segment - - Note: - ScalarType can be either float or str. - - Examples: - - specify a piecewise constant of [0.5,1.5] with duration [0.1,3.8] - for (spatial) uniform rydberg detuning. - - >>> node = bloqade.start.rydberg.detuning.uniform - >>> node = node.piecewise_constant(values=[0.5,1.5],durations=[0.1,3.8]) - - Possible Next: - - - Possible Next : - - -> `.location(int)` - :: creating new channel to address another location(s) - - - Possible Next : - - -> `.slice()` - :: slice current waveform - - -> `.record(str)` - :: record the value of waveform at current time - - - Possible Next : - - :: Append waveform into current channel - - -> `.linear()` - - -> `.constant()` - - -> `.ploy()` - - -> `.apply()` - - -> `.piecewise_linear()` - - -> `.piecewise_constant()` - - -> `.fn()` - - - Possible Next : - - -> `.rydberg` - :: Create/Switch to new rydberg level coupling channel - - -> `.hyperfine` - :: Create/Switch to new hyperfine level coupling channel - - - - Possible Next : - - -> `.assign()` - :: assign varialbe an actual value/number - - -> `.batch_assign()` - :: create batch job with different sets - of values assign to each variable. - - - - Possible Next : - - -> `.quera` - :: specify QuEra backend - - -> `.braket` - :: specify QuEra backend - + - Append or assign a piecewise constant waveform to current location(s). + - The `durations` argument should have number of elements = len(values). + - `durations` should be the duration PER section of the waveform, NON-CUMULATIVE. + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: + - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you have already specified a waveform previously: + - You will now be appending this waveform to that previous waveform or other options below: + - You can now: + - Continue building your waveform via: + - |_ `...piecewise_linear(durations, values).linear(start, stop, duration)`: to append another linear waveform + - |_ `...piecewise_linear(durations, values).constant(value, duration)`: to append a constant waveform + - |_ `...piecewise_linear(durations, values).piecewise_linear(durations, values)`: to append a piecewise linear waveform + - |_ `...piecewise_linear(durations, values).piecewise_constant(durations, values)`: to append a piecewise constant waveform + - |_ `...piecewise_linear(durations, values).poly([coefficients], duration)`: to append a polynomial waveform + - |_ `...piecewise_linear(durations, values).apply(waveform)`: to append a pre-defined waveform + - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: to append a waveform defined by a python function + - Slice a portion of the waveform to be used: + - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` + - Save the ending value of your waveform to be reused elsewhere + - |_ `...piecewise_linear(durations, values).record("you_variable_here")` + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): + -|_ `...piecewise_linear(durations, values).uniform`: To address all atoms in the field + -|_ `...piecewise_linear(durations, values).var`: To address an atom at a specific location via index + -|_ `...piecewise_linear(durations, values).detuning` + - |_ To address an atom at a specific location via variable + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - Assign values to pre-existing variables via: + - |_ `...piecewise_linear(durations, values).assign(variable_name = value)`: to assign a single value to a variable + - |_ `...piecewise_linear(durations, values).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable + - |_ `...piecewise_linear(durations, values).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - Select the backend you want your program to run on via: + - |_ `...piecewise_linear(durations, values).braket`: to run on Braket local emulator or QuEra hardware remotely + - |_ `...piecewise_linear(durations, values).bloqade`: to run on the Bloqade local emulator + - |_ `...piecewise_linear(durations, values).device`: to specify the backend via string + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...piecewise_linear(durations, values).parallelize(spacing)` + - Start targeting another level coupling + - |_ `...piecewise_linear(durations, values).rydberg`: to target the Rydberg level coupling + - |_ `...piecewise_linear(durations, values).hyperfine`: to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): + - |_ `...piecewise_linear(durations, values).amplitude`: to target the real-valued Rabi Amplitude field + - |_ `...piecewise_linear(durations, values).phase`: to target the real-valued Rabi Phase field + - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field + - |_ `....rabi`: to target the complex-valued Rabi field + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.rabi.phase.uniform + # create a staircase, we hold 0.0 rad/us for 1.0 us, then + # to 1.0 rad/us for 0.5 us before stopping at 0.8 rad/us for 0.9 us. + >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[1.0, 0.5, 0.9]) """ return PiecewiseConstant(durations, values, self) @beartype def fn(self, fn: Callable, duration: ScalarType) -> "Fn": """ - Append/assign a waveform defined by a python function to the current location. - - This function create a waveform with user-defined - python function `fn(t)` with duration `duration`. - - Args: - fn (Callable): The python function defining the waveform - duration (ScalarType): The durations of each constant segment - - Note: - - ScalarType can be either float or str. - - The python function should take a single argument `t` and return a float. - - - Examples: - - create a cosine waveform with duration 0.5 - for (spatial) uniform rydberg detuning. - - >>> import numpy as np - >>> def my_fn(t): - >>> return np.cos(2*np.pi*t) - >>> node = bloqade.start.rydberg.detuning.uniform - >>> node = node.fn(my_fn,duration=0.5) - - Possible Next: - - - Possible Next : - - -> `.location(int)` - :: creating new channel to address another location(s) - - - Possible Next : - - -> `.slice()` - :: slice current waveform - - -> `.record(str)` - :: record the value of waveform at current time - - -> `.sample()` - :: sample current callable at given time points - - - Possible Next : - - :: Append waveform into current channel - - -> `.linear()` - - -> `.constant()` - - -> `.ploy()` - - -> `.apply()` - - -> `.piecewise_linear()` - - -> `.piecewise_constant()` - - -> `.fn()` - - - Possible Next : - - -> `.rydberg` - :: Create/Switch to new rydberg level coupling channel - - -> `.hyperfine` - :: Create/Switch to new hyperfine level coupling channel - - - - Possible Next : - - -> `.assign()` - :: assign varialbe an actual value/number - - -> `.batch_assign()` - :: create batch job with different sets - of values assign to each variable. - - - - Possible Next : - - -> `.quera` - :: specify QuEra backend - - -> `.braket` - :: specify QuEra backend - + - Append or assign a custom function as a waveform. + - The function must have its first argument be that of time but can also have other arguments which are treated as variables you can assign values to later in the program via `.assign` + or `.batch_assign`. + - The function must return a singular float value + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: + - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you have already specified a waveform previously: + - You will now be appending this waveform to that previous waveform or other options below: + - You can now: + - Continue building your waveform via: + - |_ `...piecewise_linear(durations, values).linear(start, stop, duration)`: to append another linear waveform + - |_ `...piecewise_linear(durations, values).constant(value, duration)`: to append a constant waveform + - |_ `...piecewise_linear(durations, values).piecewise_linear(durations, values)`: to append a piecewise linear waveform + - |_ `...piecewise_linear(durations, values).piecewise_constant(durations, values)`: to append a piecewise constant waveform + - |_ `...piecewise_linear(durations, values).poly([coefficients], duration)`: to append a polynomial waveform + - |_ `...piecewise_linear(durations, values).apply(waveform)`: to append a pre-defined waveform + - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: to append a waveform defined by a python function + - Slice a portion of the waveform to be used: + - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` + - Save the ending value of your waveform to be reused elsewhere + - |_ `...piecewise_linear(durations, values).record("you_variable_here")` + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): + -|_ `...piecewise_linear(durations, values).uniform`: To address all atoms in the field + -|_ `...piecewise_linear(durations, values).var`: To address an atom at a specific location via index + -|_ `...piecewise_linear(durations, values).detuning` + - |_ To address an atom at a specific location via variable + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - Assign values to pre-existing variables via: + - |_ `...piecewise_linear(durations, values).assign(variable_name = value)`: to assign a single value to a variable + - |_ `...piecewise_linear(durations, values).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable + - |_ `...piecewise_linear(durations, values).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - Select the backend you want your program to run on via: + - |_ `...piecewise_linear(durations, values).braket`: to run on Braket local emulator or QuEra hardware remotely + - |_ `...piecewise_linear(durations, values).bloqade`: to run on the Bloqade local emulator + - |_ `...piecewise_linear(durations, values).device`: to specify the backend via string + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...piecewise_linear(durations, values).parallelize(spacing)` + - Start targeting another level coupling + - |_ `...piecewise_linear(durations, values).rydberg`: to target the Rydberg level coupling + - |_ `...piecewise_linear(durations, values).hyperfine`: to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): + - |_ `...piecewise_linear(durations, values).amplitude`: to target the real-valued Rabi Amplitude field + - |_ `...piecewise_linear(durations, values).phase`: to target the real-valued Rabi Phase field + - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field + - |_ `...piecewise_linear(durations, values).rabi`: to target the complex-valued Rabi field + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + # define our custom waveform. It must have one argument + # be time followed by any other number of arguments that can + # be assigned a value later in the program via `.assign` or `.batch_assign` + >>> def custom_waveform_function(t, arg1, arg2): + return arg1*t + arg2 + >>> prog = prog.fn(custom_waveform_function, duration = 0.5) + # assign values + >>> assigned_vars_prog = prog.assign(arg1 = 1.0, arg2 = 2.0) + # or go for batching! + >>> assigned_vars_batch_prog = prog.assign(arg1 = 1.0, arg2 = [1.0, 2.0, 3.0]) """ return Fn(fn, duration, self) From 604f07caab523d8078624ad343bcea6dc35aa6ef Mon Sep 17 00:00:00 2001 From: John Long Date: Wed, 27 Sep 2023 22:04:00 -0400 Subject: [PATCH 04/20] WIP pragmas/device() docstring --- src/bloqade/builder/backend/__init__.py | 11 +++ src/bloqade/builder/pragmas.py | 100 ++++++++++++++++-------- 2 files changed, 78 insertions(+), 33 deletions(-) diff --git a/src/bloqade/builder/backend/__init__.py b/src/bloqade/builder/backend/__init__.py index dbec406a4..5c883f889 100644 --- a/src/bloqade/builder/backend/__init__.py +++ b/src/bloqade/builder/backend/__init__.py @@ -4,6 +4,17 @@ class BackendRoute(QuEraService, BraketService, BloqadeService): + """ + - Specify the backend to run your program on via a string (versus more formal builder syntax) + of specifying the vendor/product first (Bloqade/Braket) nad narrowing it down + - ...device("quera.aquila") versus ...quera.aquila() + - You can pass the following arguments: + - `"braket.aquila"` + - `"braket.local_emulator"` + - `"bloqade.python"` + - `"bloqade.julia"` + + """ def device(self, name: str, *args, **kwargs): if name == "quera.aquila": dev = self.quera.aquila diff --git a/src/bloqade/builder/pragmas.py b/src/bloqade/builder/pragmas.py index d3f9e547a..c382a2e65 100644 --- a/src/bloqade/builder/pragmas.py +++ b/src/bloqade/builder/pragmas.py @@ -18,23 +18,33 @@ def args(self, args_list: List[Union[str, Variable]]) -> "Args": class Assignable: def assign(self, **assignments) -> "Assign": """ - Assign values to variables declared previously in the program. - - Args: - assignments (Dict[str, Union[Number]]): - The assignments, which should be a kwargs - where the key is the variable name and the - value is the value to assign to the variable. - - Examples: - - Assign the value 0.0 to the variable "ival" - and 0.5 to the variable "span_time". - - >>> reg = bloqade.start - ... .add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> seq = reg.rydberg.detuning.uniform - ... .linear(start="ival",stop=1,duration="span_time") - >>> seq = seq.assign(span_time = 0.5, ival = 0.0) + - assign values to variables declared previously in the program. + - This is reserved for variables that should only take single values OR + for spatial modulations that were created with `.var` in which case you can + pass in a list. This is the ONLY circumstance in which multiple values are allowed. + - You can: + - |_ ...assign(assignments).bloqade: select the bloqade local emulator backend + - |_ ...assign(assignments).braket: select braket local emulator or QuEra hardware + - |_ ...assign(assignments).device(specifier_string): select backend by specifying a string + - Assign multiple values to a single variable for a parameter sweep: + - |_ ...assign(assignments).batch_assign(assignments): + - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available + space/qubits on the QPU: + - |_ ...assign(assignments).parallelize(cluster_spacing) + - Defer value assignment of certain variables to runtime: + - |_ ...assign(assignments).args([previously_defined_vars]) + + Usage Examples: + ``` + # define geometry + >>> reg = bloqade.start + ... .add_position([(0,0),(1,1),(2,2),(3,3)]) + # define variables in program + >>> seq = reg.rydberg.detuning.uniform + ... .linear(start="ival",stop=1,duration="span_time") + # assign values to variables + >>> seq = seq.assign(span_time = 0.5, ival = 0.0) + ``` """ from bloqade.builder.assign import Assign @@ -44,6 +54,32 @@ def assign(self, **assignments) -> "Assign": class BatchAssignable: def batch_assign(self, **assignments: ParamType) -> "BatchAssign": + """ + + - Assign multiple values to a single variable to create a parameter sweep. + - Bloqade automatically handles the multiple programs this would generate and treats it as + object with unified results for easy post-processing. + - NOTE: if you assign multiple values to multiple variables in your program, the values must + be of the same length. Bloqade will NOT do a Cartesian product (e.g. if "var1" is assigned + [1,2,3] and "var2" is assigned [4,5,6] then the resulting programs will have assignments [1,4], [2,5], [3,6]). + - Next steps are: + - |_ ...batch_assign(assignments).bloqade: select the bloqade local emulator backend + - |_ ...batch_assign(assignments).braket: select braket local emulator or QuEra hardware + - |_ ...batch_assign(assignments).device(specifier_string): select backend by specifying a string + - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available + space/qubits on the QPU: + - |_ ...batch_assign(assignments).parallelize(cluster_spacing) + - Defer value assignment of certain variables to runtime: + - |_ ...batch_assign(assignments).args([previously_defined_vars]) + + Usage Example: + ``` + >>> reg = start.add_position([(0,0), (0, "atom_distance")]) + >>> prog = reg.rydberg.rabi.amplitude.uniform.constant("value", 5.0) + >>> var_assigned_prog = prog.batch_assign(value = [1.0, 2.0, 3.0], atom_distance = [1.0, 2.0, 3.0]) + ``` + + """ from bloqade.builder.assign import BatchAssign return BatchAssign(parent=self, **assignments) @@ -52,22 +88,20 @@ def batch_assign(self, **assignments: ParamType) -> "BatchAssign": class Parallelizable: def parallelize(self, cluster_spacing: LiteralType) -> "Parallelize": """ - Parallelize the current problem (register & sequnece) to fill entire FOV - with the given cluster spacing. - - Args: - cluster_spacing (Real | Decimal): - the spacing between parallel clusters. - - Examples: - - Parallelize the current problem with cluster spacing 7.2 um. - - >>> prob = ( - bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - .rydberg.detuning.uniform - .linear(start=0,stop=1,duration=1) - ) - >>> prob = prob.parallelize(7.2) + - Parallelize the current problem (register and sequence) by duplicating + the geometry to take advantage of all available space/qubits on hardware. + - The singular argument lets you specify how far apart the clusters should be in micrometers. + - Your next steps are: + |_ `...parallelize(cluster_spacing).bloqade`: select the bloqade local emulator backend + |_ `...parallelize(cluster_spacing).braket`: select braket local emulator or QuEra hardware on the cloud + |_ `...parallelize(cluster_spacing).device(specifier_string)`: select backend by specifying a string + + Usage Example: + ``` + >>> reg = start.add_position((0,0)).rydberg.rabi.uniform.amplitude.constant(1.0, 1.0) + # copy-paste the geometry and waveforms + >>> parallelized_prog = reg.parallelize(24) + ``` """ from bloqade.builder.parallelize import Parallelize From e8afbebd5cba9290008d146427704d8cacd326bb Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 28 Sep 2023 11:01:43 -0400 Subject: [PATCH 05/20] Complete backend docstrings --- src/bloqade/builder/backend/bloqade.py | 20 ++++----- src/bloqade/builder/backend/braket.py | 56 +++++++++----------------- 2 files changed, 27 insertions(+), 49 deletions(-) diff --git a/src/bloqade/builder/backend/bloqade.py b/src/bloqade/builder/backend/bloqade.py index d1d4efc5b..23ad51040 100644 --- a/src/bloqade/builder/backend/bloqade.py +++ b/src/bloqade/builder/backend/bloqade.py @@ -5,14 +5,10 @@ class BloqadeService(Builder): @property def bloqade(self): """ - - Specify bloqade emulator - - Possible Next: - - -> `...bloqade.python` - :: Bloqade python backend - - -> `...bloqade.julia` - :: Bloqade julia backend + - Specify the Bloqade backend + - Possible Next Steps: + - |_ `...bloqade.python()`: target submission to the Bloqade python backend + - |_ `...bloqade.julia()`: (CURRENTLY NOT IMPLEMENTED!)target submission to the Bloqade.jl backend """ return BloqadeDeviceRoute(self) @@ -20,11 +16,9 @@ def bloqade(self): class BloqadeDeviceRoute(Builder): def python(self): """ - - Specify bloqade python backend - - Possible Next: - - -> `...python().run(shots, ...)` - :: Run the current program using bloqade python backend + - Specify the Bloqade Python backend + - Possible Next Steps: + - |_ `...python().run(shots)`: to submit to the python emulator and await results """ return self.parse().bloqade.python() diff --git a/src/bloqade/builder/backend/braket.py b/src/bloqade/builder/backend/braket.py index 14ad2bcac..761380ae8 100644 --- a/src/bloqade/builder/backend/braket.py +++ b/src/bloqade/builder/backend/braket.py @@ -5,14 +5,11 @@ class BraketService(Builder): @property def braket(self): """ - - Specify braket service - - Possible Next: - - -> `...braket.aquila` - :: Aquila QPU, via braket service - - -> `...braket.local_emulator` - :: braket local emulator backend + - Specify the Braket backend + - This allows you to access the AWS Braket local emulator OR go submit things to QuEra hardware on AWS Braket service. + - Possible Next Steps are: + - |_ `...braket.aquila()`: target submission to the QuEra Aquila QPU + - |_ `...braket.local_emulator()`: target submission to the Braket local emulator """ return BraketDeviceRoute(self) @@ -20,39 +17,26 @@ def braket(self): class BraketDeviceRoute(Builder): def aquila(self): """ - Specify QuEra's Aquila QPU - - Return: - BraketHardwareRoutine - - - Possible Next: - - -> `...aquila().submit` - :: submit aync remote job - - -> `...aquila().run` - :: submit job and wait until job finished - and results returned - - -> `...aquila().__callable__` - :: submit job and wait until job finished - and results returned - + - Specify QuEra's Aquila QPU on Braket to submit your program to. + - The number of shots you specify in the subsequent `.run` method will either: + - dictate the number of times your program is run + - dictate the number of times per parameter your program is run if you have a + variable with batch assignments/intend to conduct a parameter sweep + - Possible next steps are: + - |_ `...aquila().run(shots)`: To submit to hardware and WAIT for results (blocking) + - |_ `...aquila().run_async(shots)`: To submit to hardware and immediately allow for other operations to occur """ return self.parse().braket.aquila() def local_emulator(self): """ - Using Braket local emulator - - Return: - BraketLocalEmulatorRoutine - - - - Possible Next: - - -> `...local_emulator().run` - :: run on local emulator + - Specify the Braket local emulator to submit your program to. + - The number of shots you specify in the subsequent `.run` method will either: + - dictate the number of times your program is run + - dictate the number of times per parameter your program is run if you have a + variable with batch assignments/intend to conduct a parameter sweep + - Possible next steps are: + - |_ `...local_emulator().run(shots)`: to submit to the emulator and await results """ return self.parse().braket.local_emulator() From dae3ae743761a2d5307ae3d56f1d53ec8e2fc3fe Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 28 Sep 2023 12:00:06 -0400 Subject: [PATCH 06/20] finish .apply docstring --- src/bloqade/builder/start.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/bloqade/builder/start.py b/src/bloqade/builder/start.py index 13405c378..8fa739b14 100644 --- a/src/bloqade/builder/start.py +++ b/src/bloqade/builder/start.py @@ -12,5 +12,33 @@ class ProgramStart(Drive, Builder): @beartype def apply(self, sequence: Sequence) -> SequenceBuilder: - """apply an existing pulse sequence to the program.""" + """ + - Apply a pre-built sequence to a program. + - This allows you to build a program independent of any geoemetry + and then `apply` the program to said geometry. Or, if you have a + program you would like to try on multiple geometries you can + trivially do so with this. + - From here you can now: + - |_ ...assign(assignments).bloqade: select the bloqade local emulator backend + - |_ ...assign(assignments).braket: select braket local emulator or QuEra hardware + - |_ ...assign(assignments).device(specifier_string): select backend by specifying a string + - Assign multiple values to a single variable for a parameter sweep: + - |_ ...assign(assignments).batch_assign(assignments): + - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available + space/qubits on the QPU: + - |_ ...assign(assignments).parallelize(cluster_spacing) + - Defer value assignment of certain variables to runtime: + - |_ ...assign(assignments).args([previously_defined_vars]) + + Example Usage: + ``` + >>> from numpy import pi + >>> seq = start.rydberg.rabi.amplitude.constant(2.0 * pi, 4.5) + # choose a geometry of interest to apply the program on + >>> from bloqade.atom_arrangement import Chain, Kagome + >>> complete_program = Chain(10).apply(seq) + # you can .apply to as many geometries as you like + >>> another_complete_program = Kagome(3).apply(seq) + ``` + """ return SequenceBuilder(sequence, self) From 599dd20548bb17a176e21a52c6c449e4ca0aa892 Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 28 Sep 2023 17:36:53 -0400 Subject: [PATCH 07/20] Fix issue with pragams docstring --- src/bloqade/builder/pragmas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bloqade/builder/pragmas.py b/src/bloqade/builder/pragmas.py index 6cd505426..47dffd643 100644 --- a/src/bloqade/builder/pragmas.py +++ b/src/bloqade/builder/pragmas.py @@ -59,7 +59,7 @@ def batch_assign( __batch_params: List[Dict[str, ParamType]] = [], **assignments: List[ParamType], ) -> Union["BatchAssign", "ListAssign"]: - """ + """ - Assign multiple values to a single variable to create a parameter sweep. - Bloqade automatically handles the multiple programs this would generate and treats it as From 55cd7add4924b7417c9fc5fdca17750b1b08a427 Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 28 Sep 2023 22:49:46 -0400 Subject: [PATCH 08/20] Reorganize docstring arrangement so usage example takes priority BEFORE recommending next steps --- src/bloqade/builder/backend/bloqade.py | 6 +- src/bloqade/builder/backend/braket.py | 14 +++-- src/bloqade/builder/coupling.py | 31 ++++------- src/bloqade/builder/drive.py | 6 +- src/bloqade/builder/field.py | 77 ++++++++++++++------------ src/bloqade/builder/pragmas.py | 72 +++++++++++++----------- src/bloqade/builder/start.py | 35 ++++++------ src/bloqade/ir/location/list.py | 7 ++- 8 files changed, 134 insertions(+), 114 deletions(-) diff --git a/src/bloqade/builder/backend/bloqade.py b/src/bloqade/builder/backend/bloqade.py index 23ad51040..8b4c67efa 100644 --- a/src/bloqade/builder/backend/bloqade.py +++ b/src/bloqade/builder/backend/bloqade.py @@ -5,7 +5,8 @@ class BloqadeService(Builder): @property def bloqade(self): """ - - Specify the Bloqade backend + Specify the Bloqade backend. + - Possible Next Steps: - |_ `...bloqade.python()`: target submission to the Bloqade python backend - |_ `...bloqade.julia()`: (CURRENTLY NOT IMPLEMENTED!)target submission to the Bloqade.jl backend @@ -16,7 +17,8 @@ def bloqade(self): class BloqadeDeviceRoute(Builder): def python(self): """ - - Specify the Bloqade Python backend + Specify the Bloqade Python backend. + - Possible Next Steps: - |_ `...python().run(shots)`: to submit to the python emulator and await results """ diff --git a/src/bloqade/builder/backend/braket.py b/src/bloqade/builder/backend/braket.py index 761380ae8..e532499f4 100644 --- a/src/bloqade/builder/backend/braket.py +++ b/src/bloqade/builder/backend/braket.py @@ -5,8 +5,8 @@ class BraketService(Builder): @property def braket(self): """ - - Specify the Braket backend - - This allows you to access the AWS Braket local emulator OR go submit things to QuEra hardware on AWS Braket service. + Specify the Braket backend. This allows you to access the AWS Braket local emulator OR go submit things to QuEra hardware on AWS Braket service. + - Possible Next Steps are: - |_ `...braket.aquila()`: target submission to the QuEra Aquila QPU - |_ `...braket.local_emulator()`: target submission to the Braket local emulator @@ -17,11 +17,14 @@ def braket(self): class BraketDeviceRoute(Builder): def aquila(self): """ - - Specify QuEra's Aquila QPU on Braket to submit your program to. - - The number of shots you specify in the subsequent `.run` method will either: + Specify QuEra's Aquila QPU on Braket to submit your program to. + + The number of shots you specify in the subsequent `.run` method will either: - dictate the number of times your program is run - dictate the number of times per parameter your program is run if you have a variable with batch assignments/intend to conduct a parameter sweep + + - Possible next steps are: - |_ `...aquila().run(shots)`: To submit to hardware and WAIT for results (blocking) - |_ `...aquila().run_async(shots)`: To submit to hardware and immediately allow for other operations to occur @@ -30,7 +33,8 @@ def aquila(self): def local_emulator(self): """ - - Specify the Braket local emulator to submit your program to. + Specify the Braket local emulator to submit your program to. + - The number of shots you specify in the subsequent `.run` method will either: - dictate the number of times your program is run - dictate the number of times per parameter your program is run if you have a diff --git a/src/bloqade/builder/coupling.py b/src/bloqade/builder/coupling.py index 4eb9bbafd..6f678dcfb 100644 --- a/src/bloqade/builder/coupling.py +++ b/src/bloqade/builder/coupling.py @@ -6,9 +6,12 @@ class LevelCoupling(Builder): @property def detuning(self) -> Detuning: # field is summation of one or more drives, waveform + spatial modulation = drive """ - - Specify the [`Detuning`][bloqade.builder.field.Detuning] [`Field`][bloqade.builder.Field] of your program. - - A "field" is a summation of one or more "drives", with a drive being the sum of a waveform and spatial modulation. - - You are currently building the spatial modulation component and will be able to specify a waveform + Specify the [`Detuning`][bloqade.builder.field.Detuning] [`Field`][bloqade.builder.Field] of your program. + + A "field" is a summation of one or more "drives", with a drive being the sum of a waveform and spatial modulation. + + You are currently building the spatial modulation component and will be able to specify a waveform. + - You can do this by: - |_ `...detuning.uniform`: To address all atoms in the field - |_ `...detuning.location(int)`: To address an atom at a specific location via index @@ -16,22 +19,6 @@ def detuning(self) -> Detuning: # field is summation of one or more drives, wave - |_ To address an atom at a specific location via variable - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Usage Examples: - ``` - >>> prog = start.add_position(([(0,0), (1,2)]).rydberg.detuning - # subsequent specified waveform will target all atoms equally - >>> prog.uniform - # target individual atoms via index in the list of coordinates you passed in earlier - # (This is chainable) - >>> prog.location(0).location(1) - # target individual atoms via index represented as a variable - # (This is NOT chainable) - >>> prog.var("atom_1") - # target MULTIPLE atoms via indices represented as a single variable - # where ... is the rest of your program as you cannot perform variable - # assignment afterwards - >>> prog.var("locations")...assign(locations = [0, 1]) - ``` """ return Detuning(self) @@ -39,8 +26,10 @@ def detuning(self) -> Detuning: # field is summation of one or more drives, wave @property def rabi(self) -> Rabi: """ - - Specify the complex-valued [`Rabi`][bloqade.builder.field.Rabi] field of your program. - - The Rabi field is composed of a real-valued Amplitude and Phase field + Specify the complex-valued [`Rabi`][bloqade.builder.field.Rabi] field of your program. + + The Rabi field is composed of a real-valued Amplitude and Phase field. + - Next possible steps to build your program are creating the [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude] field and [`RabiPhase`][bloqade.builder.field.RabiAmplitude] field of the field: - |_ `...rabi.amplitude`: To create the Rabi amplitude field diff --git a/src/bloqade/builder/drive.py b/src/bloqade/builder/drive.py index b774bca4b..f50a66885 100644 --- a/src/bloqade/builder/drive.py +++ b/src/bloqade/builder/drive.py @@ -5,7 +5,8 @@ class Drive: @property def rydberg(self) -> Rydberg: """ - - Address the Rydberg level coupling in your program. + Address the Rydberg level coupling in your program. + - Next possible steps to build your program are specifying the [`Rabi`][bloqade.builder.field.Rabi] field or [`Detuning`][bloqade.builder.field.Detuning] field. - |_ `...rydberg.rabi`: for Rabi field - |_ `...rydberg.detuning`: for Detuning field @@ -18,7 +19,8 @@ def rydberg(self) -> Rydberg: @property def hyperfine(self) -> Hyperfine: """ - - Address the Hyperfine level coupling in your program. + Address the Hyperfine level coupling in your program. + - Next possible steps to build your program are specifying the [`Rabi`][bloqade.builder.field.Rabi] field or [`Detuning`][bloqade.builder.field.Detuning] field. - |_ `...hyperfine.rabi`: for Rabi field - |_ `...hyperfine.detuning`: for Detuning field diff --git a/src/bloqade/builder/field.py b/src/bloqade/builder/field.py index 458f13c0f..d56729fe5 100644 --- a/src/bloqade/builder/field.py +++ b/src/bloqade/builder/field.py @@ -6,11 +6,14 @@ class Field(Builder): @property def uniform(self): """ - - Address all atoms as part of defining the spatial modulation component of a drive. - - Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive - - The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field - (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) - - You can do: + Address all atoms as part of defining the spatial modulation component of a drive. + + Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive. + + The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field + (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.). + + - You can now do: - |_ `...uniform.linear(start, stop, duration)` : to apply a linear waveform - |_ `...uniform.constant(value, duration)` : to apply a constant waveform - |_ `...uniform.poly([coefficients], duration)` : to apply a polynomial waveform @@ -27,24 +30,12 @@ def uniform(self): @beartype def location(self, label: int): """ - - Address a single atom (or multiple via chaining calls, see below) as part of defining the spatial modulation component of a drive. - - Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive - - The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field - (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) - - |_ `...location(int).linear(start, stop, duration)` : to apply a linear waveform - - |_ `...location(int).constant(value, duration)` : to apply a constant waveform - - |_ `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...location(int).piecewise_linear([durations], [values])`: to apply a piecewise linear waveform - - |_ `...location(int).piecewise_constant([durations], [values])`: to apply a piecewise constant waveform - - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform - - You can also address multiple atoms by chaining: - - |_ `...location(int).location(int)` - - The waveform you specify after the last `location` in the chain will be applied to all atoms in the chain - - And you can scale any waveform by a multiplicative factor on a specific atom via: - - |_ `...location(int).scale(float)` - - You cannot define a scaling across multiple atoms with one method call! They must be specified atom-by-atom. + Address a single atom (or multiple via chaining calls, see below) as part of defining the spatial modulation component of a drive. + + Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive. + The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field. + (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) Usage Example: ``` @@ -55,6 +46,21 @@ def location(self, label: int): >>> multi_location_prog = prog.location(0).location(2) ``` + - You can now do: + - |_ `...location(int).linear(start, stop, duration)` : to apply a linear waveform + - |_ `...location(int).constant(value, duration)` : to apply a constant waveform + - |_ `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform + - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform + - |_ `...location(int).piecewise_linear([durations], [values])`: to apply a piecewise linear waveform + - |_ `...location(int).piecewise_constant([durations], [values])`: to apply a piecewise constant waveform + - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform + - You can also address multiple atoms by chaining: + - |_ `...location(int).location(int)` + - The waveform you specify after the last `location` in the chain will be applied to all atoms in the chain + - And you can scale any waveform by a multiplicative factor on a specific atom via: + - |_ `...location(int).scale(float)` + - You cannot define a scaling across multiple atoms with one method call! They must be specified atom-by-atom. + """ from bloqade.builder.spatial import Location @@ -63,18 +69,12 @@ def location(self, label: int): @beartype def var(self, name: str): """ - - Address a single atom (or multiple via assigning a list of values) as part of defining the spatial modulation component of a drive. - - Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive - - The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field - (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) - - |_ `...location(int).linear(start, stop, duration)` : to apply a linear waveform - - |_ `...location(int).constant(value, duration)` : to apply a constant waveform - - |_ `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...location(int).piecewise_linear(durations, values)`: to apply a piecewise linear waveform - - |_ `...location(int).piecewise_constant(durations, values)`: to apply a piecewise constant waveform - - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform + Address a single atom (or multiple via assigning a list of values) as part of defining the spatial modulation component of a drive. + Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive. + + The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field + (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) Usage Example: ``` @@ -86,7 +86,16 @@ def var(self, name: str): >>> target_one_atom = ...assign(a = 0) >>> target_multiple_atoms = ...assign(a = [0, 2]) # Note that `assign` is used, you cannot batch_assign variables used in .var() calls - ``` + ``` + + - You can now do: + - |_ `...location(int).linear(start, stop, duration)` : to apply a linear waveform + - |_ `...location(int).constant(value, duration)` : to apply a constant waveform + - |_ `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform + - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform + - |_ `...location(int).piecewise_linear(durations, values)`: to apply a piecewise linear waveform + - |_ `...location(int).piecewise_constant(durations, values)`: to apply a piecewise constant waveform + - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform """ from bloqade.builder.spatial import Var diff --git a/src/bloqade/builder/pragmas.py b/src/bloqade/builder/pragmas.py index 47dffd643..de3c89d61 100644 --- a/src/bloqade/builder/pragmas.py +++ b/src/bloqade/builder/pragmas.py @@ -18,21 +18,11 @@ def args(self, args_list: List[Union[str, Variable]]) -> "Args": class Assignable: def assign(self, **assignments) -> "Assign": """ - - assign values to variables declared previously in the program. - - This is reserved for variables that should only take single values OR + Assign values to variables declared previously in the program. + + This is reserved for variables that should only take single values OR for spatial modulations that were created with `.var` in which case you can pass in a list. This is the ONLY circumstance in which multiple values are allowed. - - You can: - - |_ ...assign(assignments).bloqade: select the bloqade local emulator backend - - |_ ...assign(assignments).braket: select braket local emulator or QuEra hardware - - |_ ...assign(assignments).device(specifier_string): select backend by specifying a string - - Assign multiple values to a single variable for a parameter sweep: - - |_ ...assign(assignments).batch_assign(assignments): - - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available - space/qubits on the QPU: - - |_ ...assign(assignments).parallelize(cluster_spacing) - - Defer value assignment of certain variables to runtime: - - |_ ...assign(assignments).args([previously_defined_vars]) Usage Examples: ``` @@ -46,6 +36,18 @@ def assign(self, **assignments) -> "Assign": >>> seq = seq.assign(span_time = 0.5, ival = 0.0) ``` + - You can now: + - |_ ...assign(assignments).bloqade: select the bloqade local emulator backend + - |_ ...assign(assignments).braket: select braket local emulator or QuEra hardware + - |_ ...assign(assignments).device(specifier_string): select backend by specifying a string + - Assign multiple values to a single variable for a parameter sweep: + - |_ ...assign(assignments).batch_assign(assignments): + - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available + space/qubits on the QPU: + - |_ ...assign(assignments).parallelize(cluster_spacing) + - Defer value assignment of certain variables to runtime: + - |_ ...assign(assignments).args([previously_defined_vars]) + """ from bloqade.builder.assign import Assign @@ -61,12 +63,22 @@ def batch_assign( ) -> Union["BatchAssign", "ListAssign"]: """ - - Assign multiple values to a single variable to create a parameter sweep. - - Bloqade automatically handles the multiple programs this would generate and treats it as - object with unified results for easy post-processing. - - NOTE: if you assign multiple values to multiple variables in your program, the values must - be of the same length. Bloqade will NOT do a Cartesian product (e.g. if "var1" is assigned - [1,2,3] and "var2" is assigned [4,5,6] then the resulting programs will have assignments [1,4], [2,5], [3,6]). + Assign multiple values to a single variable to create a parameter sweep. + + Bloqade automatically handles the multiple programs this would generate and treats it as + object with unified results for easy post-processing. + + NOTE: if you assign multiple values to multiple variables in your program, the values must + be of the same length. Bloqade will NOT do a Cartesian product (e.g. if "var1" is assigned + [1,2,3] and "var2" is assigned [4,5,6] then the resulting programs will have assignments [1,4], [2,5], [3,6]). + + Usage Example: + ``` + >>> reg = start.add_position([(0,0), (0, "atom_distance")]) + >>> prog = reg.rydberg.rabi.amplitude.uniform.constant("value", 5.0) + >>> var_assigned_prog = prog.batch_assign(value = [1.0, 2.0, 3.0], atom_distance = [1.0, 2.0, 3.0]) + ``` + - Next steps are: - |_ ...batch_assign(assignments).bloqade: select the bloqade local emulator backend - |_ ...batch_assign(assignments).braket: select braket local emulator or QuEra hardware @@ -77,13 +89,6 @@ def batch_assign( - Defer value assignment of certain variables to runtime: - |_ ...batch_assign(assignments).args([previously_defined_vars]) - Usage Example: - ``` - >>> reg = start.add_position([(0,0), (0, "atom_distance")]) - >>> prog = reg.rydberg.rabi.amplitude.uniform.constant("value", 5.0) - >>> var_assigned_prog = prog.batch_assign(value = [1.0, 2.0, 3.0], atom_distance = [1.0, 2.0, 3.0]) - ``` - """ from bloqade.builder.assign import BatchAssign, ListAssign @@ -99,13 +104,11 @@ def batch_assign( class Parallelizable: def parallelize(self, cluster_spacing: LiteralType) -> "Parallelize": """ - - Parallelize the current problem (register and sequence) by duplicating + + Parallelize the current problem (register and sequence) by duplicating the geometry to take advantage of all available space/qubits on hardware. - - The singular argument lets you specify how far apart the clusters should be in micrometers. - - Your next steps are: - |_ `...parallelize(cluster_spacing).bloqade`: select the bloqade local emulator backend - |_ `...parallelize(cluster_spacing).braket`: select braket local emulator or QuEra hardware on the cloud - |_ `...parallelize(cluster_spacing).device(specifier_string)`: select backend by specifying a string + + The singular argument lets you specify how far apart the clusters should be in micrometers. Usage Example: ``` @@ -114,6 +117,11 @@ def parallelize(self, cluster_spacing: LiteralType) -> "Parallelize": >>> parallelized_prog = reg.parallelize(24) ``` + - Your next steps are: + |_ `...parallelize(cluster_spacing).bloqade`: select the bloqade local emulator backend + |_ `...parallelize(cluster_spacing).braket`: select braket local emulator or QuEra hardware on the cloud + |_ `...parallelize(cluster_spacing).device(specifier_string)`: select backend by specifying a string + """ from bloqade.builder.parallelize import Parallelize diff --git a/src/bloqade/builder/start.py b/src/bloqade/builder/start.py index 8fa739b14..73af76412 100644 --- a/src/bloqade/builder/start.py +++ b/src/bloqade/builder/start.py @@ -13,22 +13,12 @@ class ProgramStart(Drive, Builder): @beartype def apply(self, sequence: Sequence) -> SequenceBuilder: """ - - Apply a pre-built sequence to a program. - - This allows you to build a program independent of any geoemetry - and then `apply` the program to said geometry. Or, if you have a - program you would like to try on multiple geometries you can - trivially do so with this. - - From here you can now: - - |_ ...assign(assignments).bloqade: select the bloqade local emulator backend - - |_ ...assign(assignments).braket: select braket local emulator or QuEra hardware - - |_ ...assign(assignments).device(specifier_string): select backend by specifying a string - - Assign multiple values to a single variable for a parameter sweep: - - |_ ...assign(assignments).batch_assign(assignments): - - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available - space/qubits on the QPU: - - |_ ...assign(assignments).parallelize(cluster_spacing) - - Defer value assignment of certain variables to runtime: - - |_ ...assign(assignments).args([previously_defined_vars]) + Apply a pre-built sequence to a program. + + This allows you to build a program independent of any geometry + and then `apply` the program to said geometry. Or, if you have a + program you would like to try on multiple geometries you can + trivially do so with this. Example Usage: ``` @@ -40,5 +30,18 @@ def apply(self, sequence: Sequence) -> SequenceBuilder: # you can .apply to as many geometries as you like >>> another_complete_program = Kagome(3).apply(seq) ``` + + - From here you can now do: + - |_ `...assign(assignments).bloqade`: select the bloqade local emulator backend + - |_ `...assign(assignments).braket`: select braket local emulator or QuEra hardware + - |_ `...assign(assignments).device(specifier_string)`: select backend by specifying a string + - Assign multiple values to a single variable for a parameter sweep: + - |_ `...assign(assignments).batch_assign(assignments)`: + - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available + space/qubits on the QPU: + - |_ `...assign(assignments).parallelize(cluster_spacing)` + - Defer value assignment of certain variables to runtime: + - |_ `...assign(assignments).args([previously_defined_vars])` + """ return SequenceBuilder(sequence, self) diff --git a/src/bloqade/ir/location/list.py b/src/bloqade/ir/location/list.py index 9ea24f13f..a866d64e2 100644 --- a/src/bloqade/ir/location/list.py +++ b/src/bloqade/ir/location/list.py @@ -61,12 +61,15 @@ def __iter__(self): start = ListOfLocations() """ -- A Program starting point, alias of empty [`ListOfLocations`][bloqade.ir.location.list.ListOfLocations]. -- Next possible steps to build your program are adding atom positions and addressing level couplings. +A Program starting point, alias of empty [`ListOfLocations`][bloqade.ir.location.list.ListOfLocations]. + +Next possible steps to build your program are adding atom positions and addressing level couplings. + - Specify which level coupling to address with: - |_ `start.rydberg`: for [`Rydberg`][bloqade.builder.coupling.Rydberg] Level coupling - |_ `start.hyperfine`: for [`Hyperfine`][bloqade.builder.coupling.Hyperfine] Level coupling - LOCKOUT: You cannot add atoms to your geometry after specifying level coupling. + - continue/start building your geometry with: - |_ `start.add_position()`: to add atom(s) to current register. It will accept: - A single coordinate, represented as a tuple (e.g. `(5,6)`) with a value that From 6dbee7e817d0af70e5fffcd8c920f506635a1ee7 Mon Sep 17 00:00:00 2001 From: John Long Date: Fri, 29 Sep 2023 00:36:01 -0400 Subject: [PATCH 09/20] get linter to be happy --- src/bloqade/builder/backend/__init__.py | 10 +- src/bloqade/builder/backend/bloqade.py | 8 +- src/bloqade/builder/backend/braket.py | 29 +- src/bloqade/builder/coupling.py | 34 +- src/bloqade/builder/drive.py | 10 +- src/bloqade/builder/field.py | 101 ++-- src/bloqade/builder/pragmas.py | 67 ++- src/bloqade/builder/start.py | 20 +- src/bloqade/builder/waveform.py | 649 ++++++++++++++++-------- src/bloqade/ir/location/list.py | 20 +- src/bloqade/ir/location/transform.py | 186 ++++++- 11 files changed, 792 insertions(+), 342 deletions(-) diff --git a/src/bloqade/builder/backend/__init__.py b/src/bloqade/builder/backend/__init__.py index 5c883f889..dde5c6f8a 100644 --- a/src/bloqade/builder/backend/__init__.py +++ b/src/bloqade/builder/backend/__init__.py @@ -4,17 +4,19 @@ class BackendRoute(QuEraService, BraketService, BloqadeService): - """ - - Specify the backend to run your program on via a string (versus more formal builder syntax) - of specifying the vendor/product first (Bloqade/Braket) nad narrowing it down + """ + - Specify the backend to run your program on via a string + (versus more formal builder syntax) + of specifying the vendor/product first (Bloqade/Braket) and narrowing it down - ...device("quera.aquila") versus ...quera.aquila() - You can pass the following arguments: - `"braket.aquila"` - `"braket.local_emulator"` - `"bloqade.python"` - - `"bloqade.julia"` + - `"bloqade.julia"` """ + def device(self, name: str, *args, **kwargs): if name == "quera.aquila": dev = self.quera.aquila diff --git a/src/bloqade/builder/backend/bloqade.py b/src/bloqade/builder/backend/bloqade.py index 8b4c67efa..509b92343 100644 --- a/src/bloqade/builder/backend/bloqade.py +++ b/src/bloqade/builder/backend/bloqade.py @@ -9,7 +9,8 @@ def bloqade(self): - Possible Next Steps: - |_ `...bloqade.python()`: target submission to the Bloqade python backend - - |_ `...bloqade.julia()`: (CURRENTLY NOT IMPLEMENTED!)target submission to the Bloqade.jl backend + - |_ `...bloqade.julia()`: (CURRENTLY NOT IMPLEMENTED!)target + submission to the Bloqade.jl backend """ return BloqadeDeviceRoute(self) @@ -18,9 +19,10 @@ class BloqadeDeviceRoute(Builder): def python(self): """ Specify the Bloqade Python backend. - + - Possible Next Steps: - - |_ `...python().run(shots)`: to submit to the python emulator and await results + - |_ `...python().run(shots)`: + to submit to the python emulator and await results """ return self.parse().bloqade.python() diff --git a/src/bloqade/builder/backend/braket.py b/src/bloqade/builder/backend/braket.py index e532499f4..5c91e8b1d 100644 --- a/src/bloqade/builder/backend/braket.py +++ b/src/bloqade/builder/backend/braket.py @@ -5,11 +5,13 @@ class BraketService(Builder): @property def braket(self): """ - Specify the Braket backend. This allows you to access the AWS Braket local emulator OR go submit things to QuEra hardware on AWS Braket service. + Specify the Braket backend. This allows you to access the AWS Braket local + emulator OR go submit things to QuEra hardware on AWS Braket service. - Possible Next Steps are: - |_ `...braket.aquila()`: target submission to the QuEra Aquila QPU - - |_ `...braket.local_emulator()`: target submission to the Braket local emulator + - |_ `...braket.local_emulator()`: target submission to the Braket + local emulator """ return BraketDeviceRoute(self) @@ -21,26 +23,31 @@ def aquila(self): The number of shots you specify in the subsequent `.run` method will either: - dictate the number of times your program is run - - dictate the number of times per parameter your program is run if you have a - variable with batch assignments/intend to conduct a parameter sweep + - dictate the number of times per parameter your program is run if + you have a variable with batch assignments/intend to conduct + a parameter sweep + - - Possible next steps are: - - |_ `...aquila().run(shots)`: To submit to hardware and WAIT for results (blocking) - - |_ `...aquila().run_async(shots)`: To submit to hardware and immediately allow for other operations to occur + - |_ `...aquila().run(shots)`: To submit to hardware and WAIT for + results (blocking) + - |_ `...aquila().run_async(shots)`: To submit to hardware and immediately + allow for other operations to occur """ return self.parse().braket.aquila() def local_emulator(self): """ Specify the Braket local emulator to submit your program to. - + - The number of shots you specify in the subsequent `.run` method will either: - dictate the number of times your program is run - - dictate the number of times per parameter your program is run if you have a - variable with batch assignments/intend to conduct a parameter sweep + - dictate the number of times per parameter your program is run if + you have a variable with batch assignments/intend to + conduct a parameter sweep - Possible next steps are: - - |_ `...local_emulator().run(shots)`: to submit to the emulator and await results + - |_ `...local_emulator().run(shots)`: to submit to the emulator + and await results """ return self.parse().braket.local_emulator() diff --git a/src/bloqade/builder/coupling.py b/src/bloqade/builder/coupling.py index 6f678dcfb..4131427b5 100644 --- a/src/bloqade/builder/coupling.py +++ b/src/bloqade/builder/coupling.py @@ -4,21 +4,31 @@ class LevelCoupling(Builder): @property - def detuning(self) -> Detuning: # field is summation of one or more drives, waveform + spatial modulation = drive + def detuning( + self, + ) -> ( + Detuning + ): # field is summation of one or more drives, + # waveform + spatial modulation = drive """ - Specify the [`Detuning`][bloqade.builder.field.Detuning] [`Field`][bloqade.builder.Field] of your program. + Specify the [`Detuning`][bloqade.builder.field.Detuning] + [`Field`][bloqade.builder.Field] of your program. - A "field" is a summation of one or more "drives", with a drive being the sum of a waveform and spatial modulation. - - You are currently building the spatial modulation component and will be able to specify a waveform. + A "field" is a summation of one or more "drives", with a drive being the sum + of a waveform and spatial modulation. + + You are currently building the spatial modulation component and will be + able to specify a waveform. - You can do this by: - |_ `...detuning.uniform`: To address all atoms in the field - - |_ `...detuning.location(int)`: To address an atom at a specific location via index + - |_ `...detuning.location(int)`: To address an atom at a specific + location via index - |_ `...detuning.var(str)` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - + - |_ To address multiple atoms at specific locations by specifying + a single variable and then assigning it a list of coordinates + """ return Detuning(self) @@ -26,12 +36,14 @@ def detuning(self) -> Detuning: # field is summation of one or more drives, wave @property def rabi(self) -> Rabi: """ - Specify the complex-valued [`Rabi`][bloqade.builder.field.Rabi] field of your program. + Specify the complex-valued [`Rabi`][bloqade.builder.field.Rabi] + field of your program. The Rabi field is composed of a real-valued Amplitude and Phase field. - - Next possible steps to build your program are - creating the [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude] field and [`RabiPhase`][bloqade.builder.field.RabiAmplitude] field of the field: + - Next possible steps to build your program are + creating the [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude] field + and [`RabiPhase`][bloqade.builder.field.RabiAmplitude] field of the field: - |_ `...rabi.amplitude`: To create the Rabi amplitude field - |_ `...rabi.phase`: To create the Rabi phase field diff --git a/src/bloqade/builder/drive.py b/src/bloqade/builder/drive.py index f50a66885..e4c9fce88 100644 --- a/src/bloqade/builder/drive.py +++ b/src/bloqade/builder/drive.py @@ -7,7 +7,9 @@ def rydberg(self) -> Rydberg: """ Address the Rydberg level coupling in your program. - - Next possible steps to build your program are specifying the [`Rabi`][bloqade.builder.field.Rabi] field or [`Detuning`][bloqade.builder.field.Detuning] field. + - Next possible steps to build your program are specifying the + [`Rabi`][bloqade.builder.field.Rabi] field or + [`Detuning`][bloqade.builder.field.Detuning] field. - |_ `...rydberg.rabi`: for Rabi field - |_ `...rydberg.detuning`: for Detuning field - In the absence of a field you the value is set to zero by default. @@ -20,8 +22,10 @@ def rydberg(self) -> Rydberg: def hyperfine(self) -> Hyperfine: """ Address the Hyperfine level coupling in your program. - - - Next possible steps to build your program are specifying the [`Rabi`][bloqade.builder.field.Rabi] field or [`Detuning`][bloqade.builder.field.Detuning] field. + + - Next possible steps to build your program are specifying the + [`Rabi`][bloqade.builder.field.Rabi] field or + [`Detuning`][bloqade.builder.field.Detuning] field. - |_ `...hyperfine.rabi`: for Rabi field - |_ `...hyperfine.detuning`: for Detuning field - In the absence of a field you the value is set to zero by default. diff --git a/src/bloqade/builder/field.py b/src/bloqade/builder/field.py index d56729fe5..3b9891446 100644 --- a/src/bloqade/builder/field.py +++ b/src/bloqade/builder/field.py @@ -6,20 +6,27 @@ class Field(Builder): @property def uniform(self): """ - Address all atoms as part of defining the spatial modulation component of a drive. + Address all atoms as part of defining the spatial modulation component + of a drive. - Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive. - - The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field + Next steps to build your program include choosing the waveform that + will be summed with the spatial modulation to create a drive. + + The drive by itself, or the sum of subsequent drives (created by just + chaining the construction of drives) will become the field (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.). - You can now do: - |_ `...uniform.linear(start, stop, duration)` : to apply a linear waveform - |_ `...uniform.constant(value, duration)` : to apply a constant waveform - - |_ `...uniform.poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...uniform.apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...uniform.piecewise_linear([durations], [values])`: to apply a piecewise linear waveform - - |_ `...uniform.piecewise_constant([durations], [values])`: to apply a piecewise constant waveform + - |_ `...uniform.poly([coefficients], duration)` : to apply a + polynomial waveform + - |_ `...uniform.apply(wf:bloqade.ir.Waveform)`: to apply a + pre-defined waveform + - |_ `...uniform.piecewise_linear([durations], [values])`: to apply + a piecewise linear waveform + - |_ `...uniform.piecewise_constant([durations], [values])`: to apply + a piecewise constant waveform - |_ `...uniform.fn(f(t,...))`: to apply a function as a waveform """ @@ -30,11 +37,14 @@ def uniform(self): @beartype def location(self, label: int): """ - Address a single atom (or multiple via chaining calls, see below) as part of defining the spatial modulation component of a drive. + Address a single atom (or multiple via chaining calls, see below) as + part of defining the spatial modulation component of a drive. - Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive. + Next steps to build your program include choosing the waveform that + will be summed with the spatial modulation to create a drive. - The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field. + The drive by itself, or the sum of subsequent drives (created by just + chaining the construction of drives) will become the field. (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) Usage Example: @@ -47,19 +57,28 @@ def location(self, label: int): ``` - You can now do: - - |_ `...location(int).linear(start, stop, duration)` : to apply a linear waveform - - |_ `...location(int).constant(value, duration)` : to apply a constant waveform - - |_ `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...location(int).piecewise_linear([durations], [values])`: to apply a piecewise linear waveform - - |_ `...location(int).piecewise_constant([durations], [values])`: to apply a piecewise constant waveform + - |_ `...location(int).linear(start, stop, duration)` : to apply + a linear waveform + - |_ `...location(int).constant(value, duration)` : to apply + a constant waveform + - |_ `...location(int).poly([coefficients], duration)` : to apply + a polynomial waveform + - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply + a pre-defined waveform + - |_ `...location(int).piecewise_linear([durations], [values])`: to apply + a piecewise linear waveform + - |_ `...location(int).piecewise_constant([durations], [values])`: to apply + a piecewise constant waveform - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform - You can also address multiple atoms by chaining: - - |_ `...location(int).location(int)` - - The waveform you specify after the last `location` in the chain will be applied to all atoms in the chain - - And you can scale any waveform by a multiplicative factor on a specific atom via: + - |_ `...location(int).location(int)` + - The waveform you specify after the last `location` in the chain will + be applied to all atoms in the chain + - And you can scale any waveform by a multiplicative factor on a + specific atom via: - |_ `...location(int).scale(float)` - - You cannot define a scaling across multiple atoms with one method call! They must be specified atom-by-atom. + - You cannot define a scaling across multiple atoms with one method call! + They must be specified atom-by-atom. """ from bloqade.builder.spatial import Location @@ -69,11 +88,14 @@ def location(self, label: int): @beartype def var(self, name: str): """ - Address a single atom (or multiple via assigning a list of values) as part of defining the spatial modulation component of a drive. + Address a single atom (or multiple via assigning a list of values) as + part of defining the spatial modulation component of a drive. - Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive. + Next steps to build your program include choosing the waveform that + will be summed with the spatial modulation to create a drive. - The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field + The drive by itself, or the sum of subsequent drives (created by just + chaining the construction of drives) will become the field (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) Usage Example: @@ -81,20 +103,27 @@ def var(self, name: str): >>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi >>> one_location_prog = prog.var("a") # "a" can be assigned in the END of the program during variable assignment - # indicating only a single atom should be targeted OR - # a list of values, indicating a set of atoms should be targeted. + # indicating only a single atom should be targeted OR + # a list of values, indicating a set of atoms should be targeted. >>> target_one_atom = ...assign(a = 0) >>> target_multiple_atoms = ...assign(a = [0, 2]) - # Note that `assign` is used, you cannot batch_assign variables used in .var() calls - ``` + # Note that `assign` is used, you cannot batch_assign variables used in + # .var() calls + ``` - You can now do: - - |_ `...location(int).linear(start, stop, duration)` : to apply a linear waveform - - |_ `...location(int).constant(value, duration)` : to apply a constant waveform - - |_ `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...location(int).piecewise_linear(durations, values)`: to apply a piecewise linear waveform - - |_ `...location(int).piecewise_constant(durations, values)`: to apply a piecewise constant waveform + - |_ `...location(int).linear(start, stop, duration)` : to apply + a linear waveform + - |_ `...location(int).constant(value, duration)` : to apply + a constant waveform + - |_ `...location(int).poly([coefficients], duration)` : to apply + a polynomial waveform + - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply + a pre-defined waveform + - |_ `...location(int).piecewise_linear(durations, values)`: to + apply a piecewise linear waveform + - |_ `...location(int).piecewise_constant(durations, values)`: to + apply a piecewise constant waveform - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform """ @@ -164,8 +193,8 @@ class Rabi(Builder): @property def amplitude(self) -> "RabiAmplitude": """ - - Specify the real-valued Rabi Amplitude field. - - Next steps + - Specify the real-valued Rabi Amplitude field. + - Next steps """ """ - Specify the amplitude of the rabi field. diff --git a/src/bloqade/builder/pragmas.py b/src/bloqade/builder/pragmas.py index de3c89d61..fc28d93f1 100644 --- a/src/bloqade/builder/pragmas.py +++ b/src/bloqade/builder/pragmas.py @@ -22,7 +22,8 @@ def assign(self, **assignments) -> "Assign": This is reserved for variables that should only take single values OR for spatial modulations that were created with `.var` in which case you can - pass in a list. This is the ONLY circumstance in which multiple values are allowed. + pass in a list. This is the ONLY circumstance in which multiple + values are allowed. Usage Examples: ``` @@ -37,12 +38,16 @@ def assign(self, **assignments) -> "Assign": ``` - You can now: - - |_ ...assign(assignments).bloqade: select the bloqade local emulator backend - - |_ ...assign(assignments).braket: select braket local emulator or QuEra hardware - - |_ ...assign(assignments).device(specifier_string): select backend by specifying a string + - |_ ...assign(assignments).bloqade: select the bloqade local + emulator backend + - |_ ...assign(assignments).braket: select braket local emulator or + QuEra hardware + - |_ ...assign(assignments).device(specifier_string): select backend + by specifying a string - Assign multiple values to a single variable for a parameter sweep: - - |_ ...assign(assignments).batch_assign(assignments): - - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available + - |_ ...assign(assignments).batch_assign(assignments): + - Parallelize the program register, duplicating the geometry and waveform + sequence to take advantage of all available space/qubits on the QPU: - |_ ...assign(assignments).parallelize(cluster_spacing) - Defer value assignment of certain variables to runtime: @@ -55,7 +60,6 @@ def assign(self, **assignments) -> "Assign": class BatchAssignable: - def batch_assign( self, __batch_params: List[Dict[str, ParamType]] = [], @@ -63,27 +67,33 @@ def batch_assign( ) -> Union["BatchAssign", "ListAssign"]: """ - Assign multiple values to a single variable to create a parameter sweep. + Assign multiple values to a single variable to create a parameter sweep. - Bloqade automatically handles the multiple programs this would generate and treats it as - object with unified results for easy post-processing. + Bloqade automatically handles the multiple programs this would generate + and treats it as object with unified results for easy post-processing. - NOTE: if you assign multiple values to multiple variables in your program, the values must - be of the same length. Bloqade will NOT do a Cartesian product (e.g. if "var1" is assigned - [1,2,3] and "var2" is assigned [4,5,6] then the resulting programs will have assignments [1,4], [2,5], [3,6]). + NOTE: if you assign multiple values to multiple variables in your program, + the values must be of the same length. Bloqade will NOT do a Cartesian product + (e.g. if "var1" is assigned [1,2,3] and "var2" is assigned [4,5,6] then the + resulting programs will have assignments [1,4], [2,5], [3,6]). Usage Example: ``` >>> reg = start.add_position([(0,0), (0, "atom_distance")]) >>> prog = reg.rydberg.rabi.amplitude.uniform.constant("value", 5.0) - >>> var_assigned_prog = prog.batch_assign(value = [1.0, 2.0, 3.0], atom_distance = [1.0, 2.0, 3.0]) + >>> var_assigned_prog = prog.batch_assign(value = [1.0, 2.0, 3.0], + atom_distance = [1.0, 2.0, 3.0]) ``` - Next steps are: - - |_ ...batch_assign(assignments).bloqade: select the bloqade local emulator backend - - |_ ...batch_assign(assignments).braket: select braket local emulator or QuEra hardware - - |_ ...batch_assign(assignments).device(specifier_string): select backend by specifying a string - - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available + - |_ ...batch_assign(assignments).bloqade: select the bloqade + local emulator backend + - |_ ...batch_assign(assignments).braket: select braket local + emulator or QuEra hardware + - |_ ...batch_assign(assignments).device(specifier_string): select + backend by specifying a string + - Parallelize the program register, duplicating the geometry and waveform + sequence to take advantage of all available space/qubits on the QPU: - |_ ...batch_assign(assignments).parallelize(cluster_spacing) - Defer value assignment of certain variables to runtime: @@ -104,23 +114,28 @@ def batch_assign( class Parallelizable: def parallelize(self, cluster_spacing: LiteralType) -> "Parallelize": """ - + Parallelize the current problem (register and sequence) by duplicating the geometry to take advantage of all available space/qubits on hardware. - The singular argument lets you specify how far apart the clusters should be in micrometers. + The singular argument lets you specify how far apart the clusters + should be in micrometers. Usage Example: ``` - >>> reg = start.add_position((0,0)).rydberg.rabi.uniform.amplitude.constant(1.0, 1.0) - # copy-paste the geometry and waveforms + >>> reg = start.add_position((0,0)).rydberg.rabi.uniform.amplitude + .constant(1.0, 1.0) + # copy-paste the geometry and waveforms >>> parallelized_prog = reg.parallelize(24) ``` - - Your next steps are: - |_ `...parallelize(cluster_spacing).bloqade`: select the bloqade local emulator backend - |_ `...parallelize(cluster_spacing).braket`: select braket local emulator or QuEra hardware on the cloud - |_ `...parallelize(cluster_spacing).device(specifier_string)`: select backend by specifying a string + - Your next steps are: + |_ `...parallelize(cluster_spacing).bloqade`: select the bloqade + local emulator backend + |_ `...parallelize(cluster_spacing).braket`: select braket + local emulator or QuEra hardware on the cloud + |_ `...parallelize(cluster_spacing).device(specifier_string)`: select + backend by specifying a string """ from bloqade.builder.parallelize import Parallelize diff --git a/src/bloqade/builder/start.py b/src/bloqade/builder/start.py index 73af76412..b314f9c51 100644 --- a/src/bloqade/builder/start.py +++ b/src/bloqade/builder/start.py @@ -14,10 +14,10 @@ class ProgramStart(Drive, Builder): def apply(self, sequence: Sequence) -> SequenceBuilder: """ Apply a pre-built sequence to a program. - + This allows you to build a program independent of any geometry - and then `apply` the program to said geometry. Or, if you have a - program you would like to try on multiple geometries you can + and then `apply` the program to said geometry. Or, if you have a + program you would like to try on multiple geometries you can trivially do so with this. Example Usage: @@ -32,12 +32,16 @@ def apply(self, sequence: Sequence) -> SequenceBuilder: ``` - From here you can now do: - - |_ `...assign(assignments).bloqade`: select the bloqade local emulator backend - - |_ `...assign(assignments).braket`: select braket local emulator or QuEra hardware - - |_ `...assign(assignments).device(specifier_string)`: select backend by specifying a string + - |_ `...assign(assignments).bloqade`: select the bloqade + local emulator backend + - |_ `...assign(assignments).braket`: select braket + local emulator or QuEra hardware + - |_ `...assign(assignments).device(specifier_string)`: select + backend by specifying a string - Assign multiple values to a single variable for a parameter sweep: - - |_ `...assign(assignments).batch_assign(assignments)`: - - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available + - |_ `...assign(assignments).batch_assign(assignments)`: + - Parallelize the program register, duplicating the geometry and waveform + sequence to take advantage of all available space/qubits on the QPU: - |_ `...assign(assignments).parallelize(cluster_spacing)` - Defer value assignment of certain variables to runtime: diff --git a/src/bloqade/builder/waveform.py b/src/bloqade/builder/waveform.py index f95589958..a9237de47 100644 --- a/src/bloqade/builder/waveform.py +++ b/src/bloqade/builder/waveform.py @@ -16,49 +16,78 @@ def linear( ) -> "Linear": """ - Append or assign a linear waveform to the current location(s) - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you specified a spatial modulation (e.g. `uniform`, `location`, + `var`) previously without a waveform: + - You will now complete the construction of a "drive", one or a sum of + drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform or other options below: + - You will now be appending this waveform to that previous waveform + or other options below: - You can: - Continue building your waveform via: - - |_ `...linear(start, stop, duration).linear(start, stop, duration)`: to append another linear waveform - - |_ `...linear(start, stop, duration).constant(value, duration)`: to append a constant waveform - - |_ `...linear(start, stop, duration).piecewise_linear()`: to append a piecewise linear waveform - - |_ `...linear(start, stop, duration).piecewise_constant()`: to append a piecewise constant waveform - - |_ `...linear(start, stop, duration).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...linear(start, stop, duration).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...linear(start, stop, duration).fn(f(t,...))`: to append a waveform defined by a python function + - |_ `...linear(start, stop, duration).linear(start, stop, duration)`: + to append another linear waveform + - |_ `...linear(start, stop, duration).constant(value, duration)`: + to append a constant waveform + - |_ `...linear(start, stop, duration).piecewise_linear()`: + to append a piecewise linear waveform + - |_ `...linear(start, stop, duration).piecewise_constant()`: + to append a piecewise constant waveform + - |_ `...linear(start, stop, duration).poly([coefficients], duration)`: + to append a polynomial waveform + - |_ `...linear(start, stop, duration).apply(wf:bloqade.ir.Waveform)`: + to append a pre-defined waveform + - |_ `...linear(start, stop, duration).fn(f(t,...))`: + to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...linear(start, stop, duration).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...linear(start, stop, duration).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...linear(start, stop, duration).uniform`: To address all atoms in the field - -|_ `...linear(start, stop, duration).var`: To address an atom at a specific location via index - -|_ `...linear(start, stop, duration).detuning` + - Begin constructing another drive by starting a new spatial modulation + (this drive will be summed to the one you just created): + - |_ `...linear(start, stop, duration).uniform`: + To address all atoms in the field + - |_ `...linear(start, stop, duration).var`: + To address an atom at a specific location via index + - |_ `...linear(start, stop, duration).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - |_ To address multiple atoms at specific locations by specifying + a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...linear(start, stop, duration).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...linear(start, stop, duration).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...linear(start, stop, duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - |_ `...linear(start, stop, duration).assign(variable_name = value)`: + to assign a single value to a variable + - |_ `...linear(start, stop, duration) + .batch_assign(variable_name = [value1, ...])`: + to assign multiple values to a variable + - |_ `...linear(start, stop, duration).args(["previously_defined_var"])`: + to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...linear(start, stop, duration).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...linear(start, stop, duration).bloqade`: to run on the Bloqade local emulator - - |_ `...linear(start, stop, duration).device`: to specify the backend via string - - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...linear(start, stop, duration).braket`: + to run on Braket local emulator or QuEra hardware remotely + - |_ `...linear(start, stop, duration).bloqade`: + to run on the Bloqade local emulator + - |_ `...linear(start, stop, duration).device`: + to specify the backend via string + - Choose to parallelize your atom geometry, + duplicating it to fill the whole space: - |_ `...linear(start, stop, duration).parallelize(spacing)` - Start targeting another level coupling - - |_ `...linear(start, stop, duration).rydberg`: to target the Rydberg level coupling - - |_ `...linear(start, stop, duration).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...linear(start, stop, duration).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...linear(start, stop, duration).phase`: to target the real-valued Rabi Phase field - - |_ `...linear(start, stop, duration).detuning`: to target the Detuning field - - |_ `...linear(start, stop, duration).rabi`: to target the complex-valued Rabi field - - + - |_ `...linear(start, stop, duration).rydberg`: + to target the Rydberg level coupling + - |_ `...linear(start, stop, duration).hyperfine`: + to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling + (previously selected as `rydberg` or `hyperfine`): + - |_ `...linear(start, stop, duration).amplitude`: + to target the real-valued Rabi Amplitude field + - |_ `...linear(start, stop, duration).phase`: + to target the real-valued Rabi Phase field + - |_ `...linear(start, stop, duration).detuning`: + to target the Detuning field + - |_ `...linear(start, stop, duration).rabi`: + to target the complex-valued Rabi field + + Usage Example: >>> prog = start.add_position((0,0)).rydberg.detuning.uniform # apply a linear waveform that goes from 0 to 1 radians/us in 0.5 us @@ -71,49 +100,79 @@ def linear( def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": """ - Append or assign a constant waveform to the current location(s) - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you specified a spatial modulation (e.g. `uniform`, `location`, + `var`) previously without a waveform: + - You will now complete the construction of a "drive", one or + a sum of drives creating a "field" (e.g. Real-valued Rabi + Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform or other options below: + - You will now be appending this waveform to that previous + waveform or other options below: - You can: - Continue building your waveform via: - - |_ `...constant(value, duration).linear(start, stop, duration)`: to append another linear waveform - - |_ `...constant(value, duration).constant(value, duration)`: to append a constant waveform - - |_ `...constant(value, duration).piecewise_linear()`: to append a piecewise linear waveform - - |_ `...constant(value, duration).piecewise_constant()`: to append a piecewise constant waveform - - |_ `...constant(value, duration).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...constant(value, duration).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...constant(value, duration).fn(f(t,...))`: to append a waveform defined by a python function + - |_ `...constant(value, duration).linear(start, stop, duration)`: + to append another linear waveform + - |_ `...constant(value, duration).constant(value, duration)`: + to append a constant waveform + - |_ `...constant(value, duration).piecewise_linear()`: + to append a piecewise linear waveform + - |_ `...constant(value, duration).piecewise_constant()`: + to append a piecewise constant waveform + - |_ `...constant(value, duration).poly([coefficients], duration)`: + to append a polynomial waveform + - |_ `...constant(value, duration).apply(wf:bloqade.ir.Waveform)`: + to append a pre-defined waveform + - |_ `...constant(value, duration).fn(f(t,...))`: + to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...constant(value, duration).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...constant(value, duration).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...constant(value, duration).uniform`: To address all atoms in the field - -|_ `...constant(value, duration).var`: To address an atom at a specific location via index + - Begin constructing another drive by starting a new spatial modulation + (this drive will be summed to the one you just created): + -|_ `...constant(value, duration).uniform`: + To address all atoms in the field + -|_ `...constant(value, duration).var`: + To address an atom at a specific location via index -|_ `...constant(value, duration).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - |_ To address multiple atoms at specific locations by specifying + a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...constant(value, duration).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...constant(value, duration).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...constant(value, duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - |_ `...constant(value, duration).assign(variable_name = value)`: + to assign a single value to a variable + - |_ `...constant(value, duration) + .batch_assign(variable_name = [value1, ...])`: + to assign multiple values to a variable + - |_ `...constant(value, duration).args(["previously_defined_var"])`: + to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...constant(value, duration).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...constant(value, duration).bloqade`: to run on the Bloqade local emulator - - |_ `...constant(value, duration).device`: to specify the backend via string - - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...constant(value, duration).braket`: + to run on Braket local emulator or QuEra hardware remotely + - |_ `...constant(value, duration).bloqade`: + to run on the Bloqade local emulator + - |_ `...constant(value, duration).device`: + to specify the backend via string + - Choose to parallelize your atom geometry, + duplicating it to fill the whole space: - |_ `...constant(start, stop, duration).parallelize(spacing)` - Start targeting another level coupling - - |_ `...constant(value, duration).rydberg`: to target the Rydberg level coupling - - |_ `...constant(value, duration).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...constant(value, duration).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...constant(value, duration).phase`: to target the real-valued Rabi Phase field - - |_ `...constant(value, duration).detuning`: to target the Detuning field - - |_ `...constant(value, duration).rabi`: to target the complex-valued Rabi field - - + - |_ `...constant(value, duration).rydberg`: + to target the Rydberg level coupling + - |_ `...constant(value, duration).hyperfine`: + to target the Hyperfine level coupling + - Start targeting other fields within your current + level coupling (previously selected as `rydberg` or `hyperfine`): + - |_ `...constant(value, duration).amplitude`: + to target the real-valued Rabi Amplitude field + - |_ `...constant(value, duration).phase`: + to target the real-valued Rabi Phase field + - |_ `...constant(value, duration).detuning`: + to target the Detuning field + - |_ `...constant(value, duration).rabi`: + to target the complex-valued Rabi field + + Usage Example: >>> prog = start.add_position((0,0)).rydberg.detuning.uniform # apply a constant waveform of 1.9 radians/us for 0.5 us @@ -125,58 +184,89 @@ def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": """ - Append or assign a waveform with a polynomial profile to current location(s) - - You pass in a list of coefficients and a duration to this method which obeys the following expression: + - You pass in a list of coefficients and a duration to this method + which obeys the following expression: ` wv(t) = coeffs[0] + coeffs[1]*t + coeffs[2]*t^2 + ... + coeffs[n]*t^n ` - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) + previously without a waveform: + - You will now complete the construction of a "drive", one or a sum of + drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform or other options below: + - You will now be appending this waveform to that previous waveform + or other options below: - You can: - Continue building your waveform via: - - |_ `...poly([coeffs], duration).linear(start, stop, duration)`: to append another linear waveform - - |_ `...poly([coeffs], duration).constant(value, duration)`: to append a constant waveform - - |_ `...poly([coeffs], duration).piecewise_linear()`: to append a piecewise linear waveform - - |_ `...poly([coeffs], duration).piecewise_constant()`: to append a piecewise constant waveform - - |_ `...poly([coeffs], duration).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...poly([coeffs], duration).apply(waveform)`: to append a pre-defined waveform - - |_ `...poly([coeffs], duration).fn(f(t,...))`: to append a waveform defined by a python function + - |_ `...poly([coeffs], duration).linear(start, stop, duration)`: + to append another linear waveform + - |_ `...poly([coeffs], duration).constant(value, duration)`: + to append a constant waveform + - |_ `...poly([coeffs], duration).piecewise_linear()`: + to append a piecewise linear waveform + - |_ `...poly([coeffs], duration).piecewise_constant()`: + to append a piecewise constant waveform + - |_ `...poly([coeffs], duration).poly([coefficients], duration)`: + to append a polynomial waveform + - |_ `...poly([coeffs], duration).apply(waveform)`: + to append a pre-defined waveform + - |_ `...poly([coeffs], duration).fn(f(t,...))`: + to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...poly([coeffs], duration).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...poly([coeffs], duration).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...poly([coeffs], duration).uniform`: To address all atoms in the field - -|_ `...poly([coeffs], duration).var`: To address an atom at a specific location via index + - Begin constructing another drive by starting a new spatial modulation + (this drive will be summed to the one you just created): + -|_ `...poly([coeffs], duration).uniform`: + To address all atoms in the field + -|_ `...poly([coeffs], duration).var`: + To address an atom at a specific location via index -|_ `...poly([coeffs], duration).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - |_ To address multiple atoms at specific locations by + specifying a single variable and then assigning + it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...poly([coeffs], duration).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...poly([coeffs], duration).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...poly([coeffs], duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - |_ `...poly([coeffs], duration).assign(variable_name = value)`: + to assign a single value to a variable + - |_ `...poly([coeffs], duration) + .batch_assign(variable_name = [value1, ...])`: + to assign multiple values to a variable + - |_ `...poly([coeffs], duration).args(["previously_defined_var"])`: + to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...poly([coeffs], duration).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...poly([coeffs], duration).bloqade`: to run on the Bloqade local emulator - - |_ `...poly([coeffs], duration).device`: to specify the backend via string - - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...poly([coeffs], duration).braket`: + to run on Braket local emulator or QuEra hardware remotely + - |_ `...poly([coeffs], duration).bloqade`: + to run on the Bloqade local emulator + - |_ `...poly([coeffs], duration).device`: + to specify the backend via string + - Choose to parallelize your atom geometry, + duplicating it to fill the whole space: - |_ `...poly([coeffs], duration).parallelize(spacing)` - Start targeting another level coupling - - |_ `...poly([coeffs], duration).rydberg`: to target the Rydberg level coupling - - |_ `...poly([coeffs], duration).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...poly([coeffs], duration).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...poly([coeffs], duration).phase`: to target the real-valued Rabi Phase field - - |_ `...poly([coeffs], duration).detuning`: to target the Detuning field - - |_ `...poly([coeffs], duration).rabi`: to target the complex-valued Rabi field + - |_ `...poly([coeffs], duration).rydberg`: + to target the Rydberg level coupling + - |_ `...poly([coeffs], duration).hyperfine`: + to target the Hyperfine level coupling + - Start targeting other fields within your current level + coupling (previously selected as `rydberg` or `hyperfine`): + - |_ `...poly([coeffs], duration).amplitude`: + to target the real-valued Rabi Amplitude field + - |_ `...poly([coeffs], duration).phase`: + to target the real-valued Rabi Phase field + - |_ `...poly([coeffs], duration).detuning`: + to target the Detuning field + - |_ `...poly([coeffs], duration).rabi`: + to target the complex-valued Rabi field Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.detuning.uniform >>> coeffs = [-1, 0.5, 1.2] # resulting polynomial is: - # f(t) = -1 + 0.5*t + 1.2*t^2 with duration of + # f(t) = -1 + 0.5*t + 1.2*t^2 with duration of # 0.5 us >>> prog.poly(coeffs, duration=0.5) ``` @@ -186,56 +276,84 @@ def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": @beartype def apply(self, wf: ir.Waveform) -> "Apply": """ - - Apply a [bloqade.ir.control.Waveform] built previously to current location(s) - - You can build waveforms outside of the main program with - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - Apply a [bloqade.ir.control.Waveform] built previously to + current location(s) + - You can build waveforms outside of the main program with + - If you specified a spatial modulation (e.g. `uniform`, + `location`, `var`) previously without a waveform: + - You will now complete the construction of a "drive", + one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform or other options below: + - You will now be appending this waveform to that previous + waveform or other options below: - You can: - Continue building your waveform via: - - |_ `...apply(waveform).linear(start, stop, duration)`: to append another linear waveform - - |_ `...apply(waveform).constant(value, duration)`: to append a constant waveform - - |_ `...apply(waveform).piecewise_linear()`: to append a piecewise linear waveform - - |_ `...apply(waveform).piecewise_constant()`: to append a piecewise constant waveform - - |_ `...apply(waveform).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...apply(waveform).apply(waveform)`: to append a pre-defined waveform - - |_ `...apply(waveform).fn(f(t,...))`: to append a waveform defined by a python function + - |_ `...apply(waveform).linear(start, stop, duration)`: + to append another linear waveform + - |_ `...apply(waveform).constant(value, duration)`: + to append a constant waveform + - |_ `...apply(waveform).piecewise_linear()`: + to append a piecewise linear waveform + - |_ `...apply(waveform).piecewise_constant()`: + to append a piecewise constant waveform + - |_ `...apply(waveform).poly([coefficients], duration)`: + to append a polynomial waveform + - |_ `...apply(waveform).apply(waveform)`: + to append a pre-defined waveform + - |_ `...apply(waveform).fn(f(t,...))`: + to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...apply(waveform).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...apply(waveform).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): + - Begin constructing another drive by starting a new spatial modulation + (this drive will be summed to the one you just created): -|_ `...apply(waveform).uniform`: To address all atoms in the field - -|_ `...apply(waveform).var`: To address an atom at a specific location via index + -|_ `...apply(waveform).var`: + To address an atom at a specific location via index -|_ `...apply(waveform).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - |_ To address multiple atoms at specific locations by specifying a + single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...apply(waveform).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...apply(waveform).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...apply(waveform).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - |_ `...apply(waveform).assign(variable_name = value)`: + to assign a single value to a variable + - |_ `...apply(waveform).batch_assign(variable_name = [value1, ...])`: + to assign multiple values to a variable + - |_ `...apply(waveform).args(["previously_defined_var"])`: + to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...apply(waveform).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...apply(waveform).bloqade`: to run on the Bloqade local emulator - - |_ `...apply(waveform).device`: to specify the backend via string - - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...apply(waveform).braket`: + to run on Braket local emulator or QuEra hardware remotely + - |_ `...apply(waveform).bloqade`: + to run on the Bloqade local emulator + - |_ `...apply(waveform).device`: + to specify the backend via string + - Choose to parallelize your atom geometry, + duplicating it to fill the whole space: - |_ `...apply(waveform).parallelize(spacing)` - Start targeting another level coupling - |_ `...apply(waveform).rydberg`: to target the Rydberg level coupling - |_ `...apply(waveform).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...apply(waveform).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...apply(waveform).phase`: to target the real-valued Rabi Phase field - - |_ `...apply(waveform).detuning`: to target the Detuning field - - |_ `...apply(waveform).rabi`: to target the complex-valued Rabi field + - Start targeting other fields within your current level coupling + (previously selected as `rydberg` or `hyperfine`): + - |_ `...apply(waveform).amplitude`: + to target the real-valued Rabi Amplitude field + - |_ `...apply(waveform).phase`: + to target the real-valued Rabi Phase field + - |_ `...apply(waveform).detuning`: + to target the Detuning field + - |_ `...apply(waveform).rabi`: + to target the complex-valued Rabi field Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.detuning.uniform # build our waveform independently of the main program >>> from bloqade import piecewise_linear - >>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3], values=[0.0, 2.0, 2.0, 0.0]) + >>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3], + values=[0.0, 2.0, 2.0, 0.0]) >>> prog.apply(wf) ``` """ @@ -246,60 +364,98 @@ def piecewise_linear( self, durations: List[ScalarType], values: List[ScalarType] ) -> "PiecewiseLinear": """ - - Append or assign a piecewise linear waveform to current location(s), where the waveform is - formed by connecting `values[i], values[i+1]` with linear segments. + - Append or assign a piecewise linear waveform to current location(s), + where the waveform is formed by connecting `values[i], values[i+1]` + with linear segments. - The `durations` argument should have number of elements = len(values) - 1. - - `durations` should be the duration PER section of the waveform, NON-CUMULATIVE. - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - `durations` should be the duration + PER section of the waveform, NON-CUMULATIVE. + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) + previously without a waveform: + - You will now complete the construction of a "drive", + one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform or other options below: + - You will now be appending this waveform to that previous + waveform or other options below: - You can now: - Continue building your waveform via: - - |_ `...piecewise_linear(durations, values).linear(start, stop, duration)`: to append another linear waveform - - |_ `...piecewise_linear(durations, values).constant(value, duration)`: to append a constant waveform - - |_ `...piecewise_linear(durations, values).piecewise_linear(durations, values)`: to append a piecewise linear waveform - - |_ `...piecewise_linear(durations, values).piecewise_constant(durations, values)`: to append a piecewise constant waveform - - |_ `...piecewise_linear(durations, values).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...piecewise_linear(durations, values).apply(waveform)`: to append a pre-defined waveform - - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: to append a waveform defined by a python function + - |_ `...piecewise_linear(durations, values) + .linear(start, stop, duration)`: + to append another linear waveform + - |_ `...piecewise_linear(durations, values).constant(value, duration)`: + to append a constant waveform + - |_ `...piecewise_linear(durations, values) + .piecewise_linear(durations, values)`: + to append a piecewise linear waveform + - |_ `...piecewise_linear(durations, values) + .piecewise_constant(durations, values)`: + to append a piecewise constant waveform + - |_ `...piecewise_linear(durations, values) + .poly([coefficients], duration)`: to append a polynomial waveform + - |_ `...piecewise_linear(durations, values).apply(waveform)`: + to append a pre-defined waveform + - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: + to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...piecewise_linear(durations, values).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_linear(durations, values).uniform`: To address all atoms in the field - -|_ `...piecewise_linear(durations, values).var`: To address an atom at a specific location via index + - Begin constructing another drive by starting a new spatial modulation + (this drive will be summed to the one you just created): + -|_ `...piecewise_linear(durations, values).uniform`: + To address all atoms in the field + -|_ `...piecewise_linear(durations, values).var`: + To address an atom at a specific location via index -|_ `...piecewise_linear(durations, values).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - |_ To address multiple atoms at specific locations by + specifying a single variable and then assigning it a + list of coordinates - Assign values to pre-existing variables via: - - |_ `...piecewise_linear(durations, values).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...piecewise_linear(durations, values).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...piecewise_linear(durations, values).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - |_ `...piecewise_linear(durations, values) + .assign(variable_name = value)`: + to assign a single value to a variable + - |_ `...piecewise_linear(durations, values) + .batch_assign(variable_name = [value1, ...])`: + to assign multiple values to a variable + - |_ `...piecewise_linear(durations, values) + .args(["previously_defined_var"])`: + to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_linear(durations, values).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_linear(durations, values).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_linear(durations, values).device`: to specify the backend via string - - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...piecewise_linear(durations, values).braket`: + to run on Braket local emulator or QuEra hardware remotely + - |_ `...piecewise_linear(durations, values).bloqade`: + to run on the Bloqade local emulator + - |_ `...piecewise_linear(durations, values).device`: + to specify the backend via string + - Choose to parallelize your atom geometry, + duplicating it to fill the whole space: - |_ `...piecewise_linear(durations, values).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_linear(durations, values).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_linear(durations, values).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_linear(durations, values).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_linear(durations, values).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field + - |_ `...piecewise_linear(durations, values).rydberg`: + to target the Rydberg level coupling + - |_ `...piecewise_linear(durations, values).hyperfine`: + to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling + (previously selected as `rydberg` or `hyperfine`): + - |_ `...piecewise_linear(durations, values).amplitude`: + to target the real-valued Rabi Amplitude field + - |_ `...piecewise_linear(durations, values).phase`: + to target the real-valued Rabi Phase field + - |_ `...piecewise_linear(durations, values).detuning`: + to target the Detuning field - |_ `....rabi`: to target the complex-valued Rabi field Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.detuning.uniform - # ramp our waveform up to a certain value, hold it - # then ramp down. In this case, we ramp up to 2.0 rad/us in 0.3 us, + # ramp our waveform up to a certain value, hold it + # then ramp down. In this case, we ramp up to 2.0 rad/us in 0.3 us, # then hold it for 1.5 us before ramping down in 0.3 us back to 0.0 rad/us. - >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[0.0, 2.0, 2.0, 0.0]) - ``` + >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], + values=[0.0, 2.0, 2.0, 0.0]) + ``` """ return PiecewiseLinear(durations, values, self) @@ -310,53 +466,86 @@ def piecewise_constant( """ - Append or assign a piecewise constant waveform to current location(s). - The `durations` argument should have number of elements = len(values). - - `durations` should be the duration PER section of the waveform, NON-CUMULATIVE. - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - `durations` should be the duration PER section of the waveform, + NON-CUMULATIVE. + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) + previously without a waveform: + - You will now complete the construction of a "drive", one or a sum of + drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform or other options below: + - You will now be appending this waveform to that previous + waveform or other options below: - You can now: - Continue building your waveform via: - - |_ `...piecewise_linear(durations, values).linear(start, stop, duration)`: to append another linear waveform - - |_ `...piecewise_linear(durations, values).constant(value, duration)`: to append a constant waveform - - |_ `...piecewise_linear(durations, values).piecewise_linear(durations, values)`: to append a piecewise linear waveform - - |_ `...piecewise_linear(durations, values).piecewise_constant(durations, values)`: to append a piecewise constant waveform - - |_ `...piecewise_linear(durations, values).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...piecewise_linear(durations, values).apply(waveform)`: to append a pre-defined waveform - - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: to append a waveform defined by a python function + - |_ `...piecewise_linear(durations, values) + .linear(start, stop, duration)`: to append another linear waveform + - |_ `...piecewise_linear(durations, values) + .constant(value, duration)`: to append a constant waveform + - |_ `...piecewise_linear(durations, values) + .piecewise_linear(durations, values)`: + to append a piecewise linear waveform + - |_ `...piecewise_linear(durations, values) + .piecewise_constant(durations, values)`: + to append a piecewise constant waveform + - |_ `...piecewise_linear(durations, values) + .poly([coefficients], duration)`: to append a polynomial waveform + - |_ `...piecewise_linear(durations, values) + .apply(waveform)`: to append a pre-defined waveform + - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: + to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...piecewise_linear(durations, values).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_linear(durations, values).uniform`: To address all atoms in the field - -|_ `...piecewise_linear(durations, values).var`: To address an atom at a specific location via index + - Begin constructing another drive by starting a new spatial modulation + (this drive will be summed to the one you just created): + -|_ `...piecewise_linear(durations, values).uniform`: + To address all atoms in the field + -|_ `...piecewise_linear(durations, values).var`: + To address an atom at a specific location via index -|_ `...piecewise_linear(durations, values).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - |_ To address multiple atoms at specific locations by + specifying a single variable and then assigning it a + list of coordinates - Assign values to pre-existing variables via: - - |_ `...piecewise_linear(durations, values).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...piecewise_linear(durations, values).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...piecewise_linear(durations, values).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - |_ `...piecewise_linear(durations, values) + .assign(variable_name = value)`: to assign a single value to a variable + - |_ `...piecewise_linear(durations, values) + .batch_assign(variable_name = [value1, ...])`: + to assign multiple values to a variable + - |_ `...piecewise_linear(durations, values) + .args(["previously_defined_var"])`: to defer assignment + of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_linear(durations, values).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_linear(durations, values).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_linear(durations, values).device`: to specify the backend via string - - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...piecewise_linear(durations, values).braket`: + to run on Braket local emulator or QuEra hardware remotely + - |_ `...piecewise_linear(durations, values).bloqade`: + to run on the Bloqade local emulator + - |_ `...piecewise_linear(durations, values).device`: + to specify the backend via string + - Choose to parallelize your atom geometry, + duplicating it to fill the whole space: - |_ `...piecewise_linear(durations, values).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_linear(durations, values).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_linear(durations, values).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_linear(durations, values).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_linear(durations, values).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field + - |_ `...piecewise_linear(durations, values).rydberg`: + to target the Rydberg level coupling + - |_ `...piecewise_linear(durations, values).hyperfine`: + to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling + (previously selected as `rydberg` or `hyperfine`): + - |_ `...piecewise_linear(durations, values).amplitude`: + to target the real-valued Rabi Amplitude field + - |_ `...piecewise_linear(durations, values).phase`: + to target the real-valued Rabi Phase field + - |_ `...piecewise_linear(durations, values).detuning`: + to target the Detuning field - |_ `....rabi`: to target the complex-valued Rabi field Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.rabi.phase.uniform - # create a staircase, we hold 0.0 rad/us for 1.0 us, then + # create a staircase, we hold 0.0 rad/us for 1.0 us, then # to 1.0 rad/us for 0.5 us before stopping at 0.8 rad/us for 0.9 us. >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[1.0, 0.5, 0.9]) """ @@ -366,56 +555,92 @@ def piecewise_constant( def fn(self, fn: Callable, duration: ScalarType) -> "Fn": """ - Append or assign a custom function as a waveform. - - The function must have its first argument be that of time but can also have other arguments which are treated as variables you can assign values to later in the program via `.assign` + - The function must have its first argument be that of time but + can also have other arguments which are treated as variables + you can assign values to later in the program via `.assign` or `.batch_assign`. - The function must return a singular float value - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) + previously without a waveform: + - You will now complete the construction of a "drive", + one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform or other options below: + - You will now be appending this waveform to that previous waveform + or other options below: - You can now: - Continue building your waveform via: - - |_ `...piecewise_linear(durations, values).linear(start, stop, duration)`: to append another linear waveform - - |_ `...piecewise_linear(durations, values).constant(value, duration)`: to append a constant waveform - - |_ `...piecewise_linear(durations, values).piecewise_linear(durations, values)`: to append a piecewise linear waveform - - |_ `...piecewise_linear(durations, values).piecewise_constant(durations, values)`: to append a piecewise constant waveform - - |_ `...piecewise_linear(durations, values).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...piecewise_linear(durations, values).apply(waveform)`: to append a pre-defined waveform - - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: to append a waveform defined by a python function + - |_ `...piecewise_linear(durations, values) + .linear(start, stop, duration)`: to append another linear waveform + - |_ `...piecewise_linear(durations, values) + .constant(value, duration)`: to append a constant waveform + - |_ `...piecewise_linear(durations, values) + .piecewise_linear(durations, values)`: + to append a piecewise linear waveform + - |_ `...piecewise_linear(durations, values) + .piecewise_constant(durations, values)`: + to append a piecewise constant waveform + - |_ `...piecewise_linear(durations, values) + .poly([coefficients], duration)`: to append a polynomial waveform + - |_ `...piecewise_linear(durations, values) + .apply(waveform)`: to append a pre-defined waveform + - |_ `...piecewise_linear(durations, values) + .fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...piecewise_linear(durations, values).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_linear(durations, values).uniform`: To address all atoms in the field - -|_ `...piecewise_linear(durations, values).var`: To address an atom at a specific location via index + - Begin constructing another drive by starting a new spatial modulation + (this drive will be summed to the one you just created): + -|_ `...piecewise_linear(durations, values).uniform`: + To address all atoms in the field + -|_ `...piecewise_linear(durations, values).var`: + To address an atom at a specific location via index -|_ `...piecewise_linear(durations, values).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates + - |_ To address multiple atoms at specific locations by + specifying a single variable and then assigning it a + list of coordinates - Assign values to pre-existing variables via: - - |_ `...piecewise_linear(durations, values).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...piecewise_linear(durations, values).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...piecewise_linear(durations, values).args(["previously_defined_var"])`: to defer assignment of a variable to execution time + - |_ `...piecewise_linear(durations, values) + .assign(variable_name = value)`: to assign a single value to a variable + - |_ `...piecewise_linear(durations, values) + .batch_assign(variable_name = [value1, ...])`: + to assign multiple values to a variable + - |_ `...piecewise_linear(durations, values) + .args(["previously_defined_var"])`: + to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_linear(durations, values).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_linear(durations, values).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_linear(durations, values).device`: to specify the backend via string - - Choose to parallelize your atom geometry, duplicating it to fill the whole space: + - |_ `...piecewise_linear(durations, values).braket`: + to run on Braket local emulator or QuEra hardware remotely + - |_ `...piecewise_linear(durations, values).bloqade`: + to run on the Bloqade local emulator + - |_ `...piecewise_linear(durations, values).device`: + to specify the backend via string + - Choose to parallelize your atom geometry, + duplicating it to fill the whole space: - |_ `...piecewise_linear(durations, values).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_linear(durations, values).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_linear(durations, values).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_linear(durations, values).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_linear(durations, values).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field - - |_ `...piecewise_linear(durations, values).rabi`: to target the complex-valued Rabi field + - |_ `...piecewise_linear(durations, values).rydberg`: + to target the Rydberg level coupling + - |_ `...piecewise_linear(durations, values).hyperfine`: + to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling + (previously selected as `rydberg` or `hyperfine`): + - |_ `...piecewise_linear(durations, values).amplitude`: + to target the real-valued Rabi Amplitude field + - |_ `...piecewise_linear(durations, values).phase`: + to target the real-valued Rabi Phase field + - |_ `...piecewise_linear(durations, values).detuning`: + to target the Detuning field + - |_ `...piecewise_linear(durations, values).rabi`: + to target the complex-valued Rabi field Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.detuning.uniform # define our custom waveform. It must have one argument - # be time followed by any other number of arguments that can + # be time followed by any other number of arguments that can # be assigned a value later in the program via `.assign` or `.batch_assign` >>> def custom_waveform_function(t, arg1, arg2): return arg1*t + arg2 diff --git a/src/bloqade/ir/location/list.py b/src/bloqade/ir/location/list.py index a866d64e2..ef82567d2 100644 --- a/src/bloqade/ir/location/list.py +++ b/src/bloqade/ir/location/list.py @@ -61,15 +61,16 @@ def __iter__(self): start = ListOfLocations() """ -A Program starting point, alias of empty [`ListOfLocations`][bloqade.ir.location.list.ListOfLocations]. - -Next possible steps to build your program are adding atom positions and addressing level couplings. - -- Specify which level coupling to address with: - - |_ `start.rydberg`: for [`Rydberg`][bloqade.builder.coupling.Rydberg] Level coupling - - |_ `start.hyperfine`: for [`Hyperfine`][bloqade.builder.coupling.Hyperfine] Level coupling +A Program starting point, alias of empty +[`ListOfLocations`][bloqade.ir.location.list.ListOfLocations]. + +- Next possible steps to build your program are: +- Specify which level coupling to address with: + - |_ `start.rydberg`: for [`Rydberg`][bloqade.builder.coupling.Rydberg] + Level coupling + - |_ `start.hyperfine`: for [`Hyperfine`][bloqade.builder.coupling.Hyperfine] + Level coupling - LOCKOUT: You cannot add atoms to your geometry after specifying level coupling. - - continue/start building your geometry with: - |_ `start.add_position()`: to add atom(s) to current register. It will accept: - A single coordinate, represented as a tuple (e.g. `(5,6)`) with a value that @@ -77,8 +78,7 @@ def __iter__(self): - integers: `(5,6)` - floats: `(5.1, 2.5)` - strings (for later variable assignment): `("x", "y")` - - [`Scalar`][bloqade.ir.scalar.Scalar] objects: `(2*cast("x"), 5+cast("y"))` + - [`Scalar`][bloqade.ir.scalar.Scalar] objects: `(2*cast("x"), 5+cast("y"))` - A list of coordinates, represented as a list of types mentioned previously. - A numpy array with shape (n, 2) where n is the total number of atoms - - `.add_position()` will return another `ListOfLocations` you can build on. """ diff --git a/src/bloqade/ir/location/transform.py b/src/bloqade/ir/location/transform.py index 62a7d0604..0d6dcde80 100644 --- a/src/bloqade/ir/location/transform.py +++ b/src/bloqade/ir/location/transform.py @@ -33,6 +33,40 @@ def check_bool_array(array): class TransformTrait: @beartype def scale(self, scale: ScalarType): + """ + Scale the geometry of your atoms. + + Usage Example: + ``` + >>> reg = start.add_position([(0,0), (1,1)]) + # atom positions are now (0,0), (2,2) + >>> new_reg = reg.scale(2) + # you may also use scale on pre-defined geometries + >>> from bloqade.atom_arrangement import Chain + # atoms in the chain will now be 2 um apart versus + # the default 1 um + >>> Chain(11).scale(2) + ``` + + - Next possible steps are: + - Continuing to build your geometry via: + - |_ `...add_position(positions).add_position(positions)`: + to add more positions + - |_ `...add_position(positions).apply_defect_count(n_defects)`: + to randomly drop out n_atoms + - |_ `...add_position(positions).apply_defect_density(defect_probability)`: + to drop out atoms with a certain probability + - |_ `...add_position(positions).scale(scale)`: to scale the geometry + - Targeting a level coupling once you're done with the atom geometry: + - |_ `...add_position(positions).rydberg`: + to specify Rydberg coupling + - |_ `...add_position(positions).hyperfine`: + to specify Hyperfine coupling + - Visualizing your atom geometry: + - |_ `...add_position(positions).show()`: + shows your geometry in your web browser + + """ from .list import ListOfLocations from .base import LocationInfo @@ -105,21 +139,51 @@ def add_position( ], filling: Optional[Union[BoolArray, List[bool], bool]] = None, ) -> "ListOfLocations": - """add a position or list of positions to existing atom arrangement. - - Args: - position (Tuple[ScalarType, ScalarType] - | List[Tuple[ScalarType, ScalarType] - | numpy.array with shape (n, 2)]): - position to add - filling (bool | list[bool] - | numpy.array with shape (n, ) | None, optional): - filling of the added position(s). Defaults to None. if None, all - positions are filled. - - - Returns: - ListOfLocations: new atom arrangement with added positions + """ + Add a position or multiple positions to a pre-existing geometry. + + `add_position` is capable of accepting: + - A single tuple for one atom coordinate: `(1.0, 2.5)` + - A list of tuples: `[(0.0, 1.0), (2.0,1.5), etc.] + - A numpy array of shape (N, 2) where N is the number of atoms + + You may also intersperse variables anywhere a value may be present. + + You can also pass in an optional argument which determines the atom "filling" + (whether or not at a specified coordinate an atom should be present). + + Usage Example: + ``` + # single coordinate + >>> reg = start.add_position((0,0)) + # you may chain add_position calls + >>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)]) + # you can add variables anywhere a value may be present + >>> reg_with_var = reg_plus_two.add_position(("x", "y")) + # and specify your atom fillings + >>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)], + [True, False]) + # alternatively you could use one boolean to specify + # all coordinates should be empty/filled + >>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9), + (5.2, 2.2)], False) + ``` + + - Next possible steps are: + - Continuing to build your geometry via: + - |_ `...add_position(positions).add_position(positions)`: + to add more positions + - |_ `...add_position(positions).apply_defect_count(n_defects)`: + to randomly drop out n_atoms + - |_ `...add_position(positions).apply_defect_density(defect_probability)`: + to drop out atoms with a certain probability + - |_ `...add_position(positions).scale(scale)`: to scale the geometry + - Targeting a level coupling once you're done with the atom geometry: + - |_ `...add_position(positions).rydberg`: to specify Rydberg coupling + - |_ `...add_position(positions).hyperfine`: to specify Hyperfine coupling + - Visualizing your atom geometry: + - |_ `...add_position(positions).show()`: + shows your geometry in your web browser """ return self._add_position(position, filling) @@ -128,7 +192,50 @@ def add_position( def apply_defect_count( self, n_defects: int, rng: np.random.Generator = np.random.default_rng() ): - """apply n_defects randomly to existing atom arrangement.""" + """ + Drop `n_defects` atoms from the geometry randomly. Internally this occurs + by setting certain sites to have a SiteFilling set to false indicating + no atom is present at the coordinate. + + A default numpy-based Random Number Generator is used but you can + explicitly override this by passing in your own. + + Usage Example: + + ``` + >>> from bloqade.atom_arrangement import Chain + >>> import numpy as np + # set a custom seed for a numpy-based RNG + >>> custom_rng = np.random.default_rng(888) + # randomly remove two atoms from the geometry + >>> reg = Chain(11).apply_defect_count(2, custom_rng) + # you may also chain apply_defect_count calls + >>> reg.apply_defect_count(2, custom_rng) + # you can also use apply_defect_count on custom geometries + >>> from bloqade import start + >>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng) + ``` + + - Next possible steps are: + - Continuing to build your geometry via: + - |_ `...apply_defect_count(defect_counts).add_position(positions)`: + to add more positions + - |_ `...apply_defect_count(defect_counts) + .apply_defect_count(n_defects)`: to randomly drop out n_atoms + - |_ `...apply_defect_count(defect_counts) + .apply_defect_density(defect_probability)`: + to drop out atoms with a certain probability + - |_ `...apply_defect_count(defect_counts).scale(scale)`: + to scale the geometry + - Targeting a level coupling once you're done with the atom geometry: + - |_ `...apply_defect_count(defect_counts).rydberg`: to specify + Rydberg coupling + - |_ `...apply_defect_count(defect_counts).hyperfine`: + to specify Hyperfine coupling + - Visualizing your atom geometry: + - |_ `...apply_defect_count(defect_counts).show()`: + shows your geometry in your web browser + """ from .list import ListOfLocations from .base import LocationInfo, SiteFilling @@ -164,7 +271,51 @@ def apply_defect_density( defect_probability: float, rng: np.random.Generator = np.random.default_rng(), ): - """apply defect_probability randomly to existing atom arrangement.""" + """ + Drop atoms randomly with `defect_probability` probability (range of 0 to 1). + Internally this occurs by setting certain sites to have a SiteFilling + set to false indicating no atom is present at the coordinate. + + A default numpy-based Random Number Generator is used but you can + explicitly override this by passing in your own. + + Usage Example: + + ``` + >>> from bloqade.atom_arrangement import Chain + >>> import numpy as np + # set a custom seed for a numpy-based RNG + >>> custom_rng = np.random.default_rng(888) + # randomly remove two atoms from the geometry + >>> reg = Chain(11).apply_defect_density(0.2, custom_rng) + # you may also chain apply_defect_density calls + >>> reg.apply_defect_count(0.1, custom_rng) + # you can also use apply_defect_density on custom geometries + >>> from bloqade import start + >>> start.add_position([(0,0), (1,1)]) + .apply_defect_density(0.5, custom_rng) + ``` + + - Next possible steps are: + - Continuing to build your geometry via: + - |_ `...apply_defect_count(defect_counts).add_position(positions)`: + to add more positions + - |_ `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`: + to randomly drop out n_atoms + - |_ `...apply_defect_count(defect_counts) + .apply_defect_density(defect_probability)`: + to drop out atoms with a certain probability + - |_ `...apply_defect_count(defect_counts).scale(scale)`: + to scale the geometry + - Targeting a level coupling once you're done with the atom geometry: + - |_ `...apply_defect_count(defect_counts).rydberg`: + to specify Rydberg coupling + - |_ `...apply_defect_count(defect_counts).hyperfine`: + to specify Hyperfine coupling + - Visualizing your atom geometry: + - |_ `...apply_defect_count(defect_counts).show()`: + shows your geometry in your web browser + """ from .list import ListOfLocations from .base import LocationInfo, SiteFilling @@ -189,7 +340,6 @@ def apply_defect_density( return ListOfLocations(location_list=location_list) def remove_vacant_sites(self): - """remove all vacant sites from the register.""" from .base import SiteFilling from .list import ListOfLocations From 15401e3d3dba2b75d5534e3c99ef4db4942ec0b1 Mon Sep 17 00:00:00 2001 From: John Long Date: Fri, 29 Sep 2023 00:38:49 -0400 Subject: [PATCH 10/20] ran black lint --- src/bloqade/builder/backend/__init__.py | 2 +- src/bloqade/builder/backend/bloqade.py | 4 +- src/bloqade/builder/backend/braket.py | 18 +- src/bloqade/builder/coupling.py | 16 +- src/bloqade/builder/drive.py | 8 +- src/bloqade/builder/field.py | 58 ++-- src/bloqade/builder/pragmas.py | 36 +-- src/bloqade/builder/start.py | 8 +- src/bloqade/builder/waveform.py | 366 ++++++++++++------------ 9 files changed, 257 insertions(+), 259 deletions(-) diff --git a/src/bloqade/builder/backend/__init__.py b/src/bloqade/builder/backend/__init__.py index dde5c6f8a..d77347391 100644 --- a/src/bloqade/builder/backend/__init__.py +++ b/src/bloqade/builder/backend/__init__.py @@ -5,7 +5,7 @@ class BackendRoute(QuEraService, BraketService, BloqadeService): """ - - Specify the backend to run your program on via a string + - Specify the backend to run your program on via a string (versus more formal builder syntax) of specifying the vendor/product first (Bloqade/Braket) and narrowing it down - ...device("quera.aquila") versus ...quera.aquila() diff --git a/src/bloqade/builder/backend/bloqade.py b/src/bloqade/builder/backend/bloqade.py index 509b92343..55d46abda 100644 --- a/src/bloqade/builder/backend/bloqade.py +++ b/src/bloqade/builder/backend/bloqade.py @@ -9,7 +9,7 @@ def bloqade(self): - Possible Next Steps: - |_ `...bloqade.python()`: target submission to the Bloqade python backend - - |_ `...bloqade.julia()`: (CURRENTLY NOT IMPLEMENTED!)target + - |_ `...bloqade.julia()`: (CURRENTLY NOT IMPLEMENTED!)target submission to the Bloqade.jl backend """ return BloqadeDeviceRoute(self) @@ -21,7 +21,7 @@ def python(self): Specify the Bloqade Python backend. - Possible Next Steps: - - |_ `...python().run(shots)`: + - |_ `...python().run(shots)`: to submit to the python emulator and await results """ return self.parse().bloqade.python() diff --git a/src/bloqade/builder/backend/braket.py b/src/bloqade/builder/backend/braket.py index 5c91e8b1d..554a4cf43 100644 --- a/src/bloqade/builder/backend/braket.py +++ b/src/bloqade/builder/backend/braket.py @@ -5,12 +5,12 @@ class BraketService(Builder): @property def braket(self): """ - Specify the Braket backend. This allows you to access the AWS Braket local + Specify the Braket backend. This allows you to access the AWS Braket local emulator OR go submit things to QuEra hardware on AWS Braket service. - Possible Next Steps are: - |_ `...braket.aquila()`: target submission to the QuEra Aquila QPU - - |_ `...braket.local_emulator()`: target submission to the Braket + - |_ `...braket.local_emulator()`: target submission to the Braket local emulator """ return BraketDeviceRoute(self) @@ -23,15 +23,15 @@ def aquila(self): The number of shots you specify in the subsequent `.run` method will either: - dictate the number of times your program is run - - dictate the number of times per parameter your program is run if - you have a variable with batch assignments/intend to conduct + - dictate the number of times per parameter your program is run if + you have a variable with batch assignments/intend to conduct a parameter sweep - Possible next steps are: - - |_ `...aquila().run(shots)`: To submit to hardware and WAIT for + - |_ `...aquila().run(shots)`: To submit to hardware and WAIT for results (blocking) - - |_ `...aquila().run_async(shots)`: To submit to hardware and immediately + - |_ `...aquila().run_async(shots)`: To submit to hardware and immediately allow for other operations to occur """ return self.parse().braket.aquila() @@ -42,11 +42,11 @@ def local_emulator(self): - The number of shots you specify in the subsequent `.run` method will either: - dictate the number of times your program is run - - dictate the number of times per parameter your program is run if - you have a variable with batch assignments/intend to + - dictate the number of times per parameter your program is run if + you have a variable with batch assignments/intend to conduct a parameter sweep - Possible next steps are: - - |_ `...local_emulator().run(shots)`: to submit to the emulator + - |_ `...local_emulator().run(shots)`: to submit to the emulator and await results """ diff --git a/src/bloqade/builder/coupling.py b/src/bloqade/builder/coupling.py index 4131427b5..4011c1eb8 100644 --- a/src/bloqade/builder/coupling.py +++ b/src/bloqade/builder/coupling.py @@ -6,27 +6,25 @@ class LevelCoupling(Builder): @property def detuning( self, - ) -> ( - Detuning - ): # field is summation of one or more drives, + ) -> Detuning: # field is summation of one or more drives, # waveform + spatial modulation = drive """ - Specify the [`Detuning`][bloqade.builder.field.Detuning] + Specify the [`Detuning`][bloqade.builder.field.Detuning] [`Field`][bloqade.builder.Field] of your program. A "field" is a summation of one or more "drives", with a drive being the sum of a waveform and spatial modulation. - You are currently building the spatial modulation component and will be + You are currently building the spatial modulation component and will be able to specify a waveform. - You can do this by: - |_ `...detuning.uniform`: To address all atoms in the field - - |_ `...detuning.location(int)`: To address an atom at a specific + - |_ `...detuning.location(int)`: To address an atom at a specific location via index - |_ `...detuning.var(str)` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates """ @@ -36,13 +34,13 @@ def detuning( @property def rabi(self) -> Rabi: """ - Specify the complex-valued [`Rabi`][bloqade.builder.field.Rabi] + Specify the complex-valued [`Rabi`][bloqade.builder.field.Rabi] field of your program. The Rabi field is composed of a real-valued Amplitude and Phase field. - Next possible steps to build your program are - creating the [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude] field + creating the [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude] field and [`RabiPhase`][bloqade.builder.field.RabiAmplitude] field of the field: - |_ `...rabi.amplitude`: To create the Rabi amplitude field - |_ `...rabi.phase`: To create the Rabi phase field diff --git a/src/bloqade/builder/drive.py b/src/bloqade/builder/drive.py index e4c9fce88..9f659b6a9 100644 --- a/src/bloqade/builder/drive.py +++ b/src/bloqade/builder/drive.py @@ -7,8 +7,8 @@ def rydberg(self) -> Rydberg: """ Address the Rydberg level coupling in your program. - - Next possible steps to build your program are specifying the - [`Rabi`][bloqade.builder.field.Rabi] field or + - Next possible steps to build your program are specifying the + [`Rabi`][bloqade.builder.field.Rabi] field or [`Detuning`][bloqade.builder.field.Detuning] field. - |_ `...rydberg.rabi`: for Rabi field - |_ `...rydberg.detuning`: for Detuning field @@ -23,8 +23,8 @@ def hyperfine(self) -> Hyperfine: """ Address the Hyperfine level coupling in your program. - - Next possible steps to build your program are specifying the - [`Rabi`][bloqade.builder.field.Rabi] field or + - Next possible steps to build your program are specifying the + [`Rabi`][bloqade.builder.field.Rabi] field or [`Detuning`][bloqade.builder.field.Detuning] field. - |_ `...hyperfine.rabi`: for Rabi field - |_ `...hyperfine.detuning`: for Detuning field diff --git a/src/bloqade/builder/field.py b/src/bloqade/builder/field.py index 3b9891446..443368c28 100644 --- a/src/bloqade/builder/field.py +++ b/src/bloqade/builder/field.py @@ -6,26 +6,26 @@ class Field(Builder): @property def uniform(self): """ - Address all atoms as part of defining the spatial modulation component + Address all atoms as part of defining the spatial modulation component of a drive. - Next steps to build your program include choosing the waveform that + Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive. - The drive by itself, or the sum of subsequent drives (created by just + The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.). - You can now do: - |_ `...uniform.linear(start, stop, duration)` : to apply a linear waveform - |_ `...uniform.constant(value, duration)` : to apply a constant waveform - - |_ `...uniform.poly([coefficients], duration)` : to apply a + - |_ `...uniform.poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...uniform.apply(wf:bloqade.ir.Waveform)`: to apply a + - |_ `...uniform.apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...uniform.piecewise_linear([durations], [values])`: to apply + - |_ `...uniform.piecewise_linear([durations], [values])`: to apply a piecewise linear waveform - - |_ `...uniform.piecewise_constant([durations], [values])`: to apply + - |_ `...uniform.piecewise_constant([durations], [values])`: to apply a piecewise constant waveform - |_ `...uniform.fn(f(t,...))`: to apply a function as a waveform @@ -37,13 +37,13 @@ def uniform(self): @beartype def location(self, label: int): """ - Address a single atom (or multiple via chaining calls, see below) as + Address a single atom (or multiple via chaining calls, see below) as part of defining the spatial modulation component of a drive. - Next steps to build your program include choosing the waveform that + Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive. - The drive by itself, or the sum of subsequent drives (created by just + The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field. (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) @@ -57,27 +57,27 @@ def location(self, label: int): ``` - You can now do: - - |_ `...location(int).linear(start, stop, duration)` : to apply + - |_ `...location(int).linear(start, stop, duration)` : to apply a linear waveform - - |_ `...location(int).constant(value, duration)` : to apply + - |_ `...location(int).constant(value, duration)` : to apply a constant waveform - - |_ `...location(int).poly([coefficients], duration)` : to apply + - |_ `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply + - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...location(int).piecewise_linear([durations], [values])`: to apply + - |_ `...location(int).piecewise_linear([durations], [values])`: to apply a piecewise linear waveform - - |_ `...location(int).piecewise_constant([durations], [values])`: to apply + - |_ `...location(int).piecewise_constant([durations], [values])`: to apply a piecewise constant waveform - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform - You can also address multiple atoms by chaining: - |_ `...location(int).location(int)` - - The waveform you specify after the last `location` in the chain will + - The waveform you specify after the last `location` in the chain will be applied to all atoms in the chain - - And you can scale any waveform by a multiplicative factor on a + - And you can scale any waveform by a multiplicative factor on a specific atom via: - |_ `...location(int).scale(float)` - - You cannot define a scaling across multiple atoms with one method call! + - You cannot define a scaling across multiple atoms with one method call! They must be specified atom-by-atom. """ @@ -88,13 +88,13 @@ def location(self, label: int): @beartype def var(self, name: str): """ - Address a single atom (or multiple via assigning a list of values) as + Address a single atom (or multiple via assigning a list of values) as part of defining the spatial modulation component of a drive. - Next steps to build your program include choosing the waveform that + Next steps to build your program include choosing the waveform that will be summed with the spatial modulation to create a drive. - The drive by itself, or the sum of subsequent drives (created by just + The drive by itself, or the sum of subsequent drives (created by just chaining the construction of drives) will become the field (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) @@ -107,22 +107,22 @@ def var(self, name: str): # a list of values, indicating a set of atoms should be targeted. >>> target_one_atom = ...assign(a = 0) >>> target_multiple_atoms = ...assign(a = [0, 2]) - # Note that `assign` is used, you cannot batch_assign variables used in + # Note that `assign` is used, you cannot batch_assign variables used in # .var() calls ``` - You can now do: - - |_ `...location(int).linear(start, stop, duration)` : to apply + - |_ `...location(int).linear(start, stop, duration)` : to apply a linear waveform - - |_ `...location(int).constant(value, duration)` : to apply + - |_ `...location(int).constant(value, duration)` : to apply a constant waveform - - |_ `...location(int).poly([coefficients], duration)` : to apply + - |_ `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply + - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...location(int).piecewise_linear(durations, values)`: to + - |_ `...location(int).piecewise_linear(durations, values)`: to apply a piecewise linear waveform - - |_ `...location(int).piecewise_constant(durations, values)`: to + - |_ `...location(int).piecewise_constant(durations, values)`: to apply a piecewise constant waveform - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform diff --git a/src/bloqade/builder/pragmas.py b/src/bloqade/builder/pragmas.py index fc28d93f1..857d4aa96 100644 --- a/src/bloqade/builder/pragmas.py +++ b/src/bloqade/builder/pragmas.py @@ -22,7 +22,7 @@ def assign(self, **assignments) -> "Assign": This is reserved for variables that should only take single values OR for spatial modulations that were created with `.var` in which case you can - pass in a list. This is the ONLY circumstance in which multiple + pass in a list. This is the ONLY circumstance in which multiple values are allowed. Usage Examples: @@ -38,15 +38,15 @@ def assign(self, **assignments) -> "Assign": ``` - You can now: - - |_ ...assign(assignments).bloqade: select the bloqade local + - |_ ...assign(assignments).bloqade: select the bloqade local emulator backend - - |_ ...assign(assignments).braket: select braket local emulator or + - |_ ...assign(assignments).braket: select braket local emulator or QuEra hardware - - |_ ...assign(assignments).device(specifier_string): select backend + - |_ ...assign(assignments).device(specifier_string): select backend by specifying a string - Assign multiple values to a single variable for a parameter sweep: - |_ ...assign(assignments).batch_assign(assignments): - - Parallelize the program register, duplicating the geometry and waveform + - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available space/qubits on the QPU: - |_ ...assign(assignments).parallelize(cluster_spacing) @@ -69,30 +69,30 @@ def batch_assign( Assign multiple values to a single variable to create a parameter sweep. - Bloqade automatically handles the multiple programs this would generate + Bloqade automatically handles the multiple programs this would generate and treats it as object with unified results for easy post-processing. - NOTE: if you assign multiple values to multiple variables in your program, - the values must be of the same length. Bloqade will NOT do a Cartesian product - (e.g. if "var1" is assigned [1,2,3] and "var2" is assigned [4,5,6] then the + NOTE: if you assign multiple values to multiple variables in your program, + the values must be of the same length. Bloqade will NOT do a Cartesian product + (e.g. if "var1" is assigned [1,2,3] and "var2" is assigned [4,5,6] then the resulting programs will have assignments [1,4], [2,5], [3,6]). Usage Example: ``` >>> reg = start.add_position([(0,0), (0, "atom_distance")]) >>> prog = reg.rydberg.rabi.amplitude.uniform.constant("value", 5.0) - >>> var_assigned_prog = prog.batch_assign(value = [1.0, 2.0, 3.0], + >>> var_assigned_prog = prog.batch_assign(value = [1.0, 2.0, 3.0], atom_distance = [1.0, 2.0, 3.0]) ``` - Next steps are: - - |_ ...batch_assign(assignments).bloqade: select the bloqade + - |_ ...batch_assign(assignments).bloqade: select the bloqade local emulator backend - - |_ ...batch_assign(assignments).braket: select braket local + - |_ ...batch_assign(assignments).braket: select braket local emulator or QuEra hardware - - |_ ...batch_assign(assignments).device(specifier_string): select + - |_ ...batch_assign(assignments).device(specifier_string): select backend by specifying a string - - Parallelize the program register, duplicating the geometry and waveform + - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available space/qubits on the QPU: - |_ ...batch_assign(assignments).parallelize(cluster_spacing) @@ -118,7 +118,7 @@ def parallelize(self, cluster_spacing: LiteralType) -> "Parallelize": Parallelize the current problem (register and sequence) by duplicating the geometry to take advantage of all available space/qubits on hardware. - The singular argument lets you specify how far apart the clusters + The singular argument lets you specify how far apart the clusters should be in micrometers. Usage Example: @@ -130,11 +130,11 @@ def parallelize(self, cluster_spacing: LiteralType) -> "Parallelize": ``` - Your next steps are: - |_ `...parallelize(cluster_spacing).bloqade`: select the bloqade + |_ `...parallelize(cluster_spacing).bloqade`: select the bloqade local emulator backend - |_ `...parallelize(cluster_spacing).braket`: select braket + |_ `...parallelize(cluster_spacing).braket`: select braket local emulator or QuEra hardware on the cloud - |_ `...parallelize(cluster_spacing).device(specifier_string)`: select + |_ `...parallelize(cluster_spacing).device(specifier_string)`: select backend by specifying a string """ diff --git a/src/bloqade/builder/start.py b/src/bloqade/builder/start.py index b314f9c51..b60d1810c 100644 --- a/src/bloqade/builder/start.py +++ b/src/bloqade/builder/start.py @@ -32,15 +32,15 @@ def apply(self, sequence: Sequence) -> SequenceBuilder: ``` - From here you can now do: - - |_ `...assign(assignments).bloqade`: select the bloqade + - |_ `...assign(assignments).bloqade`: select the bloqade local emulator backend - - |_ `...assign(assignments).braket`: select braket + - |_ `...assign(assignments).braket`: select braket local emulator or QuEra hardware - - |_ `...assign(assignments).device(specifier_string)`: select + - |_ `...assign(assignments).device(specifier_string)`: select backend by specifying a string - Assign multiple values to a single variable for a parameter sweep: - |_ `...assign(assignments).batch_assign(assignments)`: - - Parallelize the program register, duplicating the geometry and waveform + - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available space/qubits on the QPU: - |_ `...assign(assignments).parallelize(cluster_spacing)` diff --git a/src/bloqade/builder/waveform.py b/src/bloqade/builder/waveform.py index a9237de47..36fe58db3 100644 --- a/src/bloqade/builder/waveform.py +++ b/src/bloqade/builder/waveform.py @@ -16,75 +16,75 @@ def linear( ) -> "Linear": """ - Append or assign a linear waveform to the current location(s) - - If you specified a spatial modulation (e.g. `uniform`, `location`, + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform + - You will now be appending this waveform to that previous waveform or other options below: - You can: - Continue building your waveform via: - - |_ `...linear(start, stop, duration).linear(start, stop, duration)`: + - |_ `...linear(start, stop, duration).linear(start, stop, duration)`: to append another linear waveform - - |_ `...linear(start, stop, duration).constant(value, duration)`: + - |_ `...linear(start, stop, duration).constant(value, duration)`: to append a constant waveform - - |_ `...linear(start, stop, duration).piecewise_linear()`: + - |_ `...linear(start, stop, duration).piecewise_linear()`: to append a piecewise linear waveform - - |_ `...linear(start, stop, duration).piecewise_constant()`: + - |_ `...linear(start, stop, duration).piecewise_constant()`: to append a piecewise constant waveform - - |_ `...linear(start, stop, duration).poly([coefficients], duration)`: + - |_ `...linear(start, stop, duration).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...linear(start, stop, duration).apply(wf:bloqade.ir.Waveform)`: + - |_ `...linear(start, stop, duration).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...linear(start, stop, duration).fn(f(t,...))`: + - |_ `...linear(start, stop, duration).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...linear(start, stop, duration).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...linear(start, stop, duration).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - - |_ `...linear(start, stop, duration).uniform`: + - |_ `...linear(start, stop, duration).uniform`: To address all atoms in the field - - |_ `...linear(start, stop, duration).var`: + - |_ `...linear(start, stop, duration).var`: To address an atom at a specific location via index - |_ `...linear(start, stop, duration).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...linear(start, stop, duration).assign(variable_name = value)`: + - |_ `...linear(start, stop, duration).assign(variable_name = value)`: to assign a single value to a variable - |_ `...linear(start, stop, duration) - .batch_assign(variable_name = [value1, ...])`: + .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...linear(start, stop, duration).args(["previously_defined_var"])`: + - |_ `...linear(start, stop, duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...linear(start, stop, duration).braket`: + - |_ `...linear(start, stop, duration).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...linear(start, stop, duration).bloqade`: + - |_ `...linear(start, stop, duration).bloqade`: to run on the Bloqade local emulator - |_ `...linear(start, stop, duration).device`: to specify the backend via string - - Choose to parallelize your atom geometry, + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - |_ `...linear(start, stop, duration).parallelize(spacing)` - Start targeting another level coupling - - |_ `...linear(start, stop, duration).rydberg`: + - |_ `...linear(start, stop, duration).rydberg`: to target the Rydberg level coupling - - |_ `...linear(start, stop, duration).hyperfine`: + - |_ `...linear(start, stop, duration).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...linear(start, stop, duration).amplitude`: + - |_ `...linear(start, stop, duration).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...linear(start, stop, duration).phase`: + - |_ `...linear(start, stop, duration).phase`: to target the real-valued Rabi Phase field - - |_ `...linear(start, stop, duration).detuning`: + - |_ `...linear(start, stop, duration).detuning`: to target the Detuning field - - |_ `...linear(start, stop, duration).rabi`: + - |_ `...linear(start, stop, duration).rabi`: to target the complex-valued Rabi field @@ -100,76 +100,76 @@ def linear( def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": """ - Append or assign a constant waveform to the current location(s) - - If you specified a spatial modulation (e.g. `uniform`, `location`, + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or - a sum of drives creating a "field" (e.g. Real-valued Rabi + - You will now complete the construction of a "drive", one or + a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - You will now be appending this waveform to that previous waveform or other options below: - You can: - Continue building your waveform via: - - |_ `...constant(value, duration).linear(start, stop, duration)`: + - |_ `...constant(value, duration).linear(start, stop, duration)`: to append another linear waveform - - |_ `...constant(value, duration).constant(value, duration)`: + - |_ `...constant(value, duration).constant(value, duration)`: to append a constant waveform - - |_ `...constant(value, duration).piecewise_linear()`: + - |_ `...constant(value, duration).piecewise_linear()`: to append a piecewise linear waveform - - |_ `...constant(value, duration).piecewise_constant()`: + - |_ `...constant(value, duration).piecewise_constant()`: to append a piecewise constant waveform - - |_ `...constant(value, duration).poly([coefficients], duration)`: + - |_ `...constant(value, duration).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...constant(value, duration).apply(wf:bloqade.ir.Waveform)`: + - |_ `...constant(value, duration).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...constant(value, duration).fn(f(t,...))`: + - |_ `...constant(value, duration).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...constant(value, duration).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...constant(value, duration).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...constant(value, duration).uniform`: + -|_ `...constant(value, duration).uniform`: To address all atoms in the field - -|_ `...constant(value, duration).var`: + -|_ `...constant(value, duration).var`: To address an atom at a specific location via index -|_ `...constant(value, duration).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...constant(value, duration).assign(variable_name = value)`: + - |_ `...constant(value, duration).assign(variable_name = value)`: to assign a single value to a variable - |_ `...constant(value, duration) - .batch_assign(variable_name = [value1, ...])`: + .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...constant(value, duration).args(["previously_defined_var"])`: + - |_ `...constant(value, duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...constant(value, duration).braket`: + - |_ `...constant(value, duration).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...constant(value, duration).bloqade`: + - |_ `...constant(value, duration).bloqade`: to run on the Bloqade local emulator - - |_ `...constant(value, duration).device`: + - |_ `...constant(value, duration).device`: to specify the backend via string - - Choose to parallelize your atom geometry, + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - |_ `...constant(start, stop, duration).parallelize(spacing)` - Start targeting another level coupling - - |_ `...constant(value, duration).rydberg`: + - |_ `...constant(value, duration).rydberg`: to target the Rydberg level coupling - - |_ `...constant(value, duration).hyperfine`: + - |_ `...constant(value, duration).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...constant(value, duration).amplitude`: + - |_ `...constant(value, duration).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...constant(value, duration).phase`: + - |_ `...constant(value, duration).phase`: to target the real-valued Rabi Phase field - - |_ `...constant(value, duration).detuning`: + - |_ `...constant(value, duration).detuning`: to target the Detuning field - - |_ `...constant(value, duration).rabi`: + - |_ `...constant(value, duration).rabi`: to target the complex-valued Rabi field @@ -184,21 +184,21 @@ def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": """ - Append or assign a waveform with a polynomial profile to current location(s) - - You pass in a list of coefficients and a duration to this method + - You pass in a list of coefficients and a duration to this method which obeys the following expression: ` wv(t) = coeffs[0] + coeffs[1]*t + coeffs[2]*t^2 + ... + coeffs[n]*t^n ` - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of + - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform + - You will now be appending this waveform to that previous waveform or other options below: - You can: - Continue building your waveform via: - - |_ `...poly([coeffs], duration).linear(start, stop, duration)`: + - |_ `...poly([coeffs], duration).linear(start, stop, duration)`: to append another linear waveform - |_ `...poly([coeffs], duration).constant(value, duration)`: to append a constant waveform @@ -218,47 +218,47 @@ def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": - |_ `...poly([coeffs], duration).record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...poly([coeffs], duration).uniform`: + -|_ `...poly([coeffs], duration).uniform`: To address all atoms in the field -|_ `...poly([coeffs], duration).var`: To address an atom at a specific location via index -|_ `...poly([coeffs], duration).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by - specifying a single variable and then assigning + - |_ To address multiple atoms at specific locations by + specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...poly([coeffs], duration).assign(variable_name = value)`: + - |_ `...poly([coeffs], duration).assign(variable_name = value)`: to assign a single value to a variable - |_ `...poly([coeffs], duration) - .batch_assign(variable_name = [value1, ...])`: + .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...poly([coeffs], duration).args(["previously_defined_var"])`: + - |_ `...poly([coeffs], duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...poly([coeffs], duration).braket`: + - |_ `...poly([coeffs], duration).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...poly([coeffs], duration).bloqade`: + - |_ `...poly([coeffs], duration).bloqade`: to run on the Bloqade local emulator - - |_ `...poly([coeffs], duration).device`: + - |_ `...poly([coeffs], duration).device`: to specify the backend via string - - Choose to parallelize your atom geometry, + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - |_ `...poly([coeffs], duration).parallelize(spacing)` - Start targeting another level coupling - - |_ `...poly([coeffs], duration).rydberg`: + - |_ `...poly([coeffs], duration).rydberg`: to target the Rydberg level coupling - - |_ `...poly([coeffs], duration).hyperfine`: + - |_ `...poly([coeffs], duration).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...poly([coeffs], duration).amplitude`: + - |_ `...poly([coeffs], duration).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...poly([coeffs], duration).phase`: + - |_ `...poly([coeffs], duration).phase`: to target the real-valued Rabi Phase field - - |_ `...poly([coeffs], duration).detuning`: + - |_ `...poly([coeffs], duration).detuning`: to target the Detuning field - - |_ `...poly([coeffs], duration).rabi`: + - |_ `...poly([coeffs], duration).rabi`: to target the complex-valued Rabi field Usage Example: @@ -276,75 +276,75 @@ def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": @beartype def apply(self, wf: ir.Waveform) -> "Apply": """ - - Apply a [bloqade.ir.control.Waveform] built previously to + - Apply a [bloqade.ir.control.Waveform] built previously to current location(s) - You can build waveforms outside of the main program with - - If you specified a spatial modulation (e.g. `uniform`, + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", - one or a sum of drives creating a "field" + - You will now complete the construction of a "drive", + one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous + - You will now be appending this waveform to that previous waveform or other options below: - You can: - Continue building your waveform via: - |_ `...apply(waveform).linear(start, stop, duration)`: to append another linear waveform - - |_ `...apply(waveform).constant(value, duration)`: + - |_ `...apply(waveform).constant(value, duration)`: to append a constant waveform - - |_ `...apply(waveform).piecewise_linear()`: + - |_ `...apply(waveform).piecewise_linear()`: to append a piecewise linear waveform - - |_ `...apply(waveform).piecewise_constant()`: + - |_ `...apply(waveform).piecewise_constant()`: to append a piecewise constant waveform - - |_ `...apply(waveform).poly([coefficients], duration)`: + - |_ `...apply(waveform).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...apply(waveform).apply(waveform)`: + - |_ `...apply(waveform).apply(waveform)`: to append a pre-defined waveform - - |_ `...apply(waveform).fn(f(t,...))`: + - |_ `...apply(waveform).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...apply(waveform).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...apply(waveform).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): -|_ `...apply(waveform).uniform`: To address all atoms in the field - -|_ `...apply(waveform).var`: + -|_ `...apply(waveform).var`: To address an atom at a specific location via index -|_ `...apply(waveform).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying a + - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...apply(waveform).assign(variable_name = value)`: + - |_ `...apply(waveform).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...apply(waveform).batch_assign(variable_name = [value1, ...])`: + - |_ `...apply(waveform).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...apply(waveform).args(["previously_defined_var"])`: + - |_ `...apply(waveform).args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...apply(waveform).braket`: + - |_ `...apply(waveform).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...apply(waveform).bloqade`: + - |_ `...apply(waveform).bloqade`: to run on the Bloqade local emulator - - |_ `...apply(waveform).device`: + - |_ `...apply(waveform).device`: to specify the backend via string - - Choose to parallelize your atom geometry, + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - |_ `...apply(waveform).parallelize(spacing)` - Start targeting another level coupling - |_ `...apply(waveform).rydberg`: to target the Rydberg level coupling - |_ `...apply(waveform).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...apply(waveform).amplitude`: + - |_ `...apply(waveform).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...apply(waveform).phase`: + - |_ `...apply(waveform).phase`: to target the real-valued Rabi Phase field - - |_ `...apply(waveform).detuning`: + - |_ `...apply(waveform).detuning`: to target the Detuning field - - |_ `...apply(waveform).rabi`: + - |_ `...apply(waveform).rabi`: to target the complex-valued Rabi field Usage Example: @@ -352,7 +352,7 @@ def apply(self, wf: ir.Waveform) -> "Apply": >>> prog = start.add_position((0,0)).rydberg.detuning.uniform # build our waveform independently of the main program >>> from bloqade import piecewise_linear - >>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3], + >>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3], values=[0.0, 2.0, 2.0, 0.0]) >>> prog.apply(wf) ``` @@ -364,86 +364,86 @@ def piecewise_linear( self, durations: List[ScalarType], values: List[ScalarType] ) -> "PiecewiseLinear": """ - - Append or assign a piecewise linear waveform to current location(s), - where the waveform is formed by connecting `values[i], values[i+1]` + - Append or assign a piecewise linear waveform to current location(s), + where the waveform is formed by connecting `values[i], values[i+1]` with linear segments. - The `durations` argument should have number of elements = len(values) - 1. - - `durations` should be the duration + - `durations` should be the duration PER section of the waveform, NON-CUMULATIVE. - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", - one or a sum of drives creating a "field" + - You will now complete the construction of a "drive", + one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous + - You will now be appending this waveform to that previous waveform or other options below: - You can now: - Continue building your waveform via: - |_ `...piecewise_linear(durations, values) - .linear(start, stop, duration)`: + .linear(start, stop, duration)`: to append another linear waveform - - |_ `...piecewise_linear(durations, values).constant(value, duration)`: + - |_ `...piecewise_linear(durations, values).constant(value, duration)`: to append a constant waveform - |_ `...piecewise_linear(durations, values) - .piecewise_linear(durations, values)`: + .piecewise_linear(durations, values)`: to append a piecewise linear waveform - |_ `...piecewise_linear(durations, values) - .piecewise_constant(durations, values)`: + .piecewise_constant(durations, values)`: to append a piecewise constant waveform - |_ `...piecewise_linear(durations, values) .poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...piecewise_linear(durations, values).apply(waveform)`: + - |_ `...piecewise_linear(durations, values).apply(waveform)`: to append a pre-defined waveform - - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: + - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...piecewise_linear(durations, values).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_linear(durations, values).uniform`: + -|_ `...piecewise_linear(durations, values).uniform`: To address all atoms in the field - -|_ `...piecewise_linear(durations, values).var`: + -|_ `...piecewise_linear(durations, values).var`: To address an atom at a specific location via index -|_ `...piecewise_linear(durations, values).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by - specifying a single variable and then assigning it a + - |_ To address multiple atoms at specific locations by + specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - |_ `...piecewise_linear(durations, values) - .assign(variable_name = value)`: + .assign(variable_name = value)`: to assign a single value to a variable - |_ `...piecewise_linear(durations, values) - .batch_assign(variable_name = [value1, ...])`: + .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - |_ `...piecewise_linear(durations, values) - .args(["previously_defined_var"])`: + .args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_linear(durations, values).braket`: + - |_ `...piecewise_linear(durations, values).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_linear(durations, values).bloqade`: + - |_ `...piecewise_linear(durations, values).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_linear(durations, values).device`: + - |_ `...piecewise_linear(durations, values).device`: to specify the backend via string - - Choose to parallelize your atom geometry, + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - |_ `...piecewise_linear(durations, values).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_linear(durations, values).rydberg`: + - |_ `...piecewise_linear(durations, values).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_linear(durations, values).hyperfine`: + - |_ `...piecewise_linear(durations, values).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_linear(durations, values).amplitude`: + - |_ `...piecewise_linear(durations, values).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_linear(durations, values).phase`: + - |_ `...piecewise_linear(durations, values).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_linear(durations, values).detuning`: + - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field - |_ `....rabi`: to target the complex-valued Rabi field @@ -453,7 +453,7 @@ def piecewise_linear( # ramp our waveform up to a certain value, hold it # then ramp down. In this case, we ramp up to 2.0 rad/us in 0.3 us, # then hold it for 1.5 us before ramping down in 0.3 us back to 0.0 rad/us. - >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], + >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[0.0, 2.0, 2.0, 0.0]) ``` """ @@ -466,14 +466,14 @@ def piecewise_constant( """ - Append or assign a piecewise constant waveform to current location(s). - The `durations` argument should have number of elements = len(values). - - `durations` should be the duration PER section of the waveform, + - `durations` should be the duration PER section of the waveform, NON-CUMULATIVE. - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of + - You will now complete the construction of a "drive", one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous + - You will now be appending this waveform to that previous waveform or other options below: - You can now: - Continue building your waveform via: @@ -482,63 +482,63 @@ def piecewise_constant( - |_ `...piecewise_linear(durations, values) .constant(value, duration)`: to append a constant waveform - |_ `...piecewise_linear(durations, values) - .piecewise_linear(durations, values)`: + .piecewise_linear(durations, values)`: to append a piecewise linear waveform - |_ `...piecewise_linear(durations, values) - .piecewise_constant(durations, values)`: + .piecewise_constant(durations, values)`: to append a piecewise constant waveform - |_ `...piecewise_linear(durations, values) .poly([coefficients], duration)`: to append a polynomial waveform - |_ `...piecewise_linear(durations, values) .apply(waveform)`: to append a pre-defined waveform - - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: + - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...piecewise_linear(durations, values).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_linear(durations, values).uniform`: + -|_ `...piecewise_linear(durations, values).uniform`: To address all atoms in the field - -|_ `...piecewise_linear(durations, values).var`: + -|_ `...piecewise_linear(durations, values).var`: To address an atom at a specific location via index -|_ `...piecewise_linear(durations, values).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by - specifying a single variable and then assigning it a + - |_ To address multiple atoms at specific locations by + specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - |_ `...piecewise_linear(durations, values) .assign(variable_name = value)`: to assign a single value to a variable - |_ `...piecewise_linear(durations, values) - .batch_assign(variable_name = [value1, ...])`: + .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - |_ `...piecewise_linear(durations, values) .args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_linear(durations, values).braket`: + - |_ `...piecewise_linear(durations, values).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_linear(durations, values).bloqade`: + - |_ `...piecewise_linear(durations, values).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_linear(durations, values).device`: + - |_ `...piecewise_linear(durations, values).device`: to specify the backend via string - - Choose to parallelize your atom geometry, + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - |_ `...piecewise_linear(durations, values).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_linear(durations, values).rydberg`: + - |_ `...piecewise_linear(durations, values).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_linear(durations, values).hyperfine`: + - |_ `...piecewise_linear(durations, values).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_linear(durations, values).amplitude`: + - |_ `...piecewise_linear(durations, values).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_linear(durations, values).phase`: + - |_ `...piecewise_linear(durations, values).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_linear(durations, values).detuning`: + - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field - |_ `....rabi`: to target the complex-valued Rabi field @@ -555,18 +555,18 @@ def piecewise_constant( def fn(self, fn: Callable, duration: ScalarType) -> "Fn": """ - Append or assign a custom function as a waveform. - - The function must have its first argument be that of time but - can also have other arguments which are treated as variables + - The function must have its first argument be that of time but + can also have other arguments which are treated as variables you can assign values to later in the program via `.assign` or `.batch_assign`. - The function must return a singular float value - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) + - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", - one or a sum of drives creating a "field" + - You will now complete the construction of a "drive", + one or a sum of drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform + - You will now be appending this waveform to that previous waveform or other options below: - You can now: - Continue building your waveform via: @@ -575,10 +575,10 @@ def fn(self, fn: Callable, duration: ScalarType) -> "Fn": - |_ `...piecewise_linear(durations, values) .constant(value, duration)`: to append a constant waveform - |_ `...piecewise_linear(durations, values) - .piecewise_linear(durations, values)`: + .piecewise_linear(durations, values)`: to append a piecewise linear waveform - |_ `...piecewise_linear(durations, values) - .piecewise_constant(durations, values)`: + .piecewise_constant(durations, values)`: to append a piecewise constant waveform - |_ `...piecewise_linear(durations, values) .poly([coefficients], duration)`: to append a polynomial waveform @@ -590,50 +590,50 @@ def fn(self, fn: Callable, duration: ScalarType) -> "Fn": - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - |_ `...piecewise_linear(durations, values).record("you_variable_here")` - - Begin constructing another drive by starting a new spatial modulation + - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_linear(durations, values).uniform`: + -|_ `...piecewise_linear(durations, values).uniform`: To address all atoms in the field - -|_ `...piecewise_linear(durations, values).var`: + -|_ `...piecewise_linear(durations, values).var`: To address an atom at a specific location via index -|_ `...piecewise_linear(durations, values).detuning` - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by - specifying a single variable and then assigning it a + - |_ To address multiple atoms at specific locations by + specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - |_ `...piecewise_linear(durations, values) .assign(variable_name = value)`: to assign a single value to a variable - |_ `...piecewise_linear(durations, values) - .batch_assign(variable_name = [value1, ...])`: + .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - |_ `...piecewise_linear(durations, values) - .args(["previously_defined_var"])`: + .args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_linear(durations, values).braket`: + - |_ `...piecewise_linear(durations, values).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_linear(durations, values).bloqade`: + - |_ `...piecewise_linear(durations, values).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_linear(durations, values).device`: + - |_ `...piecewise_linear(durations, values).device`: to specify the backend via string - - Choose to parallelize your atom geometry, + - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - |_ `...piecewise_linear(durations, values).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_linear(durations, values).rydberg`: + - |_ `...piecewise_linear(durations, values).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_linear(durations, values).hyperfine`: + - |_ `...piecewise_linear(durations, values).hyperfine`: to target the Hyperfine level coupling - - Start targeting other fields within your current level coupling + - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_linear(durations, values).amplitude`: + - |_ `...piecewise_linear(durations, values).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_linear(durations, values).phase`: + - |_ `...piecewise_linear(durations, values).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_linear(durations, values).detuning`: + - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field - - |_ `...piecewise_linear(durations, values).rabi`: + - |_ `...piecewise_linear(durations, values).rabi`: to target the complex-valued Rabi field Usage Example: From f8a1391856f849ea5652e8177697c37f3f4d9984 Mon Sep 17 00:00:00 2001 From: John Long Date: Sat, 30 Sep 2023 22:45:22 -0400 Subject: [PATCH 11/20] finished rabi amplitude/phase docstrings --- src/bloqade/builder/field.py | 108 +++++++++++++---------------------- 1 file changed, 39 insertions(+), 69 deletions(-) diff --git a/src/bloqade/builder/field.py b/src/bloqade/builder/field.py index 443368c28..44dff4ef1 100644 --- a/src/bloqade/builder/field.py +++ b/src/bloqade/builder/field.py @@ -112,19 +112,19 @@ def var(self, name: str): ``` - You can now do: - - |_ `...location(int).linear(start, stop, duration)` : to apply + - |_ `...var(str).linear(start, stop, duration)` : to apply a linear waveform - - |_ `...location(int).constant(value, duration)` : to apply + - |_ `...var(str).constant(value, duration)` : to apply a constant waveform - - |_ `...location(int).poly([coefficients], duration)` : to apply + - |_ `...var(str).poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply + - |_ `...var(str).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...location(int).piecewise_linear(durations, values)`: to + - |_ `...var(str).piecewise_linear(durations, values)`: to apply a piecewise linear waveform - - |_ `...location(int).piecewise_constant(durations, values)`: to + - |_ `...var(str).piecewise_constant(durations, values)`: to apply a piecewise constant waveform - - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform + - |_ `...var(str).fn(f(t,..))`: to apply a function as a waveform """ from bloqade.builder.spatial import Var @@ -193,74 +193,44 @@ class Rabi(Builder): @property def amplitude(self) -> "RabiAmplitude": """ - - Specify the real-valued Rabi Amplitude field. - - Next steps - """ - """ - - Specify the amplitude of the rabi field. - - Next-step: - - Possible Next: - - -> `...amplitude.location(int)` - :: Address atom at specific location - - -> `...amplitude.uniform` - :: Address all atoms in register - - -> `...amplitude.var(str)` - :: Address atom at location labeled by variable - - - Examples: - - - rydberg coupling rabi amplitude - (See also [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude]) - - >>> ryd_rabi = bloqade.start.rydberg.rabi - >>> ryd_rabi_amp = ryd_rabi.amplitude - - - - hyperfine coupling rabi amplitude - (See also [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude]) - - >>> hyp_rabi = bloqade.start.hyperfine.rabi - >>> hyp_rabi_amp = hyp_rabi.amplitude - + Specify the real-valued Rabi Amplitude field. + + Next steps to build your program focus on specifying a spatial + modulation. + + The spatial modulation, when coupled with a waveform, completes the + specification of a "Drive". One or more drives can be summed together + automatically to create a field such as the Rabi Amplitude here. + + - You can now + - |_ `...amplitude.uniform`: address all atoms in the field + - |_ `...amplitude.location(int)`: address a specific atom by its + index + - |_ `...amplitude.var(str)`: Address a single atom + (or multiple via assigning a list of values) + """ return RabiAmplitude(self) @property def phase(self) -> "RabiPhase": """ - - Specify the phase of the rabi field. - - Next-step: - - Possible Next: - - -> `...phase.location(int)` - :: Address atom at specific location - - -> `...phase.uniform` - :: Address all atoms in register - - -> `...phase.var(str)` - :: Address atom at location labeled by variable - - - Examples: - - - rydberg coupling rabi phase - (See also [`RabiPhase`][bloqade.builder.field.RabiPhase]) - - >>> ryd_rabi = bloqade.start.rydberg.rabi - >>> ryd_rabi_ph = ryd_rabi.phase - - - - hyperfine coupling rabi phase - (See also [`RabiPhase`][bloqade.builder.field.RabiPhase]) - - >>> hyp_rabi = bloqade.start.hyperfine.rabi - >>> hyp_rabi_ph = hyp_rabi.phase - + Specify the real-valued Rabi Phase field. + + Next steps to build your program focus on specifying a spatial + modulation. + + The spatial modulation, when coupled with a waveform, completes the + specification of a "Drive". One or more drives can be summed together + automatically to create a field such as the Rabi Phase here. + + - You can now + - |_ `...amplitude.uniform`: address all atoms in the field + - |_ `...amplitude.location(int)`: address a specific atom by its + index + - |_ `...amplitude.var(str)`: Address a single atom + (or multiple via assigning a list of values) + """ return RabiPhase(self) From 430845c2177d7d3a6532bba207593fef3ee0ce3b Mon Sep 17 00:00:00 2001 From: John Long Date: Sat, 30 Sep 2023 22:46:58 -0400 Subject: [PATCH 12/20] fix lint --- src/bloqade/builder/field.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/bloqade/builder/field.py b/src/bloqade/builder/field.py index 44dff4ef1..7c96ab69e 100644 --- a/src/bloqade/builder/field.py +++ b/src/bloqade/builder/field.py @@ -197,8 +197,8 @@ def amplitude(self) -> "RabiAmplitude": Next steps to build your program focus on specifying a spatial modulation. - - The spatial modulation, when coupled with a waveform, completes the + + The spatial modulation, when coupled with a waveform, completes the specification of a "Drive". One or more drives can be summed together automatically to create a field such as the Rabi Amplitude here. @@ -206,9 +206,9 @@ def amplitude(self) -> "RabiAmplitude": - |_ `...amplitude.uniform`: address all atoms in the field - |_ `...amplitude.location(int)`: address a specific atom by its index - - |_ `...amplitude.var(str)`: Address a single atom + - |_ `...amplitude.var(str)`: Address a single atom (or multiple via assigning a list of values) - + """ return RabiAmplitude(self) @@ -219,8 +219,8 @@ def phase(self) -> "RabiPhase": Next steps to build your program focus on specifying a spatial modulation. - - The spatial modulation, when coupled with a waveform, completes the + + The spatial modulation, when coupled with a waveform, completes the specification of a "Drive". One or more drives can be summed together automatically to create a field such as the Rabi Phase here. @@ -228,9 +228,9 @@ def phase(self) -> "RabiPhase": - |_ `...amplitude.uniform`: address all atoms in the field - |_ `...amplitude.location(int)`: address a specific atom by its index - - |_ `...amplitude.var(str)`: Address a single atom + - |_ `...amplitude.var(str)`: Address a single atom (or multiple via assigning a list of values) - + """ return RabiPhase(self) From 6fe36691fff55dc58cdd5df3b9ebb8f06a8c2450 Mon Sep 17 00:00:00 2001 From: John Long Date: Sat, 30 Sep 2023 23:41:35 -0400 Subject: [PATCH 13/20] Completed waveform.py builder docstrings --- src/bloqade/builder/waveform.py | 608 ++++++++++++++++++-------------- 1 file changed, 341 insertions(+), 267 deletions(-) diff --git a/src/bloqade/builder/waveform.py b/src/bloqade/builder/waveform.py index 36fe58db3..acf6f788c 100644 --- a/src/bloqade/builder/waveform.py +++ b/src/bloqade/builder/waveform.py @@ -15,15 +15,25 @@ def linear( self, start: ScalarType, stop: ScalarType, duration: ScalarType ) -> "Linear": """ - - Append or assign a linear waveform to the current location(s) - - If you specified a spatial modulation (e.g. `uniform`, `location`, - `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of - drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform - or other options below: - - You can: + + Append or assign a linear waveform to the current location(s). + + If you specified a spatial modulation (e.g. `uniform`, `location`,`var`) + previously without a waveform you will now have completed the construction + of a "drive", one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase). + + If you have already specified a waveform previously you will now be appending + this waveform to that previous waveform. + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + # apply a linear waveform that goes from 0 to 1 radians/us in 0.5 us + >>> prog.linear(start=0,stop=1,duration=0.5) + ``` + + - Your next steps include: - Continue building your waveform via: - |_ `...linear(start, stop, duration).linear(start, stop, duration)`: to append another linear waveform @@ -47,9 +57,9 @@ def linear( (this drive will be summed to the one you just created): - |_ `...linear(start, stop, duration).uniform`: To address all atoms in the field - - |_ `...linear(start, stop, duration).var`: + - |_ `...linear(start, stop, duration).location(int)`: To address an atom at a specific location via index - - |_ `...linear(start, stop, duration).detuning` + - |_ `...linear(start, stop, duration).var(str)` - |_ To address an atom at a specific location via variable - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates @@ -86,12 +96,6 @@ def linear( to target the Detuning field - |_ `...linear(start, stop, duration).rabi`: to target the complex-valued Rabi field - - - Usage Example: - >>> prog = start.add_position((0,0)).rydberg.detuning.uniform - # apply a linear waveform that goes from 0 to 1 radians/us in 0.5 us - >>> prog.linear(start=0,stop=1,duration=0.5) """ return Linear(start, stop, duration, self) @@ -99,16 +103,24 @@ def linear( @beartype def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": """ - - Append or assign a constant waveform to the current location(s) - - If you specified a spatial modulation (e.g. `uniform`, `location`, - `var`) previously without a waveform: - - You will now complete the construction of a "drive", one or - a sum of drives creating a "field" (e.g. Real-valued Rabi - Amplitude/Phase) - - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous - waveform or other options below: - - You can: + Append or assign a constant waveform to the current location(s). + + If you specified a spatial modulation (e.g. `uniform`, `location`,`var`) + previously without a waveform you will now have completed the construction + of a "drive", one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase). + + If you have already specified a waveform previously you will now be appending + this waveform to that previous waveform. + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + # apply a constant waveform of 1.9 radians/us for 0.5 us + >>> prog.constant(value=1.9,duration=0.5) + ``` + + - Your next steps include: - Continue building your waveform via: - |_ `...constant(value, duration).linear(start, stop, duration)`: to append another linear waveform @@ -172,31 +184,40 @@ def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": - |_ `...constant(value, duration).rabi`: to target the complex-valued Rabi field - - Usage Example: - >>> prog = start.add_position((0,0)).rydberg.detuning.uniform - # apply a constant waveform of 1.9 radians/us for 0.5 us - >>> prog.constant(value=1.9,duration=0.5) """ return Constant(value, duration, self) @beartype def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": """ - - Append or assign a waveform with a polynomial profile to current location(s) - - You pass in a list of coefficients and a duration to this method - which obeys the following expression: - ` - wv(t) = coeffs[0] + coeffs[1]*t + coeffs[2]*t^2 + ... + coeffs[n]*t^n - ` - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) - previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of - drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform - or other options below: - - You can: + Append or assign a waveform with a polynomial profile to current location(s). + + You pass in a list of coefficients and a duration to this method which obeys + the following expression: + + ` + wv(t) = coeffs[0] + coeffs[1]*t + coeffs[2]*t^2 + ... + coeffs[n]*t^n + ` + + If you specified a spatial modulation (e.g. `uniform`, `location`,`var`) + previously without a waveform you will now have completed the construction + of a "drive", one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase). + + If you have already specified a waveform previously you will now be appending + this waveform to that previous waveform. + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + >>> coeffs = [-1, 0.5, 1.2] + # resulting polynomial is: + # f(t) = -1 + 0.5*t + 1.2*t^2 with duration of + # 0.5 us + >>> prog.poly(coeffs, duration=0.5) + ``` + + - Your next steps include: - Continue building your waveform via: - |_ `...poly([coeffs], duration).linear(start, stop, duration)`: to append another linear waveform @@ -220,9 +241,9 @@ def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": (this drive will be summed to the one you just created): -|_ `...poly([coeffs], duration).uniform`: To address all atoms in the field - -|_ `...poly([coeffs], duration).var`: + -|_ `...poly([coeffs], duration).location(int)`: To address an atom at a specific location via index - -|_ `...poly([coeffs], duration).detuning` + -|_ `...poly([coeffs], duration).var(str)` - |_ To address an atom at a specific location via variable - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning @@ -260,34 +281,34 @@ def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": to target the Detuning field - |_ `...poly([coeffs], duration).rabi`: to target the complex-valued Rabi field - - Usage Example: - ``` - >>> prog = start.add_position((0,0)).rydberg.detuning.uniform - >>> coeffs = [-1, 0.5, 1.2] - # resulting polynomial is: - # f(t) = -1 + 0.5*t + 1.2*t^2 with duration of - # 0.5 us - >>> prog.poly(coeffs, duration=0.5) - ``` """ return Poly(coeffs, duration, self) @beartype def apply(self, wf: ir.Waveform) -> "Apply": """ - - Apply a [bloqade.ir.control.Waveform] built previously to - current location(s) - - You can build waveforms outside of the main program with - - If you specified a spatial modulation (e.g. `uniform`, - `location`, `var`) previously without a waveform: - - You will now complete the construction of a "drive", - one or a sum of drives creating a "field" - (e.g. Real-valued Rabi Amplitude/Phase) - - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous - waveform or other options below: - - You can: + Apply a [`Waveform`][bloqade.ir.control.Waveform] built previously to + current location(s). + + If you specified a spatial modulation (e.g. `uniform`, `location`,`var`) + previously without a waveform you will now have completed the construction + of a "drive", one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase). + + If you have already specified a waveform previously you will now be appending + this waveform to that previous waveform. + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + # build our waveform independently of the main program + >>> from bloqade import piecewise_linear + >>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3], + values=[0.0, 2.0, 2.0, 0.0]) + >>> prog.apply(wf) + ``` + + - Your next steps include: - Continue building your waveform via: - |_ `...apply(waveform).linear(start, stop, duration)`: to append another linear waveform @@ -310,9 +331,9 @@ def apply(self, wf: ir.Waveform) -> "Apply": - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): -|_ `...apply(waveform).uniform`: To address all atoms in the field - -|_ `...apply(waveform).var`: + -|_ `...apply(waveform).location(int)`: To address an atom at a specific location via index - -|_ `...apply(waveform).detuning` + -|_ `...apply(waveform).var(str)` - |_ To address an atom at a specific location via variable - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates @@ -346,16 +367,6 @@ def apply(self, wf: ir.Waveform) -> "Apply": to target the Detuning field - |_ `...apply(waveform).rabi`: to target the complex-valued Rabi field - - Usage Example: - ``` - >>> prog = start.add_position((0,0)).rydberg.detuning.uniform - # build our waveform independently of the main program - >>> from bloqade import piecewise_linear - >>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3], - values=[0.0, 2.0, 2.0, 0.0]) - >>> prog.apply(wf) - ``` """ return Apply(wf, self) @@ -364,21 +375,32 @@ def piecewise_linear( self, durations: List[ScalarType], values: List[ScalarType] ) -> "PiecewiseLinear": """ - - Append or assign a piecewise linear waveform to current location(s), - where the waveform is formed by connecting `values[i], values[i+1]` - with linear segments. - - The `durations` argument should have number of elements = len(values) - 1. - - `durations` should be the duration - PER section of the waveform, NON-CUMULATIVE. - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) - previously without a waveform: - - You will now complete the construction of a "drive", - one or a sum of drives creating a "field" - (e.g. Real-valued Rabi Amplitude/Phase) - - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous - waveform or other options below: - - You can now: + Append or assign a piecewise linear waveform to current location(s), + where the waveform is formed by connecting `values[i], values[i+1]` + with linear segments. + + The `durations` argument should have # of elements = len(values) - 1. + `durations` should be the duration PER section of the waveform, NON-CUMULATIVE. + + If you specified a spatial modulation (e.g. `uniform`, `location`,`var`) + previously without a waveform you will now have completed the construction + of a "drive", one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase). + + If you have already specified a waveform previously you will now be appending + this waveform to that previous waveform. + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + # ramp our waveform up to a certain value, hold it + # then ramp down. In this case, we ramp up to 2.0 rad/us in 0.3 us, + # then hold it for 1.5 us before ramping down in 0.3 us back to 0.0 rad/us. + >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], + values=[0.0, 2.0, 2.0, 0.0]) + ``` + + - Your next steps include: - Continue building your waveform via: - |_ `...piecewise_linear(durations, values) .linear(start, stop, duration)`: @@ -446,16 +468,6 @@ def piecewise_linear( - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field - |_ `....rabi`: to target the complex-valued Rabi field - - Usage Example: - ``` - >>> prog = start.add_position((0,0)).rydberg.detuning.uniform - # ramp our waveform up to a certain value, hold it - # then ramp down. In this case, we ramp up to 2.0 rad/us in 0.3 us, - # then hold it for 1.5 us before ramping down in 0.3 us back to 0.0 rad/us. - >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], - values=[0.0, 2.0, 2.0, 0.0]) - ``` """ return PiecewiseLinear(durations, values, self) @@ -464,18 +476,29 @@ def piecewise_constant( self, durations: List[ScalarType], values: List[ScalarType] ) -> "PiecewiseConstant": """ - - Append or assign a piecewise constant waveform to current location(s). - - The `durations` argument should have number of elements = len(values). - - `durations` should be the duration PER section of the waveform, - NON-CUMULATIVE. - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) - previously without a waveform: - - You will now complete the construction of a "drive", one or a sum of - drives creating a "field" (e.g. Real-valued Rabi Amplitude/Phase) - - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous - waveform or other options below: - - You can now: + Append or assign a piecewise constant waveform to current location(s). + + The `durations` argument should have number of elements = len(values). + `durations` should be the duration PER section of the waveform, + NON-CUMULATIVE. + + If you specified a spatial modulation (e.g. `uniform`, `location`,`var`) + previously without a waveform you will now have completed the construction + of a "drive", one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase). + + If you have already specified a waveform previously you will now be appending + this waveform to that previous waveform. + + Usage Example: + ``` + >>> prog = start.add_position((0,0)).rydberg.rabi.phase.uniform + # create a staircase, we hold 0.0 rad/us for 1.0 us, then + # to 1.0 rad/us for 0.5 us before stopping at 0.8 rad/us for 0.9 us. + >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[1.0, 0.5, 0.9]) + ``` + + - Your next steps including: - Continue building your waveform via: - |_ `...piecewise_linear(durations, values) .linear(start, stop, duration)`: to append another linear waveform @@ -501,9 +524,9 @@ def piecewise_constant( (this drive will be summed to the one you just created): -|_ `...piecewise_linear(durations, values).uniform`: To address all atoms in the field - -|_ `...piecewise_linear(durations, values).var`: + -|_ `...piecewise_linear(durations, values).location(int)`: To address an atom at a specific location via index - -|_ `...piecewise_linear(durations, values).detuning` + -|_ `...piecewise_linear(durations, values).var(str)` - |_ To address an atom at a specific location via variable - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a @@ -541,34 +564,43 @@ def piecewise_constant( - |_ `...piecewise_linear(durations, values).detuning`: to target the Detuning field - |_ `....rabi`: to target the complex-valued Rabi field - - Usage Example: - ``` - >>> prog = start.add_position((0,0)).rydberg.rabi.phase.uniform - # create a staircase, we hold 0.0 rad/us for 1.0 us, then - # to 1.0 rad/us for 0.5 us before stopping at 0.8 rad/us for 0.9 us. - >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[1.0, 0.5, 0.9]) """ return PiecewiseConstant(durations, values, self) @beartype def fn(self, fn: Callable, duration: ScalarType) -> "Fn": """ - - Append or assign a custom function as a waveform. - - The function must have its first argument be that of time but - can also have other arguments which are treated as variables - you can assign values to later in the program via `.assign` - or `.batch_assign`. - - The function must return a singular float value - - If you specified a spatial modulation (e.g. `uniform`, `location`, `var`) - previously without a waveform: - - You will now complete the construction of a "drive", - one or a sum of drives creating a "field" - (e.g. Real-valued Rabi Amplitude/Phase) - - If you have already specified a waveform previously: - - You will now be appending this waveform to that previous waveform - or other options below: - - You can now: + Append or assign a custom function as a waveform. + + The function must have its first argument be that of time but + can also have other arguments which are treated as variables. + You can assign values to later in the program via `.assign` or `.batch_assign`. + + The function must also return a singular float value. + + If you specified a spatial modulation (e.g. `uniform`, `location`,`var`) + previously without a waveform you will now have completed the construction + of a "drive", one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase). + + If you have already specified a waveform previously you will now be appending + this waveform to that previous waveform. + + Usage Examples: + ``` + >>> prog = start.add_position((0,0)).rydberg.detuning.uniform + # define our custom waveform. It must have one argument + # be time followed by any other number of arguments that can + # be assigned a value later in the program via `.assign` or `.batch_assign` + >>> def custom_waveform_function(t, arg1, arg2): + return arg1*t + arg2 + >>> prog = prog.fn(custom_waveform_function, duration = 0.5) + # assign values + >>> assigned_vars_prog = prog.assign(arg1 = 1.0, arg2 = 2.0) + # or go for batching! + >>> assigned_vars_batch_prog = prog.assign(arg1 = 1.0, arg2 = [1.0, 2.0, 3.0]) + + - Your next steps include: - Continue building your waveform via: - |_ `...piecewise_linear(durations, values) .linear(start, stop, duration)`: to append another linear waveform @@ -636,19 +668,6 @@ def fn(self, fn: Callable, duration: ScalarType) -> "Fn": - |_ `...piecewise_linear(durations, values).rabi`: to target the complex-valued Rabi field - Usage Example: - ``` - >>> prog = start.add_position((0,0)).rydberg.detuning.uniform - # define our custom waveform. It must have one argument - # be time followed by any other number of arguments that can - # be assigned a value later in the program via `.assign` or `.batch_assign` - >>> def custom_waveform_function(t, arg1, arg2): - return arg1*t + arg2 - >>> prog = prog.fn(custom_waveform_function, duration = 0.5) - # assign values - >>> assigned_vars_prog = prog.assign(arg1 = 1.0, arg2 = 2.0) - # or go for batching! - >>> assigned_vars_batch_prog = prog.assign(arg1 = 1.0, arg2 = [1.0, 2.0, 3.0]) """ return Fn(fn, duration, self) @@ -668,65 +687,90 @@ def slice( stop: Optional[ScalarType] = None, ) -> "Slice": """ - Slice current waveform - - Possible Next: - - - Possible Next : - - -> `.location(int)` - :: creating new channel to address - another location(s) - - - Possible Next : - - -> `.record(str)` - :: record the value of waveform at current time - - - Possible Next : - - :: Append waveform into current channel - - -> `.linear()` - - -> `.constant()` - - -> `.ploy()` - - -> `.apply()` - - -> `.piecewise_linear()` - - -> `.piecewise_constant()` - - -> `.fn()` - - - Possible Next : - - -> `.rydberg` - :: Create/Switch to new rydberg level coupling channel - - -> `.hyperfine` - :: Create/Switch to new hyperfine level coupling channel + Indicate that you only want a portion of your waveform to be used in + the program. + If you specified a spatial modulation (e.g. `uniform`, `location`,`var`) + previously without a waveform you will now have completed the construction + of a "drive", one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase). - - Possible Next : + If you have already specified a waveform previously you will now be appending + this waveform to that previous waveform. - -> `.assign()` - :: assign varialbe an actual value/number - -> `.batch_assign()` - :: create batch job with different sets - of values assign to each variable. - - - - Possible Next : - - -> `.quera` - :: specify QuEra backend + Usage Example: + ``` + # define a program with a waveform of interest + >>> from bloqade import start + >>> prog = start.add_position((0,0)).rydberg.rabi.amplitude.uniform + >>> prog_with_wf = prog.piecewise_linear(durations=[0.3, 2.0, 0.3], + values=[0.0, 2.0, 2.0, 0.0]) + # instead of using the full waveform we opt to only take the first 1 us + >>> prog_with_slice = prog_with_wf.slice(0.0, 1.0) + # you may use variables as well + >>> prog_with_slice = prog_with_wf.slice("start", "end") + ``` - -> `.braket` - :: specify QuEra backend + - Your next steps include: + - Continue building your waveform via: + - |_ `...slice(start, stop).linear(start, stop, duration)`: + to append another linear waveform + - |_ `...slice(start, stop).constant(value, duration)`: + to append a constant waveform + - |_ `...slice(start, stop).piecewise_linear()`: + to append a piecewise linear waveform + - |_ `...slice(start, stop).piecewise_constant()`: + to append a piecewise constant waveform + - |_ `...slice(start, stop).poly([coefficients], duration)`: + to append a polynomial waveform + - |_ `...slice(start, stop).apply(wf:bloqade.ir.Waveform)`: + to append a pre-defined waveform + - |_ `...slilce(start, stop).fn(f(t,...))`: + to append a waveform defined by a python function + - Begin constructing another drive by starting a new spatial modulation + (this drive will be summed to the one you just created): + - |_ `...slice(start, stop).uniform`: + To address all atoms in the field + - |_ `...slice(start, stop).location(int)`: + To address an atom at a specific location via index + - |_ `...slice(start, stop).var(str)` + - |_ To address an atom at a specific location via variable + - |_ To address multiple atoms at specific locations by specifying + a single variable and then assigning it a list of coordinates + - Assign values to pre-existing variables via: + - |_ `...slice(start, stop).assign(variable_name = value)`: + to assign a single value to a variable + - |_ `...slice(start, stop) + .batch_assign(variable_name = [value1, ...])`: + to assign multiple values to a variable + - |_ `...slice(start, stop).args(["previously_defined_var"])`: + to defer assignment of a variable to execution time + - Select the backend you want your program to run on via: + - |_ `...slice(start, stop).braket`: + to run on Braket local emulator or QuEra hardware remotely + - |_ `...slice(start, stop).bloqade`: + to run on the Bloqade local emulator + - |_ `...slice(start, stop).device`: + to specify the backend via string + - Choose to parallelize your atom geometry, + duplicating it to fill the whole space: + - |_ `...slice(start, stop).parallelize(spacing)` + - Start targeting another level coupling + - |_ `...slice(start, stop).rydberg`: + to target the Rydberg level coupling + - |_ `...slice(start, stop).hyperfine`: + to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling + (previously selected as `rydberg` or `hyperfine`): + - |_ `...slice(start, stop).amplitude`: + to target the real-valued Rabi Amplitude field + - |_ `...slice(start, stop).phase`: + to target the real-valued Rabi Phase field + - |_ `...slice(start, stop).detuning`: + to target the Detuning field + - |_ `...slice(start, stop).rabi`: + to target the complex-valued Rabi field """ return Slice(start, stop, self) @@ -735,67 +779,97 @@ class Recordable: @beartype def record(self, name: str) -> "Record": """ - Record the value of the current waveform to a variable. - - Possible Next: - - - Possible Next : - - -> `.location(int)` - :: creating new channel to address - another location(s) - - - Possible Next : - - -> `.slice()` - :: slice the current waveform - - -> `.record(str)` - :: record the value of waveform at current time - - - Possible Next : - - :: Append waveform into current channel - - -> `.linear()` + Copy or "record" the value at the end of the waveform into a variable + so that it can be used in another place. - -> `.constant()` + A common design pattern is to couple this with `.slice()` considering + you may not know exactly what the end value of a `.slice()` is, + especially in parameter sweeps where it becomes cumbersome to handle. - -> `.ploy()` + If you specified a spatial modulation (e.g. `uniform`, `location`,`var`) + previously without a waveform you will now have completed the construction + of a "drive", one or a sum of drives creating a "field" + (e.g. Real-valued Rabi Amplitude/Phase). - -> `.apply()` - - -> `.piecewise_linear()` - - -> `.piecewise_constant()` - - -> `.fn()` - - - Possible Next : - - -> `.rydberg` - :: Create/Switch to new rydberg level coupling channel - - -> `.hyperfine` - :: Create/Switch to new hyperfine level coupling channel - - - Possible Next : - - -> `.assign()` - :: assign varialbe an actual value/number - - -> `.batch_assign()` - :: create batch job with different sets - of values assign to each variable. - - - Possible Next : - - -> `.quera` - :: specify QuEra backend - - -> `.braket` - :: specify QuEra backend + If you have already specified a waveform previously you will now be appending + this waveform to that previous waveform. + Usage Example: + ``` + # define program of interest + >>> from bloqade import start + >>> prog = start.rydberg.rabi.amplitude.uniform + >>> prog_with_wf = prog.piecewise_linear(durations=[0.3, 2.0, 0.3], + values=[0.0, 2.0, 2.0, 0.0]) + # We now slice the piecewise_linear from above and record the + # value at the end of that slice. We then use that value + # to construct a new waveform that can be appended to the previous + # one without introducing discontinuity (refer to the + # "Quantum Scar Dynamics" tutorial for how this could be handy) + >>> prog_with_record = prog_with_wf.slice(0.0, 1.0).record("end_of_wf") + >>> record_applied_prog = prog_with_record.linear(start="end_of_wf" + , stop=0.0, duration=0.3) + + - Your next steps include: + - Continue building your waveform via: + - |_ `...slice(start, stop).linear(start, stop, duration)`: + to append another linear waveform + - |_ `...slice(start, stop).constant(value, duration)`: + to append a constant waveform + - |_ `...slice(start, stop).piecewise_linear()`: + to append a piecewise linear waveform + - |_ `...slice(start, stop).piecewise_constant()`: + to append a piecewise constant waveform + - |_ `...slice(start, stop).poly([coefficients], duration)`: + to append a polynomial waveform + - |_ `...slice(start, stop).apply(wf:bloqade.ir.Waveform)`: + to append a pre-defined waveform + - |_ `...slilce(start, stop).fn(f(t,...))`: + to append a waveform defined by a python function + - Begin constructing another drive by starting a new spatial modulation + (this drive will be summed to the one you just created): + - |_ `...slice(start, stop).uniform`: + To address all atoms in the field + - |_ `...slice(start, stop).location(int)`: + To address an atom at a specific location via index + - |_ `...slice(start, stop).var(str)` + - |_ To address an atom at a specific location via variable + - |_ To address multiple atoms at specific locations by specifying + a single variable and then assigning it a list of coordinates + - Assign values to pre-existing variables via: + - |_ `...slice(start, stop).assign(variable_name = value)`: + to assign a single value to a variable + - |_ `...slice(start, stop) + .batch_assign(variable_name = [value1, ...])`: + to assign multiple values to a variable + - |_ `...slice(start, stop).args(["previously_defined_var"])`: + to defer assignment of a variable to execution time + - Select the backend you want your program to run on via: + - |_ `...slice(start, stop).braket`: + to run on Braket local emulator or QuEra hardware remotely + - |_ `...slice(start, stop).bloqade`: + to run on the Bloqade local emulator + - |_ `...slice(start, stop).device`: + to specify the backend via string + - Choose to parallelize your atom geometry, + duplicating it to fill the whole space: + - |_ `...slice(start, stop).parallelize(spacing)` + - Start targeting another level coupling + - |_ `...slice(start, stop).rydberg`: + to target the Rydberg level coupling + - |_ `...slice(start, stop).hyperfine`: + to target the Hyperfine level coupling + - Start targeting other fields within your current level coupling + (previously selected as `rydberg` or `hyperfine`): + - |_ `...slice(start, stop).amplitude`: + to target the real-valued Rabi Amplitude field + - |_ `...slice(start, stop).phase`: + to target the real-valued Rabi Phase field + - |_ `...slice(start, stop).detuning`: + to target the Detuning field + - |_ `...slice(start, stop).rabi`: + to target the complex-valued Rabi field + ``` """ return Record(name, self) From dfac2a9060020a542a2c6377645862c03e5e07b0 Mon Sep 17 00:00:00 2001 From: John Long Date: Sun, 1 Oct 2023 00:52:50 -0400 Subject: [PATCH 14/20] complete docstrings for Location based .location and .scale --- src/bloqade/builder/spatial.py | 258 ++++++++++++++------------------- 1 file changed, 109 insertions(+), 149 deletions(-) diff --git a/src/bloqade/builder/spatial.py b/src/bloqade/builder/spatial.py index d33e5abbf..e955309fe 100644 --- a/src/bloqade/builder/spatial.py +++ b/src/bloqade/builder/spatial.py @@ -52,57 +52,43 @@ def __init__(self, label: int, parent: Optional[Builder] = None) -> None: @beartype def location(self, label: int) -> "Location": """ - Append another location to the current location(s) - - Args: - label (int): The label of the location - - Examples: - - - Append location 1 to the current location 0. - - >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> loc = reg.rydberg.detuning.location(0) - >>> loc = loc.location(1) - - - One can keep appending by concatenating location() - - >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> loc = reg.rydberg.detuning.location(0) - >>> loc = loc.location(1).location(2) - - - Possible Next : - - -> `...location(int).location(int)` - :: keep adding location into current list - - -> `...location(int).scale(float)` - :: specify scaling factor to current location - for the preceeding waveform - - - Possible Next : - - -> `...location(int).linear()` - :: apply linear waveform - - -> `...location(int).constant()` - :: apply constant waveform - - -> `...location(int).ploy()` - :: apply polynomial waveform - - -> `...location(int).apply()` - :: apply pre-constructed waveform - - -> `...location(int).piecewise_linear()` - :: apply piecewise linear waveform - - -> `...location(int).piecewise_constant()` - :: apply piecewise constant waveform - - -> `...location(int).fn()` - :: apply callable as waveform. - + Append another `.location` to the current location(s) + as part of a singular spatial modulation definition. + + Usage Example: + ``` + # definep program + >>> from bloqade.atom_arrangement import start + >>> geometry = start.add_position([(0,0),(1,1),(3,3)]) + >>> prog = start.rydberg.rabi.amplitude.location(0) + >>> chain_loc_prog = prog.location(1).location(2) + # Atoms at indices 0, 1, and 2 will now be subject + # to the upcoming waveform definition. Thus, the + # multiple locations are part of a singular + # spatial modulation. + ``` + + - Your next steps include: + - Continuing to modify your current spatial modulation via: + - |_ `...location(int).location(int)`: To add another location + - |_ `...location(int).scale(float)`: To scale the upcoming waveform + - You may also jump directly to specifying a waveform via: + - |_ `...location(int).linear(start, stop, duration)`: + to append a linear waveform + - |_ `...location(int).constant(value, duration)`: + to append a constant waveform + - |_ `...location(int) + .piecewise_linear([durations], [values])`: + to append a piecewise linear waveform + - |_ `...location(int) + .piecewise_constant([durations], [values])`: + to append a piecewise constant waveform + - |_ `...location(int).poly([coefficients], duration)`: + to append a polynomial waveform + - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: + to append a pre-defined waveform + - |_ `...location(int).fn(f(t,...))`: + to append a waveform defined by a python function """ return Location(label, self) @@ -110,62 +96,42 @@ def location(self, label: int) -> "Location": @beartype def scale(self, value: ScalarType) -> "Scale": """ - Scale the preceeding waveform by the specified factor. - - Args: - scale (float): The factor to scale (amplitude of) - the preceeding waveform. - - Examples: - - - Scale the preceeding waveform that addressing location(0) by 1.2. - - >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> scaled = reg.rydberg.detuning.location(0).scale(1.2) - - - Scale multiple locations with different factors. - (ex. loc 0 by 1.2, loc 1 by 0.5) - - >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> loc = reg.rydberg.detuning.location(0) - >>> loc = loc.scale(1.2).location(1).scale(0.5) - - - Scale multiple locations with the same factor. (ex. loc 0 and 1 by 1.2) - - >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> scaled = reg.rydberg.detuning.location(0).location(1).scale(1.2) - - - - Possible Next : - - -> `...scale(float).location(int)` - :: keep adding location into current list - - - Possible Next : - - -> `...scale(float).linear()` - :: apply linear waveform - - -> `...scale(float).constant()` - :: apply constant waveform - - -> `...scale(float).ploy()` - :: apply polynomial waveform - - -> `...scale(float).apply()` - :: apply pre-constructed waveform(s) - - -> `...scale(float).piecewise_linear()` - :: apply piecewise linear waveform - - -> `...scale(float).piecewise_constant()` - :: apply piecewise constant waveform - - -> `...scale(float).fn()` - :: apply callable as waveform. - - + Scale the subsequent waveform to be applied on a certain set of + atoms specified by the current spatial modulation. + Usage Examples: + ``` + # define program + >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) + # scale the subsequent waveform to be applied on atom 0 by 1.2 + >>> scaled = reg.rydberg.detuning.location(0).scale(1.2) + # scale the waveform on multiple locations by different factors + >>> loc = reg.rydberg.detuning.location(0) + >>> loc = loc.scale(1.2).location(1).scale(0.5) + # scale multiple locations with the same factor + >>> scaled = reg.rydberg.detuning.location(0).location(1).scale(1.2) + ``` + + - Your next steps include: + - Continuing to modify your current spatial modulation via: + - |_ `...scale(float).location(int)`: To add another location + - You may also jump directly to specifying a waveform via: + - |_ `...scale(float).linear(start, stop, duration)`: + to append a linear waveform + - |_ `...scale(float).constant(value, duration)`: + to append a constant waveform + - |_ `...scale(float) + .piecewise_linear([durations], [values])`: + to append a piecewise linear waveform + - |_ `...scale(float) + .piecewise_constant([durations], [values])`: + to append a piecewise constant waveform + - |_ `...scale(float).poly([coefficients], duration)`: + to append a polynomial waveform + - |_ `...scale(float).apply(wf:bloqade.ir.Waveform)`: + to append a pre-defined waveform + - |_ `...scale(float).fn(f(t,...))`: + to append a waveform defined by a python function """ return Scale(value, self) @@ -183,50 +149,44 @@ def __init__(self, value: ScalarType, parent: Optional[Builder] = None) -> None: @beartype def location(self, label: int) -> "Location": """ - - Append another location to the current location after scale the previous one - - Args: - label (int): The label of the location - - Examples: - - - Append location 1 after scale location 0 by 1.2. - - >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) - >>> loc = reg.rydberg.detuning.location(0).scale(1.2) - >>> loc = loc.location(1) - - - Possible Next : - - -> `...location(int).location(int)` - :: keep adding location into current list - - -> `...location(int).scale(float)` - :: specify scaling factor to current location - for the preceeding waveform - - - Possible Next : - - -> `...location(int).linear()` - :: apply linear waveform - - -> `...location(int).constant()` - :: apply constant waveform - - -> `...location(int).ploy()` - :: apply polynomial waveform - - -> `...location(int).apply()` - :: apply pre-constructed waveform - - -> `...location(int).piecewise_linear()` - :: apply piecewise linear waveform - - -> `...location(int).piecewise_constant()` - :: apply piecewise constant waveform + Append another `.location` to the current location(s) + as part of a singular spatial modulation definition. + + Usage Example: + ``` + # definep program + >>> from bloqade.atom_arrangement import start + >>> geometry = start.add_position([(0,0),(1,1),(3,3)]) + >>> prog = start.rydberg.rabi.amplitude.location(0) + >>> chain_loc_prog = prog.location(1).location(2) + # Atoms at indices 0, 1, and 2 will now be subject + # to the upcoming waveform definition. Thus, the + # multiple locations are part of a singular + # spatial modulation. + ``` + + - Your next steps include: + - Continuing to modify your current spatial modulation via: + - |_ `...location(int).location(int)`: To add another location + - |_ `...location(int).scale(float)`: To scale the upcoming waveform + - You may also jump directly to specifying a waveform via: + - |_ `...location(int).linear(start, stop, duration)`: + to append a linear waveform + - |_ `...location(int).constant(value, duration)`: + to append a constant waveform + - |_ `...location(int) + .piecewise_linear([durations], [values])`: + to append a piecewise linear waveform + - |_ `...location(int) + .piecewise_constant([durations], [values])`: + to append a piecewise constant waveform + - |_ `...location(int).poly([coefficients], duration)`: + to append a polynomial waveform + - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: + to append a pre-defined waveform + - |_ `...location(int).fn(f(t,...))`: + to append a waveform defined by a python function - -> `...location(int).fn()` - :: apply callable as waveform. """ return Location(label, self) From 8e059c249384d469e473985eb07bef1b6d4c6ca8 Mon Sep 17 00:00:00 2001 From: John Long Date: Sun, 1 Oct 2023 00:53:34 -0400 Subject: [PATCH 15/20] fix trailing whitespace --- src/bloqade/builder/spatial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bloqade/builder/spatial.py b/src/bloqade/builder/spatial.py index e955309fe..52d18a448 100644 --- a/src/bloqade/builder/spatial.py +++ b/src/bloqade/builder/spatial.py @@ -96,7 +96,7 @@ def location(self, label: int) -> "Location": @beartype def scale(self, value: ScalarType) -> "Scale": """ - Scale the subsequent waveform to be applied on a certain set of + Scale the subsequent waveform to be applied on a certain set of atoms specified by the current spatial modulation. Usage Examples: From 551112734553714b32ec687e371d2b9302fd4048 Mon Sep 17 00:00:00 2001 From: John Long Date: Sun, 1 Oct 2023 01:12:02 -0400 Subject: [PATCH 16/20] Fixed waveforms docstrings, already issues with inconsistent formatting --- src/bloqade/builder/waveform.py | 183 +++++++++++++++++--------------- 1 file changed, 97 insertions(+), 86 deletions(-) diff --git a/src/bloqade/builder/waveform.py b/src/bloqade/builder/waveform.py index acf6f788c..7616db238 100644 --- a/src/bloqade/builder/waveform.py +++ b/src/bloqade/builder/waveform.py @@ -39,9 +39,11 @@ def linear( to append another linear waveform - |_ `...linear(start, stop, duration).constant(value, duration)`: to append a constant waveform - - |_ `...linear(start, stop, duration).piecewise_linear()`: + - |_ `...linear(start, stop, duration) + .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...linear(start, stop, duration).piecewise_constant()`: + - |_ `...linear(start, stop, duration) + .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - |_ `...linear(start, stop, duration).poly([coefficients], duration)`: to append a polynomial waveform @@ -126,9 +128,11 @@ def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": to append another linear waveform - |_ `...constant(value, duration).constant(value, duration)`: to append a constant waveform - - |_ `...constant(value, duration).piecewise_linear()`: + - |_ `...constant(value, duration) + .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...constant(value, duration).piecewise_constant()`: + - |_ `...constant(value, duration) + .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - |_ `...constant(value, duration).poly([coefficients], duration)`: to append a polynomial waveform @@ -146,7 +150,7 @@ def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": To address all atoms in the field -|_ `...constant(value, duration).var`: To address an atom at a specific location via index - -|_ `...constant(value, duration).detuning` + -|_ `...constant(value, duration).location(int)` - |_ To address an atom at a specific location via variable - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates @@ -223,9 +227,11 @@ def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": to append another linear waveform - |_ `...poly([coeffs], duration).constant(value, duration)`: to append a constant waveform - - |_ `...poly([coeffs], duration).piecewise_linear()`: + - |_ `...poly([coeffs], duration) + .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...poly([coeffs], duration).piecewise_constant()`: + - |_ `...poly([coeffs], duration) + .piecewise_constant([durations],[values])`: to append a piecewise constant waveform - |_ `...poly([coeffs], duration).poly([coefficients], duration)`: to append a polynomial waveform @@ -314,9 +320,9 @@ def apply(self, wf: ir.Waveform) -> "Apply": to append another linear waveform - |_ `...apply(waveform).constant(value, duration)`: to append a constant waveform - - |_ `...apply(waveform).piecewise_linear()`: + - |_ `...apply(waveform).piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...apply(waveform).piecewise_constant()`: + - |_ `...apply(waveform).piecewise_constant([durations], [values])`: to append a piecewise constant waveform - |_ `...apply(waveform).poly([coefficients], duration)`: to append a polynomial waveform @@ -402,70 +408,72 @@ def piecewise_linear( - Your next steps include: - Continue building your waveform via: - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_linear([durations], [values]) .linear(start, stop, duration)`: to append another linear waveform - - |_ `...piecewise_linear(durations, values).constant(value, duration)`: + - |_ `...piecewise_linear([durations], [values]).constant(value, duration)`: to append a constant waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_linear([durations], [values]) .piecewise_linear(durations, values)`: to append a piecewise linear waveform - - |_ `...piecewise_linear(durations, values) - .piecewise_constant(durations, values)`: + - |_ `...piecewise_linear([durations], [values]) + .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_linear([durations], [values]) .poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...piecewise_linear(durations, values).apply(waveform)`: + - |_ `...piecewise_linear([durations], [values]).apply(waveform)`: to append a pre-defined waveform - - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: + - |_ `...piecewise_linear([durations], [values]).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` + - |_ `...piecewise_linear([durations], [values]) + .slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - - |_ `...piecewise_linear(durations, values).record("you_variable_here")` + - |_ `...piecewise_linear([durations], [values]) + .record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_linear(durations, values).uniform`: + -|_ `...piecewise_linear([durations], [values]).uniform`: To address all atoms in the field - -|_ `...piecewise_linear(durations, values).var`: + -|_ `...piecewise_linear([durations], [values]).var`: To address an atom at a specific location via index - -|_ `...piecewise_linear(durations, values).detuning` + -|_ `...piecewise_linear([durations], [values]).location(int)` - |_ To address an atom at a specific location via variable - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_linear([durations], [values]) .assign(variable_name = value)`: to assign a single value to a variable - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_linear([durations], [values]) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_linear([durations], [values]) .args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_linear(durations, values).braket`: + - |_ `...piecewise_linear([durations], [values]).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_linear(durations, values).bloqade`: + - |_ `...piecewise_linear([durations], [values]).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_linear(durations, values).device`: + - |_ `...piecewise_linear([durations], [values]).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...piecewise_linear(durations, values).parallelize(spacing)` + - |_ `...piecewise_linear([durations], [values]).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_linear(durations, values).rydberg`: + - |_ `...piecewise_linear([durations], [values]).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_linear(durations, values).hyperfine`: + - |_ `...piecewise_linear([durations], [values]).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_linear(durations, values).amplitude`: + - |_ `...piecewise_linear([durations], [values]).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_linear(durations, values).phase`: + - |_ `...piecewise_linear([durations], [values]).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_linear(durations, values).detuning`: + - |_ `...piecewise_linear([durations], [values]).detuning`: to target the Detuning field - |_ `....rabi`: to target the complex-valued Rabi field """ @@ -500,70 +508,73 @@ def piecewise_constant( - Your next steps including: - Continue building your waveform via: - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_constant([durations], [values]) .linear(start, stop, duration)`: to append another linear waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_constant([durations], [values]) .constant(value, duration)`: to append a constant waveform - - |_ `...piecewise_linear(durations, values) - .piecewise_linear(durations, values)`: + - |_ `...piecewise_constant([durations], [values]) + .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...piecewise_linear(durations, values) - .piecewise_constant(durations, values)`: + - |_ `...piecewise_constant([durations], [values]) + .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_constant([durations], [values]) .poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_constant([durations], [values]) .apply(waveform)`: to append a pre-defined waveform - - |_ `...piecewise_linear(durations, values).fn(f(t,...))`: + - |_ `...piecewise_constant([durations], [values]).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` + - |_ `...piecewise_constant([durations], [values]) + .slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - - |_ `...piecewise_linear(durations, values).record("you_variable_here")` + - |_ `...piecewise_constant([durations], [values]) + .record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_linear(durations, values).uniform`: + -|_ `...piecewise_constant([durations], [values]).uniform`: To address all atoms in the field - -|_ `...piecewise_linear(durations, values).location(int)`: + -|_ `...piecewise_constant([durations], [values]).location(int)`: To address an atom at a specific location via index - -|_ `...piecewise_linear(durations, values).var(str)` + -|_ `...piecewise_constant([durations], [values]).var(str)` - |_ To address an atom at a specific location via variable - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_constant([durations], [values]) .assign(variable_name = value)`: to assign a single value to a variable - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_constant([durations], [values]) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...piecewise_linear(durations, values) + - |_ `...piecewise_constant([durations], [values]) .args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_linear(durations, values).braket`: + - |_ `...piecewise_constant([durations], [values]).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_linear(durations, values).bloqade`: + - |_ `...piecewise_constant([durations], [values]).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_linear(durations, values).device`: + - |_ `...piecewise_constant([durations], [values]).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...piecewise_linear(durations, values).parallelize(spacing)` + - |_ `...piecewise_constat([durations], [values]).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_linear(durations, values).rydberg`: + - |_ `...piecewise_constant([durations], [values]).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_linear(durations, values).hyperfine`: + - |_ `...piecewise_constant([durations], [values]).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_linear(durations, values).amplitude`: + - |_ `...piecewise_constant(durations, values).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_linear(durations, values).phase`: + - |_ `...piecewise_constant([durations], [values]).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_linear(durations, values).detuning`: + - |_ `...piecewise_constant([durations], [values]).detuning`: to target the Detuning field - - |_ `....rabi`: to target the complex-valued Rabi field + - |_ `...piecewise_constant([durations], [values]).rabi`: + to target the complex-valued Rabi field """ return PiecewiseConstant(durations, values, self) @@ -602,70 +613,70 @@ def fn(self, fn: Callable, duration: ScalarType) -> "Fn": - Your next steps include: - Continue building your waveform via: - - |_ `...piecewise_linear(durations, values) + - |_ `...fn(f(t,...)) .linear(start, stop, duration)`: to append another linear waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...fn(f(t,...)) .constant(value, duration)`: to append a constant waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...fn(f(t,...)) .piecewise_linear(durations, values)`: to append a piecewise linear waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...fn(f(t,...)) .piecewise_constant(durations, values)`: to append a piecewise constant waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...fn(f(t,...)) .poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...fn(f(t,...)) .apply(waveform)`: to append a pre-defined waveform - - |_ `...piecewise_linear(durations, values) + - |_ `...fn(f(t,...)) .fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - - |_ `...piecewise_linear(durations, values).slice(start, stop, duration)` + - |_ `...fn(f(t,...)).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - - |_ `...piecewise_linear(durations, values).record("you_variable_here")` + - |_ `...fn(f(t,...)).record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_linear(durations, values).uniform`: + -|_ `...fn(f(t,...)).uniform`: To address all atoms in the field - -|_ `...piecewise_linear(durations, values).var`: + -|_ `...fn(f(t,...)).var(str)`: To address an atom at a specific location via index - -|_ `...piecewise_linear(durations, values).detuning` + -|_ ...fn(f(t,...)).location(int)` - |_ To address an atom at a specific location via variable - |_ To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...piecewise_linear(durations, values) + - |_ `...fn(f(t,...)) .assign(variable_name = value)`: to assign a single value to a variable - - |_ `...piecewise_linear(durations, values) + - |_ `...fn(f(t,...)) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...piecewise_linear(durations, values) + - |_ `...fn(f(t,...)) .args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_linear(durations, values).braket`: + - |_ `...fn(f(t,...)).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_linear(durations, values).bloqade`: + - |_ `...fn(f(t,...)).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_linear(durations, values).device`: + - |_ `...fn(f(t,...)).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...piecewise_linear(durations, values).parallelize(spacing)` + - |_ `...fn(f(t,...)).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_linear(durations, values).rydberg`: + - |_ `...fn(f(t,...)).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_linear(durations, values).hyperfine`: + - |_ `...fn(f(t,...)).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_linear(durations, values).amplitude`: + - |_ `...fn(f(t,...)).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_linear(durations, values).phase`: + - |_ `...fn(f(t,...)).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_linear(durations, values).detuning`: + - |_ `...fn(f(t,...)).detuning`: to target the Detuning field - - |_ `...piecewise_linear(durations, values).rabi`: + - |_ `...fn(f(t,...)).rabi`: to target the complex-valued Rabi field """ From beb7d25a296c6ada34c695135a12241374388e4c Mon Sep 17 00:00:00 2001 From: John Long Date: Sun, 1 Oct 2023 01:16:49 -0400 Subject: [PATCH 17/20] Fix waveform docstrings (again), ensure mkdocs renders them properly --- src/bloqade/builder/waveform.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bloqade/builder/waveform.py b/src/bloqade/builder/waveform.py index 7616db238..92530bca6 100644 --- a/src/bloqade/builder/waveform.py +++ b/src/bloqade/builder/waveform.py @@ -610,6 +610,7 @@ def fn(self, fn: Callable, duration: ScalarType) -> "Fn": >>> assigned_vars_prog = prog.assign(arg1 = 1.0, arg2 = 2.0) # or go for batching! >>> assigned_vars_batch_prog = prog.assign(arg1 = 1.0, arg2 = [1.0, 2.0, 3.0]) + ``` - Your next steps include: - Continue building your waveform via: @@ -820,6 +821,7 @@ def record(self, name: str) -> "Record": >>> prog_with_record = prog_with_wf.slice(0.0, 1.0).record("end_of_wf") >>> record_applied_prog = prog_with_record.linear(start="end_of_wf" , stop=0.0, duration=0.3) + ``` - Your next steps include: - Continue building your waveform via: From fcd78a358714242f340b10bdc7b154215059f3d2 Mon Sep 17 00:00:00 2001 From: John Long Date: Tue, 3 Oct 2023 09:34:50 -0400 Subject: [PATCH 18/20] Revert back to bullet points from somewhat obnoxious tree printer style --- src/bloqade/builder/backend/bloqade.py | 6 +- src/bloqade/builder/backend/braket.py | 10 +- src/bloqade/builder/coupling.py | 14 +- src/bloqade/builder/drive.py | 8 +- src/bloqade/builder/field.py | 58 +-- src/bloqade/builder/pragmas.py | 28 +- src/bloqade/builder/spatial.py | 52 +-- src/bloqade/builder/start.py | 12 +- src/bloqade/builder/waveform.py | 478 ++++++++++++------------- src/bloqade/ir/location/list.py | 6 +- src/bloqade/ir/location/transform.py | 56 +-- 11 files changed, 364 insertions(+), 364 deletions(-) diff --git a/src/bloqade/builder/backend/bloqade.py b/src/bloqade/builder/backend/bloqade.py index 55d46abda..79db5d339 100644 --- a/src/bloqade/builder/backend/bloqade.py +++ b/src/bloqade/builder/backend/bloqade.py @@ -8,8 +8,8 @@ def bloqade(self): Specify the Bloqade backend. - Possible Next Steps: - - |_ `...bloqade.python()`: target submission to the Bloqade python backend - - |_ `...bloqade.julia()`: (CURRENTLY NOT IMPLEMENTED!)target + - `...bloqade.python()`: target submission to the Bloqade python backend + - `...bloqade.julia()`: (CURRENTLY NOT IMPLEMENTED!)target submission to the Bloqade.jl backend """ return BloqadeDeviceRoute(self) @@ -21,7 +21,7 @@ def python(self): Specify the Bloqade Python backend. - Possible Next Steps: - - |_ `...python().run(shots)`: + - `...python().run(shots)`: to submit to the python emulator and await results """ return self.parse().bloqade.python() diff --git a/src/bloqade/builder/backend/braket.py b/src/bloqade/builder/backend/braket.py index 554a4cf43..ecbdc8291 100644 --- a/src/bloqade/builder/backend/braket.py +++ b/src/bloqade/builder/backend/braket.py @@ -9,8 +9,8 @@ def braket(self): emulator OR go submit things to QuEra hardware on AWS Braket service. - Possible Next Steps are: - - |_ `...braket.aquila()`: target submission to the QuEra Aquila QPU - - |_ `...braket.local_emulator()`: target submission to the Braket + - `...braket.aquila()`: target submission to the QuEra Aquila QPU + - `...braket.local_emulator()`: target submission to the Braket local emulator """ return BraketDeviceRoute(self) @@ -29,9 +29,9 @@ def aquila(self): - Possible next steps are: - - |_ `...aquila().run(shots)`: To submit to hardware and WAIT for + - `...aquila().run(shots)`: To submit to hardware and WAIT for results (blocking) - - |_ `...aquila().run_async(shots)`: To submit to hardware and immediately + - `...aquila().run_async(shots)`: To submit to hardware and immediately allow for other operations to occur """ return self.parse().braket.aquila() @@ -46,7 +46,7 @@ def local_emulator(self): you have a variable with batch assignments/intend to conduct a parameter sweep - Possible next steps are: - - |_ `...local_emulator().run(shots)`: to submit to the emulator + - `...local_emulator().run(shots)`: to submit to the emulator and await results """ diff --git a/src/bloqade/builder/coupling.py b/src/bloqade/builder/coupling.py index 4011c1eb8..887e7d0cd 100644 --- a/src/bloqade/builder/coupling.py +++ b/src/bloqade/builder/coupling.py @@ -19,12 +19,12 @@ def detuning( able to specify a waveform. - You can do this by: - - |_ `...detuning.uniform`: To address all atoms in the field - - |_ `...detuning.location(int)`: To address an atom at a specific + - `...detuning.uniform`: To address all atoms in the field + - `...detuning.location(int)`: To address an atom at a specific location via index - - |_ `...detuning.var(str)` - - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying + - `...detuning.var(str)` + - To address an atom at a specific location via variable + - To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates """ @@ -42,8 +42,8 @@ def rabi(self) -> Rabi: - Next possible steps to build your program are creating the [`RabiAmplitude`][bloqade.builder.field.RabiAmplitude] field and [`RabiPhase`][bloqade.builder.field.RabiAmplitude] field of the field: - - |_ `...rabi.amplitude`: To create the Rabi amplitude field - - |_ `...rabi.phase`: To create the Rabi phase field + - `...rabi.amplitude`: To create the Rabi amplitude field + - `...rabi.phase`: To create the Rabi phase field """ diff --git a/src/bloqade/builder/drive.py b/src/bloqade/builder/drive.py index 9f659b6a9..8ab08ddf1 100644 --- a/src/bloqade/builder/drive.py +++ b/src/bloqade/builder/drive.py @@ -10,8 +10,8 @@ def rydberg(self) -> Rydberg: - Next possible steps to build your program are specifying the [`Rabi`][bloqade.builder.field.Rabi] field or [`Detuning`][bloqade.builder.field.Detuning] field. - - |_ `...rydberg.rabi`: for Rabi field - - |_ `...rydberg.detuning`: for Detuning field + - `...rydberg.rabi`: for Rabi field + - `...rydberg.detuning`: for Detuning field - In the absence of a field you the value is set to zero by default. ``` @@ -26,8 +26,8 @@ def hyperfine(self) -> Hyperfine: - Next possible steps to build your program are specifying the [`Rabi`][bloqade.builder.field.Rabi] field or [`Detuning`][bloqade.builder.field.Detuning] field. - - |_ `...hyperfine.rabi`: for Rabi field - - |_ `...hyperfine.detuning`: for Detuning field + - `...hyperfine.rabi`: for Rabi field + - `...hyperfine.detuning`: for Detuning field - In the absence of a field you the value is set to zero by default. ``` diff --git a/src/bloqade/builder/field.py b/src/bloqade/builder/field.py index 7c96ab69e..f2f31fca1 100644 --- a/src/bloqade/builder/field.py +++ b/src/bloqade/builder/field.py @@ -17,17 +17,17 @@ def uniform(self): (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.). - You can now do: - - |_ `...uniform.linear(start, stop, duration)` : to apply a linear waveform - - |_ `...uniform.constant(value, duration)` : to apply a constant waveform - - |_ `...uniform.poly([coefficients], duration)` : to apply a + - `...uniform.linear(start, stop, duration)` : to apply a linear waveform + - `...uniform.constant(value, duration)` : to apply a constant waveform + - `...uniform.poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...uniform.apply(wf:bloqade.ir.Waveform)`: to apply a + - `...uniform.apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...uniform.piecewise_linear([durations], [values])`: to apply + - `...uniform.piecewise_linear([durations], [values])`: to apply a piecewise linear waveform - - |_ `...uniform.piecewise_constant([durations], [values])`: to apply + - `...uniform.piecewise_constant([durations], [values])`: to apply a piecewise constant waveform - - |_ `...uniform.fn(f(t,...))`: to apply a function as a waveform + - `...uniform.fn(f(t,...))`: to apply a function as a waveform """ from bloqade.builder.spatial import Uniform @@ -57,26 +57,26 @@ def location(self, label: int): ``` - You can now do: - - |_ `...location(int).linear(start, stop, duration)` : to apply + - `...location(int).linear(start, stop, duration)` : to apply a linear waveform - - |_ `...location(int).constant(value, duration)` : to apply + - `...location(int).constant(value, duration)` : to apply a constant waveform - - |_ `...location(int).poly([coefficients], duration)` : to apply + - `...location(int).poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply + - `...location(int).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...location(int).piecewise_linear([durations], [values])`: to apply + - `...location(int).piecewise_linear([durations], [values])`: to apply a piecewise linear waveform - - |_ `...location(int).piecewise_constant([durations], [values])`: to apply + - `...location(int).piecewise_constant([durations], [values])`: to apply a piecewise constant waveform - - |_ `...location(int).fn(f(t,..))`: to apply a function as a waveform + - `...location(int).fn(f(t,..))`: to apply a function as a waveform - You can also address multiple atoms by chaining: - - |_ `...location(int).location(int)` + - `...location(int).location(int)` - The waveform you specify after the last `location` in the chain will be applied to all atoms in the chain - And you can scale any waveform by a multiplicative factor on a specific atom via: - - |_ `...location(int).scale(float)` + - `...location(int).scale(float)` - You cannot define a scaling across multiple atoms with one method call! They must be specified atom-by-atom. @@ -112,19 +112,19 @@ def var(self, name: str): ``` - You can now do: - - |_ `...var(str).linear(start, stop, duration)` : to apply + - `...var(str).linear(start, stop, duration)` : to apply a linear waveform - - |_ `...var(str).constant(value, duration)` : to apply + - `...var(str).constant(value, duration)` : to apply a constant waveform - - |_ `...var(str).poly([coefficients], duration)` : to apply + - `...var(str).poly([coefficients], duration)` : to apply a polynomial waveform - - |_ `...var(str).apply(wf:bloqade.ir.Waveform)`: to apply + - `...var(str).apply(wf:bloqade.ir.Waveform)`: to apply a pre-defined waveform - - |_ `...var(str).piecewise_linear(durations, values)`: to + - `...var(str).piecewise_linear(durations, values)`: to apply a piecewise linear waveform - - |_ `...var(str).piecewise_constant(durations, values)`: to + - `...var(str).piecewise_constant(durations, values)`: to apply a piecewise constant waveform - - |_ `...var(str).fn(f(t,..))`: to apply a function as a waveform + - `...var(str).fn(f(t,..))`: to apply a function as a waveform """ from bloqade.builder.spatial import Var @@ -203,10 +203,10 @@ def amplitude(self) -> "RabiAmplitude": automatically to create a field such as the Rabi Amplitude here. - You can now - - |_ `...amplitude.uniform`: address all atoms in the field - - |_ `...amplitude.location(int)`: address a specific atom by its + - `...amplitude.uniform`: address all atoms in the field + - `...amplitude.location(int)`: address a specific atom by its index - - |_ `...amplitude.var(str)`: Address a single atom + - `...amplitude.var(str)`: Address a single atom (or multiple via assigning a list of values) """ @@ -225,10 +225,10 @@ def phase(self) -> "RabiPhase": automatically to create a field such as the Rabi Phase here. - You can now - - |_ `...amplitude.uniform`: address all atoms in the field - - |_ `...amplitude.location(int)`: address a specific atom by its + - `...amplitude.uniform`: address all atoms in the field + - `...amplitude.location(int)`: address a specific atom by its index - - |_ `...amplitude.var(str)`: Address a single atom + - `...amplitude.var(str)`: Address a single atom (or multiple via assigning a list of values) """ diff --git a/src/bloqade/builder/pragmas.py b/src/bloqade/builder/pragmas.py index 857d4aa96..0e4568a70 100644 --- a/src/bloqade/builder/pragmas.py +++ b/src/bloqade/builder/pragmas.py @@ -38,20 +38,20 @@ def assign(self, **assignments) -> "Assign": ``` - You can now: - - |_ ...assign(assignments).bloqade: select the bloqade local + - ...assign(assignments).bloqade: select the bloqade local emulator backend - - |_ ...assign(assignments).braket: select braket local emulator or + - ...assign(assignments).braket: select braket local emulator or QuEra hardware - - |_ ...assign(assignments).device(specifier_string): select backend + - ...assign(assignments).device(specifier_string): select backend by specifying a string - Assign multiple values to a single variable for a parameter sweep: - - |_ ...assign(assignments).batch_assign(assignments): + - ...assign(assignments).batch_assign(assignments): - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available space/qubits on the QPU: - - |_ ...assign(assignments).parallelize(cluster_spacing) + - ...assign(assignments).parallelize(cluster_spacing) - Defer value assignment of certain variables to runtime: - - |_ ...assign(assignments).args([previously_defined_vars]) + - ...assign(assignments).args([previously_defined_vars]) """ from bloqade.builder.assign import Assign @@ -86,18 +86,18 @@ def batch_assign( ``` - Next steps are: - - |_ ...batch_assign(assignments).bloqade: select the bloqade + - ...batch_assign(assignments).bloqade: select the bloqade local emulator backend - - |_ ...batch_assign(assignments).braket: select braket local + - ...batch_assign(assignments).braket: select braket local emulator or QuEra hardware - - |_ ...batch_assign(assignments).device(specifier_string): select + - ...batch_assign(assignments).device(specifier_string): select backend by specifying a string - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available space/qubits on the QPU: - - |_ ...batch_assign(assignments).parallelize(cluster_spacing) + - ...batch_assign(assignments).parallelize(cluster_spacing) - Defer value assignment of certain variables to runtime: - - |_ ...batch_assign(assignments).args([previously_defined_vars]) + - ...batch_assign(assignments).args([previously_defined_vars]) """ from bloqade.builder.assign import BatchAssign, ListAssign @@ -130,11 +130,11 @@ def parallelize(self, cluster_spacing: LiteralType) -> "Parallelize": ``` - Your next steps are: - |_ `...parallelize(cluster_spacing).bloqade`: select the bloqade + `...parallelize(cluster_spacing).bloqade`: select the bloqade local emulator backend - |_ `...parallelize(cluster_spacing).braket`: select braket + `...parallelize(cluster_spacing).braket`: select braket local emulator or QuEra hardware on the cloud - |_ `...parallelize(cluster_spacing).device(specifier_string)`: select + `...parallelize(cluster_spacing).device(specifier_string)`: select backend by specifying a string """ diff --git a/src/bloqade/builder/spatial.py b/src/bloqade/builder/spatial.py index 52d18a448..8e7bd22cb 100644 --- a/src/bloqade/builder/spatial.py +++ b/src/bloqade/builder/spatial.py @@ -70,24 +70,24 @@ def location(self, label: int) -> "Location": - Your next steps include: - Continuing to modify your current spatial modulation via: - - |_ `...location(int).location(int)`: To add another location - - |_ `...location(int).scale(float)`: To scale the upcoming waveform + - `...location(int).location(int)`: To add another location + - `...location(int).scale(float)`: To scale the upcoming waveform - You may also jump directly to specifying a waveform via: - - |_ `...location(int).linear(start, stop, duration)`: + - `...location(int).linear(start, stop, duration)`: to append a linear waveform - - |_ `...location(int).constant(value, duration)`: + - `...location(int).constant(value, duration)`: to append a constant waveform - - |_ `...location(int) + - `...location(int) .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...location(int) + - `...location(int) .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - - |_ `...location(int).poly([coefficients], duration)`: + - `...location(int).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: + - `...location(int).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...location(int).fn(f(t,...))`: + - `...location(int).fn(f(t,...))`: to append a waveform defined by a python function """ @@ -114,23 +114,23 @@ def scale(self, value: ScalarType) -> "Scale": - Your next steps include: - Continuing to modify your current spatial modulation via: - - |_ `...scale(float).location(int)`: To add another location + - `...scale(float).location(int)`: To add another location - You may also jump directly to specifying a waveform via: - - |_ `...scale(float).linear(start, stop, duration)`: + - `...scale(float).linear(start, stop, duration)`: to append a linear waveform - - |_ `...scale(float).constant(value, duration)`: + - `...scale(float).constant(value, duration)`: to append a constant waveform - - |_ `...scale(float) + - `...scale(float) .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...scale(float) + - `...scale(float) .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - - |_ `...scale(float).poly([coefficients], duration)`: + - `...scale(float).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...scale(float).apply(wf:bloqade.ir.Waveform)`: + - `...scale(float).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...scale(float).fn(f(t,...))`: + - `...scale(float).fn(f(t,...))`: to append a waveform defined by a python function """ return Scale(value, self) @@ -167,24 +167,24 @@ def location(self, label: int) -> "Location": - Your next steps include: - Continuing to modify your current spatial modulation via: - - |_ `...location(int).location(int)`: To add another location - - |_ `...location(int).scale(float)`: To scale the upcoming waveform + - `...location(int).location(int)`: To add another location + - `...location(int).scale(float)`: To scale the upcoming waveform - You may also jump directly to specifying a waveform via: - - |_ `...location(int).linear(start, stop, duration)`: + - `...location(int).linear(start, stop, duration)`: to append a linear waveform - - |_ `...location(int).constant(value, duration)`: + - `...location(int).constant(value, duration)`: to append a constant waveform - - |_ `...location(int) + - `...location(int) .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...location(int) + - `...location(int) .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - - |_ `...location(int).poly([coefficients], duration)`: + - `...location(int).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...location(int).apply(wf:bloqade.ir.Waveform)`: + - `...location(int).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...location(int).fn(f(t,...))`: + - `...location(int).fn(f(t,...))`: to append a waveform defined by a python function """ diff --git a/src/bloqade/builder/start.py b/src/bloqade/builder/start.py index b60d1810c..96b928eb9 100644 --- a/src/bloqade/builder/start.py +++ b/src/bloqade/builder/start.py @@ -32,20 +32,20 @@ def apply(self, sequence: Sequence) -> SequenceBuilder: ``` - From here you can now do: - - |_ `...assign(assignments).bloqade`: select the bloqade + - `...assign(assignments).bloqade`: select the bloqade local emulator backend - - |_ `...assign(assignments).braket`: select braket + - `...assign(assignments).braket`: select braket local emulator or QuEra hardware - - |_ `...assign(assignments).device(specifier_string)`: select + - `...assign(assignments).device(specifier_string)`: select backend by specifying a string - Assign multiple values to a single variable for a parameter sweep: - - |_ `...assign(assignments).batch_assign(assignments)`: + - `...assign(assignments).batch_assign(assignments)`: - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available space/qubits on the QPU: - - |_ `...assign(assignments).parallelize(cluster_spacing)` + - `...assign(assignments).parallelize(cluster_spacing)` - Defer value assignment of certain variables to runtime: - - |_ `...assign(assignments).args([previously_defined_vars])` + - `...assign(assignments).args([previously_defined_vars])` """ return SequenceBuilder(sequence, self) diff --git a/src/bloqade/builder/waveform.py b/src/bloqade/builder/waveform.py index 92530bca6..f4ab0ef86 100644 --- a/src/bloqade/builder/waveform.py +++ b/src/bloqade/builder/waveform.py @@ -35,68 +35,68 @@ def linear( - Your next steps include: - Continue building your waveform via: - - |_ `...linear(start, stop, duration).linear(start, stop, duration)`: + - `...linear(start, stop, duration).linear(start, stop, duration)`: to append another linear waveform - - |_ `...linear(start, stop, duration).constant(value, duration)`: + - `...linear(start, stop, duration).constant(value, duration)`: to append a constant waveform - - |_ `...linear(start, stop, duration) + - `...linear(start, stop, duration) .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...linear(start, stop, duration) + - `...linear(start, stop, duration) .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - - |_ `...linear(start, stop, duration).poly([coefficients], duration)`: + - `...linear(start, stop, duration).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...linear(start, stop, duration).apply(wf:bloqade.ir.Waveform)`: + - `...linear(start, stop, duration).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...linear(start, stop, duration).fn(f(t,...))`: + - `...linear(start, stop, duration).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - - |_ `...linear(start, stop, duration).slice(start, stop, duration)` + - `...linear(start, stop, duration).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - - |_ `...linear(start, stop, duration).record("you_variable_here")` + - `...linear(start, stop, duration).record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - - |_ `...linear(start, stop, duration).uniform`: + - `...linear(start, stop, duration).uniform`: To address all atoms in the field - - |_ `...linear(start, stop, duration).location(int)`: + - `...linear(start, stop, duration).location(int)`: To address an atom at a specific location via index - - |_ `...linear(start, stop, duration).var(str)` - - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying + - `...linear(start, stop, duration).var(str)` + - To address an atom at a specific location via variable + - To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...linear(start, stop, duration).assign(variable_name = value)`: + - `...linear(start, stop, duration).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...linear(start, stop, duration) + - `...linear(start, stop, duration) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...linear(start, stop, duration).args(["previously_defined_var"])`: + - `...linear(start, stop, duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...linear(start, stop, duration).braket`: + - `...linear(start, stop, duration).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...linear(start, stop, duration).bloqade`: + - `...linear(start, stop, duration).bloqade`: to run on the Bloqade local emulator - - |_ `...linear(start, stop, duration).device`: + - `...linear(start, stop, duration).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...linear(start, stop, duration).parallelize(spacing)` + - `...linear(start, stop, duration).parallelize(spacing)` - Start targeting another level coupling - - |_ `...linear(start, stop, duration).rydberg`: + - `...linear(start, stop, duration).rydberg`: to target the Rydberg level coupling - - |_ `...linear(start, stop, duration).hyperfine`: + - `...linear(start, stop, duration).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...linear(start, stop, duration).amplitude`: + - `...linear(start, stop, duration).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...linear(start, stop, duration).phase`: + - `...linear(start, stop, duration).phase`: to target the real-valued Rabi Phase field - - |_ `...linear(start, stop, duration).detuning`: + - `...linear(start, stop, duration).detuning`: to target the Detuning field - - |_ `...linear(start, stop, duration).rabi`: + - `...linear(start, stop, duration).rabi`: to target the complex-valued Rabi field """ @@ -124,68 +124,68 @@ def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": - Your next steps include: - Continue building your waveform via: - - |_ `...constant(value, duration).linear(start, stop, duration)`: + - `...constant(value, duration).linear(start, stop, duration)`: to append another linear waveform - - |_ `...constant(value, duration).constant(value, duration)`: + - `...constant(value, duration).constant(value, duration)`: to append a constant waveform - - |_ `...constant(value, duration) + - `...constant(value, duration) .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...constant(value, duration) + - `...constant(value, duration) .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - - |_ `...constant(value, duration).poly([coefficients], duration)`: + - `...constant(value, duration).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...constant(value, duration).apply(wf:bloqade.ir.Waveform)`: + - `...constant(value, duration).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...constant(value, duration).fn(f(t,...))`: + - `...constant(value, duration).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - - |_ `...constant(value, duration).slice(start, stop, duration)` + - `...constant(value, duration).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - - |_ `...constant(value, duration).record("you_variable_here")` + - `...constant(value, duration).record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...constant(value, duration).uniform`: + - `...constant(value, duration).uniform`: To address all atoms in the field - -|_ `...constant(value, duration).var`: + - `...constant(value, duration).var`: To address an atom at a specific location via index - -|_ `...constant(value, duration).location(int)` - - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying + - `...constant(value, duration).location(int)` + - To address an atom at a specific location via variable + - To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...constant(value, duration).assign(variable_name = value)`: + - `...constant(value, duration).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...constant(value, duration) + - `...constant(value, duration) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...constant(value, duration).args(["previously_defined_var"])`: + - `...constant(value, duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...constant(value, duration).braket`: + - `...constant(value, duration).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...constant(value, duration).bloqade`: + - `...constant(value, duration).bloqade`: to run on the Bloqade local emulator - - |_ `...constant(value, duration).device`: + - `...constant(value, duration).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...constant(start, stop, duration).parallelize(spacing)` + - `...constant(start, stop, duration).parallelize(spacing)` - Start targeting another level coupling - - |_ `...constant(value, duration).rydberg`: + - `...constant(value, duration).rydberg`: to target the Rydberg level coupling - - |_ `...constant(value, duration).hyperfine`: + - `...constant(value, duration).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...constant(value, duration).amplitude`: + - `...constant(value, duration).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...constant(value, duration).phase`: + - `...constant(value, duration).phase`: to target the real-valued Rabi Phase field - - |_ `...constant(value, duration).detuning`: + - `...constant(value, duration).detuning`: to target the Detuning field - - |_ `...constant(value, duration).rabi`: + - `...constant(value, duration).rabi`: to target the complex-valued Rabi field """ @@ -223,69 +223,69 @@ def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": - Your next steps include: - Continue building your waveform via: - - |_ `...poly([coeffs], duration).linear(start, stop, duration)`: + - `...poly([coeffs], duration).linear(start, stop, duration)`: to append another linear waveform - - |_ `...poly([coeffs], duration).constant(value, duration)`: + - `...poly([coeffs], duration).constant(value, duration)`: to append a constant waveform - - |_ `...poly([coeffs], duration) + - `...poly([coeffs], duration) .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...poly([coeffs], duration) + - `...poly([coeffs], duration) .piecewise_constant([durations],[values])`: to append a piecewise constant waveform - - |_ `...poly([coeffs], duration).poly([coefficients], duration)`: + - `...poly([coeffs], duration).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...poly([coeffs], duration).apply(waveform)`: + - `...poly([coeffs], duration).apply(waveform)`: to append a pre-defined waveform - - |_ `...poly([coeffs], duration).fn(f(t,...))`: + - `...poly([coeffs], duration).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - - |_ `...poly([coeffs], duration).slice(start, stop, duration)` + - `...poly([coeffs], duration).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - - |_ `...poly([coeffs], duration).record("you_variable_here")` + - `...poly([coeffs], duration).record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...poly([coeffs], duration).uniform`: + - `...poly([coeffs], duration).uniform`: To address all atoms in the field - -|_ `...poly([coeffs], duration).location(int)`: + - `...poly([coeffs], duration).location(int)`: To address an atom at a specific location via index - -|_ `...poly([coeffs], duration).var(str)` - - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by + - `...poly([coeffs], duration).var(str)` + - To address an atom at a specific location via variable + - To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...poly([coeffs], duration).assign(variable_name = value)`: + - `...poly([coeffs], duration).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...poly([coeffs], duration) + - `...poly([coeffs], duration) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...poly([coeffs], duration).args(["previously_defined_var"])`: + - `...poly([coeffs], duration).args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...poly([coeffs], duration).braket`: + - `...poly([coeffs], duration).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...poly([coeffs], duration).bloqade`: + - `...poly([coeffs], duration).bloqade`: to run on the Bloqade local emulator - - |_ `...poly([coeffs], duration).device`: + - `...poly([coeffs], duration).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...poly([coeffs], duration).parallelize(spacing)` + - `...poly([coeffs], duration).parallelize(spacing)` - Start targeting another level coupling - - |_ `...poly([coeffs], duration).rydberg`: + - `...poly([coeffs], duration).rydberg`: to target the Rydberg level coupling - - |_ `...poly([coeffs], duration).hyperfine`: + - `...poly([coeffs], duration).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...poly([coeffs], duration).amplitude`: + - `...poly([coeffs], duration).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...poly([coeffs], duration).phase`: + - `...poly([coeffs], duration).phase`: to target the real-valued Rabi Phase field - - |_ `...poly([coeffs], duration).detuning`: + - `...poly([coeffs], duration).detuning`: to target the Detuning field - - |_ `...poly([coeffs], duration).rabi`: + - `...poly([coeffs], duration).rabi`: to target the complex-valued Rabi field """ return Poly(coeffs, duration, self) @@ -316,62 +316,62 @@ def apply(self, wf: ir.Waveform) -> "Apply": - Your next steps include: - Continue building your waveform via: - - |_ `...apply(waveform).linear(start, stop, duration)`: + - `...apply(waveform).linear(start, stop, duration)`: to append another linear waveform - - |_ `...apply(waveform).constant(value, duration)`: + - `...apply(waveform).constant(value, duration)`: to append a constant waveform - - |_ `...apply(waveform).piecewise_linear([durations], [values])`: + - `...apply(waveform).piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...apply(waveform).piecewise_constant([durations], [values])`: + - `...apply(waveform).piecewise_constant([durations], [values])`: to append a piecewise constant waveform - - |_ `...apply(waveform).poly([coefficients], duration)`: + - `...apply(waveform).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...apply(waveform).apply(waveform)`: + - `...apply(waveform).apply(waveform)`: to append a pre-defined waveform - - |_ `...apply(waveform).fn(f(t,...))`: + - `...apply(waveform).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - - |_ `...apply(waveform).slice(start, stop, duration)` + - `...apply(waveform).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - - |_ `...apply(waveform).record("you_variable_here")` + - `...apply(waveform).record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...apply(waveform).uniform`: To address all atoms in the field - -|_ `...apply(waveform).location(int)`: + - `...apply(waveform).uniform`: To address all atoms in the field + - `...apply(waveform).location(int)`: To address an atom at a specific location via index - -|_ `...apply(waveform).var(str)` - - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying a + - `...apply(waveform).var(str)` + - To address an atom at a specific location via variable + - To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...apply(waveform).assign(variable_name = value)`: + - `...apply(waveform).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...apply(waveform).batch_assign(variable_name = [value1, ...])`: + - `...apply(waveform).batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...apply(waveform).args(["previously_defined_var"])`: + - `...apply(waveform).args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...apply(waveform).braket`: + - `...apply(waveform).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...apply(waveform).bloqade`: + - `...apply(waveform).bloqade`: to run on the Bloqade local emulator - - |_ `...apply(waveform).device`: + - `...apply(waveform).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...apply(waveform).parallelize(spacing)` + - `...apply(waveform).parallelize(spacing)` - Start targeting another level coupling - - |_ `...apply(waveform).rydberg`: to target the Rydberg level coupling - - |_ `...apply(waveform).hyperfine`: to target the Hyperfine level coupling + - `...apply(waveform).rydberg`: to target the Rydberg level coupling + - `...apply(waveform).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...apply(waveform).amplitude`: + - `...apply(waveform).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...apply(waveform).phase`: + - `...apply(waveform).phase`: to target the real-valued Rabi Phase field - - |_ `...apply(waveform).detuning`: + - `...apply(waveform).detuning`: to target the Detuning field - - |_ `...apply(waveform).rabi`: + - `...apply(waveform).rabi`: to target the complex-valued Rabi field """ return Apply(wf, self) @@ -408,74 +408,74 @@ def piecewise_linear( - Your next steps include: - Continue building your waveform via: - - |_ `...piecewise_linear([durations], [values]) + - `...piecewise_linear([durations], [values]) .linear(start, stop, duration)`: to append another linear waveform - - |_ `...piecewise_linear([durations], [values]).constant(value, duration)`: + - `...piecewise_linear([durations], [values]).constant(value, duration)`: to append a constant waveform - - |_ `...piecewise_linear([durations], [values]) + - `...piecewise_linear([durations], [values]) .piecewise_linear(durations, values)`: to append a piecewise linear waveform - - |_ `...piecewise_linear([durations], [values]) + - `...piecewise_linear([durations], [values]) .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - - |_ `...piecewise_linear([durations], [values]) + - `...piecewise_linear([durations], [values]) .poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...piecewise_linear([durations], [values]).apply(waveform)`: + - `...piecewise_linear([durations], [values]).apply(waveform)`: to append a pre-defined waveform - - |_ `...piecewise_linear([durations], [values]).fn(f(t,...))`: + - `...piecewise_linear([durations], [values]).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - - |_ `...piecewise_linear([durations], [values]) + - `...piecewise_linear([durations], [values]) .slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - - |_ `...piecewise_linear([durations], [values]) + - `...piecewise_linear([durations], [values]) .record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_linear([durations], [values]).uniform`: + - `...piecewise_linear([durations], [values]).uniform`: To address all atoms in the field - -|_ `...piecewise_linear([durations], [values]).var`: + - `...piecewise_linear([durations], [values]).var`: To address an atom at a specific location via index - -|_ `...piecewise_linear([durations], [values]).location(int)` - - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by + - `...piecewise_linear([durations], [values]).location(int)` + - To address an atom at a specific location via variable + - To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...piecewise_linear([durations], [values]) + - `...piecewise_linear([durations], [values]) .assign(variable_name = value)`: to assign a single value to a variable - - |_ `...piecewise_linear([durations], [values]) + - `...piecewise_linear([durations], [values]) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...piecewise_linear([durations], [values]) + - `...piecewise_linear([durations], [values]) .args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_linear([durations], [values]).braket`: + - `...piecewise_linear([durations], [values]).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_linear([durations], [values]).bloqade`: + - `...piecewise_linear([durations], [values]).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_linear([durations], [values]).device`: + - `...piecewise_linear([durations], [values]).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...piecewise_linear([durations], [values]).parallelize(spacing)` + - `...piecewise_linear([durations], [values]).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_linear([durations], [values]).rydberg`: + - `...piecewise_linear([durations], [values]).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_linear([durations], [values]).hyperfine`: + - `...piecewise_linear([durations], [values]).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_linear([durations], [values]).amplitude`: + - `...piecewise_linear([durations], [values]).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_linear([durations], [values]).phase`: + - `...piecewise_linear([durations], [values]).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_linear([durations], [values]).detuning`: + - `...piecewise_linear([durations], [values]).detuning`: to target the Detuning field - - |_ `....rabi`: to target the complex-valued Rabi field + - `....rabi`: to target the complex-valued Rabi field """ return PiecewiseLinear(durations, values, self) @@ -508,72 +508,72 @@ def piecewise_constant( - Your next steps including: - Continue building your waveform via: - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .linear(start, stop, duration)`: to append another linear waveform - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .constant(value, duration)`: to append a constant waveform - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .piecewise_linear([durations], [values])`: to append a piecewise linear waveform - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .piecewise_constant([durations], [values])`: to append a piecewise constant waveform - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .apply(waveform)`: to append a pre-defined waveform - - |_ `...piecewise_constant([durations], [values]).fn(f(t,...))`: + - `...piecewise_constant([durations], [values]).fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...piecewise_constant([durations], [values]).uniform`: + - `...piecewise_constant([durations], [values]).uniform`: To address all atoms in the field - -|_ `...piecewise_constant([durations], [values]).location(int)`: + - `...piecewise_constant([durations], [values]).location(int)`: To address an atom at a specific location via index - -|_ `...piecewise_constant([durations], [values]).var(str)` - - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by + - `...piecewise_constant([durations], [values]).var(str)` + - To address an atom at a specific location via variable + - To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .assign(variable_name = value)`: to assign a single value to a variable - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...piecewise_constant([durations], [values]) + - `...piecewise_constant([durations], [values]) .args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...piecewise_constant([durations], [values]).braket`: + - `...piecewise_constant([durations], [values]).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...piecewise_constant([durations], [values]).bloqade`: + - `...piecewise_constant([durations], [values]).bloqade`: to run on the Bloqade local emulator - - |_ `...piecewise_constant([durations], [values]).device`: + - `...piecewise_constant([durations], [values]).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...piecewise_constat([durations], [values]).parallelize(spacing)` + - `...piecewise_constat([durations], [values]).parallelize(spacing)` - Start targeting another level coupling - - |_ `...piecewise_constant([durations], [values]).rydberg`: + - `...piecewise_constant([durations], [values]).rydberg`: to target the Rydberg level coupling - - |_ `...piecewise_constant([durations], [values]).hyperfine`: + - `...piecewise_constant([durations], [values]).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...piecewise_constant(durations, values).amplitude`: + - `...piecewise_constant(durations, values).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...piecewise_constant([durations], [values]).phase`: + - `...piecewise_constant([durations], [values]).phase`: to target the real-valued Rabi Phase field - - |_ `...piecewise_constant([durations], [values]).detuning`: + - `...piecewise_constant([durations], [values]).detuning`: to target the Detuning field - - |_ `...piecewise_constant([durations], [values]).rabi`: + - `...piecewise_constant([durations], [values]).rabi`: to target the complex-valued Rabi field """ return PiecewiseConstant(durations, values, self) @@ -614,70 +614,70 @@ def fn(self, fn: Callable, duration: ScalarType) -> "Fn": - Your next steps include: - Continue building your waveform via: - - |_ `...fn(f(t,...)) + - `...fn(f(t,...)) .linear(start, stop, duration)`: to append another linear waveform - - |_ `...fn(f(t,...)) + - `...fn(f(t,...)) .constant(value, duration)`: to append a constant waveform - - |_ `...fn(f(t,...)) + - `...fn(f(t,...)) .piecewise_linear(durations, values)`: to append a piecewise linear waveform - - |_ `...fn(f(t,...)) + - `...fn(f(t,...)) .piecewise_constant(durations, values)`: to append a piecewise constant waveform - - |_ `...fn(f(t,...)) + - `...fn(f(t,...)) .poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...fn(f(t,...)) + - `...fn(f(t,...)) .apply(waveform)`: to append a pre-defined waveform - - |_ `...fn(f(t,...)) + - `...fn(f(t,...)) .fn(f(t,...))`: to append a waveform defined by a python function - Slice a portion of the waveform to be used: - - |_ `...fn(f(t,...)).slice(start, stop, duration)` + - `...fn(f(t,...)).slice(start, stop, duration)` - Save the ending value of your waveform to be reused elsewhere - - |_ `...fn(f(t,...)).record("you_variable_here")` + - `...fn(f(t,...)).record("you_variable_here")` - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - -|_ `...fn(f(t,...)).uniform`: + - `...fn(f(t,...)).uniform`: To address all atoms in the field - -|_ `...fn(f(t,...)).var(str)`: + - `...fn(f(t,...)).var(str)`: To address an atom at a specific location via index - -|_ ...fn(f(t,...)).location(int)` - - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by + - ...fn(f(t,...)).location(int)` + - To address an atom at a specific location via variable + - To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...fn(f(t,...)) + - `...fn(f(t,...)) .assign(variable_name = value)`: to assign a single value to a variable - - |_ `...fn(f(t,...)) + - `...fn(f(t,...)) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...fn(f(t,...)) + - `...fn(f(t,...)) .args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...fn(f(t,...)).braket`: + - `...fn(f(t,...)).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...fn(f(t,...)).bloqade`: + - `...fn(f(t,...)).bloqade`: to run on the Bloqade local emulator - - |_ `...fn(f(t,...)).device`: + - `...fn(f(t,...)).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...fn(f(t,...)).parallelize(spacing)` + - `...fn(f(t,...)).parallelize(spacing)` - Start targeting another level coupling - - |_ `...fn(f(t,...)).rydberg`: + - `...fn(f(t,...)).rydberg`: to target the Rydberg level coupling - - |_ `...fn(f(t,...)).hyperfine`: + - `...fn(f(t,...)).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...fn(f(t,...)).amplitude`: + - `...fn(f(t,...)).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...fn(f(t,...)).phase`: + - `...fn(f(t,...)).phase`: to target the real-valued Rabi Phase field - - |_ `...fn(f(t,...)).detuning`: + - `...fn(f(t,...)).detuning`: to target the Detuning field - - |_ `...fn(f(t,...)).rabi`: + - `...fn(f(t,...)).rabi`: to target the complex-valued Rabi field """ @@ -726,62 +726,62 @@ def slice( - Your next steps include: - Continue building your waveform via: - - |_ `...slice(start, stop).linear(start, stop, duration)`: + - `...slice(start, stop).linear(start, stop, duration)`: to append another linear waveform - - |_ `...slice(start, stop).constant(value, duration)`: + - `...slice(start, stop).constant(value, duration)`: to append a constant waveform - - |_ `...slice(start, stop).piecewise_linear()`: + - `...slice(start, stop).piecewise_linear()`: to append a piecewise linear waveform - - |_ `...slice(start, stop).piecewise_constant()`: + - `...slice(start, stop).piecewise_constant()`: to append a piecewise constant waveform - - |_ `...slice(start, stop).poly([coefficients], duration)`: + - `...slice(start, stop).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...slice(start, stop).apply(wf:bloqade.ir.Waveform)`: + - `...slice(start, stop).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...slilce(start, stop).fn(f(t,...))`: + - `...slilce(start, stop).fn(f(t,...))`: to append a waveform defined by a python function - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - - |_ `...slice(start, stop).uniform`: + - `...slice(start, stop).uniform`: To address all atoms in the field - - |_ `...slice(start, stop).location(int)`: + - `...slice(start, stop).location(int)`: To address an atom at a specific location via index - - |_ `...slice(start, stop).var(str)` - - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying + - `...slice(start, stop).var(str)` + - To address an atom at a specific location via variable + - To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...slice(start, stop).assign(variable_name = value)`: + - `...slice(start, stop).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...slice(start, stop) + - `...slice(start, stop) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...slice(start, stop).args(["previously_defined_var"])`: + - `...slice(start, stop).args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...slice(start, stop).braket`: + - `...slice(start, stop).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...slice(start, stop).bloqade`: + - `...slice(start, stop).bloqade`: to run on the Bloqade local emulator - - |_ `...slice(start, stop).device`: + - `...slice(start, stop).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...slice(start, stop).parallelize(spacing)` + - `...slice(start, stop).parallelize(spacing)` - Start targeting another level coupling - - |_ `...slice(start, stop).rydberg`: + - `...slice(start, stop).rydberg`: to target the Rydberg level coupling - - |_ `...slice(start, stop).hyperfine`: + - `...slice(start, stop).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...slice(start, stop).amplitude`: + - `...slice(start, stop).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...slice(start, stop).phase`: + - `...slice(start, stop).phase`: to target the real-valued Rabi Phase field - - |_ `...slice(start, stop).detuning`: + - `...slice(start, stop).detuning`: to target the Detuning field - - |_ `...slice(start, stop).rabi`: + - `...slice(start, stop).rabi`: to target the complex-valued Rabi field """ return Slice(start, stop, self) @@ -825,62 +825,62 @@ def record(self, name: str) -> "Record": - Your next steps include: - Continue building your waveform via: - - |_ `...slice(start, stop).linear(start, stop, duration)`: + - `...slice(start, stop).linear(start, stop, duration)`: to append another linear waveform - - |_ `...slice(start, stop).constant(value, duration)`: + - `...slice(start, stop).constant(value, duration)`: to append a constant waveform - - |_ `...slice(start, stop).piecewise_linear()`: + - `...slice(start, stop).piecewise_linear()`: to append a piecewise linear waveform - - |_ `...slice(start, stop).piecewise_constant()`: + - `...slice(start, stop).piecewise_constant()`: to append a piecewise constant waveform - - |_ `...slice(start, stop).poly([coefficients], duration)`: + - `...slice(start, stop).poly([coefficients], duration)`: to append a polynomial waveform - - |_ `...slice(start, stop).apply(wf:bloqade.ir.Waveform)`: + - `...slice(start, stop).apply(wf:bloqade.ir.Waveform)`: to append a pre-defined waveform - - |_ `...slilce(start, stop).fn(f(t,...))`: + - `...slilce(start, stop).fn(f(t,...))`: to append a waveform defined by a python function - Begin constructing another drive by starting a new spatial modulation (this drive will be summed to the one you just created): - - |_ `...slice(start, stop).uniform`: + - `...slice(start, stop).uniform`: To address all atoms in the field - - |_ `...slice(start, stop).location(int)`: + - `...slice(start, stop).location(int)`: To address an atom at a specific location via index - - |_ `...slice(start, stop).var(str)` - - |_ To address an atom at a specific location via variable - - |_ To address multiple atoms at specific locations by specifying + - `...slice(start, stop).var(str)` + - To address an atom at a specific location via variable + - To address multiple atoms at specific locations by specifying a single variable and then assigning it a list of coordinates - Assign values to pre-existing variables via: - - |_ `...slice(start, stop).assign(variable_name = value)`: + - `...slice(start, stop).assign(variable_name = value)`: to assign a single value to a variable - - |_ `...slice(start, stop) + - `...slice(start, stop) .batch_assign(variable_name = [value1, ...])`: to assign multiple values to a variable - - |_ `...slice(start, stop).args(["previously_defined_var"])`: + - `...slice(start, stop).args(["previously_defined_var"])`: to defer assignment of a variable to execution time - Select the backend you want your program to run on via: - - |_ `...slice(start, stop).braket`: + - `...slice(start, stop).braket`: to run on Braket local emulator or QuEra hardware remotely - - |_ `...slice(start, stop).bloqade`: + - `...slice(start, stop).bloqade`: to run on the Bloqade local emulator - - |_ `...slice(start, stop).device`: + - `...slice(start, stop).device`: to specify the backend via string - Choose to parallelize your atom geometry, duplicating it to fill the whole space: - - |_ `...slice(start, stop).parallelize(spacing)` + - `...slice(start, stop).parallelize(spacing)` - Start targeting another level coupling - - |_ `...slice(start, stop).rydberg`: + - `...slice(start, stop).rydberg`: to target the Rydberg level coupling - - |_ `...slice(start, stop).hyperfine`: + - `...slice(start, stop).hyperfine`: to target the Hyperfine level coupling - Start targeting other fields within your current level coupling (previously selected as `rydberg` or `hyperfine`): - - |_ `...slice(start, stop).amplitude`: + - `...slice(start, stop).amplitude`: to target the real-valued Rabi Amplitude field - - |_ `...slice(start, stop).phase`: + - `...slice(start, stop).phase`: to target the real-valued Rabi Phase field - - |_ `...slice(start, stop).detuning`: + - `...slice(start, stop).detuning`: to target the Detuning field - - |_ `...slice(start, stop).rabi`: + - `...slice(start, stop).rabi`: to target the complex-valued Rabi field ``` """ diff --git a/src/bloqade/ir/location/list.py b/src/bloqade/ir/location/list.py index ef82567d2..360be00ba 100644 --- a/src/bloqade/ir/location/list.py +++ b/src/bloqade/ir/location/list.py @@ -66,13 +66,13 @@ def __iter__(self): - Next possible steps to build your program are: - Specify which level coupling to address with: - - |_ `start.rydberg`: for [`Rydberg`][bloqade.builder.coupling.Rydberg] + - `start.rydberg`: for [`Rydberg`][bloqade.builder.coupling.Rydberg] Level coupling - - |_ `start.hyperfine`: for [`Hyperfine`][bloqade.builder.coupling.Hyperfine] + - `start.hyperfine`: for [`Hyperfine`][bloqade.builder.coupling.Hyperfine] Level coupling - LOCKOUT: You cannot add atoms to your geometry after specifying level coupling. - continue/start building your geometry with: - - |_ `start.add_position()`: to add atom(s) to current register. It will accept: + - `start.add_position()`: to add atom(s) to current register. It will accept: - A single coordinate, represented as a tuple (e.g. `(5,6)`) with a value that can either be: - integers: `(5,6)` diff --git a/src/bloqade/ir/location/transform.py b/src/bloqade/ir/location/transform.py index 0d6dcde80..4ac719386 100644 --- a/src/bloqade/ir/location/transform.py +++ b/src/bloqade/ir/location/transform.py @@ -50,20 +50,20 @@ def scale(self, scale: ScalarType): - Next possible steps are: - Continuing to build your geometry via: - - |_ `...add_position(positions).add_position(positions)`: + - `...add_position(positions).add_position(positions)`: to add more positions - - |_ `...add_position(positions).apply_defect_count(n_defects)`: + - `...add_position(positions).apply_defect_count(n_defects)`: to randomly drop out n_atoms - - |_ `...add_position(positions).apply_defect_density(defect_probability)`: + - `...add_position(positions).apply_defect_density(defect_probability)`: to drop out atoms with a certain probability - - |_ `...add_position(positions).scale(scale)`: to scale the geometry + - `...add_position(positions).scale(scale)`: to scale the geometry - Targeting a level coupling once you're done with the atom geometry: - - |_ `...add_position(positions).rydberg`: + - `...add_position(positions).rydberg`: to specify Rydberg coupling - - |_ `...add_position(positions).hyperfine`: + - `...add_position(positions).hyperfine`: to specify Hyperfine coupling - Visualizing your atom geometry: - - |_ `...add_position(positions).show()`: + - `...add_position(positions).show()`: shows your geometry in your web browser """ @@ -171,18 +171,18 @@ def add_position( - Next possible steps are: - Continuing to build your geometry via: - - |_ `...add_position(positions).add_position(positions)`: + - `...add_position(positions).add_position(positions)`: to add more positions - - |_ `...add_position(positions).apply_defect_count(n_defects)`: + - `...add_position(positions).apply_defect_count(n_defects)`: to randomly drop out n_atoms - - |_ `...add_position(positions).apply_defect_density(defect_probability)`: + - `...add_position(positions).apply_defect_density(defect_probability)`: to drop out atoms with a certain probability - - |_ `...add_position(positions).scale(scale)`: to scale the geometry + - `...add_position(positions).scale(scale)`: to scale the geometry - Targeting a level coupling once you're done with the atom geometry: - - |_ `...add_position(positions).rydberg`: to specify Rydberg coupling - - |_ `...add_position(positions).hyperfine`: to specify Hyperfine coupling + - `...add_position(positions).rydberg`: to specify Rydberg coupling + - `...add_position(positions).hyperfine`: to specify Hyperfine coupling - Visualizing your atom geometry: - - |_ `...add_position(positions).show()`: + - `...add_position(positions).show()`: shows your geometry in your web browser """ @@ -218,22 +218,22 @@ def apply_defect_count( - Next possible steps are: - Continuing to build your geometry via: - - |_ `...apply_defect_count(defect_counts).add_position(positions)`: + - `...apply_defect_count(defect_counts).add_position(positions)`: to add more positions - - |_ `...apply_defect_count(defect_counts) + - `...apply_defect_count(defect_counts) .apply_defect_count(n_defects)`: to randomly drop out n_atoms - - |_ `...apply_defect_count(defect_counts) + - `...apply_defect_count(defect_counts) .apply_defect_density(defect_probability)`: to drop out atoms with a certain probability - - |_ `...apply_defect_count(defect_counts).scale(scale)`: + - `...apply_defect_count(defect_counts).scale(scale)`: to scale the geometry - Targeting a level coupling once you're done with the atom geometry: - - |_ `...apply_defect_count(defect_counts).rydberg`: to specify + - `...apply_defect_count(defect_counts).rydberg`: to specify Rydberg coupling - - |_ `...apply_defect_count(defect_counts).hyperfine`: + - `...apply_defect_count(defect_counts).hyperfine`: to specify Hyperfine coupling - Visualizing your atom geometry: - - |_ `...apply_defect_count(defect_counts).show()`: + - `...apply_defect_count(defect_counts).show()`: shows your geometry in your web browser """ from .list import ListOfLocations @@ -298,22 +298,22 @@ def apply_defect_density( - Next possible steps are: - Continuing to build your geometry via: - - |_ `...apply_defect_count(defect_counts).add_position(positions)`: + - `...apply_defect_count(defect_counts).add_position(positions)`: to add more positions - - |_ `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`: + - `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`: to randomly drop out n_atoms - - |_ `...apply_defect_count(defect_counts) + - `...apply_defect_count(defect_counts) .apply_defect_density(defect_probability)`: to drop out atoms with a certain probability - - |_ `...apply_defect_count(defect_counts).scale(scale)`: + - `...apply_defect_count(defect_counts).scale(scale)`: to scale the geometry - Targeting a level coupling once you're done with the atom geometry: - - |_ `...apply_defect_count(defect_counts).rydberg`: + - `...apply_defect_count(defect_counts).rydberg`: to specify Rydberg coupling - - |_ `...apply_defect_count(defect_counts).hyperfine`: + - `...apply_defect_count(defect_counts).hyperfine`: to specify Hyperfine coupling - Visualizing your atom geometry: - - |_ `...apply_defect_count(defect_counts).show()`: + - `...apply_defect_count(defect_counts).show()`: shows your geometry in your web browser """ from .list import ListOfLocations From 317958f719c55f5ef12c7e631e0b733620e2d3e8 Mon Sep 17 00:00:00 2001 From: John Long Date: Tue, 3 Oct 2023 09:36:36 -0400 Subject: [PATCH 19/20] Convert usage example to be markdown compatible --- src/bloqade/builder/field.py | 4 ++-- src/bloqade/builder/pragmas.py | 6 +++--- src/bloqade/builder/spatial.py | 6 +++--- src/bloqade/builder/waveform.py | 18 +++++++++--------- src/bloqade/ir/location/transform.py | 8 ++++---- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/bloqade/builder/field.py b/src/bloqade/builder/field.py index f2f31fca1..3db654d51 100644 --- a/src/bloqade/builder/field.py +++ b/src/bloqade/builder/field.py @@ -47,7 +47,7 @@ def location(self, label: int): chaining the construction of drives) will become the field. (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) - Usage Example: + ### Usage Example: ``` >>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi # to target a single atom with a waveform @@ -98,7 +98,7 @@ def var(self, name: str): chaining the construction of drives) will become the field (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.) - Usage Example: + ### Usage Example: ``` >>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi >>> one_location_prog = prog.var("a") diff --git a/src/bloqade/builder/pragmas.py b/src/bloqade/builder/pragmas.py index 0e4568a70..614e3752b 100644 --- a/src/bloqade/builder/pragmas.py +++ b/src/bloqade/builder/pragmas.py @@ -25,7 +25,7 @@ def assign(self, **assignments) -> "Assign": pass in a list. This is the ONLY circumstance in which multiple values are allowed. - Usage Examples: + ### ### Usage Examples: ``` # define geometry >>> reg = bloqade.start @@ -77,7 +77,7 @@ def batch_assign( (e.g. if "var1" is assigned [1,2,3] and "var2" is assigned [4,5,6] then the resulting programs will have assignments [1,4], [2,5], [3,6]). - Usage Example: + ### Usage Example: ``` >>> reg = start.add_position([(0,0), (0, "atom_distance")]) >>> prog = reg.rydberg.rabi.amplitude.uniform.constant("value", 5.0) @@ -121,7 +121,7 @@ def parallelize(self, cluster_spacing: LiteralType) -> "Parallelize": The singular argument lets you specify how far apart the clusters should be in micrometers. - Usage Example: + ### Usage Example: ``` >>> reg = start.add_position((0,0)).rydberg.rabi.uniform.amplitude .constant(1.0, 1.0) diff --git a/src/bloqade/builder/spatial.py b/src/bloqade/builder/spatial.py index 8e7bd22cb..89243b025 100644 --- a/src/bloqade/builder/spatial.py +++ b/src/bloqade/builder/spatial.py @@ -55,7 +55,7 @@ def location(self, label: int) -> "Location": Append another `.location` to the current location(s) as part of a singular spatial modulation definition. - Usage Example: + ### Usage Example: ``` # definep program >>> from bloqade.atom_arrangement import start @@ -99,7 +99,7 @@ def scale(self, value: ScalarType) -> "Scale": Scale the subsequent waveform to be applied on a certain set of atoms specified by the current spatial modulation. - Usage Examples: + ### ### Usage Examples: ``` # define program >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) @@ -152,7 +152,7 @@ def location(self, label: int) -> "Location": Append another `.location` to the current location(s) as part of a singular spatial modulation definition. - Usage Example: + ### Usage Example: ``` # definep program >>> from bloqade.atom_arrangement import start diff --git a/src/bloqade/builder/waveform.py b/src/bloqade/builder/waveform.py index f4ab0ef86..fa4d7d092 100644 --- a/src/bloqade/builder/waveform.py +++ b/src/bloqade/builder/waveform.py @@ -26,7 +26,7 @@ def linear( If you have already specified a waveform previously you will now be appending this waveform to that previous waveform. - Usage Example: + ### Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.detuning.uniform # apply a linear waveform that goes from 0 to 1 radians/us in 0.5 us @@ -115,7 +115,7 @@ def constant(self, value: ScalarType, duration: ScalarType) -> "Constant": If you have already specified a waveform previously you will now be appending this waveform to that previous waveform. - Usage Example: + ### Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.detuning.uniform # apply a constant waveform of 1.9 radians/us for 0.5 us @@ -211,7 +211,7 @@ def poly(self, coeffs: List[ScalarType], duration: ScalarType) -> "Poly": If you have already specified a waveform previously you will now be appending this waveform to that previous waveform. - Usage Example: + ### Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.detuning.uniform >>> coeffs = [-1, 0.5, 1.2] @@ -304,7 +304,7 @@ def apply(self, wf: ir.Waveform) -> "Apply": If you have already specified a waveform previously you will now be appending this waveform to that previous waveform. - Usage Example: + ### Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.detuning.uniform # build our waveform independently of the main program @@ -396,7 +396,7 @@ def piecewise_linear( If you have already specified a waveform previously you will now be appending this waveform to that previous waveform. - Usage Example: + ### Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.detuning.uniform # ramp our waveform up to a certain value, hold it @@ -498,7 +498,7 @@ def piecewise_constant( If you have already specified a waveform previously you will now be appending this waveform to that previous waveform. - Usage Example: + ### Usage Example: ``` >>> prog = start.add_position((0,0)).rydberg.rabi.phase.uniform # create a staircase, we hold 0.0 rad/us for 1.0 us, then @@ -597,7 +597,7 @@ def fn(self, fn: Callable, duration: ScalarType) -> "Fn": If you have already specified a waveform previously you will now be appending this waveform to that previous waveform. - Usage Examples: + ### ### Usage Examples: ``` >>> prog = start.add_position((0,0)).rydberg.detuning.uniform # define our custom waveform. It must have one argument @@ -711,7 +711,7 @@ def slice( this waveform to that previous waveform. - Usage Example: + ### Usage Example: ``` # define a program with a waveform of interest >>> from bloqade import start @@ -806,7 +806,7 @@ def record(self, name: str) -> "Record": If you have already specified a waveform previously you will now be appending this waveform to that previous waveform. - Usage Example: + ### Usage Example: ``` # define program of interest >>> from bloqade import start diff --git a/src/bloqade/ir/location/transform.py b/src/bloqade/ir/location/transform.py index 4ac719386..2bb2f6e2c 100644 --- a/src/bloqade/ir/location/transform.py +++ b/src/bloqade/ir/location/transform.py @@ -36,7 +36,7 @@ def scale(self, scale: ScalarType): """ Scale the geometry of your atoms. - Usage Example: + ### Usage Example: ``` >>> reg = start.add_position([(0,0), (1,1)]) # atom positions are now (0,0), (2,2) @@ -152,7 +152,7 @@ def add_position( You can also pass in an optional argument which determines the atom "filling" (whether or not at a specified coordinate an atom should be present). - Usage Example: + ### Usage Example: ``` # single coordinate >>> reg = start.add_position((0,0)) @@ -200,7 +200,7 @@ def apply_defect_count( A default numpy-based Random Number Generator is used but you can explicitly override this by passing in your own. - Usage Example: + ### Usage Example: ``` >>> from bloqade.atom_arrangement import Chain @@ -279,7 +279,7 @@ def apply_defect_density( A default numpy-based Random Number Generator is used but you can explicitly override this by passing in your own. - Usage Example: + ### Usage Example: ``` >>> from bloqade.atom_arrangement import Chain From cf7cda9decf08f216a04145465ca2e26716e3a78 Mon Sep 17 00:00:00 2001 From: John Long Date: Tue, 3 Oct 2023 11:17:01 -0400 Subject: [PATCH 20/20] Corrected rendering issues after verifying local build --- src/bloqade/builder/backend/__init__.py | 8 ++++---- src/bloqade/builder/coupling.py | 2 +- src/bloqade/builder/drive.py | 3 --- src/bloqade/builder/pragmas.py | 24 ++++++++++++------------ src/bloqade/builder/spatial.py | 2 +- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/bloqade/builder/backend/__init__.py b/src/bloqade/builder/backend/__init__.py index d77347391..796ba4692 100644 --- a/src/bloqade/builder/backend/__init__.py +++ b/src/bloqade/builder/backend/__init__.py @@ -5,10 +5,10 @@ class BackendRoute(QuEraService, BraketService, BloqadeService): """ - - Specify the backend to run your program on via a string - (versus more formal builder syntax) - of specifying the vendor/product first (Bloqade/Braket) and narrowing it down - - ...device("quera.aquila") versus ...quera.aquila() + Specify the backend to run your program on via a string + (versus more formal builder syntax) of specifying the vendor/product first + (Bloqade/Braket) and narrowing it down + (e.g: ...device("quera.aquila") versus ...quera.aquila()) - You can pass the following arguments: - `"braket.aquila"` - `"braket.local_emulator"` diff --git a/src/bloqade/builder/coupling.py b/src/bloqade/builder/coupling.py index 887e7d0cd..1c9aee45f 100644 --- a/src/bloqade/builder/coupling.py +++ b/src/bloqade/builder/coupling.py @@ -10,7 +10,7 @@ def detuning( # waveform + spatial modulation = drive """ Specify the [`Detuning`][bloqade.builder.field.Detuning] - [`Field`][bloqade.builder.Field] of your program. + [`Field`][bloqade.builder.Field] of your program. A "field" is a summation of one or more "drives", with a drive being the sum of a waveform and spatial modulation. diff --git a/src/bloqade/builder/drive.py b/src/bloqade/builder/drive.py index 8ab08ddf1..6504a3063 100644 --- a/src/bloqade/builder/drive.py +++ b/src/bloqade/builder/drive.py @@ -13,8 +13,6 @@ def rydberg(self) -> Rydberg: - `...rydberg.rabi`: for Rabi field - `...rydberg.detuning`: for Detuning field - In the absence of a field you the value is set to zero by default. - - ``` """ return Rydberg(self) @@ -30,6 +28,5 @@ def hyperfine(self) -> Hyperfine: - `...hyperfine.detuning`: for Detuning field - In the absence of a field you the value is set to zero by default. - ``` """ return Hyperfine(self) diff --git a/src/bloqade/builder/pragmas.py b/src/bloqade/builder/pragmas.py index 614e3752b..90030d724 100644 --- a/src/bloqade/builder/pragmas.py +++ b/src/bloqade/builder/pragmas.py @@ -25,7 +25,7 @@ def assign(self, **assignments) -> "Assign": pass in a list. This is the ONLY circumstance in which multiple values are allowed. - ### ### Usage Examples: + ### Usage Examples: ``` # define geometry >>> reg = bloqade.start @@ -38,20 +38,20 @@ def assign(self, **assignments) -> "Assign": ``` - You can now: - - ...assign(assignments).bloqade: select the bloqade local + - `...assign(assignments).bloqade`: select the bloqade local emulator backend - - ...assign(assignments).braket: select braket local emulator or + - `...assign(assignments).braket`: select braket local emulator or QuEra hardware - - ...assign(assignments).device(specifier_string): select backend + - `...assign(assignments).device(specifier_string)`: select backend by specifying a string - Assign multiple values to a single variable for a parameter sweep: - - ...assign(assignments).batch_assign(assignments): + - `...assign(assignments).batch_assign(assignments)`: - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available space/qubits on the QPU: - - ...assign(assignments).parallelize(cluster_spacing) + - `...assign(assignments).parallelize(cluster_spacing)` - Defer value assignment of certain variables to runtime: - - ...assign(assignments).args([previously_defined_vars]) + - `...assign(assignments).args([previously_defined_vars])` """ from bloqade.builder.assign import Assign @@ -86,18 +86,18 @@ def batch_assign( ``` - Next steps are: - - ...batch_assign(assignments).bloqade: select the bloqade + - `...batch_assign(assignments).bloqade`: select the bloqade local emulator backend - - ...batch_assign(assignments).braket: select braket local + - `...batch_assign(assignments).braket`: select braket local emulator or QuEra hardware - - ...batch_assign(assignments).device(specifier_string): select + - `...batch_assign(assignments).device(specifier_string)`: select backend by specifying a string - Parallelize the program register, duplicating the geometry and waveform sequence to take advantage of all available space/qubits on the QPU: - - ...batch_assign(assignments).parallelize(cluster_spacing) + - `...batch_assign(assignments).parallelize(cluster_spacing)` - Defer value assignment of certain variables to runtime: - - ...batch_assign(assignments).args([previously_defined_vars]) + - `...batch_assign(assignments).args([previously_defined_vars])` """ from bloqade.builder.assign import BatchAssign, ListAssign diff --git a/src/bloqade/builder/spatial.py b/src/bloqade/builder/spatial.py index 89243b025..a7ccd9040 100644 --- a/src/bloqade/builder/spatial.py +++ b/src/bloqade/builder/spatial.py @@ -99,7 +99,7 @@ def scale(self, value: ScalarType) -> "Scale": Scale the subsequent waveform to be applied on a certain set of atoms specified by the current spatial modulation. - ### ### Usage Examples: + ### Usage Examples: ``` # define program >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)])