Skip to content

Commit

Permalink
Release 2.24.0
Browse files Browse the repository at this point in the history
  • Loading branch information
polwel committed Feb 9, 2024
1 parent 8c12b24 commit 467bda3
Show file tree
Hide file tree
Showing 66 changed files with 3,833 additions and 1,570 deletions.
635 changes: 635 additions & 0 deletions examples/00_reference/14_PRNG_usage.ipynb

Large diffs are not rendered by default.

483 changes: 483 additions & 0 deletions examples/00_reference/15_Advanced_sweeping_examples.ipynb

Large diffs are not rendered by default.

374 changes: 205 additions & 169 deletions examples/02_advanced_qubit_experiments/01_randomized_benchmarking.ipynb

Large diffs are not rendered by default.

873 changes: 873 additions & 0 deletions examples/06_qasm/02_RandomizedBenchmarking_from_Qiskit.ipynb

Large diffs are not rendered by default.

621 changes: 0 additions & 621 deletions examples/06_qasm/02_Two_Qubit_RB_Qiskit.ipynb

This file was deleted.

2 changes: 1 addition & 1 deletion laboneq/VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.23.0
2.24.0
2 changes: 2 additions & 0 deletions laboneq/compiler/code_generator/analyze_playback.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,8 @@ def analyze_play_wave_times(
"signal_id": signal_id,
},
)
if len(signal_ids) > 1:
interval_event.params["multiplexed_signal_ids"] = signal_ids
interval_events.add(start, interval_event)

# A value of 'None' indicates that we do not know the current value (e.g. after a
Expand Down
41 changes: 20 additions & 21 deletions laboneq/compiler/code_generator/code_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -716,13 +716,10 @@ def gen_seq_c(self, events: List[Any], pulse_defs: Dict[str, PulseDef]):
):
self._gen_seq_c_per_awg(awg, events, pulse_defs)

for (
awg,
target_fb_register,
) in self._feedback_register_allocator.target_feedback_registers.items():
self._feedback_register_config[
awg
].target_feedback_register = target_fb_register
tgt_feedback_regs = self._feedback_register_allocator.target_feedback_registers
for awg, target_fb_register in tgt_feedback_regs.items():
feedback_reg_config = self._feedback_register_config[awg]
feedback_reg_config.target_feedback_register = target_fb_register

@staticmethod
def _calc_global_awg_params(awg: AWGInfo) -> Tuple[float, float]:
Expand Down Expand Up @@ -1399,9 +1396,10 @@ def _gen_seq_c_per_awg(
)
raise LabOneQException(f"Compiler error. {msg}") from error

self._feedback_register_config[
awg.key
].command_table_offset = handler.command_table_match_offset
fb_register_config = self._feedback_register_config[awg.key]
fb_register_config.command_table_offset = handler.command_table_match_offset
if handler.use_zsync_feedback is False:
fb_register_config.source_feedback_register = "local"

_logger.debug(
"*** Finished event processing, loop_stack_generators: %s",
Expand Down Expand Up @@ -1834,7 +1832,7 @@ def post_process_sampled_events(
start = None
end = None
acquisition_types = set()
feedback_registers = list()
feedback_register: None | int = None
play: List[AWGEvent] = []
acquire: List[AWGEvent] = []
for x in sampled_event_list:
Expand All @@ -1846,20 +1844,23 @@ def post_process_sampled_events(
start = x.start
if "acquisition_type" in x.params:
acquisition_types.update(x.params["acquisition_type"])
feedback_register = x.params.get("feedback_register")
if feedback_register is not None:
feedback_registers.append(feedback_register)
this_feedback_register = x.params.get("feedback_register")
if this_feedback_register is not None:
if (
feedback_register is not None
and feedback_register != this_feedback_register
):
raise LabOneQException(
"Conflicting feedback register allocation detected, please contact development."
)
feedback_register = this_feedback_register
acquire.append(x)
end = x.end if end is None else max(end, x.end)
if len(play) > 0 and len(acquire) == 0 and has_acquire:
_logger.warning("Problem:")
for log_event in sampled_event_list:
_logger.warning(" %s", log_event)
raise Exception("Play and acquire must happen at the same time")
if len(feedback_registers) > 1:
_logger.warning(
"Conflicting feedback register allocation detected, please contact development."
)
if len(play) > 0 or len(acquire) > 0:
end = (
round(end / DeviceType.SHFQA.sample_multiple)
Expand All @@ -1876,9 +1877,7 @@ def post_process_sampled_events(
"acquire_handles": [
a.params["acquire_handles"][0] for a in acquire
],
"feedback_register": None
if len(feedback_registers) == 0
else feedback_registers[0],
"feedback_register": feedback_register,
},
)

Expand Down
20 changes: 18 additions & 2 deletions laboneq/compiler/code_generator/sampled_event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ def __init__(
self.last_event: Optional[AWGEvent] = None
self.match_parent_event: Optional[AWGEvent] = None
self.command_table_match_offset = None

# If true, this AWG sources feedback data from Zsync. If False, it sources data
# from the local bus. None means neither source is used. Using both is illegal.
self.use_zsync_feedback: bool | None = None

self.match_command_table_entries: dict[
int, tuple
] = {} # For feedback or prng match
Expand Down Expand Up @@ -330,7 +335,6 @@ def handle_playwave_on_feedback(
assert self.use_command_table
assert self.match_parent_event is not None
state = signature.state
signal_id = sampled_event.params["signal_id"]

if state in self.match_command_table_entries:
if self.match_command_table_entries[state] != (
Expand All @@ -348,9 +352,15 @@ def handle_playwave_on_feedback(
wave_index,
sampled_event.start - self.match_parent_event.start,
)

if "multiplexed_signal_ids" in sampled_event.params:
drive_signal_ids = sampled_event.params["multiplexed_signal_ids"]
else:
drive_signal_ids = (sampled_event.params["signal_id"],)

self.feedback_connections.setdefault(
self.match_parent_event.params["handle"], FeedbackConnection(None)
).drive.add(signal_id)
).drive.update(drive_signal_ids)

def handle_playwave_on_user_register(
self,
Expand Down Expand Up @@ -960,6 +970,12 @@ def close_event_list_for_handle(self):
+ (f"+ {latency}" if latency >= 0 else f"- {-latency}"),
comment="Match handle " + handle,
)
use_zsync: bool = not ev.params["local"]
if self.use_zsync_feedback is not None and self.use_zsync_feedback != use_zsync:
raise LabOneQException(
"Mixed feedback paths (global and local) are illegal"
)
self.use_zsync_feedback = use_zsync
self.seqc_tracker.add_timing_comment(ev.end)
self.seqc_tracker.flush_deferred_function_calls()
self.seqc_tracker.current_time = self.match_parent_event.end
Expand Down
10 changes: 4 additions & 6 deletions laboneq/compiler/experiment_access/experiment_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,10 @@ def section_signals(self, section_id):

@cached_method()
def section_signals_with_children(self, section_id):
retval = set()
section_with_children = self.all_section_children(section_id)
section_with_children.add(section_id)
for child in section_with_children:
retval |= self.section_signals(child)
return retval
signals = set(self.section_signals(section_id))
for child in self.all_section_children(section_id):
signals |= self.section_signals(child)
return signals

def pulses(self) -> list[str]:
return list(self._data["pulses"].keys())
Expand Down
26 changes: 6 additions & 20 deletions laboneq/compiler/feedback_router/feedback_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
from typing import Literal, Union

from laboneq._utils import cached_method
from laboneq.compiler.common.awg_info import AWGInfo, AwgKey
from laboneq.compiler.common.awg_signal_type import AWGSignalType
from laboneq.compiler.common.device_type import DeviceType
from laboneq.compiler.common.awg_info import AwgKey
from laboneq.compiler.common.feedback_connection import FeedbackConnection
from laboneq.compiler.common.feedback_register_config import FeedbackRegisterConfig
from laboneq.compiler.common.signal_obj import SignalObj
Expand Down Expand Up @@ -60,11 +58,14 @@ def calculate_feedback_routing(self):
for awg in self._awgs.values():
if (tx_qa := self._transmitter_qa_for_awg(awg.key)) is None:
continue
feedback_register_config = feedback_register_configs[awg.key]
qa_awg, qa_signal = tx_qa
register = feedback_register_configs[qa_awg].target_feedback_register
assert register is not None

use_local_feedback = self._local_feedback_allowed(awg, self._awgs[qa_awg])
use_local_feedback = (
feedback_register_config.source_feedback_register == "local"
)

register_bitshift, width, mask = self._register_bitshift(
register,
Expand All @@ -87,7 +88,6 @@ def calculate_feedback_routing(self):
"Measurement result must not span across indices"
)

feedback_register_config = feedback_register_configs[awg.key]
feedback_register_config.source_feedback_register = register
feedback_register_config.codeword_bitshift = codeword_bitshift
feedback_register_config.register_index_select = register_index_select
Expand All @@ -97,11 +97,7 @@ def calculate_feedback_routing(self):
def _transmitter_qa_for_awg(self, awg_key: AwgKey) -> tuple[AwgKey, str] | None:
"""Find the QA core that is transmitting feedback data to this AWG."""
awg = self._awgs[AwgKey(awg_key.device_id, awg_key.awg_number)]
signal_type = awg.signal_type
if signal_type == AWGSignalType.DOUBLE:
awg_signals = {f"{awg.signal_channels[0][0]}_{awg.signal_channels[1][0]}"}
else:
awg_signals = {c for c, _ in awg.signal_channels}
awg_signals = {c for c, _ in awg.signal_channels}
qa_signal_ids = {
h.acquire
for h in self._feedback_connections.values()
Expand Down Expand Up @@ -178,16 +174,6 @@ def _register_bitshift(
raise AssertionError(f"Signal {qa_signal} not found in register {register}")
return register_bitshift, width, mask

@staticmethod
def _local_feedback_allowed(sg_awg: AWGInfo, qa_awg: AWGInfo):
# todo: this check for QC is quite brittle

return (
sg_awg.device_type == DeviceType.SHFSG
and qa_awg.device_type == DeviceType.SHFQA
and sg_awg.device_id == f"{qa_awg.device_id}_sg"
)


@singledispatch
def _do_compute_feedback_routing(
Expand Down
6 changes: 5 additions & 1 deletion laboneq/compiler/scheduler/match_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import TYPE_CHECKING, Iterable, List, Tuple

from attrs import define
from zhinst.utils.feedback_model import (
from zhinst.timing_models import (
FeedbackPath,
PQSCMode,
QAType,
Expand Down Expand Up @@ -179,6 +179,10 @@ def _compute_start_with_latency(
time_of_arrival_at_register + EXECUTETABLEENTRY_LATENCY
)

# Extra slack to avoid issues with marginal model.
# See HBAR-1934
time_of_pulse_played += 5

sg_seq_rate = schedule_data.sampling_rate_tracker.sequencer_rate_for_device(
sg_signal_obj.awg.device_id
)
Expand Down
45 changes: 36 additions & 9 deletions laboneq/compiler/scheduler/preorder_map.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,51 @@
# Copyright 2022 Zurich Instruments AG
# SPDX-License-Identifier: Apache-2.0

from typing import Dict
from __future__ import annotations

import intervaltree

from laboneq.compiler.scheduler.interval_schedule import IntervalSchedule
from laboneq.compiler.scheduler.loop_iteration_schedule import LoopIterationSchedule
from laboneq.compiler.scheduler.loop_schedule import LoopSchedule
from laboneq.compiler.scheduler.section_schedule import SectionSchedule


def calculate_preorder_map(
schedule: IntervalSchedule, preorder_map: Dict, current_depth=0
schedule: IntervalSchedule,
preorder_map: dict[str, int],
section_children: dict[str, set[str]],
current_depth=0,
) -> int:
max_depth = current_depth
intervals = intervaltree.IntervalTree()
if not isinstance(schedule, SectionSchedule):
return current_depth
max_depth = current_depth
intervals = intervaltree.IntervalTree()
if isinstance(schedule, LoopSchedule):
# In the PSV, we do not consider the loop and the loop iteration separately
schedule = schedule.children[0]
# Normally we only need to look at the first loop iteration to find all the
# sections. When there are statically resolved branches however, not every
# iteration may contain all the subsections.
for child in schedule.children:
assert isinstance(child, LoopIterationSchedule)
# In the PSV, we do not consider the loop and the loop iteration separately, so
# we immediately pass to the children without incrementing the depth.
max_depth = max(
max_depth,
calculate_preorder_map(
child, preorder_map, section_children, current_depth
),
)
if section_children[schedule.section].issubset(preorder_map.keys()):
break
else:
# When we sweep a parameter in near-time (or the pipeliner), a section can
# legitimately be absent from the schedule we just generated. This is not
# an error.
pass
return max_depth

if isinstance(schedule, SectionSchedule):
# Draw the section on this row
assert schedule.section not in preorder_map
preorder_map[schedule.section] = current_depth
current_depth += 1

Expand All @@ -37,13 +59,18 @@ def calculate_preorder_map(
if not intervals.overlap(c_start, c_end):
# Place child in this row
max_depth = max(
max_depth, calculate_preorder_map(c, preorder_map, current_depth)
max_depth,
calculate_preorder_map(
c, preorder_map, section_children, current_depth
),
)
else:
# Place child in next free row
max_depth = max(
max_depth,
calculate_preorder_map(c, preorder_map, max_depth + 1),
calculate_preorder_map(
c, preorder_map, section_children, max_depth + 1
),
)
if c_start != c_end:
intervals.addi(c_start, c_end, data=c.section)
Expand Down
5 changes: 5 additions & 0 deletions laboneq/compiler/scheduler/schedule_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ class ScheduleData:

def __post_init__(self):
self.TINYSAMPLE = self.settings.TINYSAMPLE

def reset(self):
"""`ScheduleData` is persistent between scheduler runs, so we must clear the
cache of acquire pulses that are included in the schedule."""
self.acquire_pulses = {}
Loading

0 comments on commit 467bda3

Please sign in to comment.