feat(med): integrate MED 4.1 bitmask field tracker into writer#8
Open
fznoussi wants to merge 1 commit into
Open
feat(med): integrate MED 4.1 bitmask field tracker into writer#8fznoussi wants to merge 1 commit into
fznoussi wants to merge 1 commit into
Conversation
This commit integrates the FieldBitmaskWriter introduced in _med41.py
into the main writer in _med.py and adds the med_to_geo_type translation
table required to map internal MED short codes (PO1, SE2, TR3, ...) to
their fully qualified MED 4.1 geometric type names (MED_POINT1,
MED_SEG2, MED_TRIA3, ...).
─────────────────────────────────────────────────────────────────────
Context: why the tracker needs to be called from _med.py
─────────────────────────────────────────────────────────────────────
The bitmask attributes defined by MED 4.1 (LEN, LGC, LCA, ...) must
be written on the field group after all time steps and all entity/geo
type combinations have been written. This requires a two-phase approach:
phase 1 - notify(): called once per (entity_type, geo_type, step)
as each data group is created inside _write_data()
phase 2 - flush(): called once per field after all writes are done
to commit the accumulated bitmasks to the HDF5 field group
The tracker is therefore instantiated once per write() call and shared
across all _write_data() calls for that file.
─────────────────────────────────────────────────────────────────────
Fix A - Add med_to_geo_type translation table
─────────────────────────────────────────────────────────────────────
The FieldBitmaskWriter works with fully qualified MED 4.1 type names
(MED_POINT1, MED_SEG2, ...) while the rest of the writer uses short
MED codes (PO1, SE2, ...). A new translation table bridges the two:
med_to_geo_type = {
PO1: MED_POINT1,
SE2: MED_SEG2, SE3: MED_SEG3, SE4: MED_SEG4,
TR3: MED_TRIA3, TR6: MED_TRIA6, TR7: MED_TRIA7,
QU4: MED_QUAD4, QU8: MED_QUAD8, QU9: MED_QUAD9,
TE4: MED_TETRA4,T10: MED_TETRA10,
HE8: MED_HEXA8, H20: MED_HEXA20,H27: MED_HEXA27,
PY5: MED_PYRA5, P13: MED_PYRA13,
PE6: MED_PENTA6,P15: MED_PENTA15,PE18:MED_PENTA18,
POG: MED_POLYGON,POG2:MED_POLYGON2,
}
─────────────────────────────────────────────────────────────────────
Fix B - Instantiate FieldBitmaskWriter in write()
─────────────────────────────────────────────────────────────────────
A single FieldBitmaskWriter instance is created at the start of the
write pass and passed as the tracker argument to every _write_data()
call:
tracker = FieldBitmaskWriter()
# nodal data
for name, data in mesh.point_data.items():
tracker.notify(MED_NODE, MED_POINT1, step)
_write_data(..., tracker=tracker)
# cell data
for name, d in mesh.cell_data.items():
for cell, data in zip(mesh.cells, d):
med_type = meshio_to_med_type[cell.type]
_write_data(..., med_type, tracker=tracker)
for field_name in fields.keys():
tracker.flush(fields[field_name]) # commit bitmasks
─────────────────────────────────────────────────────────────────────
Fix C - Pass tracker through _write_data() signature
─────────────────────────────────────────────────────────────────────
The tracker parameter (defaulting to None) was added to _write_data()
to allow the caller to inject the shared FieldBitmaskWriter instance.
When tracker is None the function behaves exactly as before, preserving
full backward compatibility with all existing call sites and tests.
─────────────────────────────────────────────────────────────────────
Fix D - flush() placement: once per field, after all steps written
─────────────────────────────────────────────────────────────────────
flush() is called inside the cell_data loop after all cell blocks for
a given field name have been written:
for name, d in mesh.cell_data.items():
for cell, data in zip(mesh.cells, d):
_write_data(...)
for field_name in fields.keys(): # flush after all blocks
tracker.flush(fields[field_name])
This ensures that the bitmasks reflect the complete set of entity and
geometry types for the field before being committed to the HDF5 file.
Writing the bitmasks before all steps are done would produce incomplete
masks that would cause MED 4.1 readers to skip valid data.
─────────────────────────────────────────────────────────────────────
Tests added
─────────────────────────────────────────────────────────────────────
Unit tests for primitive bit operations (_med41.py)
test_bit_set
Verifies that _bit_set correctly sets individual bits at positions
0, 1 and 3 and that the resulting uint32 value matches the expected
binary representation.
test_bit_test
Verifies that _bit_test correctly detects set and unset bits on a
mask with bits 0 and 2 active (0b00101).
Unit tests for decode functions (_med41.py)
test_decode_entity_mask_empty
A zero mask returns an empty list.
test_decode_entity_mask_node
Bit 3 set (0b001000 = 8) decodes to [MED_NODE].
test_decode_entity_mask_cell
Bit 0 set (0b000001 = 1) decodes to [MED_CELL].
test_decode_entity_mask_multiple
Bits 0 and 3 set (0b001001 = 9) decodes to both MED_CELL and
MED_NODE. Order and completeness are both asserted.
test_decode_geo_mask_triangle
MED_TRIA3 is at index 4 in _GEO_ORDER[MED_CELL], so bit 4
(0b010000 = 16) must decode to [MED_TRIA3].
test_decode_geo_mask_empty
A zero geo mask returns an empty list.
Unit tests for FieldBitmaskWriter
test_bitmask_writer_notify_node
After notify(MED_NODE, MED_NO_GEOTYPE, step), bit 3 of
_g_entity must be set (MED_NODE = bit 3).
test_bitmask_writer_notify_cell
After notify(MED_CELL, MED_TRIA3, step), bit 0 of _g_entity
must be set (MED_CELL = bit 0) and bit 4 of _g_geo[MED_CELL]
must be set (MED_TRIA3 = index 4 in _GEO_ORDER[MED_CELL]).
test_bitmask_writer_notify_multiple_steps
Two notify() calls on different steps must update _s_entity
independently. step1 must have only bit 3 set (MED_NODE) and
step2 must have only bit 0 set (MED_CELL).
Integration tests (HDF5 level)
test_bitmask_writer_flush (tmp_path)
Creates an HDF5 file, calls notify(MED_NODE, ...) then flush()
and verifies directly in the HDF5 file that:
- LEN attribute exists and has bit 3 set (MED_NODE)
- LNA attribute exists (step count for MED_NODE geo mask)
- LAA attribute exists and equals 1 (one step where all types present)
test_bitmask_written_in_real_med_file (tmp_path)
Writes a complete mesh with point_data using meshio.med.write()
and verifies in the resulting HDF5 file that for every field in CHA:
- LEN attribute is present
- LAA attribute is present
- bit 3 of LEN is set (MED_NODE present, since data is nodal)
This is the highest-level test and covers the full write pipeline
including tracker instantiation, notify() and flush() calls.
─────────────────────────────────────────────────────────────────────
What is unchanged
─────────────────────────────────────────────────────────────────────
- The HDF5 structure written for nodes, cells, families and profiles
is identical to the previous version. The bitmask attributes are
purely additive and do not affect readers that do not support MED 4.1.
- _write_data() internal logic is unchanged. The tracker parameter is
optional and the function can still be called without it.
- The read() path is unchanged. read_field_types() from _med41.py is
available for future use but is not yet called during read().
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This commit integrates the FieldBitmaskWriter introduced in _med41.py into the main writer in _med.py and adds the med_to_geo_type translation table required to map internal MED short codes (PO1, SE2, TR3, ...) to their fully qualified MED 4.1 geometric type names (MED_POINT1, MED_SEG2, MED_TRIA3, ...).
Context: why the tracker needs to be called from _med.py
The bitmask attributes defined by MED 4.1 (LEN, LGC, LCA, ...) must be written on the field group after all time steps and all entity/geo type combinations have been written. This requires a two-phase approach:
phase 1 - notify(): called once per (entity_type, geo_type, step)
as each data group is created inside _write_data()
phase 2 - flush(): called once per field after all writes are done
to commit the accumulated bitmasks to the HDF5 field group
The tracker is therefore instantiated once per write() call and shared across all _write_data() calls for that file.
Fix A : Add med_to_geo_type translation table
The FieldBitmaskWriter works with fully qualified MED 4.1 type names (MED_POINT1, MED_SEG2, ...) while the rest of the writer uses short MED codes (PO1, SE2, ...). A new translation table bridges the two:
med_to_geo_type = {
PO1: MED_POINT1,
SE2: MED_SEG2, SE3: MED_SEG3, SE4: MED_SEG4,
TR3: MED_TRIA3, TR6: MED_TRIA6, TR7: MED_TRIA7,
QU4: MED_QUAD4, QU8: MED_QUAD8, QU9: MED_QUAD9,
TE4: MED_TETRA4,T10: MED_TETRA10,
HE8: MED_HEXA8, H20: MED_HEXA20,H27: MED_HEXA27,
PY5: MED_PYRA5, P13: MED_PYRA13,
PE6: MED_PENTA6,P15: MED_PENTA15,PE18:MED_PENTA18,
POG: MED_POLYGON,POG2:MED_POLYGON2,
}
Fix B : Instantiate FieldBitmaskWriter in write()
A single FieldBitmaskWriter instance is created at the start of the write pass and passed as the tracker argument to every _write_data() call:
tracker = FieldBitmaskWriter()
nodal data
for name, data in mesh.point_data.items():
tracker.notify(MED_NODE, MED_POINT1, step)
_write_data(..., tracker=tracker)
cell data
for name, d in mesh.cell_data.items():
for cell, data in zip(mesh.cells, d):
med_type = meshio_to_med_type[cell.type]
_write_data(..., med_type, tracker=tracker)
for field_name in fields.keys():
tracker.flush(fields[field_name]) # commit bitmasks
Fix C : Pass tracker through _write_data() signature
The tracker parameter (defaulting to None) was added to _write_data() to allow the caller to inject the shared FieldBitmaskWriter instance. When tracker is None the function behaves exactly as before, preserving full backward compatibility with all existing call sites and tests.
Fix D - flush() placement: once per field, after all steps written
flush() is called inside the cell_data loop after all cell blocks for a given field name have been written:
for name, d in mesh.cell_data.items():
for cell, data in zip(mesh.cells, d):
_write_data(...)
for field_name in fields.keys(): # flush after all blocks
tracker.flush(fields[field_name])
This ensures that the bitmasks reflect the complete set of entity and geometry types for the field before being committed to the HDF5 file. Writing the bitmasks before all steps are done would produce incomplete masks that would cause MED 4.1 readers to skip valid data.
Tests added
Unit tests for primitive bit operations (_med41.py)
test_bit_set
Verifies that _bit_set correctly sets individual bits at positions
0, 1 and 3 and that the resulting uint32 value matches the expected
binary representation.
test_bit_test
Verifies that _bit_test correctly detects set and unset bits on a
mask with bits 0 and 2 active (0b00101).
Unit tests for decode functions (_med41.py)
test_decode_entity_mask_empty
A zero mask returns an empty list.
test_decode_entity_mask_node
Bit 3 set (0b001000 = 8) decodes to [MED_NODE].
test_decode_entity_mask_cell
Bit 0 set (0b000001 = 1) decodes to [MED_CELL].
test_decode_entity_mask_multiple
Bits 0 and 3 set (0b001001 = 9) decodes to both MED_CELL and
MED_NODE. Order and completeness are both asserted.
test_decode_geo_mask_triangle
MED_TRIA3 is at index 4 in _GEO_ORDER[MED_CELL], so bit 4
(0b010000 = 16) must decode to [MED_TRIA3].
test_decode_geo_mask_empty
A zero geo mask returns an empty list.
Unit tests for FieldBitmaskWriter
test_bitmask_writer_notify_node
After notify(MED_NODE, MED_NO_GEOTYPE, step), bit 3 of
_g_entity must be set (MED_NODE = bit 3).
test_bitmask_writer_notify_cell
After notify(MED_CELL, MED_TRIA3, step), bit 0 of _g_entity
must be set (MED_CELL = bit 0) and bit 4 of _g_geo[MED_CELL]
must be set (MED_TRIA3 = index 4 in _GEO_ORDER[MED_CELL]).
test_bitmask_writer_notify_multiple_steps
Two notify() calls on different steps must update _s_entity
independently. step1 must have only bit 3 set (MED_NODE) and
step2 must have only bit 0 set (MED_CELL).
Integration tests (HDF5 level)
test_bitmask_writer_flush (tmp_path)
Creates an HDF5 file, calls notify(MED_NODE, ...) then flush()
and verifies directly in the HDF5 file that:
- LEN attribute exists and has bit 3 set (MED_NODE)
- LNA attribute exists (step count for MED_NODE geo mask)
- LAA attribute exists and equals 1 (one step where all types present)
test_bitmask_written_in_real_med_file (tmp_path)
Writes a complete mesh with point_data using meshio.med.write()
and verifies in the resulting HDF5 file that for every field in CHA:
- LEN attribute is present
- LAA attribute is present
- bit 3 of LEN is set (MED_NODE present, since data is nodal) This is the highest-level test and covers the full write pipeline including tracker instantiation, notify() and flush() calls.
What is unchanged