From 96ffce17e7f2fcb8530708f82aa013aec15c66f6 Mon Sep 17 00:00:00 2001 From: Jianyaogu Date: Mon, 23 Mar 2026 11:04:01 -0500 Subject: [PATCH 01/12] Changing KMeans + projection to simple complex plane oscillation fitting.py --- .../fluxonium/fluxonium_power_rabi.py | 191 ++++++++---------- 1 file changed, 86 insertions(+), 105 deletions(-) diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py b/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py index f6e1f74..17d3eae 100644 --- a/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py +++ b/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py @@ -4,8 +4,6 @@ import matplotlib.pyplot as plt from cqedtoolbox.fitfuncs.resonators import HangerResponseBruno from scipy.optimize import curve_fit -from scipy.signal import find_peaks -from sklearn.cluster import KMeans plt.switch_backend("agg") @@ -66,99 +64,84 @@ def _pe_rabi_after_pulse(EC, EL, EJ, flux_ext, f_drive_GHz, V_qubit_volt, t_nsec return P_e_after -def _find_state_centers(sig_2d): - """Find |0> and |1> centers using clustering.""" - - sig_flat = sig_2d.reshape(-1) - X = np.column_stack([sig_flat.real, sig_flat.imag]) - kmeans = KMeans(n_clusters=2, n_init=20, random_state=0).fit(X) - centers = kmeans.cluster_centers_ - z0 = centers[0, 0] + 1j * centers[0, 1] - z1 = centers[1, 0] + 1j * centers[1, 1] - return z0, z1 - - -def _project_to_prob(signals_1d, z0, z1): - """Using this axis gives better SNR for isotropic noise (most common for fluxonium) than fitting with real, imaginary or magnitude axis.""" - - signals_1d = np.asarray(signals_1d) - axis = z1 - z0 - t = np.real((signals_1d - z0) * np.conj(axis)) / (np.abs(axis) ** 2) - return np.clip(t, 0, 1) - - -def _fit_sin2_and_quality(voltage_1d, p): +def _fit_complex_rabi(voltage_1d, signal_1d): """ - Fit the probability projection p(V) = B + A * sin^2(k V + phi), then return V_pi = pi / (2k). - Assumptions: - - The scan span contains at most ~4 peaks of p(V), best for 2 to 3 peaks. - Returns V_pi in same units as voltage_1d. + Fit averaged complex signal directly with + S(V) = C + D * cos(omega * V + phi) + where C and D are complex, omega and phi are real. + Returns + v_pi : float + Pi-pulse amplitude in same units as voltage_1d.Since p(V) ~ sin^2(kV+phi0), so V_pi = pi / (2k) = pi / omega. + fit_dict : dict + Fitted parameters and a simple residual-based quality metric. """ v = np.asarray(voltage_1d, dtype=float) - p = np.asarray(p, dtype=float) - m = np.isfinite(v) & np.isfinite(p) + s = np.asarray(signal_1d, dtype=np.complex128) + + m = np.isfinite(v) & np.isfinite(s.real) & np.isfinite(s.imag) v = v[m] - p = p[m] + s = s[m] + order = np.argsort(v) v = v[order] - p = p[order] - span = float(v[-1] - v[0]) + s = s[order] - def sin2(V, A, k, phi, B): - return B + A * (np.sin(k * V + phi) ** 2) - - pmin = float(np.min(p)) - pmax = float(np.max(p)) - A0 = float(np.clip(pmax - pmin, 1e-3, 1.0)) - B0 = float(np.clip(np.median(p), 0.0, 1.0)) - n = v.size - w = max(5, n // 25) - if w % 2 == 0: - w += 1 - ps = np.convolve(p, np.ones(w) / w, mode="same") - prom = 0.05 * float(np.ptp(ps)) if np.ptp(ps) > 0 else 0.0 - peaks, _ = find_peaks(ps, prominence=prom) - - if peaks.size >= 2: - peaks = np.sort(peaks) - vp = v[peaks[:4]] - dV = float(np.median(np.diff(vp))) if vp.size >= 3 else float(vp[1] - vp[0]) - k_peak = (np.pi / dV) if (np.isfinite(dV) and dV > 0) else None - else: - k_peak = None - k_max = (4.2 * np.pi) / span - k_min = (0.4 * np.pi) / span - k_base = k_peak if (k_peak is not None and np.isfinite(k_peak)) else (2.0 * np.pi / span) - k_base = float(np.clip(k_base, k_min, k_max)) - - bounds_lo = (0.0, k_min, -np.pi, 0.0) - bounds_hi = (1.0, k_max, np.pi, 1.0) - phi_guesses = (0.0, np.pi / 4, np.pi / 2, -np.pi / 4) - k_scales = (1.0, 0.8, 1.2, 0.6, 1.4) + span = float(v[-1] - v[0]) + C0 = np.mean(s) + D0 = 0.5 * (np.max(np.abs(s - C0)) + np.std(s)) + D0 = max(float(np.real(D0)), 1e-6) + omega_min = (0.8 * np.pi) / span + omega_max = (8.4 * np.pi) / span + omega0 = (4.0 * np.pi) / span + + def model_concat(V, c_re, c_im, d_re, d_im, omega, phi): + C = c_re + 1j * c_im + D = d_re + 1j * d_im + y = C + D * np.cos(omega * V + phi) + return np.concatenate([y.real, y.imag]) + + ydata = np.concatenate([s.real, s.imag]) + p0 = [float(C0.real), float(C0.imag), float(D0), 0.0, float(np.clip(omega0, omega_min, omega_max)), 0.0] + bounds_lo = [-np.inf, -np.inf, -np.inf, -np.inf, omega_min, -np.pi] + bounds_hi = [ np.inf, np.inf, np.inf, np.inf, omega_max, np.pi] best = None - for ph0 in phi_guesses: - for s in k_scales: - k0 = float(np.clip(k_base * s, k_min, k_max)) - p0 = (A0, k0, ph0, B0) - try: - popt, _ = curve_fit(sin2, v, p, p0=p0, bounds=(bounds_lo, bounds_hi), maxfev=40000) - resid = p - sin2(v, *popt) - sse = float(np.sum(resid * resid)) - if (best is None) or (sse < best[0]): - best = (sse, popt) - except Exception: - continue - - A_fit, k_fit, phi_fit, B_fit = best[1] - v_pi = np.pi / (2.0 * k_fit) + for phi0 in (0.0, np.pi/4, np.pi/2, -np.pi/4, -np.pi/2): + p0_try = p0.copy() + p0_try[-1] = phi0 + try: + popt, _ = curve_fit(model_concat, v, ydata, p0=p0_try, bounds=(bounds_lo, bounds_hi), maxfev=50000) + yfit = model_concat(v, *popt) + resid = ydata - yfit + sse = float(np.sum(resid**2)) + if (best is None) or (sse < best[0]): + best = (sse, popt) + except Exception: + continue + + _, popt = best + c_re, c_im, d_re, d_im, omega_fit, phi_fit = popt + C_fit = c_re + 1j * c_im + D_fit = d_re + 1j * d_im + v_pi = float(np.pi / omega_fit) v_pi = float(np.clip(v_pi, span / 100.0, 2.0 * span)) + + yfit = C_fit + D_fit * np.cos(omega_fit * v + phi_fit) + resid_complex = s - yfit + resid_rms = float(np.sqrt(np.mean(np.abs(resid_complex)**2))) + amp = float(np.abs(D_fit)) + snr_like = float(amp / resid_rms) if resid_rms > 0 else np.nan + fit_dict = { - "A": float(A_fit), - "k": float(k_fit), + "C_re": float(C_fit.real), + "C_im": float(C_fit.imag), + "D_re": float(D_fit.real), + "D_im": float(D_fit.imag), + "omega": float(omega_fit), "phi": float(phi_fit), - "B": float(B_fit), + "resid_rms": resid_rms, + "snr_like": snr_like, } return v_pi, fit_dict @@ -288,43 +271,41 @@ def _load_data_dummy(self): def analyze(self): """ Analyze Rabi-amplitude sweep and extract π-pulse amplitude vs flux. + Method: 1) Average over repetitions to get one complex mean point per (flux, V). - 2) Estimate the two readout state centers (z0, z1) using clustering. - 3) Project mean IQ points onto the z0→z1 axis to obtain a proxy probability p(V) (This method gives the best snr for isotropic noise). - 4) Extract Vπ from fitting the probability feature p(V) with sine square. + 2) Fit the averaged complex signal directly with a complex cosine. + 3) Extract Vπ from the oscillation period. """ - + signals = self.dependents["signal"] - voltage = self.independents["voltages"][0,:,:] - S = np.mean(signals, axis=0) + voltage = self.independents["voltages"][0, :, :] + S = np.mean(signals, axis=0) # shape: (n_flux, n_V) + n_flux, n_V = S.shape V_grid = voltage + pi_voltages = np.full(n_flux, np.nan) snr_flux = np.full(n_flux, np.nan) fit_results = [None] * n_flux for flux_idx in range(n_flux): sig_1d = S[flux_idx] - sig_2d = signals[:,flux_idx,:] - z0, z1 = _find_state_centers(sig_2d) - p = _project_to_prob(sig_1d, z0, z1) - pi_voltages[flux_idx], fit_results[flux_idx] = _fit_sin2_and_quality(V_grid[flux_idx], p) - - axis = z1 - z0 - sig_flat = sig_2d.reshape(-1) - X = np.column_stack([sig_flat.real, sig_flat.imag]) - km = KMeans(n_clusters=2, n_init=20, random_state=0).fit(X) - labels = km.labels_ - den = np.abs(axis)**2 - t = np.real((sig_flat - z0) * np.conj(axis)) / den - t0, t1 = t[labels == 0], t[labels == 1] - sigma = np.sqrt(0.5*(np.std(t0)**2 + np.std(t1)**2)) - snr_flux[flux_idx] = (1.0 / (4.0 * sigma)) if sigma > 0 else np.nan + + try: + pi_voltages[flux_idx], fit_results[flux_idx] = _fit_complex_rabi( + V_grid[flux_idx], sig_1d + ) + snr_flux[flux_idx] = fit_results[flux_idx]["snr_like"] + except Exception as e: + logger.warning(f"Complex Rabi fit failed at flux index {flux_idx}: {e}") + pi_voltages[flux_idx] = np.nan + snr_flux[flux_idx] = np.nan + fit_results[flux_idx] = None self.pi_power_vs_flux = pi_voltages self.snr = snr_flux - self.fit_results = np.asarray(fit_results) + self.fit_results = np.asarray(fit_results, dtype=object) with DatasetAnalysis(self.data_loc, self.name) as ds: ds.add( @@ -334,7 +315,7 @@ def analyze(self): fit_results=self.fit_results, ) fig, ax = plt.subplots() - ax.plot(self.independents["flux"][0,:,0], self.pi_power_vs_flux, "o-") + ax.plot(self.independents["flux"][0, :, 0], self.pi_power_vs_flux, "o-") ax.set_xlabel("Flux (rad)") ax.set_ylabel("Pi pulse amplitude (V)") ax.set_title("Extracted π pulse amplitude vs flux") From f50bc31c973247a92c09f0c4af7dfe9f9961e302 Mon Sep 17 00:00:00 2001 From: Jianyaogu Date: Mon, 23 Mar 2026 11:07:03 -0500 Subject: [PATCH 02/12] Update fluxonium_power_rabi.py --- .../operations/fluxonium/fluxonium_power_rabi.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py b/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py index 17d3eae..6b3a626 100644 --- a/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py +++ b/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py @@ -291,17 +291,8 @@ def analyze(self): for flux_idx in range(n_flux): sig_1d = S[flux_idx] - - try: - pi_voltages[flux_idx], fit_results[flux_idx] = _fit_complex_rabi( - V_grid[flux_idx], sig_1d - ) - snr_flux[flux_idx] = fit_results[flux_idx]["snr_like"] - except Exception as e: - logger.warning(f"Complex Rabi fit failed at flux index {flux_idx}: {e}") - pi_voltages[flux_idx] = np.nan - snr_flux[flux_idx] = np.nan - fit_results[flux_idx] = None + pi_voltages[flux_idx], fit_results[flux_idx] = _fit_complex_rabi(V_grid[flux_idx], sig_1d) + snr_flux[flux_idx] = fit_results[flux_idx]["snr_like"] self.pi_power_vs_flux = pi_voltages self.snr = snr_flux From 2e7f9b2a995bce7ac47f826be324327d225746ee Mon Sep 17 00:00:00 2001 From: Jianyaogu Date: Mon, 23 Mar 2026 11:36:25 -0500 Subject: [PATCH 03/12] Update fluxonium_power_rabi evaluation.py --- .../fluxonium/fluxonium_power_rabi.py | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py b/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py index 6b3a626..83e0900 100644 --- a/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py +++ b/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py @@ -317,72 +317,67 @@ def analyze(self): def evaluate(self) -> OperationStatus: """ - Final evaluation: determine quality of extracted π-pulse amplitude vs flux. - Criteria: - 1) Edge avoidance (and finite): Vπ not at scan edges. - 2) Readout quality: SNR at each flux point must be good often enough. - 3) Smoothness: Vπ(Φ) should be reasonably smooth on the good points. + Final evaluation using only: + 1) SNR quality of the direct complex Rabi fit + 2) Smoothness of extracted pi-power vs flux on SNR-good points + + Designed to catch the failure mode where the two IQ clusters are too close: + the oscillation becomes weak (low SNR) and the extracted pi values become jagged. """ if not len(self.pi_power_vs_flux): self.report_output = ["Empty π-pulse result."] return OperationStatus.FAILURE - vmin = np.nanmin(self.independents["voltages"][0, :, :], axis=1) - vmax = np.nanmax(self.independents["voltages"][0, :, :], axis=1) - scan_span = vmax - vmin - GOOD_FRAC_MIN = 0.80 # minimum fraction of flux points that must have good Vπ extraction (not edge/finite and good SNR) for overall success - EDGE_FRAC = 0.05 # minimum distance from edges as fraction of scan span for a point to be considered edge-good JUMP_FRAC = 0.40 JUMP_RATE_MAX = 0.20 - edge_bad = ( - (self.pi_power_vs_flux <= vmin + EDGE_FRAC * scan_span) - | (self.pi_power_vs_flux >= vmax - EDGE_FRAC * scan_span) - | ~np.isfinite(self.pi_power_vs_flux) - ) - snr_bad = ( ~np.isfinite(self.snr) + | ~np.isfinite(self.pi_power_vs_flux) | (self.snr < self.SNR_THRESHOLD) ) - - good = (~edge_bad) & (~snr_bad) + good = ~snr_bad good_frac = float(np.mean(good)) pi_g = self.pi_power_vs_flux[good] if pi_g.size >= 3: d = np.abs(np.diff(pi_g)) scale = np.maximum(np.abs(pi_g[:-1]), np.abs(pi_g[1:])) - jump_rate = np.mean(d > JUMP_FRAC * scale) + scale = np.maximum(scale, 1e-12) + jump_rate = float(np.mean(d > JUMP_FRAC * scale)) smooth_pass = jump_rate <= JUMP_RATE_MAX else: jump_rate = np.nan - smooth_pass = True - + smooth_pass = False + finite_frac = float(np.mean(np.isfinite(self.pi_power_vs_flux))) - edge_good_frac = float(np.mean(~edge_bad)) snr_good_frac = float(np.mean(~snr_bad)) snr_med = float(np.nanmedian(self.snr)) if np.any(np.isfinite(self.snr)) else np.nan self.report_output = [( "## Rabi π-pulse extraction\n" f"Flux points: {len(self.pi_power_vs_flux)}\n" - f"Vπ finite fraction: {finite_frac:.2%}\n\n" - f"Edge-good fraction (±{EDGE_FRAC:.0%}): {edge_good_frac:.2%}\n" + f"Vπ finite fraction: {finite_frac:.2%}\n" f"SNR-good fraction (>= {self.SNR_THRESHOLD}): {snr_good_frac:.2%}\n" - f"Good fraction (edge-good & SNR-good): {good_frac:.2%}\n" + f"Good fraction: {good_frac:.2%}\n" f"Median SNR: {snr_med if np.isfinite(snr_med) else 'n/a'}\n" - f"Jump rate (good points): {jump_rate if np.isfinite(jump_rate) else 'n/a'}\n" + f"Jump rate (SNR-good points): {jump_rate if np.isfinite(jump_rate) else 'n/a'}\n" )] if (good_frac >= GOOD_FRAC_MIN) and smooth_pass: return OperationStatus.SUCCESS if good_frac < GOOD_FRAC_MIN: - logger.warning("π extraction failed: not enough good flux points (edge/finite/SNR).") - if not smooth_pass: - logger.warning("π extraction warning: Vπ vs flux unstable on good points.") + logger.warning( + f"Power Rabi failed: only {good_frac:.1%} of flux points passed SNR threshold " + f"{self.SNR_THRESHOLD}." + ) + elif not smooth_pass: + logger.warning( + f"Power Rabi failed: extracted π-pulse amplitudes are too jagged " + f"(jump rate {jump_rate:.2f} > {JUMP_RATE_MAX:.2f})." + ) return OperationStatus.FAILURE From 91c2b5a30c9620a283617ec2e08f2d03c3e2b7b1 Mon Sep 17 00:00:00 2001 From: Jianyao Gu Date: Tue, 5 May 2026 20:38:37 -0500 Subject: [PATCH 04/12] Update fluxonium_pi_spec.py --- .../protocols/operations/fluxonium/fluxonium_pi_spec.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_pi_spec.py b/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_pi_spec.py index c440785..b944dd7 100644 --- a/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_pi_spec.py +++ b/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_pi_spec.py @@ -19,7 +19,7 @@ from cqedtoolbox.protocols.operations.fluxonium.res_spec_vs_flux import _readout_frequencies, _fluxonium_basis from cqedtoolbox.protocols.parameters import ( Repetition, StartFlux, EndFlux, FluxSteps, ResonatorSpecSteps, StartPiSpecFrequency, EndPiSpecFrequency, - QubitFrequency, GainPulseDuration, ECParam, ELParam, EJParam, ZeroFluxCurrent, ReadoutFrequency + QubitFrequency, GainPulseDuration, ECParam, ELParam, EJParam, ZeroFluxCurrent, ReadoutFrequency, GainMultiplier ) logger = logging.getLogger(__name__) @@ -122,6 +122,7 @@ def __init__(self, params): start_freq=StartPiSpecFrequency(params), end_freq=EndPiSpecFrequency(params), rabi_duration=GainPulseDuration(params), + gain_multiplier=GainMultiplier(params), EC=ECParam(params), EL=ELParam(params), EJ=EJParam(params), @@ -165,7 +166,7 @@ def _measure_dummy(self) -> Path: end_flux = self.flux_end() flux_vals = np.linspace(start_flux, end_flux, n_flux) freq_vals = np.linspace(start_freq, end_freq, n_freq) - V_drive = _solve_for_amplitude(5*np.pi, self.rabi_duration(), self.EC(), self.EL(), self.EJ(), flux_vals[n_flux//2]+self.Earth_flux()) + V_drive = self.gain_multiplier()*_solve_for_amplitude(5*np.pi, self.rabi_duration(), self.EC(), self.EL(), self.EJ(), flux_vals[n_flux//2]+self.Earth_flux()) fr_g_vs_flux=[] fr_e_vs_flux=[] for flux in flux_vals: From da753953b1be17dae78ff579890dfc98b891ac93 Mon Sep 17 00:00:00 2001 From: Jianyao Gu Date: Tue, 5 May 2026 20:59:02 -0500 Subject: [PATCH 05/12] Update parameters.py --- src/cqedtoolbox/protocols/parameters.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cqedtoolbox/protocols/parameters.py b/src/cqedtoolbox/protocols/parameters.py index 029347a..203acf4 100644 --- a/src/cqedtoolbox/protocols/parameters.py +++ b/src/cqedtoolbox/protocols/parameters.py @@ -778,3 +778,15 @@ def _dummy_getter(self): def _dummy_setter(self, value): return self.params.readout.fr(value) + + +@dataclass +class GainMultiplier(ProtocolParameterBase): + name: str = field(default="gain_multiplier", init=False) + description: str = field(default="Multiplier for the physical qubit drive amplitude applied to the qubit (e.g. n = only 1/n amplitude reaches to the real qubit)", init=False) + + def _dummy_getter(self): + return self.params.qubit.gain_multiplier() + + def _dummy_setter(self, value): + return self.params.qubit.gain_multiplier(value) From 926927fac1aa339aff06981c6938e4a6e65fdba1 Mon Sep 17 00:00:00 2001 From: Jianyao Gu Date: Tue, 5 May 2026 22:46:51 -0500 Subject: [PATCH 06/12] Create README.md --- src/cqedtoolbox/protocols/operations/fluxonium/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/cqedtoolbox/protocols/operations/fluxonium/README.md diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/README.md b/src/cqedtoolbox/protocols/operations/fluxonium/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/cqedtoolbox/protocols/operations/fluxonium/README.md @@ -0,0 +1 @@ + From 69083fbb78ebd491751874f934383f3f42416564 Mon Sep 17 00:00:00 2001 From: Jianyao Gu Date: Tue, 5 May 2026 23:45:32 -0500 Subject: [PATCH 07/12] Update README.md --- .../protocols/operations/fluxonium/README.md | 366 ++++++++++++++++++ 1 file changed, 366 insertions(+) diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/README.md b/src/cqedtoolbox/protocols/operations/fluxonium/README.md index 8b13789..c57cc8a 100644 --- a/src/cqedtoolbox/protocols/operations/fluxonium/README.md +++ b/src/cqedtoolbox/protocols/operations/fluxonium/README.md @@ -1 +1,367 @@ +# Fluxonium Protocol Notes +This folder contains fluxonium-specific protocol operations for auto-tuning and characterization in `CQEDToolbox`. + +Before using these protocols, first read the main `CQEDToolbox` README for installation, dependencies, and the general package structure. + +CQEDToolbox builds lab-specific measurement protocols on top of `labcore` and QCodes. `labcore` provides sweep framework, HDF5 data storage, and fitting tools, while CQEDToolbox connects these tools to real circuit-QED experiments. + +--- + +# Folder Structure + +This folder contains fluxonium-specific operations: + +```text +fluxonium/ + res_spec_vs_flux.py + fluxonium_pi_spec.py + fluxonium_power_rabi.py +``` + +Coherence measurements are not duplicated here. After the fluxonium-specific tuning steps, use the parallel folder: + +```text +single_qubit/ +``` + +for: + +```text +T1 +T2 Ramsey +T2 Echo +``` + +--- + +# Fluxonium Auto-Tuning Workflow + +The intended workflow is: + +```text +Resonator spectroscopy vs flux + ↓ +Flux offset / zero-flux calibration + ↓ +Fluxonium qubit / pi spectroscopy vs flux + ↓ +Fluxonium power Rabi vs flux + ↓ +T1 / T2 measurements + ↓ +Use protocols in single_qubit/ +``` + +The main difference between fluxonium and ordinary single-qubit protocols is that fluxonium calibration usually includes an additional flux/current sweep dimension. + +--- + +# General Protocol Structure + +Each protocol follows the `ProtocolOperation` structure: + +```text +measure +load +analyze +evaluate +``` + +For testing, dummy measurement methods may exist: + +```python +_measure_dummy(...) +``` + +For real hardware execution, the production path should use QICK methods such as: + +```python +_measure_qick(...) +``` + +The QICK implementation should follow the style of the corresponding `single_qubit/` protocol, but with the extra flux/current sweep added where needed. + +--- + +# Parameter Manager / Instrument Server + +Each protocol registers its inputs and outputs through parameter classes: + +```python +self._register_inputs(...) +self._register_outputs(...) +``` + +These are connected to the parameter manager in `instrumentserver`. + +Before running a protocol, open the parameter manager in instrumentserver and make sure the required parameters exist and are set correctly. + +Typical required parameters include: + +```text +repetitions +readout frequency +start / end flux +flux steps +frequency sweep range +frequency steps +fluxonium parameters: EC, EL, EJ +zero-flux current +drive pulse duration +gain or gain multiplier +``` + +--- + +# How to Run a Protocol in Dummy Mode + +Example structure: + +```python +%load_ext autoreload +%autoreload 2 + +from cqedtoolbox.protocols import base +from cqedtoolbox.protocols.operations.fluxonium.fluxonium_power_rabi import FluxoniumPowerRabi + +base.PLATFORMTYPE = base.PlatformTypes.DUMMY +``` + +Then connect to the parameter manager: + +```python +from instrumentserver.client import Client + +cli = Client(host="127.0.0.1", port=5555, timeout=100) + +pm = cli.find_or_create_instrument("parameter_manager") + +p = FluxoniumPowerRabi(params=pm) +``` + +Run: + +```python +p.execute() +``` + +The same structure can be used for other fluxonium protocols by changing the imported protocol class. + +--- + +# 1. `res_spec_vs_flux.py` + +## Purpose + +Resonator spectroscopy vs flux/current. + +This is the first step in the fluxonium auto-tuning flow. + +It sweeps readout frequency and flux/current, then extracts the resonator frequency as a function of flux. + +## Inputs + +Typical inputs: + +```text +repetitions +start / end readout frequency +readout frequency steps +start / end flux +flux steps +fluxonium guess parameters: EC, EL, EJ +coupling g +bare resonator frequency fr +``` + +## Outputs + +Typical outputs: + +```text +resonator frequency vs flux +zero-flux current estimate +half-flux current estimate +fit parameters +SNR / fit quality +figures +``` + +## Analysis + +The analysis may include: + +```text +complex resonator fitting +single/double hanger fit +SNR filtering +resonator-frequency curve extraction +comparison with fluxonium model +zero-flux / half-flux calibration +``` + +This step can be connected to an ML offset calibration pipeline if desired. + +--- + +# 2. `fluxonium_pi_spec.py` + +## Purpose + +Fluxonium qubit / pi spectroscopy vs flux/current. + +This protocol sweeps qubit drive frequency and flux/current to extract the qubit transition frequency as a function of flux. + +## Inputs + +Typical inputs: + +```text +repetitions +readout frequency +start / end qubit drive frequency +qubit drive frequency steps +start / end flux +flux steps +drive pulse duration +fluxonium parameters: EC, EL, EJ +zero-flux current +gain multiplier +``` + +## Outputs + +Typical outputs: + +```text +qubit frequency vs flux +best-SNR component +SNR vs flux +Gaussian fit parameters +figures +``` + +## Analysis + +The analysis usually: + +```text +averages repetitions +fits real / imaginary / magnitude / phase components +selects the best component by SNR +extracts f01 vs flux +evaluates whether enough flux points pass SNR threshold +``` + +## Gain Multiplier + +A gain multiplier can be used to scale the physical qubit drive amplitude during spectroscopy. + +Example: + +```text +gain_multiplier = 2.0 +``` + +means twice the applied drive amplitude is used. + +This is useful because the theoretical drive amplitude may not match the experimentally required amplitude due to attenuation, line loss, and device-dependent coupling. + +In code: + +```python +final_drive_amplitude = gain_multiplier * theoretical_drive_amplitude +``` + +Recommended parameter description: + +```python +"Multiplier for the physical qubit drive amplitude sent to the qubit during spectroscopy." +``` + +--- + +# 3. `fluxonium_power_rabi.py` + +## Purpose + +Fluxonium power Rabi vs flux/current. + +This protocol sweeps qubit drive gain and flux/current to extract the pi-pulse gain as a function of flux. + +## Inputs + +Typical inputs: + +```text +repetitions +readout frequency +qubit drive frequency +start / end flux +flux steps +start / end gain +gain steps +drive pulse duration +fluxonium parameters: EC, EL, EJ +zero-flux current +``` + +## Outputs + +Typical outputs: + +```text +pi-pulse gain vs flux +Rabi fit parameters +SNR / fit quality +figures +``` + +## Analysis + +The analysis may include: + +```text +averaging repetitions +projecting signal onto the best measurement axis +fitting Rabi oscillations +extracting Vpi(flux) +checking edge avoidance +checking SNR and smoothness +``` + +--- + +# 4. T1 / T2 Measurements + +After resonator spectroscopy, qubit spectroscopy, and power Rabi calibration are complete, use the corresponding protocols in: + +```text +single_qubit/ +``` + +for: + +```text +T1 +T2 Ramsey +T2 Echo +``` + +These protocols are shared with other single-qubit workflows and do not need to be duplicated inside the fluxonium folder unless fluxonium-specific behavior is added later. + +--- + +# Development Notes + +When adding or modifying a fluxonium protocol: + +1. Follow the `ProtocolOperation` structure. +2. Register inputs and outputs clearly. +3. Keep `_measure_dummy` for simulation/testing. +4. Add or update `_measure_qick` for real hardware. +5. Use the corresponding `single_qubit/` protocol as a template when possible. +6. Add flux/current as an extra sweep dimension where required. +7. Save analysis outputs and figures using `DatasetAnalysis`. +8. Keep fluxonium-specific notes here; keep general installation and package usage in the root README. From f2e960f7613288f9e5d551987d5f4404c4673815 Mon Sep 17 00:00:00 2001 From: Jianyao Gu Date: Tue, 5 May 2026 23:49:49 -0500 Subject: [PATCH 08/12] Update README.md --- src/cqedtoolbox/protocols/operations/fluxonium/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/README.md b/src/cqedtoolbox/protocols/operations/fluxonium/README.md index c57cc8a..d1746f2 100644 --- a/src/cqedtoolbox/protocols/operations/fluxonium/README.md +++ b/src/cqedtoolbox/protocols/operations/fluxonium/README.md @@ -42,7 +42,7 @@ The intended workflow is: ```text Resonator spectroscopy vs flux ↓ -Flux offset / zero-flux calibration +Flux offset / zero-flux calibration (Use ML model https://github.com/Jianyaogu/Fluxonium-offset-inverse-model/tree/main if method in resonator spec vs flux works bad) ↓ Fluxonium qubit / pi spectroscopy vs flux ↓ From 28abeafe7fc83fda28b4cec98c7b6f99b2fad389 Mon Sep 17 00:00:00 2001 From: Jianyao Gu Date: Wed, 6 May 2026 00:00:23 -0500 Subject: [PATCH 09/12] Update README.md --- .../protocols/operations/fluxonium/README.md | 81 ++----------------- 1 file changed, 6 insertions(+), 75 deletions(-) diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/README.md b/src/cqedtoolbox/protocols/operations/fluxonium/README.md index d1746f2..afc9964 100644 --- a/src/cqedtoolbox/protocols/operations/fluxonium/README.md +++ b/src/cqedtoolbox/protocols/operations/fluxonium/README.md @@ -42,7 +42,7 @@ The intended workflow is: ```text Resonator spectroscopy vs flux ↓ -Flux offset / zero-flux calibration (Use ML model https://github.com/Jianyaogu/Fluxonium-offset-inverse-model/tree/main if method in resonator spec vs flux works bad) +Flux offset / zero-flux calibration (Use ML model "https://github.com/Jianyaogu/Fluxonium-offset-inverse-model/tree/main" if method in resonator spec vs flux works bad) ↓ Fluxonium qubit / pi spectroscopy vs flux ↓ @@ -80,7 +80,7 @@ For real hardware execution, the production path should use QICK methods such as _measure_qick(...) ``` -The QICK implementation should follow the style of the corresponding `single_qubit/` protocol, but with the extra flux/current sweep added where needed. +Dummy code should be replaced by written QICK implementation following the style of the corresponding `single_qubit/` protocol, but with the extra flux/current sweep added where needed. --- @@ -101,15 +101,14 @@ Typical required parameters include: ```text repetitions -readout frequency +readout/drive frequency start / end flux flux steps frequency sweep range frequency steps -fluxonium parameters: EC, EL, EJ +fluxonium parameters: EC, EL, EJ, g, fr zero-flux current -drive pulse duration -gain or gain multiplier +gain pulse duration ``` --- @@ -182,27 +181,11 @@ Typical outputs: ```text resonator frequency vs flux zero-flux current estimate -half-flux current estimate fit parameters SNR / fit quality figures ``` -## Analysis - -The analysis may include: - -```text -complex resonator fitting -single/double hanger fit -SNR filtering -resonator-frequency curve extraction -comparison with fluxonium model -zero-flux / half-flux calibration -``` - -This step can be connected to an ML offset calibration pipeline if desired. - --- # 2. `fluxonium_pi_spec.py` @@ -242,18 +225,6 @@ Gaussian fit parameters figures ``` -## Analysis - -The analysis usually: - -```text -averages repetitions -fits real / imaginary / magnitude / phase components -selects the best component by SNR -extracts f01 vs flux -evaluates whether enough flux points pass SNR threshold -``` - ## Gain Multiplier A gain multiplier can be used to scale the physical qubit drive amplitude during spectroscopy. @@ -264,21 +235,7 @@ Example: gain_multiplier = 2.0 ``` -means twice the applied drive amplitude is used. - -This is useful because the theoretical drive amplitude may not match the experimentally required amplitude due to attenuation, line loss, and device-dependent coupling. - -In code: - -```python -final_drive_amplitude = gain_multiplier * theoretical_drive_amplitude -``` - -Recommended parameter description: - -```python -"Multiplier for the physical qubit drive amplitude sent to the qubit during spectroscopy." -``` +means twice the applied drive amplitude is used. This is useful because the theoretical drive amplitude may not match the experimentally required amplitude due to attenuation, line loss, and device-dependent coupling. --- @@ -318,19 +275,6 @@ SNR / fit quality figures ``` -## Analysis - -The analysis may include: - -```text -averaging repetitions -projecting signal onto the best measurement axis -fitting Rabi oscillations -extracting Vpi(flux) -checking edge avoidance -checking SNR and smoothness -``` - --- # 4. T1 / T2 Measurements @@ -352,16 +296,3 @@ T2 Echo These protocols are shared with other single-qubit workflows and do not need to be duplicated inside the fluxonium folder unless fluxonium-specific behavior is added later. --- - -# Development Notes - -When adding or modifying a fluxonium protocol: - -1. Follow the `ProtocolOperation` structure. -2. Register inputs and outputs clearly. -3. Keep `_measure_dummy` for simulation/testing. -4. Add or update `_measure_qick` for real hardware. -5. Use the corresponding `single_qubit/` protocol as a template when possible. -6. Add flux/current as an extra sweep dimension where required. -7. Save analysis outputs and figures using `DatasetAnalysis`. -8. Keep fluxonium-specific notes here; keep general installation and package usage in the root README. From b4aebae4b0512e9922229d43c0522c5187db2618 Mon Sep 17 00:00:00 2001 From: Jianyao Gu Date: Wed, 6 May 2026 00:07:44 -0500 Subject: [PATCH 10/12] Update fluxonium_power_rabi.py --- .../protocols/operations/fluxonium/fluxonium_power_rabi.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py b/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py index 83e0900..e477ce9 100644 --- a/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py +++ b/src/cqedtoolbox/protocols/operations/fluxonium/fluxonium_power_rabi.py @@ -16,7 +16,7 @@ from labcore.protocols.base import ProtocolOperation, OperationStatus from cqedtoolbox.protocols.operations.fluxonium.res_spec_vs_flux import _fluxonium_basis, _readout_frequencies from cqedtoolbox.protocols.parameters import ( - Repetition, StartFlux, EndFlux, FluxSteps, NumGainSteps, GainPulseDuration, + Repetition, StartFlux, EndFlux, FluxSteps, NumGainSteps, GainPulseDuration, GainMultiplier, QubitGain, QubitFrequency, ReadoutFrequency, ECParam, ELParam, EJParam, ZeroFluxCurrent ) @@ -178,6 +178,7 @@ def __init__(self, params): rabi_duration=GainPulseDuration(params), rabi_steps=NumGainSteps(params), f_rabi=QubitFrequency(params), + gain_multiplier=GainMultiplier(params), EC=ECParam(params), EL=ELParam(params), EJ=EJParam(params), @@ -219,7 +220,7 @@ def _measure_dummy(self) -> Path: for i_flux in range(n_flux): Veff_vec[i_flux] = _solve_for_amplitude(3*np.pi, self.rabi_duration(), self.EC(), self.EL(), self.EJ(), flux_vals[i_flux] + self.Earth_flux()) - V_grid = (Veff_vec[:, None]) * np.linspace(0.0, 1.0, n_Volts)[None, :] + V_grid = self.gain_multiplier() * (Veff_vec[:, None]) * np.linspace(0.0, 1.0, n_Volts)[None, :] fr_g_vs_flux=[] fr_e_vs_flux=[] for flux_ext in flux_vals: From 1c41954d7d366e0e813cad1dada4dae2bb09cee7 Mon Sep 17 00:00:00 2001 From: Jianyao Gu Date: Wed, 6 May 2026 00:09:50 -0500 Subject: [PATCH 11/12] Update README.md --- src/cqedtoolbox/protocols/operations/fluxonium/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/README.md b/src/cqedtoolbox/protocols/operations/fluxonium/README.md index afc9964..7578219 100644 --- a/src/cqedtoolbox/protocols/operations/fluxonium/README.md +++ b/src/cqedtoolbox/protocols/operations/fluxonium/README.md @@ -235,7 +235,7 @@ Example: gain_multiplier = 2.0 ``` -means twice the applied drive amplitude is used. This is useful because the theoretical drive amplitude may not match the experimentally required amplitude due to attenuation, line loss, and device-dependent coupling. +means twice the applied drive amplitude is used (due to 1/2 will be disspated before driving qubit). This is useful because the theoretical drive amplitude may not match the experimentally required amplitude due to attenuation, line loss, and device-dependent coupling. --- From 59f12968292c06e48b82c35166ed495f96e0dc2a Mon Sep 17 00:00:00 2001 From: Jianyao Gu Date: Wed, 6 May 2026 00:10:26 -0500 Subject: [PATCH 12/12] Update README.md --- src/cqedtoolbox/protocols/operations/fluxonium/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cqedtoolbox/protocols/operations/fluxonium/README.md b/src/cqedtoolbox/protocols/operations/fluxonium/README.md index 7578219..a4e40f0 100644 --- a/src/cqedtoolbox/protocols/operations/fluxonium/README.md +++ b/src/cqedtoolbox/protocols/operations/fluxonium/README.md @@ -262,6 +262,7 @@ gain steps drive pulse duration fluxonium parameters: EC, EL, EJ zero-flux current +gain multiplier ``` ## Outputs