Skip to content

Commit da1407c

Browse files
committed
Functions moved out of class, fitting works.
The functions have been moved out of the class which now functions as a wrapper. The lru_cache method is applied to both the _calculate_albedo_matrix and _get_green_matrix functions, the outputs when fitting are identical with or without the decorators, and the code runs much quicker this way.
1 parent bce28e6 commit da1407c

File tree

1 file changed

+129
-83
lines changed

1 file changed

+129
-83
lines changed

sunkit_spex/models/physical/albedo.py

Lines changed: 129 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from sunpy.data import cache
1313

14-
__all__ = ["Albedo"]
14+
__all__ = ["Albedo","get_albedo_matrix"]
1515

1616

1717
class 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

Comments
 (0)