sqil-core 0.1.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqil_core/__init__.py +1 -0
- sqil_core/config_log.py +42 -0
- sqil_core/experiment/__init__.py +11 -0
- sqil_core/experiment/_analysis.py +125 -0
- sqil_core/experiment/_events.py +25 -0
- sqil_core/experiment/_experiment.py +553 -0
- sqil_core/experiment/data/plottr.py +778 -0
- sqil_core/experiment/helpers/_function_override_handler.py +111 -0
- sqil_core/experiment/helpers/_labone_wrappers.py +12 -0
- sqil_core/experiment/instruments/__init__.py +2 -0
- sqil_core/experiment/instruments/_instrument.py +190 -0
- sqil_core/experiment/instruments/drivers/SignalCore_SC5511A.py +515 -0
- sqil_core/experiment/instruments/local_oscillator.py +205 -0
- sqil_core/experiment/instruments/server.py +175 -0
- sqil_core/experiment/instruments/setup.yaml +21 -0
- sqil_core/experiment/instruments/zurich_instruments.py +55 -0
- sqil_core/fit/__init__.py +23 -0
- sqil_core/fit/_core.py +179 -31
- sqil_core/fit/_fit.py +544 -94
- sqil_core/fit/_guess.py +304 -0
- sqil_core/fit/_models.py +50 -1
- sqil_core/fit/_quality.py +266 -0
- sqil_core/resonator/__init__.py +2 -0
- sqil_core/resonator/_resonator.py +256 -74
- sqil_core/utils/__init__.py +40 -13
- sqil_core/utils/_analysis.py +226 -0
- sqil_core/utils/_const.py +83 -18
- sqil_core/utils/_formatter.py +127 -55
- sqil_core/utils/_plot.py +272 -6
- sqil_core/utils/_read.py +178 -95
- sqil_core/utils/_utils.py +147 -0
- {sqil_core-0.1.0.dist-info → sqil_core-1.1.0.dist-info}/METADATA +9 -1
- sqil_core-1.1.0.dist-info/RECORD +36 -0
- {sqil_core-0.1.0.dist-info → sqil_core-1.1.0.dist-info}/WHEEL +1 -1
- sqil_core-0.1.0.dist-info/RECORD +0 -19
- {sqil_core-0.1.0.dist-info → sqil_core-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -1,10 +1,21 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
1
3
|
import matplotlib.pyplot as plt
|
2
4
|
import numpy as np
|
3
5
|
from lmfit import Model
|
4
6
|
from scipy.optimize import leastsq, minimize
|
7
|
+
from tabulate import tabulate
|
5
8
|
|
6
|
-
from sqil_core.fit import
|
7
|
-
|
9
|
+
from sqil_core.fit import (
|
10
|
+
FitResult,
|
11
|
+
fit_circle_algebraic,
|
12
|
+
fit_lorentzian,
|
13
|
+
fit_output,
|
14
|
+
fit_skewed_lorentzian,
|
15
|
+
get_best_fit,
|
16
|
+
)
|
17
|
+
from sqil_core.utils import estimate_linear_background, format_number
|
18
|
+
from sqil_core.utils._plot import reset_plot_style, set_plot_style
|
8
19
|
|
9
20
|
|
10
21
|
@fit_output
|
@@ -188,7 +199,7 @@ def fit_phase_vs_freq(freq, phase, theta0, Q_tot, fr):
|
|
188
199
|
p_final = leastsq(
|
189
200
|
lambda a, b, c: residuals_3(a, b, c, theta0, Q_tot), p0, args=(freq, phase)
|
190
201
|
)
|
191
|
-
fr = float(p_final[0])
|
202
|
+
fr = float(p_final[0].item())
|
192
203
|
|
193
204
|
# Step 4: Optimize Q_tot alone
|
194
205
|
def residuals_4(p, x, y, theta0, fr):
|
@@ -200,7 +211,7 @@ def fit_phase_vs_freq(freq, phase, theta0, Q_tot, fr):
|
|
200
211
|
p_final = leastsq(
|
201
212
|
lambda a, b, c: residuals_4(a, b, c, theta0, fr), p0, args=(freq, phase)
|
202
213
|
)
|
203
|
-
Q_tot = float(p_final[0])
|
214
|
+
Q_tot = float(p_final[0].item())
|
204
215
|
|
205
216
|
# Step 5: Joint optimization of θ₀, Q_tot, and fr
|
206
217
|
def residuals_5(p, x, y):
|
@@ -226,8 +237,8 @@ def S11_reflection(
|
|
226
237
|
alpha: float,
|
227
238
|
tau: float,
|
228
239
|
Q_tot: float,
|
229
|
-
Q_ext: float,
|
230
240
|
fr: float,
|
241
|
+
Q_ext: float,
|
231
242
|
phi: float,
|
232
243
|
mag_bg: np.ndarray | None = None,
|
233
244
|
) -> np.ndarray:
|
@@ -260,10 +271,10 @@ def S11_reflection(
|
|
260
271
|
Time delay (in seconds) representing the signal path delay.
|
261
272
|
Q_tot : float
|
262
273
|
Total quality factor of the resonator (includes internal and external losses).
|
263
|
-
Q_ext : float
|
264
|
-
External quality factor, representing coupling losses to external circuitry.
|
265
274
|
fr : float
|
266
275
|
Resonant frequency of the resonator (in Hz).
|
276
|
+
Q_ext : float
|
277
|
+
External quality factor, representing coupling losses to external circuitry.
|
267
278
|
phi : float
|
268
279
|
Additional phase shift (in radians) in the resonator response.
|
269
280
|
mag_bg : np.ndarray or None, optional
|
@@ -284,7 +295,7 @@ def S11_reflection(
|
|
284
295
|
>>> freq = np.linspace(4.9e9, 5.1e9, 500) # Frequency sweep around 5 GHz
|
285
296
|
>>> mag_bg = freq**2 + 3 * freq # Example magnitude background
|
286
297
|
>>> S11 = S11_reflection(freq, a=1.0, alpha=0.0, tau=1e-9,
|
287
|
-
... Q_tot=5000,
|
298
|
+
... Q_tot=5000, fr=5e9, Q_ext=10000, phi=0.0, mag_bg=mag_bg)
|
288
299
|
>>> import matplotlib.pyplot as plt
|
289
300
|
>>> plt.plot(freq, 20 * np.log10(np.abs(S11))) # Plot magnitude in dB
|
290
301
|
>>> plt.xlabel("Frequency (Hz)")
|
@@ -310,8 +321,8 @@ def S21_hanger(
|
|
310
321
|
alpha: float,
|
311
322
|
tau: float,
|
312
323
|
Q_tot: float,
|
313
|
-
Q_ext: float,
|
314
324
|
fr: float,
|
325
|
+
Q_ext: float,
|
315
326
|
phi: float,
|
316
327
|
mag_bg: np.ndarray | None = None,
|
317
328
|
) -> np.ndarray:
|
@@ -344,10 +355,10 @@ def S21_hanger(
|
|
344
355
|
Time delay (in seconds) representing the signal path delay.
|
345
356
|
Q_tot : float
|
346
357
|
Total quality factor of the resonator (includes internal and external losses).
|
347
|
-
Q_ext : float
|
348
|
-
External quality factor, representing coupling losses to external circuitry.
|
349
358
|
fr : float
|
350
359
|
Resonant frequency of the resonator (in Hz).
|
360
|
+
Q_ext : float
|
361
|
+
External quality factor, representing coupling losses to external circuitry.
|
351
362
|
phi : float
|
352
363
|
Additional phase shift (in radians) in the resonator response.
|
353
364
|
mag_bg : np.ndarray or None, optional
|
@@ -368,7 +379,7 @@ def S21_hanger(
|
|
368
379
|
>>> freq = np.linspace(4.9e9, 5.1e9, 500) # Frequency sweep around 5 GHz
|
369
380
|
>>> mag_bg = freq**2 + 3 * freq # Example magnitude background
|
370
381
|
>>> S21 = S21_hanger(freq, a=1.0, alpha=0.0, tau=1e-9,
|
371
|
-
... Q_tot=5000,
|
382
|
+
... Q_tot=5000, fr=5e9, Q_ext=10000, phi=0.0, mag_bg=mag_bg)
|
372
383
|
>>> import matplotlib.pyplot as plt
|
373
384
|
>>> plt.plot(freq, 20 * np.log10(np.abs(S21))) # Plot magnitude in dB
|
374
385
|
>>> plt.xlabel("Frequency (Hz)")
|
@@ -388,6 +399,54 @@ def S21_hanger(
|
|
388
399
|
return env * resonator
|
389
400
|
|
390
401
|
|
402
|
+
def S21_transmission(
|
403
|
+
freq: np.ndarray,
|
404
|
+
a: float,
|
405
|
+
alpha: float,
|
406
|
+
tau: float,
|
407
|
+
Q_tot: float,
|
408
|
+
fr: float,
|
409
|
+
mag_bg: float | None = None,
|
410
|
+
) -> np.ndarray:
|
411
|
+
"""
|
412
|
+
Computes the complex S21 transmission for a single-pole resonator model.
|
413
|
+
|
414
|
+
This model describes the transmission response of a resonator. The total response
|
415
|
+
includes both the resonator and a complex background envelope with a possible linear phase delay.
|
416
|
+
|
417
|
+
Parameters
|
418
|
+
----------
|
419
|
+
freq : np.ndarray
|
420
|
+
Frequency array (in Hz) over which the S21 transmission is evaluated.
|
421
|
+
a : float
|
422
|
+
Amplitude scaling factor of the background envelope.
|
423
|
+
alpha : float
|
424
|
+
Phase offset of the background envelope (in radians).
|
425
|
+
tau : float
|
426
|
+
Time delay in the background response (in seconds).
|
427
|
+
Q_tot : float
|
428
|
+
Total quality factor of the resonator.
|
429
|
+
fr : float
|
430
|
+
Resonant frequency of the resonator (in Hz).
|
431
|
+
mag_bg : float or None, optional
|
432
|
+
Optional background magnitude scaling. If `None` or `NaN`, it defaults to 1.
|
433
|
+
|
434
|
+
Returns
|
435
|
+
-------
|
436
|
+
np.ndarray
|
437
|
+
Complex-valued S21 transmission array over the specified frequency range.
|
438
|
+
"""
|
439
|
+
|
440
|
+
if mag_bg is None:
|
441
|
+
mag_bg = 1
|
442
|
+
elif np.isscalar(mag_bg) and np.isnan(mag_bg):
|
443
|
+
mag_bg = 1
|
444
|
+
|
445
|
+
env = a * np.exp(1j * alpha) * np.exp(2j * np.pi * (freq - freq[0]) * tau)
|
446
|
+
resonator = 1 / (1 + 2j * Q_tot * (freq / fr - 1))
|
447
|
+
return env * resonator
|
448
|
+
|
449
|
+
|
391
450
|
def S11_reflection_mesh(freq, a, alpha, tau, Q_tot, Q_ext, fr, phi):
|
392
451
|
"""
|
393
452
|
Vectorized S11 reflection function.
|
@@ -437,10 +496,45 @@ def S11_reflection_mesh(freq, a, alpha, tau, Q_tot, Q_ext, fr, phi):
|
|
437
496
|
return env * resonator
|
438
497
|
|
439
498
|
|
499
|
+
def linmag_fit(freq: np.ndarray, data: np.ndarray) -> FitResult:
|
500
|
+
"""
|
501
|
+
Fits the magnitude squared of complex data to a Lorentzian profile.
|
502
|
+
|
503
|
+
This function computes the normalized magnitude of the input complex data and fits
|
504
|
+
its squared value to a Lorentzian function to characterize resonance features.
|
505
|
+
If the initial Lorentzian fit quality is poor (based on NRMSE), it attempts a fit
|
506
|
+
using a skewed Lorentzian model and returns the better fit.
|
507
|
+
|
508
|
+
Parameters
|
509
|
+
----------
|
510
|
+
freq : np.ndarray
|
511
|
+
Frequency values corresponding to the data points.
|
512
|
+
data : np.ndarray
|
513
|
+
Complex-valued data to be fitted.
|
514
|
+
|
515
|
+
Returns
|
516
|
+
-------
|
517
|
+
FitResult
|
518
|
+
The best fit result from either the Lorentzian or skewed Lorentzian fit, selected
|
519
|
+
based on fit quality.
|
520
|
+
"""
|
521
|
+
|
522
|
+
linmag = np.abs(data)
|
523
|
+
norm_linmag = linmag / np.max(linmag)
|
524
|
+
# Lorentzian fit
|
525
|
+
fit_res = fit_lorentzian(freq, norm_linmag**2)
|
526
|
+
# If the lorentzian fit is bad, try a skewed lorentzian
|
527
|
+
if not fit_res.is_acceptable("nrmse"):
|
528
|
+
fit_res_skewed = fit_skewed_lorentzian(freq, norm_linmag**2)
|
529
|
+
fit_res = get_best_fit(fit_res, fit_res_skewed)
|
530
|
+
|
531
|
+
return fit_res
|
532
|
+
|
533
|
+
|
440
534
|
def quick_fit(
|
441
535
|
freq: np.ndarray,
|
442
536
|
data: np.ndarray,
|
443
|
-
measurement:
|
537
|
+
measurement: Literal["reflection", "hanger", "transmission"],
|
444
538
|
tau: float | None = None,
|
445
539
|
Q_tot: float | None = None,
|
446
540
|
fr: float | None = None,
|
@@ -455,7 +549,8 @@ def quick_fit(
|
|
455
549
|
|
456
550
|
This function analyzes complex-valued resonator data by fitting a circle in the complex plane and
|
457
551
|
refining key resonator parameters. It estimates or refines the total quality factor (Q_tot),
|
458
|
-
resonance frequency (fr)
|
552
|
+
resonance frequency (fr). For reflection and hanger it also estimates the external quality
|
553
|
+
factor (Q_ext), while correcting for impedance mismatch.
|
459
554
|
|
460
555
|
Parameters
|
461
556
|
----------
|
@@ -490,11 +585,11 @@ def quick_fit(
|
|
490
585
|
A tuple containing:
|
491
586
|
- a (float): Amplitude scaling factor from the off-resonant point.
|
492
587
|
- alpha (float): Phase offset from the off-resonant point (in radians).
|
588
|
+
- tau (float): Estimated cable delay (in radians).
|
493
589
|
- Q_tot (float): Total quality factor.
|
494
|
-
- Q_ext (complex): External quality factor, accounting for impedance mismatch.
|
495
590
|
- fr (float): Resonance frequency.
|
591
|
+
- Q_ext (complex): External quality factor, accounting for impedance mismatch.
|
496
592
|
- phi0 (float): Phase shift due to impedance mismatch (in radians).
|
497
|
-
- theta0 (float): Refined phase offset at resonance.
|
498
593
|
|
499
594
|
Notes
|
500
595
|
-----
|
@@ -510,9 +605,13 @@ def quick_fit(
|
|
510
605
|
>>> print(f"Resonance Frequency: {fr} Hz, Q_tot: {Q_tot}, Q_ext: {Q_ext}")
|
511
606
|
"""
|
512
607
|
# Sanitize inputs
|
513
|
-
if
|
608
|
+
if (
|
609
|
+
measurement != "reflection"
|
610
|
+
and measurement != "hanger"
|
611
|
+
and measurement != "transmission"
|
612
|
+
):
|
514
613
|
raise Exception(
|
515
|
-
f"Invalid measurement type {measurement}. Must be either 'reflection' or '
|
614
|
+
f"Invalid measurement type {measurement}. Must be either 'reflection', 'hanger' or 'transmission'"
|
516
615
|
)
|
517
616
|
if mag_bg is None:
|
518
617
|
mag_bg = np.nan
|
@@ -521,18 +620,27 @@ def quick_fit(
|
|
521
620
|
linmag = np.abs(data)
|
522
621
|
phase = np.unwrap(np.angle(data))
|
523
622
|
|
524
|
-
# Inital estimate for Q_tot and fr by fitting a
|
623
|
+
# Inital estimate for Q_tot and fr by fitting a lorentzian on
|
525
624
|
# the squared manitude data
|
526
625
|
if (Q_tot is None) or (fr is None):
|
527
626
|
if verbose:
|
528
|
-
print("*
|
627
|
+
print("* Lorentzian estimation of fr and Q_tot")
|
529
628
|
norm_linmag = linmag / np.max(linmag)
|
530
|
-
fit_res =
|
531
|
-
|
629
|
+
fit_res = fit_lorentzian(freq, norm_linmag**2)
|
630
|
+
_, efr, fwhm, _ = fit_res.params
|
631
|
+
eQ_tot = efr / fwhm
|
632
|
+
# If the lorentzian fit is bad, try a skewed lorentzian
|
633
|
+
if fit_res.metrics["nrmse"] > 0.5:
|
634
|
+
fit_res_skewed = fit_skewed_lorentzian(freq, norm_linmag**2)
|
635
|
+
(A1, A2, A3, A4, efr, eQ_tot) = fit_res.params
|
636
|
+
delta_aic = fit_res["aic"] - fit_res_skewed["aic"]
|
637
|
+
fit_res = fit_res_skewed if delta_aic >= 0 else fit_res
|
638
|
+
|
532
639
|
# Assign only parameters for which no initial guess was provided
|
533
640
|
Q_tot = Q_tot or eQ_tot
|
534
641
|
fr = fr or efr
|
535
642
|
if verbose:
|
643
|
+
print(fit_res.model_name)
|
536
644
|
fit_res.summary()
|
537
645
|
if fr != efr:
|
538
646
|
print(f" -> Still considering fr = {fr}\n")
|
@@ -595,31 +703,39 @@ def quick_fit(
|
|
595
703
|
phase5 = phase1 - alpha
|
596
704
|
data5 = linmag5 * np.exp(1j * phase5)
|
597
705
|
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
im
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
706
|
+
Q_ext = None
|
707
|
+
phi0 = None
|
708
|
+
if measurement == "reflection" or measurement == "hanger":
|
709
|
+
# Find impedence mismatch
|
710
|
+
if bias_toward_fr:
|
711
|
+
fr_idx = np.abs(freq - fr).argmin()
|
712
|
+
re, im = np.real(data5), np.imag(data5)
|
713
|
+
fit_res = fit_circle_algebraic(
|
714
|
+
re[fr_idx - idx_range : fr_idx + idx_range],
|
715
|
+
im[fr_idx - idx_range : fr_idx + idx_range],
|
716
|
+
)
|
717
|
+
else:
|
718
|
+
fit_res = fit_circle_algebraic(np.real(data5), np.imag(data5))
|
719
|
+
(xc6, yc6, r06) = fit_res.params
|
720
|
+
phi0 = -np.arcsin(yc6 / r06)
|
721
|
+
|
722
|
+
# Q_ext and Q_int
|
723
|
+
if measurement == "reflection":
|
724
|
+
Q_ext = Q_tot / (r06 * np.exp(-1j * phi0))
|
725
|
+
elif measurement == "hanger":
|
726
|
+
Q_ext = Q_tot / (2 * r06 * np.exp(-1j * phi0))
|
727
|
+
|
728
|
+
# Refine phase offset and amplitude scaling
|
618
729
|
if measurement == "reflection":
|
619
730
|
res6 = S11_reflection(freq, a, alpha, tau, Q_tot, Q_ext, fr, phi0, mag_bg / a)
|
620
731
|
elif measurement == "hanger":
|
621
732
|
res6 = S21_hanger(freq, a, alpha, tau, Q_tot, Q_ext, fr, phi0, mag_bg / a)
|
622
|
-
|
733
|
+
elif measurement == "transmission":
|
734
|
+
res6 = S21_transmission(freq, a, alpha, tau, Q_tot, fr)
|
735
|
+
a *= (np.max(linmag) - np.min(linmag)) / (
|
736
|
+
np.max(np.abs(res6)) - np.min(np.abs(res6))
|
737
|
+
)
|
738
|
+
alpha += phase[0] - np.unwrap(np.angle(res6))[0]
|
623
739
|
|
624
740
|
# Plot small summary
|
625
741
|
if do_plot:
|
@@ -654,13 +770,13 @@ def quick_fit(
|
|
654
770
|
fig.tight_layout()
|
655
771
|
plt.show()
|
656
772
|
|
657
|
-
return a, alpha,
|
773
|
+
return a, alpha, tau, Q_tot, fr, Q_ext, phi0
|
658
774
|
|
659
775
|
|
660
776
|
@fit_output
|
661
777
|
def full_fit(
|
662
|
-
freq, data, measurement, a, alpha, tau, Q_tot,
|
663
|
-
):
|
778
|
+
freq, data, measurement, a, alpha, tau, Q_tot, fr, Q_ext=1, phi0=0, mag_bg=None
|
779
|
+
) -> FitResult:
|
664
780
|
"""
|
665
781
|
Performs a full fit of the measured resonator data using a selected model
|
666
782
|
(either reflection or hanger-type measurement). The fitting is handled
|
@@ -694,14 +810,15 @@ def full_fit(
|
|
694
810
|
Q_tot : float
|
695
811
|
Total quality factor of the resonator.
|
696
812
|
|
697
|
-
Q_ext : float
|
698
|
-
External quality factor (coupling quality factor).
|
699
|
-
|
700
813
|
fr : float
|
701
814
|
Resonant frequency.
|
702
815
|
|
816
|
+
Q_ext : float
|
817
|
+
External quality factor (coupling quality factor).
|
818
|
+
Only for reflection and hanger.
|
819
|
+
|
703
820
|
phi0 : float
|
704
|
-
Phase offset at resonance.
|
821
|
+
Phase offset at resonance. Only for relfection and hanger.
|
705
822
|
|
706
823
|
mag_bg : np.ndarray, optional
|
707
824
|
A 1D array representing the magnitude background response, if available.
|
@@ -723,27 +840,48 @@ def full_fit(
|
|
723
840
|
>>> fit_result = full_fit(freq, data, "reflection", 1, 0, 0, 1e4, 2e4, 1.5e9, 0)
|
724
841
|
>>> fit_result.summary()
|
725
842
|
"""
|
843
|
+
model_name = None
|
726
844
|
|
727
|
-
|
728
|
-
return S11_reflection(freq, a, alpha, tau, Q_tot, Q_ext_mag, f_r, phi, mag_bg)
|
845
|
+
if measurement == "reflection":
|
729
846
|
|
730
|
-
|
731
|
-
|
847
|
+
def S11_reflection_fixed(freq, a, alpha, tau, Q_tot, fr, Q_ext_mag, phi):
|
848
|
+
return S11_reflection(
|
849
|
+
freq, a, alpha, tau, Q_tot, fr, Q_ext_mag, phi, mag_bg
|
850
|
+
)
|
732
851
|
|
733
|
-
if measurement == "reflection":
|
734
852
|
model = Model(S11_reflection_fixed)
|
853
|
+
params = model.make_params(
|
854
|
+
a=a, alpha=alpha, tau=tau, Q_tot=Q_tot, fr=fr, Q_ext_mag=Q_ext, phi=phi0
|
855
|
+
)
|
856
|
+
model_name = "S11_reflection"
|
857
|
+
|
735
858
|
elif measurement == "hanger":
|
859
|
+
|
860
|
+
def S21_hanger_fixed(freq, a, alpha, tau, Q_tot, fr, Q_ext_mag, phi):
|
861
|
+
return S21_hanger(freq, a, alpha, tau, Q_tot, fr, Q_ext_mag, phi, mag_bg)
|
862
|
+
|
736
863
|
model = Model(S21_hanger_fixed)
|
864
|
+
params = model.make_params(
|
865
|
+
a=a, alpha=alpha, tau=tau, Q_tot=Q_tot, fr=fr, Q_ext_mag=Q_ext, phi=phi0
|
866
|
+
)
|
867
|
+
model_name = "S21_hanger"
|
737
868
|
|
738
|
-
|
739
|
-
a=a, alpha=alpha, tau=tau, Q_tot=Q_tot, Q_ext_mag=Q_ext, f_r=fr, phi=phi0
|
740
|
-
)
|
741
|
-
res = model.fit(data, params, freq=freq)
|
869
|
+
elif measurement == "transmission":
|
742
870
|
|
743
|
-
|
871
|
+
def S21_transmission_fixed(freq, a, alpha, tau, Q_tot, fr):
|
872
|
+
return S21_transmission(freq, a, alpha, tau, Q_tot, fr, mag_bg)
|
873
|
+
|
874
|
+
model = Model(S21_transmission_fixed)
|
875
|
+
params = model.make_params(a=a, alpha=alpha, tau=tau, Q_tot=Q_tot, fr=fr)
|
876
|
+
model_name = "S21_transmission"
|
877
|
+
|
878
|
+
res = model.fit(data, params, freq=freq)
|
879
|
+
return res, {"model_name": model_name}
|
744
880
|
|
745
881
|
|
746
|
-
def plot_resonator(
|
882
|
+
def plot_resonator(
|
883
|
+
freq, data, x_fit=None, y_fit=None, mag_bg: np.ndarray | None = None, title=""
|
884
|
+
):
|
747
885
|
"""
|
748
886
|
Plots the resonator response in three different representations:
|
749
887
|
- Complex plane (Re vs. Im)
|
@@ -768,38 +906,82 @@ def plot_resonator(freq, data, fit=None, mag_bg: np.ndarray | None = None, title
|
|
768
906
|
The title of the plot. Default is an empty string.
|
769
907
|
"""
|
770
908
|
|
771
|
-
|
909
|
+
set_plot_style(plt)
|
910
|
+
|
911
|
+
fig = plt.figure(figsize=(24, 10))
|
772
912
|
gs = fig.add_gridspec(2, 2)
|
913
|
+
ms = plt.rcParams.get("lines.markersize")
|
773
914
|
|
774
915
|
# Subplot on the left (full height, first column)
|
775
916
|
ax1 = fig.add_subplot(gs[:, 0]) # Left side spans both rows
|
776
|
-
ax1.
|
777
|
-
|
917
|
+
ax1.plot(np.real(data), np.imag(data), "o", color="tab:blue", ms=ms + 1)
|
918
|
+
if y_fit is not None:
|
919
|
+
ax1.plot(np.real(y_fit), np.imag(y_fit), color="tab:orange")
|
778
920
|
ax1.set_aspect("equal")
|
779
|
-
ax1.set_xlabel("
|
780
|
-
ax1.set_ylabel("
|
781
|
-
ax1.grid()
|
921
|
+
ax1.set_xlabel("In-phase")
|
922
|
+
ax1.set_ylabel("Quadrature")
|
923
|
+
ax1.grid(True)
|
782
924
|
|
783
925
|
# Subplot on the top-right (first row, second column)
|
784
926
|
ax2 = fig.add_subplot(gs[0, 1])
|
785
|
-
ax2.
|
786
|
-
|
927
|
+
ax2.plot(freq, np.abs(data), "o", color="tab:blue", ms=ms - 1)
|
928
|
+
if y_fit is not None:
|
929
|
+
ax2.plot(x_fit, np.abs(y_fit), color="tab:orange")
|
787
930
|
if (mag_bg is not None) and (not np.isnan(mag_bg).any()):
|
788
931
|
ax2.plot(freq, mag_bg, "-.", color="tab:green")
|
789
|
-
ax2.set_ylabel("
|
932
|
+
ax2.set_ylabel("Magnitude [V]")
|
790
933
|
ax2.grid(True)
|
791
934
|
|
792
935
|
# Subplot on the bottom-right (second row, second column)
|
793
936
|
ax3 = fig.add_subplot(gs[1, 1])
|
794
|
-
ax3.
|
795
|
-
|
796
|
-
|
937
|
+
ax3.plot(freq, np.unwrap(np.angle(data)), "o", color="tab:blue", ms=ms - 1)
|
938
|
+
if y_fit is not None:
|
939
|
+
ax3.plot(x_fit, np.unwrap(np.angle(y_fit)), color="tab:orange")
|
940
|
+
ax3.set_ylabel("Phase [rad]")
|
797
941
|
ax3.set_xlabel("Frequency [Hz]")
|
798
942
|
ax3.grid(True)
|
799
943
|
|
800
944
|
fig.suptitle(title)
|
801
945
|
fig.tight_layout()
|
802
|
-
|
946
|
+
|
947
|
+
return fig, (ax1, ax2, ax3)
|
948
|
+
|
949
|
+
|
950
|
+
def print_resonator_params(fit_params, measurement):
|
951
|
+
table_data = []
|
952
|
+
|
953
|
+
if measurement == "reflection" or measurement == "hanger":
|
954
|
+
a, alpha, tau, Q_tot, fr, Q_ext_mag, phi0 = fit_params
|
955
|
+
Q_ext = Q_ext_mag * np.exp(1j * phi0)
|
956
|
+
Q_int = compute_Q_int(Q_tot, Q_ext_mag, phi0)
|
957
|
+
kappa_ext = fr / np.real(Q_ext)
|
958
|
+
kappa_int = fr / Q_int
|
959
|
+
kappa_tot = fr / Q_tot
|
960
|
+
|
961
|
+
table_data.append(["fr", f"{format_number(fr, 6, unit="Hz", latex=False)}"])
|
962
|
+
table_data.append(["Re[Q_ext]", f"{np.real(Q_ext):.0f}"])
|
963
|
+
table_data.append(["Q_int", f"{Q_int:.0f}"])
|
964
|
+
table_data.append(["Q_tot", f"{Q_tot:.0f}"])
|
965
|
+
|
966
|
+
table_data.append(
|
967
|
+
["kappa_ext", format_number(kappa_ext, 4, unit="Hz", latex=False)]
|
968
|
+
)
|
969
|
+
table_data.append(
|
970
|
+
["kappa_int", format_number(kappa_int, 4, unit="Hz", latex=False)]
|
971
|
+
)
|
972
|
+
table_data.append(
|
973
|
+
["kappa_tot", format_number(kappa_tot, 4, unit="Hz", latex=False)]
|
974
|
+
)
|
975
|
+
elif measurement == "transmission":
|
976
|
+
a, alpha, tau, Q_tot, fr = fit_params
|
977
|
+
kappa_tot = fr / Q_tot
|
978
|
+
table_data.append(["fr", f"{format_number(fr, 6, unit="Hz", latex=False)}"])
|
979
|
+
table_data.append(["Q_tot", f"{Q_tot:.0f}"])
|
980
|
+
table_data.append(
|
981
|
+
["kappa_tot", format_number(kappa_tot, 4, unit="Hz", latex=False)]
|
982
|
+
)
|
983
|
+
|
984
|
+
print(tabulate(table_data, headers=["Param", "Value"], tablefmt="github"))
|
803
985
|
|
804
986
|
|
805
987
|
def compute_Q_int(Q_tot, Q_ext_mag, Q_ext_phase):
|
sqil_core/utils/__init__.py
CHANGED
@@ -1,33 +1,46 @@
|
|
1
1
|
from ._analysis import (
|
2
|
+
compute_fft,
|
2
3
|
compute_snr_peaked,
|
3
4
|
estimate_linear_background,
|
5
|
+
find_closest_index,
|
6
|
+
find_first_minima_idx,
|
7
|
+
get_peaks,
|
4
8
|
line_between_2_points,
|
5
9
|
linear_interpolation,
|
6
10
|
remove_linear_background,
|
7
11
|
remove_offset,
|
12
|
+
soft_normalize,
|
8
13
|
)
|
9
|
-
from ._const import ONE_TONE_PARAMS, TWO_TONE_PARAMS
|
14
|
+
from ._const import ONE_TONE_PARAMS, PARAM_METADATA, TWO_TONE_PARAMS
|
10
15
|
from ._formatter import (
|
16
|
+
ParamDict,
|
17
|
+
ParamInfo,
|
18
|
+
enrich_qubit_params,
|
19
|
+
format_fit_params,
|
11
20
|
format_number,
|
12
21
|
get_name_and_unit,
|
13
|
-
|
14
|
-
|
22
|
+
get_relevant_exp_parameters,
|
23
|
+
param_info_from_schema,
|
15
24
|
)
|
16
25
|
from ._plot import (
|
17
26
|
build_title,
|
27
|
+
finalize_plot,
|
18
28
|
get_x_id_by_plot_dim,
|
19
29
|
guess_plot_dimension,
|
30
|
+
plot_mag_phase,
|
31
|
+
plot_projection_IQ,
|
20
32
|
reset_plot_style,
|
21
33
|
set_plot_style,
|
22
34
|
)
|
23
35
|
from ._read import (
|
24
|
-
ParamDict,
|
25
|
-
ParamInfo,
|
26
36
|
extract_h5_data,
|
37
|
+
extract_mapped_data,
|
38
|
+
get_data_and_info,
|
27
39
|
get_measurement_id,
|
28
|
-
|
40
|
+
map_data_dict,
|
29
41
|
read_json,
|
30
|
-
|
42
|
+
read_qpu,
|
43
|
+
read_yaml,
|
31
44
|
)
|
32
45
|
|
33
46
|
__all__ = [
|
@@ -37,27 +50,41 @@ __all__ = [
|
|
37
50
|
"remove_linear_background",
|
38
51
|
"linear_interpolation",
|
39
52
|
"line_between_2_points",
|
53
|
+
"soft_normalize",
|
54
|
+
"find_closest_index",
|
40
55
|
"compute_snr_peaked",
|
56
|
+
"find_first_minima_idx",
|
57
|
+
"compute_fft",
|
58
|
+
"get_peaks",
|
41
59
|
# Const
|
60
|
+
"PARAM_METADATA",
|
42
61
|
"ONE_TONE_PARAMS",
|
43
62
|
"TWO_TONE_PARAMS",
|
44
63
|
# Formatter
|
45
64
|
"format_number",
|
46
65
|
"get_name_and_unit",
|
47
|
-
"
|
48
|
-
"
|
66
|
+
"format_fit_params",
|
67
|
+
"ParamInfo",
|
68
|
+
"ParamDict",
|
69
|
+
"param_info_from_schema",
|
70
|
+
"enrich_qubit_params",
|
71
|
+
"get_relevant_exp_parameters",
|
49
72
|
# Plot
|
50
73
|
"set_plot_style",
|
51
74
|
"reset_plot_style",
|
52
75
|
"get_x_id_by_plot_dim",
|
53
76
|
"build_title",
|
54
77
|
"guess_plot_dimension",
|
78
|
+
"plot_mag_phase",
|
79
|
+
"plot_projection_IQ",
|
80
|
+
"finalize_plot",
|
55
81
|
# Read
|
56
82
|
"extract_h5_data",
|
83
|
+
"map_data_dict",
|
84
|
+
"extract_mapped_data",
|
57
85
|
"read_json",
|
58
|
-
"
|
59
|
-
"
|
60
|
-
"read_param_dict",
|
61
|
-
"get_sweep_param",
|
86
|
+
"read_yaml",
|
87
|
+
"read_qpu",
|
62
88
|
"get_measurement_id",
|
89
|
+
"get_data_and_info",
|
63
90
|
]
|