PythonicDISORT 0.6.1__tar.gz → 0.7.0__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.
- {PythonicDISORT-0.6.1/src/PythonicDISORT.egg-info → pythonicdisort-0.7.0}/PKG-INFO +4 -4
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/README.md +3 -3
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/pyproject.toml +1 -1
- PythonicDISORT-0.6.1/src/PythonicDISORT/_assemble_solution_functions.py → pythonicdisort-0.7.0/src/PythonicDISORT/_assemble_intensity_and_fluxes.py +102 -46
- PythonicDISORT-0.6.1/src/PythonicDISORT/_solve_for_coefs.py → pythonicdisort-0.7.0/src/PythonicDISORT/_solve_for_coeffs.py +107 -65
- PythonicDISORT-0.6.1/src/PythonicDISORT/_diagonalize.py → pythonicdisort-0.7.0/src/PythonicDISORT/_solve_for_gen_and_part_sols.py +70 -33
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT/pydisort.py +121 -64
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT/subroutines.py +49 -9
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0/src/PythonicDISORT.egg-info}/PKG-INFO +4 -4
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT.egg-info/SOURCES.txt +3 -3
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/LICENSE.md +0 -0
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/setup.cfg +0 -0
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT/__init__.py +0 -0
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT.egg-info/dependency_links.txt +0 -0
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT.egg-info/requires.txt +0 -0
- {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PythonicDISORT
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
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
|
|
@@ -46,7 +46,7 @@ We highly recommend reading the non-optional parts of sections 1 and 2 before us
|
|
|
46
46
|
|
|
47
47
|
## PyTest and examples of how to use PythonicDISORT
|
|
48
48
|
|
|
49
|
-
Not only do we have verification tests in the Jupyter Notebook, we also
|
|
49
|
+
Not only do we have verification tests in the Jupyter Notebook, we have also recreated most of the test problems in Stamnes' `disotest.f90`;
|
|
50
50
|
`disotest.f90` is included in the `disort4.0.99_f2py` directory of our GitHub repository. In these tests, the solutions from PythonicDISORT are compared
|
|
51
51
|
against solutions from our F2PY-wrapped Stamnes' DISORT (version 4.0.99). With PyTest installed, execute the console command `pytest`
|
|
52
52
|
in the `pydisotest` directory to run these tests. The `pydisotest` directory also contains Jupyter Notebooks, one for each test,
|
|
@@ -56,13 +56,13 @@ to show how the tests were implemented. These notebooks double up as examples of
|
|
|
56
56
|
|
|
57
57
|
* From PyPI: `pip install PythonicDISORT`
|
|
58
58
|
* From Conda-forge: (TODO: we need to first publish on Conda-forge)
|
|
59
|
-
* By cloning repository: `pip install .` in the `Pythonic-DISORT` directory; `pip install -r all_optional_dependencies.txt` to install all optional dependencies
|
|
59
|
+
* By cloning repository: `pip install .` in the `Pythonic-DISORT` directory; `pip install -r all_optional_dependencies.txt` to install all optional dependencies (see *Requirements to run PythonicDISORT*)
|
|
60
60
|
|
|
61
61
|
## Requirements to run PythonicDISORT
|
|
62
62
|
* Python 3.8+
|
|
63
63
|
* `numpy >= 1.8.0`
|
|
64
64
|
* `scipy >= 1.8.0`
|
|
65
|
-
* (OPTIONAL) `pytest >= 6.2.5` (Required
|
|
65
|
+
* (OPTIONAL) `pytest >= 6.2.5` (Required to use the command `pytest`, see *PyTest and examples of how to use PythonicDISORT*)
|
|
66
66
|
|
|
67
67
|
## Additional requirements to run the Jupyter Notebook
|
|
68
68
|
* `autograd >= 1.5`
|
|
@@ -25,7 +25,7 @@ We highly recommend reading the non-optional parts of sections 1 and 2 before us
|
|
|
25
25
|
|
|
26
26
|
## PyTest and examples of how to use PythonicDISORT
|
|
27
27
|
|
|
28
|
-
Not only do we have verification tests in the Jupyter Notebook, we also
|
|
28
|
+
Not only do we have verification tests in the Jupyter Notebook, we have also recreated most of the test problems in Stamnes' `disotest.f90`;
|
|
29
29
|
`disotest.f90` is included in the `disort4.0.99_f2py` directory of our GitHub repository. In these tests, the solutions from PythonicDISORT are compared
|
|
30
30
|
against solutions from our F2PY-wrapped Stamnes' DISORT (version 4.0.99). With PyTest installed, execute the console command `pytest`
|
|
31
31
|
in the `pydisotest` directory to run these tests. The `pydisotest` directory also contains Jupyter Notebooks, one for each test,
|
|
@@ -35,13 +35,13 @@ to show how the tests were implemented. These notebooks double up as examples of
|
|
|
35
35
|
|
|
36
36
|
* From PyPI: `pip install PythonicDISORT`
|
|
37
37
|
* From Conda-forge: (TODO: we need to first publish on Conda-forge)
|
|
38
|
-
* By cloning repository: `pip install .` in the `Pythonic-DISORT` directory; `pip install -r all_optional_dependencies.txt` to install all optional dependencies
|
|
38
|
+
* By cloning repository: `pip install .` in the `Pythonic-DISORT` directory; `pip install -r all_optional_dependencies.txt` to install all optional dependencies (see *Requirements to run PythonicDISORT*)
|
|
39
39
|
|
|
40
40
|
## Requirements to run PythonicDISORT
|
|
41
41
|
* Python 3.8+
|
|
42
42
|
* `numpy >= 1.8.0`
|
|
43
43
|
* `scipy >= 1.8.0`
|
|
44
|
-
* (OPTIONAL) `pytest >= 6.2.5` (Required
|
|
44
|
+
* (OPTIONAL) `pytest >= 6.2.5` (Required to use the command `pytest`, see *PyTest and examples of how to use PythonicDISORT*)
|
|
45
45
|
|
|
46
46
|
## Additional requirements to run the Jupyter Notebook
|
|
47
47
|
* `autograd >= 1.5`
|
|
@@ -1,52 +1,108 @@
|
|
|
1
1
|
from PythonicDISORT.subroutines import _mathscr_v
|
|
2
|
-
from PythonicDISORT.
|
|
3
|
-
from PythonicDISORT.
|
|
2
|
+
from PythonicDISORT._solve_for_gen_and_part_sols import _solve_for_gen_and_part_sols
|
|
3
|
+
from PythonicDISORT._solve_for_coeffs import _solve_for_coeffs
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from math import pi
|
|
7
7
|
|
|
8
|
-
def
|
|
9
|
-
scaled_omega_arr,
|
|
10
|
-
tau_arr,
|
|
11
|
-
scaled_tau_arr_with_0,
|
|
12
|
-
mu_arr_pos, mu_arr,
|
|
13
|
-
M_inv, W,
|
|
14
|
-
N, NQuad, NLeg,
|
|
15
|
-
NLayers, NBDRF,
|
|
16
|
-
|
|
17
|
-
weighted_scaled_Leg_coeffs,
|
|
18
|
-
BDRF_Fourier_modes,
|
|
19
|
-
mu0, I0, I0_orig, phi0,
|
|
20
|
-
there_is_beam_source,
|
|
21
|
-
b_pos, b_neg,
|
|
22
|
-
b_pos_is_scalar, b_neg_is_scalar,
|
|
23
|
-
s_poly_coeffs,
|
|
24
|
-
Nscoeffs,
|
|
25
|
-
there_is_iso_source,
|
|
26
|
-
scale_tau,
|
|
27
|
-
only_flux,
|
|
28
|
-
|
|
29
|
-
|
|
8
|
+
def _assemble_intensity_and_fluxes(
|
|
9
|
+
scaled_omega_arr, # Delta-scaled single-scattering albedos
|
|
10
|
+
tau_arr, # Lower boundary of layers
|
|
11
|
+
scaled_tau_arr_with_0, # Delta-scaled lower boundary of layers with 0 inserted in front
|
|
12
|
+
mu_arr_pos, mu_arr, # Quadrature nodes for 1) upper 2) both hemispheres
|
|
13
|
+
M_inv, W, # 1) 1 / mu; 2) quadrature weights for each hemisphere
|
|
14
|
+
N, NQuad, NLeg, # Number of 1) upper 2) both hemispheres quadrature nodes; 3) phase function Legendre coefficients
|
|
15
|
+
NFourier, NLayers, NBDRF, # Number of 1) intensity Fourier modes; 2) layers; 3) BDRF Fourier modes
|
|
16
|
+
is_atmos_multilayered, # Is the atmosphere multilayered?
|
|
17
|
+
weighted_scaled_Leg_coeffs, # Weighted and delta-scaled Legendre coefficients
|
|
18
|
+
BDRF_Fourier_modes, # BDRF Fourier modes
|
|
19
|
+
mu0, I0, I0_orig, phi0, # Properties of direct beam. If I0 was rescaled to 1, I0_orig is the original I0
|
|
20
|
+
there_is_beam_source, # Is there a beam source?
|
|
21
|
+
b_pos, b_neg, # Dirichlet BCs
|
|
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
|
+
Nscoeffs, # Number of isotropic source polynomial coefficients
|
|
25
|
+
there_is_iso_source, # Is there an isotropic source?
|
|
26
|
+
scale_tau, # Delta-scale factor for tau
|
|
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?
|
|
30
30
|
):
|
|
31
|
-
"""
|
|
31
|
+
"""In this function the solution functions: intensity `u`, upward flux `flux_up`, downward flux `flux_down`
|
|
32
|
+
are assembled from the eigenpairs and the coefficients which have been solved from the boundary conditions.
|
|
33
|
+
These solution functions are then returned.
|
|
34
|
+
This function is wrapped by the `pydisort` function.
|
|
32
35
|
It should be called through `pydisort` and never directly.
|
|
33
|
-
It has many seemingly redundant arguments
|
|
34
|
-
|
|
36
|
+
It has many seemingly redundant arguments, which are described below,
|
|
37
|
+
to maximize precomputation in `pydisort`
|
|
38
|
+
These arguments are passed on to the `_solve_for_gen_and_part_sols` and `_solve_for_coeffs` functions
|
|
35
39
|
which this function wraps. See the Jupyter Notebook, especially section 3, for
|
|
36
40
|
documentation, explanation and derivation.
|
|
37
41
|
|
|
42
|
+
Arguments of _assemble_intensity_and_fluxes
|
|
43
|
+
| Variable | Type / Shape |
|
|
44
|
+
| ------------------------------ | ---------------------------------- |
|
|
45
|
+
| `scaled_omega_arr` | `NLayers` |
|
|
46
|
+
| `tau_arr` | `NLayers` |
|
|
47
|
+
| `scaled_tau_arr_with_0` | `NLayers + 1` |
|
|
48
|
+
| `mu_arr_pos` | `NQuad/2` |
|
|
49
|
+
| `mu_arr` | `NQuad` |
|
|
50
|
+
| `M_inv` | `NQuad/2` |
|
|
51
|
+
| `W` | `NQuad/2` |
|
|
52
|
+
| `N` | scalar |
|
|
53
|
+
| `NQuad` | scalar |
|
|
54
|
+
| `NLeg` | scalar |
|
|
55
|
+
| `NFourier` | scalar |
|
|
56
|
+
| `NLayers` | scalar |
|
|
57
|
+
| `NBDRF` | scalar |
|
|
58
|
+
| `is_atmos_multilayered` | boolean |
|
|
59
|
+
| `weighted_scaled_Leg_coeffs` | `NLayers x NLeg` |
|
|
60
|
+
| `BDRF_Fourier_modes` | `NBDRF` |
|
|
61
|
+
| `mu0` | scalar |
|
|
62
|
+
| `I0` | scalar |
|
|
63
|
+
| `I0_orig` | scalar |
|
|
64
|
+
| `phi0` | scalar |
|
|
65
|
+
| `there_is_beam_source` | scalar |
|
|
66
|
+
| `b_pos` | `NQuad/2 x NFourier` or scalar |
|
|
67
|
+
| `b_neg` | `NQuad/2 x NFourier` or scalar |
|
|
68
|
+
| `b_pos_is_scalar` | boolean |
|
|
69
|
+
| `b_neg_is_scalar` | boolean |
|
|
70
|
+
| `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
|
|
71
|
+
| `Nscoeffs` | scalar |
|
|
72
|
+
| `there_is_iso_source` | boolean |
|
|
73
|
+
| `scale_tau` | `NLayers` |
|
|
74
|
+
| `only_flux` | boolean |
|
|
75
|
+
| `NLayers_to_use_sparse` | scalar |
|
|
76
|
+
| `_autograd_compatible` | boolean |
|
|
77
|
+
|
|
78
|
+
Notable internal variables of _assemble_intensity_and_fluxes
|
|
79
|
+
| Variable | Type / Shape |
|
|
80
|
+
| ----------------- | -------------------------------------- |
|
|
81
|
+
| `G_collect` | `NFourier x NLayers x NQuad x NQuad` |
|
|
82
|
+
| `G_collect_0` | `NLayers x NQuad x NQuad` |
|
|
83
|
+
| `G_inv_collect_0` | `NLayers x NQuad x NQuad` or `None` |
|
|
84
|
+
| `K_collect` | `NFourier x NLayers x NQuad` |
|
|
85
|
+
| `K_collect_0` | `NLayers x NQuad` |
|
|
86
|
+
| `B_collect` | `NFourier x NLayers x NQuad` or `None` |
|
|
87
|
+
| `B_collect_0` | `NLayers x NQuad` or `None` |
|
|
88
|
+
| `B_pos` | `NLayers x NQuad/2` or `None` |
|
|
89
|
+
| `B_neg` | `NLayers x NQuad/2` or `None` |
|
|
90
|
+
| `GC_collect` | `NFourier x NLayers x NQuad x NQuad` |
|
|
91
|
+
| `GC_collect_0` | `NLayers x NQuad x NQuad` |
|
|
92
|
+
| `GC_pos` | `NLayers x NQuad/2 x NQuad` |
|
|
93
|
+
| `GC_neg` | `NLayers x NQuad/2 x NQuad` |
|
|
38
94
|
"""
|
|
39
|
-
if
|
|
95
|
+
if _autograd_compatible:
|
|
40
96
|
import autograd.numpy as np
|
|
41
97
|
else:
|
|
42
98
|
import numpy as np
|
|
43
99
|
|
|
44
|
-
|
|
100
|
+
################################## Assemble uncorrected intensity and flux functions #######################################
|
|
45
101
|
|
|
46
102
|
# Compute all the necessary quantities
|
|
47
103
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
48
|
-
outputs =
|
|
49
|
-
|
|
104
|
+
outputs = _solve_for_gen_and_part_sols(
|
|
105
|
+
NFourier,
|
|
50
106
|
scaled_omega_arr,
|
|
51
107
|
mu_arr_pos, mu_arr,
|
|
52
108
|
M_inv, W,
|
|
@@ -73,18 +129,18 @@ def _assemble_solution_functions(
|
|
|
73
129
|
G_inv_collect_0 = None
|
|
74
130
|
B_collect = None
|
|
75
131
|
|
|
76
|
-
GC_collect =
|
|
77
|
-
|
|
132
|
+
GC_collect = _solve_for_coeffs(
|
|
133
|
+
NFourier,
|
|
78
134
|
G_collect,
|
|
79
135
|
K_collect,
|
|
80
136
|
B_collect,
|
|
81
137
|
G_inv_collect_0,
|
|
82
138
|
tau_arr,
|
|
83
139
|
scaled_tau_arr_with_0,
|
|
84
|
-
mu_arr_pos, mu_arr_pos * W,
|
|
140
|
+
mu_arr, mu_arr_pos, mu_arr_pos * W,
|
|
85
141
|
N, NQuad,
|
|
86
142
|
NLayers, NBDRF,
|
|
87
|
-
|
|
143
|
+
is_atmos_multilayered,
|
|
88
144
|
BDRF_Fourier_modes,
|
|
89
145
|
mu0, I0,
|
|
90
146
|
there_is_beam_source,
|
|
@@ -93,7 +149,7 @@ def _assemble_solution_functions(
|
|
|
93
149
|
s_poly_coeffs,
|
|
94
150
|
Nscoeffs,
|
|
95
151
|
there_is_iso_source,
|
|
96
|
-
|
|
152
|
+
NLayers_to_use_sparse,
|
|
97
153
|
)
|
|
98
154
|
|
|
99
155
|
G_collect_0 = G_collect[0, :, :, :]
|
|
@@ -103,7 +159,7 @@ def _assemble_solution_functions(
|
|
|
103
159
|
|
|
104
160
|
if not only_flux:
|
|
105
161
|
|
|
106
|
-
# Construct the intensity function
|
|
162
|
+
# Construct the intensity function (refer to Section 3.7 of the Comprehensive Documentation)
|
|
107
163
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
108
164
|
def u(tau, phi, return_Fourier_error=False):
|
|
109
165
|
tau = np.atleast_1d(tau)
|
|
@@ -150,18 +206,18 @@ def _assemble_solution_functions(
|
|
|
150
206
|
K_collect_0,
|
|
151
207
|
G_inv_collect_0,
|
|
152
208
|
mu_arr,
|
|
153
|
-
|
|
209
|
+
_autograd_compatible,
|
|
154
210
|
)
|
|
155
211
|
|
|
156
|
-
if
|
|
157
|
-
um = um + np.concatenate((_mathscr_v_contribution.T, np.zeros((
|
|
212
|
+
if _autograd_compatible:
|
|
213
|
+
um = um + np.concatenate((_mathscr_v_contribution.T, np.zeros((NFourier - 1, len(tau), NQuad))))
|
|
158
214
|
else:
|
|
159
215
|
um[0, :, :] += _mathscr_v_contribution.T
|
|
160
216
|
|
|
161
217
|
intensities = np.einsum(
|
|
162
218
|
"mti, mp -> itp",
|
|
163
219
|
um,
|
|
164
|
-
np.cos(np.arange(
|
|
220
|
+
np.cos(np.arange(NFourier)[:, None] * (phi0 - phi)[None, :]),
|
|
165
221
|
optimize=True,
|
|
166
222
|
)
|
|
167
223
|
|
|
@@ -183,7 +239,7 @@ def _assemble_solution_functions(
|
|
|
183
239
|
)
|
|
184
240
|
intensities_abs = np.abs(intensities)
|
|
185
241
|
Fourier_error = np.max(
|
|
186
|
-
np.abs(ulast[:, :, None] * np.cos((
|
|
242
|
+
np.abs(ulast[:, :, None] * np.cos((NFourier - 1) * (phi0 - phi))[None, None, :]),
|
|
187
243
|
intensities_abs,
|
|
188
244
|
out=np.zeros_like(intensities_abs),
|
|
189
245
|
where=intensities_abs > 1e-8,
|
|
@@ -245,7 +301,7 @@ def _assemble_solution_functions(
|
|
|
245
301
|
K_collect_0,
|
|
246
302
|
G_inv_collect_0,
|
|
247
303
|
mu_arr,
|
|
248
|
-
|
|
304
|
+
_autograd_compatible,
|
|
249
305
|
)
|
|
250
306
|
u0 += _mathscr_v_contribution
|
|
251
307
|
|
|
@@ -255,7 +311,7 @@ def _assemble_solution_functions(
|
|
|
255
311
|
return I0_orig * np.squeeze(u0)
|
|
256
312
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
257
313
|
|
|
258
|
-
# Construct the flux functions
|
|
314
|
+
# Construct the flux functions (refer to Section 3.8 of the Comprehensive Documentation)
|
|
259
315
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
260
316
|
GC_pos = GC_collect_0[:, :N, :]
|
|
261
317
|
GC_neg = GC_collect_0[:, N:, :]
|
|
@@ -291,7 +347,7 @@ def _assemble_solution_functions(
|
|
|
291
347
|
K_collect_0,
|
|
292
348
|
G_inv_collect_0,
|
|
293
349
|
mu_arr,
|
|
294
|
-
|
|
350
|
+
_autograd_compatible,
|
|
295
351
|
)[:N, :]
|
|
296
352
|
else:
|
|
297
353
|
_mathscr_v_contribution = 0
|
|
@@ -345,7 +401,7 @@ def _assemble_solution_functions(
|
|
|
345
401
|
K_collect_0,
|
|
346
402
|
G_inv_collect_0,
|
|
347
403
|
mu_arr,
|
|
348
|
-
|
|
404
|
+
_autograd_compatible,
|
|
349
405
|
)[N:, :]
|
|
350
406
|
else:
|
|
351
407
|
_mathscr_v_contribution = 0
|
|
@@ -6,41 +6,82 @@ import scipy as sc
|
|
|
6
6
|
from math import pi
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
G_collect,
|
|
12
|
-
K_collect,
|
|
13
|
-
B_collect,
|
|
14
|
-
G_inv_collect_0,
|
|
15
|
-
tau_arr,
|
|
16
|
-
scaled_tau_arr_with_0,
|
|
17
|
-
mu_arr_pos, mu_arr_pos_times_W,
|
|
18
|
-
N, NQuad,
|
|
19
|
-
NLayers, NBDRF,
|
|
20
|
-
|
|
21
|
-
BDRF_Fourier_modes,
|
|
22
|
-
mu0, I0,
|
|
23
|
-
there_is_beam_source,
|
|
24
|
-
b_pos, b_neg,
|
|
25
|
-
b_pos_is_scalar, b_neg_is_scalar,
|
|
26
|
-
s_poly_coeffs,
|
|
27
|
-
Nscoeffs,
|
|
28
|
-
there_is_iso_source,
|
|
29
|
-
|
|
9
|
+
def _solve_for_coeffs(
|
|
10
|
+
NFourier, # Number of intensity Fourier modes
|
|
11
|
+
G_collect, # Eigenvector matrices
|
|
12
|
+
K_collect, # Eigenvalues
|
|
13
|
+
B_collect, # Coefficients vectors for particular solutions
|
|
14
|
+
G_inv_collect_0, # Inverse of eigenvector matrix for the 0th Fourier mode
|
|
15
|
+
tau_arr, # Lower boundary of layers
|
|
16
|
+
scaled_tau_arr_with_0, # Delta-scaled lower boundary of layers with 0 inserted in front
|
|
17
|
+
mu_arr, mu_arr_pos, mu_arr_pos_times_W, # Quadrature nodes for 1) both 2) upper hemispheres; 3) upper hemisphere quadrature nodes times weights
|
|
18
|
+
N, NQuad, # Number of 1) upper 2) both hemispheres quadrature nodes
|
|
19
|
+
NLayers, NBDRF, # Number of 1) intensity Fourier modes; 2) layers
|
|
20
|
+
is_atmos_multilayered, # Is the atmosphere multilayered?
|
|
21
|
+
BDRF_Fourier_modes, # BDRF Fourier modes
|
|
22
|
+
mu0, I0, # Properties of direct beam
|
|
23
|
+
there_is_beam_source, # Is there a beam source?
|
|
24
|
+
b_pos, b_neg, # Dirichlet BCs
|
|
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
|
+
Nscoeffs, # Number of isotropic source polynomial coefficients
|
|
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
|
|
30
30
|
):
|
|
31
|
-
"""
|
|
31
|
+
"""
|
|
32
|
+
In this function the unknown coefficients of the general solution to the system
|
|
33
|
+
of ODEs for each Fourier mode is solved using the boundary conditions
|
|
34
|
+
and the product of the coefficients and the eigenvectors is returned.
|
|
35
|
+
This function is wrapped by the `_assemble_intensity_and_fluxes` function.
|
|
32
36
|
It has many seemingly redundant arguments to maximize precomputation in the `pydisort` function.
|
|
33
37
|
See the Jupyter Notebook, especially section 3, for documentation, explanation and derivation.
|
|
34
38
|
The labels in this file reference labels in the Jupyter Notebook, especially sections 3 and 4.
|
|
35
39
|
|
|
40
|
+
Arguments of _solve_for_coeffs
|
|
41
|
+
| Variable | Type / Shape |
|
|
42
|
+
| ------------------------------ | -------------------------------------- |
|
|
43
|
+
| `NFourier` | scalar |
|
|
44
|
+
| `G_collect` | `NFourier x NLayers x NQuad x NQuad` |
|
|
45
|
+
| `K_collect` | `NFourier x NLayers x NQuad` |
|
|
46
|
+
| `B_collect` | `NFourier x NLayers x NQuad` or `None` |
|
|
47
|
+
| `G_inv_collect_0` | `NLayers x NQuad x NQuad` or `None` |
|
|
48
|
+
| `tau_arr` | `NLayers` |
|
|
49
|
+
| `scaled_tau_arr_with_0` | `NLayers + 1` |
|
|
50
|
+
| `mu_arr` | `NQuad` |
|
|
51
|
+
| `mu_arr_pos` | `NQuad/2` |
|
|
52
|
+
| `mu_arr_pos_times_W` | `NQuad/2` |
|
|
53
|
+
| `N` | scalar |
|
|
54
|
+
| `NQuad` | scalar |
|
|
55
|
+
| `NLayers` | scalar |
|
|
56
|
+
| `NBDRF` | scalar |
|
|
57
|
+
| `is_atmos_multilayered` | boolean |
|
|
58
|
+
| `BDRF_Fourier_modes` | `NBDRF` |
|
|
59
|
+
| `mu0` | scalar |
|
|
60
|
+
| `I0` | scalar |
|
|
61
|
+
| `there_is_beam_source` | boolean |
|
|
62
|
+
| `b_pos` | `NQuad/2 x NFourier` or scalar |
|
|
63
|
+
| `b_neg` | `NQuad/2 x NFourier` or scalar |
|
|
64
|
+
| `b_pos_is_scalar` | boolean |
|
|
65
|
+
| `b_neg_is_scalar` | boolean |
|
|
66
|
+
| `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
|
|
67
|
+
| `Nscoeffs` | scalar |
|
|
68
|
+
| `there_is_iso_source` | boolean |
|
|
69
|
+
| `NLayers_to_use_sparse` | scalar |
|
|
70
|
+
|
|
71
|
+
Notable internal variables of _solve_for_coeffs
|
|
72
|
+
| Variable | Shape |
|
|
73
|
+
| ------------ | ------------------------------------ |
|
|
74
|
+
| `GC_collect` | `NFourier x NLayers x NQuad x NQuad` |
|
|
75
|
+
|
|
36
76
|
"""
|
|
37
77
|
################################## Solve for coefficients of homogeneous solution ##########################################
|
|
78
|
+
############# Refer to Section 3.6.2 (single-layer) and 4 (multi-layer) of the Comprehensive Documentation #################
|
|
38
79
|
|
|
39
|
-
GC_collect = np.empty((
|
|
40
|
-
use_sparse_framework = NLayers >=
|
|
80
|
+
GC_collect = np.empty((NFourier, NLayers, NQuad, NQuad))
|
|
81
|
+
use_sparse_framework = NLayers >= NLayers_to_use_sparse
|
|
41
82
|
|
|
42
83
|
# The following loops can easily be parallelized, but the speed-up is unlikely to be worth the overhead
|
|
43
|
-
for m in range(
|
|
84
|
+
for m in range(NFourier):
|
|
44
85
|
m_equals_0 = (m == 0)
|
|
45
86
|
BDRF_bool = m < NBDRF
|
|
46
87
|
|
|
@@ -50,6 +91,7 @@ def _solve_for_coefs(
|
|
|
50
91
|
B_collect_m = B_collect[m, :, :]
|
|
51
92
|
|
|
52
93
|
# Generate mathscr_D and mathscr_X (BDRF terms)
|
|
94
|
+
# Just for this part, refer to Section 3.4.2 of the Comprehensive Documentation
|
|
53
95
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
54
96
|
if BDRF_bool:
|
|
55
97
|
mathscr_D_neg = (1 + m_equals_0 * 1) * BDRF_Fourier_modes[m](mu_arr_pos, -mu_arr_pos)
|
|
@@ -92,7 +134,7 @@ def _solve_for_coefs(
|
|
|
92
134
|
).ravel()
|
|
93
135
|
|
|
94
136
|
_mathscr_v_contribution_middle = np.array([])
|
|
95
|
-
if
|
|
137
|
+
if is_atmos_multilayered:
|
|
96
138
|
indices = np.arange(NLayers - 1)
|
|
97
139
|
_mathscr_v_contribution_middle = (
|
|
98
140
|
_mathscr_v(
|
|
@@ -147,7 +189,7 @@ def _solve_for_coefs(
|
|
|
147
189
|
BDRF_RHS_contribution = 0
|
|
148
190
|
|
|
149
191
|
RHS_middle = np.array([])
|
|
150
|
-
if
|
|
192
|
+
if is_atmos_multilayered:
|
|
151
193
|
RHS_middle = np.array(
|
|
152
194
|
[
|
|
153
195
|
(B_collect_m[l + 1, :] - B_collect_m[l, :])
|
|
@@ -173,7 +215,7 @@ def _solve_for_coefs(
|
|
|
173
215
|
RHS = np.concatenate([b_neg_m, RHS_middle, b_pos_m]) + _mathscr_v_contribution
|
|
174
216
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
175
217
|
|
|
176
|
-
# Assemble LHS
|
|
218
|
+
# Assemble LHS (much of this code is replicated in Section 4 of the Comprehensive Documentation)
|
|
177
219
|
dim = NLayers * NQuad
|
|
178
220
|
|
|
179
221
|
G_0_nn = G_collect_m[0, N:, :N]
|
|
@@ -193,8 +235,8 @@ def _solve_for_coefs(
|
|
|
193
235
|
BDRF_LHS_contribution_pos = 0
|
|
194
236
|
|
|
195
237
|
if use_sparse_framework:
|
|
196
|
-
|
|
197
|
-
density = 4 *
|
|
238
|
+
N_sq = N**2
|
|
239
|
+
density = 4 * N_sq * (2 * NLayers - 1)
|
|
198
240
|
row_indices = np.empty(density)
|
|
199
241
|
col_indices = np.empty(density)
|
|
200
242
|
block_rows = np.empty(density)
|
|
@@ -203,35 +245,35 @@ def _solve_for_coefs(
|
|
|
203
245
|
block_row_ind = np.arange(block_row_len).reshape(NQuad, 2 * NQuad)
|
|
204
246
|
block_row_sort = np.empty(block_row_len, dtype='int')
|
|
205
247
|
|
|
206
|
-
block_row_sort[:
|
|
207
|
-
block_row_sort[
|
|
208
|
-
block_row_sort[2 *
|
|
209
|
-
block_row_sort[4 *
|
|
210
|
-
block_row_sort[6 *
|
|
211
|
-
block_row_sort[7 *
|
|
248
|
+
block_row_sort[:N_sq] = block_row_ind[:N, :N].ravel()
|
|
249
|
+
block_row_sort[N_sq : 2 * N_sq] = block_row_ind[N : 2 * N, :N].ravel()
|
|
250
|
+
block_row_sort[2 * N_sq : 4 * N_sq] = block_row_ind[:2 * N, N : 2 * N].ravel()
|
|
251
|
+
block_row_sort[4 * N_sq : 6 * N_sq] = block_row_ind[:2 * N, 2 * N : 3 * N].ravel()
|
|
252
|
+
block_row_sort[6 * N_sq : 7 * N_sq] = block_row_ind[:N, 3 * N : 4 * N].ravel()
|
|
253
|
+
block_row_sort[7 * N_sq : 8 * N_sq] = block_row_ind[N : 2 * N, 3 * N : 4 * N].ravel()
|
|
212
254
|
|
|
213
255
|
# BCs for the entire atmosphere
|
|
214
|
-
row_indices[:
|
|
215
|
-
block_rows[:
|
|
256
|
+
row_indices[:N_sq], col_indices[:N_sq] = _nd_slice_to_indexes(np.s_[:N, :N])
|
|
257
|
+
block_rows[:N_sq] = G_0_nn.ravel()
|
|
216
258
|
(
|
|
217
|
-
row_indices[
|
|
218
|
-
col_indices[
|
|
259
|
+
row_indices[N_sq : 2 * N_sq],
|
|
260
|
+
col_indices[N_sq : 2 * N_sq],
|
|
219
261
|
) = _nd_slice_to_indexes(np.s_[:N, N:NQuad])
|
|
220
|
-
block_rows[
|
|
262
|
+
block_rows[N_sq : 2 * N_sq] = (
|
|
221
263
|
G_0_np * np.exp(K_collect_m[0, :N] * scaled_tau_arr_with_0[1])[None, :]
|
|
222
264
|
).ravel()
|
|
223
265
|
(
|
|
224
|
-
row_indices[2 *
|
|
225
|
-
col_indices[2 *
|
|
266
|
+
row_indices[2 * N_sq : 3 * N_sq],
|
|
267
|
+
col_indices[2 * N_sq : 3 * N_sq],
|
|
226
268
|
) = _nd_slice_to_indexes(np.s_[dim - N : dim, dim - NQuad : dim - N])
|
|
227
|
-
block_rows[2 *
|
|
269
|
+
block_rows[2 * N_sq : 3 * N_sq] = (
|
|
228
270
|
(G_L_pn - BDRF_LHS_contribution_neg) * E_Lm1L[None, :]
|
|
229
271
|
).ravel()
|
|
230
272
|
(
|
|
231
|
-
row_indices[3 *
|
|
232
|
-
col_indices[3 *
|
|
273
|
+
row_indices[3 * N_sq : 4 * N_sq],
|
|
274
|
+
col_indices[3 * N_sq : 4 * N_sq],
|
|
233
275
|
) = _nd_slice_to_indexes(np.s_[dim - N : dim, dim - N : dim])
|
|
234
|
-
block_rows[3 *
|
|
276
|
+
block_rows[3 * N_sq : 4 * N_sq] = (
|
|
235
277
|
G_L_pp - BDRF_LHS_contribution_pos
|
|
236
278
|
).ravel()
|
|
237
279
|
|
|
@@ -256,19 +298,19 @@ def _solve_for_coefs(
|
|
|
256
298
|
np.s_[N + l * NQuad : N + (l + 1) * NQuad, l * NQuad : (l + 2) * NQuad]
|
|
257
299
|
)
|
|
258
300
|
row_indices[
|
|
259
|
-
4 *
|
|
301
|
+
4 * N_sq + l * block_row_len : 4 * N_sq + (l + 1) * block_row_len
|
|
260
302
|
] = row_indices_l[block_row_sort]
|
|
261
303
|
col_indices[
|
|
262
|
-
4 *
|
|
304
|
+
4 * N_sq + l * block_row_len : 4 * N_sq + (l + 1) * block_row_len
|
|
263
305
|
] = col_indices_l[block_row_sort]
|
|
264
306
|
|
|
265
|
-
|
|
266
|
-
block_rows[
|
|
267
|
-
block_rows[
|
|
268
|
-
block_rows[
|
|
269
|
-
block_rows[
|
|
270
|
-
block_rows[
|
|
271
|
-
block_rows[
|
|
307
|
+
start_ind = 4 * N_sq + l * block_row_len
|
|
308
|
+
block_rows[start_ind : N_sq + start_ind] = (G_l_pn * E_lm1l[None, :]).ravel()
|
|
309
|
+
block_rows[start_ind + N_sq : 2 * N_sq + start_ind] = (G_l_nn * E_lm1l[None, :]).ravel()
|
|
310
|
+
block_rows[start_ind + 2 * N_sq : 4 * N_sq + start_ind] = G_l_ap.ravel()
|
|
311
|
+
block_rows[start_ind + 4 * N_sq : 6 * N_sq + start_ind] = -G_lp1_an.ravel()
|
|
312
|
+
block_rows[start_ind + 6 * N_sq : 7 * N_sq + start_ind] = -(G_lp1_pp * E_llp1[None, :]).ravel()
|
|
313
|
+
block_rows[start_ind + 7 * N_sq : 8 * N_sq + start_ind] = -(G_lp1_np * E_llp1[None, :]).ravel()
|
|
272
314
|
|
|
273
315
|
LHS = sc.sparse.coo_matrix(
|
|
274
316
|
(
|
|
@@ -284,7 +326,7 @@ def _solve_for_coefs(
|
|
|
284
326
|
# Solve the system
|
|
285
327
|
C_m = sc.sparse.linalg.spsolve(LHS.tocsr(), RHS).reshape(NLayers, NQuad)
|
|
286
328
|
|
|
287
|
-
else:
|
|
329
|
+
else:
|
|
288
330
|
LHS = np.zeros((dim, dim))
|
|
289
331
|
|
|
290
332
|
# BCs for the entire atmosphere
|
|
@@ -313,14 +355,14 @@ def _solve_for_coefs(
|
|
|
313
355
|
E_lm1l = np.exp(K_l_pos * (scaled_tau_arr_lm1 - scaled_tau_arr_l))
|
|
314
356
|
E_llp1 = np.exp(K_lp1_pos * (scaled_tau_arr_l - scaled_tau_arr_lp1))
|
|
315
357
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
LHS[
|
|
319
|
-
LHS[N +
|
|
320
|
-
LHS[
|
|
321
|
-
LHS[
|
|
322
|
-
LHS[
|
|
323
|
-
LHS[N +
|
|
358
|
+
start_row = N + l * NQuad
|
|
359
|
+
start_col = l * NQuad
|
|
360
|
+
LHS[start_row : N + start_row, start_col : N + start_col] = G_l_pn * E_lm1l[None, :]
|
|
361
|
+
LHS[N + start_row : 2 * N + start_row, start_col : N + start_col] = G_l_nn * E_lm1l[None, :]
|
|
362
|
+
LHS[start_row : 2 * N + start_row, N + start_col : 2 * N + start_col] = G_l_ap
|
|
363
|
+
LHS[start_row : 2 * N + start_row, 2 * N + start_col : 3 * N + start_col] = -G_lp1_an
|
|
364
|
+
LHS[start_row : N + start_row, 3 * N + start_col : 4 * N + start_col] = -G_lp1_pp * E_llp1[None, :]
|
|
365
|
+
LHS[N + start_row : 2 * N + start_row, 3 * N + start_col : 4 * N + start_col] = -G_lp1_np * E_llp1[None, :]
|
|
324
366
|
|
|
325
367
|
# Solve the system
|
|
326
368
|
C_m = np.linalg.solve(LHS, RHS).reshape(NLayers, NQuad)
|