Source code for microcalorimetry.analysis._sensitivity

# third party dependencies
import matplotlib.pyplot as plt
import numpy as np
from rmellipse.propagators import RMEProp
from microcalorimetry.math import rfpower, fitting
from pathlib import Path
import microcalorimetry._helpers._intf_tools as clitools
import microcalorimetry.configs as configs
import click

__all__ = ['make_k_coeffs']


@click.command(name='make-sensitivity-coeffs')
@click.argument('parsed-dcsweep', type=Path)
@click.option('--constrain-zero', is_flag=True)
@click.option('--p-of-e', is_flag=True)
@click.option('--deg', type=int)
@click.option('--output-file', '-o', type=Path)
@click.option('--show-plots', is_flag=True)
@click.option('--save-plots', type=Path)
@click.option('--plot-ext', type=str, default='.png')
def _cli_make_k_coeffs(
    *args,
    output_file: Path = None,
    show_plots: bool = False,
    save_plots: Path = None,
    plot_ext: str = 'png',
    **kwargs,
):
    """
    Interface for the command line for parsing DC sweeps.

    Parameters
    ----------
    show_plots : bool, optional
        If true, shows the plots in a gui and freezes the terminal, by default False.
        Path to save group under can
        be provided with the group appended to the hdf5 path (i.e. file.h5/group/path)
    output_file : Path, optional
        If provided, outputs any saveable objects to an HDF5 file, by default None.
    save_plots : Path, optional
        If provided, save plots in this directory
    plot_ext : str, optional
        Save extension for the output plots.
    Returns
    -------
    _type_
        _description_
    """
    kwargs['make_plots'] = show_plots or save_plots

    outputs = clitools.run_and_show_plots(
        make_k_coeffs,
        *args,
        show_plots=show_plots,
        save_plots=save_plots,
        plot_ext=plot_ext,
        **kwargs,
    )
    if output_file:
        clitools.save_saveable_objects(outputs[0], output_file=output_file)
    return outputs


[docs] def make_k_coeffs( parsed_dcsweep: configs.ParsedDCSweep, constrain_zero: bool = False, p_of_e: bool = False, deg: int = 2, make_plots: bool = False, ) -> tuple[dict[configs.ThermoelectricFitCoefficients], plt.Figure]: r""" Load and analyze a sensitivity run. Takes in a sensitivity experiment and generate sensitivity coefficients and other relevant data. Parameters ---------- parsed_dcsweep : microcalorimetry.configs.ParsedDCSweep Steps parsed from sensitivity measurement. The first field is the applied voltage, the second field is the applied current, and the last field is the measure thermopile voltage. constrain_zero : bool, optional Constrains the fit to zero. The default is False. p_of_e : bool, optional Fit power as a function of thermopile voltage. The default is False. deg : int, optional Fit degrees of polynomial, the default is 2. make_plots : bool, optional Output plots if True, outputs None in place of figure otherwise. Returns ------- sensitivity : dict[configs.ThermoelectricFitCoefficients] Sensitivity coefficients figures : list[plt.Figure] List of figures generated (empty if not made). Fit and residuals. """ # pass through to the underlying function propagator = RMEProp(sensitivity=True) # wrap any uncertainty functions calc_thermopile_sensitivity = propagator.propagate(rfpower.thermopile_sensitivity) parsed_dcsweep = configs.ParsedDCSweep(parsed_dcsweep) v = configs.DCSweep(parsed_dcsweep.pop('v')).load() i = configs.DCSweep(parsed_dcsweep.pop('i')).load() coeffs_dict = {} figures = [] for ename in parsed_dcsweep.keys(): e = configs.DCSweep(parsed_dcsweep[ename]).load() p = v * i coeffs = calc_thermopile_sensitivity( p, e, constrain_zero=constrain_zero, p_of_e=p_of_e, deg=deg, punc=p.stdunc().cov, eunc=e.stdunc().cov, ) coeffs.name = 'coeffs' v.name = 'voltage_steps' i.name = 'current_steps' e.name = 'thermopile_steps' coeffs.attrs['constrain_zero'] = constrain_zero coeffs.attrs['p_of_e'] = p_of_e coeffs_dict[ename] = coeffs # remove units from the name, don't want thos its confusing. ename.replace('(V)', '') # make plots if asked to fig = None if make_plots: fig = plot_coeffs(propagator, v, i, e, coeffs, p_of_e, ename) figures.append(fig) return coeffs_dict, figures
_cli_make_k_coeffs = clitools.format_from_npdoc(make_k_coeffs)(_cli_make_k_coeffs) def plot_coeffs(propagator, v, i, e, coeffs, p_of_e, ename): """ Plots coefficients Helper function for make_k_coeffs Parameters ---------- propagator : RMEProp Propagator to use v : RMEMeas Voltage measured at each step i : RMEMeas Voltage measured at each step e : RMEMeas Voltage measured at each step coeffs : _type_ Fit coefficients p_of_e : _type_ IF fit was done in terms of power (True) or voltage (False) ename : _type_ Name of e voltage column Returns ------- plt.Figure matplotlib figure object """ polyval = propagator.propagate(fitting.polyval2) @propagator.propagate def mult(v, i): return v * i @propagator.propagate def minus(ref, vals): out = ref.copy() out.values = ref.values - vals.values return out k = 2 p = mult(v, i) if p_of_e: x = e xscale = 1e6 xname = 'e' xunits = r'$\mu$V' y = p yscale = 1e3 yname = 'Power' yunits = 'mW' else: x = p xscale = 1e3 xname = 'Power' xunits = 'mW' y = e yscale = 1e6 yname = 'e' yunits = r'$\mu$V' x_fit = x sortind = np.argsort(x_fit.nom.values) y_fit = polyval(coeffs, x_fit) delta = minus(y_fit, y) x_fit = x_fit.isel(steps=sortind) x = x.isel(steps=sortind) y = y.isel(steps=sortind) y_fit = y_fit.isel(steps=sortind) delta = delta.isel(steps=sortind) fig, ax = plt.subplots(2, 1, sharex = True) upper = y_fit.uncbounds(k=k)[0] lower = y_fit.uncbounds(k=-k)[0] x_upper = x.stdunc(k=k)[0] y_upper = y.stdunc(k=k)[0] y_fit_upper = y_fit.stdunc(k=k)[0] ax[0].fill_between( x_fit.nom * xscale, lower * yscale, upper * yscale, color='k', alpha=0.2, label='k = 2 uncertainty', ) ax[0].plot(x_fit.nom * xscale, y_fit.nom * yscale, 'r-', lw=2, label='Fit') ax[0].errorbar( x.nom * xscale, y.nom * yscale, xerr=x_upper * xscale, yerr=y_upper * yscale, marker='o', markersize=8, linestyle='', label='Measured (k = 2 Uncertainty)', ) ax[1].errorbar( x.nom * xscale, delta.nom * yscale, xerr=x_upper * xscale, yerr=y_upper * yscale, marker='.', markersize=8, capsize=5, linestyle='', label='Measured (k = 2 Uncertainty)', ) ax[1].fill_between( x_fit.nom * xscale, -1 * y_fit_upper * yscale, y_fit_upper * yscale, color='k', alpha=0.2, label='k = 2 Uncertainty', ) ax[0].set_ylabel(f'{yname} ({yunits})') ax[1].set_ylabel(f'Fit - Measured ({yunits})') ax[1].set_xlabel(f'{xname} ({xunits})') for a in ax: a.legend(loc='best') fig.suptitle(f'Sensitivity Fit {ename}') # msg = 'coeffs (units V/W^i or W/V^i) \n' # for i in coeffs.nom.deg: # msg += r'c_' + str(int(i)) + ' = ' + str(float(coeffs.nom.sel(deg=i))) + '\n' # ax[0].text( # 0.9, # 0.01, # msg, # ha='center', # fontsize=12, # bbox={'facecolor': 'orange', 'alpha': 0.5, 'pad': 5}, # ) return fig