Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8d0ccfa
fix: correct cbrt on Double64 (DoubleFloats v1.9 returns a tuple)
lucifer1004 Jun 4, 2026
0600304
test: add performance benchmark suite
lucifer1004 Jun 4, 2026
9c7b374
ci: add PR benchmark workflow via AirspeedVelocity
lucifer1004 Jun 4, 2026
0b340a5
perf(ebcm): factor 𝐐 once instead of explicit inv, reuse across Jacob…
lucifer1004 Jun 4, 2026
6526a92
perf(iitm): solve M \ X instead of forming inv(M) in the radial recur…
lucifer1004 Jun 4, 2026
8f48a60
feat(ebcm): numerically stable F⁺ core for spheroid integrands (S1)
lucifer1004 Jun 4, 2026
51e14ef
fix: handle nₘₐₓ=1 in ricattibessely
lucifer1004 Jun 4, 2026
cd5cdca
feat(ebcm): cancellation-free modified Bessel products for stabilized…
lucifer1004 Jun 4, 2026
e17fd0e
feat(ebcm): all-x stable F⁺ matrix via series-seeded Eq.51 recursion …
lucifer1004 Jun 4, 2026
7f88cc9
feat(ebcm): wire stabilized spheroid path into transition_matrix + m>…
lucifer1004 Jun 4, 2026
2fb69bf
perf(ebcm)+docs: precompute F⁺ last-row coeffs, benchmark, document s…
lucifer1004 Jun 4, 2026
a84ab97
chore: format code
lucifer1004 Jun 4, 2026
85427e9
feat(ebcm): Sh-matrix moment separation — m=0 P and U reconstruction
lucifer1004 Jun 4, 2026
10776a4
feat(ebcm): Sh-matrix m>0 K and L block reconstruction
lucifer1004 Jun 4, 2026
2c71973
feat(ebcm): Sh-matrix two-step API — prepare_sh + transition_matrix +…
lucifer1004 Jun 4, 2026
6a90cf5
test+bench+docs(ebcm): Sh-matrix moment separation
lucifer1004 Jun 4, 2026
cea3eb8
perf(ebcm): Sh-matrix — precompute coefficients + folded convolution
lucifer1004 Jun 4, 2026
fabed66
perf(ebcm): Sh-matrix — hoist geometry, drop Mdτ, hoist angular products
lucifer1004 Jun 4, 2026
7574270
perf(ebcm): Sh-matrix — precision-split the moment precompute
lucifer1004 Jun 4, 2026
3279a8d
perf(ebcm): Sh-matrix — analytic-zeroing fast precompute for spheroids
lucifer1004 Jun 4, 2026
1911617
feat(api)!: two-layer solver API — fixed solvers + Iterative wrapper
lucifer1004 Jun 5, 2026
71a9d23
test(ebcm): Sh-matrix reconstructs non-spheroid shapes
lucifer1004 Jun 5, 2026
49d4dc1
docs(examples): Pluto notebook — spectral sensitivity via Sh-matrix
lucifer1004 Jun 5, 2026
5883e18
docs(examples): add four showcase Pluto notebooks
lucifer1004 Jun 5, 2026
93b98fa
docs: fix build and filter internal docstrings from the API reference
lucifer1004 Jun 5, 2026
b8a7dc1
docs: render example notebooks into the site (PlutoStaticHTML)
lucifer1004 Jun 5, 2026
0097c64
fix(ebcm): relax prepare_sh Ng-even check; validate spectrum lengths
lucifer1004 Jun 5, 2026
feb9c98
docs: backfill CHANGELOG with per-tag history; current work as Unrele…
lucifer1004 Jun 5, 2026
de80920
fix(docs): pre-render example notebooks; CI builds from committed pages
lucifer1004 Jun 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/Benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Benchmark

# Runs the benchmark suite (benchmark/benchmarks.jl, the global `SUITE`) on each
# relevant pull request and writes a comparison table to the job summary.
#
# How the comparison works (AirspeedVelocity.jl):
# * It benchmarks TWO git revisions — the base branch (`main`) and this PR's
# head commit — NOT a stored historical baseline.
# * Both revisions are checked out into separate temp environments and run in
# separate Julia processes, back-to-back on the SAME runner within this one
# job. The reported `ratio` is head/base (<1 faster, >1 slower).
# * Running both on the same machine cancels systematic hardware differences,
# but shared GitHub runners are still noisy (~±10–30% on microbenchmarks):
# trust this for order-of-magnitude / large regressions, not 5% wobble.
#
# Security: this uses the `pull_request` event (not `pull_request_target`), so a
# fork's PR code runs with a read-only token and no secrets. Output goes to the
# Actions job summary (`job-summary: "true"`) instead of a PR comment, so no
# write token is ever needed — nothing is posted to fork (or same-repo) PRs.
# View results under the workflow run's "Summary" tab.

on:
pull_request:
branches:
- main
paths:
- "src/**"
- "benchmark/**"
- "Project.toml"
- ".github/workflows/Benchmark.yml"
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
benchmark:
name: Benchmark PR vs main
runs-on: ubuntu-latest
steps:
- uses: MilesCranmer/AirspeedVelocity.jl@action-v1
with:
julia-version: "1"
job-summary: "true"
Comment on lines +45 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the commit SHA behind the action-v1 tag
gh api repos/MilesCranmer/AirspeedVelocity.jl/git/ref/tags/action-v1 --jq '.object.sha' 2>/dev/null || \
gh api repos/MilesCranmer/AirspeedVelocity.jl/commits/action-v1 --jq '.sha'

Repository: JuliaRemoteSensing/TransitionMatrices.jl

Length of output: 125


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Benchmark.yml uses lines around 40-60 =="
nl -ba .github/workflows/Benchmark.yml | sed -n '35,70p'

echo
echo "== Any other unpinned uses: lines (tags, no sha) in the workflow =="
rg -n '^\s*uses:\s*.*@[^#\s]+' .github/workflows/Benchmark.yml || true

echo
echo "== Specifically check for action-v1 in the workflow =="
rg -n 'action-v1' .github/workflows/Benchmark.yml || true

Repository: JuliaRemoteSensing/TransitionMatrices.jl

Length of output: 189


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Benchmark.yml uses lines around 40-60 =="
cat -n .github/workflows/Benchmark.yml | sed -n '35,70p'

echo
echo "== Any other unpinned uses: lines (tags, no sha) in the workflow =="
rg -n '^\s*uses:\s*.*@[^#\s]+' .github/workflows/Benchmark.yml || true

echo
echo "== Specifically check for action-v1 in the workflow =="
rg -n 'action-v1' .github/workflows/Benchmark.yml || true

Repository: JuliaRemoteSensing/TransitionMatrices.jl

Length of output: 929


Pin the AirspeedVelocity action to a commit SHA

MilesCranmer/AirspeedVelocity.jl@action-v1 is a mutable tag; pin it to commit 315c11b51ceee8ebd6063d70cff6ae499a040d28 to prevent supply-chain mutation.

Replace:

- uses: MilesCranmer/AirspeedVelocity.jl@action-v1

With:

- uses: MilesCranmer/AirspeedVelocity.jl@315c11b51ceee8ebd6063d70cff6ae499a040d28 # action-v1
🧰 Tools
🪛 zizmor (1.25.2)

[error] 45-45: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/Benchmark.yml around lines 45 - 48, Replace the mutable
tag usage of the GitHub Action MilesCranmer/AirspeedVelocity.jl@action-v1 with
the specific commit SHA 315c11b51ceee8ebd6063d70cff6ae499a040d28 to pin the
action; locate the line containing "uses:
MilesCranmer/AirspeedVelocity.jl@action-v1" in the Benchmark.yml workflow and
update it to reference the commit SHA instead of the tag (keeping a comment like
"# action-v1" optional for context).

# Default compares `main` against this PR's head SHA. Uncomment to
# restrict to a fast, low-noise subset of the suite in CI:
# filter: "special_functions,ebcm,linearization,postprocessing"
14 changes: 14 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ It is split into:
The local `just ci` command mirrors the core local checks: package tests,
doctests, and documentation build.

## Benchmarks

`Benchmark.yml` runs the benchmark suite (`benchmark/benchmarks.jl`, the global
`SUITE`) via [AirspeedVelocity.jl](https://github.com/MilesCranmer/AirspeedVelocity.jl)
on pull requests that touch `src/`, `benchmark/`, or `Project.toml`. It
benchmarks the PR head commit against `main` — both revisions are run
back-to-back on the same runner (not against a stored baseline) — and writes a
`ratio` comparison table to the workflow run's **Summary** tab. It uses the
`pull_request` event with a read-only token and `job-summary: "true"`, so fork
PR code never runs with a write token and nothing is posted as a comment.
Shared CI runners are noisy, so treat the results as a guard for large
regressions rather than sub-10% changes. Run the same suite locally with
`just bench`.

## Maintenance

- `CompatHelper.yml` opens compatibility-bound update pull requests.
Expand Down
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@
Manifest.toml
.CondaPkg/

# Benchmark result artifacts (benchmark/params.json is committed; raw run
# outputs are machine-specific and stay local)
/results*.json
/benchmark/results*.json

# Documentation artifacts
/docs/build/

# Intermediate Pluto render output (the committed copies live in
# docs/src/examples/, which is tracked).
/examples/*.md
!/examples/README.md

# Editor and OS metadata
.DS_Store
.idea/
Expand Down
84 changes: 84 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Changelog

All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

Work on top of `v0.4.0`. Includes a breaking change to solver selection, so the
next release is expected to be `v0.5.0`.

### Added

- **Sh-matrix moment-separation method** for fast parameter sweeps:
`prepare_sh`, `transition_matrix(prep, λ, mᵣ)`, and `transition_matrix_spectrum`.
The geometry quadrature is computed once and reused, so wavelength /
refractive-index sweeps are cheap; for spheroids it also reproduces the
high-aspect stabilization analytically. Works for any axisymmetric shape.
- **Two-layer solver API**: fixed-discretization solvers `EBCM`, `IITM`,
`ShMatrix`, and the `Iterative` convergence wrapper (with `ConvergencePolicy`),
selected via `transition_matrix(s, λ, solver)`.
- **Stabilized high-aspect-ratio spheroids** (`stable = true`): the
cancellation-free `F⁺` formulation of Somerville, Auguié & Le Ru (2013),
available as `EBCM(nₘₐₓ, Ng; stable = true)` and, auto-converged, as
`Iterative(EBCM; stable = true)`.
- **Performance benchmark suite** and a PR benchmark CI workflow
(AirspeedVelocity).
- **`examples/` folder** with five runnable Pluto notebooks — shapes gallery,
angular scattering & polarization, orientation averaging, solver landscape, and
spectral sensitivity — rendered into the documentation site.

### Changed

- **(Breaking)** Solver selection moves to solver objects passed to
`transition_matrix(s, λ, solver)`. The keyword-based auto-converge
`transition_matrix(s, λ; threshold, …)` is replaced by
`Iterative(EBCM; threshold, …)`. The bare `transition_matrix(s, λ)` /
`calc_T(s, λ)` is unchanged (auto-converged classic EBCM).
- Performance: factor `𝐐` once and reuse it across Jacobian slices instead of an
explicit `inv`; the IITM radial recursion solves `M \ X` instead of forming
`inv(M)`; the Sh-matrix precompute is optimized (coefficient precompute, folded
convolution, precision split, and an analytic-zeroing fast path for spheroids).
- Documentation: the API reference now filters internal (underscore-prefixed)
docstrings and uses `checkdocs = :exported`.

### Fixed

- `prepare_sh` requires an even `Ng` only for shapes with an equatorial symmetry
plane (non-symmetric shapes accept odd `Ng`).
- `transition_matrix_spectrum` validates that `λs` and `mᵣs` have matching lengths
instead of silently truncating to the shorter one.
- `ricattibessely` handles `nₘₐₓ = 1`.
- `cbrt` on `Double64` (DoubleFloats v1.9 returns a tuple).

## [0.4.0] - 2026-06-04

- Performance optimizations and the analytical linearization framework
(`LinearizationProblem`, `linearize_transition_matrix`, with Mie / EBCM / IITM
backends). See [#7](https://github.com/JuliaRemoteSensing/TransitionMatrices.jl/pull/7).

## [0.3.1] - 2026-01-18

- Raise the `Wigxjpf` compatibility bound to v0.2; CI configuration updates.

## [0.3.0] - 2025-04-03

- Compatible with Julia 1.13.
- Removed `ArbNumerics` from the dependencies; its types are no longer
re-exported (use `Arblib`'s `Arb`/`Acb`).

## [0.2.0] - 2023-04-02

- Early development release.

## [0.1.0] - 2023-03-17

- Initial release.

[Unreleased]: https://github.com/JuliaRemoteSensing/TransitionMatrices.jl/compare/v0.4.0...HEAD
[0.4.0]: https://github.com/JuliaRemoteSensing/TransitionMatrices.jl/compare/v0.3.1...v0.4.0
[0.3.1]: https://github.com/JuliaRemoteSensing/TransitionMatrices.jl/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/JuliaRemoteSensing/TransitionMatrices.jl/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/JuliaRemoteSensing/TransitionMatrices.jl/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/JuliaRemoteSensing/TransitionMatrices.jl/releases/tag/v0.1.0
7 changes: 7 additions & 0 deletions benchmark/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[deps]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
TransitionMatrices = "057c4241-e127-4181-840e-6b4b92e6eef5"

[compat]
BenchmarkTools = "1"
julia = "1.10"
70 changes: 70 additions & 0 deletions benchmark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Benchmarks

Performance benchmark suite for `TransitionMatrices.jl`, built on
[BenchmarkTools.jl](https://github.com/JuliaCI/BenchmarkTools.jl). The suite
follows the standard Julia convention — `benchmark/benchmarks.jl` defines a
single global `const SUITE::BenchmarkGroup` — so it works unchanged with both
[PkgBenchmark.jl](https://github.com/JuliaCI/PkgBenchmark.jl) and
[AirspeedVelocity.jl](https://github.com/MilesCranmer/AirspeedVelocity.jl).

## Layout

| File | Purpose |
| --- | --- |
| `benchmarks.jl` | Defines `SUITE` (consumed by every tool below). |
| `runbenchmarks.jl` | Local runner: tune once, cache params, save results JSON. |
| `Project.toml` | Isolated environment; dev-depends on the parent package. |
| `params.json` | Cached `tune!` parameters (created on first run). |

## Groups

- `special_functions` — Riccati-Bessel and Wigner-d recursions (inner kernels).
- `ebcm` — EBCM blocks, fixed-order solves, and end-to-end auto-convergence.
- `iitm` — Invariant Imbedding T-Matrix (axisymmetric and N-fold).
- `postprocessing` — far-field observables from a precomputed T-matrix.
- `linearization` — analytical EBCM Jacobian (baseline for the `inv` → `lu` work).
- `precision` — Float64 vs Double64 on the same EBCM block.

## Running

The simplest path uses the parent `justfile`:

```sh
just bench # instantiate the env and run the whole suite
```

Or directly:

```sh
julia --project=benchmark -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate()'
julia --project=benchmark benchmark/runbenchmarks.jl results-baseline.json
```

Run a single group while iterating:

```julia
julia --project=benchmark
julia> include("benchmark/benchmarks.jl");
julia> run(SUITE["ebcm"]; verbose = true)
```

## Comparing two revisions

Capture a baseline, make your change, capture again, then judge:

```julia
using BenchmarkTools
old = BenchmarkTools.load("results-baseline.json")[1]
new = BenchmarkTools.load("results.json")[1]
judge(minimum(new), minimum(old)) # :improvement / :regression / :invariant
```

`minimum` is used because it is the most noise-robust statistic for these
deterministic numerical kernels.

For automated cross-revision runs (and PR comments), AirspeedVelocity.jl reads
the same `SUITE`:

```sh
benchpkg TransitionMatrices --rev=main,HEAD --bench-on=HEAD
```
Loading
Loading