diff --git a/README.md b/README.md index 18ae74a..8e1ed72 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -# LorentzVectorBase - -Base interfaces for four-momenta in high-energy physics. +# LorentzVectorBase.jl [![Stable Documentation](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaHEP.github.io/LorentzVectorBase.jl/stable/) [![In development documentation](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaHEP.github.io/LorentzVectorBase.jl/dev/) @@ -8,17 +6,69 @@ Base interfaces for four-momenta in high-energy physics. [![Docs workflow Status](https://github.com/JuliaHEP/LorentzVectorBase.jl/actions/workflows/Docs.yml/badge.svg?branch=main)](https://github.com/JuliaHEP/LorentzVectorBase.jl/actions/workflows/Docs.yml?query=branch%3Amain) [![Coverage](https://codecov.io/gh/JuliaHEP/LorentzVectorBase.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/JuliaHEP/LorentzVectorBase.jl) -# Code Formatting +**LorentzVectorBase.jl** provides base interfaces for four-momenta in high-energy physics, facilitating standardized representations and operations on Lorentz vectors. + +## Installation + +Install the package using Julia's package manager: + +```julia +using Pkg +Pkg.add("LorentzVectorBase") +``` + +Alternatively, in the Julia REPL, enter `pkg>` mode by typing `]`, then + +```julia-repl +add LorentzVectorBase +``` + +## Usage + +This package defines abstract interfaces for Lorentz vectors. To utilize concrete +implementations, consider packages like +[LorentzVectorHEP.jl](https://github.com/JuliaHEP/LorentzVectorHEP.jl) or +[FourVectors.jl](https://github.com/mmikhasenko/FourVectors.jl). -This package follows a standardized formatting configuration to ensure consistency across -the codebase. We enforce these formatting guidelines by running checks on all pull requests -through continuous integration (CI). +## Example -To format your code locally and ensure it meets the required standards, you can run the -following command in your terminal: +The following example shows how to define a custom Lorentz vector type and implement the +minimal set of interface functions required by `LorentzVectorBase`: -```bash +```julia +struct CustomLVector + id::Int + x::Float64 + y::Float64 + z::Float64 + t::Float64 +end + +LorentzVectorBase.coordinate_system(::CustomLVector) = LorentzVectorBase.XYZT() +LorentzVectorBase.x(lv::CustomLVector) = lv.x +LorentzVectorBase.y(lv::CustomLVector) = lv.y +LorentzVectorBase.z(lv::CustomLVector) = lv.z +LorentzVectorBase.t(lv::CustomLVector) = lv.t +``` + +Your custom type can include any additional fields or logic as needed. +In this example, an extra `id` field is included alongside the four-vector components: + +```julia +c = CustomLVector(rand(1:100), 1, 2, 3, 4) +@assert isapprox(LorentzVectorBase.spatial_magnitude(c), sqrt(1^2 + 2^2 + 3^2)) +``` + +## Code Formatting + +To maintain code consistency, format your code with: + +```julia julia --project=.formatting -e 'using Pkg; Pkg.instantiate(); include(".formatting/format_all.jl")' ``` -This will apply the necessary formatting rules to your code before submission. +This ensures adherence to the project's formatting standards. + +## License + +This project is licensed under the MIT License. diff --git a/docs/Project.toml b/docs/Project.toml index 1c23ddb..c18c4f0 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,3 +1,4 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" LorentzVectorBase = "592a752d-6533-4762-a71b-738712ea30ec" diff --git a/docs/make.jl b/docs/make.jl index 64ecb3f..2819ed1 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,28 +7,81 @@ Pkg.develop(; path=project_path) using LorentzVectorBase using Documenter +using Literate DocMeta.setdocmeta!( LorentzVectorBase, :DocTestSetup, :(using LorentzVectorBase); recursive=true ) +# some paths for links +readme_path = joinpath(project_path, "README.md") +index_path = joinpath(project_path, "docs/src/index.md") +license_path = "https://github.com/QEDjl-project/QEDcore.jl/blob/main/LICENSE" + +# Copy README.md from the project base folder and use it as the start page +open(readme_path, "r") do readme_in + readme_string = read(readme_in, String) + + # replace relative links in the README.md + readme_string = replace(readme_string, "[MIT](LICENSE)" => "[MIT]($(license_path))") + + open(index_path, "w") do readme_out + write(readme_out, readme_string) + end +end + +# setup examples using Literate.jl +literate_paths = [ + Base.Filesystem.joinpath(project_path, "docs/src/tutorial/20-new_four_vector.jl"), + Base.Filesystem.joinpath(project_path, "docs/src/tutorial/21-new_coord_system.jl"), +] + +tutorial_output_dir = joinpath(project_path, "docs/src/generated/") +!ispath(tutorial_output_dir) && mkdir(tutorial_output_dir) +@info "Literate: create temp dir at $tutorial_output_dir" + +tutorial_output_dir_name = splitpath(tutorial_output_dir)[end] + pages = [ - "Home" => "index.md", "Interface" => "10-interface.md", "Reference" => "95-reference.md" + "Home" => "index.md", + "Interface" => "10-interface.md", + "Tutorial" => [ + "New Four-Vector" => joinpath(tutorial_output_dir_name, "20-new_four_vector.md"), + "New Coordinate System" => + joinpath(tutorial_output_dir_name, "21-new_coord_system.md"), + ], + "Contributors guide" => "90-contributing.md", + "Developer docs" => "91-dev_docs.md", + "API Reference" => "95-reference.md", ] -makedocs(; - modules=[LorentzVectorBase], - authors="Uwe Hernandez Acosta ", - repo=Documenter.Remotes.GitHub("JuliaHEP", "LorentzVectorBase.jl"), - sitename="LorentzVectorBase.jl", - format=Documenter.HTML(; - prettyurls=get(ENV, "CI", "false") == "true", - canonical="https://JuliaHEP.github.io/LorentzVectorBase.jl", - # repolink="https://github.com/JuliaHEP/LorentzVectorBase.jl", - # edit_link="main", - assets=String[], - ), - pages=pages, -) +try + + # generate markdown files with Literate.jl + for file in literate_paths + Literate.markdown(file, tutorial_output_dir; documenter=true) + end + makedocs(; + modules=[LorentzVectorBase], + authors="Uwe Hernandez Acosta ", + repo=Documenter.Remotes.GitHub("JuliaHEP", "LorentzVectorBase.jl"), + sitename="LorentzVectorBase.jl", + format=Documenter.HTML(; + prettyurls=get(ENV, "CI", "false") == "true", + canonical="https://JuliaHEP.github.io/LorentzVectorBase.jl", + # repolink="https://github.com/JuliaHEP/LorentzVectorBase.jl", + # edit_link="main", + assets=String[], + ), + pages=pages, + ) + + deploydocs(; repo="github.com/JuliaHEP/LorentzVectorBase.jl", push_preview=true) +finally + # doing some garbage collection + @info "GarbageCollection: remove generated landing page" + rm(index_path) -deploydocs(; repo="github.com/JuliaHEP/LorentzVectorBase.jl", push_preview=true) + @info "GarbageCollection: remove generated tutorial files" + rm(tutorial_output_dir; recursive=true) +end diff --git a/docs/src/10-interface.md b/docs/src/10-interface.md index f16fd22..2d04864 100644 --- a/docs/src/10-interface.md +++ b/docs/src/10-interface.md @@ -1,88 +1,159 @@ -# [Interface](@id interface) +```@meta +CurrentModule = LorentzVectorBase +``` -This section explains how an object can become a _object with kinematic informations_. +# [`LorentzVectorBase` Interface](@id interface) -## Definition +The `LorentzVectorBase` package defines a **common interface for +LorentzVectorBase-compliant types** in Julia. This interface allows developers to define +their own custom four-vector types (e.g., for particles or kinematic configurations) and +automatically gain access to a large suite of common kinematic computations. For maximum +flexibility, it is **not necessary** to inherit from an abstract base +type. -A type that adheres to the interface described in this section will be referred to as -_`KinematicInterface`-compliant_. A package providing such a type will be called _the provider_. +## Purpose -## Coordinate Systems +The main goal is to provide a lightweight abstraction that enables: -The provider must define a preferred coordinate system for its _`KinematicInterface`-compliant_ -type and provide accessors for the components of this system using standardized methods -(outlined below). If the object natively supports multiple coordinate systems, the provider -should choose the one in which component access is the most efficient as the _preferred -coordinate system_. This system must be one of the supported options. +- Interoperability between different packages using Lorentz vectors +- Automatic derivation of many derived quantities (e.g. $p_T$, $\eta$, $m$) from a minimal interface +- Coordinate system flexibility while maintaining performance -The `LorentzVectorBase` package supplements these component accessors to cover all supported -coordinate systems. It uses the components of the _preferred coordinate system_ to implement -complementary accessors. Julia’s dispatch mechanism prioritizes the accessors provided by -the object itself. +## Defining a Lorentz-Vector-Like Type -!!! note +To make your type compliant with `LorentzVectorBase`, you must: - A `KinematicInterface`-compliant type can store additional data beyond the four-vector. For instance, - a type representing an elementary particle may comply while containing more information than - just the particle’s four-momentum. +Assign a coordinate system using: -## Implementation +```julia +LorentzVectorBase.coordinate_system(::Type{MyVector}) = XYZT() +``` -A type `MyLorentzVector` (which do not necessary need to be a vector) will comply with the `KinematicInterface` if it implements -one of the following sets of methods: +Coordinate systems are tagged using constructors like `XYZT()`, `PtEtaPhiE()`, etc. These indicate how the four components are interpreted. -### Option 1: Position with Cartesian Coordinates +Implement the four accessors required by the chosen coordinate system. -| Required Methods | Brief Description | -| --------------------------------------------------------------------------------------- | ----------------------------------------------- | -| `LorentzVectorBase.islorentzvector(::Type{MyLorentzVector})` | Declare that your type implements the interface | -| `LorentzVectorBase.coordinate_system(::Type{MyLorentzVector}) = LorentzVectorBase.XYZE` | Declare the preferred coordinate system | -| `LorentzVectorBase.x(::MyLorentzVector)` | X Cartesian coordinate | -| `LorentzVectorBase.y(::MyLorentzVector)` | Y Cartesian coordinate | -| `LorentzVectorBase.z(::MyLorentzVector)` | Z Cartesian coordinate | -| `LorentzVectorBase.t(::MyLorentzVector)` | Time coordinate (t) | +For example, with `XYZT()`: -### Option 2: Four-Momentum with Cartesian Coordinates +```julia +x(::MyVector) +y(::MyVector) +z(::MyVector) +t(::MyVector) +``` -| Required Methods | Brief Description | -| --------------------------------------------------------------------------------------- | ----------------------------------------------- | -| `LorentzVectorBase.islorentzvector(::Type{MyLorentzVector})` | Declare that your type implements the interface | -| `LorentzVectorBase.coordinate_system(::Type{MyLorentzVector}) = LorentzVectorBase.XYZE` | Declare the preferred coordinate system | -| `LorentzVectorBase.px(::MyLorentzVector)` | Momentum X-component | -| `LorentzVectorBase.py(::MyLorentzVector)` | Momentum Y-component | -| `LorentzVectorBase.pz(::MyLorentzVector)` | Momentum Z-component | -| `LorentzVectorBase.energy(::MyLorentzVector)` | Energy | +You can inspect the required accessors for a given coordinate system using: -### Option 3: Four-Momentum with Cylindrical Coordinates +```julia +coordinate_system(XYZT()) # returns (:x, :y, :z, :t) +``` -| Required Methods | Brief Description | -| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| `LorentzVectorBase.islorentzvector(::Type{MyLorentzVector})` | Declare that your type implements the interface | -| `LorentzVectorBase.coordinate_system(::Type{MyLorentzVector})` | Declare the preferred coordinate system, which must return `PtEtaPhiM`, `PtEtaPhiE`, `PtYPhiM`, or `PtYPhiE` (from `LorentzVectorBase`) | -| `LorentzVectorBase.pt(::MyLorentzVector)` | Transverse momentum | -| `LorentzVectorBase.phi(::MyLorentzVector)` | Azimuthal angle | +This indicates which component accessors your type must implement to be compliant with that system. -Additionally, you must implement _one_ of the following: +That's it! Once those are defined, the `LorentzVectorBase` package will automatically +provide implementations for a wide variety of additional kinematic functions and +coordinate conversions. -| Required Method | Description | -| ----------------------------------------------- | ------------------------------------------- | -| `LorentzVectorBase.eta(::MyLorentzVector)` | Pseudorapidity | -| `LorentzVectorBase.rapidity(::MyLorentzVector)` | Rapidity relative to the beam axis (z-axis) | +## [What You Get Automatically](@id getter) -And _one_ of: +Once a minimal interface is implemented, the following functions become available (among others), categorized by topic: -| Required Method | Description | -| --------------------------------------------- | -------------- | -| `LorentzVectorBase.energy(::MyLorentzVector)` | Energy | -| `LorentzVectorBase.mass(::MyLorentzVector)` | Invariant mass | +### Cartesian Components -The methods returning the coordinates of the preferred system (as specified by `coordinate_system()`) must be implemented. +- [`x`](@ref), [`y`](@ref), [`z`](@ref), [`t`](@ref) +- [`px`](@ref), [`py`](@ref), [`pz`](@ref), [`E`](@ref) -## Optional Methods +### Spherical and Cylindrical Coordinates -| Optional Method | Description | -| -------------------------------------------- | -------------------------------------------- | -| `LorentzVectorBase.mass2(::MyLorentzVector)` | Square of the mass | -| `LorentzVectorBase.rho2(::MyLorentzVector)` | ρ² = \|**p**\|² (squared momentum magnitude) | +- [`spatial_magnitude`](@ref), [`spatial_magnitude2`](@ref) +- [`polar_angle`](@ref), [`cos_theta`](@ref) +- [`phi`](@ref), [`cos_phi`](@ref), [`sin_phi`](@ref) -Additionally, any method from another option (i.e., a method from Option Y when methods from Option X are provided) may also be implemented. +### Mass and Invariant Quantities + +- [`mass`](@ref), [`mass2`](@ref) +- [`mt`](@ref), [`mt2`](@ref) +- [`pt`](@ref), [`pt2`](@ref) + +### Rapidity and Related Quantities + +- [`eta`](@ref): pseudorapidity +- [`rapidity`](@ref) + +### Boost Parameters + +- [`boost_beta`](@ref), [`boost_gamma`](@ref) + +### Light-Cone Coordinates + +- [`plus_component`](@ref), [`minus_component`](@ref) + +### Accessor Aliases + +To improve readability and interoperability, `LorentzVectorBase` provides a set of +**aliases** for common physics terminology. These aliases map frequently used or +alternative names to the canonical accessor functions. + +For example, `energy` is an alias for `t`, and `invariant_mass` maps to `mass`. + +```julia +energy(lv) === t(lv) +invariant_mass(lv) === mass(lv) +transverse_momentum(lv) === pt(lv) +``` + +This allows users to choose more descriptive or domain-specific terminology without losing compatibility. + +### Available Aliases + +| Alias | Canonical Function | +| ---------------------- | ------------------ | +| `energy` | `t` | +| `invariant_mass` | `mass` | +| `invariant_mass2` | `mass2` | +| `transverse_momentum` | `pt` | +| `transverse_momentum2` | `pt2` | +| `perp` | `pt` | +| `perp2` | `pt2` | +| `transverse_mass` | `mt` | +| `transverse_mass2` | `mt2` | +| `azimuthal_angle` | `phi` | +| `pseudorapidity` | `eta` | + +## Coordinate System Tags + +The following coordinate systems are supported via tags like `XYZT()`, `PtEtaPhiM()`, etc.: + +- `XYZT`, `PxPyPzE` — position/time or cartesian four-momentum +- `PtEtaPhiM` — common in collider physics + +Each tag specifies which component names (`x`, `pt`, `eta`, etc.) you must implement. + +## Example + +```julia +struct MyVector + px::Float64 + py::Float64 + pz::Float64 + E::Float64 +end + +LorentzVectorBase.coordinate_system(::Type{MyVector}) = XYZE() + +LorentzVectorBase.px(v::MyVector) = v.px +LorentzVectorBase.py(v::MyVector) = v.py +LorentzVectorBase.pz(v::MyVector) = v.pz +LorentzVectorBase.energy(v::MyVector) = v.E +``` + +Now your type supports: + +```julia +mass(MyVector(...)) +eta(MyVector(...)) +pt(MyVector(...)) +phi(MyVector(...)) +``` + +without implementing them manually. diff --git a/docs/src/90-contributing.md b/docs/src/90-contributing.md new file mode 100644 index 0000000..3bdf771 --- /dev/null +++ b/docs/src/90-contributing.md @@ -0,0 +1,26 @@ +# [Contributing guidelines](@id contributing) + +First of all, thanks for the interest! + +We welcome all kinds of contribution, including, but not limited to code, documentation, examples, configuration, issue creating, etc. + +Be polite and respectful, and follow the code of conduct. + +## Bug reports and discussions + +If you think you found a bug, feel free to open an [issue](https://github.com/JuliaHEP/LorentzVectorBase.jl/issues). +Focused suggestions and requests can also be opened as issues. +Before opening a pull request, start an issue or a discussion on the topic, please. + +## Working on an issue + +If you found an issue that interests you, comment on that issue what your plans are. +If the solution to the issue is clear, you can immediately create a pull request (see below). +Otherwise, say what your proposed solution is and wait for a discussion around it. + +!!! tip + + If you haven’t received a reply after a few days, don’t hesitate to follow up. + +If your solution involves code (or something that requires running the package locally), check the [developer documentation](91-dev_docs.md). +Otherwise, you can use the GitHub interface directly to create your pull request. diff --git a/docs/src/91-dev_docs.md b/docs/src/91-dev_docs.md new file mode 100644 index 0000000..11fd3a2 --- /dev/null +++ b/docs/src/91-dev_docs.md @@ -0,0 +1,141 @@ +# [Developer documentation](@id dev_docs) + +!!! note "Contributing guidelines" + + If you haven't, please read the [Contributing guidelines](90-contributing.md) first. + +If you want to make contributions to this package that involves code, then this guide is for you. + +## First time clone + +!!! tip "If you have write access to the repository" + + If you have write access you don't have to fork. Instead, simply clone and skip ahead. Whenever **upstream** is mentioned, use **origin** instead. This also allows pull requests to push a preview of the docs. + +If this is the first time you work with this repository, follow the instructions below to clone the repository. + +1. Fork this repo +2. Clone your repo (this will create a `git remote` called `origin`) +3. Add this repo as a remote: + + ```bash + git remote add upstream https://github.com/JuliaHEP/LorentzVectorBase.jl.git + ``` + +This will ensure that you have two remotes in your git: `origin` and `upstream`. +You will create branches and push to `origin`, and you will fetch and update your local `main` branch from `upstream`. + +## Code Formatting + +To maintain code consistency, format your code with: + +```julia +julia --project=.formatting -e 'using Pkg; Pkg.instantiate(); include(".formatting/format_all.jl")' +``` + +This ensures adherence to the project's formatting standards. + +## Testing + +As with most Julia packages, you can just open Julia in the repository folder, activate the environment, and run `test`: + +```julia-repl +julia> # press ] +pkg> activate . +pkg> test +``` + +## Working on a new issue + +We try to keep a linear history in this repo, so it is important to keep your branches up-to-date. + +1. Fetch from the remote and fast-forward your local main + + ```bash + git fetch upstream + git switch main + git merge --ff-only upstream/main + ``` + +2. Branch from `main` to address the issue (see below for naming) + + ```bash + git switch -c 42-add-answer-universe + ``` + +3. Push the new local branch to your personal remote repository + + ```bash + git push -u origin 42-add-answer-universe + ``` + +4. Create a pull request to merge your remote branch into the org main. + +### Branch naming + +- If there is an associated issue, add the issue number. +- If there is no associated issue, **and the changes are small**, add a prefix such as "typo", "hotfix", "small-refactor", according to the type of update. +- If the changes are not small and there is no associated issue, then create the issue first, so we can properly discuss the changes. +- Use dash separated imperative wording related to the issue (e.g., `14-add-tests`, `15-fix-model`, `16-remove-obsolete-files`). + +### Commit message + +- Use imperative or present tense, for instance: _Add feature_ or _Fix bug_. +- Have informative titles. +- When necessary, add a body with details. +- If there are breaking changes, add the information to the commit message. + +### Before creating a pull request + +!!! tip "Atomic git commits" + + Try to create "atomic git commits" (recommended reading: [The Utopic Git History](https://blog.esciencecenter.nl/the-utopic-git-history-d44b81c09593)). + +- Make sure the tests pass. +- Make sure the pre-commit tests pass. +- Fetch any `main` updates from upstream and rebase your branch, if necessary: + + ```bash + git fetch upstream + git rebase upstream/main BRANCH_NAME + ``` + +- Then you can open a pull request and work with the reviewer to address any issues. + +## Building and viewing the documentation locally + +Following the latest suggestions, we recommend using `LiveServer` to build the documentation. +Here is how you do it: + +1. Run `julia --project=docs` to open Julia in the environment of the docs. +1. If this is the first time building the docs + 1. Press `]` to enter `pkg` mode + 1. Run `pkg> dev .` to use the development version of your package + 1. Press backspace to leave `pkg` mode +1. Run `julia> using LiveServer` +1. Run `julia> servedocs()` + +## Making a new release + +To create a new release, you can follow these simple steps: + +- Create a branch `release-x.y.z` +- Update `version` in `Project.toml` +- Update the `CHANGELOG.md`: + - Rename the section "Unreleased" to "[x.y.z] - yyyy-mm-dd" (i.e., version under brackets, dash, and date in ISO format) + - Add a new section on top of it named "Unreleased" + - Add a new link in the bottom for version "x.y.z" + - Change the "[unreleased]" link to use the latest version - end of line, `vx.y.z ... HEAD`. +- Create a commit "Release vx.y.z", push, create a PR, wait for it to pass, merge the PR. +- Go back to main screen and click on the latest commit (link: ) +- At the bottom, write `@JuliaRegistrator register` + +After that, you only need to wait and verify: + +- Wait for the bot to comment (should take < 1m) with a link to a PR to the registry +- Follow the link and wait for a comment on the auto-merge +- The comment should said all is well and auto-merge should occur shortly +- After the merge happens, TagBot will trigger and create a new GitHub tag. Check on +- After the release is created, a "docs" GitHub action will start for the tag. +- After it passes, a deploy action will run. +- After that runs, the [stable docs](https://JuliaHEP.github.io/LorentzVectorBase.jl/stable) should be updated. Check them and look for the version number. diff --git a/docs/src/95-reference.md b/docs/src/95-reference.md index f257171..11dc6a8 100644 --- a/docs/src/95-reference.md +++ b/docs/src/95-reference.md @@ -62,3 +62,9 @@ PtEtaPhiM plus_component minus_component ``` + +## Utility + +```@docs +available_accessors +``` diff --git a/docs/src/index.md b/docs/src/index.md deleted file mode 100644 index ff82c14..0000000 --- a/docs/src/index.md +++ /dev/null @@ -1,7 +0,0 @@ -```@meta -CurrentModule = LorentzVectorBase -``` - -# LorentzVectorBase - -Documentation for [LorentzVectorBase](https://github.com/JuliaHEP/LorentzVectorBase.jl). diff --git a/docs/src/tutorial/20-new_four_vector.jl b/docs/src/tutorial/20-new_four_vector.jl new file mode 100644 index 0000000..23e8f8e --- /dev/null +++ b/docs/src/tutorial/20-new_four_vector.jl @@ -0,0 +1,72 @@ +# # Implementing the `LorentzVectorBase` Interface +# +# This tutorial demonstrates how to make a custom Julia type compatible with the +# `LorentzVectorBase` interface. Once your type implements the minimal required +# methods, you gain access to a rich suite of automatically derived kinematic +# functions such as `mass`, `pt`, `rapidity`, `phi`, and many others. See [here](@ref +# getter) for a complete list. +# +# ## Step 1: Load the package + +using LorentzVectorBase + +# ## Step 2: Define your custom Lorentz vector type +# +# Let's define a simple concrete type representing a four-momentum vector +# using Cartesian coordinates: `x`, `y`, `z`, and `t`. + +struct MyVector + x::Float64 + y::Float64 + z::Float64 + t::Float64 +end + +# ## Step 3: Specify the coordinate system +# +# Next, we tell `LorentzVectorBase` which coordinate system our type uses. Since +# `MyVector` stores its components in Cartesian momentum form, we declare: + +LorentzVectorBase.coordinate_system(::MyVector) = LorentzVectorBase.XYZT() + +# The tag `XYZT()` indicates a four-momentum representation with components +# `(x, y, z, t)`. +# ## Step 4: Implement the required accessors +# +# Finally, we provide methods to extract the components expected by the `XYZT` +# coordinate system: `x`, `y`, `z`, and `t`. + +LorentzVectorBase.x(v::MyVector) = v.x +LorentzVectorBase.y(v::MyVector) = v.y +LorentzVectorBase.z(v::MyVector) = v.z +LorentzVectorBase.t(v::MyVector) = v.t + +# With these definitions in place, `MyVector` now fully satisfies the +# `LorentzVectorBase` interface! +# +# ## Step 5: Use the derived functionality +# +# Let's create an instance of our custom type and use some of the functionality +# that `LorentzVectorBase` now provides for free: + +using LorentzVectorBase: mass, pt, eta, rapidity, phi + +v = MyVector(1.0, 2.0, 3.0, 4.0) + +mass(v) # Invariant mass +pt(v) # Transverse momentum +eta(v) # Pseudorapidity +rapidity(v) # Rapidity +phi(v) # Azimuthal angle + +# ## Optional: Use aliases for convenience +# +# The package also defines aliases like `energy`, `invariant_mass`, and +# `transverse_momentum`. These are mapped automatically to their canonical +# counterparts: + +using LorentzVectorBase: energy, invariant_mass, transverse_momentum + +energy(v) # Same as `t(v)` or `E(v)` +invariant_mass(v) # Same as `mass(v)` +transverse_momentum(v) # Same as `pt(v)` diff --git a/docs/src/tutorial/21-new_coord_system.jl b/docs/src/tutorial/21-new_coord_system.jl new file mode 100644 index 0000000..d8c7a07 --- /dev/null +++ b/docs/src/tutorial/21-new_coord_system.jl @@ -0,0 +1,144 @@ +# # Implementing a New Coordinate System in `LorentzVectorBase.jl` +# +# In `LorentzVectorBase.jl`, **coordinate systems** are independent of any +# concrete four-vector type. +# +# This tutorial shows how to implement a **light-cone coordinate system** +# for four-momentum, so that *any* Lorentz vector type can use it — provided +# that type implements the coordinate-specific getter functions. +# +# ## Light-cone coordinates +# +# We'll define: +# +# - `plus_component` = (t + z) / √2 +# - `minus_component` = (t - z) / √2 +# - `x` +# - `y` +# +# The **coordinate-specific getters** will be: +# +# ```julia +# LorentzVectorBase.plus_component(v) +# LorentzVectorBase.minus_component(v) +# LorentzVectorBase.x(v) +# LorentzVectorBase.y(v) +# ``` +# +# All other kinematic quantities (`mass`, `pt`, `eta`, `phi`, etc.) +# will be defined *once* in the coordinate system and will work +# for any vector type implementing the coordinate getters. + +using LorentzVectorBase + +# ## 1. Define the coordinate system type +# +# Coordinate systems are [singletons](https://docs.julialang.org/en/v1/manual/types/#man-singleton-types) and must be subtypes of +# `AbstractCoordinateSystem`. + +struct LightConeCoordinates <: LorentzVectorBase.AbstractCoordinateSystem end + +# ## 2. Define the coordinate names +# +# `coordinate_names` returns the tuple of **getter function names** +# that *must* be implemented for any vector type using this coordinate system. + +function LorentzVectorBase.coordinate_names(::LightConeCoordinates) + (:plus_component, :minus_component, :x, :y) +end + +# ## 3. Implement the derived kinematic functions +# +# The coordinate system must implement all relevant getter functions listed in +# `FOURMOMENTUM_GETTER_FUNCTIONS`, except those returned by `coordinate_names`. +# +# These implementations are *type-generic* — they take a coordinate system +# instance and a `mom` (momentum object), and call only the coordinate-specific +# getters (`plus_component`, `minus_component`, `x`, `y`). + +const SQRT2 = sqrt(2.0) + +# Cartesian component accessors +function LorentzVectorBase.t(::LightConeCoordinates, mom) + (plus_component(mom) + minus_component(mom)) / SQRT2 +end +function LorentzVectorBase.z(::LightConeCoordinates, mom) + (plus_component(mom) - minus_component(mom)) / SQRT2 +end + +# Momentum magnitudes +LorentzVectorBase.px(::LightConeCoordinates, mom) = p_x(mom) +LorentzVectorBase.py(::LightConeCoordinates, mom) = p_y(mom) +function LorentzVectorBase.pz(::LightConeCoordinates, mom) + (plus_component(mom) - minus_component(mom)) / SQRT2 +end +function LorentzVectorBase.E(::LightConeCoordinates, mom) + (plus_component(mom) + minus_component(mom)) / SQRT2 +end + +LorentzVectorBase.pt2(::LightConeCoordinates, mom) = p_x(mom)^2 + p_y(mom)^2 +LorentzVectorBase.pt(cs::LightConeCoordinates, mom) = sqrt(LorentzVectorBase.pt2(cs, mom)) + +function LorentzVectorBase.spatial_magnitude2(cs::LightConeCoordinates, mom) + LorentzVectorBase.pt2(cs, mom) + LorentzVectorBase.pz(cs, mom)^2 +end +function LorentzVectorBase.spatial_magnitude(cs::LightConeCoordinates, mom) + sqrt(LorentzVectorBase.spatial_magnitude2(cs, mom)) +end + +# Mass and energy-related +function LorentzVectorBase.mass2(cs::LightConeCoordinates, mom) + LorentzVectorBase.E(cs, mom)^2 - LorentzVectorBase.spatial_magnitude2(cs, mom) +end +function LorentzVectorBase.mass(cs::LightConeCoordinates, mom) + sqrt(LorentzVectorBase.mass2(cs, mom)) +end + +function LorentzVectorBase.boost_beta(cs::LightConeCoordinates, mom) + LorentzVectorBase.spatial_magnitude(cs, mom) / LorentzVectorBase.E(cs, mom) +end +function LorentzVectorBase.boost_gamma(cs::LightConeCoordinates, mom) + 1 / sqrt(1 - LorentzVectorBase.boost_beta(cs, mom)^2) +end + +function LorentzVectorBase.mt2(cs::LightConeCoordinates, mom) + LorentzVectorBase.E(cs, mom)^2 - LorentzVectorBase.pz(cs, mom)^2 +end +LorentzVectorBase.mt(cs::LightConeCoordinates, mom) = sqrt(LorentzVectorBase.mt2(cs, mom)) + +# Angular coordinates +function LorentzVectorBase.rapidity(cs::LightConeCoordinates, mom) + 0.5 * log( + (LorentzVectorBase.E(cs, mom) + LorentzVectorBase.pz(cs, mom)) / + (LorentzVectorBase.E(cs, mom) - LorentzVectorBase.pz(cs, mom)), + ) +end + +function LorentzVectorBase.polar_angle(cs::LightConeCoordinates, mom) + atan(LorentzVectorBase.pt(cs, mom), LorentzVectorBase.pz(cs, mom)) +end + +function LorentzVectorBase.cos_theta(cs::LightConeCoordinates, mom) + LorentzVectorBase.pz(cs, mom) / LorentzVectorBase.spatial_magnitude(cs, mom) +end + +function LorentzVectorBase.phi(cs::LightConeCoordinates, mom) + atan(LorentzVectorBase.py(cs, mom), LorentzVectorBase.px(cs, mom)) +end +function LorentzVectorBase.cos_phi(cs::LightConeCoordinates, mom) + LorentzVectorBase.px(cs, mom) / LorentzVectorBase.pt(cs, mom) +end +function LorentzVectorBase.sin_phi(cs::LightConeCoordinates, mom) + LorentzVectorBase.py(cs, mom) / LorentzVectorBase.pt(cs, mom) +end + +# ## 4. Using the coordinate system +# +# To use `LightConeCoordinates`, a Lorentz vector type needs to: +# +# 1. Implement `coordinate_system(::MyVectorType) = LightConeCoordinates()` +# 2. Implement the four getters listed in `coordinate_names`: +# `plus_component`, `minus_component`, `x`, `y` +# +# Once that’s done, **all** the functions we defined here +# will work automatically. diff --git a/src/LorentzVectorBase.jl b/src/LorentzVectorBase.jl index fac3c7d..7a8adc8 100644 --- a/src/LorentzVectorBase.jl +++ b/src/LorentzVectorBase.jl @@ -1,9 +1,11 @@ module LorentzVectorBase export coordinate_system, coordinate_names +export available_accessors include("getter.jl") include("interface.jl") +include("utility.jl") include("coordinate_systems/XYZT.jl") include("coordinate_systems/PxPyPzE.jl") include("coordinate_systems/PtEtaPhiM.jl") diff --git a/src/coordinate_systems/PtEtaPhiM.jl b/src/coordinate_systems/PtEtaPhiM.jl index a99b438..aabcfb1 100644 --- a/src/coordinate_systems/PtEtaPhiM.jl +++ b/src/coordinate_systems/PtEtaPhiM.jl @@ -1,16 +1,50 @@ """ - PtEtaPhiM <: AbstractCoordinateSystem -Cylindrical coordinate system for four-momenta. Using this requires the implementation of the following interface functions: +Represents the cylindrical coordinate system for four-momenta, commonly used in high-energy physics. +This system expresses four-momentum components in terms of transverse momentum (`pt`), pseudorapidity (`eta`), +azimuthal angle (`phi`), and mass (`mass`). + +To use this coordinate system with a custom four-momentum type, you must implement the following interface methods: ```julia -pt(::CustomFourMomentum) -eta(::CustomFourMomentum) -phi(::CustomFourMomentum) -mass(::CustomFourMomentum) +LorentzVectorBase.pt(::CustomFourMomentum) # Returns the transverse momentum +LorentzVectorBase.eta(::CustomFourMomentum) # Returns the pseudorapidity +LorentzVectorBase.phi(::CustomFourMomentum) # Returns the azimuthal angle +LorentzVectorBase.mass(::CustomFourMomentum) # Returns the mass +``` + +### Example + +The following example demonstrates how to define a custom four-momentum type and implement the required interface: + +```jldoctest +julia> struct CustomFourMomentum + pt + eta + phi + mass + end + +julia> LorentzVectorBase.coordinate_system(::CustomFourMomentum) = LorentzVectorBase.PtEtaPhiM() + +julia> LorentzVectorBase.pt(p::CustomFourMomentum) = p.pt + +julia> LorentzVectorBase.eta(p::CustomFourMomentum) = p.eta + +julia> LorentzVectorBase.phi(p::CustomFourMomentum) = p.phi + +julia> LorentzVectorBase.mass(p::CustomFourMomentum) = p.mass + +julia> p = CustomFourMomentum(10.0, 2.5, 1.57, 0.105) +CustomFourMomentum(10.0, 2.5, 1.57, 0.105) + +julia> isapprox(LorentzVectorBase.polar_angle(p), 2 * atan(exp(-2.5))) +true + ``` +By implementing these methods, the custom type `CustomFourMomentum` becomes compatible with `LorentzVectorBase` operations in the `PtEtaPhiM` coordinate system. """ struct PtEtaPhiM <: AbstractCoordinateSystem end coordinate_names(::PtEtaPhiM) = (:pt, :eta, :phi, :mass) diff --git a/src/coordinate_systems/PxPyPzE.jl b/src/coordinate_systems/PxPyPzE.jl index 32270b1..aca6e60 100644 --- a/src/coordinate_systems/PxPyPzE.jl +++ b/src/coordinate_systems/PxPyPzE.jl @@ -1,17 +1,50 @@ """ - PxPyPzE <: AbstractCoordinateSystem -Cartesian coordinate system for four-momenta. Using this requires the implementation of the following interface functions: +Represents the Cartesian coordinate system for four-momenta, where the components are labeled as (px, py, pz, E). +This system is commonly used in high-energy physics to describe the momentum and energy of particles. + +To use this coordinate system with a custom four-momentum type, you must implement the following interface methods: ```julia -LorentzVectorBase.px(::CustomFourMomentum) -LorentzVectorBase.py(::CustomFourMomentum) -LorentzVectorBase.pz(::CustomFourMomentum) -LorentzVectorBase.E(::CustomFourMomentum) +LorentzVectorBase.px(::CustomFourMomentum) # Returns the x-component of momentum +LorentzVectorBase.py(::CustomFourMomentum) # Returns the y-component of momentum +LorentzVectorBase.pz(::CustomFourMomentum) # Returns the z-component of momentum +LorentzVectorBase.E(::CustomFourMomentum) # Returns the energy component +``` + +### Example + +The following example demonstrates how to define a custom four-momentum type and implement the required interface: + +```jldoctest +julia> struct CustomFourMomentum + px + py + pz + E + end + +julia> LorentzVectorBase.coordinate_system(::CustomFourMomentum) = LorentzVectorBase.PxPyPzE() + +julia> LorentzVectorBase.px(p::CustomFourMomentum) = p.px + +julia> LorentzVectorBase.py(p::CustomFourMomentum) = p.py + +julia> LorentzVectorBase.pz(p::CustomFourMomentum) = p.pz + +julia> LorentzVectorBase.E(p::CustomFourMomentum) = p.E + +julia> p = CustomFourMomentum(1.0, 2.0, 3.0, 4.0) +CustomFourMomentum(1.0, 2.0, 3.0, 4.0) + +julia> isapprox(LorentzVectorBase.spatial_magnitude(p), sqrt(1.0^2 + 2.0^2 + 3.0^2)) +true + ``` +By implementing these methods, the custom type `CustomFourMomentum` becomes compatible with `LorentzVectorBase` operations in the `PxPyPzE` coordinate system. """ struct PxPyPzE <: AbstractCoordinateSystem end coordinate_names(::PxPyPzE) = (:px, :py, :pz, :E) @@ -59,9 +92,7 @@ const DELEGATED_GETTER_FUNCTIONS = ( ) for func in DELEGATED_GETTER_FUNCTIONS - eval( - quote - ($func)(::PxPyPzE, mom) = ($func)(XYZT(), mom) - end, - ) + eval(quote + ($func)(::PxPyPzE, mom) = ($func)(XYZT(), mom) + end) end diff --git a/src/coordinate_systems/XYZT.jl b/src/coordinate_systems/XYZT.jl index 134314d..0c66bf0 100644 --- a/src/coordinate_systems/XYZT.jl +++ b/src/coordinate_systems/XYZT.jl @@ -1,16 +1,47 @@ """ - XYZT <: AbstractCoordinateSystem -Cartesian coordinate system for four-vectors. Using this requires the implementation of the following interface functions: +Represents the Cartesian coordinate system for four-vectors, where the components are labeled as (x, y, z, t). + +To use this coordinate system with a custom four-vector type, you must implement the following interface methods: ```julia -LorentzVectorBase.x(::CustomFourVector) -LorentzVectorBase.y(::CustomFourVector) -LorentzVectorBase.z(::CustomFourVector) -LorentzVectorBase.t(::CustomFourVector) +LorentzVectorBase.x(::CustomFourVector) # Returns the x-component +LorentzVectorBase.y(::CustomFourVector) # Returns the y-component +LorentzVectorBase.z(::CustomFourVector) # Returns the z-component +LorentzVectorBase.t(::CustomFourVector) # Returns the time component +``` + +### Example + +The following example demonstrates how to define a custom four-vector type and implement the required interface: + +```jldoctest +julia> struct CustomLVector + x + y + z + t + end + +julia> LorentzVectorBase.coordinate_system(::CustomLVector) = LorentzVectorBase.XYZT() + +julia> LorentzVectorBase.x(lv::CustomLVector) = lv.x + +julia> LorentzVectorBase.y(lv::CustomLVector) = lv.y + +julia> LorentzVectorBase.z(lv::CustomLVector) = lv.z + +julia> LorentzVectorBase.t(lv::CustomLVector) = lv.t + +julia> c = CustomLVector(1, 2, 3, 4) +CustomLVector(1, 2, 3, 4) + +julia> @assert isapprox(LorentzVectorBase.spatial_magnitude(c), sqrt(1^2 + 2^2 + 3^2)) + ``` +By implementing these methods, the custom type `CustomLVector` becomes compatible with `LorentzVectorBase` operations in the `XYZT` coordinate system. """ struct XYZT <: AbstractCoordinateSystem end coordinate_names(::XYZT) = (:x, :y, :z, :t) diff --git a/src/interface.jl b/src/interface.jl index 7f421ca..f513d69 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -45,11 +45,9 @@ const FOURMOMENTUM_GETTER_FUNCTIONS = ( ) for func in FOURMOMENTUM_GETTER_FUNCTIONS - eval( - quote - ($func)(mom) = ($func)($coordinate_system(mom), mom) - end, - ) + eval(quote + ($func)(mom) = ($func)($coordinate_system(mom), mom) + end) end const FOURMOMENTUM_GETTER_ALIASSES = Dict( @@ -67,9 +65,7 @@ const FOURMOMENTUM_GETTER_ALIASSES = Dict( ) for (alias, func) in FOURMOMENTUM_GETTER_ALIASSES - eval( - quote - ($alias)(mom) = ($func)(mom) - end, - ) + eval(quote + ($alias)(mom) = ($func)(mom) + end) end diff --git a/src/utility.jl b/src/utility.jl new file mode 100644 index 0000000..520577d --- /dev/null +++ b/src/utility.jl @@ -0,0 +1,38 @@ +""" + available_accessors() + +Returns a list of available accessor functions for four-momentum components. + +This function gathers all defined accessor methods (such as px, py, pz, E, etc.) that are available +for **any** custom four-momentum type implementing the LorentzVectorBase interface. + +### Example + +```julia +julia> LorentzVectorBase.available_accessors() +38-element Vector{Symbol}: + :x + :y + :z + :t + :energy + :px + :py + ... +``` + +This allows users to query which accessor functions are available for any custom four-momentum type. +""" +function available_accessors() + res = Symbol[] + for acc in FOURMOMENTUM_GETTER_FUNCTIONS + push!(res, acc) + for (key, val) in FOURMOMENTUM_GETTER_ALIASSES + if val == acc + push!(res, key) + continue + end + end + end + return res +end diff --git a/test/interface.jl b/test/interface.jl index 776b8a4..ea81c98 100644 --- a/test/interface.jl +++ b/test/interface.jl @@ -4,11 +4,9 @@ struct TestCoordinteSystem <: LorentzVectorBase.AbstractCoordinateSystem end LorentzVectorBase.coordinate_names(::TestCoordinteSystem) = (:a, :b, :c, :d) for (fun_idx, fun) in enumerate(LorentzVectorBase.FOURMOMENTUM_GETTER_FUNCTIONS) - eval( - quote - ($LorentzVectorBase.$fun)(::$TestCoordinteSystem, mom) = $fun_idx - end, - ) + eval(quote + ($LorentzVectorBase.$fun)(::$TestCoordinteSystem, mom) = $fun_idx + end) end struct TestMomentum end @@ -18,27 +16,21 @@ LorentzVectorBase.coordinate_system(::TestMomentum) = TestCoordinteSystem() @testset "Accessor functions" begin mom = TestMomentum() @testset "$fun" for (i, fun) in enumerate(LorentzVectorBase.FOURMOMENTUM_GETTER_FUNCTIONS) - @test eval( - quote - $LorentzVectorBase.$fun - end, - )(mom) == i + @test eval(quote + $LorentzVectorBase.$fun + end)(mom) == i end end @testset "Aliasses" begin mom = TestMomentum() @testset "$alias, $fun" for (alias, fun) in LorentzVectorBase.FOURMOMENTUM_GETTER_ALIASSES - alias_result = eval( - quote - $LorentzVectorBase.$alias - end, - )(mom) - groundtruth = eval( - quote - $LorentzVectorBase.$fun - end, - )(mom) + alias_result = eval(quote + $LorentzVectorBase.$alias + end)(mom) + groundtruth = eval(quote + $LorentzVectorBase.$fun + end)(mom) @test alias_result == groundtruth end end diff --git a/test/runtests.jl b/test/runtests.jl index 8efd3d3..b42aedb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,6 +7,10 @@ begin include("interface.jl") end + @time @safetestset "utility" begin + include("utility.jl") + end + @time @safetestset "XYZT" begin include("XYZT.jl") end diff --git a/test/utility.jl b/test/utility.jl new file mode 100644 index 0000000..f4be5c0 --- /dev/null +++ b/test/utility.jl @@ -0,0 +1,16 @@ +using LorentzVectorBase + +av_accessors = available_accessors() + +groundtruth = ( + LorentzVectorBase.FOURMOMENTUM_GETTER_FUNCTIONS..., + keys(LorentzVectorBase.FOURMOMENTUM_GETTER_ALIASSES)..., +) + +@testset "available accessors" begin + @test length(av_accessors) == length(groundtruth) + + @testset "acc: $acc" for acc in groundtruth + @test any(x -> acc == x, av_accessors) + end +end