Skip to content

Commit

Permalink
Merge pull request #2
Browse files Browse the repository at this point in the history
Allow Custom Plots
  • Loading branch information
JJCoding01 authored Apr 21, 2020
2 parents e9b4307 + b056d2c commit 4d17d9b
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 42 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Change Log

## v0.1.7a1
- Update `Beam.plot` method to allow customization of plots generated
- Added tests for `Beam.plot`
- Added depreciation warning to `Beam.bending_stress` method

### Backwards Incompatible Changes
- Removed `bending_stress` parameter from `Beam.plot` method


## v0.1.6dev
- Add documentation on [Read The Docs](https://femethods.readthedocs.io/en/latest/index.html)
- Expand module and function documentation
Expand Down
25 changes: 25 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
PACKAGE_NAME=femethods

.PHONY: install docs lint-html tests

docs:
cd docs && make html

install:
python setup.py install

lint:
black $(PACKAGE_NAME) --line-length=79
isort -rc $(PACKAGE_NAME)
pylint $(PACKAGE_NAME)

lint-tests:
black tests --line-length=79
isort -rc tests
pylint tests

tests:
pytest --cov-report html --cov=$(PACKAGE_NAME) tests/$(PACKAGE_NAME)

tests-ci:
pytest --cov-report html --cov=$(PACKAGE_NAME) tests/$(PACKAGE_NAME) -v
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '0.1.5dev'
release = '0.1.7a1'

# -- General configuration ---------------------------------------------------

Expand Down
6 changes: 5 additions & 1 deletion femethods/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
name = "femethods"
__name__ = "femethods"
__version__ = "0.1.7a1"
__author__ = "Joseph Contreras Jr."
__license__ = "MIT"
__copyright__ = "Copyright 2019 Joseph Contreras Jr."
147 changes: 108 additions & 39 deletions femethods/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,64 +199,133 @@ def shear(self, x, dx=0.01, order=5):
)

def bending_stress(self, x, dx=1, c=1):
"""returns the bending stress at global coordinate x"""
"""
returns the bending stress at global coordinate x
.. deprecated:: 0.1.7a1
calculate bending stress as :obj:`Beam.moment(x) * c / Ixx`
"""
warn("bending_stress will be removed soon", DeprecationWarning)
return self.moment(x, dx=dx) * c / self.Ixx

@staticmethod
def __validate_plot_diagrams(diagrams, diagram_labels):
"""
Validate the parameters for the plot function
"""

# create default (and complete list of valid) diagrams that are
# implemented
default_diagrams = ("shear", "moment", "deflection")
if diagrams is None and diagram_labels is None:
# set both the diagrams and labels to their defaults
# no need for further validation of these values since they are
# set internally
return default_diagrams, default_diagrams

if diagrams is None and diagram_labels is not None:
raise ValueError("cannot set diagrams from labels")

if diagram_labels is None:
diagram_labels = diagrams

if len(diagrams) != len(diagram_labels):
raise ValueError(
"length of diagram_labels must match length of diagrams"
)
for diagram in diagrams:
if diagram not in default_diagrams:
raise ValueError(
f"values of diagrams must be in {default_diagrams}"
)
return diagrams, diagram_labels

def plot(
self, n=250, plot_stress=False, title="Beam Analysis"
): # pragma: no cover
self,
n=250,
title="Beam Analysis",
diagrams=None,
diagram_labels=None,
**kwargs,
):
"""
plot the deflection, moment, and shear along the length of the beam
Plot the deflection, moment, and shear along the length of the beam
The plot method will create a matplotlib.pyplot figure with the
deflection, moment, shear, and optionally stress along the length of
the beam element.
The plot method will create a :obj:`matplotlib.pyplot` figure with the
deflection, moment, and shear diagrams along the length of the beam
element. Which of these diagrams, and their order may be customized.
Parameters:
n (:obj:`int`): defaults to `250`:
number of data-points to use in plots
title (:obj:`str`) defaults to 'Beam Analysis`
title on top of plot
diagrams (:obj:`tuple`): defaults to
`('shear', 'moment', 'deflection')`
tuple of diagrams to plot. All values in tuple must be strings,
and one of the defaults.
Valid values are :obj:`('shear', 'moment', 'deflection')`
diagram_labels (:obj:`tuple`): y-axis labels for subplots.
Must have the same length as `diagrams`
Returns:
:obj:`tuple`: Tuple of matplitlib.pyplot figure and list of axes
in the form (figure, axes)
:obj:`tuple`:
Tuple of :obj:`matplotlib.pyplot` figure and list of axes in
the form :obj:`(figure, axes)`
.. note:: The plot method will create the figure handle, but will not
automatically show the figure.
To show the figure use :obj:`Beam.show()` or
:obj:`matplotlib.pyplot.show()`
.. versionchanged:: 0.1.7a1 Removed :obj:`bending_stress` parameter
.. versionchanged:: 0.1.7a1
Added :obj:`diagrams` and :obj:`diagram_labels` parameters
"""
rows = 4 if plot_stress else 3
fig, axes = plt.subplots(rows, 1, sharex="all")

# locations of nodes in global coordinate system
locations = self.mesh.nodes
kwargs.setdefault("title", "Beam Analysis")
kwargs.setdefault("grid", True)
kwargs.setdefault("xlabel", "Beam position, x")
kwargs.setdefault("fill", True)
kwargs.setdefault("plot_kwargs", {})
kwargs.setdefault("fill_kwargs", {"color": "b", "alpha": 0.25})

diagrams, diagram_labels = self.__validate_plot_diagrams(
diagrams, diagram_labels
)
fig, axes = plt.subplots(len(diagrams), 1, sharex="all")
if len(diagrams) == 1:
# make sure axes are iterable, even if there is only one
axes = [axes]

# Get the global x values. Note that the x-values for the moment and
# shear do not contain the endpoints of the x values for the deflection
# curve. This is because differentiation technique used is the central
# difference formula, which cannot calculate the value at the
# endpoints
xd = np.linspace(0, self.length, n) # deflection
xm = xd[1:-2] # moment (and stress)
xv = xm[2:-3] # shear
v = [self.deflection(xi) for xi in xd] # deflection
m = [self.moment(xi, dx=self.length / n) for xi in xm] # moment
V = [self.shear(xi, dx=self.length / n) for xi in xv] # shear

# Set up plotting variables to be able to iterate over them more easily
xs = [xv, xm, xd]
y = [V, m, v]
labels = ["shear", "moment", "deflection"]
if plot_stress:
q = [self.bending_stress(xi, dx=self.length / n) for xi in xm]
xs.append(xm)
y.append(q)
labels.append("stress")

for ax, x, y, label in zip(axes, xs, y, labels):
ax.plot(x, y)
ax.fill_between(x, y, 0, color="b", alpha=0.25)
x, y = None, None
for ax, diagram, label in zip(axes, diagrams, diagram_labels):
if diagram == "deflection":
x = xd
y = [self.deflection(xi) for xi in x]
if diagram == "moment":
x = xd
y = [self.moment(xi, dx=self.length / (n + 3)) for xi in x]
if diagram == "shear":
x = np.linspace(0, self.length, n + 4)[2:-2]
y = [self.shear(xi, dx=self.length / (n + 4)) for xi in x]

# regardless of the diagram that is being plotted, the number of
# data points should always equal the number specified by user
assert len(x) == n, "x does not match n"
assert len(y) == n, "y does not match n"

ax.plot(x, y, **kwargs["plot_kwargs"])
if kwargs["fill"]:
ax.fill_between(x, y, 0, **kwargs["fill_kwargs"])
ax.set_ylabel(label)
ax.grid(True)
ax.grid(kwargs["grid"])

axes[-1].set_xlabel("Beam position, x")
locations = self.mesh.nodes # in global coordinate system
axes[-1].set_xlabel(kwargs["xlabel"])
axes[-1].set_xticks(locations)

fig.subplots_adjust(hspace=0.25)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='femethods',
version='0.1.6dev',
version='0.1.7a1',
author='Joseph Contreras',
author_email='26684136+JosephJContreras@users.noreply.github.com',
description='Implementation of Finite Element Analysis',
Expand Down
58 changes: 58 additions & 0 deletions tests/femethods/test_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
from femethods.reactions import FixedReaction, PinnedReaction


def test_bending_stress_depreciation_warning():
with pytest.warns(DeprecationWarning):
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
b.bending_stress(x=5, c=1)


def test_beam_params():

reactions = [PinnedReaction(x) for x in [1, 120]]
Expand Down Expand Up @@ -248,3 +254,55 @@ def test_shear():
for x in [-5, 0, 25, 35]:
with pytest.raises(ValueError):
beam.shear(x)


def test_plot_diagrams_invalid_value():
with pytest.raises(ValueError):
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
b.plot(diagrams=("shear", "bad value"))


def test_plot_diagrams_diagrams_label_mismatch():
with pytest.raises(ValueError):
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
b.plot(diagrams=("shear",), diagram_labels=("shear", "moment"))


def test_plot_diagram_labels_without_diagrams():
with pytest.raises(ValueError):
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
b.plot(diagram_labels=("V, lb", "M, in/lb", "delta, in"))


def test_plot_default_labels():
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
fig, axes = b.plot()
x_labels = ("", "", "Beam position, x")
y_labels = ("shear", "moment", "deflection")

assert len(axes) == len(x_labels), "wrong number of sub-plots"
for ax, x_label, y_label in zip(axes, x_labels, y_labels):
assert ax.get_xlabel() == x_label
assert ax.get_ylabel() == y_label


def test_plot_custom_labels():
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
diagrams = ("deflection", "deflection", "moment", "shear")
labels = ("def1", "def2", "M", "V")
fig, axes = b.plot(diagrams=diagrams, diagram_labels=labels)
assert len(axes) == len(diagrams), "wrong number of sub-plots"

x_labels = ["" for _ in range(len(diagrams) - 1)]
x_labels.append("Beam position, x")
for ax, x_label, y_label in zip(axes, x_labels, labels):
assert ax.get_xlabel() == x_label
assert ax.get_ylabel() == y_label


def test_plot_one_diagram():
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
fig, axes = b.plot(diagrams=("deflection",))
assert len(axes) == 1, "expected length of axes was 1"
for ax, y_label in zip(axes, ("deflection",)):
assert ax.get_ylabel() == y_label

0 comments on commit 4d17d9b

Please sign in to comment.