diff --git a/Analysis/Ostap/doc/release.notes b/Analysis/Ostap/doc/release.notes index d89e22b5df5dbcdd095ff6b780c8dfe1818431d9..70d0a0dc8eaa18165e644f603e8b027338e8e61c 100644 --- a/Analysis/Ostap/doc/release.notes +++ b/Analysis/Ostap/doc/release.notes @@ -8,6 +8,13 @@ ! by $Author$ ! ----------------------------------------------------------------------------- +! 2017-01-16 - Vanya Belyaev + - GraphDeco: + 1. couple of minor fixes + 2. allow graph[-1] + 3. add new function fill_area to represent the area between two functions/curves + + ! 2016-12-14 - Vanya Belyaev - fix tests diff --git a/Analysis/Ostap/python/Ostap/Core.py b/Analysis/Ostap/python/Ostap/Core.py index fa4a26b1ab0c7bab52ced94179aca6805da2552e..50c63eb0506da59704b13a62421a448d7c6ec98b 100644 --- a/Analysis/Ostap/python/Ostap/Core.py +++ b/Analysis/Ostap/python/Ostap/Core.py @@ -9,9 +9,6 @@ # @author Vanya BELYAEV Ivan.Belyaev@itep.ru # @date 2011-06-07 # -# $Revision$ -# Last modification $Date$ -# by $Author$ # ============================================================================= """Core objects for Ostap """ diff --git a/Analysis/Ostap/python/Ostap/GraphDeco.py b/Analysis/Ostap/python/Ostap/GraphDeco.py index c8b5b1c63b5acd7e291831799968c64f9e7ee31b..9dbf988ed970ac7797c2bd778f0d616879f53c3f 100644 --- a/Analysis/Ostap/python/Ostap/GraphDeco.py +++ b/Analysis/Ostap/python/Ostap/GraphDeco.py @@ -9,9 +9,6 @@ # @author Vanya BELYAEV Ivan.Belyaev@itep.ru # @date 2011-06-07 # -# $Revision$ -# Last modification $Date$ -# by $Author$ # ============================================================================= """TGraph-related decorations""" # ============================================================================= @@ -28,6 +25,7 @@ __all__ = ( 'hToGraph2' , ## convert histogram to graph 'hToGraph3' , ## convert histogram to graph 'lw_graph' , ## make Laffery-Wyatt's graph + 'fill_area' , ## helper function to show an area between two curves/functions ## ) # ============================================================================= @@ -660,13 +658,13 @@ ROOT.TH1D.toGraph3 = hToGraph3 # @see TGraph::Eval # @author Vanya BELYAEV Ivan.Belyaev@itep.ru # @date 2011-06-07 -def _gr_call_ ( graph , x , spline = None , opts = 'S' ) : +def _gr_call_ ( graph , x , *args ) : """ Use graph as function >>> graph = ... - >>> y = graph ( 0.2 ) + >>> y = graph ( 0.2 ) + - see ROOT.TGraph.Eval """ - if not spline : spline = ROOT.MakeNullPointer(ROOT.TSpline) - return graph.Eval ( float( x ) , spline , opts ) + return graph.Eval ( float( x ) , *args ) # ============================================================================= ## Calculate an integral over the range \f$x_{low} \le x \le x_{high}\f$ @@ -695,11 +693,9 @@ def _gr_integral_ ( graph , xlow , xhigh , scipy = True ) : # @author Vanya BELYAEV Ivan.Belyaev@itep.ru # @date 2011-06-07 def _gr_iter_ ( graph ) : - """ - Iterate over graph points + """Iterate over graph points >>> gr = ... - >>> for i in gr : ... - + >>> for i in gr : ... """ for ip in range ( 0 , len ( graph ) ) : yield ip @@ -713,6 +709,7 @@ def _gr_getitem_ ( graph , ipoint ) : >>> graph = ... >>> x,y = graph[3] """ + if ipoint < 0 : ipoint += len(graph) if not ipoint in graph : raise IndexError # @@ -737,6 +734,7 @@ def _gr_setitem_ ( graph , ipoint , point ) : >>> graph[1] = x,y """ # + if ipoint < 0 : ipoint += len(graph) if not ipoint in graph : raise IndexError # @@ -777,6 +775,7 @@ def _gre_getitem_ ( graph , ipoint ) : >>> graph = ... >>> x,y = graph[3] """ + if ipoint < 0 : ipoint += len(graph) if not ipoint in graph : raise IndexError # @@ -804,6 +803,7 @@ def _gre_setitem_ ( graph , ipoint , point ) : >>> graph[4] = x,y """ # + if ipoint < 0 : ipoint += len(graph) if not ipoint in graph : raise IndexError if not 2 == len ( point ) : raise AttributeError("Invalid dimension of 'point'") @@ -856,6 +856,7 @@ def _grae_getitem_ ( graph , ipoint ) : >>> grae = ... >>> x,xl,xh,y,yl,yh = grae[ 1 ] """ + if ipoint < 0 : ipoint += len(graph) if not ipoint in graph : raise IndexError # @@ -882,6 +883,7 @@ def _grae_setitem_ ( graph , ipoint , point ) : >>> grae[1] = x,xl,xh,y,yl,yh """ + if ipoint < 0 : ipoint += len(graph) if not ipoint in graph : raise IndexError if 6 != len(point) : raise AttributeError("Invalid lenght of 'point'") # @@ -1803,6 +1805,126 @@ def lw_graph ( histo , func ) : """ return _lw_graph_ ( histo , func ) + +pos_infinity = float('+inf') +neg_infinity = float('-inf') +# ============================================================================= +## Create a graph, that represents the area between two curves/functions: +# @code +# import math +# graph = fill_area ( math.sin , math.cos , xmin = 0 , xmax = 5 ) +# graph.Draw('f') +# @endcode +# ``Functions'' could be +# - plain functions +# - function objects +# - histograms +# - graphs +# - ... +# Inspired by Rene Brun's example +# @see https://root.cern.ch/phpBB3/viewtopic.php?t=6346 +def fill_area ( fun1 , + fun2 , + n = 100 , + xmin = neg_infinity , + xmax = pos_infinity , + log_scale = False ) : + """Create a graph, that represents the area between + two curves/functions: + >>> import math + >>> graph = fill_area ( math.sin , math.cos , xmin = 0 , xmax = 5 ) + >>> graph.Draw('f') + ``Functions'' could be + - plain functions + - function objects + - histograms + - graphs + - ... + Inspired by Rene Brun's example + - see https://root.cern.ch/phpBB3/viewtopic.php?t=6346 + """ + + # + ## try to define proper x-range for graph.. + # - from input arguments + # - from fun1 and fun2. + + x1mn , x1mx = neg_infinity , pos_infinity + + if hasattr ( fun1 , 'xminmax' ) : + x1mn,x1mx = fun1.xminmax() + elif hasattr ( fun1 , 'xmin' ) and hasattr ( fun1 , 'xmax' ) : + x1mn,x1mx = fun1.xmin(), fun1.xmax() + elif hasattr ( fun1 , 'GetXmin' ) and hasattr ( fun1 , 'GetXmax' ) : + x1mn,x1mx = fun1.GetXmin(), fun1.GetXmax() + elif hasattr ( fun1 , 'GetXaxis' ) : + axis = fun1.GetXaxis() + x1mn,x1mx = axis.GetXmin(), axis.GetXmax() + + x1mn = max ( x1mn , xmin ) + x1mx = min ( x1mx , xmax ) + + x2mn , x2mx = neg_infinity , pos_infinity + if hasattr ( fun2 , 'xminmax' ) : + x2mn,x2mx = fun2.xminmax() + elif hasattr ( fun2 , 'xmin' ) and hasattr ( fun2 , 'xmax' ) : + x2mn,x2mx = fun2.xmin(), fun2.xmax() + elif hasattr ( fun2 , 'GetXmin' ) and hasattr ( fun2 , 'GetXmax' ) : + x2mn,x2mx = fun2.GetXmin(), fun2.GetXmax() + elif hasattr ( fun2 , 'GetXaxis' ) : + axis = fun2.GetXaxis() + x2mn,x2mx = axis.GetXmin(), axis.GetXmax() + + x2mn = max ( x2mn , xmin ) + x2mx = min ( x2mx , xmax ) + + ## to be replaced with numpy.isfinite + if x1mn == neg_infinity and x2mn != neg_infinity : x1mn = x2mn + if x1mn != neg_infinity and x2mn == neg_infinity : x2mn = x1mn + if x1mx == pos_infinity and x2mn != pos_infinity : x1mx = x2mx + if x1mx != pos_infinity and x2mn == pos_infinity : x2mx = x1mx + + if x1mn == neg_infinity or x2mn == neg_infinity or \ + x1mx == pos_infinity or x2mn == pos_infinity : + raise ArrtibuteError("Can't define proper xmin/xmax") + + ## create the graph + graph = ROOT.TGraph ( 2 * n + 3 ) + + if log_scale and 0 < x1mn < x1mx and 0 < x2mn < x2mx : + + dx1 = (x1mx/x1mn)**(1.0/n) + dx2 = (x2mx/x2mn)**(1.0/n) + + x1f = lambda i : x1mn*(dx1**i) + x2f = lambda i : x2mx/(dx2**i) + + else : + + dx1 = float(x1mx-x1mn)/n + dx2 = float(x2mx-x2mn)/n + + x1f = lambda i : x1mn + i * dx1 + x2f = lambda i : x2mx - i * dx2 + + + for i in xrange ( n + 1 ) : + xi = x1f ( i ) + yi = float( fun1( xi ) ) + graph[i] = xi , yi + + for i in xrange ( n + 1 ) : + xi = x2f ( i ) + yi = float( fun2 ( xi ) ) + graph[i+n+1] = xi , yi + + ## the last point is the same as the first one + graph[-1] = graph[0] + + graph.SetFillStyle(3013) + return graph + + # ============================================================================= if '__main__' == __name__ :