Skip to content

Commit

Permalink
Merge pull request #6 from FaustinCarter/feature-inline-resonator-fits
Browse files Browse the repository at this point in the history
Feature inline resonator fits
  • Loading branch information
FaustinCarter committed Aug 20, 2021
2 parents bf926b8 + 85961d6 commit 872605a
Show file tree
Hide file tree
Showing 14 changed files with 1,086 additions and 280 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version 0.5.0:
* Deprecated cmplxIQ module in favor of hanger_resonator module. The functions are the same, only the names have changed. The cmplxIQ module will be removed in a future version.
* Added a new inline_resonator module to fitsS21. This module has fit functions for an inline, or transmission resonator.
* Added a new inline_ground_terminated_resonator_S11 module to fitsS21. This module has fit functions for the type of resonator described in Samuel J. Whiteley's dissertation (U. Chicago, 2019) equation 3.41.
* Fix a small math error in the hanger_resonator guessing module. Was accidentally subtracting the baseline instead of dividing by the baseline before finding the peak location. In most cases, with well-behaved baselines, this would not have introduced any errors.

Version 0.4.4:
* Version bump. No other changes.

Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.4.4
0.5.0
12 changes: 7 additions & 5 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,19 @@ and one that takes parameters and data and returns a residual.

I and Q vs frequency
^^^^^^^^^^^^^^^^^^^^
The built-in fit model is called complx_IQ.py and is located in the fitsS21 folder.
The built-in fit model is called hanger_resonator.py and is located in the fitsS21 folder.
It has two functions, one that calculates best guess values for each of the ten fit
parameters, and one that applies those guesses to the data and calculates the residual.

cmplxIQ_params()
*Note:* This module used to be called cmplxIQ.py. The hanger functions are the same, just renamed.

hanger_params()
----------------
.. autofunction :: scraps.cmplxIQ_params
.. autofunction :: scraps.hanger_params
cmplxIQ_fit()
hanger_fit()
-------------
.. autofunction :: scraps.cmplxIQ_fit
.. autofunction :: scraps.hanger_fit
Two-level system (TLS) and Mattis-Bardeen effect (BMD)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
25 changes: 19 additions & 6 deletions scraps/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
from .resonator import Resonator, makeResFromData, makeResList, indexResList
from .resonator_sweep import ResonatorSweep
from .fitsS21 import cmplxIQ_fit, cmplxIQ_params
from .fitsSweep import qi_tlsAndMBT, f0_tlsAndMBT
from .process_file import process_file
from .plot_tools import plotResListData, plotResSweepParamsVsX, plotResSweepParamsVsTemp, plotResSweepParamsVsPwr, plotResSweep3D
from scraps.fitsS21 import (
cmplxIQ_fit,
cmplxIQ_params,
hanger_fit,
hanger_params,
inline_fit,
inline_params,
)
from scraps.fitsSweep import f0_tlsAndMBT, qi_tlsAndMBT
from scraps.plot_tools import (
plotResListData,
plotResSweep3D,
plotResSweepParamsVsPwr,
plotResSweepParamsVsTemp,
plotResSweepParamsVsX,
)
from scraps.process_file import process_file
from scraps.resonator import Resonator, indexResList, makeResFromData, makeResList
from scraps.resonator_sweep import ResonatorSweep
8 changes: 7 additions & 1 deletion scraps/fitsS21/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
from .cmplxIQ import cmplxIQ_fit, cmplxIQ_params
from scraps.fitsS21.cmplxIQ import cmplxIQ_fit, cmplxIQ_params
from scraps.fitsS21.hanger_resonator import hanger_fit, hanger_params
from scraps.fitsS21.inline_ground_terminated_resonator_s11 import (
inline_ground_terminated_fit,
inline_ground_terminated_params,
)
from scraps.fitsS21.inline_resonator import inline_fit, inline_params
134 changes: 134 additions & 0 deletions scraps/fitsS21/baselines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import lmfit
import numpy as np
from scraps.fitsS21 import utils


def offset(freqs, re0, im0):
"""Complex offset re + j*im.
Freqs vector is ignored, but required for lmfit Model."""
return re0 + 1j * im0


def mag(freqs, g0, g1, g2):
"""2nd order polynomial.
References the freqs-array midpoint, which is important because the baseline coefs shouldn't drift
around with changes in f0 due to power or temperature."""
x = utils.reduce_by_midpoint(freqs)
return g0 + g1 * x + g2 * x ** 2


def phase(freqs, p0, p1, p2):
"""Angle in complex plane parameterized by 2nd order polynomial.
References the freqs-array midpoint, which is important because the baseline coefs shouldn't drift
around with changes in f0 due to power or temperature."""
x = utils.reduce_by_midpoint(freqs)
phi = p0 + p1 * x + p2 * x ** 2
return np.exp(1j * phi)


class ModelMagBaseline(lmfit.Model):
__doc__ = (
"lmfit model that fits a 2nd order polynomial as a function of reduced frequency."
+ lmfit.models.COMMON_DOC
)

def __init__(self, *args, **kwargs):
super().__init__(mag, *args, **kwargs)

def guess(self, data, freqs=None, mask=None, **kwargs):
"""Mask is an number for how many points to keep on each end. If int, then assumes
number of points, if float then assumes percent of data. If None, keeps everything."""

if freqs is None:
raise ValueError("Must pass a frequencies vector")

mag_data = np.abs(data)
xvals = utils.reduce_by_midpoint(freqs)

masked_data = utils.mask_array_ends(mag_data, mask)
masked_xvals = utils.mask_array_ends(xvals, mask)

g2, g1, g0 = np.polyfit(masked_xvals, masked_data, 2)

params = self.make_params(g2=g2, g1=g1, g0=g0)

return lmfit.models.update_param_vals(params, self.prefix, **kwargs)


class ModelPhaseBaseline(lmfit.Model):
__doc__ = (
"lmfit model that fits a 2nd order polynomial to phase angle as a function of reduced frequency."
+ lmfit.models.COMMON_DOC
)

def __init__(self, *args, **kwargs):
super().__init__(phase, *args, **kwargs)

def guess(self, data, freqs=None, mask=None, phase_step=None, **kwargs):
"""Treats phase angle as either a 1st or 2nd order polynomial.
Passing a float to phase_step will cause that amount to be subtracted
from the first half of the masked data prior to fitting a polynomial.
This is useful if it's clear that the phase rolls sharply
through either pi or 2pi, but it's also obvious there is some strong
frequency-dependent behavior.
Passing fit_quadratic_phase will cause the baseline to be modeled as
a second-order polynomial. The default value for this is False."""

if freqs is None:
raise ValueError("Must pass a frequencies vector")

fit_quadratic_phase = kwargs.pop("fit_quadratic_phase", False)
unwrap_phase = kwargs.pop("unwrap_phase", True)

phase_data = np.angle(data)
xvals = utils.reduce_by_midpoint(freqs)

if unwrap_phase:
phase_data = np.unwrap(phase_data)

masked_data = utils.mask_array_ends(phase_data, mask)
masked_xvals = utils.mask_array_ends(xvals, mask)

if phase_step:
step_vector = np.ones_like(masked_data)
step_vector[-len(step_vector) // 2 :] = 0
step_vector *= phase_step
masked_data -= step_vector

if fit_quadratic_phase:
p2, p1, p0 = np.polyfit(masked_xvals, masked_data, 2)
else:
p1, p0 = np.polyfit(masked_xvals, masked_data, 1)
p2 = 0

params = self.make_params(p2=p2, p1=p1, p0=p0)

if not fit_quadratic_phase:
params[f"{self.prefix}p2"].set(vary=False)

return lmfit.models.update_param_vals(params, self.prefix, **kwargs)


class ModelComplexOffset(lmfit.Model):
__doc__ = "lmfit model that fits a complex offset." + lmfit.models.COMMON_DOC

def __init__(self, *args, **kwargs):
super().__init__(offset, *args, **kwargs)

def guess(self, data, freqs=None, mask=None, **kwargs):
"""Guesses the real and imaginary offsets as the mean of the
masked real and imaginary parts of data, respectively."""

masked_data = utils.mask_array_ends(data, mask)

re = np.real(masked_data).mean()
im = np.imag(masked_data).mean()

params = self.make_params(re=re, im=im)

return lmfit.models.update_param_vals(params, self.prefix, **kwargs)

0 comments on commit 872605a

Please sign in to comment.