zpic 0.1.0__cp310-cp310-manylinux_2_35_x86_64.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.
zpic/zdf.py ADDED
@@ -0,0 +1,779 @@
1
+ """
2
+ Python module for reading ZDF data files
3
+
4
+ Copyright (C) 2017 Instituto Superior Tecnico
5
+
6
+ This file is part of the ZPIC Educational code suite
7
+
8
+ The ZPIC Educational code suite is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU Affero General Public License as
10
+ published by the Free Software Foundation, either version 3 of the
11
+ License, or (at your option) any later version.
12
+
13
+ The ZPIC Educational code suite is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU Affero General Public License for more details.
17
+
18
+ You should have received a copy of the GNU Affero General Public License
19
+ along with the ZPIC Educational code suite. If not, see <http://www.gnu.org/licenses/>.
20
+ """
21
+
22
+ import sys
23
+ import numpy as np
24
+
25
+ class ZDF_Record:
26
+ """ZDF_Record()
27
+
28
+ ZDF Datafile record information
29
+
30
+ Attributes
31
+ ----------
32
+ pos : int64
33
+ Position in ZDF file
34
+ id : int
35
+ Type id
36
+ name : str
37
+ Record name
38
+ len : uint64
39
+ Additional record length in bytes
40
+ """
41
+ def __init__( self ):
42
+ self.pos = -1
43
+ self.id = -1
44
+ self.name = ''
45
+ self.len = -1
46
+
47
+ def version( self ):
48
+ """version()
49
+
50
+ Gets record version number
51
+
52
+ Returns
53
+ -------
54
+ version : int
55
+ Record version number
56
+ """
57
+ return self.id & 0x0000FFFF
58
+
59
+ def type( self ):
60
+ """type( typeTag )
61
+
62
+ Gets record type from tag
63
+
64
+ Returns
65
+ -------
66
+ type : {'int', 'double', 'string', 'dataset', 'iteration', 'grid_info', 'part_info','unknown'}
67
+ Type of record
68
+ """
69
+
70
+ typeID = self.id & 0xFFFF0000
71
+
72
+ types = {0x00010000: "int",
73
+ 0x00020000: "double",
74
+ 0x00030000: "string",
75
+ 0x00100000: "dataset",
76
+ 0x00200000: "iteration",
77
+ 0x00210000: "grid_info",
78
+ 0x00220000: "part_info", }
79
+
80
+ if (typeID in types):
81
+ return types[typeID]
82
+ else:
83
+ return "unknown"
84
+
85
+
86
+ class ZDF_Iteration:
87
+ """ZDF_Iteration()
88
+
89
+ Class describing iteration information.
90
+
91
+ Attributes
92
+ ----------
93
+ name : str
94
+ Name for iteration metadata (usually set to 'ITERATION')
95
+ n : int
96
+ Iteration value
97
+ t : float
98
+ Time value
99
+ tunits : str
100
+ Units used for time value
101
+ """
102
+ def __init__( self ):
103
+ self.name = ""
104
+ self.n = 0
105
+ self.t = 0.0
106
+ self.tunits = ""
107
+
108
+ class ZDF_Grid_Axis:
109
+ """ZDF_Grid_Axis()
110
+
111
+ Class describing grid axis
112
+
113
+ Attributes
114
+ ----------
115
+ name : str
116
+ Axis name
117
+ type : {0,1,2}
118
+ Axis type, must be one of 0 (linear), 1 (log10) or 2 (log2)
119
+ min : float
120
+ Minimum axis value
121
+ max : float
122
+ Maximum axis value
123
+ label : str
124
+ Axis label
125
+ units : str
126
+ Axis values data units
127
+ """
128
+ def __init__( self ):
129
+ self.name = ''
130
+ self.type = 0
131
+ self.min = 0.
132
+ self.max = 0.
133
+ self.label = ''
134
+ self.units = ''
135
+
136
+ class ZDF_Grid_Info:
137
+ """ZDF_Grid_Info()
138
+
139
+ Grid dataset information
140
+
141
+ Attributes
142
+ ----------
143
+ ndims : int
144
+ Dimensionality of dataset
145
+ nx : list of int (ndims)
146
+ Number of grid points in each direction
147
+ label : str
148
+ Dataset label
149
+ units : str
150
+ Dataset units
151
+ has_axis : bool
152
+ True if the dataset includes axis information
153
+ axis : list of ZDF_Grid_Axis (ndims)
154
+ Information on each axis, when available
155
+ """
156
+ def __init__( self ):
157
+ self.name = ''
158
+ self.ndims = 0
159
+ self.nx = []
160
+ self.label = ''
161
+ self.units = ''
162
+ self.has_axis = 0
163
+ self.axis = []
164
+
165
+ class ZDF_Part_Info:
166
+ """ZDF_Part_Info()
167
+
168
+ Particle dataset information
169
+
170
+ Attributes
171
+ ----------
172
+ name : str
173
+ Particle dataset name
174
+ label : str
175
+ Particle dataset label
176
+ nquants : int
177
+ Number of quantities per particle
178
+ quants : list of str (nquants)
179
+ Name of individual quantities
180
+ qlabels : dictionary
181
+ Labels for each quantity
182
+ qunits : dictionary
183
+ Units for each quantity
184
+ nparts: int
185
+ Number of particles in dataset
186
+ """
187
+ def __init__( self ):
188
+ self.name = ''
189
+ self.label = ''
190
+ self.nquants = 0
191
+ self.quants = []
192
+ self.qlabels = dict()
193
+ self.qunits = dict()
194
+ self.nparts = 0
195
+
196
+ class ZDFfile:
197
+ """ZDFfile( file_name )
198
+
199
+ ZDF data file class
200
+
201
+ Parameters
202
+ ----------
203
+ file_name : str
204
+ File name of ZDF data file, should include path
205
+ """
206
+ def __init__(self, file_name):
207
+ self.__file = open(file_name, "rb")
208
+
209
+ # Check magic number
210
+ magic = self.__file.read(4)
211
+ if (magic != b'ZDF1'):
212
+ print('File is not a proper ZDF file, aborting', file=sys.stderr)
213
+ self.__file.close
214
+
215
+ def close(self):
216
+ """close()
217
+
218
+ Closes ZDF file
219
+ """
220
+ self.__file.close
221
+
222
+ def __read_uint32(self):
223
+ data = np.fromfile(self.__file,dtype='<u4',count=1)
224
+ if ( data.size == 0 ):
225
+ return False
226
+ else:
227
+ return data[0]
228
+
229
+ def __read_int32(self):
230
+ return np.fromfile(self.__file,dtype='<i4',count=1)[0]
231
+
232
+ def __read_uint64(self):
233
+ return np.fromfile(self.__file,dtype='<u8',count=1)[0]
234
+
235
+ def __read_int64(self):
236
+ return np.fromfile(self.__file,dtype='<i8',count=1)[0]
237
+
238
+ def __read_float32(self):
239
+ return np.fromfile(self.__file,dtype='<f4',count=1)[0]
240
+
241
+ def __read_float64(self):
242
+ return np.fromfile(self.__file,dtype='<f8',count=1)[0]
243
+
244
+ def __read_string(self):
245
+
246
+ length = self.__read_uint32()
247
+
248
+ if (length > 0):
249
+ data = self.__file.read(length)
250
+ fstring = data.decode()
251
+
252
+ # Data is always written in blocks of 4 byt
253
+ pad = ((length - 1) // 4 + 1) * 4 - length
254
+ if ( pad > 0 ):
255
+ self.__file.seek(pad,1)
256
+ else:
257
+ fstring = ""
258
+
259
+ return fstring
260
+
261
+ def record_type(self, typeTag):
262
+ version = typeTag & 0x0000FFFF
263
+ typeID = typeTag & 0xFFFF0000
264
+
265
+ types = {0x00010000: "int",
266
+ 0x00020000: "double",
267
+ 0x00030000: "string",
268
+ 0x00100000: "dataset",
269
+ 0x00110000: "cdset_start",
270
+ 0x00120000: "cdset_chunk",
271
+ 0x00130000: "cdset_end",
272
+ 0x00200000: "iteration",
273
+ 0x00210000: "grid_info",
274
+ 0x00220000: "part_info",
275
+ 0x00230000: "track_info", }
276
+
277
+ if (typeID in types):
278
+ return types[typeID]
279
+ else:
280
+ return "unknown"
281
+
282
+ # -----------------------------------------------------------------------------
283
+ # Array datatypes
284
+ # -----------------------------------------------------------------------------
285
+
286
+ def __read_int32_arr(self, nx):
287
+ size = np.prod(nx)
288
+ data = np.fromfile(self.__file,dtype='<i4',count=size)
289
+ data.shape = np.flip(nx)
290
+ return data
291
+
292
+ def __read_uint32_arr(self, nx):
293
+ size = np.prod(nx)
294
+ data = np.fromfile(self.__file,dtype='<u4',count=size)
295
+ data.shape = np.flip(nx)
296
+ return data
297
+
298
+ def __read_int64_arr(self, nx):
299
+ size = np.prod(nx)
300
+ data = np.fromfile(self.__file,dtype='<i8',count=size)
301
+ data.shape = np.flip(nx)
302
+ return data
303
+
304
+ def __read_uint64_arr(self, nx):
305
+ size = np.prod(nx)
306
+ data = np.fromfile(self.__file,dtype='<u8',count=size)
307
+ data.shape = np.flip(nx)
308
+ return data
309
+
310
+ def __read_float32_arr(self, nx):
311
+ size = np.prod(nx)
312
+ data = np.fromfile(self.__file,dtype='<f4',count=size)
313
+ data.shape = np.flip(nx)
314
+ return data
315
+
316
+ def __read_float64_arr(self, nx):
317
+ size = np.prod(nx)
318
+ data = np.fromfile(self.__file,dtype='<f8',count=size)
319
+ data.shape = np.flip(nx)
320
+ return data
321
+
322
+ def __read_arr( self, dtype, nx ):
323
+
324
+ if ( dtype == 5 ):
325
+ data = self.__read_int32_arr(nx)
326
+ elif ( dtype == 3 ):
327
+ data = self.__read_uint32_arr(nx)
328
+ elif ( dtype == 7 ):
329
+ data = self.__read_int64_arr(nx)
330
+ elif ( dtype == 8 ):
331
+ data = self.__read_uint64_arr(nx)
332
+ elif ( dtype == 9 ):
333
+ data = self.__read_float32_arr(nx)
334
+ elif ( dtype == 10 ):
335
+ data = self.__read_float64_arr(nx)
336
+ else:
337
+ print( '(*error*) ZDF: Data type not yet supported.' , file=sys.stderr)
338
+ data = False
339
+
340
+ return data
341
+
342
+ # -----------------------------------------------------------------------------
343
+ # Low level interfaces
344
+ # -----------------------------------------------------------------------------
345
+
346
+ # -----------------------------------------------------------------------------
347
+ # Read record header
348
+ # -----------------------------------------------------------------------------
349
+
350
+ def read_record(self, skip=False):
351
+ """read_record(skip=False)
352
+
353
+ Reads current record information from file
354
+
355
+ Parameters
356
+ ----------
357
+ skip : bool, optional
358
+ If set to True, skip to next record after reading record
359
+ header data
360
+ """
361
+
362
+ rec = ZDF_Record()
363
+ rec.pos = self.__file.tell()
364
+
365
+ # Read record id and check for EOF
366
+ rec.id = self.__read_uint32()
367
+
368
+ if (rec.id is False):
369
+ # If end of file return false
370
+ return False
371
+
372
+ # Read name and length
373
+ rec.name = self.__read_string()
374
+ rec.len = self.__read_uint64()
375
+
376
+ # If requested, skip over to next record
377
+ if (skip):
378
+ self.__file.seek(rec.len, 1)
379
+
380
+ return rec
381
+
382
+ def __record_skip( self, rec ):
383
+ self.__file.seek(rec.len, 1)
384
+
385
+ # -----------------------------------------------------------------------------
386
+ # Read string
387
+ # -----------------------------------------------------------------------------
388
+
389
+ def read_string(self, rec = False):
390
+ """read_string(rec = False)
391
+
392
+ Reads string record from data file
393
+
394
+ Parameters
395
+ ----------
396
+ rec : ZDF_Record, optional
397
+ If not set the routine will read the record before reading the data
398
+
399
+ Returns
400
+ -------
401
+ string : str
402
+ String data
403
+ """
404
+ if ( rec is False ):
405
+ rec = self.read_record()
406
+
407
+ fstring = self.__read_string()
408
+ return fstring
409
+
410
+ # -----------------------------------------------------------------------------
411
+ # Read iteration
412
+ # -----------------------------------------------------------------------------
413
+
414
+ def read_iteration(self, rec = False):
415
+ """read_iteration( rec = False )
416
+
417
+ Read iteration record from data file
418
+
419
+ Parameters
420
+ ----------
421
+ rec : ZDF_Record, optional
422
+ If not set the routine will read the record before reading the data
423
+
424
+ Returns
425
+ -------
426
+ iteration : ZDF_Iteration()
427
+ Iteration data
428
+ """
429
+ if ( rec is False ):
430
+ rec = self.read_record()
431
+ iteration = ZDF_Iteration()
432
+ iteration.name = rec.name
433
+ iteration.n = self.__read_int32()
434
+ iteration.t = self.__read_float64()
435
+ iteration.tunits = self.__read_string()
436
+ return iteration
437
+
438
+ # -----------------------------------------------------------------------------
439
+ # Read grid info
440
+ # -----------------------------------------------------------------------------
441
+
442
+ def read_grid_info(self, rec = False):
443
+ """read_grid_info( rec = False )
444
+
445
+ Read grid information record from data file
446
+
447
+ Parameters
448
+ ----------
449
+ rec : ZDF_Record, optional
450
+ If not set the routine will read the record before reading the data
451
+
452
+ Returns
453
+ -------
454
+ info : ZDF_Grid_Info()
455
+ Grid information data
456
+ """
457
+ if ( rec is False ):
458
+ rec = self.read_record()
459
+
460
+ # Maximum supported version
461
+ max_version = 0x00000001
462
+
463
+ # Get version
464
+ version = rec.version()
465
+ if ( version > max_version ):
466
+ print( '(*error*) ZDF: Grid info version is higher than supported.' , file=sys.stderr)
467
+ print( '(*error*) ZDF: Please update the code to a newer version.' , file=sys.stderr)
468
+ return False
469
+
470
+ info = ZDF_Grid_Info()
471
+
472
+ info.name = rec.name
473
+ info.ndims = self.__read_uint32()
474
+ info.nx = self.__read_uint64_arr( info.ndims )
475
+
476
+ info.label = self.__read_string()
477
+ info.units = self.__read_string()
478
+ info.has_axis = self.__read_int32()
479
+
480
+ if ( info.has_axis ):
481
+ for i in range(info.ndims):
482
+ ax = ZDF_Grid_Axis()
483
+ if (version > 0):
484
+ ax.name = self.__read_string()
485
+ else:
486
+ ax.name = 'axis_{}'.format(i)
487
+ ax.type = self.__read_int32()
488
+ ax.min = self.__read_float64()
489
+ ax.max = self.__read_float64()
490
+ ax.label = self.__read_string()
491
+ ax.units = self.__read_string()
492
+ info.axis.append(ax)
493
+
494
+ return info
495
+
496
+ # -----------------------------------------------------------------------------
497
+ # Read particle info
498
+ # -----------------------------------------------------------------------------
499
+
500
+ def read_part_info(self, rec = False):
501
+ """read_part_info( rec = False )
502
+
503
+ Read particle information record from data file
504
+
505
+ Parameters
506
+ ----------
507
+ rec : ZDF_Record, optional
508
+ If not set the routine will read the record before reading the data
509
+
510
+ Returns
511
+ -------
512
+ iteration : ZDF_Part_Info()
513
+ Iteration data
514
+ """
515
+ if ( rec is False ):
516
+ rec = self.read_record()
517
+
518
+ # Maximum supported version
519
+ max_version = 0x00000002
520
+
521
+ # Get version
522
+ version = rec.version()
523
+ if ( version > max_version ):
524
+ print( '(*error*) ZDF: Particles info version is higher than supported.' , file=sys.stderr)
525
+ print( '(*error*) ZDF: Please update the code to a newer version.' , file=sys.stderr)
526
+ return False
527
+
528
+ info = ZDF_Part_Info()
529
+ info.name = rec.name
530
+ info.label = self.__read_string()
531
+
532
+ if ( version >= 1 ):
533
+ # version 1
534
+ info.nparts = self.__read_uint64()
535
+ info.nquants = self.__read_uint32()
536
+
537
+ for i in range(info.nquants):
538
+ info.quants.append( self.__read_string() )
539
+ for q in info.quants:
540
+ info.qlabels[q] = self.__read_string()
541
+ for q in info.quants:
542
+ info.qunits[q] = self.__read_string()
543
+
544
+ else:
545
+ # version 0
546
+ info.nquants = self.__read_uint32()
547
+
548
+ for i in range(info.nquants):
549
+ info.quants.append( self.__read_string() )
550
+
551
+ # version 0 does not have label information
552
+ for q in info.quants:
553
+ info.qlabels[q] = q
554
+
555
+ for q in info.quants:
556
+ info.qunits[q] = self.__read_string()
557
+
558
+ info.nparts = self.__read_uint64()
559
+
560
+ return info
561
+
562
+ # -----------------------------------------------------------------------------
563
+ # Read dataset
564
+ # -----------------------------------------------------------------------------
565
+
566
+ def read_dataset(self, rec = False):
567
+ """read_dataset()
568
+
569
+ Read dataset from data file
570
+
571
+ Parameters
572
+ ----------
573
+ rec : ZDF_Record, optional
574
+ If not set the routine will read the record before reading the data
575
+
576
+ Returns
577
+ -------
578
+ data : numpy.ndarray
579
+ Numpy ndarray with data
580
+ """
581
+ if ( rec is False ):
582
+ rec = self.read_record()
583
+
584
+ if ( self.record_type(rec.id) != 'dataset' ):
585
+ print( '(*error*) ZDF: Expected dataset record but found {} instead.'.format(self.record_type(rec.id)),
586
+ file=sys.stderr)
587
+ return False
588
+
589
+
590
+ # Maximum supported version
591
+ max_version = 0x00000002
592
+
593
+ # Get version
594
+ version = rec.version()
595
+ if ( version > max_version ):
596
+ print( '(*error*) ZDF: Dataset version is higher than supported.' , file=sys.stderr)
597
+ print( '(*error*) ZDF: Please update the code to a newer version.' , file=sys.stderr)
598
+ return False
599
+
600
+ # Version 0x0001 includes id tag
601
+ if ( version >= 1 ):
602
+ id = self.__read_uint32()
603
+ else:
604
+ id = 0
605
+
606
+ data_type = self.__read_int32()
607
+ ndims = self.__read_uint32()
608
+ nx = self.__read_uint64_arr(ndims)
609
+ data = self.__read_arr( data_type, nx )
610
+
611
+ return data
612
+
613
+ # -----------------------------------------------------------------------------
614
+ # Retrieve list of file contents / print file contents
615
+ # -----------------------------------------------------------------------------
616
+
617
+ def list(self, printRec=False):
618
+ """list( printRec=False )
619
+
620
+ Gets a list of file contents and optionally prints it to screen
621
+
622
+ Parameters
623
+ ----------
624
+ printRec : bool, optional
625
+ If set to True will print all records found in the file,
626
+ defaults to False.
627
+ """
628
+ # Position file after magic number
629
+ self.__file.seek(4)
630
+
631
+ rec_list = []
632
+ while True:
633
+ rec = self.read_record(skip=True)
634
+ if (rec is False):
635
+ break
636
+ else:
637
+ rec_list.append(rec)
638
+
639
+ if (printRec and (len(rec_list) > 0)):
640
+ print('Position Size(bytes) Type Name')
641
+ print('-----------------------------------------------------')
642
+ for rec in rec_list:
643
+ print('{:#010x} {:#010x} {:11} {}'.format(
644
+ rec.pos, rec.len,
645
+ self.record_type(rec.id), rec.name)
646
+ )
647
+
648
+ return rec_list
649
+
650
+ # -----------------------------------------------------------------------------
651
+ # High level interfaces
652
+ # -----------------------------------------------------------------------------
653
+
654
+ class ZDF_Info:
655
+ """ZDF_Info()
656
+ ZDF File information
657
+
658
+ Attributes
659
+ ----------
660
+ type : {'grid','particles'}
661
+ Type of ZDF file
662
+ grid : ZDF_Grid_Info
663
+ Grid information for grid files
664
+ particles : ZDF_Part_Info
665
+ Particle information for particle files
666
+ iteration : ZDF_Iteration
667
+ Iteration information
668
+
669
+ """
670
+ def __init__(self):
671
+ self.type = ""
672
+ self.grid = None
673
+ self.particles = None
674
+ self.iteration = None
675
+
676
+
677
+ def info( file_name ):
678
+ """info( file_name )
679
+
680
+ Gets metadata for a ZDF file
681
+
682
+ Parameters
683
+ ----------
684
+ file_name : str
685
+ File name of ZDF data file, should include path
686
+
687
+ Returns
688
+ -------
689
+ info : ZDF_Info
690
+ File information. If file is invalid False is returned.
691
+ """
692
+ # Open file
693
+ zdf = ZDFfile( file_name )
694
+
695
+ # Check file type and read metadata
696
+ info = ZDF_Info()
697
+ info.type = zdf.read_string()
698
+ if ( info.type == "grid" ):
699
+ info.grid = zdf.read_grid_info()
700
+ elif ( info.type == "particles" ):
701
+ info.particles = zdf.read_part_info()
702
+ else:
703
+ print("File is not a valid ZDF grid or particles file", file=sys.stderr)
704
+ zdf.close()
705
+ return False
706
+
707
+ # Read iteration info
708
+ info.iteration = zdf.read_iteration()
709
+
710
+ # Close file
711
+ zdf.close()
712
+
713
+ return info
714
+
715
+ def read( file_name ):
716
+ """read( file_name )
717
+
718
+ Reads all data in a ZDF file
719
+
720
+ Parameters
721
+ ----------
722
+ file_name : str
723
+ File name of ZDF data file, should include path
724
+
725
+ Returns
726
+ -------
727
+ (data, info) : ( numpy.ndarray | dictionary, ZDF_Info )
728
+ Tuple containing file data and metadata. Data will be a
729
+ numpy.ndarray for grid data, and a dictionary of numpy.array for
730
+ particle data (one entry per quantity). Metadata is returned as a
731
+ ZDF_Info object. If file is invalid False is returned.
732
+ """
733
+ # Open file
734
+ zdf = ZDFfile( file_name )
735
+
736
+ # Check file type and read metadata
737
+ info = ZDF_Info()
738
+
739
+ info.type = zdf.read_string()
740
+ if ( info.type == "grid" ):
741
+ info.grid = zdf.read_grid_info()
742
+ info.iteration = zdf.read_iteration()
743
+ data = zdf.read_dataset()
744
+ elif ( info.type == "particles" ):
745
+ info.particles = zdf.read_part_info()
746
+ info.iteration = zdf.read_iteration()
747
+
748
+ # Read all quantities
749
+ data = dict()
750
+ for q in info.particles.quants:
751
+ data[q] = zdf.read_dataset()
752
+ else:
753
+ print("File is not a valid ZDF grid or particles file", file=sys.stderr)
754
+ zdf.close()
755
+ return False
756
+
757
+ #Close file
758
+ zdf.close()
759
+
760
+ return (data,info)
761
+
762
+ def list(file_name, printRec=False):
763
+ """list( printRec=False )
764
+
765
+ Gets a list of file contents and optionally print it to screen
766
+
767
+ Parameters
768
+ ----------
769
+ file_name : str
770
+ File name of ZDF data file, should include path
771
+
772
+ printRec : bool, optional
773
+ If set to True will print all records found in the file,
774
+ defaults to False.
775
+ """
776
+ zdf = ZDFfile(file_name)
777
+ zdf.list(printRec)
778
+ zdf.close()
779
+