Canonical Shape Hash and Waveform Reuse in qxpulse¶
This notebook explains waveform reuse with two pulse types:
- Predefined waveform created with
Gaussian(...) - Custom waveform created with
Arbitrary(values=...)
In both cases, qxpulse uses shape_hash to identify the canonical waveform shape, while scale and phase stay as per-event runtime factors.
import numpy as np
import qubex as qx
from qubex.clifford import CliffordGenerator
from qubex.pulse import Arbitrary, Gaussian
qx.pulse.set_sampling_period(1.0)
1) Predefined waveform with Gaussian¶
Two Gaussian pulses that differ only by scale and phase share the same canonical shape hash.
gaussian_base = Gaussian(
duration=40.0,
amplitude=1.0,
sigma=8.0,
)
scaled_and_shifted = gaussian_base.scaled(0.6).shifted(-np.pi / 6)
gaussian_base.plot(title="Gaussian base")
scaled_and_shifted.plot(title="Gaussian scaled and shifted")
print("gaussian base scale, phase:", gaussian_base.scale, gaussian_base.phase)
print(
"scaled_and_shifted scale, phase:",
scaled_and_shifted.scale,
scaled_and_shifted.phase,
)
print(
"gaussian shape_values equal:",
np.allclose(gaussian_base.shape_values, scaled_and_shifted.shape_values),
)
print(
"gaussian shape_hash equal:",
gaussian_base.shape_hash == scaled_and_shifted.shape_hash,
)
gaussian_other = Gaussian(
duration=40.0,
amplitude=1.0,
sigma=10.0,
)
print(
"different Gaussian parameters -> different shape_hash:",
gaussian_base.shape_hash != gaussian_other.shape_hash,
)
2) Custom waveform with Arbitrary¶
The same rule applies to manually defined arbitrary waveforms.
custom_base = Arbitrary(
values=np.array([0.0, 1.0, 0.5, 0.0], dtype=np.complex128),
)
custom_variant = custom_base.scaled(0.4).shifted(np.pi / 3)
custom_base.plot(title="Custom waveform base")
custom_variant.plot(title="Custom waveform scaled and shifted")
print("custom base scale, phase:", custom_base.scale, custom_base.phase)
print("custom variant scale, phase:", custom_variant.scale, custom_variant.phase)
print(
"custom shape_values equal:",
np.allclose(custom_base.shape_values, custom_variant.shape_values),
)
print("custom shape_hash equal:", custom_base.shape_hash == custom_variant.shape_hash)
3) Minimal registration simulation (mixed pulse types)¶
This emulates QuEL-3-style registration:
- Register one waveform per unique
shape_hash - Keep
gainandphase_offset_degin each event
events_input = [
gaussian_base,
scaled_and_shifted,
gaussian_other,
custom_base,
custom_variant,
]
waveform_name_by_shape_hash = {}
events = []
for pulse in events_input:
shape_hash = pulse.shape_hash
waveform_name = waveform_name_by_shape_hash.get(shape_hash)
if waveform_name is None:
waveform_name = f"waveform_{len(waveform_name_by_shape_hash):04d}"
waveform_name_by_shape_hash[shape_hash] = waveform_name
events.append(
{
"pulse_type": type(pulse).__name__,
"waveform_name": waveform_name,
"gain": pulse.scale,
"phase_offset_deg": float(np.rad2deg(pulse.phase)),
}
)
print("registered waveforms:", len(waveform_name_by_shape_hash))
print("events:", len(events))
for event in events:
print(event)
Result: even with mixed pulse types (Gaussian and Arbitrary), scale and phase variants are reused by canonical shape hash, reducing waveform registrations.
4) RB registration demo with the Clifford module¶
Randomized benchmarking (RB) sequences are generated from the Clifford module.
In the 1Q case, the sequence is composed of X90 and Z90 gates.
X90is realized by a physical pulse.Z90is virtual (frame update), so it changes phase but does not require a new registered waveform by itself.
rb_n_cliffords = 64
rb_seed = 7
generator = CliffordGenerator()
rb_cliffords, rb_inverse = generator.create_rb_sequences(
n=rb_n_cliffords,
type="1Q",
seed=rb_seed,
)
rb_gate_sequence = [gate for clifford in rb_cliffords for gate in clifford] + rb_inverse
print("number of random cliffords:", len(rb_cliffords))
print("total 1Q gates in RB sequence (including inverse):", len(rb_gate_sequence))
print("gate histogram:")
for gate in sorted(set(rb_gate_sequence)):
print(f" {gate}: {rb_gate_sequence.count(gate)}")
# Display the first 20 gates in the RB sequence
rb_gate_sequence[:20]
def summarize_rb_registration(
*,
label: str,
x90_template: qx.Pulse,
gate_sequence: list[str],
) -> None:
"""Print RB waveform-registration statistics for one X90 template."""
frame_phase = 0.0
waveform_name_by_shape_hash = {}
events = []
for gate in gate_sequence:
if gate == "Z90":
frame_phase += np.pi / 2
continue
if gate != "X90":
raise ValueError(f"Unsupported 1Q RB gate: {gate}")
pulse = x90_template.shifted(frame_phase)
shape_hash = pulse.shape_hash
waveform_name = waveform_name_by_shape_hash.get(shape_hash)
if waveform_name is None:
waveform_name = f"{label}_waveform_{len(waveform_name_by_shape_hash):04d}"
waveform_name_by_shape_hash[shape_hash] = waveform_name
events.append(
{
"waveform_name": waveform_name,
"gain": pulse.scale,
"phase_offset_deg": float(np.rad2deg(pulse.phase)),
}
)
unique_phases = sorted(
{round(event["phase_offset_deg"] % 360.0, 1) for event in events}
)
print(f"\n{label.upper()} TEMPLATE")
print("X90 events:", len(events))
print("registered waveforms (shape_hash based):", len(waveform_name_by_shape_hash))
print("distinct event phases (deg):", unique_phases)
print("first 5 events:")
for event in events[:5]:
print(" ", event)
rb_x90_gaussian = Gaussian(
duration=20.0,
amplitude=1.0,
sigma=4.0,
)
rb_x90_gaussian.plot(title="RB template: Gaussian X90")
summarize_rb_registration(
label="gaussian",
x90_template=rb_x90_gaussian,
gate_sequence=rb_gate_sequence,
)
rb_visual_gate_count = len(rb_gate_sequence)
rb_gate_sequence_visual = rb_gate_sequence[:rb_visual_gate_count]
rb_gaussian_sequence = qx.PulseArray()
for gate in rb_gate_sequence_visual:
if gate == "X90":
rb_gaussian_sequence.add(rb_x90_gaussian)
elif gate == "Z90":
rb_gaussian_sequence.add(qx.VirtualZ(np.pi / 2))
else:
raise ValueError(f"Unsupported 1Q RB gate: {gate}")
print(f"\nVisualized first {rb_visual_gate_count} gates of the RB sequence.")
rb_gaussian_sequence.plot(
title="RB sequence (Gaussian template, logical view)",
show_physical_pulse=False,
)
rb_gaussian_sequence.plot(
title="RB sequence (Gaussian template, physical view)",
show_physical_pulse=True,
)
In RB, many X90 events appear with different effective phases due to interleaved Z90 frame updates.
Shape-hash registration keeps waveform-library growth small, which is expected to reduce waveform registration overhead.