pyvlasiator 0.1.0__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.
- pyvlasiator/__init__.py +0 -0
- pyvlasiator/plot/__init__.py +14 -0
- pyvlasiator/plot/plot.py +1115 -0
- pyvlasiator/pyvlasiator.py +2 -0
- pyvlasiator/vlsv/__init__.py +7 -0
- pyvlasiator/vlsv/reader.py +959 -0
- pyvlasiator/vlsv/variables.py +187 -0
- pyvlasiator-0.1.0.dist-info/LICENSE.md +21 -0
- pyvlasiator-0.1.0.dist-info/METADATA +61 -0
- pyvlasiator-0.1.0.dist-info/RECORD +11 -0
- pyvlasiator-0.1.0.dist-info/WHEEL +4 -0
pyvlasiator/plot/plot.py
ADDED
|
@@ -0,0 +1,1115 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import math
|
|
5
|
+
import warnings
|
|
6
|
+
from typing import Callable
|
|
7
|
+
from collections import namedtuple
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from pyvlasiator.vlsv import Vlsv
|
|
10
|
+
from pyvlasiator.vlsv.reader import _getdim2d
|
|
11
|
+
from pyvlasiator.vlsv.variables import RE
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ColorScale(Enum):
|
|
15
|
+
"""
|
|
16
|
+
Represents the available color scales for data visualization.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
- Linear (1): A linear color scale, where colors are evenly distributed across the data range.
|
|
20
|
+
- Log (2): A logarithmic color scale, suitable for data with a wide range of values, where smaller values are more emphasized.
|
|
21
|
+
- SymLog (3): A symmetric logarithmic color scale, similar to Log but with symmetry around zero, useful for data with both positive and negative values.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
Linear = 1
|
|
25
|
+
Log = 2
|
|
26
|
+
SymLog = 3
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AxisUnit(Enum):
|
|
30
|
+
"""
|
|
31
|
+
Specifies the units for representing values on an axis.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
- EARTH (1): Units based on Earth's physical properties, such as Earth's radius for distance and km/s for velocity.
|
|
35
|
+
- SI (2): Units from the International System of Units (SI), such as meters, seconds, kilograms, etc.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
EARTH = 1
|
|
39
|
+
SI = 2
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Plotting arguments
|
|
43
|
+
PlotArgs = namedtuple(
|
|
44
|
+
"PlotArgs",
|
|
45
|
+
[
|
|
46
|
+
"axisunit",
|
|
47
|
+
"sizes",
|
|
48
|
+
"plotrange",
|
|
49
|
+
"origin",
|
|
50
|
+
"idlist",
|
|
51
|
+
"indexlist",
|
|
52
|
+
"str_title",
|
|
53
|
+
"strx",
|
|
54
|
+
"stry",
|
|
55
|
+
"cb_title",
|
|
56
|
+
],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def plot(
|
|
61
|
+
self: Vlsv,
|
|
62
|
+
var: str = "",
|
|
63
|
+
ax=None,
|
|
64
|
+
figsize: tuple[float, float] | None = None,
|
|
65
|
+
**kwargs,
|
|
66
|
+
) -> list[matplotlib.lines.Line2D]:
|
|
67
|
+
"""
|
|
68
|
+
Plots 1D data from a VLSV file.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
var : str
|
|
73
|
+
Name of the variable to plot from the VLSV file.
|
|
74
|
+
ax : matplotlib.axes._axes.Axes, optional
|
|
75
|
+
Axes object to plot on. If not provided, a new figure and axes will be created.
|
|
76
|
+
figsize : tuple[float, float], optional
|
|
77
|
+
Size of the figure in inches (width, height). Only used if a new
|
|
78
|
+
figure is created.
|
|
79
|
+
**kwargs
|
|
80
|
+
Additional keyword arguments passed to Matplotlib's `plot` function.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
list[matplotlib.lines.Line2D]
|
|
85
|
+
A list of created Line2D objects.
|
|
86
|
+
|
|
87
|
+
Raises
|
|
88
|
+
------
|
|
89
|
+
ValueError
|
|
90
|
+
If the specified variable is not found in the VLSV file.
|
|
91
|
+
|
|
92
|
+
Examples
|
|
93
|
+
--------
|
|
94
|
+
>>> vlsv_file = Vlsv("my_vlsv_file.vlsv")
|
|
95
|
+
>>> axes = vlsv_file.plot("proton/vg_rho") # Plot density on a new figure
|
|
96
|
+
>>> axes = vlsv_file.plot("helium/vg_v", ax=my_axes) # Plot velocity on an existing axes
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
import matplotlib.pyplot as plt
|
|
100
|
+
|
|
101
|
+
fig = kwargs.pop(
|
|
102
|
+
"figure", plt.gcf() if plt.get_fignums() else plt.figure(figsize=figsize)
|
|
103
|
+
)
|
|
104
|
+
if ax is None:
|
|
105
|
+
ax = fig.gca()
|
|
106
|
+
|
|
107
|
+
if not self.has_variable(var):
|
|
108
|
+
raise ValueError(f"Variable {var} not found in the file")
|
|
109
|
+
|
|
110
|
+
x = np.linspace(self.coordmin[0], self.coordmax[0], self.ncells[0])
|
|
111
|
+
|
|
112
|
+
data = self.read_variable(var)
|
|
113
|
+
axes = ax.plot(x, data, **kwargs)
|
|
114
|
+
|
|
115
|
+
return axes
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def pcolormesh(
|
|
119
|
+
self: Vlsv,
|
|
120
|
+
var: str = "",
|
|
121
|
+
axisunit: AxisUnit = AxisUnit.EARTH,
|
|
122
|
+
colorscale: ColorScale = ColorScale.Linear,
|
|
123
|
+
addcolorbar: bool = True,
|
|
124
|
+
vmin: float = float("-inf"),
|
|
125
|
+
vmax: float = float("inf"),
|
|
126
|
+
extent: list = [0.0, 0.0, 0.0, 0.0],
|
|
127
|
+
comp: int = -1,
|
|
128
|
+
ax=None,
|
|
129
|
+
figsize: tuple[float, float] | None = None,
|
|
130
|
+
**kwargs,
|
|
131
|
+
):
|
|
132
|
+
"""
|
|
133
|
+
Plots 2D VLSV data using pcolormesh.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
var : str
|
|
138
|
+
Name of the variable to plot from the VLSV file.
|
|
139
|
+
axisunit : AxisUnit
|
|
140
|
+
Unit of the axis, `AxisUnit.EARTH` or `AxisUnit.SI`.
|
|
141
|
+
addcolorbar : bool
|
|
142
|
+
Add colorbar to the right.
|
|
143
|
+
colorscale: ColorScale
|
|
144
|
+
Color scale of the data, `ColorScale.Linear`, `ColorScale.Log`, or `ColorScale.SymLog`.
|
|
145
|
+
extent : list
|
|
146
|
+
Extent of the domain (WIP).
|
|
147
|
+
comp : int
|
|
148
|
+
Vector composition of data, -1 is magnitude, 0 is x, 1 is y, and 2 is z.
|
|
149
|
+
ax : matplotlib.axes._axes.Axes, optional
|
|
150
|
+
Axes object to plot on. If not provided, a new figure and axes
|
|
151
|
+
will be created using `set_figure`.
|
|
152
|
+
figsize : tuple[float, float], optional
|
|
153
|
+
Size of the figure in inches. Only used if a new figure is created.
|
|
154
|
+
**kwargs
|
|
155
|
+
Additional keyword arguments passed to `ax.pcolormesh`.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
matplotlib.figure.Figure
|
|
160
|
+
The created or existing figure object.
|
|
161
|
+
|
|
162
|
+
Raises
|
|
163
|
+
------
|
|
164
|
+
ValueError
|
|
165
|
+
If the specified variable is not found in the VLSV file.
|
|
166
|
+
|
|
167
|
+
Examples
|
|
168
|
+
--------
|
|
169
|
+
>>> vlsv_file = Vlsv("my_vlsv_file.vlsv")
|
|
170
|
+
>>> # Plot density on a new figure
|
|
171
|
+
>>> fig = vlsv_file.pcolormesh("proton/vg_rho")
|
|
172
|
+
>>> # Plot velocity on an existing axes
|
|
173
|
+
>>> ax = ... # Existing axes object
|
|
174
|
+
>>> fig = vlsv_file.pcolormesh("proton/vg_v", ax=ax)
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
fig, ax = set_figure(ax, figsize, **kwargs)
|
|
178
|
+
|
|
179
|
+
return _plot2d(
|
|
180
|
+
self,
|
|
181
|
+
ax.pcolormesh,
|
|
182
|
+
var=var,
|
|
183
|
+
ax=ax,
|
|
184
|
+
comp=comp,
|
|
185
|
+
axisunit=axisunit,
|
|
186
|
+
colorscale=colorscale,
|
|
187
|
+
addcolorbar=addcolorbar,
|
|
188
|
+
vmin=vmin,
|
|
189
|
+
vmax=vmax,
|
|
190
|
+
extent=extent,
|
|
191
|
+
**kwargs,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def contourf(
|
|
196
|
+
self: Vlsv,
|
|
197
|
+
var: str = "",
|
|
198
|
+
axisunit: AxisUnit = AxisUnit.EARTH,
|
|
199
|
+
colorscale: ColorScale = ColorScale.Linear,
|
|
200
|
+
addcolorbar: bool = True,
|
|
201
|
+
vmin: float = float("-inf"),
|
|
202
|
+
vmax: float = float("inf"),
|
|
203
|
+
extent: list = [0.0, 0.0, 0.0, 0.0],
|
|
204
|
+
comp: int = -1,
|
|
205
|
+
ax=None,
|
|
206
|
+
figsize: tuple[float, float] | None = None,
|
|
207
|
+
**kwargs,
|
|
208
|
+
):
|
|
209
|
+
fig, ax = set_figure(ax, figsize, **kwargs)
|
|
210
|
+
|
|
211
|
+
return _plot2d(
|
|
212
|
+
self,
|
|
213
|
+
ax.contourf,
|
|
214
|
+
var=var,
|
|
215
|
+
ax=ax,
|
|
216
|
+
comp=comp,
|
|
217
|
+
axisunit=axisunit,
|
|
218
|
+
colorscale=colorscale,
|
|
219
|
+
addcolorbar=addcolorbar,
|
|
220
|
+
vmin=vmin,
|
|
221
|
+
vmax=vmax,
|
|
222
|
+
extent=extent,
|
|
223
|
+
**kwargs,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def contour(
|
|
228
|
+
self: Vlsv,
|
|
229
|
+
var: str = "",
|
|
230
|
+
axisunit: AxisUnit = AxisUnit.EARTH,
|
|
231
|
+
colorscale: ColorScale = ColorScale.Linear,
|
|
232
|
+
addcolorbar: bool = True,
|
|
233
|
+
vmin: float = float("-inf"),
|
|
234
|
+
vmax: float = float("inf"),
|
|
235
|
+
extent: list = [0.0, 0.0, 0.0, 0.0],
|
|
236
|
+
comp: int = -1,
|
|
237
|
+
ax=None,
|
|
238
|
+
figsize: tuple[float, float] | None = None,
|
|
239
|
+
**kwargs,
|
|
240
|
+
):
|
|
241
|
+
fig, ax = set_figure(ax, figsize, **kwargs)
|
|
242
|
+
|
|
243
|
+
return _plot2d(
|
|
244
|
+
self,
|
|
245
|
+
ax.contour,
|
|
246
|
+
var=var,
|
|
247
|
+
ax=ax,
|
|
248
|
+
comp=comp,
|
|
249
|
+
axisunit=axisunit,
|
|
250
|
+
colorscale=colorscale,
|
|
251
|
+
addcolorbar=addcolorbar,
|
|
252
|
+
vmin=vmin,
|
|
253
|
+
vmax=vmax,
|
|
254
|
+
extent=extent,
|
|
255
|
+
**kwargs,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _plot2d(
|
|
260
|
+
meta: Vlsv,
|
|
261
|
+
plot_func: Callable,
|
|
262
|
+
var: str = "",
|
|
263
|
+
axisunit: AxisUnit = AxisUnit.EARTH,
|
|
264
|
+
colorscale: ColorScale = ColorScale.Linear,
|
|
265
|
+
addcolorbar: bool = True,
|
|
266
|
+
vmin: float = float("-inf"),
|
|
267
|
+
vmax: float = float("inf"),
|
|
268
|
+
extent: list = [0.0, 0.0, 0.0, 0.0],
|
|
269
|
+
comp: int = -1,
|
|
270
|
+
ax=None,
|
|
271
|
+
**kwargs,
|
|
272
|
+
):
|
|
273
|
+
"""
|
|
274
|
+
Plot 2d data.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
var : str
|
|
279
|
+
Variable name from the VLSV file.
|
|
280
|
+
|
|
281
|
+
Returns
|
|
282
|
+
-------
|
|
283
|
+
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
if not meta.has_variable(var):
|
|
287
|
+
raise ValueError(f"Variable {var} not found in the file")
|
|
288
|
+
|
|
289
|
+
if meta.ndims() == 3 or meta.maxamr > 0:
|
|
290
|
+
# check if origin and normal exist in kwargs
|
|
291
|
+
normal = kwargs["normal"] if "normal" in kwargs else 1
|
|
292
|
+
origin = kwargs["origin"] if "origin" in kwargs else 0.0
|
|
293
|
+
kwargs.pop("normal", None)
|
|
294
|
+
kwargs.pop("origin", None)
|
|
295
|
+
|
|
296
|
+
pArgs = set_args(meta, var, axisunit, normal, origin)
|
|
297
|
+
data = prep2dslice(meta, var, normal, comp, pArgs)
|
|
298
|
+
else:
|
|
299
|
+
pArgs = set_args(meta, var, axisunit)
|
|
300
|
+
data = prep2d(meta, var, comp)
|
|
301
|
+
|
|
302
|
+
x1, x2 = get_axis(pArgs.axisunit, pArgs.plotrange, pArgs.sizes)
|
|
303
|
+
|
|
304
|
+
if var in ("fg_b", "fg_e", "vg_b_vol", "vg_e_vol") or var.endswith("vg_v"):
|
|
305
|
+
_fillinnerBC(data)
|
|
306
|
+
|
|
307
|
+
norm, ticks = set_colorbar(colorscale, vmin, vmax, data)
|
|
308
|
+
|
|
309
|
+
range1 = range(
|
|
310
|
+
np.searchsorted(x1, extent[0]), np.searchsorted(x1, extent[1], side="right")
|
|
311
|
+
)
|
|
312
|
+
range2 = range(
|
|
313
|
+
np.searchsorted(x2, extent[2]), np.searchsorted(x2, extent[3], side="right")
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
c = plot_func(x1, x2, data, **kwargs)
|
|
317
|
+
|
|
318
|
+
configure_plot(c, ax, pArgs, ticks, addcolorbar)
|
|
319
|
+
|
|
320
|
+
return c
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def streamplot(
|
|
324
|
+
meta: Vlsv,
|
|
325
|
+
var: str,
|
|
326
|
+
ax=None,
|
|
327
|
+
comp: str = "xy",
|
|
328
|
+
axisunit: AxisUnit = AxisUnit.EARTH,
|
|
329
|
+
origin: float = 0.0,
|
|
330
|
+
**kwargs,
|
|
331
|
+
) -> matplotlib.streamplot.StreamplotSet:
|
|
332
|
+
"""
|
|
333
|
+
Creates a streamplot visualization of a vector field from a VLSV dataset.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
meta: Vlsv
|
|
338
|
+
A VLSV metadata object containing the dataset information.
|
|
339
|
+
var: str
|
|
340
|
+
The name of the vector variable to visualize.
|
|
341
|
+
ax: matplotlib.axes.Axes, optional
|
|
342
|
+
The axes object to plot on. If not provided, a new figure and axes
|
|
343
|
+
will be created.
|
|
344
|
+
comp: str, optional
|
|
345
|
+
The components of the vector to plot, specified as a string containing
|
|
346
|
+
"x", "y", or "z" (e.g., "xy" for x-y components). Defaults to "xy".
|
|
347
|
+
axisunit: AxisUnit, optional
|
|
348
|
+
The unit system for the plot axes. Defaults to AxisUnit.EARTH.
|
|
349
|
+
origin: float, optional
|
|
350
|
+
The origin point for slice plots. Defaults to 0.0.
|
|
351
|
+
**kwargs
|
|
352
|
+
Additional keyword arguments passed to Matplotlib's streamplot function.
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
matplotlib.streamplot.StreamplotSet
|
|
357
|
+
The streamplot object created by Matplotlib.
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
X, Y, v1, v2 = set_vector(meta, var, comp, axisunit, origin)
|
|
361
|
+
fig, ax = set_figure(ax, **kwargs)
|
|
362
|
+
|
|
363
|
+
s = ax.streamplot(X, Y, v1, v2, **kwargs)
|
|
364
|
+
|
|
365
|
+
return s
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def set_vector(
|
|
369
|
+
meta: Vlsv, var: str, comp: str, axisunit: AxisUnit, origin: float = 0.0
|
|
370
|
+
):
|
|
371
|
+
"""
|
|
372
|
+
Extracts and prepares vector data for plotting from a VLSV dataset.
|
|
373
|
+
|
|
374
|
+
Parameters
|
|
375
|
+
----------
|
|
376
|
+
meta: Vlsv
|
|
377
|
+
A VLSV metadata object containing the dataset information.
|
|
378
|
+
var: str
|
|
379
|
+
The name of the vector variable to extract.
|
|
380
|
+
comp: str
|
|
381
|
+
The components of the vector to extract, specified as a string
|
|
382
|
+
containing "x", "y", or "z" (e.g., "xy" for x-y components).
|
|
383
|
+
axisunit: AxisUnit
|
|
384
|
+
The unit system for the plot axes.
|
|
385
|
+
origin: float, optional
|
|
386
|
+
The origin point for slice plots. Defaults to 0.0.
|
|
387
|
+
|
|
388
|
+
Returns
|
|
389
|
+
-------
|
|
390
|
+
tuple
|
|
391
|
+
A tuple containing:
|
|
392
|
+
- x: The x-axis coordinates for plotting.
|
|
393
|
+
- y: The y-axis coordinates for plotting.
|
|
394
|
+
- v1: The values of the first vector component.
|
|
395
|
+
- v2: The values of the second vector component.
|
|
396
|
+
|
|
397
|
+
Raises
|
|
398
|
+
------
|
|
399
|
+
ValueError
|
|
400
|
+
If the specified variable is not a vector variable.
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
ncells = meta.ncells
|
|
404
|
+
maxamr = meta.maxamr
|
|
405
|
+
coordmin, coordmax = meta.coordmin, meta.coordmax
|
|
406
|
+
|
|
407
|
+
if "x" in comp:
|
|
408
|
+
v1_ = 0
|
|
409
|
+
if "y" in comp:
|
|
410
|
+
dir = 2
|
|
411
|
+
v2_ = 1
|
|
412
|
+
sizes = _getdim2d(ncells, maxamr, 2)
|
|
413
|
+
plotrange = (coordmin[0], coordmax[0], coordmin[1], coordmax[1])
|
|
414
|
+
else:
|
|
415
|
+
dir = 1
|
|
416
|
+
v2_ = 2
|
|
417
|
+
sizes = _getdim2d(ncells, maxamr, 1)
|
|
418
|
+
plotrange = (coordmin[0], coordmax[0], coordmin[2], coordmax[2])
|
|
419
|
+
else:
|
|
420
|
+
dir = 0
|
|
421
|
+
v1_, v2_ = 1, 2
|
|
422
|
+
sizes = _getdim2d(ncells, maxamr, 0)
|
|
423
|
+
plotrange = (coordmin[1], coordmax[1], coordmin[2], coordmax[2])
|
|
424
|
+
|
|
425
|
+
data = meta.read_variable(var)
|
|
426
|
+
|
|
427
|
+
if not var.startswith("fg_"): # vlasov grid
|
|
428
|
+
if data.ndim != 2 and data.shape[0] == 3:
|
|
429
|
+
raise ValueError("Vector variable required!")
|
|
430
|
+
if meta.maxamr == 0:
|
|
431
|
+
data = data.reshape((sizes[1], sizes[0], 3))
|
|
432
|
+
v1 = data[:, :, v1_]
|
|
433
|
+
v2 = data[:, :, v2_]
|
|
434
|
+
else:
|
|
435
|
+
sliceoffset = origin - coordmin[dir]
|
|
436
|
+
idlist, indexlist = meta.getslicecell(
|
|
437
|
+
sliceoffset, dir, coordmin[dir], coordmax[dir]
|
|
438
|
+
)
|
|
439
|
+
v2D = data[indexlist, :]
|
|
440
|
+
v1 = meta.refineslice(idlist, v2D[:, v1_], dir)
|
|
441
|
+
v2 = meta.refineslice(idlist, v2D[:, v2_], dir)
|
|
442
|
+
else: # FS grid
|
|
443
|
+
data = np.squeeze(data)
|
|
444
|
+
v1 = np.transpose(data[:, :, v1_])
|
|
445
|
+
v2 = np.transpose(data[:, :, v2_])
|
|
446
|
+
|
|
447
|
+
x, y = get_axis(axisunit, plotrange, sizes)
|
|
448
|
+
|
|
449
|
+
return x, y, v1, v2
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def set_figure(ax, figsize: tuple = (10, 6), **kwargs) -> tuple:
|
|
453
|
+
"""
|
|
454
|
+
Sets up a Matplotlib figure and axes for plotting.
|
|
455
|
+
|
|
456
|
+
Parameters
|
|
457
|
+
----------
|
|
458
|
+
ax: matplotlib.axes.Axes, optional
|
|
459
|
+
An existing axes object to use for plotting. If not provided, a new
|
|
460
|
+
figure and axes will be created.
|
|
461
|
+
figsize: tuple, optional
|
|
462
|
+
The desired figure size in inches, as a tuple (width, height).
|
|
463
|
+
Defaults to (10, 6).
|
|
464
|
+
**kwargs
|
|
465
|
+
Additional keyword arguments passed to Matplotlib's figure() function
|
|
466
|
+
if a new figure is created.
|
|
467
|
+
|
|
468
|
+
Returns
|
|
469
|
+
-------
|
|
470
|
+
tuple
|
|
471
|
+
A tuple containing:
|
|
472
|
+
- fig: The Matplotlib figure object.
|
|
473
|
+
- ax: The Matplotlib axes object.
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
import matplotlib.pyplot as plt
|
|
477
|
+
|
|
478
|
+
fig = kwargs.pop(
|
|
479
|
+
"figure", plt.gcf() if plt.get_fignums() else plt.figure(figsize=figsize)
|
|
480
|
+
)
|
|
481
|
+
if ax is None:
|
|
482
|
+
ax = fig.gca()
|
|
483
|
+
|
|
484
|
+
return fig, ax
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def set_args(
|
|
488
|
+
meta: Vlsv,
|
|
489
|
+
var: str,
|
|
490
|
+
axisunit: AxisUnit = AxisUnit.EARTH,
|
|
491
|
+
dir: int = -1,
|
|
492
|
+
origin: float = 0.0,
|
|
493
|
+
) -> PlotArgs:
|
|
494
|
+
"""
|
|
495
|
+
Set plot-related arguments of `var` in `axisunit`.
|
|
496
|
+
|
|
497
|
+
Parameters
|
|
498
|
+
----------
|
|
499
|
+
var : str
|
|
500
|
+
Variable name from the VLSV file.
|
|
501
|
+
axisunit : AxisUnit
|
|
502
|
+
Unit of the axis.
|
|
503
|
+
dir : int
|
|
504
|
+
Normal direction of the 2D slice, 0 for x, 1 for y, and 2 for z.
|
|
505
|
+
origin : float
|
|
506
|
+
Origin of the 2D slice.
|
|
507
|
+
|
|
508
|
+
Returns
|
|
509
|
+
-------
|
|
510
|
+
PlotArgs
|
|
511
|
+
|
|
512
|
+
See Also
|
|
513
|
+
--------
|
|
514
|
+
:func:`pcolormesh`
|
|
515
|
+
"""
|
|
516
|
+
|
|
517
|
+
ncells, coordmin, coordmax = meta.ncells, meta.coordmin, meta.coordmax
|
|
518
|
+
|
|
519
|
+
if dir == 0:
|
|
520
|
+
seq = (1, 2)
|
|
521
|
+
elif dir == 1 or (ncells[1] == 1 and ncells[2] != 1): # polar
|
|
522
|
+
seq = (0, 2)
|
|
523
|
+
dir = 1
|
|
524
|
+
elif dir == 2 or (ncells[2] == 1 and ncells[1] != 1): # ecliptic
|
|
525
|
+
seq = (0, 1)
|
|
526
|
+
dir = 2
|
|
527
|
+
else:
|
|
528
|
+
raise ValueError("1D data detected. Please use 1D plot functions.")
|
|
529
|
+
|
|
530
|
+
plotrange = (coordmin[seq[0]], coordmax[seq[0]], coordmin[seq[1]], coordmax[seq[1]])
|
|
531
|
+
axislabels = tuple(("X", "Y", "Z")[i] for i in seq)
|
|
532
|
+
# Scale the sizes to the highest refinement level for data to be refined later
|
|
533
|
+
sizes = tuple(ncells[i] << meta.maxamr for i in seq)
|
|
534
|
+
|
|
535
|
+
if dir == -1:
|
|
536
|
+
idlist, indexlist = np.empty(0, dtype=int), np.empty(0, dtype=int)
|
|
537
|
+
else:
|
|
538
|
+
sliceoffset = origin - coordmin[dir]
|
|
539
|
+
idlist, indexlist = meta.getslicecell(
|
|
540
|
+
sliceoffset, dir, coordmin[dir], coordmax[dir]
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
if axisunit == AxisUnit.EARTH:
|
|
544
|
+
unitstr = r"$R_E$"
|
|
545
|
+
else:
|
|
546
|
+
unitstr = r"$m$"
|
|
547
|
+
strx = axislabels[0] + " [" + unitstr + "]"
|
|
548
|
+
stry = axislabels[1] + " [" + unitstr + "]"
|
|
549
|
+
|
|
550
|
+
str_title = f"t={meta.time:4.1f}s"
|
|
551
|
+
|
|
552
|
+
datainfo = meta.read_variable_meta(var)
|
|
553
|
+
|
|
554
|
+
if not datainfo.variableLaTeX:
|
|
555
|
+
cb_title = datainfo.variableLaTeX + " [" + datainfo.unitLaTeX + "]"
|
|
556
|
+
else:
|
|
557
|
+
cb_title = ""
|
|
558
|
+
|
|
559
|
+
return PlotArgs(
|
|
560
|
+
axisunit,
|
|
561
|
+
sizes,
|
|
562
|
+
plotrange,
|
|
563
|
+
origin,
|
|
564
|
+
idlist,
|
|
565
|
+
indexlist,
|
|
566
|
+
str_title,
|
|
567
|
+
strx,
|
|
568
|
+
stry,
|
|
569
|
+
cb_title,
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def prep2d(meta: Vlsv, var: str, comp: int = -1):
|
|
574
|
+
"""
|
|
575
|
+
Obtain data of `var` for 2D plotting. Use `comp` to select vector components.
|
|
576
|
+
|
|
577
|
+
Parameters
|
|
578
|
+
----------
|
|
579
|
+
meta : Vlsv
|
|
580
|
+
Metadata corresponding to the file.
|
|
581
|
+
var : str
|
|
582
|
+
Name of the variable.
|
|
583
|
+
comp : int
|
|
584
|
+
Vector component. -1 refers to the magnitude of the vector.
|
|
585
|
+
Returns
|
|
586
|
+
-------
|
|
587
|
+
numpy.ndarray
|
|
588
|
+
"""
|
|
589
|
+
|
|
590
|
+
dataRaw = _getdata2d(meta, var)
|
|
591
|
+
|
|
592
|
+
if dataRaw.ndim == 3:
|
|
593
|
+
if comp != -1:
|
|
594
|
+
data = dataRaw[:, :, comp]
|
|
595
|
+
else:
|
|
596
|
+
data = np.linalg.norm(dataRaw, axis=2)
|
|
597
|
+
if var.startswith("fg_"):
|
|
598
|
+
data = np.transpose(data)
|
|
599
|
+
else:
|
|
600
|
+
data = dataRaw
|
|
601
|
+
|
|
602
|
+
return data
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def prep2dslice(meta: Vlsv, var: str, dir: int, comp: int, pArgs: PlotArgs):
|
|
606
|
+
origin = pArgs.origin
|
|
607
|
+
idlist = pArgs.idlist
|
|
608
|
+
indexlist = pArgs.indexlist
|
|
609
|
+
|
|
610
|
+
data3D = meta.read_variable(var)
|
|
611
|
+
|
|
612
|
+
if var.startswith("fg_") or data3D.ndim > 2: # field or derived quantities, fsgrid
|
|
613
|
+
ncells = meta.ncells * 2**meta.maxamr
|
|
614
|
+
if not dir in (0, 1, 2):
|
|
615
|
+
raise ValueError(f"Unknown normal direction {dir}")
|
|
616
|
+
|
|
617
|
+
sliceratio = (origin - meta.coordmin[dir]) / (
|
|
618
|
+
meta.coordmax[dir] - meta.coordmin[dir]
|
|
619
|
+
)
|
|
620
|
+
if not (0.0 <= sliceratio <= 1.0):
|
|
621
|
+
raise ValueError("slice plane index out of bound!")
|
|
622
|
+
# Find the cut plane index for each refinement level
|
|
623
|
+
icut = int(np.floor(sliceratio * ncells[dir]))
|
|
624
|
+
if dir == 0:
|
|
625
|
+
if comp != -1:
|
|
626
|
+
data = data3D[icut, :, :, comp]
|
|
627
|
+
else:
|
|
628
|
+
data = np.linalg.norm(data3D[icut, :, :, :], axis=3)
|
|
629
|
+
elif dir == 1:
|
|
630
|
+
if comp != -1:
|
|
631
|
+
data = data3D[:, icut, :, comp]
|
|
632
|
+
else:
|
|
633
|
+
data = np.linalg.norm(data3D[:, icut, :, :], axis=3)
|
|
634
|
+
elif dir == 2:
|
|
635
|
+
if comp != -1:
|
|
636
|
+
data = data3D[:, :, icut, comp]
|
|
637
|
+
else:
|
|
638
|
+
data = np.linalg.norm(data3D[:, :, icut, :], axis=3)
|
|
639
|
+
else: # moments, dccrg grid
|
|
640
|
+
# vlasov grid, AMR
|
|
641
|
+
if data3D.ndim == 1:
|
|
642
|
+
data2D = data3D[indexlist]
|
|
643
|
+
|
|
644
|
+
data = meta.refineslice(idlist, data2D, dir)
|
|
645
|
+
elif data3D.ndim == 2:
|
|
646
|
+
data2D = data3D[indexlist, :]
|
|
647
|
+
|
|
648
|
+
if comp in (0, 1, 2):
|
|
649
|
+
slice = data2D[:, comp]
|
|
650
|
+
data = meta.refineslice(idlist, slice, dir)
|
|
651
|
+
elif comp == -1:
|
|
652
|
+
datax = meta.refineslice(idlist, data2D[:, 0], dir)
|
|
653
|
+
datay = meta.refineslice(idlist, data2D[:, 1], dir)
|
|
654
|
+
dataz = meta.refineslice(idlist, data2D[:, 2], dir)
|
|
655
|
+
data = np.fromiter(
|
|
656
|
+
(np.linalg.norm([x, y, z]) for x, y, z in zip(datax, datay, dataz)),
|
|
657
|
+
dtype=float,
|
|
658
|
+
)
|
|
659
|
+
else:
|
|
660
|
+
slice = data2D[:, comp]
|
|
661
|
+
data = meta.refineslice(idlist, slice, dir)
|
|
662
|
+
|
|
663
|
+
return data
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def _getdata2d(meta: Vlsv, var: str) -> np.ndarray:
|
|
667
|
+
"""
|
|
668
|
+
Retrieves and reshapes 2D data from a VLSV dataset.
|
|
669
|
+
|
|
670
|
+
Raises:
|
|
671
|
+
ValueError: If the dataset is not 2D.
|
|
672
|
+
"""
|
|
673
|
+
|
|
674
|
+
if meta.ndims() != 2:
|
|
675
|
+
raise ValueError("2D outputs required")
|
|
676
|
+
sizes = [i for i in meta.ncells if i != 1]
|
|
677
|
+
data = meta.read_variable(var)
|
|
678
|
+
if data.ndim in (1, 3) or data.shape[-1] == 1:
|
|
679
|
+
data = data.reshape((sizes[1], sizes[0]))
|
|
680
|
+
elif var.startswith("fg_"):
|
|
681
|
+
data = data.reshape((sizes[0], sizes[1], 3))
|
|
682
|
+
else:
|
|
683
|
+
data = data.reshape((sizes[1], sizes[0], 3))
|
|
684
|
+
|
|
685
|
+
return data
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def get_axis(axisunit: AxisUnit, plotrange: tuple, sizes: tuple) -> tuple:
|
|
689
|
+
"""
|
|
690
|
+
Generates the 2D domain axis coordinates, potentially applying Earth radius scaling.
|
|
691
|
+
|
|
692
|
+
Parameters
|
|
693
|
+
----------
|
|
694
|
+
axisunit: AxisUnit
|
|
695
|
+
The unit system for the plot axes.
|
|
696
|
+
plotrange: tuple
|
|
697
|
+
A tuple containing the minimum and maximum values for both axes (xmin, xmax, ymin, ymax).
|
|
698
|
+
sizes: tuple
|
|
699
|
+
A tuple containing the number of points for each axis (nx, ny).
|
|
700
|
+
|
|
701
|
+
Returns
|
|
702
|
+
-------
|
|
703
|
+
tuple
|
|
704
|
+
A tuple containing the x and y axis coordinates.
|
|
705
|
+
"""
|
|
706
|
+
|
|
707
|
+
scale_factor = 1.0 / RE if axisunit == AxisUnit.EARTH else 1.0
|
|
708
|
+
start = tuple(s * scale_factor for s in plotrange[:2])
|
|
709
|
+
stop = tuple(s * scale_factor for s in plotrange[2:])
|
|
710
|
+
|
|
711
|
+
# Vectorized generation of coordinates for efficiency
|
|
712
|
+
x, y = np.linspace(*start, num=sizes[0]), np.linspace(*stop, num=sizes[1])
|
|
713
|
+
|
|
714
|
+
return x, y
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def _fillinnerBC(data: np.ndarray):
|
|
718
|
+
"""
|
|
719
|
+
Fill sparsity/inner boundary cells with NaN.
|
|
720
|
+
"""
|
|
721
|
+
data[data == 0] = np.nan
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def set_colorbar(
|
|
725
|
+
colorscale: ColorScale = ColorScale.Linear,
|
|
726
|
+
v1: float = np.nan,
|
|
727
|
+
v2: float = np.nan,
|
|
728
|
+
data: np.ndarray = np.array([1.0]),
|
|
729
|
+
linthresh: float = 1.0,
|
|
730
|
+
logstep: float = 1.0,
|
|
731
|
+
linscale: float = 0.03,
|
|
732
|
+
):
|
|
733
|
+
"""
|
|
734
|
+
Creates a color normalization object and tick values for a colorbar.
|
|
735
|
+
|
|
736
|
+
Parameters
|
|
737
|
+
----------
|
|
738
|
+
colorscale: ColorScale, optional
|
|
739
|
+
The type of color scale to use. Can be 'Linear', 'Log', or 'SymLog'.
|
|
740
|
+
Defaults to 'Linear'.
|
|
741
|
+
v1: float, optional
|
|
742
|
+
The minimum value for the colorbar. Defaults to np.nan, which means
|
|
743
|
+
it will be inferred from the data.
|
|
744
|
+
v2: float, optional
|
|
745
|
+
The maximum value for the colorbar. Defaults to np.nan, which means
|
|
746
|
+
it will be inferred from the data.
|
|
747
|
+
data: np.ndarray, optional
|
|
748
|
+
The data to use for inferring the colorbar limits if v1 and v2 are
|
|
749
|
+
not provided. Defaults to np.array([1.0]).
|
|
750
|
+
linthresh: float, optional
|
|
751
|
+
The threshold value for symmetric log color scales. Defaults to 1.0.
|
|
752
|
+
logstep: float, optional
|
|
753
|
+
The step size for tick values in log color scales. Defaults to 1.0.
|
|
754
|
+
linscale: float, optional
|
|
755
|
+
A scaling factor for linear regions in symmetric log color scales.
|
|
756
|
+
Defaults to 0.03.
|
|
757
|
+
|
|
758
|
+
Returns
|
|
759
|
+
-------
|
|
760
|
+
tuple
|
|
761
|
+
A tuple containing:
|
|
762
|
+
- norm: A Matplotlib color normalization object for the colorbar.
|
|
763
|
+
- ticks: A list of tick values for the colorbar.
|
|
764
|
+
|
|
765
|
+
Raises
|
|
766
|
+
------
|
|
767
|
+
ValueError
|
|
768
|
+
If an invalid colorscale type is provided.
|
|
769
|
+
|
|
770
|
+
Notes
|
|
771
|
+
-----
|
|
772
|
+
- The 'SymLog' colorscale is currently not fully implemented.
|
|
773
|
+
"""
|
|
774
|
+
import matplotlib
|
|
775
|
+
|
|
776
|
+
vmin, vmax = set_plot_limits(v1, v2, data, colorscale)
|
|
777
|
+
if colorscale == ColorScale.Linear:
|
|
778
|
+
levels = matplotlib.ticker.MaxNLocator(nbins=255).tick_values(vmin, vmax)
|
|
779
|
+
norm = matplotlib.colors.BoundaryNorm(levels, ncolors=256, clip=True)
|
|
780
|
+
ticks = matplotlib.ticker.LinearLocator(numticks=9)
|
|
781
|
+
elif colorscale == ColorScale.Log: # logarithmic
|
|
782
|
+
norm = matplotlib.colors.LogNorm(vmin, vmax)
|
|
783
|
+
ticks = matplotlib.ticker.LogLocator(base=10, subs=range(0, 9))
|
|
784
|
+
else: # symmetric log
|
|
785
|
+
logthresh = int(math.floor(math.log10(linthresh)))
|
|
786
|
+
minlog = int(math.ceil(math.log10(-vmin)))
|
|
787
|
+
maxlog = int(math.ceil(math.log10(vmax)))
|
|
788
|
+
# TODO: fix this!
|
|
789
|
+
# norm = matplotlib.colors.SymLogNorm(linthresh, linscale, vmin, vmax, base=10)
|
|
790
|
+
# ticks = [ [-(10.0**x) for x in minlog:-logstep:logthresh]..., 0.0,
|
|
791
|
+
# [10.0**x for x in logthresh:logstep:maxlog]..., ]
|
|
792
|
+
|
|
793
|
+
return norm, ticks
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def set_plot_limits(
|
|
797
|
+
vmin: float,
|
|
798
|
+
vmax: float,
|
|
799
|
+
data: np.ndarray,
|
|
800
|
+
colorscale: ColorScale = ColorScale.Linear,
|
|
801
|
+
) -> tuple:
|
|
802
|
+
"""
|
|
803
|
+
Calculates appropriate plot limits based on data and colorscale.
|
|
804
|
+
"""
|
|
805
|
+
|
|
806
|
+
if colorscale in (ColorScale.Linear, ColorScale.SymLog):
|
|
807
|
+
vmin = vmin if not math.isinf(vmin) else np.nanmin(data)
|
|
808
|
+
vmax = vmax if not math.isinf(vmax) else np.nanmax(data)
|
|
809
|
+
else: # Logarithmic colorscale
|
|
810
|
+
positive_data = data[data > 0.0] # Exclude non-positive values
|
|
811
|
+
vmin = vmin if not math.isinf(vmin) else np.min(positive_data)
|
|
812
|
+
vmax = vmax if not math.isinf(vmax) else np.max(positive_data)
|
|
813
|
+
|
|
814
|
+
return vmin, vmax
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def configure_plot(
|
|
818
|
+
c: matplotlib.cm.ScalarMappable, # Assuming c is a colormap or similar
|
|
819
|
+
ax: matplotlib.pyplot.Axes,
|
|
820
|
+
plot_args: PlotArgs,
|
|
821
|
+
ticks: list,
|
|
822
|
+
add_colorbar: bool = True,
|
|
823
|
+
):
|
|
824
|
+
"""
|
|
825
|
+
Configures plot elements based on provided arguments.
|
|
826
|
+
"""
|
|
827
|
+
|
|
828
|
+
import matplotlib.pyplot as plt
|
|
829
|
+
|
|
830
|
+
title = plot_args.str_title
|
|
831
|
+
x_label = plot_args.strx
|
|
832
|
+
y_label = plot_args.stry
|
|
833
|
+
colorbar_title = plot_args.cb_title
|
|
834
|
+
|
|
835
|
+
# Add colorbar if requested
|
|
836
|
+
if add_colorbar:
|
|
837
|
+
cb = plt.colorbar(c, ax=ax, ticks=ticks, fraction=0.04, pad=0.02)
|
|
838
|
+
if colorbar_title:
|
|
839
|
+
cb.ax.set_ylabel(colorbar_title)
|
|
840
|
+
cb.ax.tick_params(direction="in")
|
|
841
|
+
|
|
842
|
+
# Set plot title and labels
|
|
843
|
+
ax.set_title(title, fontweight="bold")
|
|
844
|
+
ax.set_xlabel(x_label)
|
|
845
|
+
ax.set_ylabel(y_label)
|
|
846
|
+
ax.set_aspect("equal")
|
|
847
|
+
|
|
848
|
+
# Style plot borders and ticks
|
|
849
|
+
for spine in ax.spines.values():
|
|
850
|
+
spine.set_linewidth(2.0)
|
|
851
|
+
ax.tick_params(axis="both", which="major", width=2.0, length=3)
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def vdfslice(
|
|
855
|
+
meta: Vlsv,
|
|
856
|
+
location: tuple | list,
|
|
857
|
+
ax=None,
|
|
858
|
+
limits: tuple = (float("-inf"), float("inf"), float("-inf"), float("inf")),
|
|
859
|
+
verbose: bool = False,
|
|
860
|
+
species: str = "proton",
|
|
861
|
+
unit: AxisUnit = AxisUnit.SI,
|
|
862
|
+
unitv: str = "km/s",
|
|
863
|
+
vmin: float = float("-inf"),
|
|
864
|
+
vmax: float = float("inf"),
|
|
865
|
+
slicetype: str = None,
|
|
866
|
+
vslicethick: float = 0.0,
|
|
867
|
+
center: str = None,
|
|
868
|
+
weight: str = "particle",
|
|
869
|
+
flimit: float = -1.0,
|
|
870
|
+
**kwargs,
|
|
871
|
+
):
|
|
872
|
+
v1, v2, r1, r2, weights, strx, stry, str_title = prep_vdf(
|
|
873
|
+
meta,
|
|
874
|
+
location,
|
|
875
|
+
species,
|
|
876
|
+
unit,
|
|
877
|
+
unitv,
|
|
878
|
+
slicetype,
|
|
879
|
+
vslicethick,
|
|
880
|
+
center,
|
|
881
|
+
weight,
|
|
882
|
+
flimit,
|
|
883
|
+
verbose,
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
import matplotlib
|
|
887
|
+
import matplotlib.pyplot as plt
|
|
888
|
+
|
|
889
|
+
if math.isinf(vmin):
|
|
890
|
+
vmin = np.min(weights)
|
|
891
|
+
if math.isinf(vmax):
|
|
892
|
+
vmax = np.max(weights)
|
|
893
|
+
|
|
894
|
+
if verbose:
|
|
895
|
+
print(f"Active f range is {vmin}, {vmax}")
|
|
896
|
+
|
|
897
|
+
if not ax:
|
|
898
|
+
ax = plt.gca()
|
|
899
|
+
|
|
900
|
+
norm = matplotlib.colors.LogNorm(vmin, vmax)
|
|
901
|
+
|
|
902
|
+
h = ax.hist2d(v1, v2, bins=(r1, r2), weights=weights, norm=norm, shading="flat")
|
|
903
|
+
|
|
904
|
+
ax.set_title(str_title, fontweight="bold")
|
|
905
|
+
ax.set_xlabel(strx)
|
|
906
|
+
ax.set_ylabel(stry)
|
|
907
|
+
ax.set_aspect("equal")
|
|
908
|
+
ax.grid(color="grey", linestyle="-")
|
|
909
|
+
ax.tick_params(direction="in")
|
|
910
|
+
|
|
911
|
+
cb = plt.colorbar(h[3], ax=ax, fraction=0.04, pad=0.02)
|
|
912
|
+
cb.ax.tick_params(which="both", direction="in")
|
|
913
|
+
cb_title = cb.ax.set_ylabel("f(v)")
|
|
914
|
+
|
|
915
|
+
# TODO: Draw vector of magnetic field direction
|
|
916
|
+
# if slicetype in ("xy", "xz", "yz"):
|
|
917
|
+
|
|
918
|
+
return h[3] # h[0] is 2D data, h[1] is x axis, h[2] is y axis
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def prep_vdf(
|
|
922
|
+
meta: Vlsv,
|
|
923
|
+
location: tuple | list,
|
|
924
|
+
species: str = "proton",
|
|
925
|
+
unit: AxisUnit = AxisUnit.SI,
|
|
926
|
+
unitv: str = "km/s",
|
|
927
|
+
slicetype: str = None,
|
|
928
|
+
vslicethick: float = 0.0,
|
|
929
|
+
center: str = None,
|
|
930
|
+
weight: str = "particle",
|
|
931
|
+
flimit: float = -1.0,
|
|
932
|
+
verbose: bool = False,
|
|
933
|
+
):
|
|
934
|
+
ncells = meta.ncells
|
|
935
|
+
|
|
936
|
+
if species in meta.meshes:
|
|
937
|
+
vmesh = meta.meshes[species]
|
|
938
|
+
else:
|
|
939
|
+
raise ValueError(f"Unable to detect population {species}")
|
|
940
|
+
|
|
941
|
+
if not slicetype in (None, "xy", "xz", "yz", "bperp", "bpar1", "bpar2"):
|
|
942
|
+
raise ValueError(f"Unknown type {slicetype}")
|
|
943
|
+
|
|
944
|
+
if unit == AxisUnit.EARTH:
|
|
945
|
+
location = [loc * RE for loc in location]
|
|
946
|
+
|
|
947
|
+
# Set unit conversion factor
|
|
948
|
+
unitvfactor = 1e3 if unitv == "km/s" else 1.0
|
|
949
|
+
|
|
950
|
+
# Get closest cell ID from input coordinates
|
|
951
|
+
cidReq = meta.getcell(location)
|
|
952
|
+
cidNearest = meta.getnearestcellwithvdf(cidReq)
|
|
953
|
+
|
|
954
|
+
# Set normal direction
|
|
955
|
+
if not slicetype:
|
|
956
|
+
if ncells[1] == 1 and ncells[2] == 1: # 1D, select xz
|
|
957
|
+
slicetype = "xz"
|
|
958
|
+
elif ncells[1] == 1: # polar
|
|
959
|
+
slicetype = "xz"
|
|
960
|
+
elif ncells[2] == 1: # ecliptic
|
|
961
|
+
slicetype = "xy"
|
|
962
|
+
else:
|
|
963
|
+
slicetype = "xy"
|
|
964
|
+
|
|
965
|
+
if slicetype in ("xy", "yz", "xz"):
|
|
966
|
+
if slicetype == "xy":
|
|
967
|
+
dir1, dir2, dir3 = 0, 1, 2
|
|
968
|
+
ŝ = [0.0, 0.0, 1.0]
|
|
969
|
+
elif slicetype == "xz":
|
|
970
|
+
dir1, dir2, dir3 = 0, 2, 1
|
|
971
|
+
ŝ = [0.0, 1.0, 0.0]
|
|
972
|
+
elif slicetype == "yz":
|
|
973
|
+
dir1, dir2, dir3 = 1, 2, 0
|
|
974
|
+
ŝ = [1.0, 0.0, 0.0]
|
|
975
|
+
v1size = vmesh.vblocks[dir1] * vmesh.vblock_size[dir1]
|
|
976
|
+
v2size = vmesh.vblocks[dir2] * vmesh.vblock_size[dir2]
|
|
977
|
+
|
|
978
|
+
v1min, v1max = vmesh.vmin[dir1], vmesh.vmax[dir1]
|
|
979
|
+
v2min, v2max = vmesh.vmin[dir2], vmesh.vmax[dir2]
|
|
980
|
+
elif slicetype in ("bperp", "bpar1", "bpar2"):
|
|
981
|
+
# TODO: WIP
|
|
982
|
+
pass
|
|
983
|
+
|
|
984
|
+
if not math.isclose((v1max - v1min) / v1size, (v2max - v2min) / v2size):
|
|
985
|
+
warnings.warn("Noncubic vgrid applied!")
|
|
986
|
+
|
|
987
|
+
cellsize = (v1max - v1min) / v1size
|
|
988
|
+
|
|
989
|
+
r1 = np.linspace(v1min / unitvfactor, v1max / unitvfactor, v1size + 1)
|
|
990
|
+
r2 = np.linspace(v2min / unitvfactor, v2max / unitvfactor, v2size + 1)
|
|
991
|
+
|
|
992
|
+
vcellids, vcellf = meta.read_vcells(cidNearest, species)
|
|
993
|
+
|
|
994
|
+
V = meta.getvcellcoordinates(vcellids, species)
|
|
995
|
+
|
|
996
|
+
if center:
|
|
997
|
+
if center == "bulk": # centered with bulk velocity
|
|
998
|
+
if meta.has_variable("moments"): # From a restart file
|
|
999
|
+
Vcenter = meta.read_variable("restart_V", cidNearest)
|
|
1000
|
+
elif meta.has_variable(species * "/vg_v"): # Vlasiator 5
|
|
1001
|
+
Vcenter = meta.read_variable(species * "/vg_v", cidNearest)
|
|
1002
|
+
elif meta.has_variable(species * "/V"):
|
|
1003
|
+
Vcenter = meta.read_variable(species * "/V", cidNearest)
|
|
1004
|
+
else:
|
|
1005
|
+
Vcenter = meta.read_variable("V", cidNearest)
|
|
1006
|
+
elif center == "peak": # centered on highest VDF-value
|
|
1007
|
+
Vcenter = np.maximum(vcellf)
|
|
1008
|
+
|
|
1009
|
+
V = np.array(
|
|
1010
|
+
[np.fromiter((v[i] - Vcenter for i in range(3)), dtype=float) for v in V]
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
# Set sparsity threshold
|
|
1014
|
+
if flimit < 0:
|
|
1015
|
+
if meta.has_variable(species + "/vg_effectivesparsitythreshold"):
|
|
1016
|
+
flimit = meta.readvariable(
|
|
1017
|
+
species + "/vg_effectivesparsitythreshold", cidNearest
|
|
1018
|
+
)
|
|
1019
|
+
elif meta.has_variable(species + "/EffectiveSparsityThreshold"):
|
|
1020
|
+
flimit = meta.read_variable(
|
|
1021
|
+
species + "/EffectiveSparsityThreshold", cidNearest
|
|
1022
|
+
)
|
|
1023
|
+
else:
|
|
1024
|
+
flimit = 1e-16
|
|
1025
|
+
|
|
1026
|
+
# Drop velocity cells which are below the sparsity threshold
|
|
1027
|
+
findex_ = vcellf >= flimit
|
|
1028
|
+
fselect = vcellf[findex_]
|
|
1029
|
+
Vselect = V[findex_]
|
|
1030
|
+
|
|
1031
|
+
if slicetype in ("xy", "yz", "xz"):
|
|
1032
|
+
v1select = np.array([v[dir1] for v in Vselect])
|
|
1033
|
+
v2select = np.array([v[dir2] for v in Vselect])
|
|
1034
|
+
vnormal = np.array([v[dir3] for v in Vselect])
|
|
1035
|
+
|
|
1036
|
+
vec = ("vx", "vy", "vz")
|
|
1037
|
+
strx = vec[dir1]
|
|
1038
|
+
stry = vec[dir2]
|
|
1039
|
+
elif slicetype in ("bperp", "bpar1", "bpar2"):
|
|
1040
|
+
v1select = np.empty(len(Vselect), dtype=Vselect.dtype)
|
|
1041
|
+
v2select = np.empty(len(Vselect), dtype=Vselect.dtype)
|
|
1042
|
+
vnormal = np.empty(len(Vselect), dtype=Vselect.dtype)
|
|
1043
|
+
# TODO: WIP
|
|
1044
|
+
# for v1s, v2s, vn, vs in zip(v1select, v2select, vnormal, Vselect):
|
|
1045
|
+
# v1s, v2s, vn = Rinv * Vselect
|
|
1046
|
+
|
|
1047
|
+
# if slicetype == "bperp":
|
|
1048
|
+
# strx = r"$v_{B \times V}$"
|
|
1049
|
+
# stry = r"$v_{B \times (B \times V)}$"
|
|
1050
|
+
# elif slicetype == "bpar2":
|
|
1051
|
+
# strx = r"$v_{B}$"
|
|
1052
|
+
# stry = r"$v_{B \times V}$"
|
|
1053
|
+
# elif slicetype == "bpar1":
|
|
1054
|
+
# strx = r"$v_{B \times (B \times V)}$"
|
|
1055
|
+
# stry = r"$v_{B}$"
|
|
1056
|
+
|
|
1057
|
+
unitstr = f" [{unitv}]"
|
|
1058
|
+
strx += unitstr
|
|
1059
|
+
stry += unitstr
|
|
1060
|
+
|
|
1061
|
+
if vslicethick < 0: # Set a proper thickness
|
|
1062
|
+
if any(
|
|
1063
|
+
i == 1.0 for i in ŝ
|
|
1064
|
+
): # Assure that the slice cut through at least 1 vcell
|
|
1065
|
+
vslicethick = cellsize
|
|
1066
|
+
else: # Assume cubic vspace grid, add extra space
|
|
1067
|
+
vslicethick = cellsize * (math.sqrt(3) + 0.05)
|
|
1068
|
+
|
|
1069
|
+
# Weights using particle flux or phase-space density
|
|
1070
|
+
fweight = (
|
|
1071
|
+
fselect * np.linalg.norm([v1select, v2select, vnormal])
|
|
1072
|
+
if weight == "flux"
|
|
1073
|
+
else fselect
|
|
1074
|
+
)
|
|
1075
|
+
|
|
1076
|
+
# Select cells within the slice area
|
|
1077
|
+
if vslicethick > 0.0:
|
|
1078
|
+
ind_ = abs(vnormal) <= 0.5 * vslicethick
|
|
1079
|
+
v1, v2, fweight = v1select[ind_], v2select[ind_], fweight[ind_]
|
|
1080
|
+
else:
|
|
1081
|
+
v1, v2 = v1select, v2select
|
|
1082
|
+
|
|
1083
|
+
v1 = [v / unitvfactor for v in v1]
|
|
1084
|
+
v2 = [v / unitvfactor for v in v2]
|
|
1085
|
+
|
|
1086
|
+
str_title = f"t = {meta.time:4.1f}s"
|
|
1087
|
+
|
|
1088
|
+
if verbose:
|
|
1089
|
+
print(f"Original coordinates : {location}")
|
|
1090
|
+
print(f"Original cell : {meta.getcellcoordinates(cidReq)}")
|
|
1091
|
+
print(f"Nearest cell with VDF: {meta.getcellcoordinates(cidNearest)}")
|
|
1092
|
+
print(f"CellID: {cidNearest}")
|
|
1093
|
+
|
|
1094
|
+
if center == "bulk":
|
|
1095
|
+
print(f"Transforming to plasma frame, travelling at speed {Vcenter}")
|
|
1096
|
+
elif center == "peak":
|
|
1097
|
+
print(f"Transforming to peak f-value frame, travelling at speed {Vcenter}")
|
|
1098
|
+
|
|
1099
|
+
print(f"Using VDF threshold value of {flimit}.")
|
|
1100
|
+
|
|
1101
|
+
if vslicethick > 0:
|
|
1102
|
+
print("Performing slice with a counting thickness of $vslicethick")
|
|
1103
|
+
else:
|
|
1104
|
+
print(f"Projecting total VDF to a single plane")
|
|
1105
|
+
|
|
1106
|
+
return v1, v2, r1, r2, fweight, strx, stry, str_title
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
# Append plotting functions
|
|
1110
|
+
Vlsv.plot = plot
|
|
1111
|
+
Vlsv.pcolormesh = pcolormesh
|
|
1112
|
+
Vlsv.contourf = contourf
|
|
1113
|
+
Vlsv.contour = contour
|
|
1114
|
+
Vlsv.streamplot = streamplot
|
|
1115
|
+
Vlsv.vdfslice = vdfslice
|