Support subschedules and control-flow in circuit diagram

Explanation of changes

Supersedes !1031 (closed) since I decided to go in a direction that is too different. The way the circuit diagram is generated has been refactored quite a bit.

In short, the way it works now is:

  1. We check what qubits (gates) or ports (pulses/acquisitions) are used, so that we know how big the figure will be. Based on the port name, the pulses/acquisitions end up on the corresponding qubit "wire" or they end up on the "other" wire.
  2. We walk through all the (nested) schedulables in the schedule (depth-first). Boxes for operations, or brackets/arrows etc. for control-flow are drawn on the fly.

Style-wise I am very open to suggestions 😄

Here are some examples:

inner_schedule = Schedule("inner", repetitions=1)
ref = inner_schedule.add(Rxy(0, 0, "q0"), label="inner0")
inner_schedule.add(Rxy(0, 1, "q0"), rel_time=40e-9, ref_op=ref, label="inner1")

outer_schedule = Schedule("outer", repetitions=10)
ref = outer_schedule.add(Rxy(1, 0, "q0"), label="outer0")
outer_schedule.add(inner_schedule, rel_time=80e-9, ref_op=ref)
outer_schedule.add(Measure("q0"), label="measure")

outer_schedule.plot_circuit_diagram()

image

inner_schedule = Schedule("inner", repetitions=1)
ref = inner_schedule.add(Rxy(0, 0, "q0"), label="inner0")
inner_schedule.add(Rxy(0, 1, "q0"), rel_time=40e-9, ref_op=ref, label="inner1")

outer_schedule = Schedule("outer", repetitions=1)
ref = outer_schedule.add(Rxy(1, 0, "q0"), label="outer0")

outer_schedule.add(
    LoopOperation(body=inner_schedule, repetitions=10),
    label="loop",
)

outer_schedule.add(Measure("q0"), label="measure")
outer_schedule.plot_circuit_diagram()

image

schedule = Schedule("")
schedule.add(ConditionalReset("q0"))
schedule.add(ConditionalReset("q4"))

schedule.plot_circuit_diagram()

image

inner = Schedule("inner")
inner.add(X("q2"))

inner2 = Schedule("inner2")
inner2.add(Y("q1"))

inner.add(
    LoopOperation(body=inner2, repetitions=2),
)
inner.add(Measure("q0"))

sched = Schedule("amp_ref")
sched.add(X90("q0"))
sched.add(LoopOperation(body=inner, repetitions=3))
sched.add(X90("q0"))
sched.add(LoopOperation(body=inner2, repetitions=4))
sched.add(X90("q0"))
sched.add(LoopOperation(X("q0"), repetitions=2))

fig, ax = sched.plot_circuit_diagram()

image

inner = Schedule("inner")
inner.add(X("q0"))

inner2 = Schedule("inner2")
inner2.add(Y("q0"))

inner.add(
    LoopOperation(body=inner2, repetitions=2),
)
inner.add(Measure("q0"))

sched = Schedule("amp_ref")
sched.add(Measure("q0", acq_protocol="ThresholdedAcquisition", feedback_trigger_label="q0"))
sched.add(ConditionalOperation(body=inner, qubit_name="q0"), rel_time=364e-9)

fig, ax = sched.plot_circuit_diagram()

image


Merge checklist

See also merge request guidelines

  • Merge request has been reviewed (in-depth by a knowledgeable contributor), and is approved by a project maintainer.
  • New code is covered by unit tests (or N/A).
  • New code is documented and docstrings use numpydoc format (or N/A).
  • New functionality: considered making private instead of extending public API (or N/A).
  • Public API changed: added @deprecated and entry in deprecated code suggestions (or N/A).
  • Newly added/adjusted documentation and docstrings render properly (or N/A).
  • Pipeline fix or dependency update: post in #software-for-developers channel to merge main back in or update local packages (or N/A).
  • Tested on hardware (or N/A).
  • CHANGELOG.md and AUTHORS.md have been updated (or N/A).
  • Update Hardware backends documentation if backend interface change or N/A
  • Check whether performance is significantly affected by looking at the Performance metrics results.
  • Windows tests in CI pipeline pass (manually triggered by maintainers before merging).
    • Maintainers do not hit Auto-merge, we need to actively check as manual tests do not block pipeline

For reference, the issues workflow is described in the contribution guidelines.

Edited by Thomas Middelburg

Merge request reports

Loading