py-pluto 1.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.
- pyPLUTO/__init__.py +22 -0
- pyPLUTO/amr.py +745 -0
- pyPLUTO/baseloadmixin.py +258 -0
- pyPLUTO/baseloadstate.py +45 -0
- pyPLUTO/codes/echo_load.py +161 -0
- pyPLUTO/configure.py +261 -0
- pyPLUTO/gui/config.py +174 -0
- pyPLUTO/gui/custom_var.py +435 -0
- pyPLUTO/gui/globals.py +108 -0
- pyPLUTO/gui/main.py +17 -0
- pyPLUTO/gui/main_window.py +177 -0
- pyPLUTO/gui/panels.py +66 -0
- pyPLUTO/gui/utils.py +273 -0
- pyPLUTO/h_pypluto.py +84 -0
- pyPLUTO/image.py +302 -0
- pyPLUTO/imagefuncs/colorbar.py +240 -0
- pyPLUTO/imagefuncs/contour.py +254 -0
- pyPLUTO/imagefuncs/create_axes.py +464 -0
- pyPLUTO/imagefuncs/display.py +306 -0
- pyPLUTO/imagefuncs/figure.py +395 -0
- pyPLUTO/imagefuncs/imagetools.py +487 -0
- pyPLUTO/imagefuncs/interactive.py +403 -0
- pyPLUTO/imagefuncs/legend.py +250 -0
- pyPLUTO/imagefuncs/plot.py +311 -0
- pyPLUTO/imagefuncs/range.py +242 -0
- pyPLUTO/imagefuncs/scatter.py +270 -0
- pyPLUTO/imagefuncs/set_axis.py +497 -0
- pyPLUTO/imagefuncs/streamplot.py +297 -0
- pyPLUTO/imagefuncs/zoom.py +428 -0
- pyPLUTO/imagemixin.py +259 -0
- pyPLUTO/imagestate.py +45 -0
- pyPLUTO/load.py +447 -0
- pyPLUTO/loadfuncs/baseloadtools.py +71 -0
- pyPLUTO/loadfuncs/codeselection.py +48 -0
- pyPLUTO/loadfuncs/defpluto.py +123 -0
- pyPLUTO/loadfuncs/descriptor.py +102 -0
- pyPLUTO/loadfuncs/findfiles.py +182 -0
- pyPLUTO/loadfuncs/findformat.py +245 -0
- pyPLUTO/loadfuncs/initload.py +203 -0
- pyPLUTO/loadfuncs/loadvars.py +227 -0
- pyPLUTO/loadfuncs/offsetdata.py +87 -0
- pyPLUTO/loadfuncs/offsetfluid.py +408 -0
- pyPLUTO/loadfuncs/read_files.py +213 -0
- pyPLUTO/loadfuncs/readdata.py +619 -0
- pyPLUTO/loadfuncs/readdata_old.py +567 -0
- pyPLUTO/loadfuncs/readdefplini.py +101 -0
- pyPLUTO/loadfuncs/readfluid.py +479 -0
- pyPLUTO/loadfuncs/readformat.py +277 -0
- pyPLUTO/loadfuncs/readgridalone.py +224 -0
- pyPLUTO/loadfuncs/readgridfile.py +255 -0
- pyPLUTO/loadfuncs/readgridout.py +451 -0
- pyPLUTO/loadfuncs/readpart.py +419 -0
- pyPLUTO/loadfuncs/readtab.py +105 -0
- pyPLUTO/loadfuncs/write_files.py +283 -0
- pyPLUTO/loadmixin.py +419 -0
- pyPLUTO/loadpart.py +233 -0
- pyPLUTO/loadstate.py +68 -0
- pyPLUTO/newload.py +81 -0
- pyPLUTO/pytools.py +145 -0
- pyPLUTO/toolfuncs/findlines.py +551 -0
- pyPLUTO/toolfuncs/fourier.py +149 -0
- pyPLUTO/toolfuncs/nabla.py +676 -0
- pyPLUTO/toolfuncs/parttools.py +152 -0
- pyPLUTO/toolfuncs/transform.py +638 -0
- pyPLUTO/utils/annotator.py +27 -0
- pyPLUTO/utils/inspector.py +145 -0
- pyPLUTO/utils/make_docstrings.py +3 -0
- py_pluto-1.1.4.dist-info/METADATA +218 -0
- py_pluto-1.1.4.dist-info/RECORD +73 -0
- py_pluto-1.1.4.dist-info/WHEEL +5 -0
- py_pluto-1.1.4.dist-info/entry_points.txt +2 -0
- py_pluto-1.1.4.dist-info/licenses/LICENSE +27 -0
- py_pluto-1.1.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _is_number(value: Any) -> bool:
|
|
8
|
+
"""Checks if value is a number.
|
|
9
|
+
|
|
10
|
+
Returns
|
|
11
|
+
-------
|
|
12
|
+
- bool
|
|
13
|
+
True if value is a number, False otherwise.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
- value (not optional): Any
|
|
18
|
+
The value to check.
|
|
19
|
+
|
|
20
|
+
----
|
|
21
|
+
|
|
22
|
+
Examples
|
|
23
|
+
--------
|
|
24
|
+
- Example #1: Check if 1 is a number
|
|
25
|
+
|
|
26
|
+
>>> _is_number(1)
|
|
27
|
+
True
|
|
28
|
+
|
|
29
|
+
- Example #2: Check if '1' is a number
|
|
30
|
+
|
|
31
|
+
>>> _is_number("1")
|
|
32
|
+
False
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
return isinstance(value, (int, float))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _islice_imin_imax(xvalue, xgrid) -> list[int]:
|
|
39
|
+
"""Returns i, imin, imax for xgrid such that xgrid[i] <= xvalue <=
|
|
40
|
+
xgrid[i+1] with a stencil of 3 cells. If the grid is too small, imin
|
|
41
|
+
and imax are set to 0 and N, respectively.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
- i: int
|
|
46
|
+
The central index of the stencil, counting from the minimum.
|
|
47
|
+
- imin: int
|
|
48
|
+
The minimum index of the stencil.
|
|
49
|
+
- imax: int
|
|
50
|
+
The maximum index of the stencil.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
- xvalue (not optional): float
|
|
55
|
+
The value to check.
|
|
56
|
+
- xgrid (not optional): numpy.ndarray
|
|
57
|
+
The grid to check.
|
|
58
|
+
|
|
59
|
+
----
|
|
60
|
+
|
|
61
|
+
Examples
|
|
62
|
+
--------
|
|
63
|
+
- Example # 1: Compute the indices for the stencil of 3 cells
|
|
64
|
+
|
|
65
|
+
>>> _islice_imin_imax(0.5, np.linspace(0, 1, 11))
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
# Compute the grid length and the index of the closest grid point
|
|
69
|
+
N = len(xgrid)
|
|
70
|
+
i = np.argmin(abs(xgrid - xvalue))
|
|
71
|
+
|
|
72
|
+
# Compute the limits of the stencil
|
|
73
|
+
i_min = max(0, i - 1)
|
|
74
|
+
i_max = min(N, i + 2)
|
|
75
|
+
|
|
76
|
+
# If the grid is too small, set imin and imax to 0 and N
|
|
77
|
+
if N < 3:
|
|
78
|
+
i_min, i_max = 0, N
|
|
79
|
+
|
|
80
|
+
# If imin or imax are outside the grid, set them to 0 and/or N
|
|
81
|
+
else:
|
|
82
|
+
i_min = N - 3 if i_max == N else i_min
|
|
83
|
+
i_max = 3 if i_min == 0 else i_max
|
|
84
|
+
|
|
85
|
+
# Return i, imin, imax
|
|
86
|
+
return i - i_min, i_min, i_max
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _get_slice_indices(slice_val, grid, grid_size):
|
|
90
|
+
"""Function to get the slice indices from the slice value.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
- idx (not optional): int
|
|
95
|
+
The index of the slice.
|
|
96
|
+
- idx_min (not optional): int
|
|
97
|
+
The minimum index of the slice.
|
|
98
|
+
- idx_max (not optional): int
|
|
99
|
+
The maximum index of the slice.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
- slice_val: float
|
|
104
|
+
The value of the slice.
|
|
105
|
+
- grid: np.ndarray
|
|
106
|
+
The grid of the data.
|
|
107
|
+
- grid_size: int
|
|
108
|
+
The size of the grid.
|
|
109
|
+
|
|
110
|
+
----
|
|
111
|
+
|
|
112
|
+
Examples
|
|
113
|
+
--------
|
|
114
|
+
- Example # 1: Get the slice indices
|
|
115
|
+
|
|
116
|
+
>>> _get_slice_indices(0.5, np.linspace(0, 1, 11), 11)
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
# If the slice value is a number return the index and the slice
|
|
120
|
+
if _is_number(slice_val):
|
|
121
|
+
# Get the slice indices
|
|
122
|
+
idx, idx_min, idx_max = _islice_imin_imax(slice_val, grid)
|
|
123
|
+
|
|
124
|
+
# Return the indices and the slice
|
|
125
|
+
return idx, slice(idx_min, idx_max)
|
|
126
|
+
|
|
127
|
+
# If the slice value is a list return the indices and the slice
|
|
128
|
+
else:
|
|
129
|
+
# Return the indices and the slice
|
|
130
|
+
return slice(0, grid_size), slice(0, grid_size)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _warning_cylindrical():
|
|
134
|
+
warning_message = """"""
|
|
135
|
+
# CYLINDRICAL geometry has been deprecated since PLUTO v4.4.
|
|
136
|
+
# POLAR may be used instead by excluding the phi-direction
|
|
137
|
+
# (simply set INCLUDE_JDIR to NO in definitions.h).
|
|
138
|
+
# """
|
|
139
|
+
warnings.warn(warning_message, DeprecationWarning, stacklevel=3)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def gradient(
|
|
143
|
+
self,
|
|
144
|
+
var: np.ndarray,
|
|
145
|
+
x1slice: float | int | None = None,
|
|
146
|
+
x2slice: float | int | None = None,
|
|
147
|
+
x3slice: float | int | None = None,
|
|
148
|
+
edge_order: int = 2,
|
|
149
|
+
) -> np.ndarray:
|
|
150
|
+
"""Computes the gradient of a specified field 'var' in all available
|
|
151
|
+
directions using second-order accurate central differences via the
|
|
152
|
+
NumPy gradient() function. The first index of the resulting array
|
|
153
|
+
represents the N gradient components. If 'x1slice', 'x2slice', or
|
|
154
|
+
'x3slice' are specified, the gradient is only computed at the
|
|
155
|
+
corresponding x1, x2, or x3 values. N corresponds to the number of
|
|
156
|
+
employed dimensions unless a slice is taken.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
- np.ndarray
|
|
161
|
+
Gradient of the input field 'var'. The shape of the array depends on the
|
|
162
|
+
number of used spatial dimensions. E.g.:
|
|
163
|
+
3D: (3, self.nx1, self.nx2, self.nx3)
|
|
164
|
+
3D, INCLUDE_JDIR == NO: (2, self.nx1, self.nx3)
|
|
165
|
+
3D, x2slice = constant: (3, self.nx1, self.nx3)
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
- edge_order: int | None, default 2
|
|
170
|
+
The order of accuracy of derivatives at the domain boundaries.
|
|
171
|
+
- var (not optional): np.ndarray
|
|
172
|
+
The field whose gradient is calculated (e.g., 'rho', 'vx1'). Must have
|
|
173
|
+
the same shape as self.rho.
|
|
174
|
+
- x1slice: float | None, default None
|
|
175
|
+
If not None, specifies the constant value for the x1 axis.
|
|
176
|
+
- x2slice: float | None, default None
|
|
177
|
+
If not None, specifies the constant value for the x2 axis.
|
|
178
|
+
- x3slice: float | None, default None
|
|
179
|
+
If not None, specifies the constant value for the x3 axis.
|
|
180
|
+
|
|
181
|
+
----
|
|
182
|
+
|
|
183
|
+
Examples
|
|
184
|
+
--------
|
|
185
|
+
- Example # 1: Compute the gradient of the density field
|
|
186
|
+
|
|
187
|
+
>>> import pyPLUTO as pp
|
|
188
|
+
>>> D = pp.Load(0)
|
|
189
|
+
>>> D.gradient(D.rho)
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
# If the geometry is not defined raise an error
|
|
193
|
+
if self.geom == "UNKNOWN":
|
|
194
|
+
raise ValueError("Unknown geometry nabla cannot be computed!")
|
|
195
|
+
|
|
196
|
+
# if self.geom == 'CYLINDRICAL':
|
|
197
|
+
# _warning_cylindrical()
|
|
198
|
+
|
|
199
|
+
# Unpack the slice values and grids into tuples
|
|
200
|
+
slices = [
|
|
201
|
+
(x1slice, self.x1, self.nx1),
|
|
202
|
+
(x2slice, self.x2, self.nx2),
|
|
203
|
+
(x3slice, self.x3, self.nx3),
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
# Process each slice and store the results
|
|
207
|
+
indices, ranges = zip(
|
|
208
|
+
*[_get_slice_indices(s, g, size) for s, g, size in slices], strict=False
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Unpack the results back into individual variables
|
|
212
|
+
i, j, k = indices
|
|
213
|
+
irange, jrange, krange = ranges
|
|
214
|
+
|
|
215
|
+
if self.dim == 1:
|
|
216
|
+
grad = np.gradient(var[irange], self.x1[irange], edge_order=edge_order)
|
|
217
|
+
return np.asarray(grad)[i]
|
|
218
|
+
|
|
219
|
+
elif self.dim == 2:
|
|
220
|
+
grad = np.gradient(
|
|
221
|
+
var[irange, jrange],
|
|
222
|
+
self.x1[irange],
|
|
223
|
+
self.x2[jrange],
|
|
224
|
+
edge_order=edge_order,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if self.geom in ["SPHERICAL", "POLAR"]:
|
|
228
|
+
rr, _ = np.meshgrid(self.x1[irange], self.x2[jrange], indexing="ij")
|
|
229
|
+
grad[1] /= rr
|
|
230
|
+
|
|
231
|
+
return np.asarray(grad)[:, i, j]
|
|
232
|
+
|
|
233
|
+
elif self.dim == 3:
|
|
234
|
+
if self.nx2 == 1:
|
|
235
|
+
grad = np.gradient(
|
|
236
|
+
var[irange, 0, krange],
|
|
237
|
+
self.x1[irange],
|
|
238
|
+
self.x3[krange],
|
|
239
|
+
edge_order=edge_order,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if self.geom == "SPHERICAL":
|
|
243
|
+
rr, _ = np.meshgrid(
|
|
244
|
+
self.x1[irange], self.x3[krange], indexing="ij"
|
|
245
|
+
)
|
|
246
|
+
grad[1] /= rr * np.sin(self.x2[0])
|
|
247
|
+
|
|
248
|
+
return np.asarray(grad)[:, i, k]
|
|
249
|
+
|
|
250
|
+
else:
|
|
251
|
+
grad = np.gradient(
|
|
252
|
+
var[irange, jrange, krange],
|
|
253
|
+
self.x1[irange],
|
|
254
|
+
self.x2[jrange],
|
|
255
|
+
self.x3[krange],
|
|
256
|
+
edge_order=edge_order,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if self.geom != "CARTESIAN":
|
|
260
|
+
xx1, xx2, _ = np.meshgrid(
|
|
261
|
+
self.x1[irange],
|
|
262
|
+
self.x2[jrange],
|
|
263
|
+
self.x3[krange],
|
|
264
|
+
indexing="ij",
|
|
265
|
+
)
|
|
266
|
+
grad[1] /= xx1
|
|
267
|
+
if self.geom == "SPHERICAL":
|
|
268
|
+
grad[2] /= xx1 * np.sin(xx2)
|
|
269
|
+
|
|
270
|
+
return np.asarray(grad)[:, i, j, k]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def divergence(
|
|
274
|
+
self,
|
|
275
|
+
v1: np.ndarray | None = None,
|
|
276
|
+
v2: np.ndarray | None = None,
|
|
277
|
+
v3: np.ndarray | None = None,
|
|
278
|
+
x1slice: float | int | None = None,
|
|
279
|
+
x2slice: float | int | None = None,
|
|
280
|
+
x3slice: float | int | None = None,
|
|
281
|
+
edge_order: int = 2,
|
|
282
|
+
) -> np.ndarray:
|
|
283
|
+
"""Calculates the divergence of a vector field specified by its
|
|
284
|
+
components v1, v2, and v3 using second-order accurate central
|
|
285
|
+
differences via the NumPy gradient() function. If 'x1slice',
|
|
286
|
+
'x2slice', or 'x3slice' are specified, the divergence is only
|
|
287
|
+
computed at the corresponding x1, x2, or x3 values.
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
- np.ndarray
|
|
292
|
+
Array corresponding to the divergence of the input vector field. In 3D,
|
|
293
|
+
e.g., its shape is (self.nx1, self.nx2, self.nx3), while if
|
|
294
|
+
INCLUDE_JDIR == NO (or if x2slice = constant), its shape is
|
|
295
|
+
(self.nx1, self.nx3).
|
|
296
|
+
|
|
297
|
+
Parameters
|
|
298
|
+
----------
|
|
299
|
+
- edge_order: int, default 2
|
|
300
|
+
The order of accuracy of derivatives at the domain boundaries.
|
|
301
|
+
- v1: np.ndarray | None
|
|
302
|
+
Field corresponding to the x1 vector component. Must have the same shape
|
|
303
|
+
as self.rho. Can only be None is a given direction is not used.
|
|
304
|
+
- v2: np.ndarray | None
|
|
305
|
+
Field corresponding to the x2 vector component. Must have the same shape
|
|
306
|
+
as self.rho. Can only be None is a given direction is not used.
|
|
307
|
+
- v3: np.ndarray | None
|
|
308
|
+
Field corresponding to the x3 vector component. Must have the same shape
|
|
309
|
+
as self.rho. Can only be None is a given direction is not used.
|
|
310
|
+
- x1slice: float | None
|
|
311
|
+
If not None, specifies the constant value for the x1 axis.
|
|
312
|
+
- x2slice: float | None
|
|
313
|
+
If not None, specifies the constant value for the x2 axis.
|
|
314
|
+
- x3slice: float | None
|
|
315
|
+
If not None, specifies the constant value for the x3 axis.
|
|
316
|
+
|
|
317
|
+
----
|
|
318
|
+
|
|
319
|
+
Examples
|
|
320
|
+
--------
|
|
321
|
+
- Example #1: Calculate the divergence of a vector field
|
|
322
|
+
|
|
323
|
+
>>> import pyPLUTO as pp
|
|
324
|
+
>>> D = pp.Load()
|
|
325
|
+
>>> D.divergence(D.vx1, D.vx2, D.vx3)
|
|
326
|
+
|
|
327
|
+
"""
|
|
328
|
+
if self.geom == "CYLINDRICAL":
|
|
329
|
+
_warning_cylindrical()
|
|
330
|
+
|
|
331
|
+
if _is_number(x1slice):
|
|
332
|
+
i, imin, imax = _islice_imin_imax(x1slice, self.x1)
|
|
333
|
+
irange = slice(imin, imax)
|
|
334
|
+
else:
|
|
335
|
+
i = slice(0, self.nx1)
|
|
336
|
+
irange = i
|
|
337
|
+
|
|
338
|
+
if _is_number(x2slice):
|
|
339
|
+
j, jmin, jmax = _islice_imin_imax(x2slice, self.x2)
|
|
340
|
+
jrange = slice(jmin, jmax)
|
|
341
|
+
else:
|
|
342
|
+
j = slice(0, self.nx2)
|
|
343
|
+
jrange = j
|
|
344
|
+
|
|
345
|
+
if _is_number(x3slice):
|
|
346
|
+
k, kmin, kmax = _islice_imin_imax(x3slice, self.x3)
|
|
347
|
+
krange = slice(kmin, kmax)
|
|
348
|
+
else:
|
|
349
|
+
k = slice(0, self.nx3)
|
|
350
|
+
krange = k
|
|
351
|
+
|
|
352
|
+
if self.dim == 1:
|
|
353
|
+
var1 = np.copy(v1[irange])
|
|
354
|
+
|
|
355
|
+
rr = self.x1[irange]
|
|
356
|
+
|
|
357
|
+
if self.geom in ["POLAR", "CYLINDRICAL"]:
|
|
358
|
+
var1 *= rr
|
|
359
|
+
elif self.geom == "SPHERICAL":
|
|
360
|
+
var1 *= rr**2
|
|
361
|
+
|
|
362
|
+
div1 = np.gradient(var1, rr, edge_order=edge_order)
|
|
363
|
+
|
|
364
|
+
if self.geom in ["POLAR", "CYLINDRICAL"]:
|
|
365
|
+
div1 /= rr
|
|
366
|
+
elif self.geom == "SPHERICAL":
|
|
367
|
+
div1 /= rr**2
|
|
368
|
+
|
|
369
|
+
return div1[i]
|
|
370
|
+
|
|
371
|
+
elif self.dim == 2:
|
|
372
|
+
var1 = np.copy(v1[irange, jrange])
|
|
373
|
+
var2 = np.copy(v2[irange, jrange])
|
|
374
|
+
|
|
375
|
+
if self.geom != "CARTESIAN":
|
|
376
|
+
rr, tt = np.meshgrid(
|
|
377
|
+
self.x1[irange], self.x2[jrange], indexing="ij"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if self.geom in ["POLAR", "CYLINDRICAL"]:
|
|
381
|
+
var1 *= rr
|
|
382
|
+
elif self.geom == "SPHERICAL":
|
|
383
|
+
var1 *= rr**2
|
|
384
|
+
var2 *= np.sin(tt)
|
|
385
|
+
|
|
386
|
+
div1 = np.gradient(var1, self.x1[irange], axis=0, edge_order=edge_order)
|
|
387
|
+
div2 = np.gradient(var2, self.x2[jrange], axis=1, edge_order=edge_order)
|
|
388
|
+
|
|
389
|
+
if self.geom in ["POLAR", "CYLINDRICAL"]:
|
|
390
|
+
div1 /= rr
|
|
391
|
+
if self.geom == "POLAR":
|
|
392
|
+
div2 /= rr
|
|
393
|
+
elif self.geom == "SPHERICAL":
|
|
394
|
+
div1 /= rr**2
|
|
395
|
+
div2 /= rr * np.sin(tt)
|
|
396
|
+
|
|
397
|
+
return div1[i, j] + div2[i, j]
|
|
398
|
+
|
|
399
|
+
elif self.dim == 3:
|
|
400
|
+
if self.nx2 == 1:
|
|
401
|
+
var1 = np.copy(v1[irange, 0, krange])
|
|
402
|
+
var3 = np.copy(v3[irange, 0, krange])
|
|
403
|
+
|
|
404
|
+
if self.geom != "CARTESIAN":
|
|
405
|
+
rr, _ = np.meshgrid(
|
|
406
|
+
self.x1[irange], self.x3[krange], indexing="ij"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
if self.geom == "POLAR":
|
|
410
|
+
var1 *= rr
|
|
411
|
+
elif self.geom == "SPHERICAL":
|
|
412
|
+
var1 *= rr**2
|
|
413
|
+
|
|
414
|
+
div1 = np.gradient(
|
|
415
|
+
var1, self.x1[irange], axis=0, edge_order=edge_order
|
|
416
|
+
)
|
|
417
|
+
div3 = np.gradient(
|
|
418
|
+
var3, self.x3[krange], axis=1, edge_order=edge_order
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if self.geom == "POLAR":
|
|
422
|
+
div1 /= rr
|
|
423
|
+
elif self.geom == "SPHERICAL":
|
|
424
|
+
div1 /= rr**2
|
|
425
|
+
div3 /= rr * np.sin(self.x2[0])
|
|
426
|
+
|
|
427
|
+
return div1[i, k] + div3[i, k]
|
|
428
|
+
|
|
429
|
+
else:
|
|
430
|
+
var1 = np.copy(v1[irange, jrange, krange])
|
|
431
|
+
var2 = np.copy(v2[irange, jrange, krange])
|
|
432
|
+
var3 = np.copy(v3[irange, jrange, krange])
|
|
433
|
+
|
|
434
|
+
if self.geom != "CARTESIAN":
|
|
435
|
+
rr, tt, _ = np.meshgrid(
|
|
436
|
+
self.x1[irange],
|
|
437
|
+
self.x2[jrange],
|
|
438
|
+
self.x3[krange],
|
|
439
|
+
indexing="ij",
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
if self.geom == "POLAR":
|
|
443
|
+
var1 *= rr
|
|
444
|
+
elif self.geom == "SPHERICAL":
|
|
445
|
+
var1 *= rr**2
|
|
446
|
+
var2 *= np.sin(tt)
|
|
447
|
+
|
|
448
|
+
div1 = np.gradient(
|
|
449
|
+
var1, self.x1[irange], axis=0, edge_order=edge_order
|
|
450
|
+
)
|
|
451
|
+
div2 = np.gradient(
|
|
452
|
+
var2, self.x2[jrange], axis=1, edge_order=edge_order
|
|
453
|
+
)
|
|
454
|
+
div3 = np.gradient(
|
|
455
|
+
var3, self.x3[krange], axis=2, edge_order=edge_order
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
if self.geom == "POLAR":
|
|
459
|
+
div1 /= rr
|
|
460
|
+
div2 /= rr
|
|
461
|
+
elif self.geom == "SPHERICAL":
|
|
462
|
+
div1 /= rr**2
|
|
463
|
+
div2 /= rr * np.sin(tt)
|
|
464
|
+
div3 /= rr * np.sin(tt)
|
|
465
|
+
|
|
466
|
+
return div1[i, j, k] + div2[i, j, k] + div3[i, j, k]
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def curl(
|
|
470
|
+
self,
|
|
471
|
+
v1: np.ndarray | None = None,
|
|
472
|
+
v2: np.ndarray | None = None,
|
|
473
|
+
v3: np.ndarray | None = None,
|
|
474
|
+
x1slice: float | int | None = None,
|
|
475
|
+
x2slice: float | int | None = None,
|
|
476
|
+
x3slice: float | int | None = None,
|
|
477
|
+
edge_order: int = 2,
|
|
478
|
+
) -> np.ndarray:
|
|
479
|
+
"""Calculates the curl of a specified vector field (v1, v2, v3)
|
|
480
|
+
using second-order accurate central differences via the NumPy
|
|
481
|
+
gradient() function. Unlike in divergence(), all three vector
|
|
482
|
+
components must be specified. The resulting array has its first
|
|
483
|
+
index representing the 3 curl components, while the remaining
|
|
484
|
+
indices correspond to the grid location. If 'x1slice', 'x2slice', or
|
|
485
|
+
'x3slice' are specified, the curl is only computed at the
|
|
486
|
+
corresponding x1, x2, or x3 values.
|
|
487
|
+
|
|
488
|
+
Returns
|
|
489
|
+
-------
|
|
490
|
+
- np.ndarray
|
|
491
|
+
Curl of the specified vector field (v1, v2, v3). In 3D, e.g., its shape
|
|
492
|
+
is (3, self.nx1, self.nx2, self.nx3), while if INCLUDE_JDIR == NO (or if
|
|
493
|
+
x2slice = constant), its shape is (3, self.nx1, self.nx3).
|
|
494
|
+
|
|
495
|
+
Parameters
|
|
496
|
+
----------
|
|
497
|
+
- edge_order: int, default 2
|
|
498
|
+
The order of accuracy of derivatives at the domain boundaries.
|
|
499
|
+
- v1: np.ndarray | None
|
|
500
|
+
Field corresponding to the x1 vector component. Must have the same shape
|
|
501
|
+
as self.rho.
|
|
502
|
+
- v2: np.ndarray | None
|
|
503
|
+
Field corresponding to the x2 vector component. Must have the same shape
|
|
504
|
+
as self.rho.
|
|
505
|
+
- v3: np.ndarray | None
|
|
506
|
+
Field corresponding to the x3 vector component. Must have the same shape
|
|
507
|
+
as self.rho.
|
|
508
|
+
- x1slice: float | None
|
|
509
|
+
If not None, specifies the constant value for the x1 axis.
|
|
510
|
+
- x2slice: float | None
|
|
511
|
+
If not None, specifies the constant value for the x2 axis.
|
|
512
|
+
- x3slice: float | None
|
|
513
|
+
If not None, specifies the constant value for the x3 axis.
|
|
514
|
+
|
|
515
|
+
----
|
|
516
|
+
|
|
517
|
+
Examples
|
|
518
|
+
--------
|
|
519
|
+
- Example #1: Calculate the curl of a vector field
|
|
520
|
+
|
|
521
|
+
>>> import pyPLUTO as pp
|
|
522
|
+
>>> D = pp.Load()
|
|
523
|
+
>>> D.curl(D.vx1, D.vx2, D.vx3)
|
|
524
|
+
|
|
525
|
+
"""
|
|
526
|
+
if self.geom == "CYLINDRICAL":
|
|
527
|
+
_warning_cylindrical()
|
|
528
|
+
|
|
529
|
+
if _is_number(x1slice):
|
|
530
|
+
i, imin, imax = _islice_imin_imax(x1slice, self.x1)
|
|
531
|
+
irange = slice(imin, imax)
|
|
532
|
+
else:
|
|
533
|
+
i = slice(0, self.nx1)
|
|
534
|
+
irange = i
|
|
535
|
+
|
|
536
|
+
if _is_number(x2slice):
|
|
537
|
+
j, jmin, jmax = _islice_imin_imax(x2slice, self.x2)
|
|
538
|
+
jrange = slice(jmin, jmax)
|
|
539
|
+
else:
|
|
540
|
+
j = slice(0, self.nx2)
|
|
541
|
+
jrange = j
|
|
542
|
+
|
|
543
|
+
if _is_number(x3slice):
|
|
544
|
+
k, kmin, kmax = _islice_imin_imax(x3slice, self.x3)
|
|
545
|
+
krange = slice(kmin, kmax)
|
|
546
|
+
else:
|
|
547
|
+
k = slice(0, self.nx3)
|
|
548
|
+
krange = k
|
|
549
|
+
|
|
550
|
+
if self.dim == 1:
|
|
551
|
+
raise ValueError("curl() requires DIMENSION >= 2")
|
|
552
|
+
|
|
553
|
+
elif self.dim == 2:
|
|
554
|
+
var1 = np.copy(v1[irange, jrange])
|
|
555
|
+
var2 = np.copy(v2[irange, jrange])
|
|
556
|
+
var3 = np.copy(v3[irange, jrange])
|
|
557
|
+
|
|
558
|
+
if self.geom != "CARTESIAN":
|
|
559
|
+
rr, tt = np.meshgrid(
|
|
560
|
+
self.x1[irange], self.x2[jrange], indexing="ij"
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
if self.geom == "POLAR":
|
|
564
|
+
var2 *= rr
|
|
565
|
+
elif self.geom in "CYLINDRICAL":
|
|
566
|
+
var3 *= rr
|
|
567
|
+
elif self.geom == "SPHERICAL":
|
|
568
|
+
var2 *= rr
|
|
569
|
+
var3 *= rr * np.sin(tt)
|
|
570
|
+
|
|
571
|
+
dv1_dx2 = np.gradient(var1, self.x2, axis=1, edge_order=edge_order)
|
|
572
|
+
dv2_dx1 = np.gradient(var2, self.x1, axis=0, edge_order=edge_order)
|
|
573
|
+
dv3_dx1, dv3_dx2 = np.gradient(
|
|
574
|
+
var3, self.x1, self.x2, edge_order=edge_order
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
curl1 = dv3_dx2
|
|
578
|
+
curl2 = -dv3_dx1
|
|
579
|
+
curl3 = dv2_dx1 - dv1_dx2
|
|
580
|
+
if self.geom == "CYLINDRICAL":
|
|
581
|
+
curl1 *= -1
|
|
582
|
+
curl2 *= -1
|
|
583
|
+
curl3 *= -1
|
|
584
|
+
|
|
585
|
+
if self.geom == "POLAR":
|
|
586
|
+
curl1 /= rr
|
|
587
|
+
curl3 /= rr
|
|
588
|
+
elif self.geom == "CYLINDRICAL":
|
|
589
|
+
curl1 /= rr
|
|
590
|
+
curl2 /= rr
|
|
591
|
+
elif self.geom == "SPHERICAL":
|
|
592
|
+
curl1 /= rr**2 * np.sin(tt)
|
|
593
|
+
curl2 /= rr * np.sin(tt)
|
|
594
|
+
curl3 /= rr
|
|
595
|
+
|
|
596
|
+
return np.asarray([curl1[i, j], curl2[i, j], curl3[i, j]])
|
|
597
|
+
|
|
598
|
+
elif self.dim == 3:
|
|
599
|
+
if self.nx2 == 1:
|
|
600
|
+
var1 = np.copy(v1[irange, 0, krange])
|
|
601
|
+
var2 = np.copy(v2[irange, 0, krange])
|
|
602
|
+
var3 = np.copy(v3[irange, 0, krange])
|
|
603
|
+
|
|
604
|
+
if self.geom != "CARTESIAN":
|
|
605
|
+
rr, _ = np.meshgrid(
|
|
606
|
+
self.x1[irange], self.x3[krange], indexing="ij"
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
if self.geom == "POLAR":
|
|
610
|
+
var2 *= rr
|
|
611
|
+
elif self.geom == "SPHERICAL":
|
|
612
|
+
var2 *= rr
|
|
613
|
+
var3 *= rr * np.sin(self.x2[0])
|
|
614
|
+
|
|
615
|
+
dv1_dx3 = np.gradient(var1, self.x3, axis=1, edge_order=edge_order)
|
|
616
|
+
dv2_dx1, dv2_dx3 = np.gradient(
|
|
617
|
+
var2, self.x1, self.x3, edge_order=edge_order
|
|
618
|
+
)
|
|
619
|
+
dv3_dx1 = np.gradient(var3, self.x1, axis=0, edge_order=edge_order)
|
|
620
|
+
|
|
621
|
+
curl1 = -dv2_dx3
|
|
622
|
+
curl2 = dv1_dx3 - dv3_dx1
|
|
623
|
+
curl3 = dv2_dx1
|
|
624
|
+
|
|
625
|
+
if self.geom == "POLAR":
|
|
626
|
+
curl1 /= rr
|
|
627
|
+
curl3 /= rr
|
|
628
|
+
elif self.geom == "SPHERICAL":
|
|
629
|
+
curl1 /= rr**2 * np.sin(self.x2[0])
|
|
630
|
+
curl2 /= rr * np.sin(self.x2[0])
|
|
631
|
+
curl3 /= rr
|
|
632
|
+
|
|
633
|
+
return np.asarray([curl1[i, k], curl2[i, k], curl3[i, k]])
|
|
634
|
+
|
|
635
|
+
else:
|
|
636
|
+
var1 = np.copy(v1[irange, jrange, krange])
|
|
637
|
+
var2 = np.copy(v2[irange, jrange, krange])
|
|
638
|
+
var3 = np.copy(v3[irange, jrange, krange])
|
|
639
|
+
|
|
640
|
+
if self.geom != "CARTESIAN":
|
|
641
|
+
rr, tt, _ = np.meshgrid(
|
|
642
|
+
self.x1[irange],
|
|
643
|
+
self.x2[jrange],
|
|
644
|
+
self.x3[krange],
|
|
645
|
+
indexing="ij",
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
if self.geom == "POLAR":
|
|
649
|
+
var2 *= rr
|
|
650
|
+
elif self.geom == "SPHERICAL":
|
|
651
|
+
var2 *= rr
|
|
652
|
+
var3 *= rr * np.sin(tt)
|
|
653
|
+
|
|
654
|
+
dv1_dx2, dv1_dx3 = np.gradient(
|
|
655
|
+
var1, self.x2, self.x3, axis=[1, 2], edge_order=edge_order
|
|
656
|
+
)
|
|
657
|
+
dv2_dx1, dv2_dx3 = np.gradient(
|
|
658
|
+
var2, self.x1, self.x3, axis=[0, 2], edge_order=edge_order
|
|
659
|
+
)
|
|
660
|
+
dv3_dx1, dv3_dx2 = np.gradient(
|
|
661
|
+
var3, self.x1, self.x2, axis=[0, 1], edge_order=edge_order
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
curl1 = dv3_dx2 - dv2_dx3
|
|
665
|
+
curl2 = dv1_dx3 - dv3_dx1
|
|
666
|
+
curl3 = dv2_dx1 - dv1_dx2
|
|
667
|
+
|
|
668
|
+
if self.geom == "POLAR":
|
|
669
|
+
curl1 /= rr
|
|
670
|
+
curl3 /= rr
|
|
671
|
+
elif self.geom == "SPHERICAL":
|
|
672
|
+
curl1 /= rr**2 * np.sin(tt)
|
|
673
|
+
curl2 /= rr * np.sin(tt)
|
|
674
|
+
curl3 /= rr
|
|
675
|
+
|
|
676
|
+
return np.asarray([curl1[i, j, k], curl2[i, j, k], curl3[i, j, k]])
|