Skip to content

fix(fem): write a Sesam superelement Presel and Sestra accept - #374

Merged
Krande merged 2 commits into
Krande:mainfrom
oleandor:fix/sesam-export-correctness
Sep 22, 2026
Merged

Krande merged 2 commits into
Krande:mainfrom
oleandor:fix/sesam-export-correctness

Conversation

@oleandor

Copy link
Copy Markdown
Contributor

An Abaqus-derived superelement could not be read by Presel, and once read could not be reduced by Sestra. This is six defects in the Sesam writer, each checked against the Input Interface File description and then against the programs themselves.

The model: 689,596 nodes, 463,192 elements — 460,918 quadratic tetrahedra, 2,273 shells, one beam, joined by 8,910 BLDEP relations, with a 58-node external interface.

The defects

Element record order. GELMNT1/GELREF1 were emitted grouped by element block, so internal numbers jumped backwards and Presel stopped at ELEMENT 15 INTERNAL ALREADY EXISTS. Both blocks now come out in increasing internal element number (printed 6-65, 6-68). Ids are sorted, never renumbered.

GUNIVEC on non-beams. A transformation was created for every sectioned element and copied into GELREF1 TRANSNO, so shells and solids referenced a GUNIVEC and Presel rejected them with TRANSFORMATION NUMBER 1 DOES NOT EXSIST. GUNIVEC is defined for beam types 2, 15 and 23 only (printed 6-92). Everything else now writes TRANSNO = 0 — default element axes, printed 6-67. Worth knowing: ada's own mesher sets local_z on every shell section, so this fired on all 463k elements.

No BNBCD companions for BLDEP. BLDEP is only valid alongside boundary codes (printed 6-27). Dependent DOFs now get code 3 and every master node an explicit all-free record.

Masters are deliberately not retained, which is where this departs from the obvious reading of the manual. Printed 6-27 says the independent node's DOFs should be "retained (4)" — but code 4 is the definition of a supernode (printed 6-30), and following that literally turned 743 internal coupling masters into unconnected supernodes in Presel (801 retained nodes where 58 were intended). DNV's own GeniE V9.2-01 settles it: in a 128k-element file with 169 BLDEP records, all 52 masters carry 0 0 0 0 0 0 and the only code-4 nodes are the assembly interface, which is neither master nor dependent.

No way to declare the external interface. Retained DOFs can now be declared explicitly —

to_fem(..., metadata={"sesam_retained_dofs": {"INTERFACE": [1, 2, 3, 4, 5, 6]}})

— or by convention, through a node set named SESAM_SUPERNODES, so a plain "read an Abaqus INP, write a Sesam FEM" conversion needs no extra argument. The explicit key overrides the convention outright rather than merging with it: naming one set must not silently retain another. The reader brings code 4 back as that same set, so the round trip closes; a partial per-DOF pattern widens to the whole node, with a warning, because a node set cannot carry one.

Every node declared with six DOFs. A solid-only node has three — printed 5-91: "solid type: NDOF=3, ODOF=123". Writing six left three zero-stiffness rotations on ~687,000 nodes, and Sestra's reduction failed with:

Error in factorisation of Stiffness matrix.
Matrix is not positive definite.
The error may be related to External Node no:  278687
Degree of freedom no:   4

DOF 4 is the first rotation, and that node is used by five elements, all tetrahedra. NDOF is now derived per node from the elements attached to it, and GNODE, BNBCD, BNMASS and BNLOAD all follow it. A BC, load, mass or BLDEP term naming a DOF the node does not have raises instead of writing an invalid record. On the model above the split is 687,072 three-DOF nodes and 2,524 six-DOF nodes, with no overlap.

BELFIX wrote OPT = 3, which is not a defined value. The record's own data is the 0..1 degree of fixation that OPT = 1 describes (printed 6-8).

Two smaller things in the same area

The nearest-master pairing for a shell-to-solid coupling was order-dependent: 313 of 8,910 slaves flipped between two equally valid orderings of the same master set, every one an exact distance tie (|Δdistance| = 0.000e+00). It now breaks ties on the lowest node id, so the deck is reproducible.

Skipped elements (connectors, unsectioned elements, springs) leave holes in the internal element numbering. That is unchanged behaviour, but it is now reported in one warning alongside the count of what was skipped, since a reader assuming contiguous numbering will mis-associate results.

Also removes a dead multi-part branch in the writer: it called concatenate_fem_to_single_part, discarded the returned merged part, and re-listed the assembly's own parts — which the non-destructive merge had left untouched — so it fell straight into DoesNotSupportMultiPart. fem.formats.general already merges properly before the writer is reached. Behaviour is identical; only the misleading code goes.

Validation

  • Presel 8.0-01 read all 463,085 elements of the earlier revision and matched 58 supernodes against the global model, 0 unmatched.
  • Sestra completed the reduction without error.
  • 572 tests pass in tests/core/fem on current main. The 5 failures in that directory are pre-existing: import freetype raises RuntimeError: Freetype library not found in this environment, in image-rendering tests unrelated to this change.
  • black, ruff and isort clean.

Every manual citation refers to the Sesam Input Interface File description, 01-NOV-1996, by its printed page number.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Nej3bWxJevs9GkH6tg1WKv

An Abaqus-derived superelement (689,596 nodes, 463,192 elements: quadratic
tetrahedra, shells and one beam) could not be read by Presel, and once read
could not be reduced by Sestra. Six defects in the Sesam writer, each confirmed
against the Input Interface File description and then against the programs
themselves.

* **Element record order.** GELMNT1/GELREF1 were emitted grouped by element
  block, so internal numbers jumped backwards and Presel stopped at
  `ELEMENT n INTERNAL ALREADY EXISTS`. Both blocks are now emitted in increasing
  internal element number (printed 6-65, 6-68). Ids are sorted, never renumbered.

* **GUNIVEC on non-beams.** A transformation was created for every sectioned
  element and copied into GELREF1 TRANSNO, so shells and solids referenced a
  GUNIVEC and Presel rejected them with
  `TRANSFORMATION NUMBER 1 DOES NOT EXSIST`. GUNIVEC is defined for beam types
  2, 15 and 23 only (printed 6-92); everything else now writes TRANSNO = 0,
  default element axes (printed 6-67). A `transno` a caller placed on a non-beam
  element itself (a BNTRCOS reference) is passed through untouched.

* **No BNBCD companions for BLDEP.** BLDEP is only valid alongside boundary
  codes (printed 6-27). Dependent DOFs now get code 3 and every master node an
  explicit all-free record. Masters are deliberately *not* retained: code 4
  makes a node a supernode (printed 6-30), and marking masters retained turned
  743 internal coupling nodes into unconnected supernodes in Presel. DNV's own
  GeniE (V9.2-01) writes every one of its BLDEP masters as `0 0 0 0 0 0` and
  reserves code 4 for the assembly interface, which settles what the manual's
  wording leaves ambiguous.

* **No way to declare the external interface.** Retained DOFs are now declared
  either explicitly, `to_fem(..., metadata={"sesam_retained_dofs": {...}})`, or
  by convention through a node set named `SESAM_SUPERNODES`, so a plain "read an
  Abaqus INP, write a Sesam FEM" conversion needs no extra argument. The
  explicit key overrides the convention outright rather than merging with it.
  The reader brings code 4 back as that same set, so the round trip closes.

* **Every node declared with six DOFs.** A solid-only node has three (printed
  5-91: "solid type: NDOF=3, ODOF=123"). Writing six left three zero-stiffness
  rotations on ~687,000 nodes and Sestra's reduction failed with
  `Matrix is not positive definite` on the first one it reached. NDOF is now
  derived per node from the elements attached to it, and GNODE, BNBCD, BNMASS
  and BNLOAD all follow it; a BC, load, mass or BLDEP term naming a DOF the node
  does not have raises instead of writing an invalid record.

* **BELFIX wrote OPT = 3**, which is not a defined value; the record's own data
  is the 0..1 degree of fixation OPT = 1 describes (printed 6-8).

Two smaller things in the same area: the nearest-master pairing for a
shell-to-solid coupling was order-dependent (313 of 8,910 slaves flipped between
two equally valid orderings of the same master set, every one an exact distance
tie) and now breaks ties on the lowest node id; and skipped elements leave holes
in the internal element numbering, which is now reported in one warning
alongside the count of what was skipped.

Also removes a dead multi-part branch in the writer: it called
`concatenate_fem_to_single_part`, discarded the returned merged part and re-listed
the assembly's own parts, which the non-destructive merge had left untouched, so
it fell straight into `DoesNotSupportMultiPart`. `fem.formats.general` already
merges properly before the writer is reached.

Validated on the model above: Presel read all 463,085 elements of the earlier
revision and matched 58 supernodes against the global model, and Sestra completed
the reduction without error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nej3bWxJevs9GkH6tg1WKv
@github-actions

github-actions Bot commented Sep 22, 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.81.1"

@github-actions

github-actions Bot commented Sep 22, 2026

Copy link
Copy Markdown

🚀 Profiling Results (Top 20 most expensive calls)

Function Calls Duration (s)
<built-in method builtins.exec> (~:0) 2016 22.2878
<module> (<string>:1) 1 22.2878
test_bench_batch_tessellate (tests/profiling/test_cad_backend_bench.py:63) 1 6.6405
run (tests/profiling/test_cad_backend_bench.py:73) 6 6.3947
batch_tessellate (src/ada/visit/tessellate.py:1102) 1446 6.3937
test_build_big_ifc_pipe (tests/profiling/test_ifc_creation.py:48) 1 6.0529
tessellate_geom (src/ada/visit/tessellate.py:830) 1440 5.7953
__truediv__ (src/ada/api/spatial/part.py:2042) 8 5.6961
tessellate_occ_geom (src/ada/visit/tessellate.py:684) 1440 5.2320
tessellate_shape (src/ada/visit/tessellate.py:554) 1440 5.2171
to_ifc (src/ada/api/spatial/assembly.py:292) 5 5.0367
sync (src/ada/cadit/ifc/store.py:200) 5 4.6320
sync_added_physical_objects (src/ada/cadit/ifc/write/write_ifc.py:92) 5 4.5736
add (src/ada/cadit/ifc/write/write_ifc.py:582) 2200 4.4925
tessellate (src/ada/cad/__init__.py:1163) 1440 4.2287
add_object (src/ada/api/spatial/part.py:309) 1201 3.9994
add_pipe (src/ada/api/spatial/part.py:164) 200 3.9809
add_section (src/ada/api/spatial/part.py:294) 801 3.2979
add (src/ada/api/containers/sections.py:139) 805 3.2966
equal_props (src/ada/sections/concept.py:100) 241399 2.9957

@Krande
Krande merged commit 02d7440 into Krande:main Sep 22, 2026
12 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