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,551 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import contourpy as cp
|
|
4
|
+
import matplotlib.colors as mcol
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import numpy as np
|
|
7
|
+
from numpy.typing import NDArray
|
|
8
|
+
from scipy.integrate import solve_ivp
|
|
9
|
+
|
|
10
|
+
from ..h_pypluto import check_par, makelist
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _check_var(self, var: str | NDArray, transpose: bool = False) -> np.ndarray:
|
|
14
|
+
"""Function that checks returns a variable. If the variable is a
|
|
15
|
+
numpy array, it is simply returned (and the transpose is taken into
|
|
16
|
+
account). If the variable is a string, the variable is retrieved
|
|
17
|
+
from the dataset. If the variable is not found, an error is raised.
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
- var: np.ndarray
|
|
22
|
+
The variable.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
- var (not optional): str | np.ndarray
|
|
27
|
+
The variable to be checked.
|
|
28
|
+
- transpose: bool, default False
|
|
29
|
+
If True, the variable is transposed.
|
|
30
|
+
|
|
31
|
+
----
|
|
32
|
+
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
- Example #1: var is a numpy array
|
|
36
|
+
|
|
37
|
+
>>> var = np.array([1, 2, 3])
|
|
38
|
+
>>> D._check_var(var, False)
|
|
39
|
+
array([1, 2, 3])
|
|
40
|
+
|
|
41
|
+
- Example #2: var is a string
|
|
42
|
+
|
|
43
|
+
>>> D = pp.Load()
|
|
44
|
+
>>> D._check_var("Bx1", False)
|
|
45
|
+
D.Bx1
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
# If var is a string the code tries to recover it from the class attributes,
|
|
49
|
+
# if failed it raises an error
|
|
50
|
+
if isinstance(var, str):
|
|
51
|
+
try:
|
|
52
|
+
var = getattr(self, var)
|
|
53
|
+
except ValueError:
|
|
54
|
+
raise ValueError(f"Variable {var} not found in the dataset.")
|
|
55
|
+
|
|
56
|
+
# If the transpose keyword is True, the variable is transposed.
|
|
57
|
+
if transpose is True:
|
|
58
|
+
var = var.T
|
|
59
|
+
|
|
60
|
+
# Return the variable
|
|
61
|
+
return var
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _vector_field(
|
|
65
|
+
t: float,
|
|
66
|
+
y: np.ndarray,
|
|
67
|
+
var1: np.ndarray,
|
|
68
|
+
var2: np.ndarray,
|
|
69
|
+
xc: np.ndarray,
|
|
70
|
+
yc: np.ndarray,
|
|
71
|
+
) -> list[np.ndarray]:
|
|
72
|
+
"""Compute the vector field at the given time and coordinates by
|
|
73
|
+
interpolating the variables var1 and var2 at the given coordinates.
|
|
74
|
+
The interpolation is made through the routine np.interpolate.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
- [qx, qy]: list[np.ndarray]
|
|
79
|
+
The x1 and x2 vector field components, within a list
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
- t (not optional): float
|
|
84
|
+
The time variable (not used here).
|
|
85
|
+
- var1 (not optional): np.ndarray
|
|
86
|
+
The first variable to be interpolated.
|
|
87
|
+
- var2 (not optional): np.ndarray
|
|
88
|
+
The second variable to be interpolated.
|
|
89
|
+
- xc (not optional): np.ndarray
|
|
90
|
+
The x coordinates of the grid.
|
|
91
|
+
- y (not optional): np.ndarray
|
|
92
|
+
The coordinates. The first and second dimension are x and y.
|
|
93
|
+
- yc (not optional): np.ndarray
|
|
94
|
+
The y coordinates of the grid.
|
|
95
|
+
|
|
96
|
+
----
|
|
97
|
+
|
|
98
|
+
Examples
|
|
99
|
+
--------
|
|
100
|
+
- Example #1: Compute the vector field at the given time and coordinates
|
|
101
|
+
|
|
102
|
+
>>> vector_field(t, y, var1, var2, xc, yc)
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
# Get the coordinates
|
|
106
|
+
x, y = y
|
|
107
|
+
|
|
108
|
+
# Compute indices for closest grid points in xc and yc
|
|
109
|
+
i0 = np.abs(x - xc).argmin()
|
|
110
|
+
j0 = np.abs(y - yc).argmin()
|
|
111
|
+
|
|
112
|
+
# Interpolate U and V at the given coordinates
|
|
113
|
+
scrhUx = np.interp(x, xc, var1[:, j0])
|
|
114
|
+
scrhUy = np.interp(y, yc, var1[i0])
|
|
115
|
+
scrhVx = np.interp(x, xc, var2[:, j0])
|
|
116
|
+
scrhVy = np.interp(y, yc, var2[i0])
|
|
117
|
+
|
|
118
|
+
# Compute the resulting vector field components
|
|
119
|
+
qx = scrhUx + scrhUy - var1[i0, j0]
|
|
120
|
+
qy = scrhVx + scrhVy - var2[i0, j0]
|
|
121
|
+
|
|
122
|
+
# Return the vector field
|
|
123
|
+
return [qx, qy]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def find_fieldlines(
|
|
127
|
+
self,
|
|
128
|
+
var1: str | np.ndarray,
|
|
129
|
+
var2: str | np.ndarray,
|
|
130
|
+
x0: list | float | None = None,
|
|
131
|
+
y0: list | float | None = None,
|
|
132
|
+
text: bool = False,
|
|
133
|
+
check: bool = True,
|
|
134
|
+
**kwargs: Any,
|
|
135
|
+
) -> list:
|
|
136
|
+
"""Find field lines using the vector field. The field lines are
|
|
137
|
+
computed by interpolating the variables var1 and var2 at the
|
|
138
|
+
footpoints x0 and y0. Different integration algorithms are
|
|
139
|
+
available, based on the method solve_ivp of the scipy package.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
- linelist: list
|
|
144
|
+
A list of lists containing the coordinates of the field lines.
|
|
145
|
+
The strcuture of the list is [[x1, y1], [x2, y2], ...] where
|
|
146
|
+
x1, y1, x2, y2 are numpy arrays representing the coordinates of
|
|
147
|
+
the field lines.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
- atol: float, default 1e-6
|
|
152
|
+
The absolute tolerance for the integration.
|
|
153
|
+
- close: bool, default True
|
|
154
|
+
If True, it checks if the line is closed on itself.
|
|
155
|
+
- ctol: float, default 1e-6
|
|
156
|
+
The absolute tolerance for line closing on itself.
|
|
157
|
+
- dense: bool, default False
|
|
158
|
+
If True, the grid is dense (dense=True) or sparse (dense=False).
|
|
159
|
+
- maxstep: float, default 100*step
|
|
160
|
+
The maximum step size for the integration.
|
|
161
|
+
- minstep: float, default 0.05*step
|
|
162
|
+
The minimum step size for the integration (only used if order is LSODA).
|
|
163
|
+
- numsteps: int, default 16384
|
|
164
|
+
The maximum number of steps for the integration.
|
|
165
|
+
- order: str, default 'RK45'
|
|
166
|
+
The integration method. Available options are: 'RK45', 'RK23', 'DOP853',
|
|
167
|
+
'Radau', 'BDF', 'LSODA'.
|
|
168
|
+
- rtol: float, default 1e-6
|
|
169
|
+
The relative tolerance for the integration.
|
|
170
|
+
- step: float, default abs(min((xend-xbeg)/self.nx1, (yend-ybeg)/self.nx2))
|
|
171
|
+
The initial step size for the integration.
|
|
172
|
+
- text: bool, default False
|
|
173
|
+
If True some additional information is printed.
|
|
174
|
+
- transpose: bool, default False
|
|
175
|
+
If True, the variables are transposed.
|
|
176
|
+
- var1 (not optional): str | NDArray
|
|
177
|
+
The first variable to be interpolated.
|
|
178
|
+
- var2 (not optional): str | NDArray
|
|
179
|
+
The second variable to be interpolated.
|
|
180
|
+
- x0: list
|
|
181
|
+
The x coordinates of the footpoints.
|
|
182
|
+
- x1: NDArray | list | None, default self.x1
|
|
183
|
+
The x coordinates of the grid.
|
|
184
|
+
- x2: NDArray | list | None, default self.x2
|
|
185
|
+
The y coordinates of the grid.
|
|
186
|
+
- y0: list
|
|
187
|
+
The y coordinates of the footpoints.
|
|
188
|
+
|
|
189
|
+
----
|
|
190
|
+
|
|
191
|
+
Examples
|
|
192
|
+
--------
|
|
193
|
+
- Example #1: Find field lines using the vector field
|
|
194
|
+
|
|
195
|
+
>>> find_fieldlines(var1, var2, x0, y0)
|
|
196
|
+
|
|
197
|
+
- Example #2: Find field lines using two strings 'Bx1' and 'Bx2'
|
|
198
|
+
|
|
199
|
+
>>> find_fieldlines("Bx1", "Bx2", x0, y0)
|
|
200
|
+
|
|
201
|
+
- Example #3: Find field lines using two variables and two footpoints
|
|
202
|
+
|
|
203
|
+
>>> find_fieldlines(var1, var2, [x1, x2], [y1, y2])
|
|
204
|
+
|
|
205
|
+
"""
|
|
206
|
+
# Check parameters
|
|
207
|
+
param = {
|
|
208
|
+
"atol",
|
|
209
|
+
"close",
|
|
210
|
+
"ctol",
|
|
211
|
+
"dense",
|
|
212
|
+
"maxstep",
|
|
213
|
+
"minstep",
|
|
214
|
+
"numsteps",
|
|
215
|
+
"order",
|
|
216
|
+
"rtol",
|
|
217
|
+
"step",
|
|
218
|
+
"transpose",
|
|
219
|
+
"x1",
|
|
220
|
+
"x2",
|
|
221
|
+
}
|
|
222
|
+
if check is True:
|
|
223
|
+
check_par(param, "find_fieldlines", **kwargs)
|
|
224
|
+
|
|
225
|
+
# Get the variable, if it is a string, get the variable from the dataset.
|
|
226
|
+
# The .T is used to transpose the variable to the correct shape.
|
|
227
|
+
varx = self._check_var(var1, kwargs.get("transpose", False))
|
|
228
|
+
vary = self._check_var(var2, kwargs.get("transpose", False))
|
|
229
|
+
|
|
230
|
+
# Get the grid information
|
|
231
|
+
xc = kwargs.get("x1", self.x1)
|
|
232
|
+
yc = kwargs.get("x2", self.x2)
|
|
233
|
+
|
|
234
|
+
# Check if the grid is uniform
|
|
235
|
+
|
|
236
|
+
# if not np.all(np.diff(xc) == np.diff(xc)[0]):
|
|
237
|
+
# err = "The grid is not uniform. Only uniform grids are supported."
|
|
238
|
+
# raise ValueError(err)
|
|
239
|
+
|
|
240
|
+
# Get the footpoints
|
|
241
|
+
if x0 is None or y0 is None:
|
|
242
|
+
raise ValueError("Footpoints not provided. Please provide footpoints!")
|
|
243
|
+
|
|
244
|
+
# Make sure x0 and y0 are lists
|
|
245
|
+
x0 = makelist(x0)
|
|
246
|
+
y0 = makelist(y0)
|
|
247
|
+
|
|
248
|
+
# Get the domain size (Take the initial and final coordinates
|
|
249
|
+
# slightly larger to allow a seed to be specified on the boundary).
|
|
250
|
+
xbeg = xc[0] - 0.51 * (xc[1] - xc[0])
|
|
251
|
+
xend = xc[-1] + 0.51 * (xc[-1] - xc[-2])
|
|
252
|
+
|
|
253
|
+
ybeg = yc[0] - 0.51 * (yc[1] - yc[0])
|
|
254
|
+
yend = yc[-1] + 0.51 * (yc[-1] - yc[-2])
|
|
255
|
+
|
|
256
|
+
# Set the keywords
|
|
257
|
+
rtol = kwargs.get("rtol", 1.0e-3)
|
|
258
|
+
atol = kwargs.get("atol", 1.0e-6)
|
|
259
|
+
ctol = kwargs.get("ctol", 1.0e-6)
|
|
260
|
+
order = kwargs.get("order", "RK45")
|
|
261
|
+
dense = kwargs.get("dense", False)
|
|
262
|
+
|
|
263
|
+
# Set the initial step size and maximum number of steps
|
|
264
|
+
step = np.abs(
|
|
265
|
+
kwargs.get(
|
|
266
|
+
"step", min((xend - xbeg) / self.nx1, (yend - ybeg) / self.nx2)
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
maxstep = kwargs.get("maxstep", 100 * step)
|
|
271
|
+
numstep = int(kwargs.get("numsteps", 16384))
|
|
272
|
+
tfin = maxstep * numstep
|
|
273
|
+
|
|
274
|
+
# Define the system of differential equations
|
|
275
|
+
def system(t, y):
|
|
276
|
+
"""System of differential equations for the field lines."""
|
|
277
|
+
return _vector_field(t, y, varx, vary, xc, yc)
|
|
278
|
+
|
|
279
|
+
# Event to detect if the field line exits the domain
|
|
280
|
+
def outside_domain(t, y):
|
|
281
|
+
"""Event to detect if the field line exits the domain."""
|
|
282
|
+
if y[0] < xbeg or y[0] > xend or y[1] < ybeg or y[1] > yend:
|
|
283
|
+
return 0 # Trigger event (exiting the domain)
|
|
284
|
+
return 1 # Do not trigger event (still within the domain)
|
|
285
|
+
|
|
286
|
+
# Event to detect if the field line closes on itself
|
|
287
|
+
def close_to_start(t, y):
|
|
288
|
+
"""Event to detect if the field line closes on itself."""
|
|
289
|
+
dist_0 = np.linalg.norm(y - np.asarray(self.init_pos))
|
|
290
|
+
if dist_0 < ctol and t > maxstep:
|
|
291
|
+
self.loop_dom = True
|
|
292
|
+
return 0 # Trigger event (closing on itself)
|
|
293
|
+
self.oldpos = y
|
|
294
|
+
return 1 # Do not trigger event (open line)
|
|
295
|
+
|
|
296
|
+
# Event to detect if the maximum number of steps is reached
|
|
297
|
+
def max_num_steps(t, y):
|
|
298
|
+
"""Event to detect if the maximum number of steps is reached."""
|
|
299
|
+
self.stepnum += 1
|
|
300
|
+
if self.stepnum > numstep:
|
|
301
|
+
return 0 # Trigger event (maximum number of steps reached)
|
|
302
|
+
return 1 # Do not trigger event (still below maximum step number)
|
|
303
|
+
|
|
304
|
+
# Set the events to be triggered
|
|
305
|
+
close_to_start.terminal = (
|
|
306
|
+
True if kwargs.get("close", True) is True else False
|
|
307
|
+
)
|
|
308
|
+
close_to_start.direction = 0
|
|
309
|
+
|
|
310
|
+
outside_domain.terminal = True
|
|
311
|
+
outside_domain.direction = 0
|
|
312
|
+
|
|
313
|
+
max_num_steps.terminal = True
|
|
314
|
+
max_num_steps.direction = 0
|
|
315
|
+
|
|
316
|
+
# Initilaize the list of lines and set the keywords
|
|
317
|
+
lines_list = []
|
|
318
|
+
linekwargs = {}
|
|
319
|
+
|
|
320
|
+
if order == "LSODA":
|
|
321
|
+
linekwargs["minstep"] = kwargs.get("minstep", 0.05 * step)
|
|
322
|
+
|
|
323
|
+
# Iterate on the footpoints
|
|
324
|
+
for ind, xp in enumerate(x0):
|
|
325
|
+
# Set the initial conditions
|
|
326
|
+
self.loop_dom = False
|
|
327
|
+
yp = y0[ind]
|
|
328
|
+
self.init_pos = [xp, yp]
|
|
329
|
+
self.oldpos = [xp, yp]
|
|
330
|
+
self.stepnum = 0
|
|
331
|
+
t_span = (0, tfin)
|
|
332
|
+
|
|
333
|
+
# Integrate forward
|
|
334
|
+
sol_forward = solve_ivp(
|
|
335
|
+
system,
|
|
336
|
+
t_span,
|
|
337
|
+
[xp, yp],
|
|
338
|
+
method=order,
|
|
339
|
+
events=[outside_domain, max_num_steps, close_to_start],
|
|
340
|
+
rtol=rtol,
|
|
341
|
+
atol=atol,
|
|
342
|
+
max_step=maxstep,
|
|
343
|
+
first_step=step,
|
|
344
|
+
dense_output=dense,
|
|
345
|
+
**linekwargs,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# If the line did not close on itself, integrate backward
|
|
349
|
+
numstep = 0 if self.loop_dom is True else numstep
|
|
350
|
+
|
|
351
|
+
# Set the new conditions
|
|
352
|
+
forw_steps = self.stepnum
|
|
353
|
+
self.init_pos = [sol_forward.y.T[:, 0][-1], sol_forward.y.T[:, 1][-1]]
|
|
354
|
+
self.stepnum = 0
|
|
355
|
+
t_span = (0, -tfin)
|
|
356
|
+
|
|
357
|
+
# Integrate backward
|
|
358
|
+
sol_backward = solve_ivp(
|
|
359
|
+
system,
|
|
360
|
+
t_span,
|
|
361
|
+
[xp, yp],
|
|
362
|
+
method=order,
|
|
363
|
+
events=[outside_domain, max_num_steps, close_to_start],
|
|
364
|
+
rtol=rtol,
|
|
365
|
+
atol=atol,
|
|
366
|
+
max_step=maxstep,
|
|
367
|
+
first_step=step,
|
|
368
|
+
dense_output=dense,
|
|
369
|
+
**linekwargs,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Concatenate the solutions (backward and forward)
|
|
373
|
+
x_line = np.vstack((sol_backward.y.T[::-1], sol_forward.y.T))[:, 0]
|
|
374
|
+
y_line = np.vstack((sol_backward.y.T[::-1], sol_forward.y.T))[:, 1]
|
|
375
|
+
|
|
376
|
+
# If the line is closed on itself, close the line
|
|
377
|
+
if self.loop_dom is True:
|
|
378
|
+
x_line = np.append(x_line, x_line[0])
|
|
379
|
+
y_line = np.append(y_line, y_line[0])
|
|
380
|
+
|
|
381
|
+
# Print the time of the integration
|
|
382
|
+
if text is True:
|
|
383
|
+
print("Line with footpoint at x = ", xp, " and y = ", yp)
|
|
384
|
+
print("Final integration time forward: ", sol_forward.t[-1])
|
|
385
|
+
print("Final integration time backward: ", sol_backward.t[-1])
|
|
386
|
+
print("Final step number forward: ", forw_steps)
|
|
387
|
+
print("Final step number backward: ", self.stepnum)
|
|
388
|
+
|
|
389
|
+
# Add the line to the list (if it has more than one point)
|
|
390
|
+
lines_list.append([x_line, y_line]) if len(x_line) > 1 else None
|
|
391
|
+
|
|
392
|
+
# Delete the methods that are not needed
|
|
393
|
+
for method_name in ["init_pos", "stepnum", "out_dom", "oldpos"]:
|
|
394
|
+
if method_name in self.__class__.__dict__:
|
|
395
|
+
delattr(self.__class__, method_name)
|
|
396
|
+
|
|
397
|
+
# Return the list of lines
|
|
398
|
+
return lines_list
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def find_contour(
|
|
402
|
+
self, var: str | np.ndarray, check: bool = True, **kwargs: Any
|
|
403
|
+
) -> list:
|
|
404
|
+
"""Generate contour lines for a given variable.
|
|
405
|
+
|
|
406
|
+
Returns
|
|
407
|
+
-------
|
|
408
|
+
- lines_list: list
|
|
409
|
+
List of contour lines. The strcuture of the list is
|
|
410
|
+
[[x1, y1], [x2, y2], ...] where x1, y1, x2, y2 are numpy arrays
|
|
411
|
+
representing the coordinates of the field lines.
|
|
412
|
+
|
|
413
|
+
Parameters
|
|
414
|
+
----------
|
|
415
|
+
- cmap: str, default 'k'
|
|
416
|
+
The colormap to use to associate each level with a color.
|
|
417
|
+
The colormap can also be a color, which is used for all the levels.
|
|
418
|
+
If not provided, all the lines are associated with the color black.
|
|
419
|
+
- levels: int | np.ndarray, default 10
|
|
420
|
+
The levels of number of levels or the list of levels for the contours.
|
|
421
|
+
If an integer is provided, the levels are generated using a linear or
|
|
422
|
+
logarithmic scale. If an array is provided, the levels are taken from
|
|
423
|
+
the array.
|
|
424
|
+
- levelscale: str, default 'linear'
|
|
425
|
+
The scale of the levels. Available options are 'linear' and
|
|
426
|
+
'logarithmic'.
|
|
427
|
+
- transpose: bool, default False
|
|
428
|
+
If True, the variable is transposed.
|
|
429
|
+
- var (not optional): str | np.ndarray
|
|
430
|
+
The variable to plot. If a string is provided, the variable is taken
|
|
431
|
+
from the dataset.
|
|
432
|
+
- vmax: float, default np.max(var)
|
|
433
|
+
The maximum value of the variable.
|
|
434
|
+
- vmin: float, default np.min(var)
|
|
435
|
+
The minimum value of the variable.
|
|
436
|
+
- x1: np.ndarray, default self.x1
|
|
437
|
+
The x1 coordinates. If the geometry is non-Cartesian, the x1 cartesian
|
|
438
|
+
coordinates are taken from the dataset.
|
|
439
|
+
- x2: np.ndarray, default self.x2
|
|
440
|
+
The x2 coordinates. If the geometry is non-Cartesian, the x2 cartesian
|
|
441
|
+
coordinates are taken from the dataset.
|
|
442
|
+
|
|
443
|
+
----
|
|
444
|
+
|
|
445
|
+
Examples
|
|
446
|
+
--------
|
|
447
|
+
- Example #1: Generate contour lines for a given variable.
|
|
448
|
+
|
|
449
|
+
>>> lines_list = find_contour(var)
|
|
450
|
+
|
|
451
|
+
- Example #2: Generate contour lines for a given variable and coordinates.
|
|
452
|
+
|
|
453
|
+
>>> lines_list = find_contour(var, x1=x1, x2=x2)
|
|
454
|
+
|
|
455
|
+
- Example #3: Generate contour lines for a given variable and coordinates
|
|
456
|
+
with a logarithmic scale.
|
|
457
|
+
|
|
458
|
+
>>> lines_list = find_contour(var, x1=x1, x2=x2,
|
|
459
|
+
>>> ... levelscale='logarithmic')
|
|
460
|
+
|
|
461
|
+
- Example #4: Generate contour lines for a given variable and coordinates
|
|
462
|
+
with a logarithmic scale and a colormap.
|
|
463
|
+
|
|
464
|
+
>>> lines_list = find_contour(var, x1=x1, x2=x2,
|
|
465
|
+
>>> ... levelscale='logarithmic', cmap='jet')
|
|
466
|
+
|
|
467
|
+
"""
|
|
468
|
+
# Check parameters
|
|
469
|
+
param = {
|
|
470
|
+
"cmap",
|
|
471
|
+
"levels",
|
|
472
|
+
"levelscale",
|
|
473
|
+
"transpose",
|
|
474
|
+
"vmax",
|
|
475
|
+
"vmin",
|
|
476
|
+
"x1",
|
|
477
|
+
"x2",
|
|
478
|
+
}
|
|
479
|
+
if check is True:
|
|
480
|
+
check_par(param, "find_contour", **kwargs)
|
|
481
|
+
|
|
482
|
+
# Get the variable, if it is a string, get the variable from the dataset.
|
|
483
|
+
# The .T is used to transpose the variable to the correct shape.
|
|
484
|
+
var = self._check_var(var, kwargs.get("transpose", False)).T
|
|
485
|
+
|
|
486
|
+
# Get the grid information and provide a default value for the coordinates
|
|
487
|
+
# if they are not provided depending on the geometry.
|
|
488
|
+
if self.geom == "SPHERICAL":
|
|
489
|
+
x1 = self.x1p
|
|
490
|
+
x2 = self.x2p
|
|
491
|
+
elif self.geom == "POLAR" and self.nx2 == 1:
|
|
492
|
+
x1 = self.x1
|
|
493
|
+
x2 = self.x3
|
|
494
|
+
elif self.geom == "POLAR":
|
|
495
|
+
x1 = self.x1c
|
|
496
|
+
x2 = self.x2c
|
|
497
|
+
else:
|
|
498
|
+
x1 = self.x1
|
|
499
|
+
x2 = self.x2
|
|
500
|
+
|
|
501
|
+
# Get the coordinates from the keyword arguments (if provided)
|
|
502
|
+
x1 = kwargs.get("x1", x1)
|
|
503
|
+
x2 = kwargs.get("x2", x2)
|
|
504
|
+
|
|
505
|
+
# Get the variable information (minimum and maximum values)
|
|
506
|
+
vmin = kwargs.get("vmin", np.nanmin(var))
|
|
507
|
+
vmax = kwargs.get("vmax", np.nanmax(var))
|
|
508
|
+
|
|
509
|
+
# Compute the levels of the contours, in linear or logarithmic scale
|
|
510
|
+
levels = kwargs.get("levels", 10)
|
|
511
|
+
levelscale = kwargs.get("levelscale", "linear")
|
|
512
|
+
|
|
513
|
+
# If levels is an integer, the levels are computed in lin or log scale
|
|
514
|
+
if isinstance(levels, int):
|
|
515
|
+
levels = (
|
|
516
|
+
np.linspace(vmin, vmax, levels)
|
|
517
|
+
if levelscale == "linear"
|
|
518
|
+
else np.logspace(np.log10(vmin), np.log10(vmax), levels)
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
# If levels is a float, convert it to a list
|
|
522
|
+
if isinstance(levels, float):
|
|
523
|
+
levels = [levels]
|
|
524
|
+
|
|
525
|
+
# Set colormap (try to get it from the colormap list, if not then use
|
|
526
|
+
# the color provided), if not provided use black.
|
|
527
|
+
if "cmap" in kwargs:
|
|
528
|
+
cmap_val = kwargs.get("cmap")
|
|
529
|
+
try:
|
|
530
|
+
cmap = plt.get_cmap(cmap_val)
|
|
531
|
+
except (ValueError, TypeError):
|
|
532
|
+
cmap = mcol.ListedColormap(cmap_val)
|
|
533
|
+
else:
|
|
534
|
+
cmap = mcol.ListedColormap(["k"])
|
|
535
|
+
|
|
536
|
+
# Initialize the list of lines
|
|
537
|
+
lines_list = []
|
|
538
|
+
|
|
539
|
+
# Get the contour generator and the lines for every level
|
|
540
|
+
cont_gen = cp.contour_generator(x1, x2, var, name="serial")
|
|
541
|
+
for indx, level in enumerate(levels):
|
|
542
|
+
contour = cont_gen.lines(level)
|
|
543
|
+
for line in contour:
|
|
544
|
+
x_c = line[:, 0]
|
|
545
|
+
y_c = line[:, 1]
|
|
546
|
+
col = cmap(indx / (len(levels) - 1)) if "cmap" in kwargs else "k"
|
|
547
|
+
|
|
548
|
+
lines_list.append([x_c, y_c, col]) if len(line) > 1 else None
|
|
549
|
+
|
|
550
|
+
# Return the list of lines
|
|
551
|
+
return lines_list
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from ..h_pypluto import check_par
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def fourier(
|
|
9
|
+
self, f: np.ndarray, check: bool = True, **kwargs: Any
|
|
10
|
+
) -> tuple[list[np.ndarray], np.ndarray]:
|
|
11
|
+
"""Compute the Fourier transform of a given array. The function uses
|
|
12
|
+
the numpy.fft.fftn function. The function returns a tuple containing
|
|
13
|
+
the transformed array and the frequency array (which is a list of
|
|
14
|
+
arrays if the input is in 2D or 3D).
|
|
15
|
+
|
|
16
|
+
Returns
|
|
17
|
+
-------
|
|
18
|
+
- f: np.ndarray
|
|
19
|
+
The transformed array.
|
|
20
|
+
- freqs: np.ndarray | list[np.ndarray]
|
|
21
|
+
The frequency array. It is a list of arrays if the input is in 2D or 3D.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
- dx: float | int | list | np.ndarray | None, default None
|
|
26
|
+
The grid spacing. If None, the grid spacing is set to 1.
|
|
27
|
+
- dy: float | int | list | np.ndarray | None, default None
|
|
28
|
+
The grid spacing. If None, the grid spacing is set to 1.
|
|
29
|
+
- dz: float | int | list | np.ndarray | None, default None
|
|
30
|
+
The grid spacing. If None, the grid spacing is set to 1.
|
|
31
|
+
- f (not optional): np.ndarray
|
|
32
|
+
The array to be transformed.
|
|
33
|
+
- xdir: bool, default True
|
|
34
|
+
If True, the x-direction is transformed.
|
|
35
|
+
- ydir: bool, default True
|
|
36
|
+
If True, the y-direction is transformed.
|
|
37
|
+
- zdir: bool, default True
|
|
38
|
+
If True, the z-direction is transformed.
|
|
39
|
+
|
|
40
|
+
----
|
|
41
|
+
|
|
42
|
+
Examples
|
|
43
|
+
--------
|
|
44
|
+
- Example #1: Compute the Fourier transform of a given array
|
|
45
|
+
|
|
46
|
+
>>> freqs, f = fourier(func)
|
|
47
|
+
|
|
48
|
+
- Example #2: Compute the Fourier transform of a given array in 2D with
|
|
49
|
+
custom grid spacing
|
|
50
|
+
|
|
51
|
+
>>> freqs, f = fourier(func, dx=1, dy=1)
|
|
52
|
+
|
|
53
|
+
- Example #3: Compute the Fourier transform of a 3D without considering
|
|
54
|
+
the x-direction
|
|
55
|
+
|
|
56
|
+
>>> freqs, f = fourier(func, xdir=False)
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
# Check parameters
|
|
60
|
+
param = {"dx", "dy", "dz", "xdir", "ydir", "zdir"}
|
|
61
|
+
if check is True:
|
|
62
|
+
check_par(param, "fourier", **kwargs)
|
|
63
|
+
|
|
64
|
+
# Convert the input array to a numpy array
|
|
65
|
+
f = np.asarray(f)
|
|
66
|
+
|
|
67
|
+
# Check the dimensions of the input array
|
|
68
|
+
dim = f.ndim
|
|
69
|
+
shp = f.shape
|
|
70
|
+
|
|
71
|
+
# Define the axes to include in the Fourier transform
|
|
72
|
+
axes = []
|
|
73
|
+
freqs = []
|
|
74
|
+
|
|
75
|
+
# Check if dx/dy/dz are provided.
|
|
76
|
+
dir_par = [
|
|
77
|
+
("dx", "dx1", "xdir", 0),
|
|
78
|
+
("dy", "dx2", "ydir", 1),
|
|
79
|
+
("dz", "dx3", "zdir", 2),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
# Define the grid spacing
|
|
83
|
+
spacing = {}
|
|
84
|
+
|
|
85
|
+
# Loop over directions
|
|
86
|
+
for pars, def_attr, dir, numdir in dir_par:
|
|
87
|
+
# If the number of dimensions is less than the number of directions
|
|
88
|
+
# break
|
|
89
|
+
if dim <= numdir:
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
# Check if the grid spacing is provided
|
|
93
|
+
try:
|
|
94
|
+
spacing[pars] = _fourier_spacing(kwargs[pars])
|
|
95
|
+
# If the grid spacing is not provided or not valid, use the default
|
|
96
|
+
# grid spacing (and set it to 1 if it still not valid)
|
|
97
|
+
except ValueError:
|
|
98
|
+
spacing[pars] = _fourier_spacing(getattr(self, def_attr))
|
|
99
|
+
spacing[pars] = 1.0 if spacing[pars] is None else spacing[pars]
|
|
100
|
+
# Check if the Fourier transform should be computed in this direction
|
|
101
|
+
if kwargs.get(dir, True) is True and dim > numdir:
|
|
102
|
+
axes.append(numdir)
|
|
103
|
+
# Compute the frequencies
|
|
104
|
+
freqs.append(
|
|
105
|
+
2.0 * np.pi * np.fft.rfftfreq(shp[numdir], spacing[pars])
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Compute the Fourier transform
|
|
109
|
+
fk = np.fft.fftn(f, axes=axes)
|
|
110
|
+
|
|
111
|
+
# Return the Fourier transform and the corresponding frequencies
|
|
112
|
+
slices = tuple(slice(0, dim // 2 + 1) for dim in shp)
|
|
113
|
+
freqs = freqs[0] if len(freqs) == 1 else freqs
|
|
114
|
+
return freqs, np.abs(fk[slices])
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _fourier_spacing(dx: float | int | list | np.ndarray) -> float:
|
|
118
|
+
"""Check the grid spacing and return the correct value. If the grid
|
|
119
|
+
spacing is not valid (negative), raise an error.
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
- scrh: float
|
|
124
|
+
The grid spacing.
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
- dx (not optional): float | int | list | np.ndarray
|
|
129
|
+
The grid spacing.
|
|
130
|
+
|
|
131
|
+
----
|
|
132
|
+
|
|
133
|
+
Examples
|
|
134
|
+
--------
|
|
135
|
+
- Example #1: Check the grid spacing and return the correct value
|
|
136
|
+
|
|
137
|
+
>>> scrh = fourier_spacing(dx)
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
# Check if the grid spacing is a list or numpy array, then take the first
|
|
141
|
+
# element
|
|
142
|
+
scrh = dx[0] if not isinstance(dx, (float, int)) else dx
|
|
143
|
+
|
|
144
|
+
# Check if the grid spacing is positive, if not raise an error
|
|
145
|
+
if scrh <= 0:
|
|
146
|
+
raise ValueError("the grid spacing must be positive!")
|
|
147
|
+
|
|
148
|
+
# Return the grid spacing
|
|
149
|
+
return scrh
|