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.
Files changed (36) hide show
  1. sqil_core/__init__.py +1 -0
  2. sqil_core/config_log.py +42 -0
  3. sqil_core/experiment/__init__.py +11 -0
  4. sqil_core/experiment/_analysis.py +125 -0
  5. sqil_core/experiment/_events.py +25 -0
  6. sqil_core/experiment/_experiment.py +553 -0
  7. sqil_core/experiment/data/plottr.py +778 -0
  8. sqil_core/experiment/helpers/_function_override_handler.py +111 -0
  9. sqil_core/experiment/helpers/_labone_wrappers.py +12 -0
  10. sqil_core/experiment/instruments/__init__.py +2 -0
  11. sqil_core/experiment/instruments/_instrument.py +190 -0
  12. sqil_core/experiment/instruments/drivers/SignalCore_SC5511A.py +515 -0
  13. sqil_core/experiment/instruments/local_oscillator.py +205 -0
  14. sqil_core/experiment/instruments/server.py +175 -0
  15. sqil_core/experiment/instruments/setup.yaml +21 -0
  16. sqil_core/experiment/instruments/zurich_instruments.py +55 -0
  17. sqil_core/fit/__init__.py +23 -0
  18. sqil_core/fit/_core.py +179 -31
  19. sqil_core/fit/_fit.py +544 -94
  20. sqil_core/fit/_guess.py +304 -0
  21. sqil_core/fit/_models.py +50 -1
  22. sqil_core/fit/_quality.py +266 -0
  23. sqil_core/resonator/__init__.py +2 -0
  24. sqil_core/resonator/_resonator.py +256 -74
  25. sqil_core/utils/__init__.py +40 -13
  26. sqil_core/utils/_analysis.py +226 -0
  27. sqil_core/utils/_const.py +83 -18
  28. sqil_core/utils/_formatter.py +127 -55
  29. sqil_core/utils/_plot.py +272 -6
  30. sqil_core/utils/_read.py +178 -95
  31. sqil_core/utils/_utils.py +147 -0
  32. {sqil_core-0.1.0.dist-info → sqil_core-1.1.0.dist-info}/METADATA +9 -1
  33. sqil_core-1.1.0.dist-info/RECORD +36 -0
  34. {sqil_core-0.1.0.dist-info → sqil_core-1.1.0.dist-info}/WHEEL +1 -1
  35. sqil_core-0.1.0.dist-info/RECORD +0 -19
  36. {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 fit_circle_algebraic, fit_output, fit_skewed_lorentzian
7
- from sqil_core.utils import estimate_linear_background
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, Q_ext=10000, fr=5e9, phi=0.0, mag_bg=mag_bg)
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, Q_ext=10000, fr=5e9, phi=0.0, mag_bg=mag_bg)
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: str,
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), and external quality factor (Q_ext), while correcting for impedance mismatch.
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 measurement != "reflection" and measurement != "hanger":
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 'hanger'"
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 skewed lorentzian on
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("* Skewed lorentzian estimation of fr and Q_tot")
627
+ print("* Lorentzian estimation of fr and Q_tot")
529
628
  norm_linmag = linmag / np.max(linmag)
530
- fit_res = fit_skewed_lorentzian(freq, norm_linmag**2)
531
- (A1, A2, A3, A4, efr, eQ_tot) = fit_res.params
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
- # Find impedence mismatch
599
- if bias_toward_fr:
600
- fr_idx = np.abs(freq - fr).argmin()
601
- re, im = np.real(data5), np.imag(data5)
602
- fit_res = fit_circle_algebraic(
603
- re[fr_idx - idx_range : fr_idx + idx_range],
604
- im[fr_idx - idx_range : fr_idx + idx_range],
605
- )
606
- else:
607
- fit_res = fit_circle_algebraic(np.real(data5), np.imag(data5))
608
- (xc6, yc6, r06) = fit_res.params
609
- phi0 = -np.arcsin(yc6 / r06)
610
-
611
- # Q_ext and Q_int
612
- if measurement == "reflection":
613
- Q_ext = Q_tot / (r06 * np.exp(-1j * phi0))
614
- elif measurement == "hanger":
615
- Q_ext = Q_tot / (2 * r06 * np.exp(-1j * phi0))
616
-
617
- # Refine theta0
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
- theta0 = phase[0] - np.unwrap(np.angle(res6))[0]
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, Q_tot, Q_ext, fr, phi0, theta0
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, Q_ext, fr, phi0, mag_bg=None
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
- def S11_reflection_fixed(freq, a, alpha, tau, Q_tot, Q_ext_mag, f_r, phi):
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
- def S21_hanger_fixed(freq, a, alpha, tau, Q_tot, Q_ext_mag, f_r, phi):
731
- return S21_hanger(freq, a, alpha, tau, Q_tot, Q_ext_mag, f_r, phi, mag_bg)
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
- params = model.make_params(
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
- return res
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(freq, data, fit=None, mag_bg: np.ndarray | None = None, title=""):
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
- fig = plt.figure(figsize=(10, 5))
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.scatter(np.real(data), np.imag(data), color="tab:blue", s=20)
777
- ax1.plot(np.real(fit), np.imag(fit), color="tab:orange")
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("Re")
780
- ax1.set_ylabel("Im")
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.scatter(freq, np.abs(data), color="tab:blue", s=5)
786
- ax2.plot(freq, np.abs(fit), color="tab:orange")
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("Amplitude")
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.scatter(freq, np.unwrap(np.angle(data)), color="tab:blue", s=5)
795
- ax3.plot(freq, np.unwrap(np.angle(fit)), color="tab:orange")
796
- ax3.set_ylabel("Phase")
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
- plt.show()
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):
@@ -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
- print_fit_metrics,
14
- print_fit_params,
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
- get_sweep_param,
40
+ map_data_dict,
29
41
  read_json,
30
- read_param_dict,
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
- "print_fit_params",
48
- "print_fit_metrics",
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
- "ParamInfo",
59
- "ParamDict",
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
  ]