2121from .phyvars import FIELD_FILES_H5 , SFIELD_FILES_H5
2222
2323if typing .TYPE_CHECKING :
24- from typing import (List , Optional , Tuple , Dict , BinaryIO , Union , Any ,
25- Callable , Iterator )
24+ from typing import (List , Optional , Tuple , Dict , BinaryIO , Any , Callable ,
25+ Iterator )
2626 from pathlib import Path
2727 from xml .etree .ElementTree import Element
2828 from numpy import ndarray
@@ -339,9 +339,118 @@ def _readbin(fid: BinaryIO, fmt: str = 'i', nwords: int = 1,
339339 return elts
340340
341341
342- def fields (fieldfile : Path , only_header : bool = False ,
343- only_istep : bool = False ) -> Union [None , int , Dict [str , Any ],
344- Tuple [Dict [str , Any ], ndarray ]]:
342+ class _HeaderInfo (typing .NamedTuple ):
343+ """Header information."""
344+
345+ magic : int
346+ nval : int
347+ sfield : bool
348+ readbin : Callable
349+ header : Dict [str , Any ]
350+
351+
352+ def _legacy_header (filepath : Path , fid : BinaryIO ,
353+ stop_at_istep : bool = False ) -> _HeaderInfo :
354+ """Read the header of a legacy binary file."""
355+ readbin = partial (_readbin , fid )
356+ magic = readbin ()
357+ if magic > 8000 : # 64 bits
358+ magic -= 8000
359+ readbin () # need to read 4 more bytes
360+ readbin = partial (readbin , file64 = True )
361+
362+ # check nb components
363+ nval = 1
364+ sfield = False
365+ if magic > 400 :
366+ nval = 4
367+ elif magic > 300 :
368+ nval = 3
369+ elif magic > 100 :
370+ sfield = True
371+
372+ magic %= 100
373+ if magic < 9 :
374+ raise ParsingError (filepath , 'magic < 9 not supported' )
375+
376+ header_info = _HeaderInfo (magic , nval , sfield , readbin , {})
377+ header = header_info .header
378+ # extra ghost point in horizontal direction
379+ header ['xyp' ] = int (nval == 4 ) # magic >= 9
380+
381+ # total number of values in relevant space basis
382+ # (e1, e2, e3) = (theta, phi, radius) in spherical geometry
383+ # = (x, y, z) in cartesian geometry
384+ header ['nts' ] = readbin (nwords = 3 )
385+
386+ # number of blocks, 2 for yinyang or cubed sphere
387+ header ['ntb' ] = readbin () # magic >= 7
388+
389+ # aspect ratio
390+ header ['aspect' ] = readbin ('f' , 2 )
391+
392+ # number of parallel subdomains
393+ header ['ncs' ] = readbin (nwords = 3 ) # (e1, e2, e3) space
394+ header ['ncb' ] = readbin () # magic >= 8, blocks
395+
396+ # r - coordinates
397+ # rgeom[0:self.nrtot+1, 0] are edge radial position
398+ # rgeom[0:self.nrtot, 1] are cell-center radial position
399+ header ['rgeom' ] = readbin ('f' , header ['nts' ][2 ] * 2 + 1 ) # magic >= 2
400+ header ['rgeom' ] = np .resize (header ['rgeom' ], (header ['nts' ][2 ] + 1 , 2 ))
401+
402+ header ['rcmb' ] = readbin ('f' ) # magic >= 7
403+
404+ header ['ti_step' ] = readbin () # magic >= 3
405+ if stop_at_istep :
406+ return header_info
407+
408+ header ['ti_ad' ] = readbin ('f' ) # magic >= 3
409+ header ['erupta_total' ] = readbin ('f' ) # magic >= 5
410+ header ['bot_temp' ] = readbin ('f' ) # magic >= 6
411+ header ['core_temp' ] = readbin ('f' ) if magic >= 10 else 1
412+
413+ # magic >= 4
414+ header ['e1_coord' ] = readbin ('f' , header ['nts' ][0 ])
415+ header ['e2_coord' ] = readbin ('f' , header ['nts' ][1 ])
416+ header ['e3_coord' ] = readbin ('f' , header ['nts' ][2 ])
417+
418+ return header_info
419+
420+
421+ def field_istep (fieldfile : Path ) -> Optional [int ]:
422+ """Read istep from binary field file.
423+
424+ Args:
425+ fieldfile: path of the binary field file.
426+
427+ Returns:
428+ the time step at which the binary file was written.
429+ """
430+ if not fieldfile .is_file ():
431+ return None
432+ with fieldfile .open ('rb' ) as fid :
433+ hdr = _legacy_header (fieldfile , fid , stop_at_istep = True )
434+ return hdr .header ['ti_step' ]
435+
436+
437+ def field_header (fieldfile : Path ) -> Optional [Dict [str , Any ]]:
438+ """Read header info from binary field file.
439+
440+ Args:
441+ fieldfile: path of the binary field file.
442+
443+ Returns:
444+ the header information of the binary file.
445+ """
446+ if not fieldfile .is_file ():
447+ return None
448+ with fieldfile .open ('rb' ) as fid :
449+ hdr = _legacy_header (fieldfile , fid )
450+ return hdr .header
451+
452+
453+ def fields (fieldfile : Path ) -> Optional [Tuple [Dict [str , Any ], ndarray ]]:
345454 """Extract fields data.
346455
347456 Args:
@@ -351,83 +460,15 @@ def fields(fieldfile: Path, only_header: bool = False,
351460 only_istep: when True, only :data:`istep` is returned.
352461
353462 Returns:
354- If :data:`only_istep` is True, this function returns the time step at
355- which the binary file was written.
356-
357- Else, if :data:`only_header` is True, this function returns a dict
358- containing the header informations of the binary file.
359-
360- Otherwise, this function returns the tuple :data:`(header, fields)`.
361- :data:`fields` is an array of scalar fields indexed by variable,
362- x-direction, y-direction, z-direction, block.
463+ the tuple :data:`(header, fields)`. :data:`fields` is an array of
464+ scalar fields indexed by variable, x-direction, y-direction,
465+ z-direction, block.
363466 """
364- # something to skip header?
365467 if not fieldfile .is_file ():
366468 return None
367- header : Dict [str , Any ] = {}
368469 with fieldfile .open ('rb' ) as fid :
369- readbin = partial (_readbin , fid )
370- magic = readbin ()
371- if magic > 8000 : # 64 bits
372- magic -= 8000
373- readbin () # need to read 4 more bytes
374- readbin = partial (readbin , file64 = True )
375-
376- # check nb components
377- nval = 1
378- sfield = False
379- if magic > 400 :
380- nval = 4
381- elif magic > 300 :
382- nval = 3
383- elif magic > 100 :
384- sfield = True
385-
386- magic %= 100
387- if magic < 9 :
388- raise ParsingError (fieldfile , 'magic < 9 not supported' )
389-
390- # extra ghost point in horizontal direction
391- header ['xyp' ] = int (nval == 4 ) # magic >= 9
392-
393- # total number of values in relevant space basis
394- # (e1, e2, e3) = (theta, phi, radius) in spherical geometry
395- # = (x, y, z) in cartesian geometry
396- header ['nts' ] = readbin (nwords = 3 )
397-
398- # number of blocks, 2 for yinyang or cubed sphere
399- header ['ntb' ] = readbin () # magic >= 7
400-
401- # aspect ratio
402- header ['aspect' ] = readbin ('f' , 2 )
403-
404- # number of parallel subdomains
405- header ['ncs' ] = readbin (nwords = 3 ) # (e1, e2, e3) space
406- header ['ncb' ] = readbin () # magic >= 8, blocks
407-
408- # r - coordinates
409- # rgeom[0:self.nrtot+1, 0] are edge radial position
410- # rgeom[0:self.nrtot, 1] are cell-center radial position
411- header ['rgeom' ] = readbin ('f' , header ['nts' ][2 ] * 2 + 1 ) # magic >= 2
412- header ['rgeom' ] = np .resize (header ['rgeom' ], (header ['nts' ][2 ] + 1 , 2 ))
413-
414- header ['rcmb' ] = readbin ('f' ) # magic >= 7
415-
416- header ['ti_step' ] = readbin () # magic >= 3
417- if only_istep :
418- return header ['ti_step' ]
419- header ['ti_ad' ] = readbin ('f' ) # magic >= 3
420- header ['erupta_total' ] = readbin ('f' ) # magic >= 5
421- header ['bot_temp' ] = readbin ('f' ) # magic >= 6
422- header ['core_temp' ] = readbin ('f' ) if magic >= 10 else 1
423-
424- # magic >= 4
425- header ['e1_coord' ] = readbin ('f' , header ['nts' ][0 ])
426- header ['e2_coord' ] = readbin ('f' , header ['nts' ][1 ])
427- header ['e3_coord' ] = readbin ('f' , header ['nts' ][2 ])
428-
429- if only_header :
430- return header
470+ hdr = _legacy_header (fieldfile , fid )
471+ header = hdr .header
431472
432473 # READ FIELDS
433474 # number of points in (e1, e2, e3) directions PER CPU
@@ -436,11 +477,11 @@ def fields(fieldfile: Path, only_header: bool = False,
436477 nbk = header ['ntb' ] // header ['ncb' ]
437478 # number of values per 'read' block
438479 npi = (npc [0 ] + header ['xyp' ]) * (npc [1 ] + header ['xyp' ]) * npc [2 ] * \
439- nbk * nval
480+ nbk * hdr . nval
440481
441- header ['scalefac' ] = readbin ('f' ) if nval > 1 else 1
482+ header ['scalefac' ] = hdr . readbin ('f' ) if hdr . nval > 1 else 1
442483
443- flds = np .zeros ((nval ,
484+ flds = np .zeros ((hdr . nval ,
444485 header ['nts' ][0 ] + header ['xyp' ],
445486 header ['nts' ][1 ] + header ['xyp' ],
446487 header ['nts' ][2 ],
@@ -452,7 +493,7 @@ def fields(fieldfile: Path, only_header: bool = False,
452493 range (header ['ncs' ][1 ]),
453494 range (header ['ncs' ][0 ])):
454495 # read the data for one CPU
455- data_cpu = readbin ('f' , npi ) * header ['scalefac' ]
496+ data_cpu = hdr . readbin ('f' , npi ) * header ['scalefac' ]
456497
457498 # icpu is (icpu block, icpu z, icpu y, icpu x)
458499 # data from file is transposed to obtained a field
@@ -464,8 +505,8 @@ def fields(fieldfile: Path, only_header: bool = False,
464505 icpu [0 ] * nbk :(icpu [0 ] + 1 ) * nbk # block
465506 ] = np .transpose (data_cpu .reshape (
466507 (nbk , npc [2 ], npc [1 ] + header ['xyp' ],
467- npc [0 ] + header ['xyp' ], nval )))
468- if sfield :
508+ npc [0 ] + header ['xyp' ], hdr . nval )))
509+ if hdr . sfield :
469510 # for surface fields, variables are written along z direction
470511 flds = np .swapaxes (flds , 0 , 3 )
471512 return header , flds
0 commit comments