paddle 1.2.0__py3-none-any.whl → 1.2.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.
- paddle/__init__.py +1 -1
- paddle/cubed_sphere_ui.py +63 -0
- paddle/cubed_sphere_utils.py +356 -0
- paddle/evolve_kinetics.py +24 -8
- paddle/find_init_params.py +1 -1
- paddle/plot_cubed_sphere_panel.py +21 -0
- paddle/setup_profile.py +17 -12
- paddle/write_profile.py +3 -3
- {paddle-1.2.0.dist-info → paddle-1.2.2.dist-info}/METADATA +2 -2
- paddle-1.2.2.dist-info/RECORD +17 -0
- paddle-1.2.0.dist-info/RECORD +0 -14
- {paddle-1.2.0.dist-info → paddle-1.2.2.dist-info}/WHEEL +0 -0
- {paddle-1.2.0.dist-info → paddle-1.2.2.dist-info}/entry_points.txt +0 -0
- {paddle-1.2.0.dist-info → paddle-1.2.2.dist-info}/licenses/LICENSE +0 -0
paddle/__init__.py
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
from matplotlib.widgets import Slider, CheckButtons, Button
|
|
4
|
+
from cubed_sphere_utils import draw_panel_corner
|
|
5
|
+
|
|
6
|
+
plt.close("all")
|
|
7
|
+
fig = plt.figure(figsize=(9, 7))
|
|
8
|
+
ax = fig.add_axes([0.08, 0.18, 0.82, 0.75])
|
|
9
|
+
ax.set_aspect("equal")
|
|
10
|
+
ax.set_xlim(-1.05, 1.05)
|
|
11
|
+
ax.set_ylim(-1.05, 1.05)
|
|
12
|
+
ax.set_title(
|
|
13
|
+
"Interactive orthographic view — drag sliders to rotate view_dir\n(+X solid, +Y dotted, +Z dash-dot; ghosts dashed)"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
ax_az = fig.add_axes([0.08, 0.10, 0.6, 0.03])
|
|
17
|
+
ax_el = fig.add_axes([0.08, 0.06, 0.6, 0.03])
|
|
18
|
+
s_az = Slider(ax_az, "Azimuth (°)", 0, 360, valinit=45, valstep=1)
|
|
19
|
+
s_el = Slider(ax_el, "Elevation (°)", -85, 85, valinit=10, valstep=1)
|
|
20
|
+
|
|
21
|
+
ax_chk = fig.add_axes([0.72, 0.05, 0.18, 0.10])
|
|
22
|
+
checks = CheckButtons(ax_chk, labels=["Show +Z"], actives=[True])
|
|
23
|
+
|
|
24
|
+
ax_reset = fig.add_axes([0.65, 0.12, 0.1, 0.04])
|
|
25
|
+
btn_reset = Button(ax_reset, "Reset")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def viewdir_from_angles(az_deg, el_deg):
|
|
29
|
+
az = np.deg2rad(az_deg)
|
|
30
|
+
el = np.deg2rad(el_deg)
|
|
31
|
+
c = np.cos(el)
|
|
32
|
+
return np.array([c * np.cos(az), c * np.sin(az), np.sin(el)])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def redraw(_=None):
|
|
36
|
+
ax.cla()
|
|
37
|
+
ax.set_aspect("equal")
|
|
38
|
+
ax.set_xlim(-1.1, 1.1)
|
|
39
|
+
ax.set_ylim(-1.1, 1.1)
|
|
40
|
+
ax.set_title(
|
|
41
|
+
"Interactive orthographic view — drag sliders to rotate view_dir\n(+X solid, +Y dotted, +Z dash-dot; ghosts dashed)"
|
|
42
|
+
)
|
|
43
|
+
V = viewdir_from_angles(s_az.val, s_el.val)
|
|
44
|
+
|
|
45
|
+
draw_panel_corner(ax, N=8, nghost=3, view_dir=V)
|
|
46
|
+
|
|
47
|
+
fig.canvas.draw_idle()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def on_reset(event):
|
|
51
|
+
s_az.reset()
|
|
52
|
+
s_el.reset()
|
|
53
|
+
if not checks.get_status()[0]:
|
|
54
|
+
checks.set_active(0)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
s_az.on_changed(redraw)
|
|
58
|
+
s_el.on_changed(redraw)
|
|
59
|
+
checks.on_clicked(redraw)
|
|
60
|
+
btn_reset.on_clicked(on_reset)
|
|
61
|
+
|
|
62
|
+
redraw()
|
|
63
|
+
plt.show()
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from matplotlib.patches import Polygon
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def normalize(v: np.ndarray) -> np.ndarray:
|
|
7
|
+
return v / np.linalg.norm(v)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def gnomonic_equiangular_to_xyz(alpha, beta, face="+X"):
|
|
11
|
+
"""
|
|
12
|
+
Map equiangular gnomonic coordinates (alpha, beta) in radians to
|
|
13
|
+
Cartesian coordinates (x, y, z) on the unit sphere for a given cube face.
|
|
14
|
+
|
|
15
|
+
(alpha, beta) are the equiangular angles from the face center:
|
|
16
|
+
t = tan(alpha), u = tan(beta)
|
|
17
|
+
Faces: "+X", "-X", "+Y", "-Y", "+Z", "-Z"
|
|
18
|
+
"""
|
|
19
|
+
t = np.tan(alpha)
|
|
20
|
+
u = np.tan(beta)
|
|
21
|
+
|
|
22
|
+
if face == "+X":
|
|
23
|
+
X, Y, Z = np.ones_like(t), t, u
|
|
24
|
+
elif face == "-X":
|
|
25
|
+
X, Y, Z = -np.ones_like(t), -t, u
|
|
26
|
+
elif face == "+Y":
|
|
27
|
+
X, Y, Z = -t, np.ones_like(t), u
|
|
28
|
+
elif face == "-Y":
|
|
29
|
+
X, Y, Z = t, -np.ones_like(t), u
|
|
30
|
+
elif face == "+Z":
|
|
31
|
+
X, Y, Z = -u, t, np.ones_like(t)
|
|
32
|
+
elif face == "-Z":
|
|
33
|
+
X, Y, Z = u, t, -np.ones_like(t)
|
|
34
|
+
else:
|
|
35
|
+
raise ValueError("Invalid face specifier")
|
|
36
|
+
|
|
37
|
+
inv_norm = 1.0 / np.sqrt(X * X + Y * Y + Z * Z)
|
|
38
|
+
return X * inv_norm, Y * inv_norm, Z * inv_norm
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def orthographic_project(face_xyz, view_dir=np.array([1, 0, 0])):
|
|
42
|
+
"""Orthographic projection onto the plane normal to view_dir."""
|
|
43
|
+
x, y, z = face_xyz
|
|
44
|
+
V = normalize(np.array(view_dir))
|
|
45
|
+
# Construct orthonormal basis (e1,e2) spanning plane perpendicular to V
|
|
46
|
+
# Choose arbitrary "up" vector not parallel to V
|
|
47
|
+
up_guess = np.array([0, 0, 1]) if abs(V[2]) < 0.9 else np.array([0, 1, 0])
|
|
48
|
+
e1 = normalize(np.cross(up_guess, V))
|
|
49
|
+
e2 = np.cross(V, e1)
|
|
50
|
+
# Project coordinates
|
|
51
|
+
U = x * e1[0] + y * e1[1] + z * e1[2]
|
|
52
|
+
W = x * e2[0] + y * e2[1] + z * e2[2]
|
|
53
|
+
return U, W
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def visible_segments(u, v, depth, vis_mask):
|
|
57
|
+
segs = []
|
|
58
|
+
start = None
|
|
59
|
+
for i in range(len(u)):
|
|
60
|
+
if vis_mask[i] and start is None:
|
|
61
|
+
start = i
|
|
62
|
+
elif (not vis_mask[i]) and (start is not None):
|
|
63
|
+
segs.append((u[start:i], v[start:i], depth[start:i]))
|
|
64
|
+
start = None
|
|
65
|
+
if start is not None:
|
|
66
|
+
segs.append((u[start:], v[start:], depth[start:]))
|
|
67
|
+
return segs
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def plot_on_face(ax, alpha, beta, face="+X", view_dir=np.array([1, 0, 0]), **kwargs):
|
|
71
|
+
x, y, z = gnomonic_equiangular_to_xyz(alpha, beta, face=face)
|
|
72
|
+
# r·V (nearness for ortho)
|
|
73
|
+
depth = x * view_dir[0] + y * view_dir[1] + z * view_dir[2]
|
|
74
|
+
|
|
75
|
+
vis = depth > 0 # front hemisphere
|
|
76
|
+
u, v = orthographic_project((x, y, z), view_dir=view_dir)
|
|
77
|
+
segs = visible_segments(u, v, depth, vis)
|
|
78
|
+
segs.sort(key=lambda seg: np.max(seg[2]) if seg[2].size else -1)
|
|
79
|
+
for uu, vv, dd in segs:
|
|
80
|
+
ax.plot(uu, vv, zorder=np.max(dd) if dd.size else 0, **kwargs)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def scatter_on_face(ax, alpha, beta, face="+X", view_dir=np.array([1, 0, 0]), **kwargs):
|
|
84
|
+
x, y, z = gnomonic_equiangular_to_xyz(alpha, beta, face=face)
|
|
85
|
+
# r·V (nearness for ortho)
|
|
86
|
+
depth = x * view_dir[0] + y * view_dir[1] + z * view_dir[2]
|
|
87
|
+
|
|
88
|
+
vis = depth > 0 # front hemisphere
|
|
89
|
+
u, v = orthographic_project((x, y, z), view_dir=view_dir)
|
|
90
|
+
ax.scatter(u[vis], v[vis], zorder=np.max(depth[vis]), **kwargs)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def panel_ab_limits(
|
|
94
|
+
dxy, N, nghost, exterior=True
|
|
95
|
+
) -> Tuple[Tuple[float, float], Tuple[float, float]]:
|
|
96
|
+
"""
|
|
97
|
+
Return ((a_min, b_min), (a_max, b_max)) in equiangular gnomonic coords (radians)
|
|
98
|
+
for a single cubed-sphere panel.
|
|
99
|
+
|
|
100
|
+
Panel interior spans α,β ∈ [-π/4, +π/4]. Each cell has angular size
|
|
101
|
+
dθ = (π/2) / N. Ghost zones extend outward by 'nghost' cells.
|
|
102
|
+
|
|
103
|
+
Offsets (dx, dy) ∈ {-1, 0, 1} select a slab/corner in each dimension:
|
|
104
|
+
- dx = -1: left
|
|
105
|
+
- dx = 0: center
|
|
106
|
+
- dx = +1: right
|
|
107
|
+
(analogous for dy: bottom/center/top)
|
|
108
|
+
|
|
109
|
+
exterior=True:
|
|
110
|
+
- (0, 0) returns FULL exterior limits including ghost zones
|
|
111
|
+
- (-1, 0) returns the LEFT ghost region only (width = nghost * dθ), etc.
|
|
112
|
+
|
|
113
|
+
exterior=False:
|
|
114
|
+
- (0, 0) returns interior limits ONLY
|
|
115
|
+
- (-1, 0) returns the interior boundary slab adjacent to the left edge,
|
|
116
|
+
with the SAME thickness as the ghost zone (nghost * dθ) but inside.
|
|
117
|
+
(Analogous for other offsets and for dy.)
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
((a_min, b_min), (a_max, b_max))
|
|
121
|
+
"""
|
|
122
|
+
dx, dy = dxy
|
|
123
|
+
if dx not in (-1, 0, 1) or dy not in (-1, 0, 1):
|
|
124
|
+
raise ValueError("dx, dy must be in {-1, 0, 1}")
|
|
125
|
+
if N <= 0 or nghost < 0:
|
|
126
|
+
raise ValueError("N must be > 0 and nghost >= 0")
|
|
127
|
+
|
|
128
|
+
dtheta = (np.pi / 2.0) / N
|
|
129
|
+
aL_int, aR_int = -np.pi / 4, +np.pi / 4
|
|
130
|
+
bB_int, bT_int = -np.pi / 4, +np.pi / 4
|
|
131
|
+
|
|
132
|
+
# External (interior + ghosts) bounds along each dimension
|
|
133
|
+
aL_ext = aL_int - nghost * dtheta
|
|
134
|
+
aR_ext = aR_int + nghost * dtheta
|
|
135
|
+
bB_ext = bB_int - nghost * dtheta
|
|
136
|
+
bT_ext = bT_int + nghost * dtheta
|
|
137
|
+
|
|
138
|
+
def one_dim_limits(
|
|
139
|
+
offset: int, L_int: float, R_int: float, L_ext: float, R_ext: float
|
|
140
|
+
):
|
|
141
|
+
if offset == 0: # full span incl. ghosts
|
|
142
|
+
return (L_int, R_int)
|
|
143
|
+
if exterior:
|
|
144
|
+
if offset == -1: # left ghost slab
|
|
145
|
+
return (L_ext, L_int)
|
|
146
|
+
else: # +1: right ghost slab
|
|
147
|
+
return (R_int, R_ext)
|
|
148
|
+
else:
|
|
149
|
+
if offset == -1: # interior boundary slab (left), same thickness as ghosts
|
|
150
|
+
return (L_int, L_int + nghost * dtheta)
|
|
151
|
+
else: # +1: interior boundary slab (right)
|
|
152
|
+
return (R_int - nghost * dtheta, R_int)
|
|
153
|
+
|
|
154
|
+
a_min, a_max = one_dim_limits(dx, aL_int, aR_int, aL_ext, aR_ext)
|
|
155
|
+
b_min, b_max = one_dim_limits(dy, bB_int, bT_int, bB_ext, bT_ext)
|
|
156
|
+
|
|
157
|
+
# set to whole domain
|
|
158
|
+
if dx == 0 and dy == 0 and exterior:
|
|
159
|
+
a_min, a_max = aL_ext, aR_ext
|
|
160
|
+
b_min, b_max = bB_ext, bT_ext
|
|
161
|
+
|
|
162
|
+
# Ensure (lower-left, upper-right) ordering
|
|
163
|
+
a0, a1 = (min(a_min, a_max), max(a_min, a_max))
|
|
164
|
+
b0, b1 = (min(b_min, b_max), max(b_min, b_max))
|
|
165
|
+
return (a0, b0), (a1, b1)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def sample_edge(a0, b0, a1, b1, n_pts=64):
|
|
169
|
+
t = np.linspace(0.0, 1.0, n_pts)
|
|
170
|
+
return (a0 + (a1 - a0) * t), (b0 + (b1 - b0) * t)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def make_poly_patch(verts_ab, face="+X", view_dir=(1, 0, 0), n_pts=64, **kwargs):
|
|
174
|
+
# Sample each edge, map to sphere, cull back hemisphere
|
|
175
|
+
boundary_u, boundary_v, boundary_d = [], [], []
|
|
176
|
+
for (a0, b0), (a1, b1) in zip(verts_ab, verts_ab[1:] + verts_ab[:1]):
|
|
177
|
+
aa, bb = sample_edge(a0, b0, a1, b1, n_pts=n_pts)
|
|
178
|
+
xyz = gnomonic_equiangular_to_xyz(aa, bb, face=face)
|
|
179
|
+
u, v = orthographic_project(xyz, view_dir=view_dir)
|
|
180
|
+
d = xyz[0] * view_dir[0] + xyz[1] * view_dir[1] + xyz[2] * view_dir[2]
|
|
181
|
+
mask = d > 0
|
|
182
|
+
boundary_u.append(u[mask])
|
|
183
|
+
boundary_v.append(v[mask])
|
|
184
|
+
boundary_d.append(d[mask])
|
|
185
|
+
|
|
186
|
+
if not any(len(u) for u in boundary_u):
|
|
187
|
+
return None # fully occluded
|
|
188
|
+
|
|
189
|
+
U = np.concatenate(boundary_u)
|
|
190
|
+
V = np.concatenate(boundary_v)
|
|
191
|
+
D = (
|
|
192
|
+
np.concatenate(boundary_d)
|
|
193
|
+
if any(len(d) for d in boundary_d)
|
|
194
|
+
else np.array([0.0])
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
poly = Polygon(np.c_[U, V], closed=True, zorder=1 + float(np.max(D)), **kwargs)
|
|
198
|
+
return poly
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def draw_panel_grid(
|
|
202
|
+
ax,
|
|
203
|
+
face="+X",
|
|
204
|
+
N=8,
|
|
205
|
+
nghost=3,
|
|
206
|
+
n_pts=800,
|
|
207
|
+
view_dir=np.array([1, 0, 0]),
|
|
208
|
+
color="C0",
|
|
209
|
+
linestyle="--",
|
|
210
|
+
linewidth=0.8,
|
|
211
|
+
facecolor="none",
|
|
212
|
+
):
|
|
213
|
+
"""
|
|
214
|
+
Plot an equiangular gnomonic grid for a single cubed-sphere panel with ghost zones.
|
|
215
|
+
- N: number of interior cells per direction (there are N+1 interior grid lines).
|
|
216
|
+
- nghost: number of extra ghost cells beyond each edge (drawn dashed).
|
|
217
|
+
"""
|
|
218
|
+
# Uniform grid in equiangular coordinates; panel spans [-pi/4, +pi/4].
|
|
219
|
+
dtheta = (np.pi / 2) / N # cell size in angle
|
|
220
|
+
|
|
221
|
+
# Grid-line indices: i = -N/2 ... N/2 for interior; extend by nghost
|
|
222
|
+
halfN = N // 2
|
|
223
|
+
idx = np.arange(-halfN - nghost, halfN + nghost + 1)
|
|
224
|
+
alphas = idx * dtheta # positions of grid lines
|
|
225
|
+
centers = 0.5 * (alphas[1:] + alphas[:-1]) # cell centers
|
|
226
|
+
|
|
227
|
+
# Limit plotting domain to a slightly larger band so dashed ghost lines are visible
|
|
228
|
+
s = np.linspace(-np.pi / 4 - nghost * dtheta, np.pi / 4 + nghost * dtheta, n_pts)
|
|
229
|
+
|
|
230
|
+
# alpha = const lines
|
|
231
|
+
for i, alpha in zip(idx, alphas):
|
|
232
|
+
# skip the first and the last lines
|
|
233
|
+
if i == idx[0] or i == idx[-1]:
|
|
234
|
+
continue
|
|
235
|
+
plot_on_face(
|
|
236
|
+
ax,
|
|
237
|
+
np.full_like(s, alpha),
|
|
238
|
+
s,
|
|
239
|
+
face=face,
|
|
240
|
+
view_dir=view_dir,
|
|
241
|
+
linewidth=linewidth,
|
|
242
|
+
linestyle=linestyle,
|
|
243
|
+
color=color,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# beta = const lines
|
|
247
|
+
for j, beta in zip(idx, alphas):
|
|
248
|
+
# skip the first and the last lines
|
|
249
|
+
if j == idx[0] or j == idx[-1]:
|
|
250
|
+
continue
|
|
251
|
+
plot_on_face(
|
|
252
|
+
ax,
|
|
253
|
+
s,
|
|
254
|
+
np.full_like(s, beta),
|
|
255
|
+
face=face,
|
|
256
|
+
view_dir=view_dir,
|
|
257
|
+
linewidth=linewidth,
|
|
258
|
+
linestyle=linestyle,
|
|
259
|
+
color=color,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# cell centers (solid dots)
|
|
263
|
+
center_a, center_b = np.meshgrid(centers, centers)
|
|
264
|
+
scatter_on_face(
|
|
265
|
+
ax,
|
|
266
|
+
center_a.flatten(),
|
|
267
|
+
center_b.flatten(),
|
|
268
|
+
face=face,
|
|
269
|
+
view_dir=view_dir,
|
|
270
|
+
s=10,
|
|
271
|
+
facecolors=facecolor,
|
|
272
|
+
edgecolors=color,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Interior panel boundary (bold): |alpha|=pi/4 and |beta|=pi/4
|
|
276
|
+
for alpha in [-np.pi / 4, np.pi / 4]:
|
|
277
|
+
plot_on_face(
|
|
278
|
+
ax,
|
|
279
|
+
np.full_like(s, alpha),
|
|
280
|
+
s,
|
|
281
|
+
face=face,
|
|
282
|
+
view_dir=view_dir,
|
|
283
|
+
linestyle="-.",
|
|
284
|
+
linewidth=1.6,
|
|
285
|
+
color=color,
|
|
286
|
+
)
|
|
287
|
+
for beta in [-np.pi / 4, np.pi / 4]:
|
|
288
|
+
plot_on_face(
|
|
289
|
+
ax,
|
|
290
|
+
s,
|
|
291
|
+
np.full_like(s, beta),
|
|
292
|
+
face=face,
|
|
293
|
+
view_dir=view_dir,
|
|
294
|
+
linestyle="-.",
|
|
295
|
+
linewidth=1.6,
|
|
296
|
+
color=color,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def draw_single_panel(
|
|
301
|
+
ax, face="+X", N=8, nghost=3, color="C0", view_dir=np.array([1, 0, 0])
|
|
302
|
+
):
|
|
303
|
+
# all grid lines including ghosts
|
|
304
|
+
draw_panel_grid(ax, face=face, N=N, nghost=nghost, color=color, view_dir=view_dir)
|
|
305
|
+
|
|
306
|
+
# interior grid lines only
|
|
307
|
+
draw_panel_grid(
|
|
308
|
+
ax,
|
|
309
|
+
face=face,
|
|
310
|
+
N=N,
|
|
311
|
+
nghost=0,
|
|
312
|
+
view_dir=view_dir,
|
|
313
|
+
linestyle="-",
|
|
314
|
+
linewidth=1.2,
|
|
315
|
+
facecolor=color,
|
|
316
|
+
color=color,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def draw_panel_seam(ax, N=8, nghost=3, view_dir=np.array([1.0, 1.0, 0.0])):
|
|
321
|
+
draw_single_panel(ax, "+X", N=N, nghost=nghost, view_dir=view_dir, color="C0")
|
|
322
|
+
draw_single_panel(ax, "+Y", N=N, nghost=nghost, view_dir=view_dir, color="C1")
|
|
323
|
+
|
|
324
|
+
# ghost zone patches
|
|
325
|
+
(a0, b0), (a1, b1) = panel_ab_limits((1, 0), N=N, nghost=nghost, exterior=True)
|
|
326
|
+
verts_box = [(a0, b0), (a1, b0), (a1, b1), (a0, b1)]
|
|
327
|
+
poly = make_poly_patch(
|
|
328
|
+
verts_box,
|
|
329
|
+
face="+X",
|
|
330
|
+
view_dir=view_dir,
|
|
331
|
+
edgecolor="k",
|
|
332
|
+
# grey with some transparency
|
|
333
|
+
facecolor=(0.5, 0.5, 0.5, 0.3),
|
|
334
|
+
linewidth=1.2,
|
|
335
|
+
)
|
|
336
|
+
ax.add_patch(poly)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def draw_panel_corner(ax, N=8, nghost=3, view_dir=np.array([1.0, 1.0, 1.0])):
|
|
340
|
+
draw_single_panel(ax, "+X", N=N, nghost=nghost, view_dir=view_dir, color="C0")
|
|
341
|
+
draw_single_panel(ax, "+Y", N=N, nghost=nghost, view_dir=view_dir, color="C1")
|
|
342
|
+
draw_single_panel(ax, "+Z", N=N, nghost=nghost, view_dir=view_dir, color="C2")
|
|
343
|
+
|
|
344
|
+
# ghost zone patches
|
|
345
|
+
(a0, b0), (a1, b1) = panel_ab_limits((1, 1), N=N, nghost=nghost, exterior=True)
|
|
346
|
+
verts_box = [(a0, b0), (a1, b0), (a1, b1), (a0, b1)]
|
|
347
|
+
poly = make_poly_patch(
|
|
348
|
+
verts_box,
|
|
349
|
+
face="+X",
|
|
350
|
+
view_dir=view_dir,
|
|
351
|
+
edgecolor="k",
|
|
352
|
+
# grey with some transparency
|
|
353
|
+
facecolor=(0.5, 0.5, 0.5, 0.3),
|
|
354
|
+
linewidth=1.2,
|
|
355
|
+
)
|
|
356
|
+
ax.add_patch(poly)
|
paddle/evolve_kinetics.py
CHANGED
|
@@ -6,9 +6,10 @@ from snapy import kIDN, kIPR, kICY
|
|
|
6
6
|
|
|
7
7
|
def evolve_kinetics(
|
|
8
8
|
hydro_w: torch.Tensor,
|
|
9
|
-
|
|
10
|
-
kinet: kintera.Kinetics,
|
|
9
|
+
eos: snapy.EquationOfState,
|
|
11
10
|
thermo_x: kintera.ThermoX,
|
|
11
|
+
thermo_y: kintera.ThermoY,
|
|
12
|
+
kinet: kintera.Kinetics,
|
|
12
13
|
dt,
|
|
13
14
|
) -> torch.Tensor:
|
|
14
15
|
"""
|
|
@@ -16,30 +17,45 @@ def evolve_kinetics(
|
|
|
16
17
|
|
|
17
18
|
Args:
|
|
18
19
|
hydro_w (torch.Tensor): The primitive variables tensor.
|
|
19
|
-
|
|
20
|
-
kinet (kintera.Kinetics): The kinetics module for chemical reactions.
|
|
20
|
+
eos (snapy.EquationOfState): The equation-of-state.
|
|
21
21
|
thermo_x (kintera.ThermoX): The thermodynamics module for computing properties.
|
|
22
|
+
thermo_y (kintera.ThermoY): The thermodynamics module for computing properties.
|
|
23
|
+
kinet (kintera.Kinetics): The kinetics module for chemical reactions.
|
|
22
24
|
dt (float): The time step for evolution.
|
|
23
25
|
|
|
24
26
|
Returns:
|
|
25
27
|
torch.Tensor: The change in mass density due to chemical reactions.
|
|
26
28
|
"""
|
|
27
|
-
|
|
28
|
-
thermo_y = block.module("hydro.eos.thermo")
|
|
29
|
-
|
|
29
|
+
# compute temperature and pressure
|
|
30
30
|
temp = eos.compute("W->T", (hydro_w,))
|
|
31
31
|
pres = hydro_w[kIPR]
|
|
32
|
+
|
|
33
|
+
# compute mole fractions from mass fractions
|
|
32
34
|
xfrac = thermo_y.compute("Y->X", (hydro_w[kICY:],))
|
|
35
|
+
|
|
36
|
+
# compute molar concentrations
|
|
33
37
|
conc = thermo_x.compute("TPX->V", (temp, pres, xfrac))
|
|
38
|
+
|
|
39
|
+
# compute volumetric heat capacity
|
|
34
40
|
cp_vol = thermo_x.compute("TV->cp", (temp, conc))
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
# narrow species for kinetics
|
|
43
|
+
# conc_kinet = kinet.options.narrow_copy(conc, thermo_y.options)
|
|
44
|
+
conc_kinet = conc[:, :, :, 1:] # exclude dry species
|
|
45
|
+
|
|
46
|
+
# compute rate and rate jacobians
|
|
37
47
|
rate, rc_ddC, rc_ddT = kinet.forward_nogil(temp, pres, conc_kinet)
|
|
48
|
+
|
|
49
|
+
# compute reaction jacobian
|
|
38
50
|
jac = kinet.jacobian(temp, conc_kinet, cp_vol, rate, rc_ddC, rc_ddT)
|
|
39
51
|
|
|
52
|
+
# compute concentration change
|
|
40
53
|
stoich = kinet.buffer("stoich")
|
|
41
54
|
del_conc = kintera.evolve_implicit(rate, stoich, jac, dt)
|
|
42
55
|
|
|
56
|
+
# compute density change
|
|
43
57
|
inv_mu = thermo_y.buffer("inv_mu")
|
|
44
58
|
del_rho = del_conc / inv_mu[1:].view(1, 1, 1, -1)
|
|
59
|
+
|
|
60
|
+
# return permutated density change for hydro
|
|
45
61
|
return del_rho.permute(3, 0, 1, 2)
|
paddle/find_init_params.py
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
from cubed_sphere_utils import (
|
|
3
|
+
make_poly_patch,
|
|
4
|
+
draw_single_panel,
|
|
5
|
+
draw_panel_seam,
|
|
6
|
+
draw_panel_corner,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
fig, ax = plt.subplots(figsize=(8, 8))
|
|
11
|
+
|
|
12
|
+
# draw_single_panel(ax, N=6, nghost=3)
|
|
13
|
+
draw_panel_seam(ax, N=6, nghost=2)
|
|
14
|
+
# draw_panel_corner(ax, N=8, nghost=3)
|
|
15
|
+
|
|
16
|
+
ax.set_aspect("equal")
|
|
17
|
+
ax.set_xlabel("X (orthographic)")
|
|
18
|
+
ax.set_ylabel("Y (orthographic)")
|
|
19
|
+
ax.set_xlim(-1.1, 1.1)
|
|
20
|
+
ax.set_ylim(-1.1, 1.1)
|
|
21
|
+
plt.show()
|
paddle/setup_profile.py
CHANGED
|
@@ -34,7 +34,7 @@ def integrate_neutral(
|
|
|
34
34
|
pres_ad = pres.clone()
|
|
35
35
|
xfrac_ad = xfrac.clone()
|
|
36
36
|
|
|
37
|
-
thermo_x.
|
|
37
|
+
thermo_x.extrapolate_dz(temp_ad, pres_ad, xfrac_ad, dz, grav=grav)
|
|
38
38
|
conc_ad = thermo_x.compute("TPX->V", [temp_ad, pres_ad, xfrac_ad])
|
|
39
39
|
rho_ad = thermo_x.compute("V->D", [conc_ad])
|
|
40
40
|
rho_bar = 0.5 * (rho + rho_ad)
|
|
@@ -192,7 +192,7 @@ def setup_profile(
|
|
|
192
192
|
Tmin = param.get("Tmin", 0.0)
|
|
193
193
|
|
|
194
194
|
# get handles to modules
|
|
195
|
-
coord = block.module("
|
|
195
|
+
coord = block.module("coord")
|
|
196
196
|
thermo_y = block.module("hydro.eos.thermo")
|
|
197
197
|
|
|
198
198
|
# get coordinates
|
|
@@ -224,23 +224,26 @@ def setup_profile(
|
|
|
224
224
|
|
|
225
225
|
# start and end indices for the vertical direction
|
|
226
226
|
# excluding ghost cells
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
il = coord.il()
|
|
228
|
+
iu = coord.iu()
|
|
229
229
|
|
|
230
230
|
# vertical grid distance of the first cell
|
|
231
|
-
dz = coord.buffer("dx1f")[
|
|
231
|
+
dz = coord.buffer("dx1f")[il]
|
|
232
232
|
|
|
233
233
|
# half a grid to cell center
|
|
234
|
-
|
|
234
|
+
rainout = method.split("-")[0] != "moist"
|
|
235
|
+
thermo_x.extrapolate_dz(
|
|
236
|
+
temp, pres, xfrac, dz / 2.0, grav=grav, verbose=verbose, rainout=rainout
|
|
237
|
+
)
|
|
235
238
|
|
|
236
239
|
# adiabatic extrapolation
|
|
237
240
|
if method == "isothermal":
|
|
238
|
-
i_isothermal =
|
|
239
|
-
|
|
241
|
+
i_isothermal = il
|
|
242
|
+
il = iu
|
|
240
243
|
else:
|
|
241
|
-
i_isothermal =
|
|
244
|
+
i_isothermal = iu
|
|
242
245
|
|
|
243
|
-
for i in range(
|
|
246
|
+
for i in range(il, iu + 1):
|
|
244
247
|
# drop clouds fractions
|
|
245
248
|
if method.split("-")[0] != "moist":
|
|
246
249
|
for cid in thermo_x.options.cloud_ids():
|
|
@@ -261,14 +264,16 @@ def setup_profile(
|
|
|
261
264
|
elif method.split("-")[0] == "neutral":
|
|
262
265
|
temp, pres, xfrac = integrate_neutral(thermo_x, temp, pres, xfrac, grav, dz)
|
|
263
266
|
else:
|
|
264
|
-
thermo_x.
|
|
267
|
+
thermo_x.extrapolate_dz(
|
|
268
|
+
temp, pres, xfrac, dz, grav=grav, verbose=verbose, rainout=rainout
|
|
269
|
+
)
|
|
265
270
|
|
|
266
271
|
if torch.any(temp < Tmin):
|
|
267
272
|
i_isothermal = i + 1
|
|
268
273
|
break
|
|
269
274
|
|
|
270
275
|
# isothermal extrapolation
|
|
271
|
-
for i in range(i_isothermal,
|
|
276
|
+
for i in range(i_isothermal, iu + 1):
|
|
272
277
|
# drop clouds fractions
|
|
273
278
|
if method.split("-")[0] != "moist":
|
|
274
279
|
for cid in thermo_x.options.cloud_ids():
|
paddle/write_profile.py
CHANGED
|
@@ -11,8 +11,8 @@ from snapy import kIPR, kICY
|
|
|
11
11
|
|
|
12
12
|
def write_profile(
|
|
13
13
|
filename: str,
|
|
14
|
-
hydro_w: torch.Tensor,
|
|
15
14
|
block: snapy.MeshBlock,
|
|
15
|
+
hydro_w: torch.Tensor,
|
|
16
16
|
ref_pressure: float = 1.0e5,
|
|
17
17
|
comment: Optional[str] = None,
|
|
18
18
|
) -> None:
|
|
@@ -30,9 +30,9 @@ def write_profile(
|
|
|
30
30
|
None
|
|
31
31
|
"""
|
|
32
32
|
# useful modules
|
|
33
|
+
coord = block.module("coord")
|
|
33
34
|
thermo_y = block.module("hydro.eos.thermo")
|
|
34
|
-
|
|
35
|
-
eos = block.hydro.get_eos()
|
|
35
|
+
eos = block.module("hydro.eos")
|
|
36
36
|
|
|
37
37
|
# handling mole fraction quantities
|
|
38
38
|
thermo_x = kintera.ThermoX(thermo_y.options)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: paddle
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Python Atmospheric Dynamics: Discovery and Learning about Exoplanets. An open-source, user-friendly python frontend of canoe
|
|
5
5
|
Project-URL: Homepage, https://github.com/elijah-mullens/paddle
|
|
6
6
|
Project-URL: Repository, https://github.com/elijah-mullens/paddle
|
|
@@ -23,7 +23,7 @@ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
|
23
23
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
24
24
|
Requires-Python: >=3.9
|
|
25
25
|
Requires-Dist: scipy
|
|
26
|
-
Requires-Dist: snapy>=1.
|
|
26
|
+
Requires-Dist: snapy>=1.1.5
|
|
27
27
|
Requires-Dist: torch<=2.7.1,>=2.7.0
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest>=7; extra == 'dev'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
paddle/__init__.py,sha256=LnVUdwA5HacoapuAHkSJhROl2UxvK_0HtR_CAB3tBbU,281
|
|
2
|
+
paddle/crm.py,sha256=pME4nF5gjlJm-36yMl0N8bzT6UBr2CsOD6nopby7e9M,1993
|
|
3
|
+
paddle/cubed_sphere_ui.py,sha256=2oYfAje6ALuT21fRMFKLYigr59yWDdP3_y6dZ8B4j44,1732
|
|
4
|
+
paddle/cubed_sphere_utils.py,sha256=c7vr0S7JEiMy5UjmEK51gmGZY0j144UjVRsp2wO-iig,11686
|
|
5
|
+
paddle/evolve_kinetics.py,sha256=dMphJqggu18yVqfdcKHbaxkXZhifBfvNjMiDLbdxaOg,2020
|
|
6
|
+
paddle/example_save.py,sha256=MYVOr9J5oTU2NILPnr3qhHdbU7u81Rtca8RjVv8xE6s,610
|
|
7
|
+
paddle/find_init_params.py,sha256=h7ka8fREXjD2RmhflWpByWHFhKoHNCYGN4TATQY0FYg,2532
|
|
8
|
+
paddle/nc2pt.py,sha256=LXR0fnUTaOA_uaDsLU4YqdAVFyycB6SvRq12xWhHhLA,1136
|
|
9
|
+
paddle/plot_cubed_sphere_panel.py,sha256=jK813orIrRHjvZ5WwX3gzAZ1cub8eQFRTqU8cXtU5VM,525
|
|
10
|
+
paddle/pt2nc.py,sha256=lLviBm6a2O05RvuPQc8DxV5pGr_VEhlSkrKPV1lQ3yU,3913
|
|
11
|
+
paddle/setup_profile.py,sha256=A4ityv7RKeOyAEcsGKewxI_VHFeJ3mMGgqrxKnBCkMk,8869
|
|
12
|
+
paddle/write_profile.py,sha256=TFpRvh0OKDIuDfiUqvxrI53AokAkkiVjTtXWXNtXTzU,3912
|
|
13
|
+
paddle-1.2.2.dist-info/METADATA,sha256=7wiw18jCawqiO1ZhgkL1ekQI23w0fWZUyhmXu0f1s98,6005
|
|
14
|
+
paddle-1.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
15
|
+
paddle-1.2.2.dist-info/entry_points.txt,sha256=pDR96GW6ylBZrbFd-tRGthW8qTuYaSLjrEt1LFIEYto,48
|
|
16
|
+
paddle-1.2.2.dist-info/licenses/LICENSE,sha256=e6NthgKABUnLRqjuETcBGgsOuA-aJANpNoeXMe9RBso,1071
|
|
17
|
+
paddle-1.2.2.dist-info/RECORD,,
|
paddle-1.2.0.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
paddle/__init__.py,sha256=RAXFapvqRTn4YjzlDkCj3POZcN3LP43d14YabPEdjzE,281
|
|
2
|
-
paddle/crm.py,sha256=pME4nF5gjlJm-36yMl0N8bzT6UBr2CsOD6nopby7e9M,1993
|
|
3
|
-
paddle/evolve_kinetics.py,sha256=3fkFnYqJRNimOaxuf2FAnMUrMGaaP1vJwSWfRU3R1XM,1553
|
|
4
|
-
paddle/example_save.py,sha256=MYVOr9J5oTU2NILPnr3qhHdbU7u81Rtca8RjVv8xE6s,610
|
|
5
|
-
paddle/find_init_params.py,sha256=EwedId1KljMxOD0lD7RZmWJGJuaItiPPlW_EtW8O6L8,2528
|
|
6
|
-
paddle/nc2pt.py,sha256=LXR0fnUTaOA_uaDsLU4YqdAVFyycB6SvRq12xWhHhLA,1136
|
|
7
|
-
paddle/pt2nc.py,sha256=lLviBm6a2O05RvuPQc8DxV5pGr_VEhlSkrKPV1lQ3yU,3913
|
|
8
|
-
paddle/setup_profile.py,sha256=GkeucqDZvi3ZW5uTf4Y8fMaPe1XGQu1Mhg2NJkO6uD0,8770
|
|
9
|
-
paddle/write_profile.py,sha256=g2e7RIViwiEok0fbfgDgait2dVVYSdzUcu8WGd5sD9g,3914
|
|
10
|
-
paddle-1.2.0.dist-info/METADATA,sha256=9Ae-55Z2CMkb6ymoTVzVXms2_Gq0n1yetcwppi-b2qA,6005
|
|
11
|
-
paddle-1.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
-
paddle-1.2.0.dist-info/entry_points.txt,sha256=pDR96GW6ylBZrbFd-tRGthW8qTuYaSLjrEt1LFIEYto,48
|
|
13
|
-
paddle-1.2.0.dist-info/licenses/LICENSE,sha256=e6NthgKABUnLRqjuETcBGgsOuA-aJANpNoeXMe9RBso,1071
|
|
14
|
-
paddle-1.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|