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.
@@ -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 the function with respect to `tau`.
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
- if is_antiderivative_wrt_tau and there_is_beam_source:
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
- + (B_collect / (-scale_tau / mu0)[None, :, None])[:, l, :]
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
- um = np.einsum(
227
- "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
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
- if is_antiderivative_wrt_tau and there_is_beam_source:
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
- + (B_collect.T[-1, :, :] / (-scale_tau / mu0)[None, :])[:, l]
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
- ulast = np.einsum(
290
- "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
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-15,
310
+ where=u_abs > 1e-8,
302
311
  )
303
312
  )
304
313
 
305
- if return_tau_arr:
306
- return rescale_factor * np.squeeze(u), Fourier_error, tau_arr
307
- else:
308
- return rescale_factor * np.squeeze(u), Fourier_error
309
- else:
310
- if return_tau_arr:
311
- return rescale_factor * np.squeeze(u), tau_arr
312
- else:
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 the function with respect to `tau`.
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
- + (B_collect_0.T / (-scale_tau / mu0)[None, :])[:, l]
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
- u0 = np.einsum(
389
- "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
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
- if return_tau_arr and _return_act_dscale_for_reclass:
408
- return rescale_factor * np.squeeze(u0), tau_arr, act_dscale_reclassification
409
- elif return_tau_arr and not _return_act_dscale_for_reclass:
410
- return rescale_factor * np.squeeze(u0), tau_arr
411
- elif not return_tau_arr and _return_act_dscale_for_reclass:
412
- return rescale_factor * np.squeeze(u0), act_dscale_reclassification
413
- else:
414
- return rescale_factor * np.squeeze(u0)
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 the function with respect to `tau`.
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 the function with respect to `tau`.
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, B_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
- weighted_asso_Leg_coeffs_l = weighted_scaled_Leg_coeffs[l, :][ells] * fac
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(weighted_asso_Leg_coeffs_l > 0): # There are shortcuts if the coefficients are all zero
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 = weighted_asso_Leg_coeffs_l[None, :] * asso_leg_term_pos.T
112
- D_pos = (scaled_omega_l / 2) * D_temp @ asso_leg_term_pos
113
- D_neg = (scaled_omega_l / 2) * D_temp @ asso_leg_term_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
- * weighted_asso_Leg_coeffs_l
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[: len(no_shortcut_indices), :, :]
177
- beta_arr = beta_arr[: len(no_shortcut_indices), :, :]
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[: len(no_shortcut_indices_0), :, :]
208
+ G_collect[no_shortcut_indices_0, :, :]
209
209
  )
210
210
 
211
211
  # --------------------------------------------------------------------------------------------------------------------------
212
212
 
213
-
214
- if there_is_beam_source and there_is_iso_source:
215
- return (
216
- G_collect.reshape((NFourier, NLayers, NQuad, NQuad)),
217
- K_collect.reshape((NFourier, NLayers, NQuad)),
218
- B_collect.reshape((NFourier, NLayers, NQuad)),
219
- G_inv_collect_0,
220
- )
221
- elif there_is_beam_source and not there_is_iso_source:
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
@@ -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 scalar, or a function with arguments ``mu, -mu_p`` of type array
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 or float
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 the function with respect to ``tau``.
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 the function with respect to ``tau``.
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 the function with respect to ``tau``.
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 the function with respect to ``tau``.
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 scalars into arrays
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 thickness cannot be non-positive.")
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 as deduced from the length of `tau_arr`.")
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 as deduced from the length of `tau_arr`.")
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 as deduced from the length of `tau_arr`.")
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 as deduced from the length of `tau_arr`.")
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 must have positive intensity.")
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
- weighted_scaled_Leg_coeffs = ((Leg_coeffs - f_arr[:, None]) / (1 - f_arr[:, None])) * (
319
- 2 * np.arange(NLeg) + 1
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
- weighted_scaled_Leg_coeffs = Leg_coeffs * (2 * np.arange(NLeg) + 1)[None, :]
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
- I0 = (I0 / rescale_factor).copy()
357
- b_pos = (b_pos / rescale_factor).copy()
358
- b_neg = (b_neg / rescale_factor).copy()
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
@@ -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
- The beginning of interval [c, d].
15
+ Lower bound of interval [c, d].
16
16
  d : float
17
- The end of interval [c, d].
17
+ Upper bound of interval [c, d].
18
18
  a : float, optional
19
- The beginning of interval [a, b].
19
+ Lower bound of interval [a, b].
20
20
  b : float, optional
21
- The end of interval [a, b].
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
- The beginning of interval [c, d].
41
+ Lower bound of interval [c, d].
42
42
  d : float
43
- The end of interval [c, d].
43
+ Upper bound of interval [c, d].
44
44
  a : float, optional
45
- The beginning of interval [a, b].
45
+ Lower bound of interval [a, b].
46
46
  b : float, optional
47
- The end of interval [a, b].
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
- Start of integration interval.
98
+ Lower bound of integration interval.
99
99
  d : float, optional
100
- End of integration interval.
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 the function with respect to ``tau``.
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 the function with respect to ``tau``.
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, 0, 1)[1]
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
- results[~not_zeros_ind] = 0
318
-
319
- # Coded in this way to prevent overflow
320
- expterm = np.exp(-100 * sc.constants.h * sc.constants.c * WVNM / (sc.constants.k * T[not_zeros_ind]))
321
- results[not_zeros_ind] = (2e8 * sc.constants.h * sc.constants.c**2 * WVNM**3 * expterm) / (1 - expterm)
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
- Users will have to manually adjust emissivities, however, and they should note that PythonicDISORT allows much
390
- more flexibility in choosing blackbody emission profiles than DISORT.
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("Missing temperature specification at some boundaries / interfaces.")
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(lambda WVNM: Planck(TEMPER, WVNM), WVNMLO, WVNMHI, **kwargs)[0]
420
-
421
- return linear_spline_coefficients(tau_arr_with_0, blackbody_emission_at_each_boundary, check_inputs=False)
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
- mu_arr_pos, W = Gauss_Legendre_quad(N)
445
- return 1 - 2 * zeroth_BDRF_Fourier_mode(mu_arr_pos, mu_arr_pos) * mu_arr_pos[None, :] @ W
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, or a function with arguments ``mu, -mu_p`` of type array
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 of type array or float.
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 the function with respect to ``tau``.
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 != None:
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 != None:
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 > 1e-8,
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 > 1e-8,
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 > 1e-8,
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 != None:
859
+ if u is not None:
842
860
  # Intensity
843
- diff = np.abs(uu - u(tau_test_arr, phi_arr)[reorder_mu])[mu_to_compare]
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] > 1e-8,
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.0.3
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 With (two layers)
71
- * Test Problem 9: General Emitting / Absorbing / Scattering Medium with (many different layers)
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,,