Skip to content

Commit df7b550

Browse files
committed
New field_header and field_istep functions
This allow for a simpler API for legacy files parsers.
1 parent f22081d commit df7b550

File tree

4 files changed

+131
-91
lines changed

4 files changed

+131
-91
lines changed

stagpy/_step.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ def _header(self) -> Optional[Dict[str, Any]]:
348348
binfiles = self.step.sdat._binfiles_set(self.step.isnap)
349349
header = None
350350
if binfiles:
351-
header = stagyyparsers.fields(binfiles.pop(), only_header=True)
351+
header = stagyyparsers.field_header(binfiles.pop())
352352
elif self.step.sdat.hdf5:
353353
xmf = self.step.sdat.hdf5 / 'Data.xmf'
354354
header = stagyyparsers.read_geom_h5(xmf, self.step.isnap)[0]

stagpy/stagyydata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ def __getitem__(self, isnap):
485485
# isnap not in _isteps but not all isteps known, keep looking
486486
binfiles = self.sdat._binfiles_set(isnap)
487487
if binfiles:
488-
istep = stagyyparsers.fields(binfiles.pop(), only_istep=True)
488+
istep = stagyyparsers.field_istep(binfiles.pop())
489489
else:
490490
istep = None
491491
if istep is not None:

stagpy/stagyyparsers.py

Lines changed: 125 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
from .phyvars import FIELD_FILES_H5, SFIELD_FILES_H5
2222

2323
if 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

tests/test_parsers.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,13 @@ def test_fields_prs(sdat):
3232
assert flds.shape[1:4] == tuple(hdr['nts'])
3333

3434

35-
def test_fields_header_only_prs(sdat):
36-
hdr = prs.fields(sdat.filename('t', len(sdat.snaps) - 1), only_header=True)
35+
def test_field_header_prs(sdat):
36+
hdr = prs.field_header(sdat.filename('t', len(sdat.snaps) - 1))
3737
assert hdr['nts'].shape == (3,)
3838

3939

40-
def test_fields_istep_only_prs(sdat):
41-
istep = prs.fields(sdat.filename('t', len(sdat.snaps) - 1),
42-
only_istep=True)
40+
def test_fields_istep_prs(sdat):
41+
istep = prs.field_istep(sdat.filename('t', len(sdat.snaps) - 1))
4342
assert istep == sdat.snaps[-1].istep
4443

4544

0 commit comments

Comments
 (0)