1
1
import numpy as np
2
- from astropy .units import Quantity
2
+ from astropy .units import Quantity , Unit
3
3
from pygments .styles import vs
4
-
5
- from agnpy import InterpolatedDistribution , ParticleDistribution
4
+ from agnpy import InterpolatedDistribution
5
+ from agnpy . time_evolution . types import BinsWithDensities , FnParams
6
6
from agnpy .utils .conversion import mec2
7
7
from numpy ._typing import NDArray
8
8
from scipy .interpolate import PchipInterpolator
@@ -16,36 +16,46 @@ def update_bin_ub(gamma_bins_from, gamma_bins_to, low_change_rates_mask = None):
16
16
gamma_bins_to [~ low_change_rates_mask ] = gamma_bins_from [~ low_change_rates_mask ] * (1 + bin_size_factor )
17
17
18
18
19
- def recalc_new_rates (gm_bins , functions , unit , previous_rates = None , recalc_mask = None ):
19
+ def recalc_new_rates (gamma , functions , unit , dens , dens_groups , previous_rates = None , recalc_mask = None ):
20
20
"""
21
21
Calculates (or recalculates) the energy change rates or injection rates.
22
22
If the mask is provided, only unmasked elements will be recalculated, and previous_rates will be used for masked elements.
23
- gamma_bins - array of shape (N,) consisting of gamma values (lower bounds of gamma bins), or (2N,) consisting of inlined pairs of gamma values (lower and upper bounds of gamma bins)
23
+ gamma - array of shape (N,) consisting of gamma values (lower bounds of gamma bins), or (2N,) consisting of inlined pairs of gamma values (lower and upper bounds of gamma bins)
24
24
previous_rates - optional dict of arrays of shape (N,) or (2N,), consisting of most recently calculated rates corresponding to gamma_bins values
25
- low_change_rates_mask - optional array of shape (N,) a mask of bins that do not need recalculation
25
+ recalc_mask - optional array of shape (N,) a mask of bins that need recalculation
26
26
"""
27
27
if previous_rates is None :
28
28
previous_rates = {}
29
- mask = np .ones_like (gm_bins , dtype = bool ) if recalc_mask is None else \
30
- np .repeat (recalc_mask , 2 ) if len (recalc_mask ) * 2 == len (gm_bins ) else recalc_mask
31
- new_injection_rates = {}
29
+ mask_dens = np .ones_like (dens , dtype = bool ) if recalc_mask is None else recalc_mask
30
+ dens_masked = dens [mask_dens ]
31
+ dens_groups_masked = remap_subgroups_density (mask_to_mapping (mask_dens ), dens_groups )
32
+ mask = np .ones_like (gamma , dtype = bool ) if recalc_mask is None else \
33
+ np .repeat (recalc_mask , 2 ) if len (recalc_mask ) * 2 == len (gamma ) else recalc_mask
34
+ new_rates = {}
32
35
for label , fn in functions .items ():
33
- new_injection_rates [label ] = np .zeros_like (gm_bins ) * unit \
36
+ new_rates [label ] = np .zeros_like (gamma ) * unit \
34
37
if previous_rates .get (label ) is None \
35
38
else previous_rates [label ].copy ()
36
- new_injection_rates [label ][mask ] = fn (gm_bins [mask ]).to (unit )
37
- return new_injection_rates
39
+ new_rates [label ][mask ] = fn (FnParams ( gamma [mask ], dens_masked , dens_groups_masked ) ).to (unit )
40
+ return new_rates
38
41
39
42
40
43
def sum_change_rates_at_index (change_rates , index ):
41
44
return np .sum ([arr [index ].value for arr in change_rates .values ()])
42
45
43
46
44
- def sum_change_rates (change_rates , shape , unit ):
45
- result = np .zeros (shape ) * unit
46
- for ch_rates in change_rates .values ():
47
- result += ch_rates .to (unit )
48
- return result
47
+ def sum_change_rates (change_rates , shape , unit , subgroups = None ):
48
+ if subgroups is None :
49
+ result = np .zeros (shape ) * unit
50
+ for ch_rates in change_rates .values ():
51
+ result += ch_rates .to (unit )
52
+ return Quantity ([result ])
53
+ else :
54
+ return Quantity ([
55
+ np .sum (Quantity ([change_rates [x ] for x in row if x in change_rates ]), axis = 0 )
56
+ if any (x in change_rates for x in row ) else np .zeros (shape ) * unit
57
+ for row in subgroups
58
+ ])
49
59
50
60
51
61
def remap (mapping , values , default_value = None ):
@@ -70,6 +80,17 @@ def remap_interlaced(mapping, values, default_value = None):
70
80
return interlace (remap (mapping , v1 , default_value ), remap (mapping , v2 , default_value ))
71
81
72
82
83
+ def remap_subgroups_density (mapping , subgroups_density ):
84
+ new_subgroups_density = subgroups_density [:,mapping ]
85
+ new_subgroups_density [0 , mapping == - 1 ] = 1.0
86
+ new_subgroups_density [1 :, mapping == - 1 ] = 0.0
87
+ return new_subgroups_density
88
+
89
+
90
+ def mask_to_mapping (mask ):
91
+ return np .arange (len (mask ))[mask ]
92
+
93
+
73
94
def combine_mapping (first_mapping , second_mapping ):
74
95
""" Combines two mappings, but with special treatment of "-1" value in the mapping:
75
96
-1 does not mean the last element; it means a "new element", at must be retained across the mappings
@@ -83,11 +104,20 @@ def to_erg(gamma_bins):
83
104
return (gamma_bins * mec2 ).to ("erg" )
84
105
85
106
86
- def recalc_gamma_bins_and_density (energy_bins , abs_energy_changes , density ):
107
+ def to_densities_grouped (density , subgroups_density ):
108
+ density_grouped = density * subgroups_density if subgroups_density is not None else density .reshape (
109
+ (1 , len (density )))
110
+ return density_grouped
111
+
112
+ def second_axis_size (a : np .ndarray ) -> int :
113
+ return a .shape [1 ] if a .ndim > 1 else 1
114
+
115
+
116
+ def recalc_gamma_bins_and_density (energy_bins , abs_energy_changes , density ) -> BinsWithDensities :
87
117
new_energy_bins , density_increase = recalc_energy_bins_and_density_increase (abs_energy_changes , energy_bins )
88
118
new_density = density * density_increase
89
119
new_gamma_bins = (new_energy_bins / mec2 ).to_value ('' )
90
- return new_gamma_bins , new_density
120
+ return BinsWithDensities ( new_gamma_bins , new_density )
91
121
92
122
93
123
def recalc_energy_bins_and_density_increase (abs_energy_changes , energy_bins ):
@@ -107,26 +137,35 @@ def recalc_energy_bins_and_density_increase(abs_energy_changes, energy_bins):
107
137
return new_energy_bins , density_increase
108
138
109
139
110
- def calc_new_low_change_rates_mask (energy_bins , density , abs_energy_change , abs_particle_scaling , abs_particle_injection ,
140
+ def calc_new_low_change_rates_mask (energy_bins , density , subgroups_density , abs_energy_change_grouped , abs_particle_scaling_grouped , abs_particle_injection_grouped ,
111
141
max_energy_change_per_interval , max_density_change_per_interval , max_abs_injection_per_interval ):
112
- if np .any ((density == 0 ) & (abs_particle_injection < 0 )):
113
- raise ValueError ("Particles cannot escape when density is zero" )
114
142
# The obvious hard limit for escape is -1.0, which means "all particles escaped"; more negative value would clearly
115
143
# mean we used too long time. The -0.5 is somewhat arbitrary, perhaps it should be configurable like other limits?
116
144
# But it is tricky how to configure it, because e.g. -1.1 value makes no physical sense, on the other hand +1.1 is completely fine.
117
145
max_escape = - 0.5
118
- relative_changes_bin_lb , relative_en_changes_bin_ub = deinterlace (abs_energy_change / energy_bins )
119
- rel_density_increase_from_energy_change = recalc_energy_bins_and_density_increase (abs_energy_change , energy_bins )[1 ]
120
- rel_density_change_from_injection = np .divide (density * abs_particle_scaling + abs_particle_injection , density ,
121
- out = Quantity (np .zeros_like (density .value , dtype = float )),
122
- where = density != 0 ).value
123
- rel_density_increase_combined = rel_density_change_from_injection * rel_density_increase_from_energy_change
124
- new_low_change_rates_mask = ((abs (relative_en_changes_bin_ub ) <= max_energy_change_per_interval ) &
125
- (~ np .isnan (rel_density_increase_from_energy_change )) &
126
- (abs (abs_particle_scaling ) <= max_density_change_per_interval ) &
127
- (abs (abs_particle_injection ) <= max_abs_injection_per_interval ) &
128
- (abs_particle_scaling >= max_escape ) &
129
- (abs (rel_density_increase_combined ) <= max_density_change_per_interval ))
146
+ new_low_change_rates_mask = np .ones (len (density ), dtype = bool )
147
+ density_grouped = to_densities_grouped (density , subgroups_density )
148
+ for i in (range (subgroups_density .shape [0 ])):
149
+ if np .any ((density_grouped [i ] == 0 ) & (abs_particle_injection_grouped [i ] < 0 )):
150
+ raise ValueError ("Particles cannot escape when density is zero" )
151
+
152
+ abs_energy_change = abs_energy_change_grouped [i ]
153
+ density = density_grouped [i ]
154
+ abs_particle_scaling = abs_particle_scaling_grouped [i ]
155
+ abs_particle_injection = abs_particle_injection_grouped [i ]
156
+ relative_changes_bin_lb , relative_en_changes_bin_ub = deinterlace (abs_energy_change / energy_bins )
157
+ rel_density_increase_from_energy_change = recalc_energy_bins_and_density_increase (abs_energy_change , energy_bins )[1 ]
158
+ rel_density_change_from_injection = np .divide (density * abs_particle_scaling + abs_particle_injection , density ,
159
+ out = Quantity (np .zeros_like (density .value , dtype = float )),
160
+ where = density != 0 ).value
161
+ rel_density_increase_combined = rel_density_change_from_injection * rel_density_increase_from_energy_change
162
+ group_mask = ((abs (relative_en_changes_bin_ub ) <= max_energy_change_per_interval ) &
163
+ (~ np .isnan (rel_density_increase_from_energy_change )) &
164
+ (abs (abs_particle_scaling ) <= max_density_change_per_interval ) &
165
+ (abs (abs_particle_injection ) <= max_abs_injection_per_interval ) &
166
+ (abs_particle_scaling >= max_escape ) &
167
+ (abs (rel_density_increase_combined ) <= max_density_change_per_interval ))
168
+ new_low_change_rates_mask &= group_mask
130
169
return new_low_change_rates_mask
131
170
132
171
@@ -313,6 +352,35 @@ def update_distribution(gamma_array, n_array, blob):
313
352
blob .n_e = InterpolatedDistribution (gamma_array_sorted , n_array_sorted , interpolator = PchipInterpolator )
314
353
315
354
355
+ def apply_time_eval (en_bins , density , subgroups_density , abs_en_changes_grouped , total_injection_grouped ):
356
+ new_bins_and_densities = [recalc_gamma_bins_and_density (en_bins , abs_en_changes_grouped [i ], density * subgroups_density [i ])
357
+ for i in range (len (subgroups_density ))]
358
+ for i in range (len (new_bins_and_densities )):
359
+ new_bins_and_densities [i ] = BinsWithDensities (new_bins_and_densities [i ].gamma_bins ,
360
+ new_bins_and_densities [i ].densities + total_injection_grouped [i ])
361
+
362
+ new_gm_bins = new_bins_and_densities [0 ].gamma_bins
363
+ new_gm_bins_lb , new_gm_bins_ub = deinterlace (new_gm_bins )
364
+
365
+ new_densities = np .zeros_like (subgroups_density ) * Unit ("cm-3" )
366
+ new_densities [0 ] = new_bins_and_densities [0 ].densities
367
+
368
+ for i in range (1 , len (new_bins_and_densities )):
369
+ new_gm_bins_subgroup , _ = deinterlace (new_bins_and_densities [i ].gamma_bins )
370
+ new_dens_subgroup = new_bins_and_densities [i ].densities
371
+ if np .all (new_dens_subgroup == 0 ) or np .all (new_bins_and_densities [i ].gamma_bins == new_gm_bins ):
372
+ new_densities [i ] = new_dens_subgroup
373
+ else :
374
+ distribution = InterpolatedDistribution (new_gm_bins_subgroup , new_dens_subgroup )
375
+ densities_interpolated = distribution (new_gm_bins_lb )
376
+ new_densities [i ] = densities_interpolated
377
+
378
+ new_dens = Quantity (new_densities .sum (axis = 0 ))
379
+ subgroups_density_new = subgroups_density .copy ()
380
+ mask = new_dens != 0
381
+ subgroups_density_new [:,mask ] = (new_densities [:,mask ] / new_dens [mask ]).value
382
+ return new_gm_bins , new_gm_bins_lb , new_gm_bins_ub , new_dens , subgroups_density_new
383
+
316
384
def interlace (gamma_bins_start : NDArray , gamma_bins_end : NDArray ) -> NDArray :
317
385
""" Combines two gamma arrays into one array, to make the calculations on them in one steps instead of two;
318
386
the first array goes into odd indices, the second into even indices """
0 commit comments