felupe.plugins._characteristic_curve のソースコード

# -*- 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