Commit 85b240bd authored by Gábor Oszkár Dénes's avatar Gábor Oszkár Dénes Committed by Edgar Reehuis
Browse files

Remove unnecessary deepcopies in compilation (SE-233)

parent d2f40441
Loading
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
## Unreleased

### Breaking changes
- Qblox backend - `QuantifyCompiler.compile`, `determine_absolute_timing` and `compile_circuit_to_device` restandardized: returned schedule is the transformed schedule; and added `keep_original_schedule` argument (!771)

### Merged branches and closed issues

@@ -23,6 +24,7 @@
- Infrastructure - Add `jinja2` as dependency to quantify-scheduler (needed for `pandas.DataFrame`) (!777)
- Documentation - Split source and build folders to simplify using [`sphinx-autobuild`](https://github.com/executablebooks/sphinx-autobuild) for its editing. (!774)
- Schedules - Added two-qubit schedule generating function `two_qubit_transmon_schedules.chevron_cz_sched` for CZ tuneup (!700).
- Qblox backend - Remove unnecessary deepcopies from schedule for 30-75% performance improvement (!771)

## 0.16.0 (2023-08-17)

+12 −12
Original line number Diff line number Diff line
@@ -70,8 +70,8 @@ Note that these plots are interactive and modulation is not shown by default.

from quantify_scheduler import compilation

compilation.determine_absolute_timing(sched)
sched.plot_pulse_diagram(plot_backend="plotly")
timed_sched = compilation.determine_absolute_timing(sched)
timed_sched.plot_pulse_diagram(plot_backend="plotly")


```
@@ -88,8 +88,8 @@ sched.add(
    rel_time=500e-9,
)

compilation.determine_absolute_timing(sched)
sched.plot_pulse_diagram(plot_backend="plotly")
timed_sched = compilation.determine_absolute_timing(sched)
timed_sched.plot_pulse_diagram(plot_backend="plotly")


```
@@ -109,8 +109,8 @@ sched.add(
)
sched.add_resource(ClockResource(name="q0.01", freq=7e9))

compilation.determine_absolute_timing(sched)
sched.plot_pulse_diagram(plot_backend="plotly")
timed_sched = compilation.determine_absolute_timing(sched)
timed_sched.plot_pulse_diagram(plot_backend="plotly")


```
@@ -156,8 +156,8 @@ def pulse_train_schedule(


sched = pulse_train_schedule(1, 200e-9, 300e-9, 5)
compilation.determine_absolute_timing(sched)
sched.plot_pulse_diagram(plot_backend="plotly")
timed_sched = compilation.determine_absolute_timing(sched)
timed_sched.plot_pulse_diagram(plot_backend="plotly")


```
@@ -215,8 +215,8 @@ sched.add(
    rel_time=5e-7,
)

compilation.determine_absolute_timing(sched)
sched.plot_pulse_diagram(plot_backend="plotly")
timed_sched = compilation.determine_absolute_timing(sched)
timed_sched.plot_pulse_diagram(plot_backend="plotly")
```

Using these factory functions, the resulting square and staircase pulses use no waveform memory at all. The ramp pulse uses waveform memory for a short section of the waveform, which is repeated multiple times.
@@ -258,8 +258,8 @@ pulse = builder.build()
sched = Schedule("Long soft square pulse")
sched.add(pulse)

compilation.determine_absolute_timing(sched)
sched.plot_pulse_diagram(plot_backend="plotly")
timed_sched = compilation.determine_absolute_timing(sched)
timed_sched.plot_pulse_diagram(plot_backend="plotly")
```

Alternatively, the building methods of the {class}`~quantify_scheduler.operations.stitched_pulse.StitchedPulseBuilder` can be conveniently **chained** to create a {class}`~quantify_scheduler.operations.stitched_pulse.StitchedPulse` via more elegant syntax:
+17 −0
Original line number Diff line number Diff line
@@ -127,3 +127,20 @@ axs[1].set_title("Qblox Backend")
zhinst_compiler.draw(axs[2])
axs[2].set_title("Zhinst Backend");
```

## Performance optimization by not preserving the original schedule

There is a way to potentially decrease the compilation time by about 20-40 % depending on the schedule if you do not need to keep the original, uncompiled schedule after the compilation. Using `keep_original_schedule=False` in `compiler.compile` modifies parts of the original schedule, making the original schedule potentially unusable after the function call.

```{note}
The returned schedule references objects from the original schedule if `keep_original_schedule=False` is used. Refrain from modifying the original schedule after compilation in this case!
```

```{code-cell}
from quantify_scheduler.backends.graph_compilation import SerialCompiler

compiler = SerialCompiler(name="Device compile")
comp_sched = compiler.compile(schedule=echo_schedule, config=config, keep_original_schedule=False)

comp_sched
```
+1 −1
Original line number Diff line number Diff line
@@ -163,7 +163,7 @@ schedule = Schedule("waveforms")
schedule.add(SquarePulse(amp=0.2, duration=4e-6, port="P"))
schedule.add(RampPulse(amp=-0.1, offset=.2, duration=6e-6, port="P"))
schedule.add(SquarePulse(amp=0.1, duration=4e-6, port="Q"), ref_pt='start')
determine_absolute_timing(schedule)
schedule = determine_absolute_timing(schedule)

_ = schedule.plot_pulse_diagram(sampling_rate=20e6)

+12 −7
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ def compile_circuit_to_device(
    # with device_cfg as positional argument.
    *,  # Support for (deprecated) calling with device_cfg as keyword argument:
    device_cfg: DeviceCompilationConfig | Dict | None = None,
    keep_original_schedule=True,
) -> Schedule:
    """
    Add pulse information to all gates in the schedule.
@@ -54,11 +55,18 @@ def compile_circuit_to_device(
        (deprecated) Device compilation config. Pass a full compilation config instead
        using `config` argument. Note, if a dictionary is passed, it will be parsed to a
        :class:`~.DeviceCompilationConfig`.
    keep_original_schedule
        If `True`, this function will not modify the schedule argument.
        If `False`, the compilation modifies the schedule, thereby
        making the original schedule unusable for further usage; this
        improves compilation time. Warning: if `False`, the returned schedule
        references objects from the original schedule, please refrain from modifying
        the original schedule after compilation in this case!

    Returns
    -------
    :
        A copy of `schedule` with pulse information added to all gates.
        The modified `schedule` with pulse information added to all gates.

    Raises
    ------
@@ -93,7 +101,7 @@ def compile_circuit_to_device(
    elif not isinstance(device_cfg, DeviceCompilationConfig):
        device_cfg = DeviceCompilationConfig.parse_obj(device_cfg)

    # to prevent the original input schedule from being modified.
    if keep_original_schedule:
        schedule = deepcopy(schedule)

    for operation in schedule.operations.values():
@@ -176,7 +184,7 @@ def set_pulse_and_acquisition_clock(
    Returns
    -------
    :
        A copy of `schedule` with all clock resources added.
        The modified `schedule` with all clock resources added.

    Warns
    -----
@@ -223,9 +231,6 @@ def set_pulse_and_acquisition_clock(
        device_cfg = DeviceCompilationConfig.parse_obj(device_cfg)
    assert isinstance(device_cfg, DeviceCompilationConfig)

    # to prevent the original input schedule from being modified.
    schedule = deepcopy(schedule)

    # verify that required clocks are present; print warning if they are inconsistent
    verified_clocks = []
    for operation in schedule.operations.values():
Loading