Skip to content

Commit 58c3db5

Browse files
reverse shock correction
1 parent 723cc83 commit 58c3db5

File tree

12 files changed

+183
-148
lines changed

12 files changed

+183
-148
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ times = np.logspace(2, 8, 100)
224224
bands = np.array([1e9, 1e14, 1e17])
225225

226226
# 3. Calculate the afterglow emission at each time and frequency
227-
results = model.specific_flux_matrix(times, bands)
227+
results = model.specific_flux(times, bands)
228228

229229
# 4. Visualize the multi-wavelength light curves
230230
plt.figure(figsize=(4.8, 3.6),dpi=200)
@@ -269,7 +269,7 @@ frequencies = np.logspace(5, 22, 100)
269269
epochs = np.array([1e2, 1e3, 1e4, 1e5 ,1e6, 1e7, 1e8])
270270

271271
# 3. Calculate spectra at each epoch
272-
results = model.specific_flux_matrix(epochs, frequencies)
272+
results = model.specific_flux(epochs, frequencies)
273273

274274
# 4. Plot broadband spectra at each epoch
275275
plt.figure(figsize=(4.8, 3.6),dpi=200)

docs/source/examples.rst

Lines changed: 68 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Setting up a simple afterglow model
3939
bands = np.array([1e9, 1e14, 1e17])
4040
4141
# Calculate the afterglow emission at each time and frequency
42-
results = model.specific_flux_matrix(times, bands)
42+
results = model.specific_flux(times, bands)
4343
4444
# Visualize the multi-wavelength light curves
4545
plt.figure(figsize=(4.8, 3.6),dpi=200)
@@ -48,7 +48,7 @@ Setting up a simple afterglow model
4848
for i, nu in enumerate(bands):
4949
exp = int(np.floor(np.log10(nu)))
5050
base = nu / 10**exp
51-
plt.loglog(times, results['syn'][i,:], label=fr'${base:.1f} \times 10^{{{exp}}}$ Hz')
51+
plt.loglog(times, results['syn'][i,:], label=fr'${base:.1f} \times 10^{{{exp}}}$ Hz')
5252
5353
plt.xlabel('Time (s)')
5454
plt.ylabel('Flux Density (erg/cm²/s/Hz)')
@@ -61,7 +61,7 @@ Setting up a simple afterglow model
6161
epochs = np.array([1e2, 1e3, 1e4, 1e5 ,1e6, 1e7, 1e8])
6262
6363
# Calculate spectra at each epoch
64-
results = model.specific_flux_matrix(epochs, frequencies)
64+
results = model.specific_flux(epochs, frequencies)
6565
6666
# Plot broadband spectra at each epoch
6767
plt.figure(figsize=(4.8, 3.6),dpi=200)
@@ -235,84 +235,85 @@ Those profiles are optional and will be set to zero function if not provided.
235235
Radiation Processes
236236
-------------------
237237

238-
Synchrotron Self-Compton
238+
Inverse Compton Cooling
239239
^^^^^^^^^^^^^^^^^^^^^^^^
240240

241241
.. code-block:: python
242242
243-
from VegasAfterglow import SynchrotronSelfCompton
243+
from VegasAfterglow import Radiation
244244
245-
# Create a model with synchrotron self-Compton
246-
ssc = SynchrotronSelfCompton(
247-
epsilon_e=0.1,
248-
epsilon_B=1e-3, # Lower magnetization favors IC
249-
p=2.2,
250-
include_KN=True # Include Klein-Nishina effects
251-
)
252-
253-
# Update the model
254-
model.set_radiation(ssc)
255-
256-
# Calculate over a broader frequency range to capture IC component
257-
frequencies_broad = np.logspace(9, 24, 50) # Radio to gamma-rays
258-
259-
# Calculate spectrum at a specific time
260-
t_spec = 1e4 # 10,000 seconds
261-
spectrum = model.calculate_spectrum(t_spec, frequencies_broad)
262-
263-
# Plot the spectrum with components
264-
plt.figure(figsize=(10, 6))
265-
plt.loglog(frequencies_broad, spectrum, 'b-', label='Total')
266-
plt.loglog(frequencies_broad, model.get_synchrotron_spectrum(), 'r--', label='Synchrotron')
267-
plt.loglog(frequencies_broad, model.get_ic_spectrum(), 'g--', label='Inverse Compton')
268-
269-
plt.xlabel('Frequency (Hz)')
270-
plt.ylabel('Flux Density (erg/cm²/s/Hz)')
271-
plt.legend()
272-
plt.title(f'GRB Afterglow Spectrum at t = {t_spec} s')
273-
plt.grid(True, which='both', linestyle='--', alpha=0.5)
274-
plt.show()
245+
# Create a radiation model with inverse Compton cooling (with Klein-Nishina correction) on synchrotron radiation
246+
rad = Radiation(eps_e=1e-1, eps_B=1e-3, p=2.3, IC_cooling=True, KN=False)
275247
276-
Advanced Features
277-
-----------------
248+
#..other settings
249+
model = Model(forward_rad=rad, ...)
250+
251+
Self-Synchrotron Compton Radiation
252+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
253+
254+
.. code-block:: python
255+
256+
from VegasAfterglow import Radiation
257+
258+
# Create a radiation model with self-Compton radiation
259+
rad = Radiation(eps_e=1e-1, eps_B=1e-3, p=2.3, SSC=True, KN=True, IC_cooling=True)
260+
261+
#..other settings
262+
model = Model(forward_rad=rad, ...)
263+
264+
times = np.logspace(2, 8, 200)
265+
bands = np.array([1e9, 1e14, 1e17])
266+
267+
results = model.specific_flux(times, bands)
268+
269+
plt.figure(figsize=(4.8, 3.6),dpi=200)
270+
271+
# Plot each frequency band
272+
for i, nu in enumerate(bands):
273+
exp = int(np.floor(np.log10(nu)))
274+
base = nu / 10**exp
275+
plt.loglog(times, results['syn'][i,:], label=fr'${base:.1f} \times 10^{{{exp}}}$ Hz')#synchrotron
276+
plt.loglog(times, results['IC'][i,:], label=fr'${base:.1f} \times 10^{{{exp}}}$ Hz')#SSC
277+
278+
.. note::
279+
(IC_cooling = False, KN = False, SSC = True): The IC radiation is calculated based on synchrotron spectrum without IC cooling.
280+
(IC_cooling = True, KN = False, SSC = True): The IC radiation is calculated based on synchrotron spectrum with IC cooling without Klein-Nishina correction.
281+
(IC_cooling = True, KN = True, SSC = True): The IC radiation is calculated based on synchrotron spectrum with IC cooling and Klein-Nishina correction.
278282

279283
Reverse Shock
280284
^^^^^^^^^^^^^
281285

282286
.. code-block:: python
287+
from VegasAfterglow import Radiation
283288
284-
# Create a model with reverse shock component
285-
model_with_rs = Model(
286-
jet=jet,
287-
medium=medium,
288-
radiation=radiation,
289-
include_reverse_shock=True
290-
)
291-
292-
# Set reverse shock parameters
293-
model_with_rs.set_reverse_shock_parameters(
294-
RB=0.1, # Magnetic field ratio between reverse and forward shock
295-
Re=1.0 # Electron energy ratio between reverse and forward shock
296-
)
297-
298-
# Calculate light curves including reverse shock
299-
results_with_rs = model_with_rs.calculate_light_curves(times, frequencies)
289+
# Create a radiation model with self-Compton radiation
290+
fwd_rad = Radiation(eps_e=1e-1, eps_B=1e-3, p=2.3, SSC=True, KN=True, IC_cooling=True)
291+
rvs_rad = Radiation(eps_e=1e-2, eps_B=1e-4, p=2.4, SSC=False, KN=False, IC_cooling=False)
292+
293+
#..other settings
294+
model = Model(forward_rad=fwd_rad, reverse_rad=rvs_rad, ...)
295+
296+
times = np.logspace(2, 8, 200)
297+
bands = np.array([1e9, 1e14, 1e17])
298+
299+
results = model.specific_flux(times, bands)
300300
301-
# Plot forward vs reverse shock components
302-
plt.figure(figsize=(10, 6))
303-
for i, nu in enumerate(frequencies):
304-
plt.loglog(times, results_with_rs[:, i], label=f'Total {nu:.1e} Hz')
305-
plt.loglog(times, model_with_rs.get_forward_shock_light_curve(i), '--',
306-
label=f'FS {nu:.1e} Hz')
307-
plt.loglog(times, model_with_rs.get_reverse_shock_light_curve(i), ':',
308-
label=f'RS {nu:.1e} Hz')
301+
plt.figure(figsize=(4.8, 3.6),dpi=200)
302+
303+
# Plot each frequency band
304+
for i, nu in enumerate(bands):
305+
exp = int(np.floor(np.log10(nu)))
306+
base = nu / 10**exp
307+
plt.loglog(times, results['syn'][i,:], label=fr'${base:.1f} \times 10^{{{exp}}}$ Hz')
308+
plt.loglog(times, results['IC'][i,:], label=fr'${base:.1f} \times 10^{{{exp}}}$ Hz')
309+
plt.loglog(times, results['syn_rvs'][i,:], label=fr'${base:.1f} \times 10^{{{exp}}}$ Hz')#reverse shock synchrotron
310+
311+
.. note::
312+
You may increase the resolution of the grid to improve the accuracy of the reverse shock synchrotron radiation.
309313

310-
plt.xlabel('Time (s)')
311-
plt.ylabel('Flux Density (erg/cm²/s/Hz)')
312-
plt.legend()
313-
plt.title('GRB Afterglow with Reverse Shock')
314-
plt.grid(True, which='both', linestyle='--', alpha=0.5)
315-
plt.show()
314+
Advanced Features
315+
-----------------
316+
316317

317318
MCMC Parameter Fitting
318319
^^^^^^^^^^^^^^^^^^^^^^

docs/source/quickstart.rst

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Now, let's compute and plot multi-wavelength light curves to see how the aftergl
6363
bands = np.array([1e9, 1e14, 1e17])
6464
6565
# 3. Calculate the afterglow emission at each time and frequency
66-
results = model.specific_flux_matrix(times, bands)
66+
results = model.specific_flux(times, bands)
6767
6868
# 4. Visualize the multi-wavelength light curves
6969
plt.figure(figsize=(4.8, 3.6), dpi=200)
@@ -109,17 +109,16 @@ We can also examine how the broadband spectrum evolves at different times after
109109
epochs = np.array([1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8])
110110
111111
# 3. Calculate spectra at each epoch
112-
results = model.spectra(frequencies, epochs)
112+
results = model.specific_flux(epochs, frequencies)
113113
114114
# 4. Plot broadband spectra at each epoch
115-
plt.figure(figsize=(4.8, 3.6), dpi=200)
116-
colors = plt.cm.viridis(np.linspace(0, 1, len(epochs)))
115+
plt.figure(figsize=(4.8, 3.6),dpi=200)
116+
colors = plt.cm.viridis(np.linspace(0,1,len(epochs)))
117117
118118
for i, t in enumerate(epochs):
119119
exp = int(np.floor(np.log10(t)))
120120
base = t / 10**exp
121-
plt.loglog(frequencies, results['syn'][i,:], color=colors[i],
122-
label=fr'${base:.1f} \times 10^{{{exp}}}$ s')
121+
plt.loglog(frequencies, results['syn'][:,i], color=colors[i], label=fr'${base:.1f} \times 10^{{{exp}}}$ s')
123122
124123
# 5. Add vertical lines marking the bands from the light curve plot
125124
for i, band in enumerate(bands):
@@ -212,7 +211,7 @@ The ``Setups`` class defines the global properties and environment for your mode
212211
cfg.z = 1.58 # Redshift
213212
214213
# Physical model configuration
215-
cfg.medium = "wind" # Ambient medium: "wind", "ISM" (Interstellar Medium) or "user" (user-defined)
214+
cfg.medium = "wind" # Ambient medium: "wind", "ism" (Interstellar Medium) or "user" (user-defined)
216215
cfg.jet = "powerlaw" # Jet structure: "powerlaw", "gaussian", "tophat" or "user" (user-defined)
217216
218217

include/observer.h

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class Observer {
100100
* <!-- ************************************************************************************** -->
101101
*/
102102
template <typename... PhotonGrid>
103-
Array point_specific_flux(Array const& t_obs, Array const& nu_obs, PhotonGrid const&... photons);
103+
Array specific_flux_series(Array const& t_obs, Array const& nu_obs, PhotonGrid const&... photons);
104104

105105
/**
106106
* <!-- ************************************************************************************** -->
@@ -327,7 +327,7 @@ MeshGrid Observer::specific_flux(Array const& t_obs, Array const& nu_obs, Photon
327327
iterate_to(lg2_t(i, j, 0), lg2_t_obs, t_idx);
328328

329329
for (size_t k = 0; k < t_grid - 1 && t_idx < t_obs_len; k++) {
330-
Real const lg2_t_hi = lg2_t(i, j, k + 1);
330+
Real lg2_t_hi = lg2_t(i, j, k + 1);
331331
if (lg2_t_hi < lg2_t_obs(t_idx)) {
332332
continue;
333333
} else {
@@ -352,7 +352,7 @@ MeshGrid Observer::specific_flux(Array const& t_obs, Array const& nu_obs, Photon
352352
}
353353

354354
template <typename... PhotonGrid>
355-
Array Observer::point_specific_flux(Array const& t_obs, Array const& nu_obs, PhotonGrid const&... photons) {
355+
Array Observer::specific_flux_series(Array const& t_obs, Array const& nu_obs, PhotonGrid const&... photons) {
356356
size_t t_obs_len = t_obs.size();
357357
size_t nu_len = nu_obs.size();
358358

@@ -365,21 +365,12 @@ Array Observer::point_specific_flux(Array const& t_obs, Array const& nu_obs, Pho
365365

366366
Array F_nu({t_obs_len}, 0);
367367

368-
InterpState state;
369-
370-
// Loop over effective phi and theta grid points.
371368
for (size_t i = 0; i < eff_phi_grid; i++) {
372369
for (size_t j = 0; j < theta_grid; j++) {
373-
// Skip observation times that are below the grid's start time
374-
size_t t_idx = 0;
375-
iterate_to(lg2_t(i, j, 0), lg2_t_obs, t_idx);
376-
377-
for (size_t k = 0; k < t_grid - 1 && t_idx < t_obs_len; k++) {
378-
Real const lg2_t_hi = lg2_t(i, j, k + 1);
379-
if (lg2_t_hi < lg2_t_obs(t_idx)) {
380-
continue;
381-
} else {
382-
for (; t_idx < t_obs_len && lg2_t_obs(t_idx) <= lg2_t_hi; t_idx++) {
370+
for (size_t t_idx = 0; t_idx < t_obs_len; t_idx++) {
371+
for (size_t k = 0; k < t_grid - 1; k++) {
372+
if (lg2_t_obs(t_idx) <= lg2_t(i, j, k + 1)) {
373+
InterpState state;
383374
if (set_boundaries(state, i, j, k, lg2_nu[t_idx], photons...)) {
384375
F_nu(t_idx) += interpolate(state, i, j, k, lg2_t_obs(t_idx));
385376
}

pybind/pybind.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,12 @@ PYBIND11_MODULE(VegasAfterglowC, m) {
6060
py::arg("jet"), py::arg("medium"), py::arg("observer"), py::arg("forward_rad"),
6161
py::arg("reverse_rad") = py::none(), py::arg("resolutions") = std::make_tuple(0.5, 3., 5.),
6262
py::arg("rtol") = 1e-5, py::arg("axisymmetric") = true)
63-
.def("specific_flux", &PyModel::specific_flux, py::arg("t"), py::arg("nu"))
64-
.def("specific_flux_matrix", &PyModel::specific_flux_matrix, py::arg("t"), py::arg("nu"));
63+
.def("specific_flux", &PyModel::specific_flux, py::arg("t"), py::arg("nu"),
64+
py::call_guard<py::gil_scoped_release>())
65+
.def("specific_flux_series", &PyModel::specific_flux_series, py::arg("t"), py::arg("nu"),
66+
py::call_guard<py::gil_scoped_release>())
67+
.def("specific_flux_sorted_series", &PyModel::specific_flux_sorted_series, py::arg("t"), py::arg("nu"),
68+
py::call_guard<py::gil_scoped_release>());
6569

6670
//========================================================================================================
6771
// MCMC bindings

pybind/pymodel.cpp

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Medium PyWind(Real A_star) {
8888

8989
void PyModel::single_shock_emission(Shock const& shock, Coord const& coord, Array const& t_obs, Array const& nu_obs,
9090
Observer& obs, PyRadiation rad, FluxDict& flux_dict, std::string suffix,
91-
bool return_trace) {
91+
bool serilized) {
9292
obs.observe(coord, shock, obs_setup.lumi_dist, obs_setup.z);
9393

9494
auto syn_e = generate_syn_electrons(shock);
@@ -106,20 +106,20 @@ void PyModel::single_shock_emission(Shock const& shock, Coord const& coord, Arra
106106
if (rad.SSC) {
107107
auto IC_ph = generate_IC_photons(syn_e, syn_ph, rad.KN);
108108

109-
if (return_trace) {
110-
flux_dict["IC" + suffix] = obs.point_specific_flux(t_obs, nu_obs, IC_ph) / unit::flux_den_cgs;
109+
if (serilized) {
110+
flux_dict["IC" + suffix] = obs.specific_flux_series(t_obs, nu_obs, IC_ph) / unit::flux_den_cgs;
111111
} else {
112112
flux_dict["IC" + suffix] = obs.specific_flux(t_obs, nu_obs, IC_ph) / unit::flux_den_cgs;
113113
}
114114
}
115-
if (return_trace) {
116-
flux_dict["syn" + suffix] = obs.point_specific_flux(t_obs, nu_obs, syn_ph) / unit::flux_den_cgs;
115+
if (serilized) {
116+
flux_dict["syn" + suffix] = obs.specific_flux_series(t_obs, nu_obs, syn_ph) / unit::flux_den_cgs;
117117
} else {
118118
flux_dict["syn" + suffix] = obs.specific_flux(t_obs, nu_obs, syn_ph) / unit::flux_den_cgs;
119119
}
120120
}
121121

122-
auto PyModel::compute_specific_flux(Array const& t_obs, Array const& nu_obs, bool return_trace) -> FluxDict {
122+
auto PyModel::compute_specific_flux(Array const& t_obs, Array const& nu_obs, bool serilized) -> FluxDict {
123123
Coord coord = auto_grid(jet, t_obs, this->theta_w, obs_setup.theta_obs, obs_setup.z, phi_resol, theta_resol,
124124
t_resol, axisymmetric);
125125

@@ -130,36 +130,50 @@ auto PyModel::compute_specific_flux(Array const& t_obs, Array const& nu_obs, boo
130130
if (!rvs_rad_opt) {
131131
auto fwd_shock = generate_fwd_shock(coord, medium, jet, fwd_rad.rad, rtol);
132132

133-
single_shock_emission(fwd_shock, coord, t_obs, nu_obs, observer, fwd_rad, flux_dict, "", return_trace);
133+
single_shock_emission(fwd_shock, coord, t_obs, nu_obs, observer, fwd_rad, flux_dict, "", serilized);
134134

135135
return flux_dict;
136136
} else {
137137
auto rvs_rad = *rvs_rad_opt;
138138
auto [fwd_shock, rvs_shock] = generate_shock_pair(coord, medium, jet, fwd_rad.rad, rvs_rad.rad, rtol);
139139

140-
single_shock_emission(fwd_shock, coord, t_obs, nu_obs, observer, fwd_rad, flux_dict, "", return_trace);
140+
single_shock_emission(fwd_shock, coord, t_obs, nu_obs, observer, fwd_rad, flux_dict, "", serilized);
141141

142-
single_shock_emission(rvs_shock, coord, t_obs, nu_obs, observer, rvs_rad, flux_dict, "_rvs", return_trace);
142+
single_shock_emission(rvs_shock, coord, t_obs, nu_obs, observer, rvs_rad, flux_dict, "_rvs", serilized);
143143

144144
return flux_dict;
145145
}
146146
}
147147

148-
auto PyModel::specific_flux(PyArray const& t, PyArray const& nu) -> FluxDict {
148+
auto PyModel::specific_flux_sorted_series(PyArray const& t, PyArray const& nu) -> FluxDict {
149149
Array t_obs = t * unit::sec;
150150
Array nu_obs = nu * unit::Hz;
151-
bool return_trace = true;
151+
bool serilized = true;
152152

153153
if (t_obs.size() != nu_obs.size()) {
154154
throw std::invalid_argument(
155155
"time and frequency arrays must have the same size\n"
156-
"If you intend to get matrix output, use `specific_flux_matrix` instead");
156+
"If you intend to get matrix-like output, use the generic `specific_flux` instead");
157157
}
158158

159-
return compute_specific_flux(t_obs, nu_obs, return_trace);
159+
return compute_specific_flux(t_obs, nu_obs, serilized);
160160
}
161161

162-
auto PyModel::specific_flux_matrix(PyArray const& t, PyArray const& nu) -> FluxDict {
162+
auto PyModel::specific_flux_series(PyArray const& t, PyArray const& nu) -> FluxDict {
163+
Array t_obs = t * unit::sec;
164+
Array nu_obs = nu * unit::Hz;
165+
bool serilized = true;
166+
167+
if (t_obs.size() != nu_obs.size()) {
168+
throw std::invalid_argument(
169+
"time and frequency arrays must have the same size\n"
170+
"If you intend to get matrix-like output, use the generic `specific_flux` instead");
171+
}
172+
173+
return compute_specific_flux(t_obs, nu_obs, serilized);
174+
}
175+
176+
auto PyModel::specific_flux(PyArray const& t, PyArray const& nu) -> FluxDict {
163177
Array t_obs = t * unit::sec;
164178
Array nu_obs = nu * unit::Hz;
165179
bool return_trace = false;

0 commit comments

Comments
 (0)