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.
Files changed (73) hide show
  1. pyPLUTO/__init__.py +22 -0
  2. pyPLUTO/amr.py +745 -0
  3. pyPLUTO/baseloadmixin.py +258 -0
  4. pyPLUTO/baseloadstate.py +45 -0
  5. pyPLUTO/codes/echo_load.py +161 -0
  6. pyPLUTO/configure.py +261 -0
  7. pyPLUTO/gui/config.py +174 -0
  8. pyPLUTO/gui/custom_var.py +435 -0
  9. pyPLUTO/gui/globals.py +108 -0
  10. pyPLUTO/gui/main.py +17 -0
  11. pyPLUTO/gui/main_window.py +177 -0
  12. pyPLUTO/gui/panels.py +66 -0
  13. pyPLUTO/gui/utils.py +273 -0
  14. pyPLUTO/h_pypluto.py +84 -0
  15. pyPLUTO/image.py +302 -0
  16. pyPLUTO/imagefuncs/colorbar.py +240 -0
  17. pyPLUTO/imagefuncs/contour.py +254 -0
  18. pyPLUTO/imagefuncs/create_axes.py +464 -0
  19. pyPLUTO/imagefuncs/display.py +306 -0
  20. pyPLUTO/imagefuncs/figure.py +395 -0
  21. pyPLUTO/imagefuncs/imagetools.py +487 -0
  22. pyPLUTO/imagefuncs/interactive.py +403 -0
  23. pyPLUTO/imagefuncs/legend.py +250 -0
  24. pyPLUTO/imagefuncs/plot.py +311 -0
  25. pyPLUTO/imagefuncs/range.py +242 -0
  26. pyPLUTO/imagefuncs/scatter.py +270 -0
  27. pyPLUTO/imagefuncs/set_axis.py +497 -0
  28. pyPLUTO/imagefuncs/streamplot.py +297 -0
  29. pyPLUTO/imagefuncs/zoom.py +428 -0
  30. pyPLUTO/imagemixin.py +259 -0
  31. pyPLUTO/imagestate.py +45 -0
  32. pyPLUTO/load.py +447 -0
  33. pyPLUTO/loadfuncs/baseloadtools.py +71 -0
  34. pyPLUTO/loadfuncs/codeselection.py +48 -0
  35. pyPLUTO/loadfuncs/defpluto.py +123 -0
  36. pyPLUTO/loadfuncs/descriptor.py +102 -0
  37. pyPLUTO/loadfuncs/findfiles.py +182 -0
  38. pyPLUTO/loadfuncs/findformat.py +245 -0
  39. pyPLUTO/loadfuncs/initload.py +203 -0
  40. pyPLUTO/loadfuncs/loadvars.py +227 -0
  41. pyPLUTO/loadfuncs/offsetdata.py +87 -0
  42. pyPLUTO/loadfuncs/offsetfluid.py +408 -0
  43. pyPLUTO/loadfuncs/read_files.py +213 -0
  44. pyPLUTO/loadfuncs/readdata.py +619 -0
  45. pyPLUTO/loadfuncs/readdata_old.py +567 -0
  46. pyPLUTO/loadfuncs/readdefplini.py +101 -0
  47. pyPLUTO/loadfuncs/readfluid.py +479 -0
  48. pyPLUTO/loadfuncs/readformat.py +277 -0
  49. pyPLUTO/loadfuncs/readgridalone.py +224 -0
  50. pyPLUTO/loadfuncs/readgridfile.py +255 -0
  51. pyPLUTO/loadfuncs/readgridout.py +451 -0
  52. pyPLUTO/loadfuncs/readpart.py +419 -0
  53. pyPLUTO/loadfuncs/readtab.py +105 -0
  54. pyPLUTO/loadfuncs/write_files.py +283 -0
  55. pyPLUTO/loadmixin.py +419 -0
  56. pyPLUTO/loadpart.py +233 -0
  57. pyPLUTO/loadstate.py +68 -0
  58. pyPLUTO/newload.py +81 -0
  59. pyPLUTO/pytools.py +145 -0
  60. pyPLUTO/toolfuncs/findlines.py +551 -0
  61. pyPLUTO/toolfuncs/fourier.py +149 -0
  62. pyPLUTO/toolfuncs/nabla.py +676 -0
  63. pyPLUTO/toolfuncs/parttools.py +152 -0
  64. pyPLUTO/toolfuncs/transform.py +638 -0
  65. pyPLUTO/utils/annotator.py +27 -0
  66. pyPLUTO/utils/inspector.py +145 -0
  67. pyPLUTO/utils/make_docstrings.py +3 -0
  68. py_pluto-1.1.4.dist-info/METADATA +218 -0
  69. py_pluto-1.1.4.dist-info/RECORD +73 -0
  70. py_pluto-1.1.4.dist-info/WHEEL +5 -0
  71. py_pluto-1.1.4.dist-info/entry_points.txt +2 -0
  72. py_pluto-1.1.4.dist-info/licenses/LICENSE +27 -0
  73. 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]])