PythonicDISORT 1.0.3__py3-none-any.whl → 1.2__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 +113 -99
- PythonicDISORT/_solve_for_gen_and_part_sols.py +22 -38
- PythonicDISORT/pydisort.py +42 -31
- PythonicDISORT/subroutines.py +61 -42
- {pythonicdisort-1.0.3.dist-info → pythonicdisort-1.2.dist-info}/METADATA +3 -3
- pythonicdisort-1.2.dist-info/RECORD +11 -0
- pythonicdisort-1.0.3.dist-info/RECORD +0 -11
- {pythonicdisort-1.0.3.dist-info → pythonicdisort-1.2.dist-info}/LICENSE.md +0 -0
- {pythonicdisort-1.0.3.dist-info → pythonicdisort-1.2.dist-info}/WHEEL +0 -0
- {pythonicdisort-1.0.3.dist-info → pythonicdisort-1.2.dist-info}/top_level.txt +0 -0
|
@@ -100,6 +100,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
100
100
|
else:
|
|
101
101
|
import numpy as np
|
|
102
102
|
|
|
103
|
+
|
|
103
104
|
################################## Assemble uncorrected intensity and flux functions #######################################
|
|
104
105
|
|
|
105
106
|
# Compute all the necessary quantities
|
|
@@ -172,7 +173,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
172
173
|
Pass `return_Fourier_error = True` (defaults to `False`) to return the
|
|
173
174
|
Cauchy / Fourier convergence evaluation (type: float) for the last Fourier term.
|
|
174
175
|
Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
|
|
175
|
-
to switch to an antiderivative of
|
|
176
|
+
to switch to an antiderivative of this function with respect to `tau`.
|
|
176
177
|
"""
|
|
177
178
|
tau = np.atleast_1d(tau)
|
|
178
179
|
phi = np.atleast_1d(phi)
|
|
@@ -200,7 +201,13 @@ def _assemble_intensity_and_fluxes(
|
|
|
200
201
|
axis=-1,
|
|
201
202
|
)
|
|
202
203
|
|
|
203
|
-
|
|
204
|
+
direct_beam_contribution = 0
|
|
205
|
+
if is_antiderivative_wrt_tau:
|
|
206
|
+
if there_is_beam_source:
|
|
207
|
+
direct_beam_contribution = (B_collect / (-scale_tau / mu0)[None, :, None])[
|
|
208
|
+
:, l, :
|
|
209
|
+
] * np.exp(-scaled_tau / mu0)[None, :, None]
|
|
210
|
+
|
|
204
211
|
um = (
|
|
205
212
|
np.einsum(
|
|
206
213
|
"mtij, mtj -> mti",
|
|
@@ -208,23 +215,19 @@ def _assemble_intensity_and_fluxes(
|
|
|
208
215
|
np.exp(exponent) / (scale_tau[None, :, None] * K_collect)[:, l, :],
|
|
209
216
|
optimize=True,
|
|
210
217
|
)
|
|
211
|
-
+
|
|
212
|
-
* np.exp(-scaled_tau / mu0)[None, :, None]
|
|
213
|
-
)
|
|
214
|
-
elif is_antiderivative_wrt_tau and not there_is_beam_source:
|
|
215
|
-
um = np.einsum(
|
|
216
|
-
"mtij, mtj -> mti",
|
|
217
|
-
GC_collect[:, l, :, :],
|
|
218
|
-
np.exp(exponent) / (scale_tau[None, l, None] * K_collect)[:, l, :],
|
|
219
|
-
optimize=True,
|
|
218
|
+
+ direct_beam_contribution
|
|
220
219
|
)
|
|
221
|
-
elif not is_antiderivative_wrt_tau and there_is_beam_source:
|
|
222
|
-
um = np.einsum(
|
|
223
|
-
"mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
|
|
224
|
-
) + B_collect[:, l, :] * np.exp(-scaled_tau / mu0)[None, :, None]
|
|
225
220
|
else:
|
|
226
|
-
|
|
227
|
-
|
|
221
|
+
if there_is_beam_source:
|
|
222
|
+
direct_beam_contribution = (
|
|
223
|
+
B_collect[:, l, :] * np.exp(-scaled_tau / mu0)[None, :, None]
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
um = (
|
|
227
|
+
np.einsum(
|
|
228
|
+
"mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
|
|
229
|
+
)
|
|
230
|
+
+ direct_beam_contribution
|
|
228
231
|
)
|
|
229
232
|
|
|
230
233
|
# Contribution from particular solution for isotropic internal sources
|
|
@@ -238,7 +241,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
238
241
|
G_inv_collect_0[l_uniq],
|
|
239
242
|
mu_arr,
|
|
240
243
|
is_antiderivative_wrt_tau,
|
|
241
|
-
autograd_compatible
|
|
244
|
+
autograd_compatible,
|
|
242
245
|
)
|
|
243
246
|
|
|
244
247
|
if NFourier == 1:
|
|
@@ -254,6 +257,9 @@ def _assemble_intensity_and_fluxes(
|
|
|
254
257
|
np.cos(np.arange(NFourier)[:, None] * (phi0 - phi)[None, :]),
|
|
255
258
|
optimize=True,
|
|
256
259
|
)
|
|
260
|
+
|
|
261
|
+
outputs = (rescale_factor * np.squeeze(u),)
|
|
262
|
+
outputs_len = 1
|
|
257
263
|
|
|
258
264
|
if return_Fourier_error:
|
|
259
265
|
exponent = np.concatenate(
|
|
@@ -263,7 +269,14 @@ def _assemble_intensity_and_fluxes(
|
|
|
263
269
|
],
|
|
264
270
|
axis=-1,
|
|
265
271
|
)
|
|
266
|
-
|
|
272
|
+
|
|
273
|
+
direct_beam_contribution = 0
|
|
274
|
+
if is_antiderivative_wrt_tau:
|
|
275
|
+
if there_is_beam_source:
|
|
276
|
+
direct_beam_contribution = (
|
|
277
|
+
B_collect.T[-1, :, :] / (-scale_tau / mu0)[None, :]
|
|
278
|
+
)[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
279
|
+
|
|
267
280
|
ulast = (
|
|
268
281
|
np.einsum(
|
|
269
282
|
"tij, tj -> it",
|
|
@@ -271,25 +284,21 @@ def _assemble_intensity_and_fluxes(
|
|
|
271
284
|
np.exp(exponent) / (scale_tau[:, None] * K_collect[-1, :, :])[l, :],
|
|
272
285
|
optimize=True,
|
|
273
286
|
)
|
|
274
|
-
+
|
|
275
|
-
* np.exp(-scaled_tau / mu0)[None, :]
|
|
287
|
+
+ direct_beam_contribution
|
|
276
288
|
)
|
|
277
|
-
elif is_antiderivative_wrt_tau and not there_is_beam_source:
|
|
278
|
-
ulast = np.einsum(
|
|
279
|
-
"tij, tj -> it",
|
|
280
|
-
GC_collect[-1, l, :, :],
|
|
281
|
-
np.exp(exponent) / (scale_tau[:, None] * K_collect[-1, :, :])[l, :],
|
|
282
|
-
optimize=True,
|
|
283
|
-
)
|
|
284
|
-
elif not is_antiderivative_wrt_tau and not there_is_beam_source:
|
|
285
|
-
ulast = np.einsum(
|
|
286
|
-
"tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
|
|
287
|
-
) + B_collect.T[-1, :, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
288
289
|
else:
|
|
289
|
-
|
|
290
|
-
|
|
290
|
+
if there_is_beam_source:
|
|
291
|
+
direct_beam_contribution = (
|
|
292
|
+
B_collect.T[-1, :, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
ulast = (
|
|
296
|
+
np.einsum(
|
|
297
|
+
"tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
|
|
298
|
+
)
|
|
299
|
+
+ direct_beam_contribution
|
|
291
300
|
)
|
|
292
|
-
|
|
301
|
+
|
|
293
302
|
u_abs = np.abs(u)
|
|
294
303
|
Fourier_error = np.max(
|
|
295
304
|
np.divide(
|
|
@@ -298,19 +307,18 @@ def _assemble_intensity_and_fluxes(
|
|
|
298
307
|
),
|
|
299
308
|
u_abs,
|
|
300
309
|
out=np.zeros_like(u_abs),
|
|
301
|
-
where=u_abs > 1e-
|
|
310
|
+
where=u_abs > 1e-8,
|
|
302
311
|
)
|
|
303
312
|
)
|
|
304
313
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return rescale_factor * np.squeeze(u)
|
|
314
|
+
outputs += (Fourier_error,)
|
|
315
|
+
outputs_len += 1
|
|
316
|
+
|
|
317
|
+
if return_tau_arr:
|
|
318
|
+
outputs += (tau_arr,)
|
|
319
|
+
outputs_len += 1
|
|
320
|
+
|
|
321
|
+
return outputs[0] if outputs_len == 1 else outputs
|
|
314
322
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
315
323
|
|
|
316
324
|
# Construct u0
|
|
@@ -323,7 +331,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
323
331
|
but reclassification of delta-scaled flux and other corrections must be done manually
|
|
324
332
|
(for actinic flux `generate_diff_act_flux_funcs` will automatically perform the reclassification).
|
|
325
333
|
Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
|
|
326
|
-
to switch to an antiderivative of
|
|
334
|
+
to switch to an antiderivative of this function with respect to `tau`.
|
|
327
335
|
"""
|
|
328
336
|
tau = np.atleast_1d(tau)
|
|
329
337
|
if np.any(tau < 0) or np.any(tau > tau_arr[-1]):
|
|
@@ -362,7 +370,13 @@ def _assemble_intensity_and_fluxes(
|
|
|
362
370
|
axis=-1,
|
|
363
371
|
)
|
|
364
372
|
|
|
373
|
+
direct_beam_contribution = 0
|
|
365
374
|
if is_antiderivative_wrt_tau and there_is_beam_source:
|
|
375
|
+
if there_is_beam_source:
|
|
376
|
+
direct_beam_contribution = (B_collect_0.T / (-scale_tau / mu0)[None, :])[
|
|
377
|
+
:, l
|
|
378
|
+
] * np.exp(-scaled_tau / mu0)[None, :]
|
|
379
|
+
|
|
366
380
|
u0 = (
|
|
367
381
|
np.einsum(
|
|
368
382
|
"tij, tj -> it",
|
|
@@ -370,25 +384,21 @@ def _assemble_intensity_and_fluxes(
|
|
|
370
384
|
np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
|
|
371
385
|
optimize=True,
|
|
372
386
|
)
|
|
373
|
-
+
|
|
374
|
-
* np.exp(-scaled_tau / mu0)[None, :]
|
|
375
|
-
)
|
|
376
|
-
elif is_antiderivative_wrt_tau and not there_is_beam_source:
|
|
377
|
-
u0 = np.einsum(
|
|
378
|
-
"tij, tj -> it",
|
|
379
|
-
GC_collect_0[l, :, :],
|
|
380
|
-
np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
|
|
381
|
-
optimize=True,
|
|
387
|
+
+ direct_beam_contribution
|
|
382
388
|
)
|
|
383
|
-
elif not is_antiderivative_wrt_tau and there_is_beam_source:
|
|
384
|
-
u0 = np.einsum(
|
|
385
|
-
"tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
|
|
386
|
-
) + B_collect_0.T[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
387
389
|
else:
|
|
388
|
-
|
|
389
|
-
|
|
390
|
+
if there_is_beam_source:
|
|
391
|
+
direct_beam_contribution = (
|
|
392
|
+
B_collect_0.T[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
u0 = (
|
|
396
|
+
np.einsum(
|
|
397
|
+
"tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
|
|
398
|
+
)
|
|
399
|
+
+ direct_beam_contribution
|
|
390
400
|
)
|
|
391
|
-
|
|
401
|
+
|
|
392
402
|
# Contribution from particular solution for isotropic internal sources
|
|
393
403
|
if there_is_iso_source:
|
|
394
404
|
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
@@ -400,18 +410,20 @@ def _assemble_intensity_and_fluxes(
|
|
|
400
410
|
G_inv_collect_0[l_uniq],
|
|
401
411
|
mu_arr,
|
|
402
412
|
is_antiderivative_wrt_tau,
|
|
403
|
-
autograd_compatible
|
|
413
|
+
autograd_compatible,
|
|
404
414
|
)
|
|
405
415
|
u0 = u0 + _mathscr_v_contribution
|
|
406
416
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
417
|
+
outputs = (rescale_factor * np.squeeze(u0),)
|
|
418
|
+
outputs_len = 1
|
|
419
|
+
if return_tau_arr:
|
|
420
|
+
outputs += (tau_arr,)
|
|
421
|
+
outputs_len += 1
|
|
422
|
+
if _return_act_dscale_for_reclass:
|
|
423
|
+
outputs += (act_dscale_reclassification,)
|
|
424
|
+
outputs_len += 1
|
|
425
|
+
return outputs[0] if outputs_len == 1 else outputs
|
|
426
|
+
|
|
415
427
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
416
428
|
|
|
417
429
|
# Construct the flux functions (refer to section 3.8 of the Comprehensive Documentation)
|
|
@@ -428,7 +440,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
428
440
|
(Energetic) Flux function with argument `tau` (type: array or float) for positive (upward) `mu` values.
|
|
429
441
|
Returns the diffuse flux magnitudes (same type and size as `tau`).
|
|
430
442
|
Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
|
|
431
|
-
to switch to an antiderivative of
|
|
443
|
+
to switch to an antiderivative of this function with respect to `tau`.
|
|
432
444
|
"""
|
|
433
445
|
tau = np.atleast_1d(tau)
|
|
434
446
|
if np.any(tau < 0) or np.any(tau > tau_arr[-1]):
|
|
@@ -457,20 +469,11 @@ def _assemble_intensity_and_fluxes(
|
|
|
457
469
|
G_inv_collect_0[l_uniq],
|
|
458
470
|
mu_arr,
|
|
459
471
|
is_antiderivative_wrt_tau,
|
|
460
|
-
autograd_compatible
|
|
472
|
+
autograd_compatible,
|
|
461
473
|
)
|
|
462
474
|
else:
|
|
463
475
|
_mathscr_v_contribution = 0
|
|
464
476
|
|
|
465
|
-
if there_is_beam_source and is_antiderivative_wrt_tau:
|
|
466
|
-
direct_beam_contribution = (
|
|
467
|
-
(B_pos / (-scale_tau / mu0)[None, :])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
468
|
-
)
|
|
469
|
-
elif there_is_beam_source and not is_antiderivative_wrt_tau:
|
|
470
|
-
direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
471
|
-
else:
|
|
472
|
-
direct_beam_contribution = 0
|
|
473
|
-
|
|
474
477
|
exponent = np.concatenate(
|
|
475
478
|
[
|
|
476
479
|
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
@@ -479,7 +482,13 @@ def _assemble_intensity_and_fluxes(
|
|
|
479
482
|
axis=-1,
|
|
480
483
|
)
|
|
481
484
|
|
|
485
|
+
direct_beam_contribution = 0
|
|
482
486
|
if is_antiderivative_wrt_tau:
|
|
487
|
+
if there_is_beam_source:
|
|
488
|
+
direct_beam_contribution = (
|
|
489
|
+
(B_pos / (-scale_tau / mu0)[None, :])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
490
|
+
)
|
|
491
|
+
|
|
483
492
|
u0_pos = (
|
|
484
493
|
np.einsum(
|
|
485
494
|
"tij, tj -> it",
|
|
@@ -490,11 +499,15 @@ def _assemble_intensity_and_fluxes(
|
|
|
490
499
|
+ _mathscr_v_contribution
|
|
491
500
|
)
|
|
492
501
|
else:
|
|
502
|
+
if there_is_beam_source:
|
|
503
|
+
direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
504
|
+
|
|
493
505
|
u0_pos = (
|
|
494
506
|
np.einsum("tij, tj -> it", GC_pos[l, :, :], np.exp(exponent))
|
|
495
507
|
+ direct_beam_contribution
|
|
496
508
|
+ _mathscr_v_contribution
|
|
497
509
|
)
|
|
510
|
+
|
|
498
511
|
flux = 2 * pi * (mu_arr_pos * W) @ u0_pos
|
|
499
512
|
|
|
500
513
|
if return_tau_arr:
|
|
@@ -509,7 +522,7 @@ def _assemble_intensity_and_fluxes(
|
|
|
509
522
|
Returns a tuple of the diffuse and direct flux magnitudes respectively where each entry is of the
|
|
510
523
|
same type and size as `tau`.
|
|
511
524
|
Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
|
|
512
|
-
to switch to an antiderivative of
|
|
525
|
+
to switch to an antiderivative of this function with respect to `tau`.
|
|
513
526
|
"""
|
|
514
527
|
tau = np.atleast_1d(tau)
|
|
515
528
|
if np.any(tau < 0) or np.any(tau > tau_arr[-1]):
|
|
@@ -538,24 +551,11 @@ def _assemble_intensity_and_fluxes(
|
|
|
538
551
|
G_inv_collect_0[l_uniq],
|
|
539
552
|
mu_arr,
|
|
540
553
|
is_antiderivative_wrt_tau,
|
|
541
|
-
autograd_compatible
|
|
554
|
+
autograd_compatible,
|
|
542
555
|
)
|
|
543
556
|
else:
|
|
544
557
|
_mathscr_v_contribution = 0
|
|
545
|
-
|
|
546
|
-
if there_is_beam_source and is_antiderivative_wrt_tau:
|
|
547
|
-
direct_beam_contribution = (B_neg / (-scale_tau / mu0)[None, l])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
548
|
-
direct_beam = I0 * mu0 * np.exp(-tau / mu0) * -mu0
|
|
549
|
-
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0) / (-scale_tau / mu0)[None, l]
|
|
550
|
-
elif there_is_beam_source and not is_antiderivative_wrt_tau:
|
|
551
|
-
direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
552
|
-
direct_beam = I0 * mu0 * np.exp(-tau / mu0)
|
|
553
|
-
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0)
|
|
554
|
-
else:
|
|
555
|
-
direct_beam_contribution = 0
|
|
556
|
-
direct_beam = 0
|
|
557
|
-
direct_beam_scaled = 0
|
|
558
|
-
|
|
558
|
+
|
|
559
559
|
exponent = np.concatenate(
|
|
560
560
|
[
|
|
561
561
|
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
@@ -564,18 +564,32 @@ def _assemble_intensity_and_fluxes(
|
|
|
564
564
|
axis=-1,
|
|
565
565
|
)
|
|
566
566
|
|
|
567
|
+
direct_beam_contribution = 0
|
|
568
|
+
direct_beam = 0
|
|
569
|
+
direct_beam_scaled = 0
|
|
567
570
|
if is_antiderivative_wrt_tau:
|
|
571
|
+
if there_is_beam_source:
|
|
572
|
+
direct_beam_contribution = (B_neg / (-scale_tau / mu0)[None, l])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
573
|
+
direct_beam = I0 * mu0 * np.exp(-tau / mu0) * -mu0
|
|
574
|
+
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0) / (-scale_tau / mu0)[None, l]
|
|
575
|
+
|
|
568
576
|
u0_neg = (
|
|
569
577
|
np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :])
|
|
570
578
|
+ direct_beam_contribution
|
|
571
579
|
+ _mathscr_v_contribution
|
|
572
580
|
)
|
|
573
581
|
else:
|
|
582
|
+
if there_is_beam_source:
|
|
583
|
+
direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
584
|
+
direct_beam = I0 * mu0 * np.exp(-tau / mu0)
|
|
585
|
+
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0)
|
|
586
|
+
|
|
574
587
|
u0_neg = (
|
|
575
588
|
np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent))
|
|
576
589
|
+ direct_beam_contribution
|
|
577
590
|
+ _mathscr_v_contribution
|
|
578
591
|
)
|
|
592
|
+
|
|
579
593
|
diffuse_flux = 2 * pi * (mu_arr_pos * W) @ u0_neg + direct_beam_scaled - direct_beam
|
|
580
594
|
|
|
581
595
|
if return_tau_arr:
|
|
@@ -592,6 +606,6 @@ def _assemble_intensity_and_fluxes(
|
|
|
592
606
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
593
607
|
|
|
594
608
|
if only_flux:
|
|
595
|
-
return flux_up, flux_down, u0#, GC_collect_0, K_collect_0
|
|
609
|
+
return flux_up, flux_down, u0#, GC_collect_0, K_collect_0#, B_collect_0
|
|
596
610
|
else:
|
|
597
611
|
return flux_up, flux_down, u0, u
|
|
@@ -101,16 +101,17 @@ def _solve_for_gen_and_part_sols(
|
|
|
101
101
|
# Loop over NLayers atmospheric layers
|
|
102
102
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
103
103
|
for l in range(NLayers):
|
|
104
|
-
|
|
104
|
+
weighted_scaled_Leg_coeffs_l = weighted_scaled_Leg_coeffs[l, :][ells]
|
|
105
|
+
scaled_omega_l = scaled_omega_arr[l]
|
|
106
|
+
omega_times_Leg_coeffs = (scaled_omega_l / 2) * weighted_scaled_Leg_coeffs_l
|
|
105
107
|
|
|
106
|
-
if np.any(
|
|
107
|
-
scaled_omega_l = scaled_omega_arr[l]
|
|
108
|
+
if np.any(np.abs(omega_times_Leg_coeffs) > 1e-8): # There are shortcuts if multiple scattering is insignificant
|
|
108
109
|
|
|
109
110
|
# Generate D
|
|
110
111
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
111
|
-
D_temp =
|
|
112
|
-
D_pos =
|
|
113
|
-
D_neg =
|
|
112
|
+
D_temp = omega_times_Leg_coeffs[None, :] * (fac[None, :] * asso_leg_term_pos.T)
|
|
113
|
+
D_pos = D_temp @ asso_leg_term_pos
|
|
114
|
+
D_neg = D_temp @ asso_leg_term_neg
|
|
114
115
|
|
|
115
116
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
116
117
|
|
|
@@ -130,8 +131,8 @@ def _solve_for_gen_and_part_sols(
|
|
|
130
131
|
# Generate X
|
|
131
132
|
X_temp = (
|
|
132
133
|
(scaled_omega_l * I0 * (2 - (m == 0)) / (4 * pi))
|
|
133
|
-
*
|
|
134
|
-
* asso_leg_term_mu0
|
|
134
|
+
* weighted_scaled_Leg_coeffs_l
|
|
135
|
+
* (fac * asso_leg_term_mu0)
|
|
135
136
|
)
|
|
136
137
|
X_pos = X_temp @ asso_leg_term_pos
|
|
137
138
|
X_neg = X_temp @ asso_leg_term_neg
|
|
@@ -149,7 +150,6 @@ def _solve_for_gen_and_part_sols(
|
|
|
149
150
|
|
|
150
151
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
151
152
|
|
|
152
|
-
|
|
153
153
|
alpha_arr[ind, :, :] = alpha
|
|
154
154
|
beta_arr[ind, :, :] = beta
|
|
155
155
|
no_shortcut_indices.append(ind)
|
|
@@ -173,15 +173,15 @@ def _solve_for_gen_and_part_sols(
|
|
|
173
173
|
|
|
174
174
|
# Diagonalization of coefficient matrix (refer to section 3.4.2 of the Comprehensive Documentation)
|
|
175
175
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
176
|
-
alpha_arr = alpha_arr[
|
|
177
|
-
beta_arr = beta_arr[
|
|
176
|
+
alpha_arr = alpha_arr[no_shortcut_indices, :, :]
|
|
177
|
+
beta_arr = beta_arr[no_shortcut_indices, :, :]
|
|
178
178
|
|
|
179
179
|
K_squared_arr, eigenvecs_GpG_arr = np.linalg.eig(
|
|
180
180
|
np.einsum(
|
|
181
181
|
"lij, ljk -> lik", alpha_arr - beta_arr, alpha_arr + beta_arr, optimize=True
|
|
182
182
|
),
|
|
183
183
|
)
|
|
184
|
-
|
|
184
|
+
|
|
185
185
|
# Eigenvalues arranged negative then positive, from largest to smallest magnitude
|
|
186
186
|
K_arr = np.concatenate((-np.sqrt(K_squared_arr), np.sqrt(K_squared_arr)), axis=1)
|
|
187
187
|
eigenvecs_GpG_arr = np.concatenate((eigenvecs_GpG_arr, eigenvecs_GpG_arr), axis=2)
|
|
@@ -205,33 +205,17 @@ def _solve_for_gen_and_part_sols(
|
|
|
205
205
|
K_collect[no_shortcut_indices, :] = K_arr
|
|
206
206
|
if len(no_shortcut_indices_0) > 0: # If there is no isotropic source this list will be empty
|
|
207
207
|
G_inv_collect_0[no_shortcut_indices_0, :, :] = np.linalg.inv(
|
|
208
|
-
G_collect[
|
|
208
|
+
G_collect[no_shortcut_indices_0, :, :]
|
|
209
209
|
)
|
|
210
210
|
|
|
211
211
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
212
212
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
return (
|
|
223
|
-
G_collect.reshape((NFourier, NLayers, NQuad, NQuad)),
|
|
224
|
-
K_collect.reshape((NFourier, NLayers, NQuad)),
|
|
225
|
-
B_collect.reshape((NFourier, NLayers, NQuad)),
|
|
226
|
-
)
|
|
227
|
-
elif not there_is_beam_source and there_is_iso_source:
|
|
228
|
-
return (
|
|
229
|
-
G_collect.reshape((NFourier, NLayers, NQuad, NQuad)),
|
|
230
|
-
K_collect.reshape((NFourier, NLayers, NQuad)),
|
|
231
|
-
G_inv_collect_0,
|
|
232
|
-
)
|
|
233
|
-
else:
|
|
234
|
-
return (
|
|
235
|
-
G_collect.reshape((NFourier, NLayers, NQuad, NQuad)),
|
|
236
|
-
K_collect.reshape((NFourier, NLayers, NQuad)),
|
|
237
|
-
)
|
|
213
|
+
outputs = (
|
|
214
|
+
G_collect.reshape((NFourier, NLayers, NQuad, NQuad)),
|
|
215
|
+
K_collect.reshape((NFourier, NLayers, NQuad)),
|
|
216
|
+
)
|
|
217
|
+
if there_is_beam_source:
|
|
218
|
+
outputs += (B_collect.reshape((NFourier, NLayers, NQuad)),)
|
|
219
|
+
if there_is_iso_source:
|
|
220
|
+
outputs += (G_inv_collect_0,)
|
|
221
|
+
return outputs
|
PythonicDISORT/pydisort.py
CHANGED
|
@@ -76,7 +76,7 @@ def pydisort(
|
|
|
76
76
|
NT_cor : optional, bool
|
|
77
77
|
Perform Nakajima-Tanaka intensity corrections?
|
|
78
78
|
BDRF_Fourier_modes : optional, list of functions
|
|
79
|
-
BDRF Fourier modes, each a
|
|
79
|
+
BDRF Fourier modes, each a float, or a function with arguments ``mu, -mu_p`` of type array
|
|
80
80
|
which output has the same dimensions as the outer product of the two arrays.
|
|
81
81
|
s_poly_coeffs : optional, array
|
|
82
82
|
Polynomial coefficients of isotropic internal sources.
|
|
@@ -86,17 +86,17 @@ def pydisort(
|
|
|
86
86
|
At or above how many atmospheric layers should ``scipy.linalg.solve_banded`` be used?
|
|
87
87
|
autograd_compatible : optional, bool
|
|
88
88
|
If ``True``, the autograd package: https://github.com/HIPS/autograd can be used to compute
|
|
89
|
-
the ``tau``-derivatives of the output functions but ``pydisort`` will be less efficient.
|
|
89
|
+
the ``tau``-derivatives of the output functions but ``pydisort`` will be less efficient.
|
|
90
90
|
|
|
91
91
|
Returns
|
|
92
92
|
-------
|
|
93
|
-
mu_arr : array
|
|
93
|
+
mu_arr : array
|
|
94
94
|
All ``mu`` (cosine of polar angle) quadrature nodes.
|
|
95
95
|
Fp(tau) : function
|
|
96
96
|
(Energetic) Flux function with argument ``tau`` (type: array or float) for positive (upward) ``mu`` values.
|
|
97
97
|
Returns the diffuse flux magnitudes (same type and size as ``tau``).
|
|
98
98
|
Pass ``is_antiderivative_wrt_tau = True`` (defaults to ``False``)
|
|
99
|
-
to switch to an antiderivative of
|
|
99
|
+
to switch to an antiderivative of this function with respect to ``tau``.
|
|
100
100
|
For example, ``Fp(tau_arr, is_antiderivative_wrt_tau = True) - Fp(np.insert(tau_arr[:-1] + 1e-15, 0, 0), is_antiderivative_wrt_tau = True)``
|
|
101
101
|
will produce an array of the tau-integral over each layer.
|
|
102
102
|
Pass ``return_tau_arr`` to return ``tau_arr`` (defaults to ``False``).
|
|
@@ -105,7 +105,7 @@ def pydisort(
|
|
|
105
105
|
Returns a tuple of the diffuse and direct flux magnitudes respectively where each entry is of the
|
|
106
106
|
same type and size as ``tau``.
|
|
107
107
|
Pass ``is_antiderivative_wrt_tau = True`` (defaults to ``False``)
|
|
108
|
-
to switch to an antiderivative of
|
|
108
|
+
to switch to an antiderivative of this function with respect to ``tau``.
|
|
109
109
|
Pass ``return_tau_arr`` to return ``tau_arr`` (defaults to ``False``).
|
|
110
110
|
u0(tau) : function
|
|
111
111
|
Zeroth Fourier mode of the intensity with argument ``tau`` (type: array or float).
|
|
@@ -114,13 +114,13 @@ def pydisort(
|
|
|
114
114
|
but reclassification of delta-scaled flux and other corrections must be done manually
|
|
115
115
|
(for actinic flux ``subroutines.generate_diff_act_flux_funcs`` will automatically perform the reclassification).
|
|
116
116
|
Pass ``is_antiderivative_wrt_tau = True`` (defaults to ``False``)
|
|
117
|
-
to switch to an antiderivative of
|
|
117
|
+
to switch to an antiderivative of this function with respect to ``tau``.
|
|
118
118
|
Pass ``return_tau_arr`` to return ``tau_arr`` (defaults to ``False``).
|
|
119
119
|
u(tau, phi) : function, optional
|
|
120
120
|
Intensity function with arguments ``(tau, phi)`` each of type array or float.
|
|
121
121
|
Returns an ndarray with axes corresponding to variation with ``mu, tau, phi`` respectively.
|
|
122
122
|
Pass ``is_antiderivative_wrt_tau = True`` (defaults to ``False``)
|
|
123
|
-
to switch to an antiderivative of
|
|
123
|
+
to switch to an antiderivative of this function with respect to ``tau``.
|
|
124
124
|
Pass ``return_Fourier_error = True`` (defaults to ``False``) to return the
|
|
125
125
|
Cauchy / Fourier convergence evaluation (type: float) for the last Fourier term.
|
|
126
126
|
Pass ``return_tau_arr`` to return ``tau_arr`` (defaults to ``False``).
|
|
@@ -172,13 +172,13 @@ def pydisort(
|
|
|
172
172
|
| `Leg_coeffs_residue_avg` | `NLeg_all` |
|
|
173
173
|
| `scaled_mu0` | scalar |
|
|
174
174
|
"""
|
|
175
|
-
|
|
175
|
+
|
|
176
176
|
if autograd_compatible:
|
|
177
177
|
import autograd.numpy as np
|
|
178
178
|
else:
|
|
179
179
|
import numpy as np
|
|
180
180
|
|
|
181
|
-
# Turn
|
|
181
|
+
# Turn floats into arrays
|
|
182
182
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
183
183
|
tau_arr = np.atleast_1d(tau_arr)
|
|
184
184
|
omega_arr = np.atleast_1d(omega_arr)
|
|
@@ -222,7 +222,7 @@ def pydisort(
|
|
|
222
222
|
if not np.all(tau_arr > 0):
|
|
223
223
|
raise ValueError("tau values cannot be non-positive.")
|
|
224
224
|
if not np.all(thickness_arr > 0):
|
|
225
|
-
raise ValueError("Layer
|
|
225
|
+
raise ValueError("Layer thicknesses cannot be non-positive.")
|
|
226
226
|
# Single-scattering albedo must be between 0 and 1, excluding 1
|
|
227
227
|
if not (np.all(omega_arr >= 0) and np.all(omega_arr < 1)):
|
|
228
228
|
raise ValueError("Single-scattering albedo must be between 0 and 1, excluding 1.")
|
|
@@ -230,21 +230,23 @@ def pydisort(
|
|
|
230
230
|
# The user must supply at least as many phase function Legendre coefficients as intended for use
|
|
231
231
|
if not NLeg > 0:
|
|
232
232
|
raise ValueError("The number of phase function Legendre coefficients must be positive.")
|
|
233
|
-
if not np.all(omega_arr * Leg_coeffs_all[:, 0] == omega_arr):
|
|
234
|
-
raise ValueError("The first phase function Legendre coefficient must equal 1.")
|
|
235
|
-
if not (np.all(-1 <= Leg_coeffs_all) and np.all(Leg_coeffs_all <= 1)):
|
|
236
|
-
raise ValueError("The phase function Legendre coefficients must all be between -1 and 1.")
|
|
237
233
|
if not NLeg <= NLeg_all:
|
|
238
234
|
raise ValueError("`NLeg` cannot be larger than the number of phase function Legendre coefficients provided.")
|
|
239
235
|
# Ensure that the first dimension of the following inputs corresponds to the number of layers
|
|
240
236
|
if not np.shape(Leg_coeffs_all)[0] == NLayers:
|
|
241
|
-
raise ValueError("The zeroth dimension of the shape of `Leg_coeffs_all` does not match the number of layers
|
|
237
|
+
raise ValueError("The zeroth dimension of the shape of `Leg_coeffs_all` does not match the number of layers which is deduced from the length of `tau_arr`.")
|
|
242
238
|
if not len(omega_arr) == NLayers:
|
|
243
|
-
raise ValueError("The zeroth dimension of the shape of `omega_arr` does not match the number of layers
|
|
239
|
+
raise ValueError("The zeroth dimension of the shape of `omega_arr` does not match the number of layers which is deduced from the length of `tau_arr`.")
|
|
244
240
|
if np.any(f_arr != 0) and not len(f_arr) == NLayers:
|
|
245
|
-
raise ValueError("The length of `f_arr` does not match the number of layers
|
|
241
|
+
raise ValueError("The length of `f_arr` does not match the number of layers which is deduced from the length of `tau_arr`.")
|
|
246
242
|
if there_is_iso_source and not np.shape(s_poly_coeffs)[0] == NLayers:
|
|
247
|
-
raise ValueError("The zeroth dimension of the shape of `s_poly_coeffs` does not match the number of layers
|
|
243
|
+
raise ValueError("The zeroth dimension of the shape of `s_poly_coeffs` does not match the number of layers which is deduced from the length of `tau_arr`.")
|
|
244
|
+
# Value checks on the phase function Legendre coefficients
|
|
245
|
+
if not np.all(omega_arr * Leg_coeffs_all[:, 0] == omega_arr):
|
|
246
|
+
warnings.warn("The zeroth index phase function Legendre coefficient must be, and has been corrected to, 1.")
|
|
247
|
+
Leg_coeffs_all[:, 0] = 1
|
|
248
|
+
if not (np.all(-1 <= Leg_coeffs_all) and np.all(Leg_coeffs_all <= 1)):
|
|
249
|
+
raise ValueError("The phase function Legendre coefficients must all be between -1 and 1.")
|
|
248
250
|
# Conditions on the number of quadrature angles (NQuad), Legendre coefficients (NLeg) and loops (NFourier)
|
|
249
251
|
if not NQuad >= 2:
|
|
250
252
|
raise ValueError("There must be at least two streams.")
|
|
@@ -255,13 +257,13 @@ def pydisort(
|
|
|
255
257
|
if not NFourier <= NLeg:
|
|
256
258
|
raise ValueError("The number of Fourier modes to use in the solution must be less than or equal to the number of phase function Legendre coefficients used.")
|
|
257
259
|
if NFourier > 64 and not only_flux:
|
|
258
|
-
warnings.warn("`NFourier` is large and may cause errors, consider decreasing `NFourier` to 64 and it probably should be even less.")
|
|
260
|
+
warnings.warn("`NFourier` is large and may cause errors, consider decreasing `NFourier` to 64 and it probably should be even less. By default `NFourier` equals `NQuad`.")
|
|
259
261
|
# Not strictly necessary but there will be tremendous inaccuracies if this is violated
|
|
260
262
|
if not NLeg <= NQuad:
|
|
261
263
|
raise ValueError("There should be more streams than the number of phase function Legendre coefficients used.")
|
|
262
264
|
# We require principal angles and a downward incident beam
|
|
263
265
|
if I0 < 0:
|
|
264
|
-
raise ValueError("The incident beam
|
|
266
|
+
raise ValueError("The intensity of the incident beam cannot be negative.")
|
|
265
267
|
if there_is_beam_source:
|
|
266
268
|
if not (0 < mu0 and mu0 <= 1):
|
|
267
269
|
raise ValueError("The cosine of the polar angle of the incident beam must be between 0 and 1, excluding 0.")
|
|
@@ -315,24 +317,31 @@ def pydisort(
|
|
|
315
317
|
scale_tau = 1 - omega_arr * f_arr
|
|
316
318
|
scaled_thickness_arr = scale_tau * thickness_arr
|
|
317
319
|
scaled_tau_arr_with_0 = np.insert(np.cumsum(scaled_thickness_arr), 0, 0)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
)[None, :]
|
|
320
|
+
scaled_Leg_coeffs = (Leg_coeffs - f_arr[:, None]) / (1 - f_arr[:, None])
|
|
321
|
+
weighted_scaled_Leg_coeffs = scaled_Leg_coeffs * (2 * np.arange(NLeg) + 1)[None, :]
|
|
321
322
|
scaled_omega_arr = (1 - f_arr) / scale_tau * omega_arr
|
|
322
323
|
|
|
323
324
|
translations = scaled_tau_arr_with_0[:-1] - scale_tau * np.insert(tau_arr[:-1], 0, 0)
|
|
324
325
|
scaled_s_poly_coeffs = (
|
|
325
326
|
subroutines.affine_transform_poly_coeffs(s_poly_coeffs, scale_tau, translations)
|
|
326
327
|
/ scale_tau[:, None] # Divide by d\tau^* / d\tau
|
|
327
|
-
)
|
|
328
|
+
) * (1 - omega_arr[:, None]) # Enforce Kirchoff's Law of Thermal Radiation: absorptivity equals emissivity
|
|
328
329
|
|
|
329
330
|
else:
|
|
330
331
|
# This is a shortcut to the same results
|
|
331
332
|
scale_tau = np.ones(NLayers)
|
|
332
333
|
scaled_tau_arr_with_0 = np.insert(tau_arr, 0, 0)
|
|
333
|
-
|
|
334
|
+
scaled_Leg_coeffs = Leg_coeffs
|
|
335
|
+
weighted_scaled_Leg_coeffs = scaled_Leg_coeffs * (2 * np.arange(NLeg) + 1)[None, :]
|
|
334
336
|
scaled_omega_arr = omega_arr
|
|
335
|
-
scaled_s_poly_coeffs = s_poly_coeffs
|
|
337
|
+
scaled_s_poly_coeffs = s_poly_coeffs * (1 - omega_arr[:, None]) # Enforce Kirchoff's Law of Thermal Radiation: absorptivity equals emissivity
|
|
338
|
+
|
|
339
|
+
if np.any(scaled_omega_arr > 1 - 1e-6):
|
|
340
|
+
warnings.warn("Some delta-scaled single-scattering albedos are very close to 1 which may cause numerical instability.")
|
|
341
|
+
if (np.any(-0.95 > scaled_Leg_coeffs[:, 1:]) and np.any(scaled_Leg_coeffs[:, 1:] > 0.95)):
|
|
342
|
+
warnings.warn("Some delta-scaled phase function Legendre coefficients have a magnitude that is very close to 1" +
|
|
343
|
+
" (this excludes the zeroth index coefficient which must be 1) and this may cause numerical instability.")
|
|
344
|
+
|
|
336
345
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
337
346
|
|
|
338
347
|
# Rescale of sources
|
|
@@ -345,6 +354,7 @@ def pydisort(
|
|
|
345
354
|
np.max(b_pos),
|
|
346
355
|
np.max(b_neg),
|
|
347
356
|
scaled_s_poly_coeffs[0, 0],
|
|
357
|
+
scaled_s_poly_coeffs[-1, :] @ (scaled_tau_arr_with_0[-1] ** np.arange(Nscoeffs))
|
|
348
358
|
)
|
|
349
359
|
)
|
|
350
360
|
I0 = (I0 / rescale_factor).copy()
|
|
@@ -353,12 +363,13 @@ def pydisort(
|
|
|
353
363
|
scaled_s_poly_coeffs = (scaled_s_poly_coeffs / rescale_factor).copy()
|
|
354
364
|
else:
|
|
355
365
|
rescale_factor = np.max((I0, np.max(b_pos), np.max(b_neg)))
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
366
|
+
if rescale_factor != 0:
|
|
367
|
+
I0 = (I0 / rescale_factor).copy()
|
|
368
|
+
b_pos = (b_pos / rescale_factor).copy()
|
|
369
|
+
b_neg = (b_neg / rescale_factor).copy()
|
|
359
370
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
360
371
|
|
|
361
|
-
if NT_cor and not only_flux and there_is_beam_source and np.any(f_arr > 0) and NLeg < NLeg_all:
|
|
372
|
+
if NT_cor and not only_flux and there_is_beam_source and np.any(f_arr > 0) and NLeg < NLeg_all and np.any(omega_arr > 0):
|
|
362
373
|
|
|
363
374
|
############################### Perform NT corrections on the intensity but not the flux ###############################
|
|
364
375
|
############################### Refer to section 3.7.2 of the Comprehensive Documentation ##############################
|
|
@@ -667,7 +678,7 @@ def pydisort(
|
|
|
667
678
|
tau = np.atleast_1d(tau)
|
|
668
679
|
phi = np.atleast_1d(phi)
|
|
669
680
|
NT_corrections = TMS_correction(tau, phi, is_antiderivative_wrt_tau)
|
|
670
|
-
|
|
681
|
+
|
|
671
682
|
if autograd_compatible:
|
|
672
683
|
NT_corrections = NT_corrections + np.concatenate(
|
|
673
684
|
[np.zeros((N, len(tau), len(phi))), IMS_correction(tau, phi, is_antiderivative_wrt_tau)], axis=0
|
PythonicDISORT/subroutines.py
CHANGED
|
@@ -12,13 +12,13 @@ def transform_interval(arr, c, d, a, b):
|
|
|
12
12
|
arr : array
|
|
13
13
|
The 1D array to transform.
|
|
14
14
|
c : float
|
|
15
|
-
|
|
15
|
+
Lower bound of interval [c, d].
|
|
16
16
|
d : float
|
|
17
|
-
|
|
17
|
+
Upper bound of interval [c, d].
|
|
18
18
|
a : float, optional
|
|
19
|
-
|
|
19
|
+
Lower bound of interval [a, b].
|
|
20
20
|
b : float, optional
|
|
21
|
-
|
|
21
|
+
Upper bound of interval [a, b].
|
|
22
22
|
|
|
23
23
|
Returns
|
|
24
24
|
-------
|
|
@@ -38,13 +38,13 @@ def transform_weights(weights, c, d, a, b):
|
|
|
38
38
|
weights : array
|
|
39
39
|
The weights to transform.
|
|
40
40
|
c : float
|
|
41
|
-
|
|
41
|
+
Lower bound of interval [c, d].
|
|
42
42
|
d : float
|
|
43
|
-
|
|
43
|
+
Upper bound of interval [c, d].
|
|
44
44
|
a : float, optional
|
|
45
|
-
|
|
45
|
+
Lower bound of interval [a, b].
|
|
46
46
|
b : float, optional
|
|
47
|
-
|
|
47
|
+
Upper bound of interval [a, b].
|
|
48
48
|
|
|
49
49
|
Returns
|
|
50
50
|
-------
|
|
@@ -95,9 +95,9 @@ def Gauss_Legendre_quad(N, c=0, d=1):
|
|
|
95
95
|
N : int (will be converted to int)
|
|
96
96
|
Number of quadrature nodes.
|
|
97
97
|
c : float, optional
|
|
98
|
-
|
|
98
|
+
Lower bound of integration interval.
|
|
99
99
|
d : float, optional
|
|
100
|
-
|
|
100
|
+
Upper bound of integration interval.
|
|
101
101
|
|
|
102
102
|
Returns
|
|
103
103
|
-------
|
|
@@ -248,18 +248,18 @@ def generate_diff_act_flux_funcs(u0):
|
|
|
248
248
|
Actinic flux function with argument ``tau`` (type: array) for positive (upward) ``mu`` values.
|
|
249
249
|
Returns the diffuse flux magnitudes (type: array).
|
|
250
250
|
Pass ``is_antiderivative_wrt_tau = True`` (defaults to ``False``)
|
|
251
|
-
to switch to an antiderivative of
|
|
251
|
+
to switch to an antiderivative of this function with respect to ``tau``.
|
|
252
252
|
Pass ``return_tau_arr`` to return ``tau_arr`` (defaults to ``False``).
|
|
253
253
|
Fm_act(tau) : function
|
|
254
254
|
Actinic flux function with argument ``tau`` (type: array) for negative (downward) ``mu`` values.
|
|
255
255
|
Returns the diffuse flux magnitudes (type: array).
|
|
256
256
|
Pass ``is_antiderivative_wrt_tau = True`` (defaults to ``False``)
|
|
257
|
-
to switch to an antiderivative of
|
|
257
|
+
to switch to an antiderivative of this function with respect to ``tau``.
|
|
258
258
|
Pass ``return_tau_arr`` to return ``tau_arr`` (defaults to ``False``).
|
|
259
259
|
|
|
260
260
|
"""
|
|
261
261
|
N = len(u0(0)) // 2
|
|
262
|
-
GL_weights = Gauss_Legendre_quad(N
|
|
262
|
+
GL_weights = Gauss_Legendre_quad(N)[1]
|
|
263
263
|
|
|
264
264
|
# Note that the zeroth axis of the array u0(tau) captures variation with mu
|
|
265
265
|
def flux_act_up(tau, is_antiderivative_wrt_tau=False, return_tau_arr=False):
|
|
@@ -314,11 +314,12 @@ def Planck(T, WVNM):
|
|
|
314
314
|
not_zeros_ind = T != 0
|
|
315
315
|
|
|
316
316
|
results = np.zeros(len(T))
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
317
|
+
if np.sum(not_zeros_ind) > 0:
|
|
318
|
+
results[~not_zeros_ind] = 0
|
|
319
|
+
|
|
320
|
+
# Coded in this way to prevent overflow
|
|
321
|
+
expterm = np.exp(-100 * sc.constants.h * sc.constants.c * WVNM / (sc.constants.k * T[not_zeros_ind]))
|
|
322
|
+
results[not_zeros_ind] = (2e8 * sc.constants.h * sc.constants.c**2 * WVNM**3 * expterm) / (1 - expterm)
|
|
322
323
|
|
|
323
324
|
return np.squeeze(results)[()]
|
|
324
325
|
|
|
@@ -386,8 +387,8 @@ def linear_spline_coefficients(x, y, check_inputs=True):
|
|
|
386
387
|
def generate_s_poly_coeffs(tau_arr, TEMPER, WVNMLO, WVNMHI, **kwargs):
|
|
387
388
|
"""Generate DISORT-equivalent ``s_poly_coeffs`` for input into ``pydisort``.
|
|
388
389
|
This convenience function is provided to help match the inputs for Stamnes' DISORT to those for PythonicDISORT.
|
|
389
|
-
|
|
390
|
-
|
|
390
|
+
Note that the coefficients will be multiplied by emissivity factors equal to ``1 - omega_arr`` inside
|
|
391
|
+
`pydisort`, i.e. Kirchoff's law of thermal radiation is enforced, just like in Stamnes' DISORT.
|
|
391
392
|
|
|
392
393
|
Parameters
|
|
393
394
|
----------
|
|
@@ -413,12 +414,19 @@ def generate_s_poly_coeffs(tau_arr, TEMPER, WVNMLO, WVNMHI, **kwargs):
|
|
|
413
414
|
"""
|
|
414
415
|
tau_arr = np.atleast_1d(tau_arr)
|
|
415
416
|
if not len(TEMPER) == len(tau_arr) + 1:
|
|
416
|
-
raise ValueError(
|
|
417
|
-
|
|
417
|
+
raise ValueError(
|
|
418
|
+
"Missing temperature specification at some boundaries / interfaces."
|
|
419
|
+
)
|
|
420
|
+
|
|
418
421
|
tau_arr_with_0 = np.insert(tau_arr, 0, 0)
|
|
419
|
-
blackbody_emission_at_each_boundary = sc.integrate.quad_vec(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
+
blackbody_emission_at_each_boundary = sc.integrate.quad_vec(
|
|
423
|
+
lambda WVNM: Planck(TEMPER, WVNM), WVNMLO, WVNMHI, **kwargs
|
|
424
|
+
)[0]
|
|
425
|
+
|
|
426
|
+
return linear_spline_coefficients(
|
|
427
|
+
tau_arr_with_0, blackbody_emission_at_each_boundary, check_inputs=False
|
|
428
|
+
)
|
|
429
|
+
|
|
422
430
|
|
|
423
431
|
|
|
424
432
|
|
|
@@ -432,17 +440,24 @@ def generate_emissivity_from_BDRF(N, zeroth_BDRF_Fourier_mode):
|
|
|
432
440
|
----------
|
|
433
441
|
N : int
|
|
434
442
|
Number of upper hemisphere quadrature nodes. Equal to ``NQuad // 2``.
|
|
435
|
-
zeroth_BDRF_Fourier_mode : function
|
|
443
|
+
zeroth_BDRF_Fourier_mode : function or float
|
|
436
444
|
Zeroth BDRF Fourier mode with arguments ``mu, -mu_p`` of type array
|
|
437
445
|
and which output has the same dimensions as the outer product of the two arrays.
|
|
446
|
+
A scalar input represents a constant function and in that case the output
|
|
447
|
+
is simply one minus the scalar input.
|
|
438
448
|
|
|
439
449
|
Returns
|
|
440
450
|
-------
|
|
441
|
-
array
|
|
451
|
+
array or float
|
|
442
452
|
Emissivity for the blackbody contribution to the lower boundary source ``b_neg``.
|
|
443
453
|
"""
|
|
444
|
-
|
|
445
|
-
|
|
454
|
+
if np.isscalar(zeroth_BDRF_Fourier_mode):
|
|
455
|
+
return 1 - zeroth_BDRF_Fourier_mode
|
|
456
|
+
else:
|
|
457
|
+
mu_arr_pos, W = Gauss_Legendre_quad(N)
|
|
458
|
+
return (
|
|
459
|
+
1 - 2 * zeroth_BDRF_Fourier_mode(mu_arr_pos, mu_arr_pos) * mu_arr_pos[None, :] @ W
|
|
460
|
+
)
|
|
446
461
|
|
|
447
462
|
|
|
448
463
|
|
|
@@ -457,8 +472,9 @@ def cache_BDRF_Fourier_modes(N, mu0, BDRF_Fourier_modes):
|
|
|
457
472
|
Number of upper hemisphere quadrature nodes. Equal to ``NQuad // 2``.
|
|
458
473
|
mu0 : float
|
|
459
474
|
Cosine of polar angle of the incident beam.
|
|
460
|
-
BDRF_Fourier_modes : list of functions
|
|
461
|
-
BDRF Fourier modes, each a scalar
|
|
475
|
+
BDRF_Fourier_modes : list of functions and scalars
|
|
476
|
+
BDRF Fourier modes, each a scalar representing a constant function,
|
|
477
|
+
or a function with arguments ``mu, -mu_p`` of type array
|
|
462
478
|
which output has the same dimensions as the outer product of the two arrays.
|
|
463
479
|
|
|
464
480
|
Returns
|
|
@@ -551,15 +567,16 @@ def interpolate(u):
|
|
|
551
567
|
-------
|
|
552
568
|
u_interpol : function
|
|
553
569
|
Intensity function with arguments ``(mu, tau, phi)`` (for ``u``)
|
|
554
|
-
or ``(mu, tau)`` (for ``u0``) each
|
|
570
|
+
or ``(mu, tau)`` (for ``u0``) each an array or scalar.
|
|
555
571
|
Returns an ndarray with axes corresponding to variation with each argument in the same order.
|
|
556
572
|
Pass ``is_antiderivative_wrt_tau = True`` (defaults to ``False``)
|
|
557
|
-
to switch to an antiderivative of
|
|
573
|
+
to switch to an antiderivative of this function with respect to ``tau``.
|
|
558
574
|
Pass ``return_Fourier_error = True`` (defaults to ``False``) to return the
|
|
559
575
|
Cauchy / Fourier convergence evaluation (type: float) for the last Fourier term.
|
|
560
576
|
Pass ``return_tau_arr`` to return ``tau_arr`` (defaults to ``False``).
|
|
561
577
|
|
|
562
578
|
"""
|
|
579
|
+
|
|
563
580
|
N = len(u(0, 0)) // 2
|
|
564
581
|
mu_arr_pos = Gauss_Legendre_quad(N)[0]
|
|
565
582
|
|
|
@@ -774,18 +791,19 @@ def _mathscr_v(tau, # Input optical depths
|
|
|
774
791
|
def _compare(results, mu_to_compare, reorder_mu, flux_up, flux_down, u=None):
|
|
775
792
|
"""Performs pointwise comparisons between results from Stamnes' DISORT,
|
|
776
793
|
which are stored in ``.npz`` files, against results from PythonicDISORT. Used in our PyTests.
|
|
777
|
-
|
|
794
|
+
See the ``*_test`` Jupyter Notebooks to see how this function is used and the arguments that go into it.
|
|
795
|
+
|
|
778
796
|
"""
|
|
779
797
|
# Load saved results from Stamnes' DISORT
|
|
780
798
|
flup = results["flup"]
|
|
781
799
|
rfldn = results["rfldn"]
|
|
782
800
|
rfldir = results["rfldir"]
|
|
783
|
-
if u
|
|
801
|
+
if u is not None:
|
|
784
802
|
uu = results["uu"]
|
|
785
803
|
|
|
786
804
|
# Load comparison points
|
|
787
805
|
tau_test_arr = results["tau_test_arr"]
|
|
788
|
-
if u
|
|
806
|
+
if u is not None:
|
|
789
807
|
phi_arr = results["phi_arr"]
|
|
790
808
|
|
|
791
809
|
# Perform and print the comparisons
|
|
@@ -800,7 +818,7 @@ def _compare(results, mu_to_compare, reorder_mu, flux_up, flux_down, u=None):
|
|
|
800
818
|
diff_flux_up,
|
|
801
819
|
flup,
|
|
802
820
|
out=np.zeros_like(diff_flux_up),
|
|
803
|
-
where=flup
|
|
821
|
+
where=(flup != 0),
|
|
804
822
|
)
|
|
805
823
|
print("Difference =", np.max(diff_flux_up))
|
|
806
824
|
print("Difference ratio =", np.max(ratio_flux_up))
|
|
@@ -813,7 +831,7 @@ def _compare(results, mu_to_compare, reorder_mu, flux_up, flux_down, u=None):
|
|
|
813
831
|
diff_flux_down_diffuse,
|
|
814
832
|
rfldn,
|
|
815
833
|
out=np.zeros_like(diff_flux_down_diffuse),
|
|
816
|
-
where=rfldn
|
|
834
|
+
where=(rfldn != 0),
|
|
817
835
|
)
|
|
818
836
|
print("Difference =", np.max(diff_flux_down_diffuse))
|
|
819
837
|
print(
|
|
@@ -829,7 +847,7 @@ def _compare(results, mu_to_compare, reorder_mu, flux_up, flux_down, u=None):
|
|
|
829
847
|
diff_flux_down_direct,
|
|
830
848
|
rfldir,
|
|
831
849
|
out=np.zeros_like(diff_flux_down_direct),
|
|
832
|
-
where=rfldir
|
|
850
|
+
where=(rfldir != 0),
|
|
833
851
|
)
|
|
834
852
|
print("Difference =", np.max(diff_flux_down_direct))
|
|
835
853
|
print(
|
|
@@ -838,14 +856,15 @@ def _compare(results, mu_to_compare, reorder_mu, flux_up, flux_down, u=None):
|
|
|
838
856
|
)
|
|
839
857
|
print()
|
|
840
858
|
|
|
841
|
-
if u
|
|
859
|
+
if u is not None:
|
|
842
860
|
# Intensity
|
|
843
|
-
|
|
861
|
+
u_cache = u(tau_test_arr, phi_arr)[reorder_mu].reshape(np.shape(uu))
|
|
862
|
+
diff = np.abs(uu - u_cache)[mu_to_compare]
|
|
844
863
|
diff_ratio = np.divide(
|
|
845
864
|
diff,
|
|
846
865
|
uu[mu_to_compare],
|
|
847
866
|
out=np.zeros_like(diff),
|
|
848
|
-
where=uu[mu_to_compare]
|
|
867
|
+
where=(uu[mu_to_compare] != 0),
|
|
849
868
|
)
|
|
850
869
|
max_diff_tau_index = np.argmax(np.max(np.max(diff, axis=0), axis=1))
|
|
851
870
|
max_ratio_tau_index = np.argmax(np.max(np.max(diff_ratio, axis=0), axis=1))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PythonicDISORT
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2
|
|
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
|
|
@@ -67,8 +67,8 @@ These notebooks double up as examples of how to use PythonicDISORT. The tests wh
|
|
|
67
67
|
* Test Problem 5: Cloud C.1 Scattering, Beam Source
|
|
68
68
|
* Test Problem 6: No Scattering, Increasingly Complex Sources (relevant for modeling longwave radiation)
|
|
69
69
|
* Test Problem 7: Absorption + Scattering + All Possible Sources, Lambertian and Hapke Surface Reflectivities (one layer)
|
|
70
|
-
* Test Problem 8: Absorbing / Isotropic-Scattering Medium
|
|
71
|
-
* Test Problem 9: General Emitting / Absorbing / Scattering Medium
|
|
70
|
+
* Test Problem 8: Absorbing / Isotropic-Scattering Medium (multiple layers)
|
|
71
|
+
* Test Problem 9: General Emitting / Absorbing / Scattering Medium (multiple layers)
|
|
72
72
|
* Test Problem 11: Single-Layer vs. Multiple Layers (no corresponding Jupyter Notebook)
|
|
73
73
|
|
|
74
74
|
# Installation
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
PythonicDISORT/__init__.py,sha256=V1obNzaFgqInxqYJ5h_r6JHbZRxx4VM5VgzCIS6EEzI,84
|
|
2
|
+
PythonicDISORT/_assemble_intensity_and_fluxes.py,sha256=okORQDxUWZgmMZmIRPNzIOqk8UQAcoc7o8Wr3wxrgDc,29261
|
|
3
|
+
PythonicDISORT/_solve_for_coeffs.py,sha256=Bb-C5OJt355x8_v8WFB2sJutBtSTAFNIX4OxLQJk9-0,16539
|
|
4
|
+
PythonicDISORT/_solve_for_gen_and_part_sols.py,sha256=InZPpyRkfmt7gq5r64FPHjXJVMV41ixZygJd4pWpLI4,12461
|
|
5
|
+
PythonicDISORT/pydisort.py,sha256=ULGw_fqcnC7JQ3voJr9oKlaB3yrDI_sKe5jOU0Xj2Rc,40043
|
|
6
|
+
PythonicDISORT/subroutines.py,sha256=d0mjOgxqSFoNZnLmOkuSWqO3HjojR9S-2orGZSdGJkU,33208
|
|
7
|
+
pythonicdisort-1.2.dist-info/LICENSE.md,sha256=q1i1QVh0OLzK0fj6kaNRNWsM_KZca66JwWgWwbuCPlA,1092
|
|
8
|
+
pythonicdisort-1.2.dist-info/METADATA,sha256=PLXXaPDMmva47U6AM6cNFSrb0J-FT9xCkiww8tsvE3c,8297
|
|
9
|
+
pythonicdisort-1.2.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
10
|
+
pythonicdisort-1.2.dist-info/top_level.txt,sha256=aX6kCeI1E7naZz3xfw4_mIbWh5KBh9dmF3FIvgP5m3Y,15
|
|
11
|
+
pythonicdisort-1.2.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
PythonicDISORT/__init__.py,sha256=V1obNzaFgqInxqYJ5h_r6JHbZRxx4VM5VgzCIS6EEzI,84
|
|
2
|
-
PythonicDISORT/_assemble_intensity_and_fluxes.py,sha256=M9mS2tD-GvMgze2MnHZEIarv7bpJsVe32fVPUEn0YpE,29911
|
|
3
|
-
PythonicDISORT/_solve_for_coeffs.py,sha256=Bb-C5OJt355x8_v8WFB2sJutBtSTAFNIX4OxLQJk9-0,16539
|
|
4
|
-
PythonicDISORT/_solve_for_gen_and_part_sols.py,sha256=_sPHgBTn58nOO0u13WCmCiDa6Qbv4HEu-7F_auFUcG4,13089
|
|
5
|
-
PythonicDISORT/pydisort.py,sha256=xeHeW_XcQwSwLYTwYr2tnBUPnu0RqSTrOppO2KEHiT8,38865
|
|
6
|
-
PythonicDISORT/subroutines.py,sha256=HNZIeAihmouboHk4L--ajI4ww7O-dFKUZI1S1J-0H78,32548
|
|
7
|
-
pythonicdisort-1.0.3.dist-info/LICENSE.md,sha256=q1i1QVh0OLzK0fj6kaNRNWsM_KZca66JwWgWwbuCPlA,1092
|
|
8
|
-
pythonicdisort-1.0.3.dist-info/METADATA,sha256=stYzyWETkJTPxBAOmmIuAQngjKdS9tBCnoFaftB1TAQ,8310
|
|
9
|
-
pythonicdisort-1.0.3.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
10
|
-
pythonicdisort-1.0.3.dist-info/top_level.txt,sha256=aX6kCeI1E7naZz3xfw4_mIbWh5KBh9dmF3FIvgP5m3Y,15
|
|
11
|
-
pythonicdisort-1.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|