phepex 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {phepex-0.2.0 → phepex-0.2.2}/.gitignore +2 -0
- {phepex-0.2.0 → phepex-0.2.2}/CHANGELOG.md +28 -1
- {phepex-0.2.0 → phepex-0.2.2}/CMakeLists.txt +1 -0
- {phepex-0.2.0 → phepex-0.2.2}/PKG-INFO +10 -8
- {phepex-0.2.0 → phepex-0.2.2}/README.md +26 -21
- {phepex-0.2.0 → phepex-0.2.2}/README.pypi.md +9 -7
- {phepex-0.2.0 → phepex-0.2.2}/benchmarks/README.md +22 -3
- phepex-0.2.2/docs/_static/slides-viewer.css +113 -0
- phepex-0.2.2/docs/_static/slides-viewer.js +103 -0
- phepex-0.2.2/docs/conf.py +265 -0
- {phepex-0.2.0 → phepex-0.2.2}/docs/cpp/index.md +5 -9
- {phepex-0.2.0 → phepex-0.2.2}/docs/getting-started.md +0 -2
- {phepex-0.2.0 → phepex-0.2.2}/docs/index.md +5 -7
- {phepex-0.2.0 → phepex-0.2.2}/docs/python/index.md +2 -9
- {phepex-0.2.0 → phepex-0.2.2}/docs/requirements.txt +3 -0
- phepex-0.2.2/docs/slides/README.md +56 -0
- phepex-0.2.2/docs/slides/phepex-intro.typ +430 -0
- phepex-0.2.2/docs/slides/phepex-logo-light.svg +30 -0
- phepex-0.2.2/docs/slides.md +37 -0
- {phepex-0.2.0 → phepex-0.2.2}/include/phepex/clip.hpp +7 -3
- {phepex-0.2.0 → phepex-0.2.2}/include/phepex/extract.hpp +13 -6
- {phepex-0.2.0 → phepex-0.2.2}/include/phepex/generate.hpp +23 -17
- {phepex-0.2.0 → phepex-0.2.2}/include/phepex/neighbor.hpp +13 -5
- {phepex-0.2.0 → phepex-0.2.2}/include/phepex/phepex.hpp +5 -5
- {phepex-0.2.0 → phepex-0.2.2}/include/phepex/preprocess.hpp +38 -44
- {phepex-0.2.0 → phepex-0.2.2}/python/phepex/__init__.py +4 -3
- {phepex-0.2.0 → phepex-0.2.2}/python/phepex/extractor.py +17 -7
- {phepex-0.2.0 → phepex-0.2.2}/python/phepex/kernels.py +38 -34
- {phepex-0.2.0 → phepex-0.2.2}/src/bindings.cpp +27 -21
- {phepex-0.2.0 → phepex-0.2.2}/src/preprocess.cpp +25 -15
- {phepex-0.2.0 → phepex-0.2.2}/tests/cpp/test_phepex.cpp +37 -23
- phepex-0.2.2/tests/cpp/test_smoothing_precision.cpp +535 -0
- {phepex-0.2.0 → phepex-0.2.2}/tests/test_extractor_equivalence.py +8 -4
- {phepex-0.2.0 → phepex-0.2.2}/tests/test_kernel_guards.py +2 -3
- {phepex-0.2.0 → phepex-0.2.2}/tests/test_preprocess.py +2 -2
- phepex-0.2.0/docs/conf.py +0 -149
- {phepex-0.2.0 → phepex-0.2.2}/LICENSE +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/benchmarks/benchmark-fast-extractor.py +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/benchmarks/cpp/microbench.cpp +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/benchmarks/flashcam-config.txt +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/benchmarks/generate_events.py +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/cmake/GitVersion.cmake +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/cmake/phepexConfig.cmake.in +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/docs/.nojekyll +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/docs/Doxyfile.in +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/docs/_static/phepex-icon-dark.svg +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/docs/_static/phepex-icon-light.svg +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/docs/_static/version-switcher.css +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/docs/_static/version-switcher.js +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/docs/_templates/sidebar/version-switcher.html +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/docs/changelog.md +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/docs/gen_switcher.py +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/examples/example.cpp +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/include/phepex/version.hpp.in +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/phepex.pc.in +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/pyproject.toml +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/scripts/export-camera-config.py +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/src/clip.cpp +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/src/extract.cpp +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/src/generate.cpp +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/src/internal.hpp +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/src/neighbor.cpp +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/tests/test_extract_centroid.py +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/tests/test_generator.py +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/tests/test_neighbor.py +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/third_party/catch2/catch_amalgamated.cpp +0 -0
- {phepex-0.2.0 → phepex-0.2.2}/third_party/catch2/catch_amalgamated.hpp +0 -0
|
@@ -9,6 +9,32 @@ numbers are derived from git tags (`vX.Y.Z`) by setuptools-scm (Python) and GitV
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
### Changed
|
|
13
|
+
- The Deriche smoothing pass (`smooth_waveform` and its batched counterpart
|
|
14
|
+
`smooth_batched`) now evaluates its recursion entirely in float32. The coefficients are
|
|
15
|
+
narrowed to float once on entry, so the products in the forward and backward loops no
|
|
16
|
+
longer promote to double, and the backward pass carries float state. Measured with the
|
|
17
|
+
per-kernel micro-benchmark on Apple M1 Pro (1764-pixel FlashCam, 22 samples, upsampling
|
|
18
|
+
4): smoothing alone 163 -> 96 us/event (-41%), upsampling + smoothing 1148 -> 837
|
|
19
|
+
us/event (-27%), the tiled full sweep `preprocess up/pz/smoothing batch` 213 -> 165
|
|
20
|
+
us/event (-23%). The numerical deviation was quantified against a long double evaluation
|
|
21
|
+
of the same recursion in `tests/cpp/test_smoothing_precision.cpp`: ~10^-7 relative on
|
|
22
|
+
the samples, < 10^-3 p.e. on the extracted charge and < 10^-4 upsampled samples on the
|
|
23
|
+
centroid. The error does not accumulate with trace length; it grows with FWHM as
|
|
24
|
+
1/(1-r)^2, reaching ~10^-6 relative at FWHM 20. Both the impulse-response width and the
|
|
25
|
+
unity DC gain are preserved (< 10^-4 and ~10^-6 deviation respectively).
|
|
26
|
+
|
|
27
|
+
## [0.2.1] - 2026-07-30
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- Documentation and docstrings revised for accuracy and consistency across the README, the
|
|
31
|
+
Sphinx pages, the C++ header comments and the Python/binding docstrings.
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- Introductory slide deck (`docs/slides/phepex-intro.typ`, [Typst](https://typst.app)): eight
|
|
35
|
+
slides on where phepex sits relative to libdvr and ctapipe, the kernels it exposes, and the
|
|
36
|
+
0.2.0 benchmark figures.
|
|
37
|
+
|
|
12
38
|
## [0.2.0] - 2026-07-28
|
|
13
39
|
|
|
14
40
|
### Changed
|
|
@@ -206,6 +232,7 @@ numbers are derived from git tags (`vX.Y.Z`) by setuptools-scm (Python) and GitV
|
|
|
206
232
|
- Unified Sphinx documentation covering the C++ (Doxygen + Breathe) and Python (autodoc)
|
|
207
233
|
API surfaces, published to GitHub Pages.
|
|
208
234
|
|
|
209
|
-
[Unreleased]: https://github.com/phepex/phepex/compare/v0.2.
|
|
235
|
+
[Unreleased]: https://github.com/phepex/phepex/compare/v0.2.1...HEAD
|
|
236
|
+
[0.2.1]: https://github.com/phepex/phepex/releases/tag/v0.2.1
|
|
210
237
|
[0.2.0]: https://github.com/phepex/phepex/releases/tag/v0.2.0
|
|
211
238
|
[0.1.0]: https://github.com/phepex/phepex/releases/tag/v0.1.0
|
|
@@ -114,6 +114,7 @@ if(PHEPEX_BUILD_TESTS)
|
|
|
114
114
|
enable_testing()
|
|
115
115
|
add_executable(phepex_tests
|
|
116
116
|
tests/cpp/test_phepex.cpp
|
|
117
|
+
tests/cpp/test_smoothing_precision.cpp
|
|
117
118
|
third_party/catch2/catch_amalgamated.cpp)
|
|
118
119
|
target_include_directories(phepex_tests PRIVATE third_party/catch2)
|
|
119
120
|
target_link_libraries(phepex_tests PRIVATE phepex::phepex)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phepex
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Photo-electron pulse extraction: fast PMT/SiPM waveform charge & timing extraction for Python.
|
|
5
5
|
Keywords: waveform,charge-extraction,PMT,SiPM,Cherenkov,signal-processing,DSP
|
|
6
6
|
Author: Max-Planck-Institut für Kernphysik
|
|
@@ -26,10 +26,11 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
|
|
27
27
|
# phepex — photo-electron pulse extraction
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
waveforms (e.g. Cherenkov telescopes or Water Cherenkov Detectors): pole-zero
|
|
29
|
+
Compiled numeric kernels for extracting charge and timing from digitised PMT/SiPM
|
|
30
|
+
waveforms (e.g. from Cherenkov telescopes or Water Cherenkov Detectors): pole-zero
|
|
31
31
|
deconvolution + upsampling, neighbour-sum peak finding, soft clipping, window
|
|
32
|
-
integration and leading-edge timing — plus a
|
|
32
|
+
integration and leading-edge timing — plus a waveform generator for tests and
|
|
33
|
+
benchmarks.
|
|
33
34
|
|
|
34
35
|
The heavy lifting runs in a compiled extension; `import phepex` needs only numpy.
|
|
35
36
|
|
|
@@ -43,14 +44,15 @@ pip install phepex
|
|
|
43
44
|
|
|
44
45
|
```python
|
|
45
46
|
import phepex
|
|
46
|
-
# phepex.
|
|
47
|
+
# phepex.preprocess, phepex.deconvolve, phepex.preprocess_valid_range,
|
|
48
|
+
# phepex.deconvolve_valid_range, phepex.pos_soft_clip, phepex.neighbor_peak_indices,
|
|
47
49
|
# phepex.extract_around_peak, phepex.adaptive_centroid, phepex.generate_waveforms
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
The ctapipe-integrated `phepex.extractor.FastFlashCamExtractor` — a drop-in
|
|
51
|
-
accelerating ctapipe's `FlashCamExtractor` (typically ~5× faster end-to-end,
|
|
52
|
-
|
|
53
|
-
imported on demand. It needs the `bench` extra:
|
|
53
|
+
accelerating ctapipe's `FlashCamExtractor` (typically ~5× faster end-to-end, agreeing
|
|
54
|
+
on signal-pixel charge to a median relative deviation of ~1e-7) — lives in a separate
|
|
55
|
+
submodule imported on demand. It needs the `bench` extra:
|
|
54
56
|
|
|
55
57
|
```bash
|
|
56
58
|
pip install phepex[bench] # pulls in ctapipe
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# phepex — photo-electron pulse extraction
|
|
2
2
|
|
|
3
3
|
<!-- intro:start -->
|
|
4
|
-
A
|
|
4
|
+
A dependency-free **C++17 library** of the numeric kernels used to extract charge and timing from digitised PMT/SiPM waveforms (e.g. from Cherenkov telescopes or Water Cherenkov Detectors) — pole-zero deconvolution + upsampling, neighbour-sum peak finding, soft clipping, window integration and leading-edge timing — plus a waveform generator for tests and benchmarks.
|
|
5
5
|
|
|
6
|
-
Ships
|
|
6
|
+
Ships `libphepex.so` and `libphepex.a`, public headers under `phepex/`, and optional **Python bindings** (`import phepex`).
|
|
7
7
|
|
|
8
|
-
The C++ library depends only on the C++ standard library, so it drops into any C++ project. `ctapipe` is used only by the Python benchmark/extractor layer,
|
|
8
|
+
The C++ library depends only on the C++ standard library, so it drops into any C++ project. `ctapipe` is used only by the Python benchmark/extractor layer, not by the library itself.
|
|
9
9
|
<!-- intro:end -->
|
|
10
10
|
|
|
11
11
|
## Documentation
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
API documentation for both the C++ and Python surfaces is published to **https://phepex.github.io/phepex/**, rebuilt from source by the `docs` workflow on every push to `main`. Build it locally with:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
pip install .[bench] && pip install -r docs/requirements.txt
|
|
@@ -24,8 +24,10 @@ All kernels are free functions in namespace `phepex`, operating on caller-owned
|
|
|
24
24
|
|
|
25
25
|
```cpp
|
|
26
26
|
#include <phepex/phepex.hpp>
|
|
27
|
-
//
|
|
28
|
-
//
|
|
27
|
+
// preprocess_waveform / preprocess_waveforms (batched) / preprocess_valid_range
|
|
28
|
+
// / calculate_smoothing_coefficients / preprocess_waveforms_scratch_size
|
|
29
|
+
// pos_soft_clip / neighbor_peak_indices / extract_around_peak / adaptive_centroid
|
|
30
|
+
// generate_waveforms / generate_shower_image
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
Build & install:
|
|
@@ -64,7 +66,7 @@ bindings automatically.
|
|
|
64
66
|
| `PHEPEX_BUILD_EXAMPLES` | `OFF` | Build the C++ example (`examples/example.cpp`). |
|
|
65
67
|
| `PHEPEX_BUILD_TESTS` | `OFF` | Build the standalone C++ unit tests (vendored Catch2). |
|
|
66
68
|
| `PHEPEX_BUILD_BENCHMARKS` | `OFF` | Build the C++ per-kernel micro-benchmark. |
|
|
67
|
-
| `PHEPEX_NEIGHBOR_PAIRWISE_SUM` | `ON` | Accumulate the neighbour sum in `neighbor_peak_indices` pairwise (~18
|
|
69
|
+
| `PHEPEX_NEIGHBOR_PAIRWISE_SUM` | `ON` | Accumulate the neighbour sum in `neighbor_peak_indices` pairwise (~18–22% faster on the benchmarked aarch64 core; see the CHANGELOG for the measurement). `OFF` selects the sequential sum, bit-identical to a left-to-right accumulation in CSR order; pairwise grouping alters the float32 summation order, so the peak index can differ in near-ties within ~1 ULP. See the note in `include/phepex/neighbor.hpp`. |
|
|
68
70
|
| `PHEPEX_PREPROCESS_TILE_WIDTH` | `24` | Number of waveforms mapped onto SIMD lanes per tile in the batched `preprocess_waveforms` path. Bit-identical for any value `>= 1`; performance only. See the tuning notes below. |
|
|
69
71
|
|
|
70
72
|
### Tuning `PHEPEX_PREPROCESS_TILE_WIDTH`
|
|
@@ -83,9 +85,9 @@ Recommendations:
|
|
|
83
85
|
that inside L1; above it the tiles spill and the gain reverses.
|
|
84
86
|
- Build the micro-benchmark (`-DPHEPEX_BUILD_BENCHMARKS=ON
|
|
85
87
|
-DCMAKE_CXX_FLAGS="-march=native" -DPHEPEX_PREPROCESS_TILE_WIDTH=K`) at a few widths and
|
|
86
|
-
compare the `preprocess up/pz(/smoothing) batch` rows
|
|
87
|
-
- `24` (the default)
|
|
88
|
-
|
|
88
|
+
compare the `preprocess up/pz(/smoothing) batch` rows.
|
|
89
|
+
- `24` (the default) measured best on both Apple Silicon (NEON) and Zen 4, even though the
|
|
90
|
+
latter supports AVX-512 (float SIMD width of 16).
|
|
89
91
|
|
|
90
92
|
To obtain an optimised Python wheel, pass it through scikit-build-core, e.g.
|
|
91
93
|
`CMAKE_CXX_FLAGS="-march=native" pip install . -C cmake.define.PHEPEX_PREPROCESS_TILE_WIDTH=K`.
|
|
@@ -99,7 +101,8 @@ pip install . # builds the phepex wheel (scikit-build-core + nanobind
|
|
|
99
101
|
|
|
100
102
|
```python
|
|
101
103
|
import phepex
|
|
102
|
-
# phepex.
|
|
104
|
+
# phepex.preprocess, phepex.deconvolve, phepex.preprocess_valid_range,
|
|
105
|
+
# phepex.deconvolve_valid_range, phepex.pos_soft_clip, phepex.neighbor_peak_indices,
|
|
103
106
|
# phepex.extract_around_peak, phepex.adaptive_centroid, phepex.generate_waveforms
|
|
104
107
|
```
|
|
105
108
|
|
|
@@ -136,15 +139,17 @@ ctest --test-dir build --output-on-failure # or: ./build/phepex_tests
|
|
|
136
139
|
|
|
137
140
|
## C++ micro-benchmark (no Python/ctapipe)
|
|
138
141
|
|
|
139
|
-
`benchmarks/cpp/microbench.cpp` times each `phepex::` kernel in isolation
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
`benchmarks/cpp/microbench.cpp` times each `phepex::` kernel in isolation, so a regression
|
|
143
|
+
can be attributed to a specific kernel. It links only `libphepex` and reads the camera
|
|
144
|
+
configuration at run time from a text file, so it needs neither Python nor ctapipe. One "op"
|
|
145
|
+
is one full-camera sweep (the per-event cost); the reported statistic is the minimum over
|
|
146
|
+
all reps.
|
|
147
|
+
|
|
148
|
+
Because the peak-search and centroid kernels are data-dependent, the input is one realistic
|
|
149
|
+
artificial event: a skewed-Gaussian shower image over ~200 pixels at ~1–100 p.e. with a
|
|
150
|
+
1 ns/pixel-pitch time gradient, plus 200 MHz NSB on a 200 LSB pedestal, digitised to 12-bit
|
|
151
|
+
ADC counts. Image axes and gradient are expressed in pixel pitches, so a config for another
|
|
152
|
+
camera gives a comparable event without code changes.
|
|
148
153
|
|
|
149
154
|
```bash
|
|
150
155
|
cmake -S . -B build -DPHEPEX_BUILD_BENCHMARKS=ON -DCMAKE_CXX_FLAGS="-march=native"
|
|
@@ -172,7 +177,7 @@ python3 scripts/export-camera-config.py --camera FlashCam \
|
|
|
172
177
|
python3 benchmarks/benchmark-fast-extractor.py --events 5000
|
|
173
178
|
```
|
|
174
179
|
|
|
175
|
-
Typical result: **~5× faster** end-to-end (leading-edge timing on)
|
|
180
|
+
Typical result: **~5× faster** end-to-end (leading-edge timing on). Extracted charge agrees with stock ctapipe to a median relative deviation of ~1e-7 on signal pixels (99th percentile ~2e-5); a small tail differs materially where the neighbour peak search lands on a different local maximum. See `benchmarks/README.md` for the per-step breakdown and the cause of that tail. Tests: `python3 -m pytest tests/ -q`.
|
|
176
181
|
<!-- benchmark:end -->
|
|
177
182
|
|
|
178
183
|
## Notes
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# phepex — photo-electron pulse extraction
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
waveforms (e.g. Cherenkov telescopes or Water Cherenkov Detectors): pole-zero
|
|
3
|
+
Compiled numeric kernels for extracting charge and timing from digitised PMT/SiPM
|
|
4
|
+
waveforms (e.g. from Cherenkov telescopes or Water Cherenkov Detectors): pole-zero
|
|
5
5
|
deconvolution + upsampling, neighbour-sum peak finding, soft clipping, window
|
|
6
|
-
integration and leading-edge timing — plus a
|
|
6
|
+
integration and leading-edge timing — plus a waveform generator for tests and
|
|
7
|
+
benchmarks.
|
|
7
8
|
|
|
8
9
|
The heavy lifting runs in a compiled extension; `import phepex` needs only numpy.
|
|
9
10
|
|
|
@@ -17,14 +18,15 @@ pip install phepex
|
|
|
17
18
|
|
|
18
19
|
```python
|
|
19
20
|
import phepex
|
|
20
|
-
# phepex.
|
|
21
|
+
# phepex.preprocess, phepex.deconvolve, phepex.preprocess_valid_range,
|
|
22
|
+
# phepex.deconvolve_valid_range, phepex.pos_soft_clip, phepex.neighbor_peak_indices,
|
|
21
23
|
# phepex.extract_around_peak, phepex.adaptive_centroid, phepex.generate_waveforms
|
|
22
24
|
```
|
|
23
25
|
|
|
24
26
|
The ctapipe-integrated `phepex.extractor.FastFlashCamExtractor` — a drop-in
|
|
25
|
-
accelerating ctapipe's `FlashCamExtractor` (typically ~5× faster end-to-end,
|
|
26
|
-
|
|
27
|
-
imported on demand. It needs the `bench` extra:
|
|
27
|
+
accelerating ctapipe's `FlashCamExtractor` (typically ~5× faster end-to-end, agreeing
|
|
28
|
+
on signal-pixel charge to a median relative deviation of ~1e-7) — lives in a separate
|
|
29
|
+
submodule imported on demand. It needs the `bench` extra:
|
|
28
30
|
|
|
29
31
|
```bash
|
|
30
32
|
pip install phepex[bench] # pulls in ctapipe
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Comparative benchmark: stock ctapipe vs phepex
|
|
2
2
|
|
|
3
3
|
Compares ctapipe's `FlashCamExtractor` against `phepex.extractor.FastFlashCamExtractor`
|
|
4
|
-
(same algorithm, numeric kernels in C++) on a
|
|
4
|
+
(same algorithm bar one documented divergence, numeric kernels in C++) on a synthetic
|
|
5
|
+
workload:
|
|
5
6
|
|
|
6
7
|
- unique gamma-like events for a **1764-pixel FlashCam-MST** camera
|
|
7
8
|
- **200 MHz night-sky background** (temporally-distributed Poisson p.e.)
|
|
@@ -26,10 +27,28 @@ python3 benchmarks/benchmark-fast-extractor.py --events 5000
|
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
Typical result: **~5× faster** end-to-end with leading-edge timing on (~2700 →
|
|
29
|
-
~600 µs/event), **~4.5×** off.
|
|
30
|
-
|
|
30
|
+
~600 µs/event), **~4.5×** off.
|
|
31
|
+
|
|
32
|
+
Charge agreement with stock ctapipe, measured over 60 events (1764 pixels, upsampling 4,
|
|
33
|
+
22 samples); relative deviations are over signal pixels (true charge > 1 p.e.):
|
|
34
|
+
|
|
35
|
+
| | no NSB | 200 MHz NSB |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| signal pixels bit-exact | 40% | 8% |
|
|
38
|
+
| relative deviation, median | 7.4e-8 | 1.0e-7 |
|
|
39
|
+
| relative deviation, 99th percentile | 2.7e-7 | 2.4e-5 |
|
|
40
|
+
| relative deviation, max | 3.2e-7 | O(1) |
|
|
41
|
+
|
|
42
|
+
Without NSB the deviation is pure float32 rounding, bounded at ~3e-7. With NSB, a small
|
|
43
|
+
tail of pixels differs significantly: the neighbour-sum peak search resolves a near-tie
|
|
44
|
+
between two local maxima differently, or the true peak falls in an edge margin this
|
|
45
|
+
extractor excludes (see the notes below), and the integration window then lands elsewhere.
|
|
46
|
+
`tests/test_extractor_equivalence.py` asserts bounds that admit this tail.
|
|
31
47
|
|
|
32
48
|
## Notes
|
|
33
49
|
|
|
34
50
|
- The deconvolution (two pole-zero passes/event) dominates the extractor runtime (~50%);
|
|
35
51
|
the phepex kernel is ~7–9× faster than ctapipe's scipy `deconvolve`/`repeat`/`filtfilt`.
|
|
52
|
+
- `FastFlashCamExtractor` restricts the clip and neighbour peak search to the non-edge
|
|
53
|
+
deconvolution samples, so a peak inside an edge margin is found by stock ctapipe but not
|
|
54
|
+
here. This is the one intentional algorithmic difference.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* In-page viewer for the Typst slide deck (see docs/slides.md, slides-viewer.js).
|
|
2
|
+
*
|
|
3
|
+
* The slides carry their own white background and accent colours, so they are framed rather
|
|
4
|
+
* than recoloured: inverting them in furo's dark theme would misrender the logo and the
|
|
5
|
+
* accent fills. The frame and controls follow the theme; the slide itself does not. */
|
|
6
|
+
|
|
7
|
+
.phepex-deck {
|
|
8
|
+
margin: 1.5rem 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* The `display` rules below are author-level and would otherwise beat the user agent's
|
|
12
|
+
* `[hidden] { display: none }`, leaving the controls visible without JavaScript and a broken
|
|
13
|
+
* image when no SVGs were exported. Class+attribute outranks the bare class, so this needs
|
|
14
|
+
* no !important. */
|
|
15
|
+
.phepex-deck [hidden] {
|
|
16
|
+
display: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.phepex-deck-stage {
|
|
20
|
+
display: block;
|
|
21
|
+
width: 100%;
|
|
22
|
+
/* Reserve the box before the first SVG loads, so advancing never shifts the page. */
|
|
23
|
+
aspect-ratio: 16 / 9;
|
|
24
|
+
background: #fff;
|
|
25
|
+
border: 1px solid var(--color-background-border);
|
|
26
|
+
border-radius: 6px;
|
|
27
|
+
/* The deck is keyboard-operable, so it must show where focus is. */
|
|
28
|
+
outline-offset: 2px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.phepex-deck-stage:focus-visible {
|
|
32
|
+
outline: 2px solid var(--color-brand-primary);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Click-to-advance only makes sense once the script is driving the deck. */
|
|
36
|
+
.phepex-deck[data-ready="1"] .phepex-deck-stage {
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.phepex-deck-stage:fullscreen {
|
|
41
|
+
aspect-ratio: auto;
|
|
42
|
+
height: 100%;
|
|
43
|
+
border: 0;
|
|
44
|
+
border-radius: 0;
|
|
45
|
+
object-fit: contain;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.phepex-deck-bar {
|
|
49
|
+
display: flex;
|
|
50
|
+
gap: 0.5rem;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
margin-top: 0.5rem;
|
|
54
|
+
font-size: var(--font-size--small);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.phepex-deck-bar button {
|
|
58
|
+
min-width: 2.2rem;
|
|
59
|
+
padding: 0.15rem 0.5rem;
|
|
60
|
+
color: var(--color-foreground-primary);
|
|
61
|
+
background: var(--color-background-secondary);
|
|
62
|
+
border: 1px solid var(--color-background-border);
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
font: inherit;
|
|
66
|
+
line-height: 1.6;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.phepex-deck-bar button:hover {
|
|
70
|
+
border-color: var(--color-brand-primary);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.phepex-deck-position {
|
|
74
|
+
min-width: 4.5rem;
|
|
75
|
+
text-align: center;
|
|
76
|
+
color: var(--color-foreground-secondary);
|
|
77
|
+
font-variant-numeric: tabular-nums;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Shown only when no SVGs were exported; the script reveals it in that case. */
|
|
81
|
+
.phepex-deck-unavailable {
|
|
82
|
+
color: var(--color-foreground-secondary);
|
|
83
|
+
font-size: var(--font-size--small);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* The slide outline: the deck's text alternative, collapsed by default so it does not
|
|
87
|
+
* restate the slides to a reader who can see them. A plain <details> rather than a
|
|
88
|
+
* `:class: dropdown` admonition, which furo renders as an ordinary open block, and rather
|
|
89
|
+
* than a sphinx-design directive, which would add a dependency for one disclosure. Closing
|
|
90
|
+
* it hides nothing from Sphinx search or from slides-viewer.js: both read the text from the
|
|
91
|
+
* document, not from what is painted. */
|
|
92
|
+
.phepex-deck-outline-details {
|
|
93
|
+
margin: 1rem 0;
|
|
94
|
+
padding: 0.4rem 0.75rem;
|
|
95
|
+
background: var(--color-background-secondary);
|
|
96
|
+
border: 1px solid var(--color-background-border);
|
|
97
|
+
border-radius: 6px;
|
|
98
|
+
font-size: var(--font-size--small);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.phepex-deck-outline-details > summary {
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
font-weight: 600;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.phepex-deck-outline-details[open] > summary {
|
|
107
|
+
margin-bottom: 0.5rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.phepex-deck-outline-details > summary:focus-visible {
|
|
111
|
+
outline: 2px solid var(--color-brand-primary);
|
|
112
|
+
outline-offset: 2px;
|
|
113
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// In-page viewer for the Typst slide deck rendered on docs/slides.md.
|
|
2
|
+
//
|
|
3
|
+
// Each slide is one SVG file swapped into a single <img>, rather than eight SVGs inlined in
|
|
4
|
+
// the page: Typst names every glyph <symbol id="..."> and those ids recur across slides, so
|
|
5
|
+
// inlining would put duplicate ids in one document (see docs/conf.py). The trade-off is that
|
|
6
|
+
// SVG text is glyph outlines, hence the authored outline list the page carries as the
|
|
7
|
+
// accessible and searchable text alternative; alt text is taken from it here.
|
|
8
|
+
//
|
|
9
|
+
// Loaded on every page, so it must do nothing when there is no deck.
|
|
10
|
+
function phepexInitDeck() {
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
const deck = document.querySelector(".phepex-deck");
|
|
14
|
+
if (!deck) return;
|
|
15
|
+
|
|
16
|
+
const stage = deck.querySelector(".phepex-deck-stage");
|
|
17
|
+
const bar = deck.querySelector(".phepex-deck-bar");
|
|
18
|
+
const position = deck.querySelector(".phepex-deck-position");
|
|
19
|
+
const note = deck.querySelector(".phepex-deck-unavailable");
|
|
20
|
+
const base = deck.dataset.base || "";
|
|
21
|
+
|
|
22
|
+
// One entry per slide, so the outline that supplies the alt text also fixes the slide
|
|
23
|
+
// count -- the two cannot drift apart here. docs/conf.py checks the list against the
|
|
24
|
+
// exported deck at build time.
|
|
25
|
+
const titles = Array.from(document.querySelectorAll(".phepex-deck-outline li"), (li) =>
|
|
26
|
+
(li.textContent || "").trim().replace(/\s+/g, " "),
|
|
27
|
+
);
|
|
28
|
+
const count = titles.length;
|
|
29
|
+
|
|
30
|
+
function unavailable() {
|
|
31
|
+
if (stage) stage.hidden = true;
|
|
32
|
+
if (bar) bar.hidden = true;
|
|
33
|
+
if (note) note.hidden = false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A docs build without Typst exports no SVGs, and nothing in the page says so: the markup
|
|
37
|
+
// is static and MyST does not substitute into it. Take the first image failing to load as
|
|
38
|
+
// the signal, which also covers a partial export.
|
|
39
|
+
if (count < 1 || !stage) return unavailable();
|
|
40
|
+
stage.addEventListener("error", unavailable);
|
|
41
|
+
|
|
42
|
+
const src = (i) => `${base}${i + 1}.svg`;
|
|
43
|
+
let current = -1;
|
|
44
|
+
|
|
45
|
+
function show(index, focus) {
|
|
46
|
+
const i = ((index % count) + count) % count;
|
|
47
|
+
if (i === current) return;
|
|
48
|
+
current = i;
|
|
49
|
+
stage.src = src(i);
|
|
50
|
+
const title = titles[i];
|
|
51
|
+
stage.alt = title
|
|
52
|
+
? `Slide ${i + 1} of ${count}: ${title}`
|
|
53
|
+
: `Slide ${i + 1} of ${count}`;
|
|
54
|
+
if (position) position.textContent = `${i + 1} / ${count}`;
|
|
55
|
+
// replaceState, not pushState: paging through the deck must not fill the back button.
|
|
56
|
+
history.replaceState(null, "", `#slide-${i + 1}`);
|
|
57
|
+
if (focus) stage.focus();
|
|
58
|
+
// Warm the next slide so a click or arrow key paints without a fetch.
|
|
59
|
+
if (count > 1) new Image().src = src((i + 1) % count);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
stage.addEventListener("click", () => show(current + 1, false));
|
|
63
|
+
|
|
64
|
+
stage.addEventListener("keydown", (event) => {
|
|
65
|
+
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
|
66
|
+
const step = { ArrowRight: 1, ArrowLeft: -1, PageDown: 1, PageUp: -1 }[event.key];
|
|
67
|
+
if (step !== undefined) show(current + step, false);
|
|
68
|
+
else if (event.key === "Home") show(0, false);
|
|
69
|
+
else if (event.key === "End") show(count - 1, false);
|
|
70
|
+
else return; // leave every other key to the page (furo binds "/" to search)
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
deck.querySelector(".phepex-deck-prev")?.addEventListener("click", () => {
|
|
75
|
+
show(current - 1, true);
|
|
76
|
+
});
|
|
77
|
+
deck.querySelector(".phepex-deck-next")?.addEventListener("click", () => {
|
|
78
|
+
show(current + 1, true);
|
|
79
|
+
});
|
|
80
|
+
deck.querySelector(".phepex-deck-fullscreen")?.addEventListener("click", () => {
|
|
81
|
+
if (document.fullscreenElement) document.exitFullscreen?.();
|
|
82
|
+
else stage.requestFullscreen?.();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Deep link: #slide-3 opens on that slide, and the hash stays shareable.
|
|
86
|
+
const fromHash = () => {
|
|
87
|
+
const match = /^#slide-(\d+)$/.exec(location.hash);
|
|
88
|
+
return match ? parseInt(match[1], 10) - 1 : 0;
|
|
89
|
+
};
|
|
90
|
+
window.addEventListener("hashchange", () => show(fromHash(), false));
|
|
91
|
+
|
|
92
|
+
if (bar) bar.hidden = false;
|
|
93
|
+
deck.dataset.ready = "1";
|
|
94
|
+
show(fromHash(), false);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// furo emits html_js_files at the end of <body>, so the deck is normally parsed by now; the
|
|
98
|
+
// guard matches version-switcher.js and keeps this correct if that ever changes.
|
|
99
|
+
if (document.readyState === "loading") {
|
|
100
|
+
document.addEventListener("DOMContentLoaded", phepexInitDeck);
|
|
101
|
+
} else {
|
|
102
|
+
phepexInitDeck();
|
|
103
|
+
}
|