Source code for punpy.digital_effects_table.measurement_function

"""Use Monte Carlo to propagate uncertainties"""

import copy
import time
import warnings
from abc import ABC, abstractmethod

import numpy as np
import obsarray

import punpy
from punpy.digital_effects_table.digital_effects_table_templates import (
    DigitalEffectsTableTemplates,
)
from punpy.digital_effects_table.measurement_function_utils import (
    MeasurementFunctionUtils,
)
from punpy.mc.mc_propagation import MCPropagation

"""___Authorship___"""
__author__ = "Pieter De Vis"
__created__ = "30/03/2019"
__maintainer__ = "Pieter De Vis"
__email__ = "pieter.de.vis@npl.co.uk"
__status__ = "Development"


[docs] class MeasurementFunction(ABC): """ MeasurementFunction class which provides all the functionality for propagating uncertainties using obsarray digital effects tables. This class needs to be subclassed, and a meas_function needs to be provided for the measurement function to propagate uncertainties through. :param prop: punpy MC propagation or LPU propagation object. Defaults to None, in which case a MC propagation object with 100 MC steps is used. :type prop: punpy.MCPropagation or punpy. LPUPropagation :param xvariables: list of input quantity names, in same order as arguments in measurement function and with same exact names as provided in input datasets. Defaults to None, in which case get_argument_names function is used. :type xvariables: list(str), optional :param uncxvariables: list of input quantity names for which uncertainties should be propagated. Should be a subset of input quantity names. Defaults to None, in which case uncertainties on all input quantities are used. :type uncxvariables: list(str), optional :param yvariable: name of measurand. Defaults to None, in which case get_measurand_name_and_unit function is used. :type yvariable: str, optional :param yunit: unit of measurand. Defaults to "" (unitless). :type yunit: str, optional :param corr_between: Allows to specify the (average) error correlation coefficient between the various input quantities. Defaults to None, in which case no error-correlation is assumed. :type corr_between: numpy.ndarray , optional :param param_fixed: set to true or false to indicate for each input quantity whether it has to remain unmodified either when expand=true or when using repeated measurements, defaults to None (no inputs fixed). :type param_fixed: list of bools, optional :param repeat_dims: Used to select the axis which has repeated measurements. Axis can be specified using the name(s) of the dimension, or their index in the ydims. The calculations will be performed seperately for each of the repeated measurments and then combined, in order to save memory and speed up the process. Defaults to -99, for which there is no reduction in dimensionality.. :type repeat_dims: str or int or list(str) or list(int), optional :param corr_dims: set to positive integer to select the axis used in the correlation matrix. The correlation matrix will then be averaged over other dimensions. Defaults to -99, for which the input array will be flattened and the full correlation matrix calculated. :type corr_dims: integer, optional :param separate_corr_dims: When set to True and output_vars>1, corr_dims should be a list providing the corr_dims for each output variable, each following the format defined in the corr_dims description. Defaults to False :type separate_corr_dims: bool, optional :param allow_some_nans: set to False to ignore any MC sample which has any nan's in the measurand. Defaults to True, in which case only MC samples with only nan's are ignored. :type allow_some_nans: bool, optional :param ydims: list of dimensions of the measurand, in correct order. list of list of dimensions when there are multiple measurands. Default to None, in which case it is assumed to be the same as refxvar (see below) input quantity. :type ydims: list(str), optional :param refxvar: name of reference input quantity that has the same shape as measurand. Defaults to None :type refxvar: string, optional :param sizes_dict: Dictionary with sizes of each of the dimensions of the measurand. Defaults to None, in which cases sizes come from input quantites. :type sizes_dict: dict, optional :param use_err_corr_dict: when possible, use dictionaries with separate error-correlation info per dimension in order to save memory :type use_err_corr_dict: bool, optional :param broadcast_correlation: correlation form ("rand" or "syst" to use when broadcasting :type broadcast_correlation: str """
[docs] def __init__( self, prop=None, xvariables=None, uncxvariables=None, yvariable=None, yunit="", corr_between=None, param_fixed=None, repeat_dims=None, corr_dims=None, separate_corr_dims=False, allow_some_nans=True, ydims=None, refxvar=None, sizes_dict=None, use_err_corr_dict=False, broadcast_correlation="syst", ): if prop is None: self.prop = MCPropagation(100, dtype="float32") else: self.prop = prop self.xvariables = None if self.get_argument_names() is None: self.xvariables = xvariables else: self.xvariables = self.get_argument_names() if xvariables is not None: if xvariables != self.xvariables: raise ValueError( "punpy.MeasurementFunction: when specifying both xvariables and get_argument_names, they need to be the same!" ) if self.xvariables is None: raise ValueError( "punpy.MeasurementFunction: You need to specify xvariables as keyword when initialising MeasurementFunction object, or add get_argument_names() as a function of the class." ) if uncxvariables is None: self.uncxvariables = self.xvariables else: if isinstance(uncxvariables, str): uncxvariables = [uncxvariables] if np.all([uncvar in self.xvariables for uncvar in uncxvariables]): self.uncxvariables = uncxvariables else: raise ValueError( "punpy.MeasurementFunction: the provided uncxvariables are not a subset of the provided xvariables." ) if yvariable is None: self.yvariable, yunit = self.get_measurand_name_and_unit() else: self.yvariable = yvariable yvar = self.get_measurand_name_and_unit()[0] if yvar != "measurand": if yvariable != yvar: raise ValueError( "punpy.MeasurementFunction: when specifying both yvariable and get_measurand_name_and_unit, they need to be the same!" ) self.corr_between = corr_between if isinstance(self.yvariable, str): self.output_vars = 1 else: self.output_vars = len(self.yvariable) self.ydims = ydims # make attributes to have right shape, so later we can loop over self.output_vars if self.output_vars == 1: if isinstance(self.yvariable, str): self.yvariable = [self.yvariable] if isinstance(yunit, str): yunit = [yunit] if self.ydims is not None: if isinstance(self.ydims[0], str): self.ydims = [ydims] self.templ = DigitalEffectsTableTemplates( self.yvariable, yunit, self.output_vars ) self.sizes_dict = sizes_dict if refxvar is None: self.refxvar = self.xvariables[0] elif isinstance(refxvar, int): self.refxvar = self.xvariables[refxvar] else: self.refxvar = refxvar if repeat_dims is None: repeat_dims = [-99] elif isinstance(repeat_dims, int) or isinstance(repeat_dims, str): repeat_dims = [repeat_dims] self.repeat_dims = np.array(repeat_dims) self.num_repeat_dims = np.empty_like(self.repeat_dims, dtype=int) self.str_repeat_dims = np.empty_like(self.repeat_dims, dtype="<U30") # check the corr_dims and set the relevant attributes in the correct shapes self._check_and_set_corr_dims(corr_dims, separate_corr_dims) self.str_repeat_noncorr_dims = [] self.param_fixed = param_fixed if use_err_corr_dict and (repeat_dims[0] > -99): warnings.warn( "It is currently not possible to set repeat_dims (%s) while use_err_corr_dict is set to True (this is set to True by default). Punpy is setting use_err_corr_dict to False instead." % repeat_dims ) self.use_err_corr_dict = False else: self.use_err_corr_dict = use_err_corr_dict self.utils = MeasurementFunctionUtils( self.xvariables, self.uncxvariables, self.ydims, self.str_repeat_dims, self.str_repeat_noncorr_dims, self.prop.verbose, self.templ, self.use_err_corr_dict, broadcast_correlation, param_fixed, ) self.allow_some_nans = allow_some_nans
[docs] @abstractmethod def meas_function(self, *args, **kwargs): """ meas_function is the measurement function itself, to be used in the uncertainty propagation. This function must be overwritten by the user when creating their MeasurementFunction subclass. """ pass
[docs] def get_argument_names(self): """ This function allows to return the names of the input quantities as a list of strings. Can optionally be overwritten to provide names instead of providing xvariables as a keyword. :return: names of the input quantities :rtype: list of strings """ return None
[docs] def get_measurand_name_and_unit(self): """ This function allows to return the name and unit of the measurand as strings. Can optionally be overwritten to provide names instead of providing yvariable as a keyword. :return: name of the measurand, unit :rtype: tuple(str, str) """ return "measurand", ""
[docs] def update_measurand(self, measurand, measurand_unit): self.yvariable = measurand self.templ = DigitalEffectsTableTemplates(self.yvariable, measurand_unit)
[docs] def setup(self, *args, **kwargs): """ This function is to provide a setup stage that can be run before propagating uncertainties. This allows to set up additional class attributes etc, without needing to edit the initialiser (which is quite specific to this class). This function can optionally be overwritten by the user when creating their MeasurementFunction subclass. """ pass
[docs] def propagate_ds( self, *args, store_unc_percent=False, expand=False, ds_out_pre=None, use_ds_out_pre_unmodified=None, include_corr=True, ): """ Function to propagate the uncertainties on the input quantities present in the digital effects tables provided as the input arguments, through the measurement function to produce an output digital effects table with the combined random, systematic and structured uncertainties on the measurand :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param store_unc_percent: Boolean defining whether relative uncertainties should be returned or not. Default to False (absolute uncertainties returned) :type store_unc_percent: bool (optional) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :param ds_out_pre: Pre-existing output dataset in which the measurand & uncertainty variables should be saved. Defaults to None, in which case a new dataset is created. :type ds_out_pre: xarray.dataset (optional) :param use_ds_out_pre_unmodified: bool to specify whether the ds_out_pre should be used unmodified, or whether the error-correlations etc should be worked out by punpy. defaults to None, in which case it is automatically set to True if yvariable is present as a variable in ds_out_pre, and else to False. :type expand: bool (optional) :param include_corr: boolean to indicate whether the output dataset should include the correlation matrices. Defaults to True. :type include_corr: bool (optional) :return: digital effects table with uncertainties on measurand :rtype: obsarray dataset """ if self.prop.verbose: print( "starting propagate_ds (%s s since creation of prop object)" % (time.time() - self.prop.starttime) ) # first calculate the measurand and propagate the uncertainties y = self._check_sizes_and_run(*args, expand=expand, ds_out_pre=ds_out_pre) u_rand_y = self.propagate_random(*args, expand=expand) if self.prop.verbose: print( "propagate_random done (%s s since creation of prop object)" % (time.time() - self.prop.starttime) ) u_syst_y = self.propagate_systematic(*args, expand=expand) if self.prop.verbose: print( "propagate systematic done (%s s since creation of prop object)" % (time.time() - self.prop.starttime) ) if include_corr: if self.output_vars == 1: u_stru_y, corr_stru_y = self.propagate_structured( *args, expand=expand, return_corr=include_corr ) else: u_stru_y, corr_stru_y, corr_stru_y_between = self.propagate_structured( *args, expand=expand, return_corr=include_corr ) else: u_stru_y = self.propagate_structured( *args, expand=expand, return_corr=include_corr ) corr_stru_y = None # check if the provided pre-build dataset should be used as is, or modified to fit the automatically detected error-correlations if use_ds_out_pre_unmodified is None and ds_out_pre is not None: if all([yvar in ds_out_pre.variables for yvar in self.yvariable]): use_ds_out_pre_unmodified = True if use_ds_out_pre_unmodified: if ds_out_pre is not None: ds_out = ds_out_pre else: raise ValueError( "punpy.MeasurementFunction: ds_out_pre needs to be provided when use_ds_out_pre_unmodified is set to True." ) else: repeat_dim_err_corrs = self.utils._find_repeat_dim_corr( "str", *args, store_unc_percent=store_unc_percent, ydims=self.ydims ) self.utils._set_repeat_dims_form(repeat_dim_err_corrs) template = self.templ.make_template_main( self.ydims, self.sizes_dict, store_unc_percent=store_unc_percent, str_corr_dims=self.str_corr_dims, separate_corr_dims=self.separate_corr_dims, str_repeat_noncorr_dims=self.str_repeat_noncorr_dims, repeat_dim_err_corrs=repeat_dim_err_corrs, ) # create dataset template ds_out = obsarray.create_ds(template, self.sizes_dict) # add trivial first dimension to so we can loop over output_vars later if self.output_vars == 1: if u_rand_y is not None: u_rand_y = u_rand_y[None, ...] if u_syst_y is not None: u_syst_y = u_syst_y[None, ...] if u_stru_y is not None: u_stru_y = u_stru_y[None, ...] if corr_stru_y is not None: corr_stru_y = corr_stru_y[None, ...] # loop through measurands for i in range(self.output_vars): ds_out[self.yvariable[i]].values = y[i] if store_unc_percent: ucomp_ran = "u_rel_ran_" + self.yvariable[i] ucomp_sys = "u_rel_sys_" + self.yvariable[i] ucomp_str = "u_rel_str_" + self.yvariable[i] else: ucomp_ran = "u_ran_" + self.yvariable[i] ucomp_sys = "u_sys_" + self.yvariable[i] ucomp_str = "u_str_" + self.yvariable[i] if u_rand_y is None: ds_out = self.templ.remove_unc_component( ds_out, self.yvariable[i], ucomp_ran ) else: if store_unc_percent: ds_out[ucomp_ran].values = u_rand_y[i] / np.abs(y[i]) * 100 else: ds_out[ucomp_ran].values = u_rand_y[i] if u_syst_y is None: ds_out = self.templ.remove_unc_component( ds_out, self.yvariable[i], ucomp_sys ) else: if store_unc_percent: ds_out[ucomp_sys].values = u_syst_y[i] / np.abs(y[i]) * 100 else: ds_out[ucomp_sys].values = u_syst_y[i] if u_stru_y is None: ds_out = self.templ.remove_unc_component( ds_out, self.yvariable[i], ucomp_str, err_corr_comp="err_corr_str_" + self.yvariable[i], ) else: if store_unc_percent: ds_out[ucomp_str].values = u_stru_y[i] / np.abs(y[i]) * 100 else: ds_out[ucomp_str].values = u_stru_y[i] if include_corr: ds_out = self._store_corr( ds_out, corr_stru_y, "err_corr_str_", i, use_ds_out_pre_unmodified, ) else: ds_out.drop("err_corr_str_" + self.yvariable[i]) # ds_out.drop("err_corr_str_between") if (ds_out_pre is not None) and not use_ds_out_pre_unmodified: self.templ.join_with_preexisting_ds( ds_out, ds_out_pre, drop=self.yvariable ) if self.prop.verbose: print( "finishing propagate_ds (%s s since creation of prop object)" % (time.time() - self.prop.starttime) ) return ds_out
[docs] def propagate_ds_total( self, *args, store_unc_percent=False, expand=False, ds_out_pre=None, use_ds_out_pre_unmodified=None, include_corr=True, ): """ Function to propagate the total uncertainties present in the digital effects tables in the input arguments, through the measurement function to produce an output digital effects table with the total uncertainties on the measurand :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param store_unc_percent: Boolean defining whether relative uncertainties should be returned or not. Default to True (relative uncertaintie returned) :type store_unc_percent: bool (optional) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :param ds_out_pre: Pre-existing output dataset in which the measurand & uncertainty variables should be saved. Defaults to None, in which case a new dataset is created. :type ds_out_pre: xarray.dataset (optional) :param include_corr: boolean to indicate whether the output dataset should include the correlation matrices. Defaults to True. :type include_corr: bool (optional) :return: digital effects table with uncertainties on measurand :rtype: obsarray dataset """ if self.prop.verbose: print( "starting propagate_ds_total (%s s since creation of prop object)" % (time.time() - self.prop.starttime) ) y = self._check_sizes_and_run(*args, expand=expand, ds_out_pre=ds_out_pre) if include_corr: if self.output_vars == 1: u_tot_y, corr_tot_y = self.propagate_total( *args, expand=expand, return_corr=include_corr ) else: u_tot_y, corr_tot_y, corr_tot_y_between = self.propagate_total( *args, expand=expand, return_corr=include_corr ) else: u_tot_y = self.propagate_total( *args, expand=expand, return_corr=include_corr ) corr_tot_y = None # check if the provided pre-build dataset should be used as is, or modified to fit the automatically detected error-correlations if use_ds_out_pre_unmodified is None and ds_out_pre is not None: if all([yvar in ds_out_pre.variables for yvar in self.yvariable]): use_ds_out_pre_unmodified = True if use_ds_out_pre_unmodified: if ds_out_pre is not None: ds_out = ds_out_pre else: raise ValueError( "punpy.MeasurementFunction: ds_out_pre needs to be provided when use_ds_out_pre_unmodified is set to True." ) else: repeat_dim_err_corrs = self.utils._find_repeat_dim_corr( "tot", *args, store_unc_percent=store_unc_percent, ydims=self.ydims ) self.utils._set_repeat_dims_form(repeat_dim_err_corrs) template = self.templ.make_template_tot( self.ydims, self.sizes_dict, store_unc_percent=store_unc_percent, str_corr_dims=self.str_corr_dims, separate_corr_dims=self.separate_corr_dims, str_repeat_noncorr_dims=self.str_repeat_noncorr_dims, repeat_dim_err_corrs=repeat_dim_err_corrs, ) # create dataset template ds_out = obsarray.create_ds(template, self.sizes_dict) if self.output_vars == 1: u_tot_y = u_tot_y[None, ...] corr_tot_y = corr_tot_y[None, ...] for i in range(self.output_vars): ds_out[self.yvariable[i]].values = y[i] if store_unc_percent: ucomp = "u_rel_tot_" + self.yvariable[i] else: ucomp = "u_tot_" + self.yvariable[i] if u_tot_y is None: ds_out = self.templ.remove_unc_component( ds_out, self.yvariable[i], ucomp, err_corr_comp="err_corr_tot_" + self.yvariable[i], ) else: if store_unc_percent: ds_out[ucomp].values = u_tot_y[i] / np.abs(y[i]) * 100 else: ds_out[ucomp].values = u_tot_y[i] if include_corr: ds_out = self._store_corr( ds_out, corr_tot_y, "err_corr_tot_", i, use_ds_out_pre_unmodified, ) else: if len(self.str_corr_dims) == 1: ds_out.drop("err_corr_tot_" + self.yvariable[i]) else: for ii in range(len(self.str_corr_dims)): ds_out.drop( "err_corr_tot_" + self.yvariable[i] + "_" + self.str_corr_dims[ii] ) if (ds_out_pre is not None) and not use_ds_out_pre_unmodified: self.templ.join_with_preexisting_ds( ds_out, ds_out_pre, drop=self.yvariable[i] ) if self.prop.verbose: print( "finishing propagate_ds_total (%s s since creation of prop object)" % (time.time() - self.prop.starttime) ) return ds_out
[docs] def propagate_ds_specific( self, comp_list, *args, comp_list_out=None, store_unc_percent=False, expand=False, ds_out_pre=None, use_ds_out_pre_unmodified=None, include_corr=True, simple_random=True, simple_systematic=True, ): """ Function to propagate the uncertainties on the input quantities present in the digital effects tables provided as the input arguments, through the measurement function to produce an output digital effects table with the uncertainties of specific components listed in comp_list. :param comp_list: list of uncertainty contributions to propagate :rtype comp_list: list of strings or string :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param store_unc_percent: Boolean defining whether relative uncertainties should be returned or not. Default to True (relative uncertaintie returned) :type store_unc_percent: bool (optional) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :param ds_out_pre: Pre-existing output dataset in which the measurand & uncertainty variables should be saved. Defaults to None, in which case a new dataset is created. :type ds_out_pre: xarray.dataset (optional) :param include_corr: boolean to indicate whether the output dataset should include the correlation matrices. Defaults to True. :type include_corr: bool (optional) :return: digital effects table with uncertainties on measurand :rtype: obsarray dataset """ if self.prop.verbose: print( "starting propagate_ds_specific (%s s since creation of prop object)" % (time.time() - self.prop.starttime) ) if isinstance(comp_list, str): comp_list = [comp_list] if comp_list_out is None: comp_list_out = comp_list # first calculate the measurand and propagate the uncertainties y = self._check_sizes_and_run(*args, expand=expand, ds_out_pre=ds_out_pre) # check if the provided pre-build dataset should be used as is, or modified to fit the automatically detected error-correlations if use_ds_out_pre_unmodified is None and ds_out_pre is not None: if all([yvar in ds_out_pre.variables for yvar in self.yvariable]): use_ds_out_pre_unmodified = True if use_ds_out_pre_unmodified: if ds_out_pre is not None: ds_out = ds_out_pre else: raise ValueError( "punpy.MeasurementFunction: ds_out_pre needs to be provided when use_ds_out_pre_unmodified is set to True." ) else: repeat_dim_err_corrs = [ self.utils._find_repeat_dim_corr( form, *args, store_unc_percent=store_unc_percent, ydims=self.ydims ) for form in comp_list ] self.utils._set_repeat_dims_form(repeat_dim_err_corrs) template = self.templ.make_template_specific( comp_list_out, self.ydims, self.sizes_dict, store_unc_percent=store_unc_percent, str_corr_dims=self.str_corr_dims, separate_corr_dims=self.separate_corr_dims, str_repeat_noncorr_dims=self.str_repeat_noncorr_dims, repeat_dim_err_corrs=repeat_dim_err_corrs, simple_random=simple_random, simple_systematic=simple_systematic, ) # create dataset template ds_out = obsarray.create_ds(template, self.sizes_dict) for i in range(self.output_vars): ds_out[self.yvariable[i]].values = y[i] for icomp, comp in enumerate(comp_list): corr_comp_y = None if comp == "random" and simple_random: u_comp_y = self.propagate_random(*args, expand=expand) elif comp == "systematic" and simple_systematic: u_comp_y = self.propagate_systematic(*args, expand=expand) else: if include_corr: if self.output_vars == 1: u_comp_y, corr_comp_y = self.propagate_specific( comp, *args, return_corr=include_corr, expand=expand ) if corr_comp_y is not None: corr_comp_y = corr_comp_y[None, ...] else: ( u_comp_y, corr_comp_y, corr_comp_y_between, ) = self.propagate_specific( comp, *args, return_corr=include_corr, expand=expand ) else: u_comp_y = self.propagate_specific( comp, *args, return_corr=include_corr, expand=expand ) if self.output_vars == 1 and u_comp_y is not None: u_comp_y = u_comp_y[None, ...] for i in range(self.output_vars): if corr_comp_y is not None: try: ds_out = self._store_corr( ds_out, corr_comp_y, "err_corr_" + comp_list_out[icomp] + "_", i, use_ds_out_pre_unmodified, ) except: warnings.warn( "not able to set %s in the output dataset" % ( "err_corr_" + comp_list_out[icomp] + "_" + self.yvariable[i] ) ) else: try: ds_out.drop( "err_corr_" + comp_list_out[icomp] + "_" + self.yvariable[i] ) except: pass if u_comp_y is None: if store_unc_percent: ds_out = self.templ.remove_unc_component( ds_out, self.yvariable[i], "u_rel_" + comp_list_out[icomp] + "_" + self.yvariable[i], ) else: ds_out = self.templ.remove_unc_component( ds_out, self.yvariable[i], "u_" + comp_list_out[icomp] + "_" + self.yvariable[i], ) else: if store_unc_percent: ds_out[ "u_rel_" + comp_list_out[icomp] + "_" + self.yvariable[i] ].values = (u_comp_y[i] / np.abs(y[i]) * 100) else: ds_out[ "u_" + comp_list_out[icomp] + "_" + self.yvariable[i] ].values = u_comp_y[i] for i in range(self.output_vars): if (ds_out_pre is not None) and not use_ds_out_pre_unmodified: ds_out = self.templ.join_with_preexisting_ds( ds_out, ds_out_pre, drop=self.yvariable[i] ) if self.prop.verbose: print( "finishing propagate_ds_specific (%s s since creation of prop object)" % (time.time() - self.prop.starttime) ) return ds_out
[docs] def propagate_ds_all( self, *args, store_unc_percent=False, expand=False, ds_out_pre=None, use_ds_out_pre_unmodified=False, include_corr=True, ): """ Function to propagate the uncertainties on the input quantities present in the digital effects tables provided as the input arguments, through the measurement function to produce an output digital effects table with the combined random, systematic and structured uncertainties on the measurand :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param store_unc_percent: Boolean defining whether relative uncertainties should be returned or not. Default to True (relative uncertaintie returned) :type store_unc_percent: bool (optional) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :param ds_out_pre: Pre-existing output dataset in which the measurand & uncertainty variables should be saved. Defaults to None, in which case a new dataset is created. :type ds_out_pre: xarray.dataset (optional) :param include_corr: boolean to indicate whether the output dataset should include the correlation matrices. Defaults to True. :type include_corr: bool (optional) :return: digital effects table with uncertainties on measurand :rtype: obsarray dataset """ comp_list = [] for iv, var in enumerate(self.xvariables): for dataset in args: if var in dataset.keys(): comps = self.utils.find_comps("tot", dataset, var) if comps is not None: for comp in comps: if isinstance(comp, str): comp_name = comp else: comp_name = comp.name comp_name = comp_name.replace("_" + var, "") comp_name = comp_name.replace("u_rel_", "") if comp_name[0:2] == "u_": comp_name = comp_name[2::] comp_list.append(comp_name) comp_list = np.unique(np.array(comp_list)) return self.propagate_ds_specific( comp_list, *args, store_unc_percent=store_unc_percent, expand=expand, ds_out_pre=ds_out_pre, use_ds_out_pre_unmodified=use_ds_out_pre_unmodified, include_corr=include_corr, )
[docs] def run(self, *args, expand=False): """ Function to calculate the measurand by running input quantities through measurement function. :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :return: measurand :rtype: numpy.ndarray """ input_qty = self.utils.get_input_qty( args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims ) try: return np.array(self.meas_function(*input_qty)) except: return np.array(self.meas_function(*input_qty), dtype=object)
[docs] def propagate_total(self, *args, expand=False, return_corr=True): """ Function to propagate uncertainties for the total uncertainty component. :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :param return_corr: boolean to indicate whether the measurand error-correlation matrices should be returned. Defaults to True. :type return_corr: bool (optional) :return: uncertainty on measurand for total uncertainty component, error-correlation matrix of measurand for total uncertainty component :rtype: tuple(numpy.ndarray, numpy.ndarray) """ y = self._check_sizes_and_run(*args, expand=expand) input_qty = self.utils.get_input_qty( args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims ) input_unc = self.utils.get_input_unc( "tot", args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims, corr_dims=self.str_corr_dims, ) input_corr = self.utils.get_input_corr( "tot", args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims ) if self.prop.verbose: print( "inputs extracted (%s s since creation of prop object)" % (time.time() - self.prop.starttime) ) if all([iu is None for iu in input_unc]): return None, None else: if self.use_err_corr_dict: MC_x = self.prop.generate_MC_sample( input_qty, input_unc, corr_x=input_corr, corr_between=self.corr_between, comp_list=True, ) MC_y = self.prop.run_samples( self.meas_function, MC_x, output_vars=self.output_vars ) return self.prop.process_samples( MC_x, MC_y, return_corr=return_corr, corr_dims=self.num_corr_dims, separate_corr_dims=self.separate_corr_dims, output_vars=self.output_vars, ) else: return self.prop.propagate_standard( self.meas_function, input_qty, input_unc, input_corr, param_fixed=self.param_fixed, corr_between=self.corr_between, return_corr=return_corr, return_samples=False, repeat_dims=self.num_repeat_dims, corr_dims=self.num_corr_dims, separate_corr_dims=True, output_vars=self.output_vars, )
[docs] def propagate_random(self, *args, expand=False): """ Function to propagate uncertainties for the random uncertainty component. :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :return: uncertainty on measurand for random uncertainty component :rtype: numpy.ndarray """ y = self._check_sizes_and_run(*args, expand=expand) input_qty = self.utils.get_input_qty( args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims ) input_unc = self.utils.get_input_unc( # this should always be absolute "rand", args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims, corr_dims=self.str_corr_dims, ) if all([iu is None for iu in input_unc]): return None else: return self.prop.propagate_random( self.meas_function, input_qty, input_unc, param_fixed=self.param_fixed, corr_between=self.corr_between, return_corr=False, return_samples=False, repeat_dims=self.num_repeat_dims, corr_dims=self.num_corr_dims, separate_corr_dims=True, output_vars=self.output_vars, )
[docs] def propagate_systematic(self, *args, expand=False): """ Function to propagate uncertainties for the systemtic uncertainty component. :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :return: uncertainty on measurand for systematic uncertainty component :rtype: numpy.ndarray """ y = self._check_sizes_and_run(*args, expand=expand) input_qty = self.utils.get_input_qty( args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims ) input_unc = self.utils.get_input_unc( "syst", args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims, corr_dims=self.str_corr_dims, ) if all([iu is None for iu in input_unc]): return None else: return self.prop.propagate_systematic( self.meas_function, input_qty, input_unc, param_fixed=self.param_fixed, corr_between=self.corr_between, return_corr=False, return_samples=False, repeat_dims=self.num_repeat_dims, corr_dims=self.num_corr_dims, separate_corr_dims=True, output_vars=self.output_vars, )
[docs] def propagate_structured(self, *args, expand=False, return_corr=True): """ Function to propagate uncertainties for the structured uncertainty component. :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :param return_corr: boolean to indicate whether the measurand error-correlation matrices should be returned. Defaults to True. :type return_corr: bool (optional) :return: uncertainty on measurand for structured uncertainty component, error-correlation matrix of measurand for structured uncertainty component :rtype: tuple(numpy.ndarray, numpy.ndarray) """ y = self._check_sizes_and_run(*args, expand=expand) input_qty = self.utils.get_input_qty( args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims ) input_unc = self.utils.get_input_unc( "stru", args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims, corr_dims=self.str_corr_dims, ) input_corr = self.utils.get_input_corr( "stru", args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims ) if all([iu is None for iu in input_unc]): return None, None else: if self.use_err_corr_dict: MC_x = self.prop.generate_MC_sample( input_qty, input_unc, corr_x=input_corr, corr_between=self.corr_between, comp_list=True, ) MC_y = self.prop.run_samples( self.meas_function, MC_x, output_vars=self.output_vars ) return self.prop.process_samples( MC_x, MC_y, return_corr=return_corr, corr_dims=self.num_corr_dims, separate_corr_dims=True, output_vars=self.output_vars, ) else: return self.prop.propagate_standard( self.meas_function, input_qty, input_unc, input_corr, param_fixed=self.param_fixed, corr_between=self.corr_between, return_corr=return_corr, return_samples=False, repeat_dims=self.num_repeat_dims, corr_dims=self.num_corr_dims, separate_corr_dims=True, output_vars=self.output_vars, )
[docs] def propagate_specific(self, form, *args, expand=False, return_corr=False): """ Function to propagate uncertainties for a specific uncertainty component. :param form: name or type of uncertainty component :type form: str :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :param return_corr: boolean to indicate whether the measurand error-correlation matrices should be returned. Defaults to True. :type return_corr: bool (optional) :return: uncertainty on measurand for specific uncertainty component, error-correlation matrix of measurand for specific uncertainty component :rtype: tuple(numpy.ndarray, numpy.ndarray) """ y = self._check_sizes_and_run(*args, expand=expand) input_qty = self.utils.get_input_qty( args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims ) input_unc = self.utils.get_input_unc( form, args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims, corr_dims=self.str_corr_dims, ) input_corr = self.utils.get_input_corr( form, args, expand=expand, sizes_dict=self.sizes_dict, ydims=self.ydims ) if all([iu is None for iu in input_unc]) or self.prop.MCsteps == 0: if self.output_vars == 1: return None, None else: return None, None, None else: if self.use_err_corr_dict: MC_x = self.prop.generate_MC_sample( input_qty, input_unc, corr_x=input_corr, corr_between=self.corr_between, ) MC_y = self.prop.run_samples( self.meas_function, MC_x, output_vars=self.output_vars ) return self.prop.process_samples( MC_x, MC_y, return_corr=return_corr, corr_dims=self.num_corr_dims, separate_corr_dims=True, output_vars=self.output_vars, ) else: return self.prop.propagate_standard( self.meas_function, input_qty, input_unc, input_corr, param_fixed=self.param_fixed, corr_between=self.corr_between, return_corr=return_corr, return_samples=False, repeat_dims=self.num_repeat_dims, corr_dims=self.num_corr_dims, separate_corr_dims=True, output_vars=self.output_vars, allow_some_nans=self.allow_some_nans, )
def _store_corr( self, ds_out, corr_y, err_corr_string, i, use_ds_out_pre_unmodified ): """ function to check if err_corr should be saved separately per dimension and then save in right format :param ds_out: output dataset :param corr_y: correlation matrix :param err_corr_string: string to start error correlation variable with :param i: index of the measurand :return: output dataset (with error correlation) """ try: if corr_y[i] is None or corr_y[i][0] is None: return ds_out elif len(self.str_corr_dims[i]) == 0: ds_out[err_corr_string + self.yvariable[i]].values = corr_y[i] elif len(self.str_corr_dims[i]) == 1: try: ds_out[ err_corr_string + self.yvariable[i] + "_" + self.str_corr_dims[i][0] ].values = corr_y[i] except: ds_out[err_corr_string + self.yvariable[i]].values = corr_y[i] else: for j, corr_dim in enumerate(self.str_corr_dims[i]): if isinstance(corr_dim, str): if len(self.str_corr_dims[i]) == 1: ds_out[ err_corr_string + self.yvariable[i] + "_" + corr_dim ].values = corr_y[i] else: ds_out[ err_corr_string + self.yvariable[i] + "_" + corr_dim ].values = corr_y[i][j] elif corr_dim is None or corr_dim[0] is None: continue else: if len(corr_dim) == 1: corr_dim = corr_dim[0] for jj in range(len(corr_dim)): ds_out[ err_corr_string + self.yvariable[i] + "_" + corr_dim[j] ].values = corr_y[i][jj] except: if use_ds_out_pre_unmodified: valid_keys = [ key for key in ds_out.variables if ((err_corr_string in key) and (self.yvariable[i] in key)) ] if len(valid_keys) == 1: ds_out[valid_keys[0]].values = corr_y[i] else: raise ValueError( "punpy.MeasurementFunction: when storing error correlation of form %s for variable %s, the error correlation was not found in the provided ds_out_pre. Either set use_ds_out_pre_unmodified to False or adapt your ds_out_pre." % (err_corr_string, self.yvariable[i]) ) else: raise ValueError( "punpy.MeasurementFunction: not able to store error correlation of form %s for variable %s" % (err_corr_string, self.yvariable[i]) ) return ds_out def _check_sizes_and_run(self, *args, expand=False, ds_out_pre=None): """ Function to check the sizes of the input quantities and measurand and perform some checks and preprocessing :param args: One or multiple digital effects tables with input quantities, defined with obsarray :type args: obsarray dataset(s) :param expand: boolean to indicate whether the input quantities should be expanded/broadcasted to the shape of the measurand. Defaults to False. :type expand: bool (optional) :return: None :rtype: None """ # first check and set measurand dimensions if self.ydims is None: self.ydims = np.empty(self.output_vars, dtype=object) for i in range(self.output_vars): if ds_out_pre is not None: self.ydims[i] = ds_out_pre[self.yvariable[i]].dims else: for dataset in args: try: self.ydims[i] = dataset[self.refxvar].dims except: continue if self.output_vars > 1 and ( not all(self.ydims[0] == ydimsi for ydimsi in self.ydims) ): if self.prop.parallel_cores == 0: raise ValueError( "punpy.MeasurementFunction: When using a measurement function with multiple measurands with different shapes, you cannot set parallel_cores to 0 (the default) when creating the prop object." ) # define dictionary with dimension sizes (needs to be done before self.run() when expand==True) if self.sizes_dict is None and expand: self.sizes_dict = {} for i in range(self.output_vars): if ds_out_pre is not None: for idim, dim in enumerate(self.ydims[i]): self.sizes_dict[dim] = ds_out_pre[ self.yvariable[i] ].values.shape[idim] else: for dataset in args: try: for idim, dim in enumerate(self.ydims[i]): self.sizes_dict[dim] = dataset[ self.refxvar ].values.shape[idim] except: continue # run the measurement function y = self.run(*args, expand=expand) if self.output_vars == 1: y = y[None, ...] # define dictionary with dimension sizes if self.sizes_dict is None: self.sizes_dict = {} for i in range(self.output_vars): if ds_out_pre is not None: for idim, dim in enumerate(self.ydims[i]): self.sizes_dict[dim] = ds_out_pre[ self.yvariable[i] ].values.shape[idim] else: for idim, dim in enumerate(self.ydims[i]): try: self.sizes_dict[dim] = y[i].shape[idim] except: raise ValueError() # check repeat dims and convert to num and str versions self._check_and_convert_repeat_dims() # add repeat dims to str_repeat_noncorr_dims (these dimensions will automatically be copied from input quantities, rather than calculated, important when later making template) str_repeat_noncorr_dims = [ str_dim for str_dim in self.str_repeat_dims if str_dim != "" ] # check if we need to automatically separate the corr_dims (because the corr_dim is not present for one of measurands) if not self.separate_corr_dims: self._check_corr_dims_present_in_all_dims() # check corr dims and convert to num and str versions all_corr_dims = [] for i in range(self.output_vars): ( self.num_corr_dims[i], self.str_corr_dims[i], all_corr_dims_i, ) = self._check_and_convert_corr_dims(self.corr_dims[i], self.ydims[i]) [all_corr_dims.append(all_corr_dim) for all_corr_dim in all_corr_dims_i] # add dimensions not in corr_dims to str_repeat_noncorr_dims (these dimensions will automatically be copied from input quantities, rather than calculated, important when later making template) for i in range(self.output_vars): for idim, dim in enumerate(self.ydims[i]): if dim not in all_corr_dims and dim not in str_repeat_noncorr_dims: str_repeat_noncorr_dims.append(dim) self.str_repeat_noncorr_dims = np.array(str_repeat_noncorr_dims).flatten() # add error-correlation dimensions to sizes dict for i in range(self.output_vars): # set sizes dict for combined shape of error correlation dict key = "" val = 1 for idim, dim in enumerate(self.ydims[i]): if dim not in self.str_repeat_noncorr_dims: key += "." + dim val *= self.sizes_dict[dim] self.sizes_dict[key[1::]] = val # try to automatically detect if param_fixed should be set (when using repeat dims) if ( (not expand) and (self.param_fixed is None) and (self.num_repeat_dims[0] >= 0) ): self.param_fixed = [False] * len(self.xvariables) for iv, var in enumerate(self.xvariables): found = False for dataset in args: if hasattr(dataset, "variables"): if var in dataset.variables: if all( [ self.str_repeat_dims[i] in dataset[var].dims for i in range(len(self.str_repeat_dims)) ] ): found = True if not found: self.param_fixed[iv] = True if self.prop.verbose: print( "Variable %s not found in repeat_dims. setting param_fixed to True" % (var) ) # set utils attributes (to be used when creating template) self.utils.ydims = self.ydims self.utils.str_repeat_noncorr_dims = self.str_repeat_noncorr_dims self.utils.str_repeat_dims = self.str_repeat_dims return y def _check_corr_dims_present_in_all_dims(self): """ Function to check whether the corr dims are present in all the measurand dimensions :return: """ for i in range(self.output_vars): for idimc in range(len(self.corr_dims[i])): if isinstance(self.corr_dims[i][idimc], str): if "." in self.corr_dims[i][idimc]: corr_dims_split = self.corr_dims[i][idimc].split(".") if corr_dims_split[0].isdigit(): corr_dims_split = [ self.ydims[i][int(idim)] for idim in corr_dims_split ] if not all( [ corr_dims_split_j in self.ydims[i] for corr_dims_split_j in corr_dims_split ] ): self.separate_corr_dims = True self.corr_dims[i][idimc] = None elif self.corr_dims[i][idimc] not in self.ydims[i]: self.separate_corr_dims = True self.corr_dims[i][idimc] = None if self.separate_corr_dims: warnings.warn( "punpy.measurement_function: The provided corr_dims were not present in the dimensions of all measurands. The separate_corr_dims attribute has been set to True, and the corr_dims have been automatically adjusted." ) self._check_and_set_corr_dims(self.corr_dims, self.separate_corr_dims) def _check_and_set_corr_dims(self, corr_dims, separate_corr_dims): """ Function to check the shapes of the provided corr_dims and automatically adjust them to be a list for each measurand. Also converts to num and str forms and stores attributes. :param corr_dims: set to positive integer to select the axis used in the correlation matrix. The correlation matrix will then be averaged over other dimensions. Defaults to -99, for which the input array will be flattened and the full correlation matrix calculated. :param separate_corr_dims: When set to True and output_vars>1, corr_dims should be a list providing the corr_dims for each output variable, each following the format defined in the corr_dims description. Defaults to False :return: """ self.corr_dims = np.empty(self.output_vars, dtype=object) if separate_corr_dims and ( isinstance(corr_dims, int) or len(corr_dims) != self.output_vars ): raise ValueError( "The provided corr_dims was not a list with the corr_dims for each output variable. This needs to be the case when setting separate_corr_dims to True" ) for i in range(self.output_vars): if separate_corr_dims: corr_dim_i = corr_dims[i] else: corr_dim_i = corr_dims if corr_dim_i is None: self.corr_dims[i] = [-99] elif isinstance(corr_dim_i, str) or isinstance(corr_dim_i, int): self.corr_dims[i] = copy.copy([corr_dim_i]) else: self.corr_dims[i] = copy.copy(corr_dim_i) self.corr_dims = np.array(self.corr_dims, dtype=object) self.num_corr_dims = np.empty_like(self.corr_dims, dtype=object) self.str_corr_dims = np.empty_like(self.corr_dims, dtype=object) self.separate_corr_dims = separate_corr_dims def _check_and_convert_repeat_dims(self): """ Function to check and convert repeat dims (repeat dims need to be present and the same for each measurand) Also converts to num and str forms and stores attributes. :return: """ for idimr in range(len(self.repeat_dims)): if isinstance(self.repeat_dims[idimr], str): ( self.str_repeat_dims[idimr], self.num_repeat_dims[idimr], ) = self._check_and_convert_str_dims(self.repeat_dims[idimr]) elif isinstance(self.repeat_dims[idimr], (int, np.integer)): if self.repeat_dims[idimr] >= 0: ( self.str_repeat_dims[idimr], self.num_repeat_dims[idimr], ) = self._check_and_convert_num_dims(self.repeat_dims[idimr]) else: self.num_repeat_dims[idimr] = self.repeat_dims[idimr] else: raise ValueError( "punpy.measurment_function: repeat_dims needs to be provided as ints or strings" ) def _check_and_convert_corr_dims(self, corr_dims, ydims): """ Function to check and convert corr dims. Also converts to num and str forms and stores attributes. :param corr_dims: set to positive integer to select the axis used in the correlation matrix. The correlation matrix will then be averaged over other dimensions. Defaults to -99, for which the input array will be flattened and the full correlation matrix calculated. :param ydims: list of dimensions of the measurand, in correct order. list of list of dimensions when there are multiple measurands. Default to None, in which case it is assumed to be the same as refxvar (see below) input quantity. :return: """ all_corr_dims = [] num_corr_dims = np.empty_like(corr_dims) str_corr_dims = np.empty_like(corr_dims, dtype=object) for idimc in range(len(corr_dims)): if corr_dims[idimc] is None: num_corr_dims[idimc] = None str_corr_dims[idimc] = None elif isinstance(corr_dims[idimc], str): if "." in corr_dims[idimc]: corr_dims_split = corr_dims[idimc].split(".") str_corr_dims_split = np.empty_like(corr_dims_split) num_corr_dims_split = np.empty_like(corr_dims_split, dtype=int) if corr_dims_split[0].isdigit(): corrlen = 1 for ic in range(len(corr_dims)): if self.separate_corr_dims: ( str_corr_dims_split[ic], num_corr_dims_split[ic], ) = self._check_and_convert_num_corr_dims( int(corr_dims_split[ic]), ydims ) else: ( str_corr_dims_split[ic], num_corr_dims_split[ic], ) = self._check_and_convert_num_dims( int(corr_dims_split[ic]) ) all_corr_dims.append(str_corr_dims_split[ic]) corrlen *= self.sizes_dict[str_corr_dims_split[ic]] num_corr_dims[idimc] = copy.copy(corr_dims[idimc]) str_corr_dims[idimc] = ".".join(str_corr_dims_split) self.sizes_dict[str_corr_dims[idimc]] = corrlen else: corrlen = 1 for ic in range(len(corr_dims_split)): if self.separate_corr_dims: ( str_corr_dims_split[ic], num_corr_dims_split[ic], ) = self._check_and_convert_str_corr_dims( corr_dims_split[ic], ydims ) else: ( str_corr_dims_split[ic], num_corr_dims_split[ic], ) = self._check_and_convert_str_dims( corr_dims_split[ic] ) all_corr_dims.append(str_corr_dims_split[ic]) corrlen *= self.sizes_dict[str_corr_dims_split[ic]] str_corr_dims[idimc] = copy.copy(corr_dims[idimc]) num_corr_dims[idimc] = ".".join( [str(cdim) for cdim in num_corr_dims_split] ) self.sizes_dict[str_corr_dims[idimc]] = corrlen elif not corr_dims[idimc] in ydims: raise ValueError( "punpy.measurement_function: The corr_dim (%s) is not in the measurand dimensions (%s)." % (corr_dims[idimc], ydims) ) else: ( str_corr_dims[idimc], num_corr_dims[idimc], ) = self._check_and_convert_str_corr_dims(corr_dims[idimc], ydims) all_corr_dims.append(corr_dims[idimc]) elif isinstance(corr_dims[idimc], (int, np.integer)): if corr_dims[idimc] >= 0: ( str_corr_dims[idimc], num_corr_dims[idimc], ) = self._check_and_convert_num_corr_dims(corr_dims[idimc], ydims) all_corr_dims.append(ydims[corr_dims[idimc]]) else: num_corr_dims[idimc] = corr_dims[idimc] str_corr_dims = [] for ydim in ydims: if isinstance(ydim, str): all_corr_dims.append(ydim) else: for ydi in ydim: all_corr_dims.append(ydi) else: raise ValueError( "punpy.measurment_function: corr_dims needs to be provided as ints or strings" ) return num_corr_dims, str_corr_dims, all_corr_dims def _check_and_convert_str_dims(self, dim): """ Function to convert from a dimension string to string and dimension index :param dim: dimension string :return: str dim, num dim """ for i in range(self.output_vars): if not dim in self.ydims[i]: raise ValueError( "punpy.measurement_function: The repeat_dim or corr_dim (%s) is not in the measurand dimensions for each measurand (%s)." % (dim, self.ydims) ) if not np.all( [ydims.index(dim) == self.ydims[0].index(dim) for ydims in self.ydims] ): warnings.warn( "punpy.measurement_function: The repeat_dim or corr_dim (%s) cannot be used because it is not at the same index for every ydims of every measurand (%s)." % (dim, self.ydims) ) return copy.copy(dim), copy.copy(self.ydims[0].index(dim)) def _check_and_convert_num_dims(self, dim): """ Function to convert from a number dimension (index) to dimension string and index :param dim: dimension index :return: str dim, num dim """ if dim >= 0: if not np.all([ydims[dim] == self.ydims[0][dim] for ydims in self.ydims]): warnings.warn( "punpy.measurement_function: The repeat_dim or corr_dim (%s) cannot be used because it is not at the same index for every ydims of every measurand (%s)." % (dim, self.ydims) ) return copy.copy(self.ydims[0][dim]), dim def _check_and_convert_str_corr_dims(self, dim, ydims): """ Function to convert from a dimension string to string and dimension index for correlation dimension (different measurand dimension used for each measurand) :param dim: dimension string :param ydims: list of dimensions of the measurand, in correct order. list of list of dimensions when there are multiple measurands. Default to None, in which case it is assumed to be the same as refxvar (see below) input quantity. :return: str dim, num dim """ if not dim in ydims: raise ValueError( "punpy.measurement_function: The corr_dim (%s) is not in the measurand dimensions (%s)." % (dim, ydims) ) return copy.copy(dim), copy.copy(ydims.index(dim)) def _check_and_convert_num_corr_dims(self, dim, ydims): """ Function to convert from a dimension index to dimension string and index for correlation dimension (different measurand dimension used for each measurand) :param dim: dimension index :param ydims: list of dimensions of the measurand, in correct order. list of list of dimensions when there are multiple measurands. Default to None, in which case it is assumed to be the same as refxvar (see below) input quantity. :return: str dim, num dim """ return copy.copy(ydims[dim]), dim