PythonicDISORT 1.6__py3-none-any.whl → 1.8__py3-none-any.whl
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/_assemble_intensity_and_fluxes.py +126 -125
- PythonicDISORT/_solve_for_coeffs.py +79 -78
- PythonicDISORT/_solve_for_gen_and_part_sols.py +202 -83
- PythonicDISORT/pydisort.py +746 -287
- PythonicDISORT/subroutines.py +169 -50
- {pythonicdisort-1.6.dist-info → pythonicdisort-1.8.dist-info}/METADATA +15 -12
- pythonicdisort-1.8.dist-info/RECORD +11 -0
- {pythonicdisort-1.6.dist-info → pythonicdisort-1.8.dist-info}/WHEEL +1 -1
- pythonicdisort-1.6.dist-info/RECORD +0 -11
- {pythonicdisort-1.6.dist-info → pythonicdisort-1.8.dist-info}/LICENSE.md +0 -0
- {pythonicdisort-1.6.dist-info → pythonicdisort-1.8.dist-info}/top_level.txt +0 -0
|
@@ -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 != np.ones(NLayers)):
|
|
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
|
)
|
|
@@ -236,7 +280,8 @@ def _assemble_intensity_and_fluxes(
|
|
|
236
280
|
if there_is_iso_source:
|
|
237
281
|
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
238
282
|
_mathscr_v_contribution = _mathscr_v(
|
|
239
|
-
scaled_tau, scale_tau,
|
|
283
|
+
scaled_tau, scale_tau[l_uniq], # `scale_tau` is only required to determine the antiderivative
|
|
284
|
+
l_inv,
|
|
240
285
|
Nscoeffs,
|
|
241
286
|
scaled_s_poly_coeffs[l_uniq],
|
|
242
287
|
G_collect_0[l_uniq],
|
|
@@ -253,49 +298,52 @@ def _assemble_intensity_and_fluxes(
|
|
|
253
298
|
else:
|
|
254
299
|
um[0, :, :] += _mathscr_v_contribution
|
|
255
300
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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,
|
|
260
309
|
)
|
|
261
310
|
|
|
262
311
|
outputs = (rescale_factor * np.squeeze(u),)
|
|
263
312
|
output_is_tuple = False
|
|
264
313
|
|
|
265
314
|
if return_Fourier_error:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
K_collect[-1, l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
270
|
-
],
|
|
271
|
-
axis=-1,
|
|
272
|
-
)
|
|
273
|
-
|
|
315
|
+
# The last Fourier mode's exponentials are the last slice of the cached tensor
|
|
316
|
+
exp_exponent_last = exp_exponent[-1]
|
|
317
|
+
|
|
274
318
|
direct_beam_contribution = 0
|
|
275
319
|
if is_antiderivative_wrt_tau:
|
|
276
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)
|
|
277
324
|
direct_beam_contribution = (
|
|
278
|
-
B_collect
|
|
325
|
+
B_collect[-1].T / (-scale_tau / mu0)[None, :]
|
|
279
326
|
)[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
280
327
|
|
|
281
328
|
ulast = (
|
|
282
329
|
np.einsum(
|
|
283
330
|
"tij, tj -> it",
|
|
284
331
|
GC_collect[-1, l, :, :],
|
|
285
|
-
|
|
332
|
+
exp_exponent_last / (scale_tau[:, None] * K_collect[-1, :, :])[l, :],
|
|
286
333
|
optimize=True,
|
|
287
334
|
)
|
|
288
335
|
+ direct_beam_contribution
|
|
289
336
|
)
|
|
290
337
|
else:
|
|
291
338
|
if there_is_beam_source:
|
|
339
|
+
# See the shape note in the `is_antiderivative_wrt_tau` branch above
|
|
292
340
|
direct_beam_contribution = (
|
|
293
|
-
B_collect.T[
|
|
341
|
+
B_collect[-1].T[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
294
342
|
)
|
|
295
343
|
|
|
296
344
|
ulast = (
|
|
297
345
|
np.einsum(
|
|
298
|
-
"tij, tj -> it", GC_collect[-1, l, :, :],
|
|
346
|
+
"tij, tj -> it", GC_collect[-1, l, :, :], exp_exponent_last, optimize=True
|
|
299
347
|
)
|
|
300
348
|
+ direct_beam_contribution
|
|
301
349
|
)
|
|
@@ -338,41 +386,25 @@ def _assemble_intensity_and_fluxes(
|
|
|
338
386
|
if np.any(tau < 0) or np.any(tau > tau_arr[-1]):
|
|
339
387
|
raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
|
|
340
388
|
|
|
341
|
-
# Atmospheric layer indices
|
|
342
|
-
l =
|
|
343
|
-
|
|
344
|
-
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)
|
|
345
392
|
|
|
346
|
-
#
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
351
|
-
# The following complements the function `subroutines.generate_diff_act_flux_funcs`
|
|
352
|
-
# in performing the reclassification of delta-scaled actinic flux
|
|
353
|
-
if _return_act_dscale_for_reclass:
|
|
354
|
-
if is_antiderivative_wrt_tau:
|
|
355
|
-
act_dscale_reclassification = (
|
|
356
|
-
I0 * np.exp(-scaled_tau / mu0) / (-scale_tau[l] / mu0)
|
|
357
|
-
- I0 * np.exp(-tau / mu0) * -mu0
|
|
358
|
-
)
|
|
359
|
-
else:
|
|
360
|
-
act_dscale_reclassification = I0 * np.exp(-scaled_tau / mu0) - I0 * np.exp(-tau / mu0)
|
|
361
|
-
else:
|
|
362
|
-
scaled_tau = tau
|
|
363
|
-
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:
|
|
364
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)
|
|
365
405
|
|
|
366
|
-
exponent = np.concatenate(
|
|
367
|
-
[
|
|
368
|
-
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
369
|
-
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
370
|
-
],
|
|
371
|
-
axis=-1,
|
|
372
|
-
)
|
|
373
|
-
|
|
374
406
|
direct_beam_contribution = 0
|
|
375
|
-
if is_antiderivative_wrt_tau
|
|
407
|
+
if is_antiderivative_wrt_tau:
|
|
376
408
|
if there_is_beam_source:
|
|
377
409
|
direct_beam_contribution = (B_collect_0.T / (-scale_tau / mu0)[None, :])[
|
|
378
410
|
:, l
|
|
@@ -382,7 +414,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
382
414
|
np.einsum(
|
|
383
415
|
"tij, tj -> it",
|
|
384
416
|
GC_collect_0[l, :, :],
|
|
385
|
-
|
|
417
|
+
exp_exponent / (scale_tau[:, None] * K_collect_0)[l, :],
|
|
386
418
|
optimize=True,
|
|
387
419
|
)
|
|
388
420
|
+ direct_beam_contribution
|
|
@@ -395,7 +427,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
395
427
|
|
|
396
428
|
u0 = (
|
|
397
429
|
np.einsum(
|
|
398
|
-
"tij, tj -> it", GC_collect_0[l, :, :],
|
|
430
|
+
"tij, tj -> it", GC_collect_0[l, :, :], exp_exponent, optimize=True
|
|
399
431
|
)
|
|
400
432
|
+ direct_beam_contribution
|
|
401
433
|
)
|
|
@@ -404,7 +436,8 @@ def _assemble_intensity_and_fluxes(
|
|
|
404
436
|
if there_is_iso_source:
|
|
405
437
|
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
406
438
|
_mathscr_v_contribution = _mathscr_v(
|
|
407
|
-
scaled_tau, scale_tau,
|
|
439
|
+
scaled_tau, scale_tau[l_uniq], # `scale_tau` is only required to determine the antiderivative
|
|
440
|
+
l_inv,
|
|
408
441
|
Nscoeffs,
|
|
409
442
|
scaled_s_poly_coeffs[l_uniq],
|
|
410
443
|
G_collect_0[l_uniq],
|
|
@@ -447,23 +480,15 @@ def _assemble_intensity_and_fluxes(
|
|
|
447
480
|
if np.any(tau < 0) or np.any(tau > tau_arr[-1]):
|
|
448
481
|
raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
|
|
449
482
|
|
|
450
|
-
# Atmospheric layer indices
|
|
451
|
-
l =
|
|
452
|
-
|
|
453
|
-
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
454
|
-
|
|
455
|
-
# Delta-M scaling
|
|
456
|
-
if np.any(scale_tau != np.ones(NLayers)):
|
|
457
|
-
tau_dist_from_top = tau_arr[l] - tau
|
|
458
|
-
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
459
|
-
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
460
|
-
else:
|
|
461
|
-
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)
|
|
462
486
|
|
|
463
487
|
if there_is_iso_source:
|
|
464
488
|
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
465
489
|
_mathscr_v_contribution = _mathscr_v(
|
|
466
|
-
scaled_tau, scale_tau,
|
|
490
|
+
scaled_tau, scale_tau[l_uniq], # `scale_tau` is only required to determine the antiderivative
|
|
491
|
+
l_inv,
|
|
467
492
|
Nscoeffs,
|
|
468
493
|
scaled_s_poly_coeffs[l_uniq],
|
|
469
494
|
G_collect_0[l_uniq, :N, :],
|
|
@@ -475,14 +500,6 @@ def _assemble_intensity_and_fluxes(
|
|
|
475
500
|
else:
|
|
476
501
|
_mathscr_v_contribution = 0
|
|
477
502
|
|
|
478
|
-
exponent = np.concatenate(
|
|
479
|
-
[
|
|
480
|
-
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
481
|
-
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
482
|
-
],
|
|
483
|
-
axis=-1,
|
|
484
|
-
)
|
|
485
|
-
|
|
486
503
|
direct_beam_contribution = 0
|
|
487
504
|
if is_antiderivative_wrt_tau:
|
|
488
505
|
if there_is_beam_source:
|
|
@@ -494,7 +511,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
494
511
|
np.einsum(
|
|
495
512
|
"tij, tj -> it",
|
|
496
513
|
GC_pos[l, :, :],
|
|
497
|
-
|
|
514
|
+
exp_exponent / (scale_tau[:, None] * K_collect_0)[l, :],
|
|
498
515
|
)
|
|
499
516
|
+ direct_beam_contribution
|
|
500
517
|
+ _mathscr_v_contribution
|
|
@@ -504,7 +521,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
504
521
|
direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
505
522
|
|
|
506
523
|
u0_pos = (
|
|
507
|
-
np.einsum("tij, tj -> it", GC_pos[l, :, :],
|
|
524
|
+
np.einsum("tij, tj -> it", GC_pos[l, :, :], exp_exponent)
|
|
508
525
|
+ direct_beam_contribution
|
|
509
526
|
+ _mathscr_v_contribution
|
|
510
527
|
)
|
|
@@ -529,23 +546,15 @@ def _assemble_intensity_and_fluxes(
|
|
|
529
546
|
if np.any(tau < 0) or np.any(tau > tau_arr[-1]):
|
|
530
547
|
raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
|
|
531
548
|
|
|
532
|
-
# Atmospheric layer indices
|
|
533
|
-
l =
|
|
534
|
-
|
|
535
|
-
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
536
|
-
|
|
537
|
-
# Delta-M scaling
|
|
538
|
-
if np.any(scale_tau != np.ones(NLayers)):
|
|
539
|
-
tau_dist_from_top = tau_arr[l] - tau
|
|
540
|
-
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
541
|
-
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
542
|
-
else:
|
|
543
|
-
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)
|
|
544
552
|
|
|
545
553
|
if there_is_iso_source:
|
|
546
554
|
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
547
555
|
_mathscr_v_contribution = _mathscr_v(
|
|
548
|
-
scaled_tau, scale_tau,
|
|
556
|
+
scaled_tau, scale_tau[l_uniq], # `scale_tau` is only required to determine the antiderivative
|
|
557
|
+
l_inv,
|
|
549
558
|
Nscoeffs,
|
|
550
559
|
scaled_s_poly_coeffs[l_uniq],
|
|
551
560
|
G_collect_0[l_uniq, N:, :],
|
|
@@ -557,25 +566,17 @@ def _assemble_intensity_and_fluxes(
|
|
|
557
566
|
else:
|
|
558
567
|
_mathscr_v_contribution = 0
|
|
559
568
|
|
|
560
|
-
exponent = np.concatenate(
|
|
561
|
-
[
|
|
562
|
-
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
563
|
-
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
564
|
-
],
|
|
565
|
-
axis=-1,
|
|
566
|
-
)
|
|
567
|
-
|
|
568
569
|
direct_beam_contribution = 0
|
|
569
570
|
direct_beam = 0
|
|
570
571
|
direct_beam_scaled = 0
|
|
571
572
|
if is_antiderivative_wrt_tau:
|
|
572
573
|
if there_is_beam_source:
|
|
573
|
-
direct_beam_contribution = (B_neg / (-scale_tau / mu0)[None,
|
|
574
|
+
direct_beam_contribution = (B_neg / (-scale_tau / mu0)[None, :])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
574
575
|
direct_beam = I0 * mu0 * np.exp(-tau / mu0) * -mu0
|
|
575
|
-
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0) / (-scale_tau / mu0)
|
|
576
|
+
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0) / (-scale_tau[l] / mu0)
|
|
576
577
|
|
|
577
578
|
u0_neg = (
|
|
578
|
-
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, :])
|
|
579
580
|
+ direct_beam_contribution
|
|
580
581
|
+ _mathscr_v_contribution
|
|
581
582
|
)
|
|
@@ -586,7 +587,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
586
587
|
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0)
|
|
587
588
|
|
|
588
589
|
u0_neg = (
|
|
589
|
-
np.einsum("tij, tj -> it", GC_neg[l, :, :],
|
|
590
|
+
np.einsum("tij, tj -> it", GC_neg[l, :, :], exp_exponent)
|
|
590
591
|
+ direct_beam_contribution
|
|
591
592
|
+ _mathscr_v_contribution
|
|
592
593
|
)
|
|
@@ -79,7 +79,8 @@ def _solve_for_coeffs(
|
|
|
79
79
|
use_banded_solver = (NLayers >= use_banded_solver_NLayers)
|
|
80
80
|
dim = NLayers * NQuad
|
|
81
81
|
C_collect = np.empty((NFourier, dim))
|
|
82
|
-
|
|
82
|
+
thickness = np.diff(scaled_tau_arr_with_0) # (NLayers,)
|
|
83
|
+
|
|
83
84
|
RHS = np.empty(dim)
|
|
84
85
|
i_bot = dim - N # Equals `j_bot_right`
|
|
85
86
|
j_bot_left = dim - NQuad
|
|
@@ -89,23 +90,52 @@ def _solve_for_coeffs(
|
|
|
89
90
|
LHS_dof = np.zeros((6 * N - 1, dim), order="F") # Diagonal ordered form
|
|
90
91
|
s0, s1 = LHS_dof.strides
|
|
91
92
|
col_stride = s1 - s0 # Stride when moving +1 col and -1 row
|
|
92
|
-
|
|
93
|
+
|
|
93
94
|
def _view_slanted(i0, j0, nrows):
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
"""Single slanted view into `LHS_dof` anchored at conceptual LHS coords (i0, j0).
|
|
96
|
+
Returns a 2D view of shape (nrows, N). Used for top/bottom boundary-condition rows.
|
|
97
|
+
|
|
98
|
+
Uses `numpy.lib.stride_tricks.as_strided` (zero-copy but unsafe), which is why
|
|
99
|
+
this helper is not exposed in `PythonicDISORT.subroutines`.
|
|
98
100
|
See https://numpy.org/devdocs/reference/generated/numpy.lib.stride_tricks.as_strided.html.
|
|
99
|
-
|
|
101
|
+
|
|
100
102
|
Assume the following exist in the outer scope:
|
|
101
|
-
`LHS_dof`, `Nsupsubdiags`, `s0`, `col_stride
|
|
103
|
+
`LHS_dof`, `Nsupsubdiags`, `N`, `s0`, `col_stride`.
|
|
102
104
|
"""
|
|
103
105
|
r0 = Nsupsubdiags + i0 - j0
|
|
104
106
|
base = LHS_dof[r0 : r0 + nrows, j0 : j0 + N]
|
|
105
107
|
return as_strided(base, shape=(nrows, N), strides=(s0, col_stride))
|
|
106
|
-
|
|
108
|
+
|
|
109
|
+
def _view_slanted_batched(i0, j0, nrows):
|
|
110
|
+
"""Batched (NLayers-1, nrows, N) slanted view across interlayer continuity rows.
|
|
111
|
+
Anchored at conceptual LHS coords (i0, j0) for layer 0; layer l advances both
|
|
112
|
+
i and j by `NQuad` along the diagonal.
|
|
113
|
+
|
|
114
|
+
Same `(i0, j0)` convention as `_view_slanted` (slant arithmetic done internally).
|
|
115
|
+
Assume the following exist in the outer scope:
|
|
116
|
+
`LHS_dof`, `Nsupsubdiags`, `N`, `NQuad`, `NLayers`, `s0`, `s1`, `col_stride`.
|
|
117
|
+
"""
|
|
118
|
+
r0 = Nsupsubdiags + i0 - j0 # matches `_view_slanted`'s internal slant
|
|
119
|
+
base = LHS_dof[r0 : r0 + nrows, j0 : j0 + N + (NLayers - 2) * NQuad]
|
|
120
|
+
return as_strided(base, shape=(NLayers - 1, nrows, N),
|
|
121
|
+
strides=(NQuad * s1, s0, col_stride))
|
|
122
|
+
|
|
107
123
|
else:
|
|
108
124
|
LHS = np.zeros((dim, dim), order="F")
|
|
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))
|
|
131
|
+
def _dense_slab(r0, c0, nrows, ncols):
|
|
132
|
+
"""Batched (NLayers-1, nrows, ncols) view across interlayer blocks of `LHS`.
|
|
133
|
+
Anchored at LHS coords (r0, c0) for layer 0; layer l advances both row and column
|
|
134
|
+
anchors by `NQuad` along the diagonal. Each slab has shape (nrows, ncols)."""
|
|
135
|
+
base = LHS[r0 : r0 + nrows + (NLayers - 2) * NQuad,
|
|
136
|
+
c0 : c0 + ncols + (NLayers - 2) * NQuad]
|
|
137
|
+
return as_strided(base, shape=(NLayers - 1, nrows, ncols),
|
|
138
|
+
strides=(NQuad * (sR + sC), sR, sC))
|
|
109
139
|
|
|
110
140
|
# The following loops can easily be parallelized, but the speed-up is unlikely to be worth the overhead
|
|
111
141
|
for m in range(NFourier):
|
|
@@ -256,13 +286,13 @@ def _solve_for_coeffs(
|
|
|
256
286
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
257
287
|
|
|
258
288
|
|
|
259
|
-
# Assemble LHS and solve (
|
|
289
|
+
# Assemble LHS and solve (the old version of this code is replicated in section 4 of the Comprehensive Documentation)
|
|
260
290
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
261
291
|
G_0 = G_collect_m[0]
|
|
262
292
|
G_0_nn = G_0[N:, :N]
|
|
263
293
|
G_0_np = G_0[N:, N:]
|
|
264
294
|
E_01 = np.exp(K_collect_m[0, :N] * scaled_tau_arr_with_0[1])[None, :]
|
|
265
|
-
|
|
295
|
+
|
|
266
296
|
G_L = G_collect_m[-1]
|
|
267
297
|
G_L_pn = G_L[:N, :N]
|
|
268
298
|
G_L_nn = G_L[N:, :N]
|
|
@@ -271,6 +301,9 @@ def _solve_for_coeffs(
|
|
|
271
301
|
E_Lm1L = np.exp(
|
|
272
302
|
K_collect_m[-1, :N] * (scaled_tau_arr_with_0[-1] - scaled_tau_arr_with_0[-2])
|
|
273
303
|
)[None, :]
|
|
304
|
+
# Per-layer thickness exponentials, computed once per Fourier mode and reused below.
|
|
305
|
+
# If looping over `l in range(NLayers)`, the line below would be `E_pos_full[l] = exp(-thickness[l] * K_pos[l])`
|
|
306
|
+
E_pos_full = np.exp(-thickness[:, None] * K_collect_m[:, N:]) # (NLayers, N)
|
|
274
307
|
|
|
275
308
|
################################## Use `sc.linalg.solve_banded` ##################################
|
|
276
309
|
if use_banded_solver:
|
|
@@ -292,44 +325,27 @@ def _solve_for_coeffs(
|
|
|
292
325
|
_view_slanted(i_bot, j_bot_left, N)[:] = G_L_pn * E_Lm1L
|
|
293
326
|
_view_slanted(i_bot, i_bot, N)[:] = G_L_pp
|
|
294
327
|
|
|
295
|
-
# ---------------- Interlayer / continuity BCs ----------------
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
G_l_ap = G_l[:, N:] # (2N,N)
|
|
301
|
-
|
|
302
|
-
G_lp1 = G_collect_m[l + 1]
|
|
303
|
-
G_lp1_an = G_lp1[:, :N] # (2N,N)
|
|
304
|
-
G_lp1_pp = G_lp1[:N, N:] # (N,N)
|
|
305
|
-
G_lp1_np = G_lp1[N:, N:] # (N,N)
|
|
306
|
-
|
|
307
|
-
tau_lm1 = scaled_tau_arr_with_0[l]
|
|
308
|
-
tau_l = scaled_tau_arr_with_0[l + 1]
|
|
309
|
-
tau_lp1 = scaled_tau_arr_with_0[l + 2]
|
|
310
|
-
|
|
311
|
-
E_lm1l = np.exp(K_collect_m[l, N:] * (tau_lm1 - tau_l))[None, :] # (N,)
|
|
312
|
-
E_llp1 = np.exp(K_collect_m[l + 1, N:] * (tau_l - tau_lp1))[None, :] # (N,)
|
|
328
|
+
# ---------------- Interlayer / continuity BCs (batched across layers) ----------------
|
|
329
|
+
E_lm1l = E_pos_full[:-1, None, :] # (NLayers-1, 1, N)
|
|
330
|
+
E_llp1 = E_pos_full[1:, None, :] # (NLayers-1, 1, N)
|
|
331
|
+
G_lo = G_collect_m[:-1] # (NLayers-1, NQuad, NQuad)
|
|
332
|
+
G_hi = G_collect_m[1:] # (NLayers-1, NQuad, NQuad)
|
|
313
333
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
_view_slanted(start_row, start_col + NQuad, NQuad)[:] = -G_lp1_an
|
|
322
|
-
_view_slanted(start_row, start_col + 3 * N, N)[:] = -G_lp1_pp * E_llp1
|
|
323
|
-
_view_slanted(start_row + N, start_col + 3 * N, N)[:] = -G_lp1_np * E_llp1
|
|
334
|
+
_view_slanted_batched(N, 0, N)[:] = G_lo[:, :N, :N] * E_lm1l
|
|
335
|
+
_view_slanted_batched(2 * N, 0, N)[:] = G_lo[:, N:, :N] * E_lm1l
|
|
336
|
+
_view_slanted_batched(N, N, NQuad)[:] = G_lo[:, :, N:]
|
|
337
|
+
_view_slanted_batched(N, NQuad, NQuad)[:] = -G_hi[:, :, :N]
|
|
338
|
+
_view_slanted_batched(N, 3 * N, N)[:] = -G_hi[:, :N, N:] * E_llp1
|
|
339
|
+
_view_slanted_batched(2 * N, 3 * N, N)[:] = -G_hi[:, N:, N:] * E_llp1
|
|
324
340
|
|
|
325
341
|
# Solve the system
|
|
326
|
-
|
|
342
|
+
C_collect[m, :] = sc.linalg.solve_banded(
|
|
327
343
|
(Nsupsubdiags, Nsupsubdiags),
|
|
328
344
|
LHS_dof,
|
|
329
345
|
RHS,
|
|
330
346
|
False,
|
|
331
347
|
True,
|
|
332
|
-
|
|
348
|
+
True,
|
|
333
349
|
)
|
|
334
350
|
|
|
335
351
|
##################################################################################################
|
|
@@ -348,43 +364,28 @@ def _solve_for_coeffs(
|
|
|
348
364
|
LHS[-N:, -NQuad : -N] = G_L_pn * E_Lm1L
|
|
349
365
|
LHS[-N:, -N:] = G_L_pp
|
|
350
366
|
|
|
351
|
-
# ---------------- Interlayer / continuity BCs ----------------
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
364
|
-
scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
|
|
365
|
-
scaled_tau_arr_lp1 = scaled_tau_arr_with_0[l + 2]
|
|
366
|
-
|
|
367
|
-
# Postive eigenvalues
|
|
368
|
-
E_lm1l = np.exp(K_collect_m[l, N:] * (scaled_tau_arr_lm1 - scaled_tau_arr_l))
|
|
369
|
-
E_llp1 = np.exp(K_collect_m[l + 1, N:] * (scaled_tau_arr_l - scaled_tau_arr_lp1))
|
|
370
|
-
|
|
371
|
-
start_row = N + l * NQuad
|
|
372
|
-
start_col = l * NQuad
|
|
373
|
-
|
|
374
|
-
LHS[start_row : N + start_row, start_col : N + start_col] = G_l_pn * E_lm1l
|
|
375
|
-
LHS[N + start_row : NQuad + start_row, start_col : N + start_col] = G_l_nn * E_lm1l
|
|
376
|
-
LHS[start_row : NQuad + start_row, N + start_col : NQuad + start_col] = G_l_ap
|
|
377
|
-
|
|
378
|
-
LHS[start_row : NQuad + start_row, NQuad + start_col : 3 * N + start_col] = -G_lp1_an
|
|
379
|
-
LHS[start_row : N + start_row, 3 * N + start_col : 4 * N + start_col] = -G_lp1_pp * E_llp1
|
|
380
|
-
LHS[N + start_row : NQuad + start_row, 3 * N + start_col : 4 * N + start_col] = -G_lp1_np * E_llp1
|
|
381
|
-
|
|
382
|
-
# Solve the system
|
|
383
|
-
C_m = np.linalg.solve(LHS, RHS)
|
|
367
|
+
# ---------------- Interlayer / continuity BCs (batched across layers) ----------------
|
|
368
|
+
E_lm1l = E_pos_full[:-1, None, :] # (NLayers-1, 1, N)
|
|
369
|
+
E_llp1 = E_pos_full[1:, None, :] # (NLayers-1, 1, N)
|
|
370
|
+
G_lo = G_collect_m[:-1]
|
|
371
|
+
G_hi = G_collect_m[1:]
|
|
372
|
+
|
|
373
|
+
_dense_slab(N, 0, N, N)[:] = G_lo[:, :N, :N] * E_lm1l
|
|
374
|
+
_dense_slab(2 * N, 0, N, N)[:] = G_lo[:, N:, :N] * E_lm1l
|
|
375
|
+
_dense_slab(N, N, NQuad, N)[:] = G_lo[:, :, N:]
|
|
376
|
+
_dense_slab(N, NQuad, NQuad, N)[:] = -G_hi[:, :, :N]
|
|
377
|
+
_dense_slab(N, 3 * N, N, N)[:] = -G_hi[:, :N, N:] * E_llp1
|
|
378
|
+
_dense_slab(2 * N, 3 * N, N, N)[:] = -G_hi[:, N:, N:] * E_llp1
|
|
384
379
|
|
|
385
|
-
|
|
380
|
+
# Defer to the single batched solve over all Fourier modes below
|
|
381
|
+
LHS_batch[m] = LHS
|
|
382
|
+
RHS_batch[m] = RHS
|
|
383
|
+
|
|
384
|
+
##################################################################################################
|
|
386
385
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
+
|
|
390
391
|
return G_collect * C_collect.reshape((NFourier, NLayers, 1, NQuad))
|