diff --git a/README.rst b/README.rst index 2c379072..43719377 100644 --- a/README.rst +++ b/README.rst @@ -136,6 +136,9 @@ or, say, the name used in EvtGen, you can get a particle directly. >>> >>> Particle.from_evtgen_name("J/psi") + >>> + >>> Particle.from_nucleus_info(a=12, z=6) + A similar method exists to get a list of particles from a PDG style name: @@ -312,7 +315,7 @@ Possible use cases are the following: .. code-block:: python >>> from particle import Particle - >>> from particle import Geant3ID, PythiaID + >>> from particle import Corsika7ID, Geant3ID, PythiaID >>> >>> g3id = Geant3ID(8) >>> p = Particle.from_pdgid(g3id.to_pdgid()) @@ -328,6 +331,24 @@ Possible use cases are the following: >>> p.name 'pi+' + >>> cid = Corsika7ID(5) + >>> p = Particle.from_pdgid(cid.to_pdgid()) + >>> p.name + 'mu+' + +Corsika7 +^^^^^^^^ + +The ``Corsika7ID`` class implements features to make it easier to work with Corsika7 output. +For a full feature set, please refer to the ``particle.corsika`` submodule. + +``Corsika7ID.from_particle_description(from_particle_description: int)`` returns ``(Corsika7ID, bool)`` +to automatically parse the ``particle_description`` from the Corsika7 particle data sub-block. + +``Corsika7ID.is_particle()`` checks if the ID refers to an actual particle or something else (like additional information). + +``Corsika7ID.to_pdgid()`` converts the ``Corsika7ID`` to a ``PDGID`` if possible. + Getting started: experiment-specific modules -------------------------------------------- diff --git a/admin/dump_pdgid_to_corsika7.py b/admin/dump_pdgid_to_corsika7.py new file mode 100755 index 00000000..3e3da39d --- /dev/null +++ b/admin/dump_pdgid_to_corsika7.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python +# Copyright (c) 2018-2022, Eduardo Rodrigues and Henry Schreiner. +# +# Distributed under the 3-clause BSD license, see accompanying file LICENSE +# or https://github.com/scikit-hep/particle for details. +""" +Script to generate the pdgid_to_corsika7id.csv conversion table from Corsika7ID to PDGID and vice-versa. +This script should be kept, so the table won't need to be hand-edited in the future. +""" +from __future__ import annotations + +import csv +import datetime as dt +import pathlib + +from particle import Particle, ParticleNotFound +from particle.shared_literals import common_particles + +# Pairs of matching (Corsika7ID, PDGID), +# if the Corsika7ID has a matching PDGID, if not +# then (Corsika7ID, str), with the string from +# the Corsika7 user guide +corsica_pdg_id = [ + (1, common_particles["gamma"]), + (50, common_particles["omega_782"]), + (2, common_particles["e_plus"]), + (51, common_particles["rho_770_0"]), + (3, common_particles["e_minus"]), + (52, common_particles["rho_770_plus"]), + (53, common_particles["rho_770_minus"]), + (5, common_particles["mu_plus"]), + (54, common_particles["Delta_1232_pp"]), + (6, common_particles["mu_minus"]), + (55, common_particles["Delta_1232_plus"]), + (7, common_particles["pi_0"]), + (56, common_particles["Delta_1232_0"]), + (8, common_particles["pi_plus"]), + (57, common_particles["Delta_1232_minus"]), + (9, common_particles["pi_minus"]), + (58, common_particles["Delta_1232_mm_bar"]), + (10, common_particles["K_L_0"]), + (59, common_particles["Delta_1232_minus_bar"]), + (11, common_particles["K_plus"]), + (60, common_particles["Delta_1232_0_bar"]), + (12, common_particles["K_minus"]), + (61, common_particles["Delta_1232_plus_bar"]), + (13, common_particles["neutron"]), + (62, common_particles["Kst_892_0"]), + (14, common_particles["proton"]), + (63, common_particles["Kst_892_plus"]), + (15, common_particles["antiproton"]), + (64, common_particles["Kst_892_minus"]), + (16, common_particles["K_S_0"]), + (65, common_particles["Kst_892_0_bar"]), + (17, common_particles["eta"]), + (66, common_particles["nu_e"]), + (18, common_particles["Lambda"]), + (67, common_particles["nu_e_bar"]), + (19, common_particles["Sigma_plus"]), + (68, common_particles["nu_mu"]), + (20, common_particles["Sigma_0"]), + (69, common_particles["nu_mu_bar"]), + (21, common_particles["Sigma_minus"]), + (22, common_particles["Xi_0"]), + (71, "η → γγ"), + (23, common_particles["Xi_minus"]), + (72, "η → 3π◦"), + (24, common_particles["Omega_minus"]), + (73, "η → π+π−π◦"), + (25, common_particles["antineutron"]), + (74, "η → π+π−γ"), + (26, common_particles["Lambda_bar"]), + (75, "μ+ add. info."), + (27, common_particles["Sigma_minus_bar"]), + (76, "μ− add. info."), + (28, common_particles["Sigma_0_bar"]), + (29, common_particles["Sigma_plus_bar"]), + (85, "decaying μ+ at start"), + (30, common_particles["Xi_0_bar"]), + (86, "decaying μ− at start"), + (31, common_particles["Xi_plus_bar"]), + (32, common_particles["Omega_plus_bar"]), + (95, "decaying μ+ at end"), + (48, common_particles["etap_958"]), + (49, common_particles["phi_1020"]), + (96, "decaying μ− at end"), + (116, common_particles["D_0"]), + (155, common_particles["Xi_cp_minus_bar"]), + (117, common_particles["D_plus"]), + (156, common_particles["Xi_cp_0_bar"]), + (118, common_particles["D_minus"]), + (157, common_particles["Omega_c_0_bar"]), + (119, common_particles["D_0_bar"]), + (120, common_particles["D_s_plus"]), + (161, common_particles["Sigma_c_2455_pp"]), + (121, common_particles["D_s_minus"]), + (162, common_particles["Sigma_c_2455_plus"]), + (122, common_particles["eta_c_1S"]), + (163, common_particles["Sigma_c_2455_0"]), + (123, common_particles["Dst_2007_0"]), + (124, common_particles["Dst_2010_plus"]), + (171, common_particles["Sigma_c_2455_mm_bar"]), + (125, common_particles["Dst_2010_minus"]), + (172, common_particles["Sigma_c_2455_minus_bar"]), + (126, common_particles["Dst_2007_0_bar"]), + (173, common_particles["Sigma_c_2455_0_bar"]), + (127, common_particles["D_sst_plus"]), + (128, common_particles["D_sst_minus"]), + (176, common_particles["B_0"]), + (177, common_particles["B_plus"]), + (130, common_particles["Jpsi_1S"]), + (178, common_particles["B_minus"]), + (131, common_particles["tau_plus"]), + (179, common_particles["B_0_bar"]), + (132, common_particles["tau_minus"]), + (180, common_particles["B_s_0"]), + (133, common_particles["nu_tau"]), + (181, common_particles["B_s_0_bar"]), + (134, common_particles["nu_tau_bar"]), + (182, common_particles["B_c_plus"]), + (183, common_particles["B_c_minus"]), + (137, common_particles["Lambda_c_plus"]), + (184, common_particles["Lambda_b_0"]), + (138, common_particles["Xi_c_plus"]), + (185, common_particles["Sigma_b_minus"]), + (139, common_particles["Xi_c_0"]), + (186, common_particles["Sigma_b_plus"]), + (140, common_particles["Sigma_c_2455_pp"]), + (187, common_particles["Xi_b_0"]), + (141, common_particles["Sigma_c_2455_plus"]), + (188, common_particles["Xi_b_minus"]), + (142, common_particles["Sigma_c_2455_0"]), + (189, common_particles["Omega_b_minus"]), + (143, common_particles["Xi_cp_plus"]), + (190, common_particles["Lambda_b_0_bar"]), + (144, common_particles["Xi_cp_0"]), + (191, common_particles["Sigma_b_plus_bar"]), + (145, common_particles["Sigma_c_2455_0"]), + (192, common_particles["Sigma_b_minus_bar"]), + (193, common_particles["Xi_b_0_bar"]), + (149, common_particles["Lambda_c_minus_bar"]), + (194, common_particles["Xi_b_plus_bar"]), + (150, common_particles["Xi_c_minus_bar"]), + (195, common_particles["Omega_b_plus_bar"]), + (151, common_particles["Xi_c_0_bar"]), + (152, common_particles["Sigma_c_2455_mm_bar"]), + (153, common_particles["Sigma_c_2455_minus_bar"]), + (154, common_particles["Sigma_c_2455_0_bar"]), +] + + +def dump_pdgid_to_corsika7(file: pathlib.Path | None = None) -> None: + """ + Generates the conversion .csv file with the patching PDGID to Corsika7ID under 'src/particle/data/pdgid_to_corsika7id.csv' + (if file is None, else in the specified path). + """ + # Loop over all thinkable values and only add them if the PDG ID exists + for a in range(2, 56 + 1): + for z in range(0, a + 1): + corsikaid = a * 100 + z + try: + corsica_pdg_id.append( + (corsikaid, int(Particle.from_nucleus_info(a=a, z=z).pdgid)) + ) + except ParticleNotFound: + pass + + if file is None: + file = ( + pathlib.Path(__file__) + .parent.parent.resolve() + .absolute() + .joinpath("src/particle/data/pdgid_to_corsika7id.csv") + ) + + date = dt.datetime.today().strftime("%Y-%m-%d") + + with open( + file, + "w", + newline="", + encoding="utf-8", + ) as csvfile: + csvfile.write( + f"# (c) Scikit-HEP project - Particle package data file - pdgid_to_corsika7id.csv - {date}\n" + ) + csvfile.write( + "# Auto generated by 'admin/dump_pdgid_to_corsika7.py'\n", + ) + + writer = csv.writer(csvfile) + # Header + writer.writerow(("PDGID", "CORSIKA7ID")) + for corsikaid, pdgid in sorted(corsica_pdg_id, key=lambda x: x[0]): + if isinstance(pdgid, int): + writer.writerow((pdgid, corsikaid)) + + +if __name__ == "__main__": + dump_pdgid_to_corsika7() diff --git a/src/particle/__init__.py b/src/particle/__init__.py index 36a90289..26288773 100644 --- a/src/particle/__init__.py +++ b/src/particle/__init__.py @@ -8,7 +8,7 @@ import sys -# Direct access to other ID classes +from .corsika import Corsika7ID from .geant import Geant3ID # Direct access to Particle literals @@ -31,6 +31,7 @@ __all__ = ( "Charge", + "Corsika7ID", "Geant3ID", "Inv", "InvalidParticle", diff --git a/src/particle/converters/__init__.py b/src/particle/converters/__init__.py index f668aee8..e580894b 100644 --- a/src/particle/converters/__init__.py +++ b/src/particle/converters/__init__.py @@ -6,6 +6,7 @@ from __future__ import annotations +from .corsika import Corsika72PDGIDBiMap from .evtgen import EvtGen2PDGNameMap, EvtGenName2PDGIDBiMap, PDG2EvtGenNameMap from .geant import Geant2PDGIDBiMap from .pythia import Pythia2PDGIDBiMap @@ -16,6 +17,7 @@ "PDG2EvtGenNameMap", "Geant2PDGIDBiMap", "Pythia2PDGIDBiMap", + "Corsika72PDGIDBiMap", ) diff --git a/src/particle/converters/corsika.py b/src/particle/converters/corsika.py new file mode 100644 index 00000000..b31975df --- /dev/null +++ b/src/particle/converters/corsika.py @@ -0,0 +1,26 @@ +# Copyright (c) 2018-2022, Eduardo Rodrigues and Henry Schreiner. +# +# Distributed under the 3-clause BSD license, see accompanying file LICENSE +# or https://github.com/scikit-hep/particle for details. + + +from __future__ import annotations + +from ..corsika import Corsika7ID +from ..pdgid import PDGID +from .bimap import BiMap + +Corsika72PDGIDBiMap = BiMap(PDGID, Corsika7ID) +Corsika72PDGIDBiMap.__doc__ = """ +Bi-bidirectional map between PDG and Corsika7 IDs. + +Examples +-------- +>>> cid = Corsika72PDGIDBiMap[PDGID(-13)] +>>> cid + + +>>> cid = Corsika72PDGIDBiMap[Corsika7ID(5)] +>>> cid + +""" diff --git a/src/particle/corsika/__init__.py b/src/particle/corsika/__init__.py new file mode 100644 index 00000000..32e0b104 --- /dev/null +++ b/src/particle/corsika/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) 2018-2022, Eduardo Rodrigues and Henry Schreiner. +# +# Distributed under the 3-clause BSD license, see accompanying file LICENSE +# or https://github.com/scikit-hep/particle for details. + + +from __future__ import annotations + +from .corsika7id import Corsika7ID + +__all__ = ("Corsika7ID",) + + +def __dir__() -> tuple[str, ...]: + return __all__ diff --git a/src/particle/corsika/corsika7id.py b/src/particle/corsika/corsika7id.py new file mode 100644 index 00000000..503d29b5 --- /dev/null +++ b/src/particle/corsika/corsika7id.py @@ -0,0 +1,218 @@ +# Copyright (c) 2018-2022, Eduardo Rodrigues and Henry Schreiner. +# +# Distributed under the 3-clause BSD license, see accompanying file LICENSE +# or https://github.com/scikit-hep/particle for details. + +""" +Class representing a Corsika7 ID. + +Note +---- +Corsika8 uses Geant3 Particle IDs. +""" + + +from __future__ import annotations + +import csv +from typing import TypeVar + +from .. import data +from ..exceptions import MatchingIDNotFound +from ..pdgid import PDGID + +Self = TypeVar("Self", bound="Corsika7ID") + + +with data.basepath.joinpath("pdgid_to_corsika7id.csv").open() as _f: + _bimap = { + int(v["CORSIKA7ID"]): int(v["PDGID"]) + for v in csv.DictReader(line for line in _f if not line.startswith("#")) + } + +# Some Corsika7 ID's are not really particles +_non_particles = { + 71: "η → γγ", + 72: "η → 3π◦", + 73: "η → π+π−π◦", + 74: "η → π+π−γ", + 75: "μ+ add. info.", + 76: "μ− add. info.", + 85: "decaying μ+ at start", + 86: "decaying μ− at start", + 95: "decaying μ+ at end", + 96: "decaying μ− at end", +} + + +class Corsika7ID(int): + """ + Holds a Corsika7 ID. + + All arithmetic operations on the class (like `-Corsika7ID(5)`) will + return an integer. This is unlike for example `-PDGID(13)`, which will + return `PDGID(-13)`. But since negative values have no direct meaning + as a Corsika7ID, (in the output file they are used to indicate mother-particles) + we omit this feature. + + Examples + -------- + >>> cid = Corsika7ID(6) + + >>> from particle import Particle + >>> p = Particle.from_pdgid(cid.to_pdgid()) + + >>> (p,) = Particle.finditer(pdgid=cid.to_pdgid()) + >>> p.name + 'mu-' + """ + + __slots__ = () # Keep Corsika7ID a slots based class + + @classmethod + def from_pdgid(cls: type[Self], pdgid: int) -> Self: + """ + Constructor from a PDGID. + """ + for k, v in _bimap.items(): + if v == pdgid: + return cls(k) + raise MatchingIDNotFound(f"Non-existent Corsika7ID for input PDGID {pdgid}!") + + @classmethod + def from_particle_description( + cls: type[Self], particle_description: int + ) -> tuple[Self, bool]: + """ + Constructor from the particle description returned by Corsika7 + in the particle data sub-block, mother particle data sub-block or + the grandmother particle data sub-block. + + Returns + ------- + A tuple with + + Corsika7ID: The Corsika7 id + bool: If the particle is a (grand)mother particle. + """ + cid = abs(particle_description) // 1000 + ismother = particle_description < 0 + + if cls._is_non_particle_id(cid): + return cls(cid), ismother + + # This catches the cases of nuclei with no known PDG ID + if 200 <= cid < 5699: + return cls(cid), ismother + + if cid in _bimap: + return cls(cid), ismother + + raise MatchingIDNotFound( + f"Non-existent Corsika7ID for particle description {particle_description}!" + ) + + @classmethod + def _is_non_particle_id(cls: type[Self], corsikaid: int) -> bool: + """ + Returns True if the ID is valid but does not correspond to a particle, False otherwise. + """ + return ( + corsikaid in _non_particles + or corsikaid // 1000 == 8888 + or corsikaid == 9900 + ) + + def is_particle(self) -> bool: + """ + Returns True if the ID really belongs to a particle, since some are for example additional information. + + Examples + -------- + >>> mu_minux = Corsika7ID(6) + >>> mu_minus.is_particle() + True + >>> mu_info = Corsika7ID(76) + >>> mu_info.is_particle() + False + >>> mu_info.name() + 'μ− add. info.' + """ + iid = int(self) + + return not self._is_non_particle_id(iid) + + def name(self) -> str: + """ + Returns a human readable name of the Corsika7ID. + This also works for non-particles (is_particle()==false). + + Raises + ------ + ParticleNotFound + If it is a 'valid' PDG particle, but unknown. + This for example happens with strange nuclei, which are not in the nuclei list. + + Examples + -------- + >>> mu_minus = Corsika7ID(6) + >>> mu_minus.is_particle() + True + >>> mu_minus.name() # For a particle, this returns the same name as `Particle.name` + 'mu' + >>> mu_info = Corsika7ID(76) + >>> mu_info.is_particle() + False + >>> mu_info.name() + 'μ− add. info.' + >>> ch_photons_of = Corsika7ID(9900) + >>> ch_photons_of.is_particle() + False + >>> ch_photons_of.name() + 'Cherenkov photons on particle output file' + """ + from ..particle.particle import Particle # pylint: disable=C0415 + + if self.is_particle(): + return str(Particle.from_pdgid(self.to_pdgid()).name) + + iid = int(self) + + if iid in _non_particles: + return _non_particles[iid] + + if iid // 1000 == 8888: + return "weights of preceding particle (MULTITHIN option)" + + if iid == 9900: + return "Cherenkov photons on particle output file" + + raise RuntimeError("This should be unreachable.") + + def to_pdgid(self) -> PDGID: + """ + Raises + ------ + InvalidParticle + If it is a valid Corsika particle, but not a valid PDGID. + + Examples + -------- + >>> Corsika7ID(6).to_pdgid() + + >>> Corsika7ID(76).to_pdgid() + InvalidParticle: The Corsika7ID does not correspond to a particle and thus has no equivalent PDGID. + """ + from ..particle.particle import InvalidParticle # pylint: disable=C0415 + + if self not in _bimap: + raise InvalidParticle( + f"The Corsika7ID {self} does not correspond to a particle and thus has no equivalent PDGID." + ) + return PDGID(_bimap[self]) + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}: {int(self):d}>" + + def __str__(self) -> str: + return repr(self) diff --git a/src/particle/data/pdgid_to_corsika7id.csv b/src/particle/data/pdgid_to_corsika7id.csv new file mode 100644 index 00000000..90d7fa58 --- /dev/null +++ b/src/particle/data/pdgid_to_corsika7id.csv @@ -0,0 +1,581 @@ +# (c) Scikit-HEP project - Particle package data file - pdgid_to_corsika7id.csv - 2022-10-24 +# Auto generated by 'admin/dump_pdgid_to_lhcb.py' +PDGID,CORSIKA7ID +22,1 +-11,2 +11,3 +-13,5 +13,6 +111,7 +211,8 +-211,9 +130,10 +321,11 +-321,12 +2112,13 +2212,14 +-2212,15 +310,16 +221,17 +3122,18 +3222,19 +3212,20 +3112,21 +3322,22 +3312,23 +3334,24 +-2112,25 +-3122,26 +-3222,27 +-3212,28 +-3112,29 +-3322,30 +-3312,31 +-3334,32 +331,48 +333,49 +223,50 +113,51 +213,52 +-213,53 +2224,54 +2214,55 +2114,56 +1114,57 +-2224,58 +-2214,59 +-2114,60 +-1114,61 +313,62 +323,63 +-323,64 +-313,65 +12,66 +-12,67 +14,68 +-14,69 +421,116 +411,117 +-411,118 +-421,119 +431,120 +-431,121 +441,122 +423,123 +413,124 +-413,125 +-423,126 +433,127 +-433,128 +443,130 +-15,131 +15,132 +16,133 +-16,134 +4122,137 +4232,138 +4132,139 +4222,140 +4212,141 +4112,142 +4322,143 +4312,144 +4112,145 +-4122,149 +-4232,150 +-4132,151 +-4222,152 +-4212,153 +-4112,154 +-4322,155 +-4312,156 +-4332,157 +4222,161 +4212,162 +4112,163 +-4222,171 +-4212,172 +-4112,173 +511,176 +521,177 +-521,178 +-511,179 +531,180 +-531,181 +541,182 +-541,183 +5122,184 +5112,185 +5222,186 +5232,187 +5132,188 +5332,189 +-5122,190 +-5112,191 +-5222,192 +-5232,193 +-5132,194 +-5332,195 +1000010020,201 +1000010030,301 +1000020030,302 +1000010040,401 +1000020040,402 +1000030040,403 +1000010050,501 +1000020050,502 +1000030050,503 +1000040050,504 +1000010060,601 +1000020060,602 +1000030060,603 +1000040060,604 +1000020070,702 +1000030070,703 +1000040070,704 +1000050070,705 +1000020080,802 +1000030080,803 +1000040080,804 +1000050080,805 +1000060080,806 +1000020090,902 +1000030090,903 +1000040090,904 +1000050090,905 +1000060090,906 +1000020100,1002 +1000030100,1003 +1000040100,1004 +1000050100,1005 +1000060100,1006 +1000070100,1007 +1000030110,1103 +1000040110,1104 +1000050110,1105 +1000060110,1106 +1000070110,1107 +1000030120,1203 +1000040120,1204 +1000050120,1205 +1000060120,1206 +1000070120,1207 +1000080120,1208 +1000040130,1304 +1000050130,1305 +1000060130,1306 +1000070130,1307 +1000080130,1308 +1000040140,1404 +1000050140,1405 +1000060140,1406 +1000070140,1407 +1000080140,1408 +1000090140,1409 +1000050150,1505 +1000060150,1506 +1000070150,1507 +1000080150,1508 +1000090150,1509 +1000050160,1605 +1000060160,1606 +1000070160,1607 +1000080160,1608 +1000090160,1609 +1000100160,1610 +1000050170,1705 +1000060170,1706 +1000070170,1707 +1000080170,1708 +1000090170,1709 +1000100170,1710 +1000050180,1805 +1000060180,1806 +1000070180,1807 +1000080180,1808 +1000090180,1809 +1000100180,1810 +1000110180,1811 +1000050190,1905 +1000060190,1906 +1000070190,1907 +1000080190,1908 +1000090190,1909 +1000100190,1910 +1000110190,1911 +1000060200,2006 +1000070200,2007 +1000080200,2008 +1000090200,2009 +1000100200,2010 +1000110200,2011 +1000120200,2012 +1000060210,2106 +1000070210,2107 +1000080210,2108 +1000090210,2109 +1000100210,2110 +1000110210,2111 +1000120210,2112 +1000130210,2113 +1000060220,2206 +1000070220,2207 +1000080220,2208 +1000090220,2209 +1000100220,2210 +1000110220,2211 +1000120220,2212 +1000130220,2213 +1000140220,2214 +1000070230,2307 +1000080230,2308 +1000090230,2309 +1000100230,2310 +1000110230,2311 +1000120230,2312 +1000130230,2313 +1000140230,2314 +1000070240,2407 +1000080240,2408 +1000090240,2409 +1000100240,2410 +1000110240,2411 +1000120240,2412 +1000130240,2413 +1000140240,2414 +1000150240,2415 +1000080250,2508 +1000090250,2509 +1000100250,2510 +1000110250,2511 +1000120250,2512 +1000130250,2513 +1000140250,2514 +1000150250,2515 +1000080260,2608 +1000090260,2609 +1000100260,2610 +1000110260,2611 +1000120260,2612 +1000130260,2613 +1000140260,2614 +1000150260,2615 +1000160260,2616 +1000090270,2709 +1000100270,2710 +1000110270,2711 +1000120270,2712 +1000130270,2713 +1000140270,2714 +1000150270,2715 +1000160270,2716 +1000090280,2809 +1000100280,2810 +1000110280,2811 +1000120280,2812 +1000130280,2813 +1000140280,2814 +1000150280,2815 +1000160280,2816 +1000170280,2817 +1000090290,2909 +1000100290,2910 +1000110290,2911 +1000120290,2912 +1000130290,2913 +1000140290,2914 +1000150290,2915 +1000160290,2916 +1000170290,2917 +1000100300,3010 +1000110300,3011 +1000120300,3012 +1000130300,3013 +1000140300,3014 +1000150300,3015 +1000160300,3016 +1000170300,3017 +1000180300,3018 +1000100310,3110 +1000110310,3111 +1000120310,3112 +1000130310,3113 +1000140310,3114 +1000150310,3115 +1000160310,3116 +1000170310,3117 +1000180310,3118 +1000100320,3210 +1000110320,3211 +1000120320,3212 +1000130320,3213 +1000140320,3214 +1000150320,3215 +1000160320,3216 +1000170320,3217 +1000180320,3218 +1000190320,3219 +1000110330,3311 +1000120330,3312 +1000130330,3313 +1000140330,3314 +1000150330,3315 +1000160330,3316 +1000170330,3317 +1000180330,3318 +1000190330,3319 +1000110340,3411 +1000120340,3412 +1000130340,3413 +1000140340,3414 +1000150340,3415 +1000160340,3416 +1000170340,3417 +1000180340,3418 +1000190340,3419 +1000200340,3420 +1000110350,3511 +1000120350,3512 +1000130350,3513 +1000140350,3514 +1000150350,3515 +1000160350,3516 +1000170350,3517 +1000180350,3518 +1000190350,3519 +1000200350,3520 +1000120360,3612 +1000130360,3613 +1000140360,3614 +1000150360,3615 +1000160360,3616 +1000170360,3617 +1000180360,3618 +1000190360,3619 +1000200360,3620 +1000210360,3621 +1000120370,3712 +1000130370,3713 +1000140370,3714 +1000150370,3715 +1000160370,3716 +1000170370,3717 +1000180370,3718 +1000190370,3719 +1000200370,3720 +1000210370,3721 +1000130380,3813 +1000140380,3814 +1000150380,3815 +1000160380,3816 +1000170380,3817 +1000180380,3818 +1000190380,3819 +1000200380,3820 +1000210380,3821 +1000220380,3822 +1000130390,3913 +1000140390,3914 +1000150390,3915 +1000160390,3916 +1000170390,3917 +1000180390,3918 +1000190390,3919 +1000200390,3920 +1000210390,3921 +1000220390,3922 +1000140400,4014 +1000150400,4015 +1000160400,4016 +1000170400,4017 +1000180400,4018 +1000190400,4019 +1000200400,4020 +1000210400,4021 +1000220400,4022 +1000230400,4023 +1000140410,4114 +1000150410,4115 +1000160410,4116 +1000170410,4117 +1000180410,4118 +1000190410,4119 +1000200410,4120 +1000210410,4121 +1000220410,4122 +1000230410,4123 +1000140420,4214 +1000150420,4215 +1000160420,4216 +1000170420,4217 +1000180420,4218 +1000190420,4219 +1000200420,4220 +1000210420,4221 +1000220420,4222 +1000230420,4223 +1000240420,4224 +1000150430,4315 +1000160430,4316 +1000170430,4317 +1000180430,4318 +1000190430,4319 +1000200430,4320 +1000210430,4321 +1000220430,4322 +1000230430,4323 +1000240430,4324 +1000150440,4415 +1000160440,4416 +1000170440,4417 +1000180440,4418 +1000190440,4419 +1000200440,4420 +1000210440,4421 +1000220440,4422 +1000230440,4423 +1000240440,4424 +1000250440,4425 +1000150450,4515 +1000160450,4516 +1000170450,4517 +1000180450,4518 +1000190450,4519 +1000200450,4520 +1000210450,4521 +1000220450,4522 +1000230450,4523 +1000240450,4524 +1000250450,4525 +1000260450,4526 +1000150460,4615 +1000160460,4616 +1000170460,4617 +1000180460,4618 +1000190460,4619 +1000200460,4620 +1000210460,4621 +1000220460,4622 +1000230460,4623 +1000240460,4624 +1000250460,4625 +1000260460,4626 +1000160470,4716 +1000170470,4717 +1000180470,4718 +1000190470,4719 +1000200470,4720 +1000210470,4721 +1000220470,4722 +1000230470,4723 +1000240470,4724 +1000250470,4725 +1000260470,4726 +1000160480,4816 +1000170480,4817 +1000180480,4818 +1000190480,4819 +1000200480,4820 +1000210480,4821 +1000220480,4822 +1000230480,4823 +1000240480,4824 +1000250480,4825 +1000260480,4826 +1000270480,4827 +1000160490,4916 +1000170490,4917 +1000180490,4918 +1000190490,4919 +1000200490,4920 +1000210490,4921 +1000220490,4922 +1000230490,4923 +1000240490,4924 +1000250490,4925 +1000260490,4926 +1000270490,4927 +1000170500,5017 +1000180500,5018 +1000190500,5019 +1000200500,5020 +1000210500,5021 +1000220500,5022 +1000230500,5023 +1000240500,5024 +1000250500,5025 +1000260500,5026 +1000270500,5027 +1000280500,5028 +1000170510,5117 +1000180510,5118 +1000190510,5119 +1000200510,5120 +1000210510,5121 +1000220510,5122 +1000230510,5123 +1000240510,5124 +1000250510,5125 +1000260510,5126 +1000270510,5127 +1000280510,5128 +1000180520,5218 +1000190520,5219 +1000200520,5220 +1000210520,5221 +1000220520,5222 +1000230520,5223 +1000240520,5224 +1000250520,5225 +1000260520,5226 +1000270520,5227 +1000280520,5228 +1000290520,5229 +1000180530,5318 +1000190530,5319 +1000200530,5320 +1000210530,5321 +1000220530,5322 +1000230530,5323 +1000240530,5324 +1000250530,5325 +1000260530,5326 +1000270530,5327 +1000280530,5328 +1000290530,5329 +1000190540,5419 +1000200540,5420 +1000210540,5421 +1000220540,5422 +1000230540,5423 +1000240540,5424 +1000250540,5425 +1000260540,5426 +1000270540,5427 +1000280540,5428 +1000290540,5429 +1000300540,5430 +1000190550,5519 +1000200550,5520 +1000210550,5521 +1000220550,5522 +1000230550,5523 +1000240550,5524 +1000250550,5525 +1000260550,5526 +1000270550,5527 +1000280550,5528 +1000290550,5529 +1000300550,5530 +1000200560,5620 +1000210560,5621 +1000220560,5622 +1000230560,5623 +1000240560,5624 +1000250560,5625 +1000260560,5626 +1000270560,5627 +1000280560,5628 +1000290560,5629 +1000300560,5630 +1000310560,5631 diff --git a/src/particle/particle/particle.py b/src/particle/particle/particle.py index effe7752..49c8bf7c 100644 --- a/src/particle/particle/particle.py +++ b/src/particle/particle/particle.py @@ -1015,6 +1015,79 @@ def from_evtgen_name(cls: type[Self], name: str) -> Self: """ return cls.from_pdgid(EvtGenName2PDGIDBiMap[name]) + @classmethod + def from_nucleus_info( + cls: type[Self], + z: int, + a: int, + anti: bool = False, + i: int = 0, + l_strange: int = 0, + ) -> Self: + """ + Get a nucleus particle from the proton Z and atomic mass A numbers. + + As described in the PDG numbering scheme: + "To avoid ambiguities, nucleus codes should not be applied to a single hadron, such as the p, n + or the Λ, where quark-contents-based codes already exist." + + Number of neutrons is equal to a-z. + The PDG ID format is ±10LZZZAAAI, see the `is_nucleus()` function in submodule + `particle.pdgid.functions.py`. + + Parameters + ---------- + z: int + Atomic number Z (number of protons). + Maximum three decimal digits. + a: int + Atomic mass number A (total number of baryons). + Maximum three decimal digits. + anti: bool, optional, defaults to False (not antimatter). + Whether the nucleus should be antimatter. + i: int, optional, default is 0 + Isomer level I. I=0 corresponds to the ground-state. + I>0 are excitations. + Maximum is one decimal digit. + l_strange: int, optional, default is 0 + Total number of strange quarks. + Maximum is one decimal digit. + + Raises + ------ + ParticleNotFound + If `from_pdgid`, internally called, returns no match. + InvalidParticle + If the input combination is invalid. + """ + if l_strange < 0 or l_strange > 9: + raise InvalidParticle( + f"Number of strange quarks l={l_strange} is invalid. Must be 0 <= l <= 9." + ) + if z < 0 or z > 999: + raise InvalidParticle( + f"Atomic number Z={z} is invalid. Must be 0 <= A <= 999." + ) + if a < 0 or a > 999: + raise InvalidParticle( + f"Atomic mass number A={a} is invalid. Must be 0 <= A <= 999." + ) + if i < 0 or i > 9: + raise InvalidParticle( + f"Isomer level I={i} is invalid. Must be 0 <= I <= 9." + ) + if z > a: + raise InvalidParticle( + f"Nucleus A={a}, Z={z} is invalid. Z must be smaller or equal to Z." + ) + + pdgid = int(1e9 + l_strange * 1e5 + z * 1e4 + a * 10 + i) + + if anti: + return cls.from_pdgid(-pdgid) + + return cls.from_pdgid(pdgid) + @classmethod def finditer( cls: type[Self], diff --git a/tests/converters/test_corsika.py b/tests/converters/test_corsika.py new file mode 100644 index 00000000..22ec46d8 --- /dev/null +++ b/tests/converters/test_corsika.py @@ -0,0 +1,22 @@ +# Copyright (c) 2018-2022, Eduardo Rodrigues and Henry Schreiner. +# +# Distributed under the 3-clause BSD license, see accompanying file LICENSE +# or https://github.com/scikit-hep/particle for details. + +from __future__ import annotations + +from particle import PDGID, Corsika7ID, Particle +from particle.converters import Corsika72PDGIDBiMap + + +def test_Corsika72PDGID(): + pdgid = Corsika72PDGIDBiMap[Corsika7ID(5)] + assert pdgid == -13 + + cid = Corsika72PDGIDBiMap[PDGID(13)] + assert cid.is_particle() + assert cid == 6 + + p = Particle.from_pdgid(cid.to_pdgid()) + # should be muon + assert p.charge == -1 diff --git a/tests/corsika/__init__.py b/tests/corsika/__init__.py new file mode 100644 index 00000000..ac564e31 --- /dev/null +++ b/tests/corsika/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2018-2022, Eduardo Rodrigues and Henry Schreiner. +# +# Distributed under the 3-clause BSD license, see accompanying file LICENSE +# or https://github.com/scikit-hep/particle for details. diff --git a/tests/corsika/test_corsika7id.py b/tests/corsika/test_corsika7id.py new file mode 100644 index 00000000..a80ac18a --- /dev/null +++ b/tests/corsika/test_corsika7id.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import pytest + +from particle.corsika import Corsika7ID +from particle.exceptions import MatchingIDNotFound +from particle.pdgid import PDGID + + +def test_class_string_representations(): + pid = Corsika7ID(1) + assert pid == 1 + assert pid.__str__() == "" + + +def test_class_return_type(): + assert isinstance(Corsika7ID(3), Corsika7ID) + + +def test_from_pdgid(): + assert Corsika7ID.from_pdgid(-13) == 5 + + assert Corsika7ID.from_pdgid(PDGID(-13)) == 5 + assert Corsika7ID.from_pdgid(PDGID(13)) == Corsika7ID(6) + + +def test_from_pdgid_non_matching(): + with pytest.raises(MatchingIDNotFound): + Corsika7ID.from_pdgid(55) + + +def test_to_pdgid(): + cid = Corsika7ID(5) + assert cid.to_pdgid() == -13 + assert cid.to_pdgid() == PDGID(-13) + + +def test_is_particle(): + cid = Corsika7ID(1) + assert cid.is_particle() + cid = Corsika7ID(75) + assert not cid.is_particle() + + +def test_from_particle_description(): + cid, is_mother = Corsika7ID.from_particle_description(-6001) + assert is_mother + assert cid.is_particle() + cid, is_mother = Corsika7ID.from_particle_description(75001) + assert not is_mother + assert not cid.is_particle() diff --git a/tests/particle/test_particle.py b/tests/particle/test_particle.py index b6f73290..f4d2aa4c 100644 --- a/tests/particle/test_particle.py +++ b/tests/particle/test_particle.py @@ -147,6 +147,27 @@ def test_pdg_convert(): assert int(p.pdgid) == 211 +def test_nucleus_convert(): + p = Particle.from_nucleus_info(1, 2) + assert p.pdgid == 1000010020 + p = Particle.from_nucleus_info(92, 235) + assert p.pdgid == 1000922350 + p = Particle.from_nucleus_info(1, 2, anti=True) + assert p.pdgid == -1000010020 + # No exited nuclei in database + try: + p = Particle.from_nucleus_info(1, 2, i=1) + assert p.pdgid == 1000010021 + except ParticleNotFound: + pass + # No strange nuclei in database and strange PDGID not implemented + try: + p = Particle.from_nucleus_info(1, 2, l_strange=1) + assert p.pdgid == 1100010020 + except ParticleNotFound and InvalidParticle: + pass + + def test_sorting(): assert Particle.from_pdgid(211) < Particle.from_pdgid(311) assert Particle.from_pdgid(211) < Particle.from_pdgid(-311)