PythonicDISORT 0.6.2__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.2/src/PythonicDISORT.egg-info → pythonicdisort-0.7.0}/PKG-INFO +4 -4
  2. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0}/README.md +3 -3
  3. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0}/pyproject.toml +1 -1
  4. PythonicDISORT-0.6.2/src/PythonicDISORT/_assemble_solution_functions.py → pythonicdisort-0.7.0/src/PythonicDISORT/_assemble_intensity_and_fluxes.py +97 -41
  5. PythonicDISORT-0.6.2/src/PythonicDISORT/_solve_for_coefs.py → pythonicdisort-0.7.0/src/PythonicDISORT/_solve_for_coeffs.py +105 -63
  6. PythonicDISORT-0.6.2/src/PythonicDISORT/_diagonalize.py → pythonicdisort-0.7.0/src/PythonicDISORT/_solve_for_gen_and_part_sols.py +52 -15
  7. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0}/src/PythonicDISORT/pydisort.py +111 -54
  8. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0}/src/PythonicDISORT/subroutines.py +49 -9
  9. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0/src/PythonicDISORT.egg-info}/PKG-INFO +4 -4
  10. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0}/src/PythonicDISORT.egg-info/SOURCES.txt +3 -3
  11. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0}/LICENSE.md +0 -0
  12. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0}/setup.cfg +0 -0
  13. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0}/src/PythonicDISORT/__init__.py +0 -0
  14. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0}/src/PythonicDISORT.egg-info/dependency_links.txt +0 -0
  15. {PythonicDISORT-0.6.2 → pythonicdisort-0.7.0}/src/PythonicDISORT.egg-info/requires.txt +0 -0
  16. {PythonicDISORT-0.6.2 → 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.2
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.2"
11
+ version = "0.7.0"
12
12
  authors = [
13
13
  { name="Dion HO Jia Xu", email="dh3065@columbia.edu" },
14
14
  ]
@@ -1,51 +1,107 @@
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, NFourier,
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(
104
+ outputs = _solve_for_gen_and_part_sols(
49
105
  NFourier,
50
106
  scaled_omega_arr,
51
107
  mu_arr_pos, mu_arr,
@@ -73,7 +129,7 @@ def _assemble_solution_functions(
73
129
  G_inv_collect_0 = None
74
130
  B_collect = None
75
131
 
76
- GC_collect = _solve_for_coefs(
132
+ GC_collect = _solve_for_coeffs(
77
133
  NFourier,
78
134
  G_collect,
79
135
  K_collect,
@@ -81,10 +137,10 @@ def _assemble_solution_functions(
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,10 +206,10 @@ 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:
212
+ if _autograd_compatible:
157
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
@@ -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,38 +6,79 @@ import scipy as sc
6
6
  from math import pi
7
7
 
8
8
 
9
- def _solve_for_coefs(
10
- NFourier,
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
80
  GC_collect = np.empty((NFourier, NLayers, NQuad, NQuad))
40
- use_sparse_framework = NLayers >= use_sparse_NLayers
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
84
  for m in range(NFourier):
@@ -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)
@@ -3,26 +3,63 @@ import scipy as sc
3
3
  from math import pi
4
4
 
5
5
 
6
- def _diagonalize(
7
- NFourier,
8
- scaled_omega_arr,
9
- mu_arr_pos, mu_arr,
10
- M_inv, W,
11
- N, NQuad, NLeg,
12
- NLayers,
13
- weighted_scaled_Leg_coeffs,
14
- mu0, I0,
15
- there_is_beam_source,
16
- Nscoeffs,
17
- there_is_iso_source,
6
+ def _solve_for_gen_and_part_sols(
7
+ NFourier, # Number of intensity Fourier modes
8
+ scaled_omega_arr, # Delta-scaled single-scattering albedos
9
+ mu_arr_pos, mu_arr, # Quadrature nodes for 1) upper 2) both hemispheres
10
+ M_inv, W, # 1) 1 / mu; 2) quadrature weights for each hemisphere
11
+ N, NQuad, NLeg, # Number of 1) upper 2) both hemispheres quadrature nodes; 3) phase function Legendre coefficients
12
+ NLayers, # Number of layers
13
+ weighted_scaled_Leg_coeffs, # Weighted and delta-scaled Legendre coefficients
14
+ mu0, I0, # Properties of direct beam
15
+ there_is_beam_source, # Is there a beam source?
16
+ Nscoeffs, # Number of isotropic source polynomial coefficients
17
+ there_is_iso_source, # Is there an isotropic source?
18
18
  ):
19
- """This function is wrapped by the `_assemble_solution_functions` function.
19
+ """
20
+ In this function the coefficient matrix of the system of ODEs for each Fourier mode is diagonalized
21
+ and the eigenpairs are returned; the general solution to each system of ODEs is determined up to unknown coefficients.
22
+ The particular solutions to each system of ODEs is also determined and its coefficient vector is returned.
23
+ This function is wrapped by the `_assemble_intensity_and_fluxes` function.
20
24
  It has many seemingly redundant arguments to maximize precomputation in the `pydisort` function.
21
25
  See the Jupyter Notebook, especially section 3, for documentation, explanation and derivation.
22
26
  The labels in this file reference labels in the Jupyter Notebook, especially sections 3 and 4.
23
27
 
28
+ Arguments of _solve_for_gen_and_part_sols
29
+ | Variable | Type / Shape |
30
+ | ------------------------------ | ---------------------------------- |
31
+ | `NFourier` | scalar |
32
+ | `scaled_omega_arr` | `NLayers` |
33
+ | `mu_arr_pos` | `NQuad/2` |
34
+ | `mu_arr` | `NQuad` |
35
+ | `M_inv` | `NQuad/2` |
36
+ | `W` | `NQuad/2` |
37
+ | `N` | scalar |
38
+ | `NQuad` | scalar |
39
+ | `NLeg` | scalar |
40
+ | `NLayers` | scalar |
41
+ | `weighted_scaled_Leg_coeffs` | `NLayers x NLeg` |
42
+ | `mu0` | scalar |
43
+ | `I0` | scalar |
44
+ | `there_is_beam_source` | boolean |
45
+ | `Nscoeffs` | scalar |
46
+ | `there_is_iso_source` | boolean |
47
+
48
+ Notable internal variables of _solve_for_gen_and_part_sols
49
+ | Variable | Type / Shape |
50
+ | ----------------- | -------------------------------------- |
51
+ | `ells_all` | `NLeg` |
52
+ | `G_collect` | `NFourier*NLayers x NQuad x NQuad` | Reshaped to NFourier x NLayers x NQuad x NQuad
53
+ | `K_collect` | `NFourier*NLayers x NQuad` | Reshaped to NFourier x NLayers x NQuad
54
+ | `alpha_arr` | `NFourier*NLayers x NQuad/2 x NQuad/2` | Reshaped to NFourier x NLayers x NQuad/2 x NQuad/2
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
+ | `B_collect` | `NFourier*NLayers x NQuad` or `None` | Reshaped to NFourier x NLayers x NQuad
58
+ | `G_inv_collect_0` | `NLayers x NQuad x NQuad` or `None` |
59
+
24
60
  """
25
61
  ############################### Assemble system and diagonalize coefficient matrix #########################################
62
+ ########################### Refer to Section 3.4.2 of the Comprehensive Documentation #####################################
26
63
 
27
64
  # Initialization
28
65
  # --------------------------------------------------------------------------------------------------------------------------
@@ -116,7 +153,7 @@ def _diagonalize(
116
153
 
117
154
  if len(no_shortcut_indices) > 0:
118
155
 
119
- # Diagonalization of coefficient matrix
156
+ # Diagonalization of coefficient matrix (refer to Section 3.4.2 of the Comprehensive Documentation)
120
157
  # --------------------------------------------------------------------------------------------------------------------------
121
158
  alpha_arr = alpha_arr[: len(no_shortcut_indices), :, :]
122
159
  beta_arr = beta_arr[: len(no_shortcut_indices), :, :]
@@ -153,7 +190,7 @@ def _diagonalize(
153
190
  G_inv_collect_0[no_shortcut_indices_0, :, :] = G_inv_arr[: len(no_shortcut_indices_0), :, :]
154
191
  # --------------------------------------------------------------------------------------------------------------------------
155
192
 
156
- # Particular solution for the sunbeam source
193
+ # Particular solution for the sunbeam source (refer to Section 3.6.1 of the Comprehensive Documentation)
157
194
  # --------------------------------------------------------------------------------------------------------------------------
158
195
  if there_is_beam_source:
159
196
  X_tilde_arr = X_tilde_arr[: len(no_shortcut_indices), :]
@@ -1,5 +1,5 @@
1
1
  from PythonicDISORT import subroutines
2
- from PythonicDISORT._assemble_solution_functions import _assemble_solution_functions
2
+ from PythonicDISORT._assemble_intensity_and_fluxes import _assemble_intensity_and_fluxes
3
3
 
4
4
  import numpy as np
5
5
  import scipy as sc
@@ -22,8 +22,8 @@ def pydisort(
22
22
  NT_cor=False,
23
23
  BDRF_Fourier_modes=[],
24
24
  s_poly_coeffs=np.array([[]]),
25
- use_sparse_NLayers=13,
26
- _is_compatible_with_autograd = False,
25
+ NLayers_to_use_sparse=13,
26
+ _autograd_compatible = False,
27
27
  ):
28
28
  """Solves the 1D RTE for the fluxes, and optionally intensity,
29
29
  of a multi-layer atmosphere with the specified optical properties, boundary conditions
@@ -37,14 +37,15 @@ def pydisort(
37
37
 
38
38
  Parameters
39
39
  ----------
40
- tau_arr : array
40
+ tau_arr : array or float
41
41
  Optical depth of the lower boundary of each atmospheric layer.
42
- omega_arr : array
42
+ omega_arr : array or float
43
43
  Single-scattering albedo of each atmospheric layer.
44
44
  NQuad : int
45
45
  Number of `mu` quadrature nodes.
46
- Leg_coeffs_all : ndarray
47
- All available unweighted phase function Legendre coefficients.
46
+ Leg_coeffs_all : 2darray
47
+ All available unweighted phase function Legendre coefficients.
48
+ Each row of coefficients pertain to an atmospheric layer.
48
49
  Each coefficient should be between 0 and 1 inclusive.
49
50
  mu0 : float
50
51
  Cosine of polar angle of the incident beam.
@@ -53,9 +54,10 @@ def pydisort(
53
54
  phi0 : float
54
55
  Azimuthal angle of the incident beam.
55
56
  NLeg : optional, int
56
- Number of phase function Legendre coefficients.
57
+ Number of phase function Legendre coefficients
58
+ to use in the pre-correction solver.
57
59
  NFourier : optional, int
58
- Number of Fourier modes to use in the intensity function.
60
+ Number of Fourier modes to use to construct the intensity function.
59
61
  b_pos : optional, 2darray or float
60
62
  Dirichlet boundary condition for the upward direction.
61
63
  b_neg : optional, 2darray or float
@@ -72,33 +74,80 @@ def pydisort(
72
74
  s_poly_coeffs : optional, array
73
75
  Polynomial coefficients of isotropic internal sources.
74
76
  Arrange coefficients from lowest order term to highest.
75
- use_sparse_NLayers : optional, int
77
+ NLayers_to_use_sparse : optional, int
76
78
  At or above how many atmospheric layers should SciPy's sparse matrix framework be used?
77
79
 
78
80
  Returns
79
81
  -------
80
- array
82
+ mu_arr : array
81
83
  All `mu` (cosine of polar angle) quadrature nodes.
82
- function
84
+ Fp(tau) : function, function
83
85
  (Energetic) Flux function with argument `tau` (type: array) for positive (upward) `mu` values.
84
86
  Returns the diffuse flux magnitudes (type: array).
85
- function
87
+ Fm(tau) : function
86
88
  (Energetic) Flux function with argument `tau` (type: array) for negative (downward) `mu` values.
87
89
  Returns a tuple of the diffuse and direct flux magnitudes respectively (type: (array, array)).
88
- function
90
+ u0(tau) : function
89
91
  Zeroth Fourier mode of the intensity with argument `tau` (type: array).
90
92
  Returns an ndarray with axes corresponding to variation with `mu` and `tau` respectively.
91
93
  This function is useful for calculating actinic fluxes and other quantities of interest,
92
94
  but reclassification of delta-scaled flux and other corrections must be done manually
93
95
  (for actinic flux `generate_diff_act_flux_funcs` will automatically perform the reclassification).
94
- function, optional
96
+ u(tau, phi) : function, optional
95
97
  Intensity function with arguments `(tau, phi, return_Fourier_error=False)` of types `(array, array, bool)`.
96
98
  Returns an ndarray with axes corresponding to variation with `mu, tau, phi` respectively.
97
99
  The optional flag `return_Fourier_error` determines whether the function will also return
98
100
  the Cauchy / Fourier convergence evaluation (type: float) for the last Fourier term.
99
101
 
100
102
  """
101
- if _is_compatible_with_autograd:
103
+
104
+ """
105
+ Arguments of pydisort
106
+ | Variable | Type / Shape |
107
+ | ----------------------- | ---------------------------------- |
108
+ | `tau_arr` | `NLayers` |
109
+ | `omega_arr` | `NLayers` |
110
+ | `NQuad` | scalar |
111
+ | `Leg_coeffs_all` | `NLeg_all` |
112
+ | `mu0` | scalar |
113
+ | `I0` | scalar |
114
+ | `phi0` | scalar |
115
+ | `NLeg` | scalar |
116
+ | `NFourier` | scalar |
117
+ | `b_pos` | `NQuad/2 x NFourier` or scalar |
118
+ | `b_neg` | `NQuad/2 x NFourier` or scalar |
119
+ | `only_flux` | boolean |
120
+ | `f_arr` | `NLayers` or scalar |
121
+ | `NT_cor` | boolean |
122
+ | `BDRF_Fourier_modes` | `NBDRF` |
123
+ | `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
124
+ | `NLayers_to_use_sparse` | scalar |
125
+
126
+ Notable internal variables of pydisort
127
+ | Variable | Type / Shape |
128
+ | ---------------------------- | -------------------- |
129
+ | `I0_orig` | scalar |
130
+ | `thickness_arr` | `NLayers` |
131
+ | `weighted_Leg_coeffs_all` | `NLayers x NLeg_all` |
132
+ | `Leg_coeffs` | `NLayers x NLeg` |
133
+ | `weighted_scaled_Leg_coeffs` | `NLayers x NLeg` |
134
+ | `mu_arr_pos` | `NQuad/2` |
135
+ | `W` | `NQuad/2` |
136
+ | `mu_arr` | `NQuad` |
137
+ | `M_inv` | `NQuad/2` |
138
+ | `scale_tau` | `NLayers` |
139
+ | `scaled_tau_arr_with_0` | `NLayers + 1` |
140
+ | `scaled_omega_arr` | `NLayers` |
141
+ | `sum1` | scalar |
142
+ | `omega_avg` | scalar |
143
+ | `sum2` | scalar |
144
+ | `f_avg` | scalar |
145
+ | `Leg_coeffs_residue` | `NLayers x NLeg_all` |
146
+ | `Leg_coeffs_residue_avg` | `NLeg_all` |
147
+ | `scaled_mu0` | scalar |
148
+ """
149
+
150
+ if _autograd_compatible:
102
151
  import autograd.numpy as np
103
152
  else:
104
153
  import numpy as np
@@ -134,10 +183,10 @@ def pydisort(
134
183
  N = NQuad // 2
135
184
  there_is_beam_source = I0 > 0
136
185
  there_is_iso_source = Nscoeffs > 0
137
- atmos_is_multilayered = NLayers > 1
186
+ is_atmos_multilayered = NLayers > 1
138
187
  # --------------------------------------------------------------------------------------------------------------------------
139
188
 
140
- # Input checks
189
+ # Input checks (refer to Section 1 of the Comprehensive Documentation)
141
190
  # --------------------------------------------------------------------------------------------------------------------------
142
191
  # Optical depths and thickness must be positive
143
192
  assert np.all(tau_arr > 0)
@@ -183,7 +232,7 @@ def pydisort(
183
232
  # The fractional scattering must be between 0 and 1
184
233
  assert np.all(0 <= f_arr) and np.all(f_arr <= 1)
185
234
  # The minimum threshold is the minimum numbers of layers: 1
186
- assert(use_sparse_NLayers >= 1)
235
+ assert(NLayers_to_use_sparse >= 1)
187
236
  # --------------------------------------------------------------------------------------------------------------------------
188
237
 
189
238
  # Some more setup
@@ -191,6 +240,7 @@ def pydisort(
191
240
  NBDRF = len(BDRF_Fourier_modes)
192
241
  weighted_Leg_coeffs_all = (2 * np.arange(NLeg_all) + 1) * Leg_coeffs_all
193
242
  Leg_coeffs = Leg_coeffs_all[:, :NLeg]
243
+ # The following is explained in Section 1.4 of the Comprehensive Documentation
194
244
  if (b_pos_is_scalar and b_pos == 0) and (b_neg_is_scalar and b_neg == 0) and not there_is_iso_source and there_is_beam_source:
195
245
  I0_orig = I0
196
246
  I0 = 1
@@ -198,7 +248,7 @@ def pydisort(
198
248
  I0_orig = 1
199
249
  # --------------------------------------------------------------------------------------------------------------------------
200
250
 
201
- # Generation of Double Gauss-Legendre quadrature weights and points
251
+ # Generation of "double-Gauss" quadrature weights and points (refer to Section 3.4 of the Comprehensive Documentation)
202
252
  # --------------------------------------------------------------------------------------------------------------------------
203
253
  # For positive mu values (the weights are identical for both domains)
204
254
  mu_arr_pos, W = subroutines.Gauss_Legendre_quad(N) # mu_arr_neg = -mu_arr_pos
@@ -210,6 +260,7 @@ def pydisort(
210
260
  # --------------------------------------------------------------------------------------------------------------------------
211
261
 
212
262
  # Delta-M scaling; there is no scaling if f = 0
263
+ # Refer to Sections 1.3.1 and 3.3 of the Comprehensive Documentation
213
264
  # --------------------------------------------------------------------------------------------------------------------------
214
265
  if np.any(f_arr > 0):
215
266
  scale_tau = 1 - omega_arr * f_arr
@@ -231,18 +282,19 @@ def pydisort(
231
282
 
232
283
  if NT_cor and not only_flux and there_is_beam_source and np.any(f_arr > 0) and NLeg < NLeg_all:
233
284
 
234
- ############################### Perform NT corrections on the intensity but not the flux ###################################
285
+ ############################### Perform NT corrections on the intensity but not the flux ###############################
286
+ ############################### Refer to Section 3.7.2 of the Comprehensive Documentation ##############################
235
287
 
236
288
  # Delta-M scaled solution; no further corrections to the flux
237
- flux_up, flux_down, u0, u_star = _assemble_solution_functions(
289
+ flux_up, flux_down, u0, u_star = _assemble_intensity_and_fluxes(
238
290
  scaled_omega_arr,
239
291
  tau_arr,
240
292
  scaled_tau_arr_with_0,
241
293
  mu_arr_pos, mu_arr,
242
294
  M_inv, W,
243
- N, NQuad, NLeg, NFourier,
244
- NLayers, NBDRF,
245
- atmos_is_multilayered,
295
+ N, NQuad, NLeg,
296
+ NFourier, NLayers, NBDRF,
297
+ is_atmos_multilayered,
246
298
  weighted_scaled_Leg_coeffs,
247
299
  BDRF_Fourier_modes,
248
300
  mu0, I0, I0_orig, phi0,
@@ -254,8 +306,8 @@ def pydisort(
254
306
  there_is_iso_source,
255
307
  scale_tau,
256
308
  only_flux,
257
- use_sparse_NLayers,
258
- _is_compatible_with_autograd,
309
+ NLayers_to_use_sparse,
310
+ _autograd_compatible,
259
311
  )
260
312
 
261
313
  # TMS correction
@@ -324,16 +376,25 @@ def pydisort(
324
376
  # TODO: Despite being vectorized and despite many attempts at further optimization,
325
377
  # this block of code is slower than desired and is the bottleneck for the TMS correction.
326
378
  # --------------------------------------------------------------------------------------------------------------------------
327
- if atmos_is_multilayered:
379
+ solution = (
380
+ mathscr_B[:, l, :] * np.vstack((TMS_correction_pos, TMS_correction_neg))[:, :, None]
381
+ )
382
+
383
+ if is_atmos_multilayered:
328
384
  layers_arr = np.arange(NLayers)[:, None]
329
385
  pos_contribution_mask = (l[None, :] < layers_arr).ravel()
330
386
  neg_contribution_mask = (l[None, :] > layers_arr).ravel()
387
+ any_pos_contribution = np.any(pos_contribution_mask)
388
+ any_neg_contribution = np.any(neg_contribution_mask)
389
+
390
+ if not (any_pos_contribution or any_neg_contribution):
391
+ return solution
331
392
 
332
393
  layers_arr_repeat = np.repeat(layers_arr, Ntau)
333
394
  scaled_tau_tile = np.tile(scaled_tau, NLayers)
334
395
  scaled_tau_arr_with_0_repeat = np.repeat(scaled_tau_arr_with_0, Ntau)
335
396
 
336
- if np.any(pos_contribution_mask):
397
+ if any_pos_contribution:
337
398
  contribution_from_each_other_layer_pos = np.zeros((N, NLayers * Ntau, Nphi))
338
399
  scaled_tau_l_tile_pos = scaled_tau_tile[pos_contribution_mask]
339
400
  mathscr_B_l_repeat_posmasked = mathscr_B[
@@ -365,7 +426,7 @@ def pydisort(
365
426
  contribution_from_each_other_layer_pos.reshape((N, NLayers, Ntau, Nphi)),
366
427
  axis=1,
367
428
  )
368
- if np.any(neg_contribution_mask):
429
+ if any_neg_contribution:
369
430
  contribution_from_each_other_layer_neg = np.zeros((N, NLayers * Ntau, Nphi))
370
431
  scaled_tau_l_tile_neg = scaled_tau_tile[neg_contribution_mask]
371
432
  mathscr_B_l_repeat_negmasked = mathscr_B[
@@ -398,30 +459,26 @@ def pydisort(
398
459
  axis=1,
399
460
  )
400
461
 
401
- if _is_compatible_with_autograd:
402
- solution = (
403
- mathscr_B[:, l, :]
404
- * np.vstack((TMS_correction_pos, TMS_correction_neg))[:, :, None]
405
- )
406
- solution[:N, :, :] += contribution_from_other_layers_pos
407
- solution[N:, :, :] += contribution_from_other_layers_neg
408
- return solution
409
-
410
- else:
411
- return mathscr_B[:, l, :] * np.vstack((TMS_correction_pos, TMS_correction_neg))[
412
- :, :, None
413
- ] + np.concatenate(
462
+ if _autograd_compatible:
463
+ if not any_pos_contribution:
464
+ TMS_correction_pos = np.zeros((N, Ntau, Nphi))
465
+ if not any_neg_contribution:
466
+ TMS_correction_neg = np.zeros((N, Ntau, Nphi))
467
+ return solution + np.concatenate(
414
468
  (contribution_from_other_layers_pos, contribution_from_other_layers_neg),
415
469
  axis=0,
416
470
  )
471
+ else:
472
+ if any_pos_contribution:
473
+ solution[:N, :, :] += contribution_from_other_layers_pos
474
+ if any_neg_contribution:
475
+ solution[N:, :, :] += contribution_from_other_layers_neg
476
+ return solution
417
477
 
418
478
  # --------------------------------------------------------------------------------------------------------------------------
419
479
 
420
480
  else:
421
- return (
422
- mathscr_B[:, l, :]
423
- * np.vstack((TMS_correction_pos, TMS_correction_neg))[:, :, None]
424
- )
481
+ return solution
425
482
  # --------------------------------------------------------------------------------------------------------------------------
426
483
 
427
484
  # IMS correction
@@ -466,7 +523,7 @@ def pydisort(
466
523
  phi = np.atleast_1d(phi)
467
524
  NT_corrections = TMS_correction(tau, phi)
468
525
 
469
- if _is_compatible_with_autograd:
526
+ if _autograd_compatible:
470
527
  NT_corrections = NT_corrections + np.concatenate(
471
528
  [np.zeros((N, len(tau), len(phi))), IMS_correction(tau, phi)], axis=0
472
529
  )
@@ -488,15 +545,15 @@ def pydisort(
488
545
  else:
489
546
  if only_flux:
490
547
  NFourier = 1 # We only need to solve for the 0th Fourier mode to compute the flux
491
- return (mu_arr,) + _assemble_solution_functions(
548
+ return (mu_arr,) + _assemble_intensity_and_fluxes(
492
549
  scaled_omega_arr,
493
550
  tau_arr,
494
551
  scaled_tau_arr_with_0,
495
552
  mu_arr_pos, mu_arr,
496
553
  M_inv, W,
497
- N, NQuad, NLeg, NFourier,
498
- NLayers, NBDRF,
499
- atmos_is_multilayered,
554
+ N, NQuad, NLeg,
555
+ NFourier, NLayers, NBDRF,
556
+ is_atmos_multilayered,
500
557
  weighted_scaled_Leg_coeffs,
501
558
  BDRF_Fourier_modes,
502
559
  mu0, I0, I0_orig, phi0,
@@ -508,6 +565,6 @@ def pydisort(
508
565
  there_is_iso_source,
509
566
  scale_tau,
510
567
  only_flux,
511
- use_sparse_NLayers,
512
- _is_compatible_with_autograd,
568
+ NLayers_to_use_sparse,
569
+ _autograd_compatible,
513
570
  )
@@ -4,7 +4,7 @@ from math import pi
4
4
  from numpy.polynomial.legendre import leggauss
5
5
 
6
6
 
7
- def transform_interval(arr, c, d, a=-1, b=1):
7
+ def transform_interval(arr, c, d, a, b):
8
8
  """Affine transformation of an array from interval [a, b] to [c, d].
9
9
 
10
10
  Parameters
@@ -29,7 +29,7 @@ def transform_interval(arr, c, d, a=-1, b=1):
29
29
  return (((arr - a) * (d - c)) / (b - a)) + c
30
30
 
31
31
 
32
- def transform_weights(weights, c, d, a=-1, b=1):
32
+ def transform_weights(weights, c, d, a, b):
33
33
  """Transforms an array of quadrature weights from interval [a, b] to [c, d].
34
34
 
35
35
  Parameters
@@ -106,7 +106,7 @@ def Gauss_Legendre_quad(N, c=0, d=1):
106
106
  """
107
107
  mu_arr_pos, W = leggauss(int(N))
108
108
 
109
- return transform_interval(mu_arr_pos, c, d), transform_weights(W, c, d)
109
+ return transform_interval(mu_arr_pos, c, d, -1, 1), transform_weights(W, c, d, -1, 1)
110
110
 
111
111
 
112
112
  def Clenshaw_Curtis_quad(Nphi, c=0, d=(2 * pi)):
@@ -142,7 +142,7 @@ def Clenshaw_Curtis_quad(Nphi, c=0, d=(2 * pi)):
142
142
  weights_phi_pos[0] /= 2
143
143
  full_weights_phi = np.hstack((weights_phi_pos, np.flip(weights_phi_pos[:-1])))
144
144
 
145
- return transform_interval(phi_arr, c, d), transform_weights(full_weights_phi, c, d)
145
+ return transform_interval(phi_arr, c, d, -1, 1), transform_weights(full_weights_phi, c, d, -1, 1)
146
146
 
147
147
 
148
148
  def generate_FD_mat(Ntau, a, b):
@@ -256,18 +256,58 @@ def generate_diff_act_flux_funcs(u0):
256
256
  return flux_act_up, flux_act_down_diffuse
257
257
 
258
258
 
259
- def _mathscr_v(tau, l, s_poly_coeffs, Nscoeffs, G, K, G_inv, mu_arr, _is_compatible_with_autograd=False):
259
+ def _mathscr_v(tau, # Input optical depths
260
+ l, # Layer index of each input optical depth
261
+ s_poly_coeffs, # Polynomial coefficients of isotropic source
262
+ Nscoeffs, # Number of isotropic source polynomial coefficients
263
+ G, # Eigenvector matrices
264
+ K, # Eigenvalues
265
+ G_inv, # Inverse of eigenvector matrix
266
+ mu_arr, # Quadrature nodes for both hemispheres
267
+ _autograd_compatible=False # Should the output functions be compatible with autograd?
268
+ ):
260
269
  """Particular solution for isotropic internal sources.
261
- It has many seemingly redundant arguments to maximize precomputation
262
- in the `pydisort` function which calls it.
263
-
270
+ Refer to Section 3.6.1 of the Comprehensive Documentation.
271
+ It has many seemingly redundant arguments to maximize
272
+ precomputation in the `_assemble_intensity_and_fluxes`
273
+ and `_solve_for_coeffs` functions which call it.
274
+
275
+ Arguments of _mathscr_v
276
+ | Variable | Shape |
277
+ | ---------------------- | ------------------------------------- |
278
+ | `tau` | `Ntau` |
279
+ | `l` | `Ntau` |
280
+ | `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
281
+ | `G` | `NLayers<= x NQuad x NQuad` |
282
+ | `K` | `NLayers<= x NQuad` |
283
+ | `G_inv` | `NLayers<= x NQuad x NQuad` or `None` |
284
+ | `mu_arr` | `NQuad` |
285
+ | `_autograd_compatible` | boolean |
286
+
287
+ Notable internal variables of _mathscr_v
288
+ | Variable | Shape |
289
+ | ---------------- | ----------------------------------- |
290
+ | i_arr | `Nscoeffs` |
291
+ | i_arr_repeat | `Nscoeffs*(Nscoeffs+1)/2` | Using triangular number formula
292
+ | j_arr | `Nscoeffs*(Nscoeffs+1)/2` | Using triangular number formula
293
+ | s_poly_coeffs_nj | `NLayers x Nscoeffs*(Nscoeffs+1)/2` |
294
+ | OUTPUT | `NQuad x Ntau` |
264
295
  """
265
296
  n = Nscoeffs - 1
266
297
 
267
- if _is_compatible_with_autograd:
298
+ if _autograd_compatible:
268
299
  import autograd.numpy as np
269
300
 
270
301
  def mathscr_b(i):
302
+ """
303
+ Notable internal variables of mathscr_b
304
+ | Variable | Shape |
305
+ | ---------------- | ------------------------------------- |
306
+ | j_arr | `i + 1` |
307
+ | s_poly_coeffs_nj | `i + 1` |
308
+ | OUTPUT | `Nscoeffs x (i + 1) x NQuad` |
309
+ """
310
+
271
311
  j_arr = np.arange(i + 1)
272
312
  s_poly_coeffs_nj = s_poly_coeffs[:, n - j_arr]
273
313
  return np.sum(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PythonicDISORT
3
- Version: 0.6.2
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`
@@ -2,9 +2,9 @@ LICENSE.md
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/PythonicDISORT/__init__.py
5
- src/PythonicDISORT/_assemble_solution_functions.py
6
- src/PythonicDISORT/_diagonalize.py
7
- src/PythonicDISORT/_solve_for_coefs.py
5
+ src/PythonicDISORT/_assemble_intensity_and_fluxes.py
6
+ src/PythonicDISORT/_solve_for_coeffs.py
7
+ src/PythonicDISORT/_solve_for_gen_and_part_sols.py
8
8
  src/PythonicDISORT/pydisort.py
9
9
  src/PythonicDISORT/subroutines.py
10
10
  src/PythonicDISORT.egg-info/PKG-INFO
File without changes