lighthit 0.2.0a1__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.
- lighthit-0.2.0a1/MANIFEST.in +25 -0
- lighthit-0.2.0a1/PKG-INFO +391 -0
- lighthit-0.2.0a1/README.md +358 -0
- lighthit-0.2.0a1/examples/production_laser.py +28 -0
- lighthit-0.2.0a1/examples/production_shower.py +25 -0
- lighthit-0.2.0a1/examples/production_track.py +24 -0
- lighthit-0.2.0a1/pyproject.toml +44 -0
- lighthit-0.2.0a1/setup.cfg +4 -0
- lighthit-0.2.0a1/setup.py +19 -0
- lighthit-0.2.0a1/src/lighthit/__init__.py +17 -0
- lighthit-0.2.0a1/src/lighthit/__main__.py +2 -0
- lighthit-0.2.0a1/src/lighthit/_angular_numba.py +71 -0
- lighthit-0.2.0a1/src/lighthit/_directional_numba.py +392 -0
- lighthit-0.2.0a1/src/lighthit/angular.py +502 -0
- lighthit-0.2.0a1/src/lighthit/assets/event_viewer.css +1 -0
- lighthit-0.2.0a1/src/lighthit/assets/event_viewer.html +33 -0
- lighthit-0.2.0a1/src/lighthit/assets/event_viewer.js +34 -0
- lighthit-0.2.0a1/src/lighthit/ballistic.py +115 -0
- lighthit-0.2.0a1/src/lighthit/bgvd.py +115 -0
- lighthit-0.2.0a1/src/lighthit/cache.py +730 -0
- lighthit-0.2.0a1/src/lighthit/cli.py +149 -0
- lighthit-0.2.0a1/src/lighthit/directional.py +695 -0
- lighthit-0.2.0a1/src/lighthit/experimental/__init__.py +1 -0
- lighthit-0.2.0a1/src/lighthit/experimental/axial_fast.py +379 -0
- lighthit-0.2.0a1/src/lighthit/experimental/axial_source.py +420 -0
- lighthit-0.2.0a1/src/lighthit/experimental/ballistic_fast.py +335 -0
- lighthit-0.2.0a1/src/lighthit/experimental/directional_reference.py +259 -0
- lighthit-0.2.0a1/src/lighthit/experimental/event_moments.py +445 -0
- lighthit-0.2.0a1/src/lighthit/experimental/g4_source.py +186 -0
- lighthit-0.2.0a1/src/lighthit/experimental/hdf5_minimal.py +408 -0
- lighthit-0.2.0a1/src/lighthit/green.py +214 -0
- lighthit-0.2.0a1/src/lighthit/medium.py +49 -0
- lighthit-0.2.0a1/src/lighthit/model.py +157 -0
- lighthit-0.2.0a1/src/lighthit/providers.py +51 -0
- lighthit-0.2.0a1/src/lighthit/py.typed +0 -0
- lighthit-0.2.0a1/src/lighthit/readout.py +100 -0
- lighthit-0.2.0a1/src/lighthit/single.py +229 -0
- lighthit-0.2.0a1/src/lighthit/single_fast.py +85 -0
- lighthit-0.2.0a1/src/lighthit/sources.py +223 -0
- lighthit-0.2.0a1/src/lighthit/transport.py +729 -0
- lighthit-0.2.0a1/src/lighthit/viewer.py +122 -0
- lighthit-0.2.0a1/src/lighthit.egg-info/PKG-INFO +391 -0
- lighthit-0.2.0a1/src/lighthit.egg-info/SOURCES.txt +45 -0
- lighthit-0.2.0a1/src/lighthit.egg-info/dependency_links.txt +1 -0
- lighthit-0.2.0a1/src/lighthit.egg-info/entry_points.txt +2 -0
- lighthit-0.2.0a1/src/lighthit.egg-info/requires.txt +21 -0
- lighthit-0.2.0a1/src/lighthit.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include pyproject.toml
|
|
3
|
+
include examples/production_laser.py
|
|
4
|
+
include examples/production_track.py
|
|
5
|
+
include examples/production_shower.py
|
|
6
|
+
recursive-include src/lighthit *.py py.typed *.html *.js *.css
|
|
7
|
+
exclude src/lighthit/experimental/axial_blocked.py
|
|
8
|
+
exclude src/lighthit/experimental/cone_segment.py
|
|
9
|
+
exclude src/lighthit/experimental/crossing_mc.py
|
|
10
|
+
exclude src/lighthit/experimental/effective_segments.py
|
|
11
|
+
exclude src/lighthit/experimental/segment_reference.py
|
|
12
|
+
exclude src/lighthit/experimental/shell_mc.py
|
|
13
|
+
exclude src/lighthit/experimental/signal_screen.py
|
|
14
|
+
exclude src/lighthit/experimental/spline_fast.py
|
|
15
|
+
exclude src/lighthit/experimental/stationary_modes.py
|
|
16
|
+
prune docs
|
|
17
|
+
prune notebooks
|
|
18
|
+
prune slides
|
|
19
|
+
prune scripts
|
|
20
|
+
prune tests
|
|
21
|
+
prune g4_data
|
|
22
|
+
prune .build
|
|
23
|
+
prune results
|
|
24
|
+
prune preview
|
|
25
|
+
global-exclude *.py[cod] __pycache__ .DS_Store
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lighthit
|
|
3
|
+
Version: 0.2.0a1
|
|
4
|
+
Summary: Spectral radiative-transfer response for neutrino-telescope optical modules
|
|
5
|
+
Author: Dmitry Naumov
|
|
6
|
+
Project-URL: Repository, https://github.com/NeutrinoHit/LightHit
|
|
7
|
+
Keywords: radiative transfer,neutrino telescope,Cherenkov,Baikal-GVD
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: numpy>=1.26
|
|
17
|
+
Requires-Dist: scipy>=1.11
|
|
18
|
+
Provides-Extra: accelerate
|
|
19
|
+
Requires-Dist: numba>=0.61; extra == "accelerate"
|
|
20
|
+
Provides-Extra: notebook
|
|
21
|
+
Requires-Dist: jupyterlab>=4; extra == "notebook"
|
|
22
|
+
Requires-Dist: ipykernel>=6; extra == "notebook"
|
|
23
|
+
Requires-Dist: nbformat>=5; extra == "notebook"
|
|
24
|
+
Requires-Dist: nbclient>=0.10; extra == "notebook"
|
|
25
|
+
Requires-Dist: nbconvert>=7; extra == "notebook"
|
|
26
|
+
Requires-Dist: matplotlib>=3.8; extra == "notebook"
|
|
27
|
+
Requires-Dist: plotly>=5; extra == "notebook"
|
|
28
|
+
Provides-Extra: viewer
|
|
29
|
+
Requires-Dist: plotly>=5; extra == "viewer"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
32
|
+
Requires-Dist: build>=1; extra == "dev"
|
|
33
|
+
|
|
34
|
+
# LightHit
|
|
35
|
+
|
|
36
|
+
Spectral, time-dependent radiative transport and optical-module response.
|
|
37
|
+
The production API folds wavelength-dependent water, Cherenkov emission,
|
|
38
|
+
measured detector geometry, OM angular acceptance, quantum efficiency and
|
|
39
|
+
glass/gel transmission into expected photoelectrons. The medium is homogeneous
|
|
40
|
+
and unbounded; scattering uses Henyey–Greenstein with explicit `g`.
|
|
41
|
+
|
|
42
|
+
## Production API
|
|
43
|
+
|
|
44
|
+
No global initialization state is used. A `TransportKernel` owns one medium,
|
|
45
|
+
one detector and reusable wavelength-dependent caches; all sources and engines
|
|
46
|
+
return the same `TransportResponse` contract.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import lighthit as lh
|
|
50
|
+
|
|
51
|
+
bgvd = lh.load_bgvd_model(
|
|
52
|
+
"/private/path/bgvd-model-master",
|
|
53
|
+
dataset="2021", # all 2304 OMs by default
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
kernel = bgvd.kernel(
|
|
57
|
+
lh.KernelConfig(
|
|
58
|
+
cache_directory="/local/cache/lighthit-bgvd",
|
|
59
|
+
wavelength_nodes=9,
|
|
60
|
+
threshold_pe=0.01,
|
|
61
|
+
),
|
|
62
|
+
).build()
|
|
63
|
+
|
|
64
|
+
laser = lh.IsotropicFlash.monochromatic(
|
|
65
|
+
position_m=[0, 0, 0], photons=1e12, wavelength_nm=532,
|
|
66
|
+
)
|
|
67
|
+
laser_response = kernel.transport(laser)
|
|
68
|
+
|
|
69
|
+
track = lh.CherenkovTrack(
|
|
70
|
+
start_m=[0, 0, -50], direction=[0.2, 0.1, 0.97], length_m=120,
|
|
71
|
+
)
|
|
72
|
+
track_response = kernel.transport(track, method="track")
|
|
73
|
+
|
|
74
|
+
shower = lh.G4Shower.from_hdf5("event.h5", event=5)
|
|
75
|
+
shower_response = kernel.transport(shower, method="axial")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`method="auto"` chooses `isotropic`, `track`, or `axial` from the source type.
|
|
79
|
+
Research variants (`axial_full`, with `generic` as its descriptive alias) share
|
|
80
|
+
the same dispatcher but require explicit permission:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
control = kernel.transport(
|
|
84
|
+
shower, method="axial_full", allow_experimental=True,
|
|
85
|
+
)
|
|
86
|
+
print(kernel.available_methods())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The BGVD adapter reads the separately supplied private checkout at runtime. It
|
|
90
|
+
uses the full `median_om_coordinates_2021.csv`, wavelength-dependent water,
|
|
91
|
+
`efficiency(lambda) * transmission_gel_glass(lambda)`, and the private angular
|
|
92
|
+
OM polynomial. It deliberately ignores the private scattering indicatrix and
|
|
93
|
+
uses HG with `g=0.9`. Neither the checkout nor derived private files are part
|
|
94
|
+
of the LightHit distribution.
|
|
95
|
+
|
|
96
|
+
Cherenkov spectra use two source fields,
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
S0(lambda) = lambda^-2
|
|
100
|
+
S2(lambda) = lambda^-2 * n_phase(lambda)^-2
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
with spatial/angular coefficients stored independently. The complete derivation
|
|
104
|
+
and direct mapping from every formula to code is in
|
|
105
|
+
[`docs/production-rte-to-pe.qmd`](docs/production-rte-to-pe.qmd).
|
|
106
|
+
|
|
107
|
+
Minimal runnable integrations are
|
|
108
|
+
`examples/production_laser.py`, `production_track.py`, and
|
|
109
|
+
`production_shower.py`.
|
|
110
|
+
|
|
111
|
+
## Low-level monochromatic solver
|
|
112
|
+
|
|
113
|
+
`PointGreenSolver` remains the reviewed low-level Green function of an
|
|
114
|
+
instantaneous monochromatic point flash and a point isotropic receiver. A
|
|
115
|
+
**directed single photon** and an **isotropic single photon** are supported.
|
|
116
|
+
|
|
117
|
+
## What the calculation returns
|
|
118
|
+
|
|
119
|
+
`PointGreenSolver.solve` computes the complex response spectrum per unit
|
|
120
|
+
effective area, m⁻². The zero-frequency component gives the integrated
|
|
121
|
+
signal ("charge"). `result.readout` gives bin integrals, m⁻²; dividing by
|
|
122
|
+
the bin width gives the mean registration rate, m⁻²·ns⁻¹.
|
|
123
|
+
|
|
124
|
+
Detection efficiency is 1, and sensitivity is the same in every direction.
|
|
125
|
+
Area is never silently replaced by a bare number: multiplying by a small
|
|
126
|
+
effective area gives the expected photon count. There is no OM surface, no
|
|
127
|
+
first-entry condition, and no shadowing in this model.
|
|
128
|
+
|
|
129
|
+
A directed delta flash observed exactly on its own forward ray gives a
|
|
130
|
+
singular response; that request is rejected. The API treats
|
|
131
|
+
$\cos\theta\ge1-10^{-12}$ as singular. Geometries that close would need a
|
|
132
|
+
finite aperture or a finite angular source distribution instead. In the
|
|
133
|
+
shipped demonstration the angle is 60°, so the direct light is zero and the
|
|
134
|
+
whole signal is scattered. For an isotropic flash the direct light is a
|
|
135
|
+
delta function in time with a finite integral; bin integrals across it are
|
|
136
|
+
exact.
|
|
137
|
+
|
|
138
|
+
## Install into a clean checkout
|
|
139
|
+
|
|
140
|
+
Python 3.11 or newer is required. Commands below run from
|
|
141
|
+
`~/Projects/LightHit`:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
python3 --version
|
|
145
|
+
python3 -m venv .venv
|
|
146
|
+
source .venv/bin/activate
|
|
147
|
+
python -m pip install --upgrade pip
|
|
148
|
+
python -m pip install -e '.[accelerate,dev]'
|
|
149
|
+
python -m pytest -q
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
After publication, the equivalent user installation is:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
python -m pip install 'lighthit[accelerate]'
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Add the `notebook` or `viewer` extra only when those interfaces are needed.
|
|
159
|
+
|
|
160
|
+
None of these commands creates a Git commit or publishes anything.
|
|
161
|
+
|
|
162
|
+
## A first calculation without Jupyter
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
python -m lighthit --config examples/point-green.toml \
|
|
166
|
+
--output .build/point-green --plots
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
With the optional acceleration dependency installed, exact first scattering
|
|
170
|
+
uses its compiled scalar kernel automatically; select the compiled angular
|
|
171
|
+
tail explicitly with `--angular-backend numba`. Use `--single-backend numpy`
|
|
172
|
+
to force the uncompiled reference path for a comparison.
|
|
173
|
+
|
|
174
|
+
Output:
|
|
175
|
+
|
|
176
|
+
- `.build/point-green/spectrum.npz`: frequencies and the 0, 1, ≥2 components;
|
|
177
|
+
- `.build/point-green/profiles.npz`: unsmeared bins and bins with the given readout;
|
|
178
|
+
- `.build/point-green/report.json`: parameters, charge, timings, diagnostics;
|
|
179
|
+
- `.build/point-green/figures/`: separate profile and spectrum plots.
|
|
180
|
+
|
|
181
|
+
Charge without building a time spectrum:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
python -m lighthit --charge-only --repeat 3 --output .build/charge
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
A quick pass and a stricter numerical setting:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
python -m lighthit --preset quick --output .build/quick --plots
|
|
191
|
+
python -m lighthit --preset refined --output .build/refined --plots
|
|
192
|
+
python scripts/compare_runs.py .build/point-green .build/refined
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Numerical library threads can be pinned for comparable timings:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 VECLIB_MAXIMUM_THREADS=1 \
|
|
199
|
+
python -m lighthit --repeat 3 --output .build/benchmark
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`--repeat` repeats the calculation itself, not loading a saved result. All
|
|
203
|
+
timings are kept in the report. This first release has no on-disk transport
|
|
204
|
+
cache. Within one `solve` call, the angular solution is shared across every
|
|
205
|
+
observation point.
|
|
206
|
+
|
|
207
|
+
## Notebook
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
python -m jupyter lab notebooks/01_point_green.ipynb
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Jupyter menu: **Run → Run All Cells**. The notebook calls the same package
|
|
214
|
+
as the CLI. It shows normalization, geometry, the phase function, the
|
|
215
|
+
spectral equations, charge, time profiles, error diagnostics, and timings.
|
|
216
|
+
|
|
217
|
+
Non-interactive execution and HTML:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
mkdir -p .build/notebook
|
|
221
|
+
python -m jupyter nbconvert --to notebook --execute \
|
|
222
|
+
notebooks/01_point_green.ipynb --output 01_point_green.executed \
|
|
223
|
+
--output-dir .build/notebook --ExecutePreprocessor.timeout=600
|
|
224
|
+
python -m jupyter nbconvert --to html \
|
|
225
|
+
.build/notebook/01_point_green.executed.ipynb
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The source notebook is kept in Git without outputs. After running it,
|
|
229
|
+
especially against private water, strip outputs before `git add`:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
python scripts/strip_notebook_outputs.py notebooks/01_point_green.ipynb
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`.gitignore` does not remove outputs already committed to a tracked
|
|
236
|
+
notebook.
|
|
237
|
+
|
|
238
|
+
## Interactive event viewer
|
|
239
|
+
|
|
240
|
+
The general event viewer shows several G4 showers and point flashes in one
|
|
241
|
+
standalone HTML file: 3-D detector geometry, integrated charge by scattering
|
|
242
|
+
order, a selectable per-OM time histogram, a full-array time heatmap, and
|
|
243
|
+
frame/cumulative animation. Shower axes are drawn as directed spindles and
|
|
244
|
+
muons as directed lines with their stored length. Signed Fourier-inversion bins
|
|
245
|
+
are displayed with a signed-log colour scale and are never clipped or
|
|
246
|
+
renormalized; adjacent bins can be combined ×2/×4 for display.
|
|
247
|
+
|
|
248
|
+
With the local files under `g4_data`, run all four stored samples (event 5) and
|
|
249
|
+
an isotropic laser through the shared fast Numba cache:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
python -m pip install -e '.[accelerate,viewer]'
|
|
253
|
+
python scripts/run_event_viewer.py --output .build/event-viewer --threads 4
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The default triangular array has three clusters. Electron showers are placed
|
|
257
|
+
obliquely near individual clusters, the long muons cross the array, and the
|
|
258
|
+
laser sits at the triangle centre. Signals are converted to expected
|
|
259
|
+
photoelectrons with explicit effective area and efficiency. Before computing a
|
|
260
|
+
full spectrum, a cheap axial proxy plus an exact zero-frequency guard skips OMs
|
|
261
|
+
below `--threshold-pe 0.01`; set the threshold to zero to disable this.
|
|
262
|
+
|
|
263
|
+
Open `.build/event-viewer/viewer.html`. The same viewer can load another
|
|
264
|
+
portable `viewer.json` through its file button. Input HDF5 files and generated
|
|
265
|
+
event arrays remain local and are not package data.
|
|
266
|
+
|
|
267
|
+
The screening construction and measured speedups are recorded in
|
|
268
|
+
`docs/research/signal-screening.md`. The separate cutoff/node/bin-width study
|
|
269
|
+
of time ringing is in `docs/research/time-ringing.md`.
|
|
270
|
+
|
|
271
|
+
## Python API
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
import numpy as np
|
|
275
|
+
from lighthit import PointGreenSolver, SolverSettings, synthetic_medium
|
|
276
|
+
|
|
277
|
+
medium = synthetic_medium() # test values close to Baikal water, not a calibration
|
|
278
|
+
solver = PointGreenSolver(medium, SolverSettings())
|
|
279
|
+
omega = np.linspace(0.0, 1.2, 241) # rad/ns; include 0 for the charge
|
|
280
|
+
result = solver.solve(
|
|
281
|
+
omega,
|
|
282
|
+
displacement_m=[17.32050807568877, 0.0, 10.0], # detector minus source, m
|
|
283
|
+
direction=[0.0, 0.0, 1.0],
|
|
284
|
+
)
|
|
285
|
+
print(result.charge_per_m2)
|
|
286
|
+
print(result.timings_s)
|
|
287
|
+
front = result.front_time_ns[0]
|
|
288
|
+
edges = np.arange(front - 30, front + 651, 2.0)
|
|
289
|
+
raw = result.readout(edges, sigma_ns=0.0)
|
|
290
|
+
measured = result.readout(edges, sigma_ns=3.0)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
For an isotropic flash, pass `direction=None`. For several detectors, pass
|
|
294
|
+
`displacement_m` of shape `(D,3)`. Emission time is set by
|
|
295
|
+
`emission_time_ns`; the number of emitted photons by `photons`. A point
|
|
296
|
+
isotropic detector needs no normal vector.
|
|
297
|
+
|
|
298
|
+
## Private BGVD model
|
|
299
|
+
|
|
300
|
+
The distribution contains **no** private optical tables, OM coefficients,
|
|
301
|
+
geometry rows, event files, or derived private responses. `load_bgvd_model`
|
|
302
|
+
reads `BaikalWater.py`, `OpticalModule.py`, and the selected geometry CSV from
|
|
303
|
+
a separately supplied checkout, recording content hashes rather than copying
|
|
304
|
+
the inputs.
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
import lighthit as lh
|
|
308
|
+
|
|
309
|
+
bgvd = lh.load_bgvd_model(
|
|
310
|
+
"/full/local/path/to/bgvd-model",
|
|
311
|
+
dataset="2021",
|
|
312
|
+
g=0.9,
|
|
313
|
+
)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
The path may point at a checkout or its `bgvd_model` directory. If the private
|
|
317
|
+
package is installed, the path can be omitted. The older
|
|
318
|
+
`providers.load_bgvd_water` remains available for a low-level monochromatic
|
|
319
|
+
`PointGreenSolver`, but intentionally has no detector response.
|
|
320
|
+
|
|
321
|
+
Keep private caches and results outside the source tree or under ignored local
|
|
322
|
+
directories. The production adapter is checked against a synthetic double and
|
|
323
|
+
locally against the separately supplied package; none of that package is
|
|
324
|
+
written into the wheel or source distribution.
|
|
325
|
+
|
|
326
|
+
A run at $g=0.9$ needs its own scan of settings: numbers validated for the
|
|
327
|
+
synthetic $g=0.7$ examples are not a validation of $g=0.9$ real water, and
|
|
328
|
+
the (scattering degree, spatial degree, $k_{\max}$) triple that converges at
|
|
329
|
+
one radius does not automatically converge at another.
|
|
330
|
+
`docs/chapters/04-bgvd-water.qmd` works through this in detail: the
|
|
331
|
+
constraint that actually governs convergence is the spatial multipole
|
|
332
|
+
degree tracking $k_{\max}\cdot r$, not $g$ as such.
|
|
333
|
+
|
|
334
|
+
## What exactly is solved, and where the formulas are
|
|
335
|
+
|
|
336
|
+
The math is written up as a short Quarto book in `docs/`:
|
|
337
|
+
|
|
338
|
+
| Chapter | Content |
|
|
339
|
+
|---|---|
|
|
340
|
+
| `docs/production-rte-to-pe.qmd` | Production chain from spectral RTE and two source fields to expected photoelectrons |
|
|
341
|
+
| `docs/chapters/01-rte.qmd` | The radiative transfer equation, from a photon balance |
|
|
342
|
+
| `docs/chapters/02-point-source.qmd` | Point source, point detector: the adjoint system, the exact free tail, spatial inversion |
|
|
343
|
+
| `docs/chapters/03-histogram-binning.qmd` | Why orders 0 and 1 are binned in physical time instead of Fourier-inverted |
|
|
344
|
+
| `docs/chapters/04-bgvd-water.qmd` | The solver run against measured Baikal water at 450 nm |
|
|
345
|
+
| `docs/appendices/notation.qmd` | Symbol table |
|
|
346
|
+
| `docs/VALIDATION.md` | Numbers from the checks that were actually run |
|
|
347
|
+
|
|
348
|
+
Render it with Quarto (`cd docs && quarto preview`, or `quarto render` for
|
|
349
|
+
a static copy in `docs/_book/`); nothing in the book needs private data or
|
|
350
|
+
Geant4.
|
|
351
|
+
|
|
352
|
+
| File | Contents |
|
|
353
|
+
|---|---|
|
|
354
|
+
| `src/lighthit/medium.py` | Parameters of one spectral node, m and ns |
|
|
355
|
+
| `src/lighthit/angular.py` | Tridiagonal adjoint problem, exact free tail |
|
|
356
|
+
| `src/lighthit/single.py` | Coordinate-space first order with the full HG function |
|
|
357
|
+
| `src/lighthit/single_fast.py` | Optional compiled scalar kernel for the same exact first order |
|
|
358
|
+
| `src/lighthit/green.py` | Radial inversion and the combined 0+1+≥2 spectrum |
|
|
359
|
+
| `src/lighthit/readout.py` | Bins and instrument smearing, independent of transport |
|
|
360
|
+
| `src/lighthit/viewer.py` | Standalone multi-event 3-D viewer, OM charges, time histograms and animation |
|
|
361
|
+
| `src/lighthit/providers.py` | Local private-water provider |
|
|
362
|
+
| `tests/` | Independent matrix, analytic, and geometric checks |
|
|
363
|
+
|
|
364
|
+
Every collision order is included. **Orders $\ge2$ use HG coefficients only
|
|
365
|
+
up to $L$**, while the exact first order uses the full HG function — an
|
|
366
|
+
explicit, stated composite approximation of the full HG response, whose
|
|
367
|
+
difference from the full HG answer vanishes as $L\to\infty$. Free angular
|
|
368
|
+
transport is not truncated at $L$: its infinite tail is removed
|
|
369
|
+
analytically. The spatial angular inversion has its own, independent degree
|
|
370
|
+
$J$.
|
|
371
|
+
|
|
372
|
+
The $k$ range and quadrature, the frequency band, and the frequency step
|
|
373
|
+
are all finite as well. `quick`, `balanced`, `refined` change several
|
|
374
|
+
spatial/angular settings at once; they do not fix a mathematical error
|
|
375
|
+
bound. Unsmeared bins can oscillate right at the light front. The code
|
|
376
|
+
keeps negative values and mass before the front; nothing is clipped or
|
|
377
|
+
renormalized. Orders 0 and 1 are integrated directly in time, so their
|
|
378
|
+
fronts do not depend on the chosen frequency cutoff; the inverse Fourier
|
|
379
|
+
transform is applied only to the $\ge2$ part (`docs/chapters/03-histogram-binning.qmd`
|
|
380
|
+
explains why).
|
|
381
|
+
|
|
382
|
+
## What is not in the first module
|
|
383
|
+
|
|
384
|
+
A finite OM sphere, real acceptance and a PDE for it, cones and track
|
|
385
|
+
segments, showers, spectral convolution, a general spatial cache,
|
|
386
|
+
SVD/NUFFT, GPU execution, boundary surfaces, and an inhomogeneous medium.
|
|
387
|
+
Measurements on the test medium do not confirm accuracy on private optics.
|
|
388
|
+
|
|
389
|
+
Sources for the mathematical construction and the license status are in
|
|
390
|
+
[PROVENANCE.md](PROVENANCE.md). Copyright holders and a license must be
|
|
391
|
+
agreed before any publication; this delivery assigns neither.
|