PythonicDISORT 0.6.2__py3-none-any.whl → 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PythonicDISORT/_assemble_intensity_and_fluxes.py +569 -0
- PythonicDISORT/{_solve_for_coefs.py → _solve_for_coeffs.py} +105 -63
- PythonicDISORT/{_diagonalize.py → _solve_for_gen_and_part_sols.py} +54 -15
- PythonicDISORT/pydisort.py +283 -149
- PythonicDISORT/subroutines.py +70 -14
- {PythonicDISORT-0.6.2.dist-info → PythonicDISORT-0.8.0.dist-info}/METADATA +27 -27
- PythonicDISORT-0.8.0.dist-info/RECORD +11 -0
- PythonicDISORT/_assemble_solution_functions.py +0 -385
- PythonicDISORT-0.6.2.dist-info/RECORD +0 -11
- {PythonicDISORT-0.6.2.dist-info → PythonicDISORT-0.8.0.dist-info}/LICENSE.md +0 -0
- {PythonicDISORT-0.6.2.dist-info → PythonicDISORT-0.8.0.dist-info}/WHEEL +0 -0
- {PythonicDISORT-0.6.2.dist-info → PythonicDISORT-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
from PythonicDISORT.subroutines import _mathscr_v
|
|
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
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from math import pi
|
|
7
|
+
|
|
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
|
+
):
|
|
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
|
+
This function is wrapped by the `pydisort` function.
|
|
35
|
+
It should be called through `pydisort` and never directly.
|
|
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
|
|
39
|
+
which this function wraps. See the Jupyter Notebook, especially section 3, for
|
|
40
|
+
documentation, explanation and derivation.
|
|
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` |
|
|
94
|
+
"""
|
|
95
|
+
if _autograd_compatible:
|
|
96
|
+
import autograd.numpy as np
|
|
97
|
+
else:
|
|
98
|
+
import numpy as np
|
|
99
|
+
|
|
100
|
+
################################## Assemble uncorrected intensity and flux functions #######################################
|
|
101
|
+
|
|
102
|
+
# Compute all the necessary quantities
|
|
103
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
104
|
+
outputs = _solve_for_gen_and_part_sols(
|
|
105
|
+
NFourier,
|
|
106
|
+
scaled_omega_arr,
|
|
107
|
+
mu_arr_pos, mu_arr,
|
|
108
|
+
M_inv, W,
|
|
109
|
+
N, NQuad, NLeg,
|
|
110
|
+
NLayers,
|
|
111
|
+
weighted_scaled_Leg_coeffs,
|
|
112
|
+
mu0, I0,
|
|
113
|
+
there_is_beam_source,
|
|
114
|
+
Nscoeffs,
|
|
115
|
+
there_is_iso_source,
|
|
116
|
+
)
|
|
117
|
+
if there_is_beam_source:
|
|
118
|
+
if there_is_iso_source:
|
|
119
|
+
G_collect, K_collect, B_collect, G_inv_collect_0 = outputs
|
|
120
|
+
else:
|
|
121
|
+
G_collect, K_collect, B_collect = outputs
|
|
122
|
+
G_inv_collect_0 = None
|
|
123
|
+
B_collect_0 = B_collect[0, :, :]
|
|
124
|
+
else:
|
|
125
|
+
if there_is_iso_source:
|
|
126
|
+
G_collect, K_collect, G_inv_collect_0 = outputs
|
|
127
|
+
else:
|
|
128
|
+
G_collect, K_collect = outputs
|
|
129
|
+
G_inv_collect_0 = None
|
|
130
|
+
B_collect = None
|
|
131
|
+
|
|
132
|
+
GC_collect = _solve_for_coeffs(
|
|
133
|
+
NFourier,
|
|
134
|
+
G_collect,
|
|
135
|
+
K_collect,
|
|
136
|
+
B_collect,
|
|
137
|
+
G_inv_collect_0,
|
|
138
|
+
tau_arr,
|
|
139
|
+
scaled_tau_arr_with_0,
|
|
140
|
+
mu_arr, mu_arr_pos, mu_arr_pos * W,
|
|
141
|
+
N, NQuad,
|
|
142
|
+
NLayers, NBDRF,
|
|
143
|
+
is_atmos_multilayered,
|
|
144
|
+
BDRF_Fourier_modes,
|
|
145
|
+
mu0, I0,
|
|
146
|
+
there_is_beam_source,
|
|
147
|
+
b_pos, b_neg,
|
|
148
|
+
b_pos_is_scalar, b_neg_is_scalar,
|
|
149
|
+
s_poly_coeffs,
|
|
150
|
+
Nscoeffs,
|
|
151
|
+
there_is_iso_source,
|
|
152
|
+
NLayers_to_use_sparse,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
G_collect_0 = G_collect[0, :, :, :]
|
|
156
|
+
K_collect_0 = K_collect[0, :, :]
|
|
157
|
+
GC_collect_0 = GC_collect[0, :, :, :]
|
|
158
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
if not only_flux:
|
|
161
|
+
|
|
162
|
+
# Construct the intensity function (refer to Section 3.7 of the Comprehensive Documentation)
|
|
163
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
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
|
+
"""
|
|
173
|
+
tau = np.atleast_1d(tau)
|
|
174
|
+
phi = np.atleast_1d(phi)
|
|
175
|
+
assert np.all(tau >= 0)
|
|
176
|
+
assert np.all(tau <= tau_arr[-1])
|
|
177
|
+
|
|
178
|
+
# Atmospheric layer indices
|
|
179
|
+
l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
|
|
180
|
+
scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
|
|
181
|
+
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
182
|
+
|
|
183
|
+
# Delta-M scaling
|
|
184
|
+
if np.any(scale_tau != np.ones(NLayers)):
|
|
185
|
+
tau_dist_from_top = tau_arr[l] - tau
|
|
186
|
+
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
187
|
+
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
188
|
+
else:
|
|
189
|
+
scaled_tau = tau
|
|
190
|
+
|
|
191
|
+
exponent = np.concatenate(
|
|
192
|
+
[
|
|
193
|
+
K_collect[:, l, :N] * (scaled_tau - scaled_tau_arr_lm1)[None, :, None],
|
|
194
|
+
K_collect[:, l, N:] * (scaled_tau - scaled_tau_arr_l)[None, :, None],
|
|
195
|
+
],
|
|
196
|
+
axis=-1,
|
|
197
|
+
)
|
|
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
|
+
)
|
|
216
|
+
else:
|
|
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
|
+
)
|
|
225
|
+
|
|
226
|
+
# Contribution from particular solution for isotropic internal sources
|
|
227
|
+
if there_is_iso_source:
|
|
228
|
+
_mathscr_v_contribution = _mathscr_v(
|
|
229
|
+
tau, l,
|
|
230
|
+
s_poly_coeffs,
|
|
231
|
+
Nscoeffs,
|
|
232
|
+
G_collect_0,
|
|
233
|
+
K_collect_0,
|
|
234
|
+
G_inv_collect_0,
|
|
235
|
+
mu_arr,
|
|
236
|
+
_autograd_compatible,
|
|
237
|
+
is_antiderivative_wrt_tau
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if _autograd_compatible:
|
|
241
|
+
um = um + np.concatenate((_mathscr_v_contribution.T, np.zeros((NFourier - 1, len(tau), NQuad))))
|
|
242
|
+
else:
|
|
243
|
+
um[0, :, :] += _mathscr_v_contribution.T
|
|
244
|
+
|
|
245
|
+
intensities = np.einsum(
|
|
246
|
+
"mti, mp -> itp",
|
|
247
|
+
um,
|
|
248
|
+
np.cos(np.arange(NFourier)[:, None] * (phi0 - phi)[None, :]),
|
|
249
|
+
optimize=True,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if return_Fourier_error:
|
|
253
|
+
exponent = np.concatenate(
|
|
254
|
+
[
|
|
255
|
+
K_collect[-1, l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
256
|
+
K_collect[-1, l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
257
|
+
],
|
|
258
|
+
axis=-1,
|
|
259
|
+
)
|
|
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
|
+
)
|
|
277
|
+
else:
|
|
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
|
+
|
|
287
|
+
intensities_abs = np.abs(intensities)
|
|
288
|
+
Fourier_error = np.max(
|
|
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
|
+
)
|
|
297
|
+
)
|
|
298
|
+
return I0_orig * np.squeeze(intensities), Fourier_error
|
|
299
|
+
else:
|
|
300
|
+
return I0_orig * np.squeeze(intensities)
|
|
301
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
# Construct u0
|
|
304
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
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
|
+
"""
|
|
315
|
+
tau = np.atleast_1d(tau)
|
|
316
|
+
assert np.all(tau >= 0)
|
|
317
|
+
assert np.all(tau <= tau_arr[-1])
|
|
318
|
+
|
|
319
|
+
# Atmospheric layer indices
|
|
320
|
+
l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
|
|
321
|
+
scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
|
|
322
|
+
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
323
|
+
|
|
324
|
+
# Delta-M scaling
|
|
325
|
+
if np.any(scale_tau != np.ones(NLayers)):
|
|
326
|
+
tau_dist_from_top = tau_arr[l] - tau
|
|
327
|
+
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
328
|
+
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
329
|
+
# The following complements the function `subroutines.generate_diff_act_flux_funcs`
|
|
330
|
+
# in performing the reclassification of delta-scaled actinic flux
|
|
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)
|
|
336
|
+
else:
|
|
337
|
+
scaled_tau = tau
|
|
338
|
+
if _return_act_dscale_for_reclass:
|
|
339
|
+
act_dscale_reclassification = 0
|
|
340
|
+
|
|
341
|
+
exponent = np.concatenate(
|
|
342
|
+
[
|
|
343
|
+
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
344
|
+
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
345
|
+
],
|
|
346
|
+
axis=-1,
|
|
347
|
+
)
|
|
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
|
+
)
|
|
366
|
+
else:
|
|
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
|
+
)
|
|
375
|
+
|
|
376
|
+
# Contribution from particular solution for isotropic internal sources
|
|
377
|
+
if there_is_iso_source:
|
|
378
|
+
_mathscr_v_contribution = _mathscr_v(
|
|
379
|
+
tau, l,
|
|
380
|
+
s_poly_coeffs,
|
|
381
|
+
Nscoeffs,
|
|
382
|
+
G_collect_0,
|
|
383
|
+
K_collect_0,
|
|
384
|
+
G_inv_collect_0,
|
|
385
|
+
mu_arr,
|
|
386
|
+
_autograd_compatible,
|
|
387
|
+
is_antiderivative_wrt_tau
|
|
388
|
+
)
|
|
389
|
+
u0 = u0 + _mathscr_v_contribution
|
|
390
|
+
|
|
391
|
+
if _return_act_dscale_for_reclass:
|
|
392
|
+
return I0_orig * np.squeeze(u0), act_dscale_reclassification
|
|
393
|
+
else:
|
|
394
|
+
return I0_orig * np.squeeze(u0)
|
|
395
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
396
|
+
|
|
397
|
+
# Construct the flux functions (refer to Section 3.8 of the Comprehensive Documentation)
|
|
398
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
399
|
+
GC_pos = GC_collect_0[:, :N, :]
|
|
400
|
+
GC_neg = GC_collect_0[:, N:, :]
|
|
401
|
+
if there_is_beam_source:
|
|
402
|
+
B_pos = B_collect_0[:, :N].T
|
|
403
|
+
B_neg = B_collect_0[:, N:].T
|
|
404
|
+
|
|
405
|
+
|
|
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
|
+
"""
|
|
413
|
+
tau = np.atleast_1d(tau)
|
|
414
|
+
assert np.all(tau >= 0)
|
|
415
|
+
assert np.all(tau <= tau_arr[-1])
|
|
416
|
+
|
|
417
|
+
# Atmospheric layer indices
|
|
418
|
+
l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
|
|
419
|
+
scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
|
|
420
|
+
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
421
|
+
|
|
422
|
+
# Delta-M scaling
|
|
423
|
+
if np.any(scale_tau != np.ones(NLayers)):
|
|
424
|
+
tau_dist_from_top = tau_arr[l] - tau
|
|
425
|
+
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
426
|
+
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
427
|
+
else:
|
|
428
|
+
scaled_tau = tau
|
|
429
|
+
|
|
430
|
+
if there_is_iso_source:
|
|
431
|
+
_mathscr_v_contribution = _mathscr_v(
|
|
432
|
+
tau, l,
|
|
433
|
+
s_poly_coeffs,
|
|
434
|
+
Nscoeffs,
|
|
435
|
+
G_collect_0,
|
|
436
|
+
K_collect_0,
|
|
437
|
+
G_inv_collect_0,
|
|
438
|
+
mu_arr,
|
|
439
|
+
_autograd_compatible,
|
|
440
|
+
is_antiderivative_wrt_tau
|
|
441
|
+
)[:N, :]
|
|
442
|
+
else:
|
|
443
|
+
_mathscr_v_contribution = 0
|
|
444
|
+
|
|
445
|
+
if there_is_beam_source:
|
|
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)
|
|
452
|
+
else:
|
|
453
|
+
direct_beam_contribution = 0
|
|
454
|
+
|
|
455
|
+
exponent = np.concatenate(
|
|
456
|
+
[
|
|
457
|
+
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
458
|
+
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
459
|
+
],
|
|
460
|
+
axis=-1,
|
|
461
|
+
)
|
|
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
|
+
)
|
|
479
|
+
|
|
480
|
+
flux = 2 * pi * (mu_arr_pos * W) @ u0_pos
|
|
481
|
+
return I0_orig * np.squeeze(flux)[()]
|
|
482
|
+
|
|
483
|
+
|
|
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
|
+
"""
|
|
492
|
+
tau = np.atleast_1d(tau)
|
|
493
|
+
assert np.all(tau >= 0)
|
|
494
|
+
assert np.all(tau <= tau_arr[-1])
|
|
495
|
+
|
|
496
|
+
# Atmospheric layer indices
|
|
497
|
+
l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
|
|
498
|
+
scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
|
|
499
|
+
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
500
|
+
|
|
501
|
+
# Delta-M scaling
|
|
502
|
+
if np.any(scale_tau != np.ones(NLayers)):
|
|
503
|
+
tau_dist_from_top = tau_arr[l] - tau
|
|
504
|
+
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
505
|
+
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
506
|
+
else:
|
|
507
|
+
scaled_tau = tau
|
|
508
|
+
|
|
509
|
+
if there_is_iso_source:
|
|
510
|
+
_mathscr_v_contribution = _mathscr_v(
|
|
511
|
+
tau, l,
|
|
512
|
+
s_poly_coeffs,
|
|
513
|
+
Nscoeffs,
|
|
514
|
+
G_collect_0,
|
|
515
|
+
K_collect_0,
|
|
516
|
+
G_inv_collect_0,
|
|
517
|
+
mu_arr,
|
|
518
|
+
_autograd_compatible,
|
|
519
|
+
is_antiderivative_wrt_tau
|
|
520
|
+
)[N:, :]
|
|
521
|
+
else:
|
|
522
|
+
_mathscr_v_contribution = 0
|
|
523
|
+
|
|
524
|
+
if there_is_beam_source:
|
|
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)
|
|
533
|
+
else:
|
|
534
|
+
direct_beam_contribution = 0
|
|
535
|
+
direct_beam = 0
|
|
536
|
+
direct_beam_scaled = 0
|
|
537
|
+
|
|
538
|
+
exponent = np.concatenate(
|
|
539
|
+
[
|
|
540
|
+
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
541
|
+
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
542
|
+
],
|
|
543
|
+
axis=-1,
|
|
544
|
+
)
|
|
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
|
+
)
|
|
558
|
+
|
|
559
|
+
diffuse_flux = 2 * pi * (mu_arr_pos * W) @ u0_neg + direct_beam_scaled - direct_beam
|
|
560
|
+
return (
|
|
561
|
+
I0_orig * np.squeeze(diffuse_flux)[()],
|
|
562
|
+
I0_orig * I0 * np.squeeze(direct_beam)[()],
|
|
563
|
+
)
|
|
564
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
565
|
+
|
|
566
|
+
if only_flux:
|
|
567
|
+
return flux_up, flux_down, u0
|
|
568
|
+
else:
|
|
569
|
+
return flux_up, flux_down, u0, u
|