femethods 0.1.7a2__py3-none-any.whl → 0.1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
femethods/elements.py DELETED
@@ -1,389 +0,0 @@
1
- """
2
- The elements module contains finite element classes
3
-
4
- Currently the only element that is defined is a beam element.
5
-
6
- """
7
-
8
- from typing import Any, List, TYPE_CHECKING, Tuple
9
- from warnings import warn
10
-
11
- import matplotlib.pyplot as plt
12
- import numpy as np
13
- from scipy.misc import derivative
14
-
15
- # local imports
16
- from femethods.core._base_elements import BeamElement
17
- from femethods.core._common import derivative as comm_derivative
18
-
19
- if TYPE_CHECKING: # pragma: no cover
20
- from femethods.loads import Load # noqa: F401 (unused import)
21
- from femethods.reactions import Reaction # noqa: F401 (unused import)
22
-
23
-
24
- # noinspection PyPep8Naming
25
- class Beam(BeamElement):
26
- """A Beam defines a beam element for analysis
27
-
28
- A beam element is a slender member that is subjected to transverse loading.
29
- It is assumed to have homogeneous properties, with a constant
30
- cross-section.
31
-
32
-
33
- Parameters:
34
- length (:obj:`float`): the length of a beam. This is the total length
35
- of the beam, this is not the length of the meshed
36
- element. This must be a float that is greater than 0.
37
- loads (:obj:`list`): list of load elements
38
- reactions (:obj:`list`): list of reactions acting on the beam
39
- E (:obj:`float`, optional): Young's modulus of the beam in units of
40
- :math:`\\frac{force}{length^2}`. Defaults to 1.
41
- The :math:`force` units used here are the same
42
- units that are used in the input forces, and
43
- calculated reaction forces. The :math:`length` unit
44
- must be the same as the area moment of inertia
45
- (**Ixx**) and the beam **length** units.
46
- Ixx (:obj:`float`, optional): Area moment of inertia of the beam.
47
- Defaults to 1. This is constant (constant cross-sectional
48
- area) along the length of the beam. This is in units of
49
- :math:`length^4`. This must be the same length unit of
50
- Young's modulus (**E**) and the beam **length**.
51
-
52
- """
53
-
54
- def __init__(
55
- self,
56
- length: float,
57
- loads: List["Load"],
58
- reactions: List["Reaction"],
59
- E: float = 1,
60
- Ixx: float = 1,
61
- ):
62
- super().__init__(length, loads, reactions, E=E, Ixx=Ixx)
63
-
64
- def deflection(self, x: float) -> np.float64:
65
- """Calculate deflection of the beam at location x
66
-
67
- Parameters:
68
- x (:obj:`float | int`): location along the length of the beam where
69
- deflection should be calculated.
70
-
71
- Returns:
72
- :obj:`float`: deflection of the beam in units of the beam length
73
-
74
- Raises:
75
- :obj:`ValueError`: when the :math:`0\\leq x \\leq length` is False
76
- :obj:`TypeError`: when x cannot be converted to a float
77
- """
78
-
79
- # TODO: store the lengths/node locations in the class so they only have
80
- # to be assessed without recalculating
81
- nodes = self.mesh.nodes
82
-
83
- # validate that x is a valid by ensuring that x is
84
- # - x is a number
85
- # - 0 <= x <= length of beam
86
- try:
87
- x = float(x)
88
- except ValueError:
89
- raise TypeError(
90
- f"Cannot calculate deflection with location of type: {type(x)}"
91
- )
92
-
93
- if x < 0 or self.length < x:
94
- raise ValueError(
95
- f"cannot calculate deflection at location {x} as "
96
- f"it is outside of the beam!"
97
- )
98
-
99
- # Using the given global x-value, determine the local x-value, length
100
- # of active element, and the nodal displacements (vertical, angular)
101
- # vector d
102
- for i in range(len(self.mesh.lengths)):
103
- if nodes[i] <= x <= nodes[i + 1]:
104
- # this is the element where the global x-value falls into.
105
- # Get the parameters in the local system and exit the loop
106
- x_local = x - nodes[i]
107
- L = self.mesh.lengths[i]
108
- d = self.node_deflections[i * 2 : i * 2 + 4]
109
- return self.shape(x_local, L).dot(d)[0]
110
-
111
- def moment(self, x: float, dx: float = 1e-5, order: int = 9) -> np.float64:
112
- """Calculate the moment at location x
113
-
114
- Calculate the moment in the beam at the global x value by taking
115
- the second derivative of the deflection curve.
116
-
117
- .. centered::
118
- :math:`M(x) = E \\cdot Ixx \\cdot \\frac{d^2 v(x)}{dx^2}`
119
-
120
- where :math:`M` is the moment, :math:`E` is Young's modulus and
121
- :math:`Ixx` is the area moment of inertia.
122
-
123
- .. note: When calculating the moment near the beginning of the beam
124
- the moment calculation may be unreliable.
125
-
126
- Parameters:
127
- x (:obj:`int`): location along the beam where moment is calculated
128
- dx (:obj:`float`, optional): spacing. Default is 1e-5
129
- order (:obj:`int`, optional): number of points to use, must be odd.
130
- Default is 9
131
-
132
- Returns:
133
- :obj:`float`: moment in beam at location x
134
-
135
- Raises:
136
- :obj:`ValueError`: when the :math:`0\\leq x \\leq length` is False
137
- :obj:`TypeError`: when x cannot be converted to a float
138
-
139
- For more information on the parameters, see the scipy.misc.derivative
140
- documentation.
141
- """
142
-
143
- # TODO: Update so that moment can be calculated at both ends of beam
144
- if x < 0.75:
145
- # this cut-off was found experimentally. Anything less than this,
146
- # and calculating the derivative is unreliable
147
- warn("Calculated moments below 0.75 may be unreliable")
148
-
149
- try:
150
- return (
151
- self.E
152
- * self.Ixx
153
- * derivative(self.deflection, x, dx=dx, n=2, order=order)
154
- )
155
- except ValueError:
156
- # there was an error, probably due to the central difference
157
- # method attempting to calculate the moment near the ends of the
158
- # beam. Determine whether the desired position is near the start
159
- # or end of the beam, and use the forward/backward difference
160
- # method accordingly
161
-
162
- if x <= self.length / 2:
163
- # the desired moment is near the beginning of the beam, use the
164
- # forward difference method
165
- method = "forward"
166
- else:
167
- # the desired moment is near the end of the beam, use the
168
- # backward difference method
169
- method = "backward"
170
- return (
171
- self.E
172
- * self.Ixx
173
- * comm_derivative(self.deflection, x, method=method, n=2)
174
- )
175
-
176
- def shear(self, x: float, dx: float = 0.01, order: int = 5) -> np.float64:
177
- """
178
- Calculate the shear force in the beam at location x
179
-
180
- Calculate the shear in the beam at the global x value by taking
181
- the third derivative of the deflection curve.
182
-
183
- .. centered::
184
- :math:`V(x) = E \\cdot Ixx \\cdot \\frac{d^3 v(x)}{dx^3}`
185
-
186
- where :math:`V` is the shear force, :math:`E` is Young's modulus and
187
- :math:`Ixx` is the area moment of inertia.
188
-
189
- .. note: When calculating the shear near the beginning of the beam
190
- the shear calculation may be unreliable.
191
-
192
- Parameters:
193
- x (:obj:`int`): location along the beam where moment is calculated
194
- dx (:obj:`float`, optional): spacing. Default is 0.01
195
- order (:obj:`int`, optional): number of points to use, must be odd.
196
- Default is 5
197
-
198
- Returns:
199
- :obj:`float`: moment in beam at location x
200
-
201
- Raises:
202
- :obj:`ValueError`: when the :math:`0\\leq x \\leq length` is False
203
- :obj:`TypeError`: when x cannot be converted to a float
204
-
205
- For more information on the parameters, see the scipy.misc.derivative
206
- documentation.
207
- """
208
- return (
209
- self.E
210
- * self.Ixx
211
- * derivative(self.deflection, x, dx=dx, n=3, order=order)
212
- )
213
-
214
- def bending_stress(self, x, dx=1, c=1):
215
- """
216
- returns the bending stress at global coordinate x
217
-
218
- .. deprecated:: 0.1.7a1
219
- calculate bending stress as :obj:`Beam.moment(x) * c / Ixx`
220
-
221
- """
222
- warn("bending_stress will be removed soon", DeprecationWarning)
223
- return self.moment(x, dx=dx) * c / self.Ixx
224
-
225
- @staticmethod
226
- def __validate_plot_diagrams(diagrams, diagram_labels):
227
- """
228
- Validate the parameters for the plot function
229
- """
230
-
231
- # create default (and complete list of valid) diagrams that are
232
- # implemented
233
- default_diagrams = ("shear", "moment", "deflection")
234
- if diagrams is None and diagram_labels is None:
235
- # set both the diagrams and labels to their defaults
236
- # no need for further validation of these values since they are
237
- # set internally
238
- return default_diagrams, default_diagrams
239
-
240
- if diagrams is None and diagram_labels is not None:
241
- raise ValueError("cannot set diagrams from labels")
242
-
243
- if diagram_labels is None:
244
- diagram_labels = diagrams
245
-
246
- if len(diagrams) != len(diagram_labels):
247
- raise ValueError(
248
- "length of diagram_labels must match length of diagrams"
249
- )
250
- for diagram in diagrams:
251
- if diagram not in default_diagrams:
252
- raise ValueError(
253
- f"values of diagrams must be in {default_diagrams}"
254
- )
255
- return diagrams, diagram_labels
256
-
257
- def plot(
258
- self,
259
- n=250,
260
- title="Beam Analysis",
261
- diagrams=None,
262
- diagram_labels=None,
263
- **kwargs,
264
- ):
265
- """
266
- Plot the deflection, moment, and shear along the length of the beam
267
-
268
- The plot method will create a :obj:`matplotlib.pyplot` figure with the
269
- deflection, moment, and shear diagrams along the length of the beam
270
- element. Which of these diagrams, and their order may be customized.
271
-
272
- Parameters:
273
- n (:obj:`int`): defaults to `250`:
274
- number of data-points to use in plots
275
- title (:obj:`str`) defaults to 'Beam Analysis`
276
- title on top of plot
277
- diagrams (:obj:`tuple`): defaults to
278
- `('shear', 'moment', 'deflection')`
279
- tuple of diagrams to plot. All values in tuple must be strings,
280
- and one of the defaults.
281
- Valid values are :obj:`('shear', 'moment', 'deflection')`
282
- diagram_labels (:obj:`tuple`): y-axis labels for subplots.
283
- Must have the same length as `diagrams`
284
-
285
- Returns:
286
- :obj:`tuple`:
287
- Tuple of :obj:`matplotlib.pyplot` figure and list of axes in
288
- the form :obj:`(figure, axes)`
289
-
290
- .. note:: The plot method will create the figure handle, but will not
291
- automatically show the figure.
292
- To show the figure use :obj:`Beam.show()` or
293
- :obj:`matplotlib.pyplot.show()`
294
-
295
- .. versionchanged:: 0.1.7a1 Removed :obj:`bending_stress` parameter
296
- .. versionchanged:: 0.1.7a1
297
- Added :obj:`diagrams` and :obj:`diagram_labels` parameters
298
-
299
- """
300
-
301
- kwargs.setdefault("title", "Beam Analysis")
302
- kwargs.setdefault("grid", True)
303
- kwargs.setdefault("xlabel", "Beam position, x")
304
- kwargs.setdefault("fill", True)
305
- kwargs.setdefault("plot_kwargs", {})
306
- kwargs.setdefault("fill_kwargs", {"color": "b", "alpha": 0.25})
307
-
308
- diagrams, diagram_labels = self.__validate_plot_diagrams(
309
- diagrams, diagram_labels
310
- )
311
- fig, axes = plt.subplots(len(diagrams), 1, sharex="all")
312
- if len(diagrams) == 1:
313
- # make sure axes are iterable, even if there is only one
314
- axes = [axes]
315
-
316
- xd = np.linspace(0, self.length, n) # deflection
317
- x, y = None, None
318
- for ax, diagram, label in zip(axes, diagrams, diagram_labels):
319
- if diagram == "deflection":
320
- x = xd
321
- y = [self.deflection(xi) for xi in x]
322
- if diagram == "moment":
323
- x = xd
324
- y = [self.moment(xi, dx=self.length / (n + 3)) for xi in x]
325
- if diagram == "shear":
326
- x = np.linspace(0, self.length, n + 4)[2:-2]
327
- y = [self.shear(xi, dx=self.length / (n + 4)) for xi in x]
328
-
329
- # regardless of the diagram that is being plotted, the number of
330
- # data points should always equal the number specified by user
331
- assert len(x) == n, "x does not match n"
332
- assert len(y) == n, "y does not match n"
333
-
334
- ax.plot(x, y, **kwargs["plot_kwargs"])
335
- if kwargs["fill"]:
336
- ax.fill_between(x, y, 0, **kwargs["fill_kwargs"])
337
- ax.set_ylabel(label)
338
- ax.grid(kwargs["grid"])
339
-
340
- locations = self.mesh.nodes # in global coordinate system
341
- axes[-1].set_xlabel(kwargs["xlabel"])
342
- axes[-1].set_xticks(locations)
343
-
344
- fig.subplots_adjust(hspace=0.25)
345
- fig.suptitle(title)
346
- return fig, axes
347
-
348
- @staticmethod
349
- def show(*args: Any, **kwargs: Any) -> None:
350
- """Wrapper function for showing matplotlib figure
351
-
352
- This method gives direct access to the matplotlib.pyplot.show function
353
- so the calling code is not required to import matplotlib directly
354
- just to show the plots
355
-
356
- Parameters:
357
- args/kwargs: args and kwargs are passed directly to
358
- matplotlib.pyplot.show
359
- """
360
- plt.show(*args, **kwargs) # pragma: no cover
361
-
362
- def __str__(self) -> str:
363
- assert self.loads is not None
364
- assert self.reactions is not None
365
-
366
- L = ""
367
- for load in self.loads:
368
- L += "Type: {}\n".format(load.name)
369
- L += " Location: {}\n".format(load.location)
370
- L += " Magnitude: {}\n".format(load.magnitude)
371
-
372
- r = ""
373
- for reaction in self.reactions:
374
- r += "Type: {}\n".format(reaction.name)
375
- r += " Location: {}\n".format(reaction.location)
376
- r += " Force: {}\n".format(reaction.force)
377
- r += " Moment: {}\n".format(reaction.moment)
378
-
379
- msg = (
380
- "PARAMETERS\n"
381
- f"Length (length): {self.length}\n"
382
- f"Young's Modulus (E): {self.E}\n"
383
- f"Area moment of inertia (Ixx): {self.Ixx}\n"
384
- f"LOADING\n"
385
- f"{L}\n"
386
- f"REACTIONS\n"
387
- f"{r}\n"
388
- )
389
- return msg
femethods/loads.py DELETED
@@ -1,38 +0,0 @@
1
- """
2
- Module to define different loads
3
- """
4
-
5
- from typing import Optional
6
-
7
- from femethods.core._common import Forces
8
-
9
-
10
- class Load(Forces):
11
- """Base class for all load types
12
-
13
- Used primarily for type checking the loads on input
14
- """
15
-
16
- name = ""
17
-
18
-
19
- class PointLoad(Load):
20
- """
21
- class specific to a point load
22
- """
23
-
24
- name = "point load"
25
-
26
- def __init__(self, magnitude: Optional[float], location: float):
27
- super().__init__(magnitude, location)
28
-
29
-
30
- class MomentLoad(Load):
31
- """
32
- class specific to a moment load
33
- """
34
-
35
- name = "moment load"
36
-
37
- def __init__(self, magnitude: float, location: float):
38
- super().__init__(magnitude, location)
femethods/reactions.py DELETED
@@ -1,176 +0,0 @@
1
- """
2
- The reactions module defines different reaction classes
3
-
4
- A reaction is required to support an element to resist any input forces.
5
-
6
- There are two types of reactions that are defined.
7
-
8
- * PinnedReaction, allows rotational displacement only
9
- * FixedReaction, does not allow any displacement
10
-
11
- """
12
- from typing import Optional, Tuple
13
-
14
- from femethods.core._common import Forces
15
-
16
- BOUNDARY_CONDITIONS = Tuple[Optional[int], Optional[int]]
17
-
18
-
19
- class Reaction(Forces):
20
- """Base class for all reactions
21
-
22
- The Reaction class defines general properties related to all reaction
23
- types.
24
-
25
- Parameters:
26
- location (:obj:`float`): the axial location of the reaction along the
27
- length of the beam.
28
-
29
- .. note:: Any force or moment values that where calculated values are
30
- invalidated (set to :obj:`None`) any time the location is set.
31
-
32
- Attributes:
33
- force (:obj:`float | None`): the force of the reaction after it has
34
- been calculated
35
- moment (:obj:`float | None`): The moment of the reaction after it has
36
- been calculated
37
- """
38
-
39
- name = ""
40
-
41
- def __init__(self, location: float):
42
- super().__init__(magnitude=None, location=location)
43
- self.force = None
44
- self.moment = None
45
- self._boundary: BOUNDARY_CONDITIONS = (None, None)
46
-
47
- @property
48
- def boundary(self) -> BOUNDARY_CONDITIONS:
49
- return self._boundary
50
-
51
- @property
52
- def location(self) -> float:
53
- """
54
- Location of the reaction along the length of the beam
55
-
56
- The units of the length property is the same as the units of the beam
57
- length.
58
-
59
- The value of the location must be a positive value that is less than
60
- or equal to the length of the beam, or it will raise a ValueError.
61
-
62
- .. note:: The force and moment values are set to :obj:`None` any time
63
- the location is set.
64
- """
65
- return self._location
66
-
67
- @location.setter
68
- def location(self, location: float) -> None:
69
- # The location is overloading the location property in Forces so that
70
- # the reaction can be invalidated when the location is changed
71
- if location < 0:
72
- # location cannot be a negative number
73
- raise ValueError("location must be positive!")
74
- self.invalidate()
75
- self._location = location
76
-
77
- @property
78
- def value(self) -> Tuple[Optional[float], Optional[float]]:
79
- """
80
- Simple tuple of force and moment
81
-
82
- Returns:
83
- :obj:`tuple` (force, moment)
84
- """
85
- return self.force, self.moment
86
-
87
- def invalidate(self) -> None:
88
- """Invalidate the reaction values
89
-
90
- This will set the force and moment values to :obj:`None`
91
-
92
- To be used whenever the parameters change and the reaction values are
93
- no longer valid.
94
- """
95
- self.force, self.moment = (None, None)
96
-
97
- def __str__(self) -> str:
98
- return (
99
- f"{self.__class__.__name__}\n"
100
- f" Location: {self.location}\n"
101
- f" Force: {self.force}\n"
102
- f" Moment: {self.moment}\n"
103
- )
104
-
105
- def __repr__(self) -> str:
106
- return f"{self.__class__.__name__}(location={self.location})"
107
-
108
- def __eq__(self, other: object) -> bool:
109
-
110
- if not isinstance(other, self.__class__):
111
- return False
112
-
113
- if (
114
- self.location == other.location
115
- and self.force == other.force
116
- and self.moment == other.moment
117
- ):
118
- return True
119
-
120
- return False
121
-
122
-
123
- class PinnedReaction(Reaction):
124
- """
125
- A PinnedReaction allows rotation displacements only
126
-
127
- A PinnedReaction represents a pinned, frictionless pivot that can
128
- resist motion both normal and axial directions to the beam. It will not
129
- resist moments.
130
- The deflection of a beam at the PinnedReaction is always zero, but
131
- the angle is free to change
132
-
133
- Parameters:
134
- location (:obj:`float`): the axial location of the reaction along the
135
- length of the beam
136
-
137
- Attributes:
138
- name (:obj:`str`): short name of the reaction (pinned). Used internally
139
-
140
- .. warning:: The **name** attribute is used internally.
141
- **Do not change this value!**
142
- """
143
-
144
- name = "pinned"
145
-
146
- def __init__(self, location: float):
147
- super().__init__(location)
148
- # limit the vertical displacement but allow rotation
149
- self._boundary: BOUNDARY_CONDITIONS = (0, None)
150
-
151
-
152
- class FixedReaction(Reaction):
153
- """
154
- A FixedReaction does not allow any displacement or change in angle
155
-
156
- A FixedReaction resists both force and moments. The displacement and the
157
- angle are both constrained and must be zero at the reaction point.
158
- FixedReactions are typically applied at the ends of a Beam.
159
-
160
- Parameters:
161
- location (:obj:`float`): the axial location of the reaction along the
162
- length of the beam
163
-
164
- Attributes:
165
- name (:obj:`str`): short name of the reaction (fixed). Used internally
166
-
167
- .. warning:: The **name** attribute is used internally.
168
- **Do not change this value!**
169
- """
170
-
171
- name = "fixed"
172
-
173
- def __init__(self, location: float):
174
- super().__init__(location)
175
- # do not allow vertical or rotational displacement
176
- self._boundary: BOUNDARY_CONDITIONS = (0, 0)
@@ -1,18 +0,0 @@
1
- femethods/__init__.py,sha256=KEvu1iKJntEfs4jna_RsP7ObqyaP7t2g13x1UvL-2Lk,157
2
- femethods/elements.py,sha256=GRNfGbRYjBDsjmQLA7SSQ2QLkTWaXGlMsH5PWfPgT9U,14850
3
- femethods/loads.py,sha256=FLoQ1js_vC1iBV0C_bVhXx2ey09uqGniMRjgJwrvDLg,679
4
- femethods/mesh.py,sha256=aeGr0y25Yt7xD1qmfOF8cAMBd44qube394B5lFrRR4s,2979
5
- femethods/reactions.py,sha256=dszKp84PtoyH99v-YI6pELp0cxBh-G3aXbBEZbyqHfI,5479
6
- femethods/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- femethods/core/_base_elements.py,sha256=Ci03v7LntAgYNs3WhxIiqfLZG5IEMRg6vXxyvRD3kAg,15427
8
- femethods/core/_common.py,sha256=8yOwIsTVqPuFmOTQRf5sSPV4hHu_52AbjTTIdlMC6hg,3461
9
- tests/__init__.py,sha256=eoZ6GfifbqhMLNzjlqRDVil-yyBkOmVN9ujSgJWNBlY,15
10
- tests/functional tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- tests/functional tests/settings.py,sha256=K2qEBPjQs-5KYkQyxMDMKhjNBLmEgo5K3kQPd8SgJqE,410
12
- tests/functional tests/test_fixed_support_beams.py,sha256=zQ3Yo78zV5s3NSm9YUtNZfMUJkl6kwzix21dBOl_EQ0,878
13
- tests/functional tests/test_simply_supported_beam.py,sha256=XDbmMpQlelC8pq9qKiMlpbc04KJ1e6ynvdvzjK3kfnQ,4067
14
- tests/functional tests/validate.py,sha256=RcLoOnse-Mug71KstiteHL7EVUI5gcCOZIqf9sdOqRo,1351
15
- femethods-0.1.7a2.dist-info/METADATA,sha256=b0zNovQTztaKzkPxw882JRQWW3dgVvnQbfdhhmDJwng,8675
16
- femethods-0.1.7a2.dist-info/WHEEL,sha256=EVRjI69F5qVjm_YgqcTXPnTAv3BfSUr0WVAHuSP3Xoo,92
17
- femethods-0.1.7a2.dist-info/top_level.txt,sha256=kHq0RoeYIm-d8AhLU7k4YhFsefCyRPywG-3nZWg0Du0,16
18
- femethods-0.1.7a2.dist-info/RECORD,,
tests/__init__.py DELETED
@@ -1 +0,0 @@
1
- # coding=utf-8
File without changes
@@ -1,11 +0,0 @@
1
- """
2
- Common constants and parameters to be used functional tests. This is done so that
3
- the beams are all similar in size and loading.
4
- """
5
-
6
- L = 120 # in, length of beam in inches
7
- P = -1000 # lbs, load acting on beam
8
- E = 29e6 # psi, Young's modulus
9
- Ixx = 350 # in^4 area moment of inertia of beam
10
- EI = E * Ixx # common constant
11
- TOL = 1e-1 # allowable tolerance between exact and numerical solutions to pass
@@ -1,38 +0,0 @@
1
- """
2
- Functional tests for beams with fixed supports
3
-
4
- https://www.awc.org/pdf/codes-standards/publications/design-aids/AWC-DA6-BeamFormulas-0710.pdf
5
-
6
- """
7
-
8
- import pytest
9
- from settings import E, EI, Ixx, L, P, TOL
10
- from validate import validate
11
-
12
- from femethods.elements import Beam
13
- from femethods.loads import PointLoad
14
- from femethods.reactions import FixedReaction
15
-
16
-
17
- def test_cantilevered_beam_load_at_end():
18
- """fixed beam with concentrated load at free end
19
- case 13
20
- """
21
-
22
- R = -P
23
- M_max = P * L # at fixed end
24
-
25
- d_max = P * L ** 3 / (3 * EI) # at free end
26
-
27
- beam = Beam(
28
- length=L,
29
- loads=[PointLoad(magnitude=P, location=0)],
30
- reactions=[FixedReaction(L)],
31
- E=E,
32
- Ixx=Ixx,
33
- )
34
- beam.solve()
35
-
36
- validate(beam, loc=0, R=[(R, M_max)], M_loc=0, d_loc=d_max)
37
-
38
- assert pytest.approx(beam.moment(L), rel=TOL) == M_max