ATLAS Offline Software
Loading...
Searching...
No Matches
pydraw.py
Go to the documentation of this file.
1# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2
3#
4# File: pydraw.py
5# Created: sss, a while ago. Added to ATLAS repository, Dec 2008
6# Purpose: Interactive python commands for plotting from a tuple-like object.
7#
8
9
10"""Interactive python commands for plotting from a tuple-like object.
11This module provides in python functionality similar to TTree::Draw
12and TTree::Scan. The major conceptual differences are:
13
14 - All expressions are written in python (and not the specialized
15 TTree::Draw language).
16 - This can be applied to any type of object providing a simple loop
17 interface (explained below), not just to TTree's. (A TTree will work;
18 there is also a wrapper for the Athena event loop.)
19
20In addition, this style of query may be somewhat
21easier to type interactively.
22
23
24Quick start
25===========
26
27Here are some examples of plotting commands to give you the flavor:
28
29 d tt.metx
30 d tt.ele_pt$i; 50 0 100*gev
31 d tt.metx:mety
32 d tt[100:1000].muo$i.pt() if abs(muo.eta)<2
33 d tt.(ele$i+ele$j).m() if $i<$j; same
34 scan tt.metx:mety
35
36To use these commands, you can pass them as strings to pydraw.cmd:
37
38 from pydraw import cmd
39 cmd('d tt.metx')
40
41Alternatively, if you execute the cmdhook function, you can type them
42directly:
43
44 from pydraw import cmdhook
45 cmdhook()
46 d tt.metx
47
48(Note that this second form doesn't work in general for code that's read
49from a file.)
50
51
52Draw syntax
53===========
54
55The general syntax for drawing a histogram looks like this:
56
57 d TUPLESPEC.[STMT@ ...]EXPR[:EXPR] [if EXPR] [; HISTSPEC]
58
59These pieces are explained in more detail below.
60
61
62Tuple specifications
63--------------------
64
65The object on which the draw operation is to be performed is given
66by TUPLESPEC. This should be the name of some object in the python
67global dictionary. Note that if the expression for the tuple
68itself contains a period or brackets, you'll need to enclose it in parentheses:
69
70 d (trees.tt[1])[:100].ele_pt$i
71
72Optionally, this can include a slice-like notation
73to restrict the rows to which the draw applies:
74
75 tuple[i:j] --- process rows i <= n < j
76 tuple[:j] --- process rows n < j
77 tuple[i:] --- process rows n >= i
78 tuple[i] --- process row i
79
80i and j can be expressions as well.
81
82The tuple object should contain a method like this:
83
84 def loop (self, func, looplo, loophi)
85
86This function should loop over rows [looplo, loophi) and
87call func on each. func will take two arguments, the first
88being the row number and the second being the event object
89(which may just be the tuple itself).
90
91If a method named loop is not available, but GetEntry and GetEntries
92methods are available, then those will be used instead.
93Similarly, if size and seekEvent are available, a wrapper
94appropriate for the Athena event loop will be used.
95
96Thus, you can make plots from within Athena like this:
97
98 d theApp.ElectronAODCollection$i.eta()
99
100
101Expressions
102-----------
103
104Once a tuple is specified, one then needs to specify what to plot.
105This is done with an arbitrary python expression, with some extensions.
106
107To plot a simple numeric variable from the tuple, just give its name.
108For example:
109
110 d tt.foo
111
112will plot the variable `foo' from each row of the tuple. You can of course
113also use more complicated expressions:
114
115 d tt.r*cos(phi)
116
117In the common case, though, the members of the tuple will be vectors
118of numbers of objects over which you want to iterate. You can iterate
119plotting over such a vector by specifying a dummy index. This is an
120identifier starting with a dollar sign:
121
122 d tt.pt_e$i
123
124(Making the dummy indices explicit was a deliberate change from the
125implicit iteration of root/paw. Such implicit iteration sometimes
126caused confusion about just what was being iterated over, and also
127when people needed to distinguish between accessing attributes
128of a container and attributes of the contained objects.)
129
130This will automatically step $i over the contents of the vector pt_e,
131plotting each one. The vector being iterated over may contain objects
132as well as numbers:
133
134 d tt.ele$i.pt()
135
136Note that the limits of the iteration are handled automatically.
137The index $i can also be used in contexts other than that of an index.
138For example, to limit the iteration in the above example to the first
139four items, one can use:
140
141 d tt.ele$i.pt() if $i<4
142
143(This uses a selection clause. This is to be described later, but the meaning
144should be obvious.)
145
146If a dummy index is used for more than one vector, the iteration limit
147will be taken from whichever vector is smaller. For example:
148
149 d tt.ele$i+muo$i
150
151will iterate min(len(ele),len(muo)) times.
152
153Multiple dummy indices may be used. In that case, all possible index
154combinations are used (a simple nested loop). If there is a symmetry
155involved, a selection clause may be used to eliminate duplicates.
156For example (given an appropriate ele definition), this will plot the
157invariant masses of all electron pairs:
158
159 d tt.(ele$i+ele$j).m() if $i>$j
160
161One can also reference individual elements of a vector using the
162notation $N. Here, the indexing is 1-based (so $1 is the first element).
163Example: to plot the pt of the leading electron
164
165 d tt.ele$1.pt()
166
167Note that one can do something similar just using standard python
168indexing directly:
169
170 d tt.ele[0].pt()
171
172This, however, will crash if ele is empty; if the $N form is used,
173an implicit selection is added to ensure that the index is in range.
174Thus, the proper equivalent of the $N-form example is:
175
176 d tt.ele[0].pt() if len(ele)>0
177
178You can also use $nCONT as an abbreviation for len(CONT):
179
180 d tt.ele[0].pt() if $nele>0
181
182Names used in expressions will be looked up first in the tuple object,
183then in the global dictionary. Note that the lookup in the tuple happens
184before looping starts, so all needed variables must already be present
185in the tuple. If you want to refer to the tuple object itself,
186you can use the special variable `_ev'. (This can be changed by assigning
187a string to pydraw._evtvar.)
188
189Multiple expressions may be given, separated by `:'. For drawing histograms,
190you can give two expressions, for the x and y axis expressions. For scanning
191tuples (see below), you give the list of expressions to be scanned.
192
193
194Selections
195----------
196
197You can select items to be filled using a boolean expression, set off
198with an `if' keyword:
199
200 d tt.ele$i.pt() if abs(ele$i.eta())<2
201
202All dummy variables are common between the variable expressions
203and the selection.
204
205
206Statements
207----------
208
209You can specify arbitrary Python expressions to be executed before looping
210starts. These come before the variables expressions, and are delimited
211with `@'. This is useful mostly for defining short aliases for tuple
212variables. For example:
213
214 d tt.ele=ElectronAODCollection@ele$i.eta() if ele$i.pt()>100*gev
215
216
217Histogram specifications
218------------------------
219
220Histogram specifications follow a semicolon. They can have the forms:
221
222 ! OPTIONS...
223 >>NAME OPTIONS...
224 NX XLO XHI OPTIONS...
225 NX XLO XHI NY YLO YHI OPTIONS...
226 OPTIONS...
227
228For the first form, specifying ! reuses the same histogram that was used
229for the last plot.
230
231For the second form, specifying >>NAME looks in the global dictionary
232for a histogram NAME and uses that.
233
234Otherwise, a new histogram is created. The binning for this histogram
235may be specified by NX XLO XHI and NY YLO YHI.
236
237Plotting options may also be given. The special option `prof' means
238to create a profile histogram. Otherwise, root drawing options may
239be used. This package uses PyAnalysisUtils.draw_obj to draw the
240histograms, so the extra options supported by that function may
241also be used. See draw_obj.py for details.
242
243The last histogram to have been drawn is available is pydraw.last_hist.
244
245
246Scan syntax
247===========
248
249The scan command is very similar to draw:
250
251 scan TUPLESPEC.[STMT@ ...]EXPR[:EXPR] [if EXPR]
252
253Instead of drawing a histogram, scan prints out a table of the expression
254values.
255
256The formatting of the data printed by scan is currently pretty rudimentary.
257This should probably be improved.
258
259
260Loop syntax
261===========
262
263There is also a loop command:
264
265 loop TUPLESPEC.[STMT@ ...]EXPR[:EXPR] [if EXPR]
266
267Loop will evaluate the given expressions in the same manner as draw and scan,
268but the results of this are ignored. So it only makes sense to use loop
269to evaluate expressions for their side effects. This can be used,
270for example, to call a function that fills some large set of histograms.
271
272
273Running commands
274================
275
276The general interface to execute one of these commands is the `cmd' function,
277which takes the command as a string:
278
279 from pydraw import cmd
280 cmd ('d tt.foo')
281
282Each command is also implemented by a single function, which may be called
283directly. The command name should not be included in this case:
284
285 from pydraw import draw
286 draw ('tt.foo')
287
288Finally, if you call the function cmdhook(), then you can give the commands
289directly on the python command line:
290
291 from pydraw import cmdhook
292 cmdhook()
293 d tt.foo
294
295
296Bugs/stuff missing
297==================
298 - No way to specify an event weight when filling a histogram using
299 the draw command.
300
301 - Hoist selection code out of the dummy index loops, when they don't
302 depend on the index? For example,
303
304 d tt.foo$i if ht>100
305
306 gets implemented like:
307
308 for _it_foo in foo:
309 if _ev.ht>100:
310 Fill(_it_foo)
311
312 but it would be more efficient to pull the selection out of the loop.
313
314 - In an expr like d em.foo$i if em.bar$i>1
315 then foo always gets evaluated even if the condition is false.
316
317 - Scan formatting.
318
319
320"""
321
322
323import sys
324import string
325import tokenize
326import token
327import copy
328import ROOT
329import cppyy # noqa: F401
330from io import StringIO #pragma: NO COVER
331from PyAnalysisUtils.draw_obj import draw_obj, get_canvas
332
333
334try:
335 ScatterH2 = ROOT.RootUtils.ScatterH2
336except AttributeError: #pragma: NO COVER
337 ScatterH2 = ROOT.TH2F #pragma: NO COVER
338 print ("WARNING: RootUtils::ScatterH2 not available; using TH2F instead") #pragma: NO COVER
339
340
341try:
342 ROOT.TH1.kCanRebin
343 def _setCanRebin (h): #pragma: NO COVER
344 h.SetBit (ROOT.TH1.kCanRebin) #pragma: NO COVER
345 def _hasCanRebin (h): #pragma: NO COVER
346 return h.TestBit (ROOT.TH1.kCanRebin) #pragma: NO COVER
347except AttributeError: #pragma: NO COVER
348 def _setCanRebin (h): #pragma: NO COVER
349 h.GetXaxis().SetCanExtend(True) #pragma: NO COVER
350 def _hasCanRebin (h): #pragma: NO COVER
351 return h.GetXaxis().CanExtend() #pragma: NO COVER
352
353
354# The last histogram we made.
355last_hist = None
356
357
358# Dictionary in which to find global names.
359_globals = sys.modules['__main__'].__dict__
360
361
362# Characters that are legal in identifiers.
363_idchars = string.ascii_letters + string.digits + '_'
364
365# This is what's used as the event formal argument in the generated functions.
366_evtvar = '_ev'
367
368# Set this to true to dump out the generated function bodies.
369_debug = False
370
372 """Name a string safe to use as a histogram name.
373
374 Root does bad things if you put / in a histogram name, so we remove them.
375 Examples:
376 >>> print (_sanitize_hname('foo'))
377 foo
378 >>> print (_sanitize_hname('foo/bar'))
379 foo DIV bar
380 """
381 return s.replace ('/', ' DIV ')
382
383
384def _untokenize (tokens):
385 """Transform tokens back into Python source code.
386
387 Each element returned by the iterable must be a token sequence
388 with at least two elements, a token number and token value.
389
390 Unlike tokenize.untokenize(), this does not handle multiple lines.
391 It also tries not to add unneeded spaces.
392
393 Examples:
394 >>> from tokenize import generate_tokens, untokenize
395 >>> from io import StringIO
396 >>> def untokenize1(tt):
397 ... tt=list(tt)
398 ... if tt[-1][0]==0: tt=tt[:-1]
399 ... return untokenize(tt)
400 >>> untokenize1(generate_tokens(StringIO('1+1').readline))
401 '1+1'
402 >>> _untokenize(generate_tokens(StringIO('1+1').readline))
403 '1+1'
404 >>> untokenize1(generate_tokens(StringIO('foo$i>2*h').readline))
405 'foo$i>2*h'
406 >>> _untokenize(generate_tokens(StringIO('foo$i>2*h').readline))
407 'foo$i>2*h'
408 """
409 lastname = False
410 toks = []
411 toks_append = toks.append
412 for tok in tokens:
413 toknum, tokval = tok[:2]
414 tokval = tokval.strip()
415 if toknum in (token.NAME, token.NUMBER):
416 if lastname:
417 tokval = ' ' + tokval
418 lastname = True
419 else:
420 lastname = False
421 toks_append (tokval)
422 return ''.join(toks)
423
424
425def _find_outer (haystack, needle, ignore_delim = False):
426 """Look for NEEDLE in HAYSTACK (token-based. Return pair (HEAD, TAIL).
427
428 HAYSTACK and NEEDLE are both strings. Look for a token in HAYSTACK with
429 a value matching NEEDLE that is outside of any paired delimiters.
430 Also ignores things in strings.
431 If IGNORE_DELIM is True, then we do find things inside delimiters
432 (strings are still ignored).
433
434 Returns a pair (HEAD, TAIL) of the pieces of the string before and
435 after NEEDLE. If there is no match, returns (HAYSTACK, None).
436 Note that whitespace and formatting in HEAD and TAIL may differ
437 from the original string.
438
439 Examples:
440 >>> _find_outer ("head.tail1.tail2", ".")
441 ('head', 'tail1.tail2')
442 >>> _find_outer ("(head.tail1).tail2", ".")
443 ('(head.tail1)', 'tail2')
444 >>> _find_outer ("[a for a in foo if good(a)] if bar", "if")
445 ('[a for a in foo if good(a)]', 'bar')
446 >>> _find_outer ("(a [b {c . d } ] ) . e", ".")
447 ('(a [b {c . d } ] )', 'e')
448 >>> _find_outer ("a.b", ";")
449 ('a.b', None)
450 >>> _find_outer ("a '$' b", '$')
451 ("a '$' b", None)
452 >>> _find_outer ("a $ b", '$')
453 ('a', 'b')
454 >>> _find_outer ("(head.tail1).tail2", ".", True)
455 ('(head', 'tail1).tail2')
456 >>> _find_outer ('a; 1 -1 1', ';')
457 ('a', '1 -1 1')
458"""
459 tlist = tokenize.generate_tokens (StringIO(haystack).readline)
460 pend = []
461 head = []
462 for (i, (tnum, val, a, b, c)) in enumerate (tlist):
463 if tnum != token.STRING and not pend and val == needle:
464 col1 = a[1]
465 col2 = b[1]
466 return (haystack[:col1].strip(),
467 haystack[col2:].strip())
468 if not ignore_delim:
469 if val == '(':
470 pend.append (')')
471 elif val == '[':
472 pend.append (']')
473 elif val == '{':
474 pend.append ('}')
475 elif pend and val == pend[-1]:
476 pend.pop()
477 head.append ((tnum, val))
478 return (haystack, None)
479
480
481def _split_outer (haystack, needle):
482 """Split HAYSTACK at the delimiters NEEDLE, as in _find_outer.
483
484 Examples:
485 >>> _split_outer ("a,(b,c),d", ",")
486 ['a', '(b,c)', 'd']
487 >>> _split_outer ("a,,b", ",")
488 ['a', '', 'b']
489 >>> _split_outer ("a", ",")
490 ['a']
491 >>> #_split_outer ("", ",")
492 []
493"""
494 out = []
495 while True:
496 (head, tail) = _find_outer (haystack, needle)
497 head = head.strip()
498 out.append (head)
499 if tail is None:
500 break
501 else:
502 haystack = tail
503 return out
504
505
507 """Wrapper for TTree, supplying a loop method.
508
509 This class wraps a TTree class and provides a loop method
510 that will work with pydraw.
511"""
512
513 def __init__ (self, tree):
514 """Make a wrapper for a tree."""
515 self._tree = tree
516 return
517
518 def loop (self, f, looplo=0, loophi=sys.maxsize):
519 """Call f(i,tree) on rows [looplo, loophi)"""
520 tree = self._tree
521 loophi = min (loophi, tree.GetEntries())
522 getentry = tree.GetEntry
523 for i in range(looplo, loophi):
524 getentry(i)
525 f(i, tree)
526 return
527
528
530 """Wrapper for the Athena event loop, supplying a loop method.
531
532 This class wraps an application manager object and provides a loop method
533 that will work with pydraw.
534"""
535 def __init__ (self, app=None):
536 from AthenaPython import PyAthena #pragma: NO COVER
537 if app is None: #pragma: NO COVER
538 from AthenaCommon.AppMgr import theApp #pragma: NO COVER
539 app = theApp #pragma: NO COVER
540 self._app = app #pragma: NO COVER
541 self._sg = PyAthena.py_svc('StoreGateSvc') #pragma: NO COVER
542 return #pragma: NO COVER
543
544
545 def loop (self, f, looplo=0, loophi=sys.maxsize):
546 """Call f(i,tree) on rows [looplo, loophi)"""
547 loophi = min (loophi, self._app.size()) #pragma: NO COVER
548 getentry = self._app.seekEvent #pragma: NO COVER
549 for i in range(looplo, loophi): #pragma: NO COVER
550 getentry(i) #pragma: NO COVER
551 f(i, self) #pragma: NO COVER
552 return #pragma: NO COVER
553
554
555 def __getattr__ (self, v):
556 if not v.startswith('_'): #pragma: NO COVER
557 return self._sg[v] #pragma: NO COVER
558 raise AttributeError() #pragma: NO COVER
559
560
562 """Holds information about a dummy loop variable.
563
564 Attributes:
565 name - The name of the dummy variable.
566 ids - Set of loop identifiers (`foo' in `foo$i') with which
567 this dummy has been used.
568 explicit - Set to true if this variable is ever used on its own
569 (just $i)
570 """
571
572 def __init__ (self, name):
573 """Initialize given the name."""
574 self.name = name
575 self.ids = set()
576 self.explicit = 0
577 return
578
579 def itname (self, id):
580 """Return the iterator variable for this dummy and loop identifier ID.
581 """
582 return "_it_%s_%s" % (self.name, id)
583
584 def dumname (self):
585 """Return the dummy variable name for this dummy."""
586 return "_dum_" + self.name
587
588 def add_id (self, id):
589 """Notice this this dummy is used with loop identifier ID.
590
591 Return the iterator variable.
592 """
593 self.ids.add (id)
594 return self.itname (id)
595
596 def get_ids (self):
597 """Return the list of loop identifiers with which we've been used."""
598 return list (self.ids)
599
600
602 """Holds information used to implement a draw/scan/loop command.
603
604 Pass the draw string to the constructor. See the file-level comments
605 for details on the syntax of this. This will define the
606 following attributes:
607
608 errstr - If set to a string, there was an error.
609 Should be None if everything's ok.
610 tuple - The name of the tuple object.
611 tuple_o - Tuple object.
612 lo - The lower bound for row iteration.
613 hi - The upper bound for row iteration.
614 stmts - List of additional statements.
615 exprs - List of draw expressions.
616 sel - Selection expression or None.
617 sel_orig - Untransformed selection expression or None.
618 expr_orig- Untransformed plotting expression.
619 histspec - The text following `;', split into space-separated words.
620
621 Other attributes:
622 _iddict - Map from loop identifiers (`foo' in `foo$i')
623 to temp variables used to reference
624 them in the loop function.
625 _limdict - Map from loop identifiers (`foo' in `foo$2')
626 to the largest explicit index seen.
627 _loopdict - Map of loop dummy variable names to _Loopvar instances.
628"""
629
630 def __init__ (self, s):
631 """Initialize from a draw string. See above for more details."""
632
633 # Assume no error.
634 self.errstr = None
635
636 self._iddict = {}
637 self._limdict = {}
638 self._loopdict = {}
639
640 try:
641 self._tupleparse (s)
642 except Exception as e:
643 import traceback
644 self.errstr = str(e)
645 self.excstr = traceback.format_exc()
646 return
647
648
649
650 def _tupleparse (self, s):
651 """Parse a draw string. See above for more details."""
652
653 # Split off the histspec.
654 (s, self.histspec) = _find_outer (s, ';')
655
656 # ??? Don't split at spaces in delimiters.
657 # _find_outer doesn't really work for this since it operates
658 # on the tokenized string, in which whitespace doesn't appear.
659 if self.histspec is None:
660 self.histspec = []
661 else:
662 self.histspec = self.histspec.split ()
663
664 # Gotta have something.
665 s = s.strip()
666 if not s:
667 self.errstr = "Empty draw string."
668 return
669
670 # Split off the tuple part --- before the first period.
671 (tuple, s) = _find_outer (s, '.')
672 if not s:
673 self.errstr = "Missing period in tuple specification."
674 return
675
676 # Handle a range specification on the sample.
677 self._parserange (tuple)
678
679 # Try to find the sample.
680 try:
681 self.tuple_o = eval (self.tuple, _globals)
682 except NameError:
683 self.tuple_o = None
684 if not self.tuple_o:
685 self.errstr = "Can't find sample " + self.tuple
686 return
687
688 # Look for additional statements.
689 self.stmts = _split_outer (s, '@')
690 s = self.stmts[-1]
691 del self.stmts[-1]
692 self.stmts = [self._mung_expr(x) for x in self.stmts]
693
694 # Split off the selection.
695 (self.expr_orig, self.sel_orig) = _find_outer (s, "if")
696 self.sel = self._mung_expr (self.sel_orig)
697
698 self.exprs = [self._mung_expr(x) for x in
699 _split_outer (self.expr_orig, ':')]
700
701 # Check the interface of the sample. If it doesn't have
702 # the loop interface but has the root tree interface,
703 # use a wrapper.
704 if hasattr (self.tuple_o, 'loop'):
705 # Ok --- has the loop interface.
706 pass
707 elif (hasattr (self.tuple_o, 'GetEntry') and
708 hasattr (self.tuple_o, 'GetEntries')):
709 # Has the TTree interface. Use a wrapper.
710 self.tuple_o = TreeLoopWrapper (self.tuple_o)
711 elif (hasattr (self.tuple_o, 'size') and
712 hasattr (self.tuple_o, 'seekEvent')): #pragma: NO COVER
713 # Has the appmgr interface. Use a wrapper.
714 self.tuple_o = AthenaLoopWrapper (self.tuple_o) #pragma: NO COVER
715 else:
716 # An error --- complain.
717 self.errstr = ("Sample " + self.tuple +
718 " doesn't have a correct interface.")
719 return
720
721 return
722
723
724
725 def _parserange (self, tuple):
726 """Parse the range part of a draw string.
727
728 See above for more details.
729 Fills self.tuple, self.lo, self.hi.
730 """
731 lo = 0
732 hi = sys.maxsize
733 (tuple, tail) = _find_outer (tuple, '[')
734 if tail:
735 g = copy.copy (_globals)
736
737 pos = tail.find (':')
738 pos2 = tail.find (']')
739 if pos2 < 0:
740 pos2 = len (tail) #pragma: NO COVER
741 if pos < 0:
742 slo = tail[:pos2].strip()
743 if len (slo) > 0:
744 lo = int (eval (slo, g))
745 hi = lo + 1
746 else:
747 slo = tail[:pos].strip()
748 if len (slo) > 0:
749 lo = int (eval (slo, g))
750 shi = tail[pos+1:pos2].strip()
751 if len (shi) > 0:
752 hi = int (eval (shi, g))
753
754 if tuple[0] == '(' and tuple[-1] == ')':
755 tuple = tuple[1:-1].strip()
756 self.tuple = tuple
757 self.lo = lo
758 self.hi = hi
759 return
760
761
762 def _mung_id (self, id):
763 """Given a loop identifier (`foo' in `foo$i'), return the identifier
764 used to reference it in loop functions.
765 """
766 out = self._iddict.get (id)
767 if not out:
768 out = '_e_' + id
769 self._iddict[id] = out
770 return out
771
772
773 def _mung_index (self, s1, s2):
774 """Handle an explicit index reference; i.e., `foo$2'.
775
776 S1 and S2 are pieces of the string before and after the `$'.
777 Returns the modified string.
778 """
779 pos2 = 0
780 while pos2 < len(s2) and s2[pos2] in string.digits:
781 pos2 += 1
782 if pos2 == 0:
783 self.errstr = "Bad index"
784 return ''
785 i = int (s2[:pos2])
786 if i < 1:
787 self.errstr = "Bad index"
788 return ''
789 s = ("[%d]" % (i-1)) + s2[pos2:]
790 pos2 = len(s1)-1
791 while pos2 >= 0 and s1[pos2] in _idchars:
792 pos2 -= 1
793 pos2 += 1
794 if pos2 == len(s1):
795 self.errstr = "Bad index"
796 return ''
797 id = s1[pos2:]
798 s = s1[:pos2] + self._mung_id (id) + s
799 self._limdict[id] = max (i, self._limdict.get(id, 0))
800 return s
801
802
803 def _mung_n (self, s1, s2):
804 """Handle a length reference; i.e., `$nfoo'.
805
806 S1 and S2 are pieces of the string before and after the `$'.
807 Returns the modified string.
808 """
809 pos2 = 1
810 while pos2 < len(s2) and s2[pos2] in _idchars:
811 pos2 += 1
812 id = s2[1:pos2]
813 s = s1 + (" len(%s)" % self._mung_id(id)) + s2[pos2:]
814 return s
815
816
817 def _mung_loop (self, s1, s2):
818 """Handle use of a dummy loop variable, such as foo$i.
819
820 S1 and S2 are pieces of the string before and after the `$'.
821 Returns the modified string.
822 """
823
824 # Scan after the $ to find the dummy variable.
825 pos2 = 0
826 while pos2 < len(s2) and s2[pos2] in _idchars:
827 pos2 += 1
828 if pos2 == 0:
829 self.errstr = "Bad loop var"
830 return ''
831 loopvar = s2[:pos2]
832
833 # Look it up. Make a new _Loopvar object if it's not in the map.
834 ll = self._loopdict.get (loopvar)
835 if not ll:
836 ll = _Loopvar(loopvar)
837 self._loopdict[loopvar] = ll
838
839 # Is the $ after an identifier?
840 if len(s1) > 0 and s1[-1] in _idchars:
841 # Yes --- find the identifier.
842 pos3 = len(s1)-1
843 while pos3 >= 0 and s1[pos3] in _idchars:
844 pos3 -= 1
845 pos3 += 1
846 assert (len(s1) - pos3 >= 1)
847 id = s1[pos3:]
848
849 # Replace with the iterator.
850 s = s1[:pos3] + ll.add_id(id) + s2[pos2:]
851 self._mung_id (id)
852 else:
853 # Explicit use of the dummy.
854 # Replace with the dummy name and note that it was used explicitly.
855 s = s1 + ("%s" % ll.dumname()) + s2[pos2:]
856 ll.explicit = 1
857 return s
858
859
860 def _mung_expr_dollar (self, s):
861 """Process $ constructions in string S.
862
863 Returns the modified string.
864 """
865 if not s:
866 return s
867 pos = 0
868 while 1:
869 (s1, s2) = _find_outer (s[pos:], '$', True)
870 if s2 is None:
871 break
872 snew = None
873 if len(s2) > 0:
874 if s2[0] in string.digits:
875 snew = self._mung_index (s1, s2)
876 elif (s2[0] == 'n' and
877 (not (len(s1) > 0 and s1[-1] in _idchars) or
878 s1.endswith (' and') or
879 s1.endswith (' or') or
880 s1.endswith ('not'))):
881 snew = self._mung_n (s1, s2)
882 elif s2[0] in string.ascii_letters:
883 snew = self._mung_loop (s1, s2)
884 s = s[:pos]
885 if snew is None:
886 snew = s1 + '$' + s2
887 pos = pos + len(s1)+1
888 s = s + snew
889 return s
890
891
892 def _mung_expr_ids (self, s):
893 """Perform id substitution in S.
894
895 For identifiers in S that are attributes of our tuple,
896 replace them with references to the tuple attribute
897 (using _mung_id).
898
899 Returns the modified string.
900 """
901 if not s:
902 return s
903
904 tlist = tokenize.generate_tokens (StringIO(s).readline)
905 out = []
906 afterDot = False
907 for tnum, val, a, b, c in tlist:
908 if tnum == token.NAME and not afterDot:
909 if hasattr (self.tuple_o, val):
910 val = self._mung_id (val)
911 #val = _evtvar + '.' + val
912 out.append ((tnum, val))
913 # Don't mung names after a period. We may have attributes
914 # with the same names as variables in the tuple.
915 if tnum == token.OP and val == '.':
916 afterDot = True
917 else:
918 afterDot = False
919 return _untokenize (out)
920
921
922 def _mung_expr (self, s):
923 """Process $ constructions and id substitution in string S.
924
925 Returns the modified string.
926 """
927 s = self._mung_expr_dollar (s)
928 return self._mung_expr_ids (s)
929
930
931 def _make_func (self, payload, extargs = ''):
932 """Create the text for the function to process this query.
933
934 PAYLOAD is the payload expression to plug in.
935 EXTARGS is an additional string to add to the end of the
936 function's argument list (to set default values, for example).
937 Returns the function definition as a string.
938 """
939
940 sel = self.sel
941 if self._limdict:
942 limsel = ' and '.join (["len(%s)>=%d" % (self._iddict[p[0]], p[1])
943 for p in self._limdict.items()])
944 if not sel:
945 sel = limsel
946 else:
947 sel = limsel + " and (" + sel + ")"
948
949 ftext = "def _loopfunc(_i, %s%s):\n" % (_evtvar, extargs)
950 for (id1, id2) in sorted(self._iddict.items()):
951 ftext += " %s = %s.%s\n" % (id2, _evtvar, id1)
952 indent = 2
953
954 for (i,l) in sorted(self._loopdict.items()):
955 ids = sorted(l.get_ids())
956 assert (not not ids)
957 if len(ids) == 1:
958 vars = l.itname (ids[0])
959 lists = self._iddict[ids[0]]
960 else:
961 vars = "(" + ','.join([l.itname (id) for id in ids]) + ")"
962 lists = ("zip(" + ','.join([self._iddict[id] for id in ids])
963 + ")")
964 if l.explicit:
965 vars = "(%s,%s)" % (l.dumname(), vars)
966 lists = "enumerate(%s)" % lists
967 ftext += ' '*indent + "for %s in %s:\n" % (vars, lists)
968 indent += 2
969
970 for s in self.stmts:
971 ftext += ' '*indent + s + '\n'
972
973 if sel and sel != '1':
974 ftext += ' '*indent + "if (%s):\n" % sel
975 indent += 2
976
977 ftext += ' '*indent + "%s\n" % payload
978
979 if _debug:
980 print (ftext)
981
982 return ftext
983
984
986 """Holds the results of _get_bins. Defined attributes:
987
988 nbins
989 lo
990 hi
991 rebin
992 """
993
994
995def _get_bins (args, ndim, axis):
996 """Parse bin specifications from split list of arguments ARGS.
997 NDIM is 1 or 2, and AXIS is 0 or 1, for the x or y axis.
998
999 Examples:
1000 >>> from PyAnalysisUtils import pydraw
1001 >>> pydraw._globals = globals()
1002 >>> import ROOT
1003 >>> ROOT.gPad.Range(0, 1,2,3)
1004 >>> b = _get_bins (["50", "10", "100"], 1, 0)
1005 >>> print (b.nbins, b.lo, b.hi, b.rebin)
1006 50 10.0 100.0 0
1007 >>> b = _get_bins ([], 1, 0)
1008 >>> print (b.nbins, b.lo, b.hi, b.rebin)
1009 50 0 1 1
1010 >>> b = _get_bins (["!", "10"], 1, 0)
1011 >>> print (b.nbins, b.lo, b.hi, b.rebin)
1012 50 10.0 11.0 1
1013 >>> b = _get_bins (["!", "!", "10"], 1, 0)
1014 >>> print (b.nbins, b.lo, b.hi, b.rebin)
1015 50 0 10.0 0
1016 >>> scale = 10
1017 >>> b = _get_bins (["50", "0", "2*scale"], 1, 0)
1018 >>> print (b.nbins, b.lo, b.hi, b.rebin)
1019 50 0.0 20.0 0
1020 >>> b = _get_bins ([], 2, 0)
1021 >>> print (b.nbins, b.lo, b.hi, b.rebin)
1022 50 0.0 2.0 1
1023 >>> b = _get_bins ([], 2, 1)
1024 >>> print (b.nbins, b.lo, b.hi, b.rebin)
1025 50 1.0 3.0 1
1026 >>> b = _get_bins ([], 2, 2)
1027 Traceback (most recent call last):
1028 ...
1029 AssertionError
1030 """
1031
1032 g = copy.copy (_globals)
1033
1034 bins = _Bins()
1035
1036 bins.nbins = 0
1037 if len(args) >= 1 and args[0] != '!' and len(args[0]) > 0:
1038 bins.nbins = int (eval (args[0], g))
1039 if bins.nbins <= 0:
1040 bins.nbins = 50
1041
1042 bins.lo = 0
1043 if len(args) >= 2 and args[1] != '!' and len(args[1]) > 0:
1044 bins.lo = float (eval (args[1], g))
1045
1046 bins.hi = 0
1047 if len(args) >= 3 and args[2] != '!' and len(args[2]) > 0:
1048 bins.hi = float (eval (args[2], g))
1049
1050 bins.rebin = 0
1051 if bins.hi <= bins.lo:
1052 bins.rebin = 1
1053 if ndim == 1:
1054 bins.hi = bins.lo + 1
1055 elif axis == 0:
1056 bins.lo = ROOT.gPad.GetUxmin()
1057 bins.hi = ROOT.gPad.GetUxmax()
1058 elif axis == 1:
1059 bins.lo = ROOT.gPad.GetUymin()
1060 bins.hi = ROOT.gPad.GetUymax()
1061 else:
1062 assert 0
1063
1064 return bins
1065
1066
1067def _get_hist (ndim, args, hname, htitle):
1068 """Create a new histogram from options.
1069
1070 NDIM is the dimensionality of the histogram (1 or 2).
1071 ARGS is a list of the arguments given to specify the histogram.
1072 HNAME and HTITLE are the histogram name and title, respectively.
1073 """
1074 get_canvas()
1075
1076 # Get the x-axis bin specifications.
1077 xbins = _get_bins (args, ndim, 0)
1078 rebin = xbins.rebin
1079
1080 # Get the y-axis bin specifications.
1081 if ndim >= 2:
1082 ybins = _get_bins (args[3:], ndim, 1)
1083 rebin = rebin or ybins.rebin
1084
1085 profile = 0
1086 # Look for drawing options.
1087 options = ''
1088 for i in range (0, len(args)):
1089 if args[i][0] in string.ascii_letters:
1090 for j in range (i, len(args)):
1091 if ndim == 2 and args[j].lower() == "prof":
1092 profile = 1
1093 args[j] = ''
1094 options = ' '.join (args[i:])
1095 break
1096
1097 # Delete any old object of the same name.
1098 hold = ROOT.gROOT.FindObject (hname)
1099 if hold:
1100 ROOT.gROOT.Remove (hold)
1101
1102 # Create the histogram.
1103 if profile:
1104 hist = ROOT.TProfile (hname, htitle, xbins.nbins, xbins.lo, xbins.hi)
1105 if not ybins.rebin:
1106 hist.SetMinimum (ybins.lo)
1107 hist.SetMaximum (ybins.hi)
1108 elif ndim == 1:
1109 hist = ROOT.TH1F (hname, htitle, xbins.nbins, xbins.lo, xbins.hi)
1110 elif ndim == 2:
1111 hist = ScatterH2 (hname, htitle,
1112 xbins.nbins, xbins.lo, xbins.hi,
1113 ybins.nbins, ybins.lo, ybins.hi)
1114 if hasattr (hist, 'scatter'):
1115 hist.scatter (1)
1116
1117 # Automatic rebinning?
1118 if rebin:
1119 _setCanRebin (hist)
1120
1121 return (hist, options)
1122
1123
1124def draw (arg):
1125 """Process a draw command.
1126
1127 ARG is the command arguments (without the command word itself).
1128 See the header comments for the command syntax.
1129 """
1130
1131 global last_hist
1132
1133 # Initial parsing of the arguments.
1134 c = Draw_Cmd (arg)
1135 if c.errstr:
1136 print (c.errstr)
1137 return False
1138
1139 # Construct the expression to use to fill the histogram.
1140 if len (c.exprs) == 1:
1141 ndim = 1
1142 payload = "_hfill (%s)" % c.exprs[0]
1143 else:
1144 ndim = 2
1145 payload = "_hfill ((%s),(%s))" % (c.exprs[0], c.exprs[1])
1146
1147 # Construct the histogram title.
1148 htitle = "%s.%s" % (c.tuple, c.expr_orig)
1149 if c.sel_orig:
1150 htitle = htitle + '{%s}' % c.sel_orig
1151
1152 # Make the histogram.
1153 # If it's `!', then we just use the last one.
1154 if len(c.histspec) >= 1 and c.histspec[0] == "!" and last_hist is not None:
1155 hist = last_hist
1156 options = ' '.join (c.histspec[1:])
1157 elif len(c.histspec) >= 1 and c.histspec[0][:2] == '>>':
1158 hname = c.histspec[0][2:]
1159 hist = _globals.get (hname)
1160 options = ' '.join (c.histspec[1:])
1161 else:
1162 (hist, options) = _get_hist (ndim, c.histspec,
1163 _sanitize_hname(c.tuple+'.'+c.expr_orig), htitle)
1164
1165 # Remember it.
1166 last_hist = hist
1167
1168 # Generate the function.
1169 # It will be defined as _loopfunc in g.
1170 g = copy.copy (_globals)
1171 g['_hfill'] = hist.Fill
1172 ftext = c._make_func (payload, ', _hfill = _hfill')
1173 exec (ftext, g)
1174
1175 # Execute the loop over the data.
1176 c.tuple_o.loop (g['_loopfunc'], c.lo, c.hi)
1177
1178 # Adjust binning, if requested.
1179 if _hasCanRebin (hist):
1180 hist.LabelsDeflate ("X")
1181 if ndim > 1:
1182 hist.LabelsDeflate ("Y")
1183
1184 # Draw the histogram.
1185 draw_obj (hist, options)
1186 return True
1187
1188
1189def _scan_print (i, *args):
1190 """Helper to print out one row of a scan.
1191
1192 I is the row number and ARGS is a tuple of the column values."""
1193
1194 s = '%6d' % i
1195 for a in args:
1196 if isinstance(a, int):
1197 s += ' %8d' % a
1198 else:
1199 s += ' %8g' % a
1200 print (s)
1201 return
1202
1203
1204def scan (arg):
1205 """Process a scan command.
1206
1207 ARG is the command arguments (without the command word itself).
1208 See the header comments for the command syntax.
1209 """
1210
1211 # Initial parsing of the arguments.
1212 c = Draw_Cmd (arg)
1213 if c.errstr:
1214 print (c.errstr)
1215 return False
1216
1217 payload = "_print (_i, %s)" % \
1218 ','.join (['(%s)'%e for e in c.exprs])
1219
1220 # Generate the function.
1221 # It will be defined as _loopfunc in g.
1222 g = copy.copy (_globals)
1223 g['_print'] = _scan_print
1224 ftext = c._make_func (payload, ', _print = _print')
1225 exec (ftext, g)
1226
1227 # Execute the loop over the data.
1228 c.tuple_o.loop (g['_loopfunc'], c.lo, c.hi)
1229
1230 return True
1231
1232
1233def loop (arg):
1234 """Process a loop command.
1235
1236 ARG is the command arguments (without the command word itself).
1237 See the header comments for the command syntax.
1238 """
1239
1240 # Initial parsing of the arguments.
1241 c = Draw_Cmd (arg)
1242 if c.errstr:
1243 print (c.errstr)
1244 return False
1245
1246 payload = "(%s,)" % ','.join (c.exprs)
1247
1248 # Generate the function.
1249 # It will be defined as _loopfunc in g.
1250 g = copy.copy (_globals)
1251 ftext = c._make_func (payload)
1252 exec (ftext, g)
1253
1254 # Execute the loop over the data.
1255 c.tuple_o.loop (g['_loopfunc'], c.lo, c.hi)
1256
1257 return True
1258
1259
1260# Dictionary of command handlers.
1261# Should return True if cmd was handled.
1262_cmddict = {'d': draw,
1263 'draw' : draw,
1264 'scan' : scan,
1265 'loop' : loop,
1266 }
1267
1268
1269def cmd (s):
1270 """Process a command S.
1271
1272 Returns True if the command was handled, False otherwise.
1273 See the header comments for the command syntax.
1274 """
1275
1276 ssplit = s.split (None, 1)
1277
1278 if len(ssplit) < 2:
1279 return False
1280
1281 cmd, args = ssplit
1282
1283 func = _cmddict.get (cmd)
1284 if func:
1285 return func (args)
1286
1287 return False
1288
1289
1290
1291
1297
1298#
1299# Hook holding the original value of the exception hook.
1300# But be careful not to overwrite this if this file is reread.
1301#
1302if '_orig_ehook' not in globals():
1303 _orig_ehook = None
1304
1305
1306def _excepthook (exctype, value, traceb):
1307 """Exception hook used by pydraw to process drawing commands."""
1308
1309 # If it's a syntax error, try interpreting as a drawing command.
1310 if isinstance (value, SyntaxError):
1311 val = value.text
1312 if val[-1] == '\n':
1313 val = val[:-1] #pragma: NO COVER
1314 if cmd (val):
1315 # Success --- update root stuff and return.
1316 # (This will swallow the original syntax error.)
1317 ROOT.gInterpreter.EndOfLineAction()
1318 return
1319
1320 # No luck --- pass it on to the original exception handler.
1321 _orig_ehook (exctype, value, traceb)
1322
1323
1325 """Enable entering drawing commands directly at the python prompt."""
1326
1327 # Store the old value of the exception hook (only if we haven't
1328 # done so already).
1329 global _orig_ehook
1330 if _orig_ehook is None:
1331 _orig_ehook = sys.excepthook
1332
1333 # Install our handler.
1334 sys.excepthook = _excepthook
1335 return
1336
1337
1338
1339
1340# import ROOT
1341# import string
1342# import sys
1343# import exceptions
1344# import copy
1345# from PyAnalysisUtils.draw_obj import draw_obj, get_canvas
1346
1347# try:
1348# ScatterH2 = ROOT.RootUtils.ScatterH2
1349# except AttributeError:
1350# ScatterH2 = ROOT.TH2F
1351# print ("WARNING: RootUtils::ScatterH2 not available; using TH2F instead")
1352
1353# kCanRebin = ROOT.TH1.kCanRebin
1354
1355# _last_hist = None
1356# #_marker_style = 3
1357# #_marker_size = 0.5
1358
1359# _evtvar = 'e'
1360
1361
1362
1363
1364
1365
1366# from array import array
1367# class hist_filler:
1368# def __init__ (self, hist, nbuf=100):
1369# self.hist = hist
1370# self.filln = hist.FillN
1371# self.nbuf = nbuf
1372# self.xarr = array ('d', 100*[0.])
1373# self.warr = array ('d', 100*[1.])
1374# self.i = [0]
1375
1376# self.xlist = 100*[0.]
1377# return
1378# def get_filler (self):
1379# def fill (x,i=self.i,nbuf=self.nbuf,xarr=self.xarr,flush=self.flush):
1380# ii = i[0]
1381# if ii == nbuf:
1382# flush()
1383# ii = i[0]
1384# xarr[ii] = x
1385# i[0] = ii + 1
1386# return
1387# return fill
1388# def flush (self):
1389# #print (self.i, self.xarr)
1390# self.filln (self.i[0], self.xarr, self.warr)
1391# self.i[0] = 0
1392# return
1393
1394# ##############################################################################
1395# # Here are the command handlers.
loop(self, f, looplo=0, loophi=sys.maxsize)
Definition pydraw.py:545
_mung_n(self, s1, s2)
Definition pydraw.py:803
_mung_expr_dollar(self, s)
Definition pydraw.py:860
_mung_loop(self, s1, s2)
Definition pydraw.py:817
_mung_index(self, s1, s2)
Definition pydraw.py:773
_make_func(self, payload, extargs='')
Definition pydraw.py:931
_parserange(self, tuple)
Definition pydraw.py:725
_mung_expr_ids(self, s)
Definition pydraw.py:892
loop(self, f, looplo=0, loophi=sys.maxsize)
Definition pydraw.py:518
__init__(self, name)
Definition pydraw.py:572
STL class.
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:130
_get_bins(args, ndim, axis)
Definition pydraw.py:995
_hasCanRebin(h)
Definition pydraw.py:345
_get_hist(ndim, args, hname, htitle)
Definition pydraw.py:1067
_split_outer(haystack, needle)
Definition pydraw.py:481
_find_outer(haystack, needle, ignore_delim=False)
Definition pydraw.py:425
_excepthook(exctype, value, traceb)
Definition pydraw.py:1306
_scan_print(i, *args)
Definition pydraw.py:1189
_sanitize_hname(s)
Definition pydraw.py:371
_setCanRebin(h)
Definition pydraw.py:343
_untokenize(tokens)
Definition pydraw.py:384