PythonicDISORT 0.6.1__py3-none-any.whl → 0.9.2__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 +594 -0
- PythonicDISORT/_solve_for_coeffs.py +308 -0
- PythonicDISORT/_solve_for_gen_and_part_sols.py +237 -0
- PythonicDISORT/pydisort.py +367 -204
- PythonicDISORT/subroutines.py +220 -46
- {PythonicDISORT-0.6.1.dist-info → PythonicDISORT-0.9.2.dist-info}/METADATA +30 -28
- PythonicDISORT-0.9.2.dist-info/RECORD +11 -0
- {PythonicDISORT-0.6.1.dist-info → PythonicDISORT-0.9.2.dist-info}/WHEEL +1 -1
- PythonicDISORT/_assemble_solution_functions.py +0 -385
- PythonicDISORT/_diagonalize.py +0 -194
- PythonicDISORT/_solve_for_coefs.py +0 -331
- PythonicDISORT-0.6.1.dist-info/RECORD +0 -11
- {PythonicDISORT-0.6.1.dist-info → PythonicDISORT-0.9.2.dist-info}/LICENSE.md +0 -0
- {PythonicDISORT-0.6.1.dist-info → PythonicDISORT-0.9.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,594 @@
|
|
|
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
|
+
Nscoeffs, # Number of isotropic source polynomial coefficients
|
|
24
|
+
s_poly_coeffs, # Polynomial coefficients of isotropic source
|
|
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
|
+
use_banded_solver_NLayers, # Number of layers above or equal which to use `scipy.linalg.solve_banded`
|
|
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
|
+
| `Nscoeffs` | scalar |
|
|
71
|
+
| `s_poly_coeffs` | `NLayers x Nscoeffs` or `Nscoeffs` |
|
|
72
|
+
| `there_is_iso_source` | boolean |
|
|
73
|
+
| `scale_tau` | `NLayers` |
|
|
74
|
+
| `only_flux` | boolean |
|
|
75
|
+
| `use_banded_solver_NLayers` | 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 and there_is_iso_source:
|
|
118
|
+
G_collect, K_collect, B_collect, G_inv_collect_0 = outputs
|
|
119
|
+
B_collect_0 = B_collect[0, :, :]
|
|
120
|
+
elif there_is_beam_source and not there_is_iso_source:
|
|
121
|
+
G_collect, K_collect, B_collect = outputs
|
|
122
|
+
B_collect_0 = B_collect[0, :, :]
|
|
123
|
+
G_inv_collect_0 = None
|
|
124
|
+
elif not there_is_beam_source and there_is_iso_source:
|
|
125
|
+
G_collect, K_collect, G_inv_collect_0 = outputs
|
|
126
|
+
B_collect = None
|
|
127
|
+
else:
|
|
128
|
+
G_collect, K_collect = outputs
|
|
129
|
+
B_collect = None
|
|
130
|
+
G_inv_collect_0 = None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
GC_collect = _solve_for_coeffs(
|
|
134
|
+
NFourier,
|
|
135
|
+
G_collect,
|
|
136
|
+
K_collect,
|
|
137
|
+
B_collect,
|
|
138
|
+
G_inv_collect_0,
|
|
139
|
+
tau_arr,
|
|
140
|
+
scaled_tau_arr_with_0,
|
|
141
|
+
mu_arr, mu_arr_pos, mu_arr_pos * W,
|
|
142
|
+
N, NQuad,
|
|
143
|
+
NLayers, NBDRF,
|
|
144
|
+
is_atmos_multilayered,
|
|
145
|
+
BDRF_Fourier_modes,
|
|
146
|
+
mu0, I0,
|
|
147
|
+
there_is_beam_source,
|
|
148
|
+
b_pos, b_neg,
|
|
149
|
+
b_pos_is_scalar, b_neg_is_scalar,
|
|
150
|
+
Nscoeffs,
|
|
151
|
+
s_poly_coeffs,
|
|
152
|
+
there_is_iso_source,
|
|
153
|
+
use_banded_solver_NLayers,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
G_collect_0 = G_collect[0, :, :, :]
|
|
157
|
+
K_collect_0 = K_collect[0, :, :]
|
|
158
|
+
GC_collect_0 = GC_collect[0, :, :, :]
|
|
159
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
if not only_flux:
|
|
162
|
+
|
|
163
|
+
# Construct the intensity function (refer to Section 3.7 of the Comprehensive Documentation)
|
|
164
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
165
|
+
def u(tau, phi, is_antiderivative_wrt_tau=False, return_Fourier_error=False, return_tau_arr=False):
|
|
166
|
+
"""
|
|
167
|
+
Intensity function with arguments `(tau, phi)` of types `(array or float, array or float)`.
|
|
168
|
+
Returns an ndarray with axes corresponding to variation with `mu, tau, phi` respectively.
|
|
169
|
+
Pass `return_Fourier_error = True` (defaults to `False`) to return the
|
|
170
|
+
Cauchy / Fourier convergence evaluation (type: float) for the last Fourier term.
|
|
171
|
+
Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
|
|
172
|
+
to switch to an antiderivative of the function with respect to `tau`.
|
|
173
|
+
"""
|
|
174
|
+
tau = np.atleast_1d(tau)
|
|
175
|
+
phi = np.atleast_1d(phi)
|
|
176
|
+
if np.all(tau < 0) or np.all(tau > tau_arr[-1]):
|
|
177
|
+
raise ValueError("tau input outside the tau range specified for the atmosphere (check `tau_arr`).")
|
|
178
|
+
|
|
179
|
+
# Atmospheric layer indices
|
|
180
|
+
l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
|
|
181
|
+
scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
|
|
182
|
+
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
183
|
+
|
|
184
|
+
# Delta-M scaling
|
|
185
|
+
if np.any(scale_tau != np.ones(NLayers)):
|
|
186
|
+
tau_dist_from_top = tau_arr[l] - tau
|
|
187
|
+
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
188
|
+
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
189
|
+
else:
|
|
190
|
+
scaled_tau = tau
|
|
191
|
+
|
|
192
|
+
exponent = np.concatenate(
|
|
193
|
+
[
|
|
194
|
+
K_collect[:, l, :N] * (scaled_tau - scaled_tau_arr_lm1)[None, :, None],
|
|
195
|
+
K_collect[:, l, N:] * (scaled_tau - scaled_tau_arr_l)[None, :, None],
|
|
196
|
+
],
|
|
197
|
+
axis=-1,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if is_antiderivative_wrt_tau and there_is_beam_source:
|
|
201
|
+
um = (
|
|
202
|
+
np.einsum(
|
|
203
|
+
"mtij, mtj -> mti",
|
|
204
|
+
GC_collect[:, l, :, :],
|
|
205
|
+
np.exp(exponent) / (scale_tau[None, :, None] * K_collect)[:, l, :],
|
|
206
|
+
optimize=True,
|
|
207
|
+
)
|
|
208
|
+
+ (B_collect / (-scale_tau / mu0)[None, :, None])[:, l, :]
|
|
209
|
+
* np.exp(-scaled_tau / mu0)[None, :, None]
|
|
210
|
+
)
|
|
211
|
+
elif is_antiderivative_wrt_tau and not there_is_beam_source:
|
|
212
|
+
um = np.einsum(
|
|
213
|
+
"mtij, mtj -> mti",
|
|
214
|
+
GC_collect[:, l, :, :],
|
|
215
|
+
np.exp(exponent) / (scale_tau[None, l, None] * K_collect)[:, l, :],
|
|
216
|
+
optimize=True,
|
|
217
|
+
)
|
|
218
|
+
elif not is_antiderivative_wrt_tau and there_is_beam_source:
|
|
219
|
+
um = np.einsum(
|
|
220
|
+
"mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
|
|
221
|
+
) + B_collect[:, l, :] * np.exp(-scaled_tau / mu0)[None, :, None]
|
|
222
|
+
else:
|
|
223
|
+
um = np.einsum(
|
|
224
|
+
"mtij, mtj -> mti", GC_collect[:, l, :, :], np.exp(exponent), optimize=True
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Contribution from particular solution for isotropic internal sources
|
|
228
|
+
if there_is_iso_source:
|
|
229
|
+
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
230
|
+
_mathscr_v_contribution = _mathscr_v(
|
|
231
|
+
tau, l_inv, Nscoeffs,
|
|
232
|
+
s_poly_coeffs[l_uniq],
|
|
233
|
+
G_collect_0[l_uniq],
|
|
234
|
+
K_collect_0[l_uniq],
|
|
235
|
+
G_inv_collect_0[l_uniq],
|
|
236
|
+
mu_arr,
|
|
237
|
+
is_antiderivative_wrt_tau,
|
|
238
|
+
autograd_compatible
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if NFourier == 1:
|
|
242
|
+
um = um + _mathscr_v_contribution.T
|
|
243
|
+
elif autograd_compatible:
|
|
244
|
+
um = um + np.concatenate((_mathscr_v_contribution.T, np.zeros((NFourier - 1, len(tau), NQuad))))
|
|
245
|
+
else:
|
|
246
|
+
um[0, :, :] += _mathscr_v_contribution.T
|
|
247
|
+
|
|
248
|
+
u = np.einsum(
|
|
249
|
+
"mti, mp -> itp",
|
|
250
|
+
um,
|
|
251
|
+
np.cos(np.arange(NFourier)[:, None] * (phi0 - phi)[None, :]),
|
|
252
|
+
optimize=True,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if return_Fourier_error:
|
|
256
|
+
exponent = np.concatenate(
|
|
257
|
+
[
|
|
258
|
+
K_collect[-1, l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
259
|
+
K_collect[-1, l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
260
|
+
],
|
|
261
|
+
axis=-1,
|
|
262
|
+
)
|
|
263
|
+
if is_antiderivative_wrt_tau and there_is_beam_source:
|
|
264
|
+
ulast = (
|
|
265
|
+
np.einsum(
|
|
266
|
+
"tij, tj -> it",
|
|
267
|
+
GC_collect[-1, l, :, :],
|
|
268
|
+
np.exp(exponent) / (scale_tau[:, None] * K_collect[-1, :, :])[l, :],
|
|
269
|
+
optimize=True,
|
|
270
|
+
)
|
|
271
|
+
+ (B_collect.T[-1, :, :] / (-scale_tau / mu0)[None, :])[:, l]
|
|
272
|
+
* np.exp(-scaled_tau / mu0)[None, :]
|
|
273
|
+
)
|
|
274
|
+
elif is_antiderivative_wrt_tau and not there_is_beam_source:
|
|
275
|
+
ulast = np.einsum(
|
|
276
|
+
"tij, tj -> it",
|
|
277
|
+
GC_collect[-1, l, :, :],
|
|
278
|
+
np.exp(exponent) / (scale_tau[:, None] * K_collect[-1, :, :])[l, :],
|
|
279
|
+
optimize=True,
|
|
280
|
+
)
|
|
281
|
+
elif not is_antiderivative_wrt_tau and not there_is_beam_source:
|
|
282
|
+
ulast = np.einsum(
|
|
283
|
+
"tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
|
|
284
|
+
) + B_collect.T[-1, :, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
285
|
+
else:
|
|
286
|
+
ulast = np.einsum(
|
|
287
|
+
"tij, tj -> it", GC_collect[-1, l, :, :], np.exp(exponent), optimize=True
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
u_abs = np.abs(u)
|
|
291
|
+
Fourier_error = np.max(
|
|
292
|
+
np.divide(
|
|
293
|
+
np.abs(
|
|
294
|
+
ulast[:, :, None] * np.cos((NFourier - 1) * (phi0 - phi))[None, None, :]
|
|
295
|
+
),
|
|
296
|
+
u_abs,
|
|
297
|
+
out=np.zeros_like(u_abs),
|
|
298
|
+
where=u_abs > 1e-15,
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if return_tau_arr:
|
|
303
|
+
return I0_orig * np.squeeze(u), Fourier_error, tau_arr
|
|
304
|
+
else:
|
|
305
|
+
return I0_orig * np.squeeze(u), Fourier_error
|
|
306
|
+
else:
|
|
307
|
+
if return_tau_arr:
|
|
308
|
+
return I0_orig * np.squeeze(u), tau_arr
|
|
309
|
+
else:
|
|
310
|
+
return I0_orig * np.squeeze(u)
|
|
311
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
# Construct u0
|
|
314
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
315
|
+
def u0(tau, is_antiderivative_wrt_tau=False, return_tau_arr=False, _return_act_dscale_for_reclass=False):
|
|
316
|
+
"""
|
|
317
|
+
Zeroth Fourier mode of the intensity with argument `tau` (type: array or float).
|
|
318
|
+
Returns an ndarray with axes corresponding to variation with `mu` and `tau` respectively.
|
|
319
|
+
This function is useful for calculating actinic fluxes and other quantities of interest,
|
|
320
|
+
but reclassification of delta-scaled flux and other corrections must be done manually
|
|
321
|
+
(for actinic flux `generate_diff_act_flux_funcs` will automatically perform the reclassification).
|
|
322
|
+
Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
|
|
323
|
+
to switch to an antiderivative of the function with respect to `tau`.
|
|
324
|
+
"""
|
|
325
|
+
tau = np.atleast_1d(tau)
|
|
326
|
+
if np.all(tau < 0) or np.all(tau > tau_arr[-1]):
|
|
327
|
+
raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
|
|
328
|
+
|
|
329
|
+
# Atmospheric layer indices
|
|
330
|
+
l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
|
|
331
|
+
scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
|
|
332
|
+
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
333
|
+
|
|
334
|
+
# Delta-M scaling
|
|
335
|
+
if np.any(scale_tau != np.ones(NLayers)):
|
|
336
|
+
tau_dist_from_top = tau_arr[l] - tau
|
|
337
|
+
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
338
|
+
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
339
|
+
# The following complements the function `subroutines.generate_diff_act_flux_funcs`
|
|
340
|
+
# in performing the reclassification of delta-scaled actinic flux
|
|
341
|
+
if _return_act_dscale_for_reclass:
|
|
342
|
+
if is_antiderivative_wrt_tau:
|
|
343
|
+
act_dscale_reclassification = (
|
|
344
|
+
I0 * np.exp(-scaled_tau / mu0) / (-scale_tau[l] / mu0)
|
|
345
|
+
- I0 * np.exp(-tau / mu0) * -mu0
|
|
346
|
+
)
|
|
347
|
+
else:
|
|
348
|
+
act_dscale_reclassification = I0 * np.exp(-scaled_tau / mu0) - I0 * np.exp(-tau / mu0)
|
|
349
|
+
else:
|
|
350
|
+
scaled_tau = tau
|
|
351
|
+
if _return_act_dscale_for_reclass:
|
|
352
|
+
act_dscale_reclassification = 0
|
|
353
|
+
|
|
354
|
+
exponent = np.concatenate(
|
|
355
|
+
[
|
|
356
|
+
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
357
|
+
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
358
|
+
],
|
|
359
|
+
axis=-1,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
if is_antiderivative_wrt_tau and there_is_beam_source:
|
|
363
|
+
u0 = (
|
|
364
|
+
np.einsum(
|
|
365
|
+
"tij, tj -> it",
|
|
366
|
+
GC_collect_0[l, :, :],
|
|
367
|
+
np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
|
|
368
|
+
optimize=True,
|
|
369
|
+
)
|
|
370
|
+
+ (B_collect_0.T / (-scale_tau / mu0)[None, :])[:, l]
|
|
371
|
+
* np.exp(-scaled_tau / mu0)[None, :]
|
|
372
|
+
)
|
|
373
|
+
if is_antiderivative_wrt_tau and not there_is_beam_source:
|
|
374
|
+
u0 = np.einsum(
|
|
375
|
+
"tij, tj -> it",
|
|
376
|
+
GC_collect_0[l, :, :],
|
|
377
|
+
np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
|
|
378
|
+
optimize=True,
|
|
379
|
+
)
|
|
380
|
+
if not is_antiderivative_wrt_tau and there_is_beam_source:
|
|
381
|
+
u0 = np.einsum(
|
|
382
|
+
"tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
|
|
383
|
+
) + B_collect_0.T[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
384
|
+
else:
|
|
385
|
+
u0 = np.einsum(
|
|
386
|
+
"tij, tj -> it", GC_collect_0[l, :, :], np.exp(exponent), optimize=True
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Contribution from particular solution for isotropic internal sources
|
|
390
|
+
if there_is_iso_source:
|
|
391
|
+
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
392
|
+
_mathscr_v_contribution = _mathscr_v(
|
|
393
|
+
tau, l_inv, Nscoeffs,
|
|
394
|
+
s_poly_coeffs[l_uniq],
|
|
395
|
+
G_collect_0[l_uniq],
|
|
396
|
+
K_collect_0[l_uniq],
|
|
397
|
+
G_inv_collect_0[l_uniq],
|
|
398
|
+
mu_arr,
|
|
399
|
+
is_antiderivative_wrt_tau,
|
|
400
|
+
autograd_compatible
|
|
401
|
+
)
|
|
402
|
+
u0 = u0 + _mathscr_v_contribution
|
|
403
|
+
|
|
404
|
+
if return_tau_arr and _return_act_dscale_for_reclass:
|
|
405
|
+
return I0_orig * np.squeeze(u0), tau_arr, act_dscale_reclassification
|
|
406
|
+
elif return_tau_arr and not _return_act_dscale_for_reclass:
|
|
407
|
+
return I0_orig * np.squeeze(u0), tau_arr
|
|
408
|
+
elif not return_tau_arr and _return_act_dscale_for_reclass:
|
|
409
|
+
return I0_orig * np.squeeze(u0), act_dscale_reclassification
|
|
410
|
+
else:
|
|
411
|
+
return I0_orig * np.squeeze(u0)
|
|
412
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
413
|
+
|
|
414
|
+
# Construct the flux functions (refer to Section 3.8 of the Comprehensive Documentation)
|
|
415
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
416
|
+
GC_pos = GC_collect_0[:, :N, :]
|
|
417
|
+
GC_neg = GC_collect_0[:, N:, :]
|
|
418
|
+
if there_is_beam_source:
|
|
419
|
+
B_pos = B_collect_0[:, :N].T
|
|
420
|
+
B_neg = B_collect_0[:, N:].T
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def flux_up(tau, is_antiderivative_wrt_tau=False, return_tau_arr=False):
|
|
424
|
+
"""
|
|
425
|
+
(Energetic) Flux function with argument `tau` (type: array or float) for positive (upward) `mu` values.
|
|
426
|
+
Returns the diffuse flux magnitudes (same type and size as `tau`).
|
|
427
|
+
Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
|
|
428
|
+
to switch to an antiderivative of the function with respect to `tau`.
|
|
429
|
+
"""
|
|
430
|
+
tau = np.atleast_1d(tau)
|
|
431
|
+
if np.all(tau < 0) or np.all(tau > tau_arr[-1]):
|
|
432
|
+
raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
|
|
433
|
+
|
|
434
|
+
# Atmospheric layer indices
|
|
435
|
+
l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
|
|
436
|
+
scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
|
|
437
|
+
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
438
|
+
|
|
439
|
+
# Delta-M scaling
|
|
440
|
+
if np.any(scale_tau != np.ones(NLayers)):
|
|
441
|
+
tau_dist_from_top = tau_arr[l] - tau
|
|
442
|
+
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
443
|
+
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
444
|
+
else:
|
|
445
|
+
scaled_tau = tau
|
|
446
|
+
|
|
447
|
+
if there_is_iso_source:
|
|
448
|
+
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
449
|
+
_mathscr_v_contribution = _mathscr_v(
|
|
450
|
+
tau, l_inv, Nscoeffs,
|
|
451
|
+
s_poly_coeffs[l_uniq],
|
|
452
|
+
G_collect_0[l_uniq, :N, :],
|
|
453
|
+
K_collect_0[l_uniq],
|
|
454
|
+
G_inv_collect_0[l_uniq],
|
|
455
|
+
mu_arr,
|
|
456
|
+
is_antiderivative_wrt_tau,
|
|
457
|
+
autograd_compatible
|
|
458
|
+
)
|
|
459
|
+
else:
|
|
460
|
+
_mathscr_v_contribution = 0
|
|
461
|
+
|
|
462
|
+
if there_is_beam_source and is_antiderivative_wrt_tau:
|
|
463
|
+
direct_beam_contribution = (
|
|
464
|
+
(B_pos / (-scale_tau / mu0)[None, :])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
465
|
+
)
|
|
466
|
+
elif there_is_beam_source and not is_antiderivative_wrt_tau:
|
|
467
|
+
direct_beam_contribution = B_pos[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
468
|
+
else:
|
|
469
|
+
direct_beam_contribution = 0
|
|
470
|
+
|
|
471
|
+
exponent = np.concatenate(
|
|
472
|
+
[
|
|
473
|
+
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
474
|
+
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
475
|
+
],
|
|
476
|
+
axis=-1,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
if is_antiderivative_wrt_tau:
|
|
480
|
+
u0_pos = (
|
|
481
|
+
np.einsum(
|
|
482
|
+
"tij, tj -> it",
|
|
483
|
+
GC_pos[l, :, :],
|
|
484
|
+
np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :],
|
|
485
|
+
)
|
|
486
|
+
+ direct_beam_contribution
|
|
487
|
+
+ _mathscr_v_contribution
|
|
488
|
+
)
|
|
489
|
+
else:
|
|
490
|
+
u0_pos = (
|
|
491
|
+
np.einsum("tij, tj -> it", GC_pos[l, :, :], np.exp(exponent))
|
|
492
|
+
+ direct_beam_contribution
|
|
493
|
+
+ _mathscr_v_contribution
|
|
494
|
+
)
|
|
495
|
+
flux = 2 * pi * (mu_arr_pos * W) @ u0_pos
|
|
496
|
+
|
|
497
|
+
if return_tau_arr:
|
|
498
|
+
return I0_orig * np.squeeze(flux)[()], tau_arr
|
|
499
|
+
else:
|
|
500
|
+
return I0_orig * np.squeeze(flux)[()]
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def flux_down(tau, is_antiderivative_wrt_tau=False, return_tau_arr=False):
|
|
504
|
+
"""
|
|
505
|
+
(Energetic) Flux function with argument `tau` (type: array or float) for negative (downward) `mu` values.
|
|
506
|
+
Returns a tuple of the diffuse and direct flux magnitudes respectively where each entry is of the
|
|
507
|
+
same type and size as `tau`.
|
|
508
|
+
Pass `is_antiderivative_wrt_tau = True` (defaults to `False`)
|
|
509
|
+
to switch to an antiderivative of the function with respect to `tau`.
|
|
510
|
+
"""
|
|
511
|
+
tau = np.atleast_1d(tau)
|
|
512
|
+
if np.all(tau < 0) or np.all(tau > tau_arr[-1]):
|
|
513
|
+
raise ValueError("tau input outside the tau range given for the atmosphere (check `tau_arr`).")
|
|
514
|
+
|
|
515
|
+
# Atmospheric layer indices
|
|
516
|
+
l = np.argmax(tau[:, None] <= tau_arr[None, :], axis=1)
|
|
517
|
+
scaled_tau_arr_l = scaled_tau_arr_with_0[l + 1]
|
|
518
|
+
scaled_tau_arr_lm1 = scaled_tau_arr_with_0[l]
|
|
519
|
+
|
|
520
|
+
# Delta-M scaling
|
|
521
|
+
if np.any(scale_tau != np.ones(NLayers)):
|
|
522
|
+
tau_dist_from_top = tau_arr[l] - tau
|
|
523
|
+
scaled_tau_dist_from_top = tau_dist_from_top * scale_tau[l]
|
|
524
|
+
scaled_tau = scaled_tau_arr_l - scaled_tau_dist_from_top
|
|
525
|
+
else:
|
|
526
|
+
scaled_tau = tau
|
|
527
|
+
|
|
528
|
+
if there_is_iso_source:
|
|
529
|
+
l_uniq, l_inv = np.unique(l, return_inverse=True)
|
|
530
|
+
_mathscr_v_contribution = _mathscr_v(
|
|
531
|
+
tau, l_inv, Nscoeffs,
|
|
532
|
+
s_poly_coeffs[l_uniq],
|
|
533
|
+
G_collect_0[l_uniq, N:, :],
|
|
534
|
+
K_collect_0[l_uniq],
|
|
535
|
+
G_inv_collect_0[l_uniq],
|
|
536
|
+
mu_arr,
|
|
537
|
+
is_antiderivative_wrt_tau,
|
|
538
|
+
autograd_compatible
|
|
539
|
+
)
|
|
540
|
+
else:
|
|
541
|
+
_mathscr_v_contribution = 0
|
|
542
|
+
|
|
543
|
+
if there_is_beam_source and is_antiderivative_wrt_tau:
|
|
544
|
+
direct_beam_contribution = (B_neg / (-scale_tau / mu0)[None, l])[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
545
|
+
direct_beam = I0 * mu0 * np.exp(-tau / mu0) * -mu0
|
|
546
|
+
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0) / (-scale_tau / mu0)[None, l]
|
|
547
|
+
elif there_is_beam_source and not is_antiderivative_wrt_tau:
|
|
548
|
+
direct_beam_contribution = B_neg[:, l] * np.exp(-scaled_tau / mu0)[None, :]
|
|
549
|
+
direct_beam = I0 * mu0 * np.exp(-tau / mu0)
|
|
550
|
+
direct_beam_scaled = I0 * mu0 * np.exp(-scaled_tau / mu0)
|
|
551
|
+
else:
|
|
552
|
+
direct_beam_contribution = 0
|
|
553
|
+
direct_beam = 0
|
|
554
|
+
direct_beam_scaled = 0
|
|
555
|
+
|
|
556
|
+
exponent = np.concatenate(
|
|
557
|
+
[
|
|
558
|
+
K_collect_0[l, :N] * (scaled_tau - scaled_tau_arr_lm1)[:, None],
|
|
559
|
+
K_collect_0[l, N:] * (scaled_tau - scaled_tau_arr_l)[:, None],
|
|
560
|
+
],
|
|
561
|
+
axis=-1,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
if is_antiderivative_wrt_tau:
|
|
565
|
+
u0_neg = (
|
|
566
|
+
np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent) / (scale_tau[:, None] * K_collect_0)[l, :])
|
|
567
|
+
+ direct_beam_contribution
|
|
568
|
+
+ _mathscr_v_contribution
|
|
569
|
+
)
|
|
570
|
+
else:
|
|
571
|
+
u0_neg = (
|
|
572
|
+
np.einsum("tij, tj -> it", GC_neg[l, :, :], np.exp(exponent))
|
|
573
|
+
+ direct_beam_contribution
|
|
574
|
+
+ _mathscr_v_contribution
|
|
575
|
+
)
|
|
576
|
+
diffuse_flux = 2 * pi * (mu_arr_pos * W) @ u0_neg + direct_beam_scaled - direct_beam
|
|
577
|
+
|
|
578
|
+
if return_tau_arr:
|
|
579
|
+
return (
|
|
580
|
+
I0_orig * np.squeeze(diffuse_flux)[()],
|
|
581
|
+
I0_orig * I0 * np.squeeze(direct_beam)[()],
|
|
582
|
+
tau_arr
|
|
583
|
+
)
|
|
584
|
+
else:
|
|
585
|
+
return (
|
|
586
|
+
I0_orig * np.squeeze(diffuse_flux)[()],
|
|
587
|
+
I0_orig * I0 * np.squeeze(direct_beam)[()],
|
|
588
|
+
)
|
|
589
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
590
|
+
|
|
591
|
+
if only_flux:
|
|
592
|
+
return flux_up, flux_down, u0, G_collect_0, K_collect_0 # TODO: Remove the temporary G_collect_0, K_collect_0
|
|
593
|
+
else:
|
|
594
|
+
return flux_up, flux_down, u0, u
|