queracomputing / bloqade-python Goto Github PK
View Code? Open in Web Editor NEWQuEra's Neutral Atom SDK for Analog QPUs
Home Page: https://bloqade.quera.com/
License: Other
QuEra's Neutral Atom SDK for Analog QPUs
Home Page: https://bloqade.quera.com/
License: Other
we should adapt a lattice IR based on BloqadeLattice, but meanwhile it might be a good playground to think if we can have a more generic abstraction based on the formal definition of a lattice in homology as complex chain.
right now the docstring is missing for most of the definitions, we should have some basic one-liner docstring to explain the functionality and also make them appear in the generated documentation.
this is WIP but just put an issue for tracking progress
I think this can be a starting point for documentation, and to answer questions about flexibility of builder pattern (with method chain as syntax sugar) in OOP with more details from both high level and low level. I'll attach reading materials to this issue, and I'd recommend people read through them and start learning basic Python design patterns. And yes I read @jon-wurtz's design proposal sent to Phillip, and IMO I don't think you know how to program idiomatic Python. And I hope design proposals that do not have a concrete PR can be formalized as short RFC issues.
To dive into this design pattern, please read the following links
https://en.wikipedia.org/wiki/Builder_pattern
https://stackoverflow.com/questions/17321167/calling-chains-methods-with-intermediate-results
https://python-patterns.guide/gang-of-four/builder/
the design of the builder here is to simplify the construction of IR objects without exposing complicated constructors within each IR class. And we just make a syntax sugar to allow people to chain these builder methods calls together. It also provides the correct flexibility instead of the wrong flexibility through dictionaries. And the reason why the builder pattern was invented is for expressiveness exactly not the opposite, this is why they are references to objects, not the objects themselves.
One can still and of course construct the objects via their own constructors, a wrapper function, but you will find they provide very limited hints and one has to remember lots of API names to import.
I, though want to admit that the current builders do not have a super clear separation on the return values thus what they return can be not clear to people, but this does not hurt the general concept. And I'm open to changes that improve this aspect.
We can continuously answer questions related to builder patterns and method chains in this thread, with more examples from the codebase as other people may have the same question.
related source code: https://github.com/Happy-Diode/bloqade-python/blob/main/src/bloqade/builder.py
also cc: @weinbe58 @johnzl-777 for more explanation on the builder pattern
wf = Linear(start=1.0, stop="x", duration=3.0)
type(wf) => bloqade.ir.waveform.Linear
type(wf[0:1]) => bloqade.ir.waveform.Slice
type(wf + wf) => bloqade.ir.waveform.Add
type(wf*"x") => bloqade.ir.waveform.Scale
etc.
Just have a waveform
type...
It will be a bit tricky if we actually want to support gradients for PennyLane because our device does not support that natively. But this should in principle supported by compiling our IR down to PennyLane's IR.
I believe we should not have the word "lattice" be fundamental to how atoms are positioned. A key feature of the device is arbitrary positioning (right now with a row-based sorting but pending FPGA magic with Pedro that may be dropped). Having the atom positions be defined in an object called "lattice" is confusing and a misrepresentation.
We should scrub all instances of the word "lattice" unless we are explicitly constructing some regular lattice. Lattice generation should be an added feature and not a fundamental one. BraKet calls it an "AtomArrangement
" and often may be called a "register
". "AtomPositions" might also work?
python -m pip install -e C:\Users\jwurtz\Documents\GITHUB\bloqadepy
Obtaining file:///C:/Users/jwurtz/Documents/GITHUB/bloqadepy
Installing build dependencies ... done
Checking if build backend supports build_editable ... done
Getting requirements to build editable ... error
error: subprocess-exited-with-error
× Getting requirements to build editable did not run successfully.
│ exit code: 1
╰─> [40 lines of output]
Traceback (most recent call last):
File "C:\Users\jwurtz\AppData\Local\Temp\pip-build-env-429dpvk6\overlay\Lib\site-packages\pdm\backend\config.py", line 53, in validate
StandardMetadata.from_pyproject(data, project_dir=root)
File "C:\Users\jwurtz\AppData\Local\Temp\pip-build-env-429dpvk6\overlay\Lib\site-packages\pdm\backend\_vendor\pyproject_metadata\__init__.py", line 234, in from_pyproject
cls._get_readme(fetcher, project_dir),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\jwurtz\AppData\Local\Temp\pip-build-env-429dpvk6\overlay\Lib\site-packages\pdm\backend\_vendor\pyproject_metadata\__init__.py", line 428, in _get_readme
raise ConfigurationError(
pdm.backend._vendor.pyproject_metadata.ConfigurationError: Readme file not found (`README.md`)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\jwurtz\AppData\Local\anaconda3\envs\bloqadepy\Lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 353, in <module>
main()
File "C:\Users\jwurtz\AppData\Local\anaconda3\envs\bloqadepy\Lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 335, in main
json_out['return_val'] = hook(**hook_input['kwargs'])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\jwurtz\AppData\Local\anaconda3\envs\bloqadepy\Lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 132, in get_requires_for_build_editable
return hook(config_settings)
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\jwurtz\AppData\Local\Temp\pip-build-env-429dpvk6\overlay\Lib\site-packages\pdm\backend\__init__.py", line 79, in get_requires_for_build_editable
return get_requires_for_build_wheel(config_settings) + ["editables"]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\jwurtz\AppData\Local\Temp\pip-build-env-429dpvk6\overlay\Lib\site-packages\pdm\backend\__init__.py", line 22, in get_requires_for_build_wheel
with WheelBuilder(Path.cwd(), config_settings) as builder:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\jwurtz\AppData\Local\Temp\pip-build-env-429dpvk6\overlay\Lib\site-packages\pdm\backend\wheel.py", line 72, in __init__
super().__init__(location, config_settings)
File "C:\Users\jwurtz\AppData\Local\Temp\pip-build-env-429dpvk6\overlay\Lib\site-packages\pdm\backend\base.py", line 99, in __init__
self.config = Config.from_pyproject(self.location)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\jwurtz\AppData\Local\Temp\pip-build-env-429dpvk6\overlay\Lib\site-packages\pdm\backend\config.py", line 69, in from_pyproject
return cls(root, data)
^^^^^^^^^^^^^^^
File "C:\Users\jwurtz\AppData\Local\Temp\pip-build-env-429dpvk6\overlay\Lib\site-packages\pdm\backend\config.py", line 36, in __init__
self.validate(data, root)
File "C:\Users\jwurtz\AppData\Local\Temp\pip-build-env-429dpvk6\overlay\Lib\site-packages\pdm\backend\config.py", line 55, in validate
raise ValidationError(e.args[0], e.key) from e
pdm.backend.exceptions.ValidationError: project.license.file: Readme file not found (`README.md`)
[end of output]
note: This error originates from a subprocess, and is likely not a problem with pip.
error: subprocess-exited-with-error
× Getting requirements to build editable did not run successfully.
│ exit code: 1
╰─> See above for output.
From Sergio:
[ scalar ] * [ function ] => [ function ]
[ vector ] * [ function ] => ??? ( Make function inhomogeneous )
[ function ] + [ function ] => [ function ]
[ function1 ] * [ function2 ] => [ function1 ] .append( [ function2 ] )
seq = Sequence( *** )
seq.children() => Dict
seq.append(seq).children => List
Bad
This probably could be implemented with Mix-in traits however for now I think something simple is OK
split from #19
This can be accomplished using InitVar
tag like how it is implemented in Waveform
with duration
When generating tasks for Aquila, the variance of the Rabi field is larger along the X direction. Hence it might be good to either/both:
One complaint we commonly see is that the row constraints of the lattice makes it difficult to create a task.
One method to get around this is to create a function that automatically generates a new set of positions that fits the constraints of the lattice.
The implementation of how to do this is pretty tricky. Ideally, the algorithm should modify the atom positions as little as possible while also working within the constraints of the device.
One thought I had is to come up with a generic algorithm that forces the atom positions into rows of fixed y-values, but as a layer on top, we have a first pass that will try every valid rotation of the atom positions.
What is a valid rotation is taking any two pairs of atoms and making that the x-axis of your system. Then you can rotate the current coordinates and check to see if those rotated positions fit the length and width constraints of the device.
If that rotation fits that constraint it next goes into the "rowify" calculation, after which, the average displacement is calculated for all the atoms.
The final transformation chosen is the rotation that is valid and has the minimum displacement of atoms.
To "rowify" the atoms, the idea would be to calculate all groups of positions that fall within the minimum row spacing and then replace those values with their average y-value to get the new positions.
Finally the x-positions need to be changed so that the atoms do not violate the minimum distance constraint.
Here is a code snippet to do the first part:
import numpy as np
def rowify(positions, min_row_spacing = 0.01, positions_resolution=1e-5):
y_sorted_indices = np.argsort(positions[:,1])
rounded_positions = np.around(
np.round(positions[y_sorted_indices,:]/positions_resolution) * positions_resolution, 13
)
ys = rounded_positions[:,1]
inds = np.arange(ys.size)
groups = set([])
for i,y in enumerate(ys):
mask = np.abs(y-ys) < min_row_spacing
group = tuple(inds[mask])
groups.add(group)
for group in groups:
ind = list(group)
new_y = np.mean(ys[ind])
ys[ind] = np.around(
np.round(new_y/positions_resolution) * positions_resolution, 13
)
print(rounded_positions)
np.random.seed(0)
positions = np.random.uniform(0,1,(100,2))
rowify(positions, min_row_spacing=0.01)
Currently, it seems to me that having the interface to refer to rabi.amplitude
and rabi.phase
is not necessary. I think its probably sufficient to have detuning
amplitude
and phase
on the same level so:
rabi.amplitude -> amplitude
rabi.phase -> phase
detuning -> detuning
There is nothing really confusing about this for me, its clear what each one corresponds to.
this package currently needs to depend on this package and it seems does not contain anything sensitive, I suspect this would also make customer installation more accessible.
In reference https://github.com/Happy-Diode/bloqade-python/blob/main/tests/test_sequence.py
As I understand there are three kinds of objects:
Field - Individual functions which I think were previously described as waveforms
, except with the baggage of their spatial interactions
Pulse - Like a field, except assigned to a channel
Sequence - A collection of pulses that make up a program
This seems overcomplete-- why not merge the spatial information of fields with the pulse? For example:
pulse = Pulse({detuning: {Uniform: Linear(start=1.0, stop="x", duration=3.0)}})
becomes
pulse = Pulse({ uniform_detuning: Linear(start=1.0, stop="x", duration=3.0) })
A Field
is then simply a function. How to deal with units though?
the task object returned by the builder should have both a submit
and a validate
. For example, with a batch task it doesn't make sense to start submitting individual tasks before they all have been validated.
TODO:
before working on them, we need to set up CI/CD for these. Ideally we should have a look at how PennyLane writes its website.
we can achieve much better visualization using bokeh, lattice and sequence plot are sync-ed so hovering on atoms will highlight corresponding waveform and vice versa
There seems to be some basic visualization done in ahs-utils, but I'd like to see something fancier:
_repr_png_
and jpeg protocol _repr_jpeg_
A basic lattice visualization can be done on list of locations before we adapt the Julia generic bravis lattice.
the example repo is now in https://github.com/QuEraComputing/bloqade-python-examples
we should implement examples from the white paper as an integration test of the interface design in this package, APIs to stabilize:
Rich has a tree printer integrated with a jupyter notebook. Still, it takes over the entire ipython mime by overloading a mime bundle, so we cannot use it because we will want to have a png
mime available for plotting the pulse program.
So I think we should just implement a tree printer for the IR with some of the tools from rich but put it under ipython pretty printer API (_repr_pretty_
)
move lattice under ir
module as well so these are part of the IR
I think we should stick to the original interface where the output of `Emit` can directly changed with a backend and then further submit things, but let's do that separately and I don't think this is a blocker
Originally posted by @Roger-luo in #64 (comment)
we need to rework the CI and improve test coverage
this is not urgent but we should be able to run emulation as an optional high-performance backend by importing bloqade.julia
module.
this would be a nice addition for people who are interested in compiling analog programs or compiling gates to analog programs externally.
Given the discussion about the Rabi Builder, I think from an IR and analysis perspective having a Unified Rabi IR node actually makes sense. Currently, we have two separate AST nodes for Amplitude and Phase, which makes some forms of analysis awkward.
When calculating the Rabi term, one needs to merge the mask for both the phase and the amplitude because only non-zero amplitudes matter for calculating the coefficient of the rabi-matrix. Currently, this has to be by storing some info about the other AST node in the compiler's state in order to do this analysis, whereas, if the rabi node was unified, this analysis would not need to use a compiler state.
We should remove the concept of Field
in place of an IR node for detuning and rabi term separately. Some input from the Hardware perspective is needed here. For example, does it even make sense to have a separate mask for the phase part of the rabi-term, given how the phase is implemented?
My guess is that generally, the amplitude of the rabi-field is easy to manipulate locally, but the phase would be more difficult, so I feel like the following makes the most sense:
Remove Field all together and replace it with:
class DetuningTerms:
terms: Dict[SpatialModulation, Waveform] = {}
class RabiDrive:
amplitude: Waveform
phase: Waveform
class RabiTerms
terms: Dict[SpatialModulation, RabiDrive] = {}
If we want to have a mask for the phase then the following could also work:
class RabiTerms
amplitude: Field
phase: Field
and Detuning just defaults to a Field
object in the Pulse IR.
Finally, we would need to update the Pulse IR:
MWE:
from bloqade.ir.prelude import *
import bloqade.lattice as lattice
(lattice.Square(3)
.rydberg.detuning.glob.apply(Linear(start=1.0, stop="x", duration=3.0))
.location(2)
.scale(3.0)
.apply(Linear(start=1.0, stop="x", duration=3.0)))
(lattice.Square(3)
.rydberg.detuning.glob.apply(Linear(start=1.0, stop="x", duration=3.0))
.location(2)
.scale(3.0)
.apply(Linear(start=1.0, stop="x", duration=3.0)))
gives
ile ~/Code/Python/bloqade/src/bloqade/builder.py:196, in ApplyBuilder._apply(self, waveform)
194 field = pulse.value.setdefault(field_name, Field({}))
195 if spatial_mod in field.value:
--> 196 raise ValueError("this field spatial modulation is already specified")
197 field.value[spatial_mod] = waveform
198 return self
ValueError: this field spatial modulation is already specified
Lattices should be a function call that generates an `AtomArrangement` (or whatever we call it). Is it possible to label or give metadata to each atom? For example, which unit cell it is in, which vertex it represents for an optimization problem, and so forth. That way an `AtomArrangement` can carry all the information a lattice might need
This way we can further distinguish generation of programs from the programs themselves
Originally posted by @jon-wurtz in #89 (comment)
We need to go back and implement the backend for TaskReport
and BatchReport
TODO:
We need to support Batch tasks eventually.
The API needs to be designed inside the builder
No actual design in mind yet; however, there are some things I'll note in terms of the behavior when submitting a batch task:
Currently, the implementation of tasks will change based on the backend chosen. This means that in order to re-import a task into python you need to know which backend to use.
Ideally, the user shouldn't have to remember which backend was used to generate a task.
To accomplish this would require creating custom types for the Pydantic models.
The idea is to use the whole field of view of the Rydberg hardware to simulate many copies of a single task.
currently, this has some uncertain design decisions, and because the builder is strictly static, the statements of lattice and sequence are always communitive, thus one should always be able to either write all lattice building statements at the beginning or in the middle.
I currently think we should only support one way of defining the same program without loss of generality, but one may think this looks restrictive in terms of coding style.
MWE:
from bloqade.location import Square
seq = (
location.Square(6)
.rydberg.detuning.uniform.piecewise_linear(
durations=["up_time","anneal_time","up_time"],
values=[
"initial_detuning",
"initial_detuning",
"final_detuning",
"final_detuning"
]
).sequence
)
print(seq)
results:
Sequence({rydberg: Pulse(value={detuning: Field({Global: waveform.Append(waveforms=[
Linear(start='final_detuning', stop='final_detuning', duration='up_time'),
Linear(start='initial_detuning', stop='final_detuning', duration='anneal_time'),
Linear(start='initial_detuning', stop='initial_detuning', duration='up_time')
])})})})
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.