Source code for edges.cal.s11.base

"""Classes representing S11 and full S-parameter measurements."""

from collections.abc import Sequence
from typing import Self

import attrs
import numpy as np
from astropy import units as un
from pygsdata.attrs import npfield

from edges.frequencies import get_mask
from edges.io import calobsdef
from edges.io.calobsdef3 import CalObsDefEDGES3

from ... import types as tp
from ...io.serialization import hickleable
from .. import reflection_coefficient as rc


[docs] @hickleable @attrs.define(kw_only=True, frozen=True) class CalibratedS11: """A class for representing calibrated S11 measurements of a device.""" s11: np.ndarray = npfield( dtype=complex, possible_ndims=(1,), ) freqs: tp.FreqType = npfield(dtype=float, possible_ndims=(1,), unit=un.MHz) @freqs.validator def _fv(self, att, val): if val.size != len(self.s11): raise ValueError(f"len(freq) != len(raw_s11) [{len(val)},{len(self.s11)}]") # Constructor Methods
[docs] @classmethod def from_receiver_filespec( cls, pathspec: calobsdef.ReceiverS11 | Sequence[calobsdef.ReceiverS11], **kwargs ) -> Self: """Create a CalibratedS11 from the file-specification of a receiver.""" from .receiver import get_receiver_s11model_from_filespec return get_receiver_s11model_from_filespec(pathspec, **kwargs)
[docs] @classmethod def from_load_and_switch( cls, loaddef: calobsdef.LoadS11, switchdef: calobsdef.SwitchingState | None, **kwargs, ) -> Self: """Construct a CalibratedS11 given a loaddef spec and internal switch spec.""" from .cal_loads import get_loads11_from_load_and_switch return get_loads11_from_load_and_switch( loaddef=loaddef, switchdef=switchdef, **kwargs )
[docs] @classmethod def from_edges2_loaddef( cls, caldef: calobsdef.CalObsDefEDGES2, load: str, **kwargs ) -> Self: """Create a CalibratedS11 object from an EDGES-2 loaddef spec.""" from .cal_loads import get_loads11_from_edges2_loaddef return get_loads11_from_edges2_loaddef(caldef, load, **kwargs)
[docs] @classmethod def from_edges3_loaddef( cls, caldef: CalObsDefEDGES3, load: str, calkit: rc.Calkit = rc.AGILENT_ALAN, **kwargs, ) -> Self: """Create a CalibratedS11 object from an EDGES-3 load definition spec.""" from .cal_loads import get_loads11_from_edges3_loaddef return get_loads11_from_edges3_loaddef( caldef, load=load, calkit=calkit, **kwargs )
[docs] @classmethod def from_s1p_files(cls, **kwargs) -> Self: """Generate from a list of four S1P files. The files are interpreted as the (open, short, match) then (external). """ raise NotImplementedError( "Use from_load_and_switch instead, and pack the four files into correct " "objects." )
[docs] @classmethod def from_calibrated_file( cls, path: tp.PathLike, f_low: tp.FreqType = 0 * un.MHz, f_high: tp.FreqType = np.inf * un.MHz, ): """Generate from a single pre-calibrated file.""" delimiter = "," if path.endswith(".csv") else " " f_orig, gamma_real, gamma_imag = np.loadtxt( path, skiprows=1, delimiter=delimiter, unpack=True, comments=["BEGIN", "END", "#"], ) mask = get_mask(f_orig * un.Hz, low=f_low, high=f_high) return cls( raw_s11=gamma_real[mask] + 1j * gamma_imag[mask], freq=f_orig[mask] * un.Hz, )
[docs] def smoothed(self, params, freqs: tp.FreqType | None = None): """Return a new CalibratedS11, smoothed and interpolated onto new frequencies. Parameters ---------- params : ~s11model.S11ModelParams The set of parameters to use to construct the smoothing model. freqs The frequencies to interpolate to. By default, the same frequencies as in this object (i.e. only smoothing, no interpolation). """ from .s11model import new_s11_modelled return new_s11_modelled(self, params, freqs)
[docs] @hickleable @attrs.define class CalibratedSParams: """A class representing calibrated S-parameters. This is similar to :class:`CalibratedS11`, except that it includes all four S-parameters instead of just S11. Parameters ---------- freqs The frequencies of the S-parameters. s11 The S11 parameter. s12 The S12 parameter. s22 The S22 parameter. s21 The S21 parameter. By default, this is equal to S12. """ freqs: tp.FreqType = npfield(dtype=float, possible_ndims=(1,), unit=un.MHz) s11: np.ndarray = npfield(dtype=complex, possible_ndims=(1,)) s12: np.ndarray = npfield(dtype=complex, possible_ndims=(1,)) s22: np.ndarray = npfield(dtype=complex, possible_ndims=(1,)) s21: np.ndarray = npfield(dtype=complex, possible_ndims=(1,)) @s21.default def _s21_default(self): return self.s12
[docs] def smoothed(self, params, freqs: tp.FreqType | None = None): """Return a new CalibratedSparams, smoothed and interpolated to new frequencies. Parameters ---------- params : ~s11model.S11ModelParams The set of parameters to use to construct the smoothing model. freqs The frequencies to interpolate to. By default, the same frequencies as in this object (i.e. only smoothing, no interpolation). """ from .s11model import S11ModelParams, new_s11_modelled if isinstance(params, S11ModelParams): params = {"s11": params, "s12": params, "s21": params, "s22": params} s11 = new_s11_modelled( self.s11, params["s11"], freqs=self.freqs, new_freqs=freqs ) s12 = new_s11_modelled( self.s12, params["s12"], freqs=self.freqs, new_freqs=freqs ) s21 = new_s11_modelled( self.s21, params.get("s21", params["s12"]), freqs=self.freqs, new_freqs=freqs, ) s22 = new_s11_modelled( self.s22, params["s22"], freqs=self.freqs, new_freqs=freqs ) return CalibratedSParams( freqs=freqs, s11=s11.s11, s12=s12.s11, s21=s21.s11, s22=s22.s11 )
@property def smatrix(self) -> rc.SMatrix: """Represent the data as an S-Matrix.""" return rc.SMatrix([[self.s11, self.s12], [self.s21, self.s22]])
[docs] def as_s11(self) -> CalibratedS11: """Return just the S11 component.""" return CalibratedS11(freqs=self.freqs, s11=self.s11)
[docs] @classmethod def from_internal_switchdef(cls, switchdef, **kwargs): """Create calibrated SParams from an internal switch definition class.""" from .internal_switch import get_calibrated_sparams_from_switchdef return get_calibrated_sparams_from_switchdef(switchdef, **kwargs)
[docs] @classmethod def from_hot_load_semi_rigid( cls, path: tp.PathLike = ":semi_rigid_s_parameters_WITH_HEADER.txt", f_low: tp.FreqType = 0 * un.MHz, f_high: tp.FreqType = np.inf * un.MHz, ): """Create a CalibratedSParams object by reading a file with semi-rigid S11s.""" from .hot_load_cable import read_semi_rigid_cable_sparams_file return read_semi_rigid_cable_sparams_file(path, f_low, f_high)