Source code for microcalorimetry.export._eff_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
from rmellipse.uobjects import RMEMeas
import numpy as np
import warnings
from pathlib import Path
from datetime import datetime

__all__ = ['as_doteff']


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_doteff( path: Path, eta: configs.Eta, s11: configs.S11, sensor_name: str = None, connect_number: int = None, expansion_factor: int | 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 ---------- path : Path File path to save to. eta : configs.Eta Effective efficiency of the sensor. s11 : configs.S11 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.Eta(eta).load() s11_dat = configs.S11(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(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)