diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..700707c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5e4b23a..48d5645 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,33 +1,48 @@ name: CI + on: push: branches: - main - tags: ["*"] + - dev + - 'releases/**' + tags: '*' pull_request: + release: workflow_dispatch: + concurrency: # Skip intermediate builds: always. # Cancel intermediate builds: only if it is a pull request build. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + jobs: test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} runs-on: ${{ matrix.os }} - timeout-minutes: 60 - permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created - actions: write - contents: read + continue-on-error: ${{ matrix.version == 'nightly' }} strategy: fail-fast: false matrix: version: - - "^1.11.0-rc3" + - '1.10' + - '1' + - 'pre' os: - ubuntu-latest arch: - x64 + include: + - version: 1 + os: ubuntu-latest + arch: x86 + - version: 1 + os: macOS-latest + arch: arm64 + - version: 1 + os: windows-latest + arch: x64 steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2 @@ -37,3 +52,29 @@ jobs: - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 + with: + coverage: ${{ matrix.version == '1' && matrix.os == 'ubuntu-latest' && matrix.arch == 'x64' }} + - uses: julia-actions/julia-processcoverage@v1 + if: matrix.version == '1' && matrix.os == 'ubuntu-latest' && matrix.arch == 'x64' + - uses: codecov/codecov-action@v5 + if: matrix.version == '1' && matrix.os == 'ubuntu-latest' && matrix.arch == 'x64' + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + docs: + name: Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: '1' + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-docdeploy@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Needed due to https://github.com/JuliaDocs/Documenter.jl/issues/1177 + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} + GKSwstype: 'nul' diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 0000000..8ad0284 --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,45 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +permissions: + contents: write + pull-requests: write +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Check if Julia is already available in the PATH + id: julia_in_path + run: which julia + continue-on-error: true + - name: Install Julia, but only if it is not already available in the PATH + uses: julia-actions/setup-julia@v2 + with: + version: '1' + arch: ${{ runner.arch }} + if: steps.julia_in_path.outcome != 'success' + - name: "Add the General registry via Git" + run: | + import Pkg + ENV["JULIA_PKG_SERVER"] = "" + Pkg.Registry.add("General") + shell: julia --color=yes {0} + - name: "Install CompatHelper" + run: | + import Pkg + name = "CompatHelper" + uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" + version = "3" + Pkg.add(; name, uuid, version) + shell: julia --color=yes {0} + - name: "Run CompatHelper" + run: | + import CompatHelper + CompatHelper.main() + shell: julia --color=yes {0} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} diff --git a/.github/workflows/DocPreviewCleanup.yml b/.github/workflows/DocPreviewCleanup.yml new file mode 100644 index 0000000..5be23b9 --- /dev/null +++ b/.github/workflows/DocPreviewCleanup.yml @@ -0,0 +1,33 @@ +name: Doc Preview Cleanup + +on: + pull_request: + types: [closed] + +# Ensure that only one "Doc Preview Cleanup" workflow is force pushing at a time +concurrency: + group: doc-preview-cleanup + cancel-in-progress: false + +jobs: + doc-preview-cleanup: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v4 + with: + ref: gh-pages + - name: Delete preview and history + push changes + run: | + if [ -d "${preview_dir}" ]; then + git config user.name "Documenter.jl" + git config user.email "documenter@juliadocs.github.io" + git rm -rf "${preview_dir}" + git commit -m "delete preview" + git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree}) + git push --force origin gh-pages-new:gh-pages + fi + env: + preview_dir: previews/PR${{ github.event.number }} diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 0000000..0cd3114 --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,31 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: + inputs: + lookback: + default: "3" +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/Project.toml b/Project.toml index 241d13e..933c9df 100644 --- a/Project.toml +++ b/Project.toml @@ -5,25 +5,19 @@ version = "0.0.1-DEV" [deps] Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd" [weakdeps] -StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" [extensions] -PlottingExt = "StatsPlots" +HEPExampleProjectMakieExt = "Makie" +HEPExampleProjectPlotsExt = ["Plots", "RecipesBase"] [compat] -QuadGK = "2.11.0" -Random = "1.11.0" -SafeTestsets = "0.1.0" -StatsPlots = "0.15.7" -julia = "1.6" - -[extras] -QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" -SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[targets] -test = ["Test", "SafeTestsets", "QuadGK"] +Makie = "0.22.9" +Plots = "1" +Random = "1" +RecipesBase = "1" +julia = "1.10" diff --git a/README.md b/README.md index 3e20f31..8288edd 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,10 @@ This project is presented as an example of how you'd develop a complete project # HEPExampleProject.jl -[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaHEP.github.io/HEPExampleProject.jl/stable/) -[![Build Status](https://github.com/JuliaHEP/HEPExampleProject.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/JuliaHEP/HEPExampleProject.jl/actions/workflows/CI.yml?query=branch%3Amain) +[![Documentation for development version](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaHEP.github.io/HEPExampleProject.jl/dev) +[![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](LICENSE.md) +[![Build Status](https://github.com/JuliaHEP/HEPExampleProject.jl/workflows/CI/badge.svg)](https://github.com/JuliaHEP/HEPExampleProject.jl/actions/workflows/CI.yml) +[![Codecov](https://codecov.io/gh/JuliaHEP/HEPExampleProject.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/JuliaHEP/HEPExampleProject.jl) `HEPExampleProject.jl` is a demonstration package for showcasing how to develop software in the context of high-energy physics (HEP) using the Julia programming language. This project @@ -61,16 +63,24 @@ incoming_electron_energy = 1000.0 event_list = generate_events_cpu(RNG,incoming_electron_energy,1_000_000) ``` -If you separately install the package `StatsPlots` (`pkg> add StatsPlots`), you can plot +If you separately install the package `Plots` (`pkg> add Plots`), you can plot the generated events: ```Julia -using StatsPlots -plot_muon_cos_theta(event_list) +using Plots +plot(event_list) ``` ![event_plot](docs/assets/event_plot.png) +You can also plot event lists using via the Makie plotting system, by using +one of the Makie plotting backends (CairoMakie, WGLMakie, etc.): + +```Julia +using CairoMakie +plot(event_list) +``` + ## Contributing We welcome contributions to improve this project! If you're interested in contributing, please: diff --git a/ext/HEPExampleProjectMakieExt.jl b/ext/HEPExampleProjectMakieExt.jl new file mode 100644 index 0000000..f2aee63 --- /dev/null +++ b/ext/HEPExampleProjectMakieExt.jl @@ -0,0 +1,42 @@ +module HEPExampleProjectMakieExt + +using HEPExampleProject +using Makie +using Makie: SpecApi as SpecApi + + +function Makie.convert_arguments(::Type{<:AbstractPlot}, event_list::AbstractVector{<:Event}) + plots = PlotSpec[] + + muon_cths = muon_cos_theta.(event_list) + push!( + plots, + SpecApi.Hist( + muon_cths, + label = "events", + bins = 100, + normalization = :pdf, + ) + ) + + + E_in = first(event_list).electron_momentum.en + tot_weight = total_cross_section(E_in) + + push!( + plots, + SpecApi.Lines( + range(-1,1; length=100), + x -> differential_cross_section(E_in,x)/tot_weight*2*pi, + label = "norm. diff. CS", + linestyle = :dash, + color = :black, + linewidth = 2, + alpha = 0.5 + ) + ) + + return plots +end + +end # module HEPExampleProjectMakieExt diff --git a/ext/HEPExampleProjectPlotsExt.jl b/ext/HEPExampleProjectPlotsExt.jl new file mode 100644 index 0000000..3061de0 --- /dev/null +++ b/ext/HEPExampleProjectPlotsExt.jl @@ -0,0 +1,39 @@ +module HEPExampleProjectPlotsExt + +using HEPExampleProject +using Plots, RecipesBase + +@recipe function f(event_list::AbstractVector{<:Event}) + muon_cths = muon_cos_theta.(event_list) + + @series begin + seriestype --> :histogram + + label --> "events" + xlabel --> "cos theta" + ylabel := "normalized event count" + nbins --> 100 + normalize := :pdf + opacity --> 0.5 + + (muon_cths,) + end + + E_in = first(event_list).electron_momentum.en + tot_weight = total_cross_section(E_in) + + @series begin + seriestype := :line + label := "norm. diff. CS" + line := (2, :black, :dash) + alpha := 0.5 + + ( + range(-1,1; length=100), + x -> differential_cross_section(E_in,x)/tot_weight*2*pi + ) + end + +end + +end # module HEPExampleProjectPlotsExt diff --git a/ext/PlottingExt.jl b/ext/PlottingExt.jl deleted file mode 100644 index 49aed76..0000000 --- a/ext/PlottingExt.jl +++ /dev/null @@ -1,30 +0,0 @@ -module PlottingExt - -using HEPExampleProject -using StatsPlots - - -function HEPExampleProject.plot_muon_cos_theta(event_list; kwargs...) - E_in = event_list[1].electron_momentum.en - muon_cths = muon_cos_theta.(event_list) - P = histogram(muon_cths; - label="events", - xlabel="cos theta", - ylabel="normalized event count", - nbins=100, - normalize=:pdf, - opacity=0.5, - kwargs... - ) - - tot_weight = total_cross_section(E_in) - plot!(P, range(-1,1; length=100), x -> differential_cross_section(E_in,x)/tot_weight*2*pi; - label="norm. diff. CS", - line=(2, :black, :dash), - alpha=0.5 - ) - - return P -end - -end diff --git a/juliac/.gitignore b/juliac/.gitignore new file mode 100644 index 0000000..103cba1 --- /dev/null +++ b/juliac/.gitignore @@ -0,0 +1,3 @@ +Manifest.toml + +hepexample diff --git a/juliac/Project.toml b/juliac/Project.toml new file mode 100644 index 0000000..a2ad99e --- /dev/null +++ b/juliac/Project.toml @@ -0,0 +1,6 @@ +[deps] +HEPExampleProject = "f779019a-d216-4ac1-8c1a-8c09799cc4e6" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[sources] +HEPExampleProject = {path = ".."} diff --git a/juliac/hepexample.jl b/juliac/hepexample.jl new file mode 100644 index 0000000..3adc48d --- /dev/null +++ b/juliac/hepexample.jl @@ -0,0 +1,41 @@ +module RunHEPExample + +using HEPExampleProject +using Random + + +function print_values(output::IO, p::FourMomentum)::Nothing + (;en, x, y, z) = p + print(output, en, ", ", x, ", ", y, ", ", z) +end + + +function print_values(output::IO, evt::Event)::Nothing + (;electron_momentum, positron_momentum, muon_momentum, anti_muon_momentum, weight) = evt + print_values(output, electron_momentum); print(output, ", ") + print_values(output, positron_momentum); print(output, ", ") + print_values(output, muon_momentum); print(output, ", ") + print_values(output, anti_muon_momentum); print(output, ", ") + print(output, weight) +end + + +Base.@ccallable function main()::Cint + n_events = 20 + + rng = Xoshiro(137) + incoming_electron_energy = 1000.0 + event_list = generate_events_cpu(rng,incoming_electron_energy, n_events) + + for evt in event_list + print_values(Core.stdout, evt) + println(Core.stdout) + end + + return zero(Cint) +end + +end # module RunHEPExample + +# # In Julia, test with +# RunHEPExample.main() diff --git a/juliac/make.sh b/juliac/make.sh new file mode 100755 index 0000000..e6d3022 --- /dev/null +++ b/juliac/make.sh @@ -0,0 +1,15 @@ +#/bin/bash + +## Must be run from same directory: +# ./make.sh && ./hepexample + +set -e + +# Binaries generated by juliac may segfault if JULIA_CPU_TARGET is set: +unset JULIA_CPU_TARGET + +jlbindir=`julia-1.12 -e 'println(dirname(Sys.BINDIR))'` + +julia-1.12 --project=. -e 'import Pkg; Pkg.instantiate()' + +julia-1.12 --project=. "$jlbindir/share/julia/juliac.jl" --experimental --trim=unsafe-warn --output-exe hepexample hepexample.jl diff --git a/src/HEPExampleProject.jl b/src/HEPExampleProject.jl index 3acc301..2e13f36 100644 --- a/src/HEPExampleProject.jl +++ b/src/HEPExampleProject.jl @@ -14,7 +14,6 @@ export muon_cos_theta, muon_rapidity export generate_flat_events_cpu, generate_events_cpu, max_weight -export plot_muon_cos_theta # Write your package code here. include("constants.jl") @@ -23,7 +22,6 @@ include("cross_section.jl") include("events.jl") include("event_generation/serial.jl") include("event_generation/threadsafe.jl") -include("plotting.jl") include("utils.jl") end diff --git a/src/plotting.jl b/src/plotting.jl deleted file mode 100644 index 8323d90..0000000 --- a/src/plotting.jl +++ /dev/null @@ -1 +0,0 @@ -function plot_muon_cos_theta end diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 0000000..a9b3e65 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,9 @@ +[deps] +QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[compat] +QuadGK = "2.11.0" +SafeTestsets = "0.1.0"