"""
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 dataset.
These files contain an effective efficiency measurement, as well
as a reflection coefficient and an expression of uncertainty. They
are light weight files that lack rich uncertianty information, but are
useful for historical systems that required effective efficienct data or
to maintain a set of files that comprise a sensors historical data for
Type A analysis.
Parameters
----------
path : Path
File path to output 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)