PythonicDISORT 0.9.0__tar.gz → 0.9.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PythonicDISORT
3
- Version: 0.9.0
3
+ Version: 0.9.1
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
@@ -8,7 +8,7 @@ dependencies = [
8
8
  "scipy>=1.8.0",
9
9
  ]
10
10
  name = "PythonicDISORT"
11
- version = "0.9.0"
11
+ version = "0.9.1"
12
12
  authors = [
13
13
  { name="Dion HO Jia Xu", email="dh3065@columbia.edu" },
14
14
  ]
@@ -20,12 +20,12 @@ 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
28
+ use_banded_solver_NLayers, # Number of layers above or equal which to use `scipy.linalg.solve_banded`
29
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`
@@ -66,13 +66,13 @@ 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 |
75
+ | `use_banded_solver_NLayers` | scalar |
76
76
  | `_autograd_compatible` | boolean |
77
77
 
78
78
  Notable internal variables of _assemble_intensity_and_fluxes
@@ -146,10 +146,10 @@ def _assemble_intensity_and_fluxes(
146
146
  there_is_beam_source,
147
147
  b_pos, b_neg,
148
148
  b_pos_is_scalar, b_neg_is_scalar,
149
- s_poly_coeffs,
150
149
  Nscoeffs,
150
+ s_poly_coeffs,
151
151
  there_is_iso_source,
152
- NLayers_to_use_sparse,
152
+ use_banded_solver_NLayers,
153
153
  )
154
154
 
155
155
  G_collect_0 = G_collect[0, :, :, :]
@@ -198,26 +198,28 @@ def _assemble_intensity_and_fluxes(
198
198
 
199
199
  if is_antiderivative_wrt_tau:
200
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
201
+ um = (
202
+ np.einsum(
203
+ "mtij, mtj -> mti",
204
+ GC_collect[:, l, :, :],
205
+ np.exp(exponent) / (scale_tau[None, :, None] * K_collect)[:, l, :],
206
+ optimize=True,
207
+ )
208
+ + (B_collect / (-scale_tau / mu0)[None, :, None])[:, l, :]
209
+ * np.exp(-scaled_tau / mu0)[None, :, None]
208
210
  )
209
211
  else:
210
212
  um = np.einsum(
211
213
  "mtij, mtj -> mti",
212
214
  GC_collect[:, l, :, :],
213
- np.exp(exponent) / (scale_tau[None, l, None] * K_collect[:, l, :]),
215
+ np.exp(exponent) / (scale_tau[None, l, None] * K_collect)[:, l, :],
214
216
  optimize=True,
215
217
  )
216
218
  else:
217
219
  if there_is_beam_source:
218
220
  um = np.einsum(
219
221
  "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
220
- ) + B_collect[:, l, :] * np.exp(-scaled_tau[None, :, None] / mu0)
222
+ ) + B_collect[:, l, :] * np.exp(-scaled_tau / mu0)[None, :, None]
221
223
  else:
222
224
  um = np.einsum(
223
225
  "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
@@ -225,13 +227,13 @@ def _assemble_intensity_and_fluxes(
225
227
 
226
228
  # Contribution from particular solution for isotropic internal sources
227
229
  if there_is_iso_source:
230
+ l_uniq, l_inv = np.unique(l, return_inverse=True)
228
231
  _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,
232
+ tau, l_inv, Nscoeffs,
233
+ s_poly_coeffs[l_uniq],
234
+ G_collect_0[l_uniq],
235
+ K_collect_0[l_uniq],
236
+ G_inv_collect_0[l_uniq],
235
237
  mu_arr,
236
238
  is_antiderivative_wrt_tau,
237
239
  _autograd_compatible
@@ -262,26 +264,28 @@ def _assemble_intensity_and_fluxes(
262
264
  )
263
265
  if is_antiderivative_wrt_tau:
264
266
  if there_is_beam_source:
265
- ulast = np.einsum(
266
- "tij, tj -> it",
267
- GC_collect[-1, l, :, :],
268
- np.exp(exponent) / (scale_tau[l, None] * K_collect[-1, l, :]),
269
- optimize=True,
270
- ) + B_collect[-1, l, :].T * np.exp(-scaled_tau[None, :] / mu0) / (
271
- -scale_tau[None, l] / mu0
267
+ ulast = (
268
+ np.einsum(
269
+ "tij, tj -> it",
270
+ GC_collect[-1, l, :, :],
271
+ np.exp(exponent) / (scale_tau[:, None] * K_collect[-1, :, :])[l, :],
272
+ optimize=True,
273
+ )
274
+ + (B_collect.T[-1, :, :] / (-scale_tau / mu0)[None, :])[:, l]
275
+ * np.exp(-scaled_tau / mu0)[None, :]
272
276
  )
273
277
  else:
274
278
  ulast = np.einsum(
275
279
  "tij, tj -> it",
276
280
  GC_collect[-1, l, :, :],
277
- np.exp(exponent) / (scale_tau[l, None] * K_collect[-1, l, :]),
281
+ np.exp(exponent) / (scale_tau[:, None] * K_collect[-1, :, :])[l, :],
278
282
  optimize=True,
279
283
  )
280
284
  else:
281
285
  if there_is_beam_source:
282
286
  ulast = np.einsum(
283
287
  "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
284
- ) + B_collect[-1, l, :].T * np.exp(-scaled_tau[None, :] / mu0)
288
+ ) + B_collect.T[-1, :, l] * np.exp(-scaled_tau / mu0)[None, :]
285
289
  else:
286
290
  ulast = np.einsum(
287
291
  "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
@@ -333,7 +337,10 @@ def _assemble_intensity_and_fluxes(
333
337
  # in performing the reclassification of delta-scaled actinic flux
334
338
  if _return_act_dscale_for_reclass:
335
339
  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
340
+ act_dscale_reclassification = (
341
+ I0 * np.exp(-scaled_tau / mu0) / (-scale_tau[l] / mu0)
342
+ - I0 * np.exp(-tau / mu0) * -mu0
343
+ )
337
344
  else:
338
345
  act_dscale_reclassification = I0 * np.exp(-scaled_tau / mu0) - I0 * np.exp(-tau / mu0)
339
346
  else:
@@ -351,26 +358,28 @@ def _assemble_intensity_and_fluxes(
351
358
 
352
359
  if is_antiderivative_wrt_tau:
353
360
  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
+ u0 = (
362
+ np.einsum(
363
+ "tij, tj -> it",
364
+ GC_collect_0[l, :, :],
365
+ np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
366
+ optimize=True,
367
+ )
368
+ + (B_collect_0.T / (-scale_tau / mu0)[None, :])[:, l]
369
+ * np.exp(-scaled_tau / mu0)[None, :]
361
370
  )
362
371
  else:
363
372
  u0 = np.einsum(
364
373
  "tij, tj -> it",
365
374
  GC_collect_0[l, :, :],
366
- np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]),
375
+ np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
367
376
  optimize=True,
368
377
  )
369
378
  else:
370
379
  if there_is_beam_source:
371
380
  u0 = np.einsum(
372
381
  "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
373
- ) + B_collect_0[l, :].T * np.exp(-scaled_tau[None, :] / mu0)
382
+ ) + B_collect_0.T[:, l] * np.exp(-scaled_tau / mu0)[None, :]
374
383
  else:
375
384
  u0 = np.einsum(
376
385
  "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
@@ -378,13 +387,13 @@ def _assemble_intensity_and_fluxes(
378
387
 
379
388
  # Contribution from particular solution for isotropic internal sources
380
389
  if there_is_iso_source:
390
+ l_uniq, l_inv = np.unique(l, return_inverse=True)
381
391
  _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,
392
+ tau, l_inv, Nscoeffs,
393
+ s_poly_coeffs[l_uniq],
394
+ G_collect_0[l_uniq],
395
+ K_collect_0[l_uniq],
396
+ G_inv_collect_0[l_uniq],
388
397
  mu_arr,
389
398
  is_antiderivative_wrt_tau,
390
399
  _autograd_compatible
@@ -431,27 +440,27 @@ def _assemble_intensity_and_fluxes(
431
440
  scaled_tau = tau
432
441
 
433
442
  if there_is_iso_source:
443
+ l_uniq, l_inv = np.unique(l, return_inverse=True)
434
444
  _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,
445
+ tau, l_inv, Nscoeffs,
446
+ s_poly_coeffs[l_uniq],
447
+ G_collect_0[l_uniq, :N, :],
448
+ K_collect_0[l_uniq],
449
+ G_inv_collect_0[l_uniq],
441
450
  mu_arr,
442
451
  is_antiderivative_wrt_tau,
443
452
  _autograd_compatible
444
- )[:N, :]
453
+ )
445
454
  else:
446
455
  _mathscr_v_contribution = 0
447
456
 
448
457
  if there_is_beam_source:
449
458
  if is_antiderivative_wrt_tau:
450
459
  direct_beam_contribution = (
451
- B_pos[:, l] * np.exp(-scaled_tau[None, :] / mu0) / (-scale_tau[None, l] / mu0)
460
+ (B_pos / (-scale_tau / mu0)[None, :])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
452
461
  )
453
462
  else:
454
- direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau[None, :] / mu0)
463
+ direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau / mu0)[None, :]
455
464
  else:
456
465
  direct_beam_contribution = 0
457
466
 
@@ -468,7 +477,7 @@ def _assemble_intensity_and_fluxes(
468
477
  np.einsum(
469
478
  "tij, tj -> it",
470
479
  GC_pos[l, :, :],
471
- np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]),
480
+ np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
472
481
  )
473
482
  + direct_beam_contribution
474
483
  + _mathscr_v_contribution
@@ -510,27 +519,27 @@ def _assemble_intensity_and_fluxes(
510
519
  scaled_tau = tau
511
520
 
512
521
  if there_is_iso_source:
522
+ l_uniq, l_inv = np.unique(l, return_inverse=True)
513
523
  _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,
524
+ tau, l_inv, Nscoeffs,
525
+ s_poly_coeffs[l_uniq],
526
+ G_collect_0[l_uniq, N:, :],
527
+ K_collect_0[l_uniq],
528
+ G_inv_collect_0[l_uniq],
520
529
  mu_arr,
521
530
  is_antiderivative_wrt_tau,
522
531
  _autograd_compatible
523
- )[N:, :]
532
+ )
524
533
  else:
525
534
  _mathscr_v_contribution = 0
526
535
 
527
536
  if there_is_beam_source:
528
537
  if is_antiderivative_wrt_tau:
529
- direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau[None, :] / mu0) / (-scale_tau[None, l] / mu0)
538
+ direct_beam_contribution = (B_neg / (-scale_tau / mu0)[None, l])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
530
539
  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)
540
+ direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0) / (-scale_tau / mu0)[None, l]
532
541
  else:
533
- direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau[None, :] / mu0)
542
+ direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau / mu0)[None, :]
534
543
  direct_beam = I0 * mu0 * np.exp(-tau / mu0)
535
544
  direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0)
536
545
  else:
@@ -548,7 +557,7 @@ def _assemble_intensity_and_fluxes(
548
557
 
549
558
  if is_antiderivative_wrt_tau:
550
559
  u0_neg = (
551
- np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]))
560
+ np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :])
552
561
  + direct_beam_contribution
553
562
  + _mathscr_v_contribution
554
563
  )
@@ -1,5 +1,5 @@
1
1
  from PythonicDISORT.subroutines import _mathscr_v
2
- from PythonicDISORT.subroutines import _nd_slice_to_indexes
2
+ from PythonicDISORT.subroutines import to_diag_ordered_form
3
3
 
4
4
  import numpy as np
5
5
  import scipy as sc
@@ -23,10 +23,10 @@ def _solve_for_coeffs(
23
23
  there_is_beam_source, # Is there a beam source?
24
24
  b_pos, b_neg, # Dirichlet BCs
25
25
  b_pos_is_scalar, b_neg_is_scalar, # Is each Dirichlet BCs scalar (isotropic)?
26
- s_poly_coeffs, # Polynomial coefficients of isotropic source
27
26
  Nscoeffs, # Number of isotropic source polynomial coefficients
27
+ s_poly_coeffs, # Polynomial coefficients of isotropic source
28
28
  there_is_iso_source, # Is there an isotropic source?
29
- NLayers_to_use_sparse, # Number of layers above or equal which to use sparse matrices
29
+ use_banded_solver_NLayers, # Number of layers above or equal which to use `scipy.linalg.solve_banded`
30
30
  ):
31
31
  """
32
32
  Uses the boundary conditions to solve for the unknown coefficients
@@ -62,11 +62,11 @@ def _solve_for_coeffs(
62
62
  | `b_pos` | `NQuad/2 x NFourier` or scalar |
63
63
  | `b_neg` | `NQuad/2 x NFourier` or scalar |
64
64
  | `b_pos_is_scalar` | boolean |
65
- | `b_neg_is_scalar` | boolean |
66
- | `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
65
+ | `b_neg_is_scalar` | boolean |
67
66
  | `Nscoeffs` | scalar |
67
+ | `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
68
68
  | `there_is_iso_source` | boolean |
69
- | `NLayers_to_use_sparse` | scalar |
69
+ | `use_banded_solver_NLayers` | scalar |
70
70
 
71
71
  Notable internal variables of _solve_for_coeffs
72
72
  | Variable | Shape |
@@ -77,13 +77,16 @@ def _solve_for_coeffs(
77
77
  ################################## Solve for coefficients of homogeneous solution ##########################################
78
78
  ############# Refer to Section 3.6.2 (single-layer) and 4 (multi-layer) of the Comprehensive Documentation #################
79
79
 
80
+ dim = NLayers * NQuad
80
81
  GC_collect = np.empty((NFourier, NLayers, NQuad, NQuad))
81
- use_sparse_framework = NLayers >= NLayers_to_use_sparse
82
+ use_banded_solver = (NLayers >= use_banded_solver_NLayers)
83
+ if use_banded_solver:
84
+ sym_offset = 3 * N - 1
82
85
 
83
86
  # The following loops can easily be parallelized, but the speed-up is unlikely to be worth the overhead
84
87
  for m in range(NFourier):
85
88
  m_equals_0 = (m == 0)
86
- BDRF_bool = m < NBDRF
89
+ BDRF_bool = (NBDRF > m)
87
90
 
88
91
  G_collect_m = G_collect[m, :, :, :]
89
92
  K_collect_m = K_collect[m, :, :]
@@ -122,12 +125,12 @@ def _solve_for_coeffs(
122
125
  b_neg_m = b_neg[:, m]
123
126
 
124
127
  # _mathscr_v_contribution
125
- if there_is_iso_source and m_equals_0:
128
+ if m_equals_0 and there_is_iso_source:
126
129
  _mathscr_v_contribution_top = -_mathscr_v(
127
130
  np.array([0]),
128
131
  np.array([0]),
129
- s_poly_coeffs[0:1, :],
130
132
  Nscoeffs,
133
+ s_poly_coeffs[0:1, :],
131
134
  G_collect_m[0:1, N:, :],
132
135
  K_collect_m[0:1, :],
133
136
  G_inv_collect_0[0:1, :, :],
@@ -140,22 +143,22 @@ def _solve_for_coeffs(
140
143
  _mathscr_v_contribution_middle = (
141
144
  _mathscr_v(
142
145
  tau_arr[:-1],
143
- indices + 1,
144
- s_poly_coeffs,
146
+ indices,
145
147
  Nscoeffs,
146
- G_collect_m,
147
- K_collect_m,
148
- G_inv_collect_0,
148
+ s_poly_coeffs[indices + 1],
149
+ G_collect_m[indices + 1],
150
+ K_collect_m[indices + 1],
151
+ G_inv_collect_0[indices + 1],
149
152
  mu_arr,
150
153
  )
151
154
  - _mathscr_v(
152
155
  tau_arr[:-1],
153
156
  indices,
154
- s_poly_coeffs,
155
157
  Nscoeffs,
156
- G_collect_m,
157
- K_collect_m,
158
- G_inv_collect_0,
158
+ s_poly_coeffs[indices],
159
+ G_collect_m[indices],
160
+ K_collect_m[indices],
161
+ G_inv_collect_0[indices],
159
162
  mu_arr,
160
163
  )
161
164
  ).ravel(order='F')
@@ -163,8 +166,8 @@ def _solve_for_coeffs(
163
166
  _mathscr_v_contribution_bottom = -_mathscr_v(
164
167
  tau_arr[-1:],
165
168
  np.array([0]),
166
- s_poly_coeffs[-1:, :],
167
169
  Nscoeffs,
170
+ s_poly_coeffs[-1:, :],
168
171
  G_collect_m[-1:, :N, :],
169
172
  K_collect_m[-1:, :],
170
173
  G_inv_collect_0[-1:, :, :],
@@ -177,8 +180,8 @@ def _solve_for_coeffs(
177
180
  @ _mathscr_v(
178
181
  tau_arr[-1:],
179
182
  np.array([0]),
180
- s_poly_coeffs[-1:, :],
181
183
  Nscoeffs,
184
+ s_poly_coeffs[-1:, :],
182
185
  G_collect_m[-1:, N:, :],
183
186
  K_collect_m[-1:, :],
184
187
  G_inv_collect_0[-1:, :, :],
@@ -197,41 +200,45 @@ def _solve_for_coeffs(
197
200
  _mathscr_v_contribution = 0
198
201
 
199
202
  if there_is_beam_source:
200
- if BDRF_bool:
201
- BDRF_RHS_contribution = mathscr_X_pos + R @ B_collect_m[-1, N:]
202
- else:
203
- BDRF_RHS_contribution = 0
204
-
205
203
  RHS_middle = np.array([])
206
204
  if is_atmos_multilayered:
207
- RHS_middle = np.array(
208
- [
209
- (B_collect_m[l + 1, :] - B_collect_m[l, :])
210
- * np.exp(-mu0 * scaled_tau_arr_with_0[l + 1])
211
- for l in range(NLayers - 1)
212
- ]
205
+ l_range = np.arange(1, NLayers)
206
+ RHS_middle = (
207
+ (B_collect_m[l_range, :] - B_collect_m[l_range - 1, :])
208
+ * np.exp(-mu0 * scaled_tau_arr_with_0)[l_range, None]
213
209
  ).ravel()
214
-
215
- RHS = (
216
- np.concatenate(
217
- [
218
- b_neg_m - B_collect_m[0, N:],
219
- RHS_middle,
220
- b_pos_m
221
- + (BDRF_RHS_contribution - B_collect_m[-1, :N])
222
- * np.exp(-scaled_tau_arr_with_0[-1] / mu0),
223
- ]
210
+
211
+ if BDRF_bool:
212
+ RHS = (
213
+ np.concatenate(
214
+ [
215
+ b_neg_m - B_collect_m[0, N:],
216
+ RHS_middle,
217
+ b_pos_m
218
+ + (mathscr_X_pos + R @ B_collect_m[-1, N:] - B_collect_m[-1, :N])
219
+ * np.exp(-scaled_tau_arr_with_0[-1] / mu0),
220
+ ]
221
+ )
222
+ + _mathscr_v_contribution
223
+ )
224
+ else:
225
+ RHS = (
226
+ np.concatenate(
227
+ [
228
+ b_neg_m - B_collect_m[0, N:],
229
+ RHS_middle,
230
+ b_pos_m
231
+ - B_collect_m[-1, :N] * np.exp(-scaled_tau_arr_with_0[-1] / mu0),
232
+ ]
233
+ )
234
+ + _mathscr_v_contribution
224
235
  )
225
- + _mathscr_v_contribution
226
- )
227
236
  else:
228
237
  RHS_middle = np.zeros(NQuad * (NLayers - 1))
229
238
  RHS = np.concatenate([b_neg_m, RHS_middle, b_pos_m]) + _mathscr_v_contribution
230
239
  # --------------------------------------------------------------------------------------------------------------------------
231
240
 
232
241
  # Assemble LHS (much of this code is replicated in Section 4 of the Comprehensive Documentation)
233
- dim = NLayers * NQuad
234
-
235
242
  G_0_nn = G_collect_m[0, N:, :N]
236
243
  G_0_np = G_collect_m[0, N:, N:]
237
244
  G_L_pn = G_collect_m[-1, :N, :N]
@@ -241,147 +248,62 @@ def _solve_for_coeffs(
241
248
  E_Lm1L = np.exp(
242
249
  K_collect_m[-1, :N] * (scaled_tau_arr_with_0[-1] - scaled_tau_arr_with_0[-2])
243
250
  )
244
- if m < NBDRF:
245
- BDRF_LHS_contribution_neg = R @ G_L_nn
246
- BDRF_LHS_contribution_pos = R @ G_L_np
247
- else:
248
- BDRF_LHS_contribution_neg = 0
249
- BDRF_LHS_contribution_pos = 0
250
251
 
251
- if use_sparse_framework:
252
- N_sq = N**2
253
- density = 4 * N_sq * (2 * NLayers - 1)
254
- row_indices = np.empty(density)
255
- col_indices = np.empty(density)
256
- block_rows = np.empty(density)
252
+ LHS = np.zeros((dim, dim))
253
+ # BCs for the entire atmosphere
254
+ LHS[:N, :N] = G_0_nn
255
+ LHS[:N, N : NQuad] = (
256
+ G_0_np
257
+ * np.exp(K_collect_m[0, :N] * scaled_tau_arr_with_0[1])[None, :]
258
+ )
259
+ if BDRF_bool:
260
+ LHS[-N:, -NQuad : -N] = (G_L_pn - R @ G_L_nn) * E_Lm1L[None, :]
261
+ LHS[-N:, -N:] = G_L_pp - R @ G_L_np
262
+ else:
263
+ LHS[-N:, -NQuad : -N] = G_L_pn * E_Lm1L[None, :]
264
+ LHS[-N:, -N:] = G_L_pp
265
+
266
+ # Interlayer / continuity BCs
267
+ for l in range(NLayers - 1):
268
+ G_l_pn = G_collect_m[l, :N, :N]
269
+ G_l_nn = G_collect_m[l, N:, :N]
270
+ G_l_ap = G_collect_m[l, :, N:]
271
+ G_lp1_an = G_collect_m[l + 1, :, :N]
272
+ G_lp1_pp = G_collect_m[l + 1, :N, N:]
273
+ G_lp1_np = G_collect_m[l + 1, N:, N:]
274
+ scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
275
+ scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
276
+ scaled_tau_arr_lp1 = scaled_tau_arr_with_0[l + 2]
277
+ # Postive eigenvalues
278
+ K_l_pos = K_collect_m[l, N:]
279
+ K_lp1_pos = K_collect_m[l + 1, N:]
280
+ E_lm1l = np.exp(K_l_pos * (scaled_tau_arr_lm1 - scaled_tau_arr_l))
281
+ E_llp1 = np.exp(K_lp1_pos * (scaled_tau_arr_l - scaled_tau_arr_lp1))
257
282
 
258
- block_row_len = 2 * NQuad**2
259
- block_row_ind = np.arange(block_row_len).reshape(NQuad, 2 * NQuad)
260
- block_row_sort = np.empty(block_row_len, dtype='int')
283
+ start_row = N + l * NQuad
284
+ start_col = l * NQuad
285
+ LHS[start_row : N + start_row, start_col : N + start_col] = G_l_pn * E_lm1l[None, :]
286
+ LHS[N + start_row : 2 * N + start_row, start_col : N + start_col] = G_l_nn * E_lm1l[None, :]
287
+ LHS[start_row : 2 * N + start_row, N + start_col : 2 * N + start_col] = G_l_ap
288
+ LHS[start_row : 2 * N + start_row, 2 * N + start_col : 3 * N + start_col] = -G_lp1_an
289
+ LHS[start_row : N + start_row, 3 * N + start_col : 4 * N + start_col] = -G_lp1_pp * E_llp1[None, :]
290
+ LHS[N + start_row : 2 * N + start_row, 3 * N + start_col : 4 * N + start_col] = -G_lp1_np * E_llp1[None, :]
261
291
 
262
- block_row_sort[:N_sq] = block_row_ind[:N, :N].ravel()
263
- block_row_sort[N_sq : 2 * N_sq] = block_row_ind[N : 2 * N, :N].ravel()
264
- block_row_sort[2 * N_sq : 4 * N_sq] = block_row_ind[:2 * N, N : 2 * N].ravel()
265
- block_row_sort[4 * N_sq : 6 * N_sq] = block_row_ind[:2 * N, 2 * N : 3 * N].ravel()
266
- block_row_sort[6 * N_sq : 7 * N_sq] = block_row_ind[:N, 3 * N : 4 * N].ravel()
267
- block_row_sort[7 * N_sq : 8 * N_sq] = block_row_ind[N : 2 * N, 3 * N : 4 * N].ravel()
268
-
269
- # BCs for the entire atmosphere
270
- row_indices[:N_sq], col_indices[:N_sq] = _nd_slice_to_indexes(np.s_[:N, :N])
271
- block_rows[:N_sq] = G_0_nn.ravel()
272
- (
273
- row_indices[N_sq : 2 * N_sq],
274
- col_indices[N_sq : 2 * N_sq],
275
- ) = _nd_slice_to_indexes(np.s_[:N, N:NQuad])
276
- block_rows[N_sq : 2 * N_sq] = (
277
- G_0_np * np.exp(K_collect_m[0, :N] * scaled_tau_arr_with_0[1])[None, :]
278
- ).ravel()
279
- (
280
- row_indices[2 * N_sq : 3 * N_sq],
281
- col_indices[2 * N_sq : 3 * N_sq],
282
- ) = _nd_slice_to_indexes(np.s_[dim - N : dim, dim - NQuad : dim - N])
283
- block_rows[2 * N_sq : 3 * N_sq] = (
284
- (G_L_pn - BDRF_LHS_contribution_neg) * E_Lm1L[None, :]
285
- ).ravel()
286
- (
287
- row_indices[3 * N_sq : 4 * N_sq],
288
- col_indices[3 * N_sq : 4 * N_sq],
289
- ) = _nd_slice_to_indexes(np.s_[dim - N : dim, dim - N : dim])
290
- block_rows[3 * N_sq : 4 * N_sq] = (
291
- G_L_pp - BDRF_LHS_contribution_pos
292
- ).ravel()
293
-
294
- # Interlayer / continuity BCs
295
- for l in range(NLayers - 1):
296
- G_l_pn = G_collect_m[l, :N, :N]
297
- G_l_nn = G_collect_m[l, N:, :N]
298
- G_l_ap = G_collect_m[l, :, N:]
299
- G_lp1_an = G_collect_m[l + 1, :, :N]
300
- G_lp1_pp = G_collect_m[l + 1, :N, N:]
301
- G_lp1_np = G_collect_m[l + 1, N:, N:]
302
- scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
303
- scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
304
- scaled_tau_arr_lp1 = scaled_tau_arr_with_0[l + 2]
305
- # Postive eigenvalues
306
- K_l_pos = K_collect_m[l, N:]
307
- K_lp1_pos = K_collect_m[l + 1, N:]
308
- E_lm1l = np.exp(K_l_pos * (scaled_tau_arr_lm1 - scaled_tau_arr_l))
309
- E_llp1 = np.exp(K_lp1_pos * (scaled_tau_arr_l - scaled_tau_arr_lp1))
310
-
311
- row_indices_l, col_indices_l = _nd_slice_to_indexes(
312
- np.s_[N + l * NQuad : N + (l + 1) * NQuad, l * NQuad : (l + 2) * NQuad]
313
- )
314
- row_indices[
315
- 4 * N_sq + l * block_row_len : 4 * N_sq + (l + 1) * block_row_len
316
- ] = row_indices_l[block_row_sort]
317
- col_indices[
318
- 4 * N_sq + l * block_row_len : 4 * N_sq + (l + 1) * block_row_len
319
- ] = col_indices_l[block_row_sort]
320
-
321
- start_ind = 4 * N_sq + l * block_row_len
322
- block_rows[start_ind : N_sq + start_ind] = (G_l_pn * E_lm1l[None, :]).ravel()
323
- block_rows[start_ind + N_sq : 2 * N_sq + start_ind] = (G_l_nn * E_lm1l[None, :]).ravel()
324
- block_rows[start_ind + 2 * N_sq : 4 * N_sq + start_ind] = G_l_ap.ravel()
325
- block_rows[start_ind + 4 * N_sq : 6 * N_sq + start_ind] = -G_lp1_an.ravel()
326
- block_rows[start_ind + 6 * N_sq : 7 * N_sq + start_ind] = -(G_lp1_pp * E_llp1[None, :]).ravel()
327
- block_rows[start_ind + 7 * N_sq : 8 * N_sq + start_ind] = -(G_lp1_np * E_llp1[None, :]).ravel()
328
-
329
- LHS = sc.sparse.coo_matrix(
330
- (
331
- block_rows,
332
- (
333
- row_indices,
334
- col_indices,
335
- ),
336
- ),
337
- shape=(dim, dim),
338
- )
339
-
340
- # Solve the system
341
- C_m = sc.sparse.linalg.spsolve(LHS.tocsr(), RHS).reshape(NLayers, NQuad)
292
+ # --------------------------------------------------------------------------------------------------------------------------
342
293
 
343
- else:
344
- LHS = np.zeros((dim, dim))
345
-
346
- # BCs for the entire atmosphere
347
- LHS[:N, :N] = G_0_nn
348
- LHS[:N, N : NQuad] = (
349
- G_0_np
350
- * np.exp(K_collect_m[0, :N] * scaled_tau_arr_with_0[1])[None, :]
294
+ # Solve the system
295
+ if use_banded_solver:
296
+ C_m = sc.linalg.solve_banded(
297
+ (sym_offset, sym_offset),
298
+ to_diag_ordered_form(LHS, sym_offset),
299
+ RHS,
300
+ True,
301
+ True,
302
+ False,
351
303
  )
352
- LHS[-N:, -NQuad : -N] = (G_L_pn - BDRF_LHS_contribution_neg) * E_Lm1L[None, :]
353
- LHS[-N:, -N:] = G_L_pp - BDRF_LHS_contribution_pos
354
-
355
- # Interlayer / continuity BCs
356
- for l in range(NLayers - 1):
357
- G_l_pn = G_collect_m[l, :N, :N]
358
- G_l_nn = G_collect_m[l, N:, :N]
359
- G_l_ap = G_collect_m[l, :, N:]
360
- G_lp1_an = G_collect_m[l + 1, :, :N]
361
- G_lp1_pp = G_collect_m[l + 1, :N, N:]
362
- G_lp1_np = G_collect_m[l + 1, N:, N:]
363
- scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
364
- scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
365
- scaled_tau_arr_lp1 = scaled_tau_arr_with_0[l + 2]
366
- # Postive eigenvalues
367
- K_l_pos = K_collect_m[l, N:]
368
- K_lp1_pos = K_collect_m[l + 1, N:]
369
- E_lm1l = np.exp(K_l_pos * (scaled_tau_arr_lm1 - scaled_tau_arr_l))
370
- E_llp1 = np.exp(K_lp1_pos * (scaled_tau_arr_l - scaled_tau_arr_lp1))
371
-
372
- start_row = N + l * NQuad
373
- start_col = l * NQuad
374
- LHS[start_row : N + start_row, start_col : N + start_col] = G_l_pn * E_lm1l[None, :]
375
- LHS[N + start_row : 2 * N + start_row, start_col : N + start_col] = G_l_nn * E_lm1l[None, :]
376
- LHS[start_row : 2 * N + start_row, N + start_col : 2 * N + start_col] = G_l_ap
377
- LHS[start_row : 2 * N + start_row, 2 * N + start_col : 3 * N + start_col] = -G_lp1_an
378
- LHS[start_row : N + start_row, 3 * N + start_col : 4 * N + start_col] = -G_lp1_pp * E_llp1[None, :]
379
- LHS[N + start_row : 2 * N + start_row, 3 * N + start_col : 4 * N + start_col] = -G_lp1_np * E_llp1[None, :]
380
-
381
- # Solve the system
382
- C_m = np.linalg.solve(LHS, RHS).reshape(NLayers, NQuad)
383
- # --------------------------------------------------------------------------------------------------------------------------
304
+ else:
305
+ C_m = np.linalg.solve(LHS, RHS)
384
306
 
385
- GC_collect[m, :, :, :] = G_collect_m * C_m[:, None, :]
307
+ GC_collect[m, :, :, :] = G_collect_m * C_m.reshape(NLayers, NQuad)[:, None, :]
386
308
 
387
309
  return GC_collect
@@ -53,7 +53,6 @@ def _solve_for_gen_and_part_sols(
53
53
  | `K_collect` | `NFourier*NLayers x NQuad` | Reshaped to NFourier x NLayers x NQuad
54
54
  | `alpha_arr` | `NFourier*NLayers x NQuad/2 x NQuad/2` | Reshaped to NFourier x NLayers x NQuad/2 x NQuad/2
55
55
  | `beta_arr` | `NFourier*NLayers x NQuad/2 x NQuad/2` | Reshaped to NFourier x NLayers x NQuad/2 x NQuad/2
56
- | `X_tilde_arr` | `NFourier*NLayers x NQuad` | Reshaped to NFourier x NLayers x NQuad
57
56
  | `B_collect` | `NFourier*NLayers x NQuad` or `None` | Reshaped to NFourier x NLayers x NQuad
58
57
  | `eigenvecs_GpG_arr` | `NFourier*NLayers x NQuad/2 x NQuad |
59
58
  | `eigenvecs_GmG_arr` | `NFourier*NLayers x NQuad/2 x NQuad |
@@ -72,7 +71,6 @@ def _solve_for_gen_and_part_sols(
72
71
  K_collect = np.empty((NFourier * NLayers, NQuad))
73
72
  alpha_arr = np.empty((NFourier * NLayers, N, N))
74
73
  beta_arr = np.empty((NFourier * NLayers, N, N))
75
- X_tilde_arr = np.empty((NFourier * NLayers, NQuad))
76
74
  no_shortcut_indices = []
77
75
  no_shortcut_indices_0 = []
78
76
 
@@ -103,21 +101,35 @@ def _solve_for_gen_and_part_sols(
103
101
  # Loop over NLayers atmospheric layers
104
102
  # --------------------------------------------------------------------------------------------------------------------------
105
103
  for l in range(NLayers):
106
- # More setup
107
104
  weighted_asso_Leg_coeffs_l = weighted_scaled_Leg_coeffs[l, :][ells] * fac
108
- scaled_omega_l = scaled_omega_arr[l]
109
105
 
110
- # We take precautions against overflow and underflow (this shouldn't happen though)
111
- if np.any(weighted_asso_Leg_coeffs_l > 0) and np.all(
112
- np.isfinite(asso_leg_term_pos)
106
+ if np.any(weighted_asso_Leg_coeffs_l > 0) and np.all( # Due to `fac` or the coeffs being zero
107
+ np.isfinite(asso_leg_term_pos) # We take precautions against overflow and underflow (this shouldn't happen though)
113
108
  ):
114
- # Generate D and X (phase function terms)
109
+ scaled_omega_l = scaled_omega_arr[l]
110
+
111
+ # Generate D
115
112
  # --------------------------------------------------------------------------------------------------------------------------
116
113
  D_temp = weighted_asso_Leg_coeffs_l[None, :] * asso_leg_term_pos.T
117
114
  D_pos = (scaled_omega_l / 2) * D_temp @ asso_leg_term_pos
118
115
  D_neg = (scaled_omega_l / 2) * D_temp @ asso_leg_term_neg
116
+
117
+ # --------------------------------------------------------------------------------------------------------------------------
118
+
119
119
 
120
+ # Assemble the coefficient matrix and additional terms
121
+ # --------------------------------------------------------------------------------------------------------------------------
122
+ DW = D_pos * W[None, :]
123
+ np.fill_diagonal(DW, np.diag(DW) - 1)
124
+ alpha = M_inv[:, None] * DW
125
+ beta = M_inv[:, None] * D_neg * W[None, :]
126
+
127
+ # --------------------------------------------------------------------------------------------------------------------------
128
+
129
+ # Particular solution for the direct beam source (refer to Section 3.6.1 of the Comprehensive Documentation)
130
+ # --------------------------------------------------------------------------------------------------------------------------
120
131
  if there_is_beam_source:
132
+ # Generate X
121
133
  X_temp = (
122
134
  (scaled_omega_l * I0 * (2 - (m == 0)) / (4 * pi))
123
135
  * weighted_asso_Leg_coeffs_l
@@ -125,32 +137,38 @@ def _solve_for_gen_and_part_sols(
125
137
  )
126
138
  X_pos = X_temp @ asso_leg_term_pos
127
139
  X_neg = X_temp @ asso_leg_term_neg
128
- # --------------------------------------------------------------------------------------------------------------------------
129
-
130
- # Assemble the coefficient matrix and additional terms
131
- # --------------------------------------------------------------------------------------------------------------------------
132
- alpha = M_inv[:, None] * (D_pos * W[None, :] - np.eye(N))
133
- beta = M_inv[:, None] * D_neg * W[None, :]
134
- if there_is_beam_source:
135
- X_tilde_arr[ind, :] = np.concatenate([-M_inv * X_pos, M_inv * X_neg])
140
+ X_tilde = np.concatenate([-M_inv * X_pos, M_inv * X_neg])
141
+
142
+ A = np.concatenate(
143
+ [
144
+ np.concatenate([-alpha, -beta], axis=1),
145
+ np.concatenate([beta, alpha], axis=1),
146
+ ],
147
+ axis=0,
148
+ )
149
+ np.fill_diagonal(A, np.diag(A) + 1 / mu0)
150
+ B_collect[ind, :] = -np.linalg.solve(A, X_tilde) # We moved the minus sign out
151
+
136
152
  # --------------------------------------------------------------------------------------------------------------------------
137
153
 
154
+
138
155
  alpha_arr[ind, :, :] = alpha
139
156
  beta_arr[ind, :, :] = beta
140
157
  no_shortcut_indices.append(ind)
141
- if there_is_iso_source and m_equals_0:
158
+ if m_equals_0 and there_is_iso_source:
142
159
  no_shortcut_indices_0.append(l)
143
160
 
144
161
  else:
145
162
  # This is a shortcut to the diagonalization results
146
163
  G = np.zeros((NQuad, NQuad))
147
- G[N:, :N] = np.eye(N)
148
- G[:N, N:] = np.eye(N)
164
+ np.fill_diagonal(G[N:, :N], 1)
165
+ np.fill_diagonal(G[:N, N:], 1)
149
166
 
150
167
  G_collect[ind, :, :] = G
151
168
  K_collect[ind, :] = -1 / mu_arr
152
- if there_is_iso_source and m_equals_0:
169
+ if m_equals_0 and there_is_iso_source:
153
170
  G_inv_collect_0[l, :, :] = G
171
+
154
172
  ind += 1
155
173
 
156
174
  if len(no_shortcut_indices) > 0:
@@ -184,26 +202,16 @@ def _solve_for_gen_and_part_sols(
184
202
  ),
185
203
  axis=1,
186
204
  )
187
- G_inv_arr = np.linalg.inv(G_arr)
188
205
 
189
206
  G_collect[no_shortcut_indices, :, :] = G_arr
190
207
  K_collect[no_shortcut_indices, :] = K_arr
191
208
  if len(no_shortcut_indices_0) > 0:
192
- G_inv_collect_0[no_shortcut_indices_0, :, :] = G_inv_arr[: len(no_shortcut_indices_0), :, :]
193
- # --------------------------------------------------------------------------------------------------------------------------
194
-
195
- # Particular solution for the direct beam source (refer to Section 3.6.1 of the Comprehensive Documentation)
196
- # --------------------------------------------------------------------------------------------------------------------------
197
- if there_is_beam_source:
198
- X_tilde_arr = X_tilde_arr[: len(no_shortcut_indices), :]
199
- B_collect[no_shortcut_indices, :] = np.einsum(
200
- "lij, ljk, lk -> li",
201
- -G_arr / (1 / mu0 + K_arr)[:, None, :],
202
- G_inv_arr,
203
- X_tilde_arr,
204
- optimize=True,
209
+ G_inv_collect_0[no_shortcut_indices_0, :, :] = np.linalg.inv(
210
+ G_collect[: len(no_shortcut_indices_0), :, :]
205
211
  )
212
+
206
213
  # --------------------------------------------------------------------------------------------------------------------------
214
+
207
215
 
208
216
  if there_is_beam_source:
209
217
  if there_is_iso_source:
@@ -22,7 +22,7 @@ def pydisort(
22
22
  NT_cor=False,
23
23
  BDRF_Fourier_modes=[],
24
24
  s_poly_coeffs=np.array([[]]),
25
- NLayers_to_use_sparse=13,
25
+ use_banded_solver_NLayers=13,
26
26
  _autograd_compatible = False,
27
27
  ):
28
28
  """Solves the 1D RTE for the fluxes, and optionally intensity,
@@ -75,8 +75,8 @@ def pydisort(
75
75
  s_poly_coeffs : optional, array
76
76
  Polynomial coefficients of isotropic internal sources.
77
77
  Arrange coefficients from lowest order term to highest.
78
- NLayers_to_use_sparse : optional, int
79
- At or above how many atmospheric layers should SciPy's sparse matrix framework be used?
78
+ use_banded_solver_NLayers : optional, int
79
+ At or above how many atmospheric layers should `scipy.linalg.solve_banded` be used?
80
80
 
81
81
  Returns
82
82
  -------
@@ -115,25 +115,25 @@ def pydisort(
115
115
 
116
116
  """
117
117
  Arguments of pydisort
118
- | Variable | Type / Shape |
119
- | ----------------------- | ---------------------------------- |
120
- | `tau_arr` | `NLayers` |
121
- | `omega_arr` | `NLayers` |
122
- | `NQuad` | scalar |
123
- | `Leg_coeffs_all` | `NLeg_all` |
124
- | `mu0` | scalar |
125
- | `I0` | scalar |
126
- | `phi0` | scalar |
127
- | `NLeg` | scalar |
128
- | `NFourier` | scalar |
129
- | `b_pos` | `NQuad/2 x NFourier` or scalar |
130
- | `b_neg` | `NQuad/2 x NFourier` or scalar |
131
- | `only_flux` | boolean |
132
- | `f_arr` | `NLayers` or scalar |
133
- | `NT_cor` | boolean |
134
- | `BDRF_Fourier_modes` | `NBDRF` |
135
- | `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
136
- | `NLayers_to_use_sparse` | scalar |
118
+ | Variable | Type / Shape |
119
+ | --------------------------- | ---------------------------------- |
120
+ | `tau_arr` | `NLayers` |
121
+ | `omega_arr` | `NLayers` |
122
+ | `NQuad` | scalar |
123
+ | `Leg_coeffs_all` | `NLeg_all` |
124
+ | `mu0` | scalar |
125
+ | `I0` | scalar |
126
+ | `phi0` | scalar |
127
+ | `NLeg` | scalar |
128
+ | `NFourier` | scalar |
129
+ | `b_pos` | `NQuad/2 x NFourier` or scalar |
130
+ | `b_neg` | `NQuad/2 x NFourier` or scalar |
131
+ | `only_flux` | boolean |
132
+ | `f_arr` | `NLayers` or scalar |
133
+ | `NT_cor` | boolean |
134
+ | `BDRF_Fourier_modes` | `NBDRF` |
135
+ | `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
136
+ | `use_banded_solver_NLayers` | scalar |
137
137
 
138
138
  Notable internal variables of pydisort
139
139
  | Variable | Type / Shape |
@@ -190,7 +190,7 @@ def pydisort(
190
190
  NLayers = len(tau_arr)
191
191
  b_pos_is_scalar = False
192
192
  b_neg_is_scalar = False
193
- thickness_arr = np.concatenate([[tau_arr[0]], np.diff(tau_arr)])
193
+ thickness_arr = np.append(tau_arr[0], np.diff(tau_arr))
194
194
  NLeg_all = np.shape(Leg_coeffs_all)[1]
195
195
  N = NQuad // 2
196
196
  there_is_beam_source = I0 > 0
@@ -243,8 +243,8 @@ def pydisort(
243
243
  assert np.shape(b_neg) == (N, NFourier)
244
244
  # The fractional scattering must be between 0 and 1
245
245
  assert np.all(0 <= f_arr) and np.all(f_arr <= 1)
246
- # The minimum threshold is the minimum numbers of layers: 1
247
- assert(NLayers_to_use_sparse >= 1)
246
+ # The minimum threshold is 3 else the matrix will not be banded
247
+ assert(use_banded_solver_NLayers >= 3)
248
248
  # --------------------------------------------------------------------------------------------------------------------------
249
249
 
250
250
  # Some more setup
@@ -277,9 +277,7 @@ def pydisort(
277
277
  if np.any(f_arr > 0):
278
278
  scale_tau = 1 - omega_arr * f_arr
279
279
  scaled_thickness_arr = scale_tau * thickness_arr
280
- scaled_tau_arr_with_0 = np.array(
281
- list(map(lambda l: np.sum(scaled_thickness_arr[:l]), range(NLayers + 1)))
282
- )
280
+ scaled_tau_arr_with_0 = np.append(0, np.cumsum(scaled_thickness_arr))
283
281
  weighted_scaled_Leg_coeffs = ((Leg_coeffs - f_arr[:, None]) / (1 - f_arr[:, None])) * (
284
282
  2 * np.arange(NLeg) + 1
285
283
  )[None, :]
@@ -287,7 +285,7 @@ def pydisort(
287
285
  else:
288
286
  # This is a shortcut to the same results
289
287
  scale_tau = np.ones(NLayers)
290
- scaled_tau_arr_with_0 = np.concatenate([[0], tau_arr])
288
+ scaled_tau_arr_with_0 = np.append(0, tau_arr)
291
289
  weighted_scaled_Leg_coeffs = Leg_coeffs * (2 * np.arange(NLeg) + 1)[None, :]
292
290
  scaled_omega_arr = omega_arr
293
291
  # --------------------------------------------------------------------------------------------------------------------------
@@ -313,12 +311,12 @@ def pydisort(
313
311
  there_is_beam_source,
314
312
  b_pos, b_neg,
315
313
  b_pos_is_scalar, b_neg_is_scalar,
316
- s_poly_coeffs,
317
314
  Nscoeffs,
315
+ s_poly_coeffs,
318
316
  there_is_iso_source,
319
317
  scale_tau,
320
318
  only_flux,
321
- NLayers_to_use_sparse,
319
+ use_banded_solver_NLayers,
322
320
  _autograd_compatible,
323
321
  )
324
322
 
@@ -371,14 +369,14 @@ def pydisort(
371
369
 
372
370
  if is_antiderivative_wrt_tau:
373
371
  TMS_correction_pos = (
374
- np.exp(-scaled_tau / mu0)[None, :] / (-scale_tau[None, l] / mu0)
372
+ np.exp(-scaled_tau / mu0)[None, :] / (-scale_tau / mu0)[None, l]
375
373
  - np.exp(
376
374
  (scaled_tau - scaled_tau_arr_l)[None, :] / mu_arr_pos[:, None]
377
375
  - scaled_tau_arr_l[None, :] / mu0
378
376
  ) / (scale_tau[None, l] / mu_arr_pos[:, None])
379
377
  )
380
378
  TMS_correction_neg = (
381
- np.exp(-scaled_tau / mu0)[None, :] / (-scale_tau[None, l] / mu0)
379
+ np.exp(-scaled_tau / mu0)[None, :] / (-scale_tau / mu0)[None, l]
382
380
  - np.exp(
383
381
  (scaled_tau_arr_lm1 - scaled_tau)[None, :] / mu_arr_pos[:, None]
384
382
  - scaled_tau_arr_lm1[None, :] / mu0
@@ -501,7 +499,7 @@ def pydisort(
501
499
  - scaled_tau_arr_with_0_l_repeat_neg / mu0
502
500
  )
503
501
  )
504
- / (-scale_tau_repeat_pos / mu_arr_pos[:, None])
502
+ / (-scale_tau_repeat_neg / mu_arr_pos[:, None])
505
503
  )[:, :, None]
506
504
  )
507
505
  contribution_from_other_layers_neg = np.sum(
@@ -637,11 +635,11 @@ def pydisort(
637
635
  there_is_beam_source,
638
636
  b_pos, b_neg,
639
637
  b_pos_is_scalar, b_neg_is_scalar,
640
- s_poly_coeffs,
641
638
  Nscoeffs,
639
+ s_poly_coeffs,
642
640
  there_is_iso_source,
643
641
  scale_tau,
644
642
  only_flux,
645
- NLayers_to_use_sparse,
643
+ use_banded_solver_NLayers,
646
644
  _autograd_compatible,
647
645
  )
@@ -136,7 +136,7 @@ def Clenshaw_Curtis_quad(Nphi, c=0, d=(2 * pi)):
136
136
  Nphi_pos = Nphi // 2
137
137
  phi_arr_pos = np.cos(pi * np.arange(Nphi_pos) / Nphi)
138
138
  phi_arr = np.concatenate([-phi_arr_pos, [0], np.flip(phi_arr_pos)])
139
- diff = np.concatenate([[2], 2 / (1 - 4 * np.arange(1, Nphi_pos + 1) ** 2)])
139
+ diff = np.append(2, 2 / (1 - 4 * np.arange(1, Nphi_pos + 1) ** 2))
140
140
  weights_phi_pos = sc.fft.idct(diff, type=1)
141
141
  weights_phi_pos[0] /= 2
142
142
  full_weights_phi = np.hstack((weights_phi_pos, np.flip(weights_phi_pos[:-1])))
@@ -320,12 +320,47 @@ def interpolate(mu_arr_pos, u):
320
320
  return np.squeeze(results)[()]
321
321
 
322
322
  return u_interpol
323
-
323
+
324
324
 
325
+ def to_diag_ordered_form(A, sym_offset):
326
+ """
327
+ Convert a matrix A to the diagonal ordered form required by `scipy.linalg.solve_banded`.
328
+ We assume that the matrix has the same number of super- and sub-diagonals.
329
+
330
+ Parameters
331
+ ----------
332
+ A : 2darray
333
+ The square matrix to be converted.
334
+ sym_offset : int
335
+ The number of super- or sub-diagonals (assumed to be equal)
336
+
337
+ Returns
338
+ -------
339
+ 2darray
340
+ The diagonal ordered form matrix as required by solve_banded.
341
+ """
342
+ n = A.shape[0]
343
+ indices = np.arange(n)
344
+
345
+ return np.concatenate(
346
+ [
347
+ A[
348
+ indices - np.arange(sym_offset, -1, -1)[:, None],
349
+ indices[None, :],
350
+ ],
351
+ A[
352
+ indices - np.arange(n - 1, n - sym_offset - 1, -1)[:, None],
353
+ indices[None, :],
354
+ ],
355
+ ],
356
+ axis=0,
357
+ )
358
+
359
+
325
360
  def _mathscr_v(tau, # Input optical depths
326
361
  l, # Layer index of each input optical depth
327
- s_poly_coeffs, # Polynomial coefficients of isotropic source
328
362
  Nscoeffs, # Number of isotropic source polynomial coefficients
363
+ s_poly_coeffs, # Polynomial coefficients of isotropic source
329
364
  G, # Eigenvector matrices
330
365
  K, # Eigenvalues
331
366
  G_inv, # Inverse of eigenvector matrix
@@ -344,6 +379,7 @@ def _mathscr_v(tau, # Input optical depths
344
379
  | --------------------------- | ------------------------------------- |
345
380
  | `tau` | `Ntau` |
346
381
  | `l` | `Ntau` |
382
+ | `Nscoeffs` | scalar |
347
383
  | `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
348
384
  | `G` | `NLayers<= x NQuad x NQuad` |
349
385
  | `K` | `NLayers<= x NQuad` |
@@ -409,30 +445,20 @@ def _mathscr_v(tau, # Input optical depths
409
445
  ),
410
446
  )
411
447
 
412
- powers = np.flip(np.arange(Nscoeffs))[None, :]
448
+ powers = np.arange(Nscoeffs - 1, -1, -1)[None, :]
413
449
  if is_antiderivative_wrt_tau:
414
450
  tau_poly = tau[:, None] ** (powers + 1) / (powers + 1)
415
451
  else:
416
452
  tau_poly = tau[:, None] ** powers
417
-
453
+
418
454
  return np.einsum(
419
- "tik, tc, ctk, tkj, j -> it",
455
+ "tik, tc, ctk -> it",
420
456
  G[l, :, :],
421
457
  tau_poly,
422
- mathscr_v_coeffs[:, l, :],
423
- G_inv[l, :, :],
424
- 1 / mu_arr,
458
+ (mathscr_v_coeffs * (G_inv @ (1 / mu_arr))[None, :, :])[:, l, :],
425
459
  optimize=True,
426
460
  )
427
461
 
428
- def _nd_slice_to_indexes(nd_slice):
429
- """Code taken from https://stackoverflow.com/questions/64097025/how-to-convert-n-d-slice-to-indexes-in-numpy.
430
- This function is used to efficiently construct sparse COO matrices.
431
-
432
- """
433
- grid = np.mgrid[{tuple: nd_slice, slice: (nd_slice,)}[type(nd_slice)]]
434
- return tuple(grid[i].ravel() for i in range(grid.shape[0]))
435
-
436
462
 
437
463
  def _compare(results, mu_to_compare, reorder_mu, flux_up, flux_down, u):
438
464
  """Performs pointwise comparisons between results from Stamnes' DISORT,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PythonicDISORT
3
- Version: 0.9.0
3
+ Version: 0.9.1
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
File without changes
File without changes