1111
1212from sunpy .data import cache
1313
14- __all__ = ["Albedo" ]
14+ __all__ = ["Albedo" , "get_albedo_matrix" ]
1515
1616
1717class Albedo (FittableModel ):
@@ -84,107 +84,153 @@ class Albedo(FittableModel):
8484
8585 _input_units_allow_dimensionless = True
8686
87- def __init__ (self , theta = u .Quantity (theta .default , theta .unit ),
88- anisotropy = anisotropy .default ,
87+ def __init__ (self , * args ,
8988 ** kwargs ):
9089
9190 self .energy_edges = kwargs .pop ("energy_edges" )
9291
93- self ._get_green_matrix (theta )
94-
95- super ().__init__ (theta = theta ,
96- anisotropy = anisotropy ,
92+ super ().__init__ (* args ,
9793 ** kwargs )
9894
95+
96+
9997 def evaluate (self , spectrum , theta , anisotropy ):
10098
10199 if hasattr (theta , "unit" ):
102100 theta = Quantity (theta .value ,theta .unit )
103101 else :
104102 theta = theta * u .deg
103+
104+ albedo_matrix = get_albedo_matrix (self .energy_edges , theta , anisotropy )
105105
106- if self .energy_edges [0 ].to_value (u .keV ) < 3 or self .energy_edges [- 1 ].to_value (u .keV ) > 600 :
107- raise ValueError ("Supported energy range 3 <= E <= 600 keV" )
108- theta = np .array (theta ).squeeze () << theta .unit
109- if np .abs (theta ) > 90 * u .deg :
110- raise ValueError (f"Theta must be between -90 and 90 degrees: { theta } ." )
111- anisotropy = np .array (anisotropy ).squeeze ()
112-
113- energy_edges = tuple (self .energy_edges .to_value (u .keV ))
114- theta = theta .to_value (u .deg )
115- anisotropy = anisotropy .item ()
106+ return spectrum + spectrum @ albedo_matrix
116107
117- albedo_interpolator = self ._get_green_matrix (theta )
118- de = np .diff (energy_edges )
119- energy_centers = energy_edges [:- 1 ] + de / 2
108+ def _parameter_units_for_data_units (self , inputs_unit , outputs_unit ):
109+ return {"theta" : u .deg }
120110
121- X , Y = np .meshgrid (energy_centers , energy_centers )
122111
123- albedo_interp = albedo_interpolator ((X , Y ))
124112
125- # Scale by anisotropy
126- albedo_interp = (albedo_interp * de ) / anisotropy
113+ @lru_cache
114+ def _get_green_matrix (theta : float ) -> RegularGridInterpolator :
115+ r"""
116+ Get greens matrix for given angle.
127117
128- albedo_matrix = albedo_interp .T
118+ Interpolates pre-computed green matrices for fixed angles. The resulting greens matrix is then loaded into an
119+ interpolator for later energy interpolation.
129120
130- return spectrum + spectrum @ albedo_matrix
131-
121+ Parameters
122+ ==========
123+ theta : float
124+ Angle between the observer and the source
132125
133- @lru_cache
134- def _get_green_matrix (self , theta : float ) -> RegularGridInterpolator :
135- r"""
136- Get greens matrix for given angle.
137-
138- Interpolates pre-computed green matrices for fixed angles. The resulting greens matrix is then loaded into an
139- interpolator for later energy interpolation.
140-
141- Parameters
142- ==========
143- theta : float
144- Angle between the observer and the source
145-
146- Returns
147- =======
148- Greens matrix interpolator
149- """
150- mu = np .cos (theta )
151-
152- base_url = "https://soho.nascom.nasa.gov/solarsoft/packages/xray/dbase/albedo/"
153- # what about 0 and 1 assume so close to 05 and 95 that it doesn't matter
154- # load precomputed green matrices
155- if 0.5 <= mu <= 0.95 :
156- low = 5 * np .floor (mu * 20 )
157- high = 5 * np .ceil (mu * 20 )
158- low_name = f"green_compton_mu{ low :03.0f} .dat"
159- high_name = f"green_compton_mu{ high :03.0f} .dat"
160- low_file = cache .download (base_url + low_name )
161- high_file = cache .download (base_url + high_name )
162- green = readsav (low_file )
163- albedo_low = green ["p" ].albedo [0 ]
164- green_high = readsav (high_file )
165- albedo_high = green_high ["p" ].albedo [0 ]
166- # why 20?
167- albedo = albedo_low + (albedo_high - albedo_low ) * (mu - (np .floor (mu * 20 )) / 20 )
168-
169- elif mu < 0.5 :
170- file = "green_compton_mu005.dat"
171- file = cache .download (base_url + file )
172- green = readsav (file )
173- albedo = green ["p" ].albedo [0 ]
174- elif mu > 0.95 :
175- file = "green_compton_mu095.dat"
176- file = cache .download (base_url + file )
177- green = readsav (file )
178- albedo = green ["p" ].albedo [0 ]
179-
180- albedo = albedo .T
181-
182- # By construction in keV
183- energy_grid_edges = green ["p" ].edges [0 ]
184- energy_grid_centers = energy_grid_edges [:, 0 ] + (np .diff (energy_grid_edges , axis = 1 ) / 2 ).reshape (- 1 )
185-
186- return RegularGridInterpolator ((energy_grid_centers , energy_grid_centers ), albedo )
126+ Returns
127+ =======
128+ Greens matrix interpolator
129+ """
130+ mu = np .cos (theta )
131+
132+ base_url = "https://soho.nascom.nasa.gov/solarsoft/packages/xray/dbase/albedo/"
133+ # what about 0 and 1 assume so close to 05 and 95 that it doesn't matter
134+ # load precomputed green matrices
135+ if 0.5 <= mu <= 0.95 :
136+ low = 5 * np .floor (mu * 20 )
137+ high = 5 * np .ceil (mu * 20 )
138+ low_name = f"green_compton_mu{ low :03.0f} .dat"
139+ high_name = f"green_compton_mu{ high :03.0f} .dat"
140+ low_file = cache .download (base_url + low_name )
141+ high_file = cache .download (base_url + high_name )
142+ green = readsav (low_file )
143+ albedo_low = green ["p" ].albedo [0 ]
144+ green_high = readsav (high_file )
145+ albedo_high = green_high ["p" ].albedo [0 ]
146+ # why 20?
147+ albedo = albedo_low + (albedo_high - albedo_low ) * (mu - (np .floor (mu * 20 )) / 20 )
148+
149+ elif mu < 0.5 :
150+ file = "green_compton_mu005.dat"
151+ file = cache .download (base_url + file )
152+ green = readsav (file )
153+ albedo = green ["p" ].albedo [0 ]
154+ elif mu > 0.95 :
155+ file = "green_compton_mu095.dat"
156+ file = cache .download (base_url + file )
157+ green = readsav (file )
158+ albedo = green ["p" ].albedo [0 ]
159+
160+ albedo = albedo .T
161+
162+ # By construction in keV
163+ energy_grid_edges = green ["p" ].edges [0 ]
164+ energy_grid_centers = energy_grid_edges [:, 0 ] + (np .diff (energy_grid_edges , axis = 1 ) / 2 ).reshape (- 1 )
165+
166+ return RegularGridInterpolator ((energy_grid_centers , energy_grid_centers ), albedo )
167+
168+
169+ @lru_cache
170+ def _calculate_albedo_matrix (energy_edges : tuple [float ], theta : float , anisotropy : float ) -> NDArray :
171+ r"""
172+ Calculate green matrix for given energies and angle.
187173
188- def _parameter_units_for_data_units (self , inputs_unit , outputs_unit ):
189- return {"theta" : u .deg }
174+ Interpolates precomputed green matrices for given energies and angle.
190175
176+ Parameters
177+ ==========
178+ energy_edges :
179+ Energy edges associated with the spectrum
180+ theta :
181+ Angle between the observer and the source
182+ anisotropy :
183+ Ratio of the flux in observer direction to the flux downwards, 1 for an isotropic source
184+ """
185+ albedo_interpolator = _get_green_matrix (theta )
186+ de = np .diff (energy_edges )
187+ energy_centers = energy_edges [:- 1 ] + de / 2
188+
189+ X , Y = np .meshgrid (energy_centers , energy_centers )
190+
191+ albedo_interp = albedo_interpolator ((X , Y ))
192+
193+ # Scale by anisotropy
194+ albedo_interp = (albedo_interp * de ) / anisotropy
195+
196+ # Take a transpose
197+ return albedo_interp .T
198+
199+
200+ def get_albedo_matrix (energy_edges : Quantity [u .keV ], theta : Quantity [u .deg ], anisotropy = 1 ):
201+ r"""
202+ Get albedo correction matrix.
203+
204+ Matrix used to correct a photon spectrum for the component reflected by the solar atmosphere following interpolated
205+ to given angle and energy indices.
206+
207+ Parameters
208+ ----------
209+ energy_edges :
210+ Energy edges associated with the spectrum
211+ theta :
212+ Angle between Sun-observer line and X-ray source
213+ anisotropy :
214+ Ratio of the flux in observer direction to the flux downwards, 1 for an isotropic source
215+
216+ Example
217+ -------
218+ >>> import astropy.units as u
219+ >>> import numpy as np
220+ >>> from sunkit_spex.models.physical.albedo import get_albedo_matrix
221+ >>> e = np.linspace(5, 500, 5)*u.keV
222+ >>> albedo_matrix = get_albedo_matrix(e,theta=45*u.deg)
223+ >>> albedo_matrix
224+ array([[3.80274484e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],
225+ [5.10487362e-01, 8.35309813e-07, 0.00000000e+00, 0.00000000e+00],
226+ [3.61059918e-01, 2.48711099e-01, 2.50744411e-09, 0.00000000e+00],
227+ [3.09323903e-01, 2.66485260e-01, 1.23563372e-01, 1.81846722e-10]])
228+ """
229+ if energy_edges [0 ].to_value (u .keV ) < 3 or energy_edges [- 1 ].to_value (u .keV ) > 600 :
230+ raise ValueError ("Supported energy range 3 <= E <= 600 keV" )
231+ theta = np .array (theta ).squeeze () << theta .unit
232+ if np .abs (theta ) > 90 * u .deg :
233+ raise ValueError (f"Theta must be between -90 and 90 degrees: { theta } ." )
234+ anisotropy = np .array (anisotropy ).squeeze ()
235+
236+ return _calculate_albedo_matrix (tuple (energy_edges .to_value (u .keV )), theta .to_value (u .deg ), anisotropy .item ())
0 commit comments