Skip to content

Commit 576a7c3

Browse files
committed
subgroups implemented
1 parent ab40eb0 commit 576a7c3

File tree

3 files changed

+246
-128
lines changed

3 files changed

+246
-128
lines changed

agnpy/time_evolution/_time_evolution_utils.py

Lines changed: 102 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import numpy as np
2-
from astropy.units import Quantity
2+
from astropy.units import Quantity, Unit
33
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
66
from agnpy.utils.conversion import mec2
77
from numpy._typing import NDArray
88
from scipy.interpolate import PchipInterpolator
@@ -16,36 +16,46 @@ def update_bin_ub(gamma_bins_from, gamma_bins_to, low_change_rates_mask = None):
1616
gamma_bins_to[~low_change_rates_mask] = gamma_bins_from[~low_change_rates_mask] * (1 + bin_size_factor)
1717

1818

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):
2020
"""
2121
Calculates (or recalculates) the energy change rates or injection rates.
2222
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)
2424
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
2626
"""
2727
if previous_rates is None:
2828
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 = {}
3235
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 \
3437
if previous_rates.get(label) is None \
3538
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
3841

3942

4043
def sum_change_rates_at_index(change_rates, index):
4144
return np.sum([arr[index].value for arr in change_rates.values()])
4245

4346

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+
])
4959

5060

5161
def remap(mapping, values, default_value=None):
@@ -70,6 +80,17 @@ def remap_interlaced(mapping, values, default_value = None):
7080
return interlace(remap(mapping, v1, default_value), remap(mapping, v2, default_value))
7181

7282

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+
7394
def combine_mapping(first_mapping, second_mapping):
7495
""" Combines two mappings, but with special treatment of "-1" value in the mapping:
7596
-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):
83104
return (gamma_bins * mec2).to("erg")
84105

85106

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:
87117
new_energy_bins, density_increase = recalc_energy_bins_and_density_increase(abs_energy_changes, energy_bins)
88118
new_density = density * density_increase
89119
new_gamma_bins = (new_energy_bins / mec2).to_value('')
90-
return new_gamma_bins, new_density
120+
return BinsWithDensities(new_gamma_bins, new_density)
91121

92122

93123
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):
107137
return new_energy_bins, density_increase
108138

109139

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,
111141
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")
114142
# The obvious hard limit for escape is -1.0, which means "all particles escaped"; more negative value would clearly
115143
# mean we used too long time. The -0.5 is somewhat arbitrary, perhaps it should be configurable like other limits?
116144
# 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.
117145
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
130169
return new_low_change_rates_mask
131170

132171

@@ -313,6 +352,35 @@ def update_distribution(gamma_array, n_array, blob):
313352
blob.n_e = InterpolatedDistribution(gamma_array_sorted, n_array_sorted, interpolator=PchipInterpolator)
314353

315354

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+
316384
def interlace(gamma_bins_start: NDArray, gamma_bins_end: NDArray) -> NDArray:
317385
""" Combines two gamma arrays into one array, to make the calculations on them in one steps instead of two;
318386
the first array goes into odd indices, the second into even indices """

0 commit comments

Comments
 (0)