Giter VIP home page Giter VIP logo

oqpy's Introduction

OQpy: Generating OpenQASM 3 + OpenPulse in Python

Latest Version Supported Python Versions Build status Documentation Status

The goal of oqpy ("ock-pie") is to make it easy to generate OpenQASM 3 + OpenPulse in Python. The oqpy library builds off of the openqasm3 and openpulse packages, which serve as Python reference implementations of the abstract syntax tree (AST) for the OpenQASM 3 and OpenPulse grammars.

What are OpenQASM 3 and OpenPulse?

OpenQASM is an imperative programming language designed for near-term quantum computing algorithms and applications. OpenQASM 3 extends the original specification by adding support for classical logic, explicit timing, and pulse-level definitions. The latter is enabled via the use of calibration grammars which allow quantum hardware builders to extend the language to support hardware-specific directives via cal and defcal blocks. One such grammar is OpenPulse, which provides the instructions required for pulse-based control of many common quantum computing architectures (e.g. superconducting qubits).

Installation and Getting Started

OQpy can be installed from PyPI or from source in an environment with Python 3.7 or greater.

To install it from PyPI (via pip), do the following:

pip install oqpy

To instead install OQpy from source, do the following from within the repository after cloning it:

poetry install

Next, check out the following example to get a sense of the kinds of programs we can write with OQpy.

Example: Ramsey Interferometry

A common and useful experiment for qubit characterization is Ramsey interferometry, which can be used for two purposes: performing a careful measurement of a qubit’s resonant frequency, and for investigating how long a qubit retains its coherence. In a typical Ramsey experiment, one varies the length of a delay between the two π/2 pulses, and then measures the state of the qubit. Below, we'll create a Ramsey interferometry experiment in OpenQASM 3 using OQpy. As part of this, we’ll use the OpenPulse grammar to allow this experiment to specify its operation implementations at the calibrated pulse level.

import oqpy
prog = oqpy.Program()  # create a new oqpy program

# Declare frames: transmon driving frame and readout receive/transmit frames
xy_frame = oqpy.FrameVar(oqpy.PortVar("dac0"), 6.431e9, name="xy_frame")
rx_frame = oqpy.FrameVar(oqpy.PortVar("adc0"), 5.752e9, name="rx_frame")
tx_frame = oqpy.FrameVar(oqpy.PortVar("dac1"), 5.752e9, name="tx_frame")

# Declare the type of waveform we are working with
constant_waveform = oqpy.declare_waveform_generator(
    "constant",
    [("length", oqpy.duration),
     ("amplitude", oqpy.float64)],
)
gaussian_waveform = oqpy.declare_waveform_generator(
    "gaussian",
    [("length", oqpy.duration),
     ("sigma", oqpy.duration),
     ("amplitude", oqpy.float64)],
)

# Provide gate / operation definitions as defcals
qubit = oqpy.PhysicalQubits[1]  # get physical qubit 1

with oqpy.defcal(prog, qubit, "reset"):
    prog.delay(1e-3)  # reset to ground state by waiting 1 ms

with oqpy.defcal(prog, qubit, "measure"):
    prog.play(tx_frame, constant_waveform(2.4e-6, 0.2))
    prog.capture(rx_frame, constant_waveform(2.4e-6, 1))

with oqpy.defcal(prog, qubit, "x90"):
    prog.play(xy_frame, gaussian_waveform(32e-9, 8e-9, 0.2063))

# Loop over shots (i.e. repetitions)
delay_time = oqpy.DurationVar(0, "delay_time")  # initialize a duration
with oqpy.ForIn(prog, range(100), "shot_index"):
    prog.set(delay_time, 0)                     # reset delay time to zero
    # Loop over delays
    with oqpy.ForIn(prog, range(101), "delay_index"):
        (prog.reset(qubit)                      # prepare in ground state
         .gate(qubit, "x90")                    # pi/2 pulse (90° rotation about the x-axis)
         .delay(delay_time, qubit)              # variable delay
         .gate(qubit, "x90")                    # pi/2 pulse (90° rotation about the x-axis)
         .measure(qubit)                        # final measurement
         .increment(delay_time, 100e-9))        # increase delay by 100 ns

Running print(prog.to_qasm(encal_declarations=True)) generates the following OpenQASM:

OPENQASM 3.0;
defcalgrammar "openpulse";
cal {
    extern constant(duration, float[64]) -> waveform;
    extern gaussian(duration, duration, float[64]) -> waveform;
    port dac1;
    port adc0;
    port dac0;
    frame tx_frame = newframe(dac1, 5752000000.0, 0);
    frame rx_frame = newframe(adc0, 5752000000.0, 0);
    frame xy_frame = newframe(dac0, 6431000000.0, 0);
}
duration delay_time = 0.0ns;
defcal reset $1 {
    delay[1000000.0ns];
}
defcal measure $1 {
    play(tx_frame, constant(2400.0ns, 0.2));
    capture(rx_frame, constant(2400.0ns, 1));
}
defcal x90 $1 {
    play(xy_frame, gaussian(32.0ns, 8.0ns, 0.2063));
}
for int shot_index in [0:99] {
    delay_time = 0.0ns;
    for int delay_index in [0:100] {
        reset $1;
        x90 $1;
        delay[delay_time] $1;
        x90 $1;
        measure $1;
        delay_time += 100.0ns;
    }
}

Contributing

We welcome contributions to OQpy including bug fixes, feature requests, etc. To get started, check out our contributing guidelines. Those who make a nontrivial contribution to the source code will be added as an author to the CITATION.cff file (see below).

Citation

If you use OQpy in your work or research, please cite it using the metadata in the CITATION.cff file in the repository (which includes a Zenodo DOI). You can copy the citation in BibTeX format using the "Cite this repository" widget in the About section of the repository page on GitHub.

oqpy's People

Contributors

anuragm avatar bastianzim avatar braised-babbage avatar dependabot[bot] avatar jcjaskula-aws avatar jmdewart avatar karalekas avatar philreinhold avatar rmshaffer avatar speller26 avatar swordcat avatar yitchen-tim 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oqpy's Issues

Optionally detect and convert float expressions with pi

Right now, if you want to generate e.g. shift_phase(frame, pi), you need to write program.shift_phase(frame, oqpy.pi), but one could imagine instead writing program.shift_phase(frame, np.pi) or program.shift_phase(frame, math.pi) which might be more natural for users.

We could accommodate this by detecting specific floating point numbers and replacing them with expressions for e.g. pi or pi/2 or 2*pi.

Support QubitArray

Currently only support individual qubits, but openqasm allows to refer to arrays of qubits as well.

`Program.to_ast` mutates program.

Currently

p1 = Program()
...
p2 = Program()
...
p1.to_qasm()
(p1 + p2).to_qasm()

and

p1 = Program()
...
p2 = Program()
...
# skip this --> p1.to_qasm() 
(p1 + p2).to_qasm()

Produce different outcomes because the autodeclaration in to_ast mutates p1. One potential solution is to (at least by default) copy the program before converting to ast, although this could have performance concerns. A faster solution could be to change the autodeclaration code such that it only affected the generated output, rather than the state of the program.

Help user by converting floats to durations and vice versa where appropriate

In python, the user will likely be representing durations as floats with some canonical unit, probably seconds. It is inconvenient for the user to need to remember where a float is allowed and where a duration is required. We can help this along by detecting when a user is passing a float someplace where a duration is expected and insert <expr> * 1s. Or vice versa, we can detect if a user is passing a duration where a float is expected and pass <expr> / 1s.

I could imagine turning this behavior on and off with a configuration setting passed to an initializing Program.

Check identifiers used in for loops

For loops can be used with identifiers:

array = {0, 1, 2};
for int i in array {}

OQpy needs to check that the identifier points to a valid iterable variable.

Variable declaration does not check input type

For (possibly) all variables types (e.g. BoolVar, IntVar) etc, we do not do any type validation/conversion if the user tries to create a variable with a value that is not a boolean or integer.

Some suggests for cases where we should consider converting instead of failing:
BoolVar(1, "i") --> BoolVar(True, "i")
IntVar(1.0, "j") --> IntVar(1, "j")

There may also be some other cases where it would make more sense to warn & convert instead of failing.

Add a method to return values from defcal statements

With #20 merged to main we can now declare return types in defcal statements. However there isn't a convenient way to return values from defcal statements (as far as I can tell, please correct me if I'm wrong). This capability is demonstrated in the capture instructions of the OpenPulse Grammar specification. I suggest implementing a new method of the opqy.Program class that adds a ReturnStatement ast node to the program

def returns(self, return_var:AstConvertible):
    self._add_statement(ast.ReturnStatement(return_var.to_ast(self)))
    return self

This method just wraps the current workaround I've found to return a value from a defcal statement which requires using the 'private' _add_statement function of opqy.Program, see e.g. the following example

from openpulse import ast

import oqpy

prog = oqpy.Program()  # create a new oqpy program

m0 = oqpy.PortVar("m0")
cap0 = oqpy.PortVar("cap0")
stimulus_frame = oqpy.FrameVar(m0, 5e9, 0, name="stimulus_frame")
caputure_frame = oqpy.FrameVar(cap0, 5e9, 0, name="capture_frame")
dummy_wfm = oqpy.WaveformVar([-1.0, 0.5, 0.25, -0.75], "dummy_wfm")
capture_v2 = oqpy.declare_extern("capture_v2", [("output", ast.FrameType()), ("duration", oqpy.duration)], oqpy.bit)
qubit = oqpy.PhysicalQubits[0]

with oqpy.defcal(prog, qubit, "measure", return_type=oqpy.bit):
    prog.play(stimulus_frame, dummy_wfm)
    prog._add_statement(
        ast.ReturnStatement(capture_v2(caputure_frame, 16000e-9).to_ast(prog))
    )  # Suggestion: replace with prog.returns(result)

prog.measure(qubit, oqpy.BitVar(name="measured_bit"))

print(prog.to_qasm(encal_declarations=True))

Which generates the following OpenQASM code

OPENQASM 3.0;
defcalgrammar "openpulse";
cal {
    extern capture_v2(frame, duration) -> bit;
    port m0;
    port cap0;
    frame stimulus_frame = newframe(m0, 5000000000.0, 0);
    waveform dummy_wfm = {-1.0, 0.5, 0.25, -0.75};
    frame capture_frame = newframe(cap0, 5000000000.0, 0);
}
bit measured_bit;
defcal measure $0 -> bit {
    play(stimulus_frame, dummy_wfm);
    return capture_v2(capture_frame, 16000.0ns);
}
measured_bit = measure $0;

I'm willing to take the lead on implementing this method and any tests that may be required.

Add None argument to barrier and delay

Barrier and delay should take None as argument (which should be the default behavior):

.barrier() -> barrier;, i.e places a barrier on all frames
.barrier([]) -> does NOT insert an AST node
.barrier([frames]) -> barrier frame0, frame1, ...;, i.e. places a barrier on all listed frames

OpenQASM AST for DelayInstruction/QuantumBarrier `qubits` attribute name is misleading

The AST for DelayInstruction and QuantumBarrier defines a qubits attribute, but in oqpy we also use them for frames so the attribute naming is misleading. The typing is not an issue here.

@dataclass
class DelayInstruction(QuantumStatement):
    """
    Delay instruction

    Example::

        delay[start_stretch] $0;
    """

    duration: Expression
    qubits: List[Union[IndexedIdentifier, Identifier]]

@dataclass
class QuantumBarrier(QuantumStatement):
    """
    A quantum barrier instruction

    Example::

        barrier q;
    """

    qubits: List[Expression]

Support BitVar indexed by IntVar

Add support for BitVar indexing with an integer variable that allows the usage:

arr = BitVar[20](name="arr")
index = IntVar(2, "index")
prog.set(arr[index], 1)

Validate variable names

For instance, we shouldn't accept "delay" as a valid variable name, since it is a keyword.

Address remaining mismatch with openqasm3 AST type hints

#48 Surfaced 2 remaining issues where the AST we produce is not consistent with the openqasm3 AST type hints.

  1. Pragmas are not statements, I think to keep them from being added anywhere other than at the top level. I would guess they should be added to the set of valid types in a Program.statements field.
  2. Box statements only allow QuantumStatement which does not include e.g. play openpulse function calls.

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.