Contributing to Newtrinos.jl
Thank you for your interest in contributing! Newtrinos.jl is a open-source neutrino physics analysis framework, and contributions of all sizes are welcome — whether you are a physicist adding a new physics model, an experimentalist integrating a new dataset, a software engineer improving performance, or a writer fixing a typo in the documentation. We aim to make the codebase a welcoming place for researchers and developers alike.
Ways to Contribute
Core Physics (src/physics/)
Theoretical models that produce predictions with no knowledge of any specific experiment. Examples of valuable contributions:
- New oscillation models (new
FlavourModel,InteractionModel, orPropagationModelsubtypes) - Updated oscillation parameters or priors from global fits
- New cross-section models (subtype
Newtrinos.Physics, follow thexsec.jlpattern) - Atmospheric or beam flux models beyond HKKM
Experimental Likelihoods (src/experiments/)
Data analysis modules that wrap real experimental results. Each experiment lives in its own subdirectory. Examples of valuable contributions:
- New experiments (DUNE, NOvA, T2K, SNO+, Borexino, …)
- Updated datasets for existing experiments (new run periods, improved systematics)
- Bug fixes or improvements to existing forward models
Analysis & Statistics (src/analysis/)
Inference tooling that treats experiments as black boxes. Useful directions:
- Improved minimisers or MLE solvers for
find_mle - New profile / scan strategies
- Bayesian samplers or importance-sampling improvements (
molewhacker.jl) - Distributed / parallel analysis utilities
Documentation & Examples
Good documentation is as valuable as good code:
- Fixing typos or unclear explanations anywhere under
docs/src/ - Writing new tutorials or Jupyter / Pluto notebooks
- Adding docstrings to exported functions that currently lack them
- Improving the API reference pages under
docs/src/api/
Architectural & Design Rules
Newtrinos.jl is built on strict decoupling between physics theory, experimental likelihoods, and statistical analysis. Please respect these rules when contributing.
Abstract Type Interfaces
Every new physics module must subtype Newtrinos.Physics and expose:
@kwdef struct MyModel <: Newtrinos.Physics
params::NamedTuple # nominal parameter values
priors::NamedTuple # Distributions.jl distributions, one per parameter
# ... callable closure fields
endEvery new experiment module must subtype Newtrinos.Experiment and expose all six fields:
@kwdef struct MyExperiment <: Newtrinos.Experiment
physics::NamedTuple # physics modules consumed by this experiment
params::NamedTuple # experiment-specific nuisance parameter values
priors::NamedTuple # priors for experiment-specific nuisance parameters
assets::NamedTuple # preloaded data, binning, MC — computed once at configure time
forward_model::Function # params -> Distribution (a Distributions.jl object)
plot::Function # (params[, data]) -> Figure
endThe canonical entry point is always configure(physics=default_physics()) -> MyExperiment. Always define default_physics() so the experiment works standalone.
Experimental Independence
Experimental modules must treat the physics layer as an interchangeable black box. An experiment module must not:
- Import or depend on a specific physics struct by name (e.g., do not reference
Oscdirectly) - Hard-code oscillation parameter values or mass orderings
- Bypass
params/priorsby using module-level global constants for fit parameters
The physics object is passed in through configure(physics) and accessed only via its documented callable closures (physics.osc.osc_prob(...), etc.).
Parameter Naming
All parameters flow as NamedTuples throughout the framework. get_params / get_priors merge across all physics and experiment modules using safe_merge, which errors on key conflicts. To avoid clashes:
- Prefix experiment-specific nuisance parameters with the experiment name (e.g.,
deepcore_ice_absorption, not justice_absorption) - Use
@resetfromAccessors.jlto modify individual NamedTuple fields non-destructively
Performance & Type Stability
The oscillation forward model runs inside the inner loop of gradient-based optimisation. ForwardDiff propagates Dual numbers through the entire call chain; any accidental cast to Float64 silently breaks gradients.
Rules for performance-critical code:
- Use
zero(T),one(T),promote_typeinstead of0.0,1.0literals - Never call
convert(Float64, x)orFloat64(x)on values that flow throughforward_model - Check type stability with
@code_warntypeorTest.@inferredbefore opening a PR - Prefer
SMatrix/SVectorfor small fixed-size linear algebra (used throughoutosc.jl) - Run the benchmarks after any change to the oscillation or likelihood hot path:
julia --project benchmark/bench_osc.jl
julia --project benchmark/bench_likelihood.jl --experiments deepcore dayabayStep-by-Step Contribution Workflow
1. Discuss First (for Non-Trivial Changes)
Before writing significant code, open a GitHub issue to describe what you want to add or change. This avoids duplicate effort, surfaces design concerns early, and lets maintainers give feedback on the proposed interface before you invest time implementing it.
For small fixes (typos, docstrings, one-line bug fixes) you can skip this step and go straight to a pull request.
2. Fork & Clone
git clone https://github.com/Newtrinos-org/Newtrinos.jl.git
cd Newtrinos.jl3. Set Up the Environment
Activate the local package environment and install all dependencies:
using Pkg
Pkg.activate(".") # or Pkg.activate("/path/to/Newtrinos.jl")
Pkg.instantiate()Or equivalently from the shell:
julia --project -e 'using Pkg; Pkg.instantiate()'4. Create a Branch
Use a descriptive branch name that summarises the change:
git checkout -b feat/add-nova-experiment
git checkout -b fix/dayabay-covariance-sign
git checkout -b docs/improve-osc-tutorial5. Implement Your Changes
Follow the architectural rules above. A few practical tips:
- Copy the structure of the nearest existing module rather than starting from scratch (e.g., copy
src/experiments/daya_bay/as a template for a new reactor experiment) - Keep physics and experiment logic separated — if you find yourself duplicating physics code inside an experiment module, it belongs in
src/physics/instead - Add docstrings to every exported function, including physical units explicitly (see the Style section below)
6. Style & Formatting
This project uses JuliaFormatter.jl. Before committing, format your code:
using JuliaFormatter
format("src/")
format("test/")Docstring conventions:
- Every exported function and type must have a docstring
- State physical units explicitly (e.g.,
energyin GeV,baselinein km,coszendimensionless) - For physics functions, include a brief statement of the model or equation implemented
Example:
"""
osc_prob(E, paths, layers, params; anti=false) -> Array{T,4}
Compute oscillation probabilities P(να → νβ) for neutrino energies `E` (GeV) and
Earth paths defined by `paths` and `layers` (output of `compute_paths`).
Returns an array of shape `(n_E, n_L, n_flav, n_flav)` where index `[i, j, β, α]`
gives P(να → νβ) at energy `E[i]` along path `j`. Set `anti=true` for antineutrinos.
"""7. Write Tests
- Add tests for any new physics function in the appropriate file under
test/ - For new experiments, add at minimum a smoke test that calls
configure()and evaluatesforward_modelat the default parameters - If your change intentionally alters any likelihood value, update
test/test_regression.jlwith the new reference values and explain the change in your PR description
Run the full test suite locally before opening a PR:
julia --project -e 'using Pkg; Pkg.test()'8. Push & Open a Pull Request
git push origin feat/add-nova-experimentThen open a pull request on GitHub. The PR description should include:
- A summary of what the change does and why
- For physics changes: a reference to the paper or derivation, or a brief mathematical summary, to help maintainers cross-verify the implementation (see the Citation Policy below)
- For experiment updates: the data release citation and any changed likelihood values
CI will automatically run the test suite on Julia stable. All tests must pass before a PR can be merged.
Scientific Data & Citation Policy
Neutrino physics depends on carefully curated experimental data. If your contribution adds or updates experimental assets (observed spectra, Monte Carlo samples, response matrices, efficiency tables):
Cite the official collaboration publication in the PR description. Preferred format: DOI or arXiv identifier (e.g.,
arXiv:2311.XXXXXorhttps://doi.org/10.1103/PhysRevLett.XXX).State the data release version or run period (e.g., "Daya Bay 3158-day dataset, PRD 95 (2017) 072006").
Include the data licence if the collaboration has released data under a specific open-data licence. If the data is proprietary or requires collaboration membership to access, note this clearly so users know the experiment will not be reproducible from the public repository alone.
Physics cross-verification: For new forward models or new theoretical predictions, include either a reference to a published validation or a brief derivation in the PR description so maintainers can verify the physics before merging.
Code of Conduct
This project follows the Julia Community Standards. All contributors are expected to treat each other with respect, regardless of background, experience level, or institutional affiliation. Please be constructive in code review and welcoming to newcomers. If you experience or witness behaviour that violates these standards, you can report it via the channels described on the Julia community page.
Questions?
If you are unsure about anything — the right place to put a new file, whether an interface change is in scope, how to handle a tricky physics edge case — please open an issue or start a discussion on GitHub. We would rather answer a question early than review a large PR that goes in the wrong direction.
Happy contributing!