diff --git a/.coveragerc b/.coveragerc index ff1a6ec2..02091d70 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,4 +3,3 @@ omit = # omit this single file particle/particle/convert.py particle/__main__.py - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1df36e4f..af02e79b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,19 @@ on: - 'v*' jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-python@v1 + - name: set PY + run: echo "::set-env name=PY::$(python --version --version | sha256sum | cut -d' ' -f1)" + - uses: actions/cache@v1 + with: + path: ~/.cache/pre-commit + key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} + - uses: pre-commit/action@v1.0.0 + checks: runs-on: ubuntu-latest strategy: @@ -15,7 +28,7 @@ jobs: matrix: python-version: - 2.7 - - 3.6 + - 3.5 - 3.8 name: Check Python ${{ matrix.python-version }} steps: @@ -73,12 +86,10 @@ jobs: zipapp: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 + - uses: actions/checkout@v1 - uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: 3.7 - name: Install wheel and sdist requirements run: python -m pip install "setuptools>=42.0" "setuptools_scm[toml]>=3.4" "wheel" @@ -87,16 +98,36 @@ jobs: run: python setup.py sdist - name: Install requirements - run: python -m pip install enum34 pathlib2 importlib_resources attrs hepunits tabulate --target src - - - name: Move enum to prevent name clash - run: mv src/enum src/enum34 + run: python -m pip install attrs hepunits tabulate importlib_resources --target src - name: Make ZipApp - run: python -m zipapp -p "/usr/bin/env python" -o ../particle.pyz . + run: python -m zipapp -c -p "/usr/bin/env python3" -m "particle.__main__:main" -o ../particle.pyz . working-directory: src - uses: actions/upload-artifact@v1 with: name: ZipApp path: particle.pyz + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + - name: Upload Release Asset + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./particle.pyz + asset_name: particle.pyz + asset_content_type: application/zip diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml deleted file mode 100644 index 809e191e..00000000 --- a/.github/workflows/pre-commit.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Format - -on: - pull_request: - push: - branches: - - master - - develop - -jobs: - pre-commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-python@v1 - - name: set PY - run: echo "::set-env name=PY::$(python --version --version | sha256sum | cut -d' ' -f1)" - - uses: actions/cache@v1 - with: - path: ~/.cache/pre-commit - key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} - - uses: pre-commit/action@v1.0.0 diff --git a/.gitignore b/.gitignore index 190ba102..489fddba 100644 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,3 @@ docs/_build /src/particle/version.py /.mypy_cache/* /pip-wheel-metadata - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f478ac5d..abf2583f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,11 @@ repos: - repo: https://github.com/psf/black - rev: 19.3b0 + rev: 19.10b0 hooks: - id: black - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + rev: v3.1.0 hooks: - id: check-added-large-files args: ['--maxkb=1000'] @@ -15,7 +15,17 @@ repos: - id: check-case-conflict - id: check-symlinks - id: check-yaml + - id: requirements-txt-fixer + - id: debug-statements + - id: end-of-file-fixer + - id: fix-encoding-pragma - repo: https://github.com/mgedmin/check-manifest - rev: "0.39" + rev: "0.42" hooks: - id: check-manifest +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.770 + hooks: + - id: mypy + files: src + additional_dependencies: [attrs==19.3.0] diff --git a/MANIFEST.in b/MANIFEST.in index 861b3082..8e88fc0e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -graft src +graft src/particle global-exclude .env* global-exclude .git* diff --git a/README.rst b/README.rst index fec837e9..07fe9687 100644 --- a/README.rst +++ b/README.rst @@ -17,6 +17,10 @@ .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.2552429.svg :target: https://doi.org/10.5281/zenodo.2552429 +.. image:: https://github.com/scikit-hep/particle/workflows/CI/badge.svg + :alt: GitHub Actions status + :target: https://github.com/scikit-hep/particle/workflows/CI + .. image:: https://dev.azure.com/scikit-hep/particle/_apis/build/status/scikit-hep.particle?branchName=master :alt: Build Status :target: https://dev.azure.com/scikit-hep/particle/_build/latest?definitionId=1?branchName=master @@ -97,7 +101,7 @@ Getting started: PDGIDs >>> pid -For convenience, all properties of the ``PDGID`` class are available as standalone functions: +For convenience, all properties of the ``PDGID`` class are available as standalone functions that work on any SupportsInt (including ``Particle``): .. code-block:: python diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 968622ef..2240f2fb 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,10 +4,10 @@ Changelog Version 0.10.0 -------------- -May 18th, 2020 +May 21th, 2020 - `Particle` class: - - Several improvements, in particular to better deal with nuclei + - Several improvements, in particular to better deal with nuclei. and diquarks. - Speed of table loading improved. - Particle enums extended for diquarks. @@ -18,6 +18,7 @@ May 18th, 2020 - `PDGID` class: - PDG ID functions extended to correctly and consistently deal with nuclei. + - Functions now accept any int-like, including Particle objects. - Data CSV files: - Version 5 of package data files, with - Diquarks added. @@ -27,7 +28,7 @@ May 18th, 2020 time). - Converter script adapted to add to the produced data files particles not in the PDG data table, such as diquarks. -- Redesigned packaging system. +- Redesigned packaging system, GHA deployment. - Miscellaneous: - Files `*requirements.txt` removed from package - use `pip install .[dev]` instead @@ -36,6 +37,8 @@ May 18th, 2020 - Deprecation warning in `attr.s` fixed, requirement on minimal version of `attr` added. - Version tags now follow standard `v#.#.#` format. + - Some Python warnings fixed. + - Some initial work on static type hints. Version 0.9.2 ------------- diff --git a/setup.cfg b/setup.cfg index 3af54464..1a117e58 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,9 +40,10 @@ classifiers = python_requires = >=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4 install_requires = enum34>=1.1; python_version<"3.4" + typing>=3.7; python_version<"3.5" importlib_resources>=1.0; python_version<"3.7" attrs>=19.2 - hepunits>=1.1.0 + hepunits>=1.2.0 packages = find: include_package_data = True package_dir = diff --git a/setup.py b/setup.py index 48457b1e..40d90372 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/__main__.py b/src/__main__.py deleted file mode 100644 index 85bdefd1..00000000 --- a/src/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -import runpy - -runpy.run_module("particle") diff --git a/src/particle/__init__.py b/src/particle/__init__.py index bd5701ed..40b169ee 100644 --- a/src/particle/__init__.py +++ b/src/particle/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/__main__.py b/src/particle/__main__.py index 9002c35c..96d7afda 100644 --- a/src/particle/__main__.py +++ b/src/particle/__main__.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE @@ -13,59 +14,66 @@ import argparse import sys -parser = argparse.ArgumentParser( - prog="particle", description="Particle command line display utility. Has two modes." -) - -parser.add_argument( - "--version", - action="version", - version="%(prog)s {version}".format(version=__version__), -) - -subparsers = parser.add_subparsers(help="Subcommands") - -search = subparsers.add_parser( - "search", - help="Look up particles by PID or name (Ex.: python -m particle search D+ D-)", -) -search.add_argument("particle", nargs="+", help="Name(s) or ID(s)") - -pdgid = subparsers.add_parser( - "pdgid", help="Print info from PID (Ex.: python -m particle pdgid 11 13)" -) -pdgid.add_argument("pdgid", nargs="+", help="ID(s)") - -opts = parser.parse_args() - -if "particle" in opts: - for cand in opts.particle: - if hasattr(cand, "decode"): - cand = cand.decode("utf-8") - - try: - value = int(cand) - except ValueError: - value = 0 - - if value: - particles = [Particle.from_pdgid(value)] - else: - particles = Particle.from_string_list(cand) - - if len(particles) == 0: - print("Particle", cand, "not found.") - sys.exit(1) - elif len(particles) == 1: - print(particles[0].describe()) - else: - for particle in particles: - print(repr(particle)) - - print() - -if "pdgid" in opts: - for value in opts.pdgid: - p = PDGID(value) - print(p) - print(PDGID(value).info()) + +def main(): + parser = argparse.ArgumentParser( + prog="particle", + description="Particle command line display utility. Has two modes.", + ) + + parser.add_argument( + "--version", + action="version", + version="%(prog)s {version}".format(version=__version__), + ) + + subparsers = parser.add_subparsers(help="Subcommands") + + search = subparsers.add_parser( + "search", + help="Look up particles by PID or name (Ex.: python -m particle search D+ D-)", + ) + search.add_argument("particle", nargs="+", help="Name(s) or ID(s)") + + pdgid = subparsers.add_parser( + "pdgid", help="Print info from PID (Ex.: python -m particle pdgid 11 13)" + ) + pdgid.add_argument("pdgid", nargs="+", help="ID(s)") + + opts = parser.parse_args() + + if "particle" in opts: + for cand in opts.particle: + if hasattr(cand, "decode"): + cand = cand.decode("utf-8") + + try: + value = int(cand) + except ValueError: + value = 0 + + if value: + particles = [Particle.from_pdgid(value)] + else: + particles = Particle.from_string_list(cand) + + if len(particles) == 0: + print("Particle", cand, "not found.") + sys.exit(1) + elif len(particles) == 1: + print(particles[0].describe()) + else: + for particle in particles: + print(repr(particle)) + + print() + + if "pdgid" in opts: + for value in opts.pdgid: + p = PDGID(value) + print(p) + print(PDGID(value).info()) + + +if __name__ == "__main__": + main() diff --git a/src/particle/converters/__init__.py b/src/particle/converters/__init__.py index df2e01a7..e465e96a 100644 --- a/src/particle/converters/__init__.py +++ b/src/particle/converters/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/converters/bimap.py b/src/particle/converters/bimap.py index b97652ce..1b347584 100644 --- a/src/particle/converters/bimap.py +++ b/src/particle/converters/bimap.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/converters/evtgen.py b/src/particle/converters/evtgen.py index 090055e3..086ffd79 100644 --- a/src/particle/converters/evtgen.py +++ b/src/particle/converters/evtgen.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/converters/geant.py b/src/particle/converters/geant.py index b49fcec4..d6717cef 100644 --- a/src/particle/converters/geant.py +++ b/src/particle/converters/geant.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/converters/pythia.py b/src/particle/converters/pythia.py index 6a38e7bc..29116896 100644 --- a/src/particle/converters/pythia.py +++ b/src/particle/converters/pythia.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/data/__init__.py b/src/particle/data/__init__.py index 4d6a8a4c..d7eacac7 100644 --- a/src/particle/data/__init__.py +++ b/src/particle/data/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/exceptions.py b/src/particle/exceptions.py index 7ad35771..8b19d8d8 100644 --- a/src/particle/exceptions.py +++ b/src/particle/exceptions.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/geant/__init__.py b/src/particle/geant/__init__.py index 4e921d3b..03052627 100644 --- a/src/particle/geant/__init__.py +++ b/src/particle/geant/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/geant/geant3id.py b/src/particle/geant/geant3id.py index 92b9fe71..b3e5f932 100644 --- a/src/particle/geant/geant3id.py +++ b/src/particle/geant/geant3id.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/particle/__init__.py b/src/particle/particle/__init__.py index 1e98ecbd..b32d8978 100644 --- a/src/particle/particle/__init__.py +++ b/src/particle/particle/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/particle/convert.py b/src/particle/particle/convert.py index 17557b82..45ccf180 100644 --- a/src/particle/particle/convert.py +++ b/src/particle/particle/convert.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE @@ -52,7 +53,7 @@ import os from datetime import date -import pandas as pd +import pandas as pd # type: ignore try: from io import StringIO diff --git a/src/particle/particle/enums.py b/src/particle/particle/enums.py index 09c17950..6b6055c2 100644 --- a/src/particle/particle/enums.py +++ b/src/particle/particle/enums.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE @@ -8,12 +9,7 @@ Examples are charge, spin and parity. """ -# Backport needed if Python 2 is used -# Rename used in ZipApp -try: - from enum import IntEnum -except ImportError: - from enum34 import IntEnum # type: ignore +from enum import IntEnum class SpinType(IntEnum): diff --git a/src/particle/particle/kinematics.py b/src/particle/particle/kinematics.py index a33ace26..a9a28cb7 100644 --- a/src/particle/particle/kinematics.py +++ b/src/particle/particle/kinematics.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE @@ -14,6 +15,7 @@ def width_to_lifetime(Gamma): + # type: (float) -> float """ Convert from a particle decay width to a lifetime. @@ -59,6 +61,7 @@ def width_to_lifetime(Gamma): def lifetime_to_width(tau): + # type: (float) -> float """ Convert from a particle lifetime to a decay width. diff --git a/src/particle/particle/literals.py b/src/particle/particle/literals.py index 1cf5538b..351dfe68 100644 --- a/src/particle/particle/literals.py +++ b/src/particle/particle/literals.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE @@ -32,19 +32,14 @@ from ..shared_literals import common_particles from .particle import Particle, ParticleNotFound -for item in common_particles: +__doc = "" +for k, v in common_particles.items(): try: - locals()[item] = Particle.from_pdgid(common_particles[item]) + locals()[k] = Particle.from_pdgid(v) + __doc += " {item!s} = Particle.from_pdgid({part})\n".format(item=k, part=v) except ParticleNotFound: pass - -__doc = "" -for item in common_particles: - __doc += " {item!s} = Particle.from_pdgid({part})\n".format( - item=item, part=common_particles[item] - ) - __doc__ = __doc__.format(__doc) diff --git a/src/particle/particle/particle.py b/src/particle/particle/particle.py index e036c579..81ee63a4 100644 --- a/src/particle/particle/particle.py +++ b/src/particle/particle/particle.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE @@ -7,21 +7,28 @@ from __future__ import absolute_import, division, print_function # Python standard library -import operator -import os -import re import csv from copy import copy -from fractions import Fraction -from functools import reduce, total_ordering - -import fileinput -from contextlib import closing +from functools import total_ordering # External dependencies import attr +from typing import ( + Optional, + Any, + Dict, + Tuple, + List, + Callable, + Iterable, + SupportsInt, + Union, + TextIO, + Set, +) + from hepunits.constants import c_light from .. import data @@ -30,7 +37,6 @@ from ..pdgid.functions import _digit from ..pdgid.functions import Location from .regex import getname, getdec - from .enums import ( SpinType, Parity, @@ -43,11 +49,8 @@ Charge_prog, Charge_mapping, ) - from .utilities import programmatic_name, str_with_unc, latex_to_html_name - from .kinematics import width_to_lifetime - from ..converters.evtgen import EvtGenName2PDGIDBiMap @@ -60,10 +63,26 @@ class InvalidParticle(RuntimeError): def _isospin_converter(isospin): - vals = {"0": 0.0, "1/2": 0.5, "1": 1.0, "3/2": 1.5} + # type: (str) -> Optional[float] + vals = { + "0": 0.0, + "1/2": 0.5, + "1": 1.0, + "3/2": 1.5, + } # type: Dict[Optional[str], Optional[float]] return vals.get(isospin, None) +def _none_or_positive_converter(value): + # type: (float) -> Optional[float] + return None if value < 0 else value + + +# These are needed to trick attrs typing +minus_one = -1.0 # type: Optional[float] +none_float = None # type: Optional[float] + + @total_ordering @attr.s(slots=True, eq=False, order=False, repr=False) class Particle(object): @@ -172,34 +191,53 @@ class Particle(object): pdgid = attr.ib(converter=PDGID) pdg_name = attr.ib() - mass = attr.ib(-1, converter=lambda v: None if v < 0 else v) - mass_upper = attr.ib(-1, converter=lambda v: None if v < 0 else v) - mass_lower = attr.ib(-1, converter=lambda v: None if v < 0 else v) - width = attr.ib(-1, converter=lambda v: None if v < 0 else v) - width_upper = attr.ib(-1, converter=lambda v: None if v < 0 else v) - width_lower = attr.ib(-1, converter=lambda v: None if v < 0 else v) + mass = attr.ib( + minus_one, converter=_none_or_positive_converter + ) # type: Optional[float] + mass_upper = attr.ib( + minus_one, converter=_none_or_positive_converter + ) # type: Optional[float] + mass_lower = attr.ib( + minus_one, converter=_none_or_positive_converter + ) # type: Optional[float] + width = attr.ib( + minus_one, converter=_none_or_positive_converter + ) # type: Optional[float] + width_upper = attr.ib( + minus_one, converter=_none_or_positive_converter + ) # type: Optional[float] + width_lower = attr.ib( + minus_one, converter=_none_or_positive_converter + ) # type: Optional[float] _three_charge = attr.ib(Charge.u, converter=Charge) # charge * 3 - I = attr.ib(None, converter=_isospin_converter) + I = attr.ib(none_float, converter=_isospin_converter) # type: Optional[float] # J = attr.ib(None) # Total angular momentum G = attr.ib(Parity.u, converter=Parity) # Parity: '', +, -, or ? P = attr.ib(Parity.u, converter=Parity) # Space parity C = attr.ib(Parity.u, converter=Parity) # Charge conjugation parity - anti_flag = attr.ib(0, converter=Inv) # Info about particle name for anti-particles + anti_flag = attr.ib( + Inv.Same, converter=Inv + ) # Info about particle name for anti-particles rank = attr.ib(0) status = attr.ib(Status.NotInPDT, converter=Status) quarks = attr.ib("", converter=str) latex_name = attr.ib("Unknown") def __repr__(self): + # type: () -> str return '<{self.__class__.__name__}: name="{self!s}", pdgid={pdgid}, mass={mass}>'.format( self=self, pdgid=int(self.pdgid), mass=self._str_mass() ) - _table = None # Loaded table of entries - _table_names = None # Names of loaded tables + # Loaded table of entries + _table = None # type: Optional[Set[Particle]] + + # Names of loaded tables + _table_names = None # type: Optional[List[str]] @classmethod def table_names(cls): + # type: () -> Tuple[str, ...] """ Return the list of names loaded. @@ -214,10 +252,14 @@ def table_names(cls): if cls._table_names is None: cls.load_table() - return tuple(cls._table_names) # make a copy to avoid user manipulation + if cls._table_names is not None: + return tuple(cls._table_names) # make a copy to avoid user manipulation + else: + return tuple() @classmethod def table_loaded(cls): + # type: () -> bool """ Check to see if the table is loaded. """ @@ -225,6 +267,7 @@ def table_loaded(cls): @classmethod def all(cls): + # type: () -> Set[Particle] """ Access, hence get hold of, the internal particle data CSV table, loading it from the default location if no table has yet been loaded. @@ -233,20 +276,21 @@ def all(cls): if not cls.table_loaded(): cls.load_table() - return cls._table + return cls._table if cls._table is not None else set() @classmethod def dump_table( cls, - exclusive_fields=[], - exclude_fields=[], + exclusive_fields=(), # type: Iterable[str] + exclude_fields=(), # type: Iterable[str] n_rows=-1, - filter_fn=None, - filename=None, + filter_fn=None, # type: Optional[Callable[[Particle], bool]] + filename=None, # type: Optional[str] tablefmt="simple", floatfmt=".12g", numalign="decimal", ): + # type: (...) -> Optional[str] """ Dump the internal particle data CSV table, loading it from the default location if no table has yet been loaded. @@ -323,12 +367,12 @@ def dump_table( cls.load_table() # Get all table headers from the class attributes - tbl_names = [a.name for a in Particle.__attrs_attrs__] + tbl_names = [a.name for a in Particle.__attrs_attrs__] # type: ignore # ... and replace '_three_charge' with the better, public property tbl_names[tbl_names.index("_three_charge")] = "three_charge" if exclusive_fields: - tbl_names = exclusive_fields + tbl_names = list(exclusive_fields) else: for fld in exclude_fields: try: @@ -337,7 +381,7 @@ def dump_table( pass # Start with the full table - tbl_all = cls.all() + tbl_all = sorted(cls.all()) # Apply a filter, if specified if filter_fn is not None: @@ -365,6 +409,7 @@ def dump_table( ), file=outfile, ) + return None else: return tabulate( tbl, @@ -375,33 +420,43 @@ def dump_table( ) @classmethod - def load_table(cls, filename=None, append=False): + def load_table(cls, filename=None, append=False, _name=None): + # type: (Union[None, str, TextIO], bool, Optional[str]) -> None """ Load a particle data CSV table. Optionally append to the existing data already loaded if append=True. As a special case, if this is called with append=True and the table is not loaded, the default will be loaded first before appending (set append=False if you don't want this behavior). + + A parameter is also included that should be considered private for now. It is _name, which + will override the filename for the stored filename in _table_names. """ + if append and not cls.table_loaded(): cls.load_table(append=False) # default load elif not append: - cls._table = [] + cls._table = set() cls._table_names = [] + # Tell MyPy that this is true + assert cls._table is not None + assert cls._table_names is not None + if filename is None: with data.open_text(data, "particle2019.csv") as f: - filename1 = f.name + cls.load_table(f, append=append, _name="particle2019.csv") with data.open_text(data, "nuclei2020.csv") as f: - filename2 = f.name - - # This only needs "closing" for Python 2 support - open_file = closing(fileinput.input(files=(filename1, filename2))) - cls._table_names.extend(["particle2019.csv", "nuclei2020.csv"]) + cls.load_table(f, append=True, _name="nuclei2020.csv") + return elif not hasattr(filename, "read"): cls._table_names.append(str(filename)) # Conversion to handle pathlib on Python < 3.6: open_file = open(str(filename)) else: - cls._table_names.append("{0!r} {1}".format(filename, len(cls._table_names))) + assert not isinstance(filename, str) # Tell typing that this is true + tmp_name = _name or getattr(filename, "name") + cls._table_names.append( + tmp_name or "{0!r} {1}".format(filename, len(cls._table_names)) + ) open_file = filename with open_file as f: @@ -411,11 +466,12 @@ def load_table(cls, filename=None, append=False): try: value = int(v["ID"]) - # Replace the previous value if appending - if append and value in cls._table: - cls._table.remove(value) + # Replace the previous value if it exists + # We can remove an int; ignore typing thinking we need a particle + if value in cls._table: + cls._table.remove(value) # type: ignore - cls._table.append( + cls._table.add( cls( pdgid=value, mass=float(v["Mass"]), @@ -443,6 +499,7 @@ def load_table(cls, filename=None, append=False): # The following __le__ and __eq__ needed for total ordering (sort, etc) def __le__(self, other): + # type: (Any) -> bool # Sort by absolute particle numbers # The positive one should come first if type(self) == type(other): @@ -453,6 +510,7 @@ def __le__(self, other): return int(self) < other def __eq__(self, other): + # type: (Any) -> bool try: return self.pdgid == other.pdgid except AttributeError: @@ -460,46 +518,52 @@ def __eq__(self, other): # Only one particle can exist per PDGID number def __hash__(self): + # type: () -> int return hash(self.pdgid) # Integer == PDGID def __int__(self): + # type: () -> int return int(self.pdgid) # Shared with PDGID @property def J(self): + # type: () -> int """ The total spin J quantum number. Note that the returned value corresponds to that effectively encoded in the particle PDG ID. """ - return self.pdgid.J + return self.pdgid.J # type: ignore @property def L(self): + # type: () -> Optional[int] """ The orbital angular momentum L quantum number (None if not a meson). Note that the returned value corresponds to that effectively encoded in the particle PDG ID. """ - return self.pdgid.L + return self.pdgid.L # type: ignore @property def S(self): + # type: () -> Optional[int] """ The spin S quantum number (None if not a meson). Note that the returned value corresponds to that effectively encoded in the particle PDG ID. """ - return self.pdgid.S + return self.pdgid.S # type: ignore @property def charge(self): + # type: () -> Optional[float] """ The particle charge, in units of the positron charge. @@ -509,19 +573,21 @@ def charge(self): Consistency of both ways of retrieving the particle charge is guaranteed for all PDG table particles. """ - return self.three_charge / 3 if self.three_charge is not None else None + return self.three_charge / 3 if self.three_charge is not None else None # type: ignore @property def three_charge(self): + # type: () -> Optional[int] "Three times the particle charge (charge * 3), in units of the positron charge." - if not self.pdgid.is_nucleus: + if not self.pdgid.is_nucleus: # type: ignore # Return int(...) not to return the actual enum Charge return int(self._three_charge) if self._three_charge != Charge.u else None else: - return self.pdgid.three_charge + return self.pdgid.three_charge # type: ignore @property def lifetime(self): + # type: () -> Optional[float] """ The particle lifetime, in nanoseconds. @@ -531,15 +597,21 @@ def lifetime(self): @property def ctau(self): + # type: () -> Optional[float] """ The particle c*tau, in millimeters. None is returned if the particle width (stored in the DB) is unknown. """ - return c_light * self.lifetime if self.width is not None else None + return ( + c_light * self.lifetime + if self.width is not None and self.lifetime is not None + else None + ) @property def is_name_barred(self): + # type: () -> bool """ Check to see if particle is inverted (hence is it an antiparticle) and has a bar in its name. @@ -547,18 +619,19 @@ def is_name_barred(self): return self.pdgid < 0 and self.anti_flag == Inv.Barred @property - def spin_type(self): # -> SpinType: + def spin_type(self): + # type: () -> SpinType """ Access the SpinType enum. Note that this is relevant for bosons only. SpinType.NonDefined is returned otherwise. """ # Non-valid or non-standard PDG IDs - if self.pdgid.j_spin is None: + if self.pdgid.j_spin is None: # type: ignore return SpinType.NonDefined # Fermions - 2J+1 is always an even number - if self.pdgid.j_spin % 2 == 0: + if self.pdgid.j_spin % 2 == 0: # type: ignore return SpinType.NonDefined if self.J in [0, 1, 2]: @@ -575,6 +648,7 @@ def spin_type(self): # -> SpinType: @property def is_self_conjugate(self): + # type: () -> bool """ Is the particle self-conjugate, i.e. its own antiparticle? """ @@ -582,16 +656,18 @@ def is_self_conjugate(self): @property def is_unflavoured_meson(self): + # type: () -> bool """ Unflavoured mesons are self-conjugate (hence zero-charge) mesons with all their flavour (strange, charm, bottom and top) quantum numbers equal to zero. """ - if self.is_self_conjugate and self.three_charge == 0 and self.pdgid.is_meson: + if self.is_self_conjugate and self.three_charge == 0 and self.pdgid.is_meson: # type: ignore return True else: return False def invert(self): + # type: () -> Particle "Get the antiparticle." if self.anti_flag == Inv.Barred or ( self.anti_flag == Inv.ChargeInv and self.three_charge != Charge.o @@ -606,6 +682,7 @@ def invert(self): # Pretty descriptions def __str__(self): + # type: () -> str _tilde = "~" if self.anti_flag == Inv.Barred and self.pdgid < 0 else "" _charge = self._str_charge() if self._charge_in_name() else "" return self.pdg_name + _tilde + _charge @@ -616,10 +693,12 @@ def __str__(self): ) def _repr_latex_(self): + # type: () -> str name = self.latex_name return ("$" + name + "$") if self.latex_name else "?" def _width_or_lifetime(self): + # type: () -> str """ Display either the particle width or the lifetime. Internally used by the describe() method. @@ -632,11 +711,12 @@ def _width_or_lifetime(self): return "Width = None" elif self.width == 0: return "Width = 0.0 MeV" - elif self.width_lower is None and self.width_upper is None: + elif self.width_lower is None or self.width_upper is None: return "Width < {width} MeV".format(width=self.width) elif ( self.width < 0.05 ): # corresponds to a lifetime of approximately 1.3e-20 seconds + assert self.lifetime is not None if self.width_lower == self.width_upper: e = width_to_lifetime(self.width - self.width_lower) - self.lifetime s = "Lifetime = {lifetime} ns".format( @@ -659,6 +739,7 @@ def _width_or_lifetime(self): ) def _charge_in_name(self): + # type: () -> bool """Assess whether the particle charge is part of the particle name. Internally used when creating the name. @@ -667,14 +748,12 @@ def _charge_in_name(self): return True # antiparticle flips sign of particle if self.pdgid in (23, 25, 111, 130, 310, 311, -311): return True # the Z0, H0, pi0, KL0, KS0, K0 and K0bar - if self.pdgid.is_diquark: + if self.pdgid.is_diquark: # type: ignore return False if abs(self.pdgid) in (2212, 2112): return False # proton and neutron if abs(self.pdgid) < 19: - return ( - False - ) # all quarks and neutrinos (charged leptons dealt with in 1st line of if statements ;-)) + return False # all quarks and neutrinos (charged leptons dealt with in 1st line of if statements ;-)) if self.three_charge is None: return False # deal with corner cases ;-) if self.is_self_conjugate: @@ -691,37 +770,41 @@ def _charge_in_name(self): ] ): return False - elif pid.has_strange or pid.has_charm or pid.has_bottom or pid.has_top: + elif pid.has_strange or pid.has_charm or pid.has_bottom or pid.has_top: # type: ignore return False else: # Light unflavoured mesons return True # Lambda baryons if ( - self.pdgid.is_baryon + self.pdgid.is_baryon # type: ignore and _digit(self.pdgid, Location.Nq2) == 1 and self.I == 0.0 # 1st check alone is not sufficient to filter out lowest-ground Sigma's - and self.pdgid.has_strange + and self.pdgid.has_strange # type: ignore and not ( - self.pdgid.has_charm or self.pdgid.has_bottom or self.pdgid.has_top + self.pdgid.has_charm or self.pdgid.has_bottom or self.pdgid.has_top # type: ignore ) ): return False - if self.pdgid.is_nucleus: + if self.pdgid.is_nucleus: # type: ignore return False return True def _str_charge(self): + # type: () -> str """ Display a reasonable particle charge printout. Internally used by the describe() and __str__ methods. """ - if not self.pdgid.is_nucleus: - return Charge_undo[self.three_charge] + if self._three_charge is None: + return "None" + elif not self.pdgid.is_nucleus: # type: ignore + return Charge_undo[Charge(self._three_charge)] else: - return int(self.pdgid.charge) + return str(self.pdgid.charge) # type: ignore def _str_mass(self): + # type: () -> str """ Display a reasonable particle mass printout even when no mass value is available. @@ -735,6 +818,7 @@ def _str_mass(self): ) def describe(self): + # type: () -> str "Make a nice high-density string for a particle's properties." if self.pdgid == 0: return "Name: Unknown" @@ -766,26 +850,31 @@ def describe(self): @property def evtgen_name(self): + # type: () -> str "This is the name used in EvtGen." return EvtGenName2PDGIDBiMap[self.pdgid] @property def programmatic_name(self): + # type: () -> str "This name could be used for a variable name." return programmatic_name(self.name) @property def html_name(self): + # type: () -> str "This is the name using HTML instead of LaTeX." return latex_to_html_name(self.latex_name) @classmethod def empty(cls): + # type: () -> Particle "Make a new empty particle." return cls(0, "Unknown", anti_flag=Inv.Same) @classmethod def from_pdgid(cls, value): + # type: (SupportsInt) -> Particle """ Get a particle from a PDGID. Uses by default the package extended PDG data table. @@ -799,14 +888,21 @@ def from_pdgid(cls, value): """ if not is_valid(value): raise InvalidParticle("Input PDGID {0} is invalid!".format(value)) - table = cls.all() - if value in table: - return table[table.index(value)] + + for item in cls.all(): + if item.pdgid == value: + return item else: raise ParticleNotFound("Could not find PDGID {0}".format(value)) @classmethod - def findall(cls, filter_fn=None, particle=None, **search_terms): + def findall( + cls, + filter_fn=None, # type: Optional[Callable[[Particle], bool]] + particle=None, # type: Optional[bool] + **search_terms # type: Any + ): + # type: (...) -> List[Particle] """ Search for a particle, returning a list of candidates. @@ -909,6 +1005,7 @@ def findall(cls, filter_fn=None, particle=None, **search_terms): @classmethod def find(cls, *args, **search_terms): + # type: (...) -> Particle """ Require that the search returns one and only one result. The method otherwise raises a ParticleNotFound or RuntimeError exception. @@ -936,6 +1033,7 @@ def find(cls, *args, **search_terms): @classmethod def from_evtgen_name(cls, name): + # type: (str) -> Particle """ Get a particle from an EvtGen particle name, as in .dec decay files. @@ -950,6 +1048,7 @@ def from_evtgen_name(cls, name): @classmethod def from_string(cls, name): + # type: (str) -> Particle "Get a particle from a PDG style name - returns the best match." matches = cls.from_string_list(name) if matches: @@ -959,6 +1058,7 @@ def from_string(cls, name): @classmethod def from_string_list(cls, name): + # type: (str) -> List[Particle] "Get a list of particles from a PDG style name." # Forcible override @@ -978,12 +1078,12 @@ def from_string_list(cls, name): if list_can: return list_can - mat = getname.match(short_name) + mat_str = getname.match(short_name) - if mat is None: + if mat_str is None: return [] - mat = mat.groupdict() + mat = mat_str.groupdict() if particle is False: mat["bar"] = "bar" @@ -995,8 +1095,9 @@ def from_string_list(cls, name): @classmethod def _from_group_dict_list(cls, mat): + # type: (Dict[str, Any]) -> List[Particle] - kw = dict() + kw = dict() # type: Dict[str, Any] kw["particle"] = ( False if mat["bar"] is not None diff --git a/src/particle/particle/regex.py b/src/particle/particle/regex.py index 7fbebaf7..9dfc7ac0 100644 --- a/src/particle/particle/regex.py +++ b/src/particle/particle/regex.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/particle/utilities.py b/src/particle/particle/utilities.py index 86c9f88a..06404c83 100644 --- a/src/particle/particle/utilities.py +++ b/src/particle/particle/utilities.py @@ -8,8 +8,11 @@ import math import unicodedata +from typing import Optional + def programmatic_name(name): + # type: (str) -> str "Return a name safe to use as a variable name." name = re.sub("0$", "_0", name) name = name if "~" not in name else "".join(name.split("~")) + "_bar" @@ -29,6 +32,7 @@ def programmatic_name(name): def str_with_unc(value, upper, lower=None): + # type: (float, Optional[float], Optional[float]) -> str """ Utility to print out an uncertainty with different or identical upper/lower bounds. Nicely formats numbers using PDG rule. @@ -115,6 +119,7 @@ def str_with_unc(value, upper, lower=None): def greek_letter_name_to_unicode(letter): + # type: (str) -> str """ Return a greek letter name as a Unicode character. @@ -132,6 +137,7 @@ def greek_letter_name_to_unicode(letter): def latex_to_html_name(name): + # type:(str) -> str """Conversion of particle names from LaTeX to HTML.""" name = re.sub(r"\^\{(.*?)\}", r"\1", name) name = re.sub(r"\_\{(.*?)\}", r"\1", name) diff --git a/src/particle/pdgid/__init__.py b/src/particle/pdgid/__init__.py index ca399108..1a408fbc 100644 --- a/src/particle/pdgid/__init__.py +++ b/src/particle/pdgid/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/pdgid/functions.py b/src/particle/pdgid/functions.py index ffbc1c39..7d49cc7a 100644 --- a/src/particle/pdgid/functions.py +++ b/src/particle/pdgid/functions.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE @@ -24,12 +25,10 @@ from __future__ import print_function, division, absolute_import -# Backport needed if Python 2 is used -# Try only needed due to zipapp install -try: - from enum import IntEnum -except ImportError: - from enum34 import IntEnum # type: ignore +from enum import IntEnum +from typing import SupportsInt, Optional + +PDGID_TYPE = SupportsInt class Location(IntEnum): @@ -50,6 +49,7 @@ class Location(IntEnum): def is_valid(pdgid): + # type: (PDGID_TYPE) -> bool """Is it a valid PDG ID?""" if _fundamental_id(pdgid) > 0: return True @@ -79,11 +79,13 @@ def is_valid(pdgid): def abspid(pdgid): + # type: (PDGID_TYPE) -> int """Returns the absolute value of the PDG ID.""" - return abs(pdgid) + return abs(int(pdgid)) def is_lepton(pdgid): + # type: (PDGID_TYPE) -> bool """Does this PDG ID correspond to a lepton?""" if _extra_bits(pdgid) > 0: return False @@ -93,10 +95,11 @@ def is_lepton(pdgid): def is_hadron(pdgid): + # type: (PDGID_TYPE) -> bool """Does this PDG ID correspond to a hadron?""" # Special case of proton and neutron: # needs to be checked first since _extra_bits(pdgid) > 0 for nuclei - if abs(pdgid) in (1000000010, 1000010010): + if abs(int(pdgid)) in (1000000010, 1000010010): return True if _extra_bits(pdgid) > 0: return False @@ -110,6 +113,7 @@ def is_hadron(pdgid): def is_meson(pdgid): + # type: (PDGID_TYPE) -> bool """Does this PDG ID correspond to a meson?""" if _extra_bits(pdgid) > 0: return False @@ -121,7 +125,7 @@ def is_meson(pdgid): return True if abspid(pdgid) in (150, 350, 510, 530): return True - if pdgid in (110, 990, 9990): + if int(pdgid) in (110, 990, 9990): return True if ( _digit(pdgid, Location.Nj) > 0 @@ -130,7 +134,10 @@ def is_meson(pdgid): and _digit(pdgid, Location.Nq1) == 0 ): # check for illegal antiparticles - if _digit(pdgid, Location.Nq3) == _digit(pdgid, Location.Nq2) and pdgid < 0: + if ( + _digit(pdgid, Location.Nq3) == _digit(pdgid, Location.Nq2) + and int(pdgid) < 0 + ): return False else: return True @@ -138,12 +145,13 @@ def is_meson(pdgid): def is_baryon(pdgid): + # type: (PDGID_TYPE) -> bool """Does this PDG ID correspond to a baryon?""" if abspid(pdgid) <= 100: return False # Special case of proton and neutron: # needs to be checked first since _extra_bits(pdgid) > 0 for nuclei - if abs(pdgid) in (1000000010, 1000010010): + if abs(int(pdgid)) in (1000000010, 1000010010): return True if _extra_bits(pdgid) > 0: return False @@ -164,6 +172,7 @@ def is_baryon(pdgid): def is_diquark(pdgid): + # type: (PDGID_TYPE) -> bool """Does this PDG ID correspond to a diquark?""" if _extra_bits(pdgid) > 0: return False @@ -182,6 +191,7 @@ def is_diquark(pdgid): def is_nucleus(pdgid): + # type: (PDGID_TYPE) -> bool """ Does this PDG ID correspond to a nucleus? @@ -198,12 +208,18 @@ def is_nucleus(pdgid): return True if _digit(pdgid, Location.N10) == 1 and _digit(pdgid, Location.N9) == 0: # Charge should always be less than or equal to the baryon number - if A(pdgid) >= abs(Z(pdgid)): + A_pdgid = A(pdgid) + Z_pdgid = Z(pdgid) + + if A_pdgid is None or Z_pdgid is None: + return False + elif A_pdgid >= abs(Z_pdgid): return True return False def is_pentaquark(pdgid): + # type: (PDGID_TYPE) -> bool """ Does the PDG ID correspond to a pentaquark? @@ -237,6 +253,7 @@ def is_pentaquark(pdgid): def is_Rhadron(pdgid): + # type: (PDGID_TYPE) -> bool """Does this PDG ID correspond to an R-hadron? An R-hadron is of the form 10abcdj, 100abcj, or 1000abj, @@ -262,6 +279,7 @@ def is_Rhadron(pdgid): def is_Qball(pdgid): + # type: (PDGID_TYPE) -> bool """ Does this PDG ID correspond to a Q-ball or any exotic particle with electric charge beyond the qqq scheme? @@ -281,6 +299,7 @@ def is_Qball(pdgid): def is_dyon(pdgid): + # type: (PDGID_TYPE) -> bool """ Does this PDG ID correspond to a Dyon, a magnetic monopole? @@ -308,6 +327,7 @@ def is_dyon(pdgid): def is_SUSY(pdgid): + # type: (PDGID_TYPE) -> bool """ Does this PDG ID correspond to a SUSY particle? @@ -325,36 +345,43 @@ def is_SUSY(pdgid): def has_down(pdgid): + # type: (PDGID_TYPE) -> bool """Does this particle contain a down quark?""" return _has_quark_q(pdgid, 1) def has_up(pdgid): + # type: (PDGID_TYPE) -> bool """Does this particle contain an up quark?""" return _has_quark_q(pdgid, 2) def has_strange(pdgid): + # type: (PDGID_TYPE) -> bool """Does this particle contain a strange quark?""" return _has_quark_q(pdgid, 3) def has_charm(pdgid): + # type: (PDGID_TYPE) -> bool """Does this particle contain a charm quark?""" return _has_quark_q(pdgid, 4) def has_bottom(pdgid): + # type: (PDGID_TYPE) -> bool """Does this particle contain a bottom quark?""" return _has_quark_q(pdgid, 5) def has_top(pdgid): + # type: (PDGID_TYPE) -> bool """Does this particle contain a top quark?""" return _has_quark_q(pdgid, 6) def has_fundamental_anti(pdgid): + # type: (PDGID_TYPE) -> bool """If this is a fundamental particle, does it have a valid antiparticle?""" # These are defined by the generator and therefore are always valid fid = _fundamental_id(pdgid) @@ -362,22 +389,26 @@ def has_fundamental_anti(pdgid): return True # Check PDGIDs from 1 to 79 _cp_conjugates = (21, 22, 23, 25, 32, 33, 35, 36, 39, 41) - if fid in range(1, 80) and fid not in _cp_conjugates and is_valid(abs(pdgid)): + if fid in range(1, 80) and fid not in _cp_conjugates and is_valid(abs(int(pdgid))): return True return False def charge(pdgid): + # type: (PDGID_TYPE) -> Optional[float] """Returns the charge.""" - if not is_valid(pdgid): + + three_charge_pdgid = three_charge(pdgid) + if three_charge_pdgid is None: return None - if not is_Qball(pdgid): - return three_charge(pdgid) / 3.0 + elif not is_Qball(pdgid): + return three_charge_pdgid / 3.0 else: - return three_charge(pdgid) / 30.0 + return three_charge_pdgid / 30.0 def three_charge(pdgid): + # type: (PDGID_TYPE) -> Optional[int] """ Returns 3 times the charge. @@ -497,7 +528,11 @@ def three_charge(pdgid): if _extra_bits(pdgid) > 0: if is_nucleus(pdgid): # ion - return 3 * Z(pdgid) + Z_pdgid = Z(pdgid) + if Z_pdgid is None: + return None + else: + return 3 * Z_pdgid elif is_Qball(pdgid): # Qball charge = 3 * ((aid // 10) % 10000) else: # this should never be reached in the present numbering scheme @@ -527,14 +562,14 @@ def three_charge(pdgid): is_Rhadron(pdgid) and _digit(pdgid, Location.Nl) == 9 ): # baryons charge = ch100[q3 - 1] + ch100[q2 - 1] + ch100[q1 - 1] - if charge == 0: - return 0 - elif pdgid < 0: + + if charge is not None and int(pdgid) < 0: charge = -charge return charge def j_spin(pdgid): + # type: (PDGID_TYPE) -> Optional[int] """Returns the total spin as 2J+1.""" if not is_valid(pdgid): return None @@ -549,7 +584,7 @@ def j_spin(pdgid): if fund > 20 and fund < 25: return 3 return None - elif abs(pdgid) in (1000000010, 1000010010): # neutron, proton + elif abs(int(pdgid)) in (1000000010, 1000010010): # neutron, proton return 2 elif _extra_bits(pdgid) > 0: return None @@ -559,6 +594,7 @@ def j_spin(pdgid): def J(pdgid): + # type: (PDGID_TYPE) -> Optional[float] """Returns the total spin J.""" value = j_spin(pdgid) return ( @@ -567,6 +603,7 @@ def J(pdgid): def S(pdgid): + # type: (PDGID_TYPE) -> Optional[int] """ Returns the spin S. @@ -601,6 +638,7 @@ def S(pdgid): def s_spin(pdgid): + # type: (PDGID_TYPE) -> Optional[int] """ Returns the spin S as 2S+1. @@ -615,6 +653,7 @@ def s_spin(pdgid): def L(pdgid): + # type: (PDGID_TYPE) -> Optional[int] """ Returns the orbital angular momentum L. @@ -674,6 +713,7 @@ def L(pdgid): def l_spin(pdgid): + # type: (PDGID_TYPE) -> Optional[int] """ Returns the orbital angular momentum L as 2L+1. @@ -688,6 +728,7 @@ def l_spin(pdgid): def A(pdgid): + # type: (PDGID_TYPE) -> Optional[int] """Returns the atomic number A if the PDG ID corresponds to a nucleus. Else it returns None.""" # A proton can be a Hydrogen nucleus # A neutron can be considered as a nucleus when given the PDG ID 1000000010, @@ -700,20 +741,22 @@ def A(pdgid): def Z(pdgid): + # type: (PDGID_TYPE) -> Optional[int] """Returns the charge Z if the PDG ID corresponds to a nucleus. Else it returns None.""" # A proton can be a Hydrogen nucleus if abspid(pdgid) == 2212: - return pdgid // 2212 + return int(pdgid) // 2212 # A neutron can be considered as a nucleus when given the PDG ID 1000000010, # hence consistency demands that Z(neutron) = 0 if abspid(pdgid) == 2112: return 0 if _digit(pdgid, Location.N10) != 1 or _digit(pdgid, Location.N9) != 0: return None - return ((abspid(pdgid) // 10000) % 1000) * (pdgid // abs(pdgid)) + return ((abspid(pdgid) // 10000) % 1000) * (int(pdgid) // abs(int(pdgid))) def _digit(pdgid, loc): + # type: (PDGID_TYPE, int) -> int """ Provides a convenient index into the PDGID number, whose format is in base 10. @@ -724,6 +767,7 @@ def _digit(pdgid, loc): def _extra_bits(pdgid): + # type: (PDGID_TYPE) -> int """ Returns everything beyond the 7th digit, so anything outside the PDG numbering scheme. """ @@ -731,6 +775,7 @@ def _extra_bits(pdgid): def _fundamental_id(pdgid): + # type: (PDGID_TYPE) -> int """ Returns the first 2 digits if this is a "fundamental" particle. Returns 0 if the particle is not fundamental or not standard (PDG ID with more than 7 digits). @@ -748,6 +793,7 @@ def _fundamental_id(pdgid): def _has_quark_q(pdgid, q): + # type: (PDGID_TYPE, int) -> bool """ Helper function - does this particle contain a quark q? diff --git a/src/particle/pdgid/literals.py b/src/particle/pdgid/literals.py index 340ac03b..b05c2f38 100644 --- a/src/particle/pdgid/literals.py +++ b/src/particle/pdgid/literals.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/pdgid/pdgid.py b/src/particle/pdgid/pdgid.py index f9468c87..9f054b35 100644 --- a/src/particle/pdgid/pdgid.py +++ b/src/particle/pdgid/pdgid.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE @@ -37,19 +38,23 @@ class PDGID(int): __slots__ = () # Keep PDGID a slots based class def __repr__(self): + # type: () -> str return "".format( - int(self), "" if self.is_valid else " (is_valid==False)" + int(self), "" if _functions.is_valid(self) else " (is_valid==False)" ) def __str__(self): + # type: () -> str return repr(self) def __neg__(self): + # type: () -> PDGID return self.__class__(-int(self)) __invert__ = __neg__ def info(self): + # type: () -> str """ Print all PDGID properties one per line, for easy inspection. """ diff --git a/src/particle/pythia/__init__.py b/src/particle/pythia/__init__.py index 5fe7abda..adf8a176 100644 --- a/src/particle/pythia/__init__.py +++ b/src/particle/pythia/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/pythia/pythiaid.py b/src/particle/pythia/pythiaid.py index eebba2a8..753c5300 100644 --- a/src/particle/pythia/pythiaid.py +++ b/src/particle/pythia/pythiaid.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/src/particle/shared_literals.py b/src/particle/shared_literals.py index e4ea4de7..a6865f3d 100644 --- a/src/particle/shared_literals.py +++ b/src/particle/shared_literals.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/__init__.py b/tests/__init__.py index 748dc704..163be0b5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/conftest.py b/tests/conftest.py index 8ae1decb..e13ec354 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/converters/__init__.py b/tests/converters/__init__.py index 748dc704..163be0b5 100644 --- a/tests/converters/__init__.py +++ b/tests/converters/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/converters/test_maps.py b/tests/converters/test_maps.py index a47a1868..c9db04bb 100644 --- a/tests/converters/test_maps.py +++ b/tests/converters/test_maps.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/geant/__init__.py b/tests/geant/__init__.py index 748dc704..163be0b5 100644 --- a/tests/geant/__init__.py +++ b/tests/geant/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/geant/test_geant3id.py b/tests/geant/test_geant3id.py index f6817035..a01d1ec7 100644 --- a/tests/geant/test_geant3id.py +++ b/tests/geant/test_geant3id.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/particle/__init__.py b/tests/particle/__init__.py index 748dc704..163be0b5 100644 --- a/tests/particle/__init__.py +++ b/tests/particle/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/particle/test_decfilenames.py b/tests/particle/test_decfilenames.py index f68ffc32..d6792ed9 100644 --- a/tests/particle/test_decfilenames.py +++ b/tests/particle/test_decfilenames.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/particle/test_enums.py b/tests/particle/test_enums.py index 074aad41..50e42472 100644 --- a/tests/particle/test_enums.py +++ b/tests/particle/test_enums.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/particle/test_generation.py b/tests/particle/test_generation.py index a1125686..ece5611c 100644 --- a/tests/particle/test_generation.py +++ b/tests/particle/test_generation.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/particle/test_kinematics.py b/tests/particle/test_kinematics.py index 2b9ef30f..febbc059 100644 --- a/tests/particle/test_kinematics.py +++ b/tests/particle/test_kinematics.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/particle/test_literals.py b/tests/particle/test_literals.py index 7c03b88a..3660d297 100644 --- a/tests/particle/test_literals.py +++ b/tests/particle/test_literals.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/particle/test_particle.py b/tests/particle/test_particle.py index b4032b44..77d897a2 100644 --- a/tests/particle/test_particle.py +++ b/tests/particle/test_particle.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/pdgid/__init__.py b/tests/pdgid/__init__.py index 748dc704..163be0b5 100644 --- a/tests/pdgid/__init__.py +++ b/tests/pdgid/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/pdgid/test_functions.py b/tests/pdgid/test_functions.py index 5063831e..7a6bf71a 100644 --- a/tests/pdgid/test_functions.py +++ b/tests/pdgid/test_functions.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/pdgid/test_literals.py b/tests/pdgid/test_literals.py index be3ce442..0dd114e0 100644 --- a/tests/pdgid/test_literals.py +++ b/tests/pdgid/test_literals.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/pdgid/test_pdgid.py b/tests/pdgid/test_pdgid.py index a2387c0b..6913ea8f 100644 --- a/tests/pdgid/test_pdgid.py +++ b/tests/pdgid/test_pdgid.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/pythia/__init__.py b/tests/pythia/__init__.py index 748dc704..163be0b5 100644 --- a/tests/pythia/__init__.py +++ b/tests/pythia/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/pythia/test_pythiaid.py b/tests/pythia/test_pythiaid.py index 9ac1c4ba..b435d05b 100644 --- a/tests/pythia/test_pythiaid.py +++ b/tests/pythia/test_pythiaid.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE diff --git a/tests/test_package.py b/tests/test_package.py index 727fa8d7..72525c43 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (c) 2018-2020, Eduardo Rodrigues and Henry Schreiner. # # Distributed under the 3-clause BSD license, see accompanying file LICENSE