Source code for microcalorimetry.export._nist_services_files

"""
This module contains functions for exporting effective efficiency datasets.
"""

from __future__ import annotations
import microcalorimetry.configs as configs
from microcalorimetry.math import rmemeas_extras, vna
from microcalorimetry._tkquick.gui_dtypes import SaveAsPath
from rmellipse import RMEProp, RMEMeas
import numpy as np
import warnings
from pathlib import Path
from datetime import datetime

__all__ = ['as_doteff', 'as_dotdut_s11']


def group_typed_uncertainties(params: RMEMeas) -> RMEMeas:
    """
    Group uncertainty mechanisms in params by Type.

    Any mechanism with a combine_id is assumed to be Type A.

    Any mechanisms that doesn't have a type assigned is assumed to be

    Parameters
    ----------
    params : RMEMeas
        Meas object to group uncertainty mechanisms.

    Returns
    -------
    grouped : RMEMeas
        Meas object with grouped uncertainties.

    """
    # treat anything with a combine ID as type A, derived from statistical means
    use = params.copy()
    cc = use.covcats
    if 'combine_id' in params.covcats.categories:
        cc.loc[{'categories': 'Type'}][cc.loc[{'categories': 'combine_id'}] != ''] = 'B'
    # anything that hasn't been assigned a Type yet is Type B

    not_a_or_b = np.logical_and(
        cc.loc[{'categories': 'Type'}] != 'A', cc.loc[{'categories': 'Type'}] != 'B'
    )
    if not_a_or_b.any():
        print(
            'Some uncertainty mechanisms not identified as type A or B, assigning as B.'
        )
        cc.loc[{'categories': 'Type'}][not_a_or_b] = 'B'

    # assume anything with a combine id is Type A
    # things that didn't have one would be grouped under uncategorized
    grouped = rmemeas_extras.categorize_by(use, 'Type')
    return grouped


[docs] def as_dotdut_s11( s11: configs.S11Like, path: SaveAsPath, device: str, expansion_factor: float = 2.0, ): """ Export a device to a .dut file for 1 port s-parameters. This is a cal services Parameters ---------- s11 : configs.S11Like Path to output file. path : SaveAsPath RMEMeas measurement s1p_c data. device : str Name of device. expansion_factor : float, optional If >1 will use the provided expansion factor. If < 1, will calculate the confidence interval (i,e 0.95 will calculate the 95% confidence interval. Returns ------- Path Path to exported DUT file. """ data = configs.S11Like(s11).load() # assign everythin got type B as default data.create_empty_categories(['Type']) data.covcats.loc[:, 'Type'] = 'B' # assign type a inherted from primary standards a_markers = ['Drift', 'trace', 'cables', 'cable', 'Cable', 'PCA', 'additive'] type_a_umechs = data.cov.umech_id[ [False] + [any([am in oi for am in a_markers]) for oi in data.umech_id] ] data.covcats.loc[type_a_umechs, 'Type'] = 'A' # propagate to real/imaginary prop = RMEProp(sensitivity=True) get_mag = prop.propagate(vna.linmag) get_phs = prop.propagate(vna.phase) # calculate mag and phase, than mag = group_typed_uncertainties(get_mag(data)).sel(s='S11') phs = group_typed_uncertainties(get_phs(data)).sel(s='S11') * 180 / np.pi assert 'uncategorized' not in mag.umech_id if expansion_factor < 1: mag_expansion = max( (mag.confint(expansion_factor, rad=True)[1] - mag.nom) / mag.stdunc(rad=True).cov ) phase_expansion = max( (phs.confint(expansion_factor, rad=True)[1] - phs.nom) / phs.stdunc(rad=True).cov ) expansion_factor = float(np.round(mag_expansion, 2)) print(f'{device} .dut export using expansion factor {expansion_factor}') # calculate phase and magnitude uncertainties utot_mag = mag.stdunc(k=expansion_factor).cov ua_mag = mag.usel(umech_id=['A']).stdunc(k=1).cov ub_mag = mag.usel(umech_id=['B']).stdunc(k=1).cov uc_mag = ub_mag * 0 utot_phs = phs.stdunc(k=expansion_factor, deg=True).cov ua_phs = phs.usel(umech_id=['A']).stdunc(k=1, deg=True).cov ub_phs = phs.usel(umech_id=['B']).stdunc(k=1, deg=True).cov uc_phs = ub_mag * 0 with open(path, 'w') as f: now = datetime.today().strftime('%d %b %Y') f.write(f'! {device} {now}\n') f.write(f'! Utot expansion factor = {expansion_factor}\n') f.write(f'! ') f.write( '! Freq, |Gamma|, Arg(Gamma), Ub|G|, Un|G|, Uc|G|, Utot|G|, UbA(G), UnA(G), UcA(G), UtotA(G)\n' ) fmt = '{:9.6f} {:7.5f} {:8.3f} {:7.5f} {:7.5f} {:7.5f} {:7.5f} {:8.3f} {:8.3f} {:8.3f} {:8.3f}\n' for freq in mag.nom.frequency: f.write( fmt.format( freq, mag.nom.sel(frequency=freq), phs.nom.sel(frequency=freq), ub_mag.sel(frequency=freq), ua_mag.sel(frequency=freq), uc_mag.sel(frequency=freq), utot_mag.sel(frequency=freq), ub_phs.sel(frequency=freq), ua_phs.sel(frequency=freq), uc_phs.sel(frequency=freq), utot_phs.sel(frequency=freq), ) )
[docs] def as_doteff( output_path: SaveAsPath, eta: configs.EtaLike, s11: configs.S11Like, sensor_name: str = None, connect_number: int = None, expansion_factor: float = 2, frequency_decimals: int = 2, eta_decimals: int = 4, s11_abs_decimals: int = 4, s11_angle_decimals: int = 2, columns: int = 7, ): """ Generate a .eff file from an effective efficiency measurement. These files contain an effective efficiency measurement, as well as a reflection coefficient and an expression of uncertainty. Type A and Type B uncertainties will be inferred from the objects covariance metadata. Exporting as a .eff file will lose covariance information about the measurement. Parameters ---------- output_path : SaveAsPath File path to save to. eta : configs.EtaLike Effective efficiency of the sensor. s11 : configs.S11Like S11 data of the sensor. sensor_name : str Name of the sensor. If not provided, won't be included in the output file. connect_number : int Connect number of the sensor. If not provided, won't be included in the comment line. expansion_factor : int, optional Expansion factor of the total uncertainty. If >= 1, will use that as the expansion factor. If < 1, then will calculate the confidence interval of the provided fraction (e.g 0.95 will calculate the 95% confidence interval) using metadata of the uncertainty mechanisms. frequency_decimals : int, optional Decimals for reporting frequency, by default 2 eta_decimals : int, optional Decimals for reporting the eta parameters, by default 4. s11_abs_decimals : int, optional Decimals for reporting the S11 absolute value, by default 4. s11_angle_decimals : int, optional Decimals for reporting the S11 angle vakule, by default 2. columns : int, optional Number of columns in the eff file. By default 7. """ eta_dat = configs.EtaLike(eta).load() s11_dat = configs.S11Like(s11).load() flist = eta_dat.cov.frequency try: s11_dat = s11_dat.sel(frequency=flist) except KeyError: print('Warning: missing frequencies in S11 data. Interpolating.') s11_dat = s11_dat.interp(frequency=flist) now = datetime.now() now = now.strftime('%d-%b-%Y') eta_dat = eta_dat[..., 0] eta_nom = eta_dat.nom.copy() eta_grouped = group_typed_uncertainties(eta_dat) eta_uA = eta_grouped.usel(umech_id=['A']).stdunc(k=1).cov eta_uB = eta_grouped.usel(umech_id=['B']).stdunc(k=1).cov # if using the DOFs, the expansion factor might be slightly different # for each frequency point. So, I'm picking the worst case scenario # to report here. if expansion_factor < 1: eta_utot = eta_dat.confint(expansion_factor)[1] - eta_nom expansion_factor = float( np.max(eta_utot / eta_dat.stdunc(k=expansion_factor).cov) ) # re calculate with the worst case eta_utot = eta_dat.stdunc(k=expansion_factor).cov del eta_dat, eta_grouped # only need the nominal here s11_nom = s11_dat.nom[:, 0] s11_abs = np.abs(s11_nom) s11_angle = np.angle(s11_nom, deg=True) del s11_dat, s11_nom data_line = [f'{{:{3 + frequency_decimals}.{frequency_decimals}f}}'] data_line += [f'{{:{2 + s11_abs_decimals}.{s11_abs_decimals}f}}'] data_line += [f'{{:{5 + s11_angle_decimals}.{s11_angle_decimals}f}}'] data_line += [f'{{:{2 + eta_decimals}.{eta_decimals}f}}'] * 4 data_line = (' ' * 7).join(data_line) # print(data_line) # calculate eta uncertainty with open(output_path, 'w') as f: def line(line: str, nl='\n'): f.write(line + nl) if sensor_name: line(f'# Gamma and Effective Efficiency of {sensor_name}') if connect_number: line(f'# Eta: Connect {connect_number}') line(f'# COL={columns}, File created {now}') line(f'# ExpansionFactor={expansion_factor:.2f}') line( '# freq |Gam| arg(Gam) eta uA uB Utot' ) for i, fi in enumerate(flist): # print(s11_angle[i]) dli = data_line.format( fi, s11_abs[i], s11_angle[i], eta_nom.sel(frequency=fi), eta_uA.sel(frequency=fi), eta_uB.sel(frequency=fi), eta_utot.sel(frequency=fi), ) # print(dli) if i == len(flist) - 1: term = '' else: term = '\n' line(dli, nl=term)