Skip to content

feat(beams3d): add structural 3d beam problem#257

Open
Vicus4 wants to merge 5 commits into
IDEALLab:mainfrom
Vicus4:beams3d-clean
Open

feat(beams3d): add structural 3d beam problem#257
Vicus4 wants to merge 5 commits into
IDEALLab:mainfrom
Vicus4:beams3d-clean

Conversation

@Vicus4

@Vicus4 Vicus4 commented Jun 19, 2026

Copy link
Copy Markdown

Description

Add the structural-only Beams3D v0 problem, aligned with Beams2D's public API
and Thermoelastic3D's 3D FEM structure.

This PR introduces:

  • engibench.problems.beams3d.Beams3D
  • a structural-only 3D FEM topology optimization workflow
  • a single public objective, c, matching the dataset compliance column
  • compact dataset-facing conditions: volfrac, rmin, forcedist_x,
    and forcedist_y
  • Beams3D documentation

The implementation keeps explicit FEM masks and solver parameters in Config,
while the public Conditions match the dataset schema. The helper
_force_elements_z_from_forcedist() reconstructs the vertical top-face load
mask from compact dataset coordinates, and _boundary_conditions() centralizes
per-call config merging for simulation and optimization.

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Screenshots

Beams3D render output:

Beams3D
beams3d_size64_vf0p3_fx0p5_fy0p5_full_figure_20260617_182436

Checklist:

  • I have run the pre-commit checks with pre-commit run --all-files
  • I have run ruff check . and ruff format
  • I have run mypy .
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Reviewer Checklist:

  • The content of this PR brings value to the community. It is not too specific to a particular use case.
  • The tests and checks pass (linting, formatting, type checking). For a new problem, double check the github actions workflow to ensure the problem is being tested.
  • The documentation is updated.
  • The code is understandable and commented. No large code blocks are left unexplained, no huge file. Can I read and understand the code easily?
  • There is no merge conflict.
  • The changes are not breaking the existing results (datasets, training curves, etc.). If they do, is there a good reason for it? And is the associated problem version bumped?
  • For a new problem, has the dataset been generated with our slurm script so we can re-generate it if needed? (This also ensures that the problem is running on the HPC.)
  • For bugfixes, it is a robust fix and not a hacky workaround.

Comment on lines +28 to +34
for dx in range(-rceil, rceil + 1):
for dy in range(-rceil, rceil + 1):
for dz in range(-rceil, rceil + 1):
dist = (dx * dx + dy * dy + dz * dz) ** 0.5
weight = rmin - dist
if weight > 0.0:
offsets.append((dx, dy, dz, weight))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about numpy.meshgrid here, together with numpy.linalg.norm?

"""Matrix-free cone-filter data."""

offsets: tuple[tuple[int, int, int, float], ...]
hs: np.ndarray

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
hs: np.ndarray
hs: npt.NDArray[np.float64]

where npt = numpy.typing

def _apply_filter(
values: np.ndarray,
sensitivity_filter: SensitivityFilter3D,
) -> np.ndarray:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

fixed_elements = np.transpose(fixed_elements, (2, 1, 0))
force_elements = np.transpose(force_elements, (2, 1, 0))

import napari # noqa: PLC0415

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why dont you want that to be top level imported?
Is napari an optional dependency?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, related to my comment on #260 do we even need napari? Could we visualize/render this with existing imports?

return x


def solve_with_spsolve(a: csr_matrix, b: NDArray[np.float64]) -> NDArray[np.float64]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need a wrapper?

asymax = 0.2
asymin = 0.01

xmma, _, _, _, _, _, _, _, _, low, up = external_mmasub(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what also should work:

xmma, *_, low, up = external_mmasub(

However, I am not sure if we want to be notified by an error if external_mmasub changes the number of arguments (in this case we should keep it like it is).


def __init__(self, seed: int = 0, config: dict[str, Any] | None = None) -> None:
"""Initialize the Beams3D problem, sizing default masks and design space to the grid."""
config = {key: (np.asarray(value) if isinstance(value, list) else value) for key, value in (config or {}).items()}

@g-braeunlich g-braeunlich Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing: default value handling is now duplicated here from (Config also provides default values).
You could use Config here:

        raw_config: dict[str, Any] = {
            key: (np.asarray(value) if isinstance(value, list) else value) for key, value in (config or {}).items()
        }
        self.config = self.Config(**raw_config)

And then optionally adapt the default values of Config to your needs.

@arthurdrake1

Copy link
Copy Markdown
Contributor

@g-braeunlich I just created a new branch where I addressed all your comments except the last one regarding Config. I will leave you to decide whether we should change this or not.

The summary for the remaining comments in order:

  • Implemented the numpy.meshgrid approach.
  • Replaced np.ndarray with npt.NDArray[np.float64]
  • Removed napari and related debug functionality completely (e.g., model/fem_plotting.py)
  • Removed the spsolve wrapper
  • Kept explicit unpacking for external_mmasub so it errors clearly if changed in the future
  • As mentioned I did not change the config default handling yet. If you have a cleaner full solution for this, we can go ahead and incorporate it

@g-braeunlich

Copy link
Copy Markdown
Collaborator

I would definitely address the config handling, as this would be technical dept

@g-braeunlich

Copy link
Copy Markdown
Collaborator

And why did you actually create an extra branch?
In the end it is going to be this PR (or ideally should be this PR) which will be merged.
Distributing changes over multiple branches also makes reviewing harder / error prone (if I now approve this PR, but the actual changes are in a different branch and you just press merge, we wont get what we (or at least I) expect.

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.

4 participants