Skip to content

Commit 0e284ea

Browse files
committed
Claude-based overhaul of the ReadTheDocs page. Now has some code blocks to give usage tutorials and includes the Sybil package to include the docs code blocks in the test suite.
1 parent 78f7ed8 commit 0e284ea

8 files changed

Lines changed: 483 additions & 19 deletions

File tree

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,28 @@ jobs:
8383
fail_ci_if_error: true
8484
continue-on-error: true # Don't fail CI if Codecov upload fails
8585

86+
docs-examples:
87+
name: Run documentation examples
88+
runs-on: ubuntu-latest
89+
steps:
90+
- name: Checkout code
91+
uses: actions/checkout@v4
92+
93+
- name: Set up Python
94+
uses: actions/setup-python@v5
95+
with:
96+
python-version: '3.12'
97+
cache: 'pip'
98+
99+
- name: Install dependencies
100+
run: |
101+
python -m pip install --upgrade pip
102+
pip install -e ".[dev]"
103+
104+
- name: Run code blocks from the docs as tests
105+
run: |
106+
pytest docs/ -v
107+
86108
build:
87109
name: Build Distribution
88110
runs-on: ubuntu-latest

docs/api.rst

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@ PDF Classes
1111
kingmaker.pdf.KingPDF
1212
kingmaker.pdf.TemplateSmearedKingPDF
1313

14-
Distribution
15-
------------
16-
17-
.. automodule:: kingmaker.distribution
18-
:members:
19-
2014
Fitting
2115
-------
2216

docs/conftest.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""
2+
Run every ``.. code-block:: python`` example in the docs as a pytest test.
3+
4+
Each .rst file is one Sybil "document": its code blocks execute in order,
5+
sharing one namespace, so later blocks can rely on names defined by earlier
6+
ones (matching how a reader would actually follow the page top to bottom).
7+
"""
8+
9+
from sybil import Sybil
10+
from sybil.parsers.rest import PythonCodeBlockParser
11+
12+
13+
def _use_headless_matplotlib(namespace):
14+
import matplotlib
15+
16+
matplotlib.use("Agg")
17+
18+
19+
pytest_collect_file = Sybil(
20+
parsers=[PythonCodeBlockParser()],
21+
patterns=["*.rst"],
22+
setup=_use_headless_matplotlib,
23+
).pytest()

docs/examples.rst

Lines changed: 256 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,266 @@
11
Examples
22
========
33

4-
The ``examples/`` directory contains Jupyter notebooks demonstrating typical workflows.
4+
This page walks through the main workflows the package supports, with
5+
short, copy-pasteable code for each. See :doc:`quickstart` first if you
6+
haven't evaluated a King PDF yet. Each section below links to the full
7+
Jupyter notebook in ``examples/`` for additional plots and benchmarks.
8+
9+
King PDF basics
10+
----------------
11+
12+
:class:`~kingmaker.pdf.KingPDF` evaluates the PDF/CDF (see :doc:`quickstart`),
13+
draws samples, and marginalizes over right ascension for signal-subtraction
14+
likelihoods.
15+
16+
**Sampling angular offsets**
17+
18+
.. code-block:: python
19+
20+
import numpy as np
21+
from kingmaker.pdf import KingPDF
22+
23+
king = KingPDF(angular_cutoff=np.pi)
24+
alpha, beta = np.radians(1.0), 2.0
25+
26+
# Angular separations (radians) drawn from the King distribution via
27+
# inverse CDF. These are offsets from the source, not full positions --
28+
# combine with a uniformly-random position angle to get a reconstructed
29+
# (ra, dec) for a given true source position.
30+
psi = king.sample(10_000, alpha, beta, n_grid=10_000)
31+
32+
**Marginalizing over right ascension**
33+
34+
.. code-block:: python
35+
36+
source_dec = np.radians(30)
37+
sindec_bins, pdf_marginalized = king.marginalize(
38+
source_dec, alpha, beta, threshold=1e-6, nbins=100,
39+
)
40+
# pdf_marginalized is the expected signal contribution as a function of
41+
# sin(declination), for use in a signal-subtraction likelihood term.
42+
43+
*Common options:*
44+
45+
- ``angular_cutoff`` (constructor): truncates the PDF's support and
46+
renormalizes. Use this to restrict evaluation to a search window instead
47+
of the full sphere.
48+
- ``n_grid`` (``sample``): size of the CDF lookup grid used for inverse
49+
transform sampling. Higher values trade memory/setup time for accuracy;
50+
the default of 10000 gives roughly arcminute accuracy.
51+
- ``threshold`` / ``nbins`` (``marginalize``): ``threshold`` discards
52+
declination/RA bins whose relative PDF value is negligible (controls
53+
sparsity); ``nbins=None`` (default) uses adaptive binning sized to
54+
``alpha`` instead of a fixed grid.
555

656
`basic_demo.ipynb <https://github.com/mjlarson/kingmaker/blob/main/examples/basic_demo.ipynb>`_
7-
King PDF basics, parameter effects, normalization, sampling, and speed benchmarks
8-
for :class:`~kingmaker.pdf.KingPDF`.
57+
Parameter effects, normalization checks, and sampling/evaluation speed
58+
benchmarks.
59+
60+
.. _fitting-psf-parameters:
61+
62+
Fitting PSF parameters from Monte Carlo
63+
----------------------------------------
64+
65+
:class:`~kingmaker.fitting.KingPSFFitter` bins signal Monte Carlo along
66+
arbitrary observables (energy, declination, angular error estimate, ...)
67+
and fits King ``alpha``/``beta`` to the angular-error distribution in each
68+
bin.
69+
70+
.. code-block:: python
71+
72+
from kingmaker.fitting import KingPSFFitter
73+
from kingmaker.pdf import KingPDF
74+
import numpy as np
75+
76+
# Build a small synthetic signal MC sample standing in for your own
77+
# simulation output -- a real dataset just needs the same field names.
78+
# Structured array with at least 'ra', 'dec' (reconstructed) and
79+
# 'trueRa', 'trueDec' (true) fields, in radians.
80+
rng = np.random.default_rng(0)
81+
n = 100_000
82+
true_logE = rng.uniform(2, 6, n)
83+
true_dec = np.arcsin(rng.uniform(-1, 1, n))
84+
true_ra = rng.uniform(0, 2 * np.pi, n)
85+
86+
psi = KingPDF().sample(n, np.radians(1.0), 2.5, rng=rng)
87+
phi = rng.uniform(0, 2 * np.pi, n)
88+
reco_dec = np.arcsin(np.clip(
89+
np.sin(true_dec) * np.cos(psi) + np.cos(true_dec) * np.sin(psi) * np.cos(phi),
90+
-1, 1,
91+
))
92+
reco_ra = true_ra + np.arctan2(
93+
np.sin(phi) * np.sin(psi),
94+
np.cos(true_dec) * np.cos(psi) - np.sin(true_dec) * np.sin(psi) * np.cos(phi),
95+
)
96+
97+
signal_events = np.empty(n, dtype=[
98+
("ra", float), ("dec", float),
99+
("trueRa", float), ("trueDec", float),
100+
("logE", float), ("ow", float), ("trueE", float),
101+
])
102+
signal_events["ra"], signal_events["dec"] = reco_ra, reco_dec
103+
signal_events["trueRa"], signal_events["trueDec"] = true_ra, true_dec
104+
signal_events["logE"] = true_logE
105+
signal_events["ow"] = 1.0
106+
signal_events["trueE"] = 10**true_logE
107+
108+
parametrization_bins = {"logE": 5, "dec": 4} # equal-probability bins
109+
110+
fitter = KingPSFFitter(
111+
signal_events=signal_events,
112+
parametrization_bins=parametrization_bins,
113+
dpsi_nbins=51,
114+
minimum_counts=100,
115+
weight_field="ow",
116+
spectral_indices=[2.0, 2.5, 3.0],
117+
)
118+
results = fitter.fit_all_bins(verbose=True)
119+
120+
alpha_fit = results["alpha"] # shape (n_gamma, n_logE, n_dec)
121+
beta_fit = results["beta"]
122+
123+
# Continuous evaluation between bin centers:
124+
alpha_interp, beta_interp = fitter.get_interpolator(gamma_index=0)
125+
point = np.array([[3.5, np.arcsin(0.0)]]) # [logE, dec]
126+
alpha_value = alpha_interp(point)
127+
128+
# Inspect a single bin's fit against its histogram:
129+
ax = fitter.plot_fit(bin_indices=(2, 2), gamma_index=0)
130+
131+
*Common options:*
132+
133+
- ``parametrization_bins``: each value is either an ``int`` (equal-probability
134+
bins computed from the MC) or an explicit array of bin edges.
135+
- ``dpsi_nbins``: resolution of the angular-error histogram used in the fit.
136+
- ``minimum_counts``: bins with fewer events than this are skipped entirely
137+
(left at the default initial guess rather than fit).
138+
- ``remove_weight_outliers`` / ``weight_outlier_percentiles``: drop events
139+
with extreme weights (by sorted-index percentile, default ``[0, 95]``)
140+
before fitting, to keep a few outsized weights from destabilizing the fit.
141+
- ``weight_field``: name of the per-event weight field (e.g. ``"ow"``); pass
142+
``None`` to use equal weights.
143+
- ``spectral_indices``: gamma values to fit independently. Each gets its own
144+
fitted alpha/beta grid, used later for interpolation over spectral index
145+
(see :class:`~kingmaker.wrapper.KingSpatialLikelihood` below).
9146

10147
`fitting_demo.ipynb <https://github.com/mjlarson/kingmaker/blob/main/examples/fitting_demo.ipynb>`_
11-
Fitting King PSF parameters to Monte Carlo simulations using
12-
:class:`~kingmaker.fitting.KingPSFFitter` as a function of energy and declination.
148+
Fitting as a function of energy and declination, with diagnostic plots.
13149

14-
`template_demo.ipynb <https://github.com/mjlarson/kingmaker/blob/main/examples/template_demo.ipynb>`_
15-
Spherical-harmonic convolution of a HEALPix template with the King PSF using
16-
:class:`~kingmaker.pdf.TemplateSmearedKingPDF`, including performance benchmarks.
150+
.. _point-source-likelihood:
151+
152+
End-to-end point-source likelihood
153+
-----------------------------------
154+
155+
:class:`~kingmaker.wrapper.KingSpatialLikelihood` wraps
156+
:class:`~kingmaker.fitting.KingPSFFitter` and
157+
:class:`~kingmaker.pdf.KingPDF` behind a single interface: fit (or load
158+
cached fit results) once, then evaluate the PDF per-event many times across
159+
trials.
160+
161+
Continuing with the synthetic ``signal_events`` from the fitting example
162+
above:
163+
164+
.. code-block:: python
165+
166+
from kingmaker.wrapper import KingSpatialLikelihood
167+
import numpy as np
168+
169+
wrapper = KingSpatialLikelihood(
170+
signal_events=signal_events,
171+
parametrization_bins=parametrization_bins,
172+
spectral_indices=[1.0, 2.0, 3.0, 4.0],
173+
cache_parameters=False,
174+
)
175+
176+
# Stand-in "data" events and a source position for one trial.
177+
data_events = signal_events[:1000]
178+
source_ra, source_dec = 0.5, 0.2
179+
180+
# Per trial: cache per-event parameters once, then evaluate as needed.
181+
wrapper.set_events(
182+
data_events,
183+
source_ras=np.array([source_ra]),
184+
source_decs=np.array([source_dec]),
185+
)
186+
pdf_values = wrapper.evaluate_pdf(data_events, gamma=2.0)
187+
pdf_values_steeper = wrapper.evaluate_pdf(data_events, gamma=2.5) # interpolated
188+
189+
*Common options:*
190+
191+
- ``cache_parameters`` / ``cache_name``: when ``True`` and ``cache_name``
192+
exists on disk, fitting is skipped entirely and parameters are loaded from
193+
the cache; otherwise the fitter runs and (if ``cache_parameters``) saves
194+
its results there. Use this to avoid refitting across repeated runs/trials.
195+
- ``spectral_indices``: the gamma grid that gets fit up front;
196+
``evaluate_pdf(events, gamma=...)`` interpolates between the two bracketing
197+
values, so pick a range that covers the spectral indices you plan to test.
198+
- ``parametrization_bins``: same ``int``-or-edges rules as
199+
:class:`~kingmaker.fitting.KingPSFFitter`.
200+
- **Gotcha:** ``evaluate_pdf`` requires ``set_events`` to have been called
201+
first with the *same* ``events`` array, and raises ``RuntimeError``
202+
otherwise. Calling ``set_events`` repeatedly with identical events/sources
203+
is a cheap no-op, so it's safe to call once per trial unconditionally.
17204

18205
`likelihood_demo.ipynb <https://github.com/mjlarson/kingmaker/blob/main/examples/likelihood_demo.ipynb>`_
19-
End-to-end likelihood analysis using the :class:`~kingmaker.wrapper.KingSpatialLikelihood`
20-
wrapper, covering event setup, spectral index interpolation, and PDF evaluation.
206+
Full walkthrough including event setup and spectral-index interpolation.
207+
208+
.. _template-smearing:
209+
210+
Template smearing for diffuse/extended sources
211+
------------------------------------------------
212+
213+
:class:`~kingmaker.pdf.TemplateSmearedKingPDF` convolves a HEALPix template
214+
map (e.g. Galactic diffuse emission) with the King PSF using a
215+
spherical-harmonic expansion, avoiding a per-event real-space convolution.
216+
217+
.. code-block:: python
218+
219+
from kingmaker.pdf import TemplateSmearedKingPDF
220+
import numpy as np
221+
import healpy as hp
222+
223+
# A small synthetic HEALPix map standing in for a real diffuse template
224+
# (e.g. Fermi-LAT diffuse emission) -- concentrated near the equator like
225+
# a toy Galactic plane. Normalized to integrate to 1 internally.
226+
nside = 32
227+
colat, _ = hp.pix2ang(nside, np.arange(hp.nside2npix(nside)))
228+
skymap = np.exp(-((colat - np.pi / 2) ** 2) / (2 * np.radians(10) ** 2))
229+
230+
tskp = TemplateSmearedKingPDF(
231+
skymap=skymap,
232+
interpolation_method="nearest",
233+
memory_limit_gb=1.0,
234+
)
235+
236+
alpha, beta = np.radians(5.0), 2.0
237+
238+
# Full convolved map (e.g. for plotting):
239+
convolved_map = tskp.convolve_map(alpha, beta)
240+
241+
# Fast evaluation at a fixed set of source positions instead:
242+
eval_decs = np.radians([0.0, 30.0, -15.0])
243+
eval_ras = np.radians([0.0, 45.0, 90.0])
244+
tskp.set_coordinates(eval_decs, eval_ras)
245+
pdf_at_sources = tskp.convolve_at_grid_point(alpha, beta)
246+
247+
*Common options:*
248+
249+
- ``interpolation_method``: ``"nearest"`` (default) snaps each
250+
``(alpha, beta)`` to the closest precomputed grid point -- cheaper, and
251+
events landing in the same grid cell reuse the same convolution.
252+
``"linear"`` bilinearly interpolates in log(alpha)/log(beta) space for
253+
smoother variation at extra cost.
254+
- ``lmax``: maximum spherical harmonic degree, defaulting to ``3 * nside - 1``
255+
of the input map. Lower it to reduce memory/compute at the cost of
256+
angular detail in the convolution.
257+
- ``memory_limit_gb``: caps the batch size used when precomputing spherical
258+
harmonics for many ``set_coordinates`` points at once.
259+
- ``points_alpha`` / ``points_beta``: the grid of King parameters over which
260+
the convolution is precomputed (100 log-spaced points each, by default).
261+
Increase density here if using ``"linear"`` interpolation and seeing
262+
visible discretization.
263+
264+
`template_demo.ipynb <https://github.com/mjlarson/kingmaker/blob/main/examples/template_demo.ipynb>`_
265+
Convolution of a Fermi-LAT diffuse template with the King PSF, including
266+
performance benchmarks against healpy's Gaussian smoothing.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ spherical-harmonic convolution with HEALPix template maps.
2424
:caption: Contents
2525

2626
installation
27+
quickstart
2728
api
2829

2930
.. toctree::

0 commit comments

Comments
 (0)