go-hep.org/x/hep@v0.38.1/fastjet/clustersequence.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 fastjet 6 7 import ( 8 "errors" 9 "fmt" 10 "math" 11 12 "go-hep.org/x/hep/fmom" 13 ) 14 15 // history holds information about the clustering 16 type history struct { 17 parent1 int // index of first parent of this jet were created 18 parent2 int // index of second parent of this jet were created 19 child int // index where the current jet is recombined with another 20 jet int 21 dij float64 22 maxdij float64 23 } 24 25 const ( 26 invalidIndex = -3 27 inexistentParent = -2 28 beamJetIndex = -1 29 ) 30 31 type ClusterSequence struct { 32 def JetDefinition 33 alg JetAlgorithm 34 strategy Strategy 35 r float64 36 r2 float64 37 invR2 float64 38 qtot float64 39 initn int 40 41 jets []Jet 42 history []history 43 structure JetStructure 44 } 45 46 func NewClusterSequence(jets []Jet, def JetDefinition) (*ClusterSequence, error) { 47 var err error 48 cs := &ClusterSequence{ 49 def: def, 50 alg: def.Algorithm(), 51 strategy: def.Strategy(), 52 r: def.R(), 53 jets: make([]Jet, len(jets), len(jets)*2), 54 } 55 56 cs.r2 = cs.r * cs.r 57 cs.invR2 = 1.0 / cs.r2 58 cs.structure = ClusterSequenceStructure{cs} 59 60 copy(cs.jets, jets) 61 err = cs.init() 62 if err != nil { 63 return nil, err 64 } 65 66 err = cs.run() 67 if err != nil { 68 return nil, err 69 } 70 71 return cs, err 72 } 73 74 // NumExclusiveJets returns the number of exclusive jets that would have been obtained 75 // running the algorithm in exclusive mode with the given dcut 76 func (cs *ClusterSequence) NumExclusiveJets(dcut float64) int { 77 // first locate where clustering would have stopped 78 i := len(cs.history) - 1 // last jet 79 for ; 0 <= i; i-- { 80 if cs.history[i].maxdij <= dcut { 81 break 82 } 83 } 84 stoppt := i + 1 85 njets := 2*cs.initn - stoppt 86 return njets 87 } 88 89 func (cs *ClusterSequence) ExclusiveJets(dcut float64) ([]Jet, error) { 90 njets := cs.NumExclusiveJets(dcut) 91 return cs.ExclusiveJetsUpTo(njets) 92 } 93 94 func (cs *ClusterSequence) ExclusiveJetsUpTo(njets int) ([]Jet, error) { 95 var err error 96 if njets > cs.initn { 97 err = errors.New("fastjet: requested too many exclusive jets") 98 return nil, err 99 } 100 // calculate the point where we have to stop the clustering 101 // relation between stoppt, njets assumes one extra jet disappears 102 // at each clustering 103 // 104 // make sure it's safe when more jets are requested than there are particles 105 stoppt := max(2*cs.initn-njets, cs.initn) 106 // additional checking 107 if 2*cs.initn != len(cs.history) { 108 err = errors.New("fastjet: too few initial jets") 109 return nil, err 110 } 111 112 // now go forwards and reconstitute the jets that we have -- 113 // basically for any history element, see if the parent jets to 114 // which it refers were created before the stopping point -- if they 115 // were then add them to the list, otherwise they are subsequent 116 // recombinations of the jets that we are looking for 117 ljets := make([]Jet, 0, imin(njets, cs.initn)) 118 for i := stoppt; i < len(cs.history); i++ { 119 parent1 := cs.history[i].parent1 120 if parent1 < stoppt { 121 ljets = append(ljets, cs.jets[cs.history[parent1].jet]) 122 } 123 parent2 := cs.history[i].parent2 124 if 0 < parent2 && parent2 < stoppt { 125 ljets = append(ljets, cs.jets[cs.history[parent2].jet]) 126 } 127 } 128 return ljets, err 129 } 130 131 func (cs *ClusterSequence) InclusiveJets(ptmin float64) ([]Jet, error) { 132 var err error 133 dcut := ptmin * ptmin 134 jets := make([]Jet, 0) 135 i := len(cs.history) - 1 // last jet 136 137 switch cs.alg { 138 case KtAlgorithm: 139 for ; 0 <= i; i-- { 140 // with our specific definition of dij and dib (ie: R appears only in 141 // dij) then dij==dib is the same as the jet.Pt2() and we can exploit 142 // this in selecting the jets... 143 if cs.history[i].maxdij < dcut { 144 break 145 } 146 if hh := cs.history[i]; hh.parent2 == beamJetIndex && hh.dij >= dcut { 147 // for beam jets 148 jets = append(jets, cs.jets[cs.history[hh.parent1].jet]) 149 } 150 } 151 case CambridgeAlgorithm: 152 for ; 0 <= i; i-- { 153 // inclusive jets are all at the end of clustering sequence in the 154 // cambridge algorithm. 155 // if we find a non-exclusive jet, exit. 156 if cs.history[i].parent2 != beamJetIndex { 157 break 158 } 159 parent1 := cs.history[i].parent1 160 jet := &cs.jets[cs.history[parent1].jet] 161 if jet.Pt2() >= dcut { 162 jets = append(jets, *jet) 163 } 164 } 165 166 case PluginAlgorithm, EeKtAlgorithm, AntiKtAlgorithm, 167 GenKtAlgorithm, EeGenKtAlgorithm, CambridgeForPassiveAlgorithm: 168 // for inclusive jets with a plugin algorithm, we make no 169 // assumption about anything (relation of dij to momenta, 170 // ordering of the dij, etc...) 171 for ; 0 <= i; i-- { 172 hh := cs.history[i] 173 if hh.parent2 != beamJetIndex { 174 continue 175 } 176 parent1 := hh.parent1 177 jet := &cs.jets[cs.history[parent1].jet] 178 if jet.Pt2() >= dcut { 179 jets = append(jets, *jet) 180 } 181 } 182 } 183 return jets, err 184 } 185 186 func (cs *ClusterSequence) init() error { 187 var err error 188 cs.history = make([]history, 0, len(cs.jets)*2) 189 cs.qtot = 0 190 191 for i := range cs.jets { 192 jet := &cs.jets[i] 193 cs.history = append(cs.history, 194 history{ 195 parent1: inexistentParent, 196 parent2: inexistentParent, 197 child: invalidIndex, 198 jet: i, 199 dij: 0.0, 200 maxdij: 0.0, 201 }, 202 ) 203 // perform any momentum pre-processing needed by the recombination scheme 204 err = cs.def.Recombiner().Preprocess(jet) 205 if err != nil { 206 return err 207 } 208 209 jet.hidx = i 210 jet.structure = cs.structure 211 212 cs.qtot += jet.E() 213 } 214 // store the initial number of particles. 215 // this is used by the ClusterSequence to compute the number of exclusive jets 216 // (in the ExclusiveJets method). 217 cs.initn = len(cs.jets) 218 return err 219 } 220 221 func (cs *ClusterSequence) run() error { 222 // nothing to run when event is empty 223 if len(cs.jets) <= 0 { 224 return nil 225 } 226 227 run := cs.runN3Dumb 228 229 switch cs.strategy { 230 case NlnNStrategy: 231 run = cs.runNlnN 232 case N3DumbStrategy: 233 run = cs.runN3Dumb 234 } 235 236 err := run() 237 if err != nil { 238 return err 239 } 240 241 return nil 242 } 243 244 // Constituents retrieves the list of constituents of a given jet 245 func (cs *ClusterSequence) Constituents(jet *Jet) ([]Jet, error) { 246 return cs.addConstituents(jet) 247 } 248 249 func (cs *ClusterSequence) addConstituents(jet *Jet) ([]Jet, error) { 250 var err error 251 var subjets []Jet 252 253 // find position in cluster history 254 i := jet.hidx 255 hh := &cs.history[i] 256 parent1 := hh.parent1 257 if parent1 == inexistentParent { 258 // It is an original particle (labelled by its parent having value 259 // inexistentParent), therefore add it on to the subjet vector 260 // Note: we add the initial particle and not simply 'jet' so that 261 // calling addConstituents with a subtracted jet containing 262 // only one particle will work. 263 subjets = append(subjets, cs.jets[i]) 264 return subjets, err 265 } 266 267 // add parent 1 268 sub1, err := cs.addConstituents(&cs.jets[cs.history[parent1].jet]) 269 if err != nil { 270 return subjets, err 271 } 272 subjets = append(subjets, sub1...) 273 274 // see if parent2 is a real jet, then add its constituents 275 parent2 := hh.parent2 276 if parent2 == beamJetIndex { 277 return subjets, err 278 } 279 280 sub2, err := cs.addConstituents(&cs.jets[cs.history[parent2].jet]) 281 if err != nil { 282 return subjets, err 283 } 284 subjets = append(subjets, sub2...) 285 286 return subjets, err 287 } 288 289 func (cs *ClusterSequence) jetScaleForAlgorithm(jet *Jet) float64 { 290 switch cs.alg { 291 292 case KtAlgorithm: 293 return jet.Pt2() 294 295 case CambridgeAlgorithm: 296 return 1.0 297 298 case AntiKtAlgorithm: 299 kt2 := jet.Pt2() 300 if kt2 > 1e-300 { 301 return 1.0 / kt2 302 } 303 return 1e300 304 305 case GenKtAlgorithm: 306 kt2 := jet.Pt2() 307 p := cs.def.ExtraParam() 308 if p <= 0 && kt2 < 1e-300 { 309 kt2 = 1e-300 310 } 311 return math.Pow(kt2, p) 312 313 case CambridgeForPassiveAlgorithm: 314 kt2 := jet.Pt2() 315 lim := cs.def.ExtraParam() 316 if kt2 < lim*lim && kt2 != 0 { 317 return 1.0 / kt2 318 } 319 return 1.0 320 321 case EeGenKtAlgorithm: 322 kt2 := jet.E() 323 p := cs.def.ExtraParam() 324 if p <= 0 && kt2 < 1e-300 { 325 kt2 = 1e-300 326 } 327 return math.Pow(kt2, 2*p) 328 329 case EeKtAlgorithm: 330 e := max(jet.E(), 1e-300) 331 return e * e 332 333 default: 334 panic(fmt.Errorf("fastjet: unrecognised jet algorithm (%v)", cs.alg)) 335 } 336 } 337 338 func (cs *ClusterSequence) setStructure(j *Jet) { 339 j.structure = cs.structure 340 } 341 342 // do_ij_recombination_step 343 func (cs *ClusterSequence) ijRecombinationStep(i, j int, dij float64) (int, error) { 344 345 k := -1 346 // create the new jet by recombining the first two 347 ijet := &cs.jets[i] 348 jjet := &cs.jets[j] 349 kjet, err := cs.def.Recombiner().Recombine(ijet, jjet) 350 if err != nil { 351 return k, err 352 } 353 k = len(cs.jets) 354 khist := len(cs.history) 355 kjet.hidx = khist 356 cs.jets = append(cs.jets, kjet) 357 358 ihist := ijet.hidx 359 jhist := jjet.hidx 360 361 err = cs.addStepToHistory(khist, imin(ihist, jhist), imax(ihist, jhist), k, dij) 362 return k, err 363 } 364 365 func (cs *ClusterSequence) ibRecombinationStep(i int, dib float64) error { 366 k := len(cs.history) 367 err := cs.addStepToHistory(k, cs.jets[i].hidx, beamJetIndex, invalidIndex, dib) 368 return err 369 } 370 371 func (cs *ClusterSequence) addStepToHistory(istep, i1, i2, idx int, dij float64) error { 372 var err error 373 374 cs.history = append(cs.history, 375 history{ 376 parent1: i1, 377 parent2: i2, 378 jet: idx, 379 child: invalidIndex, 380 dij: dij, 381 maxdij: math.Max(dij, cs.history[len(cs.history)-1].maxdij), 382 }, 383 ) 384 step := len(cs.history) - 1 385 if step != istep { 386 panic(fmt.Errorf("fastjet: internal logic error (step number dont match (%d != %d))", 387 step, istep, 388 )) 389 } 390 391 cs.history[i1].child = step 392 if i2 >= 0 { 393 cs.history[i2].child = step 394 } 395 396 // get cross-referencing right 397 if idx != invalidIndex { 398 cs.jets[idx].hidx = step 399 cs.setStructure(&cs.jets[idx]) 400 } 401 402 return err 403 } 404 405 // runs the N3Dumb strategy 406 func (cs *ClusterSequence) runN3Dumb() error { 407 var err error 408 njets := len(cs.jets) 409 type jetinfo struct { 410 jet *Jet 411 idx int 412 } 413 jets := make([]jetinfo, njets) 414 indices := make([]int, njets) 415 416 for i := range cs.jets { 417 jets[i] = jetinfo{ 418 jet: &cs.jets[i], 419 idx: i, 420 } 421 indices[i] = i 422 } 423 424 for n := njets; n > 0; n-- { 425 ii := 0 426 jj := -2 427 // find smallest beam distance 428 ymin := cs.jetScaleForAlgorithm(jets[0].jet) 429 for i := range n { 430 y := cs.jetScaleForAlgorithm(jets[i].jet) 431 if y < ymin { 432 ymin = y 433 ii = i 434 jj = -2 435 } 436 } 437 438 // find smallest distance between pair of jets 439 for i := range n - 1 { 440 ijet := jets[i].jet 441 for j := i + 1; j < n; j++ { 442 jjet := jets[j].jet 443 jetscale := math.Min( 444 cs.jetScaleForAlgorithm(ijet), 445 cs.jetScaleForAlgorithm(jjet), 446 ) 447 y := math.MaxFloat64 448 switch cs.alg { 449 case EeGenKtAlgorithm: 450 den := 1 - math.Cos(cs.r) 451 if cs.r > math.Pi { 452 den = 3 + math.Cos(cs.r) 453 } 454 if den != 0 { 455 y = jetscale * (1 - fmom.CosTheta(&ijet.PxPyPzE, &jjet.PxPyPzE)) / den 456 } 457 458 case EeKtAlgorithm: 459 y = 2 * jetscale * (1 - fmom.CosTheta(&ijet.PxPyPzE, &jjet.PxPyPzE)) 460 461 default: 462 y = jetscale * Distance(ijet, jjet) * cs.invR2 463 } 464 if y < ymin { 465 ymin = y 466 ii = i 467 jj = j 468 } 469 } 470 } 471 472 // now recombine 473 newn := 2*len(jets) - n 474 if jj >= 0 { 475 //combine pair 476 nn, err := cs.ijRecombinationStep(jets[ii].idx, jets[jj].idx, ymin) 477 if err != nil { 478 return err 479 } 480 // internal bookkeeping 481 jets[ii] = jetinfo{ 482 jet: &cs.jets[nn], 483 idx: nn, 484 } 485 // have jj point to jet that was pointed at by n-1 486 // since original jj is no longer current 487 jets[jj] = jets[n-1] 488 indices[ii] = newn 489 indices[jj] = indices[n-1] 490 } else { 491 // combine ii with beam 492 err = cs.ibRecombinationStep(jets[ii].idx, ymin) 493 if err != nil { 494 return err 495 } 496 // put last jet in place of ii which has disappeared 497 jets[ii] = jets[n-1] 498 indices[ii] = indices[n-1] 499 } 500 } 501 return err 502 } 503 504 // runNlnN runs the clustering using a Hierarchical Delaunay triangulation 505 // and a min-heap to achieve O(N*ln N) behaviour. 506 // 507 // There are internally asserted assumptions about absence of points 508 // with coincident eta-phi coordinates. 509 func (cs *ClusterSequence) runNlnN() error { 510 panic(fmt.Errorf("fastjet: runNlnN not implemented")) 511 } 512 513 // // addKtDistance adds the current kt distance for particle jeti to the heap 514 // // using information about the nearest neighbor in the eta-phi plane. 515 // // Work as follows: 516 // // 517 // // . if the kt is zero then its nearest neighbour is taken to be 518 // // the beam jet and the distance is zero. 519 // // 520 // // . if cylinder distance to nearest neighbour > r^2 then it is 521 // // yiB that is smallest and this is added to the heap. 522 // // 523 // // . otherwise if the nearest neighbour jj has a larger kt then add 524 // // dij to the heap. 525 // // 526 // // . otherwise do nothing 527 // // 528 // func (cs *ClusterSequence) addKtDistance(h *heap.Heap, jeti, jetj int, dist float64) { 529 // yiB := cs.jetScaleForAlgorithm(&cs.jets[jeti]) 530 // if yiB == 0 { 531 // h.Push(jeti, beamJetIndex, yiB) 532 // return 533 // } 534 // deltaR2 := dist * cs.invR2 535 // if deltaR2 > 1 { 536 // h.Push(jeti, beamJetIndex, yiB) 537 // return 538 // } 539 // if yiB <= cs.jetScaleForAlgorithm(&cs.jets[jetj]) { 540 // dij := deltaR2 * yiB 541 // h.Push(jeti, jetj, dij) 542 // } 543 // }