PythonicDISORT 0.9.0__py3-none-any.whl → 0.9.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.
@@ -20,13 +20,13 @@ def _assemble_intensity_and_fluxes(
20
20
  there_is_beam_source, # Is there a beam source?
21
21
  b_pos, b_neg, # Dirichlet BCs
22
22
  b_pos_is_scalar, b_neg_is_scalar, # Is each Dirichlet BCs scalar (isotropic)?
23
- s_poly_coeffs, # Polynomial coefficients of isotropic source
24
23
  Nscoeffs, # Number of isotropic source polynomial coefficients
24
+ s_poly_coeffs, # Polynomial coefficients of isotropic source
25
25
  there_is_iso_source, # Is there an isotropic source?
26
26
  scale_tau, # Delta-scale factor for tau
27
27
  only_flux, # Only compute fluxes?
28
- NLayers_to_use_sparse, # Number of layers above or equal which to use sparse matrices
29
- _autograd_compatible, # Should the output functions be compatible with autograd?
28
+ use_banded_solver_NLayers, # Number of layers above or equal which to use `scipy.linalg.solve_banded`
29
+ autograd_compatible, # Should the output functions be compatible with autograd?
30
30
  ):
31
31
  """Assembles the solution functions: intensity `u`, upward flux `flux_up`, downward flux `flux_down`
32
32
  from the previously solved eigenpairs and coefficients from the boundary conditions.
@@ -66,14 +66,14 @@ def _assemble_intensity_and_fluxes(
66
66
  | `b_pos` | `NQuad/2 x NFourier` or scalar |
67
67
  | `b_neg` | `NQuad/2 x NFourier` or scalar |
68
68
  | `b_pos_is_scalar` | boolean |
69
- | `b_neg_is_scalar` | boolean |
69
+ | `b_neg_is_scalar` | boolean |
70
+ | `Nscoeffs` | scalar |
70
71
  | `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
71
- | `Nscoeffs` | scalar |
72
72
  | `there_is_iso_source` | boolean |
73
73
  | `scale_tau` | `NLayers` |
74
74
  | `only_flux` | boolean |
75
- | `NLayers_to_use_sparse` | scalar |
76
- | `_autograd_compatible` | boolean |
75
+ | `use_banded_solver_NLayers` | scalar |
76
+ | `autograd_compatible` | boolean |
77
77
 
78
78
  Notable internal variables of _assemble_intensity_and_fluxes
79
79
  | Variable | Type / Shape |
@@ -92,7 +92,7 @@ def _assemble_intensity_and_fluxes(
92
92
  | `GC_pos` | `NLayers x NQuad/2 x NQuad` |
93
93
  | `GC_neg` | `NLayers x NQuad/2 x NQuad` |
94
94
  """
95
- if _autograd_compatible:
95
+ if autograd_compatible:
96
96
  import autograd.numpy as np
97
97
  else:
98
98
  import numpy as np
@@ -114,20 +114,21 @@ def _assemble_intensity_and_fluxes(
114
114
  Nscoeffs,
115
115
  there_is_iso_source,
116
116
  )
117
- if there_is_beam_source:
118
- if there_is_iso_source:
119
- G_collect, K_collect, B_collect, G_inv_collect_0 = outputs
120
- else:
121
- G_collect, K_collect, B_collect = outputs
122
- G_inv_collect_0 = None
117
+ if there_is_beam_source and there_is_iso_source:
118
+ G_collect, K_collect, B_collect, G_inv_collect_0 = outputs
123
119
  B_collect_0 = B_collect[0, :, :]
120
+ elif there_is_beam_source and not there_is_iso_source:
121
+ G_collect, K_collect, B_collect = outputs
122
+ B_collect_0 = B_collect[0, :, :]
123
+ G_inv_collect_0 = None
124
+ elif not there_is_beam_source and there_is_iso_source:
125
+ G_collect, K_collect, G_inv_collect_0 = outputs
126
+ B_collect = None
124
127
  else:
125
- if there_is_iso_source:
126
- G_collect, K_collect, G_inv_collect_0 = outputs
127
- else:
128
- G_collect, K_collect = outputs
129
- G_inv_collect_0 = None
128
+ G_collect, K_collect = outputs
130
129
  B_collect = None
130
+ G_inv_collect_0 = None
131
+
131
132
 
132
133
  GC_collect = _solve_for_coeffs(
133
134
  NFourier,
@@ -146,10 +147,10 @@ def _assemble_intensity_and_fluxes(
146
147
  there_is_beam_source,
147
148
  b_pos, b_neg,
148
149
  b_pos_is_scalar, b_neg_is_scalar,
149
- s_poly_coeffs,
150
150
  Nscoeffs,
151
+ s_poly_coeffs,
151
152
  there_is_iso_source,
152
- NLayers_to_use_sparse,
153
+ use_banded_solver_NLayers,
153
154
  )
154
155
 
155
156
  G_collect_0 = G_collect[0, :, :, :]
@@ -161,7 +162,7 @@ def _assemble_intensity_and_fluxes(
161
162
 
162
163
  # Construct the intensity function (refer to Section 3.7 of the Comprehensive Documentation)
163
164
  # --------------------------------------------------------------------------------------------------------------------------
164
- def u(tau, phi, return_Fourier_error=False, is_antiderivative_wrt_tau=False):
165
+ def u(tau, phi, is_antiderivative_wrt_tau=False, return_Fourier_error=False, return_tau_arr=False):
165
166
  """
166
167
  Intensity function with arguments `(tau, phi)` of types `(array or float, array or float)`.
167
168
  Returns an ndarray with axes corresponding to variation with `mu, tau, phi` respectively.
@@ -172,8 +173,8 @@ def _assemble_intensity_and_fluxes(
172
173
  """
173
174
  tau = np.atleast_1d(tau)
174
175
  phi = np.atleast_1d(phi)
175
- assert np.all(tau >= 0)
176
- assert np.all(tau <= tau_arr[-1])
176
+ if np.all(tau < 0) or np.all(tau > tau_arr[-1]):
177
+ raise ValueError("tau input outside the tau range specified for the atmosphere (check `tau_arr`).")
177
178
 
178
179
  # Atmospheric layer indices
179
180
  l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
@@ -196,56 +197,55 @@ def _assemble_intensity_and_fluxes(
196
197
  axis=-1,
197
198
  )
198
199
 
199
- if is_antiderivative_wrt_tau:
200
- if there_is_beam_source:
201
- um = np.einsum(
202
- "mtij, mtj -> mti",
203
- GC_collect[:, l, :, :],
204
- np.exp(exponent) / (scale_tau[None, l, None] * K_collect[:, l, :]),
205
- optimize=True,
206
- ) + B_collect[:, l, :] * np.exp(-scaled_tau[None, :, None] / mu0) / (
207
- -scale_tau[None, l, None] / mu0
208
- )
209
- else:
210
- um = np.einsum(
200
+ if is_antiderivative_wrt_tau and there_is_beam_source:
201
+ um = (
202
+ np.einsum(
211
203
  "mtij, mtj -> mti",
212
204
  GC_collect[:, l, :, :],
213
- np.exp(exponent) / (scale_tau[None, l, None] * K_collect[:, l, :]),
205
+ np.exp(exponent) / (scale_tau[None, :, None] * K_collect)[:, l, :],
214
206
  optimize=True,
215
207
  )
208
+ + (B_collect / (-scale_tau / mu0)[None, :, None])[:, l, :]
209
+ * np.exp(-scaled_tau / mu0)[None, :, None]
210
+ )
211
+ elif is_antiderivative_wrt_tau and not there_is_beam_source:
212
+ um = np.einsum(
213
+ "mtij, mtj -> mti",
214
+ GC_collect[:, l, :, :],
215
+ np.exp(exponent) / (scale_tau[None, l, None] * K_collect)[:, l, :],
216
+ optimize=True,
217
+ )
218
+ elif not is_antiderivative_wrt_tau and there_is_beam_source:
219
+ um = np.einsum(
220
+ "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
221
+ ) + B_collect[:, l, :] * np.exp(-scaled_tau / mu0)[None, :, None]
216
222
  else:
217
- if there_is_beam_source:
218
- um = np.einsum(
219
- "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
220
- ) + B_collect[:, l, :] * np.exp(-scaled_tau[None, :, None] / mu0)
221
- else:
222
- um = np.einsum(
223
- "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
224
- )
223
+ um = np.einsum(
224
+ "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
225
+ )
225
226
 
226
227
  # Contribution from particular solution for isotropic internal sources
227
228
  if there_is_iso_source:
229
+ l_uniq, l_inv = np.unique(l, return_inverse=True)
228
230
  _mathscr_v_contribution = _mathscr_v(
229
- tau, l,
230
- s_poly_coeffs,
231
- Nscoeffs,
232
- G_collect_0,
233
- K_collect_0,
234
- G_inv_collect_0,
231
+ tau, l_inv, Nscoeffs,
232
+ s_poly_coeffs[l_uniq],
233
+ G_collect_0[l_uniq],
234
+ K_collect_0[l_uniq],
235
+ G_inv_collect_0[l_uniq],
235
236
  mu_arr,
236
237
  is_antiderivative_wrt_tau,
237
- _autograd_compatible
238
+ autograd_compatible
238
239
  )
239
240
 
240
- if _autograd_compatible:
241
- if NFourier == 1:
242
- um = um + _mathscr_v_contribution.T
243
- else:
244
- um = um + np.concatenate((_mathscr_v_contribution.T, np.zeros((NFourier - 1, len(tau), NQuad))))
241
+ if NFourier == 1:
242
+ um = um + _mathscr_v_contribution.T
243
+ elif autograd_compatible:
244
+ um = um + np.concatenate((_mathscr_v_contribution.T, np.zeros((NFourier - 1, len(tau), NQuad))))
245
245
  else:
246
246
  um[0, :, :] += _mathscr_v_contribution.T
247
247
 
248
- intensities = np.einsum(
248
+ u = np.einsum(
249
249
  "mti, mp -> itp",
250
250
  um,
251
251
  np.cos(np.arange(NFourier)[:, None] * (phi0 - phi)[None, :]),
@@ -260,52 +260,59 @@ def _assemble_intensity_and_fluxes(
260
260
  ],
261
261
  axis=-1,
262
262
  )
263
- if is_antiderivative_wrt_tau:
264
- if there_is_beam_source:
265
- ulast = np.einsum(
263
+ if is_antiderivative_wrt_tau and there_is_beam_source:
264
+ ulast = (
265
+ np.einsum(
266
266
  "tij, tj -> it",
267
267
  GC_collect[-1, l, :, :],
268
- np.exp(exponent) / (scale_tau[l, None] * K_collect[-1, l, :]),
268
+ np.exp(exponent) / (scale_tau[:, None] * K_collect[-1, :, :])[l, :],
269
269
  optimize=True,
270
- ) + B_collect[-1, l, :].T * np.exp(-scaled_tau[None, :] / mu0) / (
271
- -scale_tau[None, l] / mu0
272
270
  )
273
- else:
274
- ulast = np.einsum(
275
- "tij, tj -> it",
276
- GC_collect[-1, l, :, :],
277
- np.exp(exponent) / (scale_tau[l, None] * K_collect[-1, l, :]),
278
- optimize=True,
271
+ + (B_collect.T[-1, :, :] / (-scale_tau / mu0)[None, :])[:, l]
272
+ * np.exp(-scaled_tau / mu0)[None, :]
273
+ )
274
+ elif is_antiderivative_wrt_tau and not there_is_beam_source:
275
+ ulast = np.einsum(
276
+ "tij, tj -> it",
277
+ GC_collect[-1, l, :, :],
278
+ np.exp(exponent) / (scale_tau[:, None] * K_collect[-1, :, :])[l, :],
279
+ optimize=True,
279
280
  )
281
+ elif not is_antiderivative_wrt_tau and not there_is_beam_source:
282
+ ulast = np.einsum(
283
+ "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
284
+ ) + B_collect.T[-1, :, l] * np.exp(-scaled_tau / mu0)[None, :]
280
285
  else:
281
- if there_is_beam_source:
282
- ulast = np.einsum(
283
- "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
284
- ) + B_collect[-1, l, :].T * np.exp(-scaled_tau[None, :] / mu0)
285
- else:
286
- ulast = np.einsum(
287
- "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
288
- )
286
+ ulast = np.einsum(
287
+ "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
288
+ )
289
289
 
290
- intensities_abs = np.abs(intensities)
290
+ u_abs = np.abs(u)
291
291
  Fourier_error = np.max(
292
292
  np.divide(
293
293
  np.abs(
294
294
  ulast[:, :, None] * np.cos((NFourier - 1) * (phi0 - phi))[None, None, :]
295
295
  ),
296
- intensities_abs,
297
- out=np.zeros_like(intensities_abs),
298
- where=intensities_abs > 1e-8,
296
+ u_abs,
297
+ out=np.zeros_like(u_abs),
298
+ where=u_abs > 1e-15,
299
299
  )
300
300
  )
301
- return I0_orig * np.squeeze(intensities), Fourier_error
301
+
302
+ if return_tau_arr:
303
+ return I0_orig * np.squeeze(u), Fourier_error, tau_arr
304
+ else:
305
+ return I0_orig * np.squeeze(u), Fourier_error
302
306
  else:
303
- return I0_orig * np.squeeze(intensities)
307
+ if return_tau_arr:
308
+ return I0_orig * np.squeeze(u), tau_arr
309
+ else:
310
+ return I0_orig * np.squeeze(u)
304
311
  # --------------------------------------------------------------------------------------------------------------------------
305
312
 
306
313
  # Construct u0
307
314
  # --------------------------------------------------------------------------------------------------------------------------
308
- def u0(tau, is_antiderivative_wrt_tau=False, _return_act_dscale_for_reclass=False):
315
+ def u0(tau, is_antiderivative_wrt_tau=False, return_tau_arr=False, _return_act_dscale_for_reclass=False):
309
316
  """
310
317
  Zeroth Fourier mode of the intensity with argument `tau` (type: array or float).
311
318
  Returns an ndarray with axes corresponding to variation with `mu` and `tau` respectively.
@@ -316,8 +323,8 @@ def _assemble_intensity_and_fluxes(
316
323
  to switch to an antiderivative of the function with respect to `tau`.
317
324
  """
318
325
  tau = np.atleast_1d(tau)
319
- assert np.all(tau >= 0)
320
- assert np.all(tau <= tau_arr[-1])
326
+ if np.all(tau < 0) or np.all(tau > tau_arr[-1]):
327
+ raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
321
328
 
322
329
  # Atmospheric layer indices
323
330
  l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
@@ -333,7 +340,10 @@ def _assemble_intensity_and_fluxes(
333
340
  # in performing the reclassification of delta-scaled actinic flux
334
341
  if _return_act_dscale_for_reclass:
335
342
  if is_antiderivative_wrt_tau:
336
- act_dscale_reclassification = I0 * np.exp(-scaled_tau / mu0) / (-scale_tau[l] / mu0) - I0 * np.exp(-tau / mu0) * -mu0
343
+ act_dscale_reclassification = (
344
+ I0 * np.exp(-scaled_tau / mu0) / (-scale_tau[l] / mu0)
345
+ - I0 * np.exp(-tau / mu0) * -mu0
346
+ )
337
347
  else:
338
348
  act_dscale_reclassification = I0 * np.exp(-scaled_tau / mu0) - I0 * np.exp(-tau / mu0)
339
349
  else:
@@ -349,49 +359,53 @@ def _assemble_intensity_and_fluxes(
349
359
  axis=-1,
350
360
  )
351
361
 
352
- if is_antiderivative_wrt_tau:
353
- if there_is_beam_source:
354
- u0 = np.einsum(
355
- "tij, tj -> it",
356
- GC_collect_0[l, :, :],
357
- np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]),
358
- optimize=True,
359
- ) + B_collect_0[l, :].T * np.exp(-scaled_tau[None, :] / mu0) / (
360
- -scale_tau[None, l] / mu0
361
- )
362
- else:
363
- u0 = np.einsum(
362
+ if is_antiderivative_wrt_tau and there_is_beam_source:
363
+ u0 = (
364
+ np.einsum(
364
365
  "tij, tj -> it",
365
366
  GC_collect_0[l, :, :],
366
- np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]),
367
+ np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
367
368
  optimize=True,
368
369
  )
370
+ + (B_collect_0.T / (-scale_tau / mu0)[None, :])[:, l]
371
+ * np.exp(-scaled_tau / mu0)[None, :]
372
+ )
373
+ if is_antiderivative_wrt_tau and not there_is_beam_source:
374
+ u0 = np.einsum(
375
+ "tij, tj -> it",
376
+ GC_collect_0[l, :, :],
377
+ np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
378
+ optimize=True,
379
+ )
380
+ if not is_antiderivative_wrt_tau and there_is_beam_source:
381
+ u0 = np.einsum(
382
+ "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
383
+ ) + B_collect_0.T[:, l] * np.exp(-scaled_tau / mu0)[None, :]
369
384
  else:
370
- if there_is_beam_source:
371
- u0 = np.einsum(
372
- "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
373
- ) + B_collect_0[l, :].T * np.exp(-scaled_tau[None, :] / mu0)
374
- else:
375
- u0 = np.einsum(
376
- "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
377
- )
385
+ u0 = np.einsum(
386
+ "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
387
+ )
378
388
 
379
389
  # Contribution from particular solution for isotropic internal sources
380
390
  if there_is_iso_source:
391
+ l_uniq, l_inv = np.unique(l, return_inverse=True)
381
392
  _mathscr_v_contribution = _mathscr_v(
382
- tau, l,
383
- s_poly_coeffs,
384
- Nscoeffs,
385
- G_collect_0,
386
- K_collect_0,
387
- G_inv_collect_0,
393
+ tau, l_inv, Nscoeffs,
394
+ s_poly_coeffs[l_uniq],
395
+ G_collect_0[l_uniq],
396
+ K_collect_0[l_uniq],
397
+ G_inv_collect_0[l_uniq],
388
398
  mu_arr,
389
399
  is_antiderivative_wrt_tau,
390
- _autograd_compatible
400
+ autograd_compatible
391
401
  )
392
402
  u0 = u0 + _mathscr_v_contribution
393
-
394
- if _return_act_dscale_for_reclass:
403
+
404
+ if return_tau_arr and _return_act_dscale_for_reclass:
405
+ return I0_orig * np.squeeze(u0), tau_arr, act_dscale_reclassification
406
+ elif return_tau_arr and not _return_act_dscale_for_reclass:
407
+ return I0_orig * np.squeeze(u0), tau_arr
408
+ elif not return_tau_arr and _return_act_dscale_for_reclass:
395
409
  return I0_orig * np.squeeze(u0), act_dscale_reclassification
396
410
  else:
397
411
  return I0_orig * np.squeeze(u0)
@@ -406,7 +420,7 @@ def _assemble_intensity_and_fluxes(
406
420
  B_neg = B_collect_0[:, N:].T
407
421
 
408
422
 
409
- def flux_up(tau, is_antiderivative_wrt_tau=False):
423
+ def flux_up(tau, is_antiderivative_wrt_tau=False, return_tau_arr=False):
410
424
  """
411
425
  (Energetic) Flux function with argument `tau` (type: array or float) for positive (upward) `mu` values.
412
426
  Returns the diffuse flux magnitudes (same type and size as `tau`).
@@ -414,8 +428,8 @@ def _assemble_intensity_and_fluxes(
414
428
  to switch to an antiderivative of the function with respect to `tau`.
415
429
  """
416
430
  tau = np.atleast_1d(tau)
417
- assert np.all(tau >= 0)
418
- assert np.all(tau <= tau_arr[-1])
431
+ if np.all(tau < 0) or np.all(tau > tau_arr[-1]):
432
+ raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
419
433
 
420
434
  # Atmospheric layer indices
421
435
  l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
@@ -431,27 +445,26 @@ def _assemble_intensity_and_fluxes(
431
445
  scaled_tau = tau
432
446
 
433
447
  if there_is_iso_source:
448
+ l_uniq, l_inv = np.unique(l, return_inverse=True)
434
449
  _mathscr_v_contribution = _mathscr_v(
435
- tau, l,
436
- s_poly_coeffs,
437
- Nscoeffs,
438
- G_collect_0,
439
- K_collect_0,
440
- G_inv_collect_0,
450
+ tau, l_inv, Nscoeffs,
451
+ s_poly_coeffs[l_uniq],
452
+ G_collect_0[l_uniq, :N, :],
453
+ K_collect_0[l_uniq],
454
+ G_inv_collect_0[l_uniq],
441
455
  mu_arr,
442
456
  is_antiderivative_wrt_tau,
443
- _autograd_compatible
444
- )[:N, :]
457
+ autograd_compatible
458
+ )
445
459
  else:
446
460
  _mathscr_v_contribution = 0
447
461
 
448
- if there_is_beam_source:
449
- if is_antiderivative_wrt_tau:
450
- direct_beam_contribution = (
451
- B_pos[:, l] * np.exp(-scaled_tau[None, :] / mu0) / (-scale_tau[None, l] / mu0)
452
- )
453
- else:
454
- direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau[None, :] / mu0)
462
+ if there_is_beam_source and is_antiderivative_wrt_tau:
463
+ direct_beam_contribution = (
464
+ (B_pos / (-scale_tau / mu0)[None, :])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
465
+ )
466
+ elif there_is_beam_source and not is_antiderivative_wrt_tau:
467
+ direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau / mu0)[None, :]
455
468
  else:
456
469
  direct_beam_contribution = 0
457
470
 
@@ -468,7 +481,7 @@ def _assemble_intensity_and_fluxes(
468
481
  np.einsum(
469
482
  "tij, tj -> it",
470
483
  GC_pos[l, :, :],
471
- np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]),
484
+ np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
472
485
  )
473
486
  + direct_beam_contribution
474
487
  + _mathscr_v_contribution
@@ -479,12 +492,15 @@ def _assemble_intensity_and_fluxes(
479
492
  + direct_beam_contribution
480
493
  + _mathscr_v_contribution
481
494
  )
482
-
483
495
  flux = 2 * pi * (mu_arr_pos * W) @ u0_pos
484
- return I0_orig * np.squeeze(flux)[()]
496
+
497
+ if return_tau_arr:
498
+ return I0_orig * np.squeeze(flux)[()], tau_arr
499
+ else:
500
+ return I0_orig * np.squeeze(flux)[()]
485
501
 
486
502
 
487
- def flux_down(tau, is_antiderivative_wrt_tau=False):
503
+ def flux_down(tau, is_antiderivative_wrt_tau=False, return_tau_arr=False):
488
504
  """
489
505
  (Energetic) Flux function with argument `tau` (type: array or float) for negative (downward) `mu` values.
490
506
  Returns a tuple of the diffuse and direct flux magnitudes respectively where each entry is of the
@@ -493,8 +509,8 @@ def _assemble_intensity_and_fluxes(
493
509
  to switch to an antiderivative of the function with respect to `tau`.
494
510
  """
495
511
  tau = np.atleast_1d(tau)
496
- assert np.all(tau >= 0)
497
- assert np.all(tau <= tau_arr[-1])
512
+ if np.all(tau < 0) or np.all(tau > tau_arr[-1]):
513
+ raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
498
514
 
499
515
  # Atmospheric layer indices
500
516
  l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
@@ -510,29 +526,28 @@ def _assemble_intensity_and_fluxes(
510
526
  scaled_tau = tau
511
527
 
512
528
  if there_is_iso_source:
529
+ l_uniq, l_inv = np.unique(l, return_inverse=True)
513
530
  _mathscr_v_contribution = _mathscr_v(
514
- tau, l,
515
- s_poly_coeffs,
516
- Nscoeffs,
517
- G_collect_0,
518
- K_collect_0,
519
- G_inv_collect_0,
531
+ tau, l_inv, Nscoeffs,
532
+ s_poly_coeffs[l_uniq],
533
+ G_collect_0[l_uniq, N:, :],
534
+ K_collect_0[l_uniq],
535
+ G_inv_collect_0[l_uniq],
520
536
  mu_arr,
521
537
  is_antiderivative_wrt_tau,
522
- _autograd_compatible
523
- )[N:, :]
538
+ autograd_compatible
539
+ )
524
540
  else:
525
541
  _mathscr_v_contribution = 0
526
542
 
527
- if there_is_beam_source:
528
- if is_antiderivative_wrt_tau:
529
- direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau[None, :] / mu0) / (-scale_tau[None, l] / mu0)
530
- direct_beam = I0 * mu0 * np.exp(-tau / mu0) * -mu0
531
- direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0) / (-scale_tau[None, l] / mu0)
532
- else:
533
- direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau[None, :] / mu0)
534
- direct_beam = I0 * mu0 * np.exp(-tau / mu0)
535
- direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0)
543
+ if there_is_beam_source and is_antiderivative_wrt_tau:
544
+ direct_beam_contribution = (B_neg / (-scale_tau / mu0)[None, l])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
545
+ direct_beam = I0 * mu0 * np.exp(-tau / mu0) * -mu0
546
+ direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0) / (-scale_tau / mu0)[None, l]
547
+ elif there_is_beam_source and not is_antiderivative_wrt_tau:
548
+ direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau / mu0)[None, :]
549
+ direct_beam = I0 * mu0 * np.exp(-tau / mu0)
550
+ direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0)
536
551
  else:
537
552
  direct_beam_contribution = 0
538
553
  direct_beam = 0
@@ -548,7 +563,7 @@ def _assemble_intensity_and_fluxes(
548
563
 
549
564
  if is_antiderivative_wrt_tau:
550
565
  u0_neg = (
551
- np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]))
566
+ np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :])
552
567
  + direct_beam_contribution
553
568
  + _mathscr_v_contribution
554
569
  )
@@ -558,15 +573,22 @@ def _assemble_intensity_and_fluxes(
558
573
  + direct_beam_contribution
559
574
  + _mathscr_v_contribution
560
575
  )
561
-
562
576
  diffuse_flux = 2 * pi * (mu_arr_pos * W) @ u0_neg + direct_beam_scaled - direct_beam
563
- return (
564
- I0_orig * np.squeeze(diffuse_flux)[()],
565
- I0_orig * I0 * np.squeeze(direct_beam)[()],
566
- )
577
+
578
+ if return_tau_arr:
579
+ return (
580
+ I0_orig * np.squeeze(diffuse_flux)[()],
581
+ I0_orig * I0 * np.squeeze(direct_beam)[()],
582
+ tau_arr
583
+ )
584
+ else:
585
+ return (
586
+ I0_orig * np.squeeze(diffuse_flux)[()],
587
+ I0_orig * I0 * np.squeeze(direct_beam)[()],
588
+ )
567
589
  # --------------------------------------------------------------------------------------------------------------------------
568
590
 
569
591
  if only_flux:
570
- return flux_up, flux_down, u0
592
+ return flux_up, flux_down, u0, G_collect_0, K_collect_0 # TODO: Remove the temporary G_collect_0, K_collect_0
571
593
  else:
572
594
  return flux_up, flux_down, u0, u