Skip to content

Support drive composition with different phases #436

Description

@alessandro-santini

Description

QoolQit should support the sequential composition of Drive objects that have different phases.

A Drive associates amplitude and detuning waveforms with a single phase. Pulser supports this use case by scheduling consecutive Pulse objects with different phases, so QoolQit should be able to express and compile the equivalent program.

Reference behavior in Pulser

The following sequence applies two consecutive pulses. The first pulse has phase $\pi$ and the second has phase $0$.

import numpy as np

from pulser import Pulse, Register, Sequence
from pulser.devices import MockDevice
from pulser.waveforms import RampWaveform


device = MockDevice
register = Register.rectangle(1, 3)

pulse_1 = Pulse(
    amplitude=RampWaveform(start=0.0, stop=1.0, duration=1000),
    detuning=RampWaveform(start=0.0, stop=1.0, duration=1000),
    phase=np.pi,
)

pulse_2 = Pulse(
    amplitude=RampWaveform(start=1.0, stop=0.0, duration=1000),
    detuning=RampWaveform(start=1.0, stop=0.0, duration=1000),
    phase=0.0,
)

sequence = Sequence(register, device)
sequence.declare_channel("ryd", "rydberg_global")
sequence.add(pulse_1, "ryd", protocol="no-delay")
sequence.add(pulse_2, "ryd", protocol="no-delay")

sequence.draw()

Desired QoolQit API

The equivalent QoolQit program should be expressible with the existing composition operator:

import numpy as np

from qoolqit import Drive, MockDevice, Register
from qoolqit.waveforms import RampWaveform


device = MockDevice
register = Register.rectangular(1, 3)

amp_waveform = RampWaveform(
    initial_value=0.0,
    final_value=1.0,
    duration=1000,
)
det_waveform = RampWaveform(
    initial_value=0.0,
    final_value=1.0,
    duration=1000,
)
drive_1 = Drive(
    amplitude=amp_waveform,
    detuning=det_waveform,
    phase=np.pi,
)

amp_waveform = RampWaveform(
    initial_value=1.0,
    final_value=0.0,
    duration=1000,
)
det_waveform = RampWaveform(
    initial_value=1.0,
    final_value=0.0,
    duration=1000,
)
drive_2 = Drive(
    amplitude=amp_waveform,
    detuning=det_waveform,
    phase=0.0,
)

composite_drive = drive_1 >> drive_2
composite_drive.draw()

When compiled through the existing QoolQit compilation path, composite_drive should produce two consecutive Pulser pulses and preserve the phase of each segment.

Proposed design

Introduce an ordered composite representation that preserves each component drive. For example:

class CompositeDrive(Drive):
    drives: tuple[Drive, ...]

The exact inheritance and storage type can be adjusted to fit the current data model. The important requirement is that the phase remains attached to each individual drive segment.

Suggested behavior:

  • drive_1 >> drive_2 returns a composite representation when the phases differ.
  • Chained composition is supported, for example drive_1 >> drive_2 >> drive_3.
  • The total duration is the sum of the component durations.
  • draw() displays the full composed drive and makes phase changes or segment boundaries visible.
  • Existing behavior for drives with the same phase remains backward-compatible; such drives may continue to use the current optimized representation.

Acceptance criteria

  • Two Drive objects with different phases can be composed with >> without an error or loss of information.
  • The composed object preserves the order, waveform data, duration, and phase of every segment.
  • Composition works for more than two drives and does not create unnecessarily nested composites.
  • The total duration equals the sum of all component-drive durations.
  • draw() works for the composed object and clearly represents the phase transition.
  • Existing same-phase composition behavior remains supported.
  • Unit tests cover different-phase composition, chained composition, compilation, drawing, and backward compatibility.
  • low priority: The drive-composition documentation includes an example with a phase change.

Expected outcome

QoolQit can represent and compile piecewise drives whose phase changes between segments, with behavior equivalent to scheduling consecutive Pulser pulses with different phases.

Issue Breakdown

Break it down into 2/3 separate PRs

To allow compilation to multiple phased pulses:

  • Modify Drive._to_pulser() -> tuple[pulser.Pulse] to always return a tuple of pulses. The rationale is that when making a pulser.Sequence, pulses are appended it one-by-one. This also leaves unchanged the single pulse/drive compilation behavior.
  • During compilation, append one by one the pulser.Pulses to the pulser.Sequence

Finally support Drive composition w/ different phases:

  • Add CompositeDrive as a tuple of Drives. Different phases are naturally stored in each Drive

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew feature or requestimprovementImproving or refining an existing feature

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions