Skip to content

Commit 4d17d9b

Browse files
authored
Merge pull request #2
Allow Custom Plots
2 parents e9b4307 + b056d2c commit 4d17d9b

7 files changed

Lines changed: 207 additions & 42 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Change Log
22

3+
## v0.1.7a1
4+
- Update `Beam.plot` method to allow customization of plots generated
5+
- Added tests for `Beam.plot`
6+
- Added depreciation warning to `Beam.bending_stress` method
7+
8+
### Backwards Incompatible Changes
9+
- Removed `bending_stress` parameter from `Beam.plot` method
10+
11+
312
## v0.1.6dev
413
- Add documentation on [Read The Docs](https://femethods.readthedocs.io/en/latest/index.html)
514
- Expand module and function documentation

Makefile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
PACKAGE_NAME=femethods
2+
3+
.PHONY: install docs lint-html tests
4+
5+
docs:
6+
cd docs && make html
7+
8+
install:
9+
python setup.py install
10+
11+
lint:
12+
black $(PACKAGE_NAME) --line-length=79
13+
isort -rc $(PACKAGE_NAME)
14+
pylint $(PACKAGE_NAME)
15+
16+
lint-tests:
17+
black tests --line-length=79
18+
isort -rc tests
19+
pylint tests
20+
21+
tests:
22+
pytest --cov-report html --cov=$(PACKAGE_NAME) tests/$(PACKAGE_NAME)
23+
24+
tests-ci:
25+
pytest --cov-report html --cov=$(PACKAGE_NAME) tests/$(PACKAGE_NAME) -v

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
# The short X.Y version
2828
version = ''
2929
# The full version, including alpha/beta/rc tags
30-
release = '0.1.5dev'
30+
release = '0.1.7a1'
3131

3232
# -- General configuration ---------------------------------------------------
3333

femethods/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
name = "femethods"
1+
__name__ = "femethods"
2+
__version__ = "0.1.7a1"
3+
__author__ = "Joseph Contreras Jr."
4+
__license__ = "MIT"
5+
__copyright__ = "Copyright 2019 Joseph Contreras Jr."

femethods/elements.py

Lines changed: 108 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -199,64 +199,133 @@ def shear(self, x, dx=0.01, order=5):
199199
)
200200

201201
def bending_stress(self, x, dx=1, c=1):
202-
"""returns the bending stress at global coordinate x"""
202+
"""
203+
returns the bending stress at global coordinate x
204+
205+
.. deprecated:: 0.1.7a1
206+
calculate bending stress as :obj:`Beam.moment(x) * c / Ixx`
207+
208+
"""
209+
warn("bending_stress will be removed soon", DeprecationWarning)
203210
return self.moment(x, dx=dx) * c / self.Ixx
204211

212+
@staticmethod
213+
def __validate_plot_diagrams(diagrams, diagram_labels):
214+
"""
215+
Validate the parameters for the plot function
216+
"""
217+
218+
# create default (and complete list of valid) diagrams that are
219+
# implemented
220+
default_diagrams = ("shear", "moment", "deflection")
221+
if diagrams is None and diagram_labels is None:
222+
# set both the diagrams and labels to their defaults
223+
# no need for further validation of these values since they are
224+
# set internally
225+
return default_diagrams, default_diagrams
226+
227+
if diagrams is None and diagram_labels is not None:
228+
raise ValueError("cannot set diagrams from labels")
229+
230+
if diagram_labels is None:
231+
diagram_labels = diagrams
232+
233+
if len(diagrams) != len(diagram_labels):
234+
raise ValueError(
235+
"length of diagram_labels must match length of diagrams"
236+
)
237+
for diagram in diagrams:
238+
if diagram not in default_diagrams:
239+
raise ValueError(
240+
f"values of diagrams must be in {default_diagrams}"
241+
)
242+
return diagrams, diagram_labels
243+
205244
def plot(
206-
self, n=250, plot_stress=False, title="Beam Analysis"
207-
): # pragma: no cover
245+
self,
246+
n=250,
247+
title="Beam Analysis",
248+
diagrams=None,
249+
diagram_labels=None,
250+
**kwargs,
251+
):
208252
"""
209-
plot the deflection, moment, and shear along the length of the beam
253+
Plot the deflection, moment, and shear along the length of the beam
210254
211-
The plot method will create a matplotlib.pyplot figure with the
212-
deflection, moment, shear, and optionally stress along the length of
213-
the beam element.
255+
The plot method will create a :obj:`matplotlib.pyplot` figure with the
256+
deflection, moment, and shear diagrams along the length of the beam
257+
element. Which of these diagrams, and their order may be customized.
258+
259+
Parameters:
260+
n (:obj:`int`): defaults to `250`:
261+
number of data-points to use in plots
262+
title (:obj:`str`) defaults to 'Beam Analysis`
263+
title on top of plot
264+
diagrams (:obj:`tuple`): defaults to
265+
`('shear', 'moment', 'deflection')`
266+
tuple of diagrams to plot. All values in tuple must be strings,
267+
and one of the defaults.
268+
Valid values are :obj:`('shear', 'moment', 'deflection')`
269+
diagram_labels (:obj:`tuple`): y-axis labels for subplots.
270+
Must have the same length as `diagrams`
214271
215272
Returns:
216-
:obj:`tuple`: Tuple of matplitlib.pyplot figure and list of axes
217-
in the form (figure, axes)
273+
:obj:`tuple`:
274+
Tuple of :obj:`matplotlib.pyplot` figure and list of axes in
275+
the form :obj:`(figure, axes)`
218276
219277
.. note:: The plot method will create the figure handle, but will not
220278
automatically show the figure.
221279
To show the figure use :obj:`Beam.show()` or
222280
:obj:`matplotlib.pyplot.show()`
223281
282+
.. versionchanged:: 0.1.7a1 Removed :obj:`bending_stress` parameter
283+
.. versionchanged:: 0.1.7a1
284+
Added :obj:`diagrams` and :obj:`diagram_labels` parameters
285+
224286
"""
225-
rows = 4 if plot_stress else 3
226-
fig, axes = plt.subplots(rows, 1, sharex="all")
227287

228-
# locations of nodes in global coordinate system
229-
locations = self.mesh.nodes
288+
kwargs.setdefault("title", "Beam Analysis")
289+
kwargs.setdefault("grid", True)
290+
kwargs.setdefault("xlabel", "Beam position, x")
291+
kwargs.setdefault("fill", True)
292+
kwargs.setdefault("plot_kwargs", {})
293+
kwargs.setdefault("fill_kwargs", {"color": "b", "alpha": 0.25})
294+
295+
diagrams, diagram_labels = self.__validate_plot_diagrams(
296+
diagrams, diagram_labels
297+
)
298+
fig, axes = plt.subplots(len(diagrams), 1, sharex="all")
299+
if len(diagrams) == 1:
300+
# make sure axes are iterable, even if there is only one
301+
axes = [axes]
230302

231-
# Get the global x values. Note that the x-values for the moment and
232-
# shear do not contain the endpoints of the x values for the deflection
233-
# curve. This is because differentiation technique used is the central
234-
# difference formula, which cannot calculate the value at the
235-
# endpoints
236303
xd = np.linspace(0, self.length, n) # deflection
237-
xm = xd[1:-2] # moment (and stress)
238-
xv = xm[2:-3] # shear
239-
v = [self.deflection(xi) for xi in xd] # deflection
240-
m = [self.moment(xi, dx=self.length / n) for xi in xm] # moment
241-
V = [self.shear(xi, dx=self.length / n) for xi in xv] # shear
242-
243-
# Set up plotting variables to be able to iterate over them more easily
244-
xs = [xv, xm, xd]
245-
y = [V, m, v]
246-
labels = ["shear", "moment", "deflection"]
247-
if plot_stress:
248-
q = [self.bending_stress(xi, dx=self.length / n) for xi in xm]
249-
xs.append(xm)
250-
y.append(q)
251-
labels.append("stress")
252-
253-
for ax, x, y, label in zip(axes, xs, y, labels):
254-
ax.plot(x, y)
255-
ax.fill_between(x, y, 0, color="b", alpha=0.25)
304+
x, y = None, None
305+
for ax, diagram, label in zip(axes, diagrams, diagram_labels):
306+
if diagram == "deflection":
307+
x = xd
308+
y = [self.deflection(xi) for xi in x]
309+
if diagram == "moment":
310+
x = xd
311+
y = [self.moment(xi, dx=self.length / (n + 3)) for xi in x]
312+
if diagram == "shear":
313+
x = np.linspace(0, self.length, n + 4)[2:-2]
314+
y = [self.shear(xi, dx=self.length / (n + 4)) for xi in x]
315+
316+
# regardless of the diagram that is being plotted, the number of
317+
# data points should always equal the number specified by user
318+
assert len(x) == n, "x does not match n"
319+
assert len(y) == n, "y does not match n"
320+
321+
ax.plot(x, y, **kwargs["plot_kwargs"])
322+
if kwargs["fill"]:
323+
ax.fill_between(x, y, 0, **kwargs["fill_kwargs"])
256324
ax.set_ylabel(label)
257-
ax.grid(True)
325+
ax.grid(kwargs["grid"])
258326

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

262331
fig.subplots_adjust(hspace=0.25)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup(
77
name='femethods',
8-
version='0.1.6dev',
8+
version='0.1.7a1',
99
author='Joseph Contreras',
1010
author_email='26684136+JosephJContreras@users.noreply.github.com',
1111
description='Implementation of Finite Element Analysis',

tests/femethods/test_elements.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
from femethods.reactions import FixedReaction, PinnedReaction
66

77

8+
def test_bending_stress_depreciation_warning():
9+
with pytest.warns(DeprecationWarning):
10+
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
11+
b.bending_stress(x=5, c=1)
12+
13+
814
def test_beam_params():
915

1016
reactions = [PinnedReaction(x) for x in [1, 120]]
@@ -248,3 +254,55 @@ def test_shear():
248254
for x in [-5, 0, 25, 35]:
249255
with pytest.raises(ValueError):
250256
beam.shear(x)
257+
258+
259+
def test_plot_diagrams_invalid_value():
260+
with pytest.raises(ValueError):
261+
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
262+
b.plot(diagrams=("shear", "bad value"))
263+
264+
265+
def test_plot_diagrams_diagrams_label_mismatch():
266+
with pytest.raises(ValueError):
267+
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
268+
b.plot(diagrams=("shear",), diagram_labels=("shear", "moment"))
269+
270+
271+
def test_plot_diagram_labels_without_diagrams():
272+
with pytest.raises(ValueError):
273+
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
274+
b.plot(diagram_labels=("V, lb", "M, in/lb", "delta, in"))
275+
276+
277+
def test_plot_default_labels():
278+
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
279+
fig, axes = b.plot()
280+
x_labels = ("", "", "Beam position, x")
281+
y_labels = ("shear", "moment", "deflection")
282+
283+
assert len(axes) == len(x_labels), "wrong number of sub-plots"
284+
for ax, x_label, y_label in zip(axes, x_labels, y_labels):
285+
assert ax.get_xlabel() == x_label
286+
assert ax.get_ylabel() == y_label
287+
288+
289+
def test_plot_custom_labels():
290+
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
291+
diagrams = ("deflection", "deflection", "moment", "shear")
292+
labels = ("def1", "def2", "M", "V")
293+
fig, axes = b.plot(diagrams=diagrams, diagram_labels=labels)
294+
assert len(axes) == len(diagrams), "wrong number of sub-plots"
295+
296+
x_labels = ["" for _ in range(len(diagrams) - 1)]
297+
x_labels.append("Beam position, x")
298+
for ax, x_label, y_label in zip(axes, x_labels, labels):
299+
assert ax.get_xlabel() == x_label
300+
assert ax.get_ylabel() == y_label
301+
302+
303+
def test_plot_one_diagram():
304+
b = Beam(10, [PointLoad(10, 10)], [FixedReaction(0)])
305+
fig, axes = b.plot(diagrams=("deflection",))
306+
assert len(axes) == 1, "expected length of axes was 1"
307+
for ax, y_label in zip(axes, ("deflection",)):
308+
assert ax.get_ylabel() == y_label

0 commit comments

Comments
 (0)