PythonicDISORT 1.7__tar.gz → 1.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pythonicdisort-1.7/src/PythonicDISORT.egg-info → pythonicdisort-1.8}/PKG-INFO +15 -12
- {pythonicdisort-1.7 → pythonicdisort-1.8}/README.md +14 -11
- {pythonicdisort-1.7 → pythonicdisort-1.8}/pyproject.toml +1 -1
- {pythonicdisort-1.7 → pythonicdisort-1.8}/src/PythonicDISORT/_assemble_intensity_and_fluxes.py +116 -119
- {pythonicdisort-1.7 → pythonicdisort-1.8}/src/PythonicDISORT/_solve_for_coeffs.py +18 -10
- {pythonicdisort-1.7 → pythonicdisort-1.8}/src/PythonicDISORT/_solve_for_gen_and_part_sols.py +160 -21
- pythonicdisort-1.8/src/PythonicDISORT/pydisort.py +1140 -0
- {pythonicdisort-1.7 → pythonicdisort-1.8}/src/PythonicDISORT/subroutines.py +134 -30
- {pythonicdisort-1.7 → pythonicdisort-1.8/src/PythonicDISORT.egg-info}/PKG-INFO +15 -12
- pythonicdisort-1.7/src/PythonicDISORT/pydisort.py +0 -700
- {pythonicdisort-1.7 → pythonicdisort-1.8}/LICENSE.md +0 -0
- {pythonicdisort-1.7 → pythonicdisort-1.8}/setup.cfg +0 -0
- {pythonicdisort-1.7 → pythonicdisort-1.8}/src/PythonicDISORT/__init__.py +0 -0
- {pythonicdisort-1.7 → pythonicdisort-1.8}/src/PythonicDISORT.egg-info/SOURCES.txt +0 -0
- {pythonicdisort-1.7 → pythonicdisort-1.8}/src/PythonicDISORT.egg-info/dependency_links.txt +0 -0
- {pythonicdisort-1.7 → pythonicdisort-1.8}/src/PythonicDISORT.egg-info/requires.txt +0 -0
- {pythonicdisort-1.7 → pythonicdisort-1.8}/src/PythonicDISORT.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PythonicDISORT
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8
|
|
4
4
|
Summary: Discrete Ordinates Solver for the (1D) Radiative Transfer Equation in a single or multi-layer atmosphere.
|
|
5
5
|
Author-email: Dion HO Jia Xu <dh3065@columbia.edu>
|
|
6
6
|
Project-URL: Homepage, https://github.com/LDEO-CREW/Pythonic-DISORT
|
|
@@ -26,15 +26,15 @@ Requires-Dist: matplotlib>=3.6.0; extra == "notebook-dependencies"
|
|
|
26
26
|
# Introduction
|
|
27
27
|
The PythonicDISORT package is a Discrete Ordinates Solver for the (1D) Radiative Transfer Equation
|
|
28
28
|
in a plane-parallel, horizontally homogeneous atmosphere.
|
|
29
|
-
It is coded entirely in Python 3 and is
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
PythonicDISORT is based on Stamnes' FORTRAN DISORT (see References, in particular [2, 3, 8])
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
It is coded entirely in Python 3 and is not a wrapper.
|
|
30
|
+
PythonicDISORT has been optimized for speed and **as of v1.8 is one-third the speed of FORTRAN DISORT while being NumPy and Scipy only**.
|
|
31
|
+
PythonicDISORT is intended to be easier to install, use, and modify than FORTRAN-based Discrete Ordinates Solvers.
|
|
32
|
+
|
|
33
|
+
PythonicDISORT is based on Stamnes' FORTRAN DISORT (see References, in particular [2, 3, 8]),
|
|
34
|
+
as of v1.8 the only **missing features** are $\delta$-M+ scaling (PythonicDISORT instead implements NT corrections),
|
|
35
|
+
the Chapman function for near-horizon solar angles, and pseudo-spherical corrections.
|
|
36
|
+
On the other hand, we added a subroutine to compute actinic fluxes to satisfy a user request,
|
|
37
|
+
added analytic integration with respect to optical depth, and improved the handling of singularities.
|
|
38
38
|
Further feature requests as well as feedback are welcome.
|
|
39
39
|
|
|
40
40
|
You may contact me, Dion, through dh3065@columbia.edu.
|
|
@@ -70,8 +70,11 @@ These notebooks double up as examples of how to use PythonicDISORT. The tests wh
|
|
|
70
70
|
* Test Problem 7: Absorption + Scattering + All Possible Sources, Lambertian and Hapke Surface Reflectivities (one layer)
|
|
71
71
|
* Test Problem 8: Absorbing / Isotropic-Scattering Medium (multiple layers)
|
|
72
72
|
* Test Problem 9: General Emitting / Absorbing / Scattering Medium (multiple layers)
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
|
|
74
|
+
(no corresponding Jupyter Notebook)
|
|
75
|
+
* Test Problem 11: Single-Layer vs. Multiple Layers
|
|
76
|
+
* Test Problem I: Antiderivative / integration functionality
|
|
77
|
+
* Test Problem Interp: Interpolation + NT Correction functionality
|
|
75
78
|
|
|
76
79
|
# Installation
|
|
77
80
|
|
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
# Introduction
|
|
5
5
|
The PythonicDISORT package is a Discrete Ordinates Solver for the (1D) Radiative Transfer Equation
|
|
6
6
|
in a plane-parallel, horizontally homogeneous atmosphere.
|
|
7
|
-
It is coded entirely in Python 3 and is
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
PythonicDISORT is based on Stamnes' FORTRAN DISORT (see References, in particular [2, 3, 8])
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
It is coded entirely in Python 3 and is not a wrapper.
|
|
8
|
+
PythonicDISORT has been optimized for speed and **as of v1.8 is one-third the speed of FORTRAN DISORT while being NumPy and Scipy only**.
|
|
9
|
+
PythonicDISORT is intended to be easier to install, use, and modify than FORTRAN-based Discrete Ordinates Solvers.
|
|
10
|
+
|
|
11
|
+
PythonicDISORT is based on Stamnes' FORTRAN DISORT (see References, in particular [2, 3, 8]),
|
|
12
|
+
as of v1.8 the only **missing features** are $\delta$-M+ scaling (PythonicDISORT instead implements NT corrections),
|
|
13
|
+
the Chapman function for near-horizon solar angles, and pseudo-spherical corrections.
|
|
14
|
+
On the other hand, we added a subroutine to compute actinic fluxes to satisfy a user request,
|
|
15
|
+
added analytic integration with respect to optical depth, and improved the handling of singularities.
|
|
16
16
|
Further feature requests as well as feedback are welcome.
|
|
17
17
|
|
|
18
18
|
You may contact me, Dion, through dh3065@columbia.edu.
|
|
@@ -48,8 +48,11 @@ These notebooks double up as examples of how to use PythonicDISORT. The tests wh
|
|
|
48
48
|
* Test Problem 7: Absorption + Scattering + All Possible Sources, Lambertian and Hapke Surface Reflectivities (one layer)
|
|
49
49
|
* Test Problem 8: Absorbing / Isotropic-Scattering Medium (multiple layers)
|
|
50
50
|
* Test Problem 9: General Emitting / Absorbing / Scattering Medium (multiple layers)
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
|
|
52
|
+
(no corresponding Jupyter Notebook)
|
|
53
|
+
* Test Problem 11: Single-Layer vs. Multiple Layers
|
|
54
|
+
* Test Problem I: Antiderivative / integration functionality
|
|
55
|
+
* Test Problem Interp: Interpolation + NT Correction functionality
|
|
53
56
|
|
|
54
57
|
# Installation
|
|
55
58
|
|
{pythonicdisort-1.7 → pythonicdisort-1.8}/src/PythonicDISORT/_assemble_intensity_and_fluxes.py
RENAMED
|
@@ -29,7 +29,8 @@ def _assemble_intensity_and_fluxes(
|
|
|
29
29
|
only_flux, # Only compute fluxes?
|
|
30
30
|
use_banded_solver_NLayers, # Number of layers above or equal which to use `scipy.linalg.solve_banded`
|
|
31
31
|
autograd_compatible, # Should the output functions be compatible with autograd?
|
|
32
|
-
|
|
32
|
+
cache_asso_leg, # Assoc. Legendre table cache mode: None, "no_mu0" or "mu0"
|
|
33
|
+
):
|
|
33
34
|
"""Assembles the solution functions: intensity `u`, upward flux `flux_up`, downward flux `flux_down`
|
|
34
35
|
from the previously solved eigenpairs and coefficients from the boundary conditions.
|
|
35
36
|
Returns these solution functions.
|
|
@@ -78,7 +79,8 @@ def _assemble_intensity_and_fluxes(
|
|
|
78
79
|
| `only_flux` | boolean |
|
|
79
80
|
| `use_banded_solver_NLayers` | scalar |
|
|
80
81
|
| `autograd_compatible` | boolean |
|
|
81
|
-
|
|
82
|
+
| `cache_asso_leg` | None, "no_mu0" or "mu0" |
|
|
83
|
+
|
|
82
84
|
Notable internal variables of _assemble_intensity_and_fluxes
|
|
83
85
|
| Variable | Type / Shape |
|
|
84
86
|
| ----------------- | -------------------------------------- |
|
|
@@ -117,6 +119,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
117
119
|
mu0, I0_div_4pi,
|
|
118
120
|
there_is_beam_source,
|
|
119
121
|
there_is_iso_source,
|
|
122
|
+
cache_asso_leg,
|
|
120
123
|
)
|
|
121
124
|
if there_is_beam_source and there_is_iso_source:
|
|
122
125
|
G_collect, K_collect, B_collect, G_inv_collect_0 = outputs
|
|
@@ -161,8 +164,66 @@ def _assemble_intensity_and_fluxes(
|
|
|
161
164
|
G_collect_0 = G_collect[0, :, :, :]
|
|
162
165
|
K_collect_0 = K_collect[0, :, :]
|
|
163
166
|
GC_collect_0 = GC_collect[0, :, :, :]
|
|
164
|
-
# --------------------------------------------------------------------------------------------------------------------------
|
|
165
|
-
|
|
167
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
# Value-keyed cache for the most recent `tau` evaluation grid, shared across the output
|
|
170
|
+
# functions `u`, `u0`, `flux_up` and `flux_down` (evaluating several outputs on the same
|
|
171
|
+
# grid, e.g. `flux_up(tau); flux_down(tau)`, then recomputes nothing).
|
|
172
|
+
# Stores a copy of `tau` (compared by value like the cache in `subroutines.interpolate`)
|
|
173
|
+
# with the derived layer indices and delta-scaled `tau`, and, lazily, the exponentials of
|
|
174
|
+
# the homogeneous-solution exponents for the 0th mode ("exp0") and for all modes ("expm").
|
|
175
|
+
# The cache is bypassed when `autograd_compatible` as it would break gradient tracing.
|
|
176
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
177
|
+
is_delta_scaled = np.any(scale_tau != 1)
|
|
178
|
+
_tau_cache = {"tau": None, "l": None, "scaled_tau": None, "exp0": None, "expm": None}
|
|
179
|
+
|
|
180
|
+
def _process_tau(tau):
|
|
181
|
+
"""Layer indices and delta-scaled `tau` for the evaluation grid `tau`, cached."""
|
|
182
|
+
if (not autograd_compatible and _tau_cache["tau"] is not None
|
|
183
|
+
and np.array_equal(tau, _tau_cache["tau"])):
|
|
184
|
+
return _tau_cache["l"], _tau_cache["scaled_tau"]
|
|
185
|
+
# Atmospheric layer indices: smallest l with tau <= tau_arr[l]
|
|
186
|
+
l = np.searchsorted(tau_arr, tau, side="left")
|
|
187
|
+
if is_delta_scaled:
|
|
188
|
+
scaled_tau = scaled_tau_arr_with_0[1:][l] - (tau_arr[l] - tau) * scale_tau[l]
|
|
189
|
+
else:
|
|
190
|
+
scaled_tau = tau
|
|
191
|
+
if not autograd_compatible:
|
|
192
|
+
_tau_cache.update(
|
|
193
|
+
tau=np.array(tau, copy=True), l=l, scaled_tau=scaled_tau, exp0=None, expm=None
|
|
194
|
+
)
|
|
195
|
+
return l, scaled_tau
|
|
196
|
+
|
|
197
|
+
def _exp_exponent(l, scaled_tau, all_modes):
|
|
198
|
+
"""exp of the homogeneous-solution exponents for the (cached) evaluation grid.
|
|
199
|
+
Returns shape (Ntau, NQuad) for the 0th mode, or (NFourier, Ntau, NQuad) for all modes."""
|
|
200
|
+
key = "expm" if all_modes else "exp0"
|
|
201
|
+
if not autograd_compatible and _tau_cache[key] is not None:
|
|
202
|
+
return _tau_cache[key]
|
|
203
|
+
scaled_tau_arr_l = scaled_tau_arr_with_0[1:][l]
|
|
204
|
+
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
205
|
+
if all_modes:
|
|
206
|
+
exponent = np.concatenate(
|
|
207
|
+
[
|
|
208
|
+
K_collect[:, l, :N] * (scaled_tau - scaled_tau_arr_lm1)[None, :, None],
|
|
209
|
+
K_collect[:, l, N:] * (scaled_tau - scaled_tau_arr_l)[None, :, None],
|
|
210
|
+
],
|
|
211
|
+
axis=-1,
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
exponent = np.concatenate(
|
|
215
|
+
[
|
|
216
|
+
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
217
|
+
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
218
|
+
],
|
|
219
|
+
axis=-1,
|
|
220
|
+
)
|
|
221
|
+
result = np.exp(exponent)
|
|
222
|
+
if not autograd_compatible:
|
|
223
|
+
_tau_cache[key] = result
|
|
224
|
+
return result
|
|
225
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
226
|
+
|
|
166
227
|
if not only_flux:
|
|
167
228
|
|
|
168
229
|
# Construct the intensity function (refer to section 3.7 of the Comprehensive Documentation)
|
|
@@ -181,27 +242,10 @@ def _assemble_intensity_and_fluxes(
|
|
|
181
242
|
if np.any(tau < 0) or np.any(tau > tau_arr[-1]):
|
|
182
243
|
raise ValueError("tau input outside the tau range specified for the atmosphere (check `tau_arr`).")
|
|
183
244
|
|
|
184
|
-
# Atmospheric layer indices
|
|
185
|
-
l =
|
|
186
|
-
|
|
187
|
-
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
245
|
+
# Atmospheric layer indices, delta-M scaling and exponentials (cached)
|
|
246
|
+
l, scaled_tau = _process_tau(tau)
|
|
247
|
+
exp_exponent = _exp_exponent(l, scaled_tau, all_modes=True)
|
|
188
248
|
|
|
189
|
-
# Delta-M scaling
|
|
190
|
-
if np.any(scale_tau != 1):
|
|
191
|
-
tau_dist_from_top = tau_arr[l] - tau
|
|
192
|
-
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
193
|
-
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
194
|
-
else:
|
|
195
|
-
scaled_tau = tau
|
|
196
|
-
|
|
197
|
-
exponent = np.concatenate(
|
|
198
|
-
[
|
|
199
|
-
K_collect[:, l, :N] * (scaled_tau - scaled_tau_arr_lm1)[None, :, None],
|
|
200
|
-
K_collect[:, l, N:] * (scaled_tau - scaled_tau_arr_l)[None, :, None],
|
|
201
|
-
],
|
|
202
|
-
axis=-1,
|
|
203
|
-
)
|
|
204
|
-
|
|
205
249
|
direct_beam_contribution = 0
|
|
206
250
|
if is_antiderivative_wrt_tau:
|
|
207
251
|
if there_is_beam_source:
|
|
@@ -213,7 +257,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
213
257
|
np.einsum(
|
|
214
258
|
"mtij, mtj -> mit",
|
|
215
259
|
GC_collect[:, l, :, :],
|
|
216
|
-
|
|
260
|
+
exp_exponent / (scale_tau[None, :, None] * K_collect)[:, l, :],
|
|
217
261
|
optimize=True,
|
|
218
262
|
)
|
|
219
263
|
+ direct_beam_contribution
|
|
@@ -227,7 +271,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
227
271
|
|
|
228
272
|
um = (
|
|
229
273
|
np.einsum(
|
|
230
|
-
"mtij, mtj -> mit", GC_collect[:, l, :, :],
|
|
274
|
+
"mtij, mtj -> mit", GC_collect[:, l, :, :], exp_exponent, optimize=True
|
|
231
275
|
)
|
|
232
276
|
+ direct_beam_contribution
|
|
233
277
|
)
|
|
@@ -254,49 +298,52 @@ def _assemble_intensity_and_fluxes(
|
|
|
254
298
|
else:
|
|
255
299
|
um[0, :, :] += _mathscr_v_contribution
|
|
256
300
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
301
|
+
# Contract over the Fourier modes directly: materializing the
|
|
302
|
+
# (NFourier, NQuad, Ntau, Nphi) product only to sum over its first axis
|
|
303
|
+
# is substantially slower and heavier for large NFourier * Nphi
|
|
304
|
+
u = np.einsum(
|
|
305
|
+
"mit, mp -> itp",
|
|
306
|
+
um,
|
|
307
|
+
np.cos(np.arange(NFourier)[:, None] * (phi0 - phi)[None, :]),
|
|
308
|
+
optimize=True,
|
|
261
309
|
)
|
|
262
310
|
|
|
263
311
|
outputs = (rescale_factor * np.squeeze(u),)
|
|
264
312
|
output_is_tuple = False
|
|
265
313
|
|
|
266
314
|
if return_Fourier_error:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
K_collect[-1, l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
271
|
-
],
|
|
272
|
-
axis=-1,
|
|
273
|
-
)
|
|
274
|
-
|
|
315
|
+
# The last Fourier mode's exponentials are the last slice of the cached tensor
|
|
316
|
+
exp_exponent_last = exp_exponent[-1]
|
|
317
|
+
|
|
275
318
|
direct_beam_contribution = 0
|
|
276
319
|
if is_antiderivative_wrt_tau:
|
|
277
320
|
if there_is_beam_source:
|
|
321
|
+
# `B_collect[-1].T` has shape (NQuad, NLayers): the last Fourier mode's
|
|
322
|
+
# particular-solution coefficients (`B_collect.T[-1]` would wrongly
|
|
323
|
+
# index the last quadrature node)
|
|
278
324
|
direct_beam_contribution = (
|
|
279
|
-
B_collect
|
|
325
|
+
B_collect[-1].T / (-scale_tau / mu0)[None, :]
|
|
280
326
|
)[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
281
327
|
|
|
282
328
|
ulast = (
|
|
283
329
|
np.einsum(
|
|
284
330
|
"tij, tj -> it",
|
|
285
331
|
GC_collect[-1, l, :, :],
|
|
286
|
-
|
|
332
|
+
exp_exponent_last / (scale_tau[:, None] * K_collect[-1, :, :])[l, :],
|
|
287
333
|
optimize=True,
|
|
288
334
|
)
|
|
289
335
|
+ direct_beam_contribution
|
|
290
336
|
)
|
|
291
337
|
else:
|
|
292
338
|
if there_is_beam_source:
|
|
339
|
+
# See the shape note in the `is_antiderivative_wrt_tau` branch above
|
|
293
340
|
direct_beam_contribution = (
|
|
294
|
-
B_collect.T[
|
|
341
|
+
B_collect[-1].T[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
295
342
|
)
|
|
296
343
|
|
|
297
344
|
ulast = (
|
|
298
345
|
np.einsum(
|
|
299
|
-
"tij, tj -> it", GC_collect[-1, l, :, :],
|
|
346
|
+
"tij, tj -> it", GC_collect[-1, l, :, :], exp_exponent_last, optimize=True
|
|
300
347
|
)
|
|
301
348
|
+ direct_beam_contribution
|
|
302
349
|
)
|
|
@@ -339,41 +386,25 @@ def _assemble_intensity_and_fluxes(
|
|
|
339
386
|
if np.any(tau < 0) or np.any(tau > tau_arr[-1]):
|
|
340
387
|
raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
|
|
341
388
|
|
|
342
|
-
# Atmospheric layer indices
|
|
343
|
-
l =
|
|
344
|
-
|
|
345
|
-
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
389
|
+
# Atmospheric layer indices, delta-M scaling and exponentials (cached)
|
|
390
|
+
l, scaled_tau = _process_tau(tau)
|
|
391
|
+
exp_exponent = _exp_exponent(l, scaled_tau, all_modes=False)
|
|
346
392
|
|
|
347
|
-
#
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
352
|
-
# The following complements the function `subroutines.generate_diff_act_flux_funcs`
|
|
353
|
-
# in performing the reclassification of delta-scaled actinic flux
|
|
354
|
-
if _return_act_dscale_for_reclass:
|
|
355
|
-
if is_antiderivative_wrt_tau:
|
|
356
|
-
act_dscale_reclassification = (
|
|
357
|
-
I0 * np.exp(-scaled_tau / mu0) / (-scale_tau[l] / mu0)
|
|
358
|
-
- I0 * np.exp(-tau / mu0) * -mu0
|
|
359
|
-
)
|
|
360
|
-
else:
|
|
361
|
-
act_dscale_reclassification = I0 * np.exp(-scaled_tau / mu0) - I0 * np.exp(-tau / mu0)
|
|
362
|
-
else:
|
|
363
|
-
scaled_tau = tau
|
|
364
|
-
if _return_act_dscale_for_reclass:
|
|
393
|
+
# The following complements the function `subroutines.generate_diff_act_flux_funcs`
|
|
394
|
+
# in performing the reclassification of delta-scaled actinic flux
|
|
395
|
+
if _return_act_dscale_for_reclass:
|
|
396
|
+
if not is_delta_scaled:
|
|
365
397
|
act_dscale_reclassification = 0
|
|
398
|
+
elif is_antiderivative_wrt_tau:
|
|
399
|
+
act_dscale_reclassification = (
|
|
400
|
+
I0 * np.exp(-scaled_tau / mu0) / (-scale_tau[l] / mu0)
|
|
401
|
+
- I0 * np.exp(-tau / mu0) * -mu0
|
|
402
|
+
)
|
|
403
|
+
else:
|
|
404
|
+
act_dscale_reclassification = I0 * np.exp(-scaled_tau / mu0) - I0 * np.exp(-tau / mu0)
|
|
366
405
|
|
|
367
|
-
exponent = np.concatenate(
|
|
368
|
-
[
|
|
369
|
-
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
370
|
-
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
371
|
-
],
|
|
372
|
-
axis=-1,
|
|
373
|
-
)
|
|
374
|
-
|
|
375
406
|
direct_beam_contribution = 0
|
|
376
|
-
if is_antiderivative_wrt_tau
|
|
407
|
+
if is_antiderivative_wrt_tau:
|
|
377
408
|
if there_is_beam_source:
|
|
378
409
|
direct_beam_contribution = (B_collect_0.T / (-scale_tau / mu0)[None, :])[
|
|
379
410
|
:, l
|
|
@@ -383,7 +414,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
383
414
|
np.einsum(
|
|
384
415
|
"tij, tj -> it",
|
|
385
416
|
GC_collect_0[l, :, :],
|
|
386
|
-
|
|
417
|
+
exp_exponent / (scale_tau[:, None] * K_collect_0)[l, :],
|
|
387
418
|
optimize=True,
|
|
388
419
|
)
|
|
389
420
|
+ direct_beam_contribution
|
|
@@ -396,7 +427,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
396
427
|
|
|
397
428
|
u0 = (
|
|
398
429
|
np.einsum(
|
|
399
|
-
"tij, tj -> it", GC_collect_0[l, :, :],
|
|
430
|
+
"tij, tj -> it", GC_collect_0[l, :, :], exp_exponent, optimize=True
|
|
400
431
|
)
|
|
401
432
|
+ direct_beam_contribution
|
|
402
433
|
)
|
|
@@ -449,18 +480,9 @@ def _assemble_intensity_and_fluxes(
|
|
|
449
480
|
if np.any(tau < 0) or np.any(tau > tau_arr[-1]):
|
|
450
481
|
raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
|
|
451
482
|
|
|
452
|
-
# Atmospheric layer indices
|
|
453
|
-
l =
|
|
454
|
-
|
|
455
|
-
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
456
|
-
|
|
457
|
-
# Delta-M scaling
|
|
458
|
-
if np.any(scale_tau != 1):
|
|
459
|
-
tau_dist_from_top = tau_arr[l] - tau
|
|
460
|
-
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
461
|
-
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
462
|
-
else:
|
|
463
|
-
scaled_tau = tau
|
|
483
|
+
# Atmospheric layer indices, delta-M scaling and exponentials (cached)
|
|
484
|
+
l, scaled_tau = _process_tau(tau)
|
|
485
|
+
exp_exponent = _exp_exponent(l, scaled_tau, all_modes=False)
|
|
464
486
|
|
|
465
487
|
if there_is_iso_source:
|
|
466
488
|
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
@@ -478,14 +500,6 @@ def _assemble_intensity_and_fluxes(
|
|
|
478
500
|
else:
|
|
479
501
|
_mathscr_v_contribution = 0
|
|
480
502
|
|
|
481
|
-
exponent = np.concatenate(
|
|
482
|
-
[
|
|
483
|
-
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
484
|
-
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
485
|
-
],
|
|
486
|
-
axis=-1,
|
|
487
|
-
)
|
|
488
|
-
|
|
489
503
|
direct_beam_contribution = 0
|
|
490
504
|
if is_antiderivative_wrt_tau:
|
|
491
505
|
if there_is_beam_source:
|
|
@@ -497,7 +511,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
497
511
|
np.einsum(
|
|
498
512
|
"tij, tj -> it",
|
|
499
513
|
GC_pos[l, :, :],
|
|
500
|
-
|
|
514
|
+
exp_exponent / (scale_tau[:, None] * K_collect_0)[l, :],
|
|
501
515
|
)
|
|
502
516
|
+ direct_beam_contribution
|
|
503
517
|
+ _mathscr_v_contribution
|
|
@@ -507,7 +521,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
507
521
|
direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
508
522
|
|
|
509
523
|
u0_pos = (
|
|
510
|
-
np.einsum("tij, tj -> it", GC_pos[l, :, :],
|
|
524
|
+
np.einsum("tij, tj -> it", GC_pos[l, :, :], exp_exponent)
|
|
511
525
|
+ direct_beam_contribution
|
|
512
526
|
+ _mathscr_v_contribution
|
|
513
527
|
)
|
|
@@ -532,18 +546,9 @@ def _assemble_intensity_and_fluxes(
|
|
|
532
546
|
if np.any(tau < 0) or np.any(tau > tau_arr[-1]):
|
|
533
547
|
raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
|
|
534
548
|
|
|
535
|
-
# Atmospheric layer indices
|
|
536
|
-
l =
|
|
537
|
-
|
|
538
|
-
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
539
|
-
|
|
540
|
-
# Delta-M scaling
|
|
541
|
-
if np.any(scale_tau != 1):
|
|
542
|
-
tau_dist_from_top = tau_arr[l] - tau
|
|
543
|
-
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
544
|
-
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
545
|
-
else:
|
|
546
|
-
scaled_tau = tau
|
|
549
|
+
# Atmospheric layer indices, delta-M scaling and exponentials (cached)
|
|
550
|
+
l, scaled_tau = _process_tau(tau)
|
|
551
|
+
exp_exponent = _exp_exponent(l, scaled_tau, all_modes=False)
|
|
547
552
|
|
|
548
553
|
if there_is_iso_source:
|
|
549
554
|
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
@@ -561,14 +566,6 @@ def _assemble_intensity_and_fluxes(
|
|
|
561
566
|
else:
|
|
562
567
|
_mathscr_v_contribution = 0
|
|
563
568
|
|
|
564
|
-
exponent = np.concatenate(
|
|
565
|
-
[
|
|
566
|
-
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
567
|
-
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
568
|
-
],
|
|
569
|
-
axis=-1,
|
|
570
|
-
)
|
|
571
|
-
|
|
572
569
|
direct_beam_contribution = 0
|
|
573
570
|
direct_beam = 0
|
|
574
571
|
direct_beam_scaled = 0
|
|
@@ -579,7 +576,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
579
576
|
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0) / (-scale_tau[l] / mu0)
|
|
580
577
|
|
|
581
578
|
u0_neg = (
|
|
582
|
-
np.einsum("tij, tj -> it", GC_neg[l, :, :],
|
|
579
|
+
np.einsum("tij, tj -> it", GC_neg[l, :, :], exp_exponent / (scale_tau[:, None] * K_collect_0)[l, :])
|
|
583
580
|
+ direct_beam_contribution
|
|
584
581
|
+ _mathscr_v_contribution
|
|
585
582
|
)
|
|
@@ -590,7 +587,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
590
587
|
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0)
|
|
591
588
|
|
|
592
589
|
u0_neg = (
|
|
593
|
-
np.einsum("tij, tj -> it", GC_neg[l, :, :],
|
|
590
|
+
np.einsum("tij, tj -> it", GC_neg[l, :, :], exp_exponent)
|
|
594
591
|
+ direct_beam_contribution
|
|
595
592
|
+ _mathscr_v_contribution
|
|
596
593
|
)
|
|
@@ -122,7 +122,12 @@ def _solve_for_coeffs(
|
|
|
122
122
|
|
|
123
123
|
else:
|
|
124
124
|
LHS = np.zeros((dim, dim), order="F")
|
|
125
|
-
sR, sC = LHS.strides
|
|
125
|
+
sR, sC = LHS.strides
|
|
126
|
+
# Stacks for a single batched `np.linalg.solve` over all Fourier modes after the
|
|
127
|
+
# assembly loop; this removes the per-mode LAPACK dispatch overhead. The banded
|
|
128
|
+
# path keeps its per-mode loop as `scipy.linalg.solve_banded` cannot batch.
|
|
129
|
+
LHS_batch = np.empty((NFourier, dim, dim))
|
|
130
|
+
RHS_batch = np.empty((NFourier, dim))
|
|
126
131
|
def _dense_slab(r0, c0, nrows, ncols):
|
|
127
132
|
"""Batched (NLayers-1, nrows, ncols) view across interlayer blocks of `LHS`.
|
|
128
133
|
Anchored at LHS coords (r0, c0) for layer 0; layer l advances both row and column
|
|
@@ -281,7 +286,7 @@ def _solve_for_coeffs(
|
|
|
281
286
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
282
287
|
|
|
283
288
|
|
|
284
|
-
# Assemble LHS and solve (
|
|
289
|
+
# Assemble LHS and solve (the old version of this code is replicated in section 4 of the Comprehensive Documentation)
|
|
285
290
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
286
291
|
G_0 = G_collect_m[0]
|
|
287
292
|
G_0_nn = G_0[N:, :N]
|
|
@@ -334,7 +339,7 @@ def _solve_for_coeffs(
|
|
|
334
339
|
_view_slanted_batched(2 * N, 3 * N, N)[:] = -G_hi[:, N:, N:] * E_llp1
|
|
335
340
|
|
|
336
341
|
# Solve the system
|
|
337
|
-
|
|
342
|
+
C_collect[m, :] = sc.linalg.solve_banded(
|
|
338
343
|
(Nsupsubdiags, Nsupsubdiags),
|
|
339
344
|
LHS_dof,
|
|
340
345
|
RHS,
|
|
@@ -372,12 +377,15 @@ def _solve_for_coeffs(
|
|
|
372
377
|
_dense_slab(N, 3 * N, N, N)[:] = -G_hi[:, :N, N:] * E_llp1
|
|
373
378
|
_dense_slab(2 * N, 3 * N, N, N)[:] = -G_hi[:, N:, N:] * E_llp1
|
|
374
379
|
|
|
375
|
-
#
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
380
|
+
# Defer to the single batched solve over all Fourier modes below
|
|
381
|
+
LHS_batch[m] = LHS
|
|
382
|
+
RHS_batch[m] = RHS
|
|
383
|
+
|
|
384
|
+
##################################################################################################
|
|
379
385
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
386
|
+
|
|
387
|
+
if not use_banded_solver:
|
|
388
|
+
# One batched call for all Fourier modes
|
|
389
|
+
C_collect = np.linalg.solve(LHS_batch, RHS_batch[:, :, None])[:, :, 0]
|
|
390
|
+
|
|
383
391
|
return G_collect * C_collect.reshape((NFourier, NLayers, 1, NQuad))
|