PythonicDISORT 1.2__py3-none-any.whl → 1.4__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 +57 -49
- PythonicDISORT/_solve_for_coeffs.py +208 -142
- PythonicDISORT/_solve_for_gen_and_part_sols.py +75 -69
- PythonicDISORT/pydisort.py +198 -197
- PythonicDISORT/subroutines.py +185 -112
- {pythonicdisort-1.2.dist-info → pythonicdisort-1.4.dist-info}/METADATA +3 -2
- pythonicdisort-1.4.dist-info/RECORD +11 -0
- {pythonicdisort-1.2.dist-info → pythonicdisort-1.4.dist-info}/WHEEL +1 -1
- pythonicdisort-1.2.dist-info/RECORD +0 -11
- {pythonicdisort-1.2.dist-info → pythonicdisort-1.4.dist-info}/LICENSE.md +0 -0
- {pythonicdisort-1.2.dist-info → pythonicdisort-1.4.dist-info}/top_level.txt +0 -0
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import scipy as sc
|
|
3
|
-
from math import pi
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
def _solve_for_gen_and_part_sols(
|
|
7
6
|
NFourier, # Number of intensity Fourier modes
|
|
8
7
|
scaled_omega_arr, # Delta-scaled single-scattering albedos
|
|
9
|
-
mu_arr_pos,
|
|
10
|
-
M_inv, W, # 1) 1 /
|
|
8
|
+
mu_arr_pos, # Upper hemisphere quadrature nodes (cosine polar angles)
|
|
9
|
+
M_inv, W, # 1) 1 / `mu_arr_pos`; 2) quadrature weights for each hemisphere
|
|
11
10
|
N, NQuad, NLeg, # Number of 1) upper 2) both hemispheres quadrature nodes; 3) phase function Legendre coefficients
|
|
12
11
|
NLayers, # Number of layers
|
|
13
12
|
weighted_scaled_Leg_coeffs, # Weighted and delta-scaled Legendre coefficients
|
|
14
|
-
mu0,
|
|
13
|
+
mu0, I0_div_4pi, # Primary properties of the direct beam
|
|
15
14
|
there_is_beam_source, # Is there a beam source?
|
|
16
|
-
Nscoeffs, # Number of isotropic source polynomial coefficients
|
|
17
15
|
there_is_iso_source, # Is there an isotropic source?
|
|
18
16
|
):
|
|
19
17
|
"""
|
|
@@ -31,7 +29,6 @@ def _solve_for_gen_and_part_sols(
|
|
|
31
29
|
| `NFourier` | scalar |
|
|
32
30
|
| `scaled_omega_arr` | `NLayers` |
|
|
33
31
|
| `mu_arr_pos` | `NQuad/2` |
|
|
34
|
-
| `mu_arr` | `NQuad` |
|
|
35
32
|
| `M_inv` | `NQuad/2` |
|
|
36
33
|
| `W` | `NQuad/2` |
|
|
37
34
|
| `N` | scalar |
|
|
@@ -40,9 +37,8 @@ def _solve_for_gen_and_part_sols(
|
|
|
40
37
|
| `NLayers` | scalar |
|
|
41
38
|
| `weighted_scaled_Leg_coeffs` | `NLayers x NLeg` |
|
|
42
39
|
| `mu0` | scalar |
|
|
43
|
-
| `
|
|
40
|
+
| `I0_div_4pi` | scalar |
|
|
44
41
|
| `there_is_beam_source` | boolean |
|
|
45
|
-
| `Nscoeffs` | scalar |
|
|
46
42
|
| `there_is_iso_source` | boolean |
|
|
47
43
|
|
|
48
44
|
Notable internal variables of _solve_for_gen_and_part_sols
|
|
@@ -65,16 +61,25 @@ def _solve_for_gen_and_part_sols(
|
|
|
65
61
|
# Initialization
|
|
66
62
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
67
63
|
ells_all = np.arange(NLeg)
|
|
68
|
-
|
|
64
|
+
signs_all = np.ones(NLeg)
|
|
65
|
+
signs_all[1::2] = -1
|
|
66
|
+
|
|
67
|
+
G_trivial = np.zeros((NQuad, NQuad))
|
|
68
|
+
stride = NQuad + 1
|
|
69
|
+
G_trivial.ravel()[N : N + N * stride : stride] = 1
|
|
70
|
+
G_trivial.ravel()[NQuad * N : NQuad * N + N * stride : stride] = 1
|
|
69
71
|
|
|
70
72
|
G_collect = np.empty((NFourier * NLayers, NQuad, NQuad))
|
|
71
73
|
K_collect = np.empty((NFourier * NLayers, NQuad))
|
|
72
|
-
|
|
73
|
-
beta_arr = np.empty((NFourier * NLayers, N, N))
|
|
74
|
+
A_arr = np.empty((NFourier * NLayers, NQuad, NQuad))
|
|
74
75
|
no_shortcut_indices = []
|
|
75
76
|
no_shortcut_indices_0 = []
|
|
77
|
+
ind = 0
|
|
76
78
|
|
|
77
79
|
if there_is_beam_source:
|
|
80
|
+
mu_arr_pos_mu0 = np.append(mu_arr_pos, -mu0)
|
|
81
|
+
|
|
82
|
+
X_arr = np.empty((NFourier * NLayers, NQuad))
|
|
78
83
|
B_collect = np.zeros((NFourier * NLayers, NQuad))
|
|
79
84
|
if there_is_iso_source:
|
|
80
85
|
G_inv_collect_0 = np.empty((NLayers, NQuad, NQuad))
|
|
@@ -88,20 +93,26 @@ def _solve_for_gen_and_part_sols(
|
|
|
88
93
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
89
94
|
m_equals_0 = (m == 0)
|
|
90
95
|
ells = ells_all[m:]
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
poch = sc.special.poch(ells + m + 1, -2 * m)
|
|
97
|
+
signs = signs_all[:NLeg - m]
|
|
98
|
+
|
|
99
|
+
if there_is_beam_source:
|
|
100
|
+
degree_tile = np.broadcast_to(ells[:, None], (NLeg - m, N + 1))
|
|
101
|
+
lpmv_all = sc.special.lpmv(m, degree_tile, mu_arr_pos_mu0)
|
|
102
|
+
asso_leg_term_pos = lpmv_all[:, :-1]
|
|
103
|
+
scalar_fac_asso_leg_term_mu0 = I0_div_4pi * (2 - m_equals_0) * poch * lpmv_all[:, -1]
|
|
104
|
+
else:
|
|
105
|
+
degree_tile = np.broadcast_to(ells[:, None], (NLeg - m, N))
|
|
106
|
+
asso_leg_term_pos = sc.special.lpmv(m, degree_tile, mu_arr_pos)
|
|
107
|
+
|
|
97
108
|
asso_leg_term_neg = asso_leg_term_pos * signs[:, None]
|
|
98
|
-
|
|
109
|
+
fac_asso_leg_term_posT = poch[None, :] * asso_leg_term_pos.T
|
|
99
110
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
100
111
|
|
|
101
112
|
# Loop over NLayers atmospheric layers
|
|
102
113
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
103
114
|
for l in range(NLayers):
|
|
104
|
-
weighted_scaled_Leg_coeffs_l = weighted_scaled_Leg_coeffs[l, :]
|
|
115
|
+
weighted_scaled_Leg_coeffs_l = weighted_scaled_Leg_coeffs[l, m:]
|
|
105
116
|
scaled_omega_l = scaled_omega_arr[l]
|
|
106
117
|
omega_times_Leg_coeffs = (scaled_omega_l / 2) * weighted_scaled_Leg_coeffs_l
|
|
107
118
|
|
|
@@ -109,7 +120,7 @@ def _solve_for_gen_and_part_sols(
|
|
|
109
120
|
|
|
110
121
|
# Generate D
|
|
111
122
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
112
|
-
D_temp = omega_times_Leg_coeffs[None, :] *
|
|
123
|
+
D_temp = omega_times_Leg_coeffs[None, :] * fac_asso_leg_term_posT
|
|
113
124
|
D_pos = D_temp @ asso_leg_term_pos
|
|
114
125
|
D_neg = D_temp @ asso_leg_term_neg
|
|
115
126
|
|
|
@@ -119,89 +130,73 @@ def _solve_for_gen_and_part_sols(
|
|
|
119
130
|
# Assemble the coefficient matrix and additional terms
|
|
120
131
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
121
132
|
DW = D_pos * W[None, :]
|
|
122
|
-
|
|
133
|
+
DW.ravel()[::N + 1] -= 1
|
|
123
134
|
alpha = M_inv[:, None] * DW
|
|
124
135
|
beta = M_inv[:, None] * D_neg * W[None, :]
|
|
125
136
|
|
|
126
137
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
127
138
|
|
|
128
|
-
#
|
|
139
|
+
# Setup to solve for particular solution w.r.t. direct beam source (see section 3.6.1 of the Comprehensive Documentation)
|
|
129
140
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
130
141
|
if there_is_beam_source:
|
|
131
142
|
# Generate X
|
|
132
143
|
X_temp = (
|
|
133
|
-
|
|
144
|
+
scalar_fac_asso_leg_term_mu0
|
|
145
|
+
* scaled_omega_l
|
|
134
146
|
* weighted_scaled_Leg_coeffs_l
|
|
135
|
-
* (fac * asso_leg_term_mu0)
|
|
136
147
|
)
|
|
137
148
|
X_pos = X_temp @ asso_leg_term_pos
|
|
138
149
|
X_neg = X_temp @ asso_leg_term_neg
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
[
|
|
143
|
-
np.concatenate([-alpha, -beta], axis=1),
|
|
144
|
-
np.concatenate([beta, alpha], axis=1),
|
|
145
|
-
],
|
|
146
|
-
axis=0,
|
|
147
|
-
)
|
|
148
|
-
np.fill_diagonal(A, np.diag(A) + 1 / mu0)
|
|
149
|
-
B_collect[ind, :] = -np.linalg.solve(A, X_tilde) # We moved the minus sign out
|
|
150
|
+
# Signs flipped due to the extra minus sign
|
|
151
|
+
X_arr[ind, :N] = M_inv * X_pos
|
|
152
|
+
X_arr[ind, N:] = -M_inv * X_neg
|
|
150
153
|
|
|
151
154
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
152
155
|
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
A_arr[ind, N:, :N] = beta
|
|
157
|
+
A_arr[ind, N:, N:] = alpha
|
|
155
158
|
no_shortcut_indices.append(ind)
|
|
156
159
|
if m_equals_0 and there_is_iso_source: # Keep the list empty if there is no isotropic source
|
|
157
160
|
no_shortcut_indices_0.append(l)
|
|
158
161
|
|
|
159
162
|
else:
|
|
160
163
|
# This is a shortcut to the diagonalization results
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
G_collect[ind, :, :] = G
|
|
166
|
-
K_collect[ind, :] = -1 / mu_arr
|
|
164
|
+
G_collect[ind, :, :] = G_trivial
|
|
165
|
+
K_collect[ind, :N] = -M_inv
|
|
166
|
+
K_collect[ind, N:] = M_inv
|
|
167
167
|
if m_equals_0 and there_is_iso_source:
|
|
168
|
-
G_inv_collect_0[l, :, :] =
|
|
168
|
+
G_inv_collect_0[l, :, :] = G_trivial
|
|
169
169
|
|
|
170
170
|
ind += 1
|
|
171
171
|
|
|
172
172
|
if len(no_shortcut_indices) > 0:
|
|
173
|
-
|
|
173
|
+
A_arr = A_arr[no_shortcut_indices, :, :]
|
|
174
|
+
alpha_arr = A_arr[:, N:, N:]
|
|
175
|
+
beta_arr = A_arr[:, N:, :N]
|
|
176
|
+
|
|
174
177
|
# Diagonalization of coefficient matrix (refer to section 3.4.2 of the Comprehensive Documentation)
|
|
175
178
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
+
apb = alpha_arr + beta_arr
|
|
180
|
+
amb = alpha_arr - beta_arr
|
|
179
181
|
K_squared_arr, eigenvecs_GpG_arr = np.linalg.eig(
|
|
180
|
-
np.einsum(
|
|
181
|
-
"lij, ljk -> lik", alpha_arr - beta_arr, alpha_arr + beta_arr, optimize=True
|
|
182
|
-
),
|
|
182
|
+
np.einsum("lij, ljk -> lik", amb, apb, optimize=True)
|
|
183
183
|
)
|
|
184
184
|
|
|
185
185
|
# Eigenvalues arranged negative then positive, from largest to smallest magnitude
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
(eigenvecs_GpG_arr + eigenvecs_GmG_arr) / 2,
|
|
200
|
-
),
|
|
201
|
-
axis=1,
|
|
202
|
-
)
|
|
186
|
+
K_arr_pos = np.sqrt(K_squared_arr)
|
|
187
|
+
K_arr = np.concatenate((-K_arr_pos, K_arr_pos), axis=1)
|
|
188
|
+
|
|
189
|
+
# Build eigenvector matrix from four blocks
|
|
190
|
+
eigenvecs_GpG_arr /= 2
|
|
191
|
+
eigenvecs_GmG_arr = np.einsum("lij, ljk -> lik", apb, eigenvecs_GpG_arr / K_arr_pos[:, None, :], optimize=True)
|
|
192
|
+
GpG_p_GmG_div_2 = eigenvecs_GpG_arr + eigenvecs_GmG_arr
|
|
193
|
+
GpG_m_GmG_div_2 = eigenvecs_GpG_arr - eigenvecs_GmG_arr
|
|
194
|
+
|
|
195
|
+
G_collect[no_shortcut_indices, :N, :N] = GpG_p_GmG_div_2
|
|
196
|
+
G_collect[no_shortcut_indices, N:, N:] = GpG_p_GmG_div_2
|
|
197
|
+
G_collect[no_shortcut_indices, :N, N:] = GpG_m_GmG_div_2
|
|
198
|
+
G_collect[no_shortcut_indices, N:, :N] = GpG_m_GmG_div_2
|
|
203
199
|
|
|
204
|
-
G_collect[no_shortcut_indices, :, :] = G_arr
|
|
205
200
|
K_collect[no_shortcut_indices, :] = K_arr
|
|
206
201
|
if len(no_shortcut_indices_0) > 0: # If there is no isotropic source this list will be empty
|
|
207
202
|
G_inv_collect_0[no_shortcut_indices_0, :, :] = np.linalg.inv(
|
|
@@ -209,6 +204,17 @@ def _solve_for_gen_and_part_sols(
|
|
|
209
204
|
)
|
|
210
205
|
|
|
211
206
|
# --------------------------------------------------------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
if there_is_beam_source:
|
|
209
|
+
|
|
210
|
+
# Solve for particular solution w.r.t. direct beam source (refer to section 3.6.1 of the Comprehensive Documentation)
|
|
211
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
212
|
+
A_arr[:, :N, N:] = -A_arr[:, N:, :N]
|
|
213
|
+
A_arr[:, :N, :N] = -A_arr[:, N:, N:]
|
|
214
|
+
A_arr.reshape(-1, NQuad * NQuad)[:, ::NQuad + 1] += 1 / mu0
|
|
215
|
+
B_collect[no_shortcut_indices, :] = np.linalg.solve(A_arr, X_arr[no_shortcut_indices, :, None]).squeeze(-1)
|
|
216
|
+
|
|
217
|
+
# --------------------------------------------------------------------------------------------------------------------------
|
|
212
218
|
|
|
213
219
|
outputs = (
|
|
214
220
|
G_collect.reshape((NFourier, NLayers, NQuad, NQuad)),
|