Experiment basic usage¶
This notebook collects the most common Experiment workflows in one place.
Update system_id, config_dir, and params_dir for your environment before you run it.
1. Create an Experiment¶
Create an Experiment first, then cache a few qubit and resonator labels for later cells.
import numpy as np
import qubex as qx
exp = qx.Experiment(
system_id="YOUR_SYSTEM_ID",
muxes=[0],
# config_dir="/path/to/qubex-config/config",
# params_dir="/path/to/qubex-config/params/YOUR_SYSTEM_ID",
)
Q0, Q1 = exp.qubit_labels[:2]
RQ0, RQ1 = exp.resonator_labels[:2]
print("qubits:", exp.qubit_labels[:4])
print("resonators:", exp.resonator_labels[:4])
2. Connect to instruments¶
Call connect() before you run measurements or schedules.
exp.connect()
3. Optionally push configuration to instruments¶
Use configure() only when you want to push the current configuration and parameters to the hardware.
# exp.configure()
4. Run simple waveform measurements with measure¶
Use measure() when you want to provide control waveforms directly and let Qubex add the readout automatically.
waveform = np.array(
[
0.01 + 0.01j,
0.01 + 0.01j,
0.01 + 0.01j,
0.01 + 0.01j,
]
)
sequence = {
Q0: waveform,
Q1: waveform,
}
result = exp.measure(
sequence=sequence,
mode="avg",
n_shots=1024,
)
result.plot()
print("avg kerneled:", result.data[Q0].kerneled)
print("avg shape:", np.asarray(result.data[Q0].kerneled).shape)
result = exp.measure(
sequence=sequence,
mode="single",
n_shots=1024,
)
result.plot()
print("single kerneled shape:", np.asarray(result.data[Q0].kerneled).shape)
print("first 5 shots:", result.data[Q0].kerneled[:5])
5. Build a control schedule with PulseSchedule¶
Use PulseSchedule when you want to build an explicit pulse sequence from reusable pulse objects.
pulse = qx.pulse.Gaussian(duration=64, amplitude=0.05, sigma=16)
pulse.plot()
schedule = qx.PulseSchedule()
with schedule as s:
s.add(Q0, pulse)
s.add(Q0, pulse.scaled(2))
s.barrier()
s.add(Q1, pulse.shifted(np.pi / 6))
schedule.plot()
6. Sweep one parameter with sweep_parameter¶
For a simple amplitude sweep, return a waveform mapping and let Experiment repeat the measurement over sweep_range.
result = exp.sweep_parameter(
sequence=lambda amplitude: {
Q0: qx.pulse.Rect(duration=64, amplitude=amplitude),
},
sweep_range=np.linspace(0.0, 0.1, 21),
n_shots=1024,
xlabel="Drive amplitude",
ylabel="Readout response",
)
result.plot()
print("sweep_range:", result.data[Q0].sweep_range)
print("data shape:", np.asarray(result.data[Q0].data).shape)
7. Sweep a PulseSchedule factory¶
A schedule factory is useful when the swept parameter changes the schedule itself, such as a wait duration.
wait_range = exp.util.discretize_time_range(
np.geomspace(100, 100e3, 51),
sampling_period=2,
)
def t1_sequence(wait: float) -> qx.PulseSchedule:
"""Build a T1-style sequence for a given wait duration."""
schedule = qx.PulseSchedule()
with schedule as s:
s.add(Q0, qx.pulse.Gaussian(duration=64, amplitude=0.05, sigma=16))
s.add(Q0, qx.pulse.Blank(duration=wait))
return schedule
result = exp.sweep_parameter(
sequence=t1_sequence,
sweep_range=wait_range,
n_shots=1024,
xlabel="Wait duration (ns)",
ylabel="Readout response",
xaxis_type="log",
)
result.plot()
print("n_points:", len(result.data[Q0].sweep_range))
print("first 5 waits:", result.data[Q0].sweep_range[:5])
8. Execute a schedule with explicit readout pulses¶
Use execute() when the schedule already includes the readout pulses you want to run.
control_pulse = qx.pulse.Gaussian(duration=64, amplitude=0.05, sigma=16)
readout_pulse = qx.pulse.FlatTop(duration=256, amplitude=0.1, tau=32)
schedule = qx.PulseSchedule()
with schedule as s:
s.add(RQ0, readout_pulse)
s.barrier()
s.add(Q0, qx.pulse.Blank(duration=128))
s.barrier()
s.add(Q0, control_pulse)
s.barrier()
s.add(RQ0, readout_pulse.scaled(0.8))
schedule.plot()
result = exp.execute(
schedule=schedule,
mode="avg",
n_shots=1024,
)
result.plot()
print("n_captures:", len(result.data[Q0]))
9. Async measurement APIs¶
In notebooks, call the async APIs with await.
schedule = qx.PulseSchedule()
with schedule as s:
s.add(Q0, control_pulse)
result = await exp.run_measurement(
schedule=schedule,
n_shots=1024,
shot_averaging=False,
time_integration=True,
)
result.plot()
print("data shape:", result.data[Q0][0].data.shape)
10. Async sweep measurement¶
Use run_sweep_measurement() when you want one async result for each sweep point.
def sweep_schedule(amplitude: float) -> qx.PulseSchedule:
"""Build a sweep schedule for one drive amplitude."""
schedule = qx.PulseSchedule()
with schedule as s:
s.add(
Q0,
qx.pulse.Gaussian(duration=64, amplitude=amplitude, sigma=16),
)
return schedule
sweep_result = await exp.run_sweep_measurement(
schedule=sweep_schedule,
sweep_values=np.linspace(0.0, 0.1, 11),
n_shots=1024,
shot_averaging=False,
time_integration=True,
)
print("n_points:", len(sweep_result.results))
print("sweep_values:", sweep_result.sweep_values)
print("data shape:", sweep_result.data[Q0][0].shape)
11. Async N-dimensional sweep measurement¶
Use run_ndsweep_measurement() when each point is identified by multiple named sweep axes.
def ndsweep_schedule(point) -> qx.PulseSchedule:
"""Build an ND sweep schedule for one sweep point."""
schedule = qx.PulseSchedule()
with schedule as s:
s.add(
Q0,
qx.pulse.Gaussian(
duration=64,
amplitude=point["amplitude"],
sigma=16,
),
)
s.add(Q0, qx.pulse.Blank(duration=point["wait"]))
return schedule
nd_result = await exp.run_ndsweep_measurement(
schedule=ndsweep_schedule,
sweep_points={
"amplitude": [0.02, 0.04, 0.06],
"wait": [32, 64],
},
sweep_axes=("amplitude", "wait"),
n_shots=1024,
shot_averaging=False,
time_integration=True,
)
print("shape:", nd_result.shape)
print("data shape:", nd_result.data[Q0][0].shape)
print("point[1, 0]:", nd_result.get_sweep_point((1, 0)))
print("n_results:", len(nd_result.results))