+
Skip to content

Add Boundary.plot() and fix Boundary(..., mode="and") #913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ All notable changes to this project will be documented in this file. The format
- Add `SolidBody.assemble.mass(density=1.0)` and `SolidBodyNearlyIncompressible.assemble.mass(density=1.0)` to assemble the mass matrix.
- Add `SolidBody.evaluate.stress(field)` to evaluate the (first Piola-Kirchhoff) stress tensor (engineering stress in linear elasticity).
- Add a free-vibration modal analysis Step/Job `FreeVibration(items, boundaries)` with methods to evaluate `FreeVibration.evaluate()` and to extract `field, frequency = FreeVibration.extract(n)` its n-th result.
- Add `Boundary.plot()` to plot the points and prescribed directions of a boundary.

### Changed
- The first Piola-Kirchhoff stress tensor is evaluated if `ViewSolid(stress_type=None)`.
- Autodetect the stress-type in `SolidBody.plot(name)` from `name`.
- Enhance the `hello_world(axisymmetric=False, planestrain=False, curve=False, xdmf=False, container=False)` function with new arguments to customize the generated template script.
- Enhance `Boundary` with added support for multiaxial prescribed values.

### Fixed
- Fix `Boundary(..., mode="and")` by ignoring any undefined axis.

## [9.1.0] - 2024-11-23

### Added
Expand Down
4 changes: 4 additions & 0 deletions docs/tutorial/examples/extut03_building_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ def W(C, mu, bulk):
boundaries["right"] = fem.Boundary(displacement, fx=f1, skip=(1, 0, 0))
boundaries["move"] = fem.Boundary(displacement, fx=f1, skip=(0, 1, 1), value=0.5)

plotter = boundaries["left"].plot(color="green")
plotter = boundaries["right"].plot(plotter=plotter, color="red")
boundaries["move"].plot(plotter=plotter, color="blue", point_size=5).show()

# %%
# Partition of deegrees of freedom
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
108 changes: 52 additions & 56 deletions src/felupe/dof/_boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,10 @@ class Boundary:
:context:
:force_static:

>>> import pyvista as pv
>>>
>>> right = fem.Boundary(displacement, fx=x.max())
>>> right = fem.Boundary(displacement, fx=lambda x: np.isclose(x, x.max()))
>>>
>>> plotter = pv.Plotter()
>>> actor = plotter.add_points(
... mesh.points[right.points],
... point_size=20,
... color="red",
... )
>>> mesh.plot(plotter=plotter, opacity=0.7).show()
>>> right.plot(color="red", plotter=mesh.plot(opacity=0.7)).show()

If ``fx`` and ``fy`` are given, the masks are combined by *logical-or*.

Expand All @@ -112,14 +104,7 @@ class Boundary:
:force_static:

>>> axes = fem.Boundary(displacement, fx=0, fy=0, mode="or")
>>>
>>> plotter = pv.Plotter()
>>> actor = plotter.add_points(
... mesh.points[axes.points],
... point_size=20,
... color="red",
... )
>>> mesh.plot(plotter=plotter, opacity=0.7).show()
>>> axes.plot(color="red", plotter=mesh.plot(opacity=0.7)).show()

This may be changed to *logical-and* if desired.

Expand All @@ -128,14 +113,7 @@ class Boundary:
:force_static:

>>> center = fem.Boundary(displacement, fx=0, fy=0, mode="and")
>>>
>>> plotter = pv.Plotter()
>>> actor = plotter.add_points(
... mesh.points[center.points],
... point_size=20,
... color="red",
... )
>>> mesh.plot(plotter=plotter, opacity=0.7).show()
>>> center.plot(color="red", plotter=mesh.plot(opacity=0.7)).show()

For the most-general case, a user-defined boolean mask for the selection of the
mesh-points is provided. While the two upper methods are useful to select
Expand All @@ -149,13 +127,7 @@ class Boundary:
>>> mask = np.logical_and(np.isclose(x**2 + y**2, 1), x >= 0)
>>> surface = fem.Boundary(displacement, mask=mask)
>>>
>>> plotter = pv.Plotter()
>>> actor = plotter.add_points(
... mesh.points[surface.points],
... point_size=20,
... color="red",
... )
>>> mesh.plot(plotter=plotter, opacity=0.7).show()
>>> surface.plot(color="red", plotter=mesh.plot(opacity=0.7)).show()

The application of a new mask allows to change the selected points of an existing
boundary condition.
Expand All @@ -167,13 +139,7 @@ class Boundary:
>>> new_mask = np.logical_and(mask, y <= 0)
>>> surface.apply_mask(new_mask)
>>>
>>> plotter = pv.Plotter()
>>> actor = plotter.add_points(
... mesh.points[surface.points],
... point_size=20,
... color="red",
... )
>>> mesh.plot(plotter=plotter, opacity=0.7).show()
>>> surface.plot(color="red", plotter=mesh.plot(opacity=0.7)).show()

A boundary condition may be skipped on given axes, i.e. if only the x-components
of a field should be prescribed on the selected points, then the y-axis must
Expand All @@ -184,14 +150,7 @@ class Boundary:
:force_static:

>>> axes_x = fem.Boundary(displacement, fx=0, fy=0, skip=(False, True))
>>>
>>> plotter = pv.Plotter()
>>> actor = plotter.add_points(
... mesh.points[axes_x.points],
... point_size=20,
... color="red",
... )
>>> mesh.plot(plotter=plotter, opacity=0.7).show()
>>> axes_x.plot(color="red", plotter=mesh.plot(opacity=0.7)).show()

Values for the prescribed degress of freedom are either applied during creation
or by the update-method.
Expand All @@ -203,13 +162,7 @@ class Boundary:
>>> left = fem.Boundary(displacement, fx=x.min(), value=-0.2)
>>> left.update(-0.3)
>>>
>>> plotter = pv.Plotter()
>>> actor = plotter.add_points(
... mesh.points[left.points],
... point_size=20,
... color="red",
... )
>>> mesh.plot(plotter=plotter, opacity=0.7).show()
>>> left.plot(color="red", plotter=mesh.plot(opacity=0.7)).show()

Sometimes it is useful to create a boundary with all axes skipped. This
boundary has no prescribed degrees of freedom and hence, is without effect.
Expand Down Expand Up @@ -265,7 +218,7 @@ def __init__(

# select the logical combination function "or" or "and"
combine = {"or": np.logical_or, "and": np.logical_and}[self.mode]
mask = combine.reduce(masks)
mask = combine.reduce(np.array(masks)[[f != np.isnan for f in self.fun]])

self.apply_mask(mask)

Expand Down Expand Up @@ -307,4 +260,47 @@ def apply_mask(self, mask):
def update(self, value):
"Update the value of the boundary in-place."

self.value = value
self.value = value #

def plot(
self, plotter=None, color="black", scale=0.125, point_size=10, width=3, **kwargs
):
"Plot the points and their prescribed directions of a boundary condition."

mesh = self.field.region.mesh

if plotter is None:
plotter = mesh.plot()

if self.dim > 1 and self.dim != mesh.dim:
raise ValueError(
" ".join(
[
"Plotting is not supported.",
"Field and Mesh must have equal dimensions.",
]
)
)

if len(self.points) > 0:
magnitude = min(mesh.points.max(axis=0) - mesh.points.min(axis=0)) * scale

_ = plotter.add_points(
mesh.points[self.points],
color=color,
point_size=point_size,
label=self.name,
)

for skip, direction in zip(self.skip, np.eye(mesh.dim)):
if not skip:
end = mesh.points[self.points] + direction * magnitude
_ = plotter.add_lines(
np.hstack([mesh.points[self.points], end]).reshape(
-1, mesh.dim
),
color=color,
width=width,
)

return plotter
19 changes: 19 additions & 0 deletions tests/test_dof.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,26 @@ def test_boundary_multiaxial():
assert ext0.shape == dof0.shape


def test_boundary_plot():
region = fem.RegionHexahedron(fem.Cube(b=(3, 1, 1), n=2))
field = fem.Field(region, dim=3).as_container()
boundaries = dict(
left=fem.Boundary(field[0], fx=0, skip=(0, 1, 0)),
right=fem.Boundary(field[0], fx=1, skip=(0, 0, 0)),
)
_ = boundaries["left"].plot(plotter=boundaries["right"].plot())

field = fem.Field(region, dim=2).as_container()
boundaries = dict(
left=fem.Boundary(field[0], fx=0, skip=(0, 1, 0)),
)

with pytest.raises(ValueError):
_ = boundaries["left"].plot()


if __name__ == "__main__":
test_boundary()
test_boundary_multiaxial()
test_boundary_plot()
test_loadcase()
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载