Skip to content

Fix surface load overlap: apply all loads even when they share elements - #420

Merged
mkofler96 merged 2 commits into
mainfrom
claude/kof-216-3lyulx
Aug 7, 2026
Merged

Fix surface load overlap: apply all loads even when they share elements#420
mkofler96 merged 2 commits into
mainfrom
claude/kof-216-3lyulx

Conversation

@mkofler96

Copy link
Copy Markdown
Owner

Summary

Fixes KOF-216: surface loads that overlap (share boundary elements) were silently dropped. The engine's apply_surface_loads assigned each load a unique boundary attribute and tagged its elements in load order. Since a boundary element carries exactly one attribute, a later load's tag overwrote an earlier one's, causing the overwritten load to integrate over zero area and vanish without warning.

Example: two equal forces on the same flange face produced the deflection of one, with no error message.

Key Changes

engine/cpp/solve_mfem.cpp

  • Replaced per-load attributes with a per-covering-set scheme: boundary elements are grouped by the set of loads covering them, each distinct set gets one attribute, and a load's marker selects every attribute whose set contains it.
  • Refactored tag_load_facescollect_load_elements: returns the sorted, de-duplicated list of boundary elements a load covers, without tagging them yet.
  • New loads_of_be map tracks which loads cover each boundary element.
  • New attr_of_set map assigns one attribute per distinct covering set.
  • Loads now integrate over a marker (set of attributes) rather than a single attribute, via integrate_marked_area (renamed from integrate_tagged_area).
  • Diagnostic message when loads overlap on boundary elements.

web/src/store/boundarySlice.ts (rebuildSurfaceLoads)

  • Changed from one surface load per picked face selection to one per load group.
  • A group's total force is now shared over all its selections (the union of their element faces), not re-applied to each selection.
  • Selections that overlap within a group contribute their shared element faces once.
  • Updated comments to clarify the per-group model and the fix for KOF-216.

web/src/workers/solver.worker.ts (distributeShellSurfaceLoad)

  • Added validation: a single load group cannot select both faces and edges, since there is no physical rule for splitting one total force between an area and a length.
  • Added error message explaining the constraint and the workaround (separate load groups).

examples/validation/multiple-loads.test.mjs (new)

  • Regression test for KOF-216: validates that multiple surface loads on a cantilever box all act, even when they overlap.
  • Four checks: disjoint loads superpose, coincident loads both act, partially overlapping loads both act, and prescribed displacements drive the model without loads.
  • Uses L2 norm of displacement field differences to catch dropped loads anywhere in the model.

web/tests/test_multi_load.mjs (new)

  • Unit test for the store's rebuildSurfaceLoads function.
  • Validates that a group's total force is not multiplied by its selection count.
  • Checks that overlapping selections within a group are de-duplicated.
  • Verifies that multiple groups stay multiple loads.

examples/web-examples/generate-crane-shell.mjs

  • Updated to store a load group's total force (sum of per-face vectors) rather than the per-face vector, matching the fixed rebuildSurfaceLoads behavior.
  • Added comment explaining the change and why the old approach would now reload the example at half load.

Notable Implementation Details

  • Attribute scheme: The old approach (one attribute per load) cannot express overlaps because a boundary element has exactly one attribute. The new scheme groups elements by their covering set and assigns attributes to sets, allowing a load to span multiple attributes.
  • Marker arrays: Each load now gets a marker (array of 0/1 flags) indicating which attributes it covers, rather than a single attribute number. The integrator uses the marker to sum contributions from all relevant attributes.
  • De-duplication: collect_load_elements sorts and de-duplicates the boundary elements a load covers, so overlapping selections within a group do not double-count shared faces.
  • Per-group loads: The store now emits one surface load per group (not per selection), with the group's total force and the union of all its selections' element faces. This matches the engine's expectation and the linear-superposition semantics of the solver.

https://claude.ai/code/session_01MZrBVJ5aP53sqx3UBoFReo

Three defects made a model with more than one load solve the wrong
problem, none of them with a word in the log.

A load group's force vector is its TOTAL, but rebuildSurfaceLoads emitted
one surface load per picked SELECTION, each carrying that full vector. A
1000 N load picked as three faces reached the engine as three 1000 N
loads and pulled 3000 N, while the panel still said 1000 N. It now emits
one load per group over the union of its selections — de-duplicated, so
overlapping picks contribute their shared element faces once — and the
engine spreads the total over the whole loaded region, the same
total-sharing rule rebuildLoads already applied to a group's nodal
selections.

apply_surface_loads gave each load its own boundary attribute and tagged
its elements in load order. A boundary element carries exactly one
attribute, so wherever two loads covered the same element the later one
overwrote the earlier one's tag, and the overwritten load then integrated
over nothing and vanished: a pressure and a bolt force on one flange face
produced the effect of whichever came last. Elements are now grouped by
the SET of loads covering them, each distinct set gets one attribute, and
a load's marker selects every attribute whose set contains it.

The coupled shell/solid path pinned a non-zero prescribed displacement to
zero — shell_core's fixed_dofs are homogeneous and it has no
inhomogeneous counterpart — turning a displacement-driven model into an
unloaded one that returns an all-zero field looking like a converged
answer. It now refuses loudly and names the node, as the explicit-shell
path already did. A load selecting both faces and edges is refused the
same way: one total force has no meaningful split between a loaded area
and a loaded edge.

The crane-hook-shell example stored its per-face vector as the group's
force and so shipped at twice its intended load; its generator now stores
the sum, and the example is regenerated.

Tests: multiple-loads.test.mjs drives the engine (disjoint, coincident,
partially overlapping and force+pressure loads, checked against
superposition, plus three Dirichlet groups with no load at all);
test_multi_load.mjs pins the store payload. Both fail on the previous
build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZrBVJ5aP53sqx3UBoFReo
@deepsource-io

deepsource-io Bot commented Aug 6, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 1d1c371...b283a65 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Coverage  

Code Review Summary

Analyzer Status Updated (UTC) Details
Code coverage Aug 6, 2026 5:16a.m. Review ↗
Rust Aug 6, 2026 5:16a.m. Review ↗
JavaScript Aug 6, 2026 5:16a.m. Review ↗
C & C++ Aug 6, 2026 5:16a.m. Review ↗

Code Coverage Summary

Language Line Coverage (New Code) Line Coverage (Overall)
Aggregate
86.7%
87.8%
Javascript
86.7%
87.8%

➟ Additional coverage metrics may have been reported. See full coverage report ↗


Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

Comment thread web/src/workers/solver.worker.ts Outdated
Comment thread web/tests/test_multi_load.mjs Outdated
Two DeepSource antipattern findings on the KOF-216 diff, both in strings:
a template literal with nothing interpolated in coupledFixedDofs' refusal
message, and a `+` concatenation inside the test helper's template. The
assembled strings are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZrBVJ5aP53sqx3UBoFReo
@mkofler96
mkofler96 merged commit 303ba07 into main Aug 7, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants