diff --git a/ostap/fitting/dataset.py b/ostap/fitting/dataset.py index b4d2d8fe..5c8f659b 100644 --- a/ostap/fitting/dataset.py +++ b/ostap/fitting/dataset.py @@ -2657,7 +2657,6 @@ def _rda_slice_ ( dataset , variables , cuts = '' , transpose = False , cut_rang names = strings ( names ) - tab = Ostap.StatVar.Table () col = Ostap.StatVar.Column () @@ -2671,7 +2670,7 @@ def _rda_slice_ ( dataset , variables , cuts = '' , transpose = False , cut_rang *args ) nc = len ( col ) assert ( dataset.isWeighted() and nc == n ) or ( 0 == nc and not dataset.isWeighted() ), \ - 'slide: invalid size of ``weights'' column! %s/%s/%s' % ( n , nc , dataset.isWeighted() ) + "slice: invalid size of `weights' column! %s/%s/%s" % ( n , nc , dataset.isWeighted() ) if 0 == n : return () , () @@ -2938,7 +2937,6 @@ def ds_equal ( ds1 , ds2 ) : logger.debug ("compare datasets: unbinned vs binned") return False - ## both weighted or non-weighted? w1 = ds1.isWeighted () w2 = ds2.isWeighted () @@ -3004,7 +3002,7 @@ def ds_equal ( ds1 , ds2 ) : return True # ============================================================================ -## Are two datastes non-equal by content? +## Are two datasets non-equal by content? # @code # ds1 = ... # ds2 = ... @@ -3072,12 +3070,9 @@ def _rad_rows_ ( dataset , variables = [] , cuts = '' , cutrange = '' , first = first, last = evt_range ( len ( dataset ) , first , last ) - if isinstance ( variables , string_types ) : - variables = split_string ( variables , var_separators , strip = True , respect_groups = True ) - vars = [] - for v in variables : - vars += split_string ( v , var_separators , strip = True , respect_groups = True ) - vars = strings ( vars ) + varlst, cuts, _ = vars_and_cuts ( variables , cuts ) + + vars = strings ( varlst ) formulas = [] varlist = dataset.varlist () @@ -3088,31 +3083,34 @@ def _rad_rows_ ( dataset , variables = [] , cuts = '' , cutrange = '' , first = fcuts = None if cuts : fcuts = make_formula ( cuts , cuts , varlist ) - weighted = dataset.isWeighted() - + weighted = dataset.isWeighted () + store_errors = weighted and daatset.store_errors () + store_asym_errors = weighted and daatset.store_asym_errors () + simple_weight = weighted and ( not srore_errors ) and ( not store_asym_errors ) ## loop over dataset for event in range ( first , last ) : + + ww = None + if weighted : vars, ww = dataset [ event ] + else : vars = dataset [ event ] - vars = dataset.get ( event ) if not vars : break if cutrange and not vars.allInRange ( cutrange ) : continue wc = fcuts.getVal() if fcuts else 1.0 - if not wc : continue - wd = dataset.weight() if weighted else 1.0 + wd = ww if weighted and not ( ww is None ) else 1.0 w = wc * wd - if not w : continue + if simple_weight and not w : continue weight = w if weighted else None - result = tuple ( tuple ( float(f) for f in formulas ) ) + result = tuple ( tuple ( float ( f ) for f in formulas ) ) yield get_result ( result ) , weight - del fcuts del formulas diff --git a/ostap/math/tests/test_math_valerrors.py b/ostap/math/tests/test_math_valerrors.py index e36eafd1..753e9293 100644 --- a/ostap/math/tests/test_math_valerrors.py +++ b/ostap/math/tests/test_math_valerrors.py @@ -51,6 +51,11 @@ def test_asymerr () : assert ae1_ == ae3 , ' %s != %s ' % ( ae1_ , ae3 ) assert ae2_ == ae3 , ' %s != %s ' % ( ae2_ , ae3 ) + logger.info ( 'Scale /1 : %s %s %s' % ( ae1/1 , ae2/1 , ae3/1 ) ) + logger.info ( 'Scale *1 : %s %s %s' % ( ae1*1 , ae2*1 , ae3*1 ) ) + logger.info ( 'Scale /2 : %s %s %s' % ( ae1/2 , ae2/2 , ae3/2 ) ) + logger.info ( 'Scale *2 : %s %s %s' % ( ae1*2 , ae2*2 , ae3*2 ) ) + logger.info ( 'Scale 2* : %s %s %s' % ( 2*ae1 , 2*ae2 , 2*ae3 ) ) # ============================================================================= ## test asymmetric errors: @@ -59,9 +64,9 @@ def test_valasymerr () : logger = getLogger( 'test_valasymerr') logger.info ( 'Test ValWithErrors' ) - ae1 = VAE ( 1.0 , -0.5 , 1.0 ) - ae2 = VAE ( VE ( 1.0 , 0.0 ) , -0.5 , 1.0 ) - ae3 = VAE ( VE ( 1.0 , 0.0 ) , AE ( -0.5 , 1.0 ) ) + ae1 = VAE ( 10.0 , -0.5 , 1.0 ) + ae2 = VAE ( VE ( 10.0 , 0.0 ) , -0.5 , 1.0 ) + ae3 = VAE ( VE ( 10.0 , 0.0 ) , AE ( -0.5 , 1.0 ) ) logger.info ( '%s %s %s' % ( ae1 , ae2 , ae3 ) ) @@ -76,12 +81,19 @@ def test_valasymerr () : assert ae1_ == ae2 , ' %s != %s ' % ( ae1_ , ae2 ) assert ae1_ == ae3 , ' %s != %s ' % ( ae1_ , ae3 ) assert ae2_ == ae3 , ' %s != %s ' % ( ae2_ , ae3 ) + + logger.info ( 'Scale /1 : %s %s %s' % ( ae1/1 , ae2/1 , ae3/1 ) ) + logger.info ( 'Scale *! : %s %s %s' % ( ae1*1 , ae2*1 , ae3*1 ) ) + logger.info ( 'Scale /2 : %s %s %s' % ( ae1/2 , ae2/2 , ae3/2 ) ) + logger.info ( 'Scale *2 : %s %s %s' % ( ae1*2 , ae2*2 , ae3*2 ) ) + logger.info ( 'Scale 2* : %s %s %s' % ( 2*ae1 , 2*ae2 , 2*ae3 ) ) ## conversion to VE without bias ve1 = ae1.asVE ( bias = False ) ve2 = ae2.asVE ( bias = False ) ve3 = ae3.asVE ( bias = False ) + logger.info ( '%s %s %s' % ( ae1 , ae2 , ae3 ) ) logger.info ( 'asVE ( bias = False ) : %s %s %s' % ( ve1.toString ( '( %+-.3f +/- %-.3f )' ) , ve2.toString ( '( %+-.3f +/- %-.3f )' ) , ve3.toString ( '( %+-.3f +/- %-.3f )' ) ) ) @@ -102,9 +114,9 @@ def test_valmulterr () : logger = getLogger( 'test_valmulterr') logger.info ( 'Test ValWithMultiErrors' ) - me1 = VME ( 1.0 , 0.5 , 0.5 , -0.6 , 1.0 , 0.7 , -0.3 ) - me2 = VME ( 1.0 , AE( 0.5 , 0.5 ) , AE ( -0.6 , 1.0 ) , AE ( 0.7 , -0.3 ) ) - me3 = VME ( VE ( 1 , 0.5 **2 ) , AE ( -0.6 , 1.0 ) , AE ( 0.7 , -0.3 ) ) + me1 = VME ( 10.0 , 0.5 , 0.5 , -0.6 , 1.0 , 0.7 , -0.3 ) + me2 = VME ( 10.0 , AE( 0.5 , 0.5 ) , AE ( -0.6 , 1.0 ) , AE ( 0.7 , -0.3 ) ) + me3 = VME ( VE ( 10 , 0.5 **2 ) , AE ( -0.6 , 1.0 ) , AE ( 0.7 , -0.3 ) ) ae1 = me1.asVAE() @@ -122,10 +134,17 @@ def test_valmulterr () : assert me1_ == me3 , ' %s != %s ' % ( me1_ , me3 ) assert me2_ == me3 , ' %s != %s ' % ( me2_ , me3 ) + logger.info ( 'Scale /1 : %s %s %s' % ( me1/1 , me2/1 , me3/1 ) ) + logger.info ( 'Scale *1 : %s %s %s' % ( me1*1 , me2*1 , me3*1 ) ) + logger.info ( 'Scale /2 : %s %s %s' % ( me1/2 , me2/2 , me3/2 ) ) + logger.info ( 'Scale *2 : %s %s %s' % ( me1*2 , me2*2 , me3*2 ) ) + logger.info ( 'Scale 2* : %s %s %s' % ( 2*me1 , 2*me2 , 2*me3 ) ) + ae1 = me1.asVAE() ae2 = me2.asVAE() ae3 = me3.asVAE() + logger.info ( '%s %s %s' % ( me1 , me2 , me3 ) ) logger.info ( 'as VAE : %s %s %s' % ( ae1 , ae2 , ae3 ) ) ## conversion to VE without bias diff --git a/ostap/utils/valerrors.py b/ostap/utils/valerrors.py index b65749cf..69738ddd 100644 --- a/ostap/utils/valerrors.py +++ b/ostap/utils/valerrors.py @@ -29,7 +29,7 @@ from ostap.core.ostap_types import num_types, sized_types, sequence_types from ostap.core.core import VE from ostap.math.base import iszero, isequal -import math +import math, copy # ============================================================================= # logging # ============================================================================= @@ -91,6 +91,12 @@ def positive ( self ) : """'positive' : get the positive (high) error""" return self.__positive + # ========================================================================= + ## make a copy + def __copy__ ( self ) : + """Make a copy""" + return AsymErrors ( negative = self.__negative , positive = self.__positive ) + # ========================================================================= ## An `effective error' (as split normal distribution) # @see https://en.wikipedia.org/wiki/Split_normal_distribution @@ -137,16 +143,54 @@ def __iadd__ ( self , other ) : elif pos : self.__positive = pos ## return self - # ========================================================================= ## Quadratic sum of two AsymErrors object def __add__ ( self , other ) : """Quadratic sum of two AsymErrors object""" if not isinstance ( other , AsymErrors ) : return NotImplemented - result = AsymErrors ( self.__negative , slef.__positive ) - result += other - return result + r = copy.copy ( self ) + r += other + return r + # ========================================================================= + ## self-scaling + def __imul__ ( self , other ) : + """Self-scaling""" + if not isinstance ( other , num_types ) : return NotImplemented + self.__negative *= other + self.__positive *= other + return self + # ========================================================================= + ## self-scaling + def __idiv__ ( self , other ) : + """Self-scaling""" + if not isinstance ( other , num_types ) : return NotImplemented + self.__negative /= other + self.__positive /= other + return self + # ========================================================================== + ## Self-scaling + __itruediv__ = __idiv__ + # ========================================================================== + ## Multiplication/scaling + def __mul__ ( self , other ) : + """Multiplication/scaling""" + if not isinstance ( other , num_types ) : return NotImplemented + r = copy.copy ( self ) + r *= other + return r + # ========================================================================== + ## division/scaling + def __div__ ( self , other ) : + """Division/scaling""" + if not isinstance ( other , num_types ) : return NotImplemented + r = copy.copy ( self ) + r /= other + return r + # ========================================================================== + ## right multuiplication + __rmul__ = __mul__ + __truediv__ = __div__ # ========================================================================= ## equality def __eq__ ( self , other ) : @@ -159,11 +203,9 @@ def __eq__ ( self , other ) : def __ne__ ( self , other ) : if not isinstance ( other , AsymErrors ) : return NotImplememnted return not ( self == other ) - # ========================================================================= ## tuple-like def __len__ ( self ) : return 2 - # ========================================================================= ## get the error by index # 0 : negative error @@ -291,6 +333,82 @@ def pos_error ( self ) : """'pos_error' : the positive (high) error""" return self.__errors.positive + # ========================================================================= + ## make a copy + def __copy__ ( self ) : + """Make a copy""" + return ValWithErrors ( self ) + + # ========================================================================= + ## self-scaling + # @code + # vae = ... + # vae *= 5 + # @endcode + def __imul__ ( self , other ) : + """Self-scaling + >>> vae = ... + >>> vae *= 5 + """ + if not isinstance ( other , num_types ) : return NotImplemented + self.__value *= other + self.__errors *= other + return self + # ========================================================================= + ## self-scaling + # @code + # vae = ... + # vae /= 5 + # @endcode + def __idiv__ ( self , other ) : + """Self-scaling + >>> vae = ... + >>> vae /= 5 + """ + if not isinstance ( other , num_types ) : return NotImplemented + self.__value /= other + self.__errors /= other + return self + # ========================================================================= + ## Self-scaling + __itruediv__ = __idiv__ + # ========================================================================== + ## Multiplication/scaling + # @code + # vae = ... + # vae * 5 + # 5 * vae + # @endcode + def __mul__ ( self , other ) : + """Multiplication/scaling + >>> vae = ... + >>> vae * 5 + >>> 5 * vae + """ + if not isinstance ( other , num_types ) : return NotImplemented + r = copy.copy ( self ) + r *= other + return r + # ========================================================================== + ## Division/scaling + # @code + # vae = ... + # vae / 5 + # @endcode + def __div__ ( self , other ) : + """Division/scaling + >>> vae = ... + >>> vae * 5 + >>> 5 * vae + """ + if not isinstance ( other , num_types ) : return NotImplemented + r = copy.copy ( self ) + r /= other + return r + # ========================================================================== + __rmul__ = __mul__ + __truediv__ = __div__ + # ======================================================================== ## conversion to float def __float__ ( self ) : @@ -448,6 +566,82 @@ def nerrors ( self ) : """'nerrors' : numer of associated asymmetrical errors (pairs)""" return len ( self.__errors ) + # ========================================================================= + ## make a copy + def __copy__ ( self ) : + """Make a copy""" + return ValWithMultiErrors ( self ) + + # ========================================================================= + ## self-scaling + # @code + # vae = ... + # vae *= 5 + # @endcode + def __imul__ ( self , other ) : + """Self-scaling + >>> vae = ... + >>> vae *= 5 + """ + if not isinstance ( other , num_types ) : return NotImplemented + self.__value *= other + self.__errors = tuple ( e*other for e in self.errors ) + return self + # ========================================================================= + ## self-scaling + # @code + # vae = ... + # vae /= 5 + # @endcode + def __idiv__ ( self , other ) : + """Self-scaling + >>> vae = ... + >>> vae /= 5 + """ + if not isinstance ( other , num_types ) : return NotImplemented + self.__value /= other + self.__errors = tuple ( e/other for e in self.errors ) + return self + # ========================================================================= + ## Self-scaling + __itruediv__ = __idiv__ + # ========================================================================== + ## Multiplication/scaling + # @code + # vae = ... + # vae * 5 + # 5 * vae + # @endcode + def __mul__ ( self , other ) : + """Multiplication/scaling + >>> vae = ... + >>> vae * 5 + >>> 5 * vae + """ + if not isinstance ( other , num_types ) : return NotImplemented + r = copy.copy ( self ) + r *= other + return r + # ========================================================================== + ## Division/scaling + # @code + # vae = ... + # vae / 5 + # @endcode + def __div__ ( self , other ) : + """Division/scaling + >>> vae = ... + >>> vae * 5 + >>> 5 * vae + """ + if not isinstance ( other , num_types ) : return NotImplemented + r = copy.copy ( self ) + r /= other + return r + # ========================================================================== + __rmul__ = __mul__ + __truediv__ = __div__ + # ========================================================================== ## conversion to float def __float__ ( self ) : """conversion to float""" @@ -469,7 +663,6 @@ def pos_error ( self ) : """'pos_error' : the effective (sum squared) positive (high) error""" return math.sqrt ( sum ( e.positive**2 for e in self.__errors ) ) - # ======================================================================== ## (numerical) equality def __eq__ ( self , other ) :