go-hep.org/x/hep@v0.38.1/fads/calo.go (about)

     1  // Copyright ©2017 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fads
     6  
     7  import (
     8  	"fmt"
     9  	"math"
    10  	"math/rand/v2"
    11  	"reflect"
    12  	"sort"
    13  	"sync"
    14  
    15  	"go-hep.org/x/hep/fwk"
    16  	"gonum.org/v1/gonum/stat/distuv"
    17  )
    18  
    19  type EtaPhiBin struct {
    20  	EtaBins []float64
    21  	PhiBins []float64
    22  }
    23  
    24  type EtaPhiGrid struct {
    25  	eta []float64
    26  	phi map[float64][]float64
    27  }
    28  
    29  func NewEtaPhiGrid(bins []EtaPhiBin) EtaPhiGrid {
    30  
    31  	neta := 0
    32  	for _, bin := range bins {
    33  		nn := len(bin.EtaBins)
    34  		if nn > neta {
    35  			neta = nn
    36  		}
    37  	}
    38  
    39  	grid := EtaPhiGrid{
    40  		eta: make([]float64, 0, neta),
    41  		phi: make(map[float64][]float64, neta),
    42  	}
    43  
    44  	for _, bin := range bins {
    45  		for _, eta := range bin.EtaBins {
    46  			phibins, ok := grid.phi[eta]
    47  			if !ok {
    48  				phibins = make([]float64, 0, len(bin.PhiBins))
    49  				grid.phi[eta] = phibins
    50  				grid.eta = append(grid.eta, eta)
    51  			}
    52  			sort.Float64s(phibins)
    53  			for _, phi := range bin.PhiBins {
    54  				i := sort.SearchFloat64s(phibins, phi)
    55  				if !(i < len(phibins) && phibins[i] == phi) {
    56  					phibins = append(phibins, phi)
    57  					sort.Float64s(phibins)
    58  				}
    59  			}
    60  			grid.phi[eta] = phibins
    61  		}
    62  	}
    63  
    64  	sort.Float64s(grid.eta)
    65  
    66  	return grid
    67  }
    68  
    69  // EtaPhiIndex returns the eta/phi bin indices corresponding to a given eta/phi pair.
    70  // EtaPhiIndex returns false if no bin contains this eta/phi pair.
    71  func (grid *EtaPhiGrid) EtaPhiIndex(eta, phi float64) (int, int, bool) {
    72  	// find eta bin
    73  	etaidx := sort.SearchFloat64s(grid.eta, eta)
    74  	if !(etaidx < len(grid.eta)) {
    75  		return -etaidx, 0, false
    76  	}
    77  	etabin := grid.eta[etaidx]
    78  	// special case of lowest-edge: test whether eta is inside acceptance
    79  	if etaidx == 0 && etabin > eta {
    80  		return -1, 0, false
    81  	}
    82  
    83  	phibins := grid.phi[etabin]
    84  
    85  	// find phi bin
    86  	phiidx := sort.SearchFloat64s(phibins, phi)
    87  	if !(phiidx < len(phibins)) {
    88  		return etaidx, -phiidx, false
    89  	}
    90  	//phibin := phibins[phiidx]
    91  
    92  	return etaidx, phiidx, true
    93  }
    94  
    95  // EtaPhiBin returns the eta/phi bin center corresponding to a given eta/phi index pair.
    96  func (grid *EtaPhiGrid) EtaPhiBin(ieta, iphi int) (float64, float64, bool) {
    97  	var etac float64
    98  	var phic float64
    99  
   100  	if ieta > len(grid.eta) || ieta <= 0 {
   101  		return etac, phic, false
   102  	}
   103  	eta := grid.eta[ieta]
   104  	etac = 0.5 * (grid.eta[ieta-1] + eta)
   105  
   106  	phibins := grid.phi[eta]
   107  	if iphi > len(phibins) || iphi <= 0 {
   108  		return etac, phic, false
   109  	}
   110  
   111  	phi := phibins[iphi]
   112  	phic = 0.5 * (phibins[iphi-1] + phi)
   113  
   114  	return etac, phic, true
   115  }
   116  
   117  type etwData struct {
   118  	Ene        float64
   119  	Time       float64
   120  	WeightTime float64
   121  }
   122  
   123  func (etw *etwData) Add(ene, t float64) {
   124  	etw.Ene += ene
   125  	sqrt := math.Sqrt(ene)
   126  	etw.Time += sqrt * t
   127  	etw.WeightTime += sqrt
   128  }
   129  
   130  type caloTrack struct {
   131  	ECal etwData
   132  	HCal etwData
   133  }
   134  
   135  type caloTower struct {
   136  	Eta   float64
   137  	Phi   float64
   138  	Edges [4]float64
   139  
   140  	ECal etwData
   141  	HCal etwData
   142  
   143  	TrackHits  int
   144  	PhotonHits int
   145  }
   146  
   147  type EneFrac struct {
   148  	ECal float64
   149  	HCal float64
   150  }
   151  
   152  type Calorimeter struct {
   153  	fwk.TaskBase
   154  
   155  	efrac map[int]EneFrac
   156  	bins  EtaPhiGrid
   157  
   158  	//etaphibins []EtaPhiBin
   159  	ecalres func(eta, ene float64) float64
   160  	hcalres func(eta, ene float64) float64
   161  
   162  	particles   string
   163  	tracks      string
   164  	towers      string
   165  	photons     string
   166  	eflowtracks string
   167  	eflowtowers string
   168  
   169  	seed uint64
   170  	src  *rand.Rand
   171  
   172  	gauss distuv.Normal
   173  	dmu   sync.Mutex
   174  }
   175  
   176  func (tsk *Calorimeter) Configure(ctx fwk.Context) error {
   177  	var err error
   178  
   179  	err = tsk.DeclInPort(tsk.particles, reflect.TypeOf([]Candidate{}))
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	err = tsk.DeclInPort(tsk.tracks, reflect.TypeOf([]Candidate{}))
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	err = tsk.DeclOutPort(tsk.towers, reflect.TypeOf([]Candidate{}))
   190  	if err != nil {
   191  		return err
   192  	}
   193  
   194  	err = tsk.DeclOutPort(tsk.photons, reflect.TypeOf([]Candidate{}))
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	err = tsk.DeclOutPort(tsk.eflowtracks, reflect.TypeOf([]Candidate{}))
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	err = tsk.DeclOutPort(tsk.eflowtowers, reflect.TypeOf([]Candidate{}))
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	tsk.src = rand.New(rand.NewPCG(tsk.seed, tsk.seed))
   210  	tsk.gauss = distuv.Normal{Mu: 0, Sigma: 1, Src: tsk.src}
   211  	return err
   212  }
   213  
   214  func (tsk *Calorimeter) StartTask(ctx fwk.Context) error {
   215  	var err error
   216  
   217  	return err
   218  }
   219  
   220  func (tsk *Calorimeter) StopTask(ctx fwk.Context) error {
   221  	var err error
   222  
   223  	return err
   224  }
   225  
   226  func (tsk *Calorimeter) Process(ctx fwk.Context) error {
   227  	var err error
   228  
   229  	store := ctx.Store()
   230  	msg := ctx.Msg()
   231  
   232  	v, err := store.Get(tsk.particles)
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	parts := v.([]Candidate)
   238  	msg.Debugf(">>> particles: %v\n", len(parts))
   239  
   240  	v, err = store.Get(tsk.tracks)
   241  	if err != nil {
   242  		return err
   243  	}
   244  	tracks := v.([]Candidate)
   245  	msg.Debugf(">>> tracks: %v\n", len(tracks))
   246  
   247  	towers := make([]Candidate, 0, len(tracks))
   248  	defer func() {
   249  		err = store.Put(tsk.towers, towers)
   250  	}()
   251  
   252  	photons := make([]Candidate, 0, len(tracks))
   253  	defer func() {
   254  		err = store.Put(tsk.photons, photons)
   255  	}()
   256  
   257  	eflowtracks := make([]Candidate, 0, len(tracks))
   258  	defer func() {
   259  		err = store.Put(tsk.eflowtracks, eflowtracks)
   260  	}()
   261  
   262  	eflowtowers := make([]Candidate, 0, len(tracks))
   263  	defer func() {
   264  		err = store.Put(tsk.eflowtowers, eflowtowers)
   265  	}()
   266  
   267  	hits := make(map[int64][]int64)
   268  	twrecal := make([]float64, 0, len(parts))
   269  	twrhcal := make([]float64, 0, len(parts))
   270  	trkecal := make([]float64, 0, len(tracks))
   271  	trkhcal := make([]float64, 0, len(tracks))
   272  
   273  	// process particles
   274  	for i := range parts {
   275  		part := &parts[i]
   276  		// msg.Debugf("part[%d]=%#v\n", i, part.Pos)
   277  
   278  		abspid := part.Pid
   279  		if abspid < 0 {
   280  			abspid = -abspid
   281  		}
   282  
   283  		frac, ok := tsk.efrac[int(abspid)]
   284  		if !ok {
   285  			frac = tsk.efrac[0]
   286  		}
   287  
   288  		twrecal = append(twrecal, frac.ECal)
   289  		twrhcal = append(twrhcal, frac.HCal)
   290  
   291  		if frac.ECal < 1e-9 && frac.HCal < 1e-9 {
   292  			// msg.Debugf("part[%d]= not enough ECal|HCal\n", i)
   293  			continue
   294  		}
   295  
   296  		// find eta/phi bin
   297  		etabin, phibin, ok := tsk.bins.EtaPhiIndex(part.Pos.Eta(), part.Pos.Phi())
   298  		if !ok {
   299  			// msg.Debugf("part[%d]= not in acceptance\n", i)
   300  			continue
   301  		}
   302  
   303  		flags := int64(0)
   304  		if abspid == 11 || abspid == 22 {
   305  			flags |= (int64(1)) << 1
   306  		}
   307  
   308  		// make tower hit:
   309  		// {16-bits: eta bin-id} {16-bits: phi bin-id} {8-bits: flags}
   310  		// {24-bits: particle number}
   311  		hit := (int64(etabin) << 48) | (int64(phibin) << 32) | (int64(flags) << 24) | int64(i)
   312  		towerid := hit >> 32
   313  		hits[towerid] = append(hits[towerid], hit)
   314  	}
   315  
   316  	// process tracks
   317  	for i := range tracks {
   318  
   319  		track := &tracks[i]
   320  		// msg.Debugf("track[%d]=%#v\n", i, track.Pos)
   321  		abspid := track.Pid
   322  		if abspid < 0 {
   323  			abspid = -abspid
   324  		}
   325  
   326  		frac, ok := tsk.efrac[int(abspid)]
   327  		if !ok {
   328  			frac = tsk.efrac[0]
   329  		}
   330  
   331  		trkecal = append(trkecal, frac.ECal)
   332  		trkhcal = append(trkhcal, frac.HCal)
   333  
   334  		if frac.ECal < 1e-9 && frac.HCal < 1e-9 {
   335  			// msg.Debugf("track[%d]= not enough ECal|HCal\n", i)
   336  			continue
   337  		}
   338  
   339  		// find eta/phi bin
   340  		etabin, phibin, ok := tsk.bins.EtaPhiIndex(track.Pos.Eta(), track.Pos.Phi())
   341  		if !ok {
   342  			// msg.Debugf("track[%d]= not in acceptance\n", i)
   343  			continue
   344  		}
   345  
   346  		flags := int64(1)
   347  
   348  		// make tower hit:
   349  		// {16-bits: eta bin-id} {16-bits: phi bin-id} {8-bits: flags}
   350  		// {24-bits: track number}
   351  		hit := (int64(etabin) << 48) | (int64(phibin) << 32) | (int64(flags) << 24) | int64(i)
   352  		towerid := hit >> 32
   353  		hits[towerid] = append(hits[towerid], hit)
   354  	}
   355  
   356  	nhits := 0
   357  	twrhits := make([]int64, 0, len(hits))
   358  	for towerid, hits := range hits {
   359  		// hits are sorted first by eta bin-id, then phi bin-id,
   360  		// then flags and then by particle or track number
   361  		sort.Sort(int64Slice(hits))
   362  		twrhits = append(twrhits, towerid)
   363  		nhits += len(hits)
   364  	}
   365  
   366  	// hits are sorted first by eta bin-id, then phi bin-id,
   367  	// then flags and then by particle or track number
   368  	sort.Sort(int64Slice(twrhits))
   369  
   370  	msg.Debugf("tower-hits: %d (%d)\n", nhits, len(twrhits))
   371  
   372  	// process hits
   373  	for _, towerid := range twrhits {
   374  		iphi := (towerid >> 00) & 0x000000000000FFFF
   375  		ieta := (towerid >> 16) & 0x000000000000FFFF
   376  
   377  		// get eta/phi of tower's center
   378  		eta, phi, ok := tsk.bins.EtaPhiBin(int(ieta), int(iphi))
   379  		if !ok {
   380  			return fmt.Errorf("calorimeter: no valid eta/phi bin (ieta=%d iphi=%d)", ieta, iphi)
   381  		}
   382  
   383  		etabins := tsk.bins.eta
   384  		phibins := tsk.bins.phi[etabins[ieta]]
   385  
   386  		calotower := caloTower{
   387  			Eta: eta,
   388  			Phi: phi,
   389  			Edges: [4]float64{
   390  				etabins[ieta-1],
   391  				etabins[ieta],
   392  				phibins[iphi-1],
   393  				phibins[iphi],
   394  			},
   395  		}
   396  
   397  		var (
   398  			calotrk = caloTrack{}
   399  			tower   Candidate
   400  			//twrtrks = make([]Candidate, 0, len(tracks)) // FIXME(sbinet)
   401  		)
   402  
   403  		for _, hit := range hits[towerid] {
   404  			flags := (hit >> 24) & 0x00000000000000FF
   405  			n := hit & 0x0000000000FFFFFF
   406  			// etaphi := hit >> 32
   407  
   408  			switch {
   409  			case (flags & 1) != 0: // track hits
   410  				calotower.TrackHits++
   411  				var (
   412  					track = &tracks[n]
   413  					ene   = track.Mom.E()
   414  					t     = track.Pos.T()
   415  				)
   416  				calotrk.ECal.Add(ene*trkecal[n], t)
   417  				calotrk.HCal.Add(ene*trkhcal[n], t)
   418  				//twrtrks = append(twrtrks, *track) // FIXME(sbinet)
   419  
   420  			default:
   421  				if (flags & 2) != 0 { // photon hits
   422  					calotower.PhotonHits++
   423  				}
   424  
   425  				var (
   426  					part = &parts[n]
   427  					ene  = part.Mom.E()
   428  					t    = part.Pos.T()
   429  				)
   430  				calotower.ECal.Add(ene*twrecal[n], t)
   431  				calotower.HCal.Add(ene*twrhcal[n], t)
   432  				tower.Add(part)
   433  			}
   434  			// msg.Debugf("hit=0x%x >> flags=0x%x, n=%d\n", hit, flags, n)
   435  		}
   436  
   437  		ecalSigma := tsk.ecalres(calotower.Eta, calotower.ECal.Ene)
   438  		ecalEne := tsk.lognormal(calotower.ECal.Ene, ecalSigma)
   439  		ecalTime := 0.0
   440  		if calotower.ECal.WeightTime >= 1e-9 {
   441  			ecalTime = calotower.ECal.Time / calotower.ECal.WeightTime
   442  		}
   443  
   444  		hcalSigma := tsk.hcalres(calotower.Eta, calotower.HCal.Ene)
   445  		hcalEne := tsk.lognormal(calotower.HCal.Ene, hcalSigma)
   446  		hcalTime := 0.0
   447  		if calotower.HCal.WeightTime >= 1e-9 {
   448  			hcalTime = calotower.HCal.Time / calotower.HCal.WeightTime
   449  		}
   450  
   451  		ene := ecalEne + hcalEne
   452  		esqrt := math.Sqrt(ecalEne)
   453  		hsqrt := math.Sqrt(hcalEne)
   454  		time := (esqrt*ecalTime + hsqrt*hcalTime) / (esqrt + hsqrt)
   455  
   456  		eta = rand.Float64()*(calotower.Edges[1]-calotower.Edges[0]) + calotower.Edges[0]
   457  		phi = rand.Float64()*(calotower.Edges[3]-calotower.Edges[2]) + calotower.Edges[2]
   458  
   459  		pt := ene / math.Cosh(eta)
   460  
   461  		tower.Pos = newPtEtaPhiE(1, eta, phi, time)
   462  		tower.Mom = newPtEtaPhiE(pt, eta, phi, ene)
   463  		tower.Eem = ecalEne
   464  		tower.Ehad = hcalEne
   465  
   466  		tower.Edges = calotower.Edges
   467  
   468  		if ene > 0 {
   469  			if calotower.PhotonHits > 0 && calotower.TrackHits == 0 {
   470  				photons = append(photons, tower)
   471  			}
   472  			towers = append(towers, tower)
   473  		}
   474  	}
   475  
   476  	return err
   477  }
   478  
   479  func (tsk *Calorimeter) lognormal(mean, sigma float64) float64 {
   480  	if mean <= 0 {
   481  		return 0
   482  	}
   483  
   484  	b := math.Sqrt(math.Log(1 + (sigma*sigma)/(mean*mean)))
   485  	a := math.Log(mean) - 0.5*b*b
   486  
   487  	tsk.dmu.Lock()
   488  	bgauss := tsk.gauss.Rand()
   489  	tsk.dmu.Unlock()
   490  	return math.Exp(a + bgauss)
   491  }
   492  
   493  func newCalorimeter(typ, name string, mgr fwk.App) (fwk.Component, error) {
   494  	var err error
   495  
   496  	tsk := &Calorimeter{
   497  		TaskBase: fwk.NewTask(typ, name, mgr),
   498  
   499  		bins:    NewEtaPhiGrid(nil),
   500  		efrac:   make(map[int]EneFrac),
   501  		ecalres: func(eta, ene float64) float64 { return 0 },
   502  		hcalres: func(eta, ene float64) float64 { return 0 },
   503  
   504  		particles:   "/fads/particles",
   505  		tracks:      "/fads/tracks",
   506  		towers:      "/fads/towers",
   507  		photons:     "/fads/photons",
   508  		eflowtracks: "/fads/eflowtracks",
   509  		eflowtowers: "/fads/eflowtowers",
   510  
   511  		seed: 1234,
   512  	}
   513  
   514  	// --
   515  
   516  	err = tsk.DeclProp("EtaPhiBins", &tsk.bins)
   517  	if err != nil {
   518  		return nil, err
   519  	}
   520  
   521  	err = tsk.DeclProp("EnergyFraction", &tsk.efrac)
   522  	if err != nil {
   523  		return nil, err
   524  	}
   525  
   526  	err = tsk.DeclProp("ECalResolution", &tsk.ecalres)
   527  	if err != nil {
   528  		return nil, err
   529  	}
   530  
   531  	err = tsk.DeclProp("HCalResolution", &tsk.hcalres)
   532  	if err != nil {
   533  		return nil, err
   534  	}
   535  
   536  	// --
   537  
   538  	err = tsk.DeclProp("Particles", &tsk.particles)
   539  	if err != nil {
   540  		return nil, err
   541  	}
   542  
   543  	err = tsk.DeclProp("Tracks", &tsk.tracks)
   544  	if err != nil {
   545  		return nil, err
   546  	}
   547  
   548  	err = tsk.DeclProp("Towers", &tsk.towers)
   549  	if err != nil {
   550  		return nil, err
   551  	}
   552  
   553  	err = tsk.DeclProp("Photons", &tsk.photons)
   554  	if err != nil {
   555  		return nil, err
   556  	}
   557  
   558  	err = tsk.DeclProp("EFlowTracks", &tsk.eflowtracks)
   559  	if err != nil {
   560  		return nil, err
   561  	}
   562  
   563  	err = tsk.DeclProp("EFlowTowers", &tsk.eflowtowers)
   564  	if err != nil {
   565  		return nil, err
   566  	}
   567  
   568  	err = tsk.DeclProp("Seed", &tsk.seed)
   569  	if err != nil {
   570  		return nil, err
   571  	}
   572  
   573  	return tsk, err
   574  }
   575  
   576  func init() {
   577  	fwk.Register(reflect.TypeOf(Calorimeter{}), newCalorimeter)
   578  }