Skip to content

Purcell fit#1444

Draft
Giuhcs wants to merge 6 commits into
mainfrom
purcell_fit
Draft

Purcell fit#1444
Giuhcs wants to merge 6 commits into
mainfrom
purcell_fit

Conversation

@Giuhcs

@Giuhcs Giuhcs commented Apr 15, 2026

Copy link
Copy Markdown

In this (draft) PR I am adding the option to fit a Purcell model in a resonator spectroscopy, as requested in #1352.

For a readout resonator coupled to a Purcell filter, the expected output $S_{out,in}$ can be modelled following an input-output prescription, resulting in a spectrum profile described by (see Wallraf)

$$S_{out,in}(\omega)=\left(A-k(\omega-\omega_0)\right)\left[\cos\phi-e^{i\phi}\frac{-2i\Delta_r^{e,g}\kappa_p}{4J^2-2i\Delta_r^{e,g}\left(\kappa_p-2i\Delta_p\right)}\right].$$

At this point, the featured fit is performed (after a removal of the baseline by means of Asymmetric Least Square) in the amplitude part of the signal, following the above reference. From that, one should be able to extract estimations for $\kappa_p$, $J$, $\phi$, as well as the frequencies $\omega_p$ and $\omega_r^{e,g}$ for the Purcell filter and readout resonator, respectively.

@codecov

codecov Bot commented Apr 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 10.00000% with 72 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.06%. Comparing base (52870fe) to head (258cdd2).
⚠️ Report is 200 commits behind head on main.

Files with missing lines Patch % Lines
...tocols/resonator_spectroscopies/resonator_utils.py 7.69% 72 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1444      +/-   ##
==========================================
- Coverage   94.69%   94.06%   -0.63%     
==========================================
  Files         135      135              
  Lines       10778    10855      +77     
==========================================
+ Hits        10206    10211       +5     
- Misses        572      644      +72     
Flag Coverage Δ
unittests 94.06% <10.00%> (-0.63%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...resonator_spectroscopies/resonator_spectroscopy.py 100.00% <100.00%> (ø)
...tocols/resonator_spectroscopies/resonator_utils.py 76.57% <7.69%> (-19.77%) ⬇️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@RoyStegeman RoyStegeman linked an issue Apr 15, 2026 that may be closed by this pull request
@sorewachigauyo

sorewachigauyo commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

For some test data from our QPU, it looks like the fitting is failing to find both troughs

    fit_result = fit.fit(
                 ^^^^^^^^
  File "/mnt/home/nqch_paul/qibolab-qcs-2.6.4/qibocal/src/qibocal/protocols/resonator_spectroscopies/resonator_utils.py", line 1047, in purcell_fit
    w_l_guess, w_h_guess = frequencies[peaks]
    ^^^^^^^^^^^^^^^^^^^^
ValueError: not enough values to unpack (expected 2, got 1)
image

purcell-fit-test.tar.gz

@RoyStegeman

Copy link
Copy Markdown
Member

@sorewachigauyo thanks for trying! We do not have any data from hardware yet ourselves to be able to test it, so we can use this data to improve the fitting algorithm

data: NDArray,
resonator_type=None,
fit=None,
) -> tuple[NDArray, NDArray]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fit dumping is failing because the default JSON encoder doesn't serialize numpy arrays

Comment on lines +1104 to +1113
fig_raw = make_subplots(
rows=1,
cols=2,
horizontal_spacing=0.1,
vertical_spacing=0.1,
specs=[
[{"rowspan": 2}, {}],
[None, {}],
],
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is failing because specs is invalid for the 1 row x 2 column configuration

@sorewachigauyo

Copy link
Copy Markdown
Contributor

I hacked some fixes together to try to make it run

image

Here is some data

purcell_test2.tar.gz

@Giuhcs

Giuhcs commented Apr 23, 2026

Copy link
Copy Markdown
Author

Hello @sorewachigauyo ! Thanks for the feedback! (and for the new data (: ) I'll keep working on it ;)

@RoyStegeman RoyStegeman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just leaving a few comments after having a look. I also notice that the file is now almost 1300 lines, unless there is some obstacle to doing this I think it makes sense to separate them into their own files.

Comment on lines +83 to +89
"purcell": ResonatorSpectroscopyFit(
lambda *params: np.abs(purcell_s_out_in(*params)),
purcell_fit,
chi2_reduced,
lambda z: (z.signal, z.phase),
lambda z: (z.error_signal, z.error_phase),
purcell_spectroscopy_plot,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

values is the tuple (z.signal, z.phase), but chi2 is chi2_reduced, which expects as input only the signal, so this is inconsistent.

chi2[qubit] = (
fit.chi2(
fit.values(data[qubit]),
fit.function(data[qubit].freq, *fitted_parameters[qubit]),
fit.errors(data[qubit]),
dof,
),
np.sqrt(2 / dof),
)

Comment on lines +1050 to +1052
widest_peaks_indices = sorted(
range(len(widths[0])), key=lambda i: widths[0][i], reverse=True
)[:2]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably np.argsort is more readable.

widest_peaks_widths = [widths[0][i] for i in widest_peaks_indices]
if widest_peaks_frequencies[0] > widest_peaks_frequencies[1]:
w_l_guess, w_h_guess = widest_peaks_frequencies[1], widest_peaks_frequencies[0]
k_l_guess, k_h_guess = widest_peaks_widths[1], widest_peaks_widths[0]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

widest_peaks_widths is the width in samples, but here and after you're putting it on the same footing as the w in Hz

)[:2]
widest_peaks_frequencies = [frequencies[peaks[i]] for i in widest_peaks_indices]
widest_peaks_widths = [widths[0][i] for i in widest_peaks_indices]
if widest_peaks_frequencies[0] > widest_peaks_frequencies[1]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if only a single peak is found? It would raise an error and fail the script instead of proceeding to generate the plot without the fit

Comment on lines +1202 to +1203
p = 0.99 # hyperparameter
z_als = baseline_als(data=np.abs(signal), lamda=lamda, p=p)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of refitting, perhaps the baseline should be stored. Either way, here p is different from before so you are reconstructing a baseline different from the one that was removed.


# solving and getting the intitial guesses for w_r, w_p, k_p and J
solution = fsolve(
equations, [600, 650, 1, 2]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How were these values determined? Please write at least a comment explaining the choise

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Purcell Filter spectroscopy fit

3 participants