import aa, ROOT import os,sys, itertools, copy, collections, pickle from array import array from math import * def integrate( func , x0, x1 , dx = 0.01 , jacobian = lambda dx : dx): c = 0 for x in aa.frange( x0, x1, dx ) : c += func( x ) * jacobian( dx ) return c class BackgroundModel : """ The background model consists = N x F(sindecl) x F(logE) [ sr-1 ] The two functions are derived from a histogram of the data. The former is a spline, the latter consists of two Gaussians that are fit to the distribution. The constant N is chosen so that the integral over the energy range and the over the sphere (2 pi * \int decl from -1 to 1 ) is exactly the number of event in the data-sample. """ def dp_dOmega( self, sindecl ) : "pdf of sin(decl) per sr" return self.decl_norm * self.decl_spline.Eval( sindecl ) def dp_dlogE( self, logE ) : "pdf per unit log10(E)" return self.loge_norm * self.loge_func.Eval( logE ) def eval( self, sindecl, logE ) : "number of backgournd events per sr and per unit logE" r = self.norm * self.dp_dOmega( sindecl ) * self.dp_dlogE( logE ) return r def __init__( self , hist ) : "histogram has decl,ra,logE on x,y,z axes " self.hdec = hist.ProjectionX() self.hra = hist.ProjectionY(); self.hra.SetMinimum(1e-3) self.hloge = hist.ProjectionZ(); self.hloge.SetMinimum(1e-3) # extract spline for sin-decl xs,ys = array('d'), array('d') hdec = self.hdec # for b in range(1,hdec.GetNbinsX()+1): # if b == 1 : x = hdec.GetXaxis().GetBinLowEdge(b) # elif b == hdec.GetNbinsX() : x = hdec.GetXaxis().GetBinUpEdge(b) # else : x = hdec.GetXaxis().GetBinCenter(b) # xs.append ( x ) # ys.append ( hdec.GetBinContent( b )) #self.decl_spline = ROOT.TSpline3( "decl_dependence", xs, ys, len(xs) , "e1b1" ) self.decl_spline = ROOT.TSpline3() ROOT.IntSpline(hdec,self.decl_spline) self.decl_spline.SetLineWidth(2) self.decl_spline.SetLineColor(2) dectest = hdec dectest.Scale(1/hdec.Integral()/hdec.GetBinWidth(0)) dectest.Draw() self.decl_spline.Draw("SAME") self.hdec.Write() # Define and fit the function for the energy dependence self.loge_func = ROOT.TF1("double_gaus", "gaus(0) + gaus(3)", 1, 8) self.loge_func.SetParNames("norm1", "mean1", "sigma1","norm2", "mean2", "sigma2") self.loge_func.SetParameters( 9e2,4,0.5, 100,2,1) self.hloge.Fit( self.loge_func ,"0") #pffff fix root on the spot ROOT.TSpline.Integral = lambda s,a,b : sum( s.Eval(x)*1e-3 for x in aa.frange(a,b,1e-3) ) # normalisation # decl_norm * spline = pdf (sr-1) print("check int is (1):", self.decl_spline.Integral(-1,1)) self.decl_norm = 1.0 / ( 2 * pi * self.decl_spline.Integral(-1,1) ) # loge_norm * loge_func = pdf ( per unit of log10(E) ) self.loge_norm = 1.0 / ( self.loge_func.Integral(1,8) ) self.norm = hist.Integral() print ( " ---------------- check integral loge : " , integrate( self.dp_dlogE, 1,8,0.01 ) ) print ( " ---------------- check integral decl : " , integrate( self.dp_dOmega , -1,1,0.01, lambda dsindecl : 2*pi*dsindecl ) ) def plot( self , canvname = "c1") : c = ROOT.TCanvas( canvname , canvname ,1200,400) c.Divide(3,1); c.cd(1) self.hdec.SetMinimum(0) self.hdec.Draw() self.decl_spline.Draw("same") c.cd(2) self.hra.Draw() self.hra.Fit("pol0") # for illustration only c.cd(3) ROOT.gPad.SetLogy() self.hloge.Draw() self.loge_func.Draw("same") return c def write_to_webpage( self, W ) : W.write ("background model
") W.write( self.plot() ) W.write( "
check integral loge : " +str( integrate( self.dp_dlogE, 1,8,0.01 ) )) W.write( "
check integral decl : " +str( integrate( self.dp_dOmega, -1,1,0.01, lambda dsindecl : 2*pi*dsindecl ) )) class DataSample : def __init__ ( self ) : self.name = "noname" self.events = [] self.binning = ( 10, -1,1, 20,0,2*pi, 14, 1,8 ) self.version_to_use = 7 def gettrk( self, evt ) : if self.version_to_use == 6 : return evt.trks[0] if self.version_to_use == 7 : return evt.trks[2] if evt.trks.size()>2 else None print ("unknown version") sys.exit() def missing_runs( self ) : if self.version_to_use == 6 : return [] if self.version_to_use == 7 : return self.missing_v7runs print ("unknown version") sys.exit() def read_dst_file( self, filename ) : self.det = ROOT.Det() self.det.longitude = 0.278819; self.det.latitude = 0.633407; self.det.meridian_convergence_angle = 0.0100733; ax = ";sin(#delta);ra;log(E_{rec}/GeV)" self.hist = ROOT.TH3D("dhist"+self.name,"data-"+self.name+ax, *self.binning) f = ROOT.EventFile( filename ) self.src = filename self.events = [ copy.copy( evt ) for evt in f ] def compute_coords(self, scramble = False ) : print ("computing celestical coordinates using version ", self.version_to_use ) self.data_is_scrambled = scramble for i,evt in enumerate( self.events ): trk = self.gettrk( evt ) if not trk or trk.dir.z < -0.1 : evt.eq = None else : t = self.events[i-1].t if scramble else evt.t evt.eq = ROOT.EquatorialCoords( self.det, trk.dir , t ) def fill_hist( self ) : self.hist.Reset() for evt in self.events : # nb: always use the v6 version of energy, since this is what is in the IRFs if evt.eq == None : continue self.hist.Fill( sin(evt.eq.dec()), evt.eq.ra(), log10( evt.trks[0].E ) ) print ( "read",len( self.events ),"events. histogram integral = ", self.hist.Integral() ) def events_table(self ) : T = ROOT.Table("number","run","time","dir.z","E", "ra","dec") for i,evt in enumerate( self.events ) : T.add( i, evt.run_id, evt.t.AsString()[:26], evt.trks[0].dir.z, evt.trks[0].E , evt.eq.dec(), evt.eq.ra() ) return T def skymap( self , scramble = False ): csky = ROOT.TCanvas("csky","csky",900,500) s = ROOT.SkyMap() m = ROOT.TMarker() m.SetMarkerSize(0.6) m.SetMarkerStyle(20) for i,e in enumerate( self.events ) : if not e.eq : continue s.add( m, e.eq ) if self.data_is_scrambled : s.add ( ROOT.TLatex( 0,0, "scrambled ")) s.Draw() return csky def plot( self , canvname = "cx") : hx1 = ROOT.TH1D("hx1","zenith angle;cos(zenith);events",20,-1,1) hx2 = ROOT.TH1D("hx2","phi angle;#phi;events",20,-pi,pi) for evt in self.events: hx1.Fill( evt.trks[0].dir.z ) hx2.Fill( evt.trks[0].dir.phi() ) cx = ROOT.TCanvas( canvname , canvname ,800,400) cx.Divide(2,1); for h in [hx1,hx2] : h.SetLineWidth(2) h.SetLineColor(4) h.SetMinimum(0) cx.cd(1) hx1.DrawCopy() cx.cd(2) hx2.DrawCopy() return cx def info_table( self ) : self.nevents_with_v7 = sum( evt.trks.size()>2 for evt in self.events ) self.nevents = len( self.events ) self.nmissing_v7 = len (self.missing_events_in_v7) self.v7downgoing = sum ( 1 for e in self.events if self.gettrk(e) and self.gettrk(e).dir.z<-0.1) t = ROOT.Table("attribute","value") for k,v in self.__dict__.items(): t.add( k ).add( str(v)[:99] ) t.title = 'DataSample' + self.name return t def write_to_webpage( self, W , scramble = True ) : T = ROOT.Table("attribuute","value") T.add( "sample name", self.name ) T.add(" number of events", len( self.events )) T.add(" histogram integral", self.hist.Integral() ) T.add(" events with coords", sum ( 1 for e in self.events if e.eq != None )) T.title ="data sample" W.write(T) W.write( self.info_table() ) W.write( self.plot() ) W.write( self.skymap( scramble ) ) def get_v7( dset, v7dir ) : """ this augments the events with the jpp muon track from another (v7) production. The new track is goind at position trks[2] the datasample object also get the following atrributes: dset.missing_v7runs # to be used for livetime corrections later dset.processed_v7runs dset.missing_events_in_v7 """ def idef( evt ) : return str(evt.frame_index) + " " + str(evt.trigger_counter) + " " + evt.t.AsString() dset.missing_v7runs = [] dset.processed_v7runs = [] dset.missing_events_in_v7 = [] # only those not already in missing v7_runs M = collections.defaultdict( list ) for evt in dset.events : M[evt.run_id].append(evt) runs = M.keys() fns = {} for run in runs : retr = "retr." if dset.name == 'P75.1' else "" rr = "%08d" % (run,) fn = f"{v7dir}/datav7.0.{retr}jchain.arca.aashower.{rr}.root" ok = os.path.exists( fn ) if ok : fns[run] = fn else : fns[run] = None dset.missing_v7runs.append(run) print ( run, "ok" if ok else " MISSING ", fn ) for i,run in enumerate( M ) : fn = fns[run] print ("run ", i, "of", len(M), run, fn ) if not fn: continue f = ROOT.EventFile( fn ) print ("building index for", run ) f.roottree().BuildIndex("frame_index","trigger_counter") print ("done") for evt in M[run] : print ( 'search', idef(evt)) nb = f.roottree().GetEntryWithIndex( evt.frame_index , evt.trigger_counter ) if nb < 1 : print ( "failed to find event with", evt.frame_index , evt.trigger_counter ) dset.missing_events_in_v7.append( evt ) else : evt.trks.resize(3) try : evt.trks[2] = ROOT.get_best_reconstructed_jppmuon( f.evt ) evt.trks[2].comment = "jpp muon from v7 production" except ROOT.Exception: print ( "no jpp muon found in event ", evt ) dset.missing_events_in_v7.append( evt ) print ( ' found', idef(evt) ) print() dset.processed_v7runs.append( run ) f0 = "/sps/km3net/users/rmuller/production/dst/arca6_data/v6.2/datav6.2.jchain.aashower.dst.merged_9635_10005_pre_upm01_antinoise_upaam01.root" f1 = "/sps/km3net/users/rmuller/production/dst/arca6_data/v6.2/datav6.2.retr.jchain.aashower.dst.merged_10046_10286_pre_upm01_antinoise_upaam01.root" def prepare_dataset( name , dst_file ): "after this, you can simply load the pickle to have your datasamples ready to go" D1 = DataSample() D1.name = name D1.read_dst_file ( dst_file ) get_v7( D1, "/sps/km3net/repo/data_processing/tag/v6_ARCA6_run_by_run/prod/data/KM3NeT_00000075/v7.0/reco/datav7.0.jchain.arca.00009743.root" ) with open ( D1.name+".datasample.pickle", 'wb' ) as ofile : pickle.dump ( D1 , ofile ) if __name__ == "__main__" : D1 = prepare_dataset("P75.1", f1 ) D0 = prepare_dataset("P75.0", f0 ) sys.exit()