def calc_angles( lv_top, lv_lep, Qlep = 1 ): '''Calculated the cosine of the angle of the lepton in the (leptonic) top rest frame relative to three axes: the beam axis, the helicity axis, and the transverse axis''' lv_lep_tag = LV.new( lv_lep ) lv_lep_tag.Boost( -lv_top.BoostVector() ) lv_beam = R.TVector3( 980 * Qlep, 0, 0 ) # haven't checked the sign. Maybe other way around. lv_perp = lv_beam.Cross( lv_top.Vect() ) beam_angle = lv_beam.Angle( lv_lep_tag.Vect() ) helicity_angle = lv_top.Vect().Angle( lv_lep_tag.Vect() ) transverse_angle = lv_perp.Angle( lv_lep_tag.Vect() ) return cos( beam_angle ), cos( helicity_angle ), cos( transverse_angle )
def fit( self, p4_lep, p4_jets, mex, mey, parton_matches, cheat = False, nofit = False, btag_indices = [], btag_vals = [], known_problem_with_lepton = False ): '''Does a kinematic fit given 4-vectors (the p4 inputs), lepton charge, met and btags. The "cheat" option means to consider only the correct assignment (if any). The parton_matches and known_problem_with_lepton are used to cheat and to create debug information If hard_btags constraints are used, only the btag_indices are needed, but the btag_vals may be useful to study tag_prob s. Otherwise, only the full btag_vals are needed. ''' fit_allowed = not nofit Nj = len( p4_jets ) assert Nj >= 4 for ib in btag_indices: assert ib >= 0 and ib < Nj if not self.hard_btags: assert len( btag_vals ) == Nj if self.dbg > 50: print 'rocfit_algebraic.fit inputs are reasonable.' mex = R.TMath.Range( -300, 300, mex ) mey = R.TMath.Range( -300, 300, mey ) raw_nu = LV.new() raw_nu.SetXYZM( mex, mey, 0., 0. ) met = raw_nu.Pt() lv_lep = LV.new() lv_lep.SetXYZM( p4_lep.X(), p4_lep.Y(), p4_lep.Z(), max( 0, p4_lep.M() ) ) for tries in range( 14 ): if lv_lep.M() >= 0: break lv_lep.SetXYZM( p4_lep.X(), p4_lep.Y(), p4_lep.Z(), max( 1E-3 * 2**tries, p4_lep.M() ) ) if tries > 9: print 'Warning: lepton mass insists on being negative. # of tries:',tries Ntag = len( btag_indices ) self.matchable = all_matched( parton_matches ) and not known_problem_with_lepton true_Bs = parton_matches[ 0:2 ] true_Qs = parton_matches[ 2:4 ] j_as_q_ITF = [ self.ITF( p4, False ) for p4 in p4_jets ] j_as_b_ITF = [ self.ITF( p4, True ) for p4 in p4_jets ] j_as_q_eff = [ self.eff( p4, False ) for p4 in p4_jets ] j_as_b_eff = [ self.eff( p4, True ) for p4 in p4_jets ] recos = [] indices = range( 4 ) hf = self.hadFit lf = self.lepFit for iPQH in itertools.permutations(indices,3) : # loop over possible hadronic assignments if iPQH[0]>iPQH[1] : continue # the two equivalent W->qq assignments iL = 6 - reduce( operator.add, iPQH ) if self.hard_btags: if Ntag >= 2: if iPQH[2] not in btag_indices or iL not in btag_indices: continue # b-quarks can only be assigned to tagged jets if Ntag <= 2: if iPQH[0] in btag_indices or iPQH[1] in btag_indices : continue # no spare tag to waste on W->q jets self.perm_is_correct = iPQH[0] in true_Qs and iPQH[1] in true_Qs and \ iPQH[2] == parton_matches[1] and iL == parton_matches[0] if cheat and not self.perm_is_correct: continue tag_prob = self.calc_tag_prob( iPQH, btag_vals ) if self.dbg > 28: print 'DBG29 iPQH:',iPQH,'-> perm_is_correct:',self.perm_is_correct,' tag_prob:',tag_prob self.fill_hist_vars( 'h_tagprob', [ math.log(tag_prob) ] ) lv_W_had = p4_jets[ iPQH[0] ] + p4_jets[ iPQH[1] ] lv_t_had = p4_jets[ iPQH[2] ] + lv_W_had m_W_had = lv_W_had.M() m_t_had = lv_t_had.M() self.fill_hist_vars( 'h_premass', [ m_t_had, m_W_had ] ) this_reco = {'iPQH': iPQH, 'Ptags' : tag_prob } if fit_allowed: if self.dbg > 49: print 'DBG50 will fit iPQH:',iPQH,'jITF0:',j_as_q_ITF[ iPQH[ 0 ] ] perm_jets = [ p4_jets[ i ] for i in iPQH ] perm_ITFs = [ j_as_q_ITF[ iPQH[ 0 ] ], j_as_q_ITF[ iPQH[ 1 ] ], j_as_b_ITF[ iPQH[ 2 ] ] ] perm_effs = [ j_as_q_eff[ iPQH[ 0 ] ], j_as_q_eff[ iPQH[ 1 ] ], j_as_b_eff[ iPQH[ 2 ] ] ] corr_b_jet = LV.new( perm_jets[2] ) self.correct_b_mass( corr_b_jet, perm_ITFs[ 2 ] ) if self.dbg > 9: print 'DBG10 TMP',lv_as_PM_4tup( perm_jets[2] ),' --> ',lv_as_PM_4tup( corr_b_jet ) hf.fit( perm_jets[ 0 ], perm_jets[ 1 ], corr_b_jet, perm_ITFs[ 0 ], perm_ITFs[ 1 ], perm_ITFs[ 2 ], perm_effs[ 0 ], perm_effs[ 1 ], perm_effs[ 2 ] ) ratios = [ hf.ratio( ii ) for ii in range( 3 ) ] delta_HT = reduce( operator.add, map( lambda p4,rat: p4*(self.fcorr * (rat-1)), perm_jets, ratios )) self.fill_hist_vars( 'h_funcalls', [hf.n_calls()] ) if self.dbg > 12: print 'DBG13',iPQH,'perm_jets:',[lv_as_3tup(lv) for lv in perm_jets],\ 'had MLL:',hf.MLL(),'ratios:',ratios,' -->',delta_HT.Print() this_reco.update( { 'hadR' : ratios, 'hadMLL' : hf.MLL(), 'dHT' : delta_HT, 'hadNC' : hf.n_calls(), 'hadW' : hf.fitW(), 'hadT' : hf.fitT(), 'hadConv' : hf._converged } ) if self.prob_track_level > 0: self.fill_hist_vars( 'h_Peff', [hf._effs[0]] ) self.fill_hist_vars( 'h_Qeff', [hf._effs[1]] ) self.fill_hist_vars( 'h_Heff', [hf._effs[2]] ) self.fill_hist_vars( 'h_Pprob', [hf._probs[0]] ) self.fill_hist_vars( 'h_Qprob', [hf._probs[1]] ) self.fill_hist_vars( 'h_Hprob', [hf._probs[2]] ) self.fill_hist_vars( 'h_Pcum', [hf._cums[0]] ) self.fill_hist_vars( 'h_Qcum', [hf._cums[1]] ) self.fill_hist_vars( 'h_Hcum', [hf._cums[2]] ) self.fill_hist_vars( 'h_WH', [hf._res[0]] ) self.fill_hist_vars( 'h_TH', [hf._res[1]] ) if self.prob_track_level > 2: this_reco.update( { 'hadEffs' : hf._effs, 'hadProbs' : hf._probs, 'hadRes' : hf._res, 'hadCums' : hf._cums } ) corr_lep_b_jet = LV.new( p4_jets[iL] ) self.correct_b_mass( corr_lep_b_jet, j_as_b_ITF[iL] ) if self.dbg > 9: print 'DBG10 TMP leptonic ',lv_as_PM_4tup( p4_jets[ iL ] ),' --> ',lv_as_PM_4tup( corr_lep_b_jet ) OK = lf.fit( corr_lep_b_jet, j_as_b_ITF[iL], j_as_b_eff[iL], lv_lep, mex, mey, self.dnu_PDF( met ) ) if not OK: print 'ERROR! leptonic_fitter_algebraic failed!?' return fitNu = lf.fitNu() if self.dbg > 39: print 'DBG40 lepFit has MLL:',lf.MLL(),'.nu:',lv_as_PM_4tup(fitNu),\ '.W:',lv_as_PM_4tup(lf.fitW()),'.T:',lv_as_PM_4tup(lf.fitT()),' --> penalty: ',lf.penalty() elif self.dbg>19: print 'DBG20 lepFit return W & top masses of:',lf.fitW().M(),'&',lf.fitT().M(),' --> penalty: ',lf.penalty() assert lf.penalty() > 0 or lv_lep.P() > 2*self.top_mass or \ (abs( lf.fitW().M() - self.W_mass ) < 1. and abs( lf.fitT().M() - self.top_mass ) < 1.) MLL = kinMLL = hf.MLL() + lf.MLL() if not self.hard_btags: MLL -= math.log( tag_prob ) self.fill_hist_vars( 'h_lep_funcalls', [lf.n_calls()] ) dnu = raw_nu - fitNu this_reco.update( {'nu' : fitNu, 'lepW' : lf.fitW(), 'lepT' : lf.fitT(), 'lepB' : lf.param_value(), 'lepL' : 1.0, 'lepX' : dnu.X(), 'lepY' : dnu.Y(), 'lepMLL' : lf.MLL(), 'kinMLL' : kinMLL, 'MLL' : MLL, 'lepNC' : lf.n_calls(), 'penalty' : lf.penalty(), 'lepConv': lf._converged, 'swap' : lf._swapped } ) if self.prob_track_level > 0: self.fill_hist_vars( "h_Beff", [lf._eff] ) self.fill_hist_vars( "h_Bprob", [lf._probs[0]] ) self.fill_hist_vars( "h_Bcum", [lf._cums[0]] ) self.fill_hist_vars( "h_Xprob", [lf._probs[1]] ) self.fill_hist_vars( "h_Yprob", [lf._probs[2]] ) self.fill_hist_vars( "h_Xcum", [lf._cums[1]] ) self.fill_hist_vars( "h_Ycum", [lf._cums[2]] ) if self.prob_track_level > 2: this_reco.update( { 'lepEffs' : [lf._eff], 'lepProbs' : lf._probs, 'lepCums' : lf._cums } ) recos.append( this_reco ) if self.dbg>89: print 'DBG90 this_reco:',sorted(this_reco.items()) if fit_allowed: if self.hard_btags: recos.sort( key=lambda assignment: assignment[ 'kinMLL' ] ) else: recos.sort( key=lambda assignment: assignment[ 'MLL' ] ) return recos
def reco( self, lv_lep, lv_jets, mex, mey, true_leptonic_b_index, cheat = False, btag_vals = [], known_problem_with_lepton = False ): '''Does a partial reconstruction of ttbar --> ( b (l nu) ) [ b q ... ]. The "cheat" option means to consider only the correct assignment (if any). The parton_matches and known_problem_with_lepton are used to cheat and to create debug information ''' Nj = len( lv_jets ) assert Nj == 3 assert (not cheat) or true_leptonic_b_index in range(3) dbg = self.dbg if dbg > 50: print 'partial_reco inputs are reasonable.' self.matchable = true_leptonic_b_index >= 0 and not known_problem_with_lepton # prepare the observables (one per reco-assignments) SR = self.SR OK = SR.reco_nu( mex, mey, lv_lep ) if not OK: raise Exception( 'Unexpected failure of simple_reco::reco_nu' ) if SR.two_solutions() : lv_nu_pzs = [ SR.nu(), SR.alt_nu() ] if dbg > 29: print 'DBG30PR SR(',mex,',',mey,',',U.lv_as_EPM_str(lv_lep),') -> '\ '2?',self.SR.two_solutions(),'nu:',U.lv_as_3tup(SR.nu()),'/',U.lv_as_3tup(SR.alt_nu()) lv_nu_iLs = [] lv_tl_iLs = [] mtls = [] mps = [] lv_3j = sum( lv_jets, LV.new() ) for iL in range( 3 ) : self.perm_is_correct = iL == true_leptonic_b_index lv_LBL = lv_jets[ iL ] + lv_lep if self.SR.two_solutions() : leptonic_tops = [ lv_LBL + lv for lv in lv_nu_pzs ] dmtops = [ abs( self.top_mass - lv.M() ) for lv in leptonic_tops ] iNu = 0 if dmtops[0] < dmtops[1] else 1 if dbg > 18: print 'DBG19PR iL:',iL,'-> perm_is_correct:',self.perm_is_correct,', dmtops:',dmtops,'-> iNu:',iNu lv_nu_iLs.append( lv_nu_pzs[ iNu ] ) lv_tl = leptonic_tops[ iNu ] else: lv_nu = self.SR.nu() lv_nu_iLs.append( lv_nu ) lv_tl = lv_LBL + lv_nu if dbg > 14: print 'DBG15PR iL:',iL,'-> nu:',U.lv_as_EPM_str(lv_nu_iLs[-1]),', tl:',U.lv_as_EPM_str(lv_tl) lv_tl_iLs.append( lv_tl ) mtls.append( lv_tl.M() ) mps.append( ( lv_3j - lv_jets[ iL ] ).M() ) # loop over statistics-assignments recos = [] for iL in range( 3 ) : self.perm_is_correct = iL == true_leptonic_b_index if cheat and not self.matchable and self.perm_is_correct: continue lv_nu = lv_nu_iLs[ iL ] lv_tl = lv_tl_iLs[ iL ] for iH in range( 3 ) : # loop over hadronic b assignment if iH == iL : continue iW = 3 - iL - iH iLHW = [iL,iH,iW] # probabilities for hypothesis H: lhq (i.e. a jet from the hadronic W is missing) prob_t_H = U.hist_value_at( self.h_mt_l, mtls[ iL ] ) * \ U.hist_value_at( self.h_mt_h, mtls[ iH ] ) * \ U.hist_value_at( self.h_mt_q, mtls[ iW ] ) prob_p_H = U.hist_value_at( self.h_mp_hq, mps[ iL ] ) * \ U.hist_value_at( self.h_mp_lq, mps[ iH ] ) * \ U.hist_value_at( self.h_mp_hl, mps[ iW ] ) prob_tag_H = self.calc_tag_prob_H( iW, btag_vals ) prob_H = prob_t_H * prob_p_H * prob_tag_H # probabilities for hypothesis Q: lqq (i.e. the hadronic b is missing) prob_t_Q = U.hist_value_at( self.h_mt_l, mtls[ iL ] ) * \ U.hist_value_at( self.h_mt_q, mtls[ iH ] ) * \ U.hist_value_at( self.h_mt_q, mtls[ iW ] ) prob_p_Q = U.hist_value_at( self.h_mp_qq, mps[ iL ] ) * \ U.hist_value_at( self.h_mp_lq, mps[ iH ] ) * \ U.hist_value_at( self.h_mp_lq, mps[ iW ] ) prob_tag_Q = self.calc_tag_prob_Q( iL, btag_vals ) prob_Q = prob_t_Q * prob_p_Q * prob_tag_Q if dbg > 28: print 'DBG29PR iLHW:',iLHW,'-> prob_tag_H:',prob_tag_H,'prob_tag_Q:',prob_tag_Q prob = self.fQ * prob_Q + ( 1-self.fQ ) * prob_H postriori_fQ = ( self.fQ * prob_Q / prob ) if prob > 0 else -1 lv_th_proxy = lv_jets[ iH ] + lv_jets[ iW ] xx = R.TMath.Range( 0.2, 1.2, 100 / lv_th_proxy.E() ) alpha = 0.6500 + 0.7076 * xx lv_ttbar = lv_tl + lv_th_proxy * alpha recos.append( { 'iLHW' : iLHW, 'tagH' : prob_tag_H, 'tagQ' : prob_tag_Q, 'tl' : lv_tl, 'proxy' : lv_th_proxy, 'nu' : lv_nu, 'alpha' : alpha, 'ttbar' : lv_ttbar, 'cmtt' : ( lv_ttbar.M() - 64.44 ) / 0.7596, 'fQ' : postriori_fQ, 'prob' : prob } ) if dbg > 34: print 'DBG35PR alpha:',alpha,'prob_Q:',prob_Q,'prob_H',prob_H recos.sort( key=lambda reco: - reco[ 'prob' ] ) denom = sum( [ reco['prob'] for reco in recos ] ) if denom <= 0: if dbg : print 'Information (DBG1): partial reco probs are non-positive:',\ [ reco['prob'] for reco in recos ],'(legitimate as long as it is rare)' averages = self.averagables_array( recos[0], lv_lep ) [1:] else: cands = [ self.averagables_array( reco, lv_lep ) for reco in recos ] if dbg > 21 : print 'DBG22 partial_reco\'s cands for averaging are',cands weighted_cands = [ [cand[0]*obs for obs in cand[1:] ] for cand in cands ] averages = [ sum(column)/denom for column in zip( *weighted_cands ) ] if dbg > 14 : print 'DBG15 partial_reco\'s averages:',averages return recos, { 'mtt': averages[ 0 ], 'yl': averages[ 1 ], 'yh': averages[ 2 ], 'cb' : averages[ 3 ], 'ch': averages[ 4 ], 'ct': averages[ 5 ] }