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  }