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.
Files changed (16) hide show
  1. {PythonicDISORT-0.6.1/src/PythonicDISORT.egg-info → pythonicdisort-0.7.0}/PKG-INFO +4 -4
  2. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/README.md +3 -3
  3. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/pyproject.toml +1 -1
  4. PythonicDISORT-0.6.1/src/PythonicDISORT/_assemble_solution_functions.py → pythonicdisort-0.7.0/src/PythonicDISORT/_assemble_intensity_and_fluxes.py +102 -46
  5. PythonicDISORT-0.6.1/src/PythonicDISORT/_solve_for_coefs.py → pythonicdisort-0.7.0/src/PythonicDISORT/_solve_for_coeffs.py +107 -65
  6. PythonicDISORT-0.6.1/src/PythonicDISORT/_diagonalize.py → pythonicdisort-0.7.0/src/PythonicDISORT/_solve_for_gen_and_part_sols.py +70 -33
  7. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT/pydisort.py +121 -64
  8. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT/subroutines.py +49 -9
  9. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0/src/PythonicDISORT.egg-info}/PKG-INFO +4 -4
  10. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT.egg-info/SOURCES.txt +3 -3
  11. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/LICENSE.md +0 -0
  12. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/setup.cfg +0 -0
  13. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT/__init__.py +0 -0
  14. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT.egg-info/dependency_links.txt +0 -0
  15. {PythonicDISORT-0.6.1 → pythonicdisort-0.7.0}/src/PythonicDISORT.egg-info/requires.txt +0 -0
  16. {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.6.1
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 used PyTest to recreate most of the test problems in Stamnes' `disotest.f90`;
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 for non-Notebook tests)
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 used PyTest to recreate most of the test problems in Stamnes' `disotest.f90`;
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 for non-Notebook tests)
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`
@@ -8,7 +8,7 @@ dependencies = [
8
8
  "scipy>=1.8.0",
9
9
  ]
10
10
  name = "PythonicDISORT"
11
- version = "0.6.1"
11
+ version = "0.7.0"
12
12
  authors = [
13
13
  { name="Dion HO Jia Xu", email="dh3065@columbia.edu" },
14
14
  ]
@@ -1,52 +1,108 @@
1
1
  from PythonicDISORT.subroutines import _mathscr_v
2
- from PythonicDISORT._diagonalize import _diagonalize
3
- from PythonicDISORT._solve_for_coefs import _solve_for_coefs
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 _assemble_solution_functions(
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, NLoops,
15
- NLayers, NBDRF,
16
- atmos_is_multilayered,
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
- use_sparse_NLayers,
29
- _is_compatible_with_autograd,
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
- """This function is wrapped by the `pydisort` function.
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 to maximize precomputation in `pydisort`.
34
- These arguments are passed on to the `_diagonalize` and `_solve_for_coefs` functions
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 _is_compatible_with_autograd:
95
+ if _autograd_compatible:
40
96
  import autograd.numpy as np
41
97
  else:
42
98
  import numpy as np
43
99
 
44
- ###################################### Assemble uncorrected solution functions #############################################
100
+ ################################## Assemble uncorrected intensity and flux functions #######################################
45
101
 
46
102
  # Compute all the necessary quantities
47
103
  # --------------------------------------------------------------------------------------------------------------------------
48
- outputs = _diagonalize(
49
- NLoops,
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 = _solve_for_coefs(
77
- NLoops,
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, mu_arr,
140
+ mu_arr, mu_arr_pos, mu_arr_pos * W,
85
141
  N, NQuad,
86
142
  NLayers, NBDRF,
87
- atmos_is_multilayered,
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
- use_sparse_NLayers,
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
- _is_compatible_with_autograd,
209
+ _autograd_compatible,
154
210
  )
155
211
 
156
- if _is_compatible_with_autograd:
157
- um = um + np.concatenate((_mathscr_v_contribution.T, np.zeros((NLoops - 1, len(tau), NQuad))))
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(NLoops)[:, None] * (phi0 - phi)[None, :]),
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((NLoops - 1) * (phi0 - phi))[None, None, :]),
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
- _is_compatible_with_autograd,
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
- _is_compatible_with_autograd,
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
- _is_compatible_with_autograd,
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 _solve_for_coefs(
10
- NLoops,
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, mu_arr,
18
- N, NQuad,
19
- NLayers, NBDRF,
20
- atmos_is_multilayered,
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
- use_sparse_NLayers,
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
- """This function is wrapped by the `_assemble_solution_functions` function.
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((NLoops, NLayers, NQuad, NQuad))
40
- use_sparse_framework = NLayers >= use_sparse_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(NLoops):
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 atmos_is_multilayered:
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 atmos_is_multilayered:
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
- N_squared = N**2
197
- density = 4 * N_squared * (2 * NLayers - 1)
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[:N_squared] = block_row_ind[:N, :N].ravel()
207
- block_row_sort[N_squared : 2 * N_squared] = block_row_ind[N : 2 * N, :N].ravel()
208
- block_row_sort[2 * N_squared : 4 * N_squared] = block_row_ind[:2 * N, N : 2 * N].ravel()
209
- block_row_sort[4 * N_squared : 6 * N_squared] = block_row_ind[:2 * N, 2 * N : 3 * N].ravel()
210
- block_row_sort[6 * N_squared : 7 * N_squared] = block_row_ind[:N, 3 * N : 4 * N].ravel()
211
- block_row_sort[7 * N_squared : 8 * N_squared] = block_row_ind[N : 2 * N, 3 * N : 4 * N].ravel()
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[:N_squared], col_indices[:N_squared] = _nd_slice_to_indexes(np.s_[:N, :N])
215
- block_rows[:N_squared] = G_0_nn.ravel()
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[N_squared : 2 * N_squared],
218
- col_indices[N_squared : 2 * N_squared],
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[N_squared : 2 * N_squared] = (
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 * N_squared : 3 * N_squared],
225
- col_indices[2 * N_squared : 3 * N_squared],
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 * N_squared : 3 * N_squared] = (
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 * N_squared : 4 * N_squared],
232
- col_indices[3 * N_squared : 4 * N_squared],
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 * N_squared : 4 * N_squared] = (
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 * N_squared + l * block_row_len : 4 * N_squared + (l + 1) * block_row_len
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 * N_squared + l * block_row_len : 4 * N_squared + (l + 1) * block_row_len
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
- starting_ind = 4 * N_squared + l * block_row_len
266
- block_rows[starting_ind : N_squared + starting_ind] = (G_l_pn * E_lm1l[None, :]).ravel()
267
- block_rows[starting_ind + N_squared : 2 * N_squared + starting_ind] = (G_l_nn * E_lm1l[None, :]).ravel()
268
- block_rows[starting_ind + 2 * N_squared : 4 * N_squared + starting_ind] = G_l_ap.ravel()
269
- block_rows[starting_ind + 4 * N_squared : 6 * N_squared + starting_ind] = -G_lp1_an.ravel()
270
- block_rows[starting_ind + 6 * N_squared : 7 * N_squared + starting_ind] = -(G_lp1_pp * E_llp1[None, :]).ravel()
271
- block_rows[starting_ind + 7 * N_squared : 8 * N_squared + starting_ind] = -(G_lp1_np * E_llp1[None, :]).ravel()
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
- starting_row = N + l * NQuad
317
- starting_col = l * NQuad
318
- LHS[starting_row : N + starting_row, starting_col : N + starting_col] = G_l_pn * E_lm1l[None, :]
319
- LHS[N + starting_row : 2 * N + starting_row, starting_col : N + starting_col] = G_l_nn * E_lm1l[None, :]
320
- LHS[starting_row : 2 * N + starting_row, N + starting_col : 2 * N + starting_col] = G_l_ap
321
- LHS[starting_row : 2 * N + starting_row, 2 * N + starting_col : 3 * N + starting_col] = -G_lp1_an
322
- LHS[starting_row : N + starting_row, 3 * N + starting_col : 4 * N + starting_col] = -G_lp1_pp * E_llp1[None, :]
323
- LHS[N + starting_row : 2 * N + starting_row, 3 * N + starting_col : 4 * N + starting_col] = -G_lp1_np * E_llp1[None, :]
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)