PythonicDISORT 0.7.0__tar.gz → 0.8.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PythonicDISORT
3
- Version: 0.7.0
3
+ Version: 0.8.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
@@ -20,42 +20,43 @@ Requires-Dist: jupyter>1.0.0; extra == "notebook-dependencies"
20
20
  Requires-Dist: notebook>6.5.2; extra == "notebook-dependencies"
21
21
 
22
22
  # Introduction
23
- The PythonicDISORT package is a Discrete Ordinates Solver for the (1D) Radiative Transfer Equation in a single or multi-layer plane-parallel atmosphere.
23
+ The PythonicDISORT package is a Discrete Ordinates Solver for the (1D) Radiative Transfer Equation
24
+ in a plane-parallel, horizontally homogeneous atmosphere.
24
25
  It is coded entirely in Python 3 and is a reimplementation instead of a wrapper.
25
26
  While PythonicDISORT has been optimized for speed, it will naturally be slower than similar FORTRAN algorithms.
26
27
  On the other hand, PythonicDISORT should be easier to install, use, and modify than FORTRAN-based Discrete Ordinates Solvers.
27
28
 
28
- PythonicDISORT is based on Stamnes' FORTRAN DISORT (see References, in particular [2, 3, 8]) and has its main features:
29
- delta-M scaling, Nakajima-Tanaka (NT) corrections, only flux option, isotropic internal sources (thermal sources),
29
+ PythonicDISORT is based on Stamnes' FORTRAN DISORT (see References, in particular [2, 3, 8]) and has its main features: multi-layer solver,
30
+ delta-M scaling, Nakajima-Tanaka (NT) corrections, only flux option, direct beam source, isotropic internal source (blackbody emission),
30
31
  Dirichlet boundary conditions (diffuse flux boundary sources), Bi-Directional Reflectance Function (BDRF) for surface reflection, and more.
31
- In addition, upon the request of a user, we added a subroutine to calculate actinic fluxes and we are open to further feature requests as well as feedback.
32
+ In addition, subroutine to calculate actinic fluxes was added to satisfy a user request and further feature requests as well as feedback are welcome.
32
33
 
33
34
  You may contact me, Dion, through dh3065@columbia.edu.
34
35
 
35
- Our **GitHub repository:** https://github.com/LDEO-CREW/Pythonic-DISORT also includes our F2PY-wrapped Stamnes DISORT (version 4.0.99) in the `disort4.0.99_f2py` directory.
36
- The original was downloaded from http://www.rtatmocn.com/disort/. The wrapper is inspired by https://github.com/kconnour/pyRT_DISORT.
36
+ The **GitHub repository** is https://github.com/LDEO-CREW/Pythonic-DISORT.
37
37
 
38
38
  # Documentation
39
39
  https://pythonic-disort.readthedocs.io/en/latest/
40
40
 
41
41
  Also see the accompanying Jupyter Notebook `Pythonic-DISORT.ipynb` in the `docs` directory
42
42
  of our GitHub repository.
43
- The Jupyter Notebook provides comprehensive documentation, suggested inputs, explanations,
43
+ This Jupyter Notebook provides comprehensive documentation, suggested inputs, explanations,
44
44
  mathematical derivations and verification tests.
45
- We highly recommend reading the non-optional parts of sections 1 and 2 before use.
45
+ It is highly recommended that new users read the non-optional parts of sections 1 and 2.
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 have also recreated most of the test problems in Stamnes' `disotest.f90`;
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
- against solutions from our F2PY-wrapped Stamnes' DISORT (version 4.0.99). With PyTest installed, execute the console command `pytest`
52
- in the `pydisotest` directory to run these tests. The `pydisotest` directory also contains Jupyter Notebooks, one for each test,
53
- to show how the tests were implemented. These notebooks double up as examples of how to use PythonicDISORT.
49
+ Not only are there verification tests in `Pythonic-DISORT.ipynb`,
50
+ most of the test problems in Stamnes' `disotest.f90` (download DISORT 4.0.99 from http://www.rtatmocn.com/disort/) have also been recreated.
51
+ In these tests, the solutions from PythonicDISORT are compared against solutions
52
+ from a F2PY-wrapped Stamnes' DISORT (version 4.0.99; wrapper inspired by https://github.com/kconnour/pyRT_DISORT). With PyTest installed, execute the console command `pytest`
53
+ in the `pydisotest` directory to run these tests. The `pydisotest` directory also contains Jupyter Notebooks that correspond to each test
54
+ to show how it was implemented. These notebooks double up as examples of how to use PythonicDISORT.
54
55
 
55
56
  # Installation
56
57
 
57
58
  * From PyPI: `pip install PythonicDISORT`
58
- * From Conda-forge: (TODO: we need to first publish on Conda-forge)
59
+ * From Conda-forge: (TODO: need to first publish on Conda-forge)
59
60
  * 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
61
 
61
62
  ## Requirements to run PythonicDISORT
@@ -64,21 +65,18 @@ to show how the tests were implemented. These notebooks double up as examples of
64
65
  * `scipy >= 1.8.0`
65
66
  * (OPTIONAL) `pytest >= 6.2.5` (Required to use the command `pytest`, see *PyTest and examples of how to use PythonicDISORT*)
66
67
 
67
- ## Additional requirements to run the Jupyter Notebook
68
+ ## (OPTIONAL) Additional requirements to run the Jupyter Notebook
68
69
  * `autograd >= 1.5`
69
70
  * `jupyter > 1.0.0`
70
71
  * `notebook > 6.5.2`
72
+ * `matplotlib >= 3.6.0`
71
73
 
72
- In addition, our F2PY-wrapped Stamnes' DISORT (in the `disort4.0.99_f2py` directory), or equivalent,
73
- must be set up to run the last section (section 6).
74
+ In addition, a F2PY-wrapped Stamnes' DISORT, or equivalent, is required to properly run the last section (section 6).
74
75
 
75
76
  ## Compatibility
76
77
 
77
- The PythonicDISORT package *should* be system agnostic given its minimal dependencies and pure Python code.
78
- We do not guarantee that our Jupyter Notebook and F2PY-wrapped Stamnes' DISORT will work on other systems though.
79
- The latter will almost certainly need user edits to work and in any case it requires FORTRAN compilers which
80
- are not included in our GitHub repository. Everything in the repository was built and tested on Windows 11 but
81
- not yet tested on other systems.
78
+ The PythonicDISORT package should be system agnostic given its minimal dependencies and pure Python code.
79
+ Everything in the repository was built and tested on Windows 11.
82
80
 
83
81
  # Acknowledgements
84
82
 
@@ -106,6 +104,8 @@ Science and Technology Center (STC) (Award #2019625) under which this package wa
106
104
 
107
105
  10) Trefethen, L. N.. 1996. *Finite difference and spectral methods for ordinary and partial differential equations.* https://people.maths.ox.ac.uk/trefethen/pdetext.html.
108
106
 
109
- 11) T. Nakajima and M. Tanaka. 1988. *Algorithms for radiative intensity calculations in moderately thick atmospheres using a truncation approximation.* https://www.sciencedirect.com/science/article/pii/0022407388900313.
107
+ 11) Knut Stamnes. 1982. *On the computation of angular distributions of radiation in planetary atmospheres.* https://www.sciencedirect.com/science/article/pii/0022407382900966.
110
108
 
111
- 12) K. Connour and A. Stcherbinine. 2022. *pyRT_DISORT.* https://github.com/kconnour/pyRT_DISORT.
109
+ 12) T. Nakajima and M. Tanaka. 1988. *Algorithms for radiative intensity calculations in moderately thick atmospheres using a truncation approximation.* https://www.sciencedirect.com/science/article/pii/0022407388900313.
110
+
111
+ 13) Connour, Kyle and Wolff, Michael. 2020. *pyRT_DISORT: A pre-processing front-end to help make DISORT simulations easier in Python.* https://github.com/kconnour/pyRT_DISORT.
@@ -1,40 +1,41 @@
1
1
  # Introduction
2
- The PythonicDISORT package is a Discrete Ordinates Solver for the (1D) Radiative Transfer Equation in a single or multi-layer plane-parallel atmosphere.
2
+ The PythonicDISORT package is a Discrete Ordinates Solver for the (1D) Radiative Transfer Equation
3
+ in a plane-parallel, horizontally homogeneous atmosphere.
3
4
  It is coded entirely in Python 3 and is a reimplementation instead of a wrapper.
4
5
  While PythonicDISORT has been optimized for speed, it will naturally be slower than similar FORTRAN algorithms.
5
6
  On the other hand, PythonicDISORT should be easier to install, use, and modify than FORTRAN-based Discrete Ordinates Solvers.
6
7
 
7
- PythonicDISORT is based on Stamnes' FORTRAN DISORT (see References, in particular [2, 3, 8]) and has its main features:
8
- delta-M scaling, Nakajima-Tanaka (NT) corrections, only flux option, isotropic internal sources (thermal sources),
8
+ PythonicDISORT is based on Stamnes' FORTRAN DISORT (see References, in particular [2, 3, 8]) and has its main features: multi-layer solver,
9
+ delta-M scaling, Nakajima-Tanaka (NT) corrections, only flux option, direct beam source, isotropic internal source (blackbody emission),
9
10
  Dirichlet boundary conditions (diffuse flux boundary sources), Bi-Directional Reflectance Function (BDRF) for surface reflection, and more.
10
- In addition, upon the request of a user, we added a subroutine to calculate actinic fluxes and we are open to further feature requests as well as feedback.
11
+ In addition, subroutine to calculate actinic fluxes was added to satisfy a user request and further feature requests as well as feedback are welcome.
11
12
 
12
13
  You may contact me, Dion, through dh3065@columbia.edu.
13
14
 
14
- Our **GitHub repository:** https://github.com/LDEO-CREW/Pythonic-DISORT also includes our F2PY-wrapped Stamnes DISORT (version 4.0.99) in the `disort4.0.99_f2py` directory.
15
- The original was downloaded from http://www.rtatmocn.com/disort/. The wrapper is inspired by https://github.com/kconnour/pyRT_DISORT.
15
+ The **GitHub repository** is https://github.com/LDEO-CREW/Pythonic-DISORT.
16
16
 
17
17
  # Documentation
18
18
  https://pythonic-disort.readthedocs.io/en/latest/
19
19
 
20
20
  Also see the accompanying Jupyter Notebook `Pythonic-DISORT.ipynb` in the `docs` directory
21
21
  of our GitHub repository.
22
- The Jupyter Notebook provides comprehensive documentation, suggested inputs, explanations,
22
+ This Jupyter Notebook provides comprehensive documentation, suggested inputs, explanations,
23
23
  mathematical derivations and verification tests.
24
- We highly recommend reading the non-optional parts of sections 1 and 2 before use.
24
+ It is highly recommended that new users read the non-optional parts of sections 1 and 2.
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 have also recreated most of the test problems in Stamnes' `disotest.f90`;
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
- against solutions from our F2PY-wrapped Stamnes' DISORT (version 4.0.99). With PyTest installed, execute the console command `pytest`
31
- in the `pydisotest` directory to run these tests. The `pydisotest` directory also contains Jupyter Notebooks, one for each test,
32
- to show how the tests were implemented. These notebooks double up as examples of how to use PythonicDISORT.
28
+ Not only are there verification tests in `Pythonic-DISORT.ipynb`,
29
+ most of the test problems in Stamnes' `disotest.f90` (download DISORT 4.0.99 from http://www.rtatmocn.com/disort/) have also been recreated.
30
+ In these tests, the solutions from PythonicDISORT are compared against solutions
31
+ from a F2PY-wrapped Stamnes' DISORT (version 4.0.99; wrapper inspired by https://github.com/kconnour/pyRT_DISORT). With PyTest installed, execute the console command `pytest`
32
+ in the `pydisotest` directory to run these tests. The `pydisotest` directory also contains Jupyter Notebooks that correspond to each test
33
+ to show how it was implemented. These notebooks double up as examples of how to use PythonicDISORT.
33
34
 
34
35
  # Installation
35
36
 
36
37
  * From PyPI: `pip install PythonicDISORT`
37
- * From Conda-forge: (TODO: we need to first publish on Conda-forge)
38
+ * From Conda-forge: (TODO: need to first publish on Conda-forge)
38
39
  * 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
40
 
40
41
  ## Requirements to run PythonicDISORT
@@ -43,21 +44,18 @@ to show how the tests were implemented. These notebooks double up as examples of
43
44
  * `scipy >= 1.8.0`
44
45
  * (OPTIONAL) `pytest >= 6.2.5` (Required to use the command `pytest`, see *PyTest and examples of how to use PythonicDISORT*)
45
46
 
46
- ## Additional requirements to run the Jupyter Notebook
47
+ ## (OPTIONAL) Additional requirements to run the Jupyter Notebook
47
48
  * `autograd >= 1.5`
48
49
  * `jupyter > 1.0.0`
49
50
  * `notebook > 6.5.2`
51
+ * `matplotlib >= 3.6.0`
50
52
 
51
- In addition, our F2PY-wrapped Stamnes' DISORT (in the `disort4.0.99_f2py` directory), or equivalent,
52
- must be set up to run the last section (section 6).
53
+ In addition, a F2PY-wrapped Stamnes' DISORT, or equivalent, is required to properly run the last section (section 6).
53
54
 
54
55
  ## Compatibility
55
56
 
56
- The PythonicDISORT package *should* be system agnostic given its minimal dependencies and pure Python code.
57
- We do not guarantee that our Jupyter Notebook and F2PY-wrapped Stamnes' DISORT will work on other systems though.
58
- The latter will almost certainly need user edits to work and in any case it requires FORTRAN compilers which
59
- are not included in our GitHub repository. Everything in the repository was built and tested on Windows 11 but
60
- not yet tested on other systems.
57
+ The PythonicDISORT package should be system agnostic given its minimal dependencies and pure Python code.
58
+ Everything in the repository was built and tested on Windows 11.
61
59
 
62
60
  # Acknowledgements
63
61
 
@@ -85,6 +83,8 @@ Science and Technology Center (STC) (Award #2019625) under which this package wa
85
83
 
86
84
  10) Trefethen, L. N.. 1996. *Finite difference and spectral methods for ordinary and partial differential equations.* https://people.maths.ox.ac.uk/trefethen/pdetext.html.
87
85
 
88
- 11) T. Nakajima and M. Tanaka. 1988. *Algorithms for radiative intensity calculations in moderately thick atmospheres using a truncation approximation.* https://www.sciencedirect.com/science/article/pii/0022407388900313.
86
+ 11) Knut Stamnes. 1982. *On the computation of angular distributions of radiation in planetary atmospheres.* https://www.sciencedirect.com/science/article/pii/0022407382900966.
89
87
 
90
- 12) K. Connour and A. Stcherbinine. 2022. *pyRT_DISORT.* https://github.com/kconnour/pyRT_DISORT.
88
+ 12) T. Nakajima and M. Tanaka. 1988. *Algorithms for radiative intensity calculations in moderately thick atmospheres using a truncation approximation.* https://www.sciencedirect.com/science/article/pii/0022407388900313.
89
+
90
+ 13) Connour, Kyle and Wolff, Michael. 2020. *pyRT_DISORT: A pre-processing front-end to help make DISORT simulations easier in Python.* https://github.com/kconnour/pyRT_DISORT.
@@ -8,7 +8,7 @@ dependencies = [
8
8
  "scipy>=1.8.0",
9
9
  ]
10
10
  name = "PythonicDISORT"
11
- version = "0.7.0"
11
+ version = "0.8.0"
12
12
  authors = [
13
13
  { name="Dion HO Jia Xu", email="dh3065@columbia.edu" },
14
14
  ]
@@ -28,9 +28,9 @@ def _assemble_intensity_and_fluxes(
28
28
  NLayers_to_use_sparse, # Number of layers above or equal which to use sparse matrices
29
29
  _autograd_compatible, # Should the output functions be compatible with autograd?
30
30
  ):
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.
31
+ """Assembles the solution functions: intensity `u`, upward flux `flux_up`, downward flux `flux_down`
32
+ from the previously solved eigenpairs and coefficients from the boundary conditions.
33
+ Returns these solution functions.
34
34
  This function is wrapped by the `pydisort` function.
35
35
  It should be called through `pydisort` and never directly.
36
36
  It has many seemingly redundant arguments, which are described below,
@@ -161,7 +161,15 @@ def _assemble_intensity_and_fluxes(
161
161
 
162
162
  # Construct the intensity function (refer to Section 3.7 of the Comprehensive Documentation)
163
163
  # --------------------------------------------------------------------------------------------------------------------------
164
- def u(tau, phi, return_Fourier_error=False):
164
+ def u(tau, phi, return_Fourier_error=False, is_antiderivative_wrt_tau=False):
165
+ """
166
+ Intensity function with arguments `(tau, phi)` of types `(array or float, array or float)`.
167
+ Returns an ndarray with axes corresponding to variation with `mu, tau, phi` respectively.
168
+ Pass `return_Fourier_error = True` (defaults to `False`) to return the
169
+ Cauchy / Fourier convergence evaluation (type: float) for the last Fourier term.
170
+ Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
171
+ to switch to an antiderivative of the function with respect to `tau`.
172
+ """
165
173
  tau = np.atleast_1d(tau)
166
174
  phi = np.atleast_1d(phi)
167
175
  assert np.all(tau >= 0)
@@ -179,7 +187,7 @@ def _assemble_intensity_and_fluxes(
179
187
  scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
180
188
  else:
181
189
  scaled_tau = tau
182
-
190
+
183
191
  exponent = np.concatenate(
184
192
  [
185
193
  K_collect[:, l, :N] * (scaled_tau - scaled_tau_arr_lm1)[None, :, None],
@@ -187,14 +195,33 @@ def _assemble_intensity_and_fluxes(
187
195
  ],
188
196
  axis=-1,
189
197
  )
190
- if there_is_beam_source:
191
- um = np.einsum(
192
- "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
193
- ) + B_collect[:, l, :] * np.exp(-scaled_tau[None, :, None] / mu0)
198
+
199
+ if is_antiderivative_wrt_tau:
200
+ if there_is_beam_source:
201
+ um = np.einsum(
202
+ "mtij, mtj -> mti",
203
+ GC_collect[:, l, :, :],
204
+ np.exp(exponent) / (scale_tau[None, l, None] * K_collect[:, l, :]),
205
+ optimize=True,
206
+ ) + B_collect[:, l, :] * np.exp(-scaled_tau[None, :, None] / mu0) / (
207
+ -scale_tau[None, l, None] / mu0
208
+ )
209
+ else:
210
+ um = np.einsum(
211
+ "mtij, mtj -> mti",
212
+ GC_collect[:, l, :, :],
213
+ np.exp(exponent) / (scale_tau[None, l, None] * K_collect[:, l, :]),
214
+ optimize=True,
215
+ )
194
216
  else:
195
- um = np.einsum(
196
- "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
197
- )
217
+ if there_is_beam_source:
218
+ um = np.einsum(
219
+ "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
220
+ ) + B_collect[:, l, :] * np.exp(-scaled_tau[None, :, None] / mu0)
221
+ else:
222
+ um = np.einsum(
223
+ "mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
224
+ )
198
225
 
199
226
  # Contribution from particular solution for isotropic internal sources
200
227
  if there_is_iso_source:
@@ -207,6 +234,7 @@ def _assemble_intensity_and_fluxes(
207
234
  G_inv_collect_0,
208
235
  mu_arr,
209
236
  _autograd_compatible,
237
+ is_antiderivative_wrt_tau
210
238
  )
211
239
 
212
240
  if _autograd_compatible:
@@ -229,20 +257,43 @@ def _assemble_intensity_and_fluxes(
229
257
  ],
230
258
  axis=-1,
231
259
  )
232
- if there_is_beam_source:
233
- ulast = np.einsum(
234
- "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
235
- ) + B_collect[-1, l, :].T * np.exp(-scaled_tau[None, :] / mu0)
260
+ if is_antiderivative_wrt_tau:
261
+ if there_is_beam_source:
262
+ ulast = np.einsum(
263
+ "tij, tj -> it",
264
+ GC_collect[-1, l, :, :],
265
+ np.exp(exponent) / (scale_tau[l, None] * K_collect[-1, l, :]),
266
+ optimize=True,
267
+ ) + B_collect[-1, l, :].T * np.exp(-scaled_tau[None, :] / mu0) / (
268
+ -scale_tau[None, l] / mu0
269
+ )
270
+ else:
271
+ ulast = np.einsum(
272
+ "tij, tj -> it",
273
+ GC_collect[-1, l, :, :],
274
+ np.exp(exponent) / (scale_tau[l, None] * K_collect[-1, l, :]),
275
+ optimize=True,
276
+ )
236
277
  else:
237
- ulast = np.einsum(
238
- "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
239
- )
278
+ if there_is_beam_source:
279
+ ulast = np.einsum(
280
+ "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
281
+ ) + B_collect[-1, l, :].T * np.exp(-scaled_tau[None, :] / mu0)
282
+ else:
283
+ ulast = np.einsum(
284
+ "tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
285
+ )
286
+
240
287
  intensities_abs = np.abs(intensities)
241
288
  Fourier_error = np.max(
242
- np.abs(ulast[:, :, None] * np.cos((NFourier - 1) * (phi0 - phi))[None, None, :]),
243
- intensities_abs,
244
- out=np.zeros_like(intensities_abs),
245
- where=intensities_abs > 1e-8,
289
+ np.divide(
290
+ np.abs(
291
+ ulast[:, :, None] * np.cos((NFourier - 1) * (phi0 - phi))[None, None, :]
292
+ ),
293
+ intensities_abs,
294
+ out=np.zeros_like(intensities_abs),
295
+ where=intensities_abs > 1e-8,
296
+ )
246
297
  )
247
298
  return I0_orig * np.squeeze(intensities), Fourier_error
248
299
  else:
@@ -251,7 +302,16 @@ def _assemble_intensity_and_fluxes(
251
302
 
252
303
  # Construct u0
253
304
  # --------------------------------------------------------------------------------------------------------------------------
254
- def u0(tau, return_act_dscale_reclassification=False):
305
+ def u0(tau, is_antiderivative_wrt_tau=False, _return_act_dscale_for_reclass=False):
306
+ """
307
+ Zeroth Fourier mode of the intensity with argument `tau` (type: array or float).
308
+ Returns an ndarray with axes corresponding to variation with `mu` and `tau` respectively.
309
+ This function is useful for calculating actinic fluxes and other quantities of interest,
310
+ but reclassification of delta-scaled flux and other corrections must be done manually
311
+ (for actinic flux `generate_diff_act_flux_funcs` will automatically perform the reclassification).
312
+ Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
313
+ to switch to an antiderivative of the function with respect to `tau`.
314
+ """
255
315
  tau = np.atleast_1d(tau)
256
316
  assert np.all(tau >= 0)
257
317
  assert np.all(tau <= tau_arr[-1])
@@ -268,11 +328,14 @@ def _assemble_intensity_and_fluxes(
268
328
  scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
269
329
  # The following complements the function `subroutines.generate_diff_act_flux_funcs`
270
330
  # in performing the reclassification of delta-scaled actinic flux
271
- if return_act_dscale_reclassification:
272
- act_dscale_reclassification = I0 * np.exp(-scaled_tau / mu0) - I0 * np.exp(-tau / mu0)
331
+ if _return_act_dscale_for_reclass:
332
+ if is_antiderivative_wrt_tau:
333
+ act_dscale_reclassification = I0 * np.exp(-scaled_tau / mu0) / (-scale_tau[l] / mu0) - I0 * np.exp(-tau / mu0) * -mu0
334
+ else:
335
+ act_dscale_reclassification = I0 * np.exp(-scaled_tau / mu0) - I0 * np.exp(-tau / mu0)
273
336
  else:
274
337
  scaled_tau = tau
275
- if return_act_dscale_reclassification:
338
+ if _return_act_dscale_for_reclass:
276
339
  act_dscale_reclassification = 0
277
340
 
278
341
  exponent = np.concatenate(
@@ -282,14 +345,33 @@ def _assemble_intensity_and_fluxes(
282
345
  ],
283
346
  axis=-1,
284
347
  )
285
- if there_is_beam_source:
286
- u0 = np.einsum(
287
- "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
288
- ) + B_collect_0[l, :].T * np.exp(-scaled_tau[None, :] / mu0)
348
+
349
+ if is_antiderivative_wrt_tau:
350
+ if there_is_beam_source:
351
+ u0 = np.einsum(
352
+ "tij, tj -> it",
353
+ GC_collect_0[l, :, :],
354
+ np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]),
355
+ optimize=True,
356
+ ) + B_collect_0[l, :].T * np.exp(-scaled_tau[None, :] / mu0) / (
357
+ -scale_tau[None, l] / mu0
358
+ )
359
+ else:
360
+ u0 = np.einsum(
361
+ "tij, tj -> it",
362
+ GC_collect_0[l, :, :],
363
+ np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]),
364
+ optimize=True,
365
+ )
289
366
  else:
290
- u0 = np.einsum(
291
- "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
292
- )
367
+ if there_is_beam_source:
368
+ u0 = np.einsum(
369
+ "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
370
+ ) + B_collect_0[l, :].T * np.exp(-scaled_tau[None, :] / mu0)
371
+ else:
372
+ u0 = np.einsum(
373
+ "tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
374
+ )
293
375
 
294
376
  # Contribution from particular solution for isotropic internal sources
295
377
  if there_is_iso_source:
@@ -302,10 +384,11 @@ def _assemble_intensity_and_fluxes(
302
384
  G_inv_collect_0,
303
385
  mu_arr,
304
386
  _autograd_compatible,
387
+ is_antiderivative_wrt_tau
305
388
  )
306
- u0 += _mathscr_v_contribution
389
+ u0 = u0 + _mathscr_v_contribution
307
390
 
308
- if return_act_dscale_reclassification:
391
+ if _return_act_dscale_for_reclass:
309
392
  return I0_orig * np.squeeze(u0), act_dscale_reclassification
310
393
  else:
311
394
  return I0_orig * np.squeeze(u0)
@@ -320,7 +403,13 @@ def _assemble_intensity_and_fluxes(
320
403
  B_neg = B_collect_0[:, N:].T
321
404
 
322
405
 
323
- def flux_up(tau):
406
+ def flux_up(tau, is_antiderivative_wrt_tau=False):
407
+ """
408
+ (Energetic) Flux function with argument `tau` (type: array or float) for positive (upward) `mu` values.
409
+ Returns the diffuse flux magnitudes (same type and size as `tau`).
410
+ Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
411
+ to switch to an antiderivative of the function with respect to `tau`.
412
+ """
324
413
  tau = np.atleast_1d(tau)
325
414
  assert np.all(tau >= 0)
326
415
  assert np.all(tau <= tau_arr[-1])
@@ -348,12 +437,18 @@ def _assemble_intensity_and_fluxes(
348
437
  G_inv_collect_0,
349
438
  mu_arr,
350
439
  _autograd_compatible,
440
+ is_antiderivative_wrt_tau
351
441
  )[:N, :]
352
442
  else:
353
443
  _mathscr_v_contribution = 0
354
-
444
+
355
445
  if there_is_beam_source:
356
- direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau[None, :] / mu0)
446
+ if is_antiderivative_wrt_tau:
447
+ direct_beam_contribution = (
448
+ B_pos[:, l] * np.exp(-scaled_tau[None, :] / mu0) / (-scale_tau[None, l] / mu0)
449
+ )
450
+ else:
451
+ direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau[None, :] / mu0)
357
452
  else:
358
453
  direct_beam_contribution = 0
359
454
 
@@ -364,17 +459,36 @@ def _assemble_intensity_and_fluxes(
364
459
  ],
365
460
  axis=-1,
366
461
  )
367
- u0_pos = (
368
- np.einsum("tij, tj -> it", GC_pos[l, :, :], np.exp(exponent))
369
- + direct_beam_contribution
370
- + _mathscr_v_contribution
371
- )
462
+
463
+ if is_antiderivative_wrt_tau:
464
+ u0_pos = (
465
+ np.einsum(
466
+ "tij, tj -> it",
467
+ GC_pos[l, :, :],
468
+ np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]),
469
+ )
470
+ + direct_beam_contribution
471
+ + _mathscr_v_contribution
472
+ )
473
+ else:
474
+ u0_pos = (
475
+ np.einsum("tij, tj -> it", GC_pos[l, :, :], np.exp(exponent))
476
+ + direct_beam_contribution
477
+ + _mathscr_v_contribution
478
+ )
372
479
 
373
480
  flux = 2 * pi * (mu_arr_pos * W) @ u0_pos
374
481
  return I0_orig * np.squeeze(flux)[()]
375
482
 
376
483
 
377
- def flux_down(tau):
484
+ def flux_down(tau, is_antiderivative_wrt_tau=False):
485
+ """
486
+ (Energetic) Flux function with argument `tau` (type: array or float) for negative (downward) `mu` values.
487
+ Returns a tuple of the diffuse and direct flux magnitudes respectively where each entry is of the
488
+ same type and size as `tau`.
489
+ Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
490
+ to switch to an antiderivative of the function with respect to `tau`.
491
+ """
378
492
  tau = np.atleast_1d(tau)
379
493
  assert np.all(tau >= 0)
380
494
  assert np.all(tau <= tau_arr[-1])
@@ -402,14 +516,20 @@ def _assemble_intensity_and_fluxes(
402
516
  G_inv_collect_0,
403
517
  mu_arr,
404
518
  _autograd_compatible,
519
+ is_antiderivative_wrt_tau
405
520
  )[N:, :]
406
521
  else:
407
522
  _mathscr_v_contribution = 0
408
523
 
409
524
  if there_is_beam_source:
410
- direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau[None, :] / mu0)
411
- direct_beam = I0 * mu0 * np.exp(-tau / mu0)
412
- direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0)
525
+ if is_antiderivative_wrt_tau:
526
+ direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau[None, :] / mu0) / (-scale_tau[None, l] / mu0)
527
+ direct_beam = I0 * mu0 * np.exp(-tau / mu0) * -mu0
528
+ direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0) / (-scale_tau[None, l] / mu0)
529
+ else:
530
+ direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau[None, :] / mu0)
531
+ direct_beam = I0 * mu0 * np.exp(-tau / mu0)
532
+ direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0)
413
533
  else:
414
534
  direct_beam_contribution = 0
415
535
  direct_beam = 0
@@ -422,11 +542,19 @@ def _assemble_intensity_and_fluxes(
422
542
  ],
423
543
  axis=-1,
424
544
  )
425
- u0_neg = (
426
- np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent))
427
- + direct_beam_contribution
428
- + _mathscr_v_contribution
429
- )
545
+
546
+ if is_antiderivative_wrt_tau:
547
+ u0_neg = (
548
+ np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent) / (scale_tau[l, None] * K_collect_0[l, :]))
549
+ + direct_beam_contribution
550
+ + _mathscr_v_contribution
551
+ )
552
+ else:
553
+ u0_neg = (
554
+ np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent))
555
+ + direct_beam_contribution
556
+ + _mathscr_v_contribution
557
+ )
430
558
 
431
559
  diffuse_flux = 2 * pi * (mu_arr_pos * W) @ u0_neg + direct_beam_scaled - direct_beam
432
560
  return (
@@ -29,9 +29,9 @@ def _solve_for_coeffs(
29
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.
32
+ Uses the boundary conditions to solve for the unknown coefficients
33
+ of the general solution to the system of ordinary differential equations for each Fourier mode.
34
+ Returns the product of the coefficients and the eigenvectors.
35
35
  This function is wrapped by the `_assemble_intensity_and_fluxes` function.
36
36
  It has many seemingly redundant arguments to maximize precomputation in the `pydisort` function.
37
37
  See the Jupyter Notebook, especially section 3, for documentation, explanation and derivation.