Skip to content

Commit

Permalink
Merge pull request #782 from qutech/issues/781_program_builder
Browse files Browse the repository at this point in the history
Program builder interface
  • Loading branch information
terrorfisch authored Mar 15, 2024
2 parents f77f2d4 + ea48f8e commit 2db27d5
Show file tree
Hide file tree
Showing 22 changed files with 1,315 additions and 317 deletions.
1 change: 1 addition & 0 deletions changes.d/781.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the `ProgramBuilder` interface pattern to make the generated program of `PulseTemplate.create_program` easily customizable.
10 changes: 8 additions & 2 deletions doc/source/concepts/program.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ Instantiated Pulse: Program
In qupulse an instantiated pulse template is called a program as it is something that an arbitrary waveform generator
(AWG) can execute/playback.
It is created by the ``create_program`` method of the pulse template which returns a hardware
independent representation which is of type ``Loop``.
This ``Loop`` object is the root node of a tree ``Loop``s of arbitrary depth.
independent representation which is of type ``Loop`` by default. The method takes a ``program_builder`` keyword argument
which is passed through the pulse template tree and thereby implements the visitor pattern. If the argument is not
passed ``default_program_builder()`` is used instead which is ``LoopBuilder`` by default.

The ``Loop`` default program is the root node of a tree ``Loop``s of arbitrary depth.
Each node consists of a repetition count and either a waveform or a sequence of nodes which are repeated that many times.
Iterations like the ```ForLoopPT`` cannot be represented natively but are unrolled into a sequence of items.
The repetition count is currently the only property of a program that can be defined as volatile. This means that the AWG driver tries to upload the program in a way, where the repetition count can quickly be changed. This is implemented via the ```VolatileRepetitionCount`` class.
Expand All @@ -20,3 +23,6 @@ The dimensions are named by the channel names.
The ``Loop`` class and its constituents ``Waveform`` and ``VolatileRepetitionCount`` are defined in the ``qupulse.program`` subpackage and it's submodules.
The private subpackage ``qupulse._program`` contains AWG driver internals that can change with any release, for example a
transpiler to Zurich Instruments sequencing C in ``qupulse._program.seqc``.

Another program format that is currently under development is ``LinSpaceProgram`` which efficiently encodes linearly
spaced sweeps in voltage space. However, the status of this is preliminary and not yet documented here.
2 changes: 2 additions & 0 deletions qupulse/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ from . import pulses
from . import hardware
from . import utils
from . import _program
from . import program

from . import comparable
from . import expressions
Expand All @@ -16,6 +17,7 @@ __all__ = ['pulses',
'hardware',
'utils',
'_program',
'program',
'comparable',
'expressions',
'parameter_scope',
Expand Down
146 changes: 146 additions & 0 deletions qupulse/program/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import contextlib
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional, Union, Sequence, ContextManager, Mapping, Tuple, Generic, TypeVar, Iterable
from numbers import Real

import numpy as np

from qupulse._program.waveforms import Waveform
from qupulse.utils.types import MeasurementWindow, TimeType
from qupulse._program.volatile import VolatileRepetitionCount
from qupulse.parameter_scope import Scope
from qupulse.expressions import sympy as sym_expr

from typing import Protocol, runtime_checkable


NumVal = TypeVar('NumVal', bound=Real)


@dataclass
class SimpleExpression(Generic[NumVal]):
"""This is a potential hardware evaluable expression of the form
C + C1*R1 + C2*R2 + ...
where R1, R2, ... are potential runtime parameters.
The main use case is the expression of for loop dependent variables where the Rs are loop indices. There the
expressions can be calculated via simple increments.
"""

base: NumVal
offsets: Sequence[Tuple[str, NumVal]]

def value(self, scope: Mapping[str, NumVal]) -> NumVal:
value = self.base
for name, factor in self.offsets:
value += scope[name] * factor
return value

def __add__(self, other):
if isinstance(other, (float, int, TimeType)):
return SimpleExpression(self.base + other, self.offsets)

if type(other) == type(self):
return SimpleExpression(self.base + other.base, self.offsets + other.offsets)

return NotImplemented

def __radd__(self, other):
return self.__add__(other)

def __sub__(self, other):
return self.__add__(-other)

def __rsub__(self, other):
(-self).__add__(other)

def __neg__(self):
return SimpleExpression(-self.base, tuple((name, -value) for name, value in self.offsets))

def __mul__(self, other: NumVal):
if isinstance(other, SimpleExpression):
return NotImplemented
return SimpleExpression(self.base * other, tuple((name, value * other) for name, value in self.offsets))

def __rmul__(self, other):
return self.__mul__(other)

def evaluate_in_scope(self, *args, **kwargs):
# TODO: remove. It is currently required to avoid nesting this class in an expression for the MappedScope
# We can maybe replace is with a HardwareScope or something along those lines
return self


RepetitionCount = Union[int, VolatileRepetitionCount, SimpleExpression[int]]
HardwareTime = Union[TimeType, SimpleExpression[TimeType]]
HardwareVoltage = Union[float, SimpleExpression[float]]


@runtime_checkable
class Program(Protocol):
"""This protocol is used to inspect and or manipulate programs"""

@property
def duration(self) -> TimeType:
raise NotImplementedError()


class ProgramBuilder(Protocol):
"""This protocol is used by PulseTemplate to build the program via the visitor pattern.
There is a default implementation which is the loop class.
Other hardware backends can use this protocol to implement easy translation of pulse templates."""

def inner_scope(self, scope: Scope) -> Scope:
"""This function is necessary to inject program builder specific parameter implementations into the build
process."""

def hold_voltage(self, duration: HardwareTime, voltages: Mapping[str, HardwareVoltage]):
"""Supports dynamic i.e. for loop generated offsets and duration"""

# further specialized commandos like play_harmoic might be added here

def play_arbitrary_waveform(self, waveform: Waveform):
""""""

def measure(self, measurements: Optional[Sequence[MeasurementWindow]]):
"""Unconditionally add given measurements relative to the current position."""

def with_repetition(self, repetition_count: RepetitionCount,
measurements: Optional[Sequence[MeasurementWindow]] = None) -> Iterable['ProgramBuilder']:
"""Measurements that are added to the new builder are dropped if the builder is empty upon exit"""

def with_sequence(self,
measurements: Optional[Sequence[MeasurementWindow]] = None) -> ContextManager['ProgramBuilder']:
"""
Measurements that are added in to the returned program builder are discarded if the sequence is empty on exit.
Args:
measurements: Measurements to attach to the potential child. Is not repeated with repetition_count.
repetition_count:
Returns:
"""

def new_subprogram(self, global_transformation: 'Transformation' = None) -> ContextManager['ProgramBuilder']:
"""Create a context managed program builder whose contents are translated into a single waveform upon exit if
it is not empty."""

def with_iteration(self, index_name: str, rng: range,
measurements: Optional[Sequence[MeasurementWindow]] = None) -> Iterable['ProgramBuilder']:
pass

def to_program(self) -> Optional[Program]:
"""Further addition of new elements might fail after finalizing the program."""


def default_program_builder() -> ProgramBuilder:
from qupulse.program.loop import LoopBuilder
return LoopBuilder()


# TODO: hackedy, hackedy
sym_expr.ALLOWED_NUMERIC_SCALAR_TYPES = sym_expr.ALLOWED_NUMERIC_SCALAR_TYPES + (SimpleExpression,)
Loading

0 comments on commit 2db27d5

Please sign in to comment.