Skip to content

Commit bce28e6

Browse files
committed
Astropy fitting is now fully functional
The code has been restrcutred such that Astropy fitting is now fully functional and all required funtionality is contained within the class rather than externally.
1 parent 7cd2333 commit bce28e6

File tree

1 file changed

+91
-127
lines changed

1 file changed

+91
-127
lines changed

sunkit_spex/models/physical/albedo.py

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

1212
from sunpy.data import cache
1313

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

1616

1717
class Albedo(FittableModel):
@@ -84,143 +84,107 @@ class Albedo(FittableModel):
8484

8585
_input_units_allow_dimensionless = True
8686

87-
def __init__(self, *args, **kwargs):
88-
self.energy_edges = kwargs.pop("energy_edges")
89-
super().__init__(*args, **kwargs)
90-
91-
def evaluate(self, spectrum, theta, anisotropy):
92-
if hasattr(theta, "unit"):
93-
albedo_matrix = get_albedo_matrix(self.energy_edges, theta, anisotropy)
94-
else:
95-
albedo_matrix = get_albedo_matrix(self.energy_edges, theta*u.deg, anisotropy)
96-
return spectrum + spectrum @ albedo_matrix
97-
98-
def _parameter_units_for_data_units(self, inputs_unit, outputs_unit):
99-
return {"theta": u.deg}
100-
87+
def __init__(self, theta=u.Quantity(theta.default, theta.unit),
88+
anisotropy=anisotropy.default,
89+
**kwargs):
10190

102-
@lru_cache
103-
def _get_green_matrix(theta: float) -> RegularGridInterpolator:
104-
r"""
105-
Get greens matrix for given angle.
91+
self.energy_edges = kwargs.pop("energy_edges")
10692

107-
Interpolates pre-computed green matrices for fixed angles. The resulting greens matrix is then loaded into an
108-
interpolator for later energy interpolation.
93+
self._get_green_matrix(theta)
10994

110-
Parameters
111-
==========
112-
theta : float
113-
Angle between the observer and the source
95+
super().__init__(theta=theta,
96+
anisotropy=anisotropy,
97+
**kwargs)
11498

115-
Returns
116-
=======
117-
Greens matrix interpolator
118-
"""
119-
mu = np.cos(theta)
120-
121-
base_url = "https://soho.nascom.nasa.gov/solarsoft/packages/xray/dbase/albedo/"
122-
# what about 0 and 1 assume so close to 05 and 95 that it doesn't matter
123-
# load precomputed green matrices
124-
if 0.5 <= mu <= 0.95:
125-
low = 5 * np.floor(mu * 20)
126-
high = 5 * np.ceil(mu * 20)
127-
low_name = f"green_compton_mu{low:03.0f}.dat"
128-
high_name = f"green_compton_mu{high:03.0f}.dat"
129-
low_file = cache.download(base_url + low_name)
130-
high_file = cache.download(base_url + high_name)
131-
green = readsav(low_file)
132-
albedo_low = green["p"].albedo[0]
133-
green_high = readsav(high_file)
134-
albedo_high = green_high["p"].albedo[0]
135-
# why 20?
136-
albedo = albedo_low + (albedo_high - albedo_low) * (mu - (np.floor(mu * 20)) / 20)
137-
138-
elif mu < 0.5:
139-
file = "green_compton_mu005.dat"
140-
file = cache.download(base_url + file)
141-
green = readsav(file)
142-
albedo = green["p"].albedo[0]
143-
elif mu > 0.95:
144-
file = "green_compton_mu095.dat"
145-
file = cache.download(base_url + file)
146-
green = readsav(file)
147-
albedo = green["p"].albedo[0]
148-
149-
albedo = albedo.T
150-
151-
# By construction in keV
152-
energy_grid_edges = green["p"].edges[0]
153-
energy_grid_centers = energy_grid_edges[:, 0] + (np.diff(energy_grid_edges, axis=1) / 2).reshape(-1)
154-
155-
return RegularGridInterpolator((energy_grid_centers, energy_grid_centers), albedo)
156-
157-
158-
@lru_cache
159-
def _calculate_albedo_matrix(energy_edges: tuple[float], theta: float, anisotropy: float) -> NDArray:
160-
r"""
161-
Calculate green matrix for given energies and angle.
162-
163-
Interpolates precomputed green matrices for given energies and angle.
99+
def evaluate(self, spectrum, theta, anisotropy):
164100

165-
Parameters
166-
==========
167-
energy_edges :
168-
Energy edges associated with the spectrum
169-
theta :
170-
Angle between the observer and the source
171-
anisotropy :
172-
Ratio of the flux in observer direction to the flux downwards, 1 for an isotropic source
173-
"""
174-
albedo_interpolator = _get_green_matrix(theta)
175-
de = np.diff(energy_edges)
176-
energy_centers = energy_edges[:-1] + de / 2
101+
if hasattr(theta, "unit"):
102+
theta = Quantity(theta.value,theta.unit)
103+
else:
104+
theta = theta*u.deg
177105

178-
X, Y = np.meshgrid(energy_centers, energy_centers)
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()
179116

180-
albedo_interp = albedo_interpolator((X, Y))
117+
albedo_interpolator = self._get_green_matrix(theta)
118+
de = np.diff(energy_edges)
119+
energy_centers = energy_edges[:-1] + de / 2
181120

182-
# Scale by anisotropy
183-
albedo_interp = (albedo_interp * de) / anisotropy
121+
X, Y = np.meshgrid(energy_centers, energy_centers)
184122

185-
# Take a transpose
186-
return albedo_interp.T
123+
albedo_interp = albedo_interpolator((X, Y))
187124

125+
# Scale by anisotropy
126+
albedo_interp = (albedo_interp * de) / anisotropy
188127

189-
@u.quantity_input
190-
def get_albedo_matrix(energy_edges: Quantity[u.keV], theta:Quantity[u.deg], anisotropy=1):
191-
r"""
192-
Get albedo correction matrix.
128+
albedo_matrix = albedo_interp.T
193129

194-
Matrix used to correct a photon spectrum for the component reflected by the solar atmosphere following interpolated
195-
to given angle and energy indices.
130+
return spectrum + spectrum @ albedo_matrix
131+
132+
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)
196187

197-
Parameters
198-
----------
199-
energy_edges :
200-
Energy edges associated with the spectrum
201-
theta :
202-
Angle between Sun-observer line and X-ray source
203-
anisotropy :
204-
Ratio of the flux in observer direction to the flux downwards, 1 for an isotropic source
188+
def _parameter_units_for_data_units(self, inputs_unit, outputs_unit):
189+
return {"theta": u.deg}
205190

206-
Example
207-
-------
208-
>>> import astropy.units as u
209-
>>> import numpy as np
210-
>>> from sunkit_spex.models.physical.albedo import get_albedo_matrix
211-
>>> e = np.linspace(5, 500, 5)*u.keV
212-
>>> albedo_matrix = get_albedo_matrix(e,theta=45*u.deg)
213-
>>> albedo_matrix
214-
array([[3.80274484e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],
215-
[5.10487362e-01, 8.35309813e-07, 0.00000000e+00, 0.00000000e+00],
216-
[3.61059918e-01, 2.48711099e-01, 2.50744411e-09, 0.00000000e+00],
217-
[3.09323903e-01, 2.66485260e-01, 1.23563372e-01, 1.81846722e-10]])
218-
"""
219-
if energy_edges[0].to_value(u.keV) < 3 or energy_edges[-1].to_value(u.keV) > 600:
220-
raise ValueError("Supported energy range 3 <= E <= 600 keV")
221-
theta = np.array(theta).squeeze() << theta.unit
222-
# theta = np.array(theta)*u.deg
223-
if np.abs(theta) > 90 * u.deg:
224-
raise ValueError(f"Theta must be between -90 and 90 degrees: {theta}.")
225-
anisotropy = np.array(anisotropy).squeeze()
226-
return _calculate_albedo_matrix(tuple(energy_edges.to_value(u.keV)), theta.to_value(u.deg), anisotropy.item())

0 commit comments

Comments
 (0)