go-hep.org/x/hep@v0.38.1/heppdt/pid.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 heppdt 6 7 import ( 8 "math" 9 ) 10 11 type location int 12 13 // PID digits (base 10) are: n Nr Nl Nq1 Nq2 Nq3 Nj 14 // The location enum provides a convenient index into the PID. 15 const ( 16 _ location = iota 17 Nj 18 Nq3 19 Nq2 20 Nq1 21 Nl 22 Nr 23 N 24 N8 25 N9 26 N10 27 ) 28 29 // Quarks describes a given quark mixture 30 type Quarks struct { 31 Nq1 int16 32 Nq2 int16 33 Nq3 int16 34 } 35 36 // Particle Identification number 37 // In the standard numbering scheme, the PID digits (base 10) are: 38 // 39 // +/- n Nr Nl Nq1 Nq2 Nq3 Nj 40 // 41 // It is expected that any 7 digit number used as a PID will adhere to 42 // the Monte Carlo numbering scheme documented by the PDG. 43 // Note that particles not already explicitly defined 44 // can be expressed within this numbering scheme. 45 type PID int 46 47 // ExtraBits returns everything beyoind the 7th digit 48 // (e.g. outside the numbering scheme) 49 func (pid PID) ExtraBits() int { 50 return pid.AbsPID() / 10000000 51 } 52 53 // FundamentalID returns the first 2 digits if this is a "fundamental" 54 // particle. 55 // ID==100 is a special case (internal generator ID's are 81-100) 56 // Also, 101 and 102 are now used for geantinos 57 func (pid PID) FundamentalID() int { 58 if pid.Digit(N10) == 1 && pid.Digit(N9) == 0 { 59 return 0 60 } 61 62 if pid.Digit(Nq2) == 2 && pid.Digit(Nq1) == 0 { 63 return pid.AbsPID() % 10000 64 } else if pid.AbsPID() <= 102 { 65 return pid.AbsPID() 66 } else { 67 return 0 68 } 69 } 70 71 // Digit splits the PID into constituent integers 72 func (pid PID) Digit(loc location) int { 73 // PID digits (base 10) are: n Nr Nl Nq1 Nq2 Nq3 Nj 74 // the location enum provides a convenient index into the PID 75 num := int(math.Pow(10.0, float64(loc-1))) 76 return int(pid.AbsPID()/num) % 10 77 } 78 79 // findQ 80 func (pid PID) findQ(q int) bool { 81 if pid.IsDyon() { 82 return false 83 } 84 if pid.IsRhadron() { 85 iz := 7 86 for i := 6; i > 1; i-- { 87 if pid.Digit(location(i)) == 0 { 88 iz = i 89 } else if i == iz-1 { 90 // ignore squark or gluino 91 } else { 92 if pid.Digit(location(i)) == q { 93 return true 94 } 95 } 96 } 97 return false 98 } 99 if pid.Digit(Nq3) == q || pid.Digit(Nq2) == q || pid.Digit(Nq1) == q { 100 return true 101 } 102 if pid.IsPentaquark() { 103 if pid.Digit(Nl) == q || pid.Digit(Nr) == q { 104 return true 105 } 106 } 107 return false 108 } 109 110 // AbsPID returns the absolute value of the particle ID 111 func (pid PID) AbsPID() int { 112 id := int(pid) 113 if id >= 0 { 114 return id 115 } 116 return -int(id) 117 } 118 119 // IsValid returns whether PID is a valid particle ID 120 func (pid PID) IsValid() bool { 121 if pid.ExtraBits() > 0 { 122 switch { 123 case pid.IsNucleus(): 124 return true 125 case pid.IsQBall(): 126 return true 127 default: 128 return false 129 } 130 } 131 132 switch { 133 case pid.IsSUSY(): 134 return true 135 case pid.IsRhadron(): 136 return true 137 case pid.IsDyon(): 138 return true 139 case pid.IsMeson(): 140 return true 141 case pid.IsBaryon(): 142 return true 143 case pid.IsDiQuark(): 144 return true 145 case pid.FundamentalID() > 0: 146 return true 147 case pid.IsPentaquark(): 148 return true 149 } 150 151 return false 152 } 153 154 // IsMeson returns whether this is a valid meson ID 155 func (pid PID) IsMeson() bool { 156 switch { 157 case pid.ExtraBits() > 0: 158 return false 159 case pid.AbsPID() <= 100: 160 return false 161 case pid.FundamentalID() <= 100 && pid.FundamentalID() > 0: 162 return false 163 } 164 apid := pid.AbsPID() 165 id := int(pid) 166 167 switch { 168 case apid == 130 || apid == 310 || apid == 210: 169 return true 170 171 case apid == 150 || apid == 350 || apid == 510 || apid == 530: 172 // EvtGen odd number 173 return true 174 case id == 110 || id == 990 || id == 9990: 175 // pomeron, etc... 176 return true 177 case pid.Digit(Nj) > 0 && pid.Digit(Nq3) > 0 && pid.Digit(Nq2) > 0 && pid.Digit(Nq1) == 0: 178 // check for illegal antiparticles 179 switch { 180 case pid.Digit(Nq3) == pid.Digit(Nq2) && id < 0: 181 return false 182 default: 183 return true 184 } 185 } 186 return false 187 } 188 189 // IsBaryon returns whether this is a valid baryon id 190 func (pid PID) IsBaryon() bool { 191 switch { 192 case pid.ExtraBits() > 0: 193 return false 194 case pid.AbsPID() <= 100: 195 return false 196 case pid.FundamentalID() <= 100 && pid.FundamentalID() > 0: 197 return false 198 case pid.AbsPID() == 2110 || pid.AbsPID() == 2210: 199 return true 200 case pid.Digit(Nj) > 0 && pid.Digit(Nq3) > 0 && pid.Digit(Nq2) > 0 && pid.Digit(Nq1) > 0: 201 return true 202 } 203 return false 204 } 205 206 // IsDiQuark returns whether this is a valid diquark id 207 func (pid PID) IsDiQuark() bool { 208 switch { 209 case pid.ExtraBits() > 0: 210 return false 211 case pid.AbsPID() <= 100: 212 return false 213 case pid.FundamentalID() <= 100 && pid.FundamentalID() > 0: 214 return false 215 case pid.Digit(Nj) > 0 && pid.Digit(Nq3) == 0 && pid.Digit(Nq2) > 0 && pid.Digit(Nq1) > 0: 216 // EvtGen uses the diquarks for quark pairs, so for instance, 217 // 5501 is a valid "diquark" for EvtGen 218 // if pid.Digit(Nj) == 1 && pid.Digit(Nq2) == pid.Digit(Nq1) { // illegal 219 // return false 220 // } else { 221 return true 222 // } 223 } 224 return false 225 } 226 227 // IsHadron returns whether this is a valid hadron id 228 func (pid PID) IsHadron() bool { 229 switch { 230 case pid.ExtraBits() > 0: 231 return false 232 case pid.IsMeson(): 233 return true 234 case pid.IsBaryon(): 235 return true 236 case pid.IsPentaquark(): 237 return true 238 } 239 return false 240 } 241 242 // IsLepton returns whether this is a valid lepton id 243 func (pid PID) IsLepton() bool { 244 if pid.ExtraBits() > 0 { 245 return false 246 } 247 if fid := pid.FundamentalID(); fid >= 11 && fid <= 18 { 248 return true 249 } 250 return false 251 } 252 253 // IsNucleus returns whether this is a valid nucleus id. 254 // This implements the 2006 Monte Carlon nuclear code scheme. 255 // Ion numbers are +/- 10LZZZAAAI. 256 // AAA is A - total baryon number 257 // ZZZ is Z - total charge 258 // L is the total number of strange quarks. 259 // I is the isomer number, with I=0 corresponding to the ground state. 260 func (pid PID) IsNucleus() bool { 261 // a proton can also be a hydrogen nucleus 262 if pid.AbsPID() == 2212 { 263 return true 264 } 265 // new standard: +/- 10LZZZAAAI 266 if pid.Digit(N10) == 1 && pid.Digit(N9) == 0 { 267 // charge should always be less than or equal to baryon number 268 if pid.A() >= pid.Z() { 269 return true 270 } 271 } 272 return false 273 } 274 275 // IsPentaquark returns whether this is a valid pentaquark id 276 func (pid PID) IsPentaquark() bool { 277 // a pentaquark is of the form 9abcdej, 278 // where j is the spin and a, b, c, d, and e are quarks 279 switch { 280 case pid.ExtraBits() > 0: 281 return false 282 case pid.Digit(N) != 9: 283 return false 284 case pid.Digit(Nr) == 9 || pid.Digit(Nr) == 0: 285 return false 286 case pid.Digit(Nj) == 9 || pid.Digit(Nl) == 0: 287 return false 288 289 case pid.Digit(Nq1) == 0: 290 return false 291 case pid.Digit(Nq2) == 0: 292 return false 293 case pid.Digit(Nq3) == 0: 294 return false 295 case pid.Digit(Nj) == 0: 296 return false 297 298 case pid.Digit(Nq2) > pid.Digit(Nq1): 299 return false 300 case pid.Digit(Nq1) > pid.Digit(Nl): 301 return false 302 case pid.Digit(Nl) > pid.Digit(Nr): 303 return false 304 } 305 306 return true 307 } 308 309 // IsSUSY returns whether this is a valid SUSY particle id 310 func (pid PID) IsSUSY() bool { 311 // fundamental SUSY particles have n =1 or =2 312 switch { 313 case pid.ExtraBits() > 0: 314 return false 315 case pid.Digit(N) != 1 && pid.Digit(N) != 2: 316 return false 317 case pid.Digit(Nr) != 0: 318 return false 319 case pid.FundamentalID() == 0: 320 return false 321 } 322 323 return true 324 } 325 326 // IsRhadron returns whether this is a valid R-hadron particle id 327 func (pid PID) IsRhadron() bool { 328 329 // an R-hadron is of the form 10abcdj, 330 // where j is the spin and a, b, c, and d are quarks or gluons 331 switch { 332 case pid.ExtraBits() > 0: 333 return false 334 case pid.Digit(N) != 1: 335 return false 336 case pid.Digit(Nr) != 0: 337 return false 338 case pid.IsSUSY(): 339 return false 340 341 case pid.Digit(Nq2) == 0: 342 return false // All R-hadrons have a least 3 core digits 343 case pid.Digit(Nq3) == 0: 344 return false // All R-hadrons have a least 3 core digits 345 case pid.Digit(Nj) == 0: 346 return false // All R-hadrons have a least 3 core digits 347 } 348 349 return true 350 } 351 352 // IsDyon returns whether this is a valid Dyon (magnetic monopole) id 353 func (pid PID) IsDyon() bool { 354 // Magnetic monopoles and Dyons are assumed to have one unit of 355 // Dirac monopole charge and a variable integer number xyz units 356 // of electric charge. 357 // 358 // Codes 411xyz0 are then used when the magnetic and electrical 359 // charge sign agree and 412xyz0 when they disagree, 360 // with the overall sign of the particle set by the magnetic charge. 361 // For now no spin information is provided. 362 363 switch { 364 case pid.ExtraBits() > 0: 365 return false 366 case pid.Digit(N) != 4: 367 return false 368 case pid.Digit(Nr) != 1: 369 return false 370 case pid.Digit(Nl) != 1 && pid.Digit(Nl) != 2: 371 return false 372 case pid.Digit(Nq3) == 0: 373 return false // all Dyons have at least 1 core digit 374 case pid.Digit(Nj) != 0: 375 return false // dyons have spin zero for now 376 } 377 378 return true 379 } 380 381 // IsQBall checks for QBall or any exotic particle with electric charge 382 // beyond the qqq scheme. 383 // Ad-hoc numbering for such particles is 100xxxx0, where xxxx is the 384 // charge in tenths. 385 func (pid PID) IsQBall() bool { 386 // Ad-hoc numbering for such particles is 100xxxx0, 387 // where xxxx is the charge in tenths. 388 switch { 389 case pid.ExtraBits() > 0: 390 return false 391 case pid.Digit(N) != 1 && pid.Digit(N) != 2: 392 return false 393 case pid.Digit(Nr) != 0: 394 return false 395 case pid.FundamentalID() == 0: 396 return false 397 } 398 return true 399 } 400 401 // HasUp returns whether this particle contains an up quark 402 func (pid PID) HasUp() bool { 403 switch { 404 case pid.ExtraBits() > 0: 405 return false 406 case pid.FundamentalID() > 0: 407 return false 408 } 409 return pid.findQ(2) 410 } 411 412 // HasDown returns whether this particle contains a down quark 413 func (pid PID) HasDown() bool { 414 switch { 415 case pid.ExtraBits() > 0: 416 return false 417 case pid.FundamentalID() > 0: 418 return false 419 } 420 return pid.findQ(1) 421 } 422 423 // HasStrange returns whether this particle contains a strange quark 424 func (pid PID) HasStrange() bool { 425 switch { 426 case pid.ExtraBits() > 0: 427 return false 428 case pid.FundamentalID() > 0: 429 return false 430 } 431 return pid.findQ(3) 432 } 433 434 // HasCharm returns whether this particle contains a charm quark 435 func (pid PID) HasCharm() bool { 436 switch { 437 case pid.ExtraBits() > 0: 438 return false 439 case pid.FundamentalID() > 0: 440 return false 441 } 442 return pid.findQ(4) 443 } 444 445 // HasBottom returns whether this particle contains a bottom quark 446 func (pid PID) HasBottom() bool { 447 switch { 448 case pid.ExtraBits() > 0: 449 return false 450 case pid.FundamentalID() > 0: 451 return false 452 } 453 return pid.findQ(5) 454 } 455 456 // HasTop returns whether this particle contains a top quark 457 func (pid PID) HasTop() bool { 458 switch { 459 case pid.ExtraBits() > 0: 460 return false 461 case pid.FundamentalID() > 0: 462 return false 463 } 464 return pid.findQ(6) 465 } 466 467 // A returns A if this is a nucleus 468 func (pid PID) A() int { 469 // a proton can also be a hydrogen nucleus 470 switch { 471 case pid.AbsPID() == 2212: 472 return 1 473 case pid.Digit(N10) != 1 || pid.Digit(N9) != 0: 474 return 0 475 } 476 return (pid.AbsPID() / 10) % 1000 477 } 478 479 // Z returns Z if this is a nucleus 480 func (pid PID) Z() int { 481 // a proton can also be a hydrogen nucleus 482 switch { 483 case pid.AbsPID() == 2212: 484 return 1 485 case pid.Digit(N10) != 1 || pid.Digit(N9) != 0: 486 return 0 487 } 488 return (pid.AbsPID() / 10000) % 1000 489 } 490 491 // Lambda returns lambda if this is a nucleus 492 func (pid PID) Lambda() int { 493 494 // a proton can also be a hydrogen nucleus 495 if pid.AbsPID() == 2212 { 496 return 0 497 } 498 499 if !pid.IsNucleus() { 500 return 0 501 } 502 503 return pid.Digit(N8) 504 } 505 506 // JSpin returns 2J+1, where J is the total spin 507 func (pid PID) JSpin() int { 508 fid := pid.FundamentalID() 509 if fid > 0 && fid <= 100 { 510 switch { 511 case fid > 0 && fid < 7: 512 return 2 513 514 case fid == 9: 515 return 3 516 517 case fid > 10 && fid < 17: 518 return 2 519 520 case fid > 20 && fid < 25: 521 return 3 522 } 523 return 0 524 } else if pid.ExtraBits() > 0 { 525 return 0 526 } 527 return pid.AbsPID() % 10 528 } 529 530 // LSpin returns the orbital angular momentum. 531 // Valid for mesons only 532 func (pid PID) LSpin() int { 533 if !pid.IsMeson() { 534 return 0 535 } 536 537 tent := (pid.AbsPID() / 1000000) % 10 538 if tent == 9 { 539 return 0 540 } 541 542 Nl := (pid.AbsPID() / 10000) % 10 543 js := pid.AbsPID() % 10 544 545 if Nl == 0 && js == 3 { 546 return 0 547 } else if Nl == 0 && js == 5 { 548 return 1 549 } else if Nl == 0 && js == 7 { 550 return 2 551 } else if Nl == 0 && js == 9 { 552 return 3 553 } else if Nl == 0 && js == 1 { 554 return 0 555 } else if Nl == 1 && js == 3 { 556 return 1 557 } else if Nl == 1 && js == 5 { 558 return 2 559 } else if Nl == 1 && js == 7 { 560 return 3 561 } else if Nl == 1 && js == 9 { 562 return 4 563 } else if Nl == 2 && js == 3 { 564 return 1 565 } else if Nl == 2 && js == 5 { 566 return 2 567 } else if Nl == 2 && js == 7 { 568 return 3 569 } else if Nl == 2 && js == 9 { 570 return 4 571 } else if Nl == 1 && js == 1 { 572 return 1 573 } else if Nl == 3 && js == 3 { 574 return 2 575 } else if Nl == 3 && js == 5 { 576 return 3 577 } else if Nl == 3 && js == 7 { 578 return 4 579 } else if Nl == 3 && js == 9 { 580 return 5 581 } 582 // default to zero 583 return 0 584 } 585 586 // SSpin returns the spin. Valid for mesons only 587 func (pid PID) SSpin() int { 588 if !pid.IsMeson() { 589 return 0 590 } 591 592 tent := (pid.AbsPID() / 1000000) % 10 593 if tent == 9 { 594 return 0 595 } 596 597 Nl := (pid.AbsPID() / 10000) % 10 598 js := pid.AbsPID() % 10 599 600 if Nl == 0 && js >= 3 { 601 return 1 602 } else if Nl == 0 && js == 1 { 603 return 0 604 } else if Nl == 1 && js >= 3 { 605 return 0 606 } else if Nl == 2 && js >= 3 { 607 return 1 608 } else if Nl == 1 && js == 1 { 609 return 1 610 } else if Nl == 3 && js >= 3 { 611 return 1 612 } 613 // default to zero 614 return 0 615 } 616 617 var ch100 = [100]int{ 618 -1, 2, -1, 2, -1, 2, -1, 2, 0, 0, 619 -3, 0, -3, 0, -3, 0, -3, 0, 0, 0, 620 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 621 0, 0, 0, 3, 0, 0, 3, 0, 0, 0, 622 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 623 0, 6, 3, 6, 0, 0, 0, 0, 0, 0, 624 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 625 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 626 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 627 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 628 } 629 630 // threeCharge 631 func (pid PID) threeCharge() int { 632 var charge int 633 q1 := pid.Digit(Nq1) 634 q2 := pid.Digit(Nq2) 635 q3 := pid.Digit(Nq3) 636 ida := pid.AbsPID() 637 fid := pid.FundamentalID() 638 639 if ida == 0 { // illegal 640 return 0 641 } else if pid.ExtraBits() > 0 { 642 if pid.IsNucleus() { // ion 643 return 3 * pid.Z() 644 } else if pid.IsQBall() { // QBall 645 charge = 3 * ((ida / 10) % 10000) 646 } else { // not an ion 647 return 0 648 } 649 } else if pid.IsDyon() { // Dyon 650 charge = 3 * ((ida / 10) % 1000) 651 // this is half right 652 // the charge sign will be changed below if pid < 0 653 if pid.Digit(Nl) == 2 { 654 charge = -charge 655 } 656 } else if fid > 0 && fid <= 100 { // use table 657 charge = ch100[fid-1] 658 if ida == 1000017 || ida == 1000018 { 659 charge = 0 660 } 661 if ida == 1000034 || ida == 1000052 { 662 charge = 0 663 } 664 if ida == 1000053 || ida == 1000054 { 665 charge = 0 666 } 667 if ida == 5100061 || ida == 5100062 { 668 charge = 6 669 } 670 } else if pid.Digit(Nj) == 0 { // KL, Ks, or undefined 671 return 0 672 } else if (q1 == 0) || (pid.IsRhadron() && (q1 == 9)) { // meson // mesons 673 if q2 == 3 || q2 == 5 { 674 charge = ch100[q3-1] - ch100[q2-1] 675 } else { 676 charge = ch100[q2-1] - ch100[q3-1] 677 } 678 } else if q3 == 0 { // diquarks 679 charge = ch100[q2-1] + ch100[q1-1] 680 } else if pid.IsBaryon() || (pid.IsRhadron() && (pid.Digit(Nl) == 9)) { // baryon // baryons 681 charge = ch100[q3-1] + ch100[q2-1] + ch100[q1-1] 682 } 683 if charge == 0 { 684 return 0 685 } else if int(pid) < 0 { 686 charge = -charge 687 } 688 return charge 689 690 } 691 692 const onethird = 1. / 3.0 693 const onethirtith = 1. / 30.0 694 695 // Charge returns the actual charge which might be fractional 696 func (pid PID) Charge() float64 { 697 c := pid.threeCharge() 698 if pid.IsQBall() { 699 return float64(c) * onethirtith 700 } 701 return float64(c) * onethird 702 } 703 704 // Quarks returns a list of 3 constituent quarks 705 func (pid PID) Quarks() Quarks { 706 return Quarks{ 707 Nq1: int16(pid.Digit(Nq1)), 708 Nq2: int16(pid.Digit(Nq2)), 709 Nq3: int16(pid.Digit(Nq3)), 710 } 711 }