29
29
import numpy as np
30
30
from mpl_toolkits .axes_grid1 import make_axes_locatable
31
31
from scipy .interpolate import interp1d
32
+ from scipy .io import readsav
32
33
from scipy .linalg import LinAlgError
33
34
# to fit the model
34
35
from scipy .optimize import minimize
35
36
37
+ import astropy .units as u
36
38
from astropy .table import Table
37
39
40
+ from sunkit_spex .legacy .fitting .albedo import get_albedo_matrix
38
41
from sunkit_spex .legacy .fitting .data_loader import LoadSpec
39
42
from sunkit_spex .legacy .fitting .instruments import rebin_any_array
40
43
from sunkit_spex .legacy .fitting .likelihoods import LogLikelihoods
@@ -439,6 +442,13 @@ def __init__(
439
442
# attribute to determine how the class is pickled
440
443
self ._pickle_reason = "normal"
441
444
445
+ # for alebdo correction
446
+ self .albedo_corr = False
447
+
448
+ self .albedo_angle = 0
449
+
450
+ self .albedo_anisotropy = 1
451
+
442
452
@property
443
453
def model (self ):
444
454
"""***Property*** Allows a model to be set and a parameter table to be generated straight away.
@@ -1453,6 +1463,7 @@ def _counts_model(self, **kwargs):
1453
1463
# return counts s^-1 (keV^-1 has been multiplied out) photon_channel_widths=ph_e_binning
1454
1464
1455
1465
cts_models = []
1466
+ albedo_excess_models = []
1456
1467
# loop through the parameter groups (params for spectrum1, then for spectrum2, etc)
1457
1468
for s , pgs in enumerate (self ._param_groups ):
1458
1469
# take the spectrum parameters (e.g., [p2_spectrum1,p1_spectrum1]) and order to be the same as the model parameters (e.g., [p1,p2])
@@ -1469,8 +1480,8 @@ def _counts_model(self, **kwargs):
1469
1480
) # np.diff(kwargs["photon_channels"][s]).flatten() # remove energy bin dependence
1470
1481
1471
1482
# fold the photon model through the SRM to create the count rate model, [photon s^-1 cm^-2] * [count photon^-1 cm^2] = [count s^-1]
1472
- cts_model = make_model (
1473
- energies = kwargs ["photon_channels" ][s ], photon_model = m , parameters = None , srm = kwargs ["total_responses" ][s ]
1483
+ cts_model , cts_albedo_exess = make_model (
1484
+ energies = kwargs ["photon_channels" ][s ], photon_model = m , parameters = None , srm = kwargs ["total_responses" ][s ], albedo_corr = self . albedo_corr , albedo_angle = self . albedo_angle , albedo_anisotropy = self . albedo_anisotropy
1474
1485
)
1475
1486
1476
1487
if "scaled_background_spectrum" + str (s + 1 ) in self ._scaled_backgrounds :
@@ -1496,7 +1507,20 @@ def _counts_model(self, **kwargs):
1496
1507
cts_model [None , :]
1497
1508
) # need [None, :] these lines get rid of a dimension meaning later concatenation fails
1498
1509
1499
- return cts_models
1510
+ if cts_albedo_exess .size > 0 :
1511
+ cts_albedo_exess = (
1512
+ cts_albedo_exess
1513
+ if len (LL_CLASS .remove_non_numbers (cts_albedo_exess [cts_albedo_exess != 0 ])) != 0
1514
+ else np .zeros (cts_albedo_exess .shape )
1515
+ )
1516
+
1517
+ albedo_excess_models .append (
1518
+ cts_albedo_exess [None , :]
1519
+ ) # need [None, :] these lines get rid of a dimension meaning later concatenation fails
1520
+ else :
1521
+ albedo_excess_models .append (np .array ([[]]))
1522
+
1523
+ return cts_models , albedo_excess_models
1500
1524
1501
1525
def _pseudo_model (self , free_params_list , tied_or_frozen_params_list , param_name_list_order , ** other_inputs ):
1502
1526
"""Bridging method between the input args (free,other) and different ordered args for the model calculation.
@@ -1776,7 +1800,7 @@ def _fit_stat(
1776
1800
"""
1777
1801
1778
1802
# make sure only the free parameters are getting varied so put them first
1779
- mu = self ._pseudo_model (
1803
+ mu , _ = self ._pseudo_model (
1780
1804
free_params_list ,
1781
1805
tied_or_frozen_params_list ,
1782
1806
param_name_list_order ,
@@ -2492,7 +2516,7 @@ def _calculate_model(self, **kwargs):
2492
2516
tied_or_frozen_params_list .extend (list (update_fixed_params .values ()))
2493
2517
2494
2518
# make sure only the free parameters are getting varied
2495
- mu = self ._pseudo_model (
2519
+ mu , alebdo_excess_count = self ._pseudo_model (
2496
2520
free_params_list ,
2497
2521
tied_or_frozen_params_list ,
2498
2522
param_name_list_order ,
@@ -2502,18 +2526,22 @@ def _calculate_model(self, **kwargs):
2502
2526
total_responses = srm ,
2503
2527
** kwargs ,
2504
2528
)
2529
+
2505
2530
# turn counts s^-1 into counts s^-1 keV^-1
2506
2531
for m , e in enumerate (e_binning ):
2507
2532
mu [m ][0 ] /= e
2533
+ if np .array (alebdo_excess_count [m ][0 ]).size > 0 :
2534
+ alebdo_excess_count [m ][0 ] /= e
2508
2535
2509
2536
self ._energy_fitting_indices = _energy_fitting_indices_orig
2510
2537
# numpy is better to store but all models need to be the same length, this might not be the case
2511
2538
try :
2512
- return np .concatenate (tuple (mu ))
2539
+ return np .concatenate (tuple (mu )), np .concatenate (tuple (alebdo_excess_count ))
2540
+
2513
2541
except ValueError :
2514
- return [mu [i ][0 ] for i in range (len (mu ))]
2542
+ return [mu [i ][0 ] for i in range (len (mu ))], alebdo_excess_count
2515
2543
2516
- def _calc_counts_model (self , photon_model , parameters = None , spectrum = "spectrum1" , include_bg = False , ** kwargs ):
2544
+ def _calc_counts_model (self , photon_model , parameters = None , spectrum = "spectrum1" , include_bg = False , for_plotting = False , ** kwargs ):
2517
2545
"""Easily calculate a spectrum's count model from the parameter table and user photon model.
2518
2546
2519
2547
Given a model (a calculated model array or a photon model function) then calcutes the counts
@@ -2572,12 +2600,27 @@ def _calc_counts_model(self, photon_model, parameters=None, spectrum="spectrum1"
2572
2600
)
2573
2601
return
2574
2602
2575
- cts_model = make_model (
2576
- energies = photon_channel_bins ,
2577
- photon_model = m * np .diff (photon_channel_bins ).flatten (),
2578
- parameters = None ,
2579
- srm = srm ,
2580
- )
2603
+ if for_plotting :
2604
+ cts_model , _ = make_model (
2605
+ energies = photon_channel_bins ,
2606
+ photon_model = m * np .diff (photon_channel_bins ).flatten (),
2607
+ parameters = None ,
2608
+ srm = srm ,
2609
+ albedo_corr = False ,
2610
+ albedo_angle = self .albedo_angle ,
2611
+ albedo_anisotropy = self .albedo_anisotropy
2612
+ )
2613
+
2614
+ else :
2615
+ cts_model , _ = make_model (
2616
+ energies = photon_channel_bins ,
2617
+ photon_model = m * np .diff (photon_channel_bins ).flatten (),
2618
+ parameters = None ,
2619
+ srm = srm ,
2620
+ albedo_corr = self .albedo_corr ,
2621
+ albedo_angle = self .albedo_angle ,
2622
+ albedo_anisotropy = self .albedo_anisotropy
2623
+ )
2581
2624
2582
2625
if include_bg and ("scaled_background_" + spectrum in self ._scaled_backgrounds ):
2583
2626
cts_model += self ._scaled_backgrounds ["scaled_background_" + spectrum ]
@@ -2693,6 +2736,7 @@ def _calculate_submodels(self, spectrum=None):
2693
2736
photon_model = self ._submod_functions [p ],
2694
2737
parameters = self ._submod_value_inputs [s - 1 ][p ],
2695
2738
spectrum = "spectrum" + str (s ),
2739
+ for_plotting = True
2696
2740
)
2697
2741
spec_submods .append (cts_mod )
2698
2742
all_spec_submods .append (spec_submods )
@@ -3231,14 +3275,20 @@ def _plot_mcmc_mods(
3231
3275
name : (_params [mcmc_freepar_labels .index (val )] if type (val ) == str else val )
3232
3276
for name , val in _spec_rpars .items ()
3233
3277
}
3278
+ _rpars ['for_plotting' ] = True
3234
3279
e_mids , ctr = self ._calc_counts_model (
3235
3280
photon_model = self ._model , parameters = _pars , spectrum = "spectrum" + str (s + 1 ), include_bg = True , ** _rpars
3236
3281
)
3237
3282
_randcts .append (ctr )
3283
+
3238
3284
if _rebin_info is not None :
3239
3285
ctr = self ._bin_model (ctr , * _rebin_info )
3240
3286
e_mids = np .mean (_rebin_info [2 ], axis = 1 )
3241
3287
3288
+ if self .albedo_corr :
3289
+ for i in range (len (res_info [1 ])):
3290
+ ctr [i ] = ctr [i ] + res_info [- 1 ][i ]
3291
+
3242
3292
residuals = [
3243
3293
(res_info [0 ][i ] - ctr [i ]) / res_info [1 ][i ] if res_info [1 ][i ] > 0 else 0 for i in range (len (res_info [1 ]))
3244
3294
]
@@ -3559,7 +3609,7 @@ def _setup_rebin_plotting(self, rebin_and_spec, data_arrays, _return_cts_rate_mo
3559
3609
count_rate_model), and a 1d array of energies for residual plotting (energy_channels_res), respectively.
3560
3610
"""
3561
3611
rebin_val , rebin_spec = rebin_and_spec [0 ], rebin_and_spec [1 ]
3562
- energy_channels , energy_channel_error , count_rates , count_rate_errors , count_rate_model = data_arrays
3612
+ energy_channels , energy_channel_error , count_rates , count_rate_errors , albedo_excess_count , count_rate_model = data_arrays
3563
3613
if type (rebin_val ) != type (None ):
3564
3614
if rebin_spec in list (self .data .loaded_spec_data .keys ()):
3565
3615
(
@@ -3714,7 +3764,7 @@ def _plot_1spec(
3714
3764
-------
3715
3765
Spectrum axes and residuals axes.
3716
3766
"""
3717
- energy_channels , energy_channel_error , count_rates , count_rate_errors , count_rate_model = data_arrays
3767
+ energy_channels , energy_channel_error , count_rates , count_rate_errors , albedo_excess_count , count_rate_model = data_arrays
3718
3768
3719
3769
axs = axes if type (axes ) != type (None ) else plt .gca ()
3720
3770
fitting_range = fitting_range if type (fitting_range ) != type (None ) else self .energy_fitting_range
@@ -3795,14 +3845,17 @@ def _plot_1spec(
3795
3845
axs .plot (energy_channels , count_rate_model , linewidth = 2 , color = "k" )
3796
3846
res .plot (energy_channels_res , residuals , color = "k" , alpha = 0.8 ) # , drawstyle='steps-mid'
3797
3847
3848
+ if self .albedo_corr and albedo_excess_count .size > 0 :
3849
+ axs .plot (energy_channels , albedo_excess_count , color = "grey" )
3850
+
3798
3851
if self ._latest_fit_run == "mcmc" :
3799
3852
_rebin_info = (
3800
3853
[old_bin_width , old_bins , new_bins , new_bin_width ] if type (rebin_and_spec [0 ]) != type (None ) else None
3801
3854
)
3802
3855
self ._plot_mcmc_mods (
3803
3856
axs ,
3804
3857
res ,
3805
- [count_rates , count_rate_errors , energy_channels_res ],
3858
+ [count_rates , count_rate_errors , energy_channels_res , albedo_excess_count ],
3806
3859
spectrum = submod_spec ,
3807
3860
num_of_samples = num_of_samples ,
3808
3861
hex_grid = hex_grid ,
@@ -4194,7 +4247,7 @@ def _get_models(self, number_of_models):
4194
4247
return self ._calculate_model ()
4195
4248
else :
4196
4249
self ._param_groups = [None ] * int (number_of_models )
4197
- return [np .array ([1 ])] * int (number_of_models )
4250
+ return [np .array ([1 ])] * int (number_of_models ), [ np . array ([ 1 ])] * int ( number_of_models ) #empty models, empty array for albedo
4198
4251
4199
4252
def plot (self , subplot_axes_grid = None , rebin = None , num_of_samples = 100 , hex_grid = False , plot_final_result = True ):
4200
4253
"""Plots the latest fit or sampling result.
@@ -4268,7 +4321,7 @@ def plot(self, subplot_axes_grid=None, rebin=None, num_of_samples=100, hex_grid=
4268
4321
self ._scaled_backgrounds = self ._scaled_background_rates_full
4269
4322
4270
4323
# only need enough axes for the number of spectra to plot so doesn't matter if more axes are given
4271
- models = self ._get_models (number_of_models = number_of_plots )
4324
+ models , albedo_excess_count = self ._get_models (number_of_models = number_of_plots )
4272
4325
4273
4326
axes , res_axes = [], []
4274
4327
_count_rates , _count_rate_errors = [], []
@@ -4282,6 +4335,7 @@ def plot(self, subplot_axes_grid=None, rebin=None, num_of_samples=100, hex_grid=
4282
4335
self .data .loaded_spec_data ["spectrum" + str (s + 1 )]["count_channel_binning" ] / 2 ,
4283
4336
self .data .loaded_spec_data ["spectrum" + str (s + 1 )]["count_rate" ],
4284
4337
self .data .loaded_spec_data ["spectrum" + str (s + 1 )]["count_rate_error" ],
4338
+ albedo_excess_count [s ],
4285
4339
models [s ],
4286
4340
),
4287
4341
axes = ax ,
@@ -4308,6 +4362,7 @@ def plot(self, subplot_axes_grid=None, rebin=None, num_of_samples=100, hex_grid=
4308
4362
self .data .loaded_spec_data ["spectrum" + str (s )]["count_channel_binning" ] / 2 ,
4309
4363
np .mean (np .array (_count_rates ), axis = 0 ),
4310
4364
np .sqrt (np .sum (np .array (_count_rate_errors ) ** 2 , axis = 0 )) / len (_count_rate_errors ),
4365
+ np .array ([]),
4311
4366
np .mean (models , axis = 0 ),
4312
4367
),
4313
4368
axes = ax ,
@@ -5600,7 +5655,7 @@ def imports():
5600
5655
return _imps
5601
5656
5602
5657
5603
- def make_model (energies = None , photon_model = None , parameters = None , srm = None ):
5658
+ def make_model (energies = None , photon_model = None , parameters = None , srm = None , albedo_corr = False , albedo_angle = 0 , albedo_anisotropy = 1 ):
5604
5659
"""Takes a photon model array ( or function if you provide the pinputs with parameters), the spectral response matrix and returns a model count spectrum.
5605
5660
5606
5661
Parameters
@@ -5632,6 +5687,36 @@ def make_model(energies=None, photon_model=None, parameters=None, srm=None):
5632
5687
else :
5633
5688
photon_spec = photon_model (energies , * parameters )
5634
5689
5690
+ if albedo_corr :
5691
+ photon_spec , albedo_excess_phot = albedo (photon_spec , energies , albedo_angle , anisotropy = albedo_anisotropy )
5692
+ albedo_excess_count = np .matmul (albedo_excess_phot , srm )
5693
+ else :
5694
+ albedo_excess_count = np .array ([])
5695
+
5635
5696
model_cts_spectrum = np .matmul (photon_spec , srm )
5636
5697
5637
- return model_cts_spectrum
5698
+ return model_cts_spectrum , albedo_excess_count
5699
+
5700
+ #####
5701
+ # Function to calculate the albedo correction, adapted from Shane's PR request
5702
+ #####
5703
+
5704
+ def albedo (spec , energy , theta , anisotropy = 1 ):
5705
+ r"""
5706
+ Gets the albedo matrix for given angle and anisotropy and returns the albedo corrected spectrum as well as the albedo component by itself.
5707
+
5708
+ Parameters
5709
+ ----------
5710
+ spec :
5711
+ Spectrum object
5712
+ energy :
5713
+ Energy edges associated with the spectrum
5714
+ theta :
5715
+ Angle between Sun-observer line and X-ray source
5716
+ anisotropy :
5717
+ Ratio of the flux in observer direction to the flux downwards, 1 for an isotropic source
5718
+ """
5719
+
5720
+ albedo_matrix = get_albedo_matrix (energy * u .keV , theta , anisotropy )
5721
+
5722
+ return spec + spec @ albedo_matrix , spec @ albedo_matrix
0 commit comments