osiris-utils 1.0.5__py3-none-any.whl → 1.0.9__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.
- osiris_utils/__init__.py +3 -2
- osiris_utils/data.py +436 -117
- osiris_utils/data_readers.py +14 -14
- osiris_utils/gui.py +40 -40
- osiris_utils/mean_field_theory.py +52 -0
- osiris_utils/utils.py +23 -72
- {osiris_utils-1.0.5.dist-info → osiris_utils-1.0.9.dist-info}/METADATA +6 -3
- osiris_utils-1.0.9.dist-info/RECORD +11 -0
- {osiris_utils-1.0.5.dist-info → osiris_utils-1.0.9.dist-info}/WHEEL +1 -1
- osiris_utils-1.0.5.dist-info/RECORD +0 -10
- {osiris_utils-1.0.5.dist-info → osiris_utils-1.0.9.dist-info}/LICENSE.txt +0 -0
- {osiris_utils-1.0.5.dist-info → osiris_utils-1.0.9.dist-info}/top_level.txt +0 -0
osiris_utils/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from .utils import (time_estimation, filesize_estimation, transverse_average, integrate, animate_2D,
|
|
2
|
-
save_data, read_data,
|
|
2
|
+
save_data, read_data, courant2D)
|
|
3
3
|
from .data_readers import open1D, open2D, open3D, read_osiris_file
|
|
4
4
|
from .gui import LAVA_Qt, LAVA
|
|
5
|
-
from .data import OsirisGridFile, OsirisRawFile
|
|
5
|
+
from .data import OsirisGridFile, OsirisRawFile, OsirisData
|
|
6
|
+
from .mean_field_theory import MeanFieldTheory
|
osiris_utils/data.py
CHANGED
|
@@ -1,158 +1,477 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import h5py
|
|
3
3
|
|
|
4
|
-
class
|
|
4
|
+
class OsirisData():
|
|
5
|
+
'''
|
|
6
|
+
Parent class for Osiris data files.
|
|
7
|
+
|
|
8
|
+
Input:
|
|
9
|
+
------
|
|
10
|
+
- filename: the path to the HDF5 file
|
|
11
|
+
Attributes:
|
|
12
|
+
----------
|
|
13
|
+
- filename - the path to the HDF5 file
|
|
14
|
+
str
|
|
15
|
+
- file - the HDF5 file
|
|
16
|
+
h5py.File
|
|
17
|
+
- verbose - if True, the class will print messages
|
|
18
|
+
bool
|
|
19
|
+
- dt - the time step
|
|
20
|
+
float
|
|
21
|
+
- dim - the number of dimensions
|
|
22
|
+
int
|
|
23
|
+
- time - the time and its units
|
|
24
|
+
list [time, units]
|
|
25
|
+
list [float, str]
|
|
26
|
+
- iter - the iteration number
|
|
27
|
+
int
|
|
28
|
+
- name - the name of the data
|
|
29
|
+
str
|
|
30
|
+
- type - the type of data
|
|
31
|
+
str
|
|
32
|
+
'''
|
|
33
|
+
def __init__(self, filename):
|
|
34
|
+
self._filename = filename
|
|
35
|
+
self._file = None
|
|
36
|
+
|
|
37
|
+
self._verbose = False
|
|
38
|
+
|
|
39
|
+
self._open_file(self._filename)
|
|
40
|
+
self._load_basic_attributes(self._file)
|
|
41
|
+
|
|
42
|
+
def _load_basic_attributes(self, f: h5py.File) -> None:
|
|
43
|
+
'''Load common attributes from HDF5 file'''
|
|
44
|
+
self._dt = float(f['SIMULATION'].attrs['DT'][0])
|
|
45
|
+
self._dim = int(f['SIMULATION'].attrs['NDIMS'][0])
|
|
46
|
+
self._time = [float(f.attrs['TIME'][0]), f.attrs['TIME UNITS'][0].decode('utf-8')]
|
|
47
|
+
self._iter = int(f.attrs['ITER'][0])
|
|
48
|
+
self._name = f.attrs['NAME'][0].decode('utf-8')
|
|
49
|
+
self._type = f.attrs['TYPE'][0].decode('utf-8')
|
|
50
|
+
|
|
51
|
+
def verbose(self, verbose: bool = True):
|
|
52
|
+
'''
|
|
53
|
+
Set the verbosity of the class
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
verbose : bool, optional
|
|
58
|
+
If True, the class will print messages, by default True when calling (False when not calling)
|
|
59
|
+
'''
|
|
60
|
+
self._verbose = verbose
|
|
61
|
+
|
|
62
|
+
def _open_file(self, filename):
|
|
63
|
+
'''
|
|
64
|
+
Open the HDF5 file.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
filename : str
|
|
69
|
+
The path to the HDF5 file.
|
|
70
|
+
'''
|
|
71
|
+
if self._verbose: print(f'Opening file > {filename}')
|
|
72
|
+
self._file = h5py.File(self._filename, 'r')
|
|
73
|
+
|
|
74
|
+
def _close_file(self):
|
|
75
|
+
'''
|
|
76
|
+
Close the HDF5 file.
|
|
77
|
+
'''
|
|
78
|
+
if self._verbose: print('Closing file')
|
|
79
|
+
if self._file:
|
|
80
|
+
self._file.close()
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def dt(self):
|
|
84
|
+
'''
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
float
|
|
88
|
+
The time step
|
|
89
|
+
'''
|
|
90
|
+
return self._dt
|
|
91
|
+
@property
|
|
92
|
+
def dim(self):
|
|
93
|
+
'''
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
int
|
|
97
|
+
The number of dimensions
|
|
98
|
+
'''
|
|
99
|
+
return self._dim
|
|
100
|
+
@property
|
|
101
|
+
def time(self):
|
|
102
|
+
'''
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
list
|
|
106
|
+
The time and its units
|
|
107
|
+
'''
|
|
108
|
+
return self._time
|
|
109
|
+
@property
|
|
110
|
+
def iter(self):
|
|
111
|
+
'''
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
int
|
|
115
|
+
The iteration number
|
|
116
|
+
'''
|
|
117
|
+
return self._iter
|
|
118
|
+
@property
|
|
119
|
+
def name(self):
|
|
120
|
+
'''
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
str
|
|
124
|
+
The name of the data
|
|
125
|
+
'''
|
|
126
|
+
return self._name
|
|
127
|
+
@property
|
|
128
|
+
def type(self):
|
|
129
|
+
'''
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
str
|
|
133
|
+
The type of data
|
|
134
|
+
'''
|
|
135
|
+
return self._type
|
|
136
|
+
|
|
137
|
+
class OsirisGridFile(OsirisData):
|
|
5
138
|
'''
|
|
6
139
|
Class to read the grid data from an OSIRIS HDF5 file.
|
|
7
140
|
|
|
8
141
|
Input:
|
|
142
|
+
-----
|
|
9
143
|
- filename: the path to the HDF5 file
|
|
10
144
|
|
|
11
145
|
Attributes:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- data: the data (numpy array) with shape (nx1, nx2, nx3) (Transpose to use `plt.imshow`)
|
|
21
|
-
numpy.ndarray
|
|
22
|
-
- dt: the time step
|
|
146
|
+
----------
|
|
147
|
+
- filename - the path to the HDF5 file
|
|
148
|
+
str
|
|
149
|
+
- file - the HDF5 file
|
|
150
|
+
h5py.File
|
|
151
|
+
- verbose - if True, the class will print messages
|
|
152
|
+
bool
|
|
153
|
+
- dt - the time step
|
|
23
154
|
float
|
|
24
|
-
- dim
|
|
155
|
+
- dim - the number of dimensions
|
|
25
156
|
int
|
|
26
|
-
- time
|
|
157
|
+
- time - the time and its units
|
|
27
158
|
list [time, units]
|
|
28
159
|
list [float, str]
|
|
29
|
-
- iter
|
|
160
|
+
- iter - the iteration number
|
|
30
161
|
int
|
|
31
|
-
- name
|
|
162
|
+
- name - the name of the data
|
|
163
|
+
str
|
|
164
|
+
- type - the type of data
|
|
32
165
|
str
|
|
33
|
-
-
|
|
166
|
+
- grid - the grid data ((x1.min, x1.max), (x2.min, x2.max), (x3.min, x3.max))
|
|
167
|
+
numpy.ndarray
|
|
168
|
+
- nx - the number of grid points (nx1, nx2, nx3)
|
|
169
|
+
numpy.ndarray
|
|
170
|
+
- dx - the grid spacing (dx1, dx2, dx3)
|
|
171
|
+
numpy.ndarray
|
|
172
|
+
- axis - the axis data [(name_x1, units_x1, long_name_x1, type_x1), ...]
|
|
173
|
+
list of dictionaries
|
|
174
|
+
- data: the data (numpy array) with shape (nx1, nx2, nx3) (Transpose to use `plt.imshow`)
|
|
175
|
+
numpy.ndarray
|
|
176
|
+
- units - the units of the data
|
|
34
177
|
str
|
|
35
|
-
- label
|
|
178
|
+
- label - the label of the data (LaTeX formatted)
|
|
36
179
|
|
|
37
|
-
Example:
|
|
38
|
-
example = utils.OsirisGridFile("grid/file/osiris/output-000000.h5")
|
|
39
|
-
plt.imshow(example.data.T, aspect='auto', origin='lower', extent=[example.grid[0][0], example.grid[0][-1], example.grid[1][0], example.grid[1][-1]])
|
|
40
|
-
plt.xlabel(rf"${example.axis[0]["long_name"]}$ [${example.axis[0]["units"]}$]")
|
|
41
|
-
plt.ylabel(rf"${example.axis[1]["long_name"]}$ [${example.axis[1]["units"]}$]")
|
|
42
|
-
plt.title(rf"${example.label}$, t={example.time[0]} ${example.time[1]}$")
|
|
43
180
|
'''
|
|
44
181
|
def __init__(self, filename):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
182
|
+
super().__init__(filename)
|
|
183
|
+
|
|
184
|
+
variable_key = self._get_variable_key(self._file)
|
|
185
|
+
|
|
186
|
+
self._units = self._file.attrs['UNITS'][0].decode('utf-8')
|
|
187
|
+
self._label = self._file.attrs['LABEL'][0].decode('utf-8')
|
|
188
|
+
self._FFTdata = None
|
|
189
|
+
|
|
190
|
+
data = np.array(self._file[variable_key][:])
|
|
191
|
+
|
|
192
|
+
axis = list(self._file['AXIS'].keys())
|
|
193
|
+
if len(axis) == 1:
|
|
194
|
+
self._grid = self._file['AXIS/' + axis[0]][()]
|
|
195
|
+
self._nx = len(data)
|
|
196
|
+
self._dx = (self.grid[1] - self.grid[0] ) / self.nx
|
|
197
|
+
self._x = np.arange(self.grid[0], self.grid[1], self.dx)
|
|
198
|
+
else:
|
|
199
|
+
grid = []
|
|
200
|
+
for ax in axis: grid.append(self._file['AXIS/' + ax][()])
|
|
201
|
+
self._grid = np.array(grid)
|
|
202
|
+
self._nx = self._file[variable_key][()].transpose().shape
|
|
203
|
+
self._dx = (self.grid[:, 1] - self.grid[:, 0])/self.nx
|
|
204
|
+
self._x = [np.arange(self.grid[i, 0], self.grid[i, 1], self.dx[i]) for i in range(self.dim)]
|
|
205
|
+
|
|
206
|
+
self._axis = []
|
|
207
|
+
for ax in axis:
|
|
208
|
+
axis_data = {
|
|
209
|
+
'name': self._file['AXIS/'+ax].attrs['NAME'][0].decode('utf-8'),
|
|
210
|
+
'units': self._file['AXIS/'+ax].attrs['UNITS'][0].decode('utf-8'),
|
|
211
|
+
'long_name': self._file['AXIS/'+ax].attrs['LONG_NAME'][0].decode('utf-8'),
|
|
212
|
+
'type': self._file['AXIS/'+ax].attrs['TYPE'][0].decode('utf-8'),
|
|
213
|
+
'plot_label': rf'${self._file["AXIS/"+ax].attrs["LONG_NAME"][0].decode("utf-8")}$ $[{self._file["AXIS/"+ax].attrs["UNITS"][0].decode("utf-8")}]$',
|
|
214
|
+
}
|
|
215
|
+
self._axis.append(axis_data)
|
|
216
|
+
|
|
217
|
+
self._data = np.ascontiguousarray(data.T)
|
|
218
|
+
|
|
219
|
+
self._close_file()
|
|
220
|
+
|
|
221
|
+
def _load_basic_attributes(self, f: h5py.File) -> None:
|
|
222
|
+
'''Load common attributes from HDF5 file'''
|
|
223
|
+
self._dt = float(f['SIMULATION'].attrs['DT'][0])
|
|
224
|
+
self._dim = int(f['SIMULATION'].attrs['NDIMS'][0])
|
|
225
|
+
self._time = [float(f.attrs['TIME'][0]), f.attrs['TIME UNITS'][0].decode('utf-8')]
|
|
226
|
+
self._iter = int(f.attrs['ITER'][0])
|
|
227
|
+
self._name = f.attrs['NAME'][0].decode('utf-8')
|
|
228
|
+
self._type = f.attrs['TYPE'][0].decode('utf-8')
|
|
50
229
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
230
|
+
def _get_variable_key(self, f: h5py.File) -> str:
|
|
231
|
+
return next(k for k in f.keys() if k not in {'AXIS', 'SIMULATION'})
|
|
232
|
+
|
|
233
|
+
# Getters
|
|
234
|
+
@property
|
|
235
|
+
def grid(self):
|
|
236
|
+
'''
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
numpy.ndarray
|
|
240
|
+
The grid data ((x1.min, x1.max), (x2.min, x2.max), (x3.min, x3.max
|
|
241
|
+
'''
|
|
242
|
+
return self._grid
|
|
243
|
+
@property
|
|
244
|
+
def nx(self):
|
|
245
|
+
'''
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
numpy.ndarray
|
|
249
|
+
The number of grid points (nx1, nx2, nx3)
|
|
250
|
+
'''
|
|
251
|
+
return self._nx
|
|
252
|
+
@property
|
|
253
|
+
def dx(self):
|
|
254
|
+
'''
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
numpy.ndarray
|
|
258
|
+
The grid spacing (dx1, dx2, dx3)
|
|
259
|
+
'''
|
|
260
|
+
return self._dx
|
|
261
|
+
@property
|
|
262
|
+
def x(self):
|
|
263
|
+
'''
|
|
264
|
+
Returns
|
|
265
|
+
-------
|
|
266
|
+
numpy.ndarray
|
|
267
|
+
The grid points in each axis
|
|
268
|
+
'''
|
|
269
|
+
return self._x
|
|
270
|
+
@property
|
|
271
|
+
def axis(self):
|
|
272
|
+
'''
|
|
273
|
+
Returns
|
|
274
|
+
-------
|
|
275
|
+
list of dictionaries
|
|
276
|
+
The axis data [(name_x1, units_x1, long_name_x1, type_x1), ...]
|
|
277
|
+
'''
|
|
278
|
+
return self._axis
|
|
279
|
+
@property
|
|
280
|
+
def data(self):
|
|
281
|
+
'''
|
|
282
|
+
Returns
|
|
283
|
+
-------
|
|
284
|
+
numpy.ndarray
|
|
285
|
+
The data (numpy array) with shape (nx1, nx2, nx3) (Transpose to use `plt.imshow`)
|
|
286
|
+
'''
|
|
287
|
+
return self._data
|
|
288
|
+
@property
|
|
289
|
+
def units(self):
|
|
290
|
+
'''
|
|
291
|
+
Returns
|
|
292
|
+
-------
|
|
293
|
+
str
|
|
294
|
+
The units of the data (LaTeX formatted)
|
|
295
|
+
'''
|
|
296
|
+
return self._units
|
|
297
|
+
@property
|
|
298
|
+
def label(self):
|
|
299
|
+
'''
|
|
300
|
+
Returns
|
|
301
|
+
-------
|
|
302
|
+
str
|
|
303
|
+
The label of the data (LaTeX formatted)
|
|
304
|
+
'''
|
|
305
|
+
return self._label
|
|
306
|
+
@property
|
|
307
|
+
def FFTdata(self):
|
|
308
|
+
'''
|
|
309
|
+
Returns
|
|
310
|
+
-------
|
|
311
|
+
numpy.ndarray
|
|
312
|
+
The FFT of the data
|
|
313
|
+
'''
|
|
314
|
+
if self._FFTdata is None:
|
|
315
|
+
raise ValueError('The FFT of the data has not been computed yet. Compute it using the FFT method.')
|
|
316
|
+
return self._FFTdata
|
|
317
|
+
# Setters
|
|
318
|
+
@data.setter
|
|
319
|
+
def data(self, data):
|
|
320
|
+
'''
|
|
321
|
+
Set the data attribute
|
|
322
|
+
'''
|
|
323
|
+
self._data = data
|
|
324
|
+
|
|
325
|
+
def __str__(self):
|
|
326
|
+
# write me a template to print with the name, label, units, time, iter, grid, nx, dx, axis, dt, dim in a logical way
|
|
327
|
+
return rf'{self.name}' + f'\n' + rf'Time: [{self.time[0]} {self.time[1]}], dt = {self.dt}' + f'\n' + f'Iteration: {self.iter}' + f'\n' + f'Grid: {self.grid}' + f'\n' + f'dx: {self.dx}' + f'\n' + f'Dimensions: {self.dim}D'
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def __array__(self):
|
|
331
|
+
return np.asarray(self.data)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _yeeToCellCorner1d(self, boundary):
|
|
335
|
+
'''
|
|
336
|
+
Converts 1d EM fields from a staggered Yee mesh to a grid with field values centered on the corner of the cell (the corner of the cell [1] has coordinates [1])
|
|
337
|
+
'''
|
|
338
|
+
|
|
339
|
+
if self.name.lower() in ['b2', 'b3', 'e1']:
|
|
340
|
+
if boundary == 'periodic': return 0.5 * (np.roll(self.data, shift=1) + self.data)
|
|
341
|
+
else: return 0.5 * (self.data[1:] + self.data[:-1])
|
|
342
|
+
elif self.name.lower() in ['b1', 'e2', 'e3']:
|
|
343
|
+
if boundary == 'periodic': return self.data
|
|
344
|
+
else: return self.data[1:]
|
|
345
|
+
else:
|
|
346
|
+
raise TypeError(f'This method expects magnetic or electric field grid data but received \'{self.name}\' instead')
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _yeeToCellCorner2d(self, boundary):
|
|
350
|
+
'''
|
|
351
|
+
Converts 2d EM fields from a staggered Yee mesh to a grid with field values centered on the corner of the cell (the corner of the cell [1,1] has coordinates [1,1])
|
|
352
|
+
'''
|
|
353
|
+
|
|
354
|
+
if self.name.lower() in ['e1', 'b2']:
|
|
355
|
+
if boundary == 'periodic': return 0.5 * (np.roll(self.data, shift=1, axis=0) + self.data)
|
|
356
|
+
else: return 0.5 * (self.data[1:, 1:] + self.data[:-1, 1:])
|
|
357
|
+
elif self.name.lower() in ['e2', 'b1']:
|
|
358
|
+
if boundary == 'periodic': return 0.5 * (np.roll(self.data, shift=1, axis=1) + self.data)
|
|
359
|
+
else: return 0.5 * (self.data[1:, 1:] + self.data[1:, :-1])
|
|
360
|
+
elif self.name.lower() in ['b3']:
|
|
361
|
+
if boundary == 'periodic':
|
|
362
|
+
return 0.5 * (np.roll((0.5 * (np.roll(self.data, shift=1, axis=0) + self.data)), shift=1, axis=1) + (0.5 * (np.roll(self.data, shift=1, axis=0) + self.data)))
|
|
363
|
+
else:
|
|
364
|
+
return 0.25 * (self.data[1:, 1:] + self.data[:-1, 1:] + self.data[1:, :-1] + self.data[:-1, :-1])
|
|
365
|
+
elif self.name.lower() in ['e3']:
|
|
366
|
+
if boundary == 'periodic': return self.data
|
|
367
|
+
else: return self.data[1:, 1:]
|
|
368
|
+
else:
|
|
369
|
+
raise TypeError(f'This method expects magnetic or electric field grid data but received \'{self.name}\' instead')
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _yeeToCellCorner3d(self, boundary):
|
|
373
|
+
'''
|
|
374
|
+
Converts 3d EM fields from a staggered Yee mesh to a grid with field values centered on the corner of the cell (the corner of the cell [1,1,1] has coordinates [1,1,1])
|
|
375
|
+
'''
|
|
376
|
+
if boundary == 'periodic':
|
|
377
|
+
raise ValueError('Centering field from 3D simulations considering periodic boundary conditions is not implemented yet')
|
|
378
|
+
if self.name.lower() == 'b1':
|
|
379
|
+
return 0.25 * (self.data[1:, 1:, 1:] + self.data[1:, :-1, 1:] + self.data[1:, 1:, :-1] + self.data[1:, :-1, :-1])
|
|
380
|
+
elif self.name.lower() == 'b2':
|
|
381
|
+
return 0.25 * (self.data[1:, 1:, 1:] + self.data[:-1, 1:, 1:] + self.data[1:, 1:, :-1] + self.data[:-1, 1:, :-1])
|
|
382
|
+
elif self.name.lower() == 'b3':
|
|
383
|
+
return 0.25 * (self.data[1:, 1:, 1:] + self.data[:-1, 1:, 1:] + self.data[1:, :-1, 1:] + self.data[:-1, :-1, 1:])
|
|
384
|
+
elif self.name.lower() == 'e1':
|
|
385
|
+
return 0.5 * (self.data[1:, 1:, 1:] + self.data[:-1, 1:, 1:])
|
|
386
|
+
elif self.name.lower() == 'e2':
|
|
387
|
+
return 0.5 * (self.data[1:, 1:, 1:] + self.data[1:, :-1, 1:])
|
|
388
|
+
elif self.name.lower() == 'e3':
|
|
389
|
+
return 0.5 * (self.data[1:, 1:, 1:] + self.data[1:, 1:, :-1])
|
|
390
|
+
else:
|
|
391
|
+
raise TypeError(f'This method expects magnetic or electric field grid data but received \'{self.name}\' instead')
|
|
392
|
+
|
|
393
|
+
def yeeToCellCorner(self, boundary=None):
|
|
394
|
+
''''
|
|
395
|
+
Converts EM fields from a staggered Yee mesh to a grid with field values centered on the corner of the cell.'
|
|
396
|
+
Can be used for 1D, 2D and 3D simulations.'
|
|
397
|
+
Creates a new attribute `data_centered` with the centered data.'
|
|
398
|
+
'''
|
|
399
|
+
|
|
400
|
+
cases = {'b1', 'b2', 'b3', 'e1', 'e2', 'e3'}
|
|
401
|
+
if self.name not in cases:
|
|
402
|
+
raise TypeError(f'This method expects magnetic or electric field grid data but received \'{self.name}\' instead')
|
|
403
|
+
|
|
404
|
+
if self.dim == 1:
|
|
405
|
+
self.data_centered = self._yeeToCellCorner1d(boundary)
|
|
406
|
+
return self.data_centered
|
|
407
|
+
elif self.dim == 2:
|
|
408
|
+
self.data_centered = self._yeeToCellCorner2d(boundary)
|
|
409
|
+
return self.data_centered
|
|
410
|
+
elif self.dim == 3:
|
|
411
|
+
self.data_centered = self._yeeToCellCorner3d(boundary)
|
|
412
|
+
return self.data_centered
|
|
413
|
+
else:
|
|
414
|
+
raise ValueError(f'Dimension {self.dim} is not supported')
|
|
415
|
+
|
|
416
|
+
def FFT(self, axis=(0, )):
|
|
417
|
+
'''
|
|
418
|
+
Computes the Fast Fourier Transform of the data along the specified axis and shifts the zero frequency to the center.
|
|
419
|
+
Transforms the data to the frequency domain. A(x, y, z) -> A(kx, ky, kz)
|
|
420
|
+
'''
|
|
421
|
+
datafft = np.fft.fftn(self.data, axes=axis)
|
|
422
|
+
self._FFTdata = np.fft.fftshift(datafft, axes=axis)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
class OsirisRawFile(OsirisData):
|
|
91
427
|
'''
|
|
92
428
|
Class to read the raw data from an OSIRIS HDF5 file.
|
|
93
429
|
|
|
94
430
|
Input:
|
|
95
431
|
- filename: the path to the HDF5 file
|
|
96
|
-
|
|
432
|
+
|
|
97
433
|
Attributes:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
long_name (str): The name of the quantity in LaTeX (e.g., 'x_1', 'En2').
|
|
434
|
+
- axis - a dictionary where each key is a dataset name, and each value is another dictionary containing
|
|
435
|
+
name (str): The name of the quantity (e.g., r'x1', r'ene').
|
|
436
|
+
units (str): The units associated with that dataset in LaTeX (e.g., r'c/\\omega_p', r'm_e c^2').
|
|
437
|
+
long_name (str): The name of the quantity in LaTeX (e.g., r'x_1', r'En2').
|
|
103
438
|
dictionary of dictionaries
|
|
104
|
-
- data
|
|
439
|
+
- data - a dictionary where each key is a dataset name, and each value is the data
|
|
105
440
|
dictionary of np.arrays
|
|
106
|
-
- dim
|
|
441
|
+
- dim - the number of dimensions
|
|
107
442
|
int
|
|
108
|
-
- dt
|
|
443
|
+
- dt - the time step
|
|
109
444
|
float
|
|
110
|
-
- grid
|
|
445
|
+
- grid - maximum and minimum coordinates of the box, for each axis
|
|
111
446
|
numpy.ndarray(dim,2)
|
|
112
|
-
- iter
|
|
447
|
+
- iter - the iteration number
|
|
113
448
|
int
|
|
114
|
-
- name
|
|
449
|
+
- name - the name of the species
|
|
115
450
|
str
|
|
116
|
-
- time
|
|
451
|
+
- time - the time and its units
|
|
117
452
|
list [time, units]
|
|
118
453
|
list [float, str]
|
|
119
|
-
-type
|
|
454
|
+
- type - type of data (particles in the case of raw files)
|
|
120
455
|
str
|
|
121
456
|
|
|
122
|
-
Example:
|
|
123
|
-
|
|
124
|
-
electrons = utils.OsirisRawFile("MS/RAW/electrons/RAW-electrons-000002.h5")
|
|
125
|
-
plt.hist2d(electrons.data["x1"], electrons.data["x2"], bins=100, cmap='plasma', norm=mpl.colors.LogNorm())
|
|
126
|
-
plt.xlabel(r"${}$".format(electrons.axis["x1"]["long_name"]) + r"$\quad[{}]$".format(electrons.axis["x1"]["units"]))
|
|
127
|
-
plt.ylabel(r"${}$".format(electrons.axis["x2"]["long_name"]) + r"$\quad[{}]$".format(electrons.axis["x2"]["units"]))
|
|
128
|
-
plt.title(r"$t={}$".format(electrons.time[0]) + r"$\quad[{}]$".format(electrons.time[1]))
|
|
129
|
-
plt.show()
|
|
130
457
|
'''
|
|
131
458
|
|
|
132
459
|
def __init__(self, filename):
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
self.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
idx = np.where(f.attrs["QUANTS"] == str(key).encode('utf-8'))
|
|
153
|
-
axis_data = {
|
|
154
|
-
"name": f.attrs["QUANTS"][idx][0].decode('utf-8'),
|
|
155
|
-
"units": f.attrs["UNITS"][idx][0].decode('utf-8'),
|
|
156
|
-
"long_name": f.attrs["LABELS"][idx][0].decode('utf-8'),
|
|
157
|
-
}
|
|
158
|
-
self.axis[key] = axis_data
|
|
460
|
+
super().__init__(filename)
|
|
461
|
+
|
|
462
|
+
self.grid = np.array([self._file['SIMULATION'].attrs['XMIN'], self._file['SIMULATION'].attrs['XMAX']]).T
|
|
463
|
+
|
|
464
|
+
self.data = {}
|
|
465
|
+
self.axis = {}
|
|
466
|
+
for key in self._file.keys():
|
|
467
|
+
if key == 'SIMULATION': continue
|
|
468
|
+
|
|
469
|
+
self.data[key] = np.array(self._file[key][()])
|
|
470
|
+
|
|
471
|
+
idx = np.where(self._file.attrs['QUANTS'] == str(key).encode('utf-8'))
|
|
472
|
+
axis_data = {
|
|
473
|
+
'name': self._file.attrs['QUANTS'][idx][0].decode('utf-8'),
|
|
474
|
+
'units': self._file.attrs['UNITS'][idx][0].decode('utf-8'),
|
|
475
|
+
'long_name': self._file.attrs['LABELS'][idx][0].decode('utf-8'),
|
|
476
|
+
}
|
|
477
|
+
self.axis[key] = axis_data
|
osiris_utils/data_readers.py
CHANGED
|
@@ -11,9 +11,9 @@ def read_osiris_file(filename, pressure = False):
|
|
|
11
11
|
f = h5py.File(filename, 'r+')
|
|
12
12
|
atr = f.attrs
|
|
13
13
|
k = [key for key in f.keys()]
|
|
14
|
-
if
|
|
14
|
+
if 'SIMULATION' in k:
|
|
15
15
|
attrs1 = atr
|
|
16
|
-
attrs2 = f[
|
|
16
|
+
attrs2 = f['SIMULATION'].attrs
|
|
17
17
|
attrs = {}
|
|
18
18
|
for i in range(len(attrs1)):
|
|
19
19
|
attrs[[key for key in attrs1][i]] = [value for value in attrs1.values()][i]
|
|
@@ -24,21 +24,21 @@ def read_osiris_file(filename, pressure = False):
|
|
|
24
24
|
axis = []
|
|
25
25
|
for i in range(leanx):
|
|
26
26
|
axis.append(ax.get([key for key in ax.keys()][i]))
|
|
27
|
-
if
|
|
27
|
+
if 'SIMULATION' in k and pressure == False:
|
|
28
28
|
data = f.get([key for key in f.keys()][2])
|
|
29
|
-
data.attrs[
|
|
30
|
-
data.attrs[
|
|
31
|
-
elif
|
|
29
|
+
data.attrs['UNITS'] = attrs1['UNITS']
|
|
30
|
+
data.attrs['LONG_NAME'] = attrs1['LABEL']
|
|
31
|
+
elif 'SIMULATION' in k and pressure == True:
|
|
32
32
|
data = f.get([key for key in f.keys()][1])
|
|
33
|
-
data.attrs[
|
|
34
|
-
data.attrs[
|
|
33
|
+
data.attrs['UNITS'] = attrs1['UNITS']
|
|
34
|
+
data.attrs['LONG_NAME'] = attrs1['LABEL']
|
|
35
35
|
else:
|
|
36
36
|
data = f.get([key for key in f.keys()][1])
|
|
37
37
|
|
|
38
38
|
return attrs, axis, data
|
|
39
39
|
|
|
40
40
|
def open1D(filename, pressure = False):
|
|
41
|
-
|
|
41
|
+
'''
|
|
42
42
|
Open a 1D OSIRIS file and return the x axis and the data array.
|
|
43
43
|
|
|
44
44
|
Parameters
|
|
@@ -52,7 +52,7 @@ def open1D(filename, pressure = False):
|
|
|
52
52
|
The x axis.
|
|
53
53
|
data_array : numpy.ndarray
|
|
54
54
|
The data array.
|
|
55
|
-
|
|
55
|
+
'''
|
|
56
56
|
attrs, axes, data = read_osiris_file(filename, pressure)
|
|
57
57
|
datash = data.shape
|
|
58
58
|
ax1 = axes[0]
|
|
@@ -61,7 +61,7 @@ def open1D(filename, pressure = False):
|
|
|
61
61
|
return x, data_array, [attrs, axes, data]
|
|
62
62
|
|
|
63
63
|
def open2D(filename, pressure = False):
|
|
64
|
-
|
|
64
|
+
'''
|
|
65
65
|
Open a 2D OSIRIS file and return the x and y axes and the data array.
|
|
66
66
|
|
|
67
67
|
Parameters
|
|
@@ -77,7 +77,7 @@ def open2D(filename, pressure = False):
|
|
|
77
77
|
The y axis.
|
|
78
78
|
data_array : numpy.ndarray
|
|
79
79
|
The data array.
|
|
80
|
-
|
|
80
|
+
'''
|
|
81
81
|
attrs, axes, data = read_osiris_file(filename, pressure)
|
|
82
82
|
datash = data.shape
|
|
83
83
|
ax1 = axes[0]
|
|
@@ -88,7 +88,7 @@ def open2D(filename, pressure = False):
|
|
|
88
88
|
return x, y, data_array, [attrs, axes, data]
|
|
89
89
|
|
|
90
90
|
def open3D(filename):
|
|
91
|
-
|
|
91
|
+
'''
|
|
92
92
|
Open a 3D OSIRIS file and return the x, y and z axes and the data array.
|
|
93
93
|
|
|
94
94
|
Parameters
|
|
@@ -106,7 +106,7 @@ def open3D(filename):
|
|
|
106
106
|
The z axis.
|
|
107
107
|
data_array : numpy.ndarray
|
|
108
108
|
The data array.
|
|
109
|
-
|
|
109
|
+
'''
|
|
110
110
|
attrs, axes, data = read_osiris_file(filename)
|
|
111
111
|
datash = data.shape
|
|
112
112
|
ax1 = axes[0]
|
osiris_utils/gui.py
CHANGED
|
@@ -14,7 +14,7 @@ from .utils import integrate, transverse_average # Update import as needed
|
|
|
14
14
|
class LAVA_Qt(QMainWindow):
|
|
15
15
|
def __init__(self):
|
|
16
16
|
super().__init__()
|
|
17
|
-
self.setWindowTitle(
|
|
17
|
+
self.setWindowTitle('LAVA (LabAstro Visualization Assistant) - OSIRIS Data Grid Viewer')
|
|
18
18
|
self.setGeometry(100, 100, 1000, 600)
|
|
19
19
|
|
|
20
20
|
# Initialize data
|
|
@@ -39,28 +39,28 @@ class LAVA_Qt(QMainWindow):
|
|
|
39
39
|
control_layout = QHBoxLayout(control_frame)
|
|
40
40
|
|
|
41
41
|
# Buttons
|
|
42
|
-
self.browse_btn = QPushButton(
|
|
42
|
+
self.browse_btn = QPushButton('Browse Folder')
|
|
43
43
|
self.browse_btn.clicked.connect(self.load_folder)
|
|
44
|
-
self.save_btn = QPushButton(
|
|
44
|
+
self.save_btn = QPushButton('Save Plot')
|
|
45
45
|
self.save_btn.clicked.connect(self.save_plot)
|
|
46
46
|
|
|
47
47
|
# File selector
|
|
48
48
|
self.file_selector = QComboBox()
|
|
49
|
-
self.file_selector.setPlaceholderText(
|
|
49
|
+
self.file_selector.setPlaceholderText('Select file...')
|
|
50
50
|
self.file_selector.currentIndexChanged.connect(self.file_selection_changed)
|
|
51
51
|
self.file_selector.view().setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
|
52
52
|
self.file_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
|
|
53
53
|
|
|
54
54
|
# Plot type combo box
|
|
55
55
|
self.plot_combo = QComboBox()
|
|
56
|
-
self.plot_combo.addItem(
|
|
56
|
+
self.plot_combo.addItem('Select Plot Type')
|
|
57
57
|
self.plot_combo.currentTextChanged.connect(self.plot_data)
|
|
58
58
|
|
|
59
59
|
control_layout.addWidget(self.browse_btn)
|
|
60
60
|
control_layout.addWidget(self.save_btn)
|
|
61
|
-
control_layout.addWidget(QLabel(
|
|
61
|
+
control_layout.addWidget(QLabel('Files:'))
|
|
62
62
|
control_layout.addWidget(self.file_selector)
|
|
63
|
-
control_layout.addWidget(QLabel(
|
|
63
|
+
control_layout.addWidget(QLabel('Plot Type:'))
|
|
64
64
|
control_layout.addWidget(self.plot_combo)
|
|
65
65
|
self.main_layout.addWidget(control_frame)
|
|
66
66
|
|
|
@@ -80,11 +80,11 @@ class LAVA_Qt(QMainWindow):
|
|
|
80
80
|
self.ylabel_edit.textChanged.connect(self.update_plot_labels)
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
labels_layout.addWidget(QLabel(
|
|
83
|
+
labels_layout.addWidget(QLabel('Title:'))
|
|
84
84
|
labels_layout.addWidget(self.title_edit)
|
|
85
|
-
labels_layout.addWidget(QLabel(
|
|
85
|
+
labels_layout.addWidget(QLabel('X Label:'))
|
|
86
86
|
labels_layout.addWidget(self.xlabel_edit)
|
|
87
|
-
labels_layout.addWidget(QLabel(
|
|
87
|
+
labels_layout.addWidget(QLabel('Y Label:'))
|
|
88
88
|
labels_layout.addWidget(self.ylabel_edit)
|
|
89
89
|
|
|
90
90
|
# define the size of the labels frame
|
|
@@ -94,7 +94,7 @@ class LAVA_Qt(QMainWindow):
|
|
|
94
94
|
def load_folder(self):
|
|
95
95
|
folder_dialog = QFileDialog()
|
|
96
96
|
folderpath = folder_dialog.getExistingDirectory(
|
|
97
|
-
self,
|
|
97
|
+
self, 'Select Folder with HDF5 Files'
|
|
98
98
|
)
|
|
99
99
|
|
|
100
100
|
if not folderpath:
|
|
@@ -119,16 +119,16 @@ class LAVA_Qt(QMainWindow):
|
|
|
119
119
|
h5_files.sort(key=sort_key)
|
|
120
120
|
|
|
121
121
|
if not h5_files:
|
|
122
|
-
raise ValueError(
|
|
122
|
+
raise ValueError('No HDF5 files found in selected folder')
|
|
123
123
|
|
|
124
124
|
self.file_selector.addItems(h5_files)
|
|
125
125
|
self.file_selector.setCurrentIndex(0)
|
|
126
126
|
|
|
127
127
|
except Exception as e:
|
|
128
|
-
QMessageBox.critical(self,
|
|
128
|
+
QMessageBox.critical(self, 'Error', str(e))
|
|
129
129
|
|
|
130
130
|
def file_selection_changed(self, index):
|
|
131
|
-
|
|
131
|
+
'''Handle file selection change in the combo box'''
|
|
132
132
|
if index >= 0 and self.current_folder:
|
|
133
133
|
filename = self.file_selector.itemText(index)
|
|
134
134
|
self.process_file(filename)
|
|
@@ -140,32 +140,32 @@ class LAVA_Qt(QMainWindow):
|
|
|
140
140
|
self.dims = len(gridfile.axis)
|
|
141
141
|
self.type = gridfile.type
|
|
142
142
|
|
|
143
|
-
if self.type ==
|
|
143
|
+
if self.type == 'grid':
|
|
144
144
|
if self.dims == 1:
|
|
145
|
-
x = np.
|
|
146
|
-
self.xlabel_edit.setText(r
|
|
147
|
-
self.ylabel_edit.setText(r
|
|
145
|
+
x = np.arange(gridfile.grid[0], gridfile.grid[1], gridfile.dx)
|
|
146
|
+
self.xlabel_edit.setText(r'$%s$ [$%s$]' % (gridfile.axis[0]['long_name'], gridfile.axis[0]['units']))
|
|
147
|
+
self.ylabel_edit.setText(r'$%s$ [$%s$]' % (gridfile.label, gridfile.units))
|
|
148
148
|
self.data_info = (x, gridfile.data)
|
|
149
149
|
elif self.dims == 2:
|
|
150
|
-
x = np.
|
|
151
|
-
y = np.
|
|
152
|
-
self.xlabel_edit.setText(r
|
|
153
|
-
self.ylabel_edit.setText(r
|
|
150
|
+
x = np.arange(gridfile.grid[0][0], gridfile.grid[0][1], gridfile.dx[0])
|
|
151
|
+
y = np.arange(gridfile.grid[1][0], gridfile.grid[1][1], gridfile.dx[1])
|
|
152
|
+
self.xlabel_edit.setText(r'$%s$ [$%s$]' % (gridfile.axis[0]['long_name'], gridfile.axis[0]['units']))
|
|
153
|
+
self.ylabel_edit.setText(r'$%s$ [$%s$]' % (gridfile.axis[1]['long_name'], gridfile.axis[1]['units']))
|
|
154
154
|
self.data_info = (x, y, gridfile.data)
|
|
155
155
|
elif self.dims == 3:
|
|
156
|
-
raise ValueError(
|
|
156
|
+
raise ValueError('3D not supported yet')
|
|
157
157
|
else:
|
|
158
|
-
raise ValueError(
|
|
158
|
+
raise ValueError('Unsupported dimensionality')
|
|
159
159
|
|
|
160
|
-
self.title_edit.setText(r
|
|
160
|
+
self.title_edit.setText(r'$%s$ [$%s$]' %( gridfile.label, gridfile.units))
|
|
161
161
|
self.update_plot_menu()
|
|
162
162
|
self.plot_data()
|
|
163
163
|
|
|
164
164
|
else:
|
|
165
|
-
QMessageBox.information(self,
|
|
165
|
+
QMessageBox.information(self, 'Info', f'{self.type} data not supported yet')
|
|
166
166
|
|
|
167
167
|
except Exception as e:
|
|
168
|
-
QMessageBox.critical(self,
|
|
168
|
+
QMessageBox.critical(self, 'Error', str(e))
|
|
169
169
|
|
|
170
170
|
def create_plot_area(self):
|
|
171
171
|
# Matplotlib figure and canvas
|
|
@@ -194,9 +194,9 @@ class LAVA_Qt(QMainWindow):
|
|
|
194
194
|
self.current_ax = self.figure.add_subplot(111)
|
|
195
195
|
plot_type = self.plot_combo.currentText()
|
|
196
196
|
|
|
197
|
-
if
|
|
197
|
+
if 'Line' in plot_type:
|
|
198
198
|
self.current_ax.plot(x, data)
|
|
199
|
-
elif
|
|
199
|
+
elif 'Scatter' in plot_type:
|
|
200
200
|
self.current_ax.scatter(x, data)
|
|
201
201
|
|
|
202
202
|
self.current_ax.set_xlabel(self.xlabel_edit.text())
|
|
@@ -208,17 +208,17 @@ class LAVA_Qt(QMainWindow):
|
|
|
208
208
|
self.current_ax = self.figure.add_subplot(111)
|
|
209
209
|
plot_type = self.plot_combo.currentText()
|
|
210
210
|
|
|
211
|
-
if
|
|
212
|
-
img = self.current_ax.imshow(data, extent=(x[0], x[-1], y[0], y[-1]), origin='lower', aspect='auto')
|
|
211
|
+
if 'Quantity' in plot_type:
|
|
212
|
+
img = self.current_ax.imshow(data.T, extent=(x[0], x[-1], y[0], y[-1]), origin='lower', aspect='auto')
|
|
213
213
|
self.figure.colorbar(img)
|
|
214
|
-
elif
|
|
214
|
+
elif 'Integral' in plot_type:
|
|
215
215
|
avg = integrate(transverse_average(data), x[-1]/len(x))
|
|
216
216
|
self.current_ax.plot(x, avg)
|
|
217
|
-
elif
|
|
217
|
+
elif 'Transverse' in plot_type:
|
|
218
218
|
avg = transverse_average(data)
|
|
219
219
|
self.current_ax.plot(x, avg)
|
|
220
|
-
elif
|
|
221
|
-
img = self.current_ax.imshow(np.abs(-data), extent=(x[0], x[-1], y[0], y[-1]), origin='lower', aspect='auto', norm=LogNorm())
|
|
220
|
+
elif 'Phase' in plot_type:
|
|
221
|
+
img = self.current_ax.imshow(np.abs(-data.T), extent=(x[0], x[-1], y[0], y[-1]), origin='lower', aspect='auto', norm=LogNorm())
|
|
222
222
|
self.figure.colorbar(img)
|
|
223
223
|
|
|
224
224
|
self.current_ax.set_xlabel(self.xlabel_edit.text())
|
|
@@ -233,9 +233,9 @@ class LAVA_Qt(QMainWindow):
|
|
|
233
233
|
|
|
234
234
|
# Determine items based on dimensions
|
|
235
235
|
if self.dims == 1:
|
|
236
|
-
items = [
|
|
236
|
+
items = ['Line Plot', 'Scatter Plot']
|
|
237
237
|
elif self.dims == 2:
|
|
238
|
-
items = [
|
|
238
|
+
items = ['Quantity Plot', 'T. Average Integral', 'Transverse Average', 'Phase Space']
|
|
239
239
|
else:
|
|
240
240
|
items = []
|
|
241
241
|
|
|
@@ -250,11 +250,11 @@ class LAVA_Qt(QMainWindow):
|
|
|
250
250
|
def save_plot(self):
|
|
251
251
|
file_dialog = QFileDialog()
|
|
252
252
|
filepath, _ = file_dialog.getSaveFileName(
|
|
253
|
-
self,
|
|
253
|
+
self, 'Save Plot', '', 'PNG Files (*.png);;PDF Files (*.pdf)'
|
|
254
254
|
)
|
|
255
255
|
|
|
256
256
|
if filepath:
|
|
257
|
-
self.figure.savefig(filepath, dpi=800, bbox_inches=
|
|
257
|
+
self.figure.savefig(filepath, dpi=800, bbox_inches='tight')
|
|
258
258
|
|
|
259
259
|
def LAVA():
|
|
260
260
|
app = QApplication(sys.argv)
|
|
@@ -262,5 +262,5 @@ def LAVA():
|
|
|
262
262
|
window.show()
|
|
263
263
|
sys.exit(app.exec())
|
|
264
264
|
|
|
265
|
-
if __name__ ==
|
|
265
|
+
if __name__ == '__main__':
|
|
266
266
|
LAVA()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .data import OsirisGridFile
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MeanFieldTheory(OsirisGridFile):
|
|
6
|
+
'''
|
|
7
|
+
Class to handle the mean field theory on data. Inherits from OsirisGridFile.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
source : str or OsirisGridFile
|
|
12
|
+
The filename or an OsirisGridFile object.
|
|
13
|
+
axis : int
|
|
14
|
+
The axis to average over.
|
|
15
|
+
'''
|
|
16
|
+
def __init__(self, source, axis=1):
|
|
17
|
+
if isinstance(source, OsirisGridFile):
|
|
18
|
+
self.__dict__.update(source.__dict__)
|
|
19
|
+
else:
|
|
20
|
+
super().__init__(source)
|
|
21
|
+
self._compute_mean_field(axis=axis)
|
|
22
|
+
|
|
23
|
+
def _compute_mean_field(self, axis=1):
|
|
24
|
+
self._average = np.expand_dims(np.mean(self.data, axis=axis), axis=axis)
|
|
25
|
+
self._fluctuations = self.data - self._average
|
|
26
|
+
|
|
27
|
+
def __array__(self):
|
|
28
|
+
return self.data
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def average(self):
|
|
32
|
+
return self._average
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def delta(self):
|
|
36
|
+
return self._fluctuations
|
|
37
|
+
|
|
38
|
+
def __str__(self):
|
|
39
|
+
return super().__str__() + f'\nAverage: {self.average.shape}\nDelta: {self.delta.shape}'
|
|
40
|
+
|
|
41
|
+
def derivative(self, field, axis=0):
|
|
42
|
+
'''
|
|
43
|
+
Compute the derivative of the average or the fluctuations.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
field : MeanFieldTheory.average or MeanFieldTheory.delta
|
|
48
|
+
The field to compute the derivative.
|
|
49
|
+
axis : int
|
|
50
|
+
The axis to compute the derivative.
|
|
51
|
+
'''
|
|
52
|
+
return np.gradient(field, self.dx[axis], axis=0)
|
osiris_utils/utils.py
CHANGED
|
@@ -7,7 +7,7 @@ import scipy
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
|
|
9
9
|
def courant2D(dx, dy):
|
|
10
|
-
|
|
10
|
+
'''
|
|
11
11
|
Compute the Courant number for a 2D simulation.
|
|
12
12
|
|
|
13
13
|
Parameters
|
|
@@ -21,12 +21,12 @@ def courant2D(dx, dy):
|
|
|
21
21
|
-------
|
|
22
22
|
float
|
|
23
23
|
The limit for dt.
|
|
24
|
-
|
|
24
|
+
'''
|
|
25
25
|
dt = 1 / (np.sqrt(1/dx**2 + 1/dy**2))
|
|
26
26
|
return dt
|
|
27
27
|
|
|
28
28
|
def time_estimation(n_cells, ppc, push_time, t_steps, n_cpu, hours = False):
|
|
29
|
-
|
|
29
|
+
'''
|
|
30
30
|
Estimate the simulation time.
|
|
31
31
|
|
|
32
32
|
Parameters
|
|
@@ -48,7 +48,7 @@ def time_estimation(n_cells, ppc, push_time, t_steps, n_cpu, hours = False):
|
|
|
48
48
|
-------
|
|
49
49
|
float
|
|
50
50
|
The estimated time in seconds or hours.
|
|
51
|
-
|
|
51
|
+
'''
|
|
52
52
|
time = (n_cells*ppc*push_time*t_steps)/n_cpu
|
|
53
53
|
if hours:
|
|
54
54
|
return time/3600
|
|
@@ -59,7 +59,7 @@ def filesize_estimation(n_gridpoints):
|
|
|
59
59
|
return n_gridpoints*4/(1024**2)
|
|
60
60
|
|
|
61
61
|
def transverse_average(data):
|
|
62
|
-
|
|
62
|
+
'''
|
|
63
63
|
Computes the transverse average of a 2D array.
|
|
64
64
|
|
|
65
65
|
Parameters
|
|
@@ -74,14 +74,14 @@ def transverse_average(data):
|
|
|
74
74
|
Dim: 1D.
|
|
75
75
|
The transverse average.
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
'''
|
|
78
78
|
|
|
79
79
|
if len(data.shape) != 2:
|
|
80
|
-
raise ValueError(
|
|
80
|
+
raise ValueError('The input data must be a 2D array.')
|
|
81
81
|
return np.mean(data, axis = 1)
|
|
82
82
|
|
|
83
83
|
def integrate(array, dx):
|
|
84
|
-
|
|
84
|
+
'''
|
|
85
85
|
Integrate a 1D from the left to the right. This may be changed in the future to allow
|
|
86
86
|
for integration in both directions or for other more general cases.
|
|
87
87
|
|
|
@@ -98,24 +98,24 @@ def integrate(array, dx):
|
|
|
98
98
|
numpy.ndarray
|
|
99
99
|
Dim: 1D.
|
|
100
100
|
The integrated array.
|
|
101
|
-
|
|
101
|
+
'''
|
|
102
102
|
|
|
103
103
|
if len(array.shape) != 1:
|
|
104
|
-
raise ValueError(f
|
|
104
|
+
raise ValueError(f'Array must be 1D\n Array shape: {array.shape}')
|
|
105
105
|
flip_array = np.flip(array)
|
|
106
106
|
# int = -scipy.integrate.cumulative_trapezoid(flip_array, dx = dx, initial = flip_array[0])
|
|
107
107
|
int = -scipy.integrate.cumulative_simpson(flip_array, dx = dx, initial = 0)
|
|
108
108
|
return np.flip(int)
|
|
109
109
|
|
|
110
110
|
def animate_2D(datafiles, frames, interval, fps, savename, **kwargs):
|
|
111
|
-
|
|
111
|
+
'''
|
|
112
112
|
Animate 2D OSIRIS files.
|
|
113
113
|
|
|
114
114
|
Parameters
|
|
115
115
|
----------
|
|
116
116
|
datafiles : str
|
|
117
117
|
The path to the files.
|
|
118
|
-
Must be of the type
|
|
118
|
+
Must be of the type 'path/to/file_%06d.h5'.
|
|
119
119
|
kwargs : dict
|
|
120
120
|
Additional keyword arguments for plotting.
|
|
121
121
|
|
|
@@ -123,7 +123,7 @@ def animate_2D(datafiles, frames, interval, fps, savename, **kwargs):
|
|
|
123
123
|
-------
|
|
124
124
|
matplotlib.animation.FuncAnimation
|
|
125
125
|
The animation.
|
|
126
|
-
|
|
126
|
+
'''
|
|
127
127
|
fig, ax = plt.subplots(figsize=(12, 6), tight_layout=True)
|
|
128
128
|
im = 0
|
|
129
129
|
|
|
@@ -133,8 +133,8 @@ def animate_2D(datafiles, frames, interval, fps, savename, **kwargs):
|
|
|
133
133
|
ax.clear()
|
|
134
134
|
# Display image data, make sure data shape is valid for imshow
|
|
135
135
|
im = ax.imshow(-data, extent=[x.min(), x.max(), y.min(), y.max()], aspect='auto', origin='lower', **kwargs)
|
|
136
|
-
plt.xlabel(r
|
|
137
|
-
plt.ylabel(r
|
|
136
|
+
plt.xlabel(r'x [c/$\omega_p$]')
|
|
137
|
+
plt.ylabel(r'y [c/$\omega_p$]')
|
|
138
138
|
|
|
139
139
|
|
|
140
140
|
# Creating the animation, and frames should be updated accordingly
|
|
@@ -146,7 +146,7 @@ def animate_2D(datafiles, frames, interval, fps, savename, **kwargs):
|
|
|
146
146
|
# Display the animation
|
|
147
147
|
return ani
|
|
148
148
|
|
|
149
|
-
def save_data(data, savename, option=
|
|
149
|
+
def save_data(data, savename, option='numpy'):
|
|
150
150
|
"""
|
|
151
151
|
Save the data to a .txt (with Numpy) or .csv (with Pandas) file.
|
|
152
152
|
|
|
@@ -157,17 +157,17 @@ def save_data(data, savename, option="numpy"):
|
|
|
157
157
|
savename : str
|
|
158
158
|
The path to the file.
|
|
159
159
|
option : str, optional
|
|
160
|
-
The option for saving the data. The default is
|
|
160
|
+
The option for saving the data. The default is 'numpy'. Can be 'numpy' or 'pandas'.
|
|
161
161
|
"""
|
|
162
|
-
if option ==
|
|
162
|
+
if option == 'numpy':
|
|
163
163
|
np.savetxt(savename, data)
|
|
164
|
-
elif option ==
|
|
164
|
+
elif option == 'pandas':
|
|
165
165
|
pd.DataFrame(data).to_csv(savename, index=False)
|
|
166
166
|
else:
|
|
167
167
|
raise ValueError("Option must be 'numpy' or 'pandas'.")
|
|
168
168
|
|
|
169
|
-
def read_data(filename, option=
|
|
170
|
-
|
|
169
|
+
def read_data(filename, option='numpy'):
|
|
170
|
+
'''
|
|
171
171
|
Read the data from a .txt file.
|
|
172
172
|
|
|
173
173
|
Parameters
|
|
@@ -180,54 +180,5 @@ def read_data(filename, option="numpy"):
|
|
|
180
180
|
numpy.ndarray
|
|
181
181
|
Dim: 2D.
|
|
182
182
|
The data.
|
|
183
|
-
|
|
184
|
-
return np.loadtxt(filename) if option ==
|
|
185
|
-
|
|
186
|
-
def mft_decomposition(filename, pressure = False, xy = False, data = False):
|
|
187
|
-
"""
|
|
188
|
-
Mean Field Theory decomposition of the data.
|
|
189
|
-
Considering that A = ⟨A⟩ + δA with ⟨δA⟩ = 0
|
|
190
|
-
This function returns ⟨A⟩ and δA from A.
|
|
191
|
-
|
|
192
|
-
Parameters
|
|
193
|
-
----------
|
|
194
|
-
filename : str
|
|
195
|
-
The path to the file.
|
|
196
|
-
The data is 2D.
|
|
197
|
-
pressure : bool, optional
|
|
198
|
-
If True, the file is a pressure file. The default is False.
|
|
199
|
-
xy : bool, optional
|
|
200
|
-
If True, the function returns x and y axes. The default is False.
|
|
201
|
-
data : bool, optional
|
|
202
|
-
If True, the function returns the data (2D with no transformation). The default is False.
|
|
203
|
-
|
|
204
|
-
Returns
|
|
205
|
-
-------
|
|
206
|
-
mean : numpy.ndarray
|
|
207
|
-
Dim: 1D.
|
|
208
|
-
The mean field ⟨A⟩.
|
|
209
|
-
fluctuation : numpy.ndarray
|
|
210
|
-
Dim: 2D.
|
|
211
|
-
The fluctuation δA.
|
|
212
|
-
x : numpy.ndarray
|
|
213
|
-
Dim: 1D.
|
|
214
|
-
The x axis.
|
|
215
|
-
y : numpy.ndarray
|
|
216
|
-
Dim: 1D.
|
|
217
|
-
The y axis.
|
|
218
|
-
data : numpy.ndarray
|
|
219
|
-
Dim: 2D.
|
|
220
|
-
The data.
|
|
221
|
-
"""
|
|
222
|
-
x, y, d, _ = open2D(filename, pressure)
|
|
223
|
-
mean = transverse_average(d)
|
|
224
|
-
fluctuation = d - mean
|
|
225
|
-
|
|
226
|
-
if xy and data:
|
|
227
|
-
return mean, fluctuation, x, y, d
|
|
228
|
-
elif xy:
|
|
229
|
-
return mean, fluctuation, x, y
|
|
230
|
-
elif data:
|
|
231
|
-
return mean, fluctuation, d
|
|
232
|
-
else:
|
|
233
|
-
return mean, fluctuation
|
|
183
|
+
'''
|
|
184
|
+
return np.loadtxt(filename) if option == 'numpy' else pd.read_csv(filename).values
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: osiris_utils
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.9
|
|
4
4
|
Summary: Utilities to manipulate and visualize OSIRIS framework output data
|
|
5
|
-
Author: ['João Pedro Ferreira Biu', 'João Cândido'
|
|
5
|
+
Author: ['João Pedro Ferreira Biu', 'João Cândido']
|
|
6
6
|
Author-email: ['joaopedrofbiu@tecnico.ulisboa.pt']
|
|
7
7
|
License: MIT
|
|
8
8
|
Project-URL: Issues Tracker, https://github.com/joaopedrobiu6/osiris_utils/issues
|
|
@@ -17,8 +17,9 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
21
|
Classifier: Topic :: Scientific/Engineering
|
|
21
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.11
|
|
22
23
|
Description-Content-Type: text/markdown
|
|
23
24
|
License-File: LICENSE.txt
|
|
24
25
|
Requires-Dist: numpy
|
|
@@ -27,6 +28,8 @@ Requires-Dist: pandas
|
|
|
27
28
|
Requires-Dist: scipy
|
|
28
29
|
Requires-Dist: h5py
|
|
29
30
|
Requires-Dist: pyside6
|
|
31
|
+
Requires-Dist: pyarrow
|
|
32
|
+
Requires-Dist: tqdm
|
|
30
33
|
Dynamic: author
|
|
31
34
|
Dynamic: author-email
|
|
32
35
|
Dynamic: classifier
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
osiris_utils/__init__.py,sha256=5qsicKjKzheUmdI89cIw7Egvjv6OZcGAQ5eEAiGZIcY,358
|
|
2
|
+
osiris_utils/data.py,sha256=vj5WV7SubCqR3Ia3ylFuwR5hyotk31d1xZLfYyjC5ow,16491
|
|
3
|
+
osiris_utils/data_readers.py,sha256=ZbzYQ-MK5NSv4Hfgtdc1YqXHs9yL_ZEKFFCiRn7GN5o,3264
|
|
4
|
+
osiris_utils/gui.py,sha256=YyG4MBmUFDJ141-NeorVRnRU3fcym4cfDMSGsJUFbYA,10531
|
|
5
|
+
osiris_utils/mean_field_theory.py,sha256=sJGvoJ5fTgW6fQZjNNsPswzXkh7dhMgO96qB8R5tl0M,1517
|
|
6
|
+
osiris_utils/utils.py,sha256=dlW2BvYpVSsW48kA_A6NNQ77VvKfyvs8hVwnOfJu2qk,4736
|
|
7
|
+
osiris_utils-1.0.9.dist-info/LICENSE.txt,sha256=Cawy2v7wKc7n8yL8guFu-cH9sQw9r1gll1pEFPFAB-Q,1084
|
|
8
|
+
osiris_utils-1.0.9.dist-info/METADATA,sha256=YqRPvxzvsA5TlTHP0p961tkaDrUbP7n9hqh-jJJ7_5E,1463
|
|
9
|
+
osiris_utils-1.0.9.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
10
|
+
osiris_utils-1.0.9.dist-info/top_level.txt,sha256=mM-_dX5fjzIKB7te655PhZOrPACVY-bJmiASCqW1eOA,13
|
|
11
|
+
osiris_utils-1.0.9.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
osiris_utils/__init__.py,sha256=mXRRxiNNJ6pxmRGO23OCNR3Yn3MvrBZFiNI9Rui_un8,318
|
|
2
|
-
osiris_utils/data.py,sha256=awf6v_zWsOTPjdbLQ_8E0SsaGGvp_UQZGUBh2UuCIyk,6678
|
|
3
|
-
osiris_utils/data_readers.py,sha256=ZeqbYHsFmdKRxqUNkkauurjz-1VABW4GZ-WaPQREdfc,3264
|
|
4
|
-
osiris_utils/gui.py,sha256=DPRqqFIYfgNZEMnt4rEo5TiHe5fogkWySrOllQvj0ak,10533
|
|
5
|
-
osiris_utils/utils.py,sha256=SrHsMaOq1zw9Qx9YJV7jJvS1u6hr5RLV7IvikygM4pg,6097
|
|
6
|
-
osiris_utils-1.0.5.dist-info/LICENSE.txt,sha256=Cawy2v7wKc7n8yL8guFu-cH9sQw9r1gll1pEFPFAB-Q,1084
|
|
7
|
-
osiris_utils-1.0.5.dist-info/METADATA,sha256=6kPFf27IPybWbwPEyf36VxPgRyD6-Du81tfVj04DeSM,1396
|
|
8
|
-
osiris_utils-1.0.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
9
|
-
osiris_utils-1.0.5.dist-info/top_level.txt,sha256=mM-_dX5fjzIKB7te655PhZOrPACVY-bJmiASCqW1eOA,13
|
|
10
|
-
osiris_utils-1.0.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|