""" This module can be used to solve 2D beam bending problems with singularity functions in mechanics. """ from sympy.core import S, Symbol, diff, symbols from sympy.core.add import Add from sympy.core.expr import Expr from sympy.core.function import (Derivative, Function) from sympy.core.mul import Mul from sympy.core.relational import Eq from sympy.core.sympify import sympify from sympy.solvers import linsolve from sympy.solvers.ode.ode import dsolve from sympy.solvers.solvers import solve from sympy.printing import sstr from sympy.functions import SingularityFunction, Piecewise, factorial from sympy.integrals import integrate from sympy.series import limit from sympy.plotting import plot, PlotGrid from sympy.geometry.entity import GeometryEntity from sympy.external import import_module from sympy.sets.sets import Interval from sympy.utilities.lambdify import lambdify from sympy.utilities.decorator import doctest_depends_on from sympy.utilities.iterables import iterable numpy = import_module('numpy', import_kwargs={'fromlist':['arange']}) class Beam: """ A Beam is a structural element that is capable of withstanding load primarily by resisting against bending. Beams are characterized by their cross sectional profile(Second moment of area), their length and their material. .. note:: A consistent sign convention must be used while solving a beam bending problem; the results will automatically follow the chosen sign convention. However, the chosen sign convention must respect the rule that, on the positive side of beam's axis (in respect to current section), a loading force giving positive shear yields a negative moment, as below (the curved arrow shows the positive moment and rotation): .. image:: allowed-sign-conventions.png Examples ======== There is a beam of length 4 meters. A constant distributed load of 6 N/m is applied from half of the beam till the end. There are two simple supports below the beam, one at the starting point and another at the ending point of the beam. The deflection of the beam at the end is restricted. Using the sign convention of downwards forces being positive. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols, Piecewise >>> E, I = symbols('E, I') >>> R1, R2 = symbols('R1, R2') >>> b = Beam(4, E, I) >>> b.apply_load(R1, 0, -1) >>> b.apply_load(6, 2, 0) >>> b.apply_load(R2, 4, -1) >>> b.bc_deflection = [(0, 0), (4, 0)] >>> b.boundary_conditions {'deflection': [(0, 0), (4, 0)], 'slope': []} >>> b.load R1*SingularityFunction(x, 0, -1) + R2*SingularityFunction(x, 4, -1) + 6*SingularityFunction(x, 2, 0) >>> b.solve_for_reaction_loads(R1, R2) >>> b.load -3*SingularityFunction(x, 0, -1) + 6*SingularityFunction(x, 2, 0) - 9*SingularityFunction(x, 4, -1) >>> b.shear_force() 3*SingularityFunction(x, 0, 0) - 6*SingularityFunction(x, 2, 1) + 9*SingularityFunction(x, 4, 0) >>> b.bending_moment() 3*SingularityFunction(x, 0, 1) - 3*SingularityFunction(x, 2, 2) + 9*SingularityFunction(x, 4, 1) >>> b.slope() (-3*SingularityFunction(x, 0, 2)/2 + SingularityFunction(x, 2, 3) - 9*SingularityFunction(x, 4, 2)/2 + 7)/(E*I) >>> b.deflection() (7*x - SingularityFunction(x, 0, 3)/2 + SingularityFunction(x, 2, 4)/4 - 3*SingularityFunction(x, 4, 3)/2)/(E*I) >>> b.deflection().rewrite(Piecewise) (7*x - Piecewise((x**3, x > 0), (0, True))/2 - 3*Piecewise(((x - 4)**3, x > 4), (0, True))/2 + Piecewise(((x - 2)**4, x > 2), (0, True))/4)/(E*I) Calculate the support reactions for a fully symbolic beam of length L. There are two simple supports below the beam, one at the starting point and another at the ending point of the beam. The deflection of the beam at the end is restricted. The beam is loaded with: * a downward point load P1 applied at L/4 * an upward point load P2 applied at L/8 * a counterclockwise moment M1 applied at L/2 * a clockwise moment M2 applied at 3*L/4 * a distributed constant load q1, applied downward, starting from L/2 up to 3*L/4 * a distributed constant load q2, applied upward, starting from 3*L/4 up to L No assumptions are needed for symbolic loads. However, defining a positive length will help the algorithm to compute the solution. >>> E, I = symbols('E, I') >>> L = symbols("L", positive=True) >>> P1, P2, M1, M2, q1, q2 = symbols("P1, P2, M1, M2, q1, q2") >>> R1, R2 = symbols('R1, R2') >>> b = Beam(L, E, I) >>> b.apply_load(R1, 0, -1) >>> b.apply_load(R2, L, -1) >>> b.apply_load(P1, L/4, -1) >>> b.apply_load(-P2, L/8, -1) >>> b.apply_load(M1, L/2, -2) >>> b.apply_load(-M2, 3*L/4, -2) >>> b.apply_load(q1, L/2, 0, 3*L/4) >>> b.apply_load(-q2, 3*L/4, 0, L) >>> b.bc_deflection = [(0, 0), (L, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> print(b.reaction_loads[R1]) (-3*L**2*q1 + L**2*q2 - 24*L*P1 + 28*L*P2 - 32*M1 + 32*M2)/(32*L) >>> print(b.reaction_loads[R2]) (-5*L**2*q1 + 7*L**2*q2 - 8*L*P1 + 4*L*P2 + 32*M1 - 32*M2)/(32*L) """ def __init__(self, length, elastic_modulus, second_moment, area=Symbol('A'), variable=Symbol('x'), base_char='C'): """Initializes the class. Parameters ========== length : Sympifyable A Symbol or value representing the Beam's length. elastic_modulus : Sympifyable A SymPy expression representing the Beam's Modulus of Elasticity. It is a measure of the stiffness of the Beam material. It can also be a continuous function of position along the beam. second_moment : Sympifyable or Geometry object Describes the cross-section of the beam via a SymPy expression representing the Beam's second moment of area. It is a geometrical property of an area which reflects how its points are distributed with respect to its neutral axis. It can also be a continuous function of position along the beam. Alternatively ``second_moment`` can be a shape object such as a ``Polygon`` from the geometry module representing the shape of the cross-section of the beam. In such cases, it is assumed that the x-axis of the shape object is aligned with the bending axis of the beam. The second moment of area will be computed from the shape object internally. area : Symbol/float Represents the cross-section area of beam variable : Symbol, optional A Symbol object that will be used as the variable along the beam while representing the load, shear, moment, slope and deflection curve. By default, it is set to ``Symbol('x')``. base_char : String, optional A String that will be used as base character to generate sequential symbols for integration constants in cases where boundary conditions are not sufficient to solve them. """ self.length = length self.elastic_modulus = elastic_modulus if isinstance(second_moment, GeometryEntity): self.cross_section = second_moment else: self.cross_section = None self.second_moment = second_moment self.variable = variable self._base_char = base_char self._boundary_conditions = {'deflection': [], 'slope': []} self._load = 0 self.area = area self._applied_supports = [] self._support_as_loads = [] self._applied_loads = [] self._reaction_loads = {} self._ild_reactions = {} self._ild_shear = 0 self._ild_moment = 0 # _original_load is a copy of _load equations with unsubstituted reaction # forces. It is used for calculating reaction forces in case of I.L.D. self._original_load = 0 self._composite_type = None self._hinge_position = None def __str__(self): shape_description = self._cross_section if self._cross_section else self._second_moment str_sol = 'Beam({}, {}, {})'.format(sstr(self._length), sstr(self._elastic_modulus), sstr(shape_description)) return str_sol @property def reaction_loads(self): """ Returns the reaction forces in a dictionary.""" return self._reaction_loads @property def ild_shear(self): """ Returns the I.L.D. shear equation.""" return self._ild_shear @property def ild_reactions(self): """ Returns the I.L.D. reaction forces in a dictionary.""" return self._ild_reactions @property def ild_moment(self): """ Returns the I.L.D. moment equation.""" return self._ild_moment @property def length(self): """Length of the Beam.""" return self._length @length.setter def length(self, l): self._length = sympify(l) @property def area(self): """Cross-sectional area of the Beam. """ return self._area @area.setter def area(self, a): self._area = sympify(a) @property def variable(self): """ A symbol that can be used as a variable along the length of the beam while representing load distribution, shear force curve, bending moment, slope curve and the deflection curve. By default, it is set to ``Symbol('x')``, but this property is mutable. Examples ======== >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I, A = symbols('E, I, A') >>> x, y, z = symbols('x, y, z') >>> b = Beam(4, E, I) >>> b.variable x >>> b.variable = y >>> b.variable y >>> b = Beam(4, E, I, A, z) >>> b.variable z """ return self._variable @variable.setter def variable(self, v): if isinstance(v, Symbol): self._variable = v else: raise TypeError("""The variable should be a Symbol object.""") @property def elastic_modulus(self): """Young's Modulus of the Beam. """ return self._elastic_modulus @elastic_modulus.setter def elastic_modulus(self, e): self._elastic_modulus = sympify(e) @property def second_moment(self): """Second moment of area of the Beam. """ return self._second_moment @second_moment.setter def second_moment(self, i): self._cross_section = None if isinstance(i, GeometryEntity): raise ValueError("To update cross-section geometry use `cross_section` attribute") else: self._second_moment = sympify(i) @property def cross_section(self): """Cross-section of the beam""" return self._cross_section @cross_section.setter def cross_section(self, s): if s: self._second_moment = s.second_moment_of_area()[0] self._cross_section = s @property def boundary_conditions(self): """ Returns a dictionary of boundary conditions applied on the beam. The dictionary has three keywords namely moment, slope and deflection. The value of each keyword is a list of tuple, where each tuple contains location and value of a boundary condition in the format (location, value). Examples ======== There is a beam of length 4 meters. The bending moment at 0 should be 4 and at 4 it should be 0. The slope of the beam should be 1 at 0. The deflection should be 2 at 0. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> b = Beam(4, E, I) >>> b.bc_deflection = [(0, 2)] >>> b.bc_slope = [(0, 1)] >>> b.boundary_conditions {'deflection': [(0, 2)], 'slope': [(0, 1)]} Here the deflection of the beam should be ``2`` at ``0``. Similarly, the slope of the beam should be ``1`` at ``0``. """ return self._boundary_conditions @property def bc_slope(self): return self._boundary_conditions['slope'] @bc_slope.setter def bc_slope(self, s_bcs): self._boundary_conditions['slope'] = s_bcs @property def bc_deflection(self): return self._boundary_conditions['deflection'] @bc_deflection.setter def bc_deflection(self, d_bcs): self._boundary_conditions['deflection'] = d_bcs def join(self, beam, via="fixed"): """ This method joins two beams to make a new composite beam system. Passed Beam class instance is attached to the right end of calling object. This method can be used to form beams having Discontinuous values of Elastic modulus or Second moment. Parameters ========== beam : Beam class object The Beam object which would be connected to the right of calling object. via : String States the way two Beam object would get connected - For axially fixed Beams, via="fixed" - For Beams connected via hinge, via="hinge" Examples ======== There is a cantilever beam of length 4 meters. For first 2 meters its moment of inertia is `1.5*I` and `I` for the other end. A pointload of magnitude 4 N is applied from the top at its free end. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> R1, R2 = symbols('R1, R2') >>> b1 = Beam(2, E, 1.5*I) >>> b2 = Beam(2, E, I) >>> b = b1.join(b2, "fixed") >>> b.apply_load(20, 4, -1) >>> b.apply_load(R1, 0, -1) >>> b.apply_load(R2, 0, -2) >>> b.bc_slope = [(0, 0)] >>> b.bc_deflection = [(0, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> b.load 80*SingularityFunction(x, 0, -2) - 20*SingularityFunction(x, 0, -1) + 20*SingularityFunction(x, 4, -1) >>> b.slope() (-((-80*SingularityFunction(x, 0, 1) + 10*SingularityFunction(x, 0, 2) - 10*SingularityFunction(x, 4, 2))/I + 120/I)/E + 80.0/(E*I))*SingularityFunction(x, 2, 0) - 0.666666666666667*(-80*SingularityFunction(x, 0, 1) + 10*SingularityFunction(x, 0, 2) - 10*SingularityFunction(x, 4, 2))*SingularityFunction(x, 0, 0)/(E*I) + 0.666666666666667*(-80*SingularityFunction(x, 0, 1) + 10*SingularityFunction(x, 0, 2) - 10*SingularityFunction(x, 4, 2))*SingularityFunction(x, 2, 0)/(E*I) """ x = self.variable E = self.elastic_modulus new_length = self.length + beam.length if self.second_moment != beam.second_moment: new_second_moment = Piecewise((self.second_moment, x<=self.length), (beam.second_moment, x<=new_length)) else: new_second_moment = self.second_moment if via == "fixed": new_beam = Beam(new_length, E, new_second_moment, x) new_beam._composite_type = "fixed" return new_beam if via == "hinge": new_beam = Beam(new_length, E, new_second_moment, x) new_beam._composite_type = "hinge" new_beam._hinge_position = self.length return new_beam def apply_support(self, loc, type="fixed"): """ This method applies support to a particular beam object. Parameters ========== loc : Sympifyable Location of point at which support is applied. type : String Determines type of Beam support applied. To apply support structure with - zero degree of freedom, type = "fixed" - one degree of freedom, type = "pin" - two degrees of freedom, type = "roller" Examples ======== There is a beam of length 30 meters. A moment of magnitude 120 Nm is applied in the clockwise direction at the end of the beam. A pointload of magnitude 8 N is applied from the top of the beam at the starting point. There are two simple supports below the beam. One at the end and another one at a distance of 10 meters from the start. The deflection is restricted at both the supports. Using the sign convention of upward forces and clockwise moment being positive. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> b = Beam(30, E, I) >>> b.apply_support(10, 'roller') >>> b.apply_support(30, 'roller') >>> b.apply_load(-8, 0, -1) >>> b.apply_load(120, 30, -2) >>> R_10, R_30 = symbols('R_10, R_30') >>> b.solve_for_reaction_loads(R_10, R_30) >>> b.load -8*SingularityFunction(x, 0, -1) + 6*SingularityFunction(x, 10, -1) + 120*SingularityFunction(x, 30, -2) + 2*SingularityFunction(x, 30, -1) >>> b.slope() (-4*SingularityFunction(x, 0, 2) + 3*SingularityFunction(x, 10, 2) + 120*SingularityFunction(x, 30, 1) + SingularityFunction(x, 30, 2) + 4000/3)/(E*I) """ loc = sympify(loc) self._applied_supports.append((loc, type)) if type in ("pin", "roller"): reaction_load = Symbol('R_'+str(loc)) self.apply_load(reaction_load, loc, -1) self.bc_deflection.append((loc, 0)) else: reaction_load = Symbol('R_'+str(loc)) reaction_moment = Symbol('M_'+str(loc)) self.apply_load(reaction_load, loc, -1) self.apply_load(reaction_moment, loc, -2) self.bc_deflection.append((loc, 0)) self.bc_slope.append((loc, 0)) self._support_as_loads.append((reaction_moment, loc, -2, None)) self._support_as_loads.append((reaction_load, loc, -1, None)) def apply_load(self, value, start, order, end=None): """ This method adds up the loads given to a particular beam object. Parameters ========== value : Sympifyable The value inserted should have the units [Force/(Distance**(n+1)] where n is the order of applied load. Units for applied loads: - For moments, unit = kN*m - For point loads, unit = kN - For constant distributed load, unit = kN/m - For ramp loads, unit = kN/m/m - For parabolic ramp loads, unit = kN/m/m/m - ... so on. start : Sympifyable The starting point of the applied load. For point moments and point forces this is the location of application. order : Integer The order of the applied load. - For moments, order = -2 - For point loads, order =-1 - For constant distributed load, order = 0 - For ramp loads, order = 1 - For parabolic ramp loads, order = 2 - ... so on. end : Sympifyable, optional An optional argument that can be used if the load has an end point within the length of the beam. Examples ======== There is a beam of length 4 meters. A moment of magnitude 3 Nm is applied in the clockwise direction at the starting point of the beam. A point load of magnitude 4 N is applied from the top of the beam at 2 meters from the starting point and a parabolic ramp load of magnitude 2 N/m is applied below the beam starting from 2 meters to 3 meters away from the starting point of the beam. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> b = Beam(4, E, I) >>> b.apply_load(-3, 0, -2) >>> b.apply_load(4, 2, -1) >>> b.apply_load(-2, 2, 2, end=3) >>> b.load -3*SingularityFunction(x, 0, -2) + 4*SingularityFunction(x, 2, -1) - 2*SingularityFunction(x, 2, 2) + 2*SingularityFunction(x, 3, 0) + 4*SingularityFunction(x, 3, 1) + 2*SingularityFunction(x, 3, 2) """ x = self.variable value = sympify(value) start = sympify(start) order = sympify(order) self._applied_loads.append((value, start, order, end)) self._load += value*SingularityFunction(x, start, order) self._original_load += value*SingularityFunction(x, start, order) if end: # load has an end point within the length of the beam. self._handle_end(x, value, start, order, end, type="apply") def remove_load(self, value, start, order, end=None): """ This method removes a particular load present on the beam object. Returns a ValueError if the load passed as an argument is not present on the beam. Parameters ========== value : Sympifyable The magnitude of an applied load. start : Sympifyable The starting point of the applied load. For point moments and point forces this is the location of application. order : Integer The order of the applied load. - For moments, order= -2 - For point loads, order=-1 - For constant distributed load, order=0 - For ramp loads, order=1 - For parabolic ramp loads, order=2 - ... so on. end : Sympifyable, optional An optional argument that can be used if the load has an end point within the length of the beam. Examples ======== There is a beam of length 4 meters. A moment of magnitude 3 Nm is applied in the clockwise direction at the starting point of the beam. A pointload of magnitude 4 N is applied from the top of the beam at 2 meters from the starting point and a parabolic ramp load of magnitude 2 N/m is applied below the beam starting from 2 meters to 3 meters away from the starting point of the beam. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> b = Beam(4, E, I) >>> b.apply_load(-3, 0, -2) >>> b.apply_load(4, 2, -1) >>> b.apply_load(-2, 2, 2, end=3) >>> b.load -3*SingularityFunction(x, 0, -2) + 4*SingularityFunction(x, 2, -1) - 2*SingularityFunction(x, 2, 2) + 2*SingularityFunction(x, 3, 0) + 4*SingularityFunction(x, 3, 1) + 2*SingularityFunction(x, 3, 2) >>> b.remove_load(-2, 2, 2, end = 3) >>> b.load -3*SingularityFunction(x, 0, -2) + 4*SingularityFunction(x, 2, -1) """ x = self.variable value = sympify(value) start = sympify(start) order = sympify(order) if (value, start, order, end) in self._applied_loads: self._load -= value*SingularityFunction(x, start, order) self._original_load -= value*SingularityFunction(x, start, order) self._applied_loads.remove((value, start, order, end)) else: msg = "No such load distribution exists on the beam object." raise ValueError(msg) if end: # load has an end point within the length of the beam. self._handle_end(x, value, start, order, end, type="remove") def _handle_end(self, x, value, start, order, end, type): """ This functions handles the optional `end` value in the `apply_load` and `remove_load` functions. When the value of end is not NULL, this function will be executed. """ if order.is_negative: msg = ("If 'end' is provided the 'order' of the load cannot " "be negative, i.e. 'end' is only valid for distributed " "loads.") raise ValueError(msg) # NOTE : A Taylor series can be used to define the summation of # singularity functions that subtract from the load past the end # point such that it evaluates to zero past 'end'. f = value*x**order if type == "apply": # iterating for "apply_load" method for i in range(0, order + 1): self._load -= (f.diff(x, i).subs(x, end - start) * SingularityFunction(x, end, i)/factorial(i)) self._original_load -= (f.diff(x, i).subs(x, end - start) * SingularityFunction(x, end, i)/factorial(i)) elif type == "remove": # iterating for "remove_load" method for i in range(0, order + 1): self._load += (f.diff(x, i).subs(x, end - start) * SingularityFunction(x, end, i)/factorial(i)) self._original_load += (f.diff(x, i).subs(x, end - start) * SingularityFunction(x, end, i)/factorial(i)) @property def load(self): """ Returns a Singularity Function expression which represents the load distribution curve of the Beam object. Examples ======== There is a beam of length 4 meters. A moment of magnitude 3 Nm is applied in the clockwise direction at the starting point of the beam. A point load of magnitude 4 N is applied from the top of the beam at 2 meters from the starting point and a parabolic ramp load of magnitude 2 N/m is applied below the beam starting from 3 meters away from the starting point of the beam. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> b = Beam(4, E, I) >>> b.apply_load(-3, 0, -2) >>> b.apply_load(4, 2, -1) >>> b.apply_load(-2, 3, 2) >>> b.load -3*SingularityFunction(x, 0, -2) + 4*SingularityFunction(x, 2, -1) - 2*SingularityFunction(x, 3, 2) """ return self._load @property def applied_loads(self): """ Returns a list of all loads applied on the beam object. Each load in the list is a tuple of form (value, start, order, end). Examples ======== There is a beam of length 4 meters. A moment of magnitude 3 Nm is applied in the clockwise direction at the starting point of the beam. A pointload of magnitude 4 N is applied from the top of the beam at 2 meters from the starting point. Another pointload of magnitude 5 N is applied at same position. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> b = Beam(4, E, I) >>> b.apply_load(-3, 0, -2) >>> b.apply_load(4, 2, -1) >>> b.apply_load(5, 2, -1) >>> b.load -3*SingularityFunction(x, 0, -2) + 9*SingularityFunction(x, 2, -1) >>> b.applied_loads [(-3, 0, -2, None), (4, 2, -1, None), (5, 2, -1, None)] """ return self._applied_loads def _solve_hinge_beams(self, *reactions): """Method to find integration constants and reactional variables in a composite beam connected via hinge. This method resolves the composite Beam into its sub-beams and then equations of shear force, bending moment, slope and deflection are evaluated for both of them separately. These equations are then solved for unknown reactions and integration constants using the boundary conditions applied on the Beam. Equal deflection of both sub-beams at the hinge joint gives us another equation to solve the system. Examples ======== A combined beam, with constant fkexural rigidity E*I, is formed by joining a Beam of length 2*l to the right of another Beam of length l. The whole beam is fixed at both of its both end. A point load of magnitude P is also applied from the top at a distance of 2*l from starting point. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> l=symbols('l', positive=True) >>> b1=Beam(l, E, I) >>> b2=Beam(2*l, E, I) >>> b=b1.join(b2,"hinge") >>> M1, A1, M2, A2, P = symbols('M1 A1 M2 A2 P') >>> b.apply_load(A1,0,-1) >>> b.apply_load(M1,0,-2) >>> b.apply_load(P,2*l,-1) >>> b.apply_load(A2,3*l,-1) >>> b.apply_load(M2,3*l,-2) >>> b.bc_slope=[(0,0), (3*l, 0)] >>> b.bc_deflection=[(0,0), (3*l, 0)] >>> b.solve_for_reaction_loads(M1, A1, M2, A2) >>> b.reaction_loads {A1: -5*P/18, A2: -13*P/18, M1: 5*P*l/18, M2: -4*P*l/9} >>> b.slope() (5*P*l*SingularityFunction(x, 0, 1)/18 - 5*P*SingularityFunction(x, 0, 2)/36 + 5*P*SingularityFunction(x, l, 2)/36)*SingularityFunction(x, 0, 0)/(E*I) - (5*P*l*SingularityFunction(x, 0, 1)/18 - 5*P*SingularityFunction(x, 0, 2)/36 + 5*P*SingularityFunction(x, l, 2)/36)*SingularityFunction(x, l, 0)/(E*I) + (P*l**2/18 - 4*P*l*SingularityFunction(-l + x, 2*l, 1)/9 - 5*P*SingularityFunction(-l + x, 0, 2)/36 + P*SingularityFunction(-l + x, l, 2)/2 - 13*P*SingularityFunction(-l + x, 2*l, 2)/36)*SingularityFunction(x, l, 0)/(E*I) >>> b.deflection() (5*P*l*SingularityFunction(x, 0, 2)/36 - 5*P*SingularityFunction(x, 0, 3)/108 + 5*P*SingularityFunction(x, l, 3)/108)*SingularityFunction(x, 0, 0)/(E*I) - (5*P*l*SingularityFunction(x, 0, 2)/36 - 5*P*SingularityFunction(x, 0, 3)/108 + 5*P*SingularityFunction(x, l, 3)/108)*SingularityFunction(x, l, 0)/(E*I) + (5*P*l**3/54 + P*l**2*(-l + x)/18 - 2*P*l*SingularityFunction(-l + x, 2*l, 2)/9 - 5*P*SingularityFunction(-l + x, 0, 3)/108 + P*SingularityFunction(-l + x, l, 3)/6 - 13*P*SingularityFunction(-l + x, 2*l, 3)/108)*SingularityFunction(x, l, 0)/(E*I) """ x = self.variable l = self._hinge_position E = self._elastic_modulus I = self._second_moment if isinstance(I, Piecewise): I1 = I.args[0][0] I2 = I.args[1][0] else: I1 = I2 = I load_1 = 0 # Load equation on first segment of composite beam load_2 = 0 # Load equation on second segment of composite beam # Distributing load on both segments for load in self.applied_loads: if load[1] < l: load_1 += load[0]*SingularityFunction(x, load[1], load[2]) if load[2] == 0: load_1 -= load[0]*SingularityFunction(x, load[3], load[2]) elif load[2] > 0: load_1 -= load[0]*SingularityFunction(x, load[3], load[2]) + load[0]*SingularityFunction(x, load[3], 0) elif load[1] == l: load_1 += load[0]*SingularityFunction(x, load[1], load[2]) load_2 += load[0]*SingularityFunction(x, load[1] - l, load[2]) elif load[1] > l: load_2 += load[0]*SingularityFunction(x, load[1] - l, load[2]) if load[2] == 0: load_2 -= load[0]*SingularityFunction(x, load[3] - l, load[2]) elif load[2] > 0: load_2 -= load[0]*SingularityFunction(x, load[3] - l, load[2]) + load[0]*SingularityFunction(x, load[3] - l, 0) h = Symbol('h') # Force due to hinge load_1 += h*SingularityFunction(x, l, -1) load_2 -= h*SingularityFunction(x, 0, -1) eq = [] shear_1 = integrate(load_1, x) shear_curve_1 = limit(shear_1, x, l) eq.append(shear_curve_1) bending_1 = integrate(shear_1, x) moment_curve_1 = limit(bending_1, x, l) eq.append(moment_curve_1) shear_2 = integrate(load_2, x) shear_curve_2 = limit(shear_2, x, self.length - l) eq.append(shear_curve_2) bending_2 = integrate(shear_2, x) moment_curve_2 = limit(bending_2, x, self.length - l) eq.append(moment_curve_2) C1 = Symbol('C1') C2 = Symbol('C2') C3 = Symbol('C3') C4 = Symbol('C4') slope_1 = S.One/(E*I1)*(integrate(bending_1, x) + C1) def_1 = S.One/(E*I1)*(integrate((E*I)*slope_1, x) + C1*x + C2) slope_2 = S.One/(E*I2)*(integrate(integrate(integrate(load_2, x), x), x) + C3) def_2 = S.One/(E*I2)*(integrate((E*I)*slope_2, x) + C4) for position, value in self.bc_slope: if position>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> R1, R2 = symbols('R1, R2') >>> b = Beam(30, E, I) >>> b.apply_load(-8, 0, -1) >>> b.apply_load(R1, 10, -1) # Reaction force at x = 10 >>> b.apply_load(R2, 30, -1) # Reaction force at x = 30 >>> b.apply_load(120, 30, -2) >>> b.bc_deflection = [(10, 0), (30, 0)] >>> b.load R1*SingularityFunction(x, 10, -1) + R2*SingularityFunction(x, 30, -1) - 8*SingularityFunction(x, 0, -1) + 120*SingularityFunction(x, 30, -2) >>> b.solve_for_reaction_loads(R1, R2) >>> b.reaction_loads {R1: 6, R2: 2} >>> b.load -8*SingularityFunction(x, 0, -1) + 6*SingularityFunction(x, 10, -1) + 120*SingularityFunction(x, 30, -2) + 2*SingularityFunction(x, 30, -1) """ if self._composite_type == "hinge": return self._solve_hinge_beams(*reactions) x = self.variable l = self.length C3 = Symbol('C3') C4 = Symbol('C4') shear_curve = limit(self.shear_force(), x, l) moment_curve = limit(self.bending_moment(), x, l) slope_eqs = [] deflection_eqs = [] slope_curve = integrate(self.bending_moment(), x) + C3 for position, value in self._boundary_conditions['slope']: eqs = slope_curve.subs(x, position) - value slope_eqs.append(eqs) deflection_curve = integrate(slope_curve, x) + C4 for position, value in self._boundary_conditions['deflection']: eqs = deflection_curve.subs(x, position) - value deflection_eqs.append(eqs) solution = list((linsolve([shear_curve, moment_curve] + slope_eqs + deflection_eqs, (C3, C4) + reactions).args)[0]) solution = solution[2:] self._reaction_loads = dict(zip(reactions, solution)) self._load = self._load.subs(self._reaction_loads) def shear_force(self): """ Returns a Singularity Function expression which represents the shear force curve of the Beam object. Examples ======== There is a beam of length 30 meters. A moment of magnitude 120 Nm is applied in the clockwise direction at the end of the beam. A pointload of magnitude 8 N is applied from the top of the beam at the starting point. There are two simple supports below the beam. One at the end and another one at a distance of 10 meters from the start. The deflection is restricted at both the supports. Using the sign convention of upward forces and clockwise moment being positive. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> R1, R2 = symbols('R1, R2') >>> b = Beam(30, E, I) >>> b.apply_load(-8, 0, -1) >>> b.apply_load(R1, 10, -1) >>> b.apply_load(R2, 30, -1) >>> b.apply_load(120, 30, -2) >>> b.bc_deflection = [(10, 0), (30, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> b.shear_force() 8*SingularityFunction(x, 0, 0) - 6*SingularityFunction(x, 10, 0) - 120*SingularityFunction(x, 30, -1) - 2*SingularityFunction(x, 30, 0) """ x = self.variable return -integrate(self.load, x) def max_shear_force(self): """Returns maximum Shear force and its coordinate in the Beam object.""" shear_curve = self.shear_force() x = self.variable terms = shear_curve.args singularity = [] # Points at which shear function changes for term in terms: if isinstance(term, Mul): term = term.args[-1] # SingularityFunction in the term singularity.append(term.args[1]) singularity.sort() singularity = list(set(singularity)) intervals = [] # List of Intervals with discrete value of shear force shear_values = [] # List of values of shear force in each interval for i, s in enumerate(singularity): if s == 0: continue try: shear_slope = Piecewise((float("nan"), x<=singularity[i-1]),(self._load.rewrite(Piecewise), x>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> R1, R2 = symbols('R1, R2') >>> b = Beam(30, E, I) >>> b.apply_load(-8, 0, -1) >>> b.apply_load(R1, 10, -1) >>> b.apply_load(R2, 30, -1) >>> b.apply_load(120, 30, -2) >>> b.bc_deflection = [(10, 0), (30, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> b.bending_moment() 8*SingularityFunction(x, 0, 1) - 6*SingularityFunction(x, 10, 1) - 120*SingularityFunction(x, 30, 0) - 2*SingularityFunction(x, 30, 1) """ x = self.variable return integrate(self.shear_force(), x) def max_bmoment(self): """Returns maximum Shear force and its coordinate in the Beam object.""" bending_curve = self.bending_moment() x = self.variable terms = bending_curve.args singularity = [] # Points at which bending moment changes for term in terms: if isinstance(term, Mul): term = term.args[-1] # SingularityFunction in the term singularity.append(term.args[1]) singularity.sort() singularity = list(set(singularity)) intervals = [] # List of Intervals with discrete value of bending moment moment_values = [] # List of values of bending moment in each interval for i, s in enumerate(singularity): if s == 0: continue try: moment_slope = Piecewise((float("nan"), x<=singularity[i-1]),(self.shear_force().rewrite(Piecewise), x>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> b = Beam(10, E, I) >>> b.apply_load(-4, 0, -1) >>> b.apply_load(-46, 6, -1) >>> b.apply_load(10, 2, -1) >>> b.apply_load(20, 4, -1) >>> b.apply_load(3, 6, 0) >>> b.point_cflexure() [10/3] """ # To restrict the range within length of the Beam moment_curve = Piecewise((float("nan"), self.variable<=0), (self.bending_moment(), self.variable>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> R1, R2 = symbols('R1, R2') >>> b = Beam(30, E, I) >>> b.apply_load(-8, 0, -1) >>> b.apply_load(R1, 10, -1) >>> b.apply_load(R2, 30, -1) >>> b.apply_load(120, 30, -2) >>> b.bc_deflection = [(10, 0), (30, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> b.slope() (-4*SingularityFunction(x, 0, 2) + 3*SingularityFunction(x, 10, 2) + 120*SingularityFunction(x, 30, 1) + SingularityFunction(x, 30, 2) + 4000/3)/(E*I) """ x = self.variable E = self.elastic_modulus I = self.second_moment if self._composite_type == "hinge": return self._hinge_beam_slope if not self._boundary_conditions['slope']: return diff(self.deflection(), x) if isinstance(I, Piecewise) and self._composite_type == "fixed": args = I.args slope = 0 prev_slope = 0 prev_end = 0 for i in range(len(args)): if i != 0: prev_end = args[i-1][1].args[1] slope_value = -S.One/E*integrate(self.bending_moment()/args[i][0], (x, prev_end, x)) if i != len(args) - 1: slope += (prev_slope + slope_value)*SingularityFunction(x, prev_end, 0) - \ (prev_slope + slope_value)*SingularityFunction(x, args[i][1].args[1], 0) else: slope += (prev_slope + slope_value)*SingularityFunction(x, prev_end, 0) prev_slope = slope_value.subs(x, args[i][1].args[1]) return slope C3 = Symbol('C3') slope_curve = -integrate(S.One/(E*I)*self.bending_moment(), x) + C3 bc_eqs = [] for position, value in self._boundary_conditions['slope']: eqs = slope_curve.subs(x, position) - value bc_eqs.append(eqs) constants = list(linsolve(bc_eqs, C3)) slope_curve = slope_curve.subs({C3: constants[0][0]}) return slope_curve def deflection(self): """ Returns a Singularity Function expression which represents the elastic curve or deflection of the Beam object. Examples ======== There is a beam of length 30 meters. A moment of magnitude 120 Nm is applied in the clockwise direction at the end of the beam. A pointload of magnitude 8 N is applied from the top of the beam at the starting point. There are two simple supports below the beam. One at the end and another one at a distance of 10 meters from the start. The deflection is restricted at both the supports. Using the sign convention of upward forces and clockwise moment being positive. >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> E, I = symbols('E, I') >>> R1, R2 = symbols('R1, R2') >>> b = Beam(30, E, I) >>> b.apply_load(-8, 0, -1) >>> b.apply_load(R1, 10, -1) >>> b.apply_load(R2, 30, -1) >>> b.apply_load(120, 30, -2) >>> b.bc_deflection = [(10, 0), (30, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> b.deflection() (4000*x/3 - 4*SingularityFunction(x, 0, 3)/3 + SingularityFunction(x, 10, 3) + 60*SingularityFunction(x, 30, 2) + SingularityFunction(x, 30, 3)/3 - 12000)/(E*I) """ x = self.variable E = self.elastic_modulus I = self.second_moment if self._composite_type == "hinge": return self._hinge_beam_deflection if not self._boundary_conditions['deflection'] and not self._boundary_conditions['slope']: if isinstance(I, Piecewise) and self._composite_type == "fixed": args = I.args prev_slope = 0 prev_def = 0 prev_end = 0 deflection = 0 for i in range(len(args)): if i != 0: prev_end = args[i-1][1].args[1] slope_value = -S.One/E*integrate(self.bending_moment()/args[i][0], (x, prev_end, x)) recent_segment_slope = prev_slope + slope_value deflection_value = integrate(recent_segment_slope, (x, prev_end, x)) if i != len(args) - 1: deflection += (prev_def + deflection_value)*SingularityFunction(x, prev_end, 0) \ - (prev_def + deflection_value)*SingularityFunction(x, args[i][1].args[1], 0) else: deflection += (prev_def + deflection_value)*SingularityFunction(x, prev_end, 0) prev_slope = slope_value.subs(x, args[i][1].args[1]) prev_def = deflection_value.subs(x, args[i][1].args[1]) return deflection base_char = self._base_char constants = symbols(base_char + '3:5') return S.One/(E*I)*integrate(-integrate(self.bending_moment(), x), x) + constants[0]*x + constants[1] elif not self._boundary_conditions['deflection']: base_char = self._base_char constant = symbols(base_char + '4') return integrate(self.slope(), x) + constant elif not self._boundary_conditions['slope'] and self._boundary_conditions['deflection']: if isinstance(I, Piecewise) and self._composite_type == "fixed": args = I.args prev_slope = 0 prev_def = 0 prev_end = 0 deflection = 0 for i in range(len(args)): if i != 0: prev_end = args[i-1][1].args[1] slope_value = -S.One/E*integrate(self.bending_moment()/args[i][0], (x, prev_end, x)) recent_segment_slope = prev_slope + slope_value deflection_value = integrate(recent_segment_slope, (x, prev_end, x)) if i != len(args) - 1: deflection += (prev_def + deflection_value)*SingularityFunction(x, prev_end, 0) \ - (prev_def + deflection_value)*SingularityFunction(x, args[i][1].args[1], 0) else: deflection += (prev_def + deflection_value)*SingularityFunction(x, prev_end, 0) prev_slope = slope_value.subs(x, args[i][1].args[1]) prev_def = deflection_value.subs(x, args[i][1].args[1]) return deflection base_char = self._base_char C3, C4 = symbols(base_char + '3:5') # Integration constants slope_curve = -integrate(self.bending_moment(), x) + C3 deflection_curve = integrate(slope_curve, x) + C4 bc_eqs = [] for position, value in self._boundary_conditions['deflection']: eqs = deflection_curve.subs(x, position) - value bc_eqs.append(eqs) constants = list(linsolve(bc_eqs, (C3, C4))) deflection_curve = deflection_curve.subs({C3: constants[0][0], C4: constants[0][1]}) return S.One/(E*I)*deflection_curve if isinstance(I, Piecewise) and self._composite_type == "fixed": args = I.args prev_slope = 0 prev_def = 0 prev_end = 0 deflection = 0 for i in range(len(args)): if i != 0: prev_end = args[i-1][1].args[1] slope_value = S.One/E*integrate(self.bending_moment()/args[i][0], (x, prev_end, x)) recent_segment_slope = prev_slope + slope_value deflection_value = integrate(recent_segment_slope, (x, prev_end, x)) if i != len(args) - 1: deflection += (prev_def + deflection_value)*SingularityFunction(x, prev_end, 0) \ - (prev_def + deflection_value)*SingularityFunction(x, args[i][1].args[1], 0) else: deflection += (prev_def + deflection_value)*SingularityFunction(x, prev_end, 0) prev_slope = slope_value.subs(x, args[i][1].args[1]) prev_def = deflection_value.subs(x, args[i][1].args[1]) return deflection C4 = Symbol('C4') deflection_curve = integrate(self.slope(), x) + C4 bc_eqs = [] for position, value in self._boundary_conditions['deflection']: eqs = deflection_curve.subs(x, position) - value bc_eqs.append(eqs) constants = list(linsolve(bc_eqs, C4)) deflection_curve = deflection_curve.subs({C4: constants[0][0]}) return deflection_curve def max_deflection(self): """ Returns point of max deflection and its corresponding deflection value in a Beam object. """ # To restrict the range within length of the Beam slope_curve = Piecewise((float("nan"), self.variable<=0), (self.slope(), self.variable>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> R1, R2 = symbols('R1, R2') >>> b = Beam(8, 200*(10**9), 400*(10**-6), 2) >>> b.apply_load(5000, 2, -1) >>> b.apply_load(R1, 0, -1) >>> b.apply_load(R2, 8, -1) >>> b.apply_load(10000, 4, 0, end=8) >>> b.bc_deflection = [(0, 0), (8, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> b.plot_shear_stress() Plot object containing: [0]: cartesian line: 6875*SingularityFunction(x, 0, 0) - 2500*SingularityFunction(x, 2, 0) - 5000*SingularityFunction(x, 4, 1) + 15625*SingularityFunction(x, 8, 0) + 5000*SingularityFunction(x, 8, 1) for x over (0.0, 8.0) """ shear_stress = self.shear_stress() x = self.variable length = self.length if subs is None: subs = {} for sym in shear_stress.atoms(Symbol): if sym != x and sym not in subs: raise ValueError('value of %s was not passed.' %sym) if length in subs: length = subs[length] # Returns Plot of Shear Stress return plot (shear_stress.subs(subs), (x, 0, length), title='Shear Stress', xlabel=r'$\mathrm{x}$', ylabel=r'$\tau$', line_color='r') def plot_shear_force(self, subs=None): """ Returns a plot for Shear force present in the Beam object. Parameters ========== subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 8 meters. A constant distributed load of 10 KN/m is applied from half of the beam till the end. There are two simple supports below the beam, one at the starting point and another at the ending point of the beam. A pointload of magnitude 5 KN is also applied from top of the beam, at a distance of 4 meters from the starting point. Take E = 200 GPa and I = 400*(10**-6) meter**4. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> R1, R2 = symbols('R1, R2') >>> b = Beam(8, 200*(10**9), 400*(10**-6)) >>> b.apply_load(5000, 2, -1) >>> b.apply_load(R1, 0, -1) >>> b.apply_load(R2, 8, -1) >>> b.apply_load(10000, 4, 0, end=8) >>> b.bc_deflection = [(0, 0), (8, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> b.plot_shear_force() Plot object containing: [0]: cartesian line: 13750*SingularityFunction(x, 0, 0) - 5000*SingularityFunction(x, 2, 0) - 10000*SingularityFunction(x, 4, 1) + 31250*SingularityFunction(x, 8, 0) + 10000*SingularityFunction(x, 8, 1) for x over (0.0, 8.0) """ shear_force = self.shear_force() if subs is None: subs = {} for sym in shear_force.atoms(Symbol): if sym == self.variable: continue if sym not in subs: raise ValueError('Value of %s was not passed.' %sym) if self.length in subs: length = subs[self.length] else: length = self.length return plot(shear_force.subs(subs), (self.variable, 0, length), title='Shear Force', xlabel=r'$\mathrm{x}$', ylabel=r'$\mathrm{V}$', line_color='g') def plot_bending_moment(self, subs=None): """ Returns a plot for Bending moment present in the Beam object. Parameters ========== subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 8 meters. A constant distributed load of 10 KN/m is applied from half of the beam till the end. There are two simple supports below the beam, one at the starting point and another at the ending point of the beam. A pointload of magnitude 5 KN is also applied from top of the beam, at a distance of 4 meters from the starting point. Take E = 200 GPa and I = 400*(10**-6) meter**4. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> R1, R2 = symbols('R1, R2') >>> b = Beam(8, 200*(10**9), 400*(10**-6)) >>> b.apply_load(5000, 2, -1) >>> b.apply_load(R1, 0, -1) >>> b.apply_load(R2, 8, -1) >>> b.apply_load(10000, 4, 0, end=8) >>> b.bc_deflection = [(0, 0), (8, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> b.plot_bending_moment() Plot object containing: [0]: cartesian line: 13750*SingularityFunction(x, 0, 1) - 5000*SingularityFunction(x, 2, 1) - 5000*SingularityFunction(x, 4, 2) + 31250*SingularityFunction(x, 8, 1) + 5000*SingularityFunction(x, 8, 2) for x over (0.0, 8.0) """ bending_moment = self.bending_moment() if subs is None: subs = {} for sym in bending_moment.atoms(Symbol): if sym == self.variable: continue if sym not in subs: raise ValueError('Value of %s was not passed.' %sym) if self.length in subs: length = subs[self.length] else: length = self.length return plot(bending_moment.subs(subs), (self.variable, 0, length), title='Bending Moment', xlabel=r'$\mathrm{x}$', ylabel=r'$\mathrm{M}$', line_color='b') def plot_slope(self, subs=None): """ Returns a plot for slope of deflection curve of the Beam object. Parameters ========== subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 8 meters. A constant distributed load of 10 KN/m is applied from half of the beam till the end. There are two simple supports below the beam, one at the starting point and another at the ending point of the beam. A pointload of magnitude 5 KN is also applied from top of the beam, at a distance of 4 meters from the starting point. Take E = 200 GPa and I = 400*(10**-6) meter**4. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> R1, R2 = symbols('R1, R2') >>> b = Beam(8, 200*(10**9), 400*(10**-6)) >>> b.apply_load(5000, 2, -1) >>> b.apply_load(R1, 0, -1) >>> b.apply_load(R2, 8, -1) >>> b.apply_load(10000, 4, 0, end=8) >>> b.bc_deflection = [(0, 0), (8, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> b.plot_slope() Plot object containing: [0]: cartesian line: -8.59375e-5*SingularityFunction(x, 0, 2) + 3.125e-5*SingularityFunction(x, 2, 2) + 2.08333333333333e-5*SingularityFunction(x, 4, 3) - 0.0001953125*SingularityFunction(x, 8, 2) - 2.08333333333333e-5*SingularityFunction(x, 8, 3) + 0.00138541666666667 for x over (0.0, 8.0) """ slope = self.slope() if subs is None: subs = {} for sym in slope.atoms(Symbol): if sym == self.variable: continue if sym not in subs: raise ValueError('Value of %s was not passed.' %sym) if self.length in subs: length = subs[self.length] else: length = self.length return plot(slope.subs(subs), (self.variable, 0, length), title='Slope', xlabel=r'$\mathrm{x}$', ylabel=r'$\theta$', line_color='m') def plot_deflection(self, subs=None): """ Returns a plot for deflection curve of the Beam object. Parameters ========== subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 8 meters. A constant distributed load of 10 KN/m is applied from half of the beam till the end. There are two simple supports below the beam, one at the starting point and another at the ending point of the beam. A pointload of magnitude 5 KN is also applied from top of the beam, at a distance of 4 meters from the starting point. Take E = 200 GPa and I = 400*(10**-6) meter**4. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> R1, R2 = symbols('R1, R2') >>> b = Beam(8, 200*(10**9), 400*(10**-6)) >>> b.apply_load(5000, 2, -1) >>> b.apply_load(R1, 0, -1) >>> b.apply_load(R2, 8, -1) >>> b.apply_load(10000, 4, 0, end=8) >>> b.bc_deflection = [(0, 0), (8, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> b.plot_deflection() Plot object containing: [0]: cartesian line: 0.00138541666666667*x - 2.86458333333333e-5*SingularityFunction(x, 0, 3) + 1.04166666666667e-5*SingularityFunction(x, 2, 3) + 5.20833333333333e-6*SingularityFunction(x, 4, 4) - 6.51041666666667e-5*SingularityFunction(x, 8, 3) - 5.20833333333333e-6*SingularityFunction(x, 8, 4) for x over (0.0, 8.0) """ deflection = self.deflection() if subs is None: subs = {} for sym in deflection.atoms(Symbol): if sym == self.variable: continue if sym not in subs: raise ValueError('Value of %s was not passed.' %sym) if self.length in subs: length = subs[self.length] else: length = self.length return plot(deflection.subs(subs), (self.variable, 0, length), title='Deflection', xlabel=r'$\mathrm{x}$', ylabel=r'$\delta$', line_color='r') def plot_loading_results(self, subs=None): """ Returns a subplot of Shear Force, Bending Moment, Slope and Deflection of the Beam object. Parameters ========== subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 8 meters. A constant distributed load of 10 KN/m is applied from half of the beam till the end. There are two simple supports below the beam, one at the starting point and another at the ending point of the beam. A pointload of magnitude 5 KN is also applied from top of the beam, at a distance of 4 meters from the starting point. Take E = 200 GPa and I = 400*(10**-6) meter**4. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> R1, R2 = symbols('R1, R2') >>> b = Beam(8, 200*(10**9), 400*(10**-6)) >>> b.apply_load(5000, 2, -1) >>> b.apply_load(R1, 0, -1) >>> b.apply_load(R2, 8, -1) >>> b.apply_load(10000, 4, 0, end=8) >>> b.bc_deflection = [(0, 0), (8, 0)] >>> b.solve_for_reaction_loads(R1, R2) >>> axes = b.plot_loading_results() """ length = self.length variable = self.variable if subs is None: subs = {} for sym in self.deflection().atoms(Symbol): if sym == self.variable: continue if sym not in subs: raise ValueError('Value of %s was not passed.' %sym) if length in subs: length = subs[length] ax1 = plot(self.shear_force().subs(subs), (variable, 0, length), title="Shear Force", xlabel=r'$\mathrm{x}$', ylabel=r'$\mathrm{V}$', line_color='g', show=False) ax2 = plot(self.bending_moment().subs(subs), (variable, 0, length), title="Bending Moment", xlabel=r'$\mathrm{x}$', ylabel=r'$\mathrm{M}$', line_color='b', show=False) ax3 = plot(self.slope().subs(subs), (variable, 0, length), title="Slope", xlabel=r'$\mathrm{x}$', ylabel=r'$\theta$', line_color='m', show=False) ax4 = plot(self.deflection().subs(subs), (variable, 0, length), title="Deflection", xlabel=r'$\mathrm{x}$', ylabel=r'$\delta$', line_color='r', show=False) return PlotGrid(4, 1, ax1, ax2, ax3, ax4) def _solve_for_ild_equations(self): """ Helper function for I.L.D. It takes the unsubstituted copy of the load equation and uses it to calculate shear force and bending moment equations. """ x = self.variable shear_force = -integrate(self._original_load, x) bending_moment = integrate(shear_force, x) return shear_force, bending_moment def solve_for_ild_reactions(self, value, *reactions): """ Determines the Influence Line Diagram equations for reaction forces under the effect of a moving load. Parameters ========== value : Integer Magnitude of moving load reactions : The reaction forces applied on the beam. Examples ======== There is a beam of length 10 meters. There are two simple supports below the beam, one at the starting point and another at the ending point of the beam. Calculate the I.L.D. equations for reaction forces under the effect of a moving load of magnitude 1kN. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy import symbols >>> from sympy.physics.continuum_mechanics.beam import Beam >>> E, I = symbols('E, I') >>> R_0, R_10 = symbols('R_0, R_10') >>> b = Beam(10, E, I) >>> b.apply_support(0, 'roller') >>> b.apply_support(10, 'roller') >>> b.solve_for_ild_reactions(1,R_0,R_10) >>> b.ild_reactions {R_0: x/10 - 1, R_10: -x/10} """ shear_force, bending_moment = self._solve_for_ild_equations() x = self.variable l = self.length C3 = Symbol('C3') C4 = Symbol('C4') shear_curve = limit(shear_force, x, l) - value moment_curve = limit(bending_moment, x, l) - value*(l-x) slope_eqs = [] deflection_eqs = [] slope_curve = integrate(bending_moment, x) + C3 for position, value in self._boundary_conditions['slope']: eqs = slope_curve.subs(x, position) - value slope_eqs.append(eqs) deflection_curve = integrate(slope_curve, x) + C4 for position, value in self._boundary_conditions['deflection']: eqs = deflection_curve.subs(x, position) - value deflection_eqs.append(eqs) solution = list((linsolve([shear_curve, moment_curve] + slope_eqs + deflection_eqs, (C3, C4) + reactions).args)[0]) solution = solution[2:] # Determining the equations and solving them. self._ild_reactions = dict(zip(reactions, solution)) def plot_ild_reactions(self, subs=None): """ Plots the Influence Line Diagram of Reaction Forces under the effect of a moving load. This function should be called after calling solve_for_ild_reactions(). Parameters ========== subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 10 meters. A point load of magnitude 5KN is also applied from top of the beam, at a distance of 4 meters from the starting point. There are two simple supports below the beam, located at the starting point and at a distance of 7 meters from the starting point. Plot the I.L.D. equations for reactions at both support points under the effect of a moving load of magnitude 1kN. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy import symbols >>> from sympy.physics.continuum_mechanics.beam import Beam >>> E, I = symbols('E, I') >>> R_0, R_7 = symbols('R_0, R_7') >>> b = Beam(10, E, I) >>> b.apply_support(0, 'roller') >>> b.apply_support(7, 'roller') >>> b.apply_load(5,4,-1) >>> b.solve_for_ild_reactions(1,R_0,R_7) >>> b.ild_reactions {R_0: x/7 - 22/7, R_7: -x/7 - 20/7} >>> b.plot_ild_reactions() PlotGrid object containing: Plot[0]:Plot object containing: [0]: cartesian line: x/7 - 22/7 for x over (0.0, 10.0) Plot[1]:Plot object containing: [0]: cartesian line: -x/7 - 20/7 for x over (0.0, 10.0) """ if not self._ild_reactions: raise ValueError("I.L.D. reaction equations not found. Please use solve_for_ild_reactions() to generate the I.L.D. reaction equations.") x = self.variable ildplots = [] if subs is None: subs = {} for reaction in self._ild_reactions: for sym in self._ild_reactions[reaction].atoms(Symbol): if sym != x and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) for sym in self._length.atoms(Symbol): if sym != x and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) for reaction in self._ild_reactions: ildplots.append(plot(self._ild_reactions[reaction].subs(subs), (x, 0, self._length.subs(subs)), title='I.L.D. for Reactions', xlabel=x, ylabel=reaction, line_color='blue', show=False)) return PlotGrid(len(ildplots), 1, *ildplots) def solve_for_ild_shear(self, distance, value, *reactions): """ Determines the Influence Line Diagram equations for shear at a specified point under the effect of a moving load. Parameters ========== distance : Integer Distance of the point from the start of the beam for which equations are to be determined value : Integer Magnitude of moving load reactions : The reaction forces applied on the beam. Examples ======== There is a beam of length 12 meters. There are two simple supports below the beam, one at the starting point and another at a distance of 8 meters. Calculate the I.L.D. equations for Shear at a distance of 4 meters under the effect of a moving load of magnitude 1kN. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy import symbols >>> from sympy.physics.continuum_mechanics.beam import Beam >>> E, I = symbols('E, I') >>> R_0, R_8 = symbols('R_0, R_8') >>> b = Beam(12, E, I) >>> b.apply_support(0, 'roller') >>> b.apply_support(8, 'roller') >>> b.solve_for_ild_reactions(1, R_0, R_8) >>> b.solve_for_ild_shear(4, 1, R_0, R_8) >>> b.ild_shear Piecewise((x/8, x < 4), (x/8 - 1, x > 4)) """ x = self.variable l = self.length shear_force, _ = self._solve_for_ild_equations() shear_curve1 = value - limit(shear_force, x, distance) shear_curve2 = (limit(shear_force, x, l) - limit(shear_force, x, distance)) - value for reaction in reactions: shear_curve1 = shear_curve1.subs(reaction,self._ild_reactions[reaction]) shear_curve2 = shear_curve2.subs(reaction,self._ild_reactions[reaction]) shear_eq = Piecewise((shear_curve1, x < distance), (shear_curve2, x > distance)) self._ild_shear = shear_eq def plot_ild_shear(self,subs=None): """ Plots the Influence Line Diagram for Shear under the effect of a moving load. This function should be called after calling solve_for_ild_shear(). Parameters ========== subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 12 meters. There are two simple supports below the beam, one at the starting point and another at a distance of 8 meters. Plot the I.L.D. for Shear at a distance of 4 meters under the effect of a moving load of magnitude 1kN. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy import symbols >>> from sympy.physics.continuum_mechanics.beam import Beam >>> E, I = symbols('E, I') >>> R_0, R_8 = symbols('R_0, R_8') >>> b = Beam(12, E, I) >>> b.apply_support(0, 'roller') >>> b.apply_support(8, 'roller') >>> b.solve_for_ild_reactions(1, R_0, R_8) >>> b.solve_for_ild_shear(4, 1, R_0, R_8) >>> b.ild_shear Piecewise((x/8, x < 4), (x/8 - 1, x > 4)) >>> b.plot_ild_shear() Plot object containing: [0]: cartesian line: Piecewise((x/8, x < 4), (x/8 - 1, x > 4)) for x over (0.0, 12.0) """ if not self._ild_shear: raise ValueError("I.L.D. shear equation not found. Please use solve_for_ild_shear() to generate the I.L.D. shear equations.") x = self.variable l = self._length if subs is None: subs = {} for sym in self._ild_shear.atoms(Symbol): if sym != x and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) for sym in self._length.atoms(Symbol): if sym != x and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) return plot(self._ild_shear.subs(subs), (x, 0, l), title='I.L.D. for Shear', xlabel=r'$\mathrm{X}$', ylabel=r'$\mathrm{V}$', line_color='blue',show=True) def solve_for_ild_moment(self, distance, value, *reactions): """ Determines the Influence Line Diagram equations for moment at a specified point under the effect of a moving load. Parameters ========== distance : Integer Distance of the point from the start of the beam for which equations are to be determined value : Integer Magnitude of moving load reactions : The reaction forces applied on the beam. Examples ======== There is a beam of length 12 meters. There are two simple supports below the beam, one at the starting point and another at a distance of 8 meters. Calculate the I.L.D. equations for Moment at a distance of 4 meters under the effect of a moving load of magnitude 1kN. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy import symbols >>> from sympy.physics.continuum_mechanics.beam import Beam >>> E, I = symbols('E, I') >>> R_0, R_8 = symbols('R_0, R_8') >>> b = Beam(12, E, I) >>> b.apply_support(0, 'roller') >>> b.apply_support(8, 'roller') >>> b.solve_for_ild_reactions(1, R_0, R_8) >>> b.solve_for_ild_moment(4, 1, R_0, R_8) >>> b.ild_moment Piecewise((-x/2, x < 4), (x/2 - 4, x > 4)) """ x = self.variable l = self.length _, moment = self._solve_for_ild_equations() moment_curve1 = value*(distance-x) - limit(moment, x, distance) moment_curve2= (limit(moment, x, l)-limit(moment, x, distance))-value*(l-x) for reaction in reactions: moment_curve1 = moment_curve1.subs(reaction, self._ild_reactions[reaction]) moment_curve2 = moment_curve2.subs(reaction, self._ild_reactions[reaction]) moment_eq = Piecewise((moment_curve1, x < distance), (moment_curve2, x > distance)) self._ild_moment = moment_eq def plot_ild_moment(self,subs=None): """ Plots the Influence Line Diagram for Moment under the effect of a moving load. This function should be called after calling solve_for_ild_moment(). Parameters ========== subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 12 meters. There are two simple supports below the beam, one at the starting point and another at a distance of 8 meters. Plot the I.L.D. for Moment at a distance of 4 meters under the effect of a moving load of magnitude 1kN. Using the sign convention of downwards forces being positive. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy import symbols >>> from sympy.physics.continuum_mechanics.beam import Beam >>> E, I = symbols('E, I') >>> R_0, R_8 = symbols('R_0, R_8') >>> b = Beam(12, E, I) >>> b.apply_support(0, 'roller') >>> b.apply_support(8, 'roller') >>> b.solve_for_ild_reactions(1, R_0, R_8) >>> b.solve_for_ild_moment(4, 1, R_0, R_8) >>> b.ild_moment Piecewise((-x/2, x < 4), (x/2 - 4, x > 4)) >>> b.plot_ild_moment() Plot object containing: [0]: cartesian line: Piecewise((-x/2, x < 4), (x/2 - 4, x > 4)) for x over (0.0, 12.0) """ if not self._ild_moment: raise ValueError("I.L.D. moment equation not found. Please use solve_for_ild_moment() to generate the I.L.D. moment equations.") x = self.variable if subs is None: subs = {} for sym in self._ild_moment.atoms(Symbol): if sym != x and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) for sym in self._length.atoms(Symbol): if sym != x and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) return plot(self._ild_moment.subs(subs), (x, 0, self._length), title='I.L.D. for Moment', xlabel=r'$\mathrm{X}$', ylabel=r'$\mathrm{M}$', line_color='blue', show=True) @doctest_depends_on(modules=('numpy',)) def draw(self, pictorial=True): """ Returns a plot object representing the beam diagram of the beam. .. note:: The user must be careful while entering load values. The draw function assumes a sign convention which is used for plotting loads. Given a right handed coordinate system with XYZ coordinates, the beam's length is assumed to be along the positive X axis. The draw function recognizes positive loads(with n>-2) as loads acting along negative Y direction and positive moments acting along positive Z direction. Parameters ========== pictorial: Boolean (default=True) Setting ``pictorial=True`` would simply create a pictorial (scaled) view of the beam diagram not with the exact dimensions. Although setting ``pictorial=False`` would create a beam diagram with the exact dimensions on the plot Examples ======== .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam >>> from sympy import symbols >>> R1, R2 = symbols('R1, R2') >>> E, I = symbols('E, I') >>> b = Beam(50, 20, 30) >>> b.apply_load(10, 2, -1) >>> b.apply_load(R1, 10, -1) >>> b.apply_load(R2, 30, -1) >>> b.apply_load(90, 5, 0, 23) >>> b.apply_load(10, 30, 1, 50) >>> b.apply_support(50, "pin") >>> b.apply_support(0, "fixed") >>> b.apply_support(20, "roller") >>> p = b.draw() >>> p Plot object containing: [0]: cartesian line: 25*SingularityFunction(x, 5, 0) - 25*SingularityFunction(x, 23, 0) + SingularityFunction(x, 30, 1) - 20*SingularityFunction(x, 50, 0) - SingularityFunction(x, 50, 1) + 5 for x over (0.0, 50.0) [1]: cartesian line: 5 for x over (0.0, 50.0) >>> p.show() """ if not numpy: raise ImportError("To use this function numpy module is required") x = self.variable # checking whether length is an expression in terms of any Symbol. if isinstance(self.length, Expr): l = list(self.length.atoms(Symbol)) # assigning every Symbol a default value of 10 l = {i:10 for i in l} length = self.length.subs(l) else: l = {} length = self.length height = length/10 rectangles = [] rectangles.append({'xy':(0, 0), 'width':length, 'height': height, 'facecolor':"brown"}) annotations, markers, load_eq,load_eq1, fill = self._draw_load(pictorial, length, l) support_markers, support_rectangles = self._draw_supports(length, l) rectangles += support_rectangles markers += support_markers sing_plot = plot(height + load_eq, height + load_eq1, (x, 0, length), xlim=(-height, length + height), ylim=(-length, 1.25*length), annotations=annotations, markers=markers, rectangles=rectangles, line_color='brown', fill=fill, axis=False, show=False) return sing_plot def _draw_load(self, pictorial, length, l): loads = list(set(self.applied_loads) - set(self._support_as_loads)) height = length/10 x = self.variable annotations = [] markers = [] load_args = [] scaled_load = 0 load_args1 = [] scaled_load1 = 0 load_eq = 0 # For positive valued higher order loads load_eq1 = 0 # For negative valued higher order loads fill = None plus = 0 # For positive valued higher order loads minus = 0 # For negative valued higher order loads for load in loads: # check if the position of load is in terms of the beam length. if l: pos = load[1].subs(l) else: pos = load[1] # point loads if load[2] == -1: if isinstance(load[0], Symbol) or load[0].is_negative: annotations.append({'text':'', 'xy':(pos, 0), 'xytext':(pos, height - 4*height), 'arrowprops':{"width": 1.5, "headlength": 5, "headwidth": 5, "facecolor": 'black'}}) else: annotations.append({'text':'', 'xy':(pos, height), 'xytext':(pos, height*4), 'arrowprops':{"width": 1.5, "headlength": 4, "headwidth": 4, "facecolor": 'black'}}) # moment loads elif load[2] == -2: if load[0].is_negative: markers.append({'args':[[pos], [height/2]], 'marker': r'$\circlearrowright$', 'markersize':15}) else: markers.append({'args':[[pos], [height/2]], 'marker': r'$\circlearrowleft$', 'markersize':15}) # higher order loads elif load[2] >= 0: # `fill` will be assigned only when higher order loads are present value, start, order, end = load # Positive loads have their separate equations if(value>0): plus = 1 # if pictorial is True we remake the load equation again with # some constant magnitude values. if pictorial: value = 10**(1-order) if order > 0 else length/2 scaled_load += value*SingularityFunction(x, start, order) if end: f2 = 10**(1-order)*x**order if order > 0 else length/2*x**order for i in range(0, order + 1): scaled_load -= (f2.diff(x, i).subs(x, end - start)* SingularityFunction(x, end, i)/factorial(i)) if pictorial: if isinstance(scaled_load, Add): load_args = scaled_load.args else: # when the load equation consists of only a single term load_args = (scaled_load,) load_eq = [i.subs(l) for i in load_args] else: if isinstance(self.load, Add): load_args = self.load.args else: load_args = (self.load,) load_eq = [i.subs(l) for i in load_args if list(i.atoms(SingularityFunction))[0].args[2] >= 0] load_eq = Add(*load_eq) # filling higher order loads with colour expr = height + load_eq.rewrite(Piecewise) y1 = lambdify(x, expr, 'numpy') # For loads with negative value else: minus = 1 # if pictorial is True we remake the load equation again with # some constant magnitude values. if pictorial: value = 10**(1-order) if order > 0 else length/2 scaled_load1 += value*SingularityFunction(x, start, order) if end: f2 = 10**(1-order)*x**order if order > 0 else length/2*x**order for i in range(0, order + 1): scaled_load1 -= (f2.diff(x, i).subs(x, end - start)* SingularityFunction(x, end, i)/factorial(i)) if pictorial: if isinstance(scaled_load1, Add): load_args1 = scaled_load1.args else: # when the load equation consists of only a single term load_args1 = (scaled_load1,) load_eq1 = [i.subs(l) for i in load_args1] else: if isinstance(self.load, Add): load_args1 = self.load.args1 else: load_args1 = (self.load,) load_eq1 = [i.subs(l) for i in load_args if list(i.atoms(SingularityFunction))[0].args[2] >= 0] load_eq1 = -Add(*load_eq1)-height # filling higher order loads with colour expr = height + load_eq1.rewrite(Piecewise) y1_ = lambdify(x, expr, 'numpy') y = numpy.arange(0, float(length), 0.001) y2 = float(height) if(plus == 1 and minus == 1): fill = {'x': y, 'y1': y1(y), 'y2': y1_(y), 'color':'darkkhaki'} elif(plus == 1): fill = {'x': y, 'y1': y1(y), 'y2': y2, 'color':'darkkhaki'} else: fill = {'x': y, 'y1': y1_(y), 'y2': y2, 'color':'darkkhaki'} return annotations, markers, load_eq, load_eq1, fill def _draw_supports(self, length, l): height = float(length/10) support_markers = [] support_rectangles = [] for support in self._applied_supports: if l: pos = support[0].subs(l) else: pos = support[0] if support[1] == "pin": support_markers.append({'args':[pos, [0]], 'marker':6, 'markersize':13, 'color':"black"}) elif support[1] == "roller": support_markers.append({'args':[pos, [-height/2.5]], 'marker':'o', 'markersize':11, 'color':"black"}) elif support[1] == "fixed": if pos == 0: support_rectangles.append({'xy':(0, -3*height), 'width':-length/20, 'height':6*height + height, 'fill':False, 'hatch':'/////'}) else: support_rectangles.append({'xy':(length, -3*height), 'width':length/20, 'height': 6*height + height, 'fill':False, 'hatch':'/////'}) return support_markers, support_rectangles class Beam3D(Beam): """ This class handles loads applied in any direction of a 3D space along with unequal values of Second moment along different axes. .. note:: A consistent sign convention must be used while solving a beam bending problem; the results will automatically follow the chosen sign convention. This class assumes that any kind of distributed load/moment is applied through out the span of a beam. Examples ======== There is a beam of l meters long. A constant distributed load of magnitude q is applied along y-axis from start till the end of beam. A constant distributed moment of magnitude m is also applied along z-axis from start till the end of beam. Beam is fixed at both of its end. So, deflection of the beam at the both ends is restricted. >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols, simplify, collect, factor >>> l, E, G, I, A = symbols('l, E, G, I, A') >>> b = Beam3D(l, E, G, I, A) >>> x, q, m = symbols('x, q, m') >>> b.apply_load(q, 0, 0, dir="y") >>> b.apply_moment_load(m, 0, -1, dir="z") >>> b.shear_force() [0, -q*x, 0] >>> b.bending_moment() [0, 0, -m*x + q*x**2/2] >>> b.bc_slope = [(0, [0, 0, 0]), (l, [0, 0, 0])] >>> b.bc_deflection = [(0, [0, 0, 0]), (l, [0, 0, 0])] >>> b.solve_slope_deflection() >>> factor(b.slope()) [0, 0, x*(-l + x)*(-A*G*l**3*q + 2*A*G*l**2*q*x - 12*E*I*l*q - 72*E*I*m + 24*E*I*q*x)/(12*E*I*(A*G*l**2 + 12*E*I))] >>> dx, dy, dz = b.deflection() >>> dy = collect(simplify(dy), x) >>> dx == dz == 0 True >>> dy == (x*(12*E*I*l*(A*G*l**2*q - 2*A*G*l*m + 12*E*I*q) ... + x*(A*G*l*(3*l*(A*G*l**2*q - 2*A*G*l*m + 12*E*I*q) + x*(-2*A*G*l**2*q + 4*A*G*l*m - 24*E*I*q)) ... + A*G*(A*G*l**2 + 12*E*I)*(-2*l**2*q + 6*l*m - 4*m*x + q*x**2) ... - 12*E*I*q*(A*G*l**2 + 12*E*I)))/(24*A*E*G*I*(A*G*l**2 + 12*E*I))) True References ========== .. [1] https://homes.civil.aau.dk/jc/FemteSemester/Beams3D.pdf """ def __init__(self, length, elastic_modulus, shear_modulus, second_moment, area, variable=Symbol('x')): """Initializes the class. Parameters ========== length : Sympifyable A Symbol or value representing the Beam's length. elastic_modulus : Sympifyable A SymPy expression representing the Beam's Modulus of Elasticity. It is a measure of the stiffness of the Beam material. shear_modulus : Sympifyable A SymPy expression representing the Beam's Modulus of rigidity. It is a measure of rigidity of the Beam material. second_moment : Sympifyable or list A list of two elements having SymPy expression representing the Beam's Second moment of area. First value represent Second moment across y-axis and second across z-axis. Single SymPy expression can be passed if both values are same area : Sympifyable A SymPy expression representing the Beam's cross-sectional area in a plane perpendicular to length of the Beam. variable : Symbol, optional A Symbol object that will be used as the variable along the beam while representing the load, shear, moment, slope and deflection curve. By default, it is set to ``Symbol('x')``. """ super().__init__(length, elastic_modulus, second_moment, variable) self.shear_modulus = shear_modulus self.area = area self._load_vector = [0, 0, 0] self._moment_load_vector = [0, 0, 0] self._torsion_moment = {} self._load_Singularity = [0, 0, 0] self._slope = [0, 0, 0] self._deflection = [0, 0, 0] self._angular_deflection = 0 @property def shear_modulus(self): """Young's Modulus of the Beam. """ return self._shear_modulus @shear_modulus.setter def shear_modulus(self, e): self._shear_modulus = sympify(e) @property def second_moment(self): """Second moment of area of the Beam. """ return self._second_moment @second_moment.setter def second_moment(self, i): if isinstance(i, list): i = [sympify(x) for x in i] self._second_moment = i else: self._second_moment = sympify(i) @property def area(self): """Cross-sectional area of the Beam. """ return self._area @area.setter def area(self, a): self._area = sympify(a) @property def load_vector(self): """ Returns a three element list representing the load vector. """ return self._load_vector @property def moment_load_vector(self): """ Returns a three element list representing moment loads on Beam. """ return self._moment_load_vector @property def boundary_conditions(self): """ Returns a dictionary of boundary conditions applied on the beam. The dictionary has two keywords namely slope and deflection. The value of each keyword is a list of tuple, where each tuple contains location and value of a boundary condition in the format (location, value). Further each value is a list corresponding to slope or deflection(s) values along three axes at that location. Examples ======== There is a beam of length 4 meters. The slope at 0 should be 4 along the x-axis and 0 along others. At the other end of beam, deflection along all the three axes should be zero. >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(30, E, G, I, A, x) >>> b.bc_slope = [(0, (4, 0, 0))] >>> b.bc_deflection = [(4, [0, 0, 0])] >>> b.boundary_conditions {'deflection': [(4, [0, 0, 0])], 'slope': [(0, (4, 0, 0))]} Here the deflection of the beam should be ``0`` along all the three axes at ``4``. Similarly, the slope of the beam should be ``4`` along x-axis and ``0`` along y and z axis at ``0``. """ return self._boundary_conditions def polar_moment(self): """ Returns the polar moment of area of the beam about the X axis with respect to the centroid. Examples ======== >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A = symbols('l, E, G, I, A') >>> b = Beam3D(l, E, G, I, A) >>> b.polar_moment() 2*I >>> I1 = [9, 15] >>> b = Beam3D(l, E, G, I1, A) >>> b.polar_moment() 24 """ if not iterable(self.second_moment): return 2*self.second_moment return sum(self.second_moment) def apply_load(self, value, start, order, dir="y"): """ This method adds up the force load to a particular beam object. Parameters ========== value : Sympifyable The magnitude of an applied load. dir : String Axis along which load is applied. order : Integer The order of the applied load. - For point loads, order=-1 - For constant distributed load, order=0 - For ramp loads, order=1 - For parabolic ramp loads, order=2 - ... so on. """ x = self.variable value = sympify(value) start = sympify(start) order = sympify(order) if dir == "x": if not order == -1: self._load_vector[0] += value self._load_Singularity[0] += value*SingularityFunction(x, start, order) elif dir == "y": if not order == -1: self._load_vector[1] += value self._load_Singularity[1] += value*SingularityFunction(x, start, order) else: if not order == -1: self._load_vector[2] += value self._load_Singularity[2] += value*SingularityFunction(x, start, order) def apply_moment_load(self, value, start, order, dir="y"): """ This method adds up the moment loads to a particular beam object. Parameters ========== value : Sympifyable The magnitude of an applied moment. dir : String Axis along which moment is applied. order : Integer The order of the applied load. - For point moments, order=-2 - For constant distributed moment, order=-1 - For ramp moments, order=0 - For parabolic ramp moments, order=1 - ... so on. """ x = self.variable value = sympify(value) start = sympify(start) order = sympify(order) if dir == "x": if not order == -2: self._moment_load_vector[0] += value else: if start in list(self._torsion_moment): self._torsion_moment[start] += value else: self._torsion_moment[start] = value self._load_Singularity[0] += value*SingularityFunction(x, start, order) elif dir == "y": if not order == -2: self._moment_load_vector[1] += value self._load_Singularity[0] += value*SingularityFunction(x, start, order) else: if not order == -2: self._moment_load_vector[2] += value self._load_Singularity[0] += value*SingularityFunction(x, start, order) def apply_support(self, loc, type="fixed"): if type in ("pin", "roller"): reaction_load = Symbol('R_'+str(loc)) self._reaction_loads[reaction_load] = reaction_load self.bc_deflection.append((loc, [0, 0, 0])) else: reaction_load = Symbol('R_'+str(loc)) reaction_moment = Symbol('M_'+str(loc)) self._reaction_loads[reaction_load] = [reaction_load, reaction_moment] self.bc_deflection.append((loc, [0, 0, 0])) self.bc_slope.append((loc, [0, 0, 0])) def solve_for_reaction_loads(self, *reaction): """ Solves for the reaction forces. Examples ======== There is a beam of length 30 meters. It it supported by rollers at of its end. A constant distributed load of magnitude 8 N is applied from start till its end along y-axis. Another linear load having slope equal to 9 is applied along z-axis. >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(30, E, G, I, A, x) >>> b.apply_load(8, start=0, order=0, dir="y") >>> b.apply_load(9*x, start=0, order=0, dir="z") >>> b.bc_deflection = [(0, [0, 0, 0]), (30, [0, 0, 0])] >>> R1, R2, R3, R4 = symbols('R1, R2, R3, R4') >>> b.apply_load(R1, start=0, order=-1, dir="y") >>> b.apply_load(R2, start=30, order=-1, dir="y") >>> b.apply_load(R3, start=0, order=-1, dir="z") >>> b.apply_load(R4, start=30, order=-1, dir="z") >>> b.solve_for_reaction_loads(R1, R2, R3, R4) >>> b.reaction_loads {R1: -120, R2: -120, R3: -1350, R4: -2700} """ x = self.variable l = self.length q = self._load_Singularity shear_curves = [integrate(load, x) for load in q] moment_curves = [integrate(shear, x) for shear in shear_curves] for i in range(3): react = [r for r in reaction if (shear_curves[i].has(r) or moment_curves[i].has(r))] if len(react) == 0: continue shear_curve = limit(shear_curves[i], x, l) moment_curve = limit(moment_curves[i], x, l) sol = list((linsolve([shear_curve, moment_curve], react).args)[0]) sol_dict = dict(zip(react, sol)) reaction_loads = self._reaction_loads # Check if any of the evaluated reaction exists in another direction # and if it exists then it should have same value. for key in sol_dict: if key in reaction_loads and sol_dict[key] != reaction_loads[key]: raise ValueError("Ambiguous solution for %s in different directions." % key) self._reaction_loads.update(sol_dict) def shear_force(self): """ Returns a list of three expressions which represents the shear force curve of the Beam object along all three axes. """ x = self.variable q = self._load_vector return [integrate(-q[0], x), integrate(-q[1], x), integrate(-q[2], x)] def axial_force(self): """ Returns expression of Axial shear force present inside the Beam object. """ return self.shear_force()[0] def shear_stress(self): """ Returns a list of three expressions which represents the shear stress curve of the Beam object along all three axes. """ return [self.shear_force()[0]/self._area, self.shear_force()[1]/self._area, self.shear_force()[2]/self._area] def axial_stress(self): """ Returns expression of Axial stress present inside the Beam object. """ return self.axial_force()/self._area def bending_moment(self): """ Returns a list of three expressions which represents the bending moment curve of the Beam object along all three axes. """ x = self.variable m = self._moment_load_vector shear = self.shear_force() return [integrate(-m[0], x), integrate(-m[1] + shear[2], x), integrate(-m[2] - shear[1], x) ] def torsional_moment(self): """ Returns expression of Torsional moment present inside the Beam object. """ return self.bending_moment()[0] def solve_for_torsion(self): """ Solves for the angular deflection due to the torsional effects of moments being applied in the x-direction i.e. out of or into the beam. Here, a positive torque means the direction of the torque is positive i.e. out of the beam along the beam-axis. Likewise, a negative torque signifies a torque into the beam cross-section. Examples ======== >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(20, E, G, I, A, x) >>> b.apply_moment_load(4, 4, -2, dir='x') >>> b.apply_moment_load(4, 8, -2, dir='x') >>> b.apply_moment_load(4, 8, -2, dir='x') >>> b.solve_for_torsion() >>> b.angular_deflection().subs(x, 3) 18/(G*I) """ x = self.variable sum_moments = 0 for point in list(self._torsion_moment): sum_moments += self._torsion_moment[point] list(self._torsion_moment).sort() pointsList = list(self._torsion_moment) torque_diagram = Piecewise((sum_moments, x<=pointsList[0]), (0, x>=pointsList[0])) for i in range(len(pointsList))[1:]: sum_moments -= self._torsion_moment[pointsList[i-1]] torque_diagram += Piecewise((0, x<=pointsList[i-1]), (sum_moments, x<=pointsList[i]), (0, x>=pointsList[i])) integrated_torque_diagram = integrate(torque_diagram) self._angular_deflection = integrated_torque_diagram/(self.shear_modulus*self.polar_moment()) def solve_slope_deflection(self): x = self.variable l = self.length E = self.elastic_modulus G = self.shear_modulus I = self.second_moment if isinstance(I, list): I_y, I_z = I[0], I[1] else: I_y = I_z = I A = self._area load = self._load_vector moment = self._moment_load_vector defl = Function('defl') theta = Function('theta') # Finding deflection along x-axis(and corresponding slope value by differentiating it) # Equation used: Derivative(E*A*Derivative(def_x(x), x), x) + load_x = 0 eq = Derivative(E*A*Derivative(defl(x), x), x) + load[0] def_x = dsolve(Eq(eq, 0), defl(x)).args[1] # Solving constants originated from dsolve C1 = Symbol('C1') C2 = Symbol('C2') constants = list((linsolve([def_x.subs(x, 0), def_x.subs(x, l)], C1, C2).args)[0]) def_x = def_x.subs({C1:constants[0], C2:constants[1]}) slope_x = def_x.diff(x) self._deflection[0] = def_x self._slope[0] = slope_x # Finding deflection along y-axis and slope across z-axis. System of equation involved: # 1: Derivative(E*I_z*Derivative(theta_z(x), x), x) + G*A*(Derivative(defl_y(x), x) - theta_z(x)) + moment_z = 0 # 2: Derivative(G*A*(Derivative(defl_y(x), x) - theta_z(x)), x) + load_y = 0 C_i = Symbol('C_i') # Substitute value of `G*A*(Derivative(defl_y(x), x) - theta_z(x))` from (2) in (1) eq1 = Derivative(E*I_z*Derivative(theta(x), x), x) + (integrate(-load[1], x) + C_i) + moment[2] slope_z = dsolve(Eq(eq1, 0)).args[1] # Solve for constants originated from using dsolve on eq1 constants = list((linsolve([slope_z.subs(x, 0), slope_z.subs(x, l)], C1, C2).args)[0]) slope_z = slope_z.subs({C1:constants[0], C2:constants[1]}) # Put value of slope obtained back in (2) to solve for `C_i` and find deflection across y-axis eq2 = G*A*(Derivative(defl(x), x)) + load[1]*x - C_i - G*A*slope_z def_y = dsolve(Eq(eq2, 0), defl(x)).args[1] # Solve for constants originated from using dsolve on eq2 constants = list((linsolve([def_y.subs(x, 0), def_y.subs(x, l)], C1, C_i).args)[0]) self._deflection[1] = def_y.subs({C1:constants[0], C_i:constants[1]}) self._slope[2] = slope_z.subs(C_i, constants[1]) # Finding deflection along z-axis and slope across y-axis. System of equation involved: # 1: Derivative(E*I_y*Derivative(theta_y(x), x), x) - G*A*(Derivative(defl_z(x), x) + theta_y(x)) + moment_y = 0 # 2: Derivative(G*A*(Derivative(defl_z(x), x) + theta_y(x)), x) + load_z = 0 # Substitute value of `G*A*(Derivative(defl_y(x), x) + theta_z(x))` from (2) in (1) eq1 = Derivative(E*I_y*Derivative(theta(x), x), x) + (integrate(load[2], x) - C_i) + moment[1] slope_y = dsolve(Eq(eq1, 0)).args[1] # Solve for constants originated from using dsolve on eq1 constants = list((linsolve([slope_y.subs(x, 0), slope_y.subs(x, l)], C1, C2).args)[0]) slope_y = slope_y.subs({C1:constants[0], C2:constants[1]}) # Put value of slope obtained back in (2) to solve for `C_i` and find deflection across z-axis eq2 = G*A*(Derivative(defl(x), x)) + load[2]*x - C_i + G*A*slope_y def_z = dsolve(Eq(eq2,0)).args[1] # Solve for constants originated from using dsolve on eq2 constants = list((linsolve([def_z.subs(x, 0), def_z.subs(x, l)], C1, C_i).args)[0]) self._deflection[2] = def_z.subs({C1:constants[0], C_i:constants[1]}) self._slope[1] = slope_y.subs(C_i, constants[1]) def slope(self): """ Returns a three element list representing slope of deflection curve along all the three axes. """ return self._slope def deflection(self): """ Returns a three element list representing deflection curve along all the three axes. """ return self._deflection def angular_deflection(self): """ Returns a function in x depicting how the angular deflection, due to moments in the x-axis on the beam, varies with x. """ return self._angular_deflection def _plot_shear_force(self, dir, subs=None): shear_force = self.shear_force() if dir == 'x': dir_num = 0 color = 'r' elif dir == 'y': dir_num = 1 color = 'g' elif dir == 'z': dir_num = 2 color = 'b' if subs is None: subs = {} for sym in shear_force[dir_num].atoms(Symbol): if sym != self.variable and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) if self.length in subs: length = subs[self.length] else: length = self.length return plot(shear_force[dir_num].subs(subs), (self.variable, 0, length), show = False, title='Shear Force along %c direction'%dir, xlabel=r'$\mathrm{X}$', ylabel=r'$\mathrm{V(%c)}$'%dir, line_color=color) def plot_shear_force(self, dir="all", subs=None): """ Returns a plot for Shear force along all three directions present in the Beam object. Parameters ========== dir : string (default : "all") Direction along which shear force plot is required. If no direction is specified, all plots are displayed. subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 20 meters. It it supported by rollers at of its end. A linear load having slope equal to 12 is applied along y-axis. A constant distributed load of magnitude 15 N is applied from start till its end along z-axis. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(20, E, G, I, A, x) >>> b.apply_load(15, start=0, order=0, dir="z") >>> b.apply_load(12*x, start=0, order=0, dir="y") >>> b.bc_deflection = [(0, [0, 0, 0]), (20, [0, 0, 0])] >>> R1, R2, R3, R4 = symbols('R1, R2, R3, R4') >>> b.apply_load(R1, start=0, order=-1, dir="z") >>> b.apply_load(R2, start=20, order=-1, dir="z") >>> b.apply_load(R3, start=0, order=-1, dir="y") >>> b.apply_load(R4, start=20, order=-1, dir="y") >>> b.solve_for_reaction_loads(R1, R2, R3, R4) >>> b.plot_shear_force() PlotGrid object containing: Plot[0]:Plot object containing: [0]: cartesian line: 0 for x over (0.0, 20.0) Plot[1]:Plot object containing: [0]: cartesian line: -6*x**2 for x over (0.0, 20.0) Plot[2]:Plot object containing: [0]: cartesian line: -15*x for x over (0.0, 20.0) """ dir = dir.lower() # For shear force along x direction if dir == "x": Px = self._plot_shear_force('x', subs) return Px.show() # For shear force along y direction elif dir == "y": Py = self._plot_shear_force('y', subs) return Py.show() # For shear force along z direction elif dir == "z": Pz = self._plot_shear_force('z', subs) return Pz.show() # For shear force along all direction else: Px = self._plot_shear_force('x', subs) Py = self._plot_shear_force('y', subs) Pz = self._plot_shear_force('z', subs) return PlotGrid(3, 1, Px, Py, Pz) def _plot_bending_moment(self, dir, subs=None): bending_moment = self.bending_moment() if dir == 'x': dir_num = 0 color = 'g' elif dir == 'y': dir_num = 1 color = 'c' elif dir == 'z': dir_num = 2 color = 'm' if subs is None: subs = {} for sym in bending_moment[dir_num].atoms(Symbol): if sym != self.variable and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) if self.length in subs: length = subs[self.length] else: length = self.length return plot(bending_moment[dir_num].subs(subs), (self.variable, 0, length), show = False, title='Bending Moment along %c direction'%dir, xlabel=r'$\mathrm{X}$', ylabel=r'$\mathrm{M(%c)}$'%dir, line_color=color) def plot_bending_moment(self, dir="all", subs=None): """ Returns a plot for bending moment along all three directions present in the Beam object. Parameters ========== dir : string (default : "all") Direction along which bending moment plot is required. If no direction is specified, all plots are displayed. subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 20 meters. It it supported by rollers at of its end. A linear load having slope equal to 12 is applied along y-axis. A constant distributed load of magnitude 15 N is applied from start till its end along z-axis. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(20, E, G, I, A, x) >>> b.apply_load(15, start=0, order=0, dir="z") >>> b.apply_load(12*x, start=0, order=0, dir="y") >>> b.bc_deflection = [(0, [0, 0, 0]), (20, [0, 0, 0])] >>> R1, R2, R3, R4 = symbols('R1, R2, R3, R4') >>> b.apply_load(R1, start=0, order=-1, dir="z") >>> b.apply_load(R2, start=20, order=-1, dir="z") >>> b.apply_load(R3, start=0, order=-1, dir="y") >>> b.apply_load(R4, start=20, order=-1, dir="y") >>> b.solve_for_reaction_loads(R1, R2, R3, R4) >>> b.plot_bending_moment() PlotGrid object containing: Plot[0]:Plot object containing: [0]: cartesian line: 0 for x over (0.0, 20.0) Plot[1]:Plot object containing: [0]: cartesian line: -15*x**2/2 for x over (0.0, 20.0) Plot[2]:Plot object containing: [0]: cartesian line: 2*x**3 for x over (0.0, 20.0) """ dir = dir.lower() # For bending moment along x direction if dir == "x": Px = self._plot_bending_moment('x', subs) return Px.show() # For bending moment along y direction elif dir == "y": Py = self._plot_bending_moment('y', subs) return Py.show() # For bending moment along z direction elif dir == "z": Pz = self._plot_bending_moment('z', subs) return Pz.show() # For bending moment along all direction else: Px = self._plot_bending_moment('x', subs) Py = self._plot_bending_moment('y', subs) Pz = self._plot_bending_moment('z', subs) return PlotGrid(3, 1, Px, Py, Pz) def _plot_slope(self, dir, subs=None): slope = self.slope() if dir == 'x': dir_num = 0 color = 'b' elif dir == 'y': dir_num = 1 color = 'm' elif dir == 'z': dir_num = 2 color = 'g' if subs is None: subs = {} for sym in slope[dir_num].atoms(Symbol): if sym != self.variable and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) if self.length in subs: length = subs[self.length] else: length = self.length return plot(slope[dir_num].subs(subs), (self.variable, 0, length), show = False, title='Slope along %c direction'%dir, xlabel=r'$\mathrm{X}$', ylabel=r'$\mathrm{\theta(%c)}$'%dir, line_color=color) def plot_slope(self, dir="all", subs=None): """ Returns a plot for Slope along all three directions present in the Beam object. Parameters ========== dir : string (default : "all") Direction along which Slope plot is required. If no direction is specified, all plots are displayed. subs : dictionary Python dictionary containing Symbols as keys and their corresponding values. Examples ======== There is a beam of length 20 meters. It it supported by rollers at of its end. A linear load having slope equal to 12 is applied along y-axis. A constant distributed load of magnitude 15 N is applied from start till its end along z-axis. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(20, 40, 21, 100, 25, x) >>> b.apply_load(15, start=0, order=0, dir="z") >>> b.apply_load(12*x, start=0, order=0, dir="y") >>> b.bc_deflection = [(0, [0, 0, 0]), (20, [0, 0, 0])] >>> R1, R2, R3, R4 = symbols('R1, R2, R3, R4') >>> b.apply_load(R1, start=0, order=-1, dir="z") >>> b.apply_load(R2, start=20, order=-1, dir="z") >>> b.apply_load(R3, start=0, order=-1, dir="y") >>> b.apply_load(R4, start=20, order=-1, dir="y") >>> b.solve_for_reaction_loads(R1, R2, R3, R4) >>> b.solve_slope_deflection() >>> b.plot_slope() PlotGrid object containing: Plot[0]:Plot object containing: [0]: cartesian line: 0 for x over (0.0, 20.0) Plot[1]:Plot object containing: [0]: cartesian line: -x**3/1600 + 3*x**2/160 - x/8 for x over (0.0, 20.0) Plot[2]:Plot object containing: [0]: cartesian line: x**4/8000 - 19*x**2/172 + 52*x/43 for x over (0.0, 20.0) """ dir = dir.lower() # For Slope along x direction if dir == "x": Px = self._plot_slope('x', subs) return Px.show() # For Slope along y direction elif dir == "y": Py = self._plot_slope('y', subs) return Py.show() # For Slope along z direction elif dir == "z": Pz = self._plot_slope('z', subs) return Pz.show() # For Slope along all direction else: Px = self._plot_slope('x', subs) Py = self._plot_slope('y', subs) Pz = self._plot_slope('z', subs) return PlotGrid(3, 1, Px, Py, Pz) def _plot_deflection(self, dir, subs=None): deflection = self.deflection() if dir == 'x': dir_num = 0 color = 'm' elif dir == 'y': dir_num = 1 color = 'r' elif dir == 'z': dir_num = 2 color = 'c' if subs is None: subs = {} for sym in deflection[dir_num].atoms(Symbol): if sym != self.variable and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) if self.length in subs: length = subs[self.length] else: length = self.length return plot(deflection[dir_num].subs(subs), (self.variable, 0, length), show = False, title='Deflection along %c direction'%dir, xlabel=r'$\mathrm{X}$', ylabel=r'$\mathrm{\delta(%c)}$'%dir, line_color=color) def plot_deflection(self, dir="all", subs=None): """ Returns a plot for Deflection along all three directions present in the Beam object. Parameters ========== dir : string (default : "all") Direction along which deflection plot is required. If no direction is specified, all plots are displayed. subs : dictionary Python dictionary containing Symbols as keys and their corresponding values. Examples ======== There is a beam of length 20 meters. It it supported by rollers at of its end. A linear load having slope equal to 12 is applied along y-axis. A constant distributed load of magnitude 15 N is applied from start till its end along z-axis. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(20, 40, 21, 100, 25, x) >>> b.apply_load(15, start=0, order=0, dir="z") >>> b.apply_load(12*x, start=0, order=0, dir="y") >>> b.bc_deflection = [(0, [0, 0, 0]), (20, [0, 0, 0])] >>> R1, R2, R3, R4 = symbols('R1, R2, R3, R4') >>> b.apply_load(R1, start=0, order=-1, dir="z") >>> b.apply_load(R2, start=20, order=-1, dir="z") >>> b.apply_load(R3, start=0, order=-1, dir="y") >>> b.apply_load(R4, start=20, order=-1, dir="y") >>> b.solve_for_reaction_loads(R1, R2, R3, R4) >>> b.solve_slope_deflection() >>> b.plot_deflection() PlotGrid object containing: Plot[0]:Plot object containing: [0]: cartesian line: 0 for x over (0.0, 20.0) Plot[1]:Plot object containing: [0]: cartesian line: x**5/40000 - 4013*x**3/90300 + 26*x**2/43 + 1520*x/903 for x over (0.0, 20.0) Plot[2]:Plot object containing: [0]: cartesian line: x**4/6400 - x**3/160 + 27*x**2/560 + 2*x/7 for x over (0.0, 20.0) """ dir = dir.lower() # For deflection along x direction if dir == "x": Px = self._plot_deflection('x', subs) return Px.show() # For deflection along y direction elif dir == "y": Py = self._plot_deflection('y', subs) return Py.show() # For deflection along z direction elif dir == "z": Pz = self._plot_deflection('z', subs) return Pz.show() # For deflection along all direction else: Px = self._plot_deflection('x', subs) Py = self._plot_deflection('y', subs) Pz = self._plot_deflection('z', subs) return PlotGrid(3, 1, Px, Py, Pz) def plot_loading_results(self, dir='x', subs=None): """ Returns a subplot of Shear Force, Bending Moment, Slope and Deflection of the Beam object along the direction specified. Parameters ========== dir : string (default : "x") Direction along which plots are required. If no direction is specified, plots along x-axis are displayed. subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 20 meters. It it supported by rollers at of its end. A linear load having slope equal to 12 is applied along y-axis. A constant distributed load of magnitude 15 N is applied from start till its end along z-axis. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(20, E, G, I, A, x) >>> subs = {E:40, G:21, I:100, A:25} >>> b.apply_load(15, start=0, order=0, dir="z") >>> b.apply_load(12*x, start=0, order=0, dir="y") >>> b.bc_deflection = [(0, [0, 0, 0]), (20, [0, 0, 0])] >>> R1, R2, R3, R4 = symbols('R1, R2, R3, R4') >>> b.apply_load(R1, start=0, order=-1, dir="z") >>> b.apply_load(R2, start=20, order=-1, dir="z") >>> b.apply_load(R3, start=0, order=-1, dir="y") >>> b.apply_load(R4, start=20, order=-1, dir="y") >>> b.solve_for_reaction_loads(R1, R2, R3, R4) >>> b.solve_slope_deflection() >>> b.plot_loading_results('y',subs) PlotGrid object containing: Plot[0]:Plot object containing: [0]: cartesian line: -6*x**2 for x over (0.0, 20.0) Plot[1]:Plot object containing: [0]: cartesian line: -15*x**2/2 for x over (0.0, 20.0) Plot[2]:Plot object containing: [0]: cartesian line: -x**3/1600 + 3*x**2/160 - x/8 for x over (0.0, 20.0) Plot[3]:Plot object containing: [0]: cartesian line: x**5/40000 - 4013*x**3/90300 + 26*x**2/43 + 1520*x/903 for x over (0.0, 20.0) """ dir = dir.lower() if subs is None: subs = {} ax1 = self._plot_shear_force(dir, subs) ax2 = self._plot_bending_moment(dir, subs) ax3 = self._plot_slope(dir, subs) ax4 = self._plot_deflection(dir, subs) return PlotGrid(4, 1, ax1, ax2, ax3, ax4) def _plot_shear_stress(self, dir, subs=None): shear_stress = self.shear_stress() if dir == 'x': dir_num = 0 color = 'r' elif dir == 'y': dir_num = 1 color = 'g' elif dir == 'z': dir_num = 2 color = 'b' if subs is None: subs = {} for sym in shear_stress[dir_num].atoms(Symbol): if sym != self.variable and sym not in subs: raise ValueError('Value of %s was not passed.' %sym) if self.length in subs: length = subs[self.length] else: length = self.length return plot(shear_stress[dir_num].subs(subs), (self.variable, 0, length), show = False, title='Shear stress along %c direction'%dir, xlabel=r'$\mathrm{X}$', ylabel=r'$\tau(%c)$'%dir, line_color=color) def plot_shear_stress(self, dir="all", subs=None): """ Returns a plot for Shear Stress along all three directions present in the Beam object. Parameters ========== dir : string (default : "all") Direction along which shear stress plot is required. If no direction is specified, all plots are displayed. subs : dictionary Python dictionary containing Symbols as key and their corresponding values. Examples ======== There is a beam of length 20 meters and area of cross section 2 square meters. It it supported by rollers at of its end. A linear load having slope equal to 12 is applied along y-axis. A constant distributed load of magnitude 15 N is applied from start till its end along z-axis. .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(20, E, G, I, 2, x) >>> b.apply_load(15, start=0, order=0, dir="z") >>> b.apply_load(12*x, start=0, order=0, dir="y") >>> b.bc_deflection = [(0, [0, 0, 0]), (20, [0, 0, 0])] >>> R1, R2, R3, R4 = symbols('R1, R2, R3, R4') >>> b.apply_load(R1, start=0, order=-1, dir="z") >>> b.apply_load(R2, start=20, order=-1, dir="z") >>> b.apply_load(R3, start=0, order=-1, dir="y") >>> b.apply_load(R4, start=20, order=-1, dir="y") >>> b.solve_for_reaction_loads(R1, R2, R3, R4) >>> b.plot_shear_stress() PlotGrid object containing: Plot[0]:Plot object containing: [0]: cartesian line: 0 for x over (0.0, 20.0) Plot[1]:Plot object containing: [0]: cartesian line: -3*x**2 for x over (0.0, 20.0) Plot[2]:Plot object containing: [0]: cartesian line: -15*x/2 for x over (0.0, 20.0) """ dir = dir.lower() # For shear stress along x direction if dir == "x": Px = self._plot_shear_stress('x', subs) return Px.show() # For shear stress along y direction elif dir == "y": Py = self._plot_shear_stress('y', subs) return Py.show() # For shear stress along z direction elif dir == "z": Pz = self._plot_shear_stress('z', subs) return Pz.show() # For shear stress along all direction else: Px = self._plot_shear_stress('x', subs) Py = self._plot_shear_stress('y', subs) Pz = self._plot_shear_stress('z', subs) return PlotGrid(3, 1, Px, Py, Pz) def _max_shear_force(self, dir): """ Helper function for max_shear_force(). """ dir = dir.lower() if dir == 'x': dir_num = 0 elif dir == 'y': dir_num = 1 elif dir == 'z': dir_num = 2 if not self.shear_force()[dir_num]: return (0,0) # To restrict the range within length of the Beam load_curve = Piecewise((float("nan"), self.variable<=0), (self._load_vector[dir_num], self.variable>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(20, 40, 21, 100, 25, x) >>> b.apply_load(15, start=0, order=0, dir="z") >>> b.apply_load(12*x, start=0, order=0, dir="y") >>> b.bc_deflection = [(0, [0, 0, 0]), (20, [0, 0, 0])] >>> R1, R2, R3, R4 = symbols('R1, R2, R3, R4') >>> b.apply_load(R1, start=0, order=-1, dir="z") >>> b.apply_load(R2, start=20, order=-1, dir="z") >>> b.apply_load(R3, start=0, order=-1, dir="y") >>> b.apply_load(R4, start=20, order=-1, dir="y") >>> b.solve_for_reaction_loads(R1, R2, R3, R4) >>> b.max_shear_force() [(0, 0), (20, 2400), (20, 300)] """ max_shear = [] max_shear.append(self._max_shear_force('x')) max_shear.append(self._max_shear_force('y')) max_shear.append(self._max_shear_force('z')) return max_shear def _max_bending_moment(self, dir): """ Helper function for max_bending_moment(). """ dir = dir.lower() if dir == 'x': dir_num = 0 elif dir == 'y': dir_num = 1 elif dir == 'z': dir_num = 2 if not self.bending_moment()[dir_num]: return (0,0) # To restrict the range within length of the Beam shear_curve = Piecewise((float("nan"), self.variable<=0), (self.shear_force()[dir_num], self.variable>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(20, 40, 21, 100, 25, x) >>> b.apply_load(15, start=0, order=0, dir="z") >>> b.apply_load(12*x, start=0, order=0, dir="y") >>> b.bc_deflection = [(0, [0, 0, 0]), (20, [0, 0, 0])] >>> R1, R2, R3, R4 = symbols('R1, R2, R3, R4') >>> b.apply_load(R1, start=0, order=-1, dir="z") >>> b.apply_load(R2, start=20, order=-1, dir="z") >>> b.apply_load(R3, start=0, order=-1, dir="y") >>> b.apply_load(R4, start=20, order=-1, dir="y") >>> b.solve_for_reaction_loads(R1, R2, R3, R4) >>> b.max_bending_moment() [(0, 0), (20, 3000), (20, 16000)] """ max_bmoment = [] max_bmoment.append(self._max_bending_moment('x')) max_bmoment.append(self._max_bending_moment('y')) max_bmoment.append(self._max_bending_moment('z')) return max_bmoment max_bmoment = max_bending_moment def _max_deflection(self, dir): """ Helper function for max_Deflection() """ dir = dir.lower() if dir == 'x': dir_num = 0 elif dir == 'y': dir_num = 1 elif dir == 'z': dir_num = 2 if not self.deflection()[dir_num]: return (0,0) # To restrict the range within length of the Beam slope_curve = Piecewise((float("nan"), self.variable<=0), (self.slope()[dir_num], self.variable>> from sympy.physics.continuum_mechanics.beam import Beam3D >>> from sympy import symbols >>> l, E, G, I, A, x = symbols('l, E, G, I, A, x') >>> b = Beam3D(20, 40, 21, 100, 25, x) >>> b.apply_load(15, start=0, order=0, dir="z") >>> b.apply_load(12*x, start=0, order=0, dir="y") >>> b.bc_deflection = [(0, [0, 0, 0]), (20, [0, 0, 0])] >>> R1, R2, R3, R4 = symbols('R1, R2, R3, R4') >>> b.apply_load(R1, start=0, order=-1, dir="z") >>> b.apply_load(R2, start=20, order=-1, dir="z") >>> b.apply_load(R3, start=0, order=-1, dir="y") >>> b.apply_load(R4, start=20, order=-1, dir="y") >>> b.solve_for_reaction_loads(R1, R2, R3, R4) >>> b.solve_slope_deflection() >>> b.max_deflection() [(0, 0), (10, 495/14), (-10 + 10*sqrt(10793)/43, (10 - 10*sqrt(10793)/43)**3/160 - 20/7 + (10 - 10*sqrt(10793)/43)**4/6400 + 20*sqrt(10793)/301 + 27*(10 - 10*sqrt(10793)/43)**2/560)] """ max_def = [] max_def.append(self._max_deflection('x')) max_def.append(self._max_deflection('y')) max_def.append(self._max_deflection('z')) return max_def