Coder Social home page Coder Social logo

queracomputing / bloqade-python Goto Github PK

View Code? Open in Web Editor NEW
51.0 5.0 14.0 79.27 MB

QuEra's Neutral Atom SDK for Analog QPUs

Home Page: https://bloqade.quera.com/

License: Other

Python 100.00%
python quantum-computing quantum-physics quantum-simulation

bloqade-python's People

Contributors

btrainwilson avatar dependabot[bot] avatar github-actions[bot] avatar guomanmin avatar johnzl-777 avatar kaihsin avatar manvi-agrawal avatar roger-luo avatar ryanhill1 avatar shubhusion avatar wang-shengtao avatar weinbe58 avatar wingcode avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

bloqade-python's Issues

adapt BloqadeLattice

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.

basic docstring coverage on methods

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.

The builder pattern: what are the chain method call and why

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

Type mangling of waveforms

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...

PennyLane plugin

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.

"lattice" vs "positions" vs ???

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?

Installation Issue with pip

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.

Add vector space and field functionality to fields

[ scalar ] * [ function ] => [ function ]
[ vector ] * [ function ] => ??? ( Make function inhomogeneous )
[ function ] + [ function ] => [ function ]
[ function1 ] * [ function2 ] => [ function1 ] .append( [ function2 ] )

Option for `Chain` lattice to be generated horizontal/vertical

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:

  1. Default generate the lattice positions along the y-direction to minimize the variance
  2. Allow the user to specify which direction to generate the chain.

Row-ify atom positions.

Issue

One complaint we commonly see is that the row constraints of the lattice makes it difficult to create a task.

Proposed solution

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.

Row-ify lattice

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)

Rabi Builder interface.

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.

Field vs Pulse vs Sequence

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?

Task Validation separate from submission.

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.

setup example & documentation

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.

Fancier interactive visualization

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

IR visualization

There seems to be some basic visualization done in ahs-utils, but I'd like to see something fancier:

  • it should at least print out the fields and custom names (if it's a named sequence or pulse)
  • if pulse is shared by multiple location the plot should also indicate that.
  • the visualization should implement ipython png protocol _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.

IR tree printer

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_)

codegen to Julia

this is not urgent but we should be able to run emulation as an optional high-performance backend by importing bloqade.julia module.

Formalizing Rabi in IR.

Main Issue

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.

One use-case:

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.

Proposal

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:

Field IR module:

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:

Field IR module:

class RabiTerms
    amplitude: Field
    phase: Field

and Detuning just defaults to a Field object in the Pulse IR.

In Pulse IR module:

Finally, we would need to update the Pulse IR:

run sequence builder twice result in already defined error

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

attach metadata for atom labels

          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)

`Report` implementations.

We need to go back and implement the backend for TaskReport and BatchReport

TODO:

  • Calculate Pandas dataframe from task results
  • Calculate "bit strings" from task results
  • Implement some common analysis methods
  • Implement filters for post-processing
    EDIT:
  • Add Multiplex into Task IR objects.

Batch tasks

We need to support Batch tasks eventually.

The API needs to be designed inside the builder

  • How to specify a batch
  • How to specify batch coefficients

No actual design in mind yet; however, there are some things I'll note in terms of the behavior when submitting a batch task:

  • All tasks should be validated before submitting them.
  • If the backend doesn't support validation the fallback behavior should be that if anything goes wrong during the submission, the batch task should immediately cancel all previously submitted tasks to avoid issues related to having to go back and figure out which tasks are invalid.

Unified Task representation

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.

explicit program start vs using lattice as program start

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.

Emit builder reverses order for waveform.Append AST

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')
])})})})

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.