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 }