|
11 | 11 |
|
12 | 12 | from sunpy.data import cache |
13 | 13 |
|
14 | | -__all__ = ["Albedo", "get_albedo_matrix"] |
| 14 | +__all__ = ["Albedo"] |
15 | 15 |
|
16 | 16 |
|
17 | 17 | class Albedo(FittableModel): |
@@ -84,143 +84,107 @@ class Albedo(FittableModel): |
84 | 84 |
|
85 | 85 | _input_units_allow_dimensionless = True |
86 | 86 |
|
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): |
101 | 90 |
|
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") |
106 | 92 |
|
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) |
109 | 94 |
|
110 | | - Parameters |
111 | | - ========== |
112 | | - theta : float |
113 | | - Angle between the observer and the source |
| 95 | + super().__init__(theta=theta, |
| 96 | + anisotropy=anisotropy, |
| 97 | + **kwargs) |
114 | 98 |
|
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): |
164 | 100 |
|
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 |
177 | 105 |
|
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() |
179 | 116 |
|
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 |
181 | 120 |
|
182 | | - # Scale by anisotropy |
183 | | - albedo_interp = (albedo_interp * de) / anisotropy |
| 121 | + X, Y = np.meshgrid(energy_centers, energy_centers) |
184 | 122 |
|
185 | | - # Take a transpose |
186 | | - return albedo_interp.T |
| 123 | + albedo_interp = albedo_interpolator((X, Y)) |
187 | 124 |
|
| 125 | + # Scale by anisotropy |
| 126 | + albedo_interp = (albedo_interp * de) / anisotropy |
188 | 127 |
|
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 |
193 | 129 |
|
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) |
196 | 187 |
|
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} |
205 | 190 |
|
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