In order to make room for more features inside the Bloqade ecosystem, we have created a new package to take the place of the old bloqade package. The new package is called bloqade-analog. The old package bloqade will house a namespace package for other features such as our new Bloqade Digital package with support for circuit-based quantum computers!
In order to make room for more features inside the Bloqade ecosystem, we have created a new package to take the place of the old bloqade package. The new package is called bloqade-analog. The old package bloqade will house a namespace package for other features such as our new Bloqade Digital package with support for circuit-based quantum computers!
The new package is a drop-in replacement for the old one. You can simply replace import bloqade with import bloqade.analog or from bloqade.analog import ... in your code. Everything else should work as before.
If you have old bloqade json files, you will not be able to directly deserialize them anymore because of the package restructuring. Howver we have provided some tools to migrate those JSON files to be compatible with bloqade-analog. You can do this by running the following command in the command line for a single file:
This will create a new file with the same name as the old file, but with _analog appended to the end of the filename. For example, if you have a file called my_bloqade.json, the new file will be called my_bloqade_analog.json. You can then use load to deserialize this file with the bloqade-analog package.
Another option is to use the migration tool in a python script:
frombloqade.analog.migrateimportmigrate
-
-# set the indent level for the output file
-indent:int=...
-# set to True if you want to overwrite the old file, otherwise the new file will be created with -analog appended to the end of the filename
-overwrite:bool=...
-f
-orfilenamein["file1.json","file2.json",...]:
-migrate(filename,indent=indent,overwrite=overwrite)
+
If you have old bloqade JSON files, you will not be able to directly deserialize them anymore because of the package restructuring. Howver we have provided some tools to migrate those JSON files to be compatible with bloqade-analog. You can do this by running the following command in the command line for a single file:
This will create a new file with the same name as the old file, but with _analog appended to the end of the filename. For example, if you have a file called my_bloqade.json, the new file will be called my_bloqade-analog.json. You can then use load to deserialize this file with the bloqade-analog package. There are other options for converting the file, such as setting the indent level for the output file or overwriting the old file. You can see all the options by running:
python-mbloqade.analog.migrate--help
+
You can also migrate multiple files at once by running:
Another option is to use the migration tool in a python script:
frombloqade.analog.migrateimportmigrate
+
+# set the indent level for the output file
+indent:int=...
+# set to True if you want to overwrite the old file, otherwise the new file will be created with -analog appended to the end of the filename
+overwrite:bool=...
+f
+orfilenamein["file1.json","file2.json",...]:
+migrate(filename,indent=indent,overwrite=overwrite)
This will migrate all the files in the list to the new format.
\ No newline at end of file
diff --git a/pr-preview/pr-1009/search/search_index.json b/pr-preview/pr-1009/search/search_index.json
index 683f6c55b..a2bef693b 100644
--- a/pr-preview/pr-1009/search/search_index.json
+++ b/pr-preview/pr-1009/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-,:!=\\[\\: )\"`/]+|\\.(?!\\d)|&[lg]t;|(?!\\b)(?=[A-Z][a-z])","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Index","text":"
[!IMPORTANT]
Bloqade has been restructured to make room for new features and improvements. Please refer to the migration guide for more information.
"},{"location":"#welcome-to-bloqade-queras-neutral-atom-sdk","title":"Welcome to Bloqade: QuEra's Neutral Atom SDK","text":""},{"location":"#what-is-bloqade","title":"What is Bloqade?","text":"
Bloqade is a Python SDK for QuEra's neutral atom quantum computer Aquila (check out our paper!). It's designed to make writing and analyzing the results of analog quantum programs on Aquila as easy as possible. It features custom atom geometries and flexible waveform definitions in both emulation and real hardware. Bloqade interfaces with the AWS Braket cloud service where Aquila is hosted, enabling you to submit programs as well as retrieve and analyze real hardware results all-in-one.
You can install the package with pip in your Python environment of choice via:
pip install bloqade\n
"},{"location":"#a-glimpse-of-bloqade","title":"A Glimpse of Bloqade","text":"
Let's try a simple example where we drive a Rabi oscillation on a single neutral atom. Don't worry if you're unfamiliar with neutral atom physics, (you can check out our Background for more information!) the goal here is to just give you a taste of what Bloqade can do.
We start by defining where our atoms go, otherwise known as the atom geometry. In this particular example we will use a small Honeycomb lattice:
from bloqade.analog.atom_arrangement import Honeycomb\n\ngeometry = Honeycomb(2, lattice_spacing = 10.0)\n
We can verify what the atom geometry looks like by .show()'ing it:
geometry.show()\n
We now define what the time evolution looks like using a pulse sequence. The pulse sequence here is the time profile of the Rabi Drive targeting the ground-Rydberg two level transition, which causes the Rabi oscillations. We choose a constant waveform with a value of \\(\\frac{\\pi}{2} \\text{rad}/\\text{us}\\) and a duration of \\(1.0 \\,\\text{us}\\). This produces a \\(\\frac{\\pi}{2}\\) rotation on the Bloch sphere meaning our final measurements should be split 50/50 between the ground and Rydberg state.
from math import pi\nrabi_program = (\n geometry\n .rydberg.rabi.amplitude.uniform\n .constant(value=pi/2, duration=1.0)\n)\n
Here rabi.amplitude means exactly what it is, the Rabi amplitude term of the Hamiltonian. uniform refers to applying the waveform uniformly across all the atom locations.
We can visualize what our program looks like again with .show():
We can now run the program through Bloqade's built-in emulator to get some results. We designate that we want the program to be run and measurements performed 100 times:
With the results we can generate a report object that contains a number of methods for analyzing our data, including the number of counts per unique bitstring:
If we want to submit our program to hardware we'll need to adjust the waveform as there is a constraint the Rabi amplitude waveform must start and end at zero. This is easy to do as we can build off the atom geometry we saved previously but apply a piecewise linear waveform:
Now instead of using the built-in Bloqade emulator we submit the program to Aquila. You will need to use the AWS CLI to obtain credentials from your AWS account or set the proper environment variables before hand.
.run_async is a non-blocking version of the standard .run method, allowing you to continue work while waiting for results from Aquila. .run_async immediately returns an object you can query for the status of your tasks in the queue as well.
You can do the exact same analysis you do on emulation results with hardware results too:
"},{"location":"#contributing-to-bloqade","title":"Contributing to Bloqade","text":"
Bloqade is released under the Apache License, Version 2.0. If you'd like the chance to shape the future of neutral atom quantum computation, see our Contributing Guide for more info!
Thank you for your interest in contributing to the project! We welcome all contributions. There are many different ways to contribute to Bloqade, and we are always looking for more help. We accept contributions in the form of bug reports, feature requests, documentation improvements, and code contributions. For more information about how to contribute, please read the following sections.
"},{"location":"contributing/#table-of-contents","title":"Table of Contents","text":"
Reporting a Bug
Reporting Documentation Issues
Feature Requests
Developing Bloqade
Design Philosophy and Architecture
Community Slack
Ask a Question
Providing Feedback
"},{"location":"contributing/asking-a-question/","title":"Ask a Question","text":"
If you're interested in contributing to Bloqade, or just want to discuss the project, join the discussion on GitHub Discussions at https://github.com/QuEraComputing/bloqade-analog/discussions
"},{"location":"contributing/code-of-conduct/","title":"Design Philosophy and Architecture","text":"
Given the heterogeneous nature of the hardware we target, We have decided to use a compiler-based approach to our software stack, allowing us to target different hardware backends with the same high-level language. Below is a diagram of the software stack in Bloqade.
When programming Bloqade using the Python API, the user constructs a representation of an analog quantum circuit. This representation is a flattened version of the actual analog circuit. Flattened means that the user input is a linear sequence of operations where the context of neighboring nodes in the sequence of instructions can determine the program tree structure. The Bloqade AST describes the actual analog circuit.
The Bloqade AST is a representation of a quantum analog circuit for neutral atom computing. It is a directed acyclic graph (DAG) with nodes for different hierarchical levels of the circuit. The base node is the AnalogCircuit which contains the geometry of the atoms stored as a AtomArragment or ParallelRegister objects. The other part of the circuit is the Sequence, which contains the waveforms that describe the drives for the Ryberg/Hyperfine transitions of each Rydberg atom. Each transition is represented by a Pulse including a Field for the drive's detuning, Rabi amplitude, and Rabi phase . A Field relates the spatial and temporal dependence of a drive. The spatial modulates the temporal dependence of the waveform. A DAG also describes the Waveform object. Finally, we have basic Scalar expressions as well for describing the syntax of real-valued continuous numbers.
"},{"location":"contributing/code-of-conduct/#bloqade-compilers-and-transpilers","title":"Bloqade Compilers and Transpilers","text":"
Given a user program expressed as the Bloqade AST, we can target various backends by transforming from the Bloqade AST to other kinds of IR. For example, when submitting a task to QuEra's hardware, we transform the Bloqade AST to the IR that describes a valid program for the hardware.
This process is referred to as lowering, which in a general sense is a transformation that takes you from one IR to another where the target IR is specialized or has a smaller syntactical structure. Transpiling corresponds to a transformation that takes you from one language to equivalent expressions in another. For example, we can transpile from the Bloqade AST in Python to the Bloqade AST in Julia. The generic term for both of these types of transformation in Bloqade is Code Generation. You will find various code generation implementations in various codegen modules.
You can join QuEra's Slack workspace with this link. Join the #bloqade channel to discuss anything related to Bloqade.
"},{"location":"contributing/design-philosophy-and-architecture/","title":"Design Philosophy and Architecture","text":"
Given the heterogeneous nature of the hardware we target, We have decided to use a compiler-based approach to our software stack, allowing us to target different hardware backends with the same high-level language. Below is a diagram of the software stack in Bloqade.
When programming Bloqade using the Python API, the user constructs a representation of an analog quantum circuit. This representation is a flattened version of the actual analog circuit. Flattened means that the user input is a linear sequence of operations where the context of neighboring nodes in the sequence of instructions can determine the program tree structure. The Bloqade AST describes the actual analog circuit.
The Bloqade AST is a representation of a quantum analog circuit for neutral atom computing. It is a directed acyclic graph (DAG) with nodes for different hierarchical levels of the circuit. The base node is the AnalogCircuit which contains the geometry of the atoms stored as a AtomArragment or ParallelRegister objects. The other part of the circuit is the Sequence, which contains the waveforms that describe the drives for the Ryberg/Hyperfine transitions of each Rydberg atom. Each transition is represented by a Pulse including a Field for the drive's detuning, Rabi amplitude, and Rabi phase . A Field relates the spatial and temporal dependence of a drive. The spatial modulates the temporal dependence of the waveform. A DAG also describes the Waveform object. Finally, we have basic Scalar expressions as well for describing the syntax of real-valued continuous numbers.
"},{"location":"contributing/design-philosophy-and-architecture/#bloqade-compilers-and-transpilers","title":"Bloqade Compilers and Transpilers","text":"
Given a user program expressed as the Bloqade AST, we can target various backends by transforming from the Bloqade AST to other kinds of IR. For example, when submitting a task to QuEra's hardware, we transform the Bloqade AST to the IR that describes a valid program for the hardware.
This process is referred to as lowering, which in a general sense is a transformation that takes you from one IR to another where the target IR is specialized or has a smaller syntactical structure. Transpiling corresponds to a transformation that takes you from one language to equivalent expressions in another. For example, we can transpile from the Bloqade AST in Python to the Bloqade AST in Julia. The generic term for both of these types of transformation in Bloqade is Code Generation. You will find various code generation implementations in various codegen modules.
"},{"location":"contributing/developing-bloqade/","title":"Setting up your Development Environment","text":"
Before You Get Started
Depending on the complexity of the contribution you'd like to make to Bloqade, it may be worth reading the Design Philosophy and Architecture section to get an idea of why Bloqade is structured the way that it is and how to make your contribution adhere to this philosophy.
Our development environment contains a set of tools we use for development, testing, and documentation. This section describes how to set up the development environment. We primarily use pdm to manage python environments and dependencies.
"},{"location":"contributing/developing-bloqade/#setting-up-python","title":"Setting up Python","text":"
We use pdm to manage dependencies and virtual environment. After cloning the repository, run the following command to install dependencies:
We primarily use ruff - an extremely fast linter for Python, and black as formatter. These have been configured into pre-commit hooks. After installing pre-commit on your own system, you can install pre-commit hooks to git via
pre-commit install\n
"},{"location":"contributing/documentation-issues/","title":"Reporting a Documentation Issue","text":"
We are always looking to improve our documentation. If you find a typo or think something is unclear, please open an issue on our GitHub page: https://github.com/QuEraComputing/bloqade-analog/issues
For typos or other minor problems, create an issue that contains a link to the specific page that includes the problem, along with a description of the problem and possibly a solution.
For a request for new documentation content, please open up an issue and describe what you think is missing from the documentation.
"},{"location":"contributing/feature-requests/","title":"Requesting new Features","text":"
Given that we are currently at the beginning of the development of the Bloqade python interface, we are open to suggestions about what features would be helpful to include in future package iterations. If you have a request for a new feature, please open an issue on our GitHub page: https://github.com/QuEraComputing/bloqade-analog/issues
We ask that the feature requests be as specific as possible. Please include the following information in your feature request:
A short, descriptive title.
A detailed description of the feature, including your attempt to solve the problem with the current version of Bloqade.
A minimal code example that demonstrates the need for the feature.
While Github Issues are a great way for us to better understand any issues your having with Bloqade as well as provide us with feature requests, we're always looking for ways to collect more general feedback about what the user experience with Bloqade is like.
To do that we have this form where you can provide your thoughts after using/experimenting/tinkering/hacking with Bloqade.
Your feedback will help guide the future of Bloqade's design so be honest and know that you're contributing to the future of Quantum Computing with Neutral Atoms!
"},{"location":"contributing/reporting-a-bug/","title":"Reporting a Bug","text":"
Bloqade is currently in the alpha phase of development, meaning bugs most likely exist in the current implementation. We are continuously striving to improve the stability of Bloqade. As such, we encourage our users to report all bugs they find. To do this, we ask you to submit an issue to our GitHub page: https://github.com/QuEraComputing/bloqade-analog/issues
Please include the following information in your bug report:
A short, descriptive title.
A detailed description of the bug, including the expected behavior and what happened.
A minimal code example that reproduces the bug.
The version of Bloqade you are using.
The version of Python you are using.
The version of your operating system.
"},{"location":"home/background/","title":"Background","text":""},{"location":"home/background/#neutral-atom-qubits","title":"Neutral Atom Qubits","text":"
The qubits that QuEra's neutral atom computer Aquila and Bloqade are designed to emulate are based on neutral atoms. As the name implies they are atoms that are neutrally charged but are also capable of achieving a Rydberg state where a single electron can be excited to an incredibly high energy level without ionizing the atom.
This incredibly excited electron energy level \\(|r\\rangle\\) and its default ground state \\(|g\\rangle\\) create a two-level system where superposition can occur. For enabling interaction between two or more qubits and achieving entanglement, when the neutral atoms are in the Rydberg state a phenomenon known as the Rydberg blockade can occur where an atom in the Rydberg state prevents a neighboring atom from also being excited to the same state.
For a more nuanced and in-depth read about the neutral atoms that Bloqade and Aquila use, refer to QuEra's qBook section on Qubits by puffing up atoms.
"},{"location":"home/background/#analog-vs-digital-quantum-computing","title":"Analog vs Digital Quantum Computing","text":"
There are two modes of quantum computation that neutral atoms are capable of: Analog and Digital.
You can find a brief explanation of the distinction below but for a more in-depth explanation you can refer to QuEra's qBook section on Analog vs Digital Quantum Computing
In the analog mode (supported by Bloqade and Aquila) you control your computation through the parameters of a time-dependent Hamiltonian that influences all the qubits at once. There are options for local control of the Hamiltonian on certain qubits however.
In the Digital Mode individual or multiple groups of qubits are controlled by applying gates (individual unitary operations). For neutral atoms, this digital mode can be accomplished with the introduction of hyperfine coupling, enabling a quantum state to be stored for long periods of time while also allowing for multi-qubit gates.
where: \\(\\Omega_j\\), \\(\\phi_j\\), and \\(\\Delta_j\\) denote the Rabi frequency amplitude, laser phase, and the detuning of the driving laser field on atom (qubit) \\(j\\) coupling the two states \\(| g_j \\rangle\\) (ground state) and \\(| r_j \\rangle\\) (Rydberg state); \\(\\hat{n}_j = |r_j\\rangle \\langle r_j|\\) is the number operator, and \\(V_{jk} = C_6/|\\mathbf{x}_j - \\mathbf{x}_k|^6\\) describes the Rydberg interaction (van der Waals interaction) between atoms \\(j\\) and \\(k\\) where \\(\\mathbf{x}_j\\) denotes the position of the atom \\(j\\); \\(C_6\\) is the Rydberg interaction constant that depends on the particular Rydberg state used. For Bloqade, the default \\(C_6 = 862690 \\times 2\\pi \\text{ MHz \u03bcm}^6\\) for \\(|r \\rangle = \\lvert 70S_{1/2} \\rangle\\) of the \\(^{87}\\)Rb atoms; \\(\\hbar\\) is the reduced Planck's constant.
The Rydberg Many-Body Hamiltonian already implies from its subscripts that you can also have local control over your atoms. In Bloqade this local control extends to any term in the Hamiltonian while on Aquila this is currently restricted to the \\(\\Delta_j\\) laser detuning term.
Fields in Bloqade give you local (single-atom) control over the many-body Rydberg Hamiltonian.
They are a sum of one or more spatial modulations, which allows you to scale the amplitude of the waveform across the different sites in the system:
"},{"location":"home/gotchas/","title":"Bloqade Gotchas: Common Mistakes in Using Bloqade","text":"
It is tempting when coming from different quantum SDKs and frameworks to apply the same pattern of thought to Bloqade. However, a lot of practices from those prior tools end up being anti-patterns in Bloqade. While you can use those patterns and they can still work, it ends up causing you the developer to write unnecessarily verbose, complex, and hard-to-read code as well as preventing you from reaping the full benefits of what Bloqade has to offer.
This page is dedicated to cataloguing those anti-patterns and what you can do instead to maximize the benefit Bloqade can offer you.
"},{"location":"home/gotchas/#redefining-lattices-and-common-atom-arrangements","title":"Redefining Lattices and Common Atom Arrangements","text":"
You might be tempted to define lattice-based geometries through the following means:
from bloqade import start\n\nspacing = 4.0\ngeometry = start.add_positions(\n [(i * spacing, j * spacing) for i in range(4) for j in range(4)]\n)\n
This is quite redundant and verbose, especially considering Bloqade offers a large number of pre-defined lattices you can customize the spacing of in bloqade.atom_arrangement. In the code above, we're just defining a 4x4 square lattice of atoms with 4.0 micrometers of spacing between them. This can be expressed as follows
"},{"location":"home/gotchas/#copying-a-program-to-create-new-ones","title":"Copying a Program to create New Ones","text":"
Many gate-based SDKs rely on having a mutable object representing your circuit. This means if you want to build on top of some base circuit without mutating it, you have to copy it:
import copy\n\nbase_circuit = qubits.x(0)....\n# make copy of base circuit\ncustom_circuit_1 = copy(base_circuit)\n# build on top of copy of base circuit\ncustom_circuit_1.x(0).z(5)...\n# create a new circuit by copying the base again\ncustom_circuit_2 = copy(base_circuit)\n# build on top of that copy again\ncustom_circuit_2.y(5).cz(0,2)...\n
In Bloqade Python this is unnecessary because at every step of your program an immutable object is returned which means you can save it and not have to worry about mutating any internal state.
from bloqade import start\nbase_program = start.add_position((0,0)).rydberg.rabi.amplitude.uniform\n# Just recycle your base program! No `copy` needed!\nnew_program_1 = base_program.constant(duration=5.0, value=5.0)\nnew_program_2 = base_program.piecewise_linear(\n durations=[5.0], values = [0.0, 5.0]\n)\n
"},{"location":"home/gotchas/#creating-new-programs-instead-of-using-batch_assign","title":"Creating New Programs Instead of Using .batch_assign","text":"
If you have a set of parameters you'd like to test your program on versus a single parameter, don't generate a new program for each value:
rabi_values = [2.0, 4.7, 6.1]\nprograms_with_different_rabi_values = []\n\nfor rabi_value in rabi_values:\n program = start.add_position((0, 0)).rydberg.rabi.amplitude.uniform.constant(\n duration=5.0, value=rabi_value\n )\n programs_with_different_rabi_values.append(program)\n\nresults = []\n\nfor program in programs_with_different_rabi_values:\n result = program.bloqade.python().run(100)\n results.append(result)\n
Instead take advantage of the fact Bloqade has facilities specifically designed to make trying out multiple values in your program without needing to make individual copies via .batch_assign. The results are also automatically handled for you so each value you test has its own set of results, but all collected in a singular dataframe versus the above where you'd have to keep track of individual results.
rabi_values = [2.0, 4.7, 6.1]\n# place a variable for the Rabi Value and then batch assign values to it\nprogram_with_rabi_values = start.add_position(\n 0, 0\n).rydberg.rabi.amplitude.uniform.constant(duration=5.0, value=\"rabi_value\")\nprogram_with_assignments = program_with_rabi_values.batch_assign(\n rabi_value=rabi_values\n)\n\n# get your results in one dataframe versus having to keep track of a\n# bunch of individual programs and their individual results\nbatch = program_with_assignments.bloqade.python().run(100)\nresults_dataframe = batch.report().dataframe\n
"},{"location":"home/migration/","title":"Migrating to Bloqade Analog","text":""},{"location":"home/migration/#introduction","title":"Introduction","text":"
In order to make room for more features inside the Bloqade ecosystem, we have created a new package to take the place of the old bloqade package. The new package is called bloqade-analog. The old package bloqade will house a namespace package for other features such as our new Bloqade Digital package with support for circuit-based quantum computers!
The new package is a drop-in replacement for the old one. You can simply replace import bloqade with import bloqade.analog or from bloqade.analog import ... in your code. Everything else should work as before.
lets say your header of your python script looks like this:
from bloqade import var\nfrom bloqade.atom_arrangement import Square\n...\n
You can simply replace it with:
from bloqade.analog import var\nfrom bloqade.analog.atom_arrangement import Square\n...\n
"},{"location":"home/migration/#migrating-old-bloqade-json-files","title":"Migrating old bloqade json files","text":"
If you have old bloqade json files, you will not be able to directly deserialize them anymore because of the package restructuring. Howver we have provided some tools to migrate those JSON files to be compatible with bloqade-analog. You can do this by running the following command in the command line for a single file:
This will create a new file with the same name as the old file, but with _analog appended to the end of the filename. For example, if you have a file called my_bloqade.json, the new file will be called my_bloqade_analog.json. You can then use load to deserialize this file with the bloqade-analog package.
Another option is to use the migration tool in a python script:
from bloqade.analog.migrate import migrate\n\n # set the indent level for the output file\nindent: int = ...\n# set to True if you want to overwrite the old file, otherwise the new file will be created with -analog appended to the end of the filename\noverwrite: bool = ...\nf\nor filename in [\"file1.json\", \"file2.json\", ...]:\n migrate(filename, indent=indent, overwrite=overwrite)\n
This will migrate all the files in the list to the new format."},{"location":"home/migration/#having-trouble-comments-or-concerns","title":"Having trouble, comments, or concerns?","text":"
All the sections below are self-contained, you can click on the links in the Table of Contents to read the relevant parts.
"},{"location":"home/quick_start/#navigating-the-bloqade-api","title":"Navigating the Bloqade API","text":"
As you develop your Bloqade program, you are expected to rely on pop-up \"hints\" provided in your development environment to help you determine what the next part of your program should be.
"},{"location":"home/quick_start/#defining-atom-geometry","title":"Defining Atom Geometry","text":"
You can import pre-defined geometries based on Bravais lattices from bloqade.atom_arrangement. You may also specify a lattice spacing which dictates the spacing between the atoms as well as the number of atom sites in a certain direction.
Specify which level coupling to drive: rydberg or hyperfine
Specify detuning, rabi.amplitude or rabi.phase
Specify the spatial modulation
Which then leads you to the ability to specify a waveform of interest and begin constructing your pulse sequence. In the example below, we target the ground-Rydberg level coupling to drive with uniform spatial modulation for the Rabi amplitude. Our waveform is a piecewise linear one which ramps from \\(0\\) to \\(5 \\,\\text{rad/us}\\), holds that value for \\(1 \\,\\text{us}\\) and then ramps back down to \\(0 \\,\\text{rad/us}\\).
You aren't restricted to just piecewise linear waveforms however, you can also specify:
linear - Define a transition from one value to another over a duration
constant - Define a fixed value over a duration
piecewise_constant - Define a step-wise function with specific durations for each step
poly - Define a polynomial waveform using coefficients over a duration
"},{"location":"home/quick_start/#arbitrary-functions-as-waveforms","title":"Arbitrary Functions as Waveforms","text":"
For more complex waveforms it may provide to be tedious trying to chain together a large number of piecewise_constant or piecewise_linear methods and instead easier to just define the waveform as a function of time.
Bloqade lets you easily plug in an arbitrary function with .fn:
In this form you can immediately emulate it if you'd like but to run this on hardware you need to discretize it. The waveform on hardware has to either be:
Piecewise linear for Rabi amplitude and detuning terms of the Hamiltonian
Piecewise constant for the Phase term of the Hamiltonian
Bloqade can automatically perform this conversion with sample(), all you need to do is specify the kind of interpolation and the size of the discretization step in time. Below we set the step duration to be \\(0.05 \\,\\text{us}\\) with \"linear\" interpolation to give us a resulting piecewise linear waveform.
Programs that have custom functions as waveforms are not fully serializable. This means that when you are saving and reloading results, the original embedded program will be missing that custom waveform. You will still be able to analyze the saved results!
"},{"location":"home/quick_start/#slicing-and-recording-waveforms","title":"Slicing and Recording Waveforms","text":"
When you conduct parameter sweeps with your program, you may want to sweep over your program across time. This will require \"slicing\" your waveforms, where you define the waveform of interest and then, using a variable with .slice, indicate the times at which the waveform duration should be cut short.
In the example below we define a simple piecewise linear waveform but slice it starting from a time duration of \\(0 \\,\\text{us}\\) to values between \\(1\\) to \\(2 \\,\\text{us}\\).
This program will run fine in emulation but due to hardware constraints certain waveforms (such as those targeting the Rabi Amplitude), the waveform needs to start and end at \\(0 \\,\\text{rad}/\\text{us}\\). Thus, there needs to be a way to slice our waveform but also add an end component to that waveform. .record in Bloqade lets you literally \"record\" the value at the end of a .slice and then use it to construct further parts of the waveform.
In the program below the waveform is still sliced but with the help of .record a linear segment that pulls the waveform down to \\(0.0 \\,\\text{rad}/\\text{us}\\) from whatever its current value at the slice is in \\(0.7 \\,\\text{us}\\) is added.
"},{"location":"home/quick_start/#waveforms-with-no-geometry","title":"Waveforms with No Geometry","text":"
If you have multiple atom geometries you'd like to apply a pulse sequence to or you simply don't want to worry about what atom geometry to start with, you can just build straight off of start:
When you've completed the definition of your program you can use Bloqade's own emulator to get results. The emulation performs the time evolution of the analog Rydberg Hamiltonian. Here we say we want to the program to be run and measurements obtained 1000 times.
"},{"location":"home/quick_start/#submitting-to-hardware","title":"Submitting to Hardware","text":"
To submit your program to hardware ensure you have your AWS Braket credentials loaded. You will need to use the AWS CLI to do this.
Then it's just a matter of selecting the Aquila on Braket backend. Before going any further Bloqade provides two options for running your program on actual hardware:
Using .run is blocking, meaning you will not be able to execute anything else while Bloqade waits for results
Using .run_async lets you submit to hardware and continue any further execution, while also letting you query the status of your program in the queue.
In the example below we use .run_async to specify the program should be run and measurements obtained 1000 times.
Which gives us the Task ID, a unique identifier for the task as well as the status of the task. In the example below the task is Enqueued meaning it has been successfully created and is awaiting execution on the cloud. When the task is actually running on hardware, the status will change to Running.
task ID status shots\n0 arn:aws:braket:us-east-1:XXXXXXXXXXXX:quantum-... Enqueued 100\n
You can easily do parameter sweeps in emulation and on Aquila with variables. Bloqade automatically detects strings in your program as variables that you can later assign singular or multiple values to.
In the example below, we define a program with a singular variable that controls the amplitude of the waveform.
import numpy as np\nrabi_amplitudes = np.linspace(1.0, 2.0, 20)\n\nmultiple_value_assignment = rabi_oscillations_program.batch_assign(rabi_amplitude=rabi_amplitudes)\n
This will actually create multiple versions of the program internally, with each program assigned a fixed value from the sweep. Bloqade will automatically handle the compilation of results from these multiple programs in order, meaning there is no major departure from what you saw in analyzing the results of your program.
You can also delay assignment of a value to a variable by first declaring it in .args() and then passing a value when you call run:
Variables in Bloqade can also be symbolically manipulated, giving you even more flexibility when you construct your program.
In the example below, we externally declare a variable my_var that then has some arithmetic done on it to allow it to have a different value in a later part of the program:
You still perform variable assignment just like you normally would:
program = detuning_waveform.assign(my_variable=1.0)\n
You can also use Python's built-in sum if you want the sum of multiple variables as a value in your program. This is quite useful when it comes to needing to indicate a full duration for a waveform that doesn't need to be split up:
During program development, it can be quite handy to know what true hardware capabilities are and incorporate that information programmaticaly. Bloqade offers the ability to do this via get_capabilities().
get_capabilities() (importable directly from bloqade) returns a QuEraCapabilities object. This object contains all the hardware constraints in Decimal format for the Aquila machine, our publically-accessible QPU on AWS Braket.
An example of using get_capabilities() is presented below:
from bloqade import get_capabilities, piecewise_linear\n\n# get capabilities for Aquila\naquila_capabilities = get_capabilities()\n\n# obtain maximum Rabi frequency as Decimal\nmax_rabi = aquila_capabilities.capabilities.rydberg.global_.rabi_frequency_max\n\n# use that value in constructing a neat Rabi waveform\nrabi_wf = piecewise_linear(durations = [0.5, 1.0, 0.5], values = [0, max_rabi, max_rabi, 0])\n
The attribute names for each value have been provided below but will require you to provide the proper prefix like in the example above (e.g. the maximum number of qubits lives under the number_qubits_max attribute which can be navigated to via *your_QuEra_Capabilities_Object*.lattice.number_qubits_max).
Use prefix your_capabilities_object.capabilities.task for:
minimum number of shots
maximum number of shots
Capability Attribute Value Minimum Number of Shots number_shots_min 1 Maximum Number of Shots number_shots_max 1000"},{"location":"reference/hardware-capabilities/#lattice-geometry","title":"Lattice Geometry","text":"
Use prefix your_capabilities_object.capabilities.lattice for:
maximum number of qubits
Use prefix your_capabilities_object.capabilities.lattice.area for:
maximum lattice area width
maximum lattice area height
Use prefix your_capabilities_object.capabilities.lattice.geometry for:
maximum number of sites
position resolution
minimum radial spacing
minimum vertical spacing
Capability Attribute Value Maximum Number of Qubits number_qubits_max 256 Maximum Lattice Area Width width 75.0 \u00b5m Maximum Lattice Area Height height 76.0 \u00b5m Minimum Radial Spacing between Qubits spacing_radial_min 4.0 \u00b5m Minimum Vertical Spacing between Qubits spacing_vertical_min 4.0 \u00b5m Position Resolution position_resolution 0.1 \u00b5m Maximum Number of Sites number_sites_max 256"},{"location":"reference/hardware-capabilities/#global-rydberg-values","title":"Global Rydberg Values","text":"
Use prefix your_capabilities_object.capabilities.rydberg for:
C6 Coefficient
Use prefix your_capabilities_object.capabilities.rydberg.global_ for:
Everything else related to global (applied to all atom) capabilities
Capability Attribute Value Rydberg Interaction Constant c6_coefficient 5.42\u00d710\u2076 rad/\u03bcs \u00d7 \u00b5m\u2076 Minimum Rabi Frequency rabi_frequency_min 0.00 rad/\u03bcs Maximum Rabi Frequency rabi_frequency_max 15.8 rad/\u03bcs Rabi Frequency Resolution rabi_frequency_resolution 0.0004 rad/\u03bcs Maximum Rabi Frequency Slew Rate rabi_frequency_slew_rate_max 250.0 rad/\u00b5s\u00b2 Minimum Detuning detuning_min -125.0 rad/\u03bcs Maximum Detuning detuning_max 125.0 rad/\u03bcs Detuning Resolution detuning_resolution 2.0\u00d710\u207b\u2077 rad/\u03bcs Maximum Detuning Slew Rate detuning_slew_rate_max 2500.0 rad/\u00b5s\u00b2 Minimum Phase phase_min -99.0 rad Maximum Phase phase_max 99.0 rad Phase Resolution phase_resolution 5.0\u00d710\u207b\u2077 rad Minimum Time time_min 0.0 \u00b5s Maximum Time time_max 4.0 \u00b5s Time Resolution time_resolution 0.001 \u00b5s Minimum \u0394t time_delta_min 0.05 \u00b5s"},{"location":"reference/hardware-capabilities/#local-detuning-values","title":"Local Detuning Values","text":"
Use prefix your_capabilities_object.capabilities.rydberg.local for the following values:
Capability Attribute Value Maximum Detuning detuning_max 125.0 rad/\u03bcs Minimum Detuning detuning_min 0 rad/\u03bcs Maximum Detuning Slew Rate detuning_slew_rate_max 1256.0 rad/\u00b5s\u00b2 Maximum Number of Local Detuning Sites number_local_detuning_sites 200 Maximum Site Coefficient site_coefficient_max 1.0 Minimum Site Coefficient site_ceofficient_min 0.0 Minimum Radial Spacing spacing_radial_min 5 \u00b5m Minimum \u0394t time_delta_min 0.05 \u03bcs Time Resolution time_resolution 0.001 \u00b5s"},{"location":"reference/overview/","title":"Builder Overview","text":"
You may have noticed from the Getting Started and Tutorials that Bloqade uses this interesting, dot-intensive syntax.
from bloqade import start\n\nprog = start.add_position((0,0)).rydberg.rabi.amplitude.uniform.constant(1,1)\n
Exhibit A: Lots of Dots
In fact, it might look remniscent of what you see in some gate-based Quantum Computing SDKs:
# this is strictly pseudocode\ncircuit = init_qubits(n_qubits)\n# note the dots!\ncircuit.x(0).z(1).cnot(0, 1)...\n
We call this syntax the builder or builder syntax and as its name implies, it is designed to let you build programs for Analog Hamiltonian Simulation hardware as easily and as straightforward as possible.
The linear structure implies a natural hierarchy in how you think about targeting the various degrees of freedom (detuning, atom positions, Rabi amplitude, etc.) your program will have. In the beginning you have unrestricted access to all these degrees of freedom but in order to do something useful you need to:
Narrow down and explicitly identify what you want to control
Provide the instructions on how you want to control what your focused on
Context is a strong component of the builder syntax, as you are both actively restricted from doing certain things that can introduce ambiguity based on where you are in your program and repeating the same action in different parts of the program yields different results.
While we hope the Smart Documentation (the ability to instantly see all your next possible steps and their capabilities in your favorite IDE/IPython) is sufficient to get you where you need to go, we undestand it's particularly beneficial to get a high-level overview of things before diving in.
The Standard Representation is a nice flow chart that gives a high-level overview of the different steps and components in the builder syntax.
cast Real number (or list/tuple of Real numbers) to Scalar Literal.
cast str (or list/tuple of Real numbers) to Scalar Variable.
Parameters:
Name Type Description Default pyUnion[str, Real, Tuple[Real], List[Real]]
python object to cast
required
Returns:
Type Description Scalar
Scalar
Source code in src/bloqade/analog/ir/scalar.py
def cast(py) -> \"Scalar\":\n \"\"\"\n 1. cast Real number (or list/tuple of Real numbers)\n to [`Scalar Literal`][bloqade.ir.scalar.Literal].\n\n 2. cast str (or list/tuple of Real numbers)\n to [`Scalar Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str,Real,Tuple[Real],List[Real]]): python object to cast\n\n Returns:\n Scalar\n \"\"\"\n ret = trycast(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Scalar Literal\")\n\n return ret\n
@beartype\ndef constant(duration: ScalarType, value: ScalarType) -> Constant:\n \"\"\"Create a Constant waveform.\n\n Args:\n duration (ScalarType): Duration of the Constant waveform.\n value (ScalarType): Value of the Constant waveform.s\n\n Returns:\n Constant: A Constant waveform.\n \"\"\"\n return Constant(value, duration)\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.dumps
{}
Returns:
Name Type Description strstr
the serialized object as a string
Source code in src/bloqade/analog/serialize.py
@beartype\ndef dumps(\n o: Any,\n use_decimal: bool = True,\n **json_kwargs,\n) -> str:\n \"\"\"Serialize object to string\n\n Args:\n o (Any): the object to serialize\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.dumps\n\n Returns:\n str: the serialized object as a string\n \"\"\"\n if not isinstance(o, Serializer.types):\n raise TypeError(\n f\"Object of type {type(o)} is not JSON serializable. \"\n f\"Only {Serializer.types} are supported.\"\n )\n return json.dumps(o, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n
Name Type Description Default use_experimentalbool
Get experimental capabilities instead of standard ones. By default value is False.
False
Returns:
Name Type Description QuEraCapabilitiesQuEraCapabilities
capabilities object for Aquila device.
Note
Units of time, distance, and energy are microseconds (us), micrometers (um), and rad / us, respectively.
For a comprehensive list of capabilities, see the Hardware Reference page
Source code in src/bloqade/analog/factory.py
def get_capabilities(use_experimental: bool = False) -> \"QuEraCapabilities\":\n \"\"\"Get the device capabilities for Aquila\n\n Args:\n use_experimental (bool): Get experimental capabilities instead of\n standard ones. By default value is False.\n\n Returns:\n QuEraCapabilities: capabilities object for Aquila device.\n\n\n Note:\n Units of time, distance, and energy are microseconds (us),\n micrometers (um), and rad / us, respectively.\n\n For a comprehensive list of capabilities,\n see the [Hardware Reference](../../reference/hardware-capabilities.md)\n page\n \"\"\"\n\n from bloqade.analog.submission.capabilities import get_capabilities\n\n # manually convert to units\n return get_capabilities(use_experimental=use_experimental).scale_units(\n Decimal(\"1e6\"), Decimal(\"1e-6\")\n )\n
@beartype\ndef linear(duration: ScalarType, start: ScalarType, stop: ScalarType) -> Linear:\n \"\"\"Create a Linear waveform.\n\n Args:\n duration (ScalarType): Duration of linear waveform\n start (ScalarType): Starting value of linear waveform\n stop (ScalarType): Ending value of linear waveform\n\n Returns:\n Linear: Linear waveform\n \"\"\"\n return Linear(start, stop, duration)\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.loads
{}
Returns:
Name Type Description Any
the deserialized object
Source code in src/bloqade/analog/serialize.py
@beartype\ndef loads(s: str, use_decimal: bool = True, **json_kwargs):\n \"\"\"Load object from string\n\n Args:\n s (str): the string to load\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.loads\n\n Returns:\n Any: the deserialized object\n \"\"\"\n load_bloqade()\n return json.loads(\n s, object_hook=Serializer.object_hook, use_decimal=use_decimal, **json_kwargs\n )\n
Create a piecewise constant waveform from a list of durations and values. The value duration[i] corresponds to the length of time for the i'th segment with a value of values[i].
Parameters:
Name Type Description Default durationsList[ScalarType]
The duration of each segment
required valuesList[ScalarType]
The values for each segment
required
Raises:
Type Description ValueError
If the length of values is not the same as the length of
Returns:
Name Type Description WaveformWaveform
The piecewise linear waveform.
Source code in src/bloqade/analog/factory.py
@beartype\ndef piecewise_constant(\n durations: List[ScalarType], values: List[ScalarType]\n) -> Waveform:\n \"\"\"Create a piecewise linear waveform.\n\n Create a piecewise constant waveform from a list of durations and values. The\n value `duration[i]` corresponds to the length of time for the i'th segment\n with a value of `values[i]`.\n\n Args:\n durations (List[ScalarType]): The duration of each segment\n values (List[ScalarType]): The values for each segment\n\n Raises:\n ValueError: If the length of `values` is not the same as the length of\n `durations`.\n\n Returns:\n Waveform: The piecewise linear waveform.\n \"\"\"\n if len(durations) != len(values):\n raise ValueError(\n \"The length of values must be the same as the length of durations\"\n )\n\n pwc_wf = None\n for duration, value in zip(durations, values):\n if pwc_wf is None:\n pwc_wf = Constant(value, duration)\n else:\n pwc_wf = pwc_wf.append(Constant(value, duration))\n\n return pwc_wf\n
Create a piecewise linear waveform from a list of durations and values. The value duration[i] is of the linear segment between values[i] and values[i+1].
Parameters:
Name Type Description Default durationsList[ScalarType]
The duration of each segment
required valuesList[ScalarType]
The values for each segment
required
Raises:
Type Description ValueError
If the length of values is not one greater than the length of
Returns:
Name Type Description WaveformWaveform
The piecewise linear waveform.
Source code in src/bloqade/analog/factory.py
@beartype\ndef piecewise_linear(durations: List[ScalarType], values: List[ScalarType]) -> Waveform:\n \"\"\"Create a piecewise linear waveform.\n\n Create a piecewise linear waveform from a list of durations and values. The\n value `duration[i]` is of the linear segment between `values[i]` and `values[i+1]`.\n\n Args:\n durations (List[ScalarType]): The duration of each segment\n values (List[ScalarType]): The values for each segment\n\n Raises:\n ValueError: If the length of `values` is not one greater than the length of\n `durations`.\n\n Returns:\n Waveform: The piecewise linear waveform.\n \"\"\"\n\n if len(durations) + 1 != len(values):\n raise ValueError(\n \"The length of values must be one greater than the length of durations\"\n )\n\n pwl_wf = None\n for duration, start, stop in zip(durations, values[:-1], values[1:]):\n if pwl_wf is None:\n pwl_wf = Linear(start, stop, duration)\n else:\n pwl_wf = pwl_wf.append(Linear(start, stop, duration))\n\n return pwl_wf\n
List of arguments to leave till runtime. Defaults to [].
[]
Returns:
Name Type Description RoutineRoutine
An object that can be used to dispatch a rydberg program to multiple backends.
Source code in src/bloqade/analog/factory.py
@beartype\ndef rydberg_h(\n atoms_positions: Any,\n detuning: Optional[Waveform] = None,\n amplitude: Optional[Waveform] = None,\n phase: Optional[Waveform] = None,\n static_params: Dict[str, Any] = {},\n batch_params: Union[List[Dict[str, Any]], Dict[str, Any]] = [],\n args: List[str] = [],\n) -> Routine:\n \"\"\"Create a rydberg program with uniform detuning, amplitude, and phase.\n\n Args:\n atoms_positions (Any): Description of geometry of atoms in system.\n detuning (Optional[Waveform], optional): Waveform for detuning.\n Defaults to None.\n amplitude (Optional[Waveform], optional): Waveform describing the amplitude of\n the rabi term. Defaults to None.\n phase (Optional[Waveform], optional): Waveform describing the phase of rabi\n term. Defaults to None.\n static_params (Dict[str, Any], optional): Define static parameters of your\n program. Defaults to {}.\n batch_params (Union[List[Dict[str, Any]], Dict[str, Any]], optional):\n Parmaters for a batch of tasks. Defaults to [].\n args (List[str], optional): List of arguments to leave till runtime.\n Defaults to [].\n\n Returns:\n Routine: An object that can be used to dispatch a rydberg program to\n multiple backends.\n \"\"\"\n from bloqade.analog import start\n from bloqade.analog.atom_arrangement import AtomArrangement\n\n if isinstance(atoms_positions, AtomArrangement):\n prog = atoms_positions\n else:\n prog = start.add_position(atoms_positions)\n\n if detuning is not None:\n prog = prog.rydberg.detuning.uniform.apply(detuning)\n\n if amplitude is not None:\n prog = prog.amplitude.uniform.apply(amplitude)\n\n if phase is not None:\n prog = prog.phase.uniform.apply(phase)\n\n prog = prog.assign(**static_params)\n\n if isinstance(batch_params, dict):\n prog = prog.batch_assign(**batch_params)\n else:\n prog = prog.batch_assign(batch_params)\n\n prog = prog.args(args)\n\n return prog.parse()\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.dump
{}
Returns:
Type Description None
None
Source code in src/bloqade/analog/serialize.py
@beartype\ndef save(\n o: Any,\n fp: Union[TextIO, str],\n use_decimal=True,\n **json_kwargs,\n) -> None:\n \"\"\"Serialize object to file\n\n Args:\n o (Any): the object to serialize\n fp (Union[TextIO, str]): the file path or file object\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.dump\n\n Returns:\n None\n \"\"\"\n if not isinstance(o, Serializer.types):\n raise TypeError(\n f\"Object of type {type(o)} is not JSON serializable. \"\n f\"Only {Serializer.types} are supported.\"\n )\n if isinstance(fp, str):\n with open(fp, \"w\") as f:\n json.dump(o, f, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n else:\n json.dump(o, fp, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n
If depth=None, return current depth. If depth is provided, setting current depth to depth
Parameters:
Name Type Description Default depthint
the user specified depth. Defaults to None.
None
Returns:
Name Type Description int
current updated depth
Source code in src/bloqade/analog/__init__.py
def tree_depth(depth: int = None):\n \"\"\"Setting globally maximum depth for tree printing\n\n If `depth=None`, return current depth.\n If `depth` is provided, setting current depth to `depth`\n\n Args:\n depth (int, optional): the user specified depth. Defaults to None.\n\n Returns:\n int: current updated depth\n \"\"\"\n if depth is not None:\n _ir.tree_print.MAX_TREE_DEPTH = depth\n return _ir.tree_print.MAX_TREE_DEPTH\n
cast string (or list/tuple of strings) to Variable.
Parameters:
Name Type Description Default pyUnion[str, List[str]]
a string or list/tuple of strings
required
Returns:
Type Description Variable
Union[Variable]
Source code in src/bloqade/analog/ir/scalar.py
def var(py: str) -> \"Variable\":\n \"\"\"cast string (or list/tuple of strings)\n to [`Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str, List[str]]): a string or list/tuple of strings\n\n Returns:\n Union[Variable]\n \"\"\"\n ret = tryvar(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Variable\")\n\n return ret\n
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).
# single coordinate\n>>> reg = start.add_position((0,0))\n# you may chain add_position calls\n>>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n# you can add variables anywhere a value may be present\n>>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n# and specify your atom fillings\n>>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n[True, False])\n# alternatively you could use one boolean to specify\n# all coordinates should be empty/filled\n>>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n(5.2, 2.2)], False)\n
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
Source code in src/bloqade/analog/ir/location/location.py
def add_position(\n self,\n position: Union[\n PositionArray,\n List[Tuple[ScalarType, ScalarType]],\n Tuple[ScalarType, ScalarType],\n ],\n filling: Optional[Union[BoolArray, List[bool], bool]] = None,\n) -> \"ListOfLocations\":\n \"\"\"\n Add a position or multiple positions to a pre-existing geometry.\n\n `add_position` is capable of accepting:\n - A single tuple for one atom coordinate: `(1.0, 2.5)`\n - A list of tuples: `[(0.0, 1.0), (2.0,1.5), etc.]\n - A numpy array of shape (N, 2) where N is the number of atoms\n\n You may also intersperse variables anywhere a value may be present.\n\n You can also pass in an optional argument which determines the atom \"filling\"\n (whether or not at a specified coordinate an atom should be present).\n\n ### Usage Example:\n ```\n # single coordinate\n >>> reg = start.add_position((0,0))\n # you may chain add_position calls\n >>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n # you can add variables anywhere a value may be present\n >>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n # and specify your atom fillings\n >>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n [True, False])\n # alternatively you could use one boolean to specify\n # all coordinates should be empty/filled\n >>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n (5.2, 2.2)], False)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`: to specify Rydberg coupling\n - `...add_position(positions).hyperfine`: to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n if is_bearable(position, PositionArray) and is_bearable(\n filling, Optional[BoolArray]\n ):\n return self.add_position_ndarray(position, filling)\n elif is_bearable(position, List[Tuple[ScalarType, ScalarType]]) and is_bearable(\n filling, Optional[List[bool]]\n ):\n return self.add_position_list_tuples(position, filling)\n elif is_bearable(position, Tuple[ScalarType, ScalarType]) and is_bearable(\n filling, Optional[bool]\n ):\n return self.add_position_single_tupe(position, filling)\n else:\n raise TypeError(\"Invalid input types for add_position provided!\")\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_count(2, custom_rng)\n# you may also chain apply_defect_count calls\n>>> reg.apply_defect_count(2, custom_rng)\n# you can also use apply_defect_count on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_count(\n self, n_defects: int, rng: np.random.Generator = np.random.default_rng()\n):\n \"\"\"\n Drop `n_defects` atoms from the geometry randomly. Internally this occurs\n by setting certain sites to have a SiteFilling set to false indicating\n no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_count(2, custom_rng)\n # you may also chain apply_defect_count calls\n >>> reg.apply_defect_count(2, custom_rng)\n # you can also use apply_defect_count on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts)\n .apply_defect_count(n_defects)`: to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`: to specify\n Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n location_list = []\n for location_info in self.enumerate():\n location_list.append(location_info)\n\n filled_sites = []\n\n for index, location_info in enumerate(location_list):\n if location_info.filling is SiteFilling.filled:\n filled_sites.append(index)\n\n if n_defects >= len(filled_sites):\n raise ValueError(\n f\"n_defects {n_defects} must be less than the number of filled sites \"\n f\"({len(filled_sites)})\"\n )\n\n for _ in range(n_defects):\n index = rng.choice(filled_sites)\n location_list[index] = LocationInfo.create(\n location_list[index].position,\n (False if location_list[index].filling is SiteFilling.filled else True),\n )\n filled_sites.remove(index)\n\n return ListOfLocations(location_list)\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n# you may also chain apply_defect_density calls\n>>> reg.apply_defect_count(0.1, custom_rng)\n# you can also use apply_defect_density on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)])\n.apply_defect_density(0.5, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_density(\n self,\n defect_probability: float,\n rng: np.random.Generator = np.random.default_rng(),\n):\n \"\"\"\n Drop atoms randomly with `defect_probability` probability (range of 0 to 1).\n Internally this occurs by setting certain sites to have a SiteFilling\n set to false indicating no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n # you may also chain apply_defect_density calls\n >>> reg.apply_defect_count(0.1, custom_rng)\n # you can also use apply_defect_density on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)])\n .apply_defect_density(0.5, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`:\n to specify Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n p = min(1, max(0, defect_probability))\n location_list = []\n\n for location_info in self.enumerate():\n if rng.random() < p:\n location_list.append(\n LocationInfo.create(\n location_info.position,\n (\n False\n if location_info.filling is SiteFilling.filled\n else True\n ),\n )\n )\n else:\n location_list.append(location_info)\n\n return ListOfLocations(location_list=location_list)\n
Source code in src/bloqade/analog/ir/location/location.py
def figure(self, fig_kwargs=None, **assignments):\n \"\"\"obtain a figure object from the atom arrangement.\"\"\"\n return get_atom_arrangement_figure(self, fig_kwargs=fig_kwargs, **assignments)\n
the values to assign to the variables in the register.
{}
Returns:
Name Type Description NDArrayNDArray
the Rydberg interaction matrix in the lower triangular form.
Source code in src/bloqade/analog/ir/location/location.py
def rydberg_interaction(self, **assignments) -> NDArray:\n \"\"\"calculate the Rydberg interaction matrix.\n\n Args:\n **assignments: the values to assign to the variables in the register.\n\n Returns:\n NDArray: the Rydberg interaction matrix in the lower triangular form.\n\n \"\"\"\n\n from bloqade.analog.constants import RB_C6\n\n # calculate the Interaction matrix\n V_ij = np.zeros((self.n_sites, self.n_sites))\n for i, site_i in enumerate(self.enumerate()):\n pos_i = np.array([float(ele(**assignments)) for ele in site_i.position])\n\n for j, site_j in enumerate(self.enumerate()):\n if j >= i:\n break # enforce lower triangular form\n\n pos_j = np.array([float(ele(**assignments)) for ele in site_j.position])\n r_ij = np.linalg.norm(pos_i - pos_j)\n\n V_ij[i, j] = RB_C6 / r_ij**6\n\n return V_ij\n
>>> reg = start.add_position([(0,0), (1,1)])\n# atom positions are now (0,0), (2,2)\n>>> new_reg = reg.scale(2)\n# you may also use scale on pre-defined geometries\n>>> from bloqade.analog.atom_arrangement import Chain\n# atoms in the chain will now be 2 um apart versus\n# the default 1 um\n>>> Chain(11).scale(2)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef scale(self, scale: ScalarType):\n \"\"\"\n Scale the geometry of your atoms.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position([(0,0), (1,1)])\n # atom positions are now (0,0), (2,2)\n >>> new_reg = reg.scale(2)\n # you may also use scale on pre-defined geometries\n >>> from bloqade.analog.atom_arrangement import Chain\n # atoms in the chain will now be 2 um apart versus\n # the default 1 um\n >>> Chain(11).scale(2)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`:\n to specify Rydberg coupling\n - `...add_position(positions).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n scale = cast(scale)\n location_list = []\n for location_info in self.enumerate():\n x, y = location_info.position\n new_position = (scale * x, scale * y)\n location_list.append(\n LocationInfo.create(new_position, bool(location_info.filling.value))\n )\n\n return ListOfLocations(location_list)\n
@beartype\ndef constant(duration: ScalarType, value: ScalarType) -> Constant:\n \"\"\"Create a Constant waveform.\n\n Args:\n duration (ScalarType): Duration of the Constant waveform.\n value (ScalarType): Value of the Constant waveform.s\n\n Returns:\n Constant: A Constant waveform.\n \"\"\"\n return Constant(value, duration)\n
Name Type Description Default use_experimentalbool
Get experimental capabilities instead of standard ones. By default value is False.
False
Returns:
Name Type Description QuEraCapabilitiesQuEraCapabilities
capabilities object for Aquila device.
Note
Units of time, distance, and energy are microseconds (us), micrometers (um), and rad / us, respectively.
For a comprehensive list of capabilities, see the Hardware Reference page
Source code in src/bloqade/analog/factory.py
def get_capabilities(use_experimental: bool = False) -> \"QuEraCapabilities\":\n \"\"\"Get the device capabilities for Aquila\n\n Args:\n use_experimental (bool): Get experimental capabilities instead of\n standard ones. By default value is False.\n\n Returns:\n QuEraCapabilities: capabilities object for Aquila device.\n\n\n Note:\n Units of time, distance, and energy are microseconds (us),\n micrometers (um), and rad / us, respectively.\n\n For a comprehensive list of capabilities,\n see the [Hardware Reference](../../reference/hardware-capabilities.md)\n page\n \"\"\"\n\n from bloqade.analog.submission.capabilities import get_capabilities\n\n # manually convert to units\n return get_capabilities(use_experimental=use_experimental).scale_units(\n Decimal(\"1e6\"), Decimal(\"1e-6\")\n )\n
@beartype\ndef linear(duration: ScalarType, start: ScalarType, stop: ScalarType) -> Linear:\n \"\"\"Create a Linear waveform.\n\n Args:\n duration (ScalarType): Duration of linear waveform\n start (ScalarType): Starting value of linear waveform\n stop (ScalarType): Ending value of linear waveform\n\n Returns:\n Linear: Linear waveform\n \"\"\"\n return Linear(start, stop, duration)\n
Create a piecewise constant waveform from a list of durations and values. The value duration[i] corresponds to the length of time for the i'th segment with a value of values[i].
Parameters:
Name Type Description Default durationsList[ScalarType]
The duration of each segment
required valuesList[ScalarType]
The values for each segment
required
Raises:
Type Description ValueError
If the length of values is not the same as the length of
Returns:
Name Type Description WaveformWaveform
The piecewise linear waveform.
Source code in src/bloqade/analog/factory.py
@beartype\ndef piecewise_constant(\n durations: List[ScalarType], values: List[ScalarType]\n) -> Waveform:\n \"\"\"Create a piecewise linear waveform.\n\n Create a piecewise constant waveform from a list of durations and values. The\n value `duration[i]` corresponds to the length of time for the i'th segment\n with a value of `values[i]`.\n\n Args:\n durations (List[ScalarType]): The duration of each segment\n values (List[ScalarType]): The values for each segment\n\n Raises:\n ValueError: If the length of `values` is not the same as the length of\n `durations`.\n\n Returns:\n Waveform: The piecewise linear waveform.\n \"\"\"\n if len(durations) != len(values):\n raise ValueError(\n \"The length of values must be the same as the length of durations\"\n )\n\n pwc_wf = None\n for duration, value in zip(durations, values):\n if pwc_wf is None:\n pwc_wf = Constant(value, duration)\n else:\n pwc_wf = pwc_wf.append(Constant(value, duration))\n\n return pwc_wf\n
Create a piecewise linear waveform from a list of durations and values. The value duration[i] is of the linear segment between values[i] and values[i+1].
Parameters:
Name Type Description Default durationsList[ScalarType]
The duration of each segment
required valuesList[ScalarType]
The values for each segment
required
Raises:
Type Description ValueError
If the length of values is not one greater than the length of
Returns:
Name Type Description WaveformWaveform
The piecewise linear waveform.
Source code in src/bloqade/analog/factory.py
@beartype\ndef piecewise_linear(durations: List[ScalarType], values: List[ScalarType]) -> Waveform:\n \"\"\"Create a piecewise linear waveform.\n\n Create a piecewise linear waveform from a list of durations and values. The\n value `duration[i]` is of the linear segment between `values[i]` and `values[i+1]`.\n\n Args:\n durations (List[ScalarType]): The duration of each segment\n values (List[ScalarType]): The values for each segment\n\n Raises:\n ValueError: If the length of `values` is not one greater than the length of\n `durations`.\n\n Returns:\n Waveform: The piecewise linear waveform.\n \"\"\"\n\n if len(durations) + 1 != len(values):\n raise ValueError(\n \"The length of values must be one greater than the length of durations\"\n )\n\n pwl_wf = None\n for duration, start, stop in zip(durations, values[:-1], values[1:]):\n if pwl_wf is None:\n pwl_wf = Linear(start, stop, duration)\n else:\n pwl_wf = pwl_wf.append(Linear(start, stop, duration))\n\n return pwl_wf\n
List of arguments to leave till runtime. Defaults to [].
[]
Returns:
Name Type Description RoutineRoutine
An object that can be used to dispatch a rydberg program to multiple backends.
Source code in src/bloqade/analog/factory.py
@beartype\ndef rydberg_h(\n atoms_positions: Any,\n detuning: Optional[Waveform] = None,\n amplitude: Optional[Waveform] = None,\n phase: Optional[Waveform] = None,\n static_params: Dict[str, Any] = {},\n batch_params: Union[List[Dict[str, Any]], Dict[str, Any]] = [],\n args: List[str] = [],\n) -> Routine:\n \"\"\"Create a rydberg program with uniform detuning, amplitude, and phase.\n\n Args:\n atoms_positions (Any): Description of geometry of atoms in system.\n detuning (Optional[Waveform], optional): Waveform for detuning.\n Defaults to None.\n amplitude (Optional[Waveform], optional): Waveform describing the amplitude of\n the rabi term. Defaults to None.\n phase (Optional[Waveform], optional): Waveform describing the phase of rabi\n term. Defaults to None.\n static_params (Dict[str, Any], optional): Define static parameters of your\n program. Defaults to {}.\n batch_params (Union[List[Dict[str, Any]], Dict[str, Any]], optional):\n Parmaters for a batch of tasks. Defaults to [].\n args (List[str], optional): List of arguments to leave till runtime.\n Defaults to [].\n\n Returns:\n Routine: An object that can be used to dispatch a rydberg program to\n multiple backends.\n \"\"\"\n from bloqade.analog import start\n from bloqade.analog.atom_arrangement import AtomArrangement\n\n if isinstance(atoms_positions, AtomArrangement):\n prog = atoms_positions\n else:\n prog = start.add_position(atoms_positions)\n\n if detuning is not None:\n prog = prog.rydberg.detuning.uniform.apply(detuning)\n\n if amplitude is not None:\n prog = prog.amplitude.uniform.apply(amplitude)\n\n if phase is not None:\n prog = prog.phase.uniform.apply(phase)\n\n prog = prog.assign(**static_params)\n\n if isinstance(batch_params, dict):\n prog = prog.batch_assign(**batch_params)\n else:\n prog = prog.batch_assign(batch_params)\n\n prog = prog.args(args)\n\n return prog.parse()\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.dumps
{}
Returns:
Name Type Description strstr
the serialized object as a string
Source code in src/bloqade/analog/serialize.py
@beartype\ndef dumps(\n o: Any,\n use_decimal: bool = True,\n **json_kwargs,\n) -> str:\n \"\"\"Serialize object to string\n\n Args:\n o (Any): the object to serialize\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.dumps\n\n Returns:\n str: the serialized object as a string\n \"\"\"\n if not isinstance(o, Serializer.types):\n raise TypeError(\n f\"Object of type {type(o)} is not JSON serializable. \"\n f\"Only {Serializer.types} are supported.\"\n )\n return json.dumps(o, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.loads
{}
Returns:
Name Type Description Any
the deserialized object
Source code in src/bloqade/analog/serialize.py
@beartype\ndef loads(s: str, use_decimal: bool = True, **json_kwargs):\n \"\"\"Load object from string\n\n Args:\n s (str): the string to load\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.loads\n\n Returns:\n Any: the deserialized object\n \"\"\"\n load_bloqade()\n return json.loads(\n s, object_hook=Serializer.object_hook, use_decimal=use_decimal, **json_kwargs\n )\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.dump
{}
Returns:
Type Description None
None
Source code in src/bloqade/analog/serialize.py
@beartype\ndef save(\n o: Any,\n fp: Union[TextIO, str],\n use_decimal=True,\n **json_kwargs,\n) -> None:\n \"\"\"Serialize object to file\n\n Args:\n o (Any): the object to serialize\n fp (Union[TextIO, str]): the file path or file object\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.dump\n\n Returns:\n None\n \"\"\"\n if not isinstance(o, Serializer.types):\n raise TypeError(\n f\"Object of type {type(o)} is not JSON serializable. \"\n f\"Only {Serializer.types} are supported.\"\n )\n if isinstance(fp, str):\n with open(fp, \"w\") as f:\n json.dump(o, f, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n else:\n json.dump(o, fp, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n
This node represents level coupling between hyperfine states.
Examples:
- To reach the node from the start node:\n\n>>> node = bloqade.start.hyperfine\n>>> type(node)\n<class 'bloqade.builder.coupling.Hyperfine'>\n\n- Hyperfine level coupling has two reachable field nodes:\n\n - detuning term (See also [`Detuning`][bloqade.builder.field.Detuning])\n - rabi term (See also [`Rabi`][bloqade.builder.field.Rabi])\n\n>>> hyp_detune = bloqade.start.hyperfine.detuning\n>>> hyp_rabi = bloqade.start.hyperfine.rabi\n
Generate the intermediate representation (IR) for the Hyperfine level coupling.
Returns:
Name Type Description IR
An intermediate representation of the Hyperfine level coupling sequence.
Note
This method is used internally by the Bloqade framework.
Source code in src/bloqade/analog/builder/coupling.py
def __bloqade_ir__(self):\n \"\"\"\n Generate the intermediate representation (IR) for the Hyperfine level coupling.\n\n Args:\n None\n\n Returns:\n IR: An intermediate representation of the Hyperfine level coupling sequence.\n\n Raises:\n None\n\n Note:\n This method is used internally by the Bloqade framework.\n \"\"\"\n from bloqade.analog.ir.control.sequence import hyperfine\n\n return hyperfine\n
Specify the complex-valued Rabi field of your program.
The Rabi field is composed of a real-valued Amplitude and Phase field.
Returns:
Name Type Description RabiRabi
A program node representing the Rabi field.
Note
Next possible steps to build your program are creating the RabiAmplitude field and RabiPhase field of the field: - ...rabi.amplitude: To create the Rabi amplitude field - ...rabi.phase: To create the Rabi phase field
This node represents level coupling of the Rydberg state.
Examples:
- To reach the node from the start node:\n\n>>> node = bloqade.start.rydberg\n>>> type(node)\n<class 'bloqade.builder.coupling.Rydberg'>\n\n- Rydberg level coupling has two reachable field nodes:\n\n - detuning term (See also [`Detuning`][bloqade.builder.field.Detuning])\n - rabi term (See also [`Rabi`][bloqade.builder.field.Rabi])\n\n>>> ryd_detune = bloqade.start.rydberg.detuning\n>>> ryd_rabi = bloqade.start.rydberg.rabi\n
Generate the intermediate representation (IR) for the Rydberg level coupling.
Returns:
Name Type Description IR
An intermediate representation of the Rydberg level coupling sequence.
Note
This method is used internally by the Bloqade framework.
Source code in src/bloqade/analog/builder/coupling.py
def __bloqade_ir__(self):\n \"\"\"\n Generate the intermediate representation (IR) for the Rydberg level coupling.\n\n Args:\n None\n\n Returns:\n IR: An intermediate representation of the Rydberg level coupling sequence.\n\n Raises:\n None\n\n Note:\n This method is used internally by the Bloqade framework.\n \"\"\"\n from bloqade.analog.ir.control.sequence import rydberg\n\n return rydberg\n
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
...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
Address a single atom (or multiple) as part of defining the spatial modulation component of a drive. You can specify the atoms to target as a list of labels and a list of scales. The scales are used to multiply the waveform that is applied to the atom. You can also specify a single label and scale to target a single atom.
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.)
>>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi\n# to target a single atom with a waveform\n>>> one_location_prog = prog.location(0)\n# to target a single atom with a scale\n>>> one_location_prog = prog.location(0, 0.5)\n# to target multiple atoms with same waveform\n>>> multi_location_prog = prog.location([0, 2])\n# to target multiple atoms with different scales\n>>> multi_location_prog = prog.location([0, 2], [0.5, \"scale\"])\n
You can now do:
...location(labels, scales).linear(start, stop, duration) : to apply a linear waveform
...location(labels, scales).constant(value, duration) : to apply a constant waveform
...location(labels, scales).poly([coefficients], duration) : to apply a polynomial waveform
...location(labels, scales).apply(wf:bloqade.ir.Waveform): to apply a pre-defined waveform
...location(labels, scales).piecewise_linear([durations], [values]): to apply a piecewise linear waveform
...location(labels, scales).piecewise_constant([durations], [values]): to apply a piecewise constant waveform
...location(labels, scales).fn(f(t,..)): to apply a function as a waveform
Source code in src/bloqade/analog/builder/field.py
def location(\n self,\n labels: Union[List[int], int],\n scales: Union[List[ScalarType], ScalarType, None] = None,\n) -> \"Location\":\n \"\"\"Address a single atom (or multiple) atoms.\n\n Address a single atom (or multiple) as part of defining the spatial\n modulation component of a drive. You can specify the atoms to target\n as a list of labels and a list of scales. The scales are used to\n multiply the waveform that is applied to the atom. You can also specify\n a single label and scale to target a single atom.\n\n Next steps to build your program include choosing the waveform that\n will be summed with the spatial modulation to create a drive.\n\n The drive by itself, or the sum of subsequent drives (created by just\n chaining the construction of drives) will become the field.\n (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.)\n\n ### Usage Example:\n ```\n >>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi\n # to target a single atom with a waveform\n >>> one_location_prog = prog.location(0)\n # to target a single atom with a scale\n >>> one_location_prog = prog.location(0, 0.5)\n # to target multiple atoms with same waveform\n >>> multi_location_prog = prog.location([0, 2])\n # to target multiple atoms with different scales\n >>> multi_location_prog = prog.location([0, 2], [0.5, \"scale\"])\n ```\n\n - You can now do:\n - `...location(labels, scales).linear(start, stop, duration)` : to apply\n a linear waveform\n - `...location(labels, scales).constant(value, duration)` : to apply\n a constant waveform\n - `...location(labels, scales).poly([coefficients], duration)` : to apply\n a polynomial waveform\n - `...location(labels, scales).apply(wf:bloqade.ir.Waveform)`: to apply\n a pre-defined waveform\n - `...location(labels, scales).piecewise_linear([durations], [values])`:\n to apply\n a piecewise linear waveform\n - `...location(labels, scales).piecewise_constant([durations], [values])`:\n to apply\n a piecewise constant waveform\n - `...location(labels, scales).fn(f(t,..))`: to apply a function as a\n waveform\n\n \"\"\"\n return self._location(labels, scales)\n
Address all the atoms scaling each atom with an element of the list or define a variable name for the scale list to be assigned later by defining a name and using assign or batch_assign later.
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.)
>>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi\n\n# assign a literal list of values to scale each atom\n>>> one_location_prog = prog.scale([0.1, 0.2, 0.3])\n# assign a variable name to be assigned later\n>>> one_location_prog = prog.scale(\"a\")\n# \"a\" can be assigned in the END of the program during variable assignment\n# using a list of values, indicating the scaling for each atom\n>>> single_assignment = ...assign(a = [0.1, 0.2, 0.3])\n# a list of lists, indicating a set of atoms should be targeted\n# for each task in a batch.\n>>> batch_assignment = ...batch_assign(a = [list_1, list_2, list_3,...])\n
You can now do:
...scale(coeffs).linear(start, stop, duration) : to apply a linear waveform
...scale(coeffs).constant(value, duration) : to apply a constant waveform
...scale(coeffs).poly([coefficients], duration) : to apply a polynomial waveform
...scale(coeffs).apply(wf:bloqade.ir.Waveform): to apply a pre-defined waveform
...scale(coeffs).piecewise_linear(durations, values): to apply a piecewise linear waveform
...scale(coeffs).piecewise_constant(durations, values): to apply a piecewise constant waveform
...scale(coeffs).fn(f(t,..)): to apply a function as a waveform
Source code in src/bloqade/analog/builder/field.py
def scale(self, coeffs: Union[str, List[ScalarType]]) -> \"Scale\":\n \"\"\"\n Address all the atoms scaling each atom with an element of the list\n or define a variable name for the scale list to be assigned later by\n defining a `name` and using `assign` or `batch_assign` later.\n\n Next steps to build your program include choosing the waveform that\n will be summed with the spatial modulation to create a drive.\n\n The drive by itself, or the sum of subsequent drives (created by just\n chaining the construction of drives) will become the field\n (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.)\n\n ### Usage Example:\n ```\n >>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi\n\n # assign a literal list of values to scale each atom\n >>> one_location_prog = prog.scale([0.1, 0.2, 0.3])\n # assign a variable name to be assigned later\n >>> one_location_prog = prog.scale(\"a\")\n # \"a\" can be assigned in the END of the program during variable assignment\n # using a list of values, indicating the scaling for each atom\n >>> single_assignment = ...assign(a = [0.1, 0.2, 0.3])\n # a list of lists, indicating a set of atoms should be targeted\n # for each task in a batch.\n >>> batch_assignment = ...batch_assign(a = [list_1, list_2, list_3,...])\n\n ```\n\n - You can now do:\n - `...scale(coeffs).linear(start, stop, duration)` : to apply\n a linear waveform\n - `...scale(coeffs).constant(value, duration)` : to apply\n a constant waveform\n - `...scale(coeffs).poly([coefficients], duration)` : to apply\n a polynomial waveform\n - `...scale(coeffs).apply(wf:bloqade.ir.Waveform)`: to apply\n a pre-defined waveform\n - `...scale(coeffs).piecewise_linear(durations, values)`: to\n apply a piecewise linear waveform\n - `...scale(coeffs).piecewise_constant(durations, values)`: to\n apply a piecewise constant waveform\n - `...scale(coeffs).fn(f(t,..))`: to apply a function as a waveform\n\n \"\"\"\n from bloqade.analog.builder.spatial import Scale\n\n return Scale(coeffs, self)\n
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(...): Scale atoms by their indices
...amplitude.scale(...): Scale each atom with a value from a list or assign a variable name to be assigned later
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(...): Scale atoms by their indices
...amplitude.scale(...): Scale each atom with a value from a list or assign a variable name to be assigned later
Name Type Description Default args_listList[Union[str, Variable]]
List of argument names or Variable objects to be added.
required
Returns:
Name Type Description ArgsArgs
A new instance of the Args class with the specified arguments.
Raises:
Type Description TypeError
If args_list contains invalid types.
Note
This method is useful for deferring the value assignment of certain variables to runtime.
Source code in src/bloqade/analog/builder/pragmas.py
def args(self, args_list: List[Union[str, Variable]]) -> \"Args\":\n \"\"\"\n Add arguments to the current program.\n\n Args:\n args_list (List[Union[str, Variable]]): List of argument names or Variable\n objects to be added.\n\n Returns:\n Args: A new instance of the Args class with the specified arguments.\n\n Raises:\n TypeError: If args_list contains invalid types.\n\n Note:\n This method is useful for deferring the value assignment of certain\n variables to runtime.\n \"\"\"\n from bloqade.analog.builder.args import Args\n\n return Args(args_list, self)\n
Assign values to variables declared previously in the program.
Parameters:
Name Type Description Default **assignments
Key-value pairs where the key is the variable name and the value is the value to assign.
{}
Returns:
Name Type Description AssignAssign
A new instance of the Assign class with the specified assignments.
Raises:
Type Description ValueError
If an invalid assignment is provided.
Note
This is reserved for variables that should take single values or for spatial modulations created with .scale(str). After assigning values, you can choose a backend for emulation or execution.
...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).batch_assign(assignments): assign multiple values for a parameter sweep
...assign(assignments).parallelize(cluster_spacing): parallelize the program register
...assign(assignments).args([previously_defined_vars]): defer value assignment to runtime
Source code in src/bloqade/analog/builder/pragmas.py
def assign(self, **assignments) -> \"Assign\":\n \"\"\"\n Assign values to variables declared previously in the program.\n\n Args:\n **assignments: Key-value pairs where the key is the variable name and\n the value is the value to assign.\n\n Returns:\n Assign: A new instance of the Assign class with the specified\n assignments.\n\n Raises:\n ValueError: If an invalid assignment is provided.\n\n Note:\n This is reserved for variables that should take single values or for\n spatial modulations created with `.scale(str)`. After assigning values,\n you can choose a backend for emulation or execution.\n\n ### Usage Examples:\n ```\n # define geometry\n >>> reg = bloqade.start\n ... .add_position([(0,0),(1,1),(2,2),(3,3)])\n # define variables in program\n >>> seq = reg.rydberg.detuning.uniform\n ... .linear(start=\"ival\", stop=1, duration=\"span_time\")\n # assign values to variables\n >>> seq = seq.assign(span_time=0.5, ival=0.0)\n ```\n\n - Next steps:\n - `...assign(assignments).bloqade`: select the bloqade local emulator backend\n - `...assign(assignments).braket`: select braket local emulator or QuEra hardware\n - `...assign(assignments).device(specifier_string)`: select backend by specifying a\n string\n - `...assign(assignments).batch_assign(assignments)`: assign multiple values for a\n parameter sweep\n - `...assign(assignments).parallelize(cluster_spacing)`: parallelize the program\n register\n - `...assign(assignments).args([previously_defined_vars])`: defer value assignment to\n runtime\n \"\"\"\n from bloqade.analog.builder.assign import Assign\n\n return Assign(assignments, parent=self)\n
Assign multiple values to variables for creating a parameter sweep.
Parameters:
Name Type Description Default __batch_paramsList[Dict[str, ParamType]]
List of dictionaries where each dictionary contains variable assignments for one set of parameters.
[]**assignmentsList[ParamType]
Key-value pairs where the key is the variable name and the value is a list of values to assign.
{}
Returns:
Type Description Union[BatchAssign, ListAssign]
Union[BatchAssign, ListAssign]: A new instance of BatchAssign or ListAssign class with the specified assignments.
Raises:
Type Description ValueError
If both __batch_params and assignments are provided.
Note
Bloqade handles the multiple programs generated by this method and treats them as a unified object for easy post-processing. Ensure all lists of values are of the same length as Bloqade will not perform a Cartesian product.
...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
...batch_assign(assignments).parallelize(cluster_spacing): parallelize the program register
...batch_assign(assignments).args([previously_defined_vars]): defer value assignment to runtime
Source code in src/bloqade/analog/builder/pragmas.py
def batch_assign(\n self,\n __batch_params: List[Dict[str, ParamType]] = [],\n **assignments: List[ParamType],\n) -> Union[\"BatchAssign\", \"ListAssign\"]:\n \"\"\"\n Assign multiple values to variables for creating a parameter sweep.\n\n Args:\n __batch_params (List[Dict[str, ParamType]], optional): List of dictionaries\n where each dictionary contains variable assignments for one set of parameters.\n **assignments (List[ParamType]): Key-value pairs where the key is the variable\n name and the value is a list of values to assign.\n\n Returns:\n Union[BatchAssign, ListAssign]: A new instance of BatchAssign or ListAssign\n class with the specified assignments.\n\n Raises:\n ValueError: If both __batch_params and assignments are provided.\n\n Note:\n Bloqade handles the multiple programs generated by this method and treats them\n as a unified object for easy post-processing. Ensure all lists of values are of\n the same length as Bloqade will not perform a Cartesian product.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position([(0,0), (0, \"atom_distance\")])\n >>> prog = reg.rydberg.rabi.amplitude.uniform.constant(\"value\", 5.0)\n >>> var_assigned_prog = prog.batch_assign(value=[1.0, 2.0, 3.0],\n atom_distance=[1.0, 2.0, 3.0])\n ```\n\n - Next steps:\n - `...batch_assign(assignments).bloqade`: select the bloqade local emulator backend\n - `...batch_assign(assignments).braket`: select braket local emulator or QuEra hardware\n - `...batch_assign(assignments).device(specifier_string)`: select backend by specifying\n a string\n - `...batch_assign(assignments).parallelize(cluster_spacing)`: parallelize the program\n register\n - `...batch_assign(assignments).args([previously_defined_vars])`: defer value assignment\n to runtime\n \"\"\"\n from bloqade.analog.builder.assign import ListAssign, BatchAssign\n\n if len(__batch_params) > 0 and assignments:\n raise ValueError(\"batch_params and assignments cannot be used together.\")\n\n if len(__batch_params) > 0:\n return ListAssign(__batch_params, parent=self)\n else:\n return BatchAssign(assignments, parent=self)\n
Parallelize the current problem by duplicating the geometry to utilize all available space/qubits on hardware.
Parameters:
Name Type Description Default cluster_spacingLiteralType
Specifies the spacing between clusters in micrometers.
required
Returns:
Name Type Description ParallelizeParallelize
A new instance of the Parallelize class with the specified cluster spacing.
Raises:
Type Description ValueError
If the cluster_spacing is not a valid value.
Note
After calling this method, you can choose a backend for emulation or execution. Options include bloqade for a local emulator, braket for a local emulator or QuEra hardware on the cloud, or specifying a device with a string.
>>> reg = start.add_position((0,0)).rydberg.rabi.uniform.amplitude.constant(1.0, 1.0)\n# copy-paste the geometry and waveforms\n>>> parallelized_prog = reg.parallelize(24)\n
Next steps:
...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
Source code in src/bloqade/analog/builder/pragmas.py
def parallelize(self, cluster_spacing: LiteralType) -> \"Parallelize\":\n \"\"\"\n Parallelize the current problem by duplicating the geometry to utilize\n all available space/qubits on hardware.\n\n Args:\n cluster_spacing (LiteralType): Specifies the spacing between clusters\n in micrometers.\n\n Returns:\n Parallelize: A new instance of the Parallelize class with the specified\n cluster spacing.\n\n Raises:\n ValueError: If the cluster_spacing is not a valid value.\n\n Note:\n After calling this method, you can choose a backend for emulation or\n execution. Options include `bloqade` for a local emulator, `braket` for\n a local emulator or QuEra hardware on the cloud, or specifying a device\n with a string.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position((0,0)).rydberg.rabi.uniform.amplitude.constant(1.0, 1.0)\n # copy-paste the geometry and waveforms\n >>> parallelized_prog = reg.parallelize(24)\n ```\n\n - Next steps:\n - `...parallelize(cluster_spacing).bloqade`: select the bloqade local emulator backend\n - `...parallelize(cluster_spacing).braket`: select braket local emulator or QuEra\n hardware on the cloud\n - `...parallelize(cluster_spacing).device(specifier_string)`: select backend by\n specifying a string\n \"\"\"\n from bloqade.analog.builder.parallelize import Parallelize\n\n return Parallelize(cluster_spacing, self)\n
The node specify a uniform spacial modulation. Which is ready to apply waveform (See Waveform for available waveform options)
Examples:
- To hit this node from the start node:\n\n>>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)])\n>>> loc = reg.rydberg.detuning.uniform\n\n- Apply Linear waveform:\n\n>>> wv = bloqade.ir.Linear(start=0,stop=1,duration=0.5)\n>>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)])\n>>> loc = reg.rydberg.detuning.uniform.apply(wv)\n
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:
>>> from numpy import pi\n>>> seq = start.rydberg.rabi.amplitude.constant(2.0 * pi, 4.5)\n# choose a geometry of interest to apply the program on\n>>> from bloqade.analog.atom_arrangement import Chain, Kagome\n>>> complete_program = Chain(10).apply(seq)\n# you can .apply to as many geometries as you like\n>>> another_complete_program = Kagome(3).apply(seq)\n
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:
Source code in src/bloqade/analog/builder/start.py
@beartype\ndef apply(self, sequence: SequenceExpr) -> SequenceBuilder:\n \"\"\"\n Apply a pre-built sequence to a program.\n\n This allows you to build a program independent of any geometry\n and then `apply` the program to said geometry. Or, if you have a\n program you would like to try on multiple geometries you can\n trivially do so with this.\n\n Example Usage:\n ```\n >>> from numpy import pi\n >>> seq = start.rydberg.rabi.amplitude.constant(2.0 * pi, 4.5)\n # choose a geometry of interest to apply the program on\n >>> from bloqade.analog.atom_arrangement import Chain, Kagome\n >>> complete_program = Chain(10).apply(seq)\n # you can .apply to as many geometries as you like\n >>> another_complete_program = Kagome(3).apply(seq)\n ```\n\n - From here you can now do:\n - `...assign(assignments).bloqade`: select the bloqade\n local emulator backend\n - `...assign(assignments).braket`: select braket\n local emulator or QuEra hardware\n - `...assign(assignments).device(specifier_string)`: select\n backend by specifying a string\n - Assign multiple values to a single variable for a parameter sweep:\n - `...assign(assignments).batch_assign(assignments)`:\n - Parallelize the program register, duplicating the geometry and waveform\n sequence to take advantage of all available\n space/qubits on the QPU:\n - `...assign(assignments).parallelize(cluster_spacing)`\n - Defer value assignment of certain variables to runtime:\n - `...assign(assignments).args([previously_defined_vars])`\n\n \"\"\"\n return SequenceBuilder(sequence, self)\n
Copy or \"record\" the value at the end of the waveform into a variable so that it can be used in another place.
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.
If you specified a spatial modulation (e.g. uniform, location,scale) 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.
# define program of interest\n>>> from bloqade import start\n>>> prog = start.rydberg.rabi.amplitude.uniform\n>>> prog_with_wf = prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\nvalues=[0.0, 2.0, 2.0, 0.0])\n# We now slice the piecewise_linear from above and record the\n# value at the end of that slice. We then use that value\n# to construct a new waveform that can be appended to the previous\n# one without introducing discontinuity (refer to the\n# \"Quantum Scar Dynamics\" tutorial for how this could be handy)\n>>> prog_with_record = prog_with_wf.slice(0.0, 1.0).record(\"end_of_wf\")\n>>> record_applied_prog = prog_with_record.linear(start=\"end_of_wf\"\n, stop=0.0, duration=0.3)\n
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).scale(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 ```
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef record(self, name: str) -> \"Record\":\n \"\"\"\n Copy or \"record\" the value at the end of the waveform into a variable\n so that it can be used in another place.\n\n A common design pattern is to couple this with `.slice()` considering\n you may not know exactly what the end value of a `.slice()` is,\n especially in parameter sweeps where it becomes cumbersome to handle.\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n # define program of interest\n >>> from bloqade import start\n >>> prog = start.rydberg.rabi.amplitude.uniform\n >>> prog_with_wf = prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\n values=[0.0, 2.0, 2.0, 0.0])\n # We now slice the piecewise_linear from above and record the\n # value at the end of that slice. We then use that value\n # to construct a new waveform that can be appended to the previous\n # one without introducing discontinuity (refer to the\n # \"Quantum Scar Dynamics\" tutorial for how this could be handy)\n >>> prog_with_record = prog_with_wf.slice(0.0, 1.0).record(\"end_of_wf\")\n >>> record_applied_prog = prog_with_record.linear(start=\"end_of_wf\"\n , stop=0.0, duration=0.3)\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...slice(start, stop).linear(start, stop, duration)`:\n to append another linear waveform\n - `...slice(start, stop).constant(value, duration)`:\n to append a constant waveform\n - `...slice(start, stop).piecewise_linear()`:\n to append a piecewise linear waveform\n - `...slice(start, stop).piecewise_constant()`:\n to append a piecewise constant waveform\n - `...slice(start, stop).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...slice(start, stop).apply(wf:bloqade.ir.Waveform)`:\n to append a pre-defined waveform\n - `...slilce(start, stop).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...slice(start, stop).uniform`:\n To address all atoms in the field\n - `...slice(start, stop).location(int)`:\n To address an atom at a specific location via index\n - `...slice(start, stop).scale(str)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by specifying\n a single variable and then assigning it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...slice(start, stop).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...slice(start, stop)\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...slice(start, stop).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...slice(start, stop).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...slice(start, stop).bloqade`:\n to run on the Bloqade local emulator\n - `...slice(start, stop).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...slice(start, stop).parallelize(spacing)`\n - Start targeting another level coupling\n - `...slice(start, stop).rydberg`:\n to target the Rydberg level coupling\n - `...slice(start, stop).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...slice(start, stop).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...slice(start, stop).phase`:\n to target the real-valued Rabi Phase field\n - `...slice(start, stop).detuning`:\n to target the Detuning field\n - `...slice(start, stop).rabi`:\n to target the complex-valued Rabi field\n ```\n \"\"\"\n return Record(name, self)\n
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,scale) 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.
# define a program with a waveform of interest\n>>> from bloqade import start\n>>> prog = start.add_position((0,0)).rydberg.rabi.amplitude.uniform\n>>> prog_with_wf = prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\nvalues=[0.0, 2.0, 2.0, 0.0])\n# instead of using the full waveform we opt to only take the first 1 us\n>>> prog_with_slice = prog_with_wf.slice(0.0, 1.0)\n# you may use variables as well\n>>> prog_with_slice = prog_with_wf.slice(\"start\", \"end\")\n
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).scale(...)
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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef slice(\n self,\n start: Optional[ScalarType] = None,\n stop: Optional[ScalarType] = None,\n) -> \"Slice\":\n \"\"\"\n Indicate that you only want a portion of your waveform to be used in\n the program.\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n\n ### Usage Example:\n ```\n # define a program with a waveform of interest\n >>> from bloqade import start\n >>> prog = start.add_position((0,0)).rydberg.rabi.amplitude.uniform\n >>> prog_with_wf = prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\n values=[0.0, 2.0, 2.0, 0.0])\n # instead of using the full waveform we opt to only take the first 1 us\n >>> prog_with_slice = prog_with_wf.slice(0.0, 1.0)\n # you may use variables as well\n >>> prog_with_slice = prog_with_wf.slice(\"start\", \"end\")\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...slice(start, stop).linear(start, stop, duration)`:\n to append another linear waveform\n - `...slice(start, stop).constant(value, duration)`:\n to append a constant waveform\n - `...slice(start, stop).piecewise_linear()`:\n to append a piecewise linear waveform\n - `...slice(start, stop).piecewise_constant()`:\n to append a piecewise constant waveform\n - `...slice(start, stop).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...slice(start, stop).apply(wf:bloqade.ir.Waveform)`:\n to append a pre-defined waveform\n - `...slilce(start, stop).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...slice(start, stop).uniform`:\n To address all atoms in the field\n - `...slice(start, stop).location(int)`:\n To address an atom at a specific location via index\n - `...slice(start, stop).scale(...)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by specifying\n a single variable and then assigning it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...slice(start, stop).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...slice(start, stop)\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...slice(start, stop).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...slice(start, stop).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...slice(start, stop).bloqade`:\n to run on the Bloqade local emulator\n - `...slice(start, stop).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...slice(start, stop).parallelize(spacing)`\n - Start targeting another level coupling\n - `...slice(start, stop).rydberg`:\n to target the Rydberg level coupling\n - `...slice(start, stop).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...slice(start, stop).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...slice(start, stop).phase`:\n to target the real-valued Rabi Phase field\n - `...slice(start, stop).detuning`:\n to target the Detuning field\n - `...slice(start, stop).rabi`:\n to target the complex-valued Rabi field\n \"\"\"\n return Slice(start, stop, self)\n
Apply a Waveform built previously to current location(s).
If you specified a spatial modulation (e.g. uniform, location,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n# build our waveform independently of the main program\n>>> from bloqade import piecewise_linear\n>>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3],\nvalues=[0.0, 2.0, 2.0, 0.0])\n>>> prog.apply(wf)\n
Your next steps include:
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([durations], [values]): to append a piecewise linear waveform
...apply(waveform).piecewise_constant([durations], [values]): 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).location(int): To address an atom at a specific location via index
...apply(waveform).scale(...)
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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef apply(self, wf: ir.Waveform) -> \"Apply\":\n \"\"\"\n Apply a [`Waveform`][bloqade.ir.control.Waveform] built previously to\n current location(s).\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n # build our waveform independently of the main program\n >>> from bloqade import piecewise_linear\n >>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3],\n values=[0.0, 2.0, 2.0, 0.0])\n >>> prog.apply(wf)\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...apply(waveform).linear(start, stop, duration)`:\n to append another linear waveform\n - `...apply(waveform).constant(value, duration)`:\n to append a constant waveform\n - `...apply(waveform).piecewise_linear([durations], [values])`:\n to append a piecewise linear waveform\n - `...apply(waveform).piecewise_constant([durations], [values])`:\n to append a piecewise constant waveform\n - `...apply(waveform).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...apply(waveform).apply(waveform)`:\n to append a pre-defined waveform\n - `...apply(waveform).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...apply(waveform).slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...apply(waveform).record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...apply(waveform).uniform`: To address all atoms in the field\n - `...apply(waveform).location(int)`:\n To address an atom at a specific location via index\n - `...apply(waveform).scale(...)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by specifying a\n single variable and then assigning it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...apply(waveform).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...apply(waveform).batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...apply(waveform).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...apply(waveform).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...apply(waveform).bloqade`:\n to run on the Bloqade local emulator\n - `...apply(waveform).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...apply(waveform).parallelize(spacing)`\n - Start targeting another level coupling\n - `...apply(waveform).rydberg`: to target the Rydberg level coupling\n - `...apply(waveform).hyperfine`: to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...apply(waveform).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...apply(waveform).phase`:\n to target the real-valued Rabi Phase field\n - `...apply(waveform).detuning`:\n to target the Detuning field\n - `...apply(waveform).rabi`:\n to target the complex-valued Rabi field\n \"\"\"\n return Apply(wf, self)\n
Append or assign a constant waveform to the current location(s).
If you specified a spatial modulation (e.g. uniform, location,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n# apply a constant waveform of 1.9 radians/us for 0.5 us\n>>> prog.constant(value=1.9,duration=0.5)\n
Your next steps include:
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([durations], [values]): to append a piecewise linear waveform
...constant(value, duration) .piecewise_constant([durations], [values]): 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).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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef constant(self, value: ScalarType, duration: ScalarType) -> \"Constant\":\n \"\"\"\n Append or assign a constant waveform to the current location(s).\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n # apply a constant waveform of 1.9 radians/us for 0.5 us\n >>> prog.constant(value=1.9,duration=0.5)\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...constant(value, duration).linear(start, stop, duration)`:\n to append another linear waveform\n - `...constant(value, duration).constant(value, duration)`:\n to append a constant waveform\n - `...constant(value, duration)\n .piecewise_linear([durations], [values])`:\n to append a piecewise linear waveform\n - `...constant(value, duration)\n .piecewise_constant([durations], [values])`:\n to append a piecewise constant waveform\n - `...constant(value, duration).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...constant(value, duration).apply(wf:bloqade.ir.Waveform)`:\n to append a pre-defined waveform\n - `...constant(value, duration).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...constant(value, duration).slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...constant(value, duration).record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...constant(value, duration).uniform`:\n To address all atoms in the field\n - `...constant(value, duration).scale(...)`:\n To address an atom at a specific location via index\n - `...constant(value, duration).location(int)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by specifying\n a single variable and then assigning it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...constant(value, duration).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...constant(value, duration)\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...constant(value, duration).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...constant(value, duration).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...constant(value, duration).bloqade`:\n to run on the Bloqade local emulator\n - `...constant(value, duration).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...constant(start, stop, duration).parallelize(spacing)`\n - Start targeting another level coupling\n - `...constant(value, duration).rydberg`:\n to target the Rydberg level coupling\n - `...constant(value, duration).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current\n level coupling (previously selected as `rydberg` or `hyperfine`):\n - `...constant(value, duration).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...constant(value, duration).phase`:\n to target the real-valued Rabi Phase field\n - `...constant(value, duration).detuning`:\n to target the Detuning field\n - `...constant(value, duration).rabi`:\n to target the complex-valued Rabi field\n\n \"\"\"\n return Constant(value, duration, self)\n
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,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n# define our custom waveform. It must have one argument\n# be time followed by any other number of arguments that can\n# be assigned a value later in the program via `.assign` or `.batch_assign`\n>>> def custom_waveform_function(t, arg1, arg2):\n return arg1*t + arg2\n>>> prog = prog.fn(custom_waveform_function, duration = 0.5)\n# assign values\n>>> assigned_vars_prog = prog.assign(arg1 = 1.0, arg2 = 2.0)\n# or go for batching!\n>>> assigned_vars_batch_prog = prog.assign(arg1 = 1.0, arg2 = [1.0, 2.0, 3.0])\n
Your next steps include:
Continue building your waveform via:
...fn(f(t,...)) .linear(start, stop, duration): to append another linear waveform
...fn(f(t,...)) .constant(value, duration): to append a constant waveform
...fn(f(t,...)) .piecewise_linear(durations, values): to append a piecewise linear waveform
...fn(f(t,...)) .piecewise_constant(durations, values): to append a piecewise constant waveform
...fn(f(t,...)) .poly([coefficients], duration): to append a polynomial waveform
...fn(f(t,...)) .apply(waveform): to append a pre-defined waveform
...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)
Save the ending value of your waveform to be reused elsewhere
...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: To address all atoms in the field
...fn(f(t,...)).scale(...): 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 specifying a single variable and then assigning it a list of coordinates
Assign values to pre-existing variables via:
...fn(f(t,...)) .assign(variable_name = value): to assign a single value to a variable
...fn(f(t,...)) .batch_assign(variable_name = [value1, ...]): to assign multiple values to a variable
...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: to run on Braket local emulator or QuEra hardware remotely
...fn(f(t,...)).bloqade: to run on the Bloqade local emulator
...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)
Start targeting another level coupling
...fn(f(t,...)).rydberg: to target the Rydberg level coupling
...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: to target the real-valued Rabi Amplitude field
...fn(f(t,...)).phase: to target the real-valued Rabi Phase field
...fn(f(t,...)).detuning: to target the Detuning field
...fn(f(t,...)).rabi: to target the complex-valued Rabi field
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef fn(self, fn: Callable, duration: ScalarType) -> \"Fn\":\n \"\"\"\n Append or assign a custom function as a waveform.\n\n The function must have its first argument be that of time but\n can also have other arguments which are treated as variables.\n You can assign values to later in the program via `.assign` or `.batch_assign`.\n\n The function must also return a singular float value.\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### ### Usage Examples:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n # define our custom waveform. It must have one argument\n # be time followed by any other number of arguments that can\n # be assigned a value later in the program via `.assign` or `.batch_assign`\n >>> def custom_waveform_function(t, arg1, arg2):\n return arg1*t + arg2\n >>> prog = prog.fn(custom_waveform_function, duration = 0.5)\n # assign values\n >>> assigned_vars_prog = prog.assign(arg1 = 1.0, arg2 = 2.0)\n # or go for batching!\n >>> assigned_vars_batch_prog = prog.assign(arg1 = 1.0, arg2 = [1.0, 2.0, 3.0])\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...fn(f(t,...))\n .linear(start, stop, duration)`: to append another linear waveform\n - `...fn(f(t,...))\n .constant(value, duration)`: to append a constant waveform\n - `...fn(f(t,...))\n .piecewise_linear(durations, values)`:\n to append a piecewise linear waveform\n - `...fn(f(t,...))\n .piecewise_constant(durations, values)`:\n to append a piecewise constant waveform\n - `...fn(f(t,...))\n .poly([coefficients], duration)`: to append a polynomial waveform\n - `...fn(f(t,...))\n .apply(waveform)`: to append a pre-defined waveform\n - `...fn(f(t,...))\n .fn(f(t,...))`: to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...fn(f(t,...)).slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...fn(f(t,...)).record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...fn(f(t,...)).uniform`:\n To address all atoms in the field\n - `...fn(f(t,...)).scale(...)`:\n To address an atom at a specific location via index\n - ...fn(f(t,...)).location(int)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by\n specifying a single variable and then assigning it a\n list of coordinates\n - Assign values to pre-existing variables via:\n - `...fn(f(t,...))\n .assign(variable_name = value)`: to assign a single value to a variable\n - `...fn(f(t,...))\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...fn(f(t,...))\n .args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...fn(f(t,...)).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...fn(f(t,...)).bloqade`:\n to run on the Bloqade local emulator\n - `...fn(f(t,...)).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...fn(f(t,...)).parallelize(spacing)`\n - Start targeting another level coupling\n - `...fn(f(t,...)).rydberg`:\n to target the Rydberg level coupling\n - `...fn(f(t,...)).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...fn(f(t,...)).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...fn(f(t,...)).phase`:\n to target the real-valued Rabi Phase field\n - `...fn(f(t,...)).detuning`:\n to target the Detuning field\n - `...fn(f(t,...)).rabi`:\n to target the complex-valued Rabi field\n\n \"\"\"\n return Fn(fn, duration, self)\n
Append or assign a linear waveform to the current location(s).
If you specified a spatial modulation (e.g. uniform, location,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n# apply a linear waveform that goes from 0 to 1 radians/us in 0.5 us\n>>> prog.linear(start=0,stop=1,duration=0.5)\n
Your next steps include:
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([durations], [values]): to append a piecewise linear waveform
...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
...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).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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef linear(\n self, start: ScalarType, stop: ScalarType, duration: ScalarType\n) -> \"Linear\":\n \"\"\"\n\n Append or assign a linear waveform to the current location(s).\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n # apply a linear waveform that goes from 0 to 1 radians/us in 0.5 us\n >>> prog.linear(start=0,stop=1,duration=0.5)\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...linear(start, stop, duration).linear(start, stop, duration)`:\n to append another linear waveform\n - `...linear(start, stop, duration).constant(value, duration)`:\n to append a constant waveform\n - `...linear(start, stop, duration)\n .piecewise_linear([durations], [values])`:\n to append a piecewise linear waveform\n - `...linear(start, stop, duration)\n .piecewise_constant([durations], [values])`:\n to append a piecewise constant waveform\n - `...linear(start, stop, duration).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...linear(start, stop, duration).apply(wf:bloqade.ir.Waveform)`:\n to append a pre-defined waveform\n - `...linear(start, stop, duration).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...linear(start, stop, duration).slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...linear(start, stop, duration).record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...linear(start, stop, duration).uniform`:\n To address all atoms in the field\n - `...linear(start, stop, duration).location(int)`:\n To address atoms at specific location with scaling\n - `...linear(start, stop, duration).scale(...)`\n - To address atoms at specific location with scaling\n - To address multiple atoms at specific locations by specifying\n a single variable and then assigning it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...linear(start, stop, duration).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...linear(start, stop, duration)\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...linear(start, stop, duration).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...linear(start, stop, duration).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...linear(start, stop, duration).bloqade`:\n to run on the Bloqade local emulator\n - `...linear(start, stop, duration).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...linear(start, stop, duration).parallelize(spacing)`\n - Start targeting another level coupling\n - `...linear(start, stop, duration).rydberg`:\n to target the Rydberg level coupling\n - `...linear(start, stop, duration).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...linear(start, stop, duration).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...linear(start, stop, duration).phase`:\n to target the real-valued Rabi Phase field\n - `...linear(start, stop, duration).detuning`:\n to target the Detuning field\n - `...linear(start, stop, duration).rabi`:\n to target the complex-valued Rabi field\n \"\"\"\n\n return Linear(start, stop, duration, self)\n
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,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.rabi.phase.uniform\n# create a staircase, we hold 0.0 rad/us for 1.0 us, then\n# to 1.0 rad/us for 0.5 us before stopping at 0.8 rad/us for 0.9 us.\n>>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[1.0, 0.5, 0.9])\n
Your next steps including:
Continue building your waveform via:
...piecewise_constant([durations], [values]) .linear(start, stop, duration): to append another linear waveform
...piecewise_constant([durations], [values]) .constant(value, duration): to append a constant waveform
...piecewise_constant([durations], [values]) .piecewise_linear([durations], [values]): to append a piecewise linear waveform
...piecewise_constant([durations], [values]) .piecewise_constant([durations], [values]): to append a piecewise constant waveform
...piecewise_constant([durations], [values]) .poly([coefficients], duration): to append a polynomial waveform
...piecewise_constant([durations], [values]) .apply(waveform): to append a pre-defined waveform
...piecewise_constant([durations], [values]).fn(f(t,...)): to append a waveform defined by a python function
...piecewise_constant([durations], [values]).rydberg: to target the Rydberg level coupling
...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: to target the real-valued Rabi Amplitude field
...piecewise_constant([durations], [values]).phase: to target the real-valued Rabi Phase field
...piecewise_constant([durations], [values]).detuning: to target the Detuning field
...piecewise_constant([durations], [values]).rabi: to target the complex-valued Rabi field
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef piecewise_constant(\n self, durations: List[ScalarType], values: List[ScalarType]\n) -> \"PiecewiseConstant\":\n \"\"\"\n Append or assign a piecewise constant waveform to current location(s).\n\n The `durations` argument should have number of elements = len(values).\n `durations` should be the duration PER section of the waveform,\n NON-CUMULATIVE.\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.rabi.phase.uniform\n # create a staircase, we hold 0.0 rad/us for 1.0 us, then\n # to 1.0 rad/us for 0.5 us before stopping at 0.8 rad/us for 0.9 us.\n >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[1.0, 0.5, 0.9])\n ```\n\n - Your next steps including:\n - Continue building your waveform via:\n - `...piecewise_constant([durations], [values])\n .linear(start, stop, duration)`: to append another linear waveform\n - `...piecewise_constant([durations], [values])\n .constant(value, duration)`: to append a constant waveform\n - `...piecewise_constant([durations], [values])\n .piecewise_linear([durations], [values])`:\n to append a piecewise linear waveform\n - `...piecewise_constant([durations], [values])\n .piecewise_constant([durations], [values])`:\n to append a piecewise constant waveform\n - `...piecewise_constant([durations], [values])\n .poly([coefficients], duration)`: to append a polynomial waveform\n - `...piecewise_constant([durations], [values])\n .apply(waveform)`: to append a pre-defined waveform\n - `...piecewise_constant([durations], [values]).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...piecewise_constant([durations], [values])\n .slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...piecewise_constant([durations], [values])\n .record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...piecewise_constant([durations], [values]).uniform`:\n To address all atoms in the field\n - `...piecewise_constant([durations], [values]).location(int)`:\n To address an atom at a specific location via index\n - `...piecewise_constant([durations], [values]).scale(...)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by\n specifying a single variable and then assigning it a\n list of coordinates\n - Assign values to pre-existing variables via:\n - `...piecewise_constant([durations], [values])\n .assign(variable_name = value)`: to assign a single value to a variable\n - `...piecewise_constant([durations], [values])\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...piecewise_constant([durations], [values])\n .args([\"previously_defined_var\"])`: to defer assignment\n of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...piecewise_constant([durations], [values]).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...piecewise_constant([durations], [values]).bloqade`:\n to run on the Bloqade local emulator\n - `...piecewise_constant([durations], [values]).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...piecewise_constat([durations], [values]).parallelize(spacing)`\n - Start targeting another level coupling\n - `...piecewise_constant([durations], [values]).rydberg`:\n to target the Rydberg level coupling\n - `...piecewise_constant([durations], [values]).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...piecewise_constant(durations, values).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...piecewise_constant([durations], [values]).phase`:\n to target the real-valued Rabi Phase field\n - `...piecewise_constant([durations], [values]).detuning`:\n to target the Detuning field\n - `...piecewise_constant([durations], [values]).rabi`:\n to target the complex-valued Rabi field\n \"\"\"\n return PiecewiseConstant(durations, values, self)\n
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,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n# ramp our waveform up to a certain value, hold it\n# then ramp down. In this case, we ramp up to 2.0 rad/us in 0.3 us,\n# then hold it for 1.5 us before ramping down in 0.3 us back to 0.0 rad/us.\n>>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\nvalues=[0.0, 2.0, 2.0, 0.0])\n
Your next steps include:
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]).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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef piecewise_linear(\n self, durations: List[ScalarType], values: List[ScalarType]\n) -> \"PiecewiseLinear\":\n \"\"\"\n Append or assign a piecewise linear waveform to current location(s),\n where the waveform is formed by connecting `values[i], values[i+1]`\n with linear segments.\n\n The `durations` argument should have # of elements = len(values) - 1.\n `durations` should be the duration PER section of the waveform, NON-CUMULATIVE.\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n # ramp our waveform up to a certain value, hold it\n # then ramp down. In this case, we ramp up to 2.0 rad/us in 0.3 us,\n # then hold it for 1.5 us before ramping down in 0.3 us back to 0.0 rad/us.\n >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\n values=[0.0, 2.0, 2.0, 0.0])\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...piecewise_linear([durations], [values])\n .linear(start, stop, duration)`:\n to append another linear waveform\n - `...piecewise_linear([durations], [values]).constant(value, duration)`:\n to append a constant waveform\n - `...piecewise_linear([durations], [values])\n .piecewise_linear(durations, values)`:\n to append a piecewise linear waveform\n - `...piecewise_linear([durations], [values])\n .piecewise_constant([durations], [values])`:\n to append a piecewise constant waveform\n - `...piecewise_linear([durations], [values])\n .poly([coefficients], duration)`: to append a polynomial waveform\n - `...piecewise_linear([durations], [values]).apply(waveform)`:\n to append a pre-defined waveform\n - `...piecewise_linear([durations], [values]).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...piecewise_linear([durations], [values])\n .slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...piecewise_linear([durations], [values])\n .record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...piecewise_linear([durations], [values]).uniform`:\n To address all atoms in the field\n - `...piecewise_linear([durations], [values]).scale(...)`:\n To address an atom at a specific location via index\n - `...piecewise_linear([durations], [values]).location(int)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by\n specifying a single variable and then assigning it a\n list of coordinates\n - Assign values to pre-existing variables via:\n - `...piecewise_linear([durations], [values])\n .assign(variable_name = value)`:\n to assign a single value to a variable\n - `...piecewise_linear([durations], [values])\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...piecewise_linear([durations], [values])\n .args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...piecewise_linear([durations], [values]).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...piecewise_linear([durations], [values]).bloqade`:\n to run on the Bloqade local emulator\n - `...piecewise_linear([durations], [values]).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...piecewise_linear([durations], [values]).parallelize(spacing)`\n - Start targeting another level coupling\n - `...piecewise_linear([durations], [values]).rydberg`:\n to target the Rydberg level coupling\n - `...piecewise_linear([durations], [values]).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...piecewise_linear([durations], [values]).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...piecewise_linear([durations], [values]).phase`:\n to target the real-valued Rabi Phase field\n - `...piecewise_linear([durations], [values]).detuning`:\n to target the Detuning field\n - `....rabi`: to target the complex-valued Rabi field\n \"\"\"\n return PiecewiseLinear(durations, values, self)\n
If you specified a spatial modulation (e.g. uniform, location,scale) 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.
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).location(int): To address an atom at a specific location via index
...poly([coeffs], duration).scale(...)
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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef poly(self, coeffs: List[ScalarType], duration: ScalarType) -> \"Poly\":\n \"\"\"\n Append or assign a waveform with a polynomial profile to current location(s).\n\n You pass in a list of coefficients and a duration to this method which obeys\n the following expression:\n\n `\n wv(t) = coeffs[0] + coeffs[1]*t + coeffs[2]*t^2 + ... + coeffs[n]*t^n\n `\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n >>> coeffs = [-1, 0.5, 1.2]\n # resulting polynomial is:\n # f(t) = -1 + 0.5*t + 1.2*t^2 with duration of\n # 0.5 us\n >>> prog.poly(coeffs, duration=0.5)\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...poly([coeffs], duration).linear(start, stop, duration)`:\n to append another linear waveform\n - `...poly([coeffs], duration).constant(value, duration)`:\n to append a constant waveform\n - `...poly([coeffs], duration)\n .piecewise_linear([durations], [values])`:\n to append a piecewise linear waveform\n - `...poly([coeffs], duration)\n .piecewise_constant([durations],[values])`:\n to append a piecewise constant waveform\n - `...poly([coeffs], duration).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...poly([coeffs], duration).apply(waveform)`:\n to append a pre-defined waveform\n - `...poly([coeffs], duration).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...poly([coeffs], duration).slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...poly([coeffs], duration).record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...poly([coeffs], duration).uniform`:\n To address all atoms in the field\n - `...poly([coeffs], duration).location(int)`:\n To address an atom at a specific location via index\n - `...poly([coeffs], duration).scale(...)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by\n specifying a single variable and then assigning\n it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...poly([coeffs], duration).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...poly([coeffs], duration)\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...poly([coeffs], duration).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...poly([coeffs], duration).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...poly([coeffs], duration).bloqade`:\n to run on the Bloqade local emulator\n - `...poly([coeffs], duration).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...poly([coeffs], duration).parallelize(spacing)`\n - Start targeting another level coupling\n - `...poly([coeffs], duration).rydberg`:\n to target the Rydberg level coupling\n - `...poly([coeffs], duration).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level\n coupling (previously selected as `rydberg` or `hyperfine`):\n - `...poly([coeffs], duration).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...poly([coeffs], duration).phase`:\n to target the real-valued Rabi Phase field\n - `...poly([coeffs], duration).detuning`:\n to target the Detuning field\n - `...poly([coeffs], duration).rabi`:\n to target the complex-valued Rabi field\n \"\"\"\n return Poly(coeffs, duration, self)\n
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\" - \"bloqade.python\" - \"bloqade.julia\"
...python().run(shots): to submit to the python emulator and await results
Source code in src/bloqade/analog/builder/backend/bloqade.py
def python(self):\n \"\"\"\n Specify the Bloqade Python backend.\n\n - Possible Next Steps:\n - `...python().run(shots)`:\n to submit to the python emulator and await results\n \"\"\"\n return self.parse().bloqade.python()\n
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
Source code in src/bloqade/analog/builder/backend/braket.py
def aquila(self) -> \"BraketHardwareRoutine\":\n \"\"\"\n Specify QuEra's Aquila QPU on Braket to submit your program to.\n\n The number of shots you specify in the subsequent `.run` method will either:\n - dictate the number of times your program is run\n - dictate the number of times per parameter your program is run if\n you have a variable with batch assignments/intend to conduct\n a parameter sweep\n\n\n - Possible next steps are:\n - `...aquila().run(shots)`: To submit to hardware and WAIT for\n results (blocking)\n - `...aquila().run_async(shots)`: To submit to hardware and immediately\n allow for other operations to occur\n \"\"\"\n return self.parse().braket.aquila()\n
Specify QPU based on the device ARN 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:
...device(arn).run(shots): To submit to hardware and WAIT for results (blocking)
...device(arn).run_async(shots): To submit to hardware and immediately allow for other operations to occur
Source code in src/bloqade/analog/builder/backend/braket.py
def device(self, device_arn) -> \"BraketHardwareRoutine\":\n \"\"\"\n Specify QPU based on the device ARN on Braket to submit your program to.\n\n The number of shots you specify in the subsequent `.run` method will either:\n - dictate the number of times your program is run\n - dictate the number of times per parameter your program is run if\n you have a variable with batch assignments/intend to conduct\n a parameter sweep\n\n\n - Possible next steps are:\n - `...device(arn).run(shots)`: To submit to hardware and WAIT for\n results (blocking)\n - `...device(arn).run_async(shots)`: To submit to hardware and immediately\n allow for other operations to occur\n \"\"\"\n return self.parse().braket.device(device_arn)\n
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
Source code in src/bloqade/analog/builder/backend/braket.py
def local_emulator(self) -> \"BraketLocalEmulatorRoutine\":\n \"\"\"\n Specify the Braket local emulator to submit your program to.\n\n - The number of shots you specify in the subsequent `.run` method will either:\n - dictate the number of times your program is run\n - dictate the number of times per parameter your program is run if\n you have a variable with batch assignments/intend to\n conduct a parameter sweep\n - Possible next steps are:\n - `...local_emulator().run(shots)`: to submit to the emulator\n and await results\n\n \"\"\"\n return self.parse().braket.local_emulator()\n
Module for parsing builder definitions into intermediate representation (IR) using the bloqade library.
This module provides a Parser class for parsing various components of a quantum computing program, including atom arrangements, pulse sequences, analog circuits, and routines. It also defines utility functions for reading addresses, waveforms, drives, sequences, registers, and pragmas from a builder stream.
Source code in src/bloqade/analog/builder/parse/builder.py
def parse_circuit(self, builder: Builder) -> \"AnalogCircuit\":\n \"\"\"\n Parse an analog circuit from the builder.\n\n Args:\n builder (Builder): The builder instance.\n\n Returns:\n AnalogCircuit: The parsed analog circuit.\n \"\"\"\n from bloqade.analog.ir.analog_circuit import AnalogCircuit\n\n self.reset(builder)\n self.read_register()\n self.read_sequence()\n\n circuit = AnalogCircuit(self.register, self.sequence)\n\n return circuit\n
Parse an atom arrangement register from the builder.
Parameters:
Name Type Description Default builderBuilder
The builder instance.
required
Returns:
Type Description Union[AtomArrangement, ParallelRegister]
Union[ir.AtomArrangement, ir.ParallelRegister]: The parsed atom arrangement or parallel register.
Source code in src/bloqade/analog/builder/parse/builder.py
def parse_register(\n self, builder: Builder\n) -> Union[ir.AtomArrangement, ir.ParallelRegister]:\n \"\"\"\n Parse an atom arrangement register from the builder.\n\n Args:\n builder (Builder): The builder instance.\n\n Returns:\n Union[ir.AtomArrangement, ir.ParallelRegister]: The parsed atom arrangement or parallel register.\n \"\"\"\n self.reset(builder)\n self.read_register()\n self.read_pragmas()\n return self.register\n
Type Description Tuple[LevelCoupling, Field, BuilderNode]
Tuple[LevelCoupling, Field, BuilderNode]: A tuple containing the level coupling, field, and spatial modulation.
Source code in src/bloqade/analog/builder/parse/builder.py
def read_address(self, stream) -> Tuple[LevelCoupling, Field, BuilderNode]:\n \"\"\"\n Read an address from the builder stream.\n\n Args:\n stream: The builder stream.\n\n Returns:\n Tuple[LevelCoupling, Field, BuilderNode]: A tuple containing the level coupling, field, and spatial modulation.\n \"\"\"\n spatial = stream.read_next([Location, Uniform, Scale])\n curr = spatial\n\n if curr is None:\n return (None, None, None)\n\n while curr.next is not None:\n if not isinstance(curr.node, SpatialModulation):\n break\n curr = curr.next\n\n if type(spatial.node.__parent__) in [Detuning, RabiAmplitude, RabiPhase]:\n field = spatial.node.__parent__ # field is updated\n if type(field) in [RabiAmplitude, RabiPhase]:\n coupling = field.__parent__.__parent__ # skip Rabi\n else:\n coupling = field.__parent__\n\n # coupling not updated\n if type(coupling) not in [Rydberg, Hyperfine]:\n coupling = None\n return (coupling, field, spatial)\n else: # only spatial is updated\n return (None, None, spatial)\n
Source code in src/bloqade/analog/builder/parse/builder.py
def read_drive(self, head) -> ir.Field:\n \"\"\"\n Read a drive from the builder stream.\n\n Args:\n head: The head of the builder stream.\n\n Returns:\n ir.Field: The drive field.\n \"\"\"\n if head is None:\n return ir.Field({})\n\n sm = head.node.__bloqade_ir__()\n wf, _ = self.read_waveform(head.next)\n\n return ir.Field({sm: wf})\n
Read an atom arrangement register from the builder stream.
Returns:
Type Description AtomArrangement
ir.AtomArrangement: The parsed atom arrangement.
Source code in src/bloqade/analog/builder/parse/builder.py
def read_register(self) -> ir.AtomArrangement:\n \"\"\"\n Read an atom arrangement register from the builder stream.\n\n Returns:\n ir.AtomArrangement: The parsed atom arrangement.\n \"\"\"\n # register is always head of the stream\n register_node = self.stream.read()\n self.register = register_node.node\n\n return self.register\n
Source code in src/bloqade/analog/builder/parse/builder.py
def read_sequence(self) -> ir.Sequence:\n \"\"\"\n Read a sequence from the builder stream.\n\n Returns:\n ir.Sequence: The parsed sequence.\n \"\"\"\n if isinstance(self.stream.curr.node, SequenceBuilder):\n # case with sequence builder object.\n self.sequence = self.stream.read().node._sequence\n return self.sequence\n\n stream = self.stream.copy()\n while stream.curr is not None:\n coupling_builder, field_builder, spatial_head = self.read_address(stream)\n\n if coupling_builder is not None:\n # update to new pulse coupling\n self.coupling_name = coupling_builder.__bloqade_ir__()\n\n if field_builder is not None:\n # update to new field coupling\n self.field_name = field_builder.__bloqade_ir__()\n\n if spatial_head is None:\n break\n\n pulse = self.sequence.pulses.get(self.coupling_name, ir.Pulse({}))\n field = pulse.fields.get(self.field_name, ir.Field({}))\n\n drive = self.read_drive(spatial_head)\n field = field.add(drive)\n\n pulse = ir.Pulse.create(pulse.fields | {self.field_name: field})\n self.sequence = ir.Sequence.create(\n self.sequence.pulses | {self.coupling_name: pulse}\n )\n\n return self.sequence\n
This module provides classes to represent builder nodes and builder streams. A builder node is a single element in the stream, representing a step in a construction process. A builder stream is a sequence of builder nodes, allowing traversal and manipulation of the construction steps.
Next method to get the next item in the builder stream.
Source code in src/bloqade/analog/builder/parse/stream.py
def __next__(self):\n \"\"\"Next method to get the next item in the builder stream.\"\"\"\n node = self.read()\n if node is None:\n raise StopIteration\n return node\n
Build BuilderNode instances from the provided Builder.
Parameters:
Name Type Description Default nodeBuilder
The root Builder instance.
required
Returns:
Name Type Description BuilderNodeBuilderNode
The head of the linked list of BuilderNodes.
Source code in src/bloqade/analog/builder/parse/stream.py
@staticmethod\ndef build_nodes(node: Builder) -> \"BuilderNode\":\n \"\"\"\n Build BuilderNode instances from the provided Builder.\n\n Args:\n node (Builder): The root Builder instance.\n\n Returns:\n BuilderNode: The head of the linked list of BuilderNodes.\n \"\"\"\n curr = node\n node = None\n while curr is not None:\n next = curr\n curr = curr.__parent__ if hasattr(curr, \"__parent__\") else None\n node = BuilderNode(next, node)\n\n return node\n
Source code in src/bloqade/analog/builder/parse/stream.py
@staticmethod\ndef create(builder: Builder) -> \"BuilderStream\":\n \"\"\"\n Create a BuilderStream instance from a Builder.\n\n Args:\n builder (Builder): The root Builder instance.\n\n Returns:\n BuilderStream: The created BuilderStream instance.\n \"\"\"\n head = BuilderStream.build_nodes(builder)\n return BuilderStream(head=head, curr=head)\n
Move the stream pointer until a node of specified types is found.
Parameters:
Name Type Description Default typesList[Type[Builder]]
List of types to move the stream pointer to.
required skipsList[Type[Builder]] | None
List of types to end the stream scan.
None
Returns:
Name Type Description BuilderNodeBuilderNode
The beginning of the stream which matches a type in types.
Source code in src/bloqade/analog/builder/parse/stream.py
def eat(\n self, types: List[Type[Builder]], skips: Optional[List[Type[Builder]]] = None\n) -> BuilderNode:\n \"\"\"\n Move the stream pointer until a node of specified types is found.\n\n Args:\n types (List[Type[Builder]]): List of types to move the stream pointer to.\n skips (List[Type[Builder]] | None, optional): List of types to end the stream scan.\n\n Returns:\n BuilderNode: The beginning of the stream which matches a type in `types`.\n \"\"\"\n head = self.read_next(types)\n curr = head\n while curr is not None:\n if type(curr.node) not in types:\n if skips and type(curr.node) not in skips:\n break\n curr = curr.next\n self.curr = curr\n return head\n
Source code in src/bloqade/analog/builder/parse/stream.py
def read(self) -> Optional[BuilderNode]:\n \"\"\"Read the next builder node from the stream.\"\"\"\n if self.curr is None:\n return None\n\n node = self.curr\n self.curr = self.curr.next\n return node\n
Read the next builder node of specified types from the stream.
Parameters:
Name Type Description Default builder_typesList[type[Builder]]
List of builder types to read from the stream.
required
Returns:
Type Description Optional[BuilderNode]
Optional[BuilderNode]: The next builder node matching one of the specified types, or None if not found.
Source code in src/bloqade/analog/builder/parse/stream.py
def read_next(self, builder_types: List[type[Builder]]) -> Optional[BuilderNode]:\n \"\"\"\n Read the next builder node of specified types from the stream.\n\n Args:\n builder_types (List[type[Builder]]): List of builder types to read from the stream.\n\n Returns:\n Optional[BuilderNode]: The next builder node matching one of the specified types, or None if not found.\n \"\"\"\n node = self.read()\n while node is not None:\n if type(node.node) in builder_types:\n return node\n node = self.read()\n return None\n
A composite class inheriting from ParseRegister, ParseSequence, ParseCircuit, and ParseRoutine. Provides a unified interface for parsing different components of the program.
Source code in src/bloqade/analog/builder/parse/trait.py
def parse_circuit(self: \"Builder\") -> \"AnalogCircuit\":\n \"\"\"\n Parse the analog circuit from the program.\n\n Returns:\n AnalogCircuit: The parsed analog circuit.\n\n Raises:\n ValueError: If the circuit cannot be parsed.\n \"\"\"\n from bloqade.analog.builder.parse.builder import Parser\n\n return Parser().parse_circuit(self)\n
Type Description Union[AtomArrangement, ParallelRegister]
Union[AtomArrangement, ParallelRegister]: The parsed atom arrangement or parallel register.
Raises:
Type Description ValueError
If the register cannot be parsed.
Source code in src/bloqade/analog/builder/parse/trait.py
def parse_register(self: \"Builder\") -> Union[\"AtomArrangement\", \"ParallelRegister\"]:\n \"\"\"\n Parse the arrangement of atoms in the program.\n\n Returns:\n Union[AtomArrangement, ParallelRegister]: The parsed atom arrangement or parallel register.\n\n Raises:\n ValueError: If the register cannot be parsed.\n \"\"\"\n from bloqade.analog.builder.parse.builder import Parser\n\n return Parser().parse_register(self)\n
Source code in src/bloqade/analog/builder/parse/trait.py
def parse(self: \"Builder\") -> \"Routine\":\n \"\"\"\n Parse the program to return a Routine object.\n\n Returns:\n Routine: The parsed routine object.\n\n Raises:\n ValueError: If the routine cannot be parsed.\n \"\"\"\n from bloqade.analog.builder.parse.builder import Parser\n\n return Parser().parse(self)\n
Source code in src/bloqade/analog/builder/parse/trait.py
def parse_sequence(self: \"Builder\") -> \"Sequence\":\n \"\"\"\n Parse the pulse sequence part of the program.\n\n Returns:\n Sequence: The parsed pulse sequence.\n\n Raises:\n ValueError: If the sequence cannot be parsed.\n \"\"\"\n from bloqade.analog.builder.parse.builder import Parser\n\n return Parser().parse_sequence(self)\n
Source code in src/bloqade/analog/builder/parse/trait.py
def show(self, *args, batch_id: int = 0):\n \"\"\"\n Display the current program being defined with the given arguments and batch ID.\n\n Args:\n *args: Additional arguments for display.\n batch_id (int, optional): The batch ID to be displayed. Defaults to 0.\n\n Note:\n This method uses the `display_builder` function to render the builder's state.\n\n Example:\n\n ```python\n >>> class MyBuilder(Show):\n ... pass\n >>> builder = MyBuilder()\n >>> builder.show()\n >>> builder.show(batch_id=1)\n >>> builder.show('arg1', 'arg2', batch_id=2)\n ```\n \"\"\"\n display_builder(self, batch_id, *args)\n
This pass checks to make sure that: * There is no hyperfine coupling in the sequence * There are no non-uniform spatial modulation for rabi phase and amplitude * there is no more than one non-uniform spatial modulation for detuning
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to analyze
required
Returns:
Name Type Description level_couplingsDict
Dictionary containing the required channels for the sequence. Note that this will insert a uniform field for any missing channels.
Raises:
Type Description ValueError
If there is hyperfine coupling in the sequence.
ValueError
If there is more than one non-uniform spatial modulation for detuning.
ValueError
If there are non-uniform spatial modulations for rabi phase and amplitude.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def analyze_channels(circuit: analog_circuit.AnalogCircuit) -> Dict:\n \"\"\"1. Scan channels\n\n This pass checks to make sure that:\n * There is no hyperfine coupling in the sequence\n * There are no non-uniform spatial modulation for rabi phase and amplitude\n * there is no more than one non-uniform spatial modulation for detuning\n\n Args:\n circuit: AnalogCircuit to analyze\n\n Returns:\n level_couplings: Dictionary containing the required channels for the\n sequence. Note that this will insert a uniform field for any missing\n channels.\n\n Raises:\n ValueError: If there is hyperfine coupling in the sequence.\n ValueError: If there is more than one non-uniform spatial modulation for\n detuning.\n ValueError: If there are non-uniform spatial modulations for rabi phase\n and amplitude.\n\n \"\"\"\n from bloqade.analog.compiler.analysis.common import ScanChannels\n from bloqade.analog.compiler.analysis.hardware import ValidateChannels\n\n ValidateChannels().scan(circuit)\n level_couplings = ScanChannels().scan(circuit)\n\n # add missing channels\n fields = level_couplings[sequence.rydberg]\n # detuning, phase and amplitude are required\n # to have at least a uniform field\n updated_fields = {\n field_name: fields.get(field_name, {field.Uniform}).union({field.Uniform})\n for field_name in [pulse.detuning, pulse.rabi.amplitude, pulse.rabi.phase]\n }\n\n return {sequence.rydberg: updated_fields}\n
This pass assigns variables to the circuit and validates that all variables have been assigned.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to assign variables to
required assignmentsDict[str, ParamType]
Dictionary containing the assignments for the variables in the circuit.
required
Returns:
Name Type Description assigned_circuitTuple[AnalogCircuit, Dict]
AnalogCircuit with variables assigned.
Raises:
Type Description ValueError
If there are any variables that have not been assigned.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def assign_circuit(\n circuit: analog_circuit.AnalogCircuit, assignments: Dict[str, ParamType]\n) -> Tuple[analog_circuit.AnalogCircuit, Dict]:\n \"\"\"3. Assign variables and validate assignment\n\n This pass assigns variables to the circuit and validates that all variables\n have been assigned.\n\n Args:\n circuit: AnalogCircuit to assign variables to\n assignments: Dictionary containing the assignments for the variables in\n the circuit.\n\n Returns:\n assigned_circuit: AnalogCircuit with variables assigned.\n\n Raises:\n ValueError: If there are any variables that have not been assigned.\n\n \"\"\"\n from bloqade.analog.compiler.rewrite.common import AssignBloqadeIR\n from bloqade.analog.compiler.analysis.common import ScanVariables, AssignmentScan\n\n final_assignments = AssignmentScan(assignments).scan(circuit)\n\n assigned_circuit = AssignBloqadeIR(final_assignments).visit(circuit)\n\n assignment_analysis = ScanVariables().scan(assigned_circuit)\n\n if not assignment_analysis.is_assigned:\n missing_vars = assignment_analysis.scalar_vars.union(\n assignment_analysis.vector_vars\n )\n raise ValueError(\n \"Missing assignments for variables:\\n\"\n + (\"\\n\".join(f\"{var}\" for var in missing_vars))\n + \"\\n\"\n )\n\n return assigned_circuit, final_assignments\n
Insert zero waveform in the explicit time intervals missing a waveform
This pass inserts a zero waveform in the explicit time intervals missing a waveform. This is required for later analysis passes to check that the waveforms are compatible with the hardware.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to add padding to
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required
Return circuit: AnalogCircuit with zero waveforms inserted in the explicit time intervals missing a waveform.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def canonicalize_circuit(\n circuit: analog_circuit.AnalogCircuit, level_couplings: Dict\n) -> analog_circuit.AnalogCircuit:\n \"\"\"2. Insert zero waveform in the explicit time intervals missing a waveform\n\n This pass inserts a zero waveform in the explicit time intervals missing a\n waveform. This is required for later analysis passes to check that the\n waveforms are compatible with the hardware.\n\n Args:\n circuit: AnalogCircuit to add padding to\n level_couplings: Dictionary containing the given channels for the\n sequence.\n\n Return\n circuit: AnalogCircuit with zero waveforms inserted in the explicit time\n intervals missing a waveform.\n\n \"\"\"\n from bloqade.analog.compiler.rewrite.common import (\n AddPadding,\n Canonicalizer,\n AssignToLiteral,\n )\n\n circuit = AddPadding(level_couplings).visit(circuit)\n # these two passes are equivalent to a constant propagation pass\n circuit = AssignToLiteral().visit(circuit)\n circuit = Canonicalizer().visit(circuit)\n\n return circuit\n
Generates the AHS code for the given circuit. This includes generating the lattice data, global detuning, global amplitude, global phase, local detuning and lattice site coefficients (if applicable).
Parameters:
Name Type Description Default capabilitiesQuEraCapabilities | None
Capabilities of the hardware.
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required circuitAnalogCircuit
AnalogCircuit to generate AHS code for.
required
Returns:
Name Type Description ahs_componentsAHSComponents
A collection of the AHS components generated for the given circuit. Can be used to generate the QuEra and Braket IR.
Raises:
Type Description ValueError
If the capabilities are not provided but the circuit has a ParallelRegister. This is because the ParallelRegister requires the capabilities to generate the lattice data.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def generate_ahs_code(\n capabilities: Optional[QuEraCapabilities],\n level_couplings: Dict,\n circuit: analog_circuit.AnalogCircuit,\n) -> AHSComponents:\n \"\"\"5. generate ahs code\n\n Generates the AHS code for the given circuit. This includes generating the\n lattice data, global detuning, global amplitude, global phase, local\n detuning and lattice site coefficients (if applicable).\n\n Args:\n capabilities (QuEraCapabilities | None): Capabilities of the hardware.\n level_couplings (Dict): Dictionary containing the given channels for the\n sequence.\n circuit (AnalogCircuit): AnalogCircuit to generate AHS code for.\n\n Returns:\n ahs_components (AHSComponents): A collection of the AHS components\n generated for the given circuit. Can be used to generate the QuEra\n and Braket IR.\n\n Raises:\n ValueError: If the capabilities are not provided but the circuit has\n a ParallelRegister. This is because the ParallelRegister requires\n the capabilities to generate the lattice data.\n\n \"\"\"\n from bloqade.analog.compiler.codegen.hardware import (\n GenerateLattice,\n GeneratePiecewiseLinearChannel,\n GenerateLatticeSiteCoefficients,\n GeneratePiecewiseConstantChannel,\n )\n from bloqade.analog.compiler.analysis.hardware import BasicLatticeValidation\n\n if capabilities is not None:\n # only validate the lattice if capabilities are provided\n BasicLatticeValidation(capabilities).visit(circuit)\n\n ahs_lattice_data = GenerateLattice(capabilities).emit(circuit)\n\n global_detuning = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.detuning, field.Uniform\n ).visit(circuit)\n\n global_amplitude = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.rabi.amplitude, field.Uniform\n ).visit(circuit)\n\n global_phase = GeneratePiecewiseConstantChannel(\n sequence.rydberg, pulse.rabi.phase, field.Uniform\n ).visit(circuit)\n\n local_detuning = None\n lattice_site_coefficients = None\n\n extra_sm = set(level_couplings[sequence.rydberg][pulse.detuning]) - {field.Uniform}\n\n if extra_sm:\n if capabilities is not None and capabilities.capabilities.rydberg.local is None:\n raise ValueError(\n \"Device does not support local detuning, but the program has a \"\n \"non-uniform spatial modulation for detuning.\"\n )\n\n sm = extra_sm.pop()\n\n lattice_site_coefficients = GenerateLatticeSiteCoefficients(\n parallel_decoder=ahs_lattice_data.parallel_decoder\n ).emit(circuit)\n\n local_detuning = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.detuning, sm\n ).visit(circuit)\n\n return AHSComponents(\n lattice_data=ahs_lattice_data,\n global_detuning=global_detuning,\n global_amplitude=global_amplitude,\n global_phase=global_phase,\n local_detuning=local_detuning,\n lattice_site_coefficients=lattice_site_coefficients,\n )\n
validate piecewise linear and piecewise constant pieces of pulses
This pass check to make sure that the waveforms are compatible with the hardware. This includes checking that the waveforms are piecewise linear or piecewise constant. It also checks that the waveforms are compatible with the given channels.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to validate waveforms for
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required
Raises:
Type Description ValueError
If the waveforms are not piecewise linear or piecewise constant, e.g. the waveform is not continuous.
ValueError
If a waveform segment is not compatible with the given channels.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def validate_waveforms(\n level_couplings: Dict, circuit: analog_circuit.AnalogCircuit\n) -> None:\n \"\"\"4. validate piecewise linear and piecewise constant pieces of pulses\n\n This pass check to make sure that the waveforms are compatible with the\n hardware. This includes checking that the waveforms are piecewise linear or\n piecewise constant. It also checks that the waveforms are compatible with\n the given channels.\n\n Args:\n circuit: AnalogCircuit to validate waveforms for\n level_couplings: Dictionary containing the given channels for the\n sequence.\n\n Raises:\n ValueError: If the waveforms are not piecewise linear or piecewise\n constant, e.g. the waveform is not continuous.\n ValueError: If a waveform segment is not compatible with the given\n channels.\n\n \"\"\"\n from bloqade.analog.compiler.analysis.common import CheckSlices\n from bloqade.analog.compiler.analysis.hardware import (\n ValidatePiecewiseLinearChannel,\n ValidatePiecewiseConstantChannel,\n )\n\n channel_iter = (\n (level_coupling, field_name, sm)\n for level_coupling, fields in level_couplings.items()\n for field_name, spatial_modulations in fields.items()\n for sm in spatial_modulations\n )\n for channel in channel_iter:\n if channel[1] in [pulse.detuning, pulse.rabi.amplitude]:\n ValidatePiecewiseLinearChannel(*channel).visit(circuit)\n else:\n ValidatePiecewiseConstantChannel(*channel).visit(circuit)\n\n CheckSlices().visit(circuit)\n\n if circuit.sequence.duration() == 0:\n raise ValueError(\"Circuit Duration must be be non-zero\")\n
This pass checks to make sure that: * There is no hyperfine coupling in the sequence * There are no non-uniform spatial modulation for rabi phase and amplitude * there is no more than one non-uniform spatial modulation for detuning
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to analyze
required
Returns:
Name Type Description level_couplingsDict
Dictionary containing the required channels for the sequence. Note that this will insert a uniform field for any missing channels.
Raises:
Type Description ValueError
If there is hyperfine coupling in the sequence.
ValueError
If there is more than one non-uniform spatial modulation for detuning.
ValueError
If there are non-uniform spatial modulations for rabi phase and amplitude.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def analyze_channels(circuit: analog_circuit.AnalogCircuit) -> Dict:\n \"\"\"1. Scan channels\n\n This pass checks to make sure that:\n * There is no hyperfine coupling in the sequence\n * There are no non-uniform spatial modulation for rabi phase and amplitude\n * there is no more than one non-uniform spatial modulation for detuning\n\n Args:\n circuit: AnalogCircuit to analyze\n\n Returns:\n level_couplings: Dictionary containing the required channels for the\n sequence. Note that this will insert a uniform field for any missing\n channels.\n\n Raises:\n ValueError: If there is hyperfine coupling in the sequence.\n ValueError: If there is more than one non-uniform spatial modulation for\n detuning.\n ValueError: If there are non-uniform spatial modulations for rabi phase\n and amplitude.\n\n \"\"\"\n from bloqade.analog.compiler.analysis.common import ScanChannels\n from bloqade.analog.compiler.analysis.hardware import ValidateChannels\n\n ValidateChannels().scan(circuit)\n level_couplings = ScanChannels().scan(circuit)\n\n # add missing channels\n fields = level_couplings[sequence.rydberg]\n # detuning, phase and amplitude are required\n # to have at least a uniform field\n updated_fields = {\n field_name: fields.get(field_name, {field.Uniform}).union({field.Uniform})\n for field_name in [pulse.detuning, pulse.rabi.amplitude, pulse.rabi.phase]\n }\n\n return {sequence.rydberg: updated_fields}\n
This pass assigns variables to the circuit and validates that all variables have been assigned.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to assign variables to
required assignmentsDict[str, ParamType]
Dictionary containing the assignments for the variables in the circuit.
required
Returns:
Name Type Description assigned_circuitTuple[AnalogCircuit, Dict]
AnalogCircuit with variables assigned.
Raises:
Type Description ValueError
If there are any variables that have not been assigned.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def assign_circuit(\n circuit: analog_circuit.AnalogCircuit, assignments: Dict[str, ParamType]\n) -> Tuple[analog_circuit.AnalogCircuit, Dict]:\n \"\"\"3. Assign variables and validate assignment\n\n This pass assigns variables to the circuit and validates that all variables\n have been assigned.\n\n Args:\n circuit: AnalogCircuit to assign variables to\n assignments: Dictionary containing the assignments for the variables in\n the circuit.\n\n Returns:\n assigned_circuit: AnalogCircuit with variables assigned.\n\n Raises:\n ValueError: If there are any variables that have not been assigned.\n\n \"\"\"\n from bloqade.analog.compiler.rewrite.common import AssignBloqadeIR\n from bloqade.analog.compiler.analysis.common import ScanVariables, AssignmentScan\n\n final_assignments = AssignmentScan(assignments).scan(circuit)\n\n assigned_circuit = AssignBloqadeIR(final_assignments).visit(circuit)\n\n assignment_analysis = ScanVariables().scan(assigned_circuit)\n\n if not assignment_analysis.is_assigned:\n missing_vars = assignment_analysis.scalar_vars.union(\n assignment_analysis.vector_vars\n )\n raise ValueError(\n \"Missing assignments for variables:\\n\"\n + (\"\\n\".join(f\"{var}\" for var in missing_vars))\n + \"\\n\"\n )\n\n return assigned_circuit, final_assignments\n
Insert zero waveform in the explicit time intervals missing a waveform
This pass inserts a zero waveform in the explicit time intervals missing a waveform. This is required for later analysis passes to check that the waveforms are compatible with the hardware.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to add padding to
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required
Return circuit: AnalogCircuit with zero waveforms inserted in the explicit time intervals missing a waveform.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def canonicalize_circuit(\n circuit: analog_circuit.AnalogCircuit, level_couplings: Dict\n) -> analog_circuit.AnalogCircuit:\n \"\"\"2. Insert zero waveform in the explicit time intervals missing a waveform\n\n This pass inserts a zero waveform in the explicit time intervals missing a\n waveform. This is required for later analysis passes to check that the\n waveforms are compatible with the hardware.\n\n Args:\n circuit: AnalogCircuit to add padding to\n level_couplings: Dictionary containing the given channels for the\n sequence.\n\n Return\n circuit: AnalogCircuit with zero waveforms inserted in the explicit time\n intervals missing a waveform.\n\n \"\"\"\n from bloqade.analog.compiler.rewrite.common import (\n AddPadding,\n Canonicalizer,\n AssignToLiteral,\n )\n\n circuit = AddPadding(level_couplings).visit(circuit)\n # these two passes are equivalent to a constant propagation pass\n circuit = AssignToLiteral().visit(circuit)\n circuit = Canonicalizer().visit(circuit)\n\n return circuit\n
Generates the AHS code for the given circuit. This includes generating the lattice data, global detuning, global amplitude, global phase, local detuning and lattice site coefficients (if applicable).
Parameters:
Name Type Description Default capabilitiesQuEraCapabilities | None
Capabilities of the hardware.
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required circuitAnalogCircuit
AnalogCircuit to generate AHS code for.
required
Returns:
Name Type Description ahs_componentsAHSComponents
A collection of the AHS components generated for the given circuit. Can be used to generate the QuEra and Braket IR.
Raises:
Type Description ValueError
If the capabilities are not provided but the circuit has a ParallelRegister. This is because the ParallelRegister requires the capabilities to generate the lattice data.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def generate_ahs_code(\n capabilities: Optional[QuEraCapabilities],\n level_couplings: Dict,\n circuit: analog_circuit.AnalogCircuit,\n) -> AHSComponents:\n \"\"\"5. generate ahs code\n\n Generates the AHS code for the given circuit. This includes generating the\n lattice data, global detuning, global amplitude, global phase, local\n detuning and lattice site coefficients (if applicable).\n\n Args:\n capabilities (QuEraCapabilities | None): Capabilities of the hardware.\n level_couplings (Dict): Dictionary containing the given channels for the\n sequence.\n circuit (AnalogCircuit): AnalogCircuit to generate AHS code for.\n\n Returns:\n ahs_components (AHSComponents): A collection of the AHS components\n generated for the given circuit. Can be used to generate the QuEra\n and Braket IR.\n\n Raises:\n ValueError: If the capabilities are not provided but the circuit has\n a ParallelRegister. This is because the ParallelRegister requires\n the capabilities to generate the lattice data.\n\n \"\"\"\n from bloqade.analog.compiler.codegen.hardware import (\n GenerateLattice,\n GeneratePiecewiseLinearChannel,\n GenerateLatticeSiteCoefficients,\n GeneratePiecewiseConstantChannel,\n )\n from bloqade.analog.compiler.analysis.hardware import BasicLatticeValidation\n\n if capabilities is not None:\n # only validate the lattice if capabilities are provided\n BasicLatticeValidation(capabilities).visit(circuit)\n\n ahs_lattice_data = GenerateLattice(capabilities).emit(circuit)\n\n global_detuning = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.detuning, field.Uniform\n ).visit(circuit)\n\n global_amplitude = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.rabi.amplitude, field.Uniform\n ).visit(circuit)\n\n global_phase = GeneratePiecewiseConstantChannel(\n sequence.rydberg, pulse.rabi.phase, field.Uniform\n ).visit(circuit)\n\n local_detuning = None\n lattice_site_coefficients = None\n\n extra_sm = set(level_couplings[sequence.rydberg][pulse.detuning]) - {field.Uniform}\n\n if extra_sm:\n if capabilities is not None and capabilities.capabilities.rydberg.local is None:\n raise ValueError(\n \"Device does not support local detuning, but the program has a \"\n \"non-uniform spatial modulation for detuning.\"\n )\n\n sm = extra_sm.pop()\n\n lattice_site_coefficients = GenerateLatticeSiteCoefficients(\n parallel_decoder=ahs_lattice_data.parallel_decoder\n ).emit(circuit)\n\n local_detuning = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.detuning, sm\n ).visit(circuit)\n\n return AHSComponents(\n lattice_data=ahs_lattice_data,\n global_detuning=global_detuning,\n global_amplitude=global_amplitude,\n global_phase=global_phase,\n local_detuning=local_detuning,\n lattice_site_coefficients=lattice_site_coefficients,\n )\n
validate piecewise linear and piecewise constant pieces of pulses
This pass check to make sure that the waveforms are compatible with the hardware. This includes checking that the waveforms are piecewise linear or piecewise constant. It also checks that the waveforms are compatible with the given channels.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to validate waveforms for
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required
Raises:
Type Description ValueError
If the waveforms are not piecewise linear or piecewise constant, e.g. the waveform is not continuous.
ValueError
If a waveform segment is not compatible with the given channels.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def validate_waveforms(\n level_couplings: Dict, circuit: analog_circuit.AnalogCircuit\n) -> None:\n \"\"\"4. validate piecewise linear and piecewise constant pieces of pulses\n\n This pass check to make sure that the waveforms are compatible with the\n hardware. This includes checking that the waveforms are piecewise linear or\n piecewise constant. It also checks that the waveforms are compatible with\n the given channels.\n\n Args:\n circuit: AnalogCircuit to validate waveforms for\n level_couplings: Dictionary containing the given channels for the\n sequence.\n\n Raises:\n ValueError: If the waveforms are not piecewise linear or piecewise\n constant, e.g. the waveform is not continuous.\n ValueError: If a waveform segment is not compatible with the given\n channels.\n\n \"\"\"\n from bloqade.analog.compiler.analysis.common import CheckSlices\n from bloqade.analog.compiler.analysis.hardware import (\n ValidatePiecewiseLinearChannel,\n ValidatePiecewiseConstantChannel,\n )\n\n channel_iter = (\n (level_coupling, field_name, sm)\n for level_coupling, fields in level_couplings.items()\n for field_name, spatial_modulations in fields.items()\n for sm in spatial_modulations\n )\n for channel in channel_iter:\n if channel[1] in [pulse.detuning, pulse.rabi.amplitude]:\n ValidatePiecewiseLinearChannel(*channel).visit(circuit)\n else:\n ValidatePiecewiseConstantChannel(*channel).visit(circuit)\n\n CheckSlices().visit(circuit)\n\n if circuit.sequence.duration() == 0:\n raise ValueError(\"Circuit Duration must be be non-zero\")\n
"},{"location":"reference/bloqade/analog/compiler/rewrite/common/add_padding/","title":"Add padding","text":""},{"location":"reference/bloqade/analog/compiler/rewrite/common/assign_to_literal/","title":"Assign to literal","text":""},{"location":"reference/bloqade/analog/compiler/rewrite/common/assign_to_literal/#bloqade.analog.compiler.rewrite.common.assign_to_literal.AssignToLiteral","title":"AssignToLiteral","text":"
Hamiltonian for a given task. With the RydbergHamiltonian you can convert the Hamiltonian to CSR matrix form as well as obtaining the average energy/variance of a register.
Attributes:
Name Type Description emulator_irEmulatorProgram
A copy of the original program used to generate the RydbergHamiltonian
spaceSpace
The Hilbert space of the Hamiltonian, should align with the register the Hamiltonian is being applied on for average energy/variance
Get energy average from RydbergHamiltonian object at time time with register register
Parameters:
Name Type Description Default registerStateVector
The state vector to take average with
required timeOptional[float]
Time value to evaluate average at.
None
Returns:
Name Type Description floatfloat
average energy at time time
Source code in src/bloqade/analog/emulate/ir/state_vector.py
@beartype\ndef average(\n self,\n register: StateVector,\n time: Optional[float] = None,\n) -> float:\n \"\"\"Get energy average from RydbergHamiltonian object at time `time` with\n register `register`\n\n Args:\n register (StateVector): The state vector to take average with\n time (Optional[float], optional): Time value to evaluate average at.\n Defaults to duration of RydbergHamiltonian.\n\n Returns:\n float: average energy at time `time`\n \"\"\"\n return np.vdot(register.data, self._apply(register.data, time)).real\n
Get energy average and variance from RydbergHamiltonian object at time time with register register
Parameters:
Name Type Description Default registerStateVector
The state vector to take average and variance with
required timeOptional[float]
Time value to evaluate average at.
None
Returns:
Type Description float
Tuple[float, float]: average and variance of energy at time time
float
respectively.
Source code in src/bloqade/analog/emulate/ir/state_vector.py
@beartype\ndef average_and_variance(\n self,\n register: StateVector,\n time: Optional[float] = None,\n) -> Tuple[float, float]:\n \"\"\"Get energy average and variance from RydbergHamiltonian object at time `time`\n with register `register`\n\n Args:\n register (StateVector): The state vector to take average and variance with\n time (Optional[float], optional): Time value to evaluate average at.\n Defaults to duration of RydbergHamiltonian.\n\n Returns:\n Tuple[float, float]: average and variance of energy at time `time`\n respectively.\n \"\"\"\n H_register_data = self._apply(register.data, time)\n\n average = np.vdot(register.data, H_register_data).real\n square_average = np.vdot(H_register_data, H_register_data).real\n\n return average, square_average - average**2\n
Return the Hamiltonian as a csr matrix at time time.
Parameters:
Name Type Description Default timefloat
time to evaluate the Hamiltonian at.
required
Returns:
Name Type Description csr_matrixcsr_matrix
The Hamiltonian as a csr matrix.
Source code in src/bloqade/analog/emulate/ir/state_vector.py
def tocsr(self, time: float) -> csr_matrix:\n \"\"\"Return the Hamiltonian as a csr matrix at time `time`.\n\n Args:\n time (float): time to evaluate the Hamiltonian at.\n\n Returns:\n csr_matrix: The Hamiltonian as a csr matrix.\n\n \"\"\"\n diagonal = sum(\n (detuning.get_diagonal(time) for detuning in self.detuning_ops),\n start=self.rydberg,\n )\n\n hamiltonian = diags(diagonal).tocsr()\n for rabi_op in self.rabi_ops:\n hamiltonian = hamiltonian + rabi_op.tocsr(time)\n\n return hamiltonian\n
Get the energy variance from RydbergHamiltonian object at time time with register register
Parameters:
Name Type Description Default registerStateVector
The state vector to take variance with
required timeOptional[float]
Time value to evaluate average at.
None
Returns:
Name Type Description complexfloat
variance of energy at time time respectively.
Source code in src/bloqade/analog/emulate/ir/state_vector.py
@beartype\ndef variance(\n self,\n register: StateVector,\n time: Optional[float] = None,\n) -> float:\n \"\"\"Get the energy variance from RydbergHamiltonian object at\n time `time` with register `register`\n\n Args:\n register (StateVector): The state vector to take variance with\n time (Optional[float], optional): Time value to evaluate average at.\n Defaults to duration of RydbergHamiltonian.\n\n Returns:\n complex: variance of energy at time `time` respectively.\n \"\"\"\n\n _, var = self.average_and_variance(register, time)\n return var\n
Square matrix representing operator in the local hilbert space.
required site_indexint | Tuple[int, int]
sites to apply one body operator to.
required
Returns:
Name Type Description complexcomplex
the trace of the operator over the state-vector.
Raises:
Type Description ValueError
Error is raised when the dimension of operator is not
ValueError
Error is raised when the site argument is out of bounds.
Source code in src/bloqade/analog/emulate/ir/state_vector.py
@plum.dispatch\ndef local_trace( # noqa: F811\n self, matrix: np.ndarray, site_index: Union[int, Tuple[int, int]]\n) -> complex: # noqa: F811\n \"\"\"return trace of an operator over the StateVector.\n\n Args:\n matrix (np.ndarray): Square matrix representing operator in the local\n hilbert space.\n site_index (int | Tuple[int, int]): sites to apply one body operator to.\n\n Returns:\n complex: the trace of the operator over the state-vector.\n\n Raises:\n ValueError: Error is raised when the dimension of `operator` is not\n consistent with `site` argument. The size of the operator must fit\n the size of the local hilbert space of `site` depending on the number\n of sites and the number of levels inside each atom, e.g. for two site\n expectation value with a three level atom the operator must be a 9 by\n 9 array.\n\n ValueError: Error is raised when the `site` argument is out of bounds.\n\n \"\"\"\n ...\n
assigning the instance value (literal) to the existing variables in the program
{} Source code in src/bloqade/analog/ir/analog_circuit.py
def show(self, **assignments):\n \"\"\"Interactive visualization of the program\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the program\n\n \"\"\"\n display_ir(self, assignments)\n
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).
# single coordinate\n>>> reg = start.add_position((0,0))\n# you may chain add_position calls\n>>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n# you can add variables anywhere a value may be present\n>>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n# and specify your atom fillings\n>>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n[True, False])\n# alternatively you could use one boolean to specify\n# all coordinates should be empty/filled\n>>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n(5.2, 2.2)], False)\n
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
Source code in src/bloqade/analog/ir/location/location.py
def add_position(\n self,\n position: Union[\n PositionArray,\n List[Tuple[ScalarType, ScalarType]],\n Tuple[ScalarType, ScalarType],\n ],\n filling: Optional[Union[BoolArray, List[bool], bool]] = None,\n) -> \"ListOfLocations\":\n \"\"\"\n Add a position or multiple positions to a pre-existing geometry.\n\n `add_position` is capable of accepting:\n - A single tuple for one atom coordinate: `(1.0, 2.5)`\n - A list of tuples: `[(0.0, 1.0), (2.0,1.5), etc.]\n - A numpy array of shape (N, 2) where N is the number of atoms\n\n You may also intersperse variables anywhere a value may be present.\n\n You can also pass in an optional argument which determines the atom \"filling\"\n (whether or not at a specified coordinate an atom should be present).\n\n ### Usage Example:\n ```\n # single coordinate\n >>> reg = start.add_position((0,0))\n # you may chain add_position calls\n >>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n # you can add variables anywhere a value may be present\n >>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n # and specify your atom fillings\n >>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n [True, False])\n # alternatively you could use one boolean to specify\n # all coordinates should be empty/filled\n >>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n (5.2, 2.2)], False)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`: to specify Rydberg coupling\n - `...add_position(positions).hyperfine`: to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n if is_bearable(position, PositionArray) and is_bearable(\n filling, Optional[BoolArray]\n ):\n return self.add_position_ndarray(position, filling)\n elif is_bearable(position, List[Tuple[ScalarType, ScalarType]]) and is_bearable(\n filling, Optional[List[bool]]\n ):\n return self.add_position_list_tuples(position, filling)\n elif is_bearable(position, Tuple[ScalarType, ScalarType]) and is_bearable(\n filling, Optional[bool]\n ):\n return self.add_position_single_tupe(position, filling)\n else:\n raise TypeError(\"Invalid input types for add_position provided!\")\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_count(2, custom_rng)\n# you may also chain apply_defect_count calls\n>>> reg.apply_defect_count(2, custom_rng)\n# you can also use apply_defect_count on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_count(\n self, n_defects: int, rng: np.random.Generator = np.random.default_rng()\n):\n \"\"\"\n Drop `n_defects` atoms from the geometry randomly. Internally this occurs\n by setting certain sites to have a SiteFilling set to false indicating\n no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_count(2, custom_rng)\n # you may also chain apply_defect_count calls\n >>> reg.apply_defect_count(2, custom_rng)\n # you can also use apply_defect_count on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts)\n .apply_defect_count(n_defects)`: to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`: to specify\n Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n location_list = []\n for location_info in self.enumerate():\n location_list.append(location_info)\n\n filled_sites = []\n\n for index, location_info in enumerate(location_list):\n if location_info.filling is SiteFilling.filled:\n filled_sites.append(index)\n\n if n_defects >= len(filled_sites):\n raise ValueError(\n f\"n_defects {n_defects} must be less than the number of filled sites \"\n f\"({len(filled_sites)})\"\n )\n\n for _ in range(n_defects):\n index = rng.choice(filled_sites)\n location_list[index] = LocationInfo.create(\n location_list[index].position,\n (False if location_list[index].filling is SiteFilling.filled else True),\n )\n filled_sites.remove(index)\n\n return ListOfLocations(location_list)\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n# you may also chain apply_defect_density calls\n>>> reg.apply_defect_count(0.1, custom_rng)\n# you can also use apply_defect_density on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)])\n.apply_defect_density(0.5, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_density(\n self,\n defect_probability: float,\n rng: np.random.Generator = np.random.default_rng(),\n):\n \"\"\"\n Drop atoms randomly with `defect_probability` probability (range of 0 to 1).\n Internally this occurs by setting certain sites to have a SiteFilling\n set to false indicating no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n # you may also chain apply_defect_density calls\n >>> reg.apply_defect_count(0.1, custom_rng)\n # you can also use apply_defect_density on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)])\n .apply_defect_density(0.5, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`:\n to specify Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n p = min(1, max(0, defect_probability))\n location_list = []\n\n for location_info in self.enumerate():\n if rng.random() < p:\n location_list.append(\n LocationInfo.create(\n location_info.position,\n (\n False\n if location_info.filling is SiteFilling.filled\n else True\n ),\n )\n )\n else:\n location_list.append(location_info)\n\n return ListOfLocations(location_list=location_list)\n
Source code in src/bloqade/analog/ir/location/location.py
def figure(self, fig_kwargs=None, **assignments):\n \"\"\"obtain a figure object from the atom arrangement.\"\"\"\n return get_atom_arrangement_figure(self, fig_kwargs=fig_kwargs, **assignments)\n
the values to assign to the variables in the register.
{}
Returns:
Name Type Description NDArrayNDArray
the Rydberg interaction matrix in the lower triangular form.
Source code in src/bloqade/analog/ir/location/location.py
def rydberg_interaction(self, **assignments) -> NDArray:\n \"\"\"calculate the Rydberg interaction matrix.\n\n Args:\n **assignments: the values to assign to the variables in the register.\n\n Returns:\n NDArray: the Rydberg interaction matrix in the lower triangular form.\n\n \"\"\"\n\n from bloqade.analog.constants import RB_C6\n\n # calculate the Interaction matrix\n V_ij = np.zeros((self.n_sites, self.n_sites))\n for i, site_i in enumerate(self.enumerate()):\n pos_i = np.array([float(ele(**assignments)) for ele in site_i.position])\n\n for j, site_j in enumerate(self.enumerate()):\n if j >= i:\n break # enforce lower triangular form\n\n pos_j = np.array([float(ele(**assignments)) for ele in site_j.position])\n r_ij = np.linalg.norm(pos_i - pos_j)\n\n V_ij[i, j] = RB_C6 / r_ij**6\n\n return V_ij\n
>>> reg = start.add_position([(0,0), (1,1)])\n# atom positions are now (0,0), (2,2)\n>>> new_reg = reg.scale(2)\n# you may also use scale on pre-defined geometries\n>>> from bloqade.analog.atom_arrangement import Chain\n# atoms in the chain will now be 2 um apart versus\n# the default 1 um\n>>> Chain(11).scale(2)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef scale(self, scale: ScalarType):\n \"\"\"\n Scale the geometry of your atoms.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position([(0,0), (1,1)])\n # atom positions are now (0,0), (2,2)\n >>> new_reg = reg.scale(2)\n # you may also use scale on pre-defined geometries\n >>> from bloqade.analog.atom_arrangement import Chain\n # atoms in the chain will now be 2 um apart versus\n # the default 1 um\n >>> Chain(11).scale(2)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`:\n to specify Rydberg coupling\n - `...add_position(positions).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n scale = cast(scale)\n location_list = []\n for location_info in self.enumerate():\n x, y = location_info.position\n new_position = (scale * x, scale * y)\n location_list.append(\n LocationInfo.create(new_position, bool(location_info.filling.value))\n )\n\n return ListOfLocations(location_list)\n
calculate the coordinates of a cell in the lattice given the cell index.
Source code in src/bloqade/analog/ir/location/bravais.py
@beartype\ndef coordinates(self, index: List[int]) -> NDArray:\n \"\"\"calculate the coordinates of a cell in the lattice\n given the cell index.\n \"\"\"\n # damn! this is like stone age broadcasting\n vectors = np.array(self.cell_vectors())\n index = np.array(index)\n pos = np.sum(vectors.T * index, axis=1)\n return pos + np.array(self.cell_atoms())\n
assigning the instance value (literal) to the existing variables in the Field
{} Source code in src/bloqade/analog/ir/control/field.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Field\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Field\n\n \"\"\"\n display_ir(self, assignments)\n
assigning the instance value (literal) to the existing variables in the Pulse
{} Source code in src/bloqade/analog/ir/control/pulse.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Pulse\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Pulse\n\n \"\"\"\n display_ir(self, assignments)\n
assigning the instance value (literal) to the existing variables in the Sequence
{} Source code in src/bloqade/analog/ir/control/sequence.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Sequence\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Sequence\n\n \"\"\"\n display_ir(self, assignments)\n
Source code in src/bloqade/analog/ir/control/waveform.py
def figure(self, **assignments):\n \"\"\"get figure of the plotting the waveform.\n\n Returns:\n figure: a bokeh figure\n \"\"\"\n return get_ir_figure(self, **assignments)\n
cast Real number (or list/tuple of Real numbers) to Scalar Literal.
cast str (or list/tuple of Real numbers) to Scalar Variable.
Parameters:
Name Type Description Default pyUnion[str, Real, Tuple[Real], List[Real]]
python object to cast
required
Returns:
Type Description Scalar
Scalar
Source code in src/bloqade/analog/ir/scalar.py
def cast(py) -> \"Scalar\":\n \"\"\"\n 1. cast Real number (or list/tuple of Real numbers)\n to [`Scalar Literal`][bloqade.ir.scalar.Literal].\n\n 2. cast str (or list/tuple of Real numbers)\n to [`Scalar Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str,Real,Tuple[Real],List[Real]]): python object to cast\n\n Returns:\n Scalar\n \"\"\"\n ret = trycast(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Scalar Literal\")\n\n return ret\n
cast string (or list/tuple of strings) to Variable.
Parameters:
Name Type Description Default pyUnion[str, List[str]]
a string or list/tuple of strings
required
Returns:
Type Description Variable
Union[Variable]
Source code in src/bloqade/analog/ir/scalar.py
def var(py: str) -> \"Variable\":\n \"\"\"cast string (or list/tuple of strings)\n to [`Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str, List[str]]): a string or list/tuple of strings\n\n Returns:\n Union[Variable]\n \"\"\"\n ret = tryvar(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Variable\")\n\n return ret\n
assigning the instance value (literal) to the existing variables in the program
{} Source code in src/bloqade/analog/ir/analog_circuit.py
def show(self, **assignments):\n \"\"\"Interactive visualization of the program\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the program\n\n \"\"\"\n display_ir(self, assignments)\n
cast Real number (or list/tuple of Real numbers) to Scalar Literal.
cast str (or list/tuple of Real numbers) to Scalar Variable.
Parameters:
Name Type Description Default pyUnion[str, Real, Tuple[Real], List[Real]]
python object to cast
required
Returns:
Type Description Scalar
Scalar
Source code in src/bloqade/analog/ir/scalar.py
def cast(py) -> \"Scalar\":\n \"\"\"\n 1. cast Real number (or list/tuple of Real numbers)\n to [`Scalar Literal`][bloqade.ir.scalar.Literal].\n\n 2. cast str (or list/tuple of Real numbers)\n to [`Scalar Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str,Real,Tuple[Real],List[Real]]): python object to cast\n\n Returns:\n Scalar\n \"\"\"\n ret = trycast(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Scalar Literal\")\n\n return ret\n
cast string (or list/tuple of strings) to Variable.
Parameters:
Name Type Description Default pyUnion[str, List[str]]
a string or list/tuple of strings
required
Returns:
Type Description Variable
Union[Variable]
Source code in src/bloqade/analog/ir/scalar.py
def var(py: str) -> \"Variable\":\n \"\"\"cast string (or list/tuple of strings)\n to [`Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str, List[str]]): a string or list/tuple of strings\n\n Returns:\n Union[Variable]\n \"\"\"\n ret = tryvar(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Variable\")\n\n return ret\n
assigning the instance value (literal) to the existing variables in the Field
{} Source code in src/bloqade/analog/ir/control/field.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Field\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Field\n\n \"\"\"\n display_ir(self, assignments)\n
assigning the instance value (literal) to the existing variables in the Pulse
{} Source code in src/bloqade/analog/ir/control/pulse.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Pulse\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Pulse\n\n \"\"\"\n display_ir(self, assignments)\n
assigning the instance value (literal) to the existing variables in the Sequence
{} Source code in src/bloqade/analog/ir/control/sequence.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Sequence\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Sequence\n\n \"\"\"\n display_ir(self, assignments)\n
Source code in src/bloqade/analog/ir/control/waveform.py
def figure(self, **assignments):\n \"\"\"get figure of the plotting the waveform.\n\n Returns:\n figure: a bokeh figure\n \"\"\"\n return get_ir_figure(self, **assignments)\n
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).
# single coordinate\n>>> reg = start.add_position((0,0))\n# you may chain add_position calls\n>>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n# you can add variables anywhere a value may be present\n>>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n# and specify your atom fillings\n>>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n[True, False])\n# alternatively you could use one boolean to specify\n# all coordinates should be empty/filled\n>>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n(5.2, 2.2)], False)\n
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
Source code in src/bloqade/analog/ir/location/location.py
def add_position(\n self,\n position: Union[\n PositionArray,\n List[Tuple[ScalarType, ScalarType]],\n Tuple[ScalarType, ScalarType],\n ],\n filling: Optional[Union[BoolArray, List[bool], bool]] = None,\n) -> \"ListOfLocations\":\n \"\"\"\n Add a position or multiple positions to a pre-existing geometry.\n\n `add_position` is capable of accepting:\n - A single tuple for one atom coordinate: `(1.0, 2.5)`\n - A list of tuples: `[(0.0, 1.0), (2.0,1.5), etc.]\n - A numpy array of shape (N, 2) where N is the number of atoms\n\n You may also intersperse variables anywhere a value may be present.\n\n You can also pass in an optional argument which determines the atom \"filling\"\n (whether or not at a specified coordinate an atom should be present).\n\n ### Usage Example:\n ```\n # single coordinate\n >>> reg = start.add_position((0,0))\n # you may chain add_position calls\n >>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n # you can add variables anywhere a value may be present\n >>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n # and specify your atom fillings\n >>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n [True, False])\n # alternatively you could use one boolean to specify\n # all coordinates should be empty/filled\n >>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n (5.2, 2.2)], False)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`: to specify Rydberg coupling\n - `...add_position(positions).hyperfine`: to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n if is_bearable(position, PositionArray) and is_bearable(\n filling, Optional[BoolArray]\n ):\n return self.add_position_ndarray(position, filling)\n elif is_bearable(position, List[Tuple[ScalarType, ScalarType]]) and is_bearable(\n filling, Optional[List[bool]]\n ):\n return self.add_position_list_tuples(position, filling)\n elif is_bearable(position, Tuple[ScalarType, ScalarType]) and is_bearable(\n filling, Optional[bool]\n ):\n return self.add_position_single_tupe(position, filling)\n else:\n raise TypeError(\"Invalid input types for add_position provided!\")\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_count(2, custom_rng)\n# you may also chain apply_defect_count calls\n>>> reg.apply_defect_count(2, custom_rng)\n# you can also use apply_defect_count on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_count(\n self, n_defects: int, rng: np.random.Generator = np.random.default_rng()\n):\n \"\"\"\n Drop `n_defects` atoms from the geometry randomly. Internally this occurs\n by setting certain sites to have a SiteFilling set to false indicating\n no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_count(2, custom_rng)\n # you may also chain apply_defect_count calls\n >>> reg.apply_defect_count(2, custom_rng)\n # you can also use apply_defect_count on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts)\n .apply_defect_count(n_defects)`: to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`: to specify\n Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n location_list = []\n for location_info in self.enumerate():\n location_list.append(location_info)\n\n filled_sites = []\n\n for index, location_info in enumerate(location_list):\n if location_info.filling is SiteFilling.filled:\n filled_sites.append(index)\n\n if n_defects >= len(filled_sites):\n raise ValueError(\n f\"n_defects {n_defects} must be less than the number of filled sites \"\n f\"({len(filled_sites)})\"\n )\n\n for _ in range(n_defects):\n index = rng.choice(filled_sites)\n location_list[index] = LocationInfo.create(\n location_list[index].position,\n (False if location_list[index].filling is SiteFilling.filled else True),\n )\n filled_sites.remove(index)\n\n return ListOfLocations(location_list)\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n# you may also chain apply_defect_density calls\n>>> reg.apply_defect_count(0.1, custom_rng)\n# you can also use apply_defect_density on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)])\n.apply_defect_density(0.5, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_density(\n self,\n defect_probability: float,\n rng: np.random.Generator = np.random.default_rng(),\n):\n \"\"\"\n Drop atoms randomly with `defect_probability` probability (range of 0 to 1).\n Internally this occurs by setting certain sites to have a SiteFilling\n set to false indicating no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n # you may also chain apply_defect_density calls\n >>> reg.apply_defect_count(0.1, custom_rng)\n # you can also use apply_defect_density on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)])\n .apply_defect_density(0.5, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`:\n to specify Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n p = min(1, max(0, defect_probability))\n location_list = []\n\n for location_info in self.enumerate():\n if rng.random() < p:\n location_list.append(\n LocationInfo.create(\n location_info.position,\n (\n False\n if location_info.filling is SiteFilling.filled\n else True\n ),\n )\n )\n else:\n location_list.append(location_info)\n\n return ListOfLocations(location_list=location_list)\n
Source code in src/bloqade/analog/ir/location/location.py
def figure(self, fig_kwargs=None, **assignments):\n \"\"\"obtain a figure object from the atom arrangement.\"\"\"\n return get_atom_arrangement_figure(self, fig_kwargs=fig_kwargs, **assignments)\n
the values to assign to the variables in the register.
{}
Returns:
Name Type Description NDArrayNDArray
the Rydberg interaction matrix in the lower triangular form.
Source code in src/bloqade/analog/ir/location/location.py
def rydberg_interaction(self, **assignments) -> NDArray:\n \"\"\"calculate the Rydberg interaction matrix.\n\n Args:\n **assignments: the values to assign to the variables in the register.\n\n Returns:\n NDArray: the Rydberg interaction matrix in the lower triangular form.\n\n \"\"\"\n\n from bloqade.analog.constants import RB_C6\n\n # calculate the Interaction matrix\n V_ij = np.zeros((self.n_sites, self.n_sites))\n for i, site_i in enumerate(self.enumerate()):\n pos_i = np.array([float(ele(**assignments)) for ele in site_i.position])\n\n for j, site_j in enumerate(self.enumerate()):\n if j >= i:\n break # enforce lower triangular form\n\n pos_j = np.array([float(ele(**assignments)) for ele in site_j.position])\n r_ij = np.linalg.norm(pos_i - pos_j)\n\n V_ij[i, j] = RB_C6 / r_ij**6\n\n return V_ij\n
>>> reg = start.add_position([(0,0), (1,1)])\n# atom positions are now (0,0), (2,2)\n>>> new_reg = reg.scale(2)\n# you may also use scale on pre-defined geometries\n>>> from bloqade.analog.atom_arrangement import Chain\n# atoms in the chain will now be 2 um apart versus\n# the default 1 um\n>>> Chain(11).scale(2)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef scale(self, scale: ScalarType):\n \"\"\"\n Scale the geometry of your atoms.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position([(0,0), (1,1)])\n # atom positions are now (0,0), (2,2)\n >>> new_reg = reg.scale(2)\n # you may also use scale on pre-defined geometries\n >>> from bloqade.analog.atom_arrangement import Chain\n # atoms in the chain will now be 2 um apart versus\n # the default 1 um\n >>> Chain(11).scale(2)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`:\n to specify Rydberg coupling\n - `...add_position(positions).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n scale = cast(scale)\n location_list = []\n for location_info in self.enumerate():\n x, y = location_info.position\n new_position = (scale * x, scale * y)\n location_list.append(\n LocationInfo.create(new_position, bool(location_info.filling.value))\n )\n\n return ListOfLocations(location_list)\n
calculate the coordinates of a cell in the lattice given the cell index.
Source code in src/bloqade/analog/ir/location/bravais.py
@beartype\ndef coordinates(self, index: List[int]) -> NDArray:\n \"\"\"calculate the coordinates of a cell in the lattice\n given the cell index.\n \"\"\"\n # damn! this is like stone age broadcasting\n vectors = np.array(self.cell_vectors())\n index = np.array(index)\n pos = np.sum(vectors.T * index, axis=1)\n return pos + np.array(self.cell_atoms())\n
calculate the coordinates of a cell in the lattice given the cell index.
Source code in src/bloqade/analog/ir/location/bravais.py
@beartype\ndef coordinates(self, index: List[int]) -> NDArray:\n \"\"\"calculate the coordinates of a cell in the lattice\n given the cell index.\n \"\"\"\n # damn! this is like stone age broadcasting\n vectors = np.array(self.cell_vectors())\n index = np.array(index)\n pos = np.sum(vectors.T * index, axis=1)\n return pos + np.array(self.cell_atoms())\n
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).
# single coordinate\n>>> reg = start.add_position((0,0))\n# you may chain add_position calls\n>>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n# you can add variables anywhere a value may be present\n>>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n# and specify your atom fillings\n>>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n[True, False])\n# alternatively you could use one boolean to specify\n# all coordinates should be empty/filled\n>>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n(5.2, 2.2)], False)\n
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
Source code in src/bloqade/analog/ir/location/location.py
def add_position(\n self,\n position: Union[\n PositionArray,\n List[Tuple[ScalarType, ScalarType]],\n Tuple[ScalarType, ScalarType],\n ],\n filling: Optional[Union[BoolArray, List[bool], bool]] = None,\n) -> \"ListOfLocations\":\n \"\"\"\n Add a position or multiple positions to a pre-existing geometry.\n\n `add_position` is capable of accepting:\n - A single tuple for one atom coordinate: `(1.0, 2.5)`\n - A list of tuples: `[(0.0, 1.0), (2.0,1.5), etc.]\n - A numpy array of shape (N, 2) where N is the number of atoms\n\n You may also intersperse variables anywhere a value may be present.\n\n You can also pass in an optional argument which determines the atom \"filling\"\n (whether or not at a specified coordinate an atom should be present).\n\n ### Usage Example:\n ```\n # single coordinate\n >>> reg = start.add_position((0,0))\n # you may chain add_position calls\n >>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n # you can add variables anywhere a value may be present\n >>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n # and specify your atom fillings\n >>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n [True, False])\n # alternatively you could use one boolean to specify\n # all coordinates should be empty/filled\n >>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n (5.2, 2.2)], False)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`: to specify Rydberg coupling\n - `...add_position(positions).hyperfine`: to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n if is_bearable(position, PositionArray) and is_bearable(\n filling, Optional[BoolArray]\n ):\n return self.add_position_ndarray(position, filling)\n elif is_bearable(position, List[Tuple[ScalarType, ScalarType]]) and is_bearable(\n filling, Optional[List[bool]]\n ):\n return self.add_position_list_tuples(position, filling)\n elif is_bearable(position, Tuple[ScalarType, ScalarType]) and is_bearable(\n filling, Optional[bool]\n ):\n return self.add_position_single_tupe(position, filling)\n else:\n raise TypeError(\"Invalid input types for add_position provided!\")\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_count(2, custom_rng)\n# you may also chain apply_defect_count calls\n>>> reg.apply_defect_count(2, custom_rng)\n# you can also use apply_defect_count on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_count(\n self, n_defects: int, rng: np.random.Generator = np.random.default_rng()\n):\n \"\"\"\n Drop `n_defects` atoms from the geometry randomly. Internally this occurs\n by setting certain sites to have a SiteFilling set to false indicating\n no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_count(2, custom_rng)\n # you may also chain apply_defect_count calls\n >>> reg.apply_defect_count(2, custom_rng)\n # you can also use apply_defect_count on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts)\n .apply_defect_count(n_defects)`: to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`: to specify\n Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n location_list = []\n for location_info in self.enumerate():\n location_list.append(location_info)\n\n filled_sites = []\n\n for index, location_info in enumerate(location_list):\n if location_info.filling is SiteFilling.filled:\n filled_sites.append(index)\n\n if n_defects >= len(filled_sites):\n raise ValueError(\n f\"n_defects {n_defects} must be less than the number of filled sites \"\n f\"({len(filled_sites)})\"\n )\n\n for _ in range(n_defects):\n index = rng.choice(filled_sites)\n location_list[index] = LocationInfo.create(\n location_list[index].position,\n (False if location_list[index].filling is SiteFilling.filled else True),\n )\n filled_sites.remove(index)\n\n return ListOfLocations(location_list)\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n# you may also chain apply_defect_density calls\n>>> reg.apply_defect_count(0.1, custom_rng)\n# you can also use apply_defect_density on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)])\n.apply_defect_density(0.5, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_density(\n self,\n defect_probability: float,\n rng: np.random.Generator = np.random.default_rng(),\n):\n \"\"\"\n Drop atoms randomly with `defect_probability` probability (range of 0 to 1).\n Internally this occurs by setting certain sites to have a SiteFilling\n set to false indicating no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n # you may also chain apply_defect_density calls\n >>> reg.apply_defect_count(0.1, custom_rng)\n # you can also use apply_defect_density on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)])\n .apply_defect_density(0.5, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`:\n to specify Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n p = min(1, max(0, defect_probability))\n location_list = []\n\n for location_info in self.enumerate():\n if rng.random() < p:\n location_list.append(\n LocationInfo.create(\n location_info.position,\n (\n False\n if location_info.filling is SiteFilling.filled\n else True\n ),\n )\n )\n else:\n location_list.append(location_info)\n\n return ListOfLocations(location_list=location_list)\n
Source code in src/bloqade/analog/ir/location/location.py
def figure(self, fig_kwargs=None, **assignments):\n \"\"\"obtain a figure object from the atom arrangement.\"\"\"\n return get_atom_arrangement_figure(self, fig_kwargs=fig_kwargs, **assignments)\n
the values to assign to the variables in the register.
{}
Returns:
Name Type Description NDArrayNDArray
the Rydberg interaction matrix in the lower triangular form.
Source code in src/bloqade/analog/ir/location/location.py
def rydberg_interaction(self, **assignments) -> NDArray:\n \"\"\"calculate the Rydberg interaction matrix.\n\n Args:\n **assignments: the values to assign to the variables in the register.\n\n Returns:\n NDArray: the Rydberg interaction matrix in the lower triangular form.\n\n \"\"\"\n\n from bloqade.analog.constants import RB_C6\n\n # calculate the Interaction matrix\n V_ij = np.zeros((self.n_sites, self.n_sites))\n for i, site_i in enumerate(self.enumerate()):\n pos_i = np.array([float(ele(**assignments)) for ele in site_i.position])\n\n for j, site_j in enumerate(self.enumerate()):\n if j >= i:\n break # enforce lower triangular form\n\n pos_j = np.array([float(ele(**assignments)) for ele in site_j.position])\n r_ij = np.linalg.norm(pos_i - pos_j)\n\n V_ij[i, j] = RB_C6 / r_ij**6\n\n return V_ij\n
>>> reg = start.add_position([(0,0), (1,1)])\n# atom positions are now (0,0), (2,2)\n>>> new_reg = reg.scale(2)\n# you may also use scale on pre-defined geometries\n>>> from bloqade.analog.atom_arrangement import Chain\n# atoms in the chain will now be 2 um apart versus\n# the default 1 um\n>>> Chain(11).scale(2)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef scale(self, scale: ScalarType):\n \"\"\"\n Scale the geometry of your atoms.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position([(0,0), (1,1)])\n # atom positions are now (0,0), (2,2)\n >>> new_reg = reg.scale(2)\n # you may also use scale on pre-defined geometries\n >>> from bloqade.analog.atom_arrangement import Chain\n # atoms in the chain will now be 2 um apart versus\n # the default 1 um\n >>> Chain(11).scale(2)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`:\n to specify Rydberg coupling\n - `...add_position(positions).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n scale = cast(scale)\n location_list = []\n for location_info in self.enumerate():\n x, y = location_info.position\n new_position = (scale * x, scale * y)\n location_list.append(\n LocationInfo.create(new_position, bool(location_info.filling.value))\n )\n\n return ListOfLocations(location_list)\n
which parameter set out of the batch to use. Default is 0. If there are no batch parameters, use 0.
*args: Any Specify the parameters that are defined in the .args([...]) build step.
Source code in src/bloqade/analog/ir/routine/base.py
def show(self: \"RoutineBase\", *args, batch_index: int = 0):\n \"\"\"Show an interactive plot of the routine.\n\n batch_index: int\n which parameter set out of the batch to use. Default is 0.\n If there are no batch parameters, use 0.\n\n *args: Any\n Specify the parameters that are defined in the `.args([...])` build step.\n\n \"\"\"\n if self.source is None:\n raise ValueError(\"Cannot show a routine without a source Builder.\")\n\n return self.source.show(*args, batch_id=batch_index)\n
Evolve an initial state vector using the Hamiltonian
Parameters:
Name Type Description Default stateOptional[StateVector]
The initial state vector to
Nonesolver_namestr
Which SciPy Solver to use. Defaults to
'dop853'atolfloat
Absolute tolerance for ODE solver. Defaults
1e-07rtolfloat
Relative tolerance for adaptive step in
1e-14nstepsint
Maximum number of steps allowed per integration
2147483647timesSequence[float]
The times to evaluate the state vector
()interaction_picturebool
Use the interaction picture when
False
Returns:
Type Description Iterator[StateVector]
Iterator[StateVector]: An iterator of the state vectors at each time step.
Source code in src/bloqade/analog/ir/routine/bloqade.py
def evolve(\n self,\n state: Optional[StateVector] = None,\n solver_name: str = \"dop853\",\n atol: float = 1e-7,\n rtol: float = 1e-14,\n nsteps: int = 2147483647,\n times: Sequence[float] = (),\n interaction_picture: bool = False,\n) -> Iterator[StateVector]:\n \"\"\"Evolve an initial state vector using the Hamiltonian\n\n Args:\n state (Optional[StateVector], optional): The initial state vector to\n evolve. if not provided, the zero state will be used. Defaults to None.\n solver_name (str, optional): Which SciPy Solver to use. Defaults to\n \"dop853\".\n atol (float, optional): Absolute tolerance for ODE solver. Defaults\n to 1e-14.\n rtol (float, optional): Relative tolerance for adaptive step in\n ODE solver. Defaults to 1e-7.\n nsteps (int, optional): Maximum number of steps allowed per integration\n step. Defaults to 2147483647.\n times (Sequence[float], optional): The times to evaluate the state vector\n at. Defaults to (). If not provided the state will be evaluated at\n the end of the bloqade program.\n interaction_picture (bool, optional): Use the interaction picture when\n solving schrodinger equation. Defaults to False.\n\n Returns:\n Iterator[StateVector]: An iterator of the state vectors at each time step.\n\n \"\"\"\n state = self.zero_state(np.complex128) if state is None else state\n\n U = AnalogGate(self.hamiltonian)\n\n return U.apply(\n state,\n times=times,\n solver_name=solver_name,\n atol=atol,\n rtol=rtol,\n nsteps=nsteps,\n interaction_picture=interaction_picture,\n )\n
Source code in src/bloqade/analog/ir/routine/bloqade.py
def fock_state(\n self, fock_state_str: str, dtype: np.dtype = np.float64\n) -> StateVector:\n \"\"\"Return the fock state for the given Hamiltonian.\"\"\"\n index = self.hamiltonian.space.fock_state_to_index(fock_state_str)\n data = np.zeros(self.hamiltonian.space.size, dtype=dtype)\n data[index] = 1\n return StateVector(data, self.hamiltonian.space)\n
Source code in src/bloqade/analog/ir/routine/bloqade.py
def zero_state(self, dtype: np.dtype = np.float64) -> StateVector:\n \"\"\"Return the zero state for the given Hamiltonian.\"\"\"\n return self.hamiltonian.space.zero_state(dtype)\n
Generates a list of BloqadeEmulation objects which contain the Hamiltonian of your program.
If you have a variable(s) in your program you have assigned multiple values via batch_assign() there will be multiple BloqadeEmulation objects, one for each value. On the other hand if the program only assumes a singular value per each variable, there will only be one BloqadeEmulation object but it will still be encapsulated in a list.
Parameters:
Name Type Description Default *argsLiteralType
If your program has a variable that was declared as run-time assignable via .args you may pass a value to it here. If there are multiple variables declared via .args the order in which you assign values to those variables through this argument should follow the order in which the declaration occurred.
()blockade_radiusfloat
The radius in which atoms blockade eachother. Default value is 0.0 micrometers.
0.0use_hyperfinebool
Should the Hamiltonian account for hyperfine levels. Default value is False.
Falsewaveform_runtimestr
Specify which runtime to use for waveforms. If \"numba\" is specify the waveform is compiled, otherwise it is interpreted via the \"interpret\" argument. Defaults to \"interpret\".
'interpret'cache_matricesbool
Speed up Hamiltonian generation by reusing data (when possible) from previously generated Hamiltonians. Default value is False.
False
Returns:
Type Description List[BloqadeEmulation]
List[BloqadeEmulation]
Source code in src/bloqade/analog/ir/routine/bloqade.py
def hamiltonian(\n self,\n *args: LiteralType,\n blockade_radius: float = 0.0,\n use_hyperfine: bool = False,\n waveform_runtime: str = \"interpret\",\n cache_matrices: bool = False,\n) -> List[BloqadeEmulation]:\n \"\"\"\n Generates a list of BloqadeEmulation objects which contain the Hamiltonian of your program.\n\n If you have a variable(s) in your program you have assigned multiple values via `batch_assign()`\n there will be multiple `BloqadeEmulation` objects, one for each value. On the other hand\n if the program only assumes a singular value per each variable, there will only be\n one `BloqadeEmulation` object but it will still be encapsulated in a list.\n\n\n Args:\n *args (LiteralType): If your program has a variable that was declared as run-time assignable\n via `.args` you may pass a value to it here. If there are multiple\n variables declared via `.args` the order in which you assign values to those variables\n through this argument should follow the order in which the declaration occurred.\n blockade_radius (float): The radius in which atoms blockade eachother. Default value is 0.0 micrometers.\n use_hyperfine (bool): Should the Hamiltonian account for hyperfine levels. Default value is False.\n waveform_runtime (str): Specify which runtime to use for waveforms. If \"numba\" is specify the waveform\n is compiled, otherwise it is interpreted via the \"interpret\" argument. Defaults to \"interpret\".\n cache_matrices (bool): Speed up Hamiltonian generation by reusing data (when possible) from previously generated Hamiltonians.\n Default value is False.\n\n Returns:\n List[BloqadeEmulation]\n\n \"\"\"\n ir_iter = self._generate_ir(\n args, blockade_radius, waveform_runtime, use_hyperfine\n )\n\n if cache_matrices:\n compile_cache = CompileCache()\n else:\n compile_cache = None\n\n return [\n BloqadeEmulation(task_data, compile_cache=compile_cache)\n for task_data in ir_iter\n ]\n
Run the current program using bloqade python backend
Parameters:
Name Type Description Default shotsint
number of shots after running state vector simulation
required argsTuple[LiteralType, ...]
The values for parameters defined
()nameOptional[str]
Name to give this run. Defaults to None.
Noneblockade_radiusfloat
Use the Blockade subspace given a
0.0waveform_runtimestr
(bool, optional): Use Numba to compile the waveforms,
'interpret'interaction_picturebool
Use the interaction picture when
Falsecache_matricesbool
Reuse previously evaluated matrcies when
Falsemultiprocessingbool
Use multiple processes to process the
Falsenum_workersOptional[int]
Number of processes to run with
Nonesolver_namestr
Which SciPy Solver to use. Defaults to
'dop853'atolfloat
Absolute tolerance for ODE solver. Defaults to
1e-07rtolfloat
Relative tolerance for adaptive step in ODE solver.
1e-14nstepsint
Maximum number of steps allowed per integration
2147483647
Raises:
Type Description ValueError
Cannot use multiprocessing and cache_matrices at the same time.
Returns:
Name Type Description LocalBatchLocalBatch
Batch of local tasks that have been executed.
Source code in src/bloqade/analog/ir/routine/bloqade.py
@beartype\ndef run(\n self,\n shots: int,\n args: Tuple[LiteralType, ...] = (),\n name: Optional[str] = None,\n blockade_radius: float = 0.0,\n waveform_runtime: str = \"interpret\",\n interaction_picture: bool = False,\n cache_matrices: bool = False,\n multiprocessing: bool = False,\n num_workers: Optional[int] = None,\n solver_name: str = \"dop853\",\n atol: float = 1e-7,\n rtol: float = 1e-14,\n nsteps: int = 2_147_483_647,\n) -> LocalBatch:\n \"\"\"Run the current program using bloqade python backend\n\n Args:\n shots (int): number of shots after running state vector simulation\n args (Tuple[LiteralType, ...], optional): The values for parameters defined\n in `args`. Defaults to ().\n name (Optional[str], optional): Name to give this run. Defaults to None.\n blockade_radius (float, optional): Use the Blockade subspace given a\n particular radius. Defaults to 0.0.\n waveform_runtime: (bool, optional): Use Numba to compile the waveforms,\n Defaults to False.\n interaction_picture (bool, optional): Use the interaction picture when\n solving schrodinger equation. Defaults to False.\n cache_matrices (bool, optional): Reuse previously evaluated matrcies when\n possible. Defaults to False.\n multiprocessing (bool, optional): Use multiple processes to process the\n batches. Defaults to False.\n num_workers (Optional[int], optional): Number of processes to run with\n multiprocessing. Defaults to None.\n solver_name (str, optional): Which SciPy Solver to use. Defaults to\n \"dop853\".\n atol (float, optional): Absolute tolerance for ODE solver. Defaults to\n 1e-14.\n rtol (float, optional): Relative tolerance for adaptive step in ODE solver.\n Defaults to 1e-7.\n nsteps (int, optional): Maximum number of steps allowed per integration\n step. Defaults to 2_147_483_647, the maximum value.\n\n Raises:\n ValueError: Cannot use multiprocessing and cache_matrices at the same time.\n\n Returns:\n LocalBatch: Batch of local tasks that have been executed.\n \"\"\"\n if multiprocessing and cache_matrices:\n raise ValueError(\n \"Cannot use multiprocessing and cache_matrices at the same time.\"\n )\n\n compile_options = dict(\n shots=shots,\n args=args,\n name=name,\n blockade_radius=blockade_radius,\n cache_matrices=cache_matrices,\n waveform_runtime=waveform_runtime,\n )\n\n solver_options = dict(\n multiprocessing=multiprocessing,\n num_workers=num_workers,\n solver_name=solver_name,\n atol=atol,\n rtol=rtol,\n nsteps=nsteps,\n interaction_picture=interaction_picture,\n )\n\n batch = self._compile(**compile_options)\n batch._run(**solver_options)\n\n return batch\n
Run state-vector simulation with a callback to access full state-vector from emulator
Parameters:
Name Type Description Default callbackCallable[[StateVector, Metadata, RydbergHamiltonian, Any], Any] required program_argsTuple[LiteralType, ...]
The values for parameters
()callback_argsTuple[Any, ...]
Extra arguments to pass into
()ignore_exceptionsbool
(bool, optional) If True any exception raised during
Falseblockade_radiusfloat
Use the Blockade subspace given a
0.0waveform_runtimestr
(str, optional): Specify which runtime to use for
'interpret'interaction_picturebool
Use the interaction picture when
Falsecache_matricesbool
Reuse previously evaluated matrcies when
Falsemultiprocessingbool
Use multiple processes to process the
Falsenum_workersOptional[int]
Number of processes to run with
Nonesolver_namestr
Which SciPy Solver to use. Defaults to
'dop853'atolfloat
Absolute tolerance for ODE solver. Defaults to
1e-07rtolfloat
Relative tolerance for adaptive step in ODE solver.
1e-14nstepsint
Maximum number of steps allowed per integration
2147483647
Returns:
Name Type Description ListList
List of resulting outputs from the callbacks
Raises:
Type Description RuntimeError
Raises the first error that occurs, only if
Note
For the callback function, first argument is the many-body wavefunction as a 1D complex numpy array, the second argument is of type Metadata which is a Named Tuple where the fields correspond to the parameters of that given task, RydbergHamiltonian is the object that contains the Hamiltonian used to generate the evolution for that task, Finally any optional positional arguments are allowed after that. The return value can be anything, the results will be collected in a list for each task in the batch.
Source code in src/bloqade/analog/ir/routine/bloqade.py
@beartype\ndef run_callback(\n self,\n callback: Callable[[StateVector, NamedTuple, RydbergHamiltonian, Any], Any],\n program_args: Tuple[LiteralType, ...] = (),\n callback_args: Tuple = (),\n ignore_exceptions: bool = False,\n blockade_radius: float = 0.0,\n waveform_runtime: str = \"interpret\",\n interaction_picture: bool = False,\n cache_matrices: bool = False,\n multiprocessing: bool = False,\n num_workers: Optional[int] = None,\n solver_name: str = \"dop853\",\n atol: float = 1e-7,\n rtol: float = 1e-14,\n nsteps: int = 2_147_483_647,\n use_hyperfine: bool = False,\n) -> List:\n \"\"\"Run state-vector simulation with a callback to access full state-vector from\n emulator\n\n Args:\n callback (Callable[[StateVector, Metadata, RydbergHamiltonian, Any], Any]):\n The callback function to run for each task in batch. See note below for more\n details about the signature of the function.\n program_args (Tuple[LiteralType, ...], optional): The values for parameters\n defined in `args`. Defaults to ().\n callback_args (Tuple[Any,...], optional): Extra arguments to pass into\n ignore_exceptions: (bool, optional) If `True` any exception raised during\n a task will be saved instead of the resulting output of the callback,\n otherwise the first exception by task number will be raised after *all*\n tasks have executed. Defaults to False.\n blockade_radius (float, optional): Use the Blockade subspace given a\n particular radius. Defaults to 0.0.\n waveform_runtime: (str, optional): Specify which runtime to use for\n waveforms. Defaults to \"interpret\".\n interaction_picture (bool, optional): Use the interaction picture when\n solving schrodinger equation. Defaults to False.\n cache_matrices (bool, optional): Reuse previously evaluated matrcies when\n possible. Defaults to False.\n multiprocessing (bool, optional): Use multiple processes to process the\n batches. Defaults to False.\n num_workers (Optional[int], optional): Number of processes to run with\n multiprocessing. Defaults to None.\n solver_name (str, optional): Which SciPy Solver to use. Defaults to\n \"dop853\".\n atol (float, optional): Absolute tolerance for ODE solver. Defaults to\n 1e-14.\n rtol (float, optional): Relative tolerance for adaptive step in ODE solver.\n Defaults to 1e-7.\n nsteps (int, optional): Maximum number of steps allowed per integration\n step. Defaults to 2_147_483_647, the maximum value.\n\n Returns:\n List: List of resulting outputs from the callbacks\n\n Raises:\n RuntimeError: Raises the first error that occurs, only if\n `ignore_exceptions=False`.\n\n Note:\n For the `callback` function, first argument is the many-body wavefunction\n as a 1D complex numpy array, the second argument is of type `Metadata` which\n is a Named Tuple where the fields correspond to the parameters of that given\n task, RydbergHamiltonian is the object that contains the Hamiltonian used to\n generate the evolution for that task, Finally any optional positional\n arguments are allowed after that. The return value can be anything, the\n results will be collected in a list for each task in the batch.\n\n\n \"\"\"\n if multiprocessing:\n from multiprocessing import Queue, Process, cpu_count\n else:\n from queue import Queue\n\n if cache_matrices:\n compile_cache = CompileCache()\n else:\n compile_cache = None\n\n solver_args = dict(\n solver_name=solver_name,\n atol=atol,\n rtol=rtol,\n nsteps=nsteps,\n interaction_picture=interaction_picture,\n )\n\n runner = self.EmuRunner(\n compile_cache=compile_cache,\n solver_args=solver_args,\n callback=callback,\n callback_args=callback_args,\n )\n\n tasks = Queue()\n results = Queue()\n\n total_tasks = 0\n ir_iter = self._generate_ir(\n program_args, blockade_radius, waveform_runtime, use_hyperfine\n )\n for task_data in ir_iter:\n task_number = task_data.task_id\n emulator_ir = task_data.emulator_ir\n metadata = task_data.metadata_dict\n total_tasks += 1\n tasks.put((task_number, (emulator_ir, metadata)))\n\n workers = []\n if multiprocessing:\n num_workers = max(int(num_workers or cpu_count()), 1)\n num_workers = min(total_tasks, num_workers)\n\n for _ in range(num_workers):\n worker = Process(\n target=BloqadePythonRoutine.process_tasks,\n args=(runner, tasks, results),\n )\n worker.start()\n\n workers.append(worker)\n else:\n self.process_tasks(runner, tasks, results)\n\n # blocks until all\n # results have been fetched\n # from the id_results Queue\n id_results = []\n for i in range(total_tasks):\n id_results.append(results.get())\n\n if workers:\n for worker in workers:\n worker.join()\n\n tasks.close()\n results.close()\n\n id_results.sort(key=lambda x: x[0])\n results = []\n\n for task_id, result in id_results:\n if not ignore_exceptions and isinstance(result, BaseException):\n try:\n raise result\n except BaseException:\n raise RuntimeError(\n f\"{result.__class__.__name__} occured during child process \"\n f\"running for task number {task_id}:\\n{traceback.format_exc()}\"\n )\n\n results.append(result)\n\n return results\n
Compile to a RemoteBatch, which contain Braket backend specific tasks, run_async to Braket, and wait until the results are coming back.
Note
This is sync, and will wait until remote results finished.
Parameters:
Name Type Description Default shotsint
number of shots
1argsLiteralType
additional arguments for args variables.
()namestr
custom name of the batch
Noneshufflebool
shuffle the order of jobs
False Return
RemoteBatch
Source code in src/bloqade/analog/ir/routine/braket.py
@beartype\ndef __call__(\n self,\n *args: LiteralType,\n shots: int = 1,\n name: Optional[str] = None,\n use_experimental: bool = False,\n shuffle: bool = False,\n **kwargs,\n):\n \"\"\"\n Compile to a RemoteBatch, which contain\n Braket backend specific tasks, run_async to Braket,\n and wait until the results are coming back.\n\n Note:\n This is sync, and will wait until remote results\n finished.\n\n Args:\n shots (int): number of shots\n args: additional arguments for args variables.\n name (str): custom name of the batch\n shuffle (bool): shuffle the order of jobs\n\n Return:\n RemoteBatch\n\n \"\"\"\n return self.run(shots, args, name, use_experimental, shuffle, **kwargs)\n
Compile to a RemoteBatch, which contain Braket backend specific tasks, run_async to Braket, and wait until the results are coming back.
Note
This is sync, and will wait until remote results finished.
Parameters:
Name Type Description Default shotsint
number of shots
required argsTuple
additional arguments
()namestr
custom name of the batch
Noneshufflebool
shuffle the order of jobs
False Return
RemoteBatch
Source code in src/bloqade/analog/ir/routine/braket.py
@beartype\ndef run(\n self,\n shots: int,\n args: Tuple[LiteralType, ...] = (),\n name: Optional[str] = None,\n use_experimental: bool = False,\n shuffle: bool = False,\n **kwargs,\n) -> RemoteBatch:\n \"\"\"\n Compile to a RemoteBatch, which contain\n Braket backend specific tasks, run_async to Braket,\n and wait until the results are coming back.\n\n Note:\n This is sync, and will wait until remote results\n finished.\n\n Args:\n shots (int): number of shots\n args (Tuple): additional arguments\n name (str): custom name of the batch\n shuffle (bool): shuffle the order of jobs\n\n Return:\n RemoteBatch\n\n \"\"\"\n\n batch = self.run_async(shots, args, name, use_experimental, shuffle, **kwargs)\n batch.pull()\n return batch\n
Compile to a RemoteBatch, which contain Braket backend specific tasks, and run_async to Braket.
Note
This is async.
Parameters:
Name Type Description Default shotsint
number of shots
required argsTuple
Values of the parameter defined in args, defaults to ()
()namestr | None
custom name of the batch, defaults to None
Noneuse_experimentalbool
Use experimental hardware capabilities
Falseshufflebool
shuffle the order of jobs
False Return
RemoteBatch
Source code in src/bloqade/analog/ir/routine/braket.py
@beartype\ndef run_async(\n self,\n shots: int,\n args: Tuple[LiteralType, ...] = (),\n name: Optional[str] = None,\n use_experimental: bool = False,\n shuffle: bool = False,\n **kwargs,\n) -> RemoteBatch:\n \"\"\"\n Compile to a RemoteBatch, which contain\n Braket backend specific tasks, and run_async to Braket.\n\n Note:\n This is async.\n\n Args:\n shots (int): number of shots\n args (Tuple): Values of the parameter defined in `args`, defaults to ()\n name (str | None): custom name of the batch, defaults to None\n use_experimental (bool): Use experimental hardware capabilities\n shuffle (bool): shuffle the order of jobs\n\n Return:\n RemoteBatch\n\n \"\"\"\n\n batch = self._compile(shots, use_experimental, args, name)\n batch._submit(shuffle, **kwargs)\n return batch\n
Compile to a LocalBatch, and run. The LocalBatch contain tasks to run on local emulator.
Note
This is sync, and will wait until remote results finished.
Parameters:
Name Type Description Default shotsint
number of shots
1argsLiteralType
additional arguments for args variables.
()multiprocessingbool
enable multi-process
Falsenum_workersint
number of workers to run the emulator
None Return
LocalBatch
Source code in src/bloqade/analog/ir/routine/braket.py
@beartype\ndef __call__(\n self,\n *args: LiteralType,\n shots: int = 1,\n name: Optional[str] = None,\n multiprocessing: bool = False,\n num_workers: Optional[int] = None,\n **kwargs,\n):\n \"\"\"\n Compile to a LocalBatch, and run.\n The LocalBatch contain tasks to run on local emulator.\n\n Note:\n This is sync, and will wait until remote results\n finished.\n\n Args:\n shots (int): number of shots\n args: additional arguments for args variables.\n multiprocessing (bool): enable multi-process\n num_workers (int): number of workers to run the emulator\n\n Return:\n LocalBatch\n\n \"\"\"\n return self.run(\n shots,\n args,\n name,\n multiprocessing=multiprocessing,\n num_workers=num_workers,\n **kwargs,\n )\n
Compile to a LocalBatch, and run. The LocalBatch contain tasks to run on local emulator.
Note
This is sync, and will wait until remote results finished.
Parameters:
Name Type Description Default shotsint
number of shots
required argsTuple[LiteralType, ...]
additional arguments for args variables.
()multiprocessingbool
enable multi-process
Falsenum_workersint
number of workers to run the emulator
None Return
LocalBatch
Source code in src/bloqade/analog/ir/routine/braket.py
@beartype\ndef run(\n self,\n shots: int,\n args: Tuple[LiteralType, ...] = (),\n name: Optional[str] = None,\n multiprocessing: bool = False,\n num_workers: Optional[int] = None,\n **kwargs,\n) -> LocalBatch:\n \"\"\"\n Compile to a LocalBatch, and run.\n The LocalBatch contain tasks to run on local emulator.\n\n Note:\n This is sync, and will wait until remote results\n finished.\n\n Args:\n shots (int): number of shots\n args: additional arguments for args variables.\n multiprocessing (bool): enable multi-process\n num_workers (int): number of workers to run the emulator\n\n Return:\n LocalBatch\n\n \"\"\"\n\n batch = self._compile(shots, args, name)\n batch._run(multiprocessing=multiprocessing, num_workers=num_workers, **kwargs)\n return batch\n
Source code in src/bloqade/analog/ir/routine/quera.py
def submit(\n self,\n shots: int,\n url: str,\n json_body_template: str,\n method: str = \"POST\",\n args: Tuple[LiteralType] = (),\n request_options: Dict[str, Any] = {},\n use_experimental: bool = False,\n sleep_time: float = 0.1,\n) -> List[Tuple[NamedTuple, Response]]:\n \"\"\"Compile to QuEraTaskSpecification and submit to a custom service.\n\n Args:\n shots (int): number of shots\n url (str): url of the custom service\n json_body_template (str): json body template, must contain '{task_ir}'\n which is a placeholder for a string representation of the task ir.\n The task ir string will be inserted into the template with\n `json_body_template.format(task_ir=task_ir_string)`.\n to be replaced by QuEraTaskSpecification\n method (str): http method to be used. Defaults to \"POST\".\n args (Tuple[LiteralType]): additional arguments to be passed into the\n compiler coming from `args` option of the build. Defaults to ().\n request_options: additional options to be passed into the request method,\n Note the `data` option will be overwritten by the\n `json_body_template.format(task_ir=task_ir_string)`.\n use_experimental (bool): Enable experimental hardware capabilities\n sleep_time (float): time to sleep between each request. Defaults to 0.1.\n\n Returns:\n List[Tuple[NamedTuple, Response]]: List of parameters for each batch in\n the task and the response from the post request.\n\n Examples:\n Here is a simple example of how to use this method. Note the body_template\n has double curly braces on the outside to escape the string formatting.\n\n ```python\n >>> body_template = \"{{\"token\": \"my_token\", \"task\": {task_ir}}}\"\n >>> responses = (\n program.quera.custom.submit(\n 100,\n \"http://my_custom_service.com\",\n body_template\n )\n )\n ```\n \"\"\"\n\n if r\"{task_ir}\" not in json_body_template:\n raise ValueError(r\"body_template must contain '{task_ir}'\")\n\n partial_eval = json_body_template.format(task_ir='\"task_ir\"')\n try:\n _ = json.loads(partial_eval)\n except json.JSONDecodeError as e:\n raise ValueError(\n \"body_template must be a valid json template. \"\n 'When evaluating template with task_ir=\"task_ir\", '\n f\"the template evaluated to: {partial_eval!r}.\\n\"\n f\"JSONDecodeError: {e}\"\n )\n\n out = []\n for metadata, task_ir in self._compile(shots, use_experimental, args):\n json_request_body = json_body_template.format(\n task_ir=task_ir.json(exclude_none=True, exclude_unset=True)\n )\n request_options.update(data=json_request_body)\n response = request(method, url, **request_options)\n out.append((metadata, response))\n time.sleep(sleep_time)\n\n return out\n
Gets the QuEraTaskStatusCode object for working with Bloqade SDK.
Parameters:
Name Type Description Default braket_statusstr
str The value of status in metadata() in the Amazon Braket. GetQuantumTask operation. If use_cached_value is True, the value most recently returned from GetQuantumTask operation is used
required
Returns:
Type Description QuEraTaskStatusCode
An object of the type Field in Braket SDK
Source code in src/bloqade/analog/submission/ir/braket.py
def from_braket_status_codes(braket_status: str) -> QuEraTaskStatusCode:\n \"\"\"Gets the `QuEraTaskStatusCode` object for working with Bloqade SDK.\n\n Args:\n braket_status: str\n The value of status in metadata() in the Amazon Braket.\n `GetQuantumTask` operation. If `use_cached_value` is `True`,\n the value most recently returned from\n `GetQuantumTask` operation is used\n\n Returns:\n An object of the type `Field` in Braket SDK\n \"\"\"\n if braket_status == str(\"QUEUED\"):\n return QuEraTaskStatusCode.Enqueued\n else:\n return QuEraTaskStatusCode(braket_status.lower().capitalize())\n
Get the QuEraTaskResults object for working with Bloqade SDK.
Parameters:
Name Type Description Default braket_task_resultsAnalogHamiltonianSimulationTaskResult
AnalogHamiltonianSimulationTaskResult Quantum task result of braket system
required
Returns:
Type Description QuEraTaskResults
An object of the type Field in Braket SDK.
Source code in src/bloqade/analog/submission/ir/braket.py
def from_braket_task_results(\n braket_task_results: AnalogHamiltonianSimulationTaskResult,\n) -> QuEraTaskResults:\n \"\"\"Get the `QuEraTaskResults` object for working with Bloqade SDK.\n\n Args:\n braket_task_results: AnalogHamiltonianSimulationTaskResult\n Quantum task result of braket system\n\n Returns:\n An object of the type `Field` in Braket SDK.\n \"\"\"\n shot_outputs = []\n for measurement in braket_task_results.measurements:\n shot_outputs.append(\n QuEraShotResult(\n shot_status=QuEraShotStatusCode.Completed,\n pre_sequence=list(measurement.pre_sequence),\n post_sequence=list(measurement.post_sequence),\n )\n )\n\n return QuEraTaskResults(\n task_status=QuEraTaskStatusCode.Completed, shot_outputs=shot_outputs\n )\n
Converts to TimeSeries object supported by Braket.
Parameters:
Name Type Description Default quera_fieldUnion[GlobalField, LocalField)]
Field supported by Quera
required
Returns:
Type Description Field
An object of the type braket.ahs.field.Field
Raises:
Type Description TypeError
If field is not of the type GlobalField or LocalField.
Source code in src/bloqade/analog/submission/ir/braket.py
def to_braket_field(quera_field: Union[GlobalField, LocalField]) -> Field:\n \"\"\"Converts to `TimeSeries` object supported by Braket.\n\n Args:\n quera_field (Union[GlobalField, LocalField)]:\n Field supported by Quera\n\n Returns:\n An object of the type `braket.ahs.field.Field`\n\n Raises:\n TypeError: If field is not of the type `GlobalField` or `LocalField`.\n \"\"\"\n if isinstance(quera_field, GlobalField):\n times = quera_field.times\n values = quera_field.values\n time_series = to_braket_time_series(times, values)\n return Field(pattern=\"uniform\", time_series=time_series)\n elif isinstance(quera_field, LocalField):\n times = quera_field.times\n values = quera_field.values\n pattern = quera_field.lattice_site_coefficients\n time_series = to_braket_time_series(times, values)\n pattern = Pattern(pattern)\n return Field(pattern=pattern, time_series=time_series)\n else:\n raise TypeError\n
Converts to Tuple[int, AnalogHamiltonianSimulation] object supported by Braket.
Parameters:
Name Type Description Default quera_task_irQuEraTaskSpecification
Quera IR(Intermediate representation) of the task.
required
Returns:
Type Description Tuple[int, AnalogHamiltonianSimulation]
An tuple of the type Tuple[int, AnalogHamiltonianSimulation].
Source code in src/bloqade/analog/submission/ir/braket.py
def to_braket_task(\n quera_task_ir: QuEraTaskSpecification,\n) -> Tuple[int, AnalogHamiltonianSimulation]:\n \"\"\"Converts to `Tuple[int, AnalogHamiltonianSimulation]` object supported by Braket.\n\n Args:\n quera_task_ir (QuEraTaskSpecification):\n Quera IR(Intermediate representation) of the task.\n\n Returns:\n An tuple of the type `Tuple[int, AnalogHamiltonianSimulation]`.\n \"\"\"\n braket_ahs_program = extract_braket_program(quera_task_ir)\n return quera_task_ir.nshots, braket_ahs_program\n
Converts quera IR(Intermendiate Representation) to to BraketTaskSpecification object.
Parameters:
Name Type Description Default quera_task_irQuEraTaskSpecification
Quera IR(Intermediate representation) of the task.
required
Returns:
Type Description BraketTaskSpecification
An object of the type BraketTaskSpecification in Braket SDK
Source code in src/bloqade/analog/submission/ir/braket.py
def to_braket_task_ir(quera_task_ir: QuEraTaskSpecification) -> BraketTaskSpecification:\n \"\"\"Converts quera IR(Intermendiate Representation) to\n to `BraketTaskSpecification` object.\n\n Args:\n quera_task_ir (QuEraTaskSpecification):\n Quera IR(Intermediate representation) of the task.\n\n Returns:\n An object of the type `BraketTaskSpecification` in Braket SDK\n\n \"\"\"\n nshots, braket_ahs_program = to_braket_task(quera_task_ir)\n return BraketTaskSpecification(nshots=nshots, program=braket_ahs_program.to_ir())\n
Converts to TimeSeries object supported by Braket.
Parameters:
Name Type Description Default timesList[Decimal]
Times of the value.
required valuesList[Decimal]
Corresponding values to add to the time series
required
Returns:
Type Description TimeSeries
An object of the type braket.timings.TimeSeries
Source code in src/bloqade/analog/submission/ir/braket.py
def to_braket_time_series(times: List[Decimal], values: List[Decimal]) -> TimeSeries:\n \"\"\"Converts to `TimeSeries` object supported by Braket.\n\n Args:\n times (List[Decimal]): Times of the value.\n values (List[Decimal]): Corresponding values to add to the time series\n\n Returns:\n An object of the type `braket.timings.TimeSeries`\n \"\"\"\n time_series = TimeSeries()\n for time, value in zip(times, values):\n time_series.put(time, value)\n\n return time_series\n
(tuple[int, int], Sequence[Tuple[int, int]]): cluster index to filter shots from. If none are provided all clusters are used, defaults to [].
[]
Returns:
Name Type Description bitstringslist of ndarray
list corresponding to each task in the report. Each element is an ndarray of shape (nshots, nsites) where nshots is the number of shots for the task and nsites is the number of sites in the task. For example:
Note that nshots may vary between tasks if filter_perfect_filling is set to True.
Source code in src/bloqade/analog/task/base.py
@beartype\ndef bitstrings(\n self,\n filter_perfect_filling: bool = True,\n clusters: Union[tuple[int, int], List[tuple[int, int]]] = [],\n) -> List[NDArray]:\n \"\"\"Get the bitstrings from the data.\n\n Args:\n filter_perfect_filling (bool): whether return will\n only contain perfect filling shots. Defaults to True.\n clusters: (tuple[int, int], Sequence[Tuple[int, int]]):\n cluster index to filter shots from. If none are provided\n all clusters are used, defaults to [].\n\n Returns:\n bitstrings (list of ndarray): list corresponding to each\n task in the report. Each element is an ndarray of shape\n (nshots, nsites) where nshots is the number of shots for\n the task and nsites is the number of sites in the task.\n For example:\n ```python3\n [array([[1, 1],\n [1, 1],\n [1, 1],\n ...,\n [1, 1],\n [1, 1],\n [1, 0]], dtype=int8)]\n ```\n\n Note:\n Note that nshots may vary between tasks if filter_perfect_filling\n is set to True.\n\n \"\"\"\n\n task_numbers = self.dataframe.index.get_level_values(\"task_number\").unique()\n\n bitstrings = []\n for task_number in task_numbers:\n mask = self._filter(\n task_number=task_number,\n filter_perfect_filling=filter_perfect_filling,\n clusters=clusters,\n )\n if np.any(mask):\n bitstrings.append(self.dataframe.loc[mask].to_numpy())\n else:\n bitstrings.append(\n np.zeros((0, self.dataframe.shape[1]), dtype=np.uint8)\n )\n\n return bitstrings\n
(tuple[int, int], Sequence[Tuple[int, int]]): cluster index to filter shots from. If none are provided all clusters are used, defaults to [].
[]
Returns:
Name Type Description countslist of OrderedDict[str, int]
list corresponding to each task in the report. Each element is an ndarray of shape (nshots, nsites) where nshots is the number of shots for the task and nsites is the number of sites in the task. For example:
Note that nshots may vary between tasks if filter_perfect_filling is set to True.
Source code in src/bloqade/analog/task/base.py
def counts(\n self,\n filter_perfect_filling: bool = True,\n clusters: Union[tuple[int, int], List[tuple[int, int]]] = [],\n) -> List[OrderedDict[str, int]]:\n \"\"\"Get the counts of unique bit strings.\n\n Args:\n filter_perfect_filling (bool): whether return will\n only contain perfect filling shots. Defaults to True.\n clusters: (tuple[int, int], Sequence[Tuple[int, int]]):\n cluster index to filter shots from. If none are provided\n all clusters are used, defaults to [].\n\n Returns:\n counts (list of OrderedDict[str, int]): list corresponding to each\n task in the report. Each element is an ndarray of shape\n (nshots, nsites) where nshots is the number of shots for\n the task and nsites is the number of sites in the task.\n For example:\n ```python\n [OrderedDict([('11', 892), ('10', 59), ('01', 49)])]\n ```\n\n Note:\n Note that nshots may vary between tasks if filter_perfect_filling\n is set to True.\n\n \"\"\"\n\n def _generate_counts(bitstring):\n output = np.unique(bitstring, axis=0, return_counts=True)\n\n count_list = [\n (\"\".join(map(str, bitstring)), int(count))\n for bitstring, count in zip(*output)\n ]\n count_list.sort(key=lambda x: x[1], reverse=True)\n count = OrderedDict(count_list)\n\n return count\n\n return list(\n map(_generate_counts, self.bitstrings(filter_perfect_filling, clusters))\n )\n
List the parameters associate with the given variable field_name for each tasks.
Parameters:
Name Type Description Default field_namestr
variable name
required Source code in src/bloqade/analog/task/base.py
def list_param(self, field_name: str) -> List[Union[Number, None]]:\n \"\"\"\n List the parameters associate with the given variable field_name\n for each tasks.\n\n Args:\n field_name (str): variable name\n\n \"\"\"\n\n def cast(x):\n if x is None:\n return None\n elif isinstance(x, (list, tuple, np.ndarray)):\n return list(map(cast, x))\n else:\n return float(x)\n\n return list(map(cast, (meta.get(field_name) for meta in self.metas)))\n
Create a Batch object that has tasks filtered based on the values of metadata.
Parameters:
Name Type Description Default __match_any__bool
if True, then a task will be included if it matches any of the metadata filters. If False, then a task will be included only if it matches all of the metadata filters. Defaults to False.
False**metadataMetadataFilterType
the metadata to filter on. The keys are the metadata names and the values (as a set) are the values to filter on. The elements in the set can be Real, Decimal, Tuple[Real], or Tuple[Decimal].
{} Return
type(self): a Batch object with the filtered tasks, either LocalBatch or RemoteBatch depending on the type of self
Source code in src/bloqade/analog/task/batch.py
@beartype\ndef filter_metadata(\n self, __match_any__: bool = False, **metadata: MetadataFilterType\n) -> Union[\"LocalBatch\", \"RemoteBatch\"]:\n \"\"\"Create a Batch object that has tasks filtered based on the\n values of metadata.\n\n Args:\n __match_any__: if True, then a task will be included if it\n matches any of the metadata filters. If False, then a\n task will be included only if it matches all of the\n metadata filters. Defaults to False.\n\n **metadata: the metadata to filter on. The keys are the metadata\n names and the values (as a set) are the values to filter on.\n The elements in the set can be Real, Decimal, Tuple[Real], or\n Tuple[Decimal].\n\n Return:\n type(self): a Batch object with the filtered tasks, either\n LocalBatch or RemoteBatch depending on the type of self\n\n \"\"\"\n\n def convert_to_decimal(element):\n if isinstance(element, list):\n return list(map(convert_to_decimal, element))\n elif isinstance(element, (Real, Decimal)):\n return Decimal(str(element))\n else:\n raise ValueError(\n f\"Invalid value {element} for metadata filter. \"\n \"Only Real, Decimal, List[Real], and List[Decimal] \"\n \"are supported.\"\n )\n\n def metadata_match_all(task):\n return all(\n task.metadata.get(key) in value for key, value in metadata.items()\n )\n\n def metadata_match_any(task):\n return any(\n task.metadata.get(key) in value for key, value in metadata.items()\n )\n\n metadata = {k: list(map(convert_to_decimal, v)) for k, v in metadata.items()}\n\n metadata_filter = metadata_match_any if __match_any__ else metadata_match_all\n\n new_tasks = OrderedDict(\n [(k, v) for k, v in self.tasks.items() if metadata_filter(v)]\n )\n\n kw = dict(self.__dict__)\n kw[\"tasks\"] = new_tasks\n\n return self.__class__(**kw)\n
If True, tasks are run in parallel using multiple processes. If False, tasks are run sequentially in a single process. Defaults to False.
Falsenum_workersOptional[int]
The maximum number of processes that can be used to execute the given calls if multiprocessing is True. If None, the number of workers will be the number of processors on the machine.
None**kwargs
Arbitrary keyword arguments passed to the task's run method.
{}
Raises:
Type Description ValueError
If num_workers is not None and multiprocessing is False.
Returns:
Name Type Description self
The instance of the batch with tasks run.
Source code in src/bloqade/analog/task/batch.py
def _run(\n self, multiprocessing: bool = False, num_workers: Optional[int] = None, **kwargs\n):\n \"\"\"\n Private method to run tasks in the batch.\n\n Args:\n multiprocessing (bool, optional): If True, tasks are run in parallel using multiple processes.\n If False, tasks are run sequentially in a single process. Defaults to False.\n num_workers (Optional[int], optional): The maximum number of processes that can be used to\n execute the given calls if multiprocessing is True. If None, the number of workers will be the number of processors on the machine.\n **kwargs: Arbitrary keyword arguments passed to the task's run method.\n\n Raises:\n ValueError: If num_workers is not None and multiprocessing is False.\n\n Returns:\n self: The instance of the batch with tasks run.\n \"\"\"\n if multiprocessing:\n from concurrent.futures import ProcessPoolExecutor as Pool\n\n with Pool(max_workers=num_workers) as pool:\n futures = OrderedDict()\n for task_number, task in enumerate(self.tasks.values()):\n futures[task_number] = pool.submit(task.run, **kwargs)\n\n for task_number, future in futures.items():\n self.tasks[task_number] = future.result()\n\n else:\n if num_workers is not None:\n raise ValueError(\n \"num_workers is only used when multiprocessing is enabled.\"\n )\n for task in self.tasks.values():\n task.run(**kwargs)\n\n return self\n
Private method to submit tasks in the RemoteBatch.
Parameters:
Name Type Description Default shuffle_submit_orderbool
If True, tasks are submitted in a random order. If False, tasks are submitted in the order they were added to the batch. Defaults to True.
Trueignore_submission_errorbool
If True, submission errors are ignored and the method continues to submit the remaining tasks. If False, the method stops at the first submission error. Defaults to False.
False**kwargs
Arbitrary keyword arguments.
{}
Returns:
Name Type Description RemoteBatchRemoteBatch
The RemoteBatch instance with tasks submitted.
Source code in src/bloqade/analog/task/batch.py
def _submit(\n self, shuffle_submit_order: bool = True, ignore_submission_error=False, **kwargs\n) -> \"RemoteBatch\":\n \"\"\"\n Private method to submit tasks in the RemoteBatch.\n\n Args:\n shuffle_submit_order (bool, optional): If True, tasks are submitted in a random order.\n If False, tasks are submitted in the order they were added to the batch. Defaults to True.\n ignore_submission_error (bool, optional): If True, submission errors are ignored and the method continues to submit the remaining tasks.\n If False, the method stops at the first submission error. Defaults to False.\n **kwargs: Arbitrary keyword arguments.\n\n Returns:\n RemoteBatch: The RemoteBatch instance with tasks submitted.\n \"\"\"\n from bloqade.analog import save\n\n # online, non-blocking\n if shuffle_submit_order:\n submission_order = np.random.permutation(list(self.tasks.keys()))\n else:\n submission_order = list(self.tasks.keys())\n\n # submit tasks in random order but store them\n # in the original order of tasks.\n # futures = OrderedDict()\n\n ## upon submit() should validate for Both backends\n ## and throw errors when fail.\n errors = BatchErrors()\n shuffled_tasks = OrderedDict()\n for task_index in submission_order:\n task = self.tasks[task_index]\n shuffled_tasks[task_index] = task\n try:\n task.submit(**kwargs)\n except BaseException as error:\n # record the error in the error dict\n errors.task_errors[int(task_index)] = TaskError(\n exception_type=error.__class__.__name__,\n stack_trace=traceback.format_exc(),\n )\n\n task.task_result_ir = QuEraTaskResults(\n task_status=QuEraTaskStatusCode.Unaccepted\n )\n\n self.tasks = shuffled_tasks # permute order using dump way\n\n if len(errors.task_errors) > 0:\n time_stamp = datetime.datetime.now()\n\n if \"win\" in sys.platform:\n time_stamp = str(time_stamp).replace(\":\", \"~\")\n\n if self.name:\n future_file = f\"{self.name}-partial-batch-future-{time_stamp}.json\"\n error_file = f\"{self.name}-partial-batch-errors-{time_stamp}.json\"\n else:\n future_file = f\"partial-batch-future-{time_stamp}.json\"\n error_file = f\"partial-batch-errors-{time_stamp}.json\"\n\n cwd = os.getcwd()\n # cloud_batch_result.save_json(future_file, indent=2)\n # saving ?\n\n save(errors, error_file)\n save(self, future_file)\n\n if ignore_submission_error:\n warnings.warn(\n \"One or more error(s) occured during submission, please see \"\n \"the following files for more information:\\n\"\n f\" - {os.path.join(cwd, future_file)}\\n\"\n f\" - {os.path.join(cwd, error_file)}\\n\",\n RuntimeWarning,\n )\n else:\n raise RemoteBatch.SubmissionException(\n str(errors)\n + \"\\n\"\n + \"One or more error(s) occured during submission, please see \"\n \"the following files for more information:\\n\"\n f\" - {os.path.join(cwd, future_file)}\\n\"\n f\" - {os.path.join(cwd, error_file)}\\n\"\n )\n\n else:\n # TODO: think about if we should automatically save successful submissions\n # as well.\n pass\n
def cancel(self) -> \"RemoteBatch\":\n \"\"\"\n Cancel all the tasks in the Batch.\n\n Return:\n self\n\n \"\"\"\n # cancel all jobs\n for task in self.tasks.values():\n task.cancel()\n\n return self\n
Fetching will update the status of tasks, and only pull the results for those tasks that have completed.
Return
self
Source code in src/bloqade/analog/task/batch.py
def fetch(self) -> \"RemoteBatch\":\n \"\"\"\n Fetch the tasks in the Batch.\n\n Note:\n Fetching will update the status of tasks,\n and only pull the results for those tasks\n that have completed.\n\n Return:\n self\n\n \"\"\"\n # online, non-blocking\n # pull the results only when its ready\n for task in self.tasks.values():\n task.fetch()\n\n return self\n
Create a RemoteBatch object that contain failed tasks from current Batch.
failed tasks with following status codes:
Failed
Unaccepted
Return
RemoteBatch
Source code in src/bloqade/analog/task/batch.py
def get_failed_tasks(self) -> \"RemoteBatch\":\n \"\"\"\n Create a RemoteBatch object that\n contain failed tasks from current Batch.\n\n failed tasks with following status codes:\n\n 1. Failed\n 2. Unaccepted\n\n Return:\n RemoteBatch\n\n \"\"\"\n # statuses that are in a state that are\n # completed because of an error\n statuses = [\"Failed\", \"Unaccepted\"]\n return self.get_tasks(*statuses)\n
Create a RemoteBatch object that contain finished tasks from current Batch.
Tasks consider finished with following status codes:
Failed
Unaccepted
Completed
Partial
Cancelled
Return
RemoteBatch
Source code in src/bloqade/analog/task/batch.py
def get_finished_tasks(self) -> \"RemoteBatch\":\n \"\"\"\n Create a RemoteBatch object that\n contain finished tasks from current Batch.\n\n Tasks consider finished with following status codes:\n\n 1. Failed\n 2. Unaccepted\n 3. Completed\n 4. Partial\n 5. Cancelled\n\n Return:\n RemoteBatch\n\n \"\"\"\n # statuses that are in a state that will\n # not run going forward for any reason\n statuses = [\"Completed\", \"Failed\", \"Unaccepted\", \"Partial\", \"Cancelled\"]\n return self.get_tasks(*statuses)\n
@beartype\ndef get_tasks(self, *status_codes: str) -> \"RemoteBatch\":\n \"\"\"\n Get Tasks with specify status_codes.\n\n Return:\n RemoteBatch\n\n \"\"\"\n # offline:\n st_codes = [QuEraTaskStatusCode(x) for x in status_codes]\n\n new_task_results = OrderedDict()\n for task_number, task in self.tasks.items():\n if task.task_result_ir.task_status in st_codes:\n new_task_results[task_number] = task\n\n return RemoteBatch(self.source, new_task_results, name=self.name)\n
Pulling will pull the results for the tasks. If a given task(s) has not been completed, wait until it finished.
Return
self
Source code in src/bloqade/analog/task/batch.py
def pull(self) -> \"RemoteBatch\":\n \"\"\"\n Pull results of the tasks in the Batch.\n\n Note:\n Pulling will pull the results for the tasks.\n If a given task(s) has not been completed, wait\n until it finished.\n\n Return:\n self\n \"\"\"\n # online, blocking\n # pull the results. if its not ready, hanging\n for task in self.tasks.values():\n task.pull()\n\n return self\n
Create a RemoteBatch object that contain tasks from current Batch, with failed tasks removed.
failed tasks with following status codes:
Failed
Unaccepted
Return
RemoteBatch
Source code in src/bloqade/analog/task/batch.py
def remove_failed_tasks(self) -> \"RemoteBatch\":\n \"\"\"\n Create a RemoteBatch object that\n contain tasks from current Batch,\n with failed tasks removed.\n\n failed tasks with following status codes:\n\n 1. Failed\n 2. Unaccepted\n\n Return:\n RemoteBatch\n\n \"\"\"\n # statuses that are in a state that will\n # not run going forward because of an error\n statuses = [\"Failed\", \"Unaccepted\"]\n return self.remove_tasks(*statuses)\n
Create a RemoteBatch object that contain tasks from current Batch, with all Unaccepted tasks removed.
Return
RemoteBatch
Source code in src/bloqade/analog/task/batch.py
def remove_invalid_tasks(self) -> \"RemoteBatch\":\n \"\"\"\n Create a RemoteBatch object that\n contain tasks from current Batch,\n with all Unaccepted tasks removed.\n\n Return:\n RemoteBatch\n\n \"\"\"\n return self.remove_tasks(\"Unaccepted\")\n
Retrieve will update the status of tasks, and only pull the results for those tasks that have completed.
Return
self
Source code in src/bloqade/analog/task/batch.py
def retrieve(self) -> \"RemoteBatch\":\n \"\"\"Retrieve missing task results.\n\n Note:\n Retrieve will update the status of tasks,\n and only pull the results for those tasks\n that have completed.\n\n Return:\n self\n\n \"\"\"\n # partially online, sometimes blocking\n # pull the results for tasks that have\n # not been pulled already.\n for task in self.tasks.values():\n if not task._result_exists():\n task.pull()\n\n return self\n
dataframe with [\"task id\", \"status\", \"shots\"]
Source code in src/bloqade/analog/task/batch.py
def tasks_metric(self) -> pd.DataFrame:\n \"\"\"\n Get current tasks status metric\n\n Return:\n dataframe with [\"task id\", \"status\", \"shots\"]\n\n \"\"\"\n # [TODO] more info on current status\n # offline, non-blocking\n tid = []\n data = []\n for int, task in self.tasks.items():\n tid.append(int)\n\n dat = [None, None, None]\n dat[0] = task.task_id\n if task.task_result_ir is not None:\n dat[1] = task.task_result_ir.task_status.name\n dat[2] = task.task_ir.nshots\n data.append(dat)\n\n return pd.DataFrame(data, index=tid, columns=[\"task ID\", \"status\", \"shots\"])\n
def json(self, **options) -> str:\n \"\"\"\n Serialize the object to JSON string.\n\n Return:\n JSON string\n\n \"\"\"\n from bloqade.analog import dumps\n\n return dumps(self, **options)\n
"},{"location":"reference/bloqade/analog/task/bloqade/","title":"Bloqade","text":""},{"location":"reference/bloqade/analog/task/braket/","title":"Braket","text":""},{"location":"reference/bloqade/analog/task/braket_simulator/","title":"Braket simulator","text":""},{"location":"reference/bloqade/analog/task/quera/","title":"Quera","text":""}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-,:!=\\[\\: )\"`/]+|\\.(?!\\d)|&[lg]t;|(?!\\b)(?=[A-Z][a-z])","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Index","text":"
[!IMPORTANT]
Bloqade has been restructured to make room for new features and improvements. Please refer to the migration guide for more information.
"},{"location":"#welcome-to-bloqade-queras-neutral-atom-sdk","title":"Welcome to Bloqade: QuEra's Neutral Atom SDK","text":""},{"location":"#what-is-bloqade","title":"What is Bloqade?","text":"
Bloqade is a Python SDK for QuEra's neutral atom quantum computer Aquila (check out our paper!). It's designed to make writing and analyzing the results of analog quantum programs on Aquila as easy as possible. It features custom atom geometries and flexible waveform definitions in both emulation and real hardware. Bloqade interfaces with the AWS Braket cloud service where Aquila is hosted, enabling you to submit programs as well as retrieve and analyze real hardware results all-in-one.
You can install the package with pip in your Python environment of choice via:
pip install bloqade\n
"},{"location":"#a-glimpse-of-bloqade","title":"A Glimpse of Bloqade","text":"
Let's try a simple example where we drive a Rabi oscillation on a single neutral atom. Don't worry if you're unfamiliar with neutral atom physics, (you can check out our Background for more information!) the goal here is to just give you a taste of what Bloqade can do.
We start by defining where our atoms go, otherwise known as the atom geometry. In this particular example we will use a small Honeycomb lattice:
from bloqade.analog.atom_arrangement import Honeycomb\n\ngeometry = Honeycomb(2, lattice_spacing = 10.0)\n
We can verify what the atom geometry looks like by .show()'ing it:
geometry.show()\n
We now define what the time evolution looks like using a pulse sequence. The pulse sequence here is the time profile of the Rabi Drive targeting the ground-Rydberg two level transition, which causes the Rabi oscillations. We choose a constant waveform with a value of \\(\\frac{\\pi}{2} \\text{rad}/\\text{us}\\) and a duration of \\(1.0 \\,\\text{us}\\). This produces a \\(\\frac{\\pi}{2}\\) rotation on the Bloch sphere meaning our final measurements should be split 50/50 between the ground and Rydberg state.
from math import pi\nrabi_program = (\n geometry\n .rydberg.rabi.amplitude.uniform\n .constant(value=pi/2, duration=1.0)\n)\n
Here rabi.amplitude means exactly what it is, the Rabi amplitude term of the Hamiltonian. uniform refers to applying the waveform uniformly across all the atom locations.
We can visualize what our program looks like again with .show():
We can now run the program through Bloqade's built-in emulator to get some results. We designate that we want the program to be run and measurements performed 100 times:
With the results we can generate a report object that contains a number of methods for analyzing our data, including the number of counts per unique bitstring:
If we want to submit our program to hardware we'll need to adjust the waveform as there is a constraint the Rabi amplitude waveform must start and end at zero. This is easy to do as we can build off the atom geometry we saved previously but apply a piecewise linear waveform:
Now instead of using the built-in Bloqade emulator we submit the program to Aquila. You will need to use the AWS CLI to obtain credentials from your AWS account or set the proper environment variables before hand.
.run_async is a non-blocking version of the standard .run method, allowing you to continue work while waiting for results from Aquila. .run_async immediately returns an object you can query for the status of your tasks in the queue as well.
You can do the exact same analysis you do on emulation results with hardware results too:
"},{"location":"#contributing-to-bloqade","title":"Contributing to Bloqade","text":"
Bloqade is released under the Apache License, Version 2.0. If you'd like the chance to shape the future of neutral atom quantum computation, see our Contributing Guide for more info!
Thank you for your interest in contributing to the project! We welcome all contributions. There are many different ways to contribute to Bloqade, and we are always looking for more help. We accept contributions in the form of bug reports, feature requests, documentation improvements, and code contributions. For more information about how to contribute, please read the following sections.
"},{"location":"contributing/#table-of-contents","title":"Table of Contents","text":"
Reporting a Bug
Reporting Documentation Issues
Feature Requests
Developing Bloqade
Design Philosophy and Architecture
Community Slack
Ask a Question
Providing Feedback
"},{"location":"contributing/asking-a-question/","title":"Ask a Question","text":"
If you're interested in contributing to Bloqade, or just want to discuss the project, join the discussion on GitHub Discussions at https://github.com/QuEraComputing/bloqade-analog/discussions
"},{"location":"contributing/code-of-conduct/","title":"Design Philosophy and Architecture","text":"
Given the heterogeneous nature of the hardware we target, We have decided to use a compiler-based approach to our software stack, allowing us to target different hardware backends with the same high-level language. Below is a diagram of the software stack in Bloqade.
When programming Bloqade using the Python API, the user constructs a representation of an analog quantum circuit. This representation is a flattened version of the actual analog circuit. Flattened means that the user input is a linear sequence of operations where the context of neighboring nodes in the sequence of instructions can determine the program tree structure. The Bloqade AST describes the actual analog circuit.
The Bloqade AST is a representation of a quantum analog circuit for neutral atom computing. It is a directed acyclic graph (DAG) with nodes for different hierarchical levels of the circuit. The base node is the AnalogCircuit which contains the geometry of the atoms stored as a AtomArragment or ParallelRegister objects. The other part of the circuit is the Sequence, which contains the waveforms that describe the drives for the Ryberg/Hyperfine transitions of each Rydberg atom. Each transition is represented by a Pulse including a Field for the drive's detuning, Rabi amplitude, and Rabi phase . A Field relates the spatial and temporal dependence of a drive. The spatial modulates the temporal dependence of the waveform. A DAG also describes the Waveform object. Finally, we have basic Scalar expressions as well for describing the syntax of real-valued continuous numbers.
"},{"location":"contributing/code-of-conduct/#bloqade-compilers-and-transpilers","title":"Bloqade Compilers and Transpilers","text":"
Given a user program expressed as the Bloqade AST, we can target various backends by transforming from the Bloqade AST to other kinds of IR. For example, when submitting a task to QuEra's hardware, we transform the Bloqade AST to the IR that describes a valid program for the hardware.
This process is referred to as lowering, which in a general sense is a transformation that takes you from one IR to another where the target IR is specialized or has a smaller syntactical structure. Transpiling corresponds to a transformation that takes you from one language to equivalent expressions in another. For example, we can transpile from the Bloqade AST in Python to the Bloqade AST in Julia. The generic term for both of these types of transformation in Bloqade is Code Generation. You will find various code generation implementations in various codegen modules.
You can join QuEra's Slack workspace with this link. Join the #bloqade channel to discuss anything related to Bloqade.
"},{"location":"contributing/design-philosophy-and-architecture/","title":"Design Philosophy and Architecture","text":"
Given the heterogeneous nature of the hardware we target, We have decided to use a compiler-based approach to our software stack, allowing us to target different hardware backends with the same high-level language. Below is a diagram of the software stack in Bloqade.
When programming Bloqade using the Python API, the user constructs a representation of an analog quantum circuit. This representation is a flattened version of the actual analog circuit. Flattened means that the user input is a linear sequence of operations where the context of neighboring nodes in the sequence of instructions can determine the program tree structure. The Bloqade AST describes the actual analog circuit.
The Bloqade AST is a representation of a quantum analog circuit for neutral atom computing. It is a directed acyclic graph (DAG) with nodes for different hierarchical levels of the circuit. The base node is the AnalogCircuit which contains the geometry of the atoms stored as a AtomArragment or ParallelRegister objects. The other part of the circuit is the Sequence, which contains the waveforms that describe the drives for the Ryberg/Hyperfine transitions of each Rydberg atom. Each transition is represented by a Pulse including a Field for the drive's detuning, Rabi amplitude, and Rabi phase . A Field relates the spatial and temporal dependence of a drive. The spatial modulates the temporal dependence of the waveform. A DAG also describes the Waveform object. Finally, we have basic Scalar expressions as well for describing the syntax of real-valued continuous numbers.
"},{"location":"contributing/design-philosophy-and-architecture/#bloqade-compilers-and-transpilers","title":"Bloqade Compilers and Transpilers","text":"
Given a user program expressed as the Bloqade AST, we can target various backends by transforming from the Bloqade AST to other kinds of IR. For example, when submitting a task to QuEra's hardware, we transform the Bloqade AST to the IR that describes a valid program for the hardware.
This process is referred to as lowering, which in a general sense is a transformation that takes you from one IR to another where the target IR is specialized or has a smaller syntactical structure. Transpiling corresponds to a transformation that takes you from one language to equivalent expressions in another. For example, we can transpile from the Bloqade AST in Python to the Bloqade AST in Julia. The generic term for both of these types of transformation in Bloqade is Code Generation. You will find various code generation implementations in various codegen modules.
"},{"location":"contributing/developing-bloqade/","title":"Setting up your Development Environment","text":"
Before You Get Started
Depending on the complexity of the contribution you'd like to make to Bloqade, it may be worth reading the Design Philosophy and Architecture section to get an idea of why Bloqade is structured the way that it is and how to make your contribution adhere to this philosophy.
Our development environment contains a set of tools we use for development, testing, and documentation. This section describes how to set up the development environment. We primarily use pdm to manage python environments and dependencies.
"},{"location":"contributing/developing-bloqade/#setting-up-python","title":"Setting up Python","text":"
We use pdm to manage dependencies and virtual environment. After cloning the repository, run the following command to install dependencies:
We primarily use ruff - an extremely fast linter for Python, and black as formatter. These have been configured into pre-commit hooks. After installing pre-commit on your own system, you can install pre-commit hooks to git via
pre-commit install\n
"},{"location":"contributing/documentation-issues/","title":"Reporting a Documentation Issue","text":"
We are always looking to improve our documentation. If you find a typo or think something is unclear, please open an issue on our GitHub page: https://github.com/QuEraComputing/bloqade-analog/issues
For typos or other minor problems, create an issue that contains a link to the specific page that includes the problem, along with a description of the problem and possibly a solution.
For a request for new documentation content, please open up an issue and describe what you think is missing from the documentation.
"},{"location":"contributing/feature-requests/","title":"Requesting new Features","text":"
Given that we are currently at the beginning of the development of the Bloqade python interface, we are open to suggestions about what features would be helpful to include in future package iterations. If you have a request for a new feature, please open an issue on our GitHub page: https://github.com/QuEraComputing/bloqade-analog/issues
We ask that the feature requests be as specific as possible. Please include the following information in your feature request:
A short, descriptive title.
A detailed description of the feature, including your attempt to solve the problem with the current version of Bloqade.
A minimal code example that demonstrates the need for the feature.
While Github Issues are a great way for us to better understand any issues your having with Bloqade as well as provide us with feature requests, we're always looking for ways to collect more general feedback about what the user experience with Bloqade is like.
To do that we have this form where you can provide your thoughts after using/experimenting/tinkering/hacking with Bloqade.
Your feedback will help guide the future of Bloqade's design so be honest and know that you're contributing to the future of Quantum Computing with Neutral Atoms!
"},{"location":"contributing/reporting-a-bug/","title":"Reporting a Bug","text":"
Bloqade is currently in the alpha phase of development, meaning bugs most likely exist in the current implementation. We are continuously striving to improve the stability of Bloqade. As such, we encourage our users to report all bugs they find. To do this, we ask you to submit an issue to our GitHub page: https://github.com/QuEraComputing/bloqade-analog/issues
Please include the following information in your bug report:
A short, descriptive title.
A detailed description of the bug, including the expected behavior and what happened.
A minimal code example that reproduces the bug.
The version of Bloqade you are using.
The version of Python you are using.
The version of your operating system.
"},{"location":"home/background/","title":"Background","text":""},{"location":"home/background/#neutral-atom-qubits","title":"Neutral Atom Qubits","text":"
The qubits that QuEra's neutral atom computer Aquila and Bloqade are designed to emulate are based on neutral atoms. As the name implies they are atoms that are neutrally charged but are also capable of achieving a Rydberg state where a single electron can be excited to an incredibly high energy level without ionizing the atom.
This incredibly excited electron energy level \\(|r\\rangle\\) and its default ground state \\(|g\\rangle\\) create a two-level system where superposition can occur. For enabling interaction between two or more qubits and achieving entanglement, when the neutral atoms are in the Rydberg state a phenomenon known as the Rydberg blockade can occur where an atom in the Rydberg state prevents a neighboring atom from also being excited to the same state.
For a more nuanced and in-depth read about the neutral atoms that Bloqade and Aquila use, refer to QuEra's qBook section on Qubits by puffing up atoms.
"},{"location":"home/background/#analog-vs-digital-quantum-computing","title":"Analog vs Digital Quantum Computing","text":"
There are two modes of quantum computation that neutral atoms are capable of: Analog and Digital.
You can find a brief explanation of the distinction below but for a more in-depth explanation you can refer to QuEra's qBook section on Analog vs Digital Quantum Computing
In the analog mode (supported by Bloqade and Aquila) you control your computation through the parameters of a time-dependent Hamiltonian that influences all the qubits at once. There are options for local control of the Hamiltonian on certain qubits however.
In the Digital Mode individual or multiple groups of qubits are controlled by applying gates (individual unitary operations). For neutral atoms, this digital mode can be accomplished with the introduction of hyperfine coupling, enabling a quantum state to be stored for long periods of time while also allowing for multi-qubit gates.
where: \\(\\Omega_j\\), \\(\\phi_j\\), and \\(\\Delta_j\\) denote the Rabi frequency amplitude, laser phase, and the detuning of the driving laser field on atom (qubit) \\(j\\) coupling the two states \\(| g_j \\rangle\\) (ground state) and \\(| r_j \\rangle\\) (Rydberg state); \\(\\hat{n}_j = |r_j\\rangle \\langle r_j|\\) is the number operator, and \\(V_{jk} = C_6/|\\mathbf{x}_j - \\mathbf{x}_k|^6\\) describes the Rydberg interaction (van der Waals interaction) between atoms \\(j\\) and \\(k\\) where \\(\\mathbf{x}_j\\) denotes the position of the atom \\(j\\); \\(C_6\\) is the Rydberg interaction constant that depends on the particular Rydberg state used. For Bloqade, the default \\(C_6 = 862690 \\times 2\\pi \\text{ MHz \u03bcm}^6\\) for \\(|r \\rangle = \\lvert 70S_{1/2} \\rangle\\) of the \\(^{87}\\)Rb atoms; \\(\\hbar\\) is the reduced Planck's constant.
The Rydberg Many-Body Hamiltonian already implies from its subscripts that you can also have local control over your atoms. In Bloqade this local control extends to any term in the Hamiltonian while on Aquila this is currently restricted to the \\(\\Delta_j\\) laser detuning term.
Fields in Bloqade give you local (single-atom) control over the many-body Rydberg Hamiltonian.
They are a sum of one or more spatial modulations, which allows you to scale the amplitude of the waveform across the different sites in the system:
"},{"location":"home/gotchas/","title":"Bloqade Gotchas: Common Mistakes in Using Bloqade","text":"
It is tempting when coming from different quantum SDKs and frameworks to apply the same pattern of thought to Bloqade. However, a lot of practices from those prior tools end up being anti-patterns in Bloqade. While you can use those patterns and they can still work, it ends up causing you the developer to write unnecessarily verbose, complex, and hard-to-read code as well as preventing you from reaping the full benefits of what Bloqade has to offer.
This page is dedicated to cataloguing those anti-patterns and what you can do instead to maximize the benefit Bloqade can offer you.
"},{"location":"home/gotchas/#redefining-lattices-and-common-atom-arrangements","title":"Redefining Lattices and Common Atom Arrangements","text":"
You might be tempted to define lattice-based geometries through the following means:
from bloqade import start\n\nspacing = 4.0\ngeometry = start.add_positions(\n [(i * spacing, j * spacing) for i in range(4) for j in range(4)]\n)\n
This is quite redundant and verbose, especially considering Bloqade offers a large number of pre-defined lattices you can customize the spacing of in bloqade.atom_arrangement. In the code above, we're just defining a 4x4 square lattice of atoms with 4.0 micrometers of spacing between them. This can be expressed as follows
"},{"location":"home/gotchas/#copying-a-program-to-create-new-ones","title":"Copying a Program to create New Ones","text":"
Many gate-based SDKs rely on having a mutable object representing your circuit. This means if you want to build on top of some base circuit without mutating it, you have to copy it:
import copy\n\nbase_circuit = qubits.x(0)....\n# make copy of base circuit\ncustom_circuit_1 = copy(base_circuit)\n# build on top of copy of base circuit\ncustom_circuit_1.x(0).z(5)...\n# create a new circuit by copying the base again\ncustom_circuit_2 = copy(base_circuit)\n# build on top of that copy again\ncustom_circuit_2.y(5).cz(0,2)...\n
In Bloqade Python this is unnecessary because at every step of your program an immutable object is returned which means you can save it and not have to worry about mutating any internal state.
from bloqade import start\nbase_program = start.add_position((0,0)).rydberg.rabi.amplitude.uniform\n# Just recycle your base program! No `copy` needed!\nnew_program_1 = base_program.constant(duration=5.0, value=5.0)\nnew_program_2 = base_program.piecewise_linear(\n durations=[5.0], values = [0.0, 5.0]\n)\n
"},{"location":"home/gotchas/#creating-new-programs-instead-of-using-batch_assign","title":"Creating New Programs Instead of Using .batch_assign","text":"
If you have a set of parameters you'd like to test your program on versus a single parameter, don't generate a new program for each value:
rabi_values = [2.0, 4.7, 6.1]\nprograms_with_different_rabi_values = []\n\nfor rabi_value in rabi_values:\n program = start.add_position((0, 0)).rydberg.rabi.amplitude.uniform.constant(\n duration=5.0, value=rabi_value\n )\n programs_with_different_rabi_values.append(program)\n\nresults = []\n\nfor program in programs_with_different_rabi_values:\n result = program.bloqade.python().run(100)\n results.append(result)\n
Instead take advantage of the fact Bloqade has facilities specifically designed to make trying out multiple values in your program without needing to make individual copies via .batch_assign. The results are also automatically handled for you so each value you test has its own set of results, but all collected in a singular dataframe versus the above where you'd have to keep track of individual results.
rabi_values = [2.0, 4.7, 6.1]\n# place a variable for the Rabi Value and then batch assign values to it\nprogram_with_rabi_values = start.add_position(\n 0, 0\n).rydberg.rabi.amplitude.uniform.constant(duration=5.0, value=\"rabi_value\")\nprogram_with_assignments = program_with_rabi_values.batch_assign(\n rabi_value=rabi_values\n)\n\n# get your results in one dataframe versus having to keep track of a\n# bunch of individual programs and their individual results\nbatch = program_with_assignments.bloqade.python().run(100)\nresults_dataframe = batch.report().dataframe\n
"},{"location":"home/migration/","title":"Migrating to Bloqade Analog","text":""},{"location":"home/migration/#introduction","title":"Introduction","text":"
In order to make room for more features inside the Bloqade ecosystem, we have created a new package to take the place of the old bloqade package. The new package is called bloqade-analog. The old package bloqade will house a namespace package for other features such as our new Bloqade Digital package with support for circuit-based quantum computers!
The new package is a drop-in replacement for the old one. You can simply replace import bloqade with import bloqade.analog or from bloqade.analog import ... in your code. Everything else should work as before.
lets say your header of your python script looks like this:
from bloqade import var\nfrom bloqade.atom_arrangement import Square\n...\n
You can simply replace it with:
from bloqade.analog import var\nfrom bloqade.analog.atom_arrangement import Square\n...\n
"},{"location":"home/migration/#migrating-old-bloqade-json-files","title":"Migrating old bloqade JSON files","text":"
If you have old bloqade JSON files, you will not be able to directly deserialize them anymore because of the package restructuring. Howver we have provided some tools to migrate those JSON files to be compatible with bloqade-analog. You can do this by running the following command in the command line for a single file:
This will create a new file with the same name as the old file, but with _analog appended to the end of the filename. For example, if you have a file called my_bloqade.json, the new file will be called my_bloqade-analog.json. You can then use load to deserialize this file with the bloqade-analog package. There are other options for converting the file, such as setting the indent level for the output file or overwriting the old file. You can see all the options by running:
python -m bloqade.analog.migrate --help\n
You can also migrate multiple files at once by running:
Another option is to use the migration tool in a python script:
from bloqade.analog.migrate import migrate\n\n # set the indent level for the output file\nindent: int = ...\n# set to True if you want to overwrite the old file, otherwise the new file will be created with -analog appended to the end of the filename\noverwrite: bool = ...\nf\nor filename in [\"file1.json\", \"file2.json\", ...]:\n migrate(filename, indent=indent, overwrite=overwrite)\n
This will migrate all the files in the list to the new format."},{"location":"home/migration/#having-trouble-comments-or-concerns","title":"Having trouble, comments, or concerns?","text":"
All the sections below are self-contained, you can click on the links in the Table of Contents to read the relevant parts.
"},{"location":"home/quick_start/#navigating-the-bloqade-api","title":"Navigating the Bloqade API","text":"
As you develop your Bloqade program, you are expected to rely on pop-up \"hints\" provided in your development environment to help you determine what the next part of your program should be.
"},{"location":"home/quick_start/#defining-atom-geometry","title":"Defining Atom Geometry","text":"
You can import pre-defined geometries based on Bravais lattices from bloqade.atom_arrangement. You may also specify a lattice spacing which dictates the spacing between the atoms as well as the number of atom sites in a certain direction.
Specify which level coupling to drive: rydberg or hyperfine
Specify detuning, rabi.amplitude or rabi.phase
Specify the spatial modulation
Which then leads you to the ability to specify a waveform of interest and begin constructing your pulse sequence. In the example below, we target the ground-Rydberg level coupling to drive with uniform spatial modulation for the Rabi amplitude. Our waveform is a piecewise linear one which ramps from \\(0\\) to \\(5 \\,\\text{rad/us}\\), holds that value for \\(1 \\,\\text{us}\\) and then ramps back down to \\(0 \\,\\text{rad/us}\\).
You aren't restricted to just piecewise linear waveforms however, you can also specify:
linear - Define a transition from one value to another over a duration
constant - Define a fixed value over a duration
piecewise_constant - Define a step-wise function with specific durations for each step
poly - Define a polynomial waveform using coefficients over a duration
"},{"location":"home/quick_start/#arbitrary-functions-as-waveforms","title":"Arbitrary Functions as Waveforms","text":"
For more complex waveforms it may provide to be tedious trying to chain together a large number of piecewise_constant or piecewise_linear methods and instead easier to just define the waveform as a function of time.
Bloqade lets you easily plug in an arbitrary function with .fn:
In this form you can immediately emulate it if you'd like but to run this on hardware you need to discretize it. The waveform on hardware has to either be:
Piecewise linear for Rabi amplitude and detuning terms of the Hamiltonian
Piecewise constant for the Phase term of the Hamiltonian
Bloqade can automatically perform this conversion with sample(), all you need to do is specify the kind of interpolation and the size of the discretization step in time. Below we set the step duration to be \\(0.05 \\,\\text{us}\\) with \"linear\" interpolation to give us a resulting piecewise linear waveform.
Programs that have custom functions as waveforms are not fully serializable. This means that when you are saving and reloading results, the original embedded program will be missing that custom waveform. You will still be able to analyze the saved results!
"},{"location":"home/quick_start/#slicing-and-recording-waveforms","title":"Slicing and Recording Waveforms","text":"
When you conduct parameter sweeps with your program, you may want to sweep over your program across time. This will require \"slicing\" your waveforms, where you define the waveform of interest and then, using a variable with .slice, indicate the times at which the waveform duration should be cut short.
In the example below we define a simple piecewise linear waveform but slice it starting from a time duration of \\(0 \\,\\text{us}\\) to values between \\(1\\) to \\(2 \\,\\text{us}\\).
This program will run fine in emulation but due to hardware constraints certain waveforms (such as those targeting the Rabi Amplitude), the waveform needs to start and end at \\(0 \\,\\text{rad}/\\text{us}\\). Thus, there needs to be a way to slice our waveform but also add an end component to that waveform. .record in Bloqade lets you literally \"record\" the value at the end of a .slice and then use it to construct further parts of the waveform.
In the program below the waveform is still sliced but with the help of .record a linear segment that pulls the waveform down to \\(0.0 \\,\\text{rad}/\\text{us}\\) from whatever its current value at the slice is in \\(0.7 \\,\\text{us}\\) is added.
"},{"location":"home/quick_start/#waveforms-with-no-geometry","title":"Waveforms with No Geometry","text":"
If you have multiple atom geometries you'd like to apply a pulse sequence to or you simply don't want to worry about what atom geometry to start with, you can just build straight off of start:
When you've completed the definition of your program you can use Bloqade's own emulator to get results. The emulation performs the time evolution of the analog Rydberg Hamiltonian. Here we say we want to the program to be run and measurements obtained 1000 times.
"},{"location":"home/quick_start/#submitting-to-hardware","title":"Submitting to Hardware","text":"
To submit your program to hardware ensure you have your AWS Braket credentials loaded. You will need to use the AWS CLI to do this.
Then it's just a matter of selecting the Aquila on Braket backend. Before going any further Bloqade provides two options for running your program on actual hardware:
Using .run is blocking, meaning you will not be able to execute anything else while Bloqade waits for results
Using .run_async lets you submit to hardware and continue any further execution, while also letting you query the status of your program in the queue.
In the example below we use .run_async to specify the program should be run and measurements obtained 1000 times.
Which gives us the Task ID, a unique identifier for the task as well as the status of the task. In the example below the task is Enqueued meaning it has been successfully created and is awaiting execution on the cloud. When the task is actually running on hardware, the status will change to Running.
task ID status shots\n0 arn:aws:braket:us-east-1:XXXXXXXXXXXX:quantum-... Enqueued 100\n
You can easily do parameter sweeps in emulation and on Aquila with variables. Bloqade automatically detects strings in your program as variables that you can later assign singular or multiple values to.
In the example below, we define a program with a singular variable that controls the amplitude of the waveform.
import numpy as np\nrabi_amplitudes = np.linspace(1.0, 2.0, 20)\n\nmultiple_value_assignment = rabi_oscillations_program.batch_assign(rabi_amplitude=rabi_amplitudes)\n
This will actually create multiple versions of the program internally, with each program assigned a fixed value from the sweep. Bloqade will automatically handle the compilation of results from these multiple programs in order, meaning there is no major departure from what you saw in analyzing the results of your program.
You can also delay assignment of a value to a variable by first declaring it in .args() and then passing a value when you call run:
Variables in Bloqade can also be symbolically manipulated, giving you even more flexibility when you construct your program.
In the example below, we externally declare a variable my_var that then has some arithmetic done on it to allow it to have a different value in a later part of the program:
You still perform variable assignment just like you normally would:
program = detuning_waveform.assign(my_variable=1.0)\n
You can also use Python's built-in sum if you want the sum of multiple variables as a value in your program. This is quite useful when it comes to needing to indicate a full duration for a waveform that doesn't need to be split up:
During program development, it can be quite handy to know what true hardware capabilities are and incorporate that information programmaticaly. Bloqade offers the ability to do this via get_capabilities().
get_capabilities() (importable directly from bloqade) returns a QuEraCapabilities object. This object contains all the hardware constraints in Decimal format for the Aquila machine, our publically-accessible QPU on AWS Braket.
An example of using get_capabilities() is presented below:
from bloqade import get_capabilities, piecewise_linear\n\n# get capabilities for Aquila\naquila_capabilities = get_capabilities()\n\n# obtain maximum Rabi frequency as Decimal\nmax_rabi = aquila_capabilities.capabilities.rydberg.global_.rabi_frequency_max\n\n# use that value in constructing a neat Rabi waveform\nrabi_wf = piecewise_linear(durations = [0.5, 1.0, 0.5], values = [0, max_rabi, max_rabi, 0])\n
The attribute names for each value have been provided below but will require you to provide the proper prefix like in the example above (e.g. the maximum number of qubits lives under the number_qubits_max attribute which can be navigated to via *your_QuEra_Capabilities_Object*.lattice.number_qubits_max).
Use prefix your_capabilities_object.capabilities.task for:
minimum number of shots
maximum number of shots
Capability Attribute Value Minimum Number of Shots number_shots_min 1 Maximum Number of Shots number_shots_max 1000"},{"location":"reference/hardware-capabilities/#lattice-geometry","title":"Lattice Geometry","text":"
Use prefix your_capabilities_object.capabilities.lattice for:
maximum number of qubits
Use prefix your_capabilities_object.capabilities.lattice.area for:
maximum lattice area width
maximum lattice area height
Use prefix your_capabilities_object.capabilities.lattice.geometry for:
maximum number of sites
position resolution
minimum radial spacing
minimum vertical spacing
Capability Attribute Value Maximum Number of Qubits number_qubits_max 256 Maximum Lattice Area Width width 75.0 \u00b5m Maximum Lattice Area Height height 76.0 \u00b5m Minimum Radial Spacing between Qubits spacing_radial_min 4.0 \u00b5m Minimum Vertical Spacing between Qubits spacing_vertical_min 4.0 \u00b5m Position Resolution position_resolution 0.1 \u00b5m Maximum Number of Sites number_sites_max 256"},{"location":"reference/hardware-capabilities/#global-rydberg-values","title":"Global Rydberg Values","text":"
Use prefix your_capabilities_object.capabilities.rydberg for:
C6 Coefficient
Use prefix your_capabilities_object.capabilities.rydberg.global_ for:
Everything else related to global (applied to all atom) capabilities
Capability Attribute Value Rydberg Interaction Constant c6_coefficient 5.42\u00d710\u2076 rad/\u03bcs \u00d7 \u00b5m\u2076 Minimum Rabi Frequency rabi_frequency_min 0.00 rad/\u03bcs Maximum Rabi Frequency rabi_frequency_max 15.8 rad/\u03bcs Rabi Frequency Resolution rabi_frequency_resolution 0.0004 rad/\u03bcs Maximum Rabi Frequency Slew Rate rabi_frequency_slew_rate_max 250.0 rad/\u00b5s\u00b2 Minimum Detuning detuning_min -125.0 rad/\u03bcs Maximum Detuning detuning_max 125.0 rad/\u03bcs Detuning Resolution detuning_resolution 2.0\u00d710\u207b\u2077 rad/\u03bcs Maximum Detuning Slew Rate detuning_slew_rate_max 2500.0 rad/\u00b5s\u00b2 Minimum Phase phase_min -99.0 rad Maximum Phase phase_max 99.0 rad Phase Resolution phase_resolution 5.0\u00d710\u207b\u2077 rad Minimum Time time_min 0.0 \u00b5s Maximum Time time_max 4.0 \u00b5s Time Resolution time_resolution 0.001 \u00b5s Minimum \u0394t time_delta_min 0.05 \u00b5s"},{"location":"reference/hardware-capabilities/#local-detuning-values","title":"Local Detuning Values","text":"
Use prefix your_capabilities_object.capabilities.rydberg.local for the following values:
Capability Attribute Value Maximum Detuning detuning_max 125.0 rad/\u03bcs Minimum Detuning detuning_min 0 rad/\u03bcs Maximum Detuning Slew Rate detuning_slew_rate_max 1256.0 rad/\u00b5s\u00b2 Maximum Number of Local Detuning Sites number_local_detuning_sites 200 Maximum Site Coefficient site_coefficient_max 1.0 Minimum Site Coefficient site_ceofficient_min 0.0 Minimum Radial Spacing spacing_radial_min 5 \u00b5m Minimum \u0394t time_delta_min 0.05 \u03bcs Time Resolution time_resolution 0.001 \u00b5s"},{"location":"reference/overview/","title":"Builder Overview","text":"
You may have noticed from the Getting Started and Tutorials that Bloqade uses this interesting, dot-intensive syntax.
from bloqade import start\n\nprog = start.add_position((0,0)).rydberg.rabi.amplitude.uniform.constant(1,1)\n
Exhibit A: Lots of Dots
In fact, it might look remniscent of what you see in some gate-based Quantum Computing SDKs:
# this is strictly pseudocode\ncircuit = init_qubits(n_qubits)\n# note the dots!\ncircuit.x(0).z(1).cnot(0, 1)...\n
We call this syntax the builder or builder syntax and as its name implies, it is designed to let you build programs for Analog Hamiltonian Simulation hardware as easily and as straightforward as possible.
The linear structure implies a natural hierarchy in how you think about targeting the various degrees of freedom (detuning, atom positions, Rabi amplitude, etc.) your program will have. In the beginning you have unrestricted access to all these degrees of freedom but in order to do something useful you need to:
Narrow down and explicitly identify what you want to control
Provide the instructions on how you want to control what your focused on
Context is a strong component of the builder syntax, as you are both actively restricted from doing certain things that can introduce ambiguity based on where you are in your program and repeating the same action in different parts of the program yields different results.
While we hope the Smart Documentation (the ability to instantly see all your next possible steps and their capabilities in your favorite IDE/IPython) is sufficient to get you where you need to go, we undestand it's particularly beneficial to get a high-level overview of things before diving in.
The Standard Representation is a nice flow chart that gives a high-level overview of the different steps and components in the builder syntax.
cast Real number (or list/tuple of Real numbers) to Scalar Literal.
cast str (or list/tuple of Real numbers) to Scalar Variable.
Parameters:
Name Type Description Default pyUnion[str, Real, Tuple[Real], List[Real]]
python object to cast
required
Returns:
Type Description Scalar
Scalar
Source code in src/bloqade/analog/ir/scalar.py
def cast(py) -> \"Scalar\":\n \"\"\"\n 1. cast Real number (or list/tuple of Real numbers)\n to [`Scalar Literal`][bloqade.ir.scalar.Literal].\n\n 2. cast str (or list/tuple of Real numbers)\n to [`Scalar Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str,Real,Tuple[Real],List[Real]]): python object to cast\n\n Returns:\n Scalar\n \"\"\"\n ret = trycast(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Scalar Literal\")\n\n return ret\n
@beartype\ndef constant(duration: ScalarType, value: ScalarType) -> Constant:\n \"\"\"Create a Constant waveform.\n\n Args:\n duration (ScalarType): Duration of the Constant waveform.\n value (ScalarType): Value of the Constant waveform.s\n\n Returns:\n Constant: A Constant waveform.\n \"\"\"\n return Constant(value, duration)\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.dumps
{}
Returns:
Name Type Description strstr
the serialized object as a string
Source code in src/bloqade/analog/serialize.py
@beartype\ndef dumps(\n o: Any,\n use_decimal: bool = True,\n **json_kwargs,\n) -> str:\n \"\"\"Serialize object to string\n\n Args:\n o (Any): the object to serialize\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.dumps\n\n Returns:\n str: the serialized object as a string\n \"\"\"\n if not isinstance(o, Serializer.types):\n raise TypeError(\n f\"Object of type {type(o)} is not JSON serializable. \"\n f\"Only {Serializer.types} are supported.\"\n )\n return json.dumps(o, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n
Name Type Description Default use_experimentalbool
Get experimental capabilities instead of standard ones. By default value is False.
False
Returns:
Name Type Description QuEraCapabilitiesQuEraCapabilities
capabilities object for Aquila device.
Note
Units of time, distance, and energy are microseconds (us), micrometers (um), and rad / us, respectively.
For a comprehensive list of capabilities, see the Hardware Reference page
Source code in src/bloqade/analog/factory.py
def get_capabilities(use_experimental: bool = False) -> \"QuEraCapabilities\":\n \"\"\"Get the device capabilities for Aquila\n\n Args:\n use_experimental (bool): Get experimental capabilities instead of\n standard ones. By default value is False.\n\n Returns:\n QuEraCapabilities: capabilities object for Aquila device.\n\n\n Note:\n Units of time, distance, and energy are microseconds (us),\n micrometers (um), and rad / us, respectively.\n\n For a comprehensive list of capabilities,\n see the [Hardware Reference](../../reference/hardware-capabilities.md)\n page\n \"\"\"\n\n from bloqade.analog.submission.capabilities import get_capabilities\n\n # manually convert to units\n return get_capabilities(use_experimental=use_experimental).scale_units(\n Decimal(\"1e6\"), Decimal(\"1e-6\")\n )\n
@beartype\ndef linear(duration: ScalarType, start: ScalarType, stop: ScalarType) -> Linear:\n \"\"\"Create a Linear waveform.\n\n Args:\n duration (ScalarType): Duration of linear waveform\n start (ScalarType): Starting value of linear waveform\n stop (ScalarType): Ending value of linear waveform\n\n Returns:\n Linear: Linear waveform\n \"\"\"\n return Linear(start, stop, duration)\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.loads
{}
Returns:
Name Type Description Any
the deserialized object
Source code in src/bloqade/analog/serialize.py
@beartype\ndef loads(s: str, use_decimal: bool = True, **json_kwargs):\n \"\"\"Load object from string\n\n Args:\n s (str): the string to load\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.loads\n\n Returns:\n Any: the deserialized object\n \"\"\"\n load_bloqade()\n return json.loads(\n s, object_hook=Serializer.object_hook, use_decimal=use_decimal, **json_kwargs\n )\n
Create a piecewise constant waveform from a list of durations and values. The value duration[i] corresponds to the length of time for the i'th segment with a value of values[i].
Parameters:
Name Type Description Default durationsList[ScalarType]
The duration of each segment
required valuesList[ScalarType]
The values for each segment
required
Raises:
Type Description ValueError
If the length of values is not the same as the length of
Returns:
Name Type Description WaveformWaveform
The piecewise linear waveform.
Source code in src/bloqade/analog/factory.py
@beartype\ndef piecewise_constant(\n durations: List[ScalarType], values: List[ScalarType]\n) -> Waveform:\n \"\"\"Create a piecewise linear waveform.\n\n Create a piecewise constant waveform from a list of durations and values. The\n value `duration[i]` corresponds to the length of time for the i'th segment\n with a value of `values[i]`.\n\n Args:\n durations (List[ScalarType]): The duration of each segment\n values (List[ScalarType]): The values for each segment\n\n Raises:\n ValueError: If the length of `values` is not the same as the length of\n `durations`.\n\n Returns:\n Waveform: The piecewise linear waveform.\n \"\"\"\n if len(durations) != len(values):\n raise ValueError(\n \"The length of values must be the same as the length of durations\"\n )\n\n pwc_wf = None\n for duration, value in zip(durations, values):\n if pwc_wf is None:\n pwc_wf = Constant(value, duration)\n else:\n pwc_wf = pwc_wf.append(Constant(value, duration))\n\n return pwc_wf\n
Create a piecewise linear waveform from a list of durations and values. The value duration[i] is of the linear segment between values[i] and values[i+1].
Parameters:
Name Type Description Default durationsList[ScalarType]
The duration of each segment
required valuesList[ScalarType]
The values for each segment
required
Raises:
Type Description ValueError
If the length of values is not one greater than the length of
Returns:
Name Type Description WaveformWaveform
The piecewise linear waveform.
Source code in src/bloqade/analog/factory.py
@beartype\ndef piecewise_linear(durations: List[ScalarType], values: List[ScalarType]) -> Waveform:\n \"\"\"Create a piecewise linear waveform.\n\n Create a piecewise linear waveform from a list of durations and values. The\n value `duration[i]` is of the linear segment between `values[i]` and `values[i+1]`.\n\n Args:\n durations (List[ScalarType]): The duration of each segment\n values (List[ScalarType]): The values for each segment\n\n Raises:\n ValueError: If the length of `values` is not one greater than the length of\n `durations`.\n\n Returns:\n Waveform: The piecewise linear waveform.\n \"\"\"\n\n if len(durations) + 1 != len(values):\n raise ValueError(\n \"The length of values must be one greater than the length of durations\"\n )\n\n pwl_wf = None\n for duration, start, stop in zip(durations, values[:-1], values[1:]):\n if pwl_wf is None:\n pwl_wf = Linear(start, stop, duration)\n else:\n pwl_wf = pwl_wf.append(Linear(start, stop, duration))\n\n return pwl_wf\n
List of arguments to leave till runtime. Defaults to [].
[]
Returns:
Name Type Description RoutineRoutine
An object that can be used to dispatch a rydberg program to multiple backends.
Source code in src/bloqade/analog/factory.py
@beartype\ndef rydberg_h(\n atoms_positions: Any,\n detuning: Optional[Waveform] = None,\n amplitude: Optional[Waveform] = None,\n phase: Optional[Waveform] = None,\n static_params: Dict[str, Any] = {},\n batch_params: Union[List[Dict[str, Any]], Dict[str, Any]] = [],\n args: List[str] = [],\n) -> Routine:\n \"\"\"Create a rydberg program with uniform detuning, amplitude, and phase.\n\n Args:\n atoms_positions (Any): Description of geometry of atoms in system.\n detuning (Optional[Waveform], optional): Waveform for detuning.\n Defaults to None.\n amplitude (Optional[Waveform], optional): Waveform describing the amplitude of\n the rabi term. Defaults to None.\n phase (Optional[Waveform], optional): Waveform describing the phase of rabi\n term. Defaults to None.\n static_params (Dict[str, Any], optional): Define static parameters of your\n program. Defaults to {}.\n batch_params (Union[List[Dict[str, Any]], Dict[str, Any]], optional):\n Parmaters for a batch of tasks. Defaults to [].\n args (List[str], optional): List of arguments to leave till runtime.\n Defaults to [].\n\n Returns:\n Routine: An object that can be used to dispatch a rydberg program to\n multiple backends.\n \"\"\"\n from bloqade.analog import start\n from bloqade.analog.atom_arrangement import AtomArrangement\n\n if isinstance(atoms_positions, AtomArrangement):\n prog = atoms_positions\n else:\n prog = start.add_position(atoms_positions)\n\n if detuning is not None:\n prog = prog.rydberg.detuning.uniform.apply(detuning)\n\n if amplitude is not None:\n prog = prog.amplitude.uniform.apply(amplitude)\n\n if phase is not None:\n prog = prog.phase.uniform.apply(phase)\n\n prog = prog.assign(**static_params)\n\n if isinstance(batch_params, dict):\n prog = prog.batch_assign(**batch_params)\n else:\n prog = prog.batch_assign(batch_params)\n\n prog = prog.args(args)\n\n return prog.parse()\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.dump
{}
Returns:
Type Description None
None
Source code in src/bloqade/analog/serialize.py
@beartype\ndef save(\n o: Any,\n fp: Union[TextIO, str],\n use_decimal=True,\n **json_kwargs,\n) -> None:\n \"\"\"Serialize object to file\n\n Args:\n o (Any): the object to serialize\n fp (Union[TextIO, str]): the file path or file object\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.dump\n\n Returns:\n None\n \"\"\"\n if not isinstance(o, Serializer.types):\n raise TypeError(\n f\"Object of type {type(o)} is not JSON serializable. \"\n f\"Only {Serializer.types} are supported.\"\n )\n if isinstance(fp, str):\n with open(fp, \"w\") as f:\n json.dump(o, f, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n else:\n json.dump(o, fp, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n
If depth=None, return current depth. If depth is provided, setting current depth to depth
Parameters:
Name Type Description Default depthint
the user specified depth. Defaults to None.
None
Returns:
Name Type Description int
current updated depth
Source code in src/bloqade/analog/__init__.py
def tree_depth(depth: int = None):\n \"\"\"Setting globally maximum depth for tree printing\n\n If `depth=None`, return current depth.\n If `depth` is provided, setting current depth to `depth`\n\n Args:\n depth (int, optional): the user specified depth. Defaults to None.\n\n Returns:\n int: current updated depth\n \"\"\"\n if depth is not None:\n _ir.tree_print.MAX_TREE_DEPTH = depth\n return _ir.tree_print.MAX_TREE_DEPTH\n
cast string (or list/tuple of strings) to Variable.
Parameters:
Name Type Description Default pyUnion[str, List[str]]
a string or list/tuple of strings
required
Returns:
Type Description Variable
Union[Variable]
Source code in src/bloqade/analog/ir/scalar.py
def var(py: str) -> \"Variable\":\n \"\"\"cast string (or list/tuple of strings)\n to [`Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str, List[str]]): a string or list/tuple of strings\n\n Returns:\n Union[Variable]\n \"\"\"\n ret = tryvar(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Variable\")\n\n return ret\n
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).
# single coordinate\n>>> reg = start.add_position((0,0))\n# you may chain add_position calls\n>>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n# you can add variables anywhere a value may be present\n>>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n# and specify your atom fillings\n>>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n[True, False])\n# alternatively you could use one boolean to specify\n# all coordinates should be empty/filled\n>>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n(5.2, 2.2)], False)\n
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
Source code in src/bloqade/analog/ir/location/location.py
def add_position(\n self,\n position: Union[\n PositionArray,\n List[Tuple[ScalarType, ScalarType]],\n Tuple[ScalarType, ScalarType],\n ],\n filling: Optional[Union[BoolArray, List[bool], bool]] = None,\n) -> \"ListOfLocations\":\n \"\"\"\n Add a position or multiple positions to a pre-existing geometry.\n\n `add_position` is capable of accepting:\n - A single tuple for one atom coordinate: `(1.0, 2.5)`\n - A list of tuples: `[(0.0, 1.0), (2.0,1.5), etc.]\n - A numpy array of shape (N, 2) where N is the number of atoms\n\n You may also intersperse variables anywhere a value may be present.\n\n You can also pass in an optional argument which determines the atom \"filling\"\n (whether or not at a specified coordinate an atom should be present).\n\n ### Usage Example:\n ```\n # single coordinate\n >>> reg = start.add_position((0,0))\n # you may chain add_position calls\n >>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n # you can add variables anywhere a value may be present\n >>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n # and specify your atom fillings\n >>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n [True, False])\n # alternatively you could use one boolean to specify\n # all coordinates should be empty/filled\n >>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n (5.2, 2.2)], False)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`: to specify Rydberg coupling\n - `...add_position(positions).hyperfine`: to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n if is_bearable(position, PositionArray) and is_bearable(\n filling, Optional[BoolArray]\n ):\n return self.add_position_ndarray(position, filling)\n elif is_bearable(position, List[Tuple[ScalarType, ScalarType]]) and is_bearable(\n filling, Optional[List[bool]]\n ):\n return self.add_position_list_tuples(position, filling)\n elif is_bearable(position, Tuple[ScalarType, ScalarType]) and is_bearable(\n filling, Optional[bool]\n ):\n return self.add_position_single_tupe(position, filling)\n else:\n raise TypeError(\"Invalid input types for add_position provided!\")\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_count(2, custom_rng)\n# you may also chain apply_defect_count calls\n>>> reg.apply_defect_count(2, custom_rng)\n# you can also use apply_defect_count on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_count(\n self, n_defects: int, rng: np.random.Generator = np.random.default_rng()\n):\n \"\"\"\n Drop `n_defects` atoms from the geometry randomly. Internally this occurs\n by setting certain sites to have a SiteFilling set to false indicating\n no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_count(2, custom_rng)\n # you may also chain apply_defect_count calls\n >>> reg.apply_defect_count(2, custom_rng)\n # you can also use apply_defect_count on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts)\n .apply_defect_count(n_defects)`: to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`: to specify\n Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n location_list = []\n for location_info in self.enumerate():\n location_list.append(location_info)\n\n filled_sites = []\n\n for index, location_info in enumerate(location_list):\n if location_info.filling is SiteFilling.filled:\n filled_sites.append(index)\n\n if n_defects >= len(filled_sites):\n raise ValueError(\n f\"n_defects {n_defects} must be less than the number of filled sites \"\n f\"({len(filled_sites)})\"\n )\n\n for _ in range(n_defects):\n index = rng.choice(filled_sites)\n location_list[index] = LocationInfo.create(\n location_list[index].position,\n (False if location_list[index].filling is SiteFilling.filled else True),\n )\n filled_sites.remove(index)\n\n return ListOfLocations(location_list)\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n# you may also chain apply_defect_density calls\n>>> reg.apply_defect_count(0.1, custom_rng)\n# you can also use apply_defect_density on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)])\n.apply_defect_density(0.5, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_density(\n self,\n defect_probability: float,\n rng: np.random.Generator = np.random.default_rng(),\n):\n \"\"\"\n Drop atoms randomly with `defect_probability` probability (range of 0 to 1).\n Internally this occurs by setting certain sites to have a SiteFilling\n set to false indicating no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n # you may also chain apply_defect_density calls\n >>> reg.apply_defect_count(0.1, custom_rng)\n # you can also use apply_defect_density on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)])\n .apply_defect_density(0.5, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`:\n to specify Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n p = min(1, max(0, defect_probability))\n location_list = []\n\n for location_info in self.enumerate():\n if rng.random() < p:\n location_list.append(\n LocationInfo.create(\n location_info.position,\n (\n False\n if location_info.filling is SiteFilling.filled\n else True\n ),\n )\n )\n else:\n location_list.append(location_info)\n\n return ListOfLocations(location_list=location_list)\n
Source code in src/bloqade/analog/ir/location/location.py
def figure(self, fig_kwargs=None, **assignments):\n \"\"\"obtain a figure object from the atom arrangement.\"\"\"\n return get_atom_arrangement_figure(self, fig_kwargs=fig_kwargs, **assignments)\n
the values to assign to the variables in the register.
{}
Returns:
Name Type Description NDArrayNDArray
the Rydberg interaction matrix in the lower triangular form.
Source code in src/bloqade/analog/ir/location/location.py
def rydberg_interaction(self, **assignments) -> NDArray:\n \"\"\"calculate the Rydberg interaction matrix.\n\n Args:\n **assignments: the values to assign to the variables in the register.\n\n Returns:\n NDArray: the Rydberg interaction matrix in the lower triangular form.\n\n \"\"\"\n\n from bloqade.analog.constants import RB_C6\n\n # calculate the Interaction matrix\n V_ij = np.zeros((self.n_sites, self.n_sites))\n for i, site_i in enumerate(self.enumerate()):\n pos_i = np.array([float(ele(**assignments)) for ele in site_i.position])\n\n for j, site_j in enumerate(self.enumerate()):\n if j >= i:\n break # enforce lower triangular form\n\n pos_j = np.array([float(ele(**assignments)) for ele in site_j.position])\n r_ij = np.linalg.norm(pos_i - pos_j)\n\n V_ij[i, j] = RB_C6 / r_ij**6\n\n return V_ij\n
>>> reg = start.add_position([(0,0), (1,1)])\n# atom positions are now (0,0), (2,2)\n>>> new_reg = reg.scale(2)\n# you may also use scale on pre-defined geometries\n>>> from bloqade.analog.atom_arrangement import Chain\n# atoms in the chain will now be 2 um apart versus\n# the default 1 um\n>>> Chain(11).scale(2)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef scale(self, scale: ScalarType):\n \"\"\"\n Scale the geometry of your atoms.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position([(0,0), (1,1)])\n # atom positions are now (0,0), (2,2)\n >>> new_reg = reg.scale(2)\n # you may also use scale on pre-defined geometries\n >>> from bloqade.analog.atom_arrangement import Chain\n # atoms in the chain will now be 2 um apart versus\n # the default 1 um\n >>> Chain(11).scale(2)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`:\n to specify Rydberg coupling\n - `...add_position(positions).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n scale = cast(scale)\n location_list = []\n for location_info in self.enumerate():\n x, y = location_info.position\n new_position = (scale * x, scale * y)\n location_list.append(\n LocationInfo.create(new_position, bool(location_info.filling.value))\n )\n\n return ListOfLocations(location_list)\n
@beartype\ndef constant(duration: ScalarType, value: ScalarType) -> Constant:\n \"\"\"Create a Constant waveform.\n\n Args:\n duration (ScalarType): Duration of the Constant waveform.\n value (ScalarType): Value of the Constant waveform.s\n\n Returns:\n Constant: A Constant waveform.\n \"\"\"\n return Constant(value, duration)\n
Name Type Description Default use_experimentalbool
Get experimental capabilities instead of standard ones. By default value is False.
False
Returns:
Name Type Description QuEraCapabilitiesQuEraCapabilities
capabilities object for Aquila device.
Note
Units of time, distance, and energy are microseconds (us), micrometers (um), and rad / us, respectively.
For a comprehensive list of capabilities, see the Hardware Reference page
Source code in src/bloqade/analog/factory.py
def get_capabilities(use_experimental: bool = False) -> \"QuEraCapabilities\":\n \"\"\"Get the device capabilities for Aquila\n\n Args:\n use_experimental (bool): Get experimental capabilities instead of\n standard ones. By default value is False.\n\n Returns:\n QuEraCapabilities: capabilities object for Aquila device.\n\n\n Note:\n Units of time, distance, and energy are microseconds (us),\n micrometers (um), and rad / us, respectively.\n\n For a comprehensive list of capabilities,\n see the [Hardware Reference](../../reference/hardware-capabilities.md)\n page\n \"\"\"\n\n from bloqade.analog.submission.capabilities import get_capabilities\n\n # manually convert to units\n return get_capabilities(use_experimental=use_experimental).scale_units(\n Decimal(\"1e6\"), Decimal(\"1e-6\")\n )\n
@beartype\ndef linear(duration: ScalarType, start: ScalarType, stop: ScalarType) -> Linear:\n \"\"\"Create a Linear waveform.\n\n Args:\n duration (ScalarType): Duration of linear waveform\n start (ScalarType): Starting value of linear waveform\n stop (ScalarType): Ending value of linear waveform\n\n Returns:\n Linear: Linear waveform\n \"\"\"\n return Linear(start, stop, duration)\n
Create a piecewise constant waveform from a list of durations and values. The value duration[i] corresponds to the length of time for the i'th segment with a value of values[i].
Parameters:
Name Type Description Default durationsList[ScalarType]
The duration of each segment
required valuesList[ScalarType]
The values for each segment
required
Raises:
Type Description ValueError
If the length of values is not the same as the length of
Returns:
Name Type Description WaveformWaveform
The piecewise linear waveform.
Source code in src/bloqade/analog/factory.py
@beartype\ndef piecewise_constant(\n durations: List[ScalarType], values: List[ScalarType]\n) -> Waveform:\n \"\"\"Create a piecewise linear waveform.\n\n Create a piecewise constant waveform from a list of durations and values. The\n value `duration[i]` corresponds to the length of time for the i'th segment\n with a value of `values[i]`.\n\n Args:\n durations (List[ScalarType]): The duration of each segment\n values (List[ScalarType]): The values for each segment\n\n Raises:\n ValueError: If the length of `values` is not the same as the length of\n `durations`.\n\n Returns:\n Waveform: The piecewise linear waveform.\n \"\"\"\n if len(durations) != len(values):\n raise ValueError(\n \"The length of values must be the same as the length of durations\"\n )\n\n pwc_wf = None\n for duration, value in zip(durations, values):\n if pwc_wf is None:\n pwc_wf = Constant(value, duration)\n else:\n pwc_wf = pwc_wf.append(Constant(value, duration))\n\n return pwc_wf\n
Create a piecewise linear waveform from a list of durations and values. The value duration[i] is of the linear segment between values[i] and values[i+1].
Parameters:
Name Type Description Default durationsList[ScalarType]
The duration of each segment
required valuesList[ScalarType]
The values for each segment
required
Raises:
Type Description ValueError
If the length of values is not one greater than the length of
Returns:
Name Type Description WaveformWaveform
The piecewise linear waveform.
Source code in src/bloqade/analog/factory.py
@beartype\ndef piecewise_linear(durations: List[ScalarType], values: List[ScalarType]) -> Waveform:\n \"\"\"Create a piecewise linear waveform.\n\n Create a piecewise linear waveform from a list of durations and values. The\n value `duration[i]` is of the linear segment between `values[i]` and `values[i+1]`.\n\n Args:\n durations (List[ScalarType]): The duration of each segment\n values (List[ScalarType]): The values for each segment\n\n Raises:\n ValueError: If the length of `values` is not one greater than the length of\n `durations`.\n\n Returns:\n Waveform: The piecewise linear waveform.\n \"\"\"\n\n if len(durations) + 1 != len(values):\n raise ValueError(\n \"The length of values must be one greater than the length of durations\"\n )\n\n pwl_wf = None\n for duration, start, stop in zip(durations, values[:-1], values[1:]):\n if pwl_wf is None:\n pwl_wf = Linear(start, stop, duration)\n else:\n pwl_wf = pwl_wf.append(Linear(start, stop, duration))\n\n return pwl_wf\n
List of arguments to leave till runtime. Defaults to [].
[]
Returns:
Name Type Description RoutineRoutine
An object that can be used to dispatch a rydberg program to multiple backends.
Source code in src/bloqade/analog/factory.py
@beartype\ndef rydberg_h(\n atoms_positions: Any,\n detuning: Optional[Waveform] = None,\n amplitude: Optional[Waveform] = None,\n phase: Optional[Waveform] = None,\n static_params: Dict[str, Any] = {},\n batch_params: Union[List[Dict[str, Any]], Dict[str, Any]] = [],\n args: List[str] = [],\n) -> Routine:\n \"\"\"Create a rydberg program with uniform detuning, amplitude, and phase.\n\n Args:\n atoms_positions (Any): Description of geometry of atoms in system.\n detuning (Optional[Waveform], optional): Waveform for detuning.\n Defaults to None.\n amplitude (Optional[Waveform], optional): Waveform describing the amplitude of\n the rabi term. Defaults to None.\n phase (Optional[Waveform], optional): Waveform describing the phase of rabi\n term. Defaults to None.\n static_params (Dict[str, Any], optional): Define static parameters of your\n program. Defaults to {}.\n batch_params (Union[List[Dict[str, Any]], Dict[str, Any]], optional):\n Parmaters for a batch of tasks. Defaults to [].\n args (List[str], optional): List of arguments to leave till runtime.\n Defaults to [].\n\n Returns:\n Routine: An object that can be used to dispatch a rydberg program to\n multiple backends.\n \"\"\"\n from bloqade.analog import start\n from bloqade.analog.atom_arrangement import AtomArrangement\n\n if isinstance(atoms_positions, AtomArrangement):\n prog = atoms_positions\n else:\n prog = start.add_position(atoms_positions)\n\n if detuning is not None:\n prog = prog.rydberg.detuning.uniform.apply(detuning)\n\n if amplitude is not None:\n prog = prog.amplitude.uniform.apply(amplitude)\n\n if phase is not None:\n prog = prog.phase.uniform.apply(phase)\n\n prog = prog.assign(**static_params)\n\n if isinstance(batch_params, dict):\n prog = prog.batch_assign(**batch_params)\n else:\n prog = prog.batch_assign(batch_params)\n\n prog = prog.args(args)\n\n return prog.parse()\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.dumps
{}
Returns:
Name Type Description strstr
the serialized object as a string
Source code in src/bloqade/analog/serialize.py
@beartype\ndef dumps(\n o: Any,\n use_decimal: bool = True,\n **json_kwargs,\n) -> str:\n \"\"\"Serialize object to string\n\n Args:\n o (Any): the object to serialize\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.dumps\n\n Returns:\n str: the serialized object as a string\n \"\"\"\n if not isinstance(o, Serializer.types):\n raise TypeError(\n f\"Object of type {type(o)} is not JSON serializable. \"\n f\"Only {Serializer.types} are supported.\"\n )\n return json.dumps(o, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.loads
{}
Returns:
Name Type Description Any
the deserialized object
Source code in src/bloqade/analog/serialize.py
@beartype\ndef loads(s: str, use_decimal: bool = True, **json_kwargs):\n \"\"\"Load object from string\n\n Args:\n s (str): the string to load\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.loads\n\n Returns:\n Any: the deserialized object\n \"\"\"\n load_bloqade()\n return json.loads(\n s, object_hook=Serializer.object_hook, use_decimal=use_decimal, **json_kwargs\n )\n
use decimal.Decimal for numbers. Defaults to True.
True**json_kwargs
other arguments passed to json.dump
{}
Returns:
Type Description None
None
Source code in src/bloqade/analog/serialize.py
@beartype\ndef save(\n o: Any,\n fp: Union[TextIO, str],\n use_decimal=True,\n **json_kwargs,\n) -> None:\n \"\"\"Serialize object to file\n\n Args:\n o (Any): the object to serialize\n fp (Union[TextIO, str]): the file path or file object\n use_decimal (bool, optional): use decimal.Decimal for numbers. Defaults to True.\n **json_kwargs: other arguments passed to json.dump\n\n Returns:\n None\n \"\"\"\n if not isinstance(o, Serializer.types):\n raise TypeError(\n f\"Object of type {type(o)} is not JSON serializable. \"\n f\"Only {Serializer.types} are supported.\"\n )\n if isinstance(fp, str):\n with open(fp, \"w\") as f:\n json.dump(o, f, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n else:\n json.dump(o, fp, cls=Serializer, use_decimal=use_decimal, **json_kwargs)\n
This node represents level coupling between hyperfine states.
Examples:
- To reach the node from the start node:\n\n>>> node = bloqade.start.hyperfine\n>>> type(node)\n<class 'bloqade.builder.coupling.Hyperfine'>\n\n- Hyperfine level coupling has two reachable field nodes:\n\n - detuning term (See also [`Detuning`][bloqade.builder.field.Detuning])\n - rabi term (See also [`Rabi`][bloqade.builder.field.Rabi])\n\n>>> hyp_detune = bloqade.start.hyperfine.detuning\n>>> hyp_rabi = bloqade.start.hyperfine.rabi\n
Generate the intermediate representation (IR) for the Hyperfine level coupling.
Returns:
Name Type Description IR
An intermediate representation of the Hyperfine level coupling sequence.
Note
This method is used internally by the Bloqade framework.
Source code in src/bloqade/analog/builder/coupling.py
def __bloqade_ir__(self):\n \"\"\"\n Generate the intermediate representation (IR) for the Hyperfine level coupling.\n\n Args:\n None\n\n Returns:\n IR: An intermediate representation of the Hyperfine level coupling sequence.\n\n Raises:\n None\n\n Note:\n This method is used internally by the Bloqade framework.\n \"\"\"\n from bloqade.analog.ir.control.sequence import hyperfine\n\n return hyperfine\n
Specify the complex-valued Rabi field of your program.
The Rabi field is composed of a real-valued Amplitude and Phase field.
Returns:
Name Type Description RabiRabi
A program node representing the Rabi field.
Note
Next possible steps to build your program are creating the RabiAmplitude field and RabiPhase field of the field: - ...rabi.amplitude: To create the Rabi amplitude field - ...rabi.phase: To create the Rabi phase field
This node represents level coupling of the Rydberg state.
Examples:
- To reach the node from the start node:\n\n>>> node = bloqade.start.rydberg\n>>> type(node)\n<class 'bloqade.builder.coupling.Rydberg'>\n\n- Rydberg level coupling has two reachable field nodes:\n\n - detuning term (See also [`Detuning`][bloqade.builder.field.Detuning])\n - rabi term (See also [`Rabi`][bloqade.builder.field.Rabi])\n\n>>> ryd_detune = bloqade.start.rydberg.detuning\n>>> ryd_rabi = bloqade.start.rydberg.rabi\n
Generate the intermediate representation (IR) for the Rydberg level coupling.
Returns:
Name Type Description IR
An intermediate representation of the Rydberg level coupling sequence.
Note
This method is used internally by the Bloqade framework.
Source code in src/bloqade/analog/builder/coupling.py
def __bloqade_ir__(self):\n \"\"\"\n Generate the intermediate representation (IR) for the Rydberg level coupling.\n\n Args:\n None\n\n Returns:\n IR: An intermediate representation of the Rydberg level coupling sequence.\n\n Raises:\n None\n\n Note:\n This method is used internally by the Bloqade framework.\n \"\"\"\n from bloqade.analog.ir.control.sequence import rydberg\n\n return rydberg\n
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
...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
Address a single atom (or multiple) as part of defining the spatial modulation component of a drive. You can specify the atoms to target as a list of labels and a list of scales. The scales are used to multiply the waveform that is applied to the atom. You can also specify a single label and scale to target a single atom.
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.)
>>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi\n# to target a single atom with a waveform\n>>> one_location_prog = prog.location(0)\n# to target a single atom with a scale\n>>> one_location_prog = prog.location(0, 0.5)\n# to target multiple atoms with same waveform\n>>> multi_location_prog = prog.location([0, 2])\n# to target multiple atoms with different scales\n>>> multi_location_prog = prog.location([0, 2], [0.5, \"scale\"])\n
You can now do:
...location(labels, scales).linear(start, stop, duration) : to apply a linear waveform
...location(labels, scales).constant(value, duration) : to apply a constant waveform
...location(labels, scales).poly([coefficients], duration) : to apply a polynomial waveform
...location(labels, scales).apply(wf:bloqade.ir.Waveform): to apply a pre-defined waveform
...location(labels, scales).piecewise_linear([durations], [values]): to apply a piecewise linear waveform
...location(labels, scales).piecewise_constant([durations], [values]): to apply a piecewise constant waveform
...location(labels, scales).fn(f(t,..)): to apply a function as a waveform
Source code in src/bloqade/analog/builder/field.py
def location(\n self,\n labels: Union[List[int], int],\n scales: Union[List[ScalarType], ScalarType, None] = None,\n) -> \"Location\":\n \"\"\"Address a single atom (or multiple) atoms.\n\n Address a single atom (or multiple) as part of defining the spatial\n modulation component of a drive. You can specify the atoms to target\n as a list of labels and a list of scales. The scales are used to\n multiply the waveform that is applied to the atom. You can also specify\n a single label and scale to target a single atom.\n\n Next steps to build your program include choosing the waveform that\n will be summed with the spatial modulation to create a drive.\n\n The drive by itself, or the sum of subsequent drives (created by just\n chaining the construction of drives) will become the field.\n (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.)\n\n ### Usage Example:\n ```\n >>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi\n # to target a single atom with a waveform\n >>> one_location_prog = prog.location(0)\n # to target a single atom with a scale\n >>> one_location_prog = prog.location(0, 0.5)\n # to target multiple atoms with same waveform\n >>> multi_location_prog = prog.location([0, 2])\n # to target multiple atoms with different scales\n >>> multi_location_prog = prog.location([0, 2], [0.5, \"scale\"])\n ```\n\n - You can now do:\n - `...location(labels, scales).linear(start, stop, duration)` : to apply\n a linear waveform\n - `...location(labels, scales).constant(value, duration)` : to apply\n a constant waveform\n - `...location(labels, scales).poly([coefficients], duration)` : to apply\n a polynomial waveform\n - `...location(labels, scales).apply(wf:bloqade.ir.Waveform)`: to apply\n a pre-defined waveform\n - `...location(labels, scales).piecewise_linear([durations], [values])`:\n to apply\n a piecewise linear waveform\n - `...location(labels, scales).piecewise_constant([durations], [values])`:\n to apply\n a piecewise constant waveform\n - `...location(labels, scales).fn(f(t,..))`: to apply a function as a\n waveform\n\n \"\"\"\n return self._location(labels, scales)\n
Address all the atoms scaling each atom with an element of the list or define a variable name for the scale list to be assigned later by defining a name and using assign or batch_assign later.
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.)
>>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi\n\n# assign a literal list of values to scale each atom\n>>> one_location_prog = prog.scale([0.1, 0.2, 0.3])\n# assign a variable name to be assigned later\n>>> one_location_prog = prog.scale(\"a\")\n# \"a\" can be assigned in the END of the program during variable assignment\n# using a list of values, indicating the scaling for each atom\n>>> single_assignment = ...assign(a = [0.1, 0.2, 0.3])\n# a list of lists, indicating a set of atoms should be targeted\n# for each task in a batch.\n>>> batch_assignment = ...batch_assign(a = [list_1, list_2, list_3,...])\n
You can now do:
...scale(coeffs).linear(start, stop, duration) : to apply a linear waveform
...scale(coeffs).constant(value, duration) : to apply a constant waveform
...scale(coeffs).poly([coefficients], duration) : to apply a polynomial waveform
...scale(coeffs).apply(wf:bloqade.ir.Waveform): to apply a pre-defined waveform
...scale(coeffs).piecewise_linear(durations, values): to apply a piecewise linear waveform
...scale(coeffs).piecewise_constant(durations, values): to apply a piecewise constant waveform
...scale(coeffs).fn(f(t,..)): to apply a function as a waveform
Source code in src/bloqade/analog/builder/field.py
def scale(self, coeffs: Union[str, List[ScalarType]]) -> \"Scale\":\n \"\"\"\n Address all the atoms scaling each atom with an element of the list\n or define a variable name for the scale list to be assigned later by\n defining a `name` and using `assign` or `batch_assign` later.\n\n Next steps to build your program include choosing the waveform that\n will be summed with the spatial modulation to create a drive.\n\n The drive by itself, or the sum of subsequent drives (created by just\n chaining the construction of drives) will become the field\n (e.g. Detuning Field, Real-Valued Rabi Amplitude/Rabi Phase Field, etc.)\n\n ### Usage Example:\n ```\n >>> prog = start.add_position([(0,0),(1,4),(2,8)]).rydberg.rabi\n\n # assign a literal list of values to scale each atom\n >>> one_location_prog = prog.scale([0.1, 0.2, 0.3])\n # assign a variable name to be assigned later\n >>> one_location_prog = prog.scale(\"a\")\n # \"a\" can be assigned in the END of the program during variable assignment\n # using a list of values, indicating the scaling for each atom\n >>> single_assignment = ...assign(a = [0.1, 0.2, 0.3])\n # a list of lists, indicating a set of atoms should be targeted\n # for each task in a batch.\n >>> batch_assignment = ...batch_assign(a = [list_1, list_2, list_3,...])\n\n ```\n\n - You can now do:\n - `...scale(coeffs).linear(start, stop, duration)` : to apply\n a linear waveform\n - `...scale(coeffs).constant(value, duration)` : to apply\n a constant waveform\n - `...scale(coeffs).poly([coefficients], duration)` : to apply\n a polynomial waveform\n - `...scale(coeffs).apply(wf:bloqade.ir.Waveform)`: to apply\n a pre-defined waveform\n - `...scale(coeffs).piecewise_linear(durations, values)`: to\n apply a piecewise linear waveform\n - `...scale(coeffs).piecewise_constant(durations, values)`: to\n apply a piecewise constant waveform\n - `...scale(coeffs).fn(f(t,..))`: to apply a function as a waveform\n\n \"\"\"\n from bloqade.analog.builder.spatial import Scale\n\n return Scale(coeffs, self)\n
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(...): Scale atoms by their indices
...amplitude.scale(...): Scale each atom with a value from a list or assign a variable name to be assigned later
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(...): Scale atoms by their indices
...amplitude.scale(...): Scale each atom with a value from a list or assign a variable name to be assigned later
Name Type Description Default args_listList[Union[str, Variable]]
List of argument names or Variable objects to be added.
required
Returns:
Name Type Description ArgsArgs
A new instance of the Args class with the specified arguments.
Raises:
Type Description TypeError
If args_list contains invalid types.
Note
This method is useful for deferring the value assignment of certain variables to runtime.
Source code in src/bloqade/analog/builder/pragmas.py
def args(self, args_list: List[Union[str, Variable]]) -> \"Args\":\n \"\"\"\n Add arguments to the current program.\n\n Args:\n args_list (List[Union[str, Variable]]): List of argument names or Variable\n objects to be added.\n\n Returns:\n Args: A new instance of the Args class with the specified arguments.\n\n Raises:\n TypeError: If args_list contains invalid types.\n\n Note:\n This method is useful for deferring the value assignment of certain\n variables to runtime.\n \"\"\"\n from bloqade.analog.builder.args import Args\n\n return Args(args_list, self)\n
Assign values to variables declared previously in the program.
Parameters:
Name Type Description Default **assignments
Key-value pairs where the key is the variable name and the value is the value to assign.
{}
Returns:
Name Type Description AssignAssign
A new instance of the Assign class with the specified assignments.
Raises:
Type Description ValueError
If an invalid assignment is provided.
Note
This is reserved for variables that should take single values or for spatial modulations created with .scale(str). After assigning values, you can choose a backend for emulation or execution.
...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).batch_assign(assignments): assign multiple values for a parameter sweep
...assign(assignments).parallelize(cluster_spacing): parallelize the program register
...assign(assignments).args([previously_defined_vars]): defer value assignment to runtime
Source code in src/bloqade/analog/builder/pragmas.py
def assign(self, **assignments) -> \"Assign\":\n \"\"\"\n Assign values to variables declared previously in the program.\n\n Args:\n **assignments: Key-value pairs where the key is the variable name and\n the value is the value to assign.\n\n Returns:\n Assign: A new instance of the Assign class with the specified\n assignments.\n\n Raises:\n ValueError: If an invalid assignment is provided.\n\n Note:\n This is reserved for variables that should take single values or for\n spatial modulations created with `.scale(str)`. After assigning values,\n you can choose a backend for emulation or execution.\n\n ### Usage Examples:\n ```\n # define geometry\n >>> reg = bloqade.start\n ... .add_position([(0,0),(1,1),(2,2),(3,3)])\n # define variables in program\n >>> seq = reg.rydberg.detuning.uniform\n ... .linear(start=\"ival\", stop=1, duration=\"span_time\")\n # assign values to variables\n >>> seq = seq.assign(span_time=0.5, ival=0.0)\n ```\n\n - Next steps:\n - `...assign(assignments).bloqade`: select the bloqade local emulator backend\n - `...assign(assignments).braket`: select braket local emulator or QuEra hardware\n - `...assign(assignments).device(specifier_string)`: select backend by specifying a\n string\n - `...assign(assignments).batch_assign(assignments)`: assign multiple values for a\n parameter sweep\n - `...assign(assignments).parallelize(cluster_spacing)`: parallelize the program\n register\n - `...assign(assignments).args([previously_defined_vars])`: defer value assignment to\n runtime\n \"\"\"\n from bloqade.analog.builder.assign import Assign\n\n return Assign(assignments, parent=self)\n
Assign multiple values to variables for creating a parameter sweep.
Parameters:
Name Type Description Default __batch_paramsList[Dict[str, ParamType]]
List of dictionaries where each dictionary contains variable assignments for one set of parameters.
[]**assignmentsList[ParamType]
Key-value pairs where the key is the variable name and the value is a list of values to assign.
{}
Returns:
Type Description Union[BatchAssign, ListAssign]
Union[BatchAssign, ListAssign]: A new instance of BatchAssign or ListAssign class with the specified assignments.
Raises:
Type Description ValueError
If both __batch_params and assignments are provided.
Note
Bloqade handles the multiple programs generated by this method and treats them as a unified object for easy post-processing. Ensure all lists of values are of the same length as Bloqade will not perform a Cartesian product.
...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
...batch_assign(assignments).parallelize(cluster_spacing): parallelize the program register
...batch_assign(assignments).args([previously_defined_vars]): defer value assignment to runtime
Source code in src/bloqade/analog/builder/pragmas.py
def batch_assign(\n self,\n __batch_params: List[Dict[str, ParamType]] = [],\n **assignments: List[ParamType],\n) -> Union[\"BatchAssign\", \"ListAssign\"]:\n \"\"\"\n Assign multiple values to variables for creating a parameter sweep.\n\n Args:\n __batch_params (List[Dict[str, ParamType]], optional): List of dictionaries\n where each dictionary contains variable assignments for one set of parameters.\n **assignments (List[ParamType]): Key-value pairs where the key is the variable\n name and the value is a list of values to assign.\n\n Returns:\n Union[BatchAssign, ListAssign]: A new instance of BatchAssign or ListAssign\n class with the specified assignments.\n\n Raises:\n ValueError: If both __batch_params and assignments are provided.\n\n Note:\n Bloqade handles the multiple programs generated by this method and treats them\n as a unified object for easy post-processing. Ensure all lists of values are of\n the same length as Bloqade will not perform a Cartesian product.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position([(0,0), (0, \"atom_distance\")])\n >>> prog = reg.rydberg.rabi.amplitude.uniform.constant(\"value\", 5.0)\n >>> var_assigned_prog = prog.batch_assign(value=[1.0, 2.0, 3.0],\n atom_distance=[1.0, 2.0, 3.0])\n ```\n\n - Next steps:\n - `...batch_assign(assignments).bloqade`: select the bloqade local emulator backend\n - `...batch_assign(assignments).braket`: select braket local emulator or QuEra hardware\n - `...batch_assign(assignments).device(specifier_string)`: select backend by specifying\n a string\n - `...batch_assign(assignments).parallelize(cluster_spacing)`: parallelize the program\n register\n - `...batch_assign(assignments).args([previously_defined_vars])`: defer value assignment\n to runtime\n \"\"\"\n from bloqade.analog.builder.assign import ListAssign, BatchAssign\n\n if len(__batch_params) > 0 and assignments:\n raise ValueError(\"batch_params and assignments cannot be used together.\")\n\n if len(__batch_params) > 0:\n return ListAssign(__batch_params, parent=self)\n else:\n return BatchAssign(assignments, parent=self)\n
Parallelize the current problem by duplicating the geometry to utilize all available space/qubits on hardware.
Parameters:
Name Type Description Default cluster_spacingLiteralType
Specifies the spacing between clusters in micrometers.
required
Returns:
Name Type Description ParallelizeParallelize
A new instance of the Parallelize class with the specified cluster spacing.
Raises:
Type Description ValueError
If the cluster_spacing is not a valid value.
Note
After calling this method, you can choose a backend for emulation or execution. Options include bloqade for a local emulator, braket for a local emulator or QuEra hardware on the cloud, or specifying a device with a string.
>>> reg = start.add_position((0,0)).rydberg.rabi.uniform.amplitude.constant(1.0, 1.0)\n# copy-paste the geometry and waveforms\n>>> parallelized_prog = reg.parallelize(24)\n
Next steps:
...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
Source code in src/bloqade/analog/builder/pragmas.py
def parallelize(self, cluster_spacing: LiteralType) -> \"Parallelize\":\n \"\"\"\n Parallelize the current problem by duplicating the geometry to utilize\n all available space/qubits on hardware.\n\n Args:\n cluster_spacing (LiteralType): Specifies the spacing between clusters\n in micrometers.\n\n Returns:\n Parallelize: A new instance of the Parallelize class with the specified\n cluster spacing.\n\n Raises:\n ValueError: If the cluster_spacing is not a valid value.\n\n Note:\n After calling this method, you can choose a backend for emulation or\n execution. Options include `bloqade` for a local emulator, `braket` for\n a local emulator or QuEra hardware on the cloud, or specifying a device\n with a string.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position((0,0)).rydberg.rabi.uniform.amplitude.constant(1.0, 1.0)\n # copy-paste the geometry and waveforms\n >>> parallelized_prog = reg.parallelize(24)\n ```\n\n - Next steps:\n - `...parallelize(cluster_spacing).bloqade`: select the bloqade local emulator backend\n - `...parallelize(cluster_spacing).braket`: select braket local emulator or QuEra\n hardware on the cloud\n - `...parallelize(cluster_spacing).device(specifier_string)`: select backend by\n specifying a string\n \"\"\"\n from bloqade.analog.builder.parallelize import Parallelize\n\n return Parallelize(cluster_spacing, self)\n
The node specify a uniform spacial modulation. Which is ready to apply waveform (See Waveform for available waveform options)
Examples:
- To hit this node from the start node:\n\n>>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)])\n>>> loc = reg.rydberg.detuning.uniform\n\n- Apply Linear waveform:\n\n>>> wv = bloqade.ir.Linear(start=0,stop=1,duration=0.5)\n>>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)])\n>>> loc = reg.rydberg.detuning.uniform.apply(wv)\n
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:
>>> from numpy import pi\n>>> seq = start.rydberg.rabi.amplitude.constant(2.0 * pi, 4.5)\n# choose a geometry of interest to apply the program on\n>>> from bloqade.analog.atom_arrangement import Chain, Kagome\n>>> complete_program = Chain(10).apply(seq)\n# you can .apply to as many geometries as you like\n>>> another_complete_program = Kagome(3).apply(seq)\n
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:
Source code in src/bloqade/analog/builder/start.py
@beartype\ndef apply(self, sequence: SequenceExpr) -> SequenceBuilder:\n \"\"\"\n Apply a pre-built sequence to a program.\n\n This allows you to build a program independent of any geometry\n and then `apply` the program to said geometry. Or, if you have a\n program you would like to try on multiple geometries you can\n trivially do so with this.\n\n Example Usage:\n ```\n >>> from numpy import pi\n >>> seq = start.rydberg.rabi.amplitude.constant(2.0 * pi, 4.5)\n # choose a geometry of interest to apply the program on\n >>> from bloqade.analog.atom_arrangement import Chain, Kagome\n >>> complete_program = Chain(10).apply(seq)\n # you can .apply to as many geometries as you like\n >>> another_complete_program = Kagome(3).apply(seq)\n ```\n\n - From here you can now do:\n - `...assign(assignments).bloqade`: select the bloqade\n local emulator backend\n - `...assign(assignments).braket`: select braket\n local emulator or QuEra hardware\n - `...assign(assignments).device(specifier_string)`: select\n backend by specifying a string\n - Assign multiple values to a single variable for a parameter sweep:\n - `...assign(assignments).batch_assign(assignments)`:\n - Parallelize the program register, duplicating the geometry and waveform\n sequence to take advantage of all available\n space/qubits on the QPU:\n - `...assign(assignments).parallelize(cluster_spacing)`\n - Defer value assignment of certain variables to runtime:\n - `...assign(assignments).args([previously_defined_vars])`\n\n \"\"\"\n return SequenceBuilder(sequence, self)\n
Copy or \"record\" the value at the end of the waveform into a variable so that it can be used in another place.
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.
If you specified a spatial modulation (e.g. uniform, location,scale) 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.
# define program of interest\n>>> from bloqade import start\n>>> prog = start.rydberg.rabi.amplitude.uniform\n>>> prog_with_wf = prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\nvalues=[0.0, 2.0, 2.0, 0.0])\n# We now slice the piecewise_linear from above and record the\n# value at the end of that slice. We then use that value\n# to construct a new waveform that can be appended to the previous\n# one without introducing discontinuity (refer to the\n# \"Quantum Scar Dynamics\" tutorial for how this could be handy)\n>>> prog_with_record = prog_with_wf.slice(0.0, 1.0).record(\"end_of_wf\")\n>>> record_applied_prog = prog_with_record.linear(start=\"end_of_wf\"\n, stop=0.0, duration=0.3)\n
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).scale(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 ```
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef record(self, name: str) -> \"Record\":\n \"\"\"\n Copy or \"record\" the value at the end of the waveform into a variable\n so that it can be used in another place.\n\n A common design pattern is to couple this with `.slice()` considering\n you may not know exactly what the end value of a `.slice()` is,\n especially in parameter sweeps where it becomes cumbersome to handle.\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n # define program of interest\n >>> from bloqade import start\n >>> prog = start.rydberg.rabi.amplitude.uniform\n >>> prog_with_wf = prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\n values=[0.0, 2.0, 2.0, 0.0])\n # We now slice the piecewise_linear from above and record the\n # value at the end of that slice. We then use that value\n # to construct a new waveform that can be appended to the previous\n # one without introducing discontinuity (refer to the\n # \"Quantum Scar Dynamics\" tutorial for how this could be handy)\n >>> prog_with_record = prog_with_wf.slice(0.0, 1.0).record(\"end_of_wf\")\n >>> record_applied_prog = prog_with_record.linear(start=\"end_of_wf\"\n , stop=0.0, duration=0.3)\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...slice(start, stop).linear(start, stop, duration)`:\n to append another linear waveform\n - `...slice(start, stop).constant(value, duration)`:\n to append a constant waveform\n - `...slice(start, stop).piecewise_linear()`:\n to append a piecewise linear waveform\n - `...slice(start, stop).piecewise_constant()`:\n to append a piecewise constant waveform\n - `...slice(start, stop).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...slice(start, stop).apply(wf:bloqade.ir.Waveform)`:\n to append a pre-defined waveform\n - `...slilce(start, stop).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...slice(start, stop).uniform`:\n To address all atoms in the field\n - `...slice(start, stop).location(int)`:\n To address an atom at a specific location via index\n - `...slice(start, stop).scale(str)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by specifying\n a single variable and then assigning it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...slice(start, stop).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...slice(start, stop)\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...slice(start, stop).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...slice(start, stop).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...slice(start, stop).bloqade`:\n to run on the Bloqade local emulator\n - `...slice(start, stop).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...slice(start, stop).parallelize(spacing)`\n - Start targeting another level coupling\n - `...slice(start, stop).rydberg`:\n to target the Rydberg level coupling\n - `...slice(start, stop).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...slice(start, stop).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...slice(start, stop).phase`:\n to target the real-valued Rabi Phase field\n - `...slice(start, stop).detuning`:\n to target the Detuning field\n - `...slice(start, stop).rabi`:\n to target the complex-valued Rabi field\n ```\n \"\"\"\n return Record(name, self)\n
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,scale) 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.
# define a program with a waveform of interest\n>>> from bloqade import start\n>>> prog = start.add_position((0,0)).rydberg.rabi.amplitude.uniform\n>>> prog_with_wf = prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\nvalues=[0.0, 2.0, 2.0, 0.0])\n# instead of using the full waveform we opt to only take the first 1 us\n>>> prog_with_slice = prog_with_wf.slice(0.0, 1.0)\n# you may use variables as well\n>>> prog_with_slice = prog_with_wf.slice(\"start\", \"end\")\n
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).scale(...)
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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef slice(\n self,\n start: Optional[ScalarType] = None,\n stop: Optional[ScalarType] = None,\n) -> \"Slice\":\n \"\"\"\n Indicate that you only want a portion of your waveform to be used in\n the program.\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n\n ### Usage Example:\n ```\n # define a program with a waveform of interest\n >>> from bloqade import start\n >>> prog = start.add_position((0,0)).rydberg.rabi.amplitude.uniform\n >>> prog_with_wf = prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\n values=[0.0, 2.0, 2.0, 0.0])\n # instead of using the full waveform we opt to only take the first 1 us\n >>> prog_with_slice = prog_with_wf.slice(0.0, 1.0)\n # you may use variables as well\n >>> prog_with_slice = prog_with_wf.slice(\"start\", \"end\")\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...slice(start, stop).linear(start, stop, duration)`:\n to append another linear waveform\n - `...slice(start, stop).constant(value, duration)`:\n to append a constant waveform\n - `...slice(start, stop).piecewise_linear()`:\n to append a piecewise linear waveform\n - `...slice(start, stop).piecewise_constant()`:\n to append a piecewise constant waveform\n - `...slice(start, stop).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...slice(start, stop).apply(wf:bloqade.ir.Waveform)`:\n to append a pre-defined waveform\n - `...slilce(start, stop).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...slice(start, stop).uniform`:\n To address all atoms in the field\n - `...slice(start, stop).location(int)`:\n To address an atom at a specific location via index\n - `...slice(start, stop).scale(...)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by specifying\n a single variable and then assigning it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...slice(start, stop).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...slice(start, stop)\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...slice(start, stop).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...slice(start, stop).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...slice(start, stop).bloqade`:\n to run on the Bloqade local emulator\n - `...slice(start, stop).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...slice(start, stop).parallelize(spacing)`\n - Start targeting another level coupling\n - `...slice(start, stop).rydberg`:\n to target the Rydberg level coupling\n - `...slice(start, stop).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...slice(start, stop).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...slice(start, stop).phase`:\n to target the real-valued Rabi Phase field\n - `...slice(start, stop).detuning`:\n to target the Detuning field\n - `...slice(start, stop).rabi`:\n to target the complex-valued Rabi field\n \"\"\"\n return Slice(start, stop, self)\n
Apply a Waveform built previously to current location(s).
If you specified a spatial modulation (e.g. uniform, location,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n# build our waveform independently of the main program\n>>> from bloqade import piecewise_linear\n>>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3],\nvalues=[0.0, 2.0, 2.0, 0.0])\n>>> prog.apply(wf)\n
Your next steps include:
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([durations], [values]): to append a piecewise linear waveform
...apply(waveform).piecewise_constant([durations], [values]): 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).location(int): To address an atom at a specific location via index
...apply(waveform).scale(...)
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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef apply(self, wf: ir.Waveform) -> \"Apply\":\n \"\"\"\n Apply a [`Waveform`][bloqade.ir.control.Waveform] built previously to\n current location(s).\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n # build our waveform independently of the main program\n >>> from bloqade import piecewise_linear\n >>> wf = piecewise_linear(durations=[0.3, 2.5, 0.3],\n values=[0.0, 2.0, 2.0, 0.0])\n >>> prog.apply(wf)\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...apply(waveform).linear(start, stop, duration)`:\n to append another linear waveform\n - `...apply(waveform).constant(value, duration)`:\n to append a constant waveform\n - `...apply(waveform).piecewise_linear([durations], [values])`:\n to append a piecewise linear waveform\n - `...apply(waveform).piecewise_constant([durations], [values])`:\n to append a piecewise constant waveform\n - `...apply(waveform).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...apply(waveform).apply(waveform)`:\n to append a pre-defined waveform\n - `...apply(waveform).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...apply(waveform).slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...apply(waveform).record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...apply(waveform).uniform`: To address all atoms in the field\n - `...apply(waveform).location(int)`:\n To address an atom at a specific location via index\n - `...apply(waveform).scale(...)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by specifying a\n single variable and then assigning it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...apply(waveform).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...apply(waveform).batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...apply(waveform).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...apply(waveform).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...apply(waveform).bloqade`:\n to run on the Bloqade local emulator\n - `...apply(waveform).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...apply(waveform).parallelize(spacing)`\n - Start targeting another level coupling\n - `...apply(waveform).rydberg`: to target the Rydberg level coupling\n - `...apply(waveform).hyperfine`: to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...apply(waveform).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...apply(waveform).phase`:\n to target the real-valued Rabi Phase field\n - `...apply(waveform).detuning`:\n to target the Detuning field\n - `...apply(waveform).rabi`:\n to target the complex-valued Rabi field\n \"\"\"\n return Apply(wf, self)\n
Append or assign a constant waveform to the current location(s).
If you specified a spatial modulation (e.g. uniform, location,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n# apply a constant waveform of 1.9 radians/us for 0.5 us\n>>> prog.constant(value=1.9,duration=0.5)\n
Your next steps include:
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([durations], [values]): to append a piecewise linear waveform
...constant(value, duration) .piecewise_constant([durations], [values]): 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).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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef constant(self, value: ScalarType, duration: ScalarType) -> \"Constant\":\n \"\"\"\n Append or assign a constant waveform to the current location(s).\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n # apply a constant waveform of 1.9 radians/us for 0.5 us\n >>> prog.constant(value=1.9,duration=0.5)\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...constant(value, duration).linear(start, stop, duration)`:\n to append another linear waveform\n - `...constant(value, duration).constant(value, duration)`:\n to append a constant waveform\n - `...constant(value, duration)\n .piecewise_linear([durations], [values])`:\n to append a piecewise linear waveform\n - `...constant(value, duration)\n .piecewise_constant([durations], [values])`:\n to append a piecewise constant waveform\n - `...constant(value, duration).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...constant(value, duration).apply(wf:bloqade.ir.Waveform)`:\n to append a pre-defined waveform\n - `...constant(value, duration).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...constant(value, duration).slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...constant(value, duration).record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...constant(value, duration).uniform`:\n To address all atoms in the field\n - `...constant(value, duration).scale(...)`:\n To address an atom at a specific location via index\n - `...constant(value, duration).location(int)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by specifying\n a single variable and then assigning it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...constant(value, duration).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...constant(value, duration)\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...constant(value, duration).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...constant(value, duration).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...constant(value, duration).bloqade`:\n to run on the Bloqade local emulator\n - `...constant(value, duration).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...constant(start, stop, duration).parallelize(spacing)`\n - Start targeting another level coupling\n - `...constant(value, duration).rydberg`:\n to target the Rydberg level coupling\n - `...constant(value, duration).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current\n level coupling (previously selected as `rydberg` or `hyperfine`):\n - `...constant(value, duration).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...constant(value, duration).phase`:\n to target the real-valued Rabi Phase field\n - `...constant(value, duration).detuning`:\n to target the Detuning field\n - `...constant(value, duration).rabi`:\n to target the complex-valued Rabi field\n\n \"\"\"\n return Constant(value, duration, self)\n
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,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n# define our custom waveform. It must have one argument\n# be time followed by any other number of arguments that can\n# be assigned a value later in the program via `.assign` or `.batch_assign`\n>>> def custom_waveform_function(t, arg1, arg2):\n return arg1*t + arg2\n>>> prog = prog.fn(custom_waveform_function, duration = 0.5)\n# assign values\n>>> assigned_vars_prog = prog.assign(arg1 = 1.0, arg2 = 2.0)\n# or go for batching!\n>>> assigned_vars_batch_prog = prog.assign(arg1 = 1.0, arg2 = [1.0, 2.0, 3.0])\n
Your next steps include:
Continue building your waveform via:
...fn(f(t,...)) .linear(start, stop, duration): to append another linear waveform
...fn(f(t,...)) .constant(value, duration): to append a constant waveform
...fn(f(t,...)) .piecewise_linear(durations, values): to append a piecewise linear waveform
...fn(f(t,...)) .piecewise_constant(durations, values): to append a piecewise constant waveform
...fn(f(t,...)) .poly([coefficients], duration): to append a polynomial waveform
...fn(f(t,...)) .apply(waveform): to append a pre-defined waveform
...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)
Save the ending value of your waveform to be reused elsewhere
...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: To address all atoms in the field
...fn(f(t,...)).scale(...): 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 specifying a single variable and then assigning it a list of coordinates
Assign values to pre-existing variables via:
...fn(f(t,...)) .assign(variable_name = value): to assign a single value to a variable
...fn(f(t,...)) .batch_assign(variable_name = [value1, ...]): to assign multiple values to a variable
...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: to run on Braket local emulator or QuEra hardware remotely
...fn(f(t,...)).bloqade: to run on the Bloqade local emulator
...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)
Start targeting another level coupling
...fn(f(t,...)).rydberg: to target the Rydberg level coupling
...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: to target the real-valued Rabi Amplitude field
...fn(f(t,...)).phase: to target the real-valued Rabi Phase field
...fn(f(t,...)).detuning: to target the Detuning field
...fn(f(t,...)).rabi: to target the complex-valued Rabi field
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef fn(self, fn: Callable, duration: ScalarType) -> \"Fn\":\n \"\"\"\n Append or assign a custom function as a waveform.\n\n The function must have its first argument be that of time but\n can also have other arguments which are treated as variables.\n You can assign values to later in the program via `.assign` or `.batch_assign`.\n\n The function must also return a singular float value.\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### ### Usage Examples:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n # define our custom waveform. It must have one argument\n # be time followed by any other number of arguments that can\n # be assigned a value later in the program via `.assign` or `.batch_assign`\n >>> def custom_waveform_function(t, arg1, arg2):\n return arg1*t + arg2\n >>> prog = prog.fn(custom_waveform_function, duration = 0.5)\n # assign values\n >>> assigned_vars_prog = prog.assign(arg1 = 1.0, arg2 = 2.0)\n # or go for batching!\n >>> assigned_vars_batch_prog = prog.assign(arg1 = 1.0, arg2 = [1.0, 2.0, 3.0])\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...fn(f(t,...))\n .linear(start, stop, duration)`: to append another linear waveform\n - `...fn(f(t,...))\n .constant(value, duration)`: to append a constant waveform\n - `...fn(f(t,...))\n .piecewise_linear(durations, values)`:\n to append a piecewise linear waveform\n - `...fn(f(t,...))\n .piecewise_constant(durations, values)`:\n to append a piecewise constant waveform\n - `...fn(f(t,...))\n .poly([coefficients], duration)`: to append a polynomial waveform\n - `...fn(f(t,...))\n .apply(waveform)`: to append a pre-defined waveform\n - `...fn(f(t,...))\n .fn(f(t,...))`: to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...fn(f(t,...)).slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...fn(f(t,...)).record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...fn(f(t,...)).uniform`:\n To address all atoms in the field\n - `...fn(f(t,...)).scale(...)`:\n To address an atom at a specific location via index\n - ...fn(f(t,...)).location(int)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by\n specifying a single variable and then assigning it a\n list of coordinates\n - Assign values to pre-existing variables via:\n - `...fn(f(t,...))\n .assign(variable_name = value)`: to assign a single value to a variable\n - `...fn(f(t,...))\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...fn(f(t,...))\n .args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...fn(f(t,...)).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...fn(f(t,...)).bloqade`:\n to run on the Bloqade local emulator\n - `...fn(f(t,...)).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...fn(f(t,...)).parallelize(spacing)`\n - Start targeting another level coupling\n - `...fn(f(t,...)).rydberg`:\n to target the Rydberg level coupling\n - `...fn(f(t,...)).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...fn(f(t,...)).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...fn(f(t,...)).phase`:\n to target the real-valued Rabi Phase field\n - `...fn(f(t,...)).detuning`:\n to target the Detuning field\n - `...fn(f(t,...)).rabi`:\n to target the complex-valued Rabi field\n\n \"\"\"\n return Fn(fn, duration, self)\n
Append or assign a linear waveform to the current location(s).
If you specified a spatial modulation (e.g. uniform, location,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n# apply a linear waveform that goes from 0 to 1 radians/us in 0.5 us\n>>> prog.linear(start=0,stop=1,duration=0.5)\n
Your next steps include:
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([durations], [values]): to append a piecewise linear waveform
...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
...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).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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef linear(\n self, start: ScalarType, stop: ScalarType, duration: ScalarType\n) -> \"Linear\":\n \"\"\"\n\n Append or assign a linear waveform to the current location(s).\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n # apply a linear waveform that goes from 0 to 1 radians/us in 0.5 us\n >>> prog.linear(start=0,stop=1,duration=0.5)\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...linear(start, stop, duration).linear(start, stop, duration)`:\n to append another linear waveform\n - `...linear(start, stop, duration).constant(value, duration)`:\n to append a constant waveform\n - `...linear(start, stop, duration)\n .piecewise_linear([durations], [values])`:\n to append a piecewise linear waveform\n - `...linear(start, stop, duration)\n .piecewise_constant([durations], [values])`:\n to append a piecewise constant waveform\n - `...linear(start, stop, duration).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...linear(start, stop, duration).apply(wf:bloqade.ir.Waveform)`:\n to append a pre-defined waveform\n - `...linear(start, stop, duration).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...linear(start, stop, duration).slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...linear(start, stop, duration).record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...linear(start, stop, duration).uniform`:\n To address all atoms in the field\n - `...linear(start, stop, duration).location(int)`:\n To address atoms at specific location with scaling\n - `...linear(start, stop, duration).scale(...)`\n - To address atoms at specific location with scaling\n - To address multiple atoms at specific locations by specifying\n a single variable and then assigning it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...linear(start, stop, duration).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...linear(start, stop, duration)\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...linear(start, stop, duration).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...linear(start, stop, duration).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...linear(start, stop, duration).bloqade`:\n to run on the Bloqade local emulator\n - `...linear(start, stop, duration).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...linear(start, stop, duration).parallelize(spacing)`\n - Start targeting another level coupling\n - `...linear(start, stop, duration).rydberg`:\n to target the Rydberg level coupling\n - `...linear(start, stop, duration).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...linear(start, stop, duration).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...linear(start, stop, duration).phase`:\n to target the real-valued Rabi Phase field\n - `...linear(start, stop, duration).detuning`:\n to target the Detuning field\n - `...linear(start, stop, duration).rabi`:\n to target the complex-valued Rabi field\n \"\"\"\n\n return Linear(start, stop, duration, self)\n
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,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.rabi.phase.uniform\n# create a staircase, we hold 0.0 rad/us for 1.0 us, then\n# to 1.0 rad/us for 0.5 us before stopping at 0.8 rad/us for 0.9 us.\n>>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[1.0, 0.5, 0.9])\n
Your next steps including:
Continue building your waveform via:
...piecewise_constant([durations], [values]) .linear(start, stop, duration): to append another linear waveform
...piecewise_constant([durations], [values]) .constant(value, duration): to append a constant waveform
...piecewise_constant([durations], [values]) .piecewise_linear([durations], [values]): to append a piecewise linear waveform
...piecewise_constant([durations], [values]) .piecewise_constant([durations], [values]): to append a piecewise constant waveform
...piecewise_constant([durations], [values]) .poly([coefficients], duration): to append a polynomial waveform
...piecewise_constant([durations], [values]) .apply(waveform): to append a pre-defined waveform
...piecewise_constant([durations], [values]).fn(f(t,...)): to append a waveform defined by a python function
...piecewise_constant([durations], [values]).rydberg: to target the Rydberg level coupling
...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: to target the real-valued Rabi Amplitude field
...piecewise_constant([durations], [values]).phase: to target the real-valued Rabi Phase field
...piecewise_constant([durations], [values]).detuning: to target the Detuning field
...piecewise_constant([durations], [values]).rabi: to target the complex-valued Rabi field
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef piecewise_constant(\n self, durations: List[ScalarType], values: List[ScalarType]\n) -> \"PiecewiseConstant\":\n \"\"\"\n Append or assign a piecewise constant waveform to current location(s).\n\n The `durations` argument should have number of elements = len(values).\n `durations` should be the duration PER section of the waveform,\n NON-CUMULATIVE.\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.rabi.phase.uniform\n # create a staircase, we hold 0.0 rad/us for 1.0 us, then\n # to 1.0 rad/us for 0.5 us before stopping at 0.8 rad/us for 0.9 us.\n >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3], values=[1.0, 0.5, 0.9])\n ```\n\n - Your next steps including:\n - Continue building your waveform via:\n - `...piecewise_constant([durations], [values])\n .linear(start, stop, duration)`: to append another linear waveform\n - `...piecewise_constant([durations], [values])\n .constant(value, duration)`: to append a constant waveform\n - `...piecewise_constant([durations], [values])\n .piecewise_linear([durations], [values])`:\n to append a piecewise linear waveform\n - `...piecewise_constant([durations], [values])\n .piecewise_constant([durations], [values])`:\n to append a piecewise constant waveform\n - `...piecewise_constant([durations], [values])\n .poly([coefficients], duration)`: to append a polynomial waveform\n - `...piecewise_constant([durations], [values])\n .apply(waveform)`: to append a pre-defined waveform\n - `...piecewise_constant([durations], [values]).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...piecewise_constant([durations], [values])\n .slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...piecewise_constant([durations], [values])\n .record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...piecewise_constant([durations], [values]).uniform`:\n To address all atoms in the field\n - `...piecewise_constant([durations], [values]).location(int)`:\n To address an atom at a specific location via index\n - `...piecewise_constant([durations], [values]).scale(...)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by\n specifying a single variable and then assigning it a\n list of coordinates\n - Assign values to pre-existing variables via:\n - `...piecewise_constant([durations], [values])\n .assign(variable_name = value)`: to assign a single value to a variable\n - `...piecewise_constant([durations], [values])\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...piecewise_constant([durations], [values])\n .args([\"previously_defined_var\"])`: to defer assignment\n of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...piecewise_constant([durations], [values]).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...piecewise_constant([durations], [values]).bloqade`:\n to run on the Bloqade local emulator\n - `...piecewise_constant([durations], [values]).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...piecewise_constat([durations], [values]).parallelize(spacing)`\n - Start targeting another level coupling\n - `...piecewise_constant([durations], [values]).rydberg`:\n to target the Rydberg level coupling\n - `...piecewise_constant([durations], [values]).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...piecewise_constant(durations, values).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...piecewise_constant([durations], [values]).phase`:\n to target the real-valued Rabi Phase field\n - `...piecewise_constant([durations], [values]).detuning`:\n to target the Detuning field\n - `...piecewise_constant([durations], [values]).rabi`:\n to target the complex-valued Rabi field\n \"\"\"\n return PiecewiseConstant(durations, values, self)\n
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,scale) 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.
>>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n# ramp our waveform up to a certain value, hold it\n# then ramp down. In this case, we ramp up to 2.0 rad/us in 0.3 us,\n# then hold it for 1.5 us before ramping down in 0.3 us back to 0.0 rad/us.\n>>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\nvalues=[0.0, 2.0, 2.0, 0.0])\n
Your next steps include:
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]).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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef piecewise_linear(\n self, durations: List[ScalarType], values: List[ScalarType]\n) -> \"PiecewiseLinear\":\n \"\"\"\n Append or assign a piecewise linear waveform to current location(s),\n where the waveform is formed by connecting `values[i], values[i+1]`\n with linear segments.\n\n The `durations` argument should have # of elements = len(values) - 1.\n `durations` should be the duration PER section of the waveform, NON-CUMULATIVE.\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n # ramp our waveform up to a certain value, hold it\n # then ramp down. In this case, we ramp up to 2.0 rad/us in 0.3 us,\n # then hold it for 1.5 us before ramping down in 0.3 us back to 0.0 rad/us.\n >>> prog.piecewise_linear(durations=[0.3, 2.0, 0.3],\n values=[0.0, 2.0, 2.0, 0.0])\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...piecewise_linear([durations], [values])\n .linear(start, stop, duration)`:\n to append another linear waveform\n - `...piecewise_linear([durations], [values]).constant(value, duration)`:\n to append a constant waveform\n - `...piecewise_linear([durations], [values])\n .piecewise_linear(durations, values)`:\n to append a piecewise linear waveform\n - `...piecewise_linear([durations], [values])\n .piecewise_constant([durations], [values])`:\n to append a piecewise constant waveform\n - `...piecewise_linear([durations], [values])\n .poly([coefficients], duration)`: to append a polynomial waveform\n - `...piecewise_linear([durations], [values]).apply(waveform)`:\n to append a pre-defined waveform\n - `...piecewise_linear([durations], [values]).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...piecewise_linear([durations], [values])\n .slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...piecewise_linear([durations], [values])\n .record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...piecewise_linear([durations], [values]).uniform`:\n To address all atoms in the field\n - `...piecewise_linear([durations], [values]).scale(...)`:\n To address an atom at a specific location via index\n - `...piecewise_linear([durations], [values]).location(int)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by\n specifying a single variable and then assigning it a\n list of coordinates\n - Assign values to pre-existing variables via:\n - `...piecewise_linear([durations], [values])\n .assign(variable_name = value)`:\n to assign a single value to a variable\n - `...piecewise_linear([durations], [values])\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...piecewise_linear([durations], [values])\n .args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...piecewise_linear([durations], [values]).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...piecewise_linear([durations], [values]).bloqade`:\n to run on the Bloqade local emulator\n - `...piecewise_linear([durations], [values]).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...piecewise_linear([durations], [values]).parallelize(spacing)`\n - Start targeting another level coupling\n - `...piecewise_linear([durations], [values]).rydberg`:\n to target the Rydberg level coupling\n - `...piecewise_linear([durations], [values]).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level coupling\n (previously selected as `rydberg` or `hyperfine`):\n - `...piecewise_linear([durations], [values]).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...piecewise_linear([durations], [values]).phase`:\n to target the real-valued Rabi Phase field\n - `...piecewise_linear([durations], [values]).detuning`:\n to target the Detuning field\n - `....rabi`: to target the complex-valued Rabi field\n \"\"\"\n return PiecewiseLinear(durations, values, self)\n
If you specified a spatial modulation (e.g. uniform, location,scale) 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.
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).location(int): To address an atom at a specific location via index
...poly([coeffs], duration).scale(...)
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
Source code in src/bloqade/analog/builder/waveform.py
@beartype\ndef poly(self, coeffs: List[ScalarType], duration: ScalarType) -> \"Poly\":\n \"\"\"\n Append or assign a waveform with a polynomial profile to current location(s).\n\n You pass in a list of coefficients and a duration to this method which obeys\n the following expression:\n\n `\n wv(t) = coeffs[0] + coeffs[1]*t + coeffs[2]*t^2 + ... + coeffs[n]*t^n\n `\n\n If you specified a spatial modulation (e.g. `uniform`, `location`,`scale`)\n previously without a waveform you will now have completed the construction\n of a \"drive\", one or a sum of drives creating a \"field\"\n (e.g. Real-valued Rabi Amplitude/Phase).\n\n If you have already specified a waveform previously you will now be appending\n this waveform to that previous waveform.\n\n ### Usage Example:\n ```\n >>> prog = start.add_position((0,0)).rydberg.detuning.uniform\n >>> coeffs = [-1, 0.5, 1.2]\n # resulting polynomial is:\n # f(t) = -1 + 0.5*t + 1.2*t^2 with duration of\n # 0.5 us\n >>> prog.poly(coeffs, duration=0.5)\n ```\n\n - Your next steps include:\n - Continue building your waveform via:\n - `...poly([coeffs], duration).linear(start, stop, duration)`:\n to append another linear waveform\n - `...poly([coeffs], duration).constant(value, duration)`:\n to append a constant waveform\n - `...poly([coeffs], duration)\n .piecewise_linear([durations], [values])`:\n to append a piecewise linear waveform\n - `...poly([coeffs], duration)\n .piecewise_constant([durations],[values])`:\n to append a piecewise constant waveform\n - `...poly([coeffs], duration).poly([coefficients], duration)`:\n to append a polynomial waveform\n - `...poly([coeffs], duration).apply(waveform)`:\n to append a pre-defined waveform\n - `...poly([coeffs], duration).fn(f(t,...))`:\n to append a waveform defined by a python function\n - Slice a portion of the waveform to be used:\n - `...poly([coeffs], duration).slice(start, stop, duration)`\n - Save the ending value of your waveform to be reused elsewhere\n - `...poly([coeffs], duration).record(\"you_variable_here\")`\n - Begin constructing another drive by starting a new spatial modulation\n (this drive will be summed to the one you just created):\n - `...poly([coeffs], duration).uniform`:\n To address all atoms in the field\n - `...poly([coeffs], duration).location(int)`:\n To address an atom at a specific location via index\n - `...poly([coeffs], duration).scale(...)`\n - To address an atom at a specific location via variable\n - To address multiple atoms at specific locations by\n specifying a single variable and then assigning\n it a list of coordinates\n - Assign values to pre-existing variables via:\n - `...poly([coeffs], duration).assign(variable_name = value)`:\n to assign a single value to a variable\n - `...poly([coeffs], duration)\n .batch_assign(variable_name = [value1, ...])`:\n to assign multiple values to a variable\n - `...poly([coeffs], duration).args([\"previously_defined_var\"])`:\n to defer assignment of a variable to execution time\n - Select the backend you want your program to run on via:\n - `...poly([coeffs], duration).braket`:\n to run on Braket local emulator or QuEra hardware remotely\n - `...poly([coeffs], duration).bloqade`:\n to run on the Bloqade local emulator\n - `...poly([coeffs], duration).device`:\n to specify the backend via string\n - Choose to parallelize your atom geometry,\n duplicating it to fill the whole space:\n - `...poly([coeffs], duration).parallelize(spacing)`\n - Start targeting another level coupling\n - `...poly([coeffs], duration).rydberg`:\n to target the Rydberg level coupling\n - `...poly([coeffs], duration).hyperfine`:\n to target the Hyperfine level coupling\n - Start targeting other fields within your current level\n coupling (previously selected as `rydberg` or `hyperfine`):\n - `...poly([coeffs], duration).amplitude`:\n to target the real-valued Rabi Amplitude field\n - `...poly([coeffs], duration).phase`:\n to target the real-valued Rabi Phase field\n - `...poly([coeffs], duration).detuning`:\n to target the Detuning field\n - `...poly([coeffs], duration).rabi`:\n to target the complex-valued Rabi field\n \"\"\"\n return Poly(coeffs, duration, self)\n
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\" - \"bloqade.python\" - \"bloqade.julia\"
...python().run(shots): to submit to the python emulator and await results
Source code in src/bloqade/analog/builder/backend/bloqade.py
def python(self):\n \"\"\"\n Specify the Bloqade Python backend.\n\n - Possible Next Steps:\n - `...python().run(shots)`:\n to submit to the python emulator and await results\n \"\"\"\n return self.parse().bloqade.python()\n
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
Source code in src/bloqade/analog/builder/backend/braket.py
def aquila(self) -> \"BraketHardwareRoutine\":\n \"\"\"\n Specify QuEra's Aquila QPU on Braket to submit your program to.\n\n The number of shots you specify in the subsequent `.run` method will either:\n - dictate the number of times your program is run\n - dictate the number of times per parameter your program is run if\n you have a variable with batch assignments/intend to conduct\n a parameter sweep\n\n\n - Possible next steps are:\n - `...aquila().run(shots)`: To submit to hardware and WAIT for\n results (blocking)\n - `...aquila().run_async(shots)`: To submit to hardware and immediately\n allow for other operations to occur\n \"\"\"\n return self.parse().braket.aquila()\n
Specify QPU based on the device ARN 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:
...device(arn).run(shots): To submit to hardware and WAIT for results (blocking)
...device(arn).run_async(shots): To submit to hardware and immediately allow for other operations to occur
Source code in src/bloqade/analog/builder/backend/braket.py
def device(self, device_arn) -> \"BraketHardwareRoutine\":\n \"\"\"\n Specify QPU based on the device ARN on Braket to submit your program to.\n\n The number of shots you specify in the subsequent `.run` method will either:\n - dictate the number of times your program is run\n - dictate the number of times per parameter your program is run if\n you have a variable with batch assignments/intend to conduct\n a parameter sweep\n\n\n - Possible next steps are:\n - `...device(arn).run(shots)`: To submit to hardware and WAIT for\n results (blocking)\n - `...device(arn).run_async(shots)`: To submit to hardware and immediately\n allow for other operations to occur\n \"\"\"\n return self.parse().braket.device(device_arn)\n
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
Source code in src/bloqade/analog/builder/backend/braket.py
def local_emulator(self) -> \"BraketLocalEmulatorRoutine\":\n \"\"\"\n Specify the Braket local emulator to submit your program to.\n\n - The number of shots you specify in the subsequent `.run` method will either:\n - dictate the number of times your program is run\n - dictate the number of times per parameter your program is run if\n you have a variable with batch assignments/intend to\n conduct a parameter sweep\n - Possible next steps are:\n - `...local_emulator().run(shots)`: to submit to the emulator\n and await results\n\n \"\"\"\n return self.parse().braket.local_emulator()\n
Module for parsing builder definitions into intermediate representation (IR) using the bloqade library.
This module provides a Parser class for parsing various components of a quantum computing program, including atom arrangements, pulse sequences, analog circuits, and routines. It also defines utility functions for reading addresses, waveforms, drives, sequences, registers, and pragmas from a builder stream.
Source code in src/bloqade/analog/builder/parse/builder.py
def parse_circuit(self, builder: Builder) -> \"AnalogCircuit\":\n \"\"\"\n Parse an analog circuit from the builder.\n\n Args:\n builder (Builder): The builder instance.\n\n Returns:\n AnalogCircuit: The parsed analog circuit.\n \"\"\"\n from bloqade.analog.ir.analog_circuit import AnalogCircuit\n\n self.reset(builder)\n self.read_register()\n self.read_sequence()\n\n circuit = AnalogCircuit(self.register, self.sequence)\n\n return circuit\n
Parse an atom arrangement register from the builder.
Parameters:
Name Type Description Default builderBuilder
The builder instance.
required
Returns:
Type Description Union[AtomArrangement, ParallelRegister]
Union[ir.AtomArrangement, ir.ParallelRegister]: The parsed atom arrangement or parallel register.
Source code in src/bloqade/analog/builder/parse/builder.py
def parse_register(\n self, builder: Builder\n) -> Union[ir.AtomArrangement, ir.ParallelRegister]:\n \"\"\"\n Parse an atom arrangement register from the builder.\n\n Args:\n builder (Builder): The builder instance.\n\n Returns:\n Union[ir.AtomArrangement, ir.ParallelRegister]: The parsed atom arrangement or parallel register.\n \"\"\"\n self.reset(builder)\n self.read_register()\n self.read_pragmas()\n return self.register\n
Type Description Tuple[LevelCoupling, Field, BuilderNode]
Tuple[LevelCoupling, Field, BuilderNode]: A tuple containing the level coupling, field, and spatial modulation.
Source code in src/bloqade/analog/builder/parse/builder.py
def read_address(self, stream) -> Tuple[LevelCoupling, Field, BuilderNode]:\n \"\"\"\n Read an address from the builder stream.\n\n Args:\n stream: The builder stream.\n\n Returns:\n Tuple[LevelCoupling, Field, BuilderNode]: A tuple containing the level coupling, field, and spatial modulation.\n \"\"\"\n spatial = stream.read_next([Location, Uniform, Scale])\n curr = spatial\n\n if curr is None:\n return (None, None, None)\n\n while curr.next is not None:\n if not isinstance(curr.node, SpatialModulation):\n break\n curr = curr.next\n\n if type(spatial.node.__parent__) in [Detuning, RabiAmplitude, RabiPhase]:\n field = spatial.node.__parent__ # field is updated\n if type(field) in [RabiAmplitude, RabiPhase]:\n coupling = field.__parent__.__parent__ # skip Rabi\n else:\n coupling = field.__parent__\n\n # coupling not updated\n if type(coupling) not in [Rydberg, Hyperfine]:\n coupling = None\n return (coupling, field, spatial)\n else: # only spatial is updated\n return (None, None, spatial)\n
Source code in src/bloqade/analog/builder/parse/builder.py
def read_drive(self, head) -> ir.Field:\n \"\"\"\n Read a drive from the builder stream.\n\n Args:\n head: The head of the builder stream.\n\n Returns:\n ir.Field: The drive field.\n \"\"\"\n if head is None:\n return ir.Field({})\n\n sm = head.node.__bloqade_ir__()\n wf, _ = self.read_waveform(head.next)\n\n return ir.Field({sm: wf})\n
Read an atom arrangement register from the builder stream.
Returns:
Type Description AtomArrangement
ir.AtomArrangement: The parsed atom arrangement.
Source code in src/bloqade/analog/builder/parse/builder.py
def read_register(self) -> ir.AtomArrangement:\n \"\"\"\n Read an atom arrangement register from the builder stream.\n\n Returns:\n ir.AtomArrangement: The parsed atom arrangement.\n \"\"\"\n # register is always head of the stream\n register_node = self.stream.read()\n self.register = register_node.node\n\n return self.register\n
Source code in src/bloqade/analog/builder/parse/builder.py
def read_sequence(self) -> ir.Sequence:\n \"\"\"\n Read a sequence from the builder stream.\n\n Returns:\n ir.Sequence: The parsed sequence.\n \"\"\"\n if isinstance(self.stream.curr.node, SequenceBuilder):\n # case with sequence builder object.\n self.sequence = self.stream.read().node._sequence\n return self.sequence\n\n stream = self.stream.copy()\n while stream.curr is not None:\n coupling_builder, field_builder, spatial_head = self.read_address(stream)\n\n if coupling_builder is not None:\n # update to new pulse coupling\n self.coupling_name = coupling_builder.__bloqade_ir__()\n\n if field_builder is not None:\n # update to new field coupling\n self.field_name = field_builder.__bloqade_ir__()\n\n if spatial_head is None:\n break\n\n pulse = self.sequence.pulses.get(self.coupling_name, ir.Pulse({}))\n field = pulse.fields.get(self.field_name, ir.Field({}))\n\n drive = self.read_drive(spatial_head)\n field = field.add(drive)\n\n pulse = ir.Pulse.create(pulse.fields | {self.field_name: field})\n self.sequence = ir.Sequence.create(\n self.sequence.pulses | {self.coupling_name: pulse}\n )\n\n return self.sequence\n
This module provides classes to represent builder nodes and builder streams. A builder node is a single element in the stream, representing a step in a construction process. A builder stream is a sequence of builder nodes, allowing traversal and manipulation of the construction steps.
Next method to get the next item in the builder stream.
Source code in src/bloqade/analog/builder/parse/stream.py
def __next__(self):\n \"\"\"Next method to get the next item in the builder stream.\"\"\"\n node = self.read()\n if node is None:\n raise StopIteration\n return node\n
Build BuilderNode instances from the provided Builder.
Parameters:
Name Type Description Default nodeBuilder
The root Builder instance.
required
Returns:
Name Type Description BuilderNodeBuilderNode
The head of the linked list of BuilderNodes.
Source code in src/bloqade/analog/builder/parse/stream.py
@staticmethod\ndef build_nodes(node: Builder) -> \"BuilderNode\":\n \"\"\"\n Build BuilderNode instances from the provided Builder.\n\n Args:\n node (Builder): The root Builder instance.\n\n Returns:\n BuilderNode: The head of the linked list of BuilderNodes.\n \"\"\"\n curr = node\n node = None\n while curr is not None:\n next = curr\n curr = curr.__parent__ if hasattr(curr, \"__parent__\") else None\n node = BuilderNode(next, node)\n\n return node\n
Source code in src/bloqade/analog/builder/parse/stream.py
@staticmethod\ndef create(builder: Builder) -> \"BuilderStream\":\n \"\"\"\n Create a BuilderStream instance from a Builder.\n\n Args:\n builder (Builder): The root Builder instance.\n\n Returns:\n BuilderStream: The created BuilderStream instance.\n \"\"\"\n head = BuilderStream.build_nodes(builder)\n return BuilderStream(head=head, curr=head)\n
Move the stream pointer until a node of specified types is found.
Parameters:
Name Type Description Default typesList[Type[Builder]]
List of types to move the stream pointer to.
required skipsList[Type[Builder]] | None
List of types to end the stream scan.
None
Returns:
Name Type Description BuilderNodeBuilderNode
The beginning of the stream which matches a type in types.
Source code in src/bloqade/analog/builder/parse/stream.py
def eat(\n self, types: List[Type[Builder]], skips: Optional[List[Type[Builder]]] = None\n) -> BuilderNode:\n \"\"\"\n Move the stream pointer until a node of specified types is found.\n\n Args:\n types (List[Type[Builder]]): List of types to move the stream pointer to.\n skips (List[Type[Builder]] | None, optional): List of types to end the stream scan.\n\n Returns:\n BuilderNode: The beginning of the stream which matches a type in `types`.\n \"\"\"\n head = self.read_next(types)\n curr = head\n while curr is not None:\n if type(curr.node) not in types:\n if skips and type(curr.node) not in skips:\n break\n curr = curr.next\n self.curr = curr\n return head\n
Source code in src/bloqade/analog/builder/parse/stream.py
def read(self) -> Optional[BuilderNode]:\n \"\"\"Read the next builder node from the stream.\"\"\"\n if self.curr is None:\n return None\n\n node = self.curr\n self.curr = self.curr.next\n return node\n
Read the next builder node of specified types from the stream.
Parameters:
Name Type Description Default builder_typesList[type[Builder]]
List of builder types to read from the stream.
required
Returns:
Type Description Optional[BuilderNode]
Optional[BuilderNode]: The next builder node matching one of the specified types, or None if not found.
Source code in src/bloqade/analog/builder/parse/stream.py
def read_next(self, builder_types: List[type[Builder]]) -> Optional[BuilderNode]:\n \"\"\"\n Read the next builder node of specified types from the stream.\n\n Args:\n builder_types (List[type[Builder]]): List of builder types to read from the stream.\n\n Returns:\n Optional[BuilderNode]: The next builder node matching one of the specified types, or None if not found.\n \"\"\"\n node = self.read()\n while node is not None:\n if type(node.node) in builder_types:\n return node\n node = self.read()\n return None\n
A composite class inheriting from ParseRegister, ParseSequence, ParseCircuit, and ParseRoutine. Provides a unified interface for parsing different components of the program.
Source code in src/bloqade/analog/builder/parse/trait.py
def parse_circuit(self: \"Builder\") -> \"AnalogCircuit\":\n \"\"\"\n Parse the analog circuit from the program.\n\n Returns:\n AnalogCircuit: The parsed analog circuit.\n\n Raises:\n ValueError: If the circuit cannot be parsed.\n \"\"\"\n from bloqade.analog.builder.parse.builder import Parser\n\n return Parser().parse_circuit(self)\n
Type Description Union[AtomArrangement, ParallelRegister]
Union[AtomArrangement, ParallelRegister]: The parsed atom arrangement or parallel register.
Raises:
Type Description ValueError
If the register cannot be parsed.
Source code in src/bloqade/analog/builder/parse/trait.py
def parse_register(self: \"Builder\") -> Union[\"AtomArrangement\", \"ParallelRegister\"]:\n \"\"\"\n Parse the arrangement of atoms in the program.\n\n Returns:\n Union[AtomArrangement, ParallelRegister]: The parsed atom arrangement or parallel register.\n\n Raises:\n ValueError: If the register cannot be parsed.\n \"\"\"\n from bloqade.analog.builder.parse.builder import Parser\n\n return Parser().parse_register(self)\n
Source code in src/bloqade/analog/builder/parse/trait.py
def parse(self: \"Builder\") -> \"Routine\":\n \"\"\"\n Parse the program to return a Routine object.\n\n Returns:\n Routine: The parsed routine object.\n\n Raises:\n ValueError: If the routine cannot be parsed.\n \"\"\"\n from bloqade.analog.builder.parse.builder import Parser\n\n return Parser().parse(self)\n
Source code in src/bloqade/analog/builder/parse/trait.py
def parse_sequence(self: \"Builder\") -> \"Sequence\":\n \"\"\"\n Parse the pulse sequence part of the program.\n\n Returns:\n Sequence: The parsed pulse sequence.\n\n Raises:\n ValueError: If the sequence cannot be parsed.\n \"\"\"\n from bloqade.analog.builder.parse.builder import Parser\n\n return Parser().parse_sequence(self)\n
Source code in src/bloqade/analog/builder/parse/trait.py
def show(self, *args, batch_id: int = 0):\n \"\"\"\n Display the current program being defined with the given arguments and batch ID.\n\n Args:\n *args: Additional arguments for display.\n batch_id (int, optional): The batch ID to be displayed. Defaults to 0.\n\n Note:\n This method uses the `display_builder` function to render the builder's state.\n\n Example:\n\n ```python\n >>> class MyBuilder(Show):\n ... pass\n >>> builder = MyBuilder()\n >>> builder.show()\n >>> builder.show(batch_id=1)\n >>> builder.show('arg1', 'arg2', batch_id=2)\n ```\n \"\"\"\n display_builder(self, batch_id, *args)\n
This pass checks to make sure that: * There is no hyperfine coupling in the sequence * There are no non-uniform spatial modulation for rabi phase and amplitude * there is no more than one non-uniform spatial modulation for detuning
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to analyze
required
Returns:
Name Type Description level_couplingsDict
Dictionary containing the required channels for the sequence. Note that this will insert a uniform field for any missing channels.
Raises:
Type Description ValueError
If there is hyperfine coupling in the sequence.
ValueError
If there is more than one non-uniform spatial modulation for detuning.
ValueError
If there are non-uniform spatial modulations for rabi phase and amplitude.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def analyze_channels(circuit: analog_circuit.AnalogCircuit) -> Dict:\n \"\"\"1. Scan channels\n\n This pass checks to make sure that:\n * There is no hyperfine coupling in the sequence\n * There are no non-uniform spatial modulation for rabi phase and amplitude\n * there is no more than one non-uniform spatial modulation for detuning\n\n Args:\n circuit: AnalogCircuit to analyze\n\n Returns:\n level_couplings: Dictionary containing the required channels for the\n sequence. Note that this will insert a uniform field for any missing\n channels.\n\n Raises:\n ValueError: If there is hyperfine coupling in the sequence.\n ValueError: If there is more than one non-uniform spatial modulation for\n detuning.\n ValueError: If there are non-uniform spatial modulations for rabi phase\n and amplitude.\n\n \"\"\"\n from bloqade.analog.compiler.analysis.common import ScanChannels\n from bloqade.analog.compiler.analysis.hardware import ValidateChannels\n\n ValidateChannels().scan(circuit)\n level_couplings = ScanChannels().scan(circuit)\n\n # add missing channels\n fields = level_couplings[sequence.rydberg]\n # detuning, phase and amplitude are required\n # to have at least a uniform field\n updated_fields = {\n field_name: fields.get(field_name, {field.Uniform}).union({field.Uniform})\n for field_name in [pulse.detuning, pulse.rabi.amplitude, pulse.rabi.phase]\n }\n\n return {sequence.rydberg: updated_fields}\n
This pass assigns variables to the circuit and validates that all variables have been assigned.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to assign variables to
required assignmentsDict[str, ParamType]
Dictionary containing the assignments for the variables in the circuit.
required
Returns:
Name Type Description assigned_circuitTuple[AnalogCircuit, Dict]
AnalogCircuit with variables assigned.
Raises:
Type Description ValueError
If there are any variables that have not been assigned.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def assign_circuit(\n circuit: analog_circuit.AnalogCircuit, assignments: Dict[str, ParamType]\n) -> Tuple[analog_circuit.AnalogCircuit, Dict]:\n \"\"\"3. Assign variables and validate assignment\n\n This pass assigns variables to the circuit and validates that all variables\n have been assigned.\n\n Args:\n circuit: AnalogCircuit to assign variables to\n assignments: Dictionary containing the assignments for the variables in\n the circuit.\n\n Returns:\n assigned_circuit: AnalogCircuit with variables assigned.\n\n Raises:\n ValueError: If there are any variables that have not been assigned.\n\n \"\"\"\n from bloqade.analog.compiler.rewrite.common import AssignBloqadeIR\n from bloqade.analog.compiler.analysis.common import ScanVariables, AssignmentScan\n\n final_assignments = AssignmentScan(assignments).scan(circuit)\n\n assigned_circuit = AssignBloqadeIR(final_assignments).visit(circuit)\n\n assignment_analysis = ScanVariables().scan(assigned_circuit)\n\n if not assignment_analysis.is_assigned:\n missing_vars = assignment_analysis.scalar_vars.union(\n assignment_analysis.vector_vars\n )\n raise ValueError(\n \"Missing assignments for variables:\\n\"\n + (\"\\n\".join(f\"{var}\" for var in missing_vars))\n + \"\\n\"\n )\n\n return assigned_circuit, final_assignments\n
Insert zero waveform in the explicit time intervals missing a waveform
This pass inserts a zero waveform in the explicit time intervals missing a waveform. This is required for later analysis passes to check that the waveforms are compatible with the hardware.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to add padding to
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required
Return circuit: AnalogCircuit with zero waveforms inserted in the explicit time intervals missing a waveform.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def canonicalize_circuit(\n circuit: analog_circuit.AnalogCircuit, level_couplings: Dict\n) -> analog_circuit.AnalogCircuit:\n \"\"\"2. Insert zero waveform in the explicit time intervals missing a waveform\n\n This pass inserts a zero waveform in the explicit time intervals missing a\n waveform. This is required for later analysis passes to check that the\n waveforms are compatible with the hardware.\n\n Args:\n circuit: AnalogCircuit to add padding to\n level_couplings: Dictionary containing the given channels for the\n sequence.\n\n Return\n circuit: AnalogCircuit with zero waveforms inserted in the explicit time\n intervals missing a waveform.\n\n \"\"\"\n from bloqade.analog.compiler.rewrite.common import (\n AddPadding,\n Canonicalizer,\n AssignToLiteral,\n )\n\n circuit = AddPadding(level_couplings).visit(circuit)\n # these two passes are equivalent to a constant propagation pass\n circuit = AssignToLiteral().visit(circuit)\n circuit = Canonicalizer().visit(circuit)\n\n return circuit\n
Generates the AHS code for the given circuit. This includes generating the lattice data, global detuning, global amplitude, global phase, local detuning and lattice site coefficients (if applicable).
Parameters:
Name Type Description Default capabilitiesQuEraCapabilities | None
Capabilities of the hardware.
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required circuitAnalogCircuit
AnalogCircuit to generate AHS code for.
required
Returns:
Name Type Description ahs_componentsAHSComponents
A collection of the AHS components generated for the given circuit. Can be used to generate the QuEra and Braket IR.
Raises:
Type Description ValueError
If the capabilities are not provided but the circuit has a ParallelRegister. This is because the ParallelRegister requires the capabilities to generate the lattice data.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def generate_ahs_code(\n capabilities: Optional[QuEraCapabilities],\n level_couplings: Dict,\n circuit: analog_circuit.AnalogCircuit,\n) -> AHSComponents:\n \"\"\"5. generate ahs code\n\n Generates the AHS code for the given circuit. This includes generating the\n lattice data, global detuning, global amplitude, global phase, local\n detuning and lattice site coefficients (if applicable).\n\n Args:\n capabilities (QuEraCapabilities | None): Capabilities of the hardware.\n level_couplings (Dict): Dictionary containing the given channels for the\n sequence.\n circuit (AnalogCircuit): AnalogCircuit to generate AHS code for.\n\n Returns:\n ahs_components (AHSComponents): A collection of the AHS components\n generated for the given circuit. Can be used to generate the QuEra\n and Braket IR.\n\n Raises:\n ValueError: If the capabilities are not provided but the circuit has\n a ParallelRegister. This is because the ParallelRegister requires\n the capabilities to generate the lattice data.\n\n \"\"\"\n from bloqade.analog.compiler.codegen.hardware import (\n GenerateLattice,\n GeneratePiecewiseLinearChannel,\n GenerateLatticeSiteCoefficients,\n GeneratePiecewiseConstantChannel,\n )\n from bloqade.analog.compiler.analysis.hardware import BasicLatticeValidation\n\n if capabilities is not None:\n # only validate the lattice if capabilities are provided\n BasicLatticeValidation(capabilities).visit(circuit)\n\n ahs_lattice_data = GenerateLattice(capabilities).emit(circuit)\n\n global_detuning = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.detuning, field.Uniform\n ).visit(circuit)\n\n global_amplitude = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.rabi.amplitude, field.Uniform\n ).visit(circuit)\n\n global_phase = GeneratePiecewiseConstantChannel(\n sequence.rydberg, pulse.rabi.phase, field.Uniform\n ).visit(circuit)\n\n local_detuning = None\n lattice_site_coefficients = None\n\n extra_sm = set(level_couplings[sequence.rydberg][pulse.detuning]) - {field.Uniform}\n\n if extra_sm:\n if capabilities is not None and capabilities.capabilities.rydberg.local is None:\n raise ValueError(\n \"Device does not support local detuning, but the program has a \"\n \"non-uniform spatial modulation for detuning.\"\n )\n\n sm = extra_sm.pop()\n\n lattice_site_coefficients = GenerateLatticeSiteCoefficients(\n parallel_decoder=ahs_lattice_data.parallel_decoder\n ).emit(circuit)\n\n local_detuning = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.detuning, sm\n ).visit(circuit)\n\n return AHSComponents(\n lattice_data=ahs_lattice_data,\n global_detuning=global_detuning,\n global_amplitude=global_amplitude,\n global_phase=global_phase,\n local_detuning=local_detuning,\n lattice_site_coefficients=lattice_site_coefficients,\n )\n
validate piecewise linear and piecewise constant pieces of pulses
This pass check to make sure that the waveforms are compatible with the hardware. This includes checking that the waveforms are piecewise linear or piecewise constant. It also checks that the waveforms are compatible with the given channels.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to validate waveforms for
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required
Raises:
Type Description ValueError
If the waveforms are not piecewise linear or piecewise constant, e.g. the waveform is not continuous.
ValueError
If a waveform segment is not compatible with the given channels.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def validate_waveforms(\n level_couplings: Dict, circuit: analog_circuit.AnalogCircuit\n) -> None:\n \"\"\"4. validate piecewise linear and piecewise constant pieces of pulses\n\n This pass check to make sure that the waveforms are compatible with the\n hardware. This includes checking that the waveforms are piecewise linear or\n piecewise constant. It also checks that the waveforms are compatible with\n the given channels.\n\n Args:\n circuit: AnalogCircuit to validate waveforms for\n level_couplings: Dictionary containing the given channels for the\n sequence.\n\n Raises:\n ValueError: If the waveforms are not piecewise linear or piecewise\n constant, e.g. the waveform is not continuous.\n ValueError: If a waveform segment is not compatible with the given\n channels.\n\n \"\"\"\n from bloqade.analog.compiler.analysis.common import CheckSlices\n from bloqade.analog.compiler.analysis.hardware import (\n ValidatePiecewiseLinearChannel,\n ValidatePiecewiseConstantChannel,\n )\n\n channel_iter = (\n (level_coupling, field_name, sm)\n for level_coupling, fields in level_couplings.items()\n for field_name, spatial_modulations in fields.items()\n for sm in spatial_modulations\n )\n for channel in channel_iter:\n if channel[1] in [pulse.detuning, pulse.rabi.amplitude]:\n ValidatePiecewiseLinearChannel(*channel).visit(circuit)\n else:\n ValidatePiecewiseConstantChannel(*channel).visit(circuit)\n\n CheckSlices().visit(circuit)\n\n if circuit.sequence.duration() == 0:\n raise ValueError(\"Circuit Duration must be be non-zero\")\n
This pass checks to make sure that: * There is no hyperfine coupling in the sequence * There are no non-uniform spatial modulation for rabi phase and amplitude * there is no more than one non-uniform spatial modulation for detuning
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to analyze
required
Returns:
Name Type Description level_couplingsDict
Dictionary containing the required channels for the sequence. Note that this will insert a uniform field for any missing channels.
Raises:
Type Description ValueError
If there is hyperfine coupling in the sequence.
ValueError
If there is more than one non-uniform spatial modulation for detuning.
ValueError
If there are non-uniform spatial modulations for rabi phase and amplitude.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def analyze_channels(circuit: analog_circuit.AnalogCircuit) -> Dict:\n \"\"\"1. Scan channels\n\n This pass checks to make sure that:\n * There is no hyperfine coupling in the sequence\n * There are no non-uniform spatial modulation for rabi phase and amplitude\n * there is no more than one non-uniform spatial modulation for detuning\n\n Args:\n circuit: AnalogCircuit to analyze\n\n Returns:\n level_couplings: Dictionary containing the required channels for the\n sequence. Note that this will insert a uniform field for any missing\n channels.\n\n Raises:\n ValueError: If there is hyperfine coupling in the sequence.\n ValueError: If there is more than one non-uniform spatial modulation for\n detuning.\n ValueError: If there are non-uniform spatial modulations for rabi phase\n and amplitude.\n\n \"\"\"\n from bloqade.analog.compiler.analysis.common import ScanChannels\n from bloqade.analog.compiler.analysis.hardware import ValidateChannels\n\n ValidateChannels().scan(circuit)\n level_couplings = ScanChannels().scan(circuit)\n\n # add missing channels\n fields = level_couplings[sequence.rydberg]\n # detuning, phase and amplitude are required\n # to have at least a uniform field\n updated_fields = {\n field_name: fields.get(field_name, {field.Uniform}).union({field.Uniform})\n for field_name in [pulse.detuning, pulse.rabi.amplitude, pulse.rabi.phase]\n }\n\n return {sequence.rydberg: updated_fields}\n
This pass assigns variables to the circuit and validates that all variables have been assigned.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to assign variables to
required assignmentsDict[str, ParamType]
Dictionary containing the assignments for the variables in the circuit.
required
Returns:
Name Type Description assigned_circuitTuple[AnalogCircuit, Dict]
AnalogCircuit with variables assigned.
Raises:
Type Description ValueError
If there are any variables that have not been assigned.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def assign_circuit(\n circuit: analog_circuit.AnalogCircuit, assignments: Dict[str, ParamType]\n) -> Tuple[analog_circuit.AnalogCircuit, Dict]:\n \"\"\"3. Assign variables and validate assignment\n\n This pass assigns variables to the circuit and validates that all variables\n have been assigned.\n\n Args:\n circuit: AnalogCircuit to assign variables to\n assignments: Dictionary containing the assignments for the variables in\n the circuit.\n\n Returns:\n assigned_circuit: AnalogCircuit with variables assigned.\n\n Raises:\n ValueError: If there are any variables that have not been assigned.\n\n \"\"\"\n from bloqade.analog.compiler.rewrite.common import AssignBloqadeIR\n from bloqade.analog.compiler.analysis.common import ScanVariables, AssignmentScan\n\n final_assignments = AssignmentScan(assignments).scan(circuit)\n\n assigned_circuit = AssignBloqadeIR(final_assignments).visit(circuit)\n\n assignment_analysis = ScanVariables().scan(assigned_circuit)\n\n if not assignment_analysis.is_assigned:\n missing_vars = assignment_analysis.scalar_vars.union(\n assignment_analysis.vector_vars\n )\n raise ValueError(\n \"Missing assignments for variables:\\n\"\n + (\"\\n\".join(f\"{var}\" for var in missing_vars))\n + \"\\n\"\n )\n\n return assigned_circuit, final_assignments\n
Insert zero waveform in the explicit time intervals missing a waveform
This pass inserts a zero waveform in the explicit time intervals missing a waveform. This is required for later analysis passes to check that the waveforms are compatible with the hardware.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to add padding to
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required
Return circuit: AnalogCircuit with zero waveforms inserted in the explicit time intervals missing a waveform.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def canonicalize_circuit(\n circuit: analog_circuit.AnalogCircuit, level_couplings: Dict\n) -> analog_circuit.AnalogCircuit:\n \"\"\"2. Insert zero waveform in the explicit time intervals missing a waveform\n\n This pass inserts a zero waveform in the explicit time intervals missing a\n waveform. This is required for later analysis passes to check that the\n waveforms are compatible with the hardware.\n\n Args:\n circuit: AnalogCircuit to add padding to\n level_couplings: Dictionary containing the given channels for the\n sequence.\n\n Return\n circuit: AnalogCircuit with zero waveforms inserted in the explicit time\n intervals missing a waveform.\n\n \"\"\"\n from bloqade.analog.compiler.rewrite.common import (\n AddPadding,\n Canonicalizer,\n AssignToLiteral,\n )\n\n circuit = AddPadding(level_couplings).visit(circuit)\n # these two passes are equivalent to a constant propagation pass\n circuit = AssignToLiteral().visit(circuit)\n circuit = Canonicalizer().visit(circuit)\n\n return circuit\n
Generates the AHS code for the given circuit. This includes generating the lattice data, global detuning, global amplitude, global phase, local detuning and lattice site coefficients (if applicable).
Parameters:
Name Type Description Default capabilitiesQuEraCapabilities | None
Capabilities of the hardware.
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required circuitAnalogCircuit
AnalogCircuit to generate AHS code for.
required
Returns:
Name Type Description ahs_componentsAHSComponents
A collection of the AHS components generated for the given circuit. Can be used to generate the QuEra and Braket IR.
Raises:
Type Description ValueError
If the capabilities are not provided but the circuit has a ParallelRegister. This is because the ParallelRegister requires the capabilities to generate the lattice data.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def generate_ahs_code(\n capabilities: Optional[QuEraCapabilities],\n level_couplings: Dict,\n circuit: analog_circuit.AnalogCircuit,\n) -> AHSComponents:\n \"\"\"5. generate ahs code\n\n Generates the AHS code for the given circuit. This includes generating the\n lattice data, global detuning, global amplitude, global phase, local\n detuning and lattice site coefficients (if applicable).\n\n Args:\n capabilities (QuEraCapabilities | None): Capabilities of the hardware.\n level_couplings (Dict): Dictionary containing the given channels for the\n sequence.\n circuit (AnalogCircuit): AnalogCircuit to generate AHS code for.\n\n Returns:\n ahs_components (AHSComponents): A collection of the AHS components\n generated for the given circuit. Can be used to generate the QuEra\n and Braket IR.\n\n Raises:\n ValueError: If the capabilities are not provided but the circuit has\n a ParallelRegister. This is because the ParallelRegister requires\n the capabilities to generate the lattice data.\n\n \"\"\"\n from bloqade.analog.compiler.codegen.hardware import (\n GenerateLattice,\n GeneratePiecewiseLinearChannel,\n GenerateLatticeSiteCoefficients,\n GeneratePiecewiseConstantChannel,\n )\n from bloqade.analog.compiler.analysis.hardware import BasicLatticeValidation\n\n if capabilities is not None:\n # only validate the lattice if capabilities are provided\n BasicLatticeValidation(capabilities).visit(circuit)\n\n ahs_lattice_data = GenerateLattice(capabilities).emit(circuit)\n\n global_detuning = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.detuning, field.Uniform\n ).visit(circuit)\n\n global_amplitude = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.rabi.amplitude, field.Uniform\n ).visit(circuit)\n\n global_phase = GeneratePiecewiseConstantChannel(\n sequence.rydberg, pulse.rabi.phase, field.Uniform\n ).visit(circuit)\n\n local_detuning = None\n lattice_site_coefficients = None\n\n extra_sm = set(level_couplings[sequence.rydberg][pulse.detuning]) - {field.Uniform}\n\n if extra_sm:\n if capabilities is not None and capabilities.capabilities.rydberg.local is None:\n raise ValueError(\n \"Device does not support local detuning, but the program has a \"\n \"non-uniform spatial modulation for detuning.\"\n )\n\n sm = extra_sm.pop()\n\n lattice_site_coefficients = GenerateLatticeSiteCoefficients(\n parallel_decoder=ahs_lattice_data.parallel_decoder\n ).emit(circuit)\n\n local_detuning = GeneratePiecewiseLinearChannel(\n sequence.rydberg, pulse.detuning, sm\n ).visit(circuit)\n\n return AHSComponents(\n lattice_data=ahs_lattice_data,\n global_detuning=global_detuning,\n global_amplitude=global_amplitude,\n global_phase=global_phase,\n local_detuning=local_detuning,\n lattice_site_coefficients=lattice_site_coefficients,\n )\n
validate piecewise linear and piecewise constant pieces of pulses
This pass check to make sure that the waveforms are compatible with the hardware. This includes checking that the waveforms are piecewise linear or piecewise constant. It also checks that the waveforms are compatible with the given channels.
Parameters:
Name Type Description Default circuitAnalogCircuit
AnalogCircuit to validate waveforms for
required level_couplingsDict
Dictionary containing the given channels for the sequence.
required
Raises:
Type Description ValueError
If the waveforms are not piecewise linear or piecewise constant, e.g. the waveform is not continuous.
ValueError
If a waveform segment is not compatible with the given channels.
Source code in src/bloqade/analog/compiler/passes/hardware/define.py
def validate_waveforms(\n level_couplings: Dict, circuit: analog_circuit.AnalogCircuit\n) -> None:\n \"\"\"4. validate piecewise linear and piecewise constant pieces of pulses\n\n This pass check to make sure that the waveforms are compatible with the\n hardware. This includes checking that the waveforms are piecewise linear or\n piecewise constant. It also checks that the waveforms are compatible with\n the given channels.\n\n Args:\n circuit: AnalogCircuit to validate waveforms for\n level_couplings: Dictionary containing the given channels for the\n sequence.\n\n Raises:\n ValueError: If the waveforms are not piecewise linear or piecewise\n constant, e.g. the waveform is not continuous.\n ValueError: If a waveform segment is not compatible with the given\n channels.\n\n \"\"\"\n from bloqade.analog.compiler.analysis.common import CheckSlices\n from bloqade.analog.compiler.analysis.hardware import (\n ValidatePiecewiseLinearChannel,\n ValidatePiecewiseConstantChannel,\n )\n\n channel_iter = (\n (level_coupling, field_name, sm)\n for level_coupling, fields in level_couplings.items()\n for field_name, spatial_modulations in fields.items()\n for sm in spatial_modulations\n )\n for channel in channel_iter:\n if channel[1] in [pulse.detuning, pulse.rabi.amplitude]:\n ValidatePiecewiseLinearChannel(*channel).visit(circuit)\n else:\n ValidatePiecewiseConstantChannel(*channel).visit(circuit)\n\n CheckSlices().visit(circuit)\n\n if circuit.sequence.duration() == 0:\n raise ValueError(\"Circuit Duration must be be non-zero\")\n
"},{"location":"reference/bloqade/analog/compiler/rewrite/common/add_padding/","title":"Add padding","text":""},{"location":"reference/bloqade/analog/compiler/rewrite/common/assign_to_literal/","title":"Assign to literal","text":""},{"location":"reference/bloqade/analog/compiler/rewrite/common/assign_to_literal/#bloqade.analog.compiler.rewrite.common.assign_to_literal.AssignToLiteral","title":"AssignToLiteral","text":"
Hamiltonian for a given task. With the RydbergHamiltonian you can convert the Hamiltonian to CSR matrix form as well as obtaining the average energy/variance of a register.
Attributes:
Name Type Description emulator_irEmulatorProgram
A copy of the original program used to generate the RydbergHamiltonian
spaceSpace
The Hilbert space of the Hamiltonian, should align with the register the Hamiltonian is being applied on for average energy/variance
Get energy average from RydbergHamiltonian object at time time with register register
Parameters:
Name Type Description Default registerStateVector
The state vector to take average with
required timeOptional[float]
Time value to evaluate average at.
None
Returns:
Name Type Description floatfloat
average energy at time time
Source code in src/bloqade/analog/emulate/ir/state_vector.py
@beartype\ndef average(\n self,\n register: StateVector,\n time: Optional[float] = None,\n) -> float:\n \"\"\"Get energy average from RydbergHamiltonian object at time `time` with\n register `register`\n\n Args:\n register (StateVector): The state vector to take average with\n time (Optional[float], optional): Time value to evaluate average at.\n Defaults to duration of RydbergHamiltonian.\n\n Returns:\n float: average energy at time `time`\n \"\"\"\n return np.vdot(register.data, self._apply(register.data, time)).real\n
Get energy average and variance from RydbergHamiltonian object at time time with register register
Parameters:
Name Type Description Default registerStateVector
The state vector to take average and variance with
required timeOptional[float]
Time value to evaluate average at.
None
Returns:
Type Description float
Tuple[float, float]: average and variance of energy at time time
float
respectively.
Source code in src/bloqade/analog/emulate/ir/state_vector.py
@beartype\ndef average_and_variance(\n self,\n register: StateVector,\n time: Optional[float] = None,\n) -> Tuple[float, float]:\n \"\"\"Get energy average and variance from RydbergHamiltonian object at time `time`\n with register `register`\n\n Args:\n register (StateVector): The state vector to take average and variance with\n time (Optional[float], optional): Time value to evaluate average at.\n Defaults to duration of RydbergHamiltonian.\n\n Returns:\n Tuple[float, float]: average and variance of energy at time `time`\n respectively.\n \"\"\"\n H_register_data = self._apply(register.data, time)\n\n average = np.vdot(register.data, H_register_data).real\n square_average = np.vdot(H_register_data, H_register_data).real\n\n return average, square_average - average**2\n
Return the Hamiltonian as a csr matrix at time time.
Parameters:
Name Type Description Default timefloat
time to evaluate the Hamiltonian at.
required
Returns:
Name Type Description csr_matrixcsr_matrix
The Hamiltonian as a csr matrix.
Source code in src/bloqade/analog/emulate/ir/state_vector.py
def tocsr(self, time: float) -> csr_matrix:\n \"\"\"Return the Hamiltonian as a csr matrix at time `time`.\n\n Args:\n time (float): time to evaluate the Hamiltonian at.\n\n Returns:\n csr_matrix: The Hamiltonian as a csr matrix.\n\n \"\"\"\n diagonal = sum(\n (detuning.get_diagonal(time) for detuning in self.detuning_ops),\n start=self.rydberg,\n )\n\n hamiltonian = diags(diagonal).tocsr()\n for rabi_op in self.rabi_ops:\n hamiltonian = hamiltonian + rabi_op.tocsr(time)\n\n return hamiltonian\n
Get the energy variance from RydbergHamiltonian object at time time with register register
Parameters:
Name Type Description Default registerStateVector
The state vector to take variance with
required timeOptional[float]
Time value to evaluate average at.
None
Returns:
Name Type Description complexfloat
variance of energy at time time respectively.
Source code in src/bloqade/analog/emulate/ir/state_vector.py
@beartype\ndef variance(\n self,\n register: StateVector,\n time: Optional[float] = None,\n) -> float:\n \"\"\"Get the energy variance from RydbergHamiltonian object at\n time `time` with register `register`\n\n Args:\n register (StateVector): The state vector to take variance with\n time (Optional[float], optional): Time value to evaluate average at.\n Defaults to duration of RydbergHamiltonian.\n\n Returns:\n complex: variance of energy at time `time` respectively.\n \"\"\"\n\n _, var = self.average_and_variance(register, time)\n return var\n
Square matrix representing operator in the local hilbert space.
required site_indexint | Tuple[int, int]
sites to apply one body operator to.
required
Returns:
Name Type Description complexcomplex
the trace of the operator over the state-vector.
Raises:
Type Description ValueError
Error is raised when the dimension of operator is not
ValueError
Error is raised when the site argument is out of bounds.
Source code in src/bloqade/analog/emulate/ir/state_vector.py
@plum.dispatch\ndef local_trace( # noqa: F811\n self, matrix: np.ndarray, site_index: Union[int, Tuple[int, int]]\n) -> complex: # noqa: F811\n \"\"\"return trace of an operator over the StateVector.\n\n Args:\n matrix (np.ndarray): Square matrix representing operator in the local\n hilbert space.\n site_index (int | Tuple[int, int]): sites to apply one body operator to.\n\n Returns:\n complex: the trace of the operator over the state-vector.\n\n Raises:\n ValueError: Error is raised when the dimension of `operator` is not\n consistent with `site` argument. The size of the operator must fit\n the size of the local hilbert space of `site` depending on the number\n of sites and the number of levels inside each atom, e.g. for two site\n expectation value with a three level atom the operator must be a 9 by\n 9 array.\n\n ValueError: Error is raised when the `site` argument is out of bounds.\n\n \"\"\"\n ...\n
assigning the instance value (literal) to the existing variables in the program
{} Source code in src/bloqade/analog/ir/analog_circuit.py
def show(self, **assignments):\n \"\"\"Interactive visualization of the program\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the program\n\n \"\"\"\n display_ir(self, assignments)\n
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).
# single coordinate\n>>> reg = start.add_position((0,0))\n# you may chain add_position calls\n>>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n# you can add variables anywhere a value may be present\n>>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n# and specify your atom fillings\n>>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n[True, False])\n# alternatively you could use one boolean to specify\n# all coordinates should be empty/filled\n>>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n(5.2, 2.2)], False)\n
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
Source code in src/bloqade/analog/ir/location/location.py
def add_position(\n self,\n position: Union[\n PositionArray,\n List[Tuple[ScalarType, ScalarType]],\n Tuple[ScalarType, ScalarType],\n ],\n filling: Optional[Union[BoolArray, List[bool], bool]] = None,\n) -> \"ListOfLocations\":\n \"\"\"\n Add a position or multiple positions to a pre-existing geometry.\n\n `add_position` is capable of accepting:\n - A single tuple for one atom coordinate: `(1.0, 2.5)`\n - A list of tuples: `[(0.0, 1.0), (2.0,1.5), etc.]\n - A numpy array of shape (N, 2) where N is the number of atoms\n\n You may also intersperse variables anywhere a value may be present.\n\n You can also pass in an optional argument which determines the atom \"filling\"\n (whether or not at a specified coordinate an atom should be present).\n\n ### Usage Example:\n ```\n # single coordinate\n >>> reg = start.add_position((0,0))\n # you may chain add_position calls\n >>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n # you can add variables anywhere a value may be present\n >>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n # and specify your atom fillings\n >>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n [True, False])\n # alternatively you could use one boolean to specify\n # all coordinates should be empty/filled\n >>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n (5.2, 2.2)], False)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`: to specify Rydberg coupling\n - `...add_position(positions).hyperfine`: to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n if is_bearable(position, PositionArray) and is_bearable(\n filling, Optional[BoolArray]\n ):\n return self.add_position_ndarray(position, filling)\n elif is_bearable(position, List[Tuple[ScalarType, ScalarType]]) and is_bearable(\n filling, Optional[List[bool]]\n ):\n return self.add_position_list_tuples(position, filling)\n elif is_bearable(position, Tuple[ScalarType, ScalarType]) and is_bearable(\n filling, Optional[bool]\n ):\n return self.add_position_single_tupe(position, filling)\n else:\n raise TypeError(\"Invalid input types for add_position provided!\")\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_count(2, custom_rng)\n# you may also chain apply_defect_count calls\n>>> reg.apply_defect_count(2, custom_rng)\n# you can also use apply_defect_count on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_count(\n self, n_defects: int, rng: np.random.Generator = np.random.default_rng()\n):\n \"\"\"\n Drop `n_defects` atoms from the geometry randomly. Internally this occurs\n by setting certain sites to have a SiteFilling set to false indicating\n no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_count(2, custom_rng)\n # you may also chain apply_defect_count calls\n >>> reg.apply_defect_count(2, custom_rng)\n # you can also use apply_defect_count on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts)\n .apply_defect_count(n_defects)`: to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`: to specify\n Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n location_list = []\n for location_info in self.enumerate():\n location_list.append(location_info)\n\n filled_sites = []\n\n for index, location_info in enumerate(location_list):\n if location_info.filling is SiteFilling.filled:\n filled_sites.append(index)\n\n if n_defects >= len(filled_sites):\n raise ValueError(\n f\"n_defects {n_defects} must be less than the number of filled sites \"\n f\"({len(filled_sites)})\"\n )\n\n for _ in range(n_defects):\n index = rng.choice(filled_sites)\n location_list[index] = LocationInfo.create(\n location_list[index].position,\n (False if location_list[index].filling is SiteFilling.filled else True),\n )\n filled_sites.remove(index)\n\n return ListOfLocations(location_list)\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n# you may also chain apply_defect_density calls\n>>> reg.apply_defect_count(0.1, custom_rng)\n# you can also use apply_defect_density on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)])\n.apply_defect_density(0.5, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_density(\n self,\n defect_probability: float,\n rng: np.random.Generator = np.random.default_rng(),\n):\n \"\"\"\n Drop atoms randomly with `defect_probability` probability (range of 0 to 1).\n Internally this occurs by setting certain sites to have a SiteFilling\n set to false indicating no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n # you may also chain apply_defect_density calls\n >>> reg.apply_defect_count(0.1, custom_rng)\n # you can also use apply_defect_density on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)])\n .apply_defect_density(0.5, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`:\n to specify Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n p = min(1, max(0, defect_probability))\n location_list = []\n\n for location_info in self.enumerate():\n if rng.random() < p:\n location_list.append(\n LocationInfo.create(\n location_info.position,\n (\n False\n if location_info.filling is SiteFilling.filled\n else True\n ),\n )\n )\n else:\n location_list.append(location_info)\n\n return ListOfLocations(location_list=location_list)\n
Source code in src/bloqade/analog/ir/location/location.py
def figure(self, fig_kwargs=None, **assignments):\n \"\"\"obtain a figure object from the atom arrangement.\"\"\"\n return get_atom_arrangement_figure(self, fig_kwargs=fig_kwargs, **assignments)\n
the values to assign to the variables in the register.
{}
Returns:
Name Type Description NDArrayNDArray
the Rydberg interaction matrix in the lower triangular form.
Source code in src/bloqade/analog/ir/location/location.py
def rydberg_interaction(self, **assignments) -> NDArray:\n \"\"\"calculate the Rydberg interaction matrix.\n\n Args:\n **assignments: the values to assign to the variables in the register.\n\n Returns:\n NDArray: the Rydberg interaction matrix in the lower triangular form.\n\n \"\"\"\n\n from bloqade.analog.constants import RB_C6\n\n # calculate the Interaction matrix\n V_ij = np.zeros((self.n_sites, self.n_sites))\n for i, site_i in enumerate(self.enumerate()):\n pos_i = np.array([float(ele(**assignments)) for ele in site_i.position])\n\n for j, site_j in enumerate(self.enumerate()):\n if j >= i:\n break # enforce lower triangular form\n\n pos_j = np.array([float(ele(**assignments)) for ele in site_j.position])\n r_ij = np.linalg.norm(pos_i - pos_j)\n\n V_ij[i, j] = RB_C6 / r_ij**6\n\n return V_ij\n
>>> reg = start.add_position([(0,0), (1,1)])\n# atom positions are now (0,0), (2,2)\n>>> new_reg = reg.scale(2)\n# you may also use scale on pre-defined geometries\n>>> from bloqade.analog.atom_arrangement import Chain\n# atoms in the chain will now be 2 um apart versus\n# the default 1 um\n>>> Chain(11).scale(2)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef scale(self, scale: ScalarType):\n \"\"\"\n Scale the geometry of your atoms.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position([(0,0), (1,1)])\n # atom positions are now (0,0), (2,2)\n >>> new_reg = reg.scale(2)\n # you may also use scale on pre-defined geometries\n >>> from bloqade.analog.atom_arrangement import Chain\n # atoms in the chain will now be 2 um apart versus\n # the default 1 um\n >>> Chain(11).scale(2)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`:\n to specify Rydberg coupling\n - `...add_position(positions).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n scale = cast(scale)\n location_list = []\n for location_info in self.enumerate():\n x, y = location_info.position\n new_position = (scale * x, scale * y)\n location_list.append(\n LocationInfo.create(new_position, bool(location_info.filling.value))\n )\n\n return ListOfLocations(location_list)\n
calculate the coordinates of a cell in the lattice given the cell index.
Source code in src/bloqade/analog/ir/location/bravais.py
@beartype\ndef coordinates(self, index: List[int]) -> NDArray:\n \"\"\"calculate the coordinates of a cell in the lattice\n given the cell index.\n \"\"\"\n # damn! this is like stone age broadcasting\n vectors = np.array(self.cell_vectors())\n index = np.array(index)\n pos = np.sum(vectors.T * index, axis=1)\n return pos + np.array(self.cell_atoms())\n
assigning the instance value (literal) to the existing variables in the Field
{} Source code in src/bloqade/analog/ir/control/field.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Field\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Field\n\n \"\"\"\n display_ir(self, assignments)\n
assigning the instance value (literal) to the existing variables in the Pulse
{} Source code in src/bloqade/analog/ir/control/pulse.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Pulse\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Pulse\n\n \"\"\"\n display_ir(self, assignments)\n
assigning the instance value (literal) to the existing variables in the Sequence
{} Source code in src/bloqade/analog/ir/control/sequence.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Sequence\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Sequence\n\n \"\"\"\n display_ir(self, assignments)\n
Source code in src/bloqade/analog/ir/control/waveform.py
def figure(self, **assignments):\n \"\"\"get figure of the plotting the waveform.\n\n Returns:\n figure: a bokeh figure\n \"\"\"\n return get_ir_figure(self, **assignments)\n
cast Real number (or list/tuple of Real numbers) to Scalar Literal.
cast str (or list/tuple of Real numbers) to Scalar Variable.
Parameters:
Name Type Description Default pyUnion[str, Real, Tuple[Real], List[Real]]
python object to cast
required
Returns:
Type Description Scalar
Scalar
Source code in src/bloqade/analog/ir/scalar.py
def cast(py) -> \"Scalar\":\n \"\"\"\n 1. cast Real number (or list/tuple of Real numbers)\n to [`Scalar Literal`][bloqade.ir.scalar.Literal].\n\n 2. cast str (or list/tuple of Real numbers)\n to [`Scalar Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str,Real,Tuple[Real],List[Real]]): python object to cast\n\n Returns:\n Scalar\n \"\"\"\n ret = trycast(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Scalar Literal\")\n\n return ret\n
cast string (or list/tuple of strings) to Variable.
Parameters:
Name Type Description Default pyUnion[str, List[str]]
a string or list/tuple of strings
required
Returns:
Type Description Variable
Union[Variable]
Source code in src/bloqade/analog/ir/scalar.py
def var(py: str) -> \"Variable\":\n \"\"\"cast string (or list/tuple of strings)\n to [`Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str, List[str]]): a string or list/tuple of strings\n\n Returns:\n Union[Variable]\n \"\"\"\n ret = tryvar(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Variable\")\n\n return ret\n
assigning the instance value (literal) to the existing variables in the program
{} Source code in src/bloqade/analog/ir/analog_circuit.py
def show(self, **assignments):\n \"\"\"Interactive visualization of the program\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the program\n\n \"\"\"\n display_ir(self, assignments)\n
cast Real number (or list/tuple of Real numbers) to Scalar Literal.
cast str (or list/tuple of Real numbers) to Scalar Variable.
Parameters:
Name Type Description Default pyUnion[str, Real, Tuple[Real], List[Real]]
python object to cast
required
Returns:
Type Description Scalar
Scalar
Source code in src/bloqade/analog/ir/scalar.py
def cast(py) -> \"Scalar\":\n \"\"\"\n 1. cast Real number (or list/tuple of Real numbers)\n to [`Scalar Literal`][bloqade.ir.scalar.Literal].\n\n 2. cast str (or list/tuple of Real numbers)\n to [`Scalar Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str,Real,Tuple[Real],List[Real]]): python object to cast\n\n Returns:\n Scalar\n \"\"\"\n ret = trycast(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Scalar Literal\")\n\n return ret\n
cast string (or list/tuple of strings) to Variable.
Parameters:
Name Type Description Default pyUnion[str, List[str]]
a string or list/tuple of strings
required
Returns:
Type Description Variable
Union[Variable]
Source code in src/bloqade/analog/ir/scalar.py
def var(py: str) -> \"Variable\":\n \"\"\"cast string (or list/tuple of strings)\n to [`Variable`][bloqade.ir.scalar.Variable].\n\n Args:\n py (Union[str, List[str]]): a string or list/tuple of strings\n\n Returns:\n Union[Variable]\n \"\"\"\n ret = tryvar(py)\n if ret is None:\n raise TypeError(f\"Cannot cast {type(py)} to Variable\")\n\n return ret\n
assigning the instance value (literal) to the existing variables in the Field
{} Source code in src/bloqade/analog/ir/control/field.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Field\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Field\n\n \"\"\"\n display_ir(self, assignments)\n
assigning the instance value (literal) to the existing variables in the Pulse
{} Source code in src/bloqade/analog/ir/control/pulse.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Pulse\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Pulse\n\n \"\"\"\n display_ir(self, assignments)\n
assigning the instance value (literal) to the existing variables in the Sequence
{} Source code in src/bloqade/analog/ir/control/sequence.py
def show(self, **assignments):\n \"\"\"\n Interactive visualization of the Sequence\n\n Args:\n **assignments: assigning the instance value (literal) to the\n existing variables in the Sequence\n\n \"\"\"\n display_ir(self, assignments)\n
Source code in src/bloqade/analog/ir/control/waveform.py
def figure(self, **assignments):\n \"\"\"get figure of the plotting the waveform.\n\n Returns:\n figure: a bokeh figure\n \"\"\"\n return get_ir_figure(self, **assignments)\n
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).
# single coordinate\n>>> reg = start.add_position((0,0))\n# you may chain add_position calls\n>>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n# you can add variables anywhere a value may be present\n>>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n# and specify your atom fillings\n>>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n[True, False])\n# alternatively you could use one boolean to specify\n# all coordinates should be empty/filled\n>>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n(5.2, 2.2)], False)\n
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
Source code in src/bloqade/analog/ir/location/location.py
def add_position(\n self,\n position: Union[\n PositionArray,\n List[Tuple[ScalarType, ScalarType]],\n Tuple[ScalarType, ScalarType],\n ],\n filling: Optional[Union[BoolArray, List[bool], bool]] = None,\n) -> \"ListOfLocations\":\n \"\"\"\n Add a position or multiple positions to a pre-existing geometry.\n\n `add_position` is capable of accepting:\n - A single tuple for one atom coordinate: `(1.0, 2.5)`\n - A list of tuples: `[(0.0, 1.0), (2.0,1.5), etc.]\n - A numpy array of shape (N, 2) where N is the number of atoms\n\n You may also intersperse variables anywhere a value may be present.\n\n You can also pass in an optional argument which determines the atom \"filling\"\n (whether or not at a specified coordinate an atom should be present).\n\n ### Usage Example:\n ```\n # single coordinate\n >>> reg = start.add_position((0,0))\n # you may chain add_position calls\n >>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n # you can add variables anywhere a value may be present\n >>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n # and specify your atom fillings\n >>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n [True, False])\n # alternatively you could use one boolean to specify\n # all coordinates should be empty/filled\n >>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n (5.2, 2.2)], False)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`: to specify Rydberg coupling\n - `...add_position(positions).hyperfine`: to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n if is_bearable(position, PositionArray) and is_bearable(\n filling, Optional[BoolArray]\n ):\n return self.add_position_ndarray(position, filling)\n elif is_bearable(position, List[Tuple[ScalarType, ScalarType]]) and is_bearable(\n filling, Optional[List[bool]]\n ):\n return self.add_position_list_tuples(position, filling)\n elif is_bearable(position, Tuple[ScalarType, ScalarType]) and is_bearable(\n filling, Optional[bool]\n ):\n return self.add_position_single_tupe(position, filling)\n else:\n raise TypeError(\"Invalid input types for add_position provided!\")\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_count(2, custom_rng)\n# you may also chain apply_defect_count calls\n>>> reg.apply_defect_count(2, custom_rng)\n# you can also use apply_defect_count on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_count(\n self, n_defects: int, rng: np.random.Generator = np.random.default_rng()\n):\n \"\"\"\n Drop `n_defects` atoms from the geometry randomly. Internally this occurs\n by setting certain sites to have a SiteFilling set to false indicating\n no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_count(2, custom_rng)\n # you may also chain apply_defect_count calls\n >>> reg.apply_defect_count(2, custom_rng)\n # you can also use apply_defect_count on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts)\n .apply_defect_count(n_defects)`: to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`: to specify\n Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n location_list = []\n for location_info in self.enumerate():\n location_list.append(location_info)\n\n filled_sites = []\n\n for index, location_info in enumerate(location_list):\n if location_info.filling is SiteFilling.filled:\n filled_sites.append(index)\n\n if n_defects >= len(filled_sites):\n raise ValueError(\n f\"n_defects {n_defects} must be less than the number of filled sites \"\n f\"({len(filled_sites)})\"\n )\n\n for _ in range(n_defects):\n index = rng.choice(filled_sites)\n location_list[index] = LocationInfo.create(\n location_list[index].position,\n (False if location_list[index].filling is SiteFilling.filled else True),\n )\n filled_sites.remove(index)\n\n return ListOfLocations(location_list)\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n# you may also chain apply_defect_density calls\n>>> reg.apply_defect_count(0.1, custom_rng)\n# you can also use apply_defect_density on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)])\n.apply_defect_density(0.5, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_density(\n self,\n defect_probability: float,\n rng: np.random.Generator = np.random.default_rng(),\n):\n \"\"\"\n Drop atoms randomly with `defect_probability` probability (range of 0 to 1).\n Internally this occurs by setting certain sites to have a SiteFilling\n set to false indicating no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n # you may also chain apply_defect_density calls\n >>> reg.apply_defect_count(0.1, custom_rng)\n # you can also use apply_defect_density on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)])\n .apply_defect_density(0.5, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`:\n to specify Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n p = min(1, max(0, defect_probability))\n location_list = []\n\n for location_info in self.enumerate():\n if rng.random() < p:\n location_list.append(\n LocationInfo.create(\n location_info.position,\n (\n False\n if location_info.filling is SiteFilling.filled\n else True\n ),\n )\n )\n else:\n location_list.append(location_info)\n\n return ListOfLocations(location_list=location_list)\n
Source code in src/bloqade/analog/ir/location/location.py
def figure(self, fig_kwargs=None, **assignments):\n \"\"\"obtain a figure object from the atom arrangement.\"\"\"\n return get_atom_arrangement_figure(self, fig_kwargs=fig_kwargs, **assignments)\n
the values to assign to the variables in the register.
{}
Returns:
Name Type Description NDArrayNDArray
the Rydberg interaction matrix in the lower triangular form.
Source code in src/bloqade/analog/ir/location/location.py
def rydberg_interaction(self, **assignments) -> NDArray:\n \"\"\"calculate the Rydberg interaction matrix.\n\n Args:\n **assignments: the values to assign to the variables in the register.\n\n Returns:\n NDArray: the Rydberg interaction matrix in the lower triangular form.\n\n \"\"\"\n\n from bloqade.analog.constants import RB_C6\n\n # calculate the Interaction matrix\n V_ij = np.zeros((self.n_sites, self.n_sites))\n for i, site_i in enumerate(self.enumerate()):\n pos_i = np.array([float(ele(**assignments)) for ele in site_i.position])\n\n for j, site_j in enumerate(self.enumerate()):\n if j >= i:\n break # enforce lower triangular form\n\n pos_j = np.array([float(ele(**assignments)) for ele in site_j.position])\n r_ij = np.linalg.norm(pos_i - pos_j)\n\n V_ij[i, j] = RB_C6 / r_ij**6\n\n return V_ij\n
>>> reg = start.add_position([(0,0), (1,1)])\n# atom positions are now (0,0), (2,2)\n>>> new_reg = reg.scale(2)\n# you may also use scale on pre-defined geometries\n>>> from bloqade.analog.atom_arrangement import Chain\n# atoms in the chain will now be 2 um apart versus\n# the default 1 um\n>>> Chain(11).scale(2)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef scale(self, scale: ScalarType):\n \"\"\"\n Scale the geometry of your atoms.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position([(0,0), (1,1)])\n # atom positions are now (0,0), (2,2)\n >>> new_reg = reg.scale(2)\n # you may also use scale on pre-defined geometries\n >>> from bloqade.analog.atom_arrangement import Chain\n # atoms in the chain will now be 2 um apart versus\n # the default 1 um\n >>> Chain(11).scale(2)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`:\n to specify Rydberg coupling\n - `...add_position(positions).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n scale = cast(scale)\n location_list = []\n for location_info in self.enumerate():\n x, y = location_info.position\n new_position = (scale * x, scale * y)\n location_list.append(\n LocationInfo.create(new_position, bool(location_info.filling.value))\n )\n\n return ListOfLocations(location_list)\n
calculate the coordinates of a cell in the lattice given the cell index.
Source code in src/bloqade/analog/ir/location/bravais.py
@beartype\ndef coordinates(self, index: List[int]) -> NDArray:\n \"\"\"calculate the coordinates of a cell in the lattice\n given the cell index.\n \"\"\"\n # damn! this is like stone age broadcasting\n vectors = np.array(self.cell_vectors())\n index = np.array(index)\n pos = np.sum(vectors.T * index, axis=1)\n return pos + np.array(self.cell_atoms())\n
calculate the coordinates of a cell in the lattice given the cell index.
Source code in src/bloqade/analog/ir/location/bravais.py
@beartype\ndef coordinates(self, index: List[int]) -> NDArray:\n \"\"\"calculate the coordinates of a cell in the lattice\n given the cell index.\n \"\"\"\n # damn! this is like stone age broadcasting\n vectors = np.array(self.cell_vectors())\n index = np.array(index)\n pos = np.sum(vectors.T * index, axis=1)\n return pos + np.array(self.cell_atoms())\n
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).
# single coordinate\n>>> reg = start.add_position((0,0))\n# you may chain add_position calls\n>>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n# you can add variables anywhere a value may be present\n>>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n# and specify your atom fillings\n>>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n[True, False])\n# alternatively you could use one boolean to specify\n# all coordinates should be empty/filled\n>>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n(5.2, 2.2)], False)\n
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
Source code in src/bloqade/analog/ir/location/location.py
def add_position(\n self,\n position: Union[\n PositionArray,\n List[Tuple[ScalarType, ScalarType]],\n Tuple[ScalarType, ScalarType],\n ],\n filling: Optional[Union[BoolArray, List[bool], bool]] = None,\n) -> \"ListOfLocations\":\n \"\"\"\n Add a position or multiple positions to a pre-existing geometry.\n\n `add_position` is capable of accepting:\n - A single tuple for one atom coordinate: `(1.0, 2.5)`\n - A list of tuples: `[(0.0, 1.0), (2.0,1.5), etc.]\n - A numpy array of shape (N, 2) where N is the number of atoms\n\n You may also intersperse variables anywhere a value may be present.\n\n You can also pass in an optional argument which determines the atom \"filling\"\n (whether or not at a specified coordinate an atom should be present).\n\n ### Usage Example:\n ```\n # single coordinate\n >>> reg = start.add_position((0,0))\n # you may chain add_position calls\n >>> reg_plus_two = reg.add_position([(2,2),(5.0, 2.1)])\n # you can add variables anywhere a value may be present\n >>> reg_with_var = reg_plus_two.add_position((\"x\", \"y\"))\n # and specify your atom fillings\n >>> reg_with_filling = reg_with_var.add_position([(3.1, 0.0), (4.1, 2.2)],\n [True, False])\n # alternatively you could use one boolean to specify\n # all coordinates should be empty/filled\n >>> reg_with_more_filling = reg_with_filling.add_positions([(3.1, 2.9),\n (5.2, 2.2)], False)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`: to specify Rydberg coupling\n - `...add_position(positions).hyperfine`: to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n if is_bearable(position, PositionArray) and is_bearable(\n filling, Optional[BoolArray]\n ):\n return self.add_position_ndarray(position, filling)\n elif is_bearable(position, List[Tuple[ScalarType, ScalarType]]) and is_bearable(\n filling, Optional[List[bool]]\n ):\n return self.add_position_list_tuples(position, filling)\n elif is_bearable(position, Tuple[ScalarType, ScalarType]) and is_bearable(\n filling, Optional[bool]\n ):\n return self.add_position_single_tupe(position, filling)\n else:\n raise TypeError(\"Invalid input types for add_position provided!\")\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_count(2, custom_rng)\n# you may also chain apply_defect_count calls\n>>> reg.apply_defect_count(2, custom_rng)\n# you can also use apply_defect_count on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_count(\n self, n_defects: int, rng: np.random.Generator = np.random.default_rng()\n):\n \"\"\"\n Drop `n_defects` atoms from the geometry randomly. Internally this occurs\n by setting certain sites to have a SiteFilling set to false indicating\n no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_count(2, custom_rng)\n # you may also chain apply_defect_count calls\n >>> reg.apply_defect_count(2, custom_rng)\n # you can also use apply_defect_count on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)]).apply_defect_count(1, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts)\n .apply_defect_count(n_defects)`: to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`: to specify\n Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n location_list = []\n for location_info in self.enumerate():\n location_list.append(location_info)\n\n filled_sites = []\n\n for index, location_info in enumerate(location_list):\n if location_info.filling is SiteFilling.filled:\n filled_sites.append(index)\n\n if n_defects >= len(filled_sites):\n raise ValueError(\n f\"n_defects {n_defects} must be less than the number of filled sites \"\n f\"({len(filled_sites)})\"\n )\n\n for _ in range(n_defects):\n index = rng.choice(filled_sites)\n location_list[index] = LocationInfo.create(\n location_list[index].position,\n (False if location_list[index].filling is SiteFilling.filled else True),\n )\n filled_sites.remove(index)\n\n return ListOfLocations(location_list)\n
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.
>>> from bloqade.analog.atom_arrangement import Chain\n>>> import numpy as np\n# set a custom seed for a numpy-based RNG\n>>> custom_rng = np.random.default_rng(888)\n# randomly remove two atoms from the geometry\n>>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n# you may also chain apply_defect_density calls\n>>> reg.apply_defect_count(0.1, custom_rng)\n# you can also use apply_defect_density on custom geometries\n>>> from bloqade import start\n>>> start.add_position([(0,0), (1,1)])\n.apply_defect_density(0.5, custom_rng)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef apply_defect_density(\n self,\n defect_probability: float,\n rng: np.random.Generator = np.random.default_rng(),\n):\n \"\"\"\n Drop atoms randomly with `defect_probability` probability (range of 0 to 1).\n Internally this occurs by setting certain sites to have a SiteFilling\n set to false indicating no atom is present at the coordinate.\n\n A default numpy-based Random Number Generator is used but you can\n explicitly override this by passing in your own.\n\n ### Usage Example:\n\n ```\n >>> from bloqade.analog.atom_arrangement import Chain\n >>> import numpy as np\n # set a custom seed for a numpy-based RNG\n >>> custom_rng = np.random.default_rng(888)\n # randomly remove two atoms from the geometry\n >>> reg = Chain(11).apply_defect_density(0.2, custom_rng)\n # you may also chain apply_defect_density calls\n >>> reg.apply_defect_count(0.1, custom_rng)\n # you can also use apply_defect_density on custom geometries\n >>> from bloqade import start\n >>> start.add_position([(0,0), (1,1)])\n .apply_defect_density(0.5, custom_rng)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...apply_defect_count(defect_counts).add_position(positions)`:\n to add more positions\n - `...apply_defect_count(defect_counts).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...apply_defect_count(defect_counts)\n .apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...apply_defect_count(defect_counts).scale(scale)`:\n to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...apply_defect_count(defect_counts).rydberg`:\n to specify Rydberg coupling\n - `...apply_defect_count(defect_counts).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...apply_defect_count(defect_counts).show()`:\n shows your geometry in your web browser\n \"\"\"\n\n p = min(1, max(0, defect_probability))\n location_list = []\n\n for location_info in self.enumerate():\n if rng.random() < p:\n location_list.append(\n LocationInfo.create(\n location_info.position,\n (\n False\n if location_info.filling is SiteFilling.filled\n else True\n ),\n )\n )\n else:\n location_list.append(location_info)\n\n return ListOfLocations(location_list=location_list)\n
Source code in src/bloqade/analog/ir/location/location.py
def figure(self, fig_kwargs=None, **assignments):\n \"\"\"obtain a figure object from the atom arrangement.\"\"\"\n return get_atom_arrangement_figure(self, fig_kwargs=fig_kwargs, **assignments)\n
the values to assign to the variables in the register.
{}
Returns:
Name Type Description NDArrayNDArray
the Rydberg interaction matrix in the lower triangular form.
Source code in src/bloqade/analog/ir/location/location.py
def rydberg_interaction(self, **assignments) -> NDArray:\n \"\"\"calculate the Rydberg interaction matrix.\n\n Args:\n **assignments: the values to assign to the variables in the register.\n\n Returns:\n NDArray: the Rydberg interaction matrix in the lower triangular form.\n\n \"\"\"\n\n from bloqade.analog.constants import RB_C6\n\n # calculate the Interaction matrix\n V_ij = np.zeros((self.n_sites, self.n_sites))\n for i, site_i in enumerate(self.enumerate()):\n pos_i = np.array([float(ele(**assignments)) for ele in site_i.position])\n\n for j, site_j in enumerate(self.enumerate()):\n if j >= i:\n break # enforce lower triangular form\n\n pos_j = np.array([float(ele(**assignments)) for ele in site_j.position])\n r_ij = np.linalg.norm(pos_i - pos_j)\n\n V_ij[i, j] = RB_C6 / r_ij**6\n\n return V_ij\n
>>> reg = start.add_position([(0,0), (1,1)])\n# atom positions are now (0,0), (2,2)\n>>> new_reg = reg.scale(2)\n# you may also use scale on pre-defined geometries\n>>> from bloqade.analog.atom_arrangement import Chain\n# atoms in the chain will now be 2 um apart versus\n# the default 1 um\n>>> Chain(11).scale(2)\n
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
Source code in src/bloqade/analog/ir/location/location.py
@beartype\ndef scale(self, scale: ScalarType):\n \"\"\"\n Scale the geometry of your atoms.\n\n ### Usage Example:\n ```\n >>> reg = start.add_position([(0,0), (1,1)])\n # atom positions are now (0,0), (2,2)\n >>> new_reg = reg.scale(2)\n # you may also use scale on pre-defined geometries\n >>> from bloqade.analog.atom_arrangement import Chain\n # atoms in the chain will now be 2 um apart versus\n # the default 1 um\n >>> Chain(11).scale(2)\n ```\n\n - Next possible steps are:\n - Continuing to build your geometry via:\n - `...add_position(positions).add_position(positions)`:\n to add more positions\n - `...add_position(positions).apply_defect_count(n_defects)`:\n to randomly drop out n_atoms\n - `...add_position(positions).apply_defect_density(defect_probability)`:\n to drop out atoms with a certain probability\n - `...add_position(positions).scale(scale)`: to scale the geometry\n - Targeting a level coupling once you're done with the atom geometry:\n - `...add_position(positions).rydberg`:\n to specify Rydberg coupling\n - `...add_position(positions).hyperfine`:\n to specify Hyperfine coupling\n - Visualizing your atom geometry:\n - `...add_position(positions).show()`:\n shows your geometry in your web browser\n\n \"\"\"\n\n scale = cast(scale)\n location_list = []\n for location_info in self.enumerate():\n x, y = location_info.position\n new_position = (scale * x, scale * y)\n location_list.append(\n LocationInfo.create(new_position, bool(location_info.filling.value))\n )\n\n return ListOfLocations(location_list)\n
which parameter set out of the batch to use. Default is 0. If there are no batch parameters, use 0.
*args: Any Specify the parameters that are defined in the .args([...]) build step.
Source code in src/bloqade/analog/ir/routine/base.py
def show(self: \"RoutineBase\", *args, batch_index: int = 0):\n \"\"\"Show an interactive plot of the routine.\n\n batch_index: int\n which parameter set out of the batch to use. Default is 0.\n If there are no batch parameters, use 0.\n\n *args: Any\n Specify the parameters that are defined in the `.args([...])` build step.\n\n \"\"\"\n if self.source is None:\n raise ValueError(\"Cannot show a routine without a source Builder.\")\n\n return self.source.show(*args, batch_id=batch_index)\n
Evolve an initial state vector using the Hamiltonian
Parameters:
Name Type Description Default stateOptional[StateVector]
The initial state vector to
Nonesolver_namestr
Which SciPy Solver to use. Defaults to
'dop853'atolfloat
Absolute tolerance for ODE solver. Defaults
1e-07rtolfloat
Relative tolerance for adaptive step in
1e-14nstepsint
Maximum number of steps allowed per integration
2147483647timesSequence[float]
The times to evaluate the state vector
()interaction_picturebool
Use the interaction picture when
False
Returns:
Type Description Iterator[StateVector]
Iterator[StateVector]: An iterator of the state vectors at each time step.
Source code in src/bloqade/analog/ir/routine/bloqade.py
def evolve(\n self,\n state: Optional[StateVector] = None,\n solver_name: str = \"dop853\",\n atol: float = 1e-7,\n rtol: float = 1e-14,\n nsteps: int = 2147483647,\n times: Sequence[float] = (),\n interaction_picture: bool = False,\n) -> Iterator[StateVector]:\n \"\"\"Evolve an initial state vector using the Hamiltonian\n\n Args:\n state (Optional[StateVector], optional): The initial state vector to\n evolve. if not provided, the zero state will be used. Defaults to None.\n solver_name (str, optional): Which SciPy Solver to use. Defaults to\n \"dop853\".\n atol (float, optional): Absolute tolerance for ODE solver. Defaults\n to 1e-14.\n rtol (float, optional): Relative tolerance for adaptive step in\n ODE solver. Defaults to 1e-7.\n nsteps (int, optional): Maximum number of steps allowed per integration\n step. Defaults to 2147483647.\n times (Sequence[float], optional): The times to evaluate the state vector\n at. Defaults to (). If not provided the state will be evaluated at\n the end of the bloqade program.\n interaction_picture (bool, optional): Use the interaction picture when\n solving schrodinger equation. Defaults to False.\n\n Returns:\n Iterator[StateVector]: An iterator of the state vectors at each time step.\n\n \"\"\"\n state = self.zero_state(np.complex128) if state is None else state\n\n U = AnalogGate(self.hamiltonian)\n\n return U.apply(\n state,\n times=times,\n solver_name=solver_name,\n atol=atol,\n rtol=rtol,\n nsteps=nsteps,\n interaction_picture=interaction_picture,\n )\n
Source code in src/bloqade/analog/ir/routine/bloqade.py
def fock_state(\n self, fock_state_str: str, dtype: np.dtype = np.float64\n) -> StateVector:\n \"\"\"Return the fock state for the given Hamiltonian.\"\"\"\n index = self.hamiltonian.space.fock_state_to_index(fock_state_str)\n data = np.zeros(self.hamiltonian.space.size, dtype=dtype)\n data[index] = 1\n return StateVector(data, self.hamiltonian.space)\n
Source code in src/bloqade/analog/ir/routine/bloqade.py
def zero_state(self, dtype: np.dtype = np.float64) -> StateVector:\n \"\"\"Return the zero state for the given Hamiltonian.\"\"\"\n return self.hamiltonian.space.zero_state(dtype)\n
Generates a list of BloqadeEmulation objects which contain the Hamiltonian of your program.
If you have a variable(s) in your program you have assigned multiple values via batch_assign() there will be multiple BloqadeEmulation objects, one for each value. On the other hand if the program only assumes a singular value per each variable, there will only be one BloqadeEmulation object but it will still be encapsulated in a list.
Parameters:
Name Type Description Default *argsLiteralType
If your program has a variable that was declared as run-time assignable via .args you may pass a value to it here. If there are multiple variables declared via .args the order in which you assign values to those variables through this argument should follow the order in which the declaration occurred.
()blockade_radiusfloat
The radius in which atoms blockade eachother. Default value is 0.0 micrometers.
0.0use_hyperfinebool
Should the Hamiltonian account for hyperfine levels. Default value is False.
Falsewaveform_runtimestr
Specify which runtime to use for waveforms. If \"numba\" is specify the waveform is compiled, otherwise it is interpreted via the \"interpret\" argument. Defaults to \"interpret\".
'interpret'cache_matricesbool
Speed up Hamiltonian generation by reusing data (when possible) from previously generated Hamiltonians. Default value is False.
False
Returns:
Type Description List[BloqadeEmulation]
List[BloqadeEmulation]
Source code in src/bloqade/analog/ir/routine/bloqade.py
def hamiltonian(\n self,\n *args: LiteralType,\n blockade_radius: float = 0.0,\n use_hyperfine: bool = False,\n waveform_runtime: str = \"interpret\",\n cache_matrices: bool = False,\n) -> List[BloqadeEmulation]:\n \"\"\"\n Generates a list of BloqadeEmulation objects which contain the Hamiltonian of your program.\n\n If you have a variable(s) in your program you have assigned multiple values via `batch_assign()`\n there will be multiple `BloqadeEmulation` objects, one for each value. On the other hand\n if the program only assumes a singular value per each variable, there will only be\n one `BloqadeEmulation` object but it will still be encapsulated in a list.\n\n\n Args:\n *args (LiteralType): If your program has a variable that was declared as run-time assignable\n via `.args` you may pass a value to it here. If there are multiple\n variables declared via `.args` the order in which you assign values to those variables\n through this argument should follow the order in which the declaration occurred.\n blockade_radius (float): The radius in which atoms blockade eachother. Default value is 0.0 micrometers.\n use_hyperfine (bool): Should the Hamiltonian account for hyperfine levels. Default value is False.\n waveform_runtime (str): Specify which runtime to use for waveforms. If \"numba\" is specify the waveform\n is compiled, otherwise it is interpreted via the \"interpret\" argument. Defaults to \"interpret\".\n cache_matrices (bool): Speed up Hamiltonian generation by reusing data (when possible) from previously generated Hamiltonians.\n Default value is False.\n\n Returns:\n List[BloqadeEmulation]\n\n \"\"\"\n ir_iter = self._generate_ir(\n args, blockade_radius, waveform_runtime, use_hyperfine\n )\n\n if cache_matrices:\n compile_cache = CompileCache()\n else:\n compile_cache = None\n\n return [\n BloqadeEmulation(task_data, compile_cache=compile_cache)\n for task_data in ir_iter\n ]\n
Run the current program using bloqade python backend
Parameters:
Name Type Description Default shotsint
number of shots after running state vector simulation
required argsTuple[LiteralType, ...]
The values for parameters defined
()nameOptional[str]
Name to give this run. Defaults to None.
Noneblockade_radiusfloat
Use the Blockade subspace given a
0.0waveform_runtimestr
(bool, optional): Use Numba to compile the waveforms,
'interpret'interaction_picturebool
Use the interaction picture when
Falsecache_matricesbool
Reuse previously evaluated matrcies when
Falsemultiprocessingbool
Use multiple processes to process the
Falsenum_workersOptional[int]
Number of processes to run with
Nonesolver_namestr
Which SciPy Solver to use. Defaults to
'dop853'atolfloat
Absolute tolerance for ODE solver. Defaults to
1e-07rtolfloat
Relative tolerance for adaptive step in ODE solver.
1e-14nstepsint
Maximum number of steps allowed per integration
2147483647
Raises:
Type Description ValueError
Cannot use multiprocessing and cache_matrices at the same time.
Returns:
Name Type Description LocalBatchLocalBatch
Batch of local tasks that have been executed.
Source code in src/bloqade/analog/ir/routine/bloqade.py
@beartype\ndef run(\n self,\n shots: int,\n args: Tuple[LiteralType, ...] = (),\n name: Optional[str] = None,\n blockade_radius: float = 0.0,\n waveform_runtime: str = \"interpret\",\n interaction_picture: bool = False,\n cache_matrices: bool = False,\n multiprocessing: bool = False,\n num_workers: Optional[int] = None,\n solver_name: str = \"dop853\",\n atol: float = 1e-7,\n rtol: float = 1e-14,\n nsteps: int = 2_147_483_647,\n) -> LocalBatch:\n \"\"\"Run the current program using bloqade python backend\n\n Args:\n shots (int): number of shots after running state vector simulation\n args (Tuple[LiteralType, ...], optional): The values for parameters defined\n in `args`. Defaults to ().\n name (Optional[str], optional): Name to give this run. Defaults to None.\n blockade_radius (float, optional): Use the Blockade subspace given a\n particular radius. Defaults to 0.0.\n waveform_runtime: (bool, optional): Use Numba to compile the waveforms,\n Defaults to False.\n interaction_picture (bool, optional): Use the interaction picture when\n solving schrodinger equation. Defaults to False.\n cache_matrices (bool, optional): Reuse previously evaluated matrcies when\n possible. Defaults to False.\n multiprocessing (bool, optional): Use multiple processes to process the\n batches. Defaults to False.\n num_workers (Optional[int], optional): Number of processes to run with\n multiprocessing. Defaults to None.\n solver_name (str, optional): Which SciPy Solver to use. Defaults to\n \"dop853\".\n atol (float, optional): Absolute tolerance for ODE solver. Defaults to\n 1e-14.\n rtol (float, optional): Relative tolerance for adaptive step in ODE solver.\n Defaults to 1e-7.\n nsteps (int, optional): Maximum number of steps allowed per integration\n step. Defaults to 2_147_483_647, the maximum value.\n\n Raises:\n ValueError: Cannot use multiprocessing and cache_matrices at the same time.\n\n Returns:\n LocalBatch: Batch of local tasks that have been executed.\n \"\"\"\n if multiprocessing and cache_matrices:\n raise ValueError(\n \"Cannot use multiprocessing and cache_matrices at the same time.\"\n )\n\n compile_options = dict(\n shots=shots,\n args=args,\n name=name,\n blockade_radius=blockade_radius,\n cache_matrices=cache_matrices,\n waveform_runtime=waveform_runtime,\n )\n\n solver_options = dict(\n multiprocessing=multiprocessing,\n num_workers=num_workers,\n solver_name=solver_name,\n atol=atol,\n rtol=rtol,\n nsteps=nsteps,\n interaction_picture=interaction_picture,\n )\n\n batch = self._compile(**compile_options)\n batch._run(**solver_options)\n\n return batch\n
Run state-vector simulation with a callback to access full state-vector from emulator
Parameters:
Name Type Description Default callbackCallable[[StateVector, Metadata, RydbergHamiltonian, Any], Any] required program_argsTuple[LiteralType, ...]
The values for parameters
()callback_argsTuple[Any, ...]
Extra arguments to pass into
()ignore_exceptionsbool
(bool, optional) If True any exception raised during
Falseblockade_radiusfloat
Use the Blockade subspace given a
0.0waveform_runtimestr
(str, optional): Specify which runtime to use for
'interpret'interaction_picturebool
Use the interaction picture when
Falsecache_matricesbool
Reuse previously evaluated matrcies when
Falsemultiprocessingbool
Use multiple processes to process the
Falsenum_workersOptional[int]
Number of processes to run with
Nonesolver_namestr
Which SciPy Solver to use. Defaults to
'dop853'atolfloat
Absolute tolerance for ODE solver. Defaults to
1e-07rtolfloat
Relative tolerance for adaptive step in ODE solver.
1e-14nstepsint
Maximum number of steps allowed per integration
2147483647
Returns:
Name Type Description ListList
List of resulting outputs from the callbacks
Raises:
Type Description RuntimeError
Raises the first error that occurs, only if
Note
For the callback function, first argument is the many-body wavefunction as a 1D complex numpy array, the second argument is of type Metadata which is a Named Tuple where the fields correspond to the parameters of that given task, RydbergHamiltonian is the object that contains the Hamiltonian used to generate the evolution for that task, Finally any optional positional arguments are allowed after that. The return value can be anything, the results will be collected in a list for each task in the batch.
Source code in src/bloqade/analog/ir/routine/bloqade.py
@beartype\ndef run_callback(\n self,\n callback: Callable[[StateVector, NamedTuple, RydbergHamiltonian, Any], Any],\n program_args: Tuple[LiteralType, ...] = (),\n callback_args: Tuple = (),\n ignore_exceptions: bool = False,\n blockade_radius: float = 0.0,\n waveform_runtime: str = \"interpret\",\n interaction_picture: bool = False,\n cache_matrices: bool = False,\n multiprocessing: bool = False,\n num_workers: Optional[int] = None,\n solver_name: str = \"dop853\",\n atol: float = 1e-7,\n rtol: float = 1e-14,\n nsteps: int = 2_147_483_647,\n use_hyperfine: bool = False,\n) -> List:\n \"\"\"Run state-vector simulation with a callback to access full state-vector from\n emulator\n\n Args:\n callback (Callable[[StateVector, Metadata, RydbergHamiltonian, Any], Any]):\n The callback function to run for each task in batch. See note below for more\n details about the signature of the function.\n program_args (Tuple[LiteralType, ...], optional): The values for parameters\n defined in `args`. Defaults to ().\n callback_args (Tuple[Any,...], optional): Extra arguments to pass into\n ignore_exceptions: (bool, optional) If `True` any exception raised during\n a task will be saved instead of the resulting output of the callback,\n otherwise the first exception by task number will be raised after *all*\n tasks have executed. Defaults to False.\n blockade_radius (float, optional): Use the Blockade subspace given a\n particular radius. Defaults to 0.0.\n waveform_runtime: (str, optional): Specify which runtime to use for\n waveforms. Defaults to \"interpret\".\n interaction_picture (bool, optional): Use the interaction picture when\n solving schrodinger equation. Defaults to False.\n cache_matrices (bool, optional): Reuse previously evaluated matrcies when\n possible. Defaults to False.\n multiprocessing (bool, optional): Use multiple processes to process the\n batches. Defaults to False.\n num_workers (Optional[int], optional): Number of processes to run with\n multiprocessing. Defaults to None.\n solver_name (str, optional): Which SciPy Solver to use. Defaults to\n \"dop853\".\n atol (float, optional): Absolute tolerance for ODE solver. Defaults to\n 1e-14.\n rtol (float, optional): Relative tolerance for adaptive step in ODE solver.\n Defaults to 1e-7.\n nsteps (int, optional): Maximum number of steps allowed per integration\n step. Defaults to 2_147_483_647, the maximum value.\n\n Returns:\n List: List of resulting outputs from the callbacks\n\n Raises:\n RuntimeError: Raises the first error that occurs, only if\n `ignore_exceptions=False`.\n\n Note:\n For the `callback` function, first argument is the many-body wavefunction\n as a 1D complex numpy array, the second argument is of type `Metadata` which\n is a Named Tuple where the fields correspond to the parameters of that given\n task, RydbergHamiltonian is the object that contains the Hamiltonian used to\n generate the evolution for that task, Finally any optional positional\n arguments are allowed after that. The return value can be anything, the\n results will be collected in a list for each task in the batch.\n\n\n \"\"\"\n if multiprocessing:\n from multiprocessing import Queue, Process, cpu_count\n else:\n from queue import Queue\n\n if cache_matrices:\n compile_cache = CompileCache()\n else:\n compile_cache = None\n\n solver_args = dict(\n solver_name=solver_name,\n atol=atol,\n rtol=rtol,\n nsteps=nsteps,\n interaction_picture=interaction_picture,\n )\n\n runner = self.EmuRunner(\n compile_cache=compile_cache,\n solver_args=solver_args,\n callback=callback,\n callback_args=callback_args,\n )\n\n tasks = Queue()\n results = Queue()\n\n total_tasks = 0\n ir_iter = self._generate_ir(\n program_args, blockade_radius, waveform_runtime, use_hyperfine\n )\n for task_data in ir_iter:\n task_number = task_data.task_id\n emulator_ir = task_data.emulator_ir\n metadata = task_data.metadata_dict\n total_tasks += 1\n tasks.put((task_number, (emulator_ir, metadata)))\n\n workers = []\n if multiprocessing:\n num_workers = max(int(num_workers or cpu_count()), 1)\n num_workers = min(total_tasks, num_workers)\n\n for _ in range(num_workers):\n worker = Process(\n target=BloqadePythonRoutine.process_tasks,\n args=(runner, tasks, results),\n )\n worker.start()\n\n workers.append(worker)\n else:\n self.process_tasks(runner, tasks, results)\n\n # blocks until all\n # results have been fetched\n # from the id_results Queue\n id_results = []\n for i in range(total_tasks):\n id_results.append(results.get())\n\n if workers:\n for worker in workers:\n worker.join()\n\n tasks.close()\n results.close()\n\n id_results.sort(key=lambda x: x[0])\n results = []\n\n for task_id, result in id_results:\n if not ignore_exceptions and isinstance(result, BaseException):\n try:\n raise result\n except BaseException:\n raise RuntimeError(\n f\"{result.__class__.__name__} occured during child process \"\n f\"running for task number {task_id}:\\n{traceback.format_exc()}\"\n )\n\n results.append(result)\n\n return results\n
Compile to a RemoteBatch, which contain Braket backend specific tasks, run_async to Braket, and wait until the results are coming back.
Note
This is sync, and will wait until remote results finished.
Parameters:
Name Type Description Default shotsint
number of shots
1argsLiteralType
additional arguments for args variables.
()namestr
custom name of the batch
Noneshufflebool
shuffle the order of jobs
False Return
RemoteBatch
Source code in src/bloqade/analog/ir/routine/braket.py
@beartype\ndef __call__(\n self,\n *args: LiteralType,\n shots: int = 1,\n name: Optional[str] = None,\n use_experimental: bool = False,\n shuffle: bool = False,\n **kwargs,\n):\n \"\"\"\n Compile to a RemoteBatch, which contain\n Braket backend specific tasks, run_async to Braket,\n and wait until the results are coming back.\n\n Note:\n This is sync, and will wait until remote results\n finished.\n\n Args:\n shots (int): number of shots\n args: additional arguments for args variables.\n name (str): custom name of the batch\n shuffle (bool): shuffle the order of jobs\n\n Return:\n RemoteBatch\n\n \"\"\"\n return self.run(shots, args, name, use_experimental, shuffle, **kwargs)\n
Compile to a RemoteBatch, which contain Braket backend specific tasks, run_async to Braket, and wait until the results are coming back.
Note
This is sync, and will wait until remote results finished.
Parameters:
Name Type Description Default shotsint
number of shots
required argsTuple
additional arguments
()namestr
custom name of the batch
Noneshufflebool
shuffle the order of jobs
False Return
RemoteBatch
Source code in src/bloqade/analog/ir/routine/braket.py
@beartype\ndef run(\n self,\n shots: int,\n args: Tuple[LiteralType, ...] = (),\n name: Optional[str] = None,\n use_experimental: bool = False,\n shuffle: bool = False,\n **kwargs,\n) -> RemoteBatch:\n \"\"\"\n Compile to a RemoteBatch, which contain\n Braket backend specific tasks, run_async to Braket,\n and wait until the results are coming back.\n\n Note:\n This is sync, and will wait until remote results\n finished.\n\n Args:\n shots (int): number of shots\n args (Tuple): additional arguments\n name (str): custom name of the batch\n shuffle (bool): shuffle the order of jobs\n\n Return:\n RemoteBatch\n\n \"\"\"\n\n batch = self.run_async(shots, args, name, use_experimental, shuffle, **kwargs)\n batch.pull()\n return batch\n
Compile to a RemoteBatch, which contain Braket backend specific tasks, and run_async to Braket.
Note
This is async.
Parameters:
Name Type Description Default shotsint
number of shots
required argsTuple
Values of the parameter defined in args, defaults to ()
()namestr | None
custom name of the batch, defaults to None
Noneuse_experimentalbool
Use experimental hardware capabilities
Falseshufflebool
shuffle the order of jobs
False Return
RemoteBatch
Source code in src/bloqade/analog/ir/routine/braket.py
@beartype\ndef run_async(\n self,\n shots: int,\n args: Tuple[LiteralType, ...] = (),\n name: Optional[str] = None,\n use_experimental: bool = False,\n shuffle: bool = False,\n **kwargs,\n) -> RemoteBatch:\n \"\"\"\n Compile to a RemoteBatch, which contain\n Braket backend specific tasks, and run_async to Braket.\n\n Note:\n This is async.\n\n Args:\n shots (int): number of shots\n args (Tuple): Values of the parameter defined in `args`, defaults to ()\n name (str | None): custom name of the batch, defaults to None\n use_experimental (bool): Use experimental hardware capabilities\n shuffle (bool): shuffle the order of jobs\n\n Return:\n RemoteBatch\n\n \"\"\"\n\n batch = self._compile(shots, use_experimental, args, name)\n batch._submit(shuffle, **kwargs)\n return batch\n
Compile to a LocalBatch, and run. The LocalBatch contain tasks to run on local emulator.
Note
This is sync, and will wait until remote results finished.
Parameters:
Name Type Description Default shotsint
number of shots
1argsLiteralType
additional arguments for args variables.
()multiprocessingbool
enable multi-process
Falsenum_workersint
number of workers to run the emulator
None Return
LocalBatch
Source code in src/bloqade/analog/ir/routine/braket.py
@beartype\ndef __call__(\n self,\n *args: LiteralType,\n shots: int = 1,\n name: Optional[str] = None,\n multiprocessing: bool = False,\n num_workers: Optional[int] = None,\n **kwargs,\n):\n \"\"\"\n Compile to a LocalBatch, and run.\n The LocalBatch contain tasks to run on local emulator.\n\n Note:\n This is sync, and will wait until remote results\n finished.\n\n Args:\n shots (int): number of shots\n args: additional arguments for args variables.\n multiprocessing (bool): enable multi-process\n num_workers (int): number of workers to run the emulator\n\n Return:\n LocalBatch\n\n \"\"\"\n return self.run(\n shots,\n args,\n name,\n multiprocessing=multiprocessing,\n num_workers=num_workers,\n **kwargs,\n )\n
Compile to a LocalBatch, and run. The LocalBatch contain tasks to run on local emulator.
Note
This is sync, and will wait until remote results finished.
Parameters:
Name Type Description Default shotsint
number of shots
required argsTuple[LiteralType, ...]
additional arguments for args variables.
()multiprocessingbool
enable multi-process
Falsenum_workersint
number of workers to run the emulator
None Return
LocalBatch
Source code in src/bloqade/analog/ir/routine/braket.py
@beartype\ndef run(\n self,\n shots: int,\n args: Tuple[LiteralType, ...] = (),\n name: Optional[str] = None,\n multiprocessing: bool = False,\n num_workers: Optional[int] = None,\n **kwargs,\n) -> LocalBatch:\n \"\"\"\n Compile to a LocalBatch, and run.\n The LocalBatch contain tasks to run on local emulator.\n\n Note:\n This is sync, and will wait until remote results\n finished.\n\n Args:\n shots (int): number of shots\n args: additional arguments for args variables.\n multiprocessing (bool): enable multi-process\n num_workers (int): number of workers to run the emulator\n\n Return:\n LocalBatch\n\n \"\"\"\n\n batch = self._compile(shots, args, name)\n batch._run(multiprocessing=multiprocessing, num_workers=num_workers, **kwargs)\n return batch\n
Source code in src/bloqade/analog/ir/routine/quera.py
def submit(\n self,\n shots: int,\n url: str,\n json_body_template: str,\n method: str = \"POST\",\n args: Tuple[LiteralType] = (),\n request_options: Dict[str, Any] = {},\n use_experimental: bool = False,\n sleep_time: float = 0.1,\n) -> List[Tuple[NamedTuple, Response]]:\n \"\"\"Compile to QuEraTaskSpecification and submit to a custom service.\n\n Args:\n shots (int): number of shots\n url (str): url of the custom service\n json_body_template (str): json body template, must contain '{task_ir}'\n which is a placeholder for a string representation of the task ir.\n The task ir string will be inserted into the template with\n `json_body_template.format(task_ir=task_ir_string)`.\n to be replaced by QuEraTaskSpecification\n method (str): http method to be used. Defaults to \"POST\".\n args (Tuple[LiteralType]): additional arguments to be passed into the\n compiler coming from `args` option of the build. Defaults to ().\n request_options: additional options to be passed into the request method,\n Note the `data` option will be overwritten by the\n `json_body_template.format(task_ir=task_ir_string)`.\n use_experimental (bool): Enable experimental hardware capabilities\n sleep_time (float): time to sleep between each request. Defaults to 0.1.\n\n Returns:\n List[Tuple[NamedTuple, Response]]: List of parameters for each batch in\n the task and the response from the post request.\n\n Examples:\n Here is a simple example of how to use this method. Note the body_template\n has double curly braces on the outside to escape the string formatting.\n\n ```python\n >>> body_template = \"{{\"token\": \"my_token\", \"task\": {task_ir}}}\"\n >>> responses = (\n program.quera.custom.submit(\n 100,\n \"http://my_custom_service.com\",\n body_template\n )\n )\n ```\n \"\"\"\n\n if r\"{task_ir}\" not in json_body_template:\n raise ValueError(r\"body_template must contain '{task_ir}'\")\n\n partial_eval = json_body_template.format(task_ir='\"task_ir\"')\n try:\n _ = json.loads(partial_eval)\n except json.JSONDecodeError as e:\n raise ValueError(\n \"body_template must be a valid json template. \"\n 'When evaluating template with task_ir=\"task_ir\", '\n f\"the template evaluated to: {partial_eval!r}.\\n\"\n f\"JSONDecodeError: {e}\"\n )\n\n out = []\n for metadata, task_ir in self._compile(shots, use_experimental, args):\n json_request_body = json_body_template.format(\n task_ir=task_ir.json(exclude_none=True, exclude_unset=True)\n )\n request_options.update(data=json_request_body)\n response = request(method, url, **request_options)\n out.append((metadata, response))\n time.sleep(sleep_time)\n\n return out\n
Gets the QuEraTaskStatusCode object for working with Bloqade SDK.
Parameters:
Name Type Description Default braket_statusstr
str The value of status in metadata() in the Amazon Braket. GetQuantumTask operation. If use_cached_value is True, the value most recently returned from GetQuantumTask operation is used
required
Returns:
Type Description QuEraTaskStatusCode
An object of the type Field in Braket SDK
Source code in src/bloqade/analog/submission/ir/braket.py
def from_braket_status_codes(braket_status: str) -> QuEraTaskStatusCode:\n \"\"\"Gets the `QuEraTaskStatusCode` object for working with Bloqade SDK.\n\n Args:\n braket_status: str\n The value of status in metadata() in the Amazon Braket.\n `GetQuantumTask` operation. If `use_cached_value` is `True`,\n the value most recently returned from\n `GetQuantumTask` operation is used\n\n Returns:\n An object of the type `Field` in Braket SDK\n \"\"\"\n if braket_status == str(\"QUEUED\"):\n return QuEraTaskStatusCode.Enqueued\n else:\n return QuEraTaskStatusCode(braket_status.lower().capitalize())\n
Get the QuEraTaskResults object for working with Bloqade SDK.
Parameters:
Name Type Description Default braket_task_resultsAnalogHamiltonianSimulationTaskResult
AnalogHamiltonianSimulationTaskResult Quantum task result of braket system
required
Returns:
Type Description QuEraTaskResults
An object of the type Field in Braket SDK.
Source code in src/bloqade/analog/submission/ir/braket.py
def from_braket_task_results(\n braket_task_results: AnalogHamiltonianSimulationTaskResult,\n) -> QuEraTaskResults:\n \"\"\"Get the `QuEraTaskResults` object for working with Bloqade SDK.\n\n Args:\n braket_task_results: AnalogHamiltonianSimulationTaskResult\n Quantum task result of braket system\n\n Returns:\n An object of the type `Field` in Braket SDK.\n \"\"\"\n shot_outputs = []\n for measurement in braket_task_results.measurements:\n shot_outputs.append(\n QuEraShotResult(\n shot_status=QuEraShotStatusCode.Completed,\n pre_sequence=list(measurement.pre_sequence),\n post_sequence=list(measurement.post_sequence),\n )\n )\n\n return QuEraTaskResults(\n task_status=QuEraTaskStatusCode.Completed, shot_outputs=shot_outputs\n )\n
Converts to TimeSeries object supported by Braket.
Parameters:
Name Type Description Default quera_fieldUnion[GlobalField, LocalField)]
Field supported by Quera
required
Returns:
Type Description Field
An object of the type braket.ahs.field.Field
Raises:
Type Description TypeError
If field is not of the type GlobalField or LocalField.
Source code in src/bloqade/analog/submission/ir/braket.py
def to_braket_field(quera_field: Union[GlobalField, LocalField]) -> Field:\n \"\"\"Converts to `TimeSeries` object supported by Braket.\n\n Args:\n quera_field (Union[GlobalField, LocalField)]:\n Field supported by Quera\n\n Returns:\n An object of the type `braket.ahs.field.Field`\n\n Raises:\n TypeError: If field is not of the type `GlobalField` or `LocalField`.\n \"\"\"\n if isinstance(quera_field, GlobalField):\n times = quera_field.times\n values = quera_field.values\n time_series = to_braket_time_series(times, values)\n return Field(pattern=\"uniform\", time_series=time_series)\n elif isinstance(quera_field, LocalField):\n times = quera_field.times\n values = quera_field.values\n pattern = quera_field.lattice_site_coefficients\n time_series = to_braket_time_series(times, values)\n pattern = Pattern(pattern)\n return Field(pattern=pattern, time_series=time_series)\n else:\n raise TypeError\n
Converts to Tuple[int, AnalogHamiltonianSimulation] object supported by Braket.
Parameters:
Name Type Description Default quera_task_irQuEraTaskSpecification
Quera IR(Intermediate representation) of the task.
required
Returns:
Type Description Tuple[int, AnalogHamiltonianSimulation]
An tuple of the type Tuple[int, AnalogHamiltonianSimulation].
Source code in src/bloqade/analog/submission/ir/braket.py
def to_braket_task(\n quera_task_ir: QuEraTaskSpecification,\n) -> Tuple[int, AnalogHamiltonianSimulation]:\n \"\"\"Converts to `Tuple[int, AnalogHamiltonianSimulation]` object supported by Braket.\n\n Args:\n quera_task_ir (QuEraTaskSpecification):\n Quera IR(Intermediate representation) of the task.\n\n Returns:\n An tuple of the type `Tuple[int, AnalogHamiltonianSimulation]`.\n \"\"\"\n braket_ahs_program = extract_braket_program(quera_task_ir)\n return quera_task_ir.nshots, braket_ahs_program\n
Converts quera IR(Intermendiate Representation) to to BraketTaskSpecification object.
Parameters:
Name Type Description Default quera_task_irQuEraTaskSpecification
Quera IR(Intermediate representation) of the task.
required
Returns:
Type Description BraketTaskSpecification
An object of the type BraketTaskSpecification in Braket SDK
Source code in src/bloqade/analog/submission/ir/braket.py
def to_braket_task_ir(quera_task_ir: QuEraTaskSpecification) -> BraketTaskSpecification:\n \"\"\"Converts quera IR(Intermendiate Representation) to\n to `BraketTaskSpecification` object.\n\n Args:\n quera_task_ir (QuEraTaskSpecification):\n Quera IR(Intermediate representation) of the task.\n\n Returns:\n An object of the type `BraketTaskSpecification` in Braket SDK\n\n \"\"\"\n nshots, braket_ahs_program = to_braket_task(quera_task_ir)\n return BraketTaskSpecification(nshots=nshots, program=braket_ahs_program.to_ir())\n
Converts to TimeSeries object supported by Braket.
Parameters:
Name Type Description Default timesList[Decimal]
Times of the value.
required valuesList[Decimal]
Corresponding values to add to the time series
required
Returns:
Type Description TimeSeries
An object of the type braket.timings.TimeSeries
Source code in src/bloqade/analog/submission/ir/braket.py
def to_braket_time_series(times: List[Decimal], values: List[Decimal]) -> TimeSeries:\n \"\"\"Converts to `TimeSeries` object supported by Braket.\n\n Args:\n times (List[Decimal]): Times of the value.\n values (List[Decimal]): Corresponding values to add to the time series\n\n Returns:\n An object of the type `braket.timings.TimeSeries`\n \"\"\"\n time_series = TimeSeries()\n for time, value in zip(times, values):\n time_series.put(time, value)\n\n return time_series\n
(tuple[int, int], Sequence[Tuple[int, int]]): cluster index to filter shots from. If none are provided all clusters are used, defaults to [].
[]
Returns:
Name Type Description bitstringslist of ndarray
list corresponding to each task in the report. Each element is an ndarray of shape (nshots, nsites) where nshots is the number of shots for the task and nsites is the number of sites in the task. For example:
Note that nshots may vary between tasks if filter_perfect_filling is set to True.
Source code in src/bloqade/analog/task/base.py
@beartype\ndef bitstrings(\n self,\n filter_perfect_filling: bool = True,\n clusters: Union[tuple[int, int], List[tuple[int, int]]] = [],\n) -> List[NDArray]:\n \"\"\"Get the bitstrings from the data.\n\n Args:\n filter_perfect_filling (bool): whether return will\n only contain perfect filling shots. Defaults to True.\n clusters: (tuple[int, int], Sequence[Tuple[int, int]]):\n cluster index to filter shots from. If none are provided\n all clusters are used, defaults to [].\n\n Returns:\n bitstrings (list of ndarray): list corresponding to each\n task in the report. Each element is an ndarray of shape\n (nshots, nsites) where nshots is the number of shots for\n the task and nsites is the number of sites in the task.\n For example:\n ```python3\n [array([[1, 1],\n [1, 1],\n [1, 1],\n ...,\n [1, 1],\n [1, 1],\n [1, 0]], dtype=int8)]\n ```\n\n Note:\n Note that nshots may vary between tasks if filter_perfect_filling\n is set to True.\n\n \"\"\"\n\n task_numbers = self.dataframe.index.get_level_values(\"task_number\").unique()\n\n bitstrings = []\n for task_number in task_numbers:\n mask = self._filter(\n task_number=task_number,\n filter_perfect_filling=filter_perfect_filling,\n clusters=clusters,\n )\n if np.any(mask):\n bitstrings.append(self.dataframe.loc[mask].to_numpy())\n else:\n bitstrings.append(\n np.zeros((0, self.dataframe.shape[1]), dtype=np.uint8)\n )\n\n return bitstrings\n
(tuple[int, int], Sequence[Tuple[int, int]]): cluster index to filter shots from. If none are provided all clusters are used, defaults to [].
[]
Returns:
Name Type Description countslist of OrderedDict[str, int]
list corresponding to each task in the report. Each element is an ndarray of shape (nshots, nsites) where nshots is the number of shots for the task and nsites is the number of sites in the task. For example:
Note that nshots may vary between tasks if filter_perfect_filling is set to True.
Source code in src/bloqade/analog/task/base.py
def counts(\n self,\n filter_perfect_filling: bool = True,\n clusters: Union[tuple[int, int], List[tuple[int, int]]] = [],\n) -> List[OrderedDict[str, int]]:\n \"\"\"Get the counts of unique bit strings.\n\n Args:\n filter_perfect_filling (bool): whether return will\n only contain perfect filling shots. Defaults to True.\n clusters: (tuple[int, int], Sequence[Tuple[int, int]]):\n cluster index to filter shots from. If none are provided\n all clusters are used, defaults to [].\n\n Returns:\n counts (list of OrderedDict[str, int]): list corresponding to each\n task in the report. Each element is an ndarray of shape\n (nshots, nsites) where nshots is the number of shots for\n the task and nsites is the number of sites in the task.\n For example:\n ```python\n [OrderedDict([('11', 892), ('10', 59), ('01', 49)])]\n ```\n\n Note:\n Note that nshots may vary between tasks if filter_perfect_filling\n is set to True.\n\n \"\"\"\n\n def _generate_counts(bitstring):\n output = np.unique(bitstring, axis=0, return_counts=True)\n\n count_list = [\n (\"\".join(map(str, bitstring)), int(count))\n for bitstring, count in zip(*output)\n ]\n count_list.sort(key=lambda x: x[1], reverse=True)\n count = OrderedDict(count_list)\n\n return count\n\n return list(\n map(_generate_counts, self.bitstrings(filter_perfect_filling, clusters))\n )\n
List the parameters associate with the given variable field_name for each tasks.
Parameters:
Name Type Description Default field_namestr
variable name
required Source code in src/bloqade/analog/task/base.py
def list_param(self, field_name: str) -> List[Union[Number, None]]:\n \"\"\"\n List the parameters associate with the given variable field_name\n for each tasks.\n\n Args:\n field_name (str): variable name\n\n \"\"\"\n\n def cast(x):\n if x is None:\n return None\n elif isinstance(x, (list, tuple, np.ndarray)):\n return list(map(cast, x))\n else:\n return float(x)\n\n return list(map(cast, (meta.get(field_name) for meta in self.metas)))\n
Create a Batch object that has tasks filtered based on the values of metadata.
Parameters:
Name Type Description Default __match_any__bool
if True, then a task will be included if it matches any of the metadata filters. If False, then a task will be included only if it matches all of the metadata filters. Defaults to False.
False**metadataMetadataFilterType
the metadata to filter on. The keys are the metadata names and the values (as a set) are the values to filter on. The elements in the set can be Real, Decimal, Tuple[Real], or Tuple[Decimal].
{} Return
type(self): a Batch object with the filtered tasks, either LocalBatch or RemoteBatch depending on the type of self
Source code in src/bloqade/analog/task/batch.py
@beartype\ndef filter_metadata(\n self, __match_any__: bool = False, **metadata: MetadataFilterType\n) -> Union[\"LocalBatch\", \"RemoteBatch\"]:\n \"\"\"Create a Batch object that has tasks filtered based on the\n values of metadata.\n\n Args:\n __match_any__: if True, then a task will be included if it\n matches any of the metadata filters. If False, then a\n task will be included only if it matches all of the\n metadata filters. Defaults to False.\n\n **metadata: the metadata to filter on. The keys are the metadata\n names and the values (as a set) are the values to filter on.\n The elements in the set can be Real, Decimal, Tuple[Real], or\n Tuple[Decimal].\n\n Return:\n type(self): a Batch object with the filtered tasks, either\n LocalBatch or RemoteBatch depending on the type of self\n\n \"\"\"\n\n def convert_to_decimal(element):\n if isinstance(element, list):\n return list(map(convert_to_decimal, element))\n elif isinstance(element, (Real, Decimal)):\n return Decimal(str(element))\n else:\n raise ValueError(\n f\"Invalid value {element} for metadata filter. \"\n \"Only Real, Decimal, List[Real], and List[Decimal] \"\n \"are supported.\"\n )\n\n def metadata_match_all(task):\n return all(\n task.metadata.get(key) in value for key, value in metadata.items()\n )\n\n def metadata_match_any(task):\n return any(\n task.metadata.get(key) in value for key, value in metadata.items()\n )\n\n metadata = {k: list(map(convert_to_decimal, v)) for k, v in metadata.items()}\n\n metadata_filter = metadata_match_any if __match_any__ else metadata_match_all\n\n new_tasks = OrderedDict(\n [(k, v) for k, v in self.tasks.items() if metadata_filter(v)]\n )\n\n kw = dict(self.__dict__)\n kw[\"tasks\"] = new_tasks\n\n return self.__class__(**kw)\n
If True, tasks are run in parallel using multiple processes. If False, tasks are run sequentially in a single process. Defaults to False.
Falsenum_workersOptional[int]
The maximum number of processes that can be used to execute the given calls if multiprocessing is True. If None, the number of workers will be the number of processors on the machine.
None**kwargs
Arbitrary keyword arguments passed to the task's run method.
{}
Raises:
Type Description ValueError
If num_workers is not None and multiprocessing is False.
Returns:
Name Type Description self
The instance of the batch with tasks run.
Source code in src/bloqade/analog/task/batch.py
def _run(\n self, multiprocessing: bool = False, num_workers: Optional[int] = None, **kwargs\n):\n \"\"\"\n Private method to run tasks in the batch.\n\n Args:\n multiprocessing (bool, optional): If True, tasks are run in parallel using multiple processes.\n If False, tasks are run sequentially in a single process. Defaults to False.\n num_workers (Optional[int], optional): The maximum number of processes that can be used to\n execute the given calls if multiprocessing is True. If None, the number of workers will be the number of processors on the machine.\n **kwargs: Arbitrary keyword arguments passed to the task's run method.\n\n Raises:\n ValueError: If num_workers is not None and multiprocessing is False.\n\n Returns:\n self: The instance of the batch with tasks run.\n \"\"\"\n if multiprocessing:\n from concurrent.futures import ProcessPoolExecutor as Pool\n\n with Pool(max_workers=num_workers) as pool:\n futures = OrderedDict()\n for task_number, task in enumerate(self.tasks.values()):\n futures[task_number] = pool.submit(task.run, **kwargs)\n\n for task_number, future in futures.items():\n self.tasks[task_number] = future.result()\n\n else:\n if num_workers is not None:\n raise ValueError(\n \"num_workers is only used when multiprocessing is enabled.\"\n )\n for task in self.tasks.values():\n task.run(**kwargs)\n\n return self\n
Private method to submit tasks in the RemoteBatch.
Parameters:
Name Type Description Default shuffle_submit_orderbool
If True, tasks are submitted in a random order. If False, tasks are submitted in the order they were added to the batch. Defaults to True.
Trueignore_submission_errorbool
If True, submission errors are ignored and the method continues to submit the remaining tasks. If False, the method stops at the first submission error. Defaults to False.
False**kwargs
Arbitrary keyword arguments.
{}
Returns:
Name Type Description RemoteBatchRemoteBatch
The RemoteBatch instance with tasks submitted.
Source code in src/bloqade/analog/task/batch.py
def _submit(\n self, shuffle_submit_order: bool = True, ignore_submission_error=False, **kwargs\n) -> \"RemoteBatch\":\n \"\"\"\n Private method to submit tasks in the RemoteBatch.\n\n Args:\n shuffle_submit_order (bool, optional): If True, tasks are submitted in a random order.\n If False, tasks are submitted in the order they were added to the batch. Defaults to True.\n ignore_submission_error (bool, optional): If True, submission errors are ignored and the method continues to submit the remaining tasks.\n If False, the method stops at the first submission error. Defaults to False.\n **kwargs: Arbitrary keyword arguments.\n\n Returns:\n RemoteBatch: The RemoteBatch instance with tasks submitted.\n \"\"\"\n from bloqade.analog import save\n\n # online, non-blocking\n if shuffle_submit_order:\n submission_order = np.random.permutation(list(self.tasks.keys()))\n else:\n submission_order = list(self.tasks.keys())\n\n # submit tasks in random order but store them\n # in the original order of tasks.\n # futures = OrderedDict()\n\n ## upon submit() should validate for Both backends\n ## and throw errors when fail.\n errors = BatchErrors()\n shuffled_tasks = OrderedDict()\n for task_index in submission_order:\n task = self.tasks[task_index]\n shuffled_tasks[task_index] = task\n try:\n task.submit(**kwargs)\n except BaseException as error:\n # record the error in the error dict\n errors.task_errors[int(task_index)] = TaskError(\n exception_type=error.__class__.__name__,\n stack_trace=traceback.format_exc(),\n )\n\n task.task_result_ir = QuEraTaskResults(\n task_status=QuEraTaskStatusCode.Unaccepted\n )\n\n self.tasks = shuffled_tasks # permute order using dump way\n\n if len(errors.task_errors) > 0:\n time_stamp = datetime.datetime.now()\n\n if \"win\" in sys.platform:\n time_stamp = str(time_stamp).replace(\":\", \"~\")\n\n if self.name:\n future_file = f\"{self.name}-partial-batch-future-{time_stamp}.json\"\n error_file = f\"{self.name}-partial-batch-errors-{time_stamp}.json\"\n else:\n future_file = f\"partial-batch-future-{time_stamp}.json\"\n error_file = f\"partial-batch-errors-{time_stamp}.json\"\n\n cwd = os.getcwd()\n # cloud_batch_result.save_json(future_file, indent=2)\n # saving ?\n\n save(errors, error_file)\n save(self, future_file)\n\n if ignore_submission_error:\n warnings.warn(\n \"One or more error(s) occured during submission, please see \"\n \"the following files for more information:\\n\"\n f\" - {os.path.join(cwd, future_file)}\\n\"\n f\" - {os.path.join(cwd, error_file)}\\n\",\n RuntimeWarning,\n )\n else:\n raise RemoteBatch.SubmissionException(\n str(errors)\n + \"\\n\"\n + \"One or more error(s) occured during submission, please see \"\n \"the following files for more information:\\n\"\n f\" - {os.path.join(cwd, future_file)}\\n\"\n f\" - {os.path.join(cwd, error_file)}\\n\"\n )\n\n else:\n # TODO: think about if we should automatically save successful submissions\n # as well.\n pass\n
def cancel(self) -> \"RemoteBatch\":\n \"\"\"\n Cancel all the tasks in the Batch.\n\n Return:\n self\n\n \"\"\"\n # cancel all jobs\n for task in self.tasks.values():\n task.cancel()\n\n return self\n
Fetching will update the status of tasks, and only pull the results for those tasks that have completed.
Return
self
Source code in src/bloqade/analog/task/batch.py
def fetch(self) -> \"RemoteBatch\":\n \"\"\"\n Fetch the tasks in the Batch.\n\n Note:\n Fetching will update the status of tasks,\n and only pull the results for those tasks\n that have completed.\n\n Return:\n self\n\n \"\"\"\n # online, non-blocking\n # pull the results only when its ready\n for task in self.tasks.values():\n task.fetch()\n\n return self\n
Create a RemoteBatch object that contain failed tasks from current Batch.
failed tasks with following status codes:
Failed
Unaccepted
Return
RemoteBatch
Source code in src/bloqade/analog/task/batch.py
def get_failed_tasks(self) -> \"RemoteBatch\":\n \"\"\"\n Create a RemoteBatch object that\n contain failed tasks from current Batch.\n\n failed tasks with following status codes:\n\n 1. Failed\n 2. Unaccepted\n\n Return:\n RemoteBatch\n\n \"\"\"\n # statuses that are in a state that are\n # completed because of an error\n statuses = [\"Failed\", \"Unaccepted\"]\n return self.get_tasks(*statuses)\n
Create a RemoteBatch object that contain finished tasks from current Batch.
Tasks consider finished with following status codes:
Failed
Unaccepted
Completed
Partial
Cancelled
Return
RemoteBatch
Source code in src/bloqade/analog/task/batch.py
def get_finished_tasks(self) -> \"RemoteBatch\":\n \"\"\"\n Create a RemoteBatch object that\n contain finished tasks from current Batch.\n\n Tasks consider finished with following status codes:\n\n 1. Failed\n 2. Unaccepted\n 3. Completed\n 4. Partial\n 5. Cancelled\n\n Return:\n RemoteBatch\n\n \"\"\"\n # statuses that are in a state that will\n # not run going forward for any reason\n statuses = [\"Completed\", \"Failed\", \"Unaccepted\", \"Partial\", \"Cancelled\"]\n return self.get_tasks(*statuses)\n
@beartype\ndef get_tasks(self, *status_codes: str) -> \"RemoteBatch\":\n \"\"\"\n Get Tasks with specify status_codes.\n\n Return:\n RemoteBatch\n\n \"\"\"\n # offline:\n st_codes = [QuEraTaskStatusCode(x) for x in status_codes]\n\n new_task_results = OrderedDict()\n for task_number, task in self.tasks.items():\n if task.task_result_ir.task_status in st_codes:\n new_task_results[task_number] = task\n\n return RemoteBatch(self.source, new_task_results, name=self.name)\n
Pulling will pull the results for the tasks. If a given task(s) has not been completed, wait until it finished.
Return
self
Source code in src/bloqade/analog/task/batch.py
def pull(self) -> \"RemoteBatch\":\n \"\"\"\n Pull results of the tasks in the Batch.\n\n Note:\n Pulling will pull the results for the tasks.\n If a given task(s) has not been completed, wait\n until it finished.\n\n Return:\n self\n \"\"\"\n # online, blocking\n # pull the results. if its not ready, hanging\n for task in self.tasks.values():\n task.pull()\n\n return self\n
Create a RemoteBatch object that contain tasks from current Batch, with failed tasks removed.
failed tasks with following status codes:
Failed
Unaccepted
Return
RemoteBatch
Source code in src/bloqade/analog/task/batch.py
def remove_failed_tasks(self) -> \"RemoteBatch\":\n \"\"\"\n Create a RemoteBatch object that\n contain tasks from current Batch,\n with failed tasks removed.\n\n failed tasks with following status codes:\n\n 1. Failed\n 2. Unaccepted\n\n Return:\n RemoteBatch\n\n \"\"\"\n # statuses that are in a state that will\n # not run going forward because of an error\n statuses = [\"Failed\", \"Unaccepted\"]\n return self.remove_tasks(*statuses)\n
Create a RemoteBatch object that contain tasks from current Batch, with all Unaccepted tasks removed.
Return
RemoteBatch
Source code in src/bloqade/analog/task/batch.py
def remove_invalid_tasks(self) -> \"RemoteBatch\":\n \"\"\"\n Create a RemoteBatch object that\n contain tasks from current Batch,\n with all Unaccepted tasks removed.\n\n Return:\n RemoteBatch\n\n \"\"\"\n return self.remove_tasks(\"Unaccepted\")\n
Retrieve will update the status of tasks, and only pull the results for those tasks that have completed.
Return
self
Source code in src/bloqade/analog/task/batch.py
def retrieve(self) -> \"RemoteBatch\":\n \"\"\"Retrieve missing task results.\n\n Note:\n Retrieve will update the status of tasks,\n and only pull the results for those tasks\n that have completed.\n\n Return:\n self\n\n \"\"\"\n # partially online, sometimes blocking\n # pull the results for tasks that have\n # not been pulled already.\n for task in self.tasks.values():\n if not task._result_exists():\n task.pull()\n\n return self\n
dataframe with [\"task id\", \"status\", \"shots\"]
Source code in src/bloqade/analog/task/batch.py
def tasks_metric(self) -> pd.DataFrame:\n \"\"\"\n Get current tasks status metric\n\n Return:\n dataframe with [\"task id\", \"status\", \"shots\"]\n\n \"\"\"\n # [TODO] more info on current status\n # offline, non-blocking\n tid = []\n data = []\n for int, task in self.tasks.items():\n tid.append(int)\n\n dat = [None, None, None]\n dat[0] = task.task_id\n if task.task_result_ir is not None:\n dat[1] = task.task_result_ir.task_status.name\n dat[2] = task.task_ir.nshots\n data.append(dat)\n\n return pd.DataFrame(data, index=tid, columns=[\"task ID\", \"status\", \"shots\"])\n
def json(self, **options) -> str:\n \"\"\"\n Serialize the object to JSON string.\n\n Return:\n JSON string\n\n \"\"\"\n from bloqade.analog import dumps\n\n return dumps(self, **options)\n
"},{"location":"reference/bloqade/analog/task/bloqade/","title":"Bloqade","text":""},{"location":"reference/bloqade/analog/task/braket/","title":"Braket","text":""},{"location":"reference/bloqade/analog/task/braket_simulator/","title":"Braket simulator","text":""},{"location":"reference/bloqade/analog/task/quera/","title":"Quera","text":""}]}
\ No newline at end of file