#ifndef OBSERVABLE_HH_INCLUDED #define OBSERVABLE_HH_INCLUDED #include #include #include "foreach.hh" #include "TInterpreter.h" #include "stringutil.hh" using stringutil::startswith; using stringutil::to; using stringutil::join; using stringutil::trim; using stringutil::strip_comment_line; using namespace stringutil::coolprinter; /*! An Computable is any number, or boolean, that can be computed from an Event. It is used either as a quantity to plot, or as a cut/selection, or as a weight for the event */ struct Computable { string name; string title; string desc; string expression; int neval; // number of times eval was called (including cache hits) int ncomp; // number of times the function was computed (excluding cache hits) int ntrue; // number of time the computation was non-zero // simple caching: if Evt.id is the same a previous, do no recompute value double _last_value = -123456; int _last_id = -987654321; std::function< double( Evt& ) > compute_func; //! Computable() : neval(0), ncomp(0), ntrue(0) {} string info() { return name + " / " + title + " / " + desc + " / " + expression ; } double eval( Evt& evt, int cache_id ) { if ( cache_id == _last_id ) { neval++; return _last_value; } _last_id = cache_id; return eval( evt ); } /*! compute the value of the Observable for this Event. */ double eval( Evt& evt ) { neval++; //print ("eval", name ); try { _last_value = compute_func( evt ); } catch ( out_of_range ) { print ("warning: out of range detected, return sentinel"); return -9.9e99; } if (_last_value ) ntrue++; return _last_value; } /*! look up name in the symbol table and return a void ptr to it */ void* get_sym( string symbol ) { auto p = gROOT->GetGlobalFunction( symbol.c_str() ); if (!p) return nullptr; return gInterpreter->FindSym( p->GetMangledName() ); } /*! Set the expersession to compute the value of the observable */ void set_expression( string expr ) { expression = expr; string name = "F___" + str(simple_hash(expr)); string c = " double " + name + "(Evt& evt) {return " + expr + ";} "; void *p; while ( ! (p = get_sym( name )) ) { bool okay = gInterpreter->Declare( c.c_str() ); if (!okay) { print ("failed to compile a piece of code"); print ( c ); exit(1); } } typedef double (*fp)(Evt&); compute_func = (fp) p; } virtual string define( string line ) { auto v = split( line, "|" ); for ( auto& s : v ) s = trim(s); if ( v.size() < 3 ) { fatal ("invalid line defining Observable", line ); }; name = v[0]; desc = v[1]; set_expression( v[2] ); // return the remaining string return (v.size() > 3) ? join( "|" , ::slice( v, 3 ) ) : ""; } virtual ~Computable() {} ClassDef(Computable,1) }; /*! Observables are Computables that hava a binning, since the intention is to make histograms */ struct Observable : public Computable { int nbins; double minval,maxval; Observable() {} void set_binning( string s ) { s = trim(s); if ( startswith(s, "irange")) { auto binning = split(s); minval = to (binning[1]) - 0.5 ; maxval = to (binning[2]) + 0.5 ; nbins = to (binning[2]) - to(binning[1]) + 1; print("integer bin range generated",nbins, minval,maxval); } else if (s=="auto") { nbins = 50; minval = 1.; maxval = 0.; return; } else { auto binning = split(s); if ( binning.size() != 3 ) { print (" Wrong binning received for parameter", binning ); fatal(""); } nbins = to ( binning[0] ); minval = to( binning[1] ); maxval = to( binning[2] ); } } /*! Parse a line, containing directive to define an observable. */ virtual string define( string line ) { line = Computable::define( line ); auto v = split( line, "|" ); if ( v.size() > 0) set_binning( v[0] ); return (v.size() > 1) ? join( "|" , ::slice( v, 1 ) ) : ""; } string axis_label() const { return name; } bool operator==( const Observable& other ) { return nbins == other.nbins && minval == other.minval && maxval == other.maxval; } virtual ~Observable() {} ClassDef(Observable,1) }; struct Selection : public Computable { Selection() {} bool require_for_tree_output; // if true, ttress are only written if the selection passes virtual string define( string line ) { line = Computable::define( line ); auto v = split( line, "|" ); if ( v.size() > 0) require_for_tree_output = (v[0]=="true") ? true : false; return (v.size() > 1) ? join( "|" , ::slice( v, 1 ) ) : ""; } virtual ~Selection() {} ClassDef(Selection,1) }; struct Weight : public Computable { Weight() {} virtual ~Weight() {}; ClassDef(Weight,1) }; /*! A variant's eval function is intended to initlialize variables for each events. This funciton is the place to make 'high-level' decission, such as which reco algorithm to use for your rec. tracks. You can have multple variants, which offer fully independent analysis of the events. Optionally, each variant can be written to an output (flat ntuple) tree. */ struct Variant : public Computable { Variant() {} virtual ~Variant() {} ClassDef( Variant, 1) }; template< typename T> T* get_by_name( string name , vector& collection , bool failure_is_an_option = false ) { for ( auto& o : collection ) { if ( o.name == name ) return &o; } if (! failure_is_an_option ) fatal ("failed to find observable/weight/selection with name ", name ); return nullptr; } template< typename T> vector get_by_names( vector names , vector& collection ) { vector r; for ( auto n : names ) { r.push_back( get_by_name( n, collection ) ); } return r; } #endif