flekspy 0.2.3__tar.gz → 0.2.4__tar.gz
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.
- {flekspy-0.2.3 → flekspy-0.2.4}/PKG-INFO +2 -2
- {flekspy-0.2.3 → flekspy-0.2.4}/pyproject.toml +1 -1
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/tp/test_particles.py +466 -428
- {flekspy-0.2.3 → flekspy-0.2.4}/LICENSE.md +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/README.md +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/__init__.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/flekspy.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/idl/__init__.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/idl/idl.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/plot/__init__.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/plot/streamplot.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/tp/__init__.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/util/__init__.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/util/data_container.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/util/utilities.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/yt/__init__.py +0 -0
- {flekspy-0.2.3 → flekspy-0.2.4}/src/flekspy/yt/yt.py +0 -0
|
@@ -1,428 +1,466 @@
|
|
|
1
|
-
from typing import List, Tuple, Dict, Union, Callable
|
|
2
|
-
|
|
3
|
-
import matplotlib.pyplot as plt
|
|
4
|
-
import os
|
|
5
|
-
import numpy as np
|
|
6
|
-
import glob
|
|
7
|
-
import struct
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class FLEKSTP(object):
|
|
11
|
-
r"""
|
|
12
|
-
A class that is used to read and plot test particles. Each particle ID consists of
|
|
13
|
-
a CPU index, a particle index on each CPU, and a location index.
|
|
14
|
-
By default, 7 real numbers saved for each step: time + position + velocity.
|
|
15
|
-
|
|
16
|
-
Args:
|
|
17
|
-
dirs (str): the path to the test particle dataset.
|
|
18
|
-
|
|
19
|
-
Examples:
|
|
20
|
-
>>> tp = FLEKSTP("res/run1/PC/test_particles", iSpecies=1)
|
|
21
|
-
>>> pIDs = list(tp.IDs())
|
|
22
|
-
>>> tp.plot_trajectory(pIDs[3])
|
|
23
|
-
>>> tp.save_trajectory_to_csv(pIDs[5])
|
|
24
|
-
>>> ids, pData = tp.read_particles_at_time(6500.8, doSave=True)
|
|
25
|
-
>>> f = tp.plot_loc(pData)
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
it_ = 0
|
|
29
|
-
ix_ = 1
|
|
30
|
-
iy_ = 2
|
|
31
|
-
iz_ = 3
|
|
32
|
-
iu_ = 4
|
|
33
|
-
iv_ = 5
|
|
34
|
-
iw_ = 6
|
|
35
|
-
iBx_ = 7
|
|
36
|
-
iBy_ = 8
|
|
37
|
-
iBz_ = 9
|
|
38
|
-
iEx_ = 10
|
|
39
|
-
iEy_ = 11
|
|
40
|
-
iEz_ = 12
|
|
41
|
-
|
|
42
|
-
def __init__(
|
|
43
|
-
self,
|
|
44
|
-
dirs: Union[str, List[str]],
|
|
45
|
-
iDomain: int = 0,
|
|
46
|
-
iSpecies: int = 0,
|
|
47
|
-
iListStart: int = 0,
|
|
48
|
-
iListEnd: int = -1,
|
|
49
|
-
readAllFiles: bool = False,
|
|
50
|
-
):
|
|
51
|
-
if type(dirs) == str:
|
|
52
|
-
dirs = [dirs]
|
|
53
|
-
|
|
54
|
-
header = dirs[0] + "/Header"
|
|
55
|
-
if os.path.exists(header):
|
|
56
|
-
with open(header, "r") as f:
|
|
57
|
-
self.nReal = int(f.readline())
|
|
58
|
-
else:
|
|
59
|
-
raise FileNotFoundError(f"Header file not found in {dirs[0]}")
|
|
60
|
-
|
|
61
|
-
self.iSpecies = iSpecies
|
|
62
|
-
self.plistfiles = list()
|
|
63
|
-
self.pfiles = list()
|
|
64
|
-
|
|
65
|
-
for outputDir in dirs:
|
|
66
|
-
self.plistfiles = self.plistfiles + glob.glob(
|
|
67
|
-
f"{outputDir}/FLEKS{iDomain}_particle_list_species_{iSpecies}_*"
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
self.pfiles = self.pfiles + glob.glob(
|
|
71
|
-
f"{outputDir}/FLEKS{iDomain}_particle_species_{iSpecies}_*"
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
self.plistfiles.sort()
|
|
75
|
-
self.pfiles.sort()
|
|
76
|
-
|
|
77
|
-
self.
|
|
78
|
-
if readAllFiles:
|
|
79
|
-
for filename in self.pfiles:
|
|
80
|
-
record = self._read_the_first_record(filename)
|
|
81
|
-
if record == None:
|
|
82
|
-
continue
|
|
83
|
-
self.
|
|
84
|
-
|
|
85
|
-
if iListEnd == -1:
|
|
86
|
-
iListEnd = len(self.plistfiles)
|
|
87
|
-
self.plistfiles = self.plistfiles[iListStart:iListEnd]
|
|
88
|
-
self.pfiles = self.pfiles[iListStart:iListEnd]
|
|
89
|
-
|
|
90
|
-
self.plists: List[Dict[Tuple[int, int], int]] = []
|
|
91
|
-
for fileName in self.plistfiles:
|
|
92
|
-
self.plists.append(self.read_particle_list(fileName))
|
|
93
|
-
|
|
94
|
-
self.IDs = set()
|
|
95
|
-
for plist in self.plists:
|
|
96
|
-
self.IDs.update(plist.keys())
|
|
97
|
-
|
|
98
|
-
self.
|
|
99
|
-
for filename in self.pfiles:
|
|
100
|
-
record = self._read_the_first_record(filename)
|
|
101
|
-
if record == None:
|
|
102
|
-
continue
|
|
103
|
-
self.
|
|
104
|
-
|
|
105
|
-
print(f"Particles of species {self.iSpecies} are read from {dirs}")
|
|
106
|
-
print(f"Number of particles: {len(self.IDs)}")
|
|
107
|
-
|
|
108
|
-
def getIDs(self):
|
|
109
|
-
return list(sorted(self.IDs))
|
|
110
|
-
|
|
111
|
-
def get_index_to_time(self) -> List:
|
|
112
|
-
|
|
113
|
-
Getter method for accessing
|
|
114
|
-
"""
|
|
115
|
-
if len(self.
|
|
116
|
-
print("Index to time mapping was not initialized")
|
|
117
|
-
return self.
|
|
118
|
-
|
|
119
|
-
def read_particle_list(self, fileName: str) -> Dict[Tuple[int, int], int]:
|
|
120
|
-
|
|
121
|
-
Read and return a list of the particle IDs.
|
|
122
|
-
"""
|
|
123
|
-
# 2 integers + 1 unsigned long long
|
|
124
|
-
listUnitSize = 2 * 4 + 8
|
|
125
|
-
nByte = os.path.getsize(fileName)
|
|
126
|
-
nPart = int(nByte / listUnitSize)
|
|
127
|
-
plist = {}
|
|
128
|
-
with open(fileName, "rb") as f:
|
|
129
|
-
for _ in range(nPart):
|
|
130
|
-
binaryData = f.read(listUnitSize)
|
|
131
|
-
(cpu, id, loc) = struct.unpack("iiQ", binaryData)
|
|
132
|
-
plist.update({(cpu, id): loc})
|
|
133
|
-
return plist
|
|
134
|
-
|
|
135
|
-
def _read_the_first_record(self, fileName: str) -> Union[List[float], None]:
|
|
136
|
-
|
|
137
|
-
Get the first record stored in one file.
|
|
138
|
-
"""
|
|
139
|
-
dataList = list()
|
|
140
|
-
with open(fileName, "rb") as f:
|
|
141
|
-
while True:
|
|
142
|
-
binaryData = f.read(4 * 4)
|
|
143
|
-
|
|
144
|
-
if not binaryData:
|
|
145
|
-
break # EOF
|
|
146
|
-
|
|
147
|
-
(cpu, idtmp, nRecord, weight) = struct.unpack("iiif", binaryData)
|
|
148
|
-
if nRecord > 0:
|
|
149
|
-
binaryData = f.read(4 * self.nReal)
|
|
150
|
-
dataList = dataList + list(
|
|
151
|
-
struct.unpack("f" * self.nReal, binaryData)
|
|
152
|
-
)
|
|
153
|
-
return dataList
|
|
154
|
-
|
|
155
|
-
def read_particles_at_time(
|
|
156
|
-
self, time: float, doSave: bool = False
|
|
157
|
-
) -> Tuple[np.ndarray, np.ndarray]:
|
|
158
|
-
r"""
|
|
159
|
-
Get the information of all the particles at a given time, and save to a csv file
|
|
160
|
-
with the name "particles_t***.csv" in the current directory if doSave is True.
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
ids: a numpy array of tuples contains the particle IDs.
|
|
164
|
-
pData: a numpy real array with the particle weight, location and velocity.
|
|
165
|
-
|
|
166
|
-
Examples:
|
|
167
|
-
>>> ids, pData = pt.read_particles_at_time(3700, doSave=True)
|
|
168
|
-
"""
|
|
169
|
-
|
|
170
|
-
nFile = len(self.pfiles)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if
|
|
176
|
-
break
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
f.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
f.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
>>>
|
|
309
|
-
>>>
|
|
310
|
-
>>>
|
|
311
|
-
>>>
|
|
312
|
-
>>>
|
|
313
|
-
>>>
|
|
314
|
-
>>>
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
def plot_data(dd, label, irow, icol):
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
t, dd, c=plt.cm.winter(tNorm), edgecolor="none", marker="o", s=10
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
def plot_vector(idx, labels, irow):
|
|
373
|
-
for i, (id, label) in enumerate(zip(idx, labels)):
|
|
374
|
-
plot_data(data[:, id], label, irow, i)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
1
|
+
from typing import List, Tuple, Dict, Union, Callable
|
|
2
|
+
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import os
|
|
5
|
+
import numpy as np
|
|
6
|
+
import glob
|
|
7
|
+
import struct
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FLEKSTP(object):
|
|
11
|
+
r"""
|
|
12
|
+
A class that is used to read and plot test particles. Each particle ID consists of
|
|
13
|
+
a CPU index, a particle index on each CPU, and a location index.
|
|
14
|
+
By default, 7 real numbers saved for each step: time + position + velocity.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
dirs (str): the path to the test particle dataset.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
>>> tp = FLEKSTP("res/run1/PC/test_particles", iSpecies=1)
|
|
21
|
+
>>> pIDs = list(tp.IDs())
|
|
22
|
+
>>> tp.plot_trajectory(pIDs[3])
|
|
23
|
+
>>> tp.save_trajectory_to_csv(pIDs[5])
|
|
24
|
+
>>> ids, pData = tp.read_particles_at_time(6500.8, doSave=True)
|
|
25
|
+
>>> f = tp.plot_loc(pData)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
it_ = 0
|
|
29
|
+
ix_ = 1
|
|
30
|
+
iy_ = 2
|
|
31
|
+
iz_ = 3
|
|
32
|
+
iu_ = 4
|
|
33
|
+
iv_ = 5
|
|
34
|
+
iw_ = 6
|
|
35
|
+
iBx_ = 7
|
|
36
|
+
iBy_ = 8
|
|
37
|
+
iBz_ = 9
|
|
38
|
+
iEx_ = 10
|
|
39
|
+
iEy_ = 11
|
|
40
|
+
iEz_ = 12
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
dirs: Union[str, List[str]],
|
|
45
|
+
iDomain: int = 0,
|
|
46
|
+
iSpecies: int = 0,
|
|
47
|
+
iListStart: int = 0,
|
|
48
|
+
iListEnd: int = -1,
|
|
49
|
+
readAllFiles: bool = False,
|
|
50
|
+
):
|
|
51
|
+
if type(dirs) == str:
|
|
52
|
+
dirs = [dirs]
|
|
53
|
+
|
|
54
|
+
header = dirs[0] + "/Header"
|
|
55
|
+
if os.path.exists(header):
|
|
56
|
+
with open(header, "r") as f:
|
|
57
|
+
self.nReal = int(f.readline())
|
|
58
|
+
else:
|
|
59
|
+
raise FileNotFoundError(f"Header file not found in {dirs[0]}")
|
|
60
|
+
|
|
61
|
+
self.iSpecies = iSpecies
|
|
62
|
+
self.plistfiles = list()
|
|
63
|
+
self.pfiles = list()
|
|
64
|
+
|
|
65
|
+
for outputDir in dirs:
|
|
66
|
+
self.plistfiles = self.plistfiles + glob.glob(
|
|
67
|
+
f"{outputDir}/FLEKS{iDomain}_particle_list_species_{iSpecies}_*"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self.pfiles = self.pfiles + glob.glob(
|
|
71
|
+
f"{outputDir}/FLEKS{iDomain}_particle_species_{iSpecies}_*"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
self.plistfiles.sort()
|
|
75
|
+
self.pfiles.sort()
|
|
76
|
+
|
|
77
|
+
self.indextotime = []
|
|
78
|
+
if readAllFiles:
|
|
79
|
+
for filename in self.pfiles:
|
|
80
|
+
record = self._read_the_first_record(filename)
|
|
81
|
+
if record == None:
|
|
82
|
+
continue
|
|
83
|
+
self.indextotime.append(record[FLEKSTP.it_])
|
|
84
|
+
|
|
85
|
+
if iListEnd == -1:
|
|
86
|
+
iListEnd = len(self.plistfiles)
|
|
87
|
+
self.plistfiles = self.plistfiles[iListStart:iListEnd]
|
|
88
|
+
self.pfiles = self.pfiles[iListStart:iListEnd]
|
|
89
|
+
|
|
90
|
+
self.plists: List[Dict[Tuple[int, int], int]] = []
|
|
91
|
+
for fileName in self.plistfiles:
|
|
92
|
+
self.plists.append(self.read_particle_list(fileName))
|
|
93
|
+
|
|
94
|
+
self.IDs = set()
|
|
95
|
+
for plist in self.plists:
|
|
96
|
+
self.IDs.update(plist.keys())
|
|
97
|
+
|
|
98
|
+
self.filetime = []
|
|
99
|
+
for filename in self.pfiles:
|
|
100
|
+
record = self._read_the_first_record(filename)
|
|
101
|
+
if record == None:
|
|
102
|
+
continue
|
|
103
|
+
self.filetime.append(record[FLEKSTP.it_])
|
|
104
|
+
|
|
105
|
+
print(f"Particles of species {self.iSpecies} are read from {dirs}")
|
|
106
|
+
print(f"Number of particles: {len(self.IDs)}")
|
|
107
|
+
|
|
108
|
+
def getIDs(self):
|
|
109
|
+
return list(sorted(self.IDs))
|
|
110
|
+
|
|
111
|
+
def get_index_to_time(self) -> List:
|
|
112
|
+
"""
|
|
113
|
+
Getter method for accessing indextotime.
|
|
114
|
+
"""
|
|
115
|
+
if len(self.indextotime) == 0:
|
|
116
|
+
print("Index to time mapping was not initialized")
|
|
117
|
+
return self.indextotime
|
|
118
|
+
|
|
119
|
+
def read_particle_list(self, fileName: str) -> Dict[Tuple[int, int], int]:
|
|
120
|
+
"""
|
|
121
|
+
Read and return a list of the particle IDs.
|
|
122
|
+
"""
|
|
123
|
+
# 2 integers + 1 unsigned long long
|
|
124
|
+
listUnitSize = 2 * 4 + 8
|
|
125
|
+
nByte = os.path.getsize(fileName)
|
|
126
|
+
nPart = int(nByte / listUnitSize)
|
|
127
|
+
plist = {}
|
|
128
|
+
with open(fileName, "rb") as f:
|
|
129
|
+
for _ in range(nPart):
|
|
130
|
+
binaryData = f.read(listUnitSize)
|
|
131
|
+
(cpu, id, loc) = struct.unpack("iiQ", binaryData)
|
|
132
|
+
plist.update({(cpu, id): loc})
|
|
133
|
+
return plist
|
|
134
|
+
|
|
135
|
+
def _read_the_first_record(self, fileName: str) -> Union[List[float], None]:
|
|
136
|
+
"""
|
|
137
|
+
Get the first record stored in one file.
|
|
138
|
+
"""
|
|
139
|
+
dataList = list()
|
|
140
|
+
with open(fileName, "rb") as f:
|
|
141
|
+
while True:
|
|
142
|
+
binaryData = f.read(4 * 4)
|
|
143
|
+
|
|
144
|
+
if not binaryData:
|
|
145
|
+
break # EOF
|
|
146
|
+
|
|
147
|
+
(cpu, idtmp, nRecord, weight) = struct.unpack("iiif", binaryData)
|
|
148
|
+
if nRecord > 0:
|
|
149
|
+
binaryData = f.read(4 * self.nReal)
|
|
150
|
+
dataList = dataList + list(
|
|
151
|
+
struct.unpack("f" * self.nReal, binaryData)
|
|
152
|
+
)
|
|
153
|
+
return dataList
|
|
154
|
+
|
|
155
|
+
def read_particles_at_time(
|
|
156
|
+
self, time: float, doSave: bool = False
|
|
157
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
158
|
+
r"""
|
|
159
|
+
Get the information of all the particles at a given time, and save to a csv file
|
|
160
|
+
with the name "particles_t***.csv" in the current directory if doSave is True.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
ids: a numpy array of tuples contains the particle IDs.
|
|
164
|
+
pData: a numpy real array with the particle weight, location and velocity.
|
|
165
|
+
|
|
166
|
+
Examples:
|
|
167
|
+
>>> ids, pData = pt.read_particles_at_time(3700, doSave=True)
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
nFile = len(self.pfiles)
|
|
171
|
+
if time < self.filetime[0] or time > self.filetime[-1]:
|
|
172
|
+
raise Exception(f"There are no particles at time {time}.")
|
|
173
|
+
iFile = 0
|
|
174
|
+
while iFile < nFile - 1:
|
|
175
|
+
if time < self.filetime[iFile + 1]:
|
|
176
|
+
break
|
|
177
|
+
iFile += 1
|
|
178
|
+
|
|
179
|
+
fileName = self.pfiles[iFile]
|
|
180
|
+
|
|
181
|
+
dataList: list[float] = []
|
|
182
|
+
idList: list[tuple] = []
|
|
183
|
+
with open(fileName, "rb") as f:
|
|
184
|
+
while True:
|
|
185
|
+
binaryData = f.read(4 * 4)
|
|
186
|
+
if not binaryData:
|
|
187
|
+
break # EOF
|
|
188
|
+
|
|
189
|
+
(cpu, idtmp, nRecord, weight) = struct.unpack("iiif", binaryData)
|
|
190
|
+
binaryData = f.read(4 * self.nReal * nRecord)
|
|
191
|
+
allRecords = list(struct.unpack("f" * nRecord * self.nReal, binaryData))
|
|
192
|
+
for i in range(nRecord):
|
|
193
|
+
if (
|
|
194
|
+
allRecords[self.nReal * i + FLEKSTP.it_] >= time
|
|
195
|
+
or i == nRecord - 1
|
|
196
|
+
):
|
|
197
|
+
dataList.append(
|
|
198
|
+
allRecords[self.nReal * i : self.nReal * (i + 1)]
|
|
199
|
+
)
|
|
200
|
+
idList.append((cpu, idtmp))
|
|
201
|
+
break
|
|
202
|
+
|
|
203
|
+
npData = np.array(dataList)
|
|
204
|
+
idData = np.array(idList, dtype="i,i")
|
|
205
|
+
if doSave:
|
|
206
|
+
fileName = f"particles_t{time}.csv"
|
|
207
|
+
header = "cpu,iid,time,x,y,z,ux,uy,uz"
|
|
208
|
+
if self.nReal == 10:
|
|
209
|
+
header += ",bx,by,bz"
|
|
210
|
+
elif self.nReal == 13:
|
|
211
|
+
header += ",bx,by,bz,ex,ey,ez"
|
|
212
|
+
|
|
213
|
+
with open(fileName, "w") as f:
|
|
214
|
+
f.write(header + "\n")
|
|
215
|
+
for id_row, data_row in zip(idData, npData):
|
|
216
|
+
f.write(
|
|
217
|
+
f"{id_row[0]},{id_row[1]},{','.join(str(x) for x in data_row)}\n"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return idData, npData
|
|
221
|
+
|
|
222
|
+
def save_trajectory_to_csv(
|
|
223
|
+
self,
|
|
224
|
+
pID: Tuple[int, int],
|
|
225
|
+
fileName: str = None,
|
|
226
|
+
shiftTime: bool = False,
|
|
227
|
+
scaleTime: bool = False,
|
|
228
|
+
) -> None:
|
|
229
|
+
r"""
|
|
230
|
+
Save the trajectory of a particle to a csv file.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
pID: particle ID.
|
|
234
|
+
shiftTime (bool): If set to True, set the initial time to be 0.
|
|
235
|
+
scaleTime (bool): If set to True, scale the time into [0,1] range, only scale time if shiftTime = True.
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
>>> tp.save_trajectory_to_csv((3,15))
|
|
239
|
+
"""
|
|
240
|
+
pData = self.read_particle_trajectory(pID)
|
|
241
|
+
if fileName == None:
|
|
242
|
+
fileName = "trajectory_" + str(pID[0]) + "_" + str(pID[1]) + ".csv"
|
|
243
|
+
header = "time [s], X [R], Y [R], Z [R], U_x [km/s], U_y [km/s], U_z [km/s]"
|
|
244
|
+
if self.nReal == 10:
|
|
245
|
+
header += ", B_x [nT], B_y [nT], B_z [nT]"
|
|
246
|
+
if self.nReal == 13:
|
|
247
|
+
header += (
|
|
248
|
+
", B_x [nT], B_y [nT], B_z [nT], E_x [uV/m], E_y [uV/m], E_z [uV/m]"
|
|
249
|
+
)
|
|
250
|
+
if shiftTime:
|
|
251
|
+
pData[:, 0] -= pData[0, 0]
|
|
252
|
+
if scaleTime:
|
|
253
|
+
pData[:, 0] /= pData[-1, 0]
|
|
254
|
+
np.savetxt(fileName, pData, delimiter=",", header=header, comments="")
|
|
255
|
+
|
|
256
|
+
def read_particle_trajectory(self, pID: Tuple[int, int]):
|
|
257
|
+
r"""
|
|
258
|
+
Read and return the trajectory of a particle.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
pID: particle ID
|
|
262
|
+
|
|
263
|
+
Examples:
|
|
264
|
+
>>> trajectory = tp.read_particle_trajectory((66,888))
|
|
265
|
+
"""
|
|
266
|
+
dataList = list()
|
|
267
|
+
for fileName, plist in zip(self.pfiles, self.plists):
|
|
268
|
+
if pID in plist:
|
|
269
|
+
ploc = plist[pID]
|
|
270
|
+
with open(fileName, "rb") as f:
|
|
271
|
+
f.seek(ploc)
|
|
272
|
+
binaryData = f.read(4 * 4)
|
|
273
|
+
(cpu, idtmp, nRecord, weight) = struct.unpack("iiif", binaryData)
|
|
274
|
+
binaryData = f.read(4 * self.nReal * nRecord)
|
|
275
|
+
dataList = dataList + list(
|
|
276
|
+
struct.unpack("f" * nRecord * self.nReal, binaryData)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
nRecord = int(len(dataList) / self.nReal)
|
|
280
|
+
return np.array(dataList).reshape(nRecord, self.nReal)
|
|
281
|
+
|
|
282
|
+
def read_initial_location(self, pID):
|
|
283
|
+
r"""
|
|
284
|
+
Read and return the initial location of a test particle
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
for fileName, plist in zip(self.pfiles, self.plists):
|
|
288
|
+
if pID in plist:
|
|
289
|
+
ploc = plist[pID]
|
|
290
|
+
with open(fileName, "rb") as f:
|
|
291
|
+
f.seek(ploc)
|
|
292
|
+
binaryData = f.read(4 * 4)
|
|
293
|
+
(cpu, idtmp, nRecord, weight) = struct.unpack("iiif", binaryData)
|
|
294
|
+
nRead = 1
|
|
295
|
+
binaryData = f.read(4 * self.nReal * nRead)
|
|
296
|
+
dataList = list(struct.unpack("f" * nRead * self.nReal, binaryData))
|
|
297
|
+
return dataList
|
|
298
|
+
|
|
299
|
+
def select_particles(self, fSelect: Callable = None) -> List[Tuple[int, int]]:
|
|
300
|
+
r"""
|
|
301
|
+
Select and return the particles whose initial condition satisfy the requirement
|
|
302
|
+
set by the user defined function fSelect. The first argument of fSelect is the
|
|
303
|
+
particle ID, and the second argument is the initial record (time, location,
|
|
304
|
+
velocity and weight of a particle) of a particle.
|
|
305
|
+
|
|
306
|
+
Examples:
|
|
307
|
+
>>> def fselect(tp, pid):
|
|
308
|
+
>>> pdata = tp.read_initial_loc_with_ID(pid)
|
|
309
|
+
>>> intime = pdata[FLEKSTP.it_] < 3601
|
|
310
|
+
>>> inregion = pdata[FLEKSTP.ix_] > 20
|
|
311
|
+
>>> return intime and inregion
|
|
312
|
+
>>>
|
|
313
|
+
>>> pselected = tp.select_particles(fselect)
|
|
314
|
+
>>> tp.plot_trajectory(list(pselected.keys())[1])
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
if fSelect == None:
|
|
318
|
+
|
|
319
|
+
def fSelect(tp, pid):
|
|
320
|
+
return True
|
|
321
|
+
|
|
322
|
+
pselected = list(filter(lambda pid: fSelect(self, pid), self.IDs))
|
|
323
|
+
|
|
324
|
+
return pselected
|
|
325
|
+
|
|
326
|
+
def get_data(self, data, name: str):
|
|
327
|
+
match name:
|
|
328
|
+
case "t":
|
|
329
|
+
x = data[:, FLEKSTP.it_]
|
|
330
|
+
case "x":
|
|
331
|
+
x = data[:, FLEKSTP.ix_]
|
|
332
|
+
case "y":
|
|
333
|
+
x = data[:, FLEKSTP.iy_]
|
|
334
|
+
case "z":
|
|
335
|
+
x = data[:, FLEKSTP.iz_]
|
|
336
|
+
case "u" | "vx" | "ux":
|
|
337
|
+
x = data[:, FLEKSTP.iu_]
|
|
338
|
+
case "v" | "vy" | "uy":
|
|
339
|
+
x = data[:, FLEKSTP.iv_]
|
|
340
|
+
case "w" | "vz" | "uz":
|
|
341
|
+
x = data[:, FLEKSTP.iw_]
|
|
342
|
+
case _:
|
|
343
|
+
raise Exception(f"Unknown plot variable {name}")
|
|
344
|
+
|
|
345
|
+
return x
|
|
346
|
+
|
|
347
|
+
def plot_trajectory(
|
|
348
|
+
self,
|
|
349
|
+
pID: Tuple[int, int],
|
|
350
|
+
*,
|
|
351
|
+
type="all",
|
|
352
|
+
xaxis="t",
|
|
353
|
+
yaxis="x",
|
|
354
|
+
ax=None,
|
|
355
|
+
**kwargs,
|
|
356
|
+
):
|
|
357
|
+
r"""
|
|
358
|
+
Plots the trajectory and velocities of the particle pID.
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
>>> tp.plot_trajectory((3,15))
|
|
362
|
+
"""
|
|
363
|
+
|
|
364
|
+
def plot_data(dd, label, irow, icol):
|
|
365
|
+
ax[irow, icol].plot(t, dd, label=label)
|
|
366
|
+
ax[irow, icol].scatter(
|
|
367
|
+
t, dd, c=plt.cm.winter(tNorm), edgecolor="none", marker="o", s=10
|
|
368
|
+
)
|
|
369
|
+
ax[irow, icol].set_xlabel("time")
|
|
370
|
+
ax[irow, icol].set_ylabel(label)
|
|
371
|
+
|
|
372
|
+
def plot_vector(idx, labels, irow):
|
|
373
|
+
for i, (id, label) in enumerate(zip(idx, labels)):
|
|
374
|
+
plot_data(data[:, id], label, irow, i, **kwargs)
|
|
375
|
+
|
|
376
|
+
data = self.read_particle_trajectory(pID)
|
|
377
|
+
t = data[:, FLEKSTP.it_]
|
|
378
|
+
tNorm = (t - t[0]) / (t[-1] - t[0])
|
|
379
|
+
|
|
380
|
+
if type == "single":
|
|
381
|
+
x = self.get_data(data, xaxis)
|
|
382
|
+
y = self.get_data(data, yaxis)
|
|
383
|
+
|
|
384
|
+
if ax == None:
|
|
385
|
+
f, ax = plt.subplots(1, 1, figsize=(10, 6), constrained_layout=True)
|
|
386
|
+
|
|
387
|
+
ax.plot(x, y, **kwargs)
|
|
388
|
+
elif type == "all":
|
|
389
|
+
ncol = 3
|
|
390
|
+
nrow = 3 # Default for X, V
|
|
391
|
+
if self.nReal == 10: # additional B field
|
|
392
|
+
nrow = 4
|
|
393
|
+
elif self.nReal == 13: # additional B and E field
|
|
394
|
+
nrow = 5
|
|
395
|
+
|
|
396
|
+
f, ax = plt.subplots(nrow, ncol, figsize=(12, 6), constrained_layout=True)
|
|
397
|
+
|
|
398
|
+
# Plot trajectories
|
|
399
|
+
for i, a in enumerate(ax[0, :]):
|
|
400
|
+
x_id = FLEKSTP.ix_ if i < 2 else FLEKSTP.iy_
|
|
401
|
+
y_id = FLEKSTP.iy_ if i == 0 else FLEKSTP.iz_
|
|
402
|
+
a.plot(data[:, x_id], data[:, y_id], "k")
|
|
403
|
+
a.scatter(
|
|
404
|
+
data[:, x_id],
|
|
405
|
+
data[:, y_id],
|
|
406
|
+
c=plt.cm.winter(tNorm),
|
|
407
|
+
edgecolor="none",
|
|
408
|
+
marker="o",
|
|
409
|
+
s=10,
|
|
410
|
+
)
|
|
411
|
+
a.set_xlabel("x" if i < 2 else "y")
|
|
412
|
+
a.set_ylabel("y" if i == 0 else "z")
|
|
413
|
+
|
|
414
|
+
plot_vector([FLEKSTP.ix_, FLEKSTP.iy_, FLEKSTP.iz_], ["x", "y", "z"], 1)
|
|
415
|
+
plot_vector([FLEKSTP.iu_, FLEKSTP.iv_, FLEKSTP.iw_], ["Vx", "Vy", "Vz"], 2)
|
|
416
|
+
|
|
417
|
+
if self.nReal > FLEKSTP.iBx_:
|
|
418
|
+
plot_vector(
|
|
419
|
+
[FLEKSTP.iBx_, FLEKSTP.iBy_, FLEKSTP.iBz_], ["Bx", "By", "Bz"], 3
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
if self.nReal > FLEKSTP.iEx_:
|
|
423
|
+
plot_vector(
|
|
424
|
+
[FLEKSTP.iEx_, FLEKSTP.iEy_, FLEKSTP.iEz_], ["Ex", "Ey", "Ez"], 4
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
return ax
|
|
428
|
+
|
|
429
|
+
def plot_loc(self, pData: np.ndarray):
|
|
430
|
+
r"""
|
|
431
|
+
Plot the location of particles pData.
|
|
432
|
+
|
|
433
|
+
Examples:
|
|
434
|
+
>>> ids, pData = tp.read_particles_at_time(3700, doSave=True)
|
|
435
|
+
>>> f = tp.plot_loc(pData)
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
px = pData[:, FLEKSTP.ix_]
|
|
439
|
+
py = pData[:, FLEKSTP.iy_]
|
|
440
|
+
pz = pData[:, FLEKSTP.iz_]
|
|
441
|
+
|
|
442
|
+
# create subplot mosaic with different keyword arguments
|
|
443
|
+
skeys = ["A", "B", "C", "D"]
|
|
444
|
+
f, ax = plt.subplot_mosaic(
|
|
445
|
+
"AB;CD",
|
|
446
|
+
per_subplot_kw={("D"): {"projection": "3d"}},
|
|
447
|
+
gridspec_kw={"width_ratios": [1, 1], "wspace": 0.1, "hspace": 0.1},
|
|
448
|
+
figsize=(10, 10),
|
|
449
|
+
constrained_layout=True,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Create 2D scatter plots
|
|
453
|
+
for i, (x, y, labels) in enumerate(
|
|
454
|
+
zip([px, px, py], [py, pz, pz], [("x", "y"), ("x", "z"), ("y", "z")])
|
|
455
|
+
):
|
|
456
|
+
ax[skeys[i]].scatter(x, y, s=1)
|
|
457
|
+
ax[skeys[i]].set_xlabel(labels[0])
|
|
458
|
+
ax[skeys[i]].set_ylabel(labels[1])
|
|
459
|
+
|
|
460
|
+
# Create 3D scatter plot
|
|
461
|
+
ax[skeys[3]].scatter(px, py, pz, s=1)
|
|
462
|
+
ax[skeys[3]].set_xlabel("x")
|
|
463
|
+
ax[skeys[3]].set_ylabel("y")
|
|
464
|
+
ax[skeys[3]].set_zlabel("z")
|
|
465
|
+
|
|
466
|
+
return ax
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|