Skip to content

fix(gxml, sat): an HP section keeps its flange on read, and a plate outline keeps its start vertex through the SAT - #406

Merged
Krande merged 4 commits into
Krande:mainfrom
oleandor:fix/genie-reader-hp-and-outline
Sep 27, 2026
Merged

Krande merged 4 commits into
Krande:mainfrom
oleandor:fix/genie-reader-hp-and-outline

Conversation

@oleandor

@oleandor oleandor commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Two GeniE round-trip defects, found by #399's round-trip test and pinned there by name because they were the reader's, not the container's. Both root causes measured; both pinned notes removed, and that test now catches both.

Rebased onto post-#407 main (0.92.0), three commits: the HP fix, the SAT outline fix, and — per review — the L-section asymmetry closed at its source in string_to_section, with the Abaqus section=L and Sesam GLSEC readers mirroring the same way (an HP180x10 read back from either deck had the same None flange the GeniE reader had). For L150x10 every GeneralProperties field, the profile outline and every written deck line are identical before and after; only the two fields, unique_props() and BoxSides._get_dim (a TypeError before) move. Details in the comment below.

1. An HP200x10 lost w_top / t_ftop on read — the reader

GeniE itself writes a bulb flat as an l_section with exactly four attributes — its own V8.4-06 export in files/fem_files/sesam/xml_all_basic_props.xml has <l_section h="0.14" b="0.019" tw="0.008" tf="0.0123"/> — so the writer is right and must not change. read_sections.angular was the only section reader that did not mirror its single width/thickness onto both flange slots the way isec, box_sec, channel_section and bar_section do (and the way profile_db_collect builds every HP). Two lines.

It hid because nothing in the ANGULAR geometry path reads the top flange, so every GeneralProperties value already round-tripped. It was not cosmetic: BoxSides._get_dim does max(w_btn, w_top), so measuring any HP beam read from a GeniE XML raised TypeError. Now (2.0, 0.038, 0.2).

2. A plate outline came back rotated by one vertex — the writer

The SAT was dumped and the reader walked: PlateFactory.get_edges_from_loop returns the loop in the order the writer wrote it — the reader is blameless. outline_ccw_about in sat/write/write_plate.py rewinds an outline to agree with the face normal using pts[::-1], and reversing a closed loop that way is the same cycle entered one step earlier ([v0,v1,v2,v3] → [v3,v2,v1,v0]); since outline[0] becomes the loop's first coedge, that step is the defect. CurvePoly2d._points_fix rewinds identically but keeps points[0], so the read plate could not undo it. Fix, one line at the single choke point all three write paths share: np.roll(pts[::-1], 1, axis=0) — reverse about the first vertex. The start vertex is now preserved through the SAT; no reader canonicalisation, no test expectation rotated.

Coverage

  • test_gxml_section_round_trip.py (44): every one of the 13 supported BaseTypes examples × dimensions, all 20 GeneralProperties fields, unique_props(), the bbox consequence, and a self-maintaining check that the unsupported set equals what get_section_props refuses. Measured pre-fix: HP was the only shape losing anything.
  • test_gxml_plate_outline_round_trip.py (11) and test_write_plate_outline_start.py (10): square in both windings, rectangle, L, C — exact vertex order, winding and normal asserted separately; a guard that the SAT path was actually used.
  • Real GeniE fixtures (the 8 concept XMLs under files/fem_files/sesam/), read and read→write→read, before vs after: counts identical, every section's Iy/Iz/Ax identical; the only diffs are the two intended ones. Outline fidelity on a round trip: 15/15 broken before, 0/15 after.

Mutations — 5, all caught

Both mirrors dropped (5 tests); only t_ftop dropped — the half fix (4); pts[::-1] restored (17, incl. the .gnx round trip); roll by 2 — same cycle, wrong start (17: proves the start is asserted, not the cycle); winding condition inverted (14, incl. four pre-existing GeniE-reference tests).

Suite tests/core/: 4381 passed, 22 skipped, 0 failed; lint clean. Re-run independently by the session opening this PR: 290/290 across gxml, sat and the new files.

Found and pinned, not fixed

  • BaseTypes.CIRCULAR has no branch in the GeniE writer's get_section_props: the <section> is dropped while the beam's section_ref is still written, so the file is internally inconsistent and only the reader complains. Pre-existing; now pinned by name.
  • Plate holes: plate_to_sat_entities authors a single loop and never sets Loop.next_loop; the reader handles multi-loop faces, the writer cannot produce one.
  • string_to_section's L branch leaves the top-flange fields unset where profile_db_collect fills them; an L150x10 now reads back with its equal legs rather than None. Correct values; closing the asymmetry would change unique_props() for every L section.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Nej3bWxJevs9GkH6tg1WKv

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

PR Review

👋 I checked your PR and found no issues. Thanks!

  • ✅ PR title is ok
  • ✅ Exactly one release label
  • ✅ SOURCE_KEY secret is set
  • ✅ Calculated next version: "0.93.1"

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

🚀 Profiling Results (Top 20 most expensive calls)

Function Calls Duration (s)
<built-in method builtins.exec> (~:0) 1373 16.0277
<module> (<string>:1) 1 16.0277
test_bench_batch_tessellate (tests/profiling/test_cad_backend_bench.py:63) 1 5.0015
run (tests/profiling/test_cad_backend_bench.py:73) 6 4.8752
batch_tessellate (src/ada/visit/tessellate.py:1102) 1446 4.8742
test_build_big_ifc_pipe (tests/profiling/test_ifc_creation.py:48) 1 4.6166
tessellate_geom (src/ada/visit/tessellate.py:830) 1440 4.4400
__truediv__ (src/ada/api/spatial/part.py:2046) 8 4.3944
tessellate_occ_geom (src/ada/visit/tessellate.py:684) 1440 4.0388
tessellate_shape (src/ada/visit/tessellate.py:554) 1440 4.0282
tessellate (src/ada/cad/__init__.py:1187) 1440 3.2861
to_ifc (src/ada/api/spatial/assembly.py:325) 5 3.2714
add_object (src/ada/api/spatial/part.py:309) 1201 3.2138
add_pipe (src/ada/api/spatial/part.py:164) 200 3.1997
sync (src/ada/cadit/ifc/store.py:200) 5 3.0472
sync_added_physical_objects (src/ada/cadit/ifc/write/write_ifc.py:92) 5 3.0055
add (src/ada/cadit/ifc/write/write_ifc.py:582) 2200 2.9485
add_section (src/ada/api/spatial/part.py:294) 801 2.7724
add (src/ada/api/containers/sections.py:139) 805 2.7714
equal_props (src/ada/sections/concept.py:100) 241399 2.5406

📈 Performance Impact per Commit

Commit Total Top-20 Duration (s) Change
1437c30 128.1378 -
c5f034d 98.3810 🟢 -29.7568 (-23.22%)

@Krande

Krande commented Sep 27, 2026

Copy link
Copy Markdown
Owner

Reviewed. Both fixes are right, and the outline one is a genuinely nice piece of work — I checked
the arithmetic rather than taking the description: [v0,v1,v2,v3] → pts[::-1] → [v3,v2,v1,v0] →
np.roll(...,1) → [v0,v3,v2,v1], so the first vertex survives and the winding still flips. And it
is safe at that site specifically because the Newell loop uses (i+1) % len(pts), i.e. the list is
implicitly closed with no duplicated first point, so a roll cannot land on a repeated vertex. Fixing
it at the one choke point all three write paths share, rather than canonicalising in the reader, is
the right end of the problem.

The HP mirror is equally clearly correct: it is what isec, box_sec, channel_section and
bar_section already do, and what profile_db_collect produces. I confirmed the consequence you
describe is real — BoxSides._get_dim does max(w_btn, w_top), so w_top=None is a TypeError on
any measurement, not a cosmetic loss.

Two things before this goes in.

1. Rebase — the base is about to be squashed away

This is stacked on #399, and #407 folds #399 (with #394, #395, #396, #400, #404) into a single squash
commit. adapy is squash-only, so once #407 merges, 4ff960ca1 stops being an ancestor of main and
the shared files here (factories.py, api/cli.py, ada_cli/formats.py, test_from_gnx.py,
test_cli_convert.py) will conflict wholesale. Your own two commits from 13517ea11 are cleanly
separable, so rebasing just those onto post-#407 main should be mechanical. #399 is now closed in
favour of #407. (#408 needs the same treatment.)

2. The L asymmetry is a regression, and it is two lines from being closed properly

You pinned this rather than fixing it, on the grounds that closing it "would change unique_props()
for every L section". I'd push back: leaving it makes the round trip worse for L than it was before
this PR, and the sweep cannot see it.

Measured on this branch's reader semantics:

L150x10    w_btn=0.15   w_top=None    t_fbtn=0.01     t_ftop=None    t_w=0.01
HP180x10   w_btn=0.035  w_top=0.035   t_fbtn=0.01975  t_ftop=0.01975 t_w=0.01

So before the fix an L150x10 went (None, None) → (None, None) and unique_props() matched by
accident
. After it, (None, None) → (0.15, 0.01) and it no longer matches — the exact
comparison test_every_supported_profile_round_trips_unique_props calls "the assertion a caller
actually relies on". It stays green only because BaseTypes.ANGULAR: ["HP180x10"] contains no L, so
the sweep never sees the one shape the change moves.

The real defect is upstream, in string_to_section.angular_section's L branch, which sets
w_btn=w and t_fbtn=t_w and leaves the top slots unset. For an equal-leg angle they are equal by
definition, so filling them is the same mirror your reader now does:

sec = Section(
    in_str,
    sec_type=SectionCat.BASETYPES.ANGULAR,
    h=h,
    w_top=w,
    w_btn=w,
    t_w=t_w,
    t_ftop=t_w,
    t_fbtn=t_w,
    ...
)

I checked that this is inert for geometry: neither sections.profiles.angular nor calc_angular
reads w_top/t_ftop (they use h, w_btn, t_w, t_fbtn only), and the Abaqus section=L data
line is w_btn, h, t_fbtn, t_w. So no property, profile or deck moves — which is what your own
_property_values(read) == _property_values(src) assertion already demonstrates. It also fixes
BoxSides._get_dim for an L built from a string, which today has the same TypeError you fixed for
a read HP.

test_an_equal_leg_angle_gains_the_legs_its_string_parser_omits then becomes obsolete — its own first
assertion says so: "the L string parser now fills these; drop this test". Adding L150x10 to the
ANGULAR example list would let the sweep cover it from then on.

If you'd rather keep that out of scope, the alternative I'd accept is adding L150x10 to
get_valid_example_map_lists() and xfail-ing it with the reason, so the mismatch is visible in the
suite instead of living only in a docstring. Silent is the one option I'd avoid.

Otherwise

The self-maintaining UNSUPPORTED check that forces the list to be updated when get_section_props
gains or loses a branch is a good pattern, and the BaseTypes.CIRCULAR finding it surfaces — the
<section> dropped while section_ref is still written, leaving the file internally inconsistent —
is worth its own issue. Same for the plate-holes note: the reader handling multi-loop faces the writer
cannot produce is exactly the sort of asymmetry that looks like it works until it doesn't.

15/15 outlines broken before and 0/15 after, on real GeniE fixtures, is the number that sells this.

oleandor and others added 3 commits September 27, 2026 11:44
``from_genie_xml`` read every bulb flat back with half its flange dimensions
unset. ``HP200x10`` is written as ``<l_section h="0.2" b="0.038" tw="0.01"
tf="0.0224">`` and came back as ``w_top=None, t_ftop=None`` against a source
that has ``w_top=0.038, t_ftop=0.0224``.

The writer is not at fault and must not change: ``l_section`` with exactly
``h``/``b``/``tw``/``tf`` is what GeniE itself writes for an HP. The V8.4-06
export in ``files/fem_files/sesam/xml_all_basic_props.xml`` carries
``<section name="HP140x8"><l_section h="0.14" b="0.019" tw="0.008"
tf="0.0123" .../>`` -- four attributes, which is all the element has and all
an ANGULAR shape needs. Adding a fifth would produce a file GeniE ignores.

The reader is: ``read_sections.angular`` was the one section reader that did
not mirror its single written width/thickness onto both flange slots.
``isec``, ``box_sec``, ``channel_section`` and ``bar_section`` all do, and
``profile_db_collect`` -- which builds every HP in the profile DB -- sets
``w_top == w_btn`` and ``t_ftop == t_fbtn``. So ``angular`` now mirrors too.

Why it hid for so long: nothing in the ANGULAR geometry path reads the top
flange. ``calc_angular`` and ``sections.profiles.angular`` use only ``h``,
``w_btn``, ``t_w`` and ``t_fbtn``, so every ``GeneralProperties`` value
round-tripped exactly while ``unique_props()`` differed -- and that is the
only section comparison that survives a file boundary, ``Section.__eq__``
being guid identity.

It was not merely cosmetic. ``BoxSides._get_dim`` (bounding_box.py:220) takes
``max(section.w_btn, section.w_top)`` for a beam, so measuring any HP beam
read from a Genie XML raised ``TypeError: '>' not supported between instances
of 'NoneType' and 'float'``. It now returns ``(2.0, 0.038, 0.2)``.

Swept rather than spot-fixed: ``test_gxml_section_round_trip.py`` round-trips
every example in ``BaseTypes.get_valid_example_map_lists()`` and compares the
eight dimensions, all 20 ``GeneralProperties`` fields and ``unique_props()``.
Measured before the fix, HP was the only shape that lost anything; BOX, TUB,
I, T, UNP and FB were already exact. ``BaseTypes.CIRCULAR`` has no writer
branch at all -- the ``<section>`` is dropped while the beam's
``section_ref`` is still written, so the read fails with ``ValueError: The
section id "CIRC100" is not found``. That pre-existing gap is now pinned by
name instead of being invisible; it is not fixed here.

One asymmetry is pinned rather than closed: ``string_to_section``'s ``L``
branch leaves ``w_top``/``t_ftop`` unset where ``profile_db_collect`` fills
them, so ``L150x10`` now reads back with the equal legs it already has
(0.15/0.01) instead of ``None``. Those are the correct values and the ones
``_get_dim`` needs; the remaining inconsistency is in the string parser, not
in this reader, and closing it would change ``unique_props()`` for L sections
everywhere.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nej3bWxJevs9GkH6tg1WKv
A plate written to Genie XML read back rotated by one vertex. Measured on the
unit square: ``poly.points3d`` of ``(0,0,0) (0,1,0) (1,1,0) (1,0,0)`` came
back as ``(1,0,0) (0,0,0) (0,1,0) (1,1,0)`` -- same loop, same winding, same
normal, entered one corner later. Identical on the ``.gnx`` and plain ``.xml``
paths, and on non-convex outlines (an L and a C rotated by exactly one step
too), so not a container or a tessellation effect.

The reader is not at fault. ``PlateFactory.get_edges_from_loop`` walks the
coedge ring from ``loop.coedge`` and ``get_points`` returns it in that order;
dumping the SAT showed the loop the reader reads is exactly the loop the
writer wrote. The SAT also has room to carry the authored start, because
``plate_to_sat_entities`` already makes ``outline[0]`` the loop's first coedge
(``coedge_ids = [loop.coedge if i == 0 else ...]``). So the start vertex is
preserved here rather than canonicalised in the reader.

The writer is: ``outline_ccw_about`` rewinds the outline so the loop agrees
with the face normal (ACIS takes the material side from that) and did it with
``pts[::-1]``. Reversing a closed loop that way is the same cyclic loop
entered one step earlier -- ``[v0, v1, v2, v3]`` becomes ``[v3, v2, v1, v0]``
-- and that one step is the whole defect. ``CurvePoly2d._points_fix`` rewinds
the same way and keeps ``points[0]`` (``[points[0]] + reversed(points[1:])``),
which is why the read plate's own rewind could not undo it: it rewound about
the already-rotated start. Reversing about the first vertex instead --
``[v0, v3, v2, v1]`` -- makes the two agree and the round trip exact.

Geometrically a no-op: a rotation of a closed loop is the same loop with the
same winding and the same face, so ``winding_dots`` stays ``[1.0]`` and the
Genie reference comparisons in ``test_write_write_basic_sat.py``, which are
cyclic by design ("record numbering is positional and carries no meaning"),
are unaffected.

Covered at both levels: the writer's contract (the rewind may only rotate, and
only to the rotation that keeps ``[0]``; the emitted body's loop starts where
the outline does) and the end-to-end Genie XML round trip, over a square each
way round, a rectangle and two non-convex outlines. Winding and normal are
asserted separately from order, since a reversal would satisfy a cyclic
comparison while flipping which side of the plate is material; the C-shape
also checks enclosed area, which a scrambled order would lose.

Not covered: a plate with a hole. ``plate_to_sat_entities`` authors a single
loop and never sets ``Loop.next_loop``, so the flat-plate path cannot express
one -- the reader handles multi-loop faces (``_get_primary_loop`` picks
periphery over hole) because Genie-authored files have them, but nothing on
this writer path produces one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nej3bWxJevs9GkH6tg1WKv
…from its string and from every deck

The HP fix in the previous commit made the Genie XML reader mirror an
``l_section``'s single ``b``/``tf`` onto ``w_top``/``w_btn`` and
``t_ftop``/``t_fbtn``, as ``profile_db_collect`` builds every HP. That
exposed the same asymmetry one level up: ``string_to_section``'s ``L``
branch set only ``w_btn``/``t_fbtn``. Before the HP fix an ``L150x10``
went ``(w_top, t_ftop) = (None, None) -> (None, None)`` through Genie XML
and ``unique_props()`` matched by accident; after it, ``(None, None) ->
(0.15, 0.01)`` and it did not -- invisible to the round-trip sweep,
whose ANGULAR examples held only ``HP180x10``.

The L branch now passes ``w_top=w, w_btn=w, t_ftop=t_w, t_fbtn=t_w``.
The parser only reads equal-leg angles (``L<h>x<t>``; the leg is set
equal to ``h``), so the mirrored value is the leg the section already
has. Inert for geometry, measured for ``L150x10`` before and after:
all 20 ``GeneralProperties`` fields identical, the profile outline
identical, and the written section lines identical -- Abaqus
``section=L`` ``0.15, 0.15, 0.01, 0.01``, Sesam ``GBEAMG``/``GLSEC``,
Genie ``<l_section h="0.15" b="0.15" tw="0.01" tf="0.01">`` and the
Code_Aster ``.comm``. ``calc_angular`` and ``profiles.angular`` read
only ``h``, ``w_btn``, ``t_w``, ``t_fbtn``; the Sesam ``GLSEC`` writer
already fell back from ``w_top`` to ``w_btn``. What does move: the
fields themselves and ``unique_props()``; the IFC ``ADA_SectionParameters``
bag gains ``w_top``/``t_ftop``; the Code_Aster ``.adapy_fem.json``
sidecar writes 0.15/0.01 where it wrote null; and
``BoxSides._get_dim`` for an L beam built from its string returns
``(2.0, 0.15, 0.15)`` where it raised ``TypeError: '>' not supported
between instances of 'NoneType' and 'float'``.

The Abaqus (``section=L``) and Sesam (``GLSEC``) readers mirror too.
Both filled only the bottom slots -- the Sesam one on purpose, to match
the L string parser -- so the full suite's one failure with the parser
fix alone was ``test_every_zoo_model_round_trips_whole[elements_line_profiles]``
(``angle/profile/w_top`` and ``t_ftop`` "only in original"). Measured
before the reader change, an ``HP180x10`` also read back from either
deck as ``w_top=None, t_ftop=None``: a pre-existing mismatch for every
HP, the same one the Genie XML reader had, now closed with the L one.

Unequal-leg angles are not parsed: the pattern is ``L<number>x<number>``, unanchored
(``re.search``), so ``L150x100x10`` silently reads as ``L150x100`` --
an equal-leg angle 150 mm with a 100 mm thickness. Nothing here
mirrors a leg that is not equal; that misparse is pre-existing and
left alone.

``L150x10`` joins the ANGULAR list in ``get_valid_example_map_lists()``
so the Genie XML sweep covers it, and
``test_an_equal_leg_angle_gains_the_legs_its_string_parser_omits`` is
gone, as its own first assertion asked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nej3bWxJevs9GkH6tg1WKv
@oleandor

Copy link
Copy Markdown
Contributor Author

Thanks. Both points are done and force-pushed.

1. Rebased onto post-#407 main

Only this branch's two commits (from 13517ea11) moved onto b5cdea129 (0.92.0), and they applied cleanly with no conflicts. The shared files (factories.py, api/cli.py, ada_cli/formats.py, test_cli_convert.py) are now upstream's copies, and this branch doesn't touch them. test_from_gnx.py in #407 is byte-identical to the #399 copy the branch was stacked on, so this branch's changes to it (the two pinned notes removed, exact outline and section comparisons) sit on upstream's copy unchanged. The old tip is tagged pre-407-genie locally. The diff against main is now just the three commits: HP reader, SAT outline, and the L fix below. The "Stacked on #399" paragraph in the PR description is now out of date.

2. The L asymmetry, fixed at its source

string_to_section.angular_section's L branch now passes w_top=w, w_btn=w, t_ftop=t_w, t_fbtn=t_w, as you suggested.

Inert for geometry. I measured L150x10 before and after the change:

before after
(w_top, t_ftop) (None, None) (0.15, 0.01)
all 20 GeneralProperties fields Ax 0.0029, Iy = Iz 6.37244e-06, Iyz -3.80172e-06, ... identical
profile outline (get_section_profile) 6 points identical
Abaqus *Beam Section, section=L 0.15, 0.15, 0.01, 0.01 identical
Sesam GBEAMG + GLSEC GLSEC 1 0.15 0.01 0.15 / 0.01 1 1 1 identical
GeniE <l_section> h="0.15" b="0.15" tw="0.01" tf="0.01" identical
Code_Aster .comm (VALE = (A, IY, IZ, JX)) identical
BoxSides._get_dim() for Beam(..., "L150x10") TypeError: '>' not supported ... 'NoneType' and 'float' (2.0, 0.15, 0.15)

What did move, and why each change is correct:

  • unique_props() of every string-built L now carries the two top-flange values. That is the point of the fix.
  • The IFC ADA_SectionParameters bag now includes w_top/t_ftop, because it writes every non-None dimension. It round-trips.
  • The Code_Aster .adapy_fem.json sidecar writes 0.15/0.01 where it used to write null.
  • The Sesam GLSEC writer already fell back from w_top to w_btn, so its card doesn't change. Searching for w_top is None / t_ftop is None found only those two Sesam fallbacks (GLSEC, GCHAN). The only w_top or 0.0 is topology/run_builder.py, which is for duct/tray sections, not angles.

The Abaqus and Sesam readers mirror too. This was the one suite failure with the parser fix alone: test_every_zoo_model_round_trips_whole[elements_line_profiles] (angle/profile/w_top, t_ftop: "only in original"). The Abaqus section=L reader and the Sesam GLSEC reader filled only the bottom slots. The Sesam one did so on purpose, per its docstring, to match the old L parser. Measured before the reader change, an HP180x10 also read back from either deck as w_top=None, t_ftop=None. That is the same pre-existing HP mismatch the GeniE reader had, so the readers now mirror the one flange onto both slots too. The ANGULAR geometry still reads only the bottom slots, so no property moves. New tests/core/fem/formats/test_angular_section_round_trip.py covers {abaqus, sesam} × {HP180x10, L150x10} on unique_props().

Unequal legs. The parser doesn't support them. The pattern is L<number>x<number> and unanchored (re.search), and the leg is always set equal to h. So L150x100x10 silently parses as L150x100: an equal-leg 150 angle with a 100 mm thickness (h=0.15, w_btn=0.15, t_w=t_fbtn=0.1, Ax = 0.02). The mirror only copies a leg the parser already treats as equal, so it creates no new unequal-leg case. The misparse is pre-existing and I've left it alone. It may deserve its own issue.

Sweep and tests. L150x10 is now in BaseTypes.ANGULAR's list in get_valid_example_map_lists(), so the GeniE sweep covers it: dimensions, all properties and unique_props(). test_an_equal_leg_angle_gains_the_legs_its_string_parser_omits is deleted. test_an_equal_leg_angle_carries_its_leg_in_both_flange_slots pins the fields and the _get_dim result for an L built from its string.

Mutations: 9, all caught (targeted set: 133 tests, green baseline asserted before and after)

mutation failures
L mirror removed (both slots) 6: sweep L150x10 dims + unique_props, _get_dim test, abaqus/sesam L round trip, Abaqus zoo
L: only w_top filled 6 (same set)
L: only t_ftop filled 6 (same set)
Abaqus reader mirror removed / only w_top 3 each: abaqus HP + L, zoo
Sesam reader mirror removed / only w_top 2 each: sesam HP + L
GeniE reader HP mirror removed (re-run after rebase) 6, now including the sweep's L150x10
SAT rewind back to pts[::-1] (re-run after rebase) 17

tests/core/: 4534 passed, 22 skipped, 16 xfailed, 0 failed, 0 errors. black (120), isort and ruff are clean.

Split out as issues


🤖 Generated with Claude Code

https://claude.ai/code/session_01Nej3bWxJevs9GkH6tg1WKv

@Krande
Krande merged commit 53b6b9d into Krande:main Sep 27, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants