redback 1.1__py3-none-any.whl → 1.12.1__py3-none-any.whl

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 (40) hide show
  1. redback/__init__.py +1 -1
  2. redback/filters.py +57 -45
  3. redback/likelihoods.py +274 -6
  4. redback/model_library.py +2 -2
  5. redback/plotting.py +5 -3
  6. redback/priors/blackbody_spectrum_at_z.prior +3 -0
  7. redback/priors/bpl_cooling_envelope.prior +9 -0
  8. redback/priors/gaussianrise_cooling_envelope.prior +1 -5
  9. redback/priors/gaussianrise_cooling_envelope_bolometric.prior +1 -5
  10. redback/priors/powerlaw_plus_blackbody.prior +12 -0
  11. redback/priors/powerlaw_plus_blackbody_spectrum_at_z.prior +13 -0
  12. redback/priors/salt2.prior +6 -0
  13. redback/priors/shock_cooling_and_arnett_bolometric.prior +11 -0
  14. redback/priors/smooth_exponential_powerlaw_cooling_envelope_bolometric.prior +9 -0
  15. redback/priors/sn_nickel_fallback.prior +9 -0
  16. redback/priors/wr_bh_merger.prior +10 -0
  17. redback/priors/wr_bh_merger_bolometric.prior +8 -0
  18. redback/priors.py +14 -3
  19. redback/sed.py +185 -41
  20. redback/simulate_transients.py +13 -3
  21. redback/tables/filters.csv +260 -258
  22. redback/tables/qdot_rosswogkorobkin24.npz +0 -0
  23. redback/transient_models/afterglow_models.py +32 -16
  24. redback/transient_models/combined_models.py +16 -11
  25. redback/transient_models/extinction_models.py +310 -84
  26. redback/transient_models/gaussianprocess_models.py +1 -12
  27. redback/transient_models/kilonova_models.py +3 -3
  28. redback/transient_models/phase_models.py +97 -43
  29. redback/transient_models/phenomenological_models.py +172 -0
  30. redback/transient_models/spectral_models.py +101 -0
  31. redback/transient_models/stellar_interaction_models.py +254 -0
  32. redback/transient_models/supernova_models.py +349 -62
  33. redback/transient_models/tde_models.py +193 -54
  34. redback/utils.py +34 -7
  35. {redback-1.1.dist-info → redback-1.12.1.dist-info}/METADATA +7 -4
  36. {redback-1.1.dist-info → redback-1.12.1.dist-info}/RECORD +39 -28
  37. {redback-1.1.dist-info → redback-1.12.1.dist-info}/WHEEL +1 -1
  38. redback/tables/qdot_rosswogkorobkin24.pck +0 -0
  39. {redback-1.1.dist-info → redback-1.12.1.dist-info}/licenses/LICENCE.md +0 -0
  40. {redback-1.1.dist-info → redback-1.12.1.dist-info}/top_level.txt +0 -0
@@ -53,15 +53,20 @@ def t0_base_model(time, t0, **kwargs):
53
53
  return output
54
54
 
55
55
 
56
- def _t0_with_extinction(time, t0, av, model_type='supernova', **kwargs):
56
+ def _t0_with_extinction(time, t0, av_host, model_type='supernova', **kwargs):
57
57
  """
58
58
  :param time: time in mjd
59
59
  :param t0: start time in mjd
60
- :param av: absolute mag extinction
61
- :param model_type: what type of transient extinction function to use
62
- :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model']
63
- and r_v, default is 3.1
64
- :return: flux_density or magnitude depending on kwargs['output_format']
60
+ :param av_host: V-band extinction from host galaxy in magnitudes
61
+ :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model'] plus:
62
+ - redshift: source redshift (required)
63
+ - av_mw: MW V-band extinction in magnitudes (default 0.0)
64
+ - rv_host: host R_V parameter (default 3.1)
65
+ - rv_mw: MW R_V parameter (default 3.1)
66
+ - host_law: host extinction law (default 'fitzpatrick99')
67
+ - mw_law: MW extinction law (default 'fitzpatrick99')
68
+ Available extinction laws: 'fitzpatrick99', 'fm07', 'calzetti00', 'odonnell94', 'ccm89'
69
+ :return: flux_density or magnitude depending on kwargs['output_format'] with extinction applied
65
70
  """
66
71
  output = namedtuple('output', ['time', 'observable'])
67
72
  function = extinction_model_functions[model_type]
@@ -70,7 +75,7 @@ def _t0_with_extinction(time, t0, av, model_type='supernova', **kwargs):
70
75
  time = (time - t0).to(uu.day).value
71
76
  transient_time = time[time >= 0.0]
72
77
  bad_time = time[time < 0.0]
73
- output_real = function(transient_time, av=av, **kwargs)
78
+ output_real = function(transient_time, av_host=av_host, **kwargs)
74
79
  if kwargs['output_format'] == 'magnitude':
75
80
  output_fake = np.zeros(len(bad_time)) + 5000
76
81
  else:
@@ -80,83 +85,132 @@ def _t0_with_extinction(time, t0, av, model_type='supernova', **kwargs):
80
85
  return output
81
86
 
82
87
  @citation_wrapper('redback')
83
- def t0_afterglow_extinction(time, t0, av, **kwargs):
88
+ def t0_afterglow_extinction(time, t0, av_host, **kwargs):
84
89
  """
90
+ Afterglow model with t0 parameter and host/MW extinction
91
+
85
92
  :param time: time in mjd
86
93
  :param t0: start time in mjd
87
- :param av: absolute mag extinction
88
- :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model']
89
- and r_v, default is 3.1
90
- :return: flux_density or magnitude depending on kwargs['output_format']
94
+ :param av_host: V-band extinction from host galaxy in magnitudes
95
+ :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model'] plus:
96
+ - redshift: source redshift (required)
97
+ - av_mw: MW V-band extinction in magnitudes (default 0.0)
98
+ - rv_host: host R_V parameter (default 3.1)
99
+ - rv_mw: MW R_V parameter (default 3.1)
100
+ - host_law: host extinction law (default 'fitzpatrick99')
101
+ - mw_law: MW extinction law (default 'fitzpatrick99')
102
+ Available extinction laws: 'fitzpatrick99', 'fm07', 'calzetti00', 'odonnell94', 'ccm89'
103
+ :return: flux_density or magnitude depending on kwargs['output_format'] with extinction applied
91
104
  """
92
- summary = _t0_with_extinction(time=time, t0=t0, av=av, model_type='afterglow', **kwargs)
105
+ summary = _t0_with_extinction(time=time, t0=t0, av_host=av_host, model_type='afterglow', **kwargs)
93
106
  return summary.observable
94
107
 
95
108
  @citation_wrapper('redback')
96
- def t0_supernova_extinction(time, t0, av, **kwargs):
109
+ def t0_supernova_extinction(time, t0, av_host, **kwargs):
97
110
  """
111
+ Supernova model with t0 parameter and host/MW extinction
112
+
98
113
  :param time: time in mjd
99
114
  :param t0: start time in mjd
100
- :param av: absolute mag extinction
101
- :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model']
102
- and r_v, default is 3.1
103
- :return: flux_density or magnitude depending on kwargs['output_format']
115
+ :param av_host: V-band extinction from host galaxy in magnitudes
116
+ :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model'] plus:
117
+ - redshift: source redshift (required)
118
+ - av_mw: MW V-band extinction in magnitudes (default 0.0)
119
+ - rv_host: host R_V parameter (default 3.1)
120
+ - rv_mw: MW R_V parameter (default 3.1)
121
+ - host_law: host extinction law (default 'fitzpatrick99')
122
+ - mw_law: MW extinction law (default 'fitzpatrick99')
123
+ Available extinction laws: 'fitzpatrick99', 'fm07', 'calzetti00', 'odonnell94', 'ccm89'
124
+ :return: flux_density or magnitude depending on kwargs['output_format'] with extinction applied
104
125
  """
105
- summary = _t0_with_extinction(time=time, t0=t0, av=av, model_type='supernova', **kwargs)
126
+ summary = _t0_with_extinction(time=time, t0=t0, av_host=av_host, model_type='supernova', **kwargs)
106
127
  return summary.observable
107
128
 
108
129
  @citation_wrapper('redback')
109
- def t0_kilonova_extinction(time, t0, av, **kwargs):
130
+ def t0_kilonova_extinction(time, t0, av_host, **kwargs):
110
131
  """
132
+ Kilonova model with t0 parameter and host/MW extinction
133
+
111
134
  :param time: time in mjd
112
135
  :param t0: start time in mjd
113
- :param av: absolute mag extinction
114
- :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model']
115
- and r_v, default is 3.1
116
- :return: flux_density or magnitude depending on kwargs['output_format']
136
+ :param av_host: V-band extinction from host galaxy in magnitudes
137
+ :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model'] plus:
138
+ - redshift: source redshift (required)
139
+ - av_mw: MW V-band extinction in magnitudes (default 0.0)
140
+ - rv_host: host R_V parameter (default 3.1)
141
+ - rv_mw: MW R_V parameter (default 3.1)
142
+ - host_law: host extinction law (default 'fitzpatrick99')
143
+ - mw_law: MW extinction law (default 'fitzpatrick99')
144
+ Available extinction laws: 'fitzpatrick99', 'fm07', 'calzetti00', 'odonnell94', 'ccm89'
145
+ :return: flux_density or magnitude depending on kwargs['output_format'] with extinction applied
117
146
  """
118
- summary = _t0_with_extinction(time=time, t0=t0, av=av, model_type='kilonova', **kwargs)
147
+ summary = _t0_with_extinction(time=time, t0=t0, av_host=av_host, model_type='kilonova', **kwargs)
119
148
  return summary.observable
120
149
 
121
150
  @citation_wrapper('redback')
122
- def t0_tde_extinction(time, t0, av, **kwargs):
151
+ def t0_tde_extinction(time, t0, av_host, **kwargs):
123
152
  """
153
+ TDE model with t0 parameter and host/MW extinction
154
+
124
155
  :param time: time in mjd
125
156
  :param t0: start time in mjd
126
- :param av: absolute mag extinction
127
- :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model']
128
- and r_v, default is 3.1
129
- :return: flux_density or magnitude depending on kwargs['output_format']
157
+ :param av_host: V-band extinction from host galaxy in magnitudes
158
+ :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model'] plus:
159
+ - redshift: source redshift (required)
160
+ - av_mw: MW V-band extinction in magnitudes (default 0.0)
161
+ - rv_host: host R_V parameter (default 3.1)
162
+ - rv_mw: MW R_V parameter (default 3.1)
163
+ - host_law: host extinction law (default 'fitzpatrick99')
164
+ - mw_law: MW extinction law (default 'fitzpatrick99')
165
+ - peak_time_mjd: peak time in mjd (if provided, will be converted to peak_time relative to t0)
166
+ Available extinction laws: 'fitzpatrick99', 'fm07', 'calzetti00', 'odonnell94', 'ccm89'
167
+ :return: flux_density or magnitude depending on kwargs['output_format'] with extinction applied
130
168
  """
131
169
  if 'peak_time_mjd' in kwargs:
132
170
  kwargs['peak_time'] = kwargs['peak_time_mjd'] - t0
133
- summary = _t0_with_extinction(time=time, t0=t0, av=av, model_type='tde', **kwargs)
171
+ summary = _t0_with_extinction(time=time, t0=t0, av_host=av_host, model_type='tde', **kwargs)
134
172
  return summary.observable
135
173
 
136
174
  @citation_wrapper('redback')
137
- def t0_magnetar_driven_extinction(time, t0, av, **kwargs):
175
+ def t0_magnetar_driven_extinction(time, t0, av_host, **kwargs):
138
176
  """
177
+ Magnetar-driven model with t0 parameter and host/MW extinction
178
+
139
179
  :param time: time in mjd
140
180
  :param t0: start time in mjd
141
- :param av: absolute mag extinction
142
- :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model']
143
- and r_v, default is 3.1
144
- :return: flux_density or magnitude depending on kwargs['output_format']
181
+ :param av_host: V-band extinction from host galaxy in magnitudes
182
+ :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model'] plus:
183
+ - redshift: source redshift (required)
184
+ - av_mw: MW V-band extinction in magnitudes (default 0.0)
185
+ - rv_host: host R_V parameter (default 3.1)
186
+ - rv_mw: MW R_V parameter (default 3.1)
187
+ - host_law: host extinction law (default 'fitzpatrick99')
188
+ - mw_law: MW extinction law (default 'fitzpatrick99')
189
+ Available extinction laws: 'fitzpatrick99', 'fm07', 'calzetti00', 'odonnell94', 'ccm89'
190
+ :return: flux_density or magnitude depending on kwargs['output_format'] with extinction applied
145
191
  """
146
- summary = _t0_with_extinction(time=time, t0=t0, av=av, model_type='magnetar_driven', **kwargs)
192
+ summary = _t0_with_extinction(time=time, t0=t0, av_host=av_host, model_type='magnetar_driven', **kwargs)
147
193
  return summary.observable
148
194
 
149
195
  @citation_wrapper('redback')
150
- def t0_shock_powered_extinction(time, t0, av, **kwargs):
196
+ def t0_shock_powered_extinction(time, t0, av_host, **kwargs):
151
197
  """
198
+ Shock-powered model with t0 parameter and host/MW extinction
199
+
152
200
  :param time: time in mjd
153
201
  :param t0: start time in mjd
154
- :param av: absolute mag extinction
155
- :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model']
156
- and r_v, default is 3.1
157
- :return: flux or magnitude depending on kwargs['output_format']
202
+ :param av_host: V-band extinction from host galaxy in magnitudes
203
+ :param kwargs: Must be all the parameters required by the base_model specified using kwargs['base_model'] plus:
204
+ - redshift: source redshift (required)
205
+ - av_mw: MW V-band extinction in magnitudes (default 0.0)
206
+ - rv_host: host R_V parameter (default 3.1)
207
+ - rv_mw: MW R_V parameter (default 3.1)
208
+ - host_law: host extinction law (default 'fitzpatrick99')
209
+ - mw_law: MW extinction law (default 'fitzpatrick99')
210
+ Available extinction laws: 'fitzpatrick99', 'fm07', 'calzetti00', 'odonnell94', 'ccm89'
211
+ :return: flux_density or magnitude depending on kwargs['output_format'] with extinction applied
158
212
  """
159
- summary = _t0_with_extinction(time=time, t0=t0, av=av, model_type='shock_powered', **kwargs)
213
+ summary = _t0_with_extinction(time=time, t0=t0, av_host=av_host, model_type='shock_powered', **kwargs)
160
214
  return summary.observable
161
215
 
162
216
  @citation_wrapper('https://ui.adsabs.harvard.edu/abs/2021arXiv210601556S/abstract')
@@ -3,6 +3,33 @@ from redback.utils import citation_wrapper
3
3
  from redback.constants import speed_of_light_si
4
4
 
5
5
 
6
+ def smooth_exponential_powerlaw(time, a_1, tpeak, alpha_1, alpha_2, smoothing_factor, **kwargs):
7
+ """
8
+ Smoothed version of exponential power law
9
+
10
+ :param time: time array in seconds
11
+ :param a_1: exponential amplitude scale
12
+ :param alpha_1: first exponent
13
+ :param alpha_2: second exponent
14
+ :param tpeak: peak time in seconds
15
+ :param smoothing_factor: controls transition smoothness (higher = smoother)
16
+ :param kwargs: Additional parameters
17
+ :return: In whatever units set by a_1
18
+ """
19
+ t_norm = time / tpeak
20
+
21
+ # Smooth transition function using tanh or similar
22
+ transition = 0.5 * (1 + np.tanh(smoothing_factor * np.log(t_norm)))
23
+
24
+ # Pre-peak behavior
25
+ pre_peak = a_1 * (t_norm ** alpha_1)
26
+
27
+ # Post-peak behavior
28
+ post_peak = a_1 * (t_norm ** alpha_2)
29
+
30
+ # Smooth combination
31
+ return pre_peak * (1 - transition) + post_peak * transition
32
+
6
33
  def exp_rise_powerlaw_decline(t, t0, m_peak, tau_rise, alpha, t_peak, **kwargs):
7
34
  """
8
35
  Compute a smooth light-curve model (in magnitudes) with an exponential rise
@@ -173,6 +200,151 @@ def villar_sne(time, aa, cc, t0, tau_rise, tau_fall, gamma, nu, **kwargs):
173
200
  flux[mask2] = norm[mask2] * ((1 - nu) * np.exp(-((time[mask2] - t0 - gamma)/tau_fall)))
174
201
  return np.concatenate((flux[mask1], flux[mask2]))
175
202
 
203
+
204
+ def powerlaw_plus_blackbody(time, redshift, pl_amplitude, pl_slope, pl_evolution_index, temperature_0, radius_0,
205
+ temp_rise_index, temp_decline_index, temp_peak_time,
206
+ radius_rise_index, radius_decline_index, radius_peak_time,
207
+ reference_time=1.0, **kwargs):
208
+ """
209
+ Power law + blackbody spectrum with piecewise evolving temperature and radius
210
+
211
+ :param time: time in observer frame in days
212
+ :param redshift: source redshift
213
+ :param pl_amplitude: power law amplitude at reference wavelength at reference_time (erg/s/cm^2/Angstrom)
214
+ :param pl_slope: power law slope (F_lambda ∝ lambda^slope)
215
+ :param pl_evolution_index: power law time evolution F_pl(t) ∝ t^(-pl_evolution_index)
216
+ :param temperature_0: initial blackbody temperature in Kelvin at reference_time
217
+ :param radius_0: initial blackbody radius in cm at reference_time
218
+ :param temp_rise_index: temperature rise T(t) ∝ t^temp_rise_index for t < temp_peak_time
219
+ :param temp_decline_index: temperature decline T(t) ∝ t^(-temp_decline_index) for t > temp_peak_time
220
+ :param temp_peak_time: time in days when temperature peaks
221
+ :param radius_rise_index: radius rise R(t) ∝ t^radius_rise_index for t < radius_peak_time
222
+ :param radius_decline_index: radius decline R(t) ∝ t^(-radius_decline_index) for t > radius_peak_time
223
+ :param radius_peak_time: time in days when radius peaks
224
+ :param reference_time: reference time for temperature_0, radius_0, and pl_amplitude in days (defaults to 1.0)
225
+ :param kwargs: Additional parameters
226
+ :param reference_wavelength: wavelength for power law amplitude normalization in Angstroms (default 5000)
227
+ :param frequency: Required if output_format is 'flux_density'
228
+ :param bands: Required if output_format is 'magnitude' or 'flux'
229
+ :param output_format: 'flux_density', 'magnitude', 'spectra', 'flux', 'sncosmo_source'
230
+ :param lambda_array: Optional wavelength array in Angstroms to evaluate SED
231
+ :param cosmology: Cosmology object for luminosity distance calculation
232
+ :return: set by output format - 'flux_density', 'magnitude', 'spectra', 'flux', 'sncosmo_source'
233
+ """
234
+ from astropy.cosmology import Planck18 as cosmo
235
+ from astropy import units as uu
236
+ from redback.utils import lambda_to_nu, calc_kcorrected_properties
237
+ import redback.sed as sed
238
+ from collections import namedtuple
239
+
240
+ cosmology = kwargs.get('cosmology', cosmo)
241
+ dl = cosmology.luminosity_distance(redshift).cgs.value
242
+ reference_wavelength = kwargs.get('reference_wavelength', 5000.0) # Angstroms
243
+
244
+ if kwargs['output_format'] == 'flux_density':
245
+ frequency = kwargs['frequency']
246
+ frequency, time = calc_kcorrected_properties(frequency=frequency, redshift=redshift, time=time)
247
+
248
+ # Calculate evolving temperature and radius
249
+ temperature, radius = _powerlaw_blackbody_evolution(time=time, temperature_0=temperature_0, radius_0=radius_0,
250
+ temp_rise_index=temp_rise_index,
251
+ temp_decline_index=temp_decline_index,
252
+ temp_peak_time=temp_peak_time,
253
+ radius_rise_index=radius_rise_index,
254
+ radius_decline_index=radius_decline_index,
255
+ radius_peak_time=radius_peak_time,
256
+ reference_time=reference_time)
257
+
258
+ # Create combined SED with time-evolving power law
259
+ sed_combined = sed.PowerlawPlusBlackbody(temperature=temperature, r_photosphere=radius,
260
+ pl_amplitude=pl_amplitude, pl_slope=pl_slope,
261
+ pl_evolution_index=pl_evolution_index, time=time,
262
+ reference_wavelength=reference_wavelength,
263
+ frequency=frequency, luminosity_distance=dl)
264
+ flux_density = sed_combined.flux_density
265
+ return flux_density.to(uu.mJy).value
266
+ else:
267
+ time_obs = time
268
+ lambda_observer_frame = kwargs.get('lambda_array', np.geomspace(100, 60000, 100))
269
+ time_temp = np.geomspace(0.1, 3000, 300) # in days
270
+ time_observer_frame = time_temp * (1. + redshift)
271
+ frequency, time = calc_kcorrected_properties(frequency=lambda_to_nu(lambda_observer_frame),
272
+ redshift=redshift, time=time_observer_frame)
273
+
274
+ # Calculate evolving temperature and radius
275
+ temperature, radius = _powerlaw_blackbody_evolution(time=time, temperature_0=temperature_0, radius_0=radius_0,
276
+ temp_rise_index=temp_rise_index,
277
+ temp_decline_index=temp_decline_index,
278
+ temp_peak_time=temp_peak_time,
279
+ radius_rise_index=radius_rise_index,
280
+ radius_decline_index=radius_decline_index,
281
+ radius_peak_time=radius_peak_time,
282
+ reference_time=reference_time)
283
+
284
+ # Create combined SED with time-evolving power law
285
+ sed_combined = sed.PowerlawPlusBlackbody(temperature=temperature, r_photosphere=radius,
286
+ pl_amplitude=pl_amplitude, pl_slope=pl_slope,
287
+ pl_evolution_index=pl_evolution_index, time=time,
288
+ reference_wavelength=reference_wavelength,
289
+ frequency=frequency[:, None], luminosity_distance=dl)
290
+ fmjy = sed_combined.flux_density.T
291
+ spectra = fmjy.to(uu.mJy).to(uu.erg / uu.cm ** 2 / uu.s / uu.Angstrom,
292
+ equivalencies=uu.spectral_density(wav=lambda_observer_frame * uu.Angstrom))
293
+ if kwargs['output_format'] == 'spectra':
294
+ return namedtuple('output', ['time', 'lambdas', 'spectra'])(time=time_observer_frame,
295
+ lambdas=lambda_observer_frame,
296
+ spectra=spectra)
297
+ else:
298
+ return sed.get_correct_output_format_from_spectra(time=time_obs, time_eval=time_observer_frame,
299
+ spectra=spectra, lambda_array=lambda_observer_frame,
300
+ **kwargs)
301
+
302
+
303
+ def _powerlaw_blackbody_evolution(time, temperature_0, radius_0, temp_rise_index, temp_decline_index,
304
+ temp_peak_time, radius_rise_index, radius_decline_index, radius_peak_time,
305
+ reference_time=1.0, **kwargs):
306
+ """
307
+ Calculate evolving temperature and radius with piecewise power-law evolution
308
+
309
+ :param time: time array in days
310
+ :param temperature_0: initial temperature at reference_time
311
+ :param radius_0: initial radius at reference_time
312
+ :param temp_rise_index: temperature rise index
313
+ :param temp_decline_index: temperature decline index
314
+ :param temp_peak_time: time when temperature peaks
315
+ :param radius_rise_index: radius rise index
316
+ :param radius_decline_index: radius decline index
317
+ :param radius_peak_time: time when radius peaks
318
+ :param reference_time: reference time for temperature_0 and radius_0 (defaults to 1.0 day)
319
+ :return: temperature and radius values (scalars if time is scalar)
320
+ """
321
+ time = np.atleast_1d(time)
322
+
323
+ # Temperature evolution
324
+ temp_peak = temperature_0 * (temp_peak_time / reference_time) ** temp_rise_index
325
+ rise_mask_temp = time <= temp_peak_time
326
+ decline_mask_temp = ~rise_mask_temp
327
+
328
+ temperature = np.zeros_like(time)
329
+ temperature[rise_mask_temp] = temperature_0 * (time[rise_mask_temp] / reference_time) ** temp_rise_index
330
+ temperature[decline_mask_temp] = temp_peak * (time[decline_mask_temp] / temp_peak_time) ** (-temp_decline_index)
331
+
332
+ # Radius evolution
333
+ radius_peak = radius_0 * (radius_peak_time / reference_time) ** radius_rise_index
334
+ rise_mask_radius = time <= radius_peak_time
335
+ decline_mask_radius = ~rise_mask_radius
336
+
337
+ radius = np.zeros_like(time)
338
+ radius[rise_mask_radius] = radius_0 * (time[rise_mask_radius] / reference_time) ** radius_rise_index
339
+ radius[decline_mask_radius] = radius_peak * (time[decline_mask_radius] / radius_peak_time) ** (
340
+ -radius_decline_index)
341
+
342
+ # Return scalars if input was scalar
343
+ if len(time) == 1:
344
+ return temperature[0], radius[0]
345
+ else:
346
+ return temperature, radius
347
+
176
348
  def fallback_lbol(time, logl1, tr, **kwargs):
177
349
  """
178
350
  :param time: time in seconds
@@ -80,3 +80,104 @@ def blackbody_spectrum_with_absorption_and_emission_lines(angstroms, redshift,
80
80
  fp1 = pm.line_spectrum_with_velocity_dispersion(angstroms, lc1, ls1, v1)
81
81
  fp2 = pm.line_spectrum_with_velocity_dispersion(angstroms, lc2, ls2, v2)
82
82
  return flux + fp1 - fp2
83
+
84
+ def blackbody_spectrum_at_z(angstroms, redshift, rph, temp, **kwargs):
85
+ """
86
+ A blackbody spectrum at a given redshift, properly accounting for redshift effects.
87
+
88
+ :param angstroms: wavelength array in angstroms in obs frame
89
+ :param redshift: redshift
90
+ :param rph: photosphere radius in cm (rest frame)
91
+ :param temp: photosphere temperature in Kelvin (rest frame)
92
+ :return: flux in ergs/s/cm^2/angstrom in obs frame
93
+ """
94
+ cosmology = kwargs.get('cosmology', cosmo)
95
+ dl = cosmology.luminosity_distance(redshift).cgs.value
96
+
97
+ # Convert observed wavelengths to rest frame
98
+ angstroms_rest = angstroms / (1 + redshift)
99
+
100
+ # Calculate blackbody in rest frame
101
+ flux_rest = _get_blackbody_spectrum(angstrom=angstroms_rest, distance=dl,
102
+ r_photosphere=rph, temperature=temp)
103
+
104
+ # Apply redshift corrections:
105
+ # - Surface brightness dimming: factor of (1+z)
106
+ # - Wavelength interval stretching: dλ_obs = dλ_rest × (1+z)
107
+ # Combined effect: divide by (1+z)
108
+ flux_obs = flux_rest / (1 + redshift)
109
+
110
+ return flux_obs
111
+
112
+ def powerlaw_plus_blackbody_spectrum_at_z(angstroms, redshift, pl_amplitude, pl_slope, pl_evolution_index,
113
+ temperature_0, radius_0, temp_rise_index, temp_decline_index,
114
+ temp_peak_time, radius_rise_index, radius_decline_index,
115
+ radius_peak_time, time, **kwargs):
116
+ """
117
+ A powerlaw + blackbody spectrum at a given redshift and time, properly accounting for redshift effects.
118
+
119
+ :param angstroms: wavelength array in angstroms in obs frame
120
+ :param redshift: source redshift
121
+ :param pl_amplitude: power law amplitude at reference wavelength at t=1 day (erg/s/cm^2/Angstrom)
122
+ :param pl_slope: power law slope (F_lambda ∝ lambda^slope)
123
+ :param pl_evolution_index: power law time evolution F_pl(t) ∝ t^(-pl_evolution_index)
124
+ :param temperature_0: initial blackbody temperature in Kelvin at t=1 day (rest frame)
125
+ :param radius_0: initial blackbody radius in cm at t=1 day (rest frame)
126
+ :param temp_rise_index: temperature rise T(t) ∝ t^temp_rise_index for t < temp_peak_time
127
+ :param temp_decline_index: temperature decline T(t) ∝ t^(-temp_decline_index) for t > temp_peak_time
128
+ :param temp_peak_time: time in days when temperature peaks
129
+ :param radius_rise_index: radius rise R(t) ∝ t^radius_rise_index for t < radius_peak_time
130
+ :param radius_decline_index: radius decline R(t) ∝ t^(-radius_decline_index) for t > radius_peak_time
131
+ :param radius_peak_time: time in days when radius peaks
132
+ :param time: time in observer frame in days
133
+ :param kwargs: Additional parameters
134
+ :param reference_wavelength: wavelength for power law amplitude normalization in Angstroms (default 5000)
135
+ :param cosmology: Cosmology object for luminosity distance calculation
136
+ :return: flux in ergs/s/cm^2/angstrom in obs frame
137
+ """
138
+ cosmology = kwargs.get('cosmology', cosmo)
139
+ dl = cosmology.luminosity_distance(redshift).cgs.value
140
+ reference_wavelength = kwargs.get('reference_wavelength', 5000.0) # Angstroms
141
+
142
+ # Convert observed wavelengths to rest frame
143
+ angstroms_rest = angstroms / (1 + redshift)
144
+
145
+ # Convert observed time to rest frame
146
+ time_rest = time / (1 + redshift)
147
+
148
+ # Convert wavelengths to frequencies for the SED calculation
149
+ frequency_rest = lambda_to_nu(wavelength=angstroms_rest)
150
+
151
+ # Calculate evolving temperature and radius at this time
152
+ temperature, radius = pm._powerlaw_blackbody_evolution(time=time_rest, temperature_0=temperature_0, radius_0=radius_0,
153
+ temp_rise_index=temp_rise_index,
154
+ temp_decline_index=temp_decline_index,
155
+ temp_peak_time=temp_peak_time,
156
+ radius_rise_index=radius_rise_index,
157
+ radius_decline_index=radius_decline_index,
158
+ radius_peak_time=radius_peak_time)
159
+
160
+ # Create combined SED in rest frame
161
+ sed_combined = sed.PowerlawPlusBlackbody(temperature=temperature, r_photosphere=radius,
162
+ pl_amplitude=pl_amplitude, pl_slope=pl_slope,
163
+ pl_evolution_index=pl_evolution_index, time=time_rest,
164
+ reference_wavelength=reference_wavelength,
165
+ frequency=frequency_rest, luminosity_distance=dl)
166
+
167
+ # Get flux density in rest frame (F_nu)
168
+ flux_density_rest = sed_combined.flux_density # erg/s/cm^2/Hz
169
+
170
+ # Convert from F_nu to F_lambda in rest frame
171
+ flux_lambda_rest = fnu_to_flambda(f_nu=flux_density_rest, wavelength_A=angstroms_rest)
172
+
173
+ # Apply redshift corrections to get observed frame flux:
174
+ # - Surface brightness dimming: factor of (1+z)
175
+ # - Wavelength interval stretching: dλ_obs = dλ_rest × (1+z)
176
+ # Combined effect: divide by (1+z)
177
+ flux_lambda_obs = flux_lambda_rest / (1 + redshift)
178
+
179
+ # Convert to plain values if needed
180
+ if hasattr(flux_lambda_obs, 'value'):
181
+ return flux_lambda_obs.value
182
+ else:
183
+ return flux_lambda_obs