viper-pwn 0.3.0__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.
Files changed (56) hide show
  1. viper_pwn-0.3.0/LICENSE +28 -0
  2. viper_pwn-0.3.0/PKG-INFO +254 -0
  3. viper_pwn-0.3.0/README.md +205 -0
  4. viper_pwn-0.3.0/pyproject.toml +66 -0
  5. viper_pwn-0.3.0/setup.cfg +4 -0
  6. viper_pwn-0.3.0/tests/test_escape.py +248 -0
  7. viper_pwn-0.3.0/tests/test_gammapy_wrapper.py +250 -0
  8. viper_pwn-0.3.0/tests/test_grid_regression.py +147 -0
  9. viper_pwn-0.3.0/tests/test_injection.py +306 -0
  10. viper_pwn-0.3.0/tests/test_invariants.py +212 -0
  11. viper_pwn-0.3.0/tests/test_kernel_equivalence.py +503 -0
  12. viper_pwn-0.3.0/tests/test_likelihood.py +425 -0
  13. viper_pwn-0.3.0/tests/test_multi_ic.py +154 -0
  14. viper_pwn-0.3.0/tests/test_nested.py +353 -0
  15. viper_pwn-0.3.0/tests/test_observables.py +75 -0
  16. viper_pwn-0.3.0/tests/test_plotting.py +234 -0
  17. viper_pwn-0.3.0/tests/test_sed_invariants.py +75 -0
  18. viper_pwn-0.3.0/tests/test_ssc.py +314 -0
  19. viper_pwn-0.3.0/tests/test_unit.py +1174 -0
  20. viper_pwn-0.3.0/viper/__init__.py +88 -0
  21. viper_pwn-0.3.0/viper/catalog.py +331 -0
  22. viper_pwn-0.3.0/viper/constants.py +155 -0
  23. viper_pwn-0.3.0/viper/data/cmb_pow_nu_ic_astropy.npz +0 -0
  24. viper_pwn-0.3.0/viper/data/cmb_pow_nu_ic_gsz2009.npz +0 -0
  25. viper_pwn-0.3.0/viper/data/psrcat.db +199688 -0
  26. viper_pwn-0.3.0/viper/data/psrcat_ref +7494 -0
  27. viper_pwn-0.3.0/viper/escape.py +126 -0
  28. viper_pwn-0.3.0/viper/evolution.py +1777 -0
  29. viper_pwn-0.3.0/viper/fitting.py +774 -0
  30. viper_pwn-0.3.0/viper/gammapy.py +487 -0
  31. viper_pwn-0.3.0/viper/grids.py +151 -0
  32. viper_pwn-0.3.0/viper/injection.py +230 -0
  33. viper_pwn-0.3.0/viper/legacy_params.py +16 -0
  34. viper_pwn-0.3.0/viper/model.py +658 -0
  35. viper_pwn-0.3.0/viper/nested.py +358 -0
  36. viper_pwn-0.3.0/viper/observables.py +522 -0
  37. viper_pwn-0.3.0/viper/parameter.py +417 -0
  38. viper_pwn-0.3.0/viper/particles.py +361 -0
  39. viper_pwn-0.3.0/viper/plotting/__init__.py +50 -0
  40. viper_pwn-0.3.0/viper/plotting/analysis.py +123 -0
  41. viper_pwn-0.3.0/viper/plotting/corner.py +195 -0
  42. viper_pwn-0.3.0/viper/plotting/data.py +251 -0
  43. viper_pwn-0.3.0/viper/plotting/electrons.py +436 -0
  44. viper_pwn-0.3.0/viper/plotting/movie.py +848 -0
  45. viper_pwn-0.3.0/viper/plotting/particle_sed.py +467 -0
  46. viper_pwn-0.3.0/viper/plotting/sed.py +476 -0
  47. viper_pwn-0.3.0/viper/plotting/style.py +155 -0
  48. viper_pwn-0.3.0/viper/pulsar.py +67 -0
  49. viper_pwn-0.3.0/viper/radiation.py +991 -0
  50. viper_pwn-0.3.0/viper/scalar_params.py +317 -0
  51. viper_pwn-0.3.0/viper/snr.py +295 -0
  52. viper_pwn-0.3.0/viper_pwn.egg-info/PKG-INFO +254 -0
  53. viper_pwn-0.3.0/viper_pwn.egg-info/SOURCES.txt +54 -0
  54. viper_pwn-0.3.0/viper_pwn.egg-info/dependency_links.txt +1 -0
  55. viper_pwn-0.3.0/viper_pwn.egg-info/requires.txt +36 -0
  56. viper_pwn-0.3.0/viper_pwn.egg-info/top_level.txt +1 -0
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Jason Alford, Joseph Gelfand, and contributors
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,254 @@
1
+ Metadata-Version: 2.4
2
+ Name: viper-pwn
3
+ Version: 0.3.0
4
+ Summary: VIPER (Volume-Integrated PWN Evolution & Radiation)
5
+ Author: Jason Alford
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/j-alford/viper-pwn
8
+ Project-URL: Documentation, https://viper-pwn.readthedocs.io
9
+ Project-URL: Repository, https://github.com/j-alford/viper-pwn
10
+ Project-URL: Issues, https://github.com/j-alford/viper-pwn/issues
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: numpy>=1.21
20
+ Requires-Dist: scipy>=1.7
21
+ Requires-Dist: astropy>=5.0
22
+ Provides-Extra: plot
23
+ Requires-Dist: matplotlib>=3.5; extra == "plot"
24
+ Provides-Extra: fast
25
+ Requires-Dist: numba>=0.57; extra == "fast"
26
+ Provides-Extra: fit
27
+ Requires-Dist: emcee>=3.0; extra == "fit"
28
+ Requires-Dist: corner; extra == "fit"
29
+ Provides-Extra: gammapy
30
+ Requires-Dist: gammapy>=2.0; extra == "gammapy"
31
+ Requires-Dist: regions<0.12; extra == "gammapy"
32
+ Provides-Extra: nested
33
+ Requires-Dist: ultranest>=4.0; extra == "nested"
34
+ Requires-Dist: scipy>=1.7; extra == "nested"
35
+ Provides-Extra: docs
36
+ Requires-Dist: sphinx>=7; extra == "docs"
37
+ Requires-Dist: pydata-sphinx-theme; extra == "docs"
38
+ Requires-Dist: myst-nb; extra == "docs"
39
+ Requires-Dist: sphinx-copybutton; extra == "docs"
40
+ Provides-Extra: all
41
+ Requires-Dist: matplotlib>=3.5; extra == "all"
42
+ Requires-Dist: numba>=0.57; extra == "all"
43
+ Requires-Dist: gammapy>=2.0; extra == "all"
44
+ Requires-Dist: regions<0.12; extra == "all"
45
+ Requires-Dist: emcee>=3.0; extra == "all"
46
+ Requires-Dist: corner; extra == "all"
47
+ Requires-Dist: ultranest>=4.0; extra == "all"
48
+ Dynamic: license-file
49
+
50
+ # VIPER - Volume-Integrated PWN Evolution & Radiation
51
+
52
+ A Python implementation of the semi-analytic PWN model described in
53
+ **Gelfand, Slane & Zhang (2009), ApJ 703, 2051**.
54
+
55
+ This code evolves a pulsar wind nebula inside a supernova
56
+ remnant, tracking the PWN radius, magnetic field, particle energy
57
+ spectrum, and broadband photon emission from radio through gamma-rays.
58
+
59
+ ## Installation
60
+
61
+ **Requirements**: Python >= 3.10, NumPy >= 1.21, SciPy >= 1.7, Astropy >= 5.0
62
+
63
+ **Optional**: Numba >= 0.57, emcee >= 3.0, corner, gammapy >= 2.0, matplotlib >= 3.5
64
+
65
+ ```bash
66
+ pip install viper-pwn
67
+ ```
68
+
69
+ Or for a development install (with [uv](https://docs.astral.sh/uv/);
70
+ install via `curl -LsSf https://astral.sh/uv/install.sh | sh`):
71
+
72
+ ```bash
73
+ git clone https://github.com/j-alford/viper-pwn
74
+ cd viper-pwn
75
+ uv venv && source .venv/bin/activate
76
+ uv pip install -e ".[fast]"
77
+ ```
78
+
79
+ ## Quick Start
80
+
81
+ ```python
82
+ import astropy.units as u
83
+ from viper import PWNModel
84
+
85
+ model = PWNModel()
86
+ model.set_pulsar('J0007+7303') # loads P, Pdot, distance from ATNF catalog
87
+ model.distance.value = 1.4 * u.kpc # override the ATNF catalog value if desired
88
+
89
+ model.esn.value = 1e51 * u.erg
90
+ model.mej.value = 6.0 * u.M_sun
91
+ model.n_ism.value = 0.4 / u.cm**3
92
+ model.braking_index.value = 2.5
93
+ model.tau_sd.value = 3000 * u.yr
94
+ model.eta_b.value = 0.01
95
+ model.e_min.value = 3.0 * u.GeV
96
+ model.e_max.value = 500 * u.TeV
97
+ model.e_break.value = 50.0 * u.GeV
98
+ model.p1.value = 0.8
99
+ model.p2.value = 2.5
100
+
101
+ # model.add_ic_field(temp=2753, norm=23.6137) # optional: add a NIR seed field (norm in eV/cm^3)
102
+
103
+ result = model.run()
104
+
105
+ dyn = result.dyninfo[-1]
106
+ print(f"R_pwn = {dyn.rad_pwn:.2f} pc")
107
+ print(f"B = {dyn.bpwn:.2f} uG")
108
+ print(f"V_pwn = {dyn.vpwn:.0f} km/s")
109
+ ```
110
+
111
+ ### Plotting the SED and Particle Spectrum
112
+
113
+ ```python
114
+ import matplotlib.pyplot as plt
115
+ from viper.plotting import plot_model_sed
116
+
117
+ fig, _ = plot_model_sed(result, distance=model.distance.value)
118
+ plt.show()
119
+ ```
120
+
121
+ ![Broadband SED example](docs/images/sed_example.png)
122
+
123
+ ### Evolution history
124
+
125
+ `result.dyninfo` records the full dynamical history at every timestep:
126
+ radii (`rad_pwn`, `rad_snr`, `rad_rs`, `rad_psr`), velocities (`vpwn`,
127
+ `vejpwn`, `vsnr`), magnetic field (`bpwn`), energies (`epwn_50`,
128
+ `epwnb_50`, `epwnp_50` in units of 10^50 erg), luminosities
129
+ (`synch_lum_40`, `ic_lum_40`, `ad_lum_40`, `edot_40` in 10^40 erg/s),
130
+ pressures (`ppwn`, `psnrpwn`), PWN-swept ejecta mass (`mswpwn`).
131
+
132
+ ![PWN evolution history grid](docs/images/evolution_grid.png)
133
+
134
+ Reproducer: [`docs/scripts/make_evolution_grid.py`](docs/scripts/make_evolution_grid.py).
135
+
136
+ ## Physics Notes
137
+
138
+ - **SNR dynamics**: Truelove & McKee (1999) analytic solution for
139
+ non-radiative SNR with power-law ejecta profile (n=9 hardcoded).
140
+ - **PWN-RS collision**: Detected when R_pwn > R_rs; switches to
141
+ Bandiera (1984) self-similar interior pressure.
142
+ - **Radiative losses**: Synchrotron + IC (full Klein-Nishina cross-section).
143
+ Adiabatic losses from PWN expansion included.
144
+ - **IC target fields**: the CMB is always included, and the model
145
+ supports including additional photon fields.
146
+ - **Single-zone**: No spatial structure within the PWN.
147
+ - **SSC**: Synchrotron self-Compton is off by default.
148
+
149
+ ## MCMC Fitting with emcee
150
+
151
+ The `fitting` module provides a complete MCMC pipeline using
152
+ [emcee](https://emcee.readthedocs.io). Set up a model, unfreeze
153
+ parameters with bounds, and pass observables in whatever units your
154
+ data are in:
155
+
156
+ ```python
157
+ import astropy.units as u
158
+ from viper import PWNModel
159
+ from viper.fitting import run_mcmc
160
+ from viper.observables import Observable
161
+
162
+ model = PWNModel()
163
+ model.set_pulsar('J1930+1852')
164
+
165
+ model.esn.value = 0.95e51 * u.erg
166
+ model.esn.frozen = False
167
+ model.esn.bounds = (1e49 * u.erg, 3e51 * u.erg)
168
+
169
+ model.mej.value = 21 * u.M_sun
170
+ model.mej.frozen = False
171
+ model.mej.bounds = (1 * u.M_sun, 100 * u.M_sun)
172
+
173
+ model.n_ism.value = 0.008 / u.cm**3
174
+ model.n_ism.frozen = False
175
+ model.n_ism.bounds = (1e-4 / u.cm**3, 1 / u.cm**3)
176
+
177
+ # ... set bounds for all free params ...
178
+
179
+ model.add_ic_field(temp=2753, norm=23.6137, frozen=False) # norm in eV/cm^3
180
+ model.parameters['ic_temp_1'].bounds = (1000 * u.K, 5000 * u.K)
181
+ model.parameters['ic_norm_1'].bounds = (1.0, 100.0) # eV/cm^3
182
+
183
+ observables = [
184
+ Observable.angular_size_snr(6.6, 0.4, 'arcmin'),
185
+ Observable.angular_size_pwn(1.14, 0.04, 'arcmin'),
186
+ Observable.flux_density(1.4, 'GHz', 433, 30, 'mJy'),
187
+ Observable.flux_density(4.7, 'GHz', 327, 25, 'mJy'),
188
+ Observable.band_flux(2, 10, 'keV', 5.43e-12, 0.035e-12),
189
+ Observable.spectral_index(2, 10, 'keV', 2.09, 0.01),
190
+ Observable.flux_density(311, 'GeV', 1.10e-11, 0.56e-11,
191
+ 'photons/cm2/s/TeV'),
192
+ ]
193
+
194
+ sampler, spec = run_mcmc(model, observables, nwalkers=32, nsteps=5000, n_cores=12)
195
+ ```
196
+
197
+ Observable types: `angular_size_snr`, `angular_size_pwn`, `flux_density`
198
+ (at any frequency/energy), `band_flux` (integrated), `spectral_index`.
199
+ Supports units: Hz/GHz/MHz, eV/keV/MeV/GeV/TeV, Jy/mJy,
200
+ erg/s/cm2/Hz, photons/cm2/s/TeV, erg/s/cm2, arcmin/arcsec/deg.
201
+
202
+ ## Gammapy Integration
203
+
204
+ VIPER includes the class `PWNSpectralModel`, which can be used
205
+ directly in [gammapy](https://gammapy.org):
206
+
207
+ ```python
208
+ from viper.gammapy import PWNSpectralModel
209
+ import astropy.units as u
210
+
211
+ # Pre-configured for CTA 1
212
+ model = PWNSpectralModel.for_cta1()
213
+ print(model)
214
+
215
+ # Plot
216
+ model.plot(energy_bounds=[1e-7, 100] * u.TeV)
217
+
218
+ # Or for G54.1+0.3 (with NIR IC field)
219
+ model = PWNSpectralModel.for_g54()
220
+
221
+ # Custom source
222
+ model = PWNSpectralModel(
223
+ period=0.136, p_dot=7.5112e-13,
224
+ esn=0.95, mej=21.0, n_ism=0.008, distance=6223.0,
225
+ braking_index=1.9, tau_sd=3574.0, eta_b=0.0024,
226
+ e_min=10.0, e_max=4e5, e_break=3232.0,
227
+ p1=2.29, p2=2.42,
228
+ ic_temp_1=2753.0, ic_norm_1=23.6137, # ic_norm_1 in eV/cm^3
229
+ )
230
+
231
+ # Freeze/thaw parameters for fitting
232
+ model.distance.frozen = True
233
+ model.braking_index.frozen = True
234
+ ```
235
+
236
+ The model caches results so repeated `evaluate` calls with unchanged
237
+ parameters (e.g., during gammapy's numerical integration) are instant.
238
+
239
+ ## References
240
+
241
+ - Gelfand, J. D., Slane, P. O., & Zhang, W. 2009, ApJ, 703, 2051.
242
+ [doi:10.1088/0004-637X/703/2/2051](https://doi.org/10.1088/0004-637X/703/2/2051)
243
+
244
+ - Truelove, J. K. & McKee, C. F. 1999, ApJS, 120, 299.
245
+ [doi:10.1086/313176](https://doi.org/10.1086/313176)
246
+
247
+ The Gelfand et al. (2009) model has been applied to constrain the
248
+ properties of numerous PWNe. Recent applications include:
249
+
250
+ - Gelfand, J. D., Slane, P. O., & Temim, T. 2015, ApJ, 807, 30.
251
+ [doi:10.1088/0004-637X/807/1/30](https://doi.org/10.1088/0004-637X/807/1/30)
252
+
253
+ - Alford, J. A. J., Zhang, G.-B., & Gelfand, J. D. 2025, ApJ, 987, 63.
254
+ [doi:10.3847/1538-4357/add92e](https://doi.org/10.3847/1538-4357/add92e)
@@ -0,0 +1,205 @@
1
+ # VIPER - Volume-Integrated PWN Evolution & Radiation
2
+
3
+ A Python implementation of the semi-analytic PWN model described in
4
+ **Gelfand, Slane & Zhang (2009), ApJ 703, 2051**.
5
+
6
+ This code evolves a pulsar wind nebula inside a supernova
7
+ remnant, tracking the PWN radius, magnetic field, particle energy
8
+ spectrum, and broadband photon emission from radio through gamma-rays.
9
+
10
+ ## Installation
11
+
12
+ **Requirements**: Python >= 3.10, NumPy >= 1.21, SciPy >= 1.7, Astropy >= 5.0
13
+
14
+ **Optional**: Numba >= 0.57, emcee >= 3.0, corner, gammapy >= 2.0, matplotlib >= 3.5
15
+
16
+ ```bash
17
+ pip install viper-pwn
18
+ ```
19
+
20
+ Or for a development install (with [uv](https://docs.astral.sh/uv/);
21
+ install via `curl -LsSf https://astral.sh/uv/install.sh | sh`):
22
+
23
+ ```bash
24
+ git clone https://github.com/j-alford/viper-pwn
25
+ cd viper-pwn
26
+ uv venv && source .venv/bin/activate
27
+ uv pip install -e ".[fast]"
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```python
33
+ import astropy.units as u
34
+ from viper import PWNModel
35
+
36
+ model = PWNModel()
37
+ model.set_pulsar('J0007+7303') # loads P, Pdot, distance from ATNF catalog
38
+ model.distance.value = 1.4 * u.kpc # override the ATNF catalog value if desired
39
+
40
+ model.esn.value = 1e51 * u.erg
41
+ model.mej.value = 6.0 * u.M_sun
42
+ model.n_ism.value = 0.4 / u.cm**3
43
+ model.braking_index.value = 2.5
44
+ model.tau_sd.value = 3000 * u.yr
45
+ model.eta_b.value = 0.01
46
+ model.e_min.value = 3.0 * u.GeV
47
+ model.e_max.value = 500 * u.TeV
48
+ model.e_break.value = 50.0 * u.GeV
49
+ model.p1.value = 0.8
50
+ model.p2.value = 2.5
51
+
52
+ # model.add_ic_field(temp=2753, norm=23.6137) # optional: add a NIR seed field (norm in eV/cm^3)
53
+
54
+ result = model.run()
55
+
56
+ dyn = result.dyninfo[-1]
57
+ print(f"R_pwn = {dyn.rad_pwn:.2f} pc")
58
+ print(f"B = {dyn.bpwn:.2f} uG")
59
+ print(f"V_pwn = {dyn.vpwn:.0f} km/s")
60
+ ```
61
+
62
+ ### Plotting the SED and Particle Spectrum
63
+
64
+ ```python
65
+ import matplotlib.pyplot as plt
66
+ from viper.plotting import plot_model_sed
67
+
68
+ fig, _ = plot_model_sed(result, distance=model.distance.value)
69
+ plt.show()
70
+ ```
71
+
72
+ ![Broadband SED example](docs/images/sed_example.png)
73
+
74
+ ### Evolution history
75
+
76
+ `result.dyninfo` records the full dynamical history at every timestep:
77
+ radii (`rad_pwn`, `rad_snr`, `rad_rs`, `rad_psr`), velocities (`vpwn`,
78
+ `vejpwn`, `vsnr`), magnetic field (`bpwn`), energies (`epwn_50`,
79
+ `epwnb_50`, `epwnp_50` in units of 10^50 erg), luminosities
80
+ (`synch_lum_40`, `ic_lum_40`, `ad_lum_40`, `edot_40` in 10^40 erg/s),
81
+ pressures (`ppwn`, `psnrpwn`), PWN-swept ejecta mass (`mswpwn`).
82
+
83
+ ![PWN evolution history grid](docs/images/evolution_grid.png)
84
+
85
+ Reproducer: [`docs/scripts/make_evolution_grid.py`](docs/scripts/make_evolution_grid.py).
86
+
87
+ ## Physics Notes
88
+
89
+ - **SNR dynamics**: Truelove & McKee (1999) analytic solution for
90
+ non-radiative SNR with power-law ejecta profile (n=9 hardcoded).
91
+ - **PWN-RS collision**: Detected when R_pwn > R_rs; switches to
92
+ Bandiera (1984) self-similar interior pressure.
93
+ - **Radiative losses**: Synchrotron + IC (full Klein-Nishina cross-section).
94
+ Adiabatic losses from PWN expansion included.
95
+ - **IC target fields**: the CMB is always included, and the model
96
+ supports including additional photon fields.
97
+ - **Single-zone**: No spatial structure within the PWN.
98
+ - **SSC**: Synchrotron self-Compton is off by default.
99
+
100
+ ## MCMC Fitting with emcee
101
+
102
+ The `fitting` module provides a complete MCMC pipeline using
103
+ [emcee](https://emcee.readthedocs.io). Set up a model, unfreeze
104
+ parameters with bounds, and pass observables in whatever units your
105
+ data are in:
106
+
107
+ ```python
108
+ import astropy.units as u
109
+ from viper import PWNModel
110
+ from viper.fitting import run_mcmc
111
+ from viper.observables import Observable
112
+
113
+ model = PWNModel()
114
+ model.set_pulsar('J1930+1852')
115
+
116
+ model.esn.value = 0.95e51 * u.erg
117
+ model.esn.frozen = False
118
+ model.esn.bounds = (1e49 * u.erg, 3e51 * u.erg)
119
+
120
+ model.mej.value = 21 * u.M_sun
121
+ model.mej.frozen = False
122
+ model.mej.bounds = (1 * u.M_sun, 100 * u.M_sun)
123
+
124
+ model.n_ism.value = 0.008 / u.cm**3
125
+ model.n_ism.frozen = False
126
+ model.n_ism.bounds = (1e-4 / u.cm**3, 1 / u.cm**3)
127
+
128
+ # ... set bounds for all free params ...
129
+
130
+ model.add_ic_field(temp=2753, norm=23.6137, frozen=False) # norm in eV/cm^3
131
+ model.parameters['ic_temp_1'].bounds = (1000 * u.K, 5000 * u.K)
132
+ model.parameters['ic_norm_1'].bounds = (1.0, 100.0) # eV/cm^3
133
+
134
+ observables = [
135
+ Observable.angular_size_snr(6.6, 0.4, 'arcmin'),
136
+ Observable.angular_size_pwn(1.14, 0.04, 'arcmin'),
137
+ Observable.flux_density(1.4, 'GHz', 433, 30, 'mJy'),
138
+ Observable.flux_density(4.7, 'GHz', 327, 25, 'mJy'),
139
+ Observable.band_flux(2, 10, 'keV', 5.43e-12, 0.035e-12),
140
+ Observable.spectral_index(2, 10, 'keV', 2.09, 0.01),
141
+ Observable.flux_density(311, 'GeV', 1.10e-11, 0.56e-11,
142
+ 'photons/cm2/s/TeV'),
143
+ ]
144
+
145
+ sampler, spec = run_mcmc(model, observables, nwalkers=32, nsteps=5000, n_cores=12)
146
+ ```
147
+
148
+ Observable types: `angular_size_snr`, `angular_size_pwn`, `flux_density`
149
+ (at any frequency/energy), `band_flux` (integrated), `spectral_index`.
150
+ Supports units: Hz/GHz/MHz, eV/keV/MeV/GeV/TeV, Jy/mJy,
151
+ erg/s/cm2/Hz, photons/cm2/s/TeV, erg/s/cm2, arcmin/arcsec/deg.
152
+
153
+ ## Gammapy Integration
154
+
155
+ VIPER includes the class `PWNSpectralModel`, which can be used
156
+ directly in [gammapy](https://gammapy.org):
157
+
158
+ ```python
159
+ from viper.gammapy import PWNSpectralModel
160
+ import astropy.units as u
161
+
162
+ # Pre-configured for CTA 1
163
+ model = PWNSpectralModel.for_cta1()
164
+ print(model)
165
+
166
+ # Plot
167
+ model.plot(energy_bounds=[1e-7, 100] * u.TeV)
168
+
169
+ # Or for G54.1+0.3 (with NIR IC field)
170
+ model = PWNSpectralModel.for_g54()
171
+
172
+ # Custom source
173
+ model = PWNSpectralModel(
174
+ period=0.136, p_dot=7.5112e-13,
175
+ esn=0.95, mej=21.0, n_ism=0.008, distance=6223.0,
176
+ braking_index=1.9, tau_sd=3574.0, eta_b=0.0024,
177
+ e_min=10.0, e_max=4e5, e_break=3232.0,
178
+ p1=2.29, p2=2.42,
179
+ ic_temp_1=2753.0, ic_norm_1=23.6137, # ic_norm_1 in eV/cm^3
180
+ )
181
+
182
+ # Freeze/thaw parameters for fitting
183
+ model.distance.frozen = True
184
+ model.braking_index.frozen = True
185
+ ```
186
+
187
+ The model caches results so repeated `evaluate` calls with unchanged
188
+ parameters (e.g., during gammapy's numerical integration) are instant.
189
+
190
+ ## References
191
+
192
+ - Gelfand, J. D., Slane, P. O., & Zhang, W. 2009, ApJ, 703, 2051.
193
+ [doi:10.1088/0004-637X/703/2/2051](https://doi.org/10.1088/0004-637X/703/2/2051)
194
+
195
+ - Truelove, J. K. & McKee, C. F. 1999, ApJS, 120, 299.
196
+ [doi:10.1086/313176](https://doi.org/10.1086/313176)
197
+
198
+ The Gelfand et al. (2009) model has been applied to constrain the
199
+ properties of numerous PWNe. Recent applications include:
200
+
201
+ - Gelfand, J. D., Slane, P. O., & Temim, T. 2015, ApJ, 807, 30.
202
+ [doi:10.1088/0004-637X/807/1/30](https://doi.org/10.1088/0004-637X/807/1/30)
203
+
204
+ - Alford, J. A. J., Zhang, G.-B., & Gelfand, J. D. 2025, ApJ, 987, 63.
205
+ [doi:10.3847/1538-4357/add92e](https://doi.org/10.3847/1538-4357/add92e)
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "viper-pwn"
7
+ version = "0.3.0"
8
+ description = "VIPER (Volume-Integrated PWN Evolution & Radiation)"
9
+ readme = "README.md"
10
+ authors = [{ name = "Jason Alford" }]
11
+ license = "BSD-3-Clause"
12
+ license-files = ["LICENSE"]
13
+ requires-python = ">=3.10"
14
+ dependencies = ["numpy>=1.21", "scipy>=1.7", "astropy>=5.0"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Science/Research",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Scientific/Engineering :: Astronomy",
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ plot = ["matplotlib>=3.5"]
25
+ fast = ["numba>=0.57"]
26
+ fit = ["emcee>=3.0", "corner"]
27
+ gammapy = [
28
+ "gammapy>=2.0",
29
+ # gammapy 2.1 imports a private regions API removed in regions 0.12
30
+ # (pixel_scale_angle_at_skycoord); lift when gammapy stops doing so.
31
+ "regions<0.12",
32
+ ]
33
+ nested = ["ultranest>=4.0", "scipy>=1.7"]
34
+ docs = [
35
+ "sphinx>=7",
36
+ "pydata-sphinx-theme",
37
+ "myst-nb",
38
+ "sphinx-copybutton",
39
+ ]
40
+ all = [
41
+ "matplotlib>=3.5",
42
+ "numba>=0.57",
43
+ "gammapy>=2.0",
44
+ "regions<0.12",
45
+ "emcee>=3.0",
46
+ "corner",
47
+ "ultranest>=4.0",
48
+ ]
49
+
50
+ [project.urls]
51
+ Homepage = "https://github.com/j-alford/viper-pwn"
52
+ Documentation = "https://viper-pwn.readthedocs.io"
53
+ Repository = "https://github.com/j-alford/viper-pwn"
54
+ Issues = "https://github.com/j-alford/viper-pwn/issues"
55
+
56
+ [tool.setuptools.packages.find]
57
+ include = ["viper*"]
58
+
59
+ [tool.setuptools.package-data]
60
+ viper = ["data/*.db", "data/psrcat_ref", "data/*.npz"]
61
+
62
+ [tool.pytest.ini_options]
63
+ testpaths = ["tests"]
64
+ markers = [
65
+ "slow: marks tests that run the full evolution model",
66
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+