From 02d4655bf4c27578ca96c082501b517710570e57 Mon Sep 17 00:00:00 2001 From: Saransh Date: Mon, 4 Jul 2022 20:33:11 +0530 Subject: [PATCH] chore: pass repo review (#219) * chore: pass repo review * chore: add `noxfile` * chore: fix pre-commit check * Add `noxfile` to `MANIFEST.in` * Ignore `DeprecationWarning`s * Update setup.cfg Co-authored-by: Henry Schreiner * chore: cleanup the changes made by pre-commit * chore: add license badge and update MANIFEST.in Co-authored-by: Henry Schreiner --- .github/CONTRIBUTING.md | 4 +- .github/workflows/cd.yml | 40 +- .github/workflows/ci.yml | 82 ++-- .pre-commit-config.yaml | 160 ++++--- .readthedocs.yml | 8 +- MANIFEST.in | 2 +- README.md | 618 +++++++++++++++------------ docs/changelog.md | 15 +- docs/index.rst | 4 +- environment.yml | 8 +- noxfile.py | 58 +++ pyproject.toml | 10 +- setup.cfg | 5 +- src/vector/_methods.py | 150 ++----- src/vector/backends/_numba_object.py | 3 +- src/vector/backends/awkward.py | 35 +- src/vector/backends/numpy.py | 105 ++--- src/vector/backends/object.py | 48 +-- 18 files changed, 672 insertions(+), 683 deletions(-) create mode 100644 noxfile.py diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 738ada06..084c896e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,7 +1,6 @@ - ## Development install -You should *always* use a virtual environment when developing software. Setup: +You should _always_ use a virtual environment when developing software. Setup: ```bash python3 -m venv .env @@ -21,7 +20,6 @@ python -m ipykernel install --user --name vector # For notebooks You can update the environment with `conda env update`. - ## Docs The documentation is in `/docs`. To rebuild the API docs: diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e36c26f5..8d71fde3 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -4,25 +4,25 @@ on: workflow_dispatch: release: types: - - published + - published jobs: dist: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 + - uses: actions/checkout@v3 + with: + fetch-depth: 0 - - name: Build SDist and wheel - run: pipx run build + - name: Build SDist and wheel + run: pipx run build - - uses: actions/upload-artifact@v3 - with: - path: dist/* + - uses: actions/upload-artifact@v3 + with: + path: dist/* - - name: Check metadata - run: pipx run twine check dist/* + - name: Check metadata + run: pipx run twine check dist/* publish: needs: [dist] @@ -30,12 +30,12 @@ jobs: if: github.event_name == 'release' && github.event.action == 'published' steps: - - uses: actions/download-artifact@v3 - with: - name: artifact - path: dist - - - uses: pypa/gh-action-pypi-publish@v1.5.0 - with: - user: __token__ - password: ${{ secrets.pypi_password }} + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + + - uses: pypa/gh-action-pypi-publish@v1.5.0 + with: + user: __token__ + password: ${{ secrets.pypi_password }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40081534..c3c12023 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,8 +4,8 @@ on: pull_request: push: branches: - - main - - develop + - main + - develop workflow_dispatch: concurrency: @@ -19,13 +19,13 @@ jobs: runs-on: ubuntu-latest name: Check SDist steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.x - - uses: pre-commit/action@v3.0.0 - with: - extra_args: --hook-stage manual check-manifest + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - uses: pre-commit/action@v3.0.0 + with: + extra_args: --hook-stage manual check-manifest checks: runs-on: ubuntu-latest @@ -33,57 +33,57 @@ jobs: fail-fast: false matrix: python-version: - - 3.6 - - 3.7 - - 3.8 - - 3.9 + - 3.6 + - 3.7 + - 3.8 + - 3.9 name: Check Python ${{ matrix.python-version }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} - - name: Requirements check - run: python -m pip list + - name: Requirements check + run: python -m pip list - - name: Install package - run: python -m pip install -e .[test] + - name: Install package + run: python -m pip install -e .[test] - - name: Test light package - run: python -m pytest -ra + - name: Test light package + run: python -m pytest -ra - - name: Install develop extras - run: python -m pip install -e .[dev] + - name: Install develop extras + run: python -m pip install -e .[dev] - - name: Test package - run: python -m pytest -ra --cov=vector tests/ + - name: Test package + run: python -m pytest -ra --cov=vector tests/ - - name: Run doctests - run: xdoctest ./src/vector/ + - name: Run doctests + run: xdoctest ./src/vector/ - - name: Upload coverage report - uses: codecov/codecov-action@v3.1.0 + - name: Upload coverage report + uses: codecov/codecov-action@v3.1.0 discheck: runs-on: ubuntu-latest name: Disassemble check steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.8.8 + - uses: actions/setup-python@v4 + with: + python-version: 3.8.8 - - name: Requirements check - run: python -m pip list + - name: Requirements check + run: python -m pip list - - name: Check compute features - run: python -m pip install .[test,test_extras] + - name: Check compute features + run: python -m pip install .[test,test_extras] - - name: Test package - run: python -m pytest -ra -m dis + - name: Test package + run: python -m pytest -ra -m dis # root: # runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7a8b6679..178f7795 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,81 +1,99 @@ +ci: + autoupdate_commit_msg: "chore: update pre-commit hooks" + autofix_commit_msg: "style: pre-commit fixes" + repos: -- repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 - hooks: - - id: pyupgrade - args: [--py36-plus] + - repo: https://github.com/asottile/pyupgrade + rev: v2.34.0 + hooks: + - id: pyupgrade + args: [--py36-plus] + + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + exclude: ^docs + - id: mixed-line-ending + - id: requirements-txt-fixer + - id: trailing-whitespace -- repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 - hooks: - - id: check-added-large-files - - id: check-case-conflict - - id: check-merge-conflict - - id: check-symlinks - - id: check-yaml - - id: debug-statements - - id: end-of-file-fixer - exclude: ^docs - - id: mixed-line-ending - - id: requirements-txt-fixer - - id: trailing-whitespace + - repo: https://github.com/asottile/setup-cfg-fmt + rev: v1.20.1 + hooks: + - id: setup-cfg-fmt -- repo: https://github.com/PyCQA/isort - rev: 5.10.1 - hooks: - - id: isort + - repo: https://github.com/mgedmin/check-manifest + rev: "0.48" + hooks: + - id: check-manifest + stages: [manual] -- repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.20.1 - hooks: - - id: setup-cfg-fmt + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.961 + hooks: + - id: mypy + files: src + args: [--show-error-codes] + additional_dependencies: + - numpy==1.22.0 + - packaging -- repo: https://github.com/mgedmin/check-manifest - rev: "0.48" - hooks: - - id: check-manifest - stages: [manual] + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + additional_dependencies: + - flake8-bugbear + - flake8-docstrings + - flake8-print -- repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.961 - hooks: - - id: mypy - files: src - args: [--show-error-codes] - additional_dependencies: - - numpy==1.22.0 - - packaging + - repo: https://github.com/codespell-project/codespell + rev: v2.1.0 + hooks: + - id: codespell + args: ["-L", "hist,nd,circularly,ba"] + exclude: ^(notebooks/xarray.ipynb|notebooks/BoostHistogramHandsOn.ipynb)$ -- repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 - hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear - - flake8-docstrings - - flake8-print + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v2.7.1" + hooks: + - id: prettier + types_or: [yaml, markdown, html, css, scss, javascript, json] + exclude: assets/js/webapp\.js -- repo: https://github.com/codespell-project/codespell - rev: v2.1.0 - hooks: - - id: codespell - args: ["-L", "hist,nd,circularly,ba"] - exclude: ^(notebooks/xarray.ipynb|notebooks/BoostHistogramHandsOn.ipynb)$ + - repo: https://github.com/asottile/blacken-docs + rev: v1.12.1 + hooks: + - id: blacken-docs + args: ["-E"] + additional_dependencies: [black==22.3.0] -- repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.9.0 - hooks: - - id: python-check-blanket-noqa - - id: python-check-blanket-type-ignore - exclude: ^src/vector/backends/_numba_object.py$ - - id: python-no-log-warn - - id: python-no-eval - - id: python-use-type-annotations - - id: rst-backticks - - id: rst-directive-colons - - id: rst-inline-touching-normal + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.9.0 + hooks: + - id: python-check-blanket-noqa + - id: python-check-blanket-type-ignore + exclude: ^src/vector/backends/_numba_object.py$ + - id: python-no-log-warn + - id: python-no-eval + - id: python-use-type-annotations + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal diff --git a/.readthedocs.yml b/.readthedocs.yml index 6ea8bfa9..93d18b40 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -15,7 +15,7 @@ formats: all python: version: 3.8 install: - - method: pip - path: . - extra_requirements: - - docs + - method: pip + path: . + extra_requirements: + - docs diff --git a/MANIFEST.in b/MANIFEST.in index 8a227269..fb6240d3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ -include LICENSE README.md +include LICENSE README.md pyproject.toml setup.py setup.cfg recursive-include src *.typed *.pyi recursive-include tests *.py *.pkl diff --git a/README.md b/README.md index 9439c05a..e70da428 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ # Vector: arrays of 2D, 3D, and Lorentz vectors - [![Actions Status][actions-badge]][actions-link] [![Documentation Status][rtd-badge]][rtd-link] [![pre-commit.ci status][pre-commit-badge]][pre-commit-link] @@ -15,25 +14,23 @@ [![PyPI version][pypi-version]][pypi-link] [![Conda latest release][conda-version]][conda-link] [![DOI][zenodo-badge]][zenodo-link] +[![LICENSE][license-badge]][license-link] [![Scikit-HEP][sk-badge]][sk-link] - - - Vector is a Python 3.6+ library for 2D, 3D, and [Lorentz vectors](https://en.wikipedia.org/wiki/Special_relativity#Physics_in_spacetime), especially _arrays of vectors_, to solve common physics problems in a NumPy-like way. Main features of Vector: - * Pure Python with NumPy as its only dependency. This makes it easier to install. - * Vectors may be represented in a variety of coordinate systems: Cartesian, cylindrical, pseudorapidity, and any combination of these with time or proper time for Lorentz vectors. In all, there are 12 coordinate systems: {_x_-_y_ vs _ρ_-_φ_ in the azimuthal plane} × {_z_ vs _θ_ vs _η_ longitudinally} × {_t_ vs _τ_ temporally}. - * Uses names and conventions set by [ROOT](https://root.cern/)'s [TLorentzVector](https://root.cern.ch/doc/master/classTLorentzVector.html) and [Math::LorentzVector](https://root.cern.ch/doc/master/classROOT_1_1Math_1_1LorentzVector.html), as well as [scikit-hep/math](https://github.com/scikit-hep/scikit-hep/tree/master/skhep/math), [uproot-methods TLorentzVector](https://github.com/scikit-hep/uproot3-methods/blob/master/uproot3_methods/classes/TLorentzVector.py), [henryiii/hepvector](https://github.com/henryiii/hepvector), and [coffea.nanoevents.methods.vector](https://coffeateam.github.io/coffea/modules/coffea.nanoevents.methods.vector.html). - * Implemented on a variety of backends: - - pure Python objects - - NumPy arrays of vectors (as a [structured array](https://numpy.org/doc/stable/user/basics.rec.html) subclass) - - [Awkward Arrays](https://awkward-array.org/) of vectors - - potential for more: CuPy, TensorFlow, Torch, JAX... - * NumPy/Awkward backends also implemented in [Numba](https://numba.pydata.org/) for JIT-compiled calculations on vectors. - * Distinction between geometrical vectors, which have a minimum of attribute and method names, and vectors representing momentum, which have synonyms like `pt` = `rho`, `energy` = `t`, `mass` = `tau`. +- Pure Python with NumPy as its only dependency. This makes it easier to install. +- Vectors may be represented in a variety of coordinate systems: Cartesian, cylindrical, pseudorapidity, and any combination of these with time or proper time for Lorentz vectors. In all, there are 12 coordinate systems: {_x_-_y_ vs _ρ_-_φ_ in the azimuthal plane} × {_z_ vs _θ_ vs _η_ longitudinally} × {_t_ vs _τ_ temporally}. +- Uses names and conventions set by [ROOT](https://root.cern/)'s [TLorentzVector](https://root.cern.ch/doc/master/classTLorentzVector.html) and [Math::LorentzVector](https://root.cern.ch/doc/master/classROOT_1_1Math_1_1LorentzVector.html), as well as [scikit-hep/math](https://github.com/scikit-hep/scikit-hep/tree/master/skhep/math), [uproot-methods TLorentzVector](https://github.com/scikit-hep/uproot3-methods/blob/master/uproot3_methods/classes/TLorentzVector.py), [henryiii/hepvector](https://github.com/henryiii/hepvector), and [coffea.nanoevents.methods.vector](https://coffeateam.github.io/coffea/modules/coffea.nanoevents.methods.vector.html). +- Implemented on a variety of backends: + - pure Python objects + - NumPy arrays of vectors (as a [structured array](https://numpy.org/doc/stable/user/basics.rec.html) subclass) + - [Awkward Arrays](https://awkward-array.org/) of vectors + - potential for more: CuPy, TensorFlow, Torch, JAX... +- NumPy/Awkward backends also implemented in [Numba](https://numba.pydata.org/) for JIT-compiled calculations on vectors. +- Distinction between geometrical vectors, which have a minimum of attribute and method names, and vectors representing momentum, which have synonyms like `pt` = `rho`, `energy` = `t`, `mass` = `tau`. ## Installation @@ -43,7 +40,6 @@ To install, use `pip install vector` or your favorite way to install in an envir This overview is based on the [documentation here](https://vector.readthedocs.io/en/develop/usage/intro.html). - ```python import vector import numpy as np @@ -55,69 +51,92 @@ import numba as nb The easiest way to create one or many vectors is with a helper function: - * `vector.obj` to make a pure Python vector object, - * `vector.arr` to make a NumPy array of vectors (or `array`, lowercase, like `np.array`), - * `vector.awk` to make an Awkward Array of vectors (or `Array`, uppercase, like `ak.Array`). +- `vector.obj` to make a pure Python vector object, +- `vector.arr` to make a NumPy array of vectors (or `array`, lowercase, like `np.array`), +- `vector.awk` to make an Awkward Array of vectors (or `Array`, uppercase, like `ak.Array`). ### Pure Python vectors - ```python -vector.obj(x=3, y=4) # Cartesian 2D vector -vector.obj(rho=5, phi=0.9273) # same in polar coordinates -vector.obj(x=3, y=4).isclose(vector.obj(rho=5, phi=0.9273)) # use "isclose" unless they are exactly equal -vector.obj(x=3, y=4, z=-2) # Cartesian 3D vector -vector.obj(x=3, y=4, z=-2, t=10) # Cartesian 4D vector -vector.obj(rho=5, phi=0.9273, eta=-0.39, t=10) # in rho-phi-eta-t cylindrical coordinates -vector.obj(pt=5, phi=0.9273, eta=-0.39, E=10) # use momentum-synonyms to get a momentum vector -vector.obj(rho=5, phi=0.9273, eta=-0.39, t=10) == vector.obj(pt=5, phi=0.9273, eta=-0.390035, E=10) -vector.obj(rho=5, phi=0.9273, eta=-0.39, t=10).tau # geometrical vectors have to use geometrical names ("tau", not "mass") -vector.obj(pt=5, phi=0.9273, eta=-0.39, E=10).mass # momentum vectors can use momentum names (as well as geometrical ones) -vector.obj(pt=5, phi=0.9273, theta=1.9513, mass=8.4262) # any combination of azimuthal, longitudinal, and temporal coordinates is allowed -vector.obj(x=3, y=4, z=-2, t=10).isclose(vector.obj(pt=5, phi=0.9273, theta=1.9513, mass=8.4262)) +# Cartesian 2D vector +vector.obj(x=3, y=4) +# same in polar coordinates +vector.obj(rho=5, phi=0.9273) +# use "isclose" unless they are exactly equal +vector.obj(x=3, y=4).isclose(vector.obj(rho=5, phi=0.9273)) +# Cartesian 3D vector +vector.obj(x=3, y=4, z=-2) +# Cartesian 4D vector +vector.obj(x=3, y=4, z=-2, t=10) +# in rho-phi-eta-t cylindrical coordinates +vector.obj(rho=5, phi=0.9273, eta=-0.39, t=10) +# use momentum-synonyms to get a momentum vector +vector.obj(pt=5, phi=0.9273, eta=-0.39, E=10) +vector.obj(rho=5, phi=0.9273, eta=-0.39, t=10) == vector.obj( + pt=5, phi=0.9273, eta=-0.390035, E=10 +) +# geometrical vectors have to use geometrical names ("tau", not "mass") +vector.obj(rho=5, phi=0.9273, eta=-0.39, t=10).tau +# momentum vectors can use momentum names (as well as geometrical ones) +vector.obj(pt=5, phi=0.9273, eta=-0.39, E=10).mass +# any combination of azimuthal, longitudinal, and temporal coordinates is allowed +vector.obj(pt=5, phi=0.9273, theta=1.9513, mass=8.4262) +vector.obj(x=3, y=4, z=-2, t=10).isclose( + vector.obj(pt=5, phi=0.9273, theta=1.9513, mass=8.4262) +) # Test instance type for any level of granularity. ( - isinstance(vector.obj(x=1.1, y=2.2), vector.Vector), # is a vector or array of vectors - isinstance(vector.obj(x=1.1, y=2.2), vector.Vector2D), # is 2D (not 3D or 4D) - isinstance(vector.obj(x=1.1, y=2.2), vector.VectorObject), # is a vector object (not an array) - isinstance(vector.obj(px=1.1, py=2.2), vector.Momentum), # has momentum synonyms - isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4), vector.Planar), # has transverse plane (2D, 3D, or 4D) - isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4), vector.Spatial), # has all spatial coordinates (3D or 4D) - isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4), vector.Lorentz), # has temporal coordinates (4D) - isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4).azimuthal, vector.AzimuthalXY), # azimuthal coordinate type - isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4).longitudinal, vector.LongitudinalZ), # longitudinal coordinate type - isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4).temporal, vector.TemporalT), # temporal coordinate type + # is a vector or array of vectors + isinstance(vector.obj(x=1.1, y=2.2), vector.Vector), + # is 2D (not 3D or 4D) + isinstance(vector.obj(x=1.1, y=2.2), vector.Vector2D), + # is a vector object (not an array) + isinstance(vector.obj(x=1.1, y=2.2), vector.VectorObject), + # has momentum synonyms + isinstance(vector.obj(px=1.1, py=2.2), vector.Momentum), + # has transverse plane (2D, 3D, or 4D) + isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4), vector.Planar), + # has all spatial coordinates (3D or 4D) + isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4), vector.Spatial), + # has temporal coordinates (4D) + isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4), vector.Lorentz), + # azimuthal coordinate type + isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4).azimuthal, vector.AzimuthalXY), + # longitudinal coordinate type + isinstance( + vector.obj(x=1.1, y=2.2, z=3.3, t=4.4).longitudinal, vector.LongitudinalZ + ), + # temporal coordinate type + isinstance(vector.obj(x=1.1, y=2.2, z=3.3, t=4.4).temporal, vector.TemporalT), ) ``` - The allowed keyword arguments for 2D vectors are: - * `x` and `y` for Cartesian azimuthal coordinates, - * `px` and `py` for momentum, - * `rho` and `phi` for polar azimuthal coordinates, - * `pt` and `phi` for momentum. +- `x` and `y` for Cartesian azimuthal coordinates, +- `px` and `py` for momentum, +- `rho` and `phi` for polar azimuthal coordinates, +- `pt` and `phi` for momentum. For 3D vectors, you need the above and: - * `z` for the Cartesian longitudinal coordinate, - * `pz` for momentum, - * `theta` for the spherical polar angle (from $0$ to $\pi$, inclusive), - * `eta` for pseudorapidity, which is a kind of spherical polar angle. +- `z` for the Cartesian longitudinal coordinate, +- `pz` for momentum, +- `theta` for the spherical polar angle (from $0$ to $\pi$, inclusive), +- `eta` for pseudorapidity, which is a kind of spherical polar angle. For 4D vectors, you need the above and: - * `t` for the Cartesian temporal coordinate, - * `E` or `energy` to get four-momentum, - * `tau` for the "proper time" (temporal coordinate in the vector's rest coordinate system), - * `M` or `mass` to get four-momentum. +- `t` for the Cartesian temporal coordinate, +- `E` or `energy` to get four-momentum, +- `tau` for the "proper time" (temporal coordinate in the vector's rest coordinate system), +- `M` or `mass` to get four-momentum. Since momentum vectors have momentum-synonyms _in addition_ to the geometrical names, any momentum-synonym will make the whole vector a momentum vector. If you want to bypass the dimension and coordinate system inference through keyword arguments (e.g. for static typing), you can use specialized constructors: - ```python vector.VectorObject2D.from_xy(1.1, 2.2) vector.MomentumObject3D.from_rhophiz(1.1, 2.2, 3.3) @@ -128,104 +147,114 @@ and so on, for all combinations of azimuthal, longitudinal, and temporal coordin ### NumPy arrays of vectors - ```python # NumPy-like arguments (literally passed through to NumPy) -vector.array([ - (1.1, 2.1), (1.2, 2.2), (1.3, 2.3), (1.4, 2.4), (1.5, 2.5) -], dtype=[("x", float), ("y", float)]) +vector.array( + [(1.1, 2.1), (1.2, 2.2), (1.3, 2.3), (1.4, 2.4), (1.5, 2.5)], + dtype=[("x", float), ("y", float)], +) # Pandas-like arguments (dict from names to column arrays) vector.array({"x": [1.1, 1.2, 1.3, 1.4, 1.5], "y": [2.1, 2.2, 2.3, 2.4, 2.5]}) # As with objects, the coordinate system and dimension is taken from the names of the fields. -vector.array({ - "x": [1.1, 1.2, 1.3, 1.4, 1.5], - "y": [2.1, 2.2, 2.3, 2.4, 2.5], - "z": [3.1, 3.2, 3.3, 3.4, 3.5], - "t": [4.1, 4.2, 4.3, 4.4, 4.5], -}) - -vector.array({ - "pt": [1.1, 1.2, 1.3, 1.4, 1.5], - "phi": [2.1, 2.2, 2.3, 2.4, 2.5], - "eta": [3.1, 3.2, 3.3, 3.4, 3.5], - "M": [4.1, 4.2, 4.3, 4.4, 4.5], -}) +vector.array( + { + "x": [1.1, 1.2, 1.3, 1.4, 1.5], + "y": [2.1, 2.2, 2.3, 2.4, 2.5], + "z": [3.1, 3.2, 3.3, 3.4, 3.5], + "t": [4.1, 4.2, 4.3, 4.4, 4.5], + } +) + +vector.array( + { + "pt": [1.1, 1.2, 1.3, 1.4, 1.5], + "phi": [2.1, 2.2, 2.3, 2.4, 2.5], + "eta": [3.1, 3.2, 3.3, 3.4, 3.5], + "M": [4.1, 4.2, 4.3, 4.4, 4.5], + } +) ``` Existing NumPy arrays can be viewed as arrays of vectors, but it needs to be a [structured array](https://numpy.org/doc/stable/user/basics.rec.html) with recognized field names. - ```python -# NumPy array # interpret groups of four values as named fields # give it vector properties and methods -np.arange(0, 24, 0.1).view([("x", float), ("y", float), ("z", float), ("t", float)]).view(vector.VectorNumpy4D) +np.arange(0, 24, 0.1).view( # NumPy array + [ + ("x", float), + ("y", float), + ("z", float), + ("t", float), + ] # interpret groups of four values as named fields +).view( + vector.VectorNumpy4D +) # give it vector properties and methods ``` - Since `VectorNumpy2D`, `VectorNumpy3D`, `VectorNumpy4D`, and their momentum equivalents are NumPy array subclasses, all of the normal NumPy methods and functions work on them. - ```python -np.arange(0, 24, 0.1).view([("x", float), ("y", float), ("z", float), ("t", float)]).view(vector.VectorNumpy4D).reshape(6, 5, 2) +np.arange(0, 24, 0.1).view( + [("x", float), ("y", float), ("z", float), ("t", float)] +).view(vector.VectorNumpy4D).reshape(6, 5, 2) ``` - All of the keyword arguments and rules that apply to `vector.obj` construction apply to `vector.arr` dtypes. Geometrical names are used in the dtype, even if momentum-synonyms are used in construction. - ```python vector.arr({"px": [1, 2, 3, 4], "py": [1.1, 2.2, 3.3, 4.4], "pz": [0.1, 0.2, 0.3, 0.4]}) ``` - ### Awkward Arrays of vectors [Awkward Arrays](https://awkward-array.org/) are arrays with more complex data structures than NumPy allows, such as variable-length lists, nested records, missing and even heterogeneous data (multiple data types: use sparingly). The `vector.awk` function behaves exactly like the [ak.Array](https://awkward-array.readthedocs.io/en/latest/_auto/ak.Array.html) constructor, except that it makes arrays of vectors. - ```python -vector.awk([ - [{"x": 1, "y": 1.1, "z": 0.1}, {"x": 2, "y": 2.2, "z": 0.2}], - [], - [{"x": 3, "y": 3.3, "z": 0.3}], - [{"x": 4, "y": 4.4, "z": 0.4}, {"x": 5, "y": 5.5, "z": 0.5}, {"x": 6, "y": 6.6, "z": 0.6}], -]) +vector.awk( + [ + [{"x": 1, "y": 1.1, "z": 0.1}, {"x": 2, "y": 2.2, "z": 0.2}], + [], + [{"x": 3, "y": 3.3, "z": 0.3}], + [ + {"x": 4, "y": 4.4, "z": 0.4}, + {"x": 5, "y": 5.5, "z": 0.5}, + {"x": 6, "y": 6.6, "z": 0.6}, + ], + ] +) ``` - If you want _any_ records named "`Vector2D`", "`Vector3D`", "`Vector4D`", "`Momentum2D`", "`Momentum3D`", or "`Momentum4D`" to be interpreted as vectors, register the behaviors globally. - ```python vector.register_awkward() -ak.Array([ - [{"x": 1, "y": 1.1, "z": 0.1}, {"x": 2, "y": 2.2, "z": 0.2}], - [], - [{"x": 3, "y": 3.3, "z": 0.3}], - [{"x": 4, "y": 4.4, "z": 0.4}, {"x": 5, "y": 5.5, "z": 0.5}, {"x": 6, "y": 6.6, "z": 0.6}], -], - with_name="Vector3D" +ak.Array( + [ + [{"x": 1, "y": 1.1, "z": 0.1}, {"x": 2, "y": 2.2, "z": 0.2}], + [], + [{"x": 3, "y": 3.3, "z": 0.3}], + [ + {"x": 4, "y": 4.4, "z": 0.4}, + {"x": 5, "y": 5.5, "z": 0.5}, + {"x": 6, "y": 6.6, "z": 0.6}, + ], + ], + with_name="Vector3D", ) ``` - - - - - All of the keyword arguments and rules that apply to `vector.obj` construction apply to `vector.awk` field names. ## Vector properties Any geometrical coordinate can be computed from vectors in any coordinate system; they'll be provided or computed as needed. - ```python vector.obj(x=3, y=4).rho vector.obj(rho=5, phi=0.9273).x @@ -234,27 +263,22 @@ vector.obj(x=1, y=2, z=3).theta vector.obj(x=1, y=2, z=3).eta ``` - Some properties are not coordinates, but derived from them. - ```python vector.obj(x=1, y=2, z=3).costheta -vector.obj(x=1, y=2, z=3).mag # spatial magnitude -vector.obj(x=1, y=2, z=3).mag2 # spatial magnitude squared +vector.obj(x=1, y=2, z=3).mag # spatial magnitude +vector.obj(x=1, y=2, z=3).mag2 # spatial magnitude squared ``` - These properties are provided because they can be computed faster or with more numerical stability in different coordinate systems. For instance, the magnitude ignores `phi` in polar coordinates. - ```python vector.obj(rho=3, phi=0.123456789, z=4).mag2 ``` Momentum vectors have geometrical properties as well as their momentum-synonyms. - ```python vector.obj(px=3, py=4).rho vector.obj(px=3, py=4).pt @@ -262,28 +286,33 @@ vector.obj(x=1, y=2, z=3, E=4).tau vector.obj(x=1, y=2, z=3, E=4).mass ``` - - - Here's the key thing: _arrays of vectors return arrays of coordinates_. - ```python -vector.arr({ - "x": [1.0, 2.0, 3.0, 4.0, 5.0], - "y": [1.1, 2.2, 3.3, 4.4, 5.5], - "z": [0.1, 0.2, 0.3, 0.4, 0.5], -}).theta - -vector.awk([ - [{"x": 1, "y": 1.1, "z": 0.1}, {"x": 2, "y": 2.2, "z": 0.2}], - [], - [{"x": 3, "y": 3.3, "z": 0.3}], - [{"x": 4, "y": 4.4, "z": 0.4}, {"x": 5, "y": 5.5, "z": 0.5}], -]).theta +vector.arr( + { + "x": [1.0, 2.0, 3.0, 4.0, 5.0], + "y": [1.1, 2.2, 3.3, 4.4, 5.5], + "z": [0.1, 0.2, 0.3, 0.4, 0.5], + } +).theta + +vector.awk( + [ + [{"x": 1, "y": 1.1, "z": 0.1}, {"x": 2, "y": 2.2, "z": 0.2}], + [], + [{"x": 3, "y": 3.3, "z": 0.3}], + [{"x": 4, "y": 4.4, "z": 0.4}, {"x": 5, "y": 5.5, "z": 0.5}], + ] +).theta # Make a large, random NumPy array of 3D momentum vectors. -array = np.random.normal(0, 1, 150).view([(x, float) for x in ("x", "y", "z")]).view(vector.MomentumNumpy3D).reshape(5, 5, 2) +array = ( + np.random.normal(0, 1, 150) + .view([(x, float) for x in ("x", "y", "z")]) + .view(vector.MomentumNumpy3D) + .reshape(5, 5, 2) +) # Get the transverse momentum of each one. array.pt @@ -293,7 +322,15 @@ array.shape array.pt.shape # Make a large, random Awkward Array of 3D momentum vectors. -array = vector.awk([[{x: np.random.normal(0, 1) for x in ("px", "py", "pz")} for inner in range(np.random.poisson(1.5))] for outer in range(50)]) +array = vector.awk( + [ + [ + {x: np.random.normal(0, 1) for x in ("px", "py", "pz")} + for inner in range(np.random.poisson(1.5)) + ] + for outer in range(50) + ] +) # Get the transverse momentum of each one, in the same nested structure. array.pt @@ -303,36 +340,50 @@ ak.num(array) ak.num(array.pt) ``` - ## Vector methods Vector methods require arguments (in parentheses), which may be scalars or other vectors, depending on the calculation. - ```python vector.obj(x=3, y=4).rotateZ(0.1) vector.obj(rho=5, phi=0.4).rotateZ(0.1) # Broadcasts a scalar rotation angle of 0.5 to all elements of the NumPy array. -print(vector.arr({"rho": [1, 2, 3, 4, 5], "phi": [0.1, 0.2, 0.3, 0.4, 0.5]}).rotateZ(0.5)) +print( + vector.arr({"rho": [1, 2, 3, 4, 5], "phi": [0.1, 0.2, 0.3, 0.4, 0.5]}).rotateZ(0.5) +) # Matches each rotation angle to an element of the NumPy array. -print(vector.arr({"rho": [1, 2, 3, 4, 5], "phi": [0.1, 0.2, 0.3, 0.4, 0.5]}).rotateZ(np.array([0.1, 0.2, 0.3, 0.4, 0.5]))) +print( + vector.arr({"rho": [1, 2, 3, 4, 5], "phi": [0.1, 0.2, 0.3, 0.4, 0.5]}).rotateZ( + np.array([0.1, 0.2, 0.3, 0.4, 0.5]) + ) +) # Broadcasts a scalar rotation angle of 0.5 to all elements of the Awkward Array. -print(vector.awk([[{"rho": 1, "phi": 0.1}, {"rho": 2, "phi": 0.2}], [], [{"rho": 3, "phi": 0.3}]]).rotateZ(0.5)) +print( + vector.awk( + [[{"rho": 1, "phi": 0.1}, {"rho": 2, "phi": 0.2}], [], [{"rho": 3, "phi": 0.3}]] + ).rotateZ(0.5) +) # Broadcasts a rotation angle of 0.1 to both elements of the first list, 0.2 to the empty list, and 0.3 to the only element of the last list. -print(vector.awk([[{"rho": 1, "phi": 0.1}, {"rho": 2, "phi": 0.2}], [], [{"rho": 3, "phi": 0.3}]]).rotateZ([0.1, 0.2, 0.3])) +print( + vector.awk( + [[{"rho": 1, "phi": 0.1}, {"rho": 2, "phi": 0.2}], [], [{"rho": 3, "phi": 0.3}]] + ).rotateZ([0.1, 0.2, 0.3]) +) # Matches each rotation angle to an element of the Awkward Array. -print(vector.awk([[{"rho": 1, "phi": 0.1}, {"rho": 2, "phi": 0.2}], [], [{"rho": 3, "phi": 0.3}]]).rotateZ([[0.1, 0.2], [], [0.3]])) +print( + vector.awk( + [[{"rho": 1, "phi": 0.1}, {"rho": 2, "phi": 0.2}], [], [{"rho": 3, "phi": 0.3}]] + ).rotateZ([[0.1, 0.2], [], [0.3]]) +) ``` - Some methods are equivalent to binary operators. - ```python vector.obj(x=3, y=4).scale(10) vector.obj(x=3, y=4) * 10 @@ -340,10 +391,8 @@ vector.obj(x=3, y=4) * 10 vector.obj(rho=5, phi=0.5) * 10 ``` - Some methods involve more than one vector. - ```python vector.obj(x=1, y=2).add(vector.obj(x=5, y=5)) vector.obj(x=1, y=2) + vector.obj(x=5, y=5) @@ -351,180 +400,182 @@ vector.obj(x=1, y=2).dot(vector.obj(x=5, y=5)) vector.obj(x=1, y=2) @ vector.obj(x=5, y=5) ``` - The vectors can use different coordinate systems. Conversions are necessary, but minimized for speed and numeric stability. - ```python -vector.obj(x=3, y=4) @ vector.obj(x=6, y=8) # both are Cartesian, dot product is exact -vector.obj(rho=5, phi=0.9273) @ vector.obj(x=6, y=8) # one is polar, dot product is approximate -vector.obj(x=3, y=4) @ vector.obj(rho=10, phi=0.9273) # one is polar, dot product is approximate -vector.obj(rho=5, phi=0.9273) @ vector.obj(rho=10, phi=0.9273) # both are polar, a formula that depends on phi differences is used +# both are Cartesian, dot product is exact +vector.obj(x=3, y=4) @ vector.obj(x=6, y=8) +# one is polar, dot product is approximate +vector.obj(rho=5, phi=0.9273) @ vector.obj(x=6, y=8) +# one is polar, dot product is approximate +vector.obj(x=3, y=4) @ vector.obj(rho=10, phi=0.9273) +# both are polar, a formula that depends on phi differences is used +vector.obj(rho=5, phi=0.9273) @ vector.obj(rho=10, phi=0.9273) ``` - - - In Python, some "operators" are actually built-in functions, such as `abs`. - ```python abs(vector.obj(x=3, y=4)) ``` - - - Note that `abs` returns - * `rho` for 2D vectors - * `mag` for 3D vectors - * `tau` (`mass`) for 4D vectors +- `rho` for 2D vectors +- `mag` for 3D vectors +- `tau` (`mass`) for 4D vectors Use the named properties when you want magnitude in a specific number of dimensions; use `abs` when you want the magnitude for any number of dimensions. The vectors can be from different backends. Normal rules for broadcasting Python numbers, NumPy arrays, and Awkward Arrays apply. - ```python -vector.arr({"x": [1, 2, 3, 4, 5], "y": [0.1, 0.2, 0.3, 0.4, 0.5]}) + vector.obj(x=10, y=5) +vector.arr({"x": [1, 2, 3, 4, 5], "y": [0.1, 0.2, 0.3, 0.4, 0.5]}) + vector.obj( + x=10, y=5 +) ( - vector.awk([ # an Awkward Array of vectors - [{"x": 1, "y": 1.1}, {"x": 2, "y": 2.2}], - [], - [{"x": 3, "y": 3.3}], - [{"x": 4, "y": 4.4}, {"x": 5, "y": 5.5}], - ]) - + vector.obj(x=10, y=5) # and a single vector object + vector.awk( + [ # an Awkward Array of vectors + [{"x": 1, "y": 1.1}, {"x": 2, "y": 2.2}], + [], + [{"x": 3, "y": 3.3}], + [{"x": 4, "y": 4.4}, {"x": 5, "y": 5.5}], + ] + ) + + vector.obj(x=10, y=5) # and a single vector object ) ( - vector.awk([ # an Awkward Array of vectors - [{"x": 1, "y": 1.1}, {"x": 2, "y": 2.2}], - [], - [{"x": 3, "y": 3.3}], - [{"x": 4, "y": 4.4}, {"x": 5, "y": 5.5}], - ]) - + vector.arr({"x": [4, 3, 2, 1], "y": [0.1, 0.1, 0.1, 0.1]}) # and a NumPy array of vectors + vector.awk( + [ # an Awkward Array of vectors + [{"x": 1, "y": 1.1}, {"x": 2, "y": 2.2}], + [], + [{"x": 3, "y": 3.3}], + [{"x": 4, "y": 4.4}, {"x": 5, "y": 5.5}], + ] + ) + + vector.arr( + {"x": [4, 3, 2, 1], "y": [0.1, 0.1, 0.1, 0.1]} + ) # and a NumPy array of vectors ) ``` - Some operations are defined for 2D or 3D vectors, but are usable on higher-dimensional vectors because the additional components can be ignored or are passed through unaffected. - ```python -vector.obj(rho=1, phi=0.5).deltaphi(vector.obj(rho=2, phi=0.3)) # deltaphi is a planar operation (defined on the transverse plane) -vector.obj(rho=1, phi=0.5, z=10).deltaphi(vector.obj(rho=2, phi=0.3, theta=1.4)) # but we can use it on 3D vectors -vector.obj(rho=1, phi=0.5, z=10, t=100).deltaphi(vector.obj(rho=2, phi=0.3, theta=1.4, tau=1000)) # and 4D vectors -vector.obj(rho=1, phi=0.5).deltaphi(vector.obj(rho=2, phi=0.3, theta=1.4, tau=1000)) # and mixed dimensionality +# deltaphi is a planar operation (defined on the transverse plane) +vector.obj(rho=1, phi=0.5).deltaphi(vector.obj(rho=2, phi=0.3)) +# but we can use it on 3D vectors +vector.obj(rho=1, phi=0.5, z=10).deltaphi(vector.obj(rho=2, phi=0.3, theta=1.4)) +# and 4D vectors +vector.obj(rho=1, phi=0.5, z=10, t=100).deltaphi( + vector.obj(rho=2, phi=0.3, theta=1.4, tau=1000) +) +# and mixed dimensionality +vector.obj(rho=1, phi=0.5).deltaphi(vector.obj(rho=2, phi=0.3, theta=1.4, tau=1000)) ``` This is especially useful for giving 4D vectors all the capabilities of 3D vectors. - ```python -vector.obj(x=1, y=2, z=3).rotateX(np.pi/4) -vector.obj(x=1, y=2, z=3, tau=10).rotateX(np.pi/4) +vector.obj(x=1, y=2, z=3).rotateX(np.pi / 4) +vector.obj(x=1, y=2, z=3, tau=10).rotateX(np.pi / 4) vector.obj(pt=1, phi=1.3, eta=2).deltaR(vector.obj(pt=2, phi=0.3, eta=1)) -vector.obj(pt=1, phi=1.3, eta=2, mass=5).deltaR(vector.obj(pt=2, phi=0.3, eta=1, mass=10)) +vector.obj(pt=1, phi=1.3, eta=2, mass=5).deltaR( + vector.obj(pt=2, phi=0.3, eta=1, mass=10) +) ``` - The opposite—using low-dimensional vectors in operations defined for higher numbers of dimensions—is sometimes defined. In these cases, a zero longitudinal or temporal component has to be imputed. - ```python vector.obj(x=1, y=2, z=3) - vector.obj(x=1, y=2) vector.obj(x=1, y=2, z=0).is_parallel(vector.obj(x=1, y=2)) ``` - And finally, in some cases, the function excludes a higher-dimensional component, even if the input vectors had them. It would be confusing if the 3D cross-product returned a fourth component. - ```python vector.obj(x=0.1, y=0.2, z=0.3, t=10).cross(vector.obj(x=0.4, y=0.5, z=0.6, t=20)) ``` - The (current) list of properties and methods is: **Planar (2D, 3D, 4D):** - * `x` (`px`) - * `y` (`py`) - * `rho` (`pt`): two-dimensional magnitude - * `rho2` (`pt2`): two-dimensional magnitude squared - * `phi` - * `deltaphi(vector)`: difference in `phi` (signed and rectified to $-\pi$ through $\pi$) - * `rotateZ(angle)` - * `transform2D(obj)`: the `obj` must supply components through `obj["xx"]`, `obj["xy"]`, `obj["yx"]`, `obj["yy"]` - * `is_parallel(vector, tolerance=1e-5)`: only true _if they're pointing in the same direction_ - * `is_antiparallel(vector, tolerance=1e-5)`: only true _if they're pointing in opposite directions_ - * `is_perpendicular(vector, tolerance=1e-5)` +- `x` (`px`) +- `y` (`py`) +- `rho` (`pt`): two-dimensional magnitude +- `rho2` (`pt2`): two-dimensional magnitude squared +- `phi` +- `deltaphi(vector)`: difference in `phi` (signed and rectified to $-\pi$ through $\pi$) +- `rotateZ(angle)` +- `transform2D(obj)`: the `obj` must supply components through `obj["xx"]`, `obj["xy"]`, `obj["yx"]`, `obj["yy"]` +- `is_parallel(vector, tolerance=1e-5)`: only true _if they're pointing in the same direction_ +- `is_antiparallel(vector, tolerance=1e-5)`: only true _if they're pointing in opposite directions_ +- `is_perpendicular(vector, tolerance=1e-5)` **Spatial (3D, 4D):** - * `z` (`pz`) - * `theta` - * `eta` - * `costheta` - * `cottheta` - * `mag` (`p`): three-dimensional magnitude, does not include temporal component - * `mag2` (`p2`): three-dimensional magnitude squared - * `cross`: cross-product (strictly 3D) - * `deltaangle(vector)`: difference in angle (always non-negative) - * `deltaeta(vector)`: difference in `eta` (signed) - * `deltaR(vector)`: $\Delta R = \sqrt{\Delta\phi^2 + \Delta\eta^2}$ - * `deltaR2(vector)`: the above, squared - * `rotateX(angle)` - * `rotateY(angle)` - * `rotate_axis(axis, angle)`: the magnitude of `axis` is ignored, but it must be at least 3D - * `rotate_euler(phi, theta, psi, order="zxz")`: the arguments are in the same order as [ROOT::Math::EulerAngles](https://root.cern.ch/doc/master/classROOT_1_1Math_1_1EulerAngles.html), and `order="zxz"` agrees with ROOT's choice of conventions - * `rotate_nautical(yaw, pitch, roll)` - * `rotate_quaternion(u, i, j, k)`: again, the conventions match [ROOT::Math::Quaternion](https://root.cern.ch/doc/master/classROOT_1_1Math_1_1Quaternion.html). - * `transform3D(obj)`: the `obj` must supply components through `obj["xx"]`, `obj["xy"]`, etc. - * `is_parallel(vector, tolerance=1e-5)`: only true _if they're pointing in the same direction_ - * `is_antiparallel(vector, tolerance=1e-5)`: only true _if they're pointing in opposite directions_ - * `is_perpendicular(vector, tolerance=1e-5)` +- `z` (`pz`) +- `theta` +- `eta` +- `costheta` +- `cottheta` +- `mag` (`p`): three-dimensional magnitude, does not include temporal component +- `mag2` (`p2`): three-dimensional magnitude squared +- `cross`: cross-product (strictly 3D) +- `deltaangle(vector)`: difference in angle (always non-negative) +- `deltaeta(vector)`: difference in `eta` (signed) +- `deltaR(vector)`: $\Delta R = \sqrt{\Delta\phi^2 + \Delta\eta^2}$ +- `deltaR2(vector)`: the above, squared +- `rotateX(angle)` +- `rotateY(angle)` +- `rotate_axis(axis, angle)`: the magnitude of `axis` is ignored, but it must be at least 3D +- `rotate_euler(phi, theta, psi, order="zxz")`: the arguments are in the same order as [ROOT::Math::EulerAngles](https://root.cern.ch/doc/master/classROOT_1_1Math_1_1EulerAngles.html), and `order="zxz"` agrees with ROOT's choice of conventions +- `rotate_nautical(yaw, pitch, roll)` +- `rotate_quaternion(u, i, j, k)`: again, the conventions match [ROOT::Math::Quaternion](https://root.cern.ch/doc/master/classROOT_1_1Math_1_1Quaternion.html). +- `transform3D(obj)`: the `obj` must supply components through `obj["xx"]`, `obj["xy"]`, etc. +- `is_parallel(vector, tolerance=1e-5)`: only true _if they're pointing in the same direction_ +- `is_antiparallel(vector, tolerance=1e-5)`: only true _if they're pointing in opposite directions_ +- `is_perpendicular(vector, tolerance=1e-5)` **Lorentz (4D only):** - * `t` (`E`, `energy`): follows the [ROOT::Math::LorentzVector](https://root.cern/doc/master/LorentzVectorPage.html) behavior of treating spacelike vectors as negative `t` and negative `tau` and truncating wrong-direction timelike vectors - * `t2` (`E2`, `energy2`) - * `tau` (`M`, `mass`): see note above - * `tau2` (`M2`, `mass2`) - * `beta`: scalar(s) between $0$ (inclusive) and $1$ (exclusive, unless the vector components are infinite) - * `deltaRapidityPhi`: $Delta R_{\mbox{rapidity}} = \Delta\phi^2 + \Delta \mbox{rapidity}^2$ - * `deltaRapidityPhi2`: the above, squared - * `gamma`: scalar(s) between $1$ (inclusive) and $\infty$ - * `rapidity`: scalar(s) between $0$ (inclusive) and $\infty$ - * `boost_p4(four_vector)`: change coordinate system using another 4D vector as the difference - * `boost_beta(three_vector)`: change coordinate system using a 3D beta vector (all components between $-1$ and $+1$) - * `boost(vector)`: uses the dimension of the given `vector` to determine behavior - * `boostX(beta=None, gamma=None)`: supply `beta` xor `gamma`, but not both - * `boostY(beta=None, gamma=None)`: supply `beta` xor `gamma`, but not both - * `boostZ(beta=None, gamma=None)`: supply `beta` xor `gamma`, but not both - * `transform4D(obj)`: the `obj` must supply components through `obj["xx"]`, `obj["xy"]`, etc. - * `to_beta3()`: turns a `four_vector` (for `boost_p4`) into a `three_vector` (for `boost_beta3`) - * `is_timelike(tolerance=0)` - * `is_spacelike(tolerance=0)` - * `is_lightlike(tolerance=1e-5)`: note the different tolerance +- `t` (`E`, `energy`): follows the [ROOT::Math::LorentzVector](https://root.cern/doc/master/LorentzVectorPage.html) behavior of treating spacelike vectors as negative `t` and negative `tau` and truncating wrong-direction timelike vectors +- `t2` (`E2`, `energy2`) +- `tau` (`M`, `mass`): see note above +- `tau2` (`M2`, `mass2`) +- `beta`: scalar(s) between $0$ (inclusive) and $1$ (exclusive, unless the vector components are infinite) +- `deltaRapidityPhi`: $Delta R_{\mbox{rapidity}} = \Delta\phi^2 + \Delta \mbox{rapidity}^2$ +- `deltaRapidityPhi2`: the above, squared +- `gamma`: scalar(s) between $1$ (inclusive) and $\infty$ +- `rapidity`: scalar(s) between $0$ (inclusive) and $\infty$ +- `boost_p4(four_vector)`: change coordinate system using another 4D vector as the difference +- `boost_beta(three_vector)`: change coordinate system using a 3D beta vector (all components between $-1$ and $+1$) +- `boost(vector)`: uses the dimension of the given `vector` to determine behavior +- `boostX(beta=None, gamma=None)`: supply `beta` xor `gamma`, but not both +- `boostY(beta=None, gamma=None)`: supply `beta` xor `gamma`, but not both +- `boostZ(beta=None, gamma=None)`: supply `beta` xor `gamma`, but not both +- `transform4D(obj)`: the `obj` must supply components through `obj["xx"]`, `obj["xy"]`, etc. +- `to_beta3()`: turns a `four_vector` (for `boost_p4`) into a `three_vector` (for `boost_beta3`) +- `is_timelike(tolerance=0)` +- `is_spacelike(tolerance=0)` +- `is_lightlike(tolerance=1e-5)`: note the different tolerance **All numbers of dimensions:** - * `unit()`: note the parentheses - * `dot(vector)`: can also use the `@` operator - * `add(vector)`: can also use the `+` operator - * `subtract(vector)`: can also use the `-` operator - * `scale(factor)`: can also use the `*` operator - * `equal(vector)`: can also use the `==` operator, but consider `isclose` instead - * `not_equal(vector)`: can also use the `!=` operator, but consider `isclose` instead - * `isclose(vector, rtol=1e-5, atol=1e-8, equal_nan=False)`: works like [np.isclose](https://numpy.org/doc/stable/reference/generated/numpy.isclose.html); arrays also have an [allclose](https://numpy.org/doc/stable/reference/generated/numpy.allclose.html) method +- `unit()`: note the parentheses +- `dot(vector)`: can also use the `@` operator +- `add(vector)`: can also use the `+` operator +- `subtract(vector)`: can also use the `-` operator +- `scale(factor)`: can also use the `*` operator +- `equal(vector)`: can also use the `==` operator, but consider `isclose` instead +- `not_equal(vector)`: can also use the `!=` operator, but consider `isclose` instead +- `isclose(vector, rtol=1e-5, atol=1e-8, equal_nan=False)`: works like [np.isclose](https://numpy.org/doc/stable/reference/generated/numpy.isclose.html); arrays also have an [allclose](https://numpy.org/doc/stable/reference/generated/numpy.allclose.html) method ## Compiling your Python with Numba @@ -534,26 +585,34 @@ The Vector library includes extensions to inform Numba about vector objects, vec For instance, consider the following function: - ```python @nb.njit def compute_mass(v1, v2): return (v1 + v2).mass + compute_mass(vector.obj(px=1, py=2, pz=3, E=4), vector.obj(px=-1, py=-2, pz=-3, E=4)) ``` - - - When the two `MomentumObject4D` objects are passed as arguments, Numba recognizes them and replaces the Python objects with low-level structs. When it compiles the function, it recognizes `+` as the 4D `add` function and recognizes `.mass` as the `tau` component of the result. Although this demonstrates that Numba can manipulate vector objects, there is no performance advantage (and a likely disadvantage) to compiling a calculation on just a few vectors. The advantage comes when many vectors are involved, in arrays. - ```python # This is still not a large number. You want millions. -array = vector.awk([[dict({x: np.random.normal(0, 1) for x in ("px", "py", "pz")}, E=np.random.normal(10, 1)) for inner in range(np.random.poisson(1.5))] for outer in range(50)]) +array = vector.awk( + [ + [ + dict( + {x: np.random.normal(0, 1) for x in ("px", "py", "pz")}, + E=np.random.normal(10, 1), + ) + for inner in range(np.random.poisson(1.5)) + ] + for outer in range(50) + ] +) + @nb.njit def compute_masses(array): @@ -569,12 +628,10 @@ def compute_masses(array): compute_masses(array) ``` - ### Status as of April 8, 2021 Undoubtedly, there are rough edges, but most of the functionality is there and Vector is ready for user-testing. It can only be improved by your feedback! - ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): @@ -604,39 +661,38 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) -specification. Contributions of any kind welcome! See +specification. Contributions of any kind welcome! See [CONTRIBUTING.md](./.github/CONTRIBUTING.md) for information on setting up a development environment. - ## Acknowledgements This library was primarily developed by Jim Pivarski, Henry Schreiner, and Eduardo Rodrigues. Support for this work was provided by the National Science Foundation cooperative agreement OAC-1836650 (IRIS-HEP) and OAC-1450377 (DIANA/HEP). Any opinions, findings, conclusions or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the National Science Foundation. - - -[actions-badge]: https://github.com/scikit-hep/vector/workflows/CI/badge.svg -[actions-link]: https://github.com/scikit-hep/vector/actions -[black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg -[black-link]: https://github.com/psf/black -[codecov-badge]: https://codecov.io/gh/scikit-hep/vector/branch/main/graph/badge.svg?token=YBv60ueORQ -[codecov-link]: https://codecov.io/gh/scikit-hep/vector -[conda-version]: https://img.shields.io/conda/vn/conda-forge/vector.svg -[conda-link]: https://github.com/conda-forge/vector-feedstock +[actions-badge]: https://github.com/scikit-hep/vector/workflows/CI/badge.svg +[actions-link]: https://github.com/scikit-hep/vector/actions +[black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg +[black-link]: https://github.com/psf/black +[codecov-badge]: https://codecov.io/gh/scikit-hep/vector/branch/main/graph/badge.svg?token=YBv60ueORQ +[codecov-link]: https://codecov.io/gh/scikit-hep/vector +[conda-version]: https://img.shields.io/conda/vn/conda-forge/vector.svg +[conda-link]: https://github.com/conda-forge/vector-feedstock [github-discussions-badge]: https://img.shields.io/static/v1?label=Discussions&message=Ask&color=blue&logo=github -[github-discussions-link]: https://github.com/scikit-hep/vector/discussions -[gitter-badge]: https://badges.gitter.im/Scikit-HEP/vector.svg -[gitter-link]: https://gitter.im/Scikit-HEP/vector?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge -[pre-commit-badge]: https://results.pre-commit.ci/badge/github/scikit-hep/vector/develop.svg -[pre-commit-link]: https://results.pre-commit.ci/repo/github/scikit-hep/vector -[pypi-link]: https://pypi.org/project/vector/ -[pypi-platforms]: https://img.shields.io/pypi/pyversions/vector -[pypi-version]: https://badge.fury.io/py/vector.svg -[rtd-badge]: https://readthedocs.org/projects/vector/badge/?version=latest -[rtd-link]: https://vector.readthedocs.io/en/latest/?badge=latest -[sk-badge]: https://scikit-hep.org/assets/images/Scikit--HEP-Project-blue.svg -[sk-link]: https://scikit-hep.org/ -[zenodo-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.5942083.svg -[zenodo-link]: https://doi.org/10.5281/zenodo.5942082 +[github-discussions-link]: https://github.com/scikit-hep/vector/discussions +[gitter-badge]: https://badges.gitter.im/Scikit-HEP/vector.svg +[gitter-link]: https://gitter.im/Scikit-HEP/vector?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge +[license-badge]: https://img.shields.io/badge/License-BSD_3--Clause-blue.svg +[license-link]: https://opensource.org/licenses/BSD-3-Clause +[pre-commit-badge]: https://results.pre-commit.ci/badge/github/scikit-hep/vector/develop.svg +[pre-commit-link]: https://results.pre-commit.ci/repo/github/scikit-hep/vector +[pypi-link]: https://pypi.org/project/vector/ +[pypi-platforms]: https://img.shields.io/pypi/pyversions/vector +[pypi-version]: https://badge.fury.io/py/vector.svg +[rtd-badge]: https://readthedocs.org/projects/vector/badge/?version=latest +[rtd-link]: https://vector.readthedocs.io/en/latest/?badge=latest +[sk-badge]: https://scikit-hep.org/assets/images/Scikit--HEP-Project-blue.svg +[sk-link]: https://scikit-hep.org/ +[zenodo-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.5942083.svg +[zenodo-link]: https://doi.org/10.5281/zenodo.5942082 diff --git a/docs/changelog.md b/docs/changelog.md index 2d923344..d1384f54 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,7 +4,7 @@ ### Version 0.8.1 -* Fix issue importing without Awkward installed [#76][] +- Fix issue importing without Awkward installed [#76][] [#76]: https://github.com/scikit-hep/vector/pull/76 @@ -12,14 +12,13 @@ First release to PyPI. Initial implementation. Initial features: -* 2D, 3D, and Lorentz vectors -* Single, Array, and Awkward forms -* Supports Numba / Awkward + Numba -* Multiple coordinate systems -* Geometric / momentum versions -* Statically typed +- 2D, 3D, and Lorentz vectors +- Single, Array, and Awkward forms +- Supports Numba / Awkward + Numba +- Multiple coordinate systems +- Geometric / momentum versions +- Statically typed You can currently construct vectors using `obj`/`arr`/`awk` (or `obj`/`array`/`Array`) for single, NumPy, and Awkward vectors, respectively. The next version is likely to improve the vector construction process. - diff --git a/docs/index.rst b/docs/index.rst index 36f52437..8f031eb5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,7 +6,7 @@ .. image:: _images/vector-logo.png |Action status| |Documentation Status| |pre-commit.ci status| |coverage| |GitHub Discussion| |Gitter| |Code style: black| -|PyPI platforms| |PyPI version| |Conda latest releasetatus| |DOI| |Scikit-HEP| +|PyPI platforms| |PyPI version| |Conda latest releasetatus| |DOI| |License| |Scikit-HEP| Overview -------- @@ -112,6 +112,8 @@ The API reference details the functionality of each ``class`` and ``function`` p :target: https://github.com/conda-forge/vector-feedstock .. |DOI| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.5942083.svg :target: https://doi.org/10.5281/zenodo.5942082 +.. |License| image:: https://img.shields.io/badge/License-BSD_3--Clause-blue.svg + :target: https://opensource.org/licenses/BSD-3-Clause .. |Scikit-HEP| image:: https://scikit-hep.org/assets/images/Scikit--HEP-Project-blue.svg :target: https://scikit-hep.org/ .. |coverage| image:: https://codecov.io/gh/scikit-hep/vector/branch/main/graph/badge.svg?token=YBv60ueORQ diff --git a/environment.yml b/environment.yml index b9c5d4d7..39b366e5 100644 --- a/environment.yml +++ b/environment.yml @@ -9,7 +9,7 @@ dependencies: - root >=6.18.04 - numba >=0.50 - pip: - - "awkward1>=0.2.29" - - "uproot==3.*" - - "scikit-hep-testdata>=0.2.0" - - -e . + - "awkward1>=0.2.29" + - "uproot==3.*" + - "scikit-hep-testdata>=0.2.0" + - -e . diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 00000000..c1540350 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,58 @@ +import shutil +from pathlib import Path + +import nox + +ALL_PYTHONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] + +nox.options.sessions = ["lint", "tests", "doctests"] + + +DIR = Path(__file__).parent.resolve() + + +@nox.session(reuse_venv=True) +def lint(session): + """Run the linter.""" + session.install("pre-commit") + session.run("pre-commit", "run", "--all-files", *session.posargs) + + +@nox.session(python=ALL_PYTHONS, reuse_venv=True) +def tests(session): + """Run the unit and regular tests.""" + session.install("-e", ".[awkward,test,test-extras]") + session.run("pytest", *session.posargs) + + +@nox.session(reuse_venv=True) +def doctests(session): + """Run the doctests.""" + session.install("-e", ".[awkward,test,test-extras]") + session.run("xdoctest", "./src/vector/", *session.posargs) + + +@nox.session(reuse_venv=True) +def docs(session): + """Build the docs. Pass "serve" to serve.""" + session.install("-e", ".[docs]") + session.chdir("docs") + session.run("sphinx-build", "-M", "html", ".", "_build") + + if session.posargs: + if "serve" in session.posargs: + print("Launching docs at http://localhost:8001/ - use Ctrl-C to quit") + session.run("python", "-m", "http.server", "8001", "-d", "_build/html") + else: + print("Unsupported argument to docs") + + +@nox.session +def build(session): + """Build an SDist and wheel.""" + build_p = DIR.joinpath("build") + if build_p.exists(): + shutil.rmtree(build_p) + + session.install("build") + session.run("python", "-m", "build") diff --git a/pyproject.toml b/pyproject.toml index c82366b2..15a5d72a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,9 +9,13 @@ build-backend = "setuptools.build_meta" write_to = "src/vector/version.py" [tool.pytest.ini_options] -addopts = "-ra --strict-markers" +minversion = "6.0" +xfail_strict = true +addopts = ["-ra", "--strict-markers", "--strict-config"] testpaths = ["tests"] markers = ["slow", "numba", "awkward", "dis"] +log_cli_level = "DEBUG" +filterwarnings = ["error", "ignore::DeprecationWarning", "ignore::UserWarning"] [tool.isort] profile = "black" @@ -21,6 +25,9 @@ files = ["src/vector"] python_version = "3.8" strict = true warn_return_any = false +show_error_codes = true +warn_unreachable = true +enable_error_code = ["ignore-without-code", "truthy-bool", "redundant-expr"] [[tool.mypy.overrides]] module = "vector._compute.*.*" @@ -37,4 +44,5 @@ ignore = [ "environment.yml", "src/vector/version.py", "docs/**", + "noxfile.py", ] diff --git a/setup.cfg b/setup.cfg index 7e759008..eb50558e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -57,7 +57,8 @@ numba_extensions = init = vector:register_numba [flake8] -ignore = E203, E231, E501, E722, W503, B950 -select = C,E,F,W,T,B,B9 +extend-ignore = E501, E203, D103, D102, D101, D100, D107, D105, D205, D400, D401, D104, D412 +extend-select = B9 per-file-ignores = tests/*: T + noxfile.py: T diff --git a/src/vector/_methods.py b/src/vector/_methods.py index 88f43e02..b07948dc 100644 --- a/src/vector/_methods.py +++ b/src/vector/_methods.py @@ -171,9 +171,7 @@ def _wrap_result( GenericClass: typing.Type["VectorProtocol"] def to_Vector2D(self) -> "VectorProtocolPlanar": - """ - Projects this vector/these vectors onto azimuthal coordinates only. - """ + """Projects this vector/these vectors onto azimuthal coordinates only.""" raise AssertionError def to_Vector3D(self) -> "VectorProtocolSpatial": @@ -432,16 +430,12 @@ def azimuthal(self) -> Azimuthal: @property def x(self) -> ScalarCollection: - """ - The Cartesian $x$ coordinate of the vector or every vector in the array. - """ + """The Cartesian $x$ coordinate of the vector or every vector in the array.""" raise AssertionError @property def y(self) -> ScalarCollection: - """ - The Cartesian $y$ coordinate of the vector or every vector in the array. - """ + """The Cartesian $y$ coordinate of the vector or every vector in the array.""" raise AssertionError @property @@ -456,9 +450,7 @@ def rho(self) -> ScalarCollection: @property def rho2(self) -> ScalarCollection: - r""" - The polar $\rho$ coordinate squared of the vector or every vector in the array. - """ + r"""The polar $\rho$ coordinate squared of the vector or every vector in the array.""" raise AssertionError @property @@ -485,9 +477,7 @@ def neg2D(self: SameVectorType) -> SameVectorType: raise AssertionError def deltaphi(self, other: VectorProtocol) -> ScalarCollection: - r""" - Signed difference in $\phi$ of ``self`` minus ``other`` (in radians). - """ + r"""Signed difference in $\phi$ of ``self`` minus ``other`` (in radians).""" raise AssertionError def rotateZ(self: SameVectorType, angle: ScalarCollection) -> SameVectorType: @@ -563,9 +553,7 @@ def longitudinal(self) -> Longitudinal: @property def z(self) -> ScalarCollection: - """ - The Cartesian $z$ coordinate of the vector or every vector in the array. - """ + """The Cartesian $z$ coordinate of the vector or every vector in the array.""" raise AssertionError @property @@ -602,16 +590,12 @@ def cottheta(self) -> ScalarCollection: @property def mag(self) -> ScalarCollection: - """ - The magnitude of the vector(s) in 3D (not including any temporal parts). - """ + """The magnitude of the vector(s) in 3D (not including any temporal parts).""" raise AssertionError @property def mag2(self) -> ScalarCollection: - """ - The magnitude-squared of the vector(s) in 3D (not including any temporal parts). - """ + """The magnitude-squared of the vector(s) in 3D (not including any temporal parts).""" raise AssertionError def scale3D(self: SameVectorType, factor: ScalarCollection) -> SameVectorType: @@ -645,9 +629,7 @@ def deltaangle(self, other: "VectorProtocol") -> ScalarCollection: raise AssertionError def deltaeta(self, other: "VectorProtocol") -> ScalarCollection: - r""" - Signed difference in $\eta$ of ``self`` minus ``other``. - """ + r"""Signed difference in $\eta$ of ``self`` minus ``other``.""" raise AssertionError def deltaR(self, other: "VectorProtocol") -> ScalarCollection: @@ -918,16 +900,12 @@ def deltaRapidityPhi2(self, other: "VectorProtocol") -> ScalarCollection: raise AssertionError def scale4D(self: SameVectorType, factor: ScalarCollection) -> SameVectorType: - """ - Same as ``scale``. - """ + """Same as ``scale``.""" raise AssertionError @property def neg4D(self: SameVectorType) -> SameVectorType: - """ - Same as multiplying by -1. - """ + """Same as multiplying by -1.""" raise AssertionError def boost_p4(self: SameVectorType, p4: "VectorProtocolLorentz") -> SameVectorType: @@ -1180,146 +1158,106 @@ def is_lightlike(self, tolerance: ScalarCollection = 1e-5) -> BoolCollection: class MomentumProtocolPlanar(VectorProtocolPlanar): @property def px(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolPlanar.x`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolPlanar.x`.""" raise AssertionError @property def py(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolPlanar.y`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolPlanar.y`.""" raise AssertionError @property def pt(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolPlanar.rho`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolPlanar.rho`.""" raise AssertionError @property def pt2(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolPlanar.rho2`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolPlanar.rho2`.""" raise AssertionError class MomentumProtocolSpatial(VectorProtocolSpatial, MomentumProtocolPlanar): @property def pz(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolSpatial.z`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolSpatial.z`.""" raise AssertionError @property def pseudorapidity(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolSpatial.eta`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolSpatial.eta`.""" raise AssertionError @property def p(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolSpatial.mag`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolSpatial.mag`.""" raise AssertionError @property def p2(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolSpatial.mag2`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolSpatial.mag2`.""" raise AssertionError class MomentumProtocolLorentz(VectorProtocolLorentz, MomentumProtocolSpatial): @property def E(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.t`. - """ + """Momentum-synonyor :attr:`vector._methods.VectorProtocolLorentz.t`.""" raise AssertionError @property def e(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.t`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.t`.""" raise AssertionError @property def energy(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.t`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.t`.""" raise AssertionError @property def E2(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.t2`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorent2`.""" raise AssertionError @property def e2(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.t2`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.t2`.""" raise AssertionError @property def energy2(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.t2`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.t2`.""" raise AssertionError @property def M(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau`.""" raise AssertionError @property def m(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau`.""" raise AssertionError @property def mass(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau`.""" raise AssertionError @property def M2(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau2`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau2`.""" raise AssertionError @property def m2(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau2`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau2`.""" raise AssertionError @property def mass2(self) -> ScalarCollection: - """ - Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau2`. - """ + """Momentum-synonym for :attr:`vector._methods.VectorProtocolLorentz.tau2`.""" raise AssertionError @property @@ -1340,9 +1278,7 @@ def et(self) -> ScalarCollection: @property def transverse_energy(self) -> ScalarCollection: - """ - Synonym for :attr:`vector._methods.MomentumProtocolLorentz.Et`. - """ + """Synonym for :attr:`vector._methods.MomentumProtocolLorentz.Et`.""" raise AssertionError @property @@ -1363,9 +1299,7 @@ def et2(self) -> ScalarCollection: @property def transverse_energy2(self) -> ScalarCollection: - """ - Synonym for :attr:`vector._methods.MomentumProtocolLorentz.Et2`. - """ + """Synonym for :attr:`vector._methods.MomentumProtocolLorentz.Et2`.""" raise AssertionError @property @@ -1386,9 +1320,7 @@ def mt(self) -> ScalarCollection: @property def transverse_mass(self) -> ScalarCollection: - """ - Synonym for :attr:`vector._methods.MomentumProtocolLorentz.Mt`. - """ + """Synonym for :attr:`vector._methods.MomentumProtocolLorentz.Mt`.""" raise AssertionError @property @@ -1409,9 +1341,7 @@ def mt2(self) -> ScalarCollection: @property def transverse_mass2(self) -> ScalarCollection: - """ - Synonym for :attr:`vector._methods.MomentumProtocolLorentz.Mt2`. - """ + """Synonym for :attr:`vector._methods.MomentumProtocolLorentz.Mt2`.""" raise AssertionError @@ -2579,9 +2509,7 @@ def transverse_mass2(self) -> ScalarCollection: def dim(v: VectorProtocol) -> int: - """ - Returns the number of dimensions in a vector: 2, 3, or 4. - """ + """Returns the number of dimensions in a vector: 2, 3, or 4.""" if isinstance(v, Vector2D): return 2 elif isinstance(v, Vector3D): @@ -2737,7 +2665,7 @@ def _lib_of(*objects: VectorProtocol) -> Module: # NumPy-like module Determines the ``lib`` of a vector or set of vectors, complaining if they're incompatible. """ - lib = None + lib: typing.Optional[typing.Any] = None for obj in objects: if isinstance(obj, Vector): if lib is None: @@ -2794,7 +2722,7 @@ def _handler_of(*objects: VectorProtocol) -> VectorProtocol: objects to NumPy arrays to Awkward Arrays whenever two are used in the same formula. """ - handler = None + handler: typing.Optional[VectorProtocol] = None for obj in objects: if not isinstance(obj, Vector): continue @@ -2815,7 +2743,7 @@ def _flavor_of(*objects: VectorProtocol) -> typing.Type[VectorProtocol]: from vector.backends.numpy import VectorNumpy from vector.backends.object import VectorObject - handler = None + handler: typing.Optional[VectorProtocol] = None is_momentum = True for obj in objects: if isinstance(obj, Vector): diff --git a/src/vector/backends/_numba_object.py b/src/vector/backends/_numba_object.py index 1c307b6b..31e1b286 100644 --- a/src/vector/backends/_numba_object.py +++ b/src/vector/backends/_numba_object.py @@ -6,7 +6,8 @@ # type: ignore """ -Implements VectorObjects in Numba. Every function should be made usable in Numba. +Implements VectorObjects in Numba. +Every function should be made usable in Numba. """ import operator diff --git a/src/vector/backends/awkward.py b/src/vector/backends/awkward.py index e4cf7083..7e0ff9a5 100644 --- a/src/vector/backends/awkward.py +++ b/src/vector/backends/awkward.py @@ -344,7 +344,8 @@ class AzimuthalAwkwardXY(AzimuthalAwkward, AzimuthalXY): >>> az AzimuthalAwkwardXY(, ) >>> az.elements - (, )""" + (, ) + """ __slots__ = ("x", "y") @@ -573,12 +574,12 @@ def elements(self) -> typing.Tuple[ArrayOrRecord]: def _class_to_name(cls: typing.Type[VectorProtocol]) -> str: if issubclass(cls, Momentum): - if issubclass(cls, Vector2D): - return "Momentum2D" - elif issubclass(cls, Vector3D): - return "Momentum3D" - elif issubclass(cls, Vector4D): - return "Momentum4D" + if issubclass(cls, Vector2D): # type: ignore[unreachable] + return "Momentum2D" # type: ignore[unreachable] + elif issubclass(cls, Vector3D): # type: ignore[unreachable] + return "Momentum3D" # type: ignore[unreachable] + elif issubclass(cls, Vector4D): # type: ignore[unreachable] + return "Momentum4D" # type: ignore[unreachable] else: if issubclass(cls, Vector2D): return "Vector2D" @@ -602,9 +603,7 @@ def _no_record(x: ak.Array) -> typing.Optional[ak.Array]: class VectorAwkward: - """ - One dimensional vector class for the Awkward backend. - """ + """One dimensional vector class for the Awkward backend.""" lib: types.ModuleType = numpy @@ -1234,9 +1233,7 @@ def allclose( atol: ScalarCollection = 1e-08, equal_nan: BoolCollection = False, ) -> BoolCollection: - """ - Like ``np.ndarray.allclose``, but for VectorArray2D. - """ + """Like ``np.ndarray.allclose``, but for VectorArray2D.""" return ak.all(self.isclose(other, rtol=rtol, atol=atol, equal_nan=equal_nan)) @@ -1272,9 +1269,7 @@ def allclose( atol: ScalarCollection = 1e-08, equal_nan: BoolCollection = False, ) -> BoolCollection: - """ - Like ``np.ndarray.allclose``, but for VectorArray3D. - """ + """Like ``np.ndarray.allclose``, but for VectorArray3D.""" return ak.all(self.isclose(other, rtol=rtol, atol=atol, equal_nan=equal_nan)) @@ -1310,9 +1305,7 @@ def allclose( atol: ScalarCollection = 1e-08, equal_nan: BoolCollection = False, ) -> BoolCollection: - """ - Like ``np.ndarray.allclose``, but for VectorArray4D. - """ + """Like ``np.ndarray.allclose``, but for VectorArray4D.""" return ak.all(self.isclose(other, rtol=rtol, atol=atol, equal_nan=equal_nan)) @@ -1348,9 +1341,7 @@ def allclose( atol: ScalarCollection = 1e-08, equal_nan: BoolCollection = False, ) -> BoolCollection: - """ - Like ``np.ndarray.allclose``, but for MomentumArray4D. - """ + """Like ``np.ndarray.allclose``, but for MomentumArray4D.""" return ak.all(self.isclose(other, rtol=rtol, atol=atol, equal_nan=equal_nan)) diff --git a/src/vector/backends/numpy.py b/src/vector/backends/numpy.py index 66e39a95..dfa08c59 100644 --- a/src/vector/backends/numpy.py +++ b/src/vector/backends/numpy.py @@ -229,7 +229,7 @@ def _has( def _toarrays( - result: typing.Tuple[ScalarCollection, ...] + result: typing.Union[typing.Tuple[ScalarCollection, ...], ScalarCollection] ) -> typing.Tuple[FloatArray, ...]: """ Converts a tuple of values to a tuple of ``numpy.array``s. @@ -263,7 +263,9 @@ def _toarrays( return result[0] -def _shape_of(result: typing.Tuple[FloatArray, ...]) -> typing.Tuple[int, ...]: +def _shape_of( + result: typing.Union[typing.Tuple[FloatArray, ...], ScalarCollection] +) -> typing.Tuple[int, ...]: """ Calculates the shape of a tuple of ``numpy.array``s. The shape returned is the highest (numerical) value of the shapes present in the tuple. @@ -286,7 +288,7 @@ def _shape_of(result: typing.Tuple[FloatArray, ...]) -> typing.Tuple[int, ...]: """ if not isinstance(result, tuple): result = (result,) - shape = None + shape: typing.Optional[typing.List[int]] = None for x in result: if hasattr(x, "shape"): thisshape = list(x.shape) @@ -315,34 +317,26 @@ def __getitem__(self, where: typing.Any) -> typing.Union[float, FloatArray]: class CoordinatesNumpy: - """ - Coordinates class for the Numpy backend. - """ + """Coordinates class for the Numpy backend.""" lib = numpy dtype: "numpy.dtype[typing.Any]" class AzimuthalNumpy(CoordinatesNumpy, Azimuthal): - """ - Azimuthal class for the NumPy backend. - """ + """Azimuthal class for the NumPy backend.""" ObjectClass: typing.Type[vector.backends.object.AzimuthalObject] class LongitudinalNumpy(CoordinatesNumpy, Longitudinal): - """ - Longitudinal class for the NumPy backend. - """ + """Longitudinal class for the NumPy backend.""" ObjectClass: typing.Type[vector.backends.object.LongitudinalObject] class TemporalNumpy(CoordinatesNumpy, Temporal): - """ - Temporal class for the NumPy backend. - """ + """Temporal class for the NumPy backend.""" ObjectClass: typing.Type[vector.backends.object.TemporalObject] @@ -389,16 +383,12 @@ def elements(self) -> typing.Tuple[FloatArray, FloatArray]: @property def x(self) -> FloatArray: - """ - The ``x`` coordinates. - """ + """The ``x`` coordinates.""" return self["x"] @property def y(self) -> FloatArray: - """ - The ``y`` coordinates. - """ + """The ``y`` coordinates.""" return self["y"] @@ -444,16 +434,12 @@ def elements(self) -> typing.Tuple[FloatArray, FloatArray]: @property def rho(self) -> FloatArray: - """ - The ``rho`` coordinates. - """ + """The ``rho`` coordinates.""" return self["rho"] @property def phi(self) -> FloatArray: - """ - The ``phi`` coordinates. - """ + """The ``phi`` coordinates.""" return self["phi"] @@ -498,9 +484,7 @@ def elements(self) -> typing.Tuple[FloatArray]: @property def z(self) -> FloatArray: - """ - The ``z`` coordinates. - """ + """The ``z`` coordinates.""" return self["z"] @@ -547,9 +531,7 @@ def elements(self) -> typing.Tuple[FloatArray]: @property def theta(self) -> FloatArray: - """ - The ``theta`` coordinates. - """ + """The ``theta`` coordinates.""" return self["theta"] @@ -594,9 +576,7 @@ def elements(self) -> typing.Tuple[FloatArray]: @property def eta(self) -> FloatArray: - """ - The ``eta`` coordinates. - """ + """The ``eta`` coordinates.""" return self["eta"] @@ -641,16 +621,12 @@ def elements(self) -> typing.Tuple[FloatArray]: @property def t(self) -> FloatArray: - """ - The ``t`` coordinates. - """ + """The ``t`` coordinates.""" return self["t"] class TemporalNumpyTau(TemporalNumpy, TemporalTau, GetItem, FloatArray): # type: ignore[misc] - """ - Class for the ``tau`` (temporal) coordinate of NumPy backend. - """ + """Class for the ``tau`` (temporal) coordinate of NumPy backend.""" ObjectClass = vector.backends.object.TemporalObjectTau _IS_MOMENTUM = False @@ -682,16 +658,12 @@ def elements(self) -> typing.Tuple[FloatArray]: @property def tau(self) -> FloatArray: - """ - The ``tau`` coordinates. - """ + """The ``tau`` coordinates.""" return self["tau"] class VectorNumpy(Vector, GetItem): - """ - One dimensional vector class for the NumPy backend. - """ + """One dimensional vector class for the NumPy backend.""" lib = numpy dtype: "numpy.dtype[typing.Any]" @@ -703,9 +675,7 @@ def allclose( atol: typing.Union[float, FloatArray] = 1e-08, equal_nan: typing.Union[bool, FloatArray] = False, ) -> FloatArray: - """ - Like ``np.ndarray.allclose``, but for VectorNumpy. - """ + """Like ``np.ndarray.allclose``, but for VectorNumpy.""" return self.isclose(other, rtol=rtol, atol=atol, equal_nan=equal_nan).all() def __eq__(self, other: typing.Any) -> typing.Any: @@ -975,9 +945,7 @@ class VectorNumpy2D(VectorNumpy, Planar, Vector2D, FloatArray): # type: ignore[ ] def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> "VectorNumpy2D": - """ - Returns the object of ``VectorNumpy2D``. Behaves as ``__init__`` in this case. - """ + """Returns the object of ``VectorNumpy2D``. Behaves as ``__init__`` in this case.""" if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], dict): array = _array_from_columns(args[0]) else: @@ -1241,9 +1209,7 @@ class VectorNumpy3D(VectorNumpy, Spatial, Vector3D, FloatArray): # type: ignore ] def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> "VectorNumpy3D": - """ - Returns the object of ``VectorNumpy3D``. Behaves as ``__init__`` in this case. - """ + """Returns the object of ``VectorNumpy3D``. Behaves as ``__init__`` in this case.""" if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], dict): array = _array_from_columns(args[0]) else: @@ -1283,17 +1249,13 @@ def __repr__(self) -> str: @property def azimuthal(self) -> AzimuthalNumpy: - """ - Returns the azimuthal type class for the given ``VectorNumpy3D`` object. - """ + """Returns the azimuthal type class for the given ``VectorNumpy3D`` object.""" # TODO: Add an example here - see https://github.com/scikit-hep/vector/issues/194 return self.view(self._azimuthal_type) # type: ignore[return-value] @property def longitudinal(self) -> LongitudinalNumpy: - """ - Returns the longitudinal type class for the given ``VectorNumpy3D`` object. - """ + """Returns the longitudinal type class for the given ``VectorNumpy3D`` object.""" # TODO: Add an example here - see https://github.com/scikit-hep/vector/issues/194 return self.view(self._longitudinal_type) # type: ignore[return-value] @@ -1537,9 +1499,7 @@ class VectorNumpy4D(VectorNumpy, Lorentz, Vector4D, FloatArray): # type: ignore ] def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> "VectorNumpy4D": - """ - Returns the object of ``VectorNumpy4D``. Behaves as ``__init__`` in this case. - """ + """Returns the object of ``VectorNumpy4D``. Behaves as ``__init__`` in this case.""" if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], dict): array = _array_from_columns(args[0]) else: @@ -1590,25 +1550,19 @@ def __repr__(self) -> str: @property def azimuthal(self) -> AzimuthalNumpy: - """ - Returns the azimuthal type class for the given ``VectorNumpy4D`` object. - """ + """Returns the azimuthal type class for the given ``VectorNumpy4D`` object.""" # TODO: Add an example here - see https://github.com/scikit-hep/vector/issues/194 return self.view(self._azimuthal_type) # type: ignore[return-value] @property def longitudinal(self) -> LongitudinalNumpy: - """ - Returns the longitudinal type class for the given ``Vectornumpy4D`` object. - """ + """Returns the longitudinal type class for the given ``Vectornumpy4D`` object.""" # TODO: Add an example here - see https://github.com/scikit-hep/vector/issues/194 return self.view(self._longitudinal_type) # type: ignore[return-value] @property def temporal(self) -> TemporalNumpy: - """ - Returns the azimuthal type class for the given ``VectorNumpy4D`` object. - """ + """Returns the azimuthal type class for the given ``VectorNumpy4D`` object.""" # TODO: Add an example here - see https://github.com/scikit-hep/vector/issues/194 return self.view(self._temporal_type) # type: ignore[return-value] @@ -1897,7 +1851,6 @@ def array(*args: typing.Any, **kwargs: typing.Any) -> VectorNumpy: are not numbers, mathematical operations will fail. Usually, you want them to be ``np.integer`` or ``np.floating``. """ - names: typing.Tuple[str, ...] if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], dict): names = tuple(args[0].keys()) diff --git a/src/vector/backends/object.py b/src/vector/backends/object.py index f2719265..4534a31b 100644 --- a/src/vector/backends/object.py +++ b/src/vector/backends/object.py @@ -59,41 +59,31 @@ class CoordinatesObject: - """ - Coordinates class for the Object backend. - """ + """Coordinates class for the Object backend.""" pass class AzimuthalObject(CoordinatesObject, Azimuthal): - """ - Azimuthal class for the Object backend. - """ + """Azimuthal class for the Object backend.""" pass class LongitudinalObject(CoordinatesObject, Longitudinal): - """ - Longitudinal class for the Object backend. - """ + """Longitudinal class for the Object backend.""" pass class TemporalObject(CoordinatesObject, Temporal): - """ - Temporal class for the Object backend. - """ + """Temporal class for the Object backend.""" pass class TupleXY(typing.NamedTuple): - """ - ``x`` and ``y`` coordinates as a ``NamedTuple``. - """ + """``x`` and ``y`` coordinates as a ``NamedTuple``.""" x: float y: float @@ -122,9 +112,7 @@ def elements(self) -> typing.Tuple[float, float]: class TupleRhoPhi(typing.NamedTuple): - """ - ``rho`` and ``phi`` coordinates as a ``NamedTuple``. - """ + """``rho`` and ``phi`` coordinates as a ``NamedTuple``.""" rho: float phi: float @@ -153,9 +141,7 @@ def elements(self) -> typing.Tuple[float, float]: class TupleZ(typing.NamedTuple): - """ - ``z`` coordinate as a ``NamedTuple``. - """ + """``z`` coordinate as a ``NamedTuple``.""" z: float @@ -183,9 +169,7 @@ def elements(self) -> typing.Tuple[float]: class TupleTheta(typing.NamedTuple): - """ - ``theta`` coordinates as a ``NamedTuple``. - """ + """``theta`` coordinates as a ``NamedTuple``.""" theta: float @@ -213,9 +197,7 @@ def elements(self) -> typing.Tuple[float]: class TupleEta(typing.NamedTuple): - """ - ``eta`` coordinate as a ``NamedTuple``. - """ + """``eta`` coordinate as a ``NamedTuple``.""" eta: float @@ -245,9 +227,7 @@ def elements(self) -> typing.Tuple[float]: class TupleT(typing.NamedTuple): - """ - ``t`` coordinate as a ``NamedTuple``. - """ + """``t`` coordinate as a ``NamedTuple``.""" t: float @@ -277,9 +257,7 @@ def elements(self) -> typing.Tuple[float]: class TupleTau(typing.NamedTuple): - """ - ``tau`` coordinate as a ``NamedTuple``. - """ + """``tau`` coordinate as a ``NamedTuple``.""" tau: float @@ -351,9 +329,7 @@ def _replace_data(obj: typing.Any, result: typing.Any) -> typing.Any: class VectorObject(Vector): - """ - One dimensional vector class for the object backend. - """ + """One dimensional vector class for the object backend.""" lib = numpy