# -*- coding: utf-8 -*-
"""
This file is part of FElupe.
FElupe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FElupe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FElupe. If not, see <http://www.gnu.org/licenses/>.
"""
import numpy as np
from ..tools import force
from ._plugin import Plugin
[ドキュメント]
class CharacteristicCurvePlugin(Plugin):
r"""A job plugin to record a characteristic curve. Force-displacement curve data is
tracked during evaluation for a given :class:`~felupe.Boundary`.
Parameters
----------
boundary : felupe.Boundary
The boundary condition for which to record the characteristic curve.
items : list of SolidBody, SolidBodyNearlyIncompressible, SolidBodyPressure, SolidBodyGravity, PointLoad, MultiPointConstraint, MultiPointContact or None, optional
A list of items with methods for the assembly of sparse vectors/matrices which
are used to evaluate the sum of reaction forces. If None, the total reaction
forces from the :class:`~felupe.tools.NewtonResult` of the substep are used.
Examples
--------
.. pyvista-plot::
:context:
:force_static:
>>> import felupe as fem
>>>
>>> mesh = fem.Cube(n=6)
>>> region = fem.RegionHexahedron(mesh)
>>> field = fem.FieldContainer([fem.Field(region, dim=3)])
>>>
>>> boundaries = dict()
>>> boundaries["fixed"] = fem.Boundary(field[0], fx=0, skip=(False, False, False))
>>> boundaries["clamped"] = fem.Boundary(field[0], fx=1, skip=(True, False, False))
>>> boundaries["move"] = fem.Boundary(field[0], fx=1, skip=(False, True, True))
>>>
>>> umat = fem.NeoHooke(mu=1, bulk=2)
>>> solid = fem.SolidBody(umat, field)
>>>
>>> move = fem.math.linsteps([0, 1], num=5)
>>> step = fem.Step(items=[solid], ramp={boundaries["move"]: move}, boundaries=boundaries)
>>>
>>> curve = fem.CharacteristicCurvePlugin(boundary=boundaries["move"])
>>> job = fem.Job(steps=[step], plugins=[curve]).evaluate()
>>> solid.plot("Principal Values of Cauchy Stress").show()
>>> fig, ax = curve.plot(
... xlabel=r"Displacement $u_1$ in mm $\rightarrow$",
... ylabel=r"Normal Force in $F_1$ in N $\rightarrow$",
... marker="o",
... )
.. pyvista-plot::
:include-source: False
:context:
:force_static:
>>> import pyvista as pv
>>>
>>> fig = ax.get_figure()
>>> chart = pv.ChartMPL(fig)
>>> chart.show()
See Also
--------
felupe.Job : A job with a list of steps and a method to evaluate them.
"""
def __init__(
self,
boundary,
items=None,
):
self.items = items
self.boundary = boundary
self.x = []
self.y = []
self.res = None
[ドキュメント]
def after_substep(self, context, state):
if self.items is not None:
fun = sum([item.results.force for item in self.items])
else:
fun = context.substep.fun
self.x.append(context.substep.x[0].values[self.boundary.points[0]])
self.y.append(force(context.substep.x, fun, self.boundary))
self.res = context.substep
[ドキュメント]
def plot(
self,
x=None,
y=None,
xaxis=0,
yaxis=0,
xlabel=None,
ylabel=None,
xscale=1.0,
yscale=1.0,
xoffset=0.0,
yoffset=0.0,
gradient=False,
swapaxes=False,
ax=None,
items=None,
**kwargs,
):
"""Plot force-displacement characteristic curves on a pre-evaluated job,
tracked on a given :class:`~felupe.Boundary`.
Parameters
----------
x : list of ndarray or None, optional
A list with arrays of displacement data. If None, the displacement is taken
from the first field of the field container from each completed substep. The
displacement data is then taken from the first point of the tracked
:class:`~felupe.Boundary`. Default is None.
y : list of ndarray or None, optional
A list with arrays of reaction force data. If None, the force is taken
from the :class:`~felupe.tools.NewtonResult` of each completed substep.
Default is None.
xaxis : int, optional
The axis for the displacement data (default is 0).
yaxis : int, optional
The axis for the reaction force data (default is 0).
xlabel : str or None, optional
The label of the x-axis (default is None).
ylabel : str or None, optional
The label of the y-axis (default is None).
xscale : float, optional
A scaling factor for the displacement data (default is 1.0).
yscale : float, optional
A scaling factor the reaction force data (default is 1.0).
xoffset : float, optional
An offset for the displacement data (default is 0.0).
yoffset : float, optional
An offset for the reaction force data (default is 0.0).
gradient : bool, optional
A flag to plot the gradient of the y-data. Uses
``numpy.gradient(edge_order=2)``. The gradient data is set to ``np.nan`` for
absolute values greater than the mean value plus two times the standard
deviation. Default is False.
swapaxes : bool, optional
A flag to flip the plot (x, y) to (y, x). Also changes the labels.
ax : matplotlib.axes.Axes
An axes object where the plot is placed in.
items : slice, ndarray or None
Indices or a range of data points to plot. If None, all data points are
plotted (default is None).
**kwargs : dict
Additional keyword arguments for plotting in ``ax.plot(**kwags)``.
Returns
-------
fig : matplotlib.figure.Figure
The figure object where the plot is placed in.
ax : matplotlib.axes.Axes
The axes object where the plot is placed in.
"""
if self.res is None:
raise ValueError(
"Results are empty. Run `job.evaluate()` and call `job.plot()` again."
)
import matplotlib.pyplot as plt
if x is None:
x = self.x
if y is None:
y = self.y
if items is None:
items = slice(None)
x = np.array(x)[items]
y = np.array(y)[items]
if gradient:
y = np.gradient(y, x[:, xaxis], edge_order=2, axis=0)
z = np.gradient(y, x[:, xaxis], edge_order=2, axis=0)
cuttoff = np.mean(abs(z[:, yaxis])) + 2 * np.std(abs(z[:, yaxis]))
y[abs(z) > cuttoff] = np.nan
if ax is None:
fig, ax = plt.subplots()
else:
fig = ax.get_figure()
if swapaxes:
x, y = y, x
xlabel, ylabel = ylabel, xlabel
xaxis, yaxis = yaxis, xaxis
xscale, yscale = yscale, xscale
ax.plot(
xoffset + x[:, xaxis] * xscale, yoffset + y[:, yaxis] * yscale, **kwargs
)
if xlabel is not None:
ax.set_xlabel(xlabel)
if ylabel is not None:
ax.set_ylabel(ylabel)
return fig, ax