| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108 |
- """
- This module can be used to solve problems related
- to 2D Trusses.
- """
- from cmath import atan, inf
- from sympy.core.add import Add
- from sympy.core.evalf import INF
- from sympy.core.mul import Mul
- from sympy.core.symbol import Symbol
- from sympy.core.sympify import sympify
- from sympy import Matrix, pi
- from sympy.external.importtools import import_module
- from sympy.functions.elementary.miscellaneous import sqrt
- from sympy.matrices.dense import zeros
- import math
- from sympy.physics.units.quantities import Quantity
- from sympy.plotting import plot
- from sympy.utilities.decorator import doctest_depends_on
- from sympy import sin, cos
- __doctest_requires__ = {('Truss.draw'): ['matplotlib']}
- numpy = import_module('numpy', import_kwargs={'fromlist':['arange']})
- class Truss:
- """
- A Truss is an assembly of members such as beams,
- connected by nodes, that create a rigid structure.
- In engineering, a truss is a structure that
- consists of two-force members only.
- Trusses are extremely important in engineering applications
- and can be seen in numerous real-world applications like bridges.
- Examples
- ========
- There is a Truss consisting of four nodes and five
- members connecting the nodes. A force P acts
- downward on the node D and there also exist pinned
- and roller joints on the nodes A and B respectively.
- .. image:: truss_example.png
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> t = Truss()
- >>> t.add_node(("node_1", 0, 0), ("node_2", 6, 0), ("node_3", 2, 2), ("node_4", 2, 0))
- >>> t.add_member(("member_1", "node_1", "node_4"), ("member_2", "node_2", "node_4"), ("member_3", "node_1", "node_3"))
- >>> t.add_member(("member_4", "node_2", "node_3"), ("member_5", "node_3", "node_4"))
- >>> t.apply_load(("node_4", 10, 270))
- >>> t.apply_support(("node_1", "pinned"), ("node_2", "roller"))
- """
- def __init__(self):
- """
- Initializes the class
- """
- self._nodes = []
- self._members = {}
- self._loads = {}
- self._supports = {}
- self._node_labels = []
- self._node_positions = []
- self._node_position_x = []
- self._node_position_y = []
- self._nodes_occupied = {}
- self._member_lengths = {}
- self._reaction_loads = {}
- self._internal_forces = {}
- self._node_coordinates = {}
- @property
- def nodes(self):
- """
- Returns the nodes of the truss along with their positions.
- """
- return self._nodes
- @property
- def node_labels(self):
- """
- Returns the node labels of the truss.
- """
- return self._node_labels
- @property
- def node_positions(self):
- """
- Returns the positions of the nodes of the truss.
- """
- return self._node_positions
- @property
- def members(self):
- """
- Returns the members of the truss along with the start and end points.
- """
- return self._members
- @property
- def member_lengths(self):
- """
- Returns the length of each member of the truss.
- """
- return self._member_lengths
- @property
- def supports(self):
- """
- Returns the nodes with provided supports along with the kind of support provided i.e.
- pinned or roller.
- """
- return self._supports
- @property
- def loads(self):
- """
- Returns the loads acting on the truss.
- """
- return self._loads
- @property
- def reaction_loads(self):
- """
- Returns the reaction forces for all supports which are all initialized to 0.
- """
- return self._reaction_loads
- @property
- def internal_forces(self):
- """
- Returns the internal forces for all members which are all initialized to 0.
- """
- return self._internal_forces
- def add_node(self, *args):
- """
- This method adds a node to the truss along with its name/label and its location.
- Multiple nodes can be added at the same time.
- Parameters
- ==========
- The input(s) for this method are tuples of the form (label, x, y).
- label: String or a Symbol
- The label for a node. It is the only way to identify a particular node.
- x: Sympifyable
- The x-coordinate of the position of the node.
- y: Sympifyable
- The y-coordinate of the position of the node.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> t = Truss()
- >>> t.add_node(('A', 0, 0))
- >>> t.nodes
- [('A', 0, 0)]
- >>> t.add_node(('B', 3, 0), ('C', 4, 1))
- >>> t.nodes
- [('A', 0, 0), ('B', 3, 0), ('C', 4, 1)]
- """
- for i in args:
- label = i[0]
- x = i[1]
- x = sympify(x)
- y=i[2]
- y = sympify(y)
- if label in self._node_coordinates:
- raise ValueError("Node needs to have a unique label")
- elif [x, y] in self._node_coordinates.values():
- raise ValueError("A node already exists at the given position")
- else :
- self._nodes.append((label, x, y))
- self._node_labels.append(label)
- self._node_positions.append((x, y))
- self._node_position_x.append(x)
- self._node_position_y.append(y)
- self._node_coordinates[label] = [x, y]
- def remove_node(self, *args):
- """
- This method removes a node from the truss.
- Multiple nodes can be removed at the same time.
- Parameters
- ==========
- The input(s) for this method are the labels of the nodes to be removed.
- label: String or Symbol
- The label of the node to be removed.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> t = Truss()
- >>> t.add_node(('A', 0, 0), ('B', 3, 0), ('C', 5, 0))
- >>> t.nodes
- [('A', 0, 0), ('B', 3, 0), ('C', 5, 0)]
- >>> t.remove_node('A', 'C')
- >>> t.nodes
- [('B', 3, 0)]
- """
- for label in args:
- for i in range(len(self.nodes)):
- if self._node_labels[i] == label:
- x = self._node_position_x[i]
- y = self._node_position_y[i]
- if label not in self._node_coordinates:
- raise ValueError("No such node exists in the truss")
- else:
- members_duplicate = self._members.copy()
- for member in members_duplicate:
- if label == self._members[member][0] or label == self._members[member][1]:
- raise ValueError("The given node already has member attached to it")
- self._nodes.remove((label, x, y))
- self._node_labels.remove(label)
- self._node_positions.remove((x, y))
- self._node_position_x.remove(x)
- self._node_position_y.remove(y)
- if label in self._loads:
- self._loads.pop(label)
- if label in self._supports:
- self._supports.pop(label)
- self._node_coordinates.pop(label)
- def add_member(self, *args):
- """
- This method adds a member between any two nodes in the given truss.
- Parameters
- ==========
- The input(s) of the method are tuple(s) of the form (label, start, end).
- label: String or Symbol
- The label for a member. It is the only way to identify a particular member.
- start: String or Symbol
- The label of the starting point/node of the member.
- end: String or Symbol
- The label of the ending point/node of the member.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> t = Truss()
- >>> t.add_node(('A', 0, 0), ('B', 3, 0), ('C', 2, 2))
- >>> t.add_member(('AB', 'A', 'B'), ('BC', 'B', 'C'))
- >>> t.members
- {'AB': ['A', 'B'], 'BC': ['B', 'C']}
- """
- for i in args:
- label = i[0]
- start = i[1]
- end = i[2]
- if start not in self._node_coordinates or end not in self._node_coordinates or start==end:
- raise ValueError("The start and end points of the member must be unique nodes")
- elif label in self._members:
- raise ValueError("A member with the same label already exists for the truss")
- elif self._nodes_occupied.get((start, end)):
- raise ValueError("A member already exists between the two nodes")
- else:
- self._members[label] = [start, end]
- self._member_lengths[label] = sqrt((self._node_coordinates[end][0]-self._node_coordinates[start][0])**2 + (self._node_coordinates[end][1]-self._node_coordinates[start][1])**2)
- self._nodes_occupied[start, end] = True
- self._nodes_occupied[end, start] = True
- self._internal_forces[label] = 0
- def remove_member(self, *args):
- """
- This method removes members from the given truss.
- Parameters
- ==========
- labels: String or Symbol
- The label for the member to be removed.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> t = Truss()
- >>> t.add_node(('A', 0, 0), ('B', 3, 0), ('C', 2, 2))
- >>> t.add_member(('AB', 'A', 'B'), ('AC', 'A', 'C'), ('BC', 'B', 'C'))
- >>> t.members
- {'AB': ['A', 'B'], 'AC': ['A', 'C'], 'BC': ['B', 'C']}
- >>> t.remove_member('AC', 'BC')
- >>> t.members
- {'AB': ['A', 'B']}
- """
- for label in args:
- if label not in self._members:
- raise ValueError("No such member exists in the Truss")
- else:
- self._nodes_occupied.pop((self._members[label][0], self._members[label][1]))
- self._nodes_occupied.pop((self._members[label][1], self._members[label][0]))
- self._members.pop(label)
- self._member_lengths.pop(label)
- self._internal_forces.pop(label)
- def change_node_label(self, *args):
- """
- This method changes the label(s) of the specified node(s).
- Parameters
- ==========
- The input(s) of this method are tuple(s) of the form (label, new_label).
- label: String or Symbol
- The label of the node for which the label has
- to be changed.
- new_label: String or Symbol
- The new label of the node.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> t = Truss()
- >>> t.add_node(('A', 0, 0), ('B', 3, 0))
- >>> t.nodes
- [('A', 0, 0), ('B', 3, 0)]
- >>> t.change_node_label(('A', 'C'), ('B', 'D'))
- >>> t.nodes
- [('C', 0, 0), ('D', 3, 0)]
- """
- for i in args:
- label = i[0]
- new_label = i[1]
- if label not in self._node_coordinates:
- raise ValueError("No such node exists for the Truss")
- elif new_label in self._node_coordinates:
- raise ValueError("A node with the given label already exists")
- else:
- for node in self._nodes:
- if node[0] == label:
- self._nodes[self._nodes.index((label, node[1], node[2]))] = (new_label, node[1], node[2])
- self._node_labels[self._node_labels.index(node[0])] = new_label
- self._node_coordinates[new_label] = self._node_coordinates[label]
- self._node_coordinates.pop(label)
- if node[0] in self._supports:
- self._supports[new_label] = self._supports[node[0]]
- self._supports.pop(node[0])
- if new_label in self._supports:
- if self._supports[new_label] == 'pinned':
- if 'R_'+str(label)+'_x' in self._reaction_loads and 'R_'+str(label)+'_y' in self._reaction_loads:
- self._reaction_loads['R_'+str(new_label)+'_x'] = self._reaction_loads['R_'+str(label)+'_x']
- self._reaction_loads['R_'+str(new_label)+'_y'] = self._reaction_loads['R_'+str(label)+'_y']
- self._reaction_loads.pop('R_'+str(label)+'_x')
- self._reaction_loads.pop('R_'+str(label)+'_y')
- self._loads[new_label] = self._loads[label]
- for load in self._loads[new_label]:
- if load[1] == 90:
- load[0] -= Symbol('R_'+str(label)+'_y')
- if load[0] == 0:
- self._loads[label].remove(load)
- break
- for load in self._loads[new_label]:
- if load[1] == 0:
- load[0] -= Symbol('R_'+str(label)+'_x')
- if load[0] == 0:
- self._loads[label].remove(load)
- break
- self.apply_load(new_label, Symbol('R_'+str(new_label)+'_x'), 0)
- self.apply_load(new_label, Symbol('R_'+str(new_label)+'_y'), 90)
- self._loads.pop(label)
- elif self._supports[new_label] == 'roller':
- self._loads[new_label] = self._loads[label]
- for load in self._loads[label]:
- if load[1] == 90:
- load[0] -= Symbol('R_'+str(label)+'_y')
- if load[0] == 0:
- self._loads[label].remove(load)
- break
- self.apply_load(new_label, Symbol('R_'+str(new_label)+'_y'), 90)
- self._loads.pop(label)
- else:
- if label in self._loads:
- self._loads[new_label] = self._loads[label]
- self._loads.pop(label)
- for member in self._members:
- if self._members[member][0] == node[0]:
- self._members[member][0] = new_label
- self._nodes_occupied[(new_label, self._members[member][1])] = True
- self._nodes_occupied[(self._members[member][1], new_label)] = True
- self._nodes_occupied.pop((label, self._members[member][1]))
- self._nodes_occupied.pop((self._members[member][1], label))
- elif self._members[member][1] == node[0]:
- self._members[member][1] = new_label
- self._nodes_occupied[(self._members[member][0], new_label)] = True
- self._nodes_occupied[(new_label, self._members[member][0])] = True
- self._nodes_occupied.pop((self._members[member][0], label))
- self._nodes_occupied.pop((label, self._members[member][0]))
- def change_member_label(self, *args):
- """
- This method changes the label(s) of the specified member(s).
- Parameters
- ==========
- The input(s) of this method are tuple(s) of the form (label, new_label)
- label: String or Symbol
- The label of the member for which the label has
- to be changed.
- new_label: String or Symbol
- The new label of the member.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> t = Truss()
- >>> t.add_node(('A', 0, 0), ('B', 3, 0), ('D', 5, 0))
- >>> t.nodes
- [('A', 0, 0), ('B', 3, 0), ('D', 5, 0)]
- >>> t.change_node_label(('A', 'C'))
- >>> t.nodes
- [('C', 0, 0), ('B', 3, 0), ('D', 5, 0)]
- >>> t.add_member(('BC', 'B', 'C'), ('BD', 'B', 'D'))
- >>> t.members
- {'BC': ['B', 'C'], 'BD': ['B', 'D']}
- >>> t.change_member_label(('BC', 'BC_new'), ('BD', 'BD_new'))
- >>> t.members
- {'BC_new': ['B', 'C'], 'BD_new': ['B', 'D']}
- """
- for i in args:
- label = i[0]
- new_label = i[1]
- if label not in self._members:
- raise ValueError("No such member exists for the Truss")
- else:
- members_duplicate = list(self._members).copy()
- for member in members_duplicate:
- if member == label:
- self._members[new_label] = [self._members[member][0], self._members[member][1]]
- self._members.pop(label)
- self._member_lengths[new_label] = self._member_lengths[label]
- self._member_lengths.pop(label)
- self._internal_forces[new_label] = self._internal_forces[label]
- self._internal_forces.pop(label)
- def apply_load(self, *args):
- """
- This method applies external load(s) at the specified node(s).
- Parameters
- ==========
- The input(s) of the method are tuple(s) of the form (location, magnitude, direction).
- location: String or Symbol
- Label of the Node at which load is applied.
- magnitude: Sympifyable
- Magnitude of the load applied. It must always be positive and any changes in
- the direction of the load are not reflected here.
- direction: Sympifyable
- The angle, in degrees, that the load vector makes with the horizontal
- in the counter-clockwise direction. It takes the values 0 to 360,
- inclusive.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> from sympy import symbols
- >>> t = Truss()
- >>> t.add_node(('A', 0, 0), ('B', 3, 0))
- >>> P = symbols('P')
- >>> t.apply_load(('A', P, 90), ('A', P/2, 45), ('A', P/4, 90))
- >>> t.loads
- {'A': [[P, 90], [P/2, 45], [P/4, 90]]}
- """
- for i in args:
- location = i[0]
- magnitude = i[1]
- direction = i[2]
- magnitude = sympify(magnitude)
- direction = sympify(direction)
- if location not in self._node_coordinates:
- raise ValueError("Load must be applied at a known node")
- else:
- if location in self._loads:
- self._loads[location].append([magnitude, direction])
- else:
- self._loads[location] = [[magnitude, direction]]
- def remove_load(self, *args):
- """
- This method removes already
- present external load(s) at specified node(s).
- Parameters
- ==========
- The input(s) of this method are tuple(s) of the form (location, magnitude, direction).
- location: String or Symbol
- Label of the Node at which load is applied and is to be removed.
- magnitude: Sympifyable
- Magnitude of the load applied.
- direction: Sympifyable
- The angle, in degrees, that the load vector makes with the horizontal
- in the counter-clockwise direction. It takes the values 0 to 360,
- inclusive.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> from sympy import symbols
- >>> t = Truss()
- >>> t.add_node(('A', 0, 0), ('B', 3, 0))
- >>> P = symbols('P')
- >>> t.apply_load(('A', P, 90), ('A', P/2, 45), ('A', P/4, 90))
- >>> t.loads
- {'A': [[P, 90], [P/2, 45], [P/4, 90]]}
- >>> t.remove_load(('A', P/4, 90), ('A', P/2, 45))
- >>> t.loads
- {'A': [[P, 90]]}
- """
- for i in args:
- location = i[0]
- magnitude = i[1]
- direction = i[2]
- magnitude = sympify(magnitude)
- direction = sympify(direction)
- if location not in self._node_coordinates:
- raise ValueError("Load must be removed from a known node")
- else:
- if [magnitude, direction] not in self._loads[location]:
- raise ValueError("No load of this magnitude and direction has been applied at this node")
- else:
- self._loads[location].remove([magnitude, direction])
- if self._loads[location] == []:
- self._loads.pop(location)
- def apply_support(self, *args):
- """
- This method adds a pinned or roller support at specified node(s).
- Parameters
- ==========
- The input(s) of this method are of the form (location, type).
- location: String or Symbol
- Label of the Node at which support is added.
- type: String
- Type of the support being provided at the node.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> t = Truss()
- >>> t.add_node(('A', 0, 0), ('B', 3, 0))
- >>> t.apply_support(('A', 'pinned'), ('B', 'roller'))
- >>> t.supports
- {'A': 'pinned', 'B': 'roller'}
- """
- for i in args:
- location = i[0]
- type = i[1]
- if location not in self._node_coordinates:
- raise ValueError("Support must be added on a known node")
- else:
- if location not in self._supports:
- if type == 'pinned':
- self.apply_load((location, Symbol('R_'+str(location)+'_x'), 0))
- self.apply_load((location, Symbol('R_'+str(location)+'_y'), 90))
- elif type == 'roller':
- self.apply_load((location, Symbol('R_'+str(location)+'_y'), 90))
- elif self._supports[location] == 'pinned':
- if type == 'roller':
- self.remove_load((location, Symbol('R_'+str(location)+'_x'), 0))
- elif self._supports[location] == 'roller':
- if type == 'pinned':
- self.apply_load((location, Symbol('R_'+str(location)+'_x'), 0))
- self._supports[location] = type
- def remove_support(self, *args):
- """
- This method removes support from specified node(s.)
- Parameters
- ==========
- locations: String or Symbol
- Label of the Node(s) at which support is to be removed.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> t = Truss()
- >>> t.add_node(('A', 0, 0), ('B', 3, 0))
- >>> t.apply_support(('A', 'pinned'), ('B', 'roller'))
- >>> t.supports
- {'A': 'pinned', 'B': 'roller'}
- >>> t.remove_support('A','B')
- >>> t.supports
- {}
- """
- for location in args:
- if location not in self._node_coordinates:
- raise ValueError("No such node exists in the Truss")
- elif location not in self._supports:
- raise ValueError("No support has been added to the given node")
- else:
- if self._supports[location] == 'pinned':
- self.remove_load((location, Symbol('R_'+str(location)+'_x'), 0))
- self.remove_load((location, Symbol('R_'+str(location)+'_y'), 90))
- elif self._supports[location] == 'roller':
- self.remove_load((location, Symbol('R_'+str(location)+'_y'), 90))
- self._supports.pop(location)
- def solve(self):
- """
- This method solves for all reaction forces of all supports and all internal forces
- of all the members in the truss, provided the Truss is solvable.
- A Truss is solvable if the following condition is met,
- 2n >= r + m
- Where n is the number of nodes, r is the number of reaction forces, where each pinned
- support has 2 reaction forces and each roller has 1, and m is the number of members.
- The given condition is derived from the fact that a system of equations is solvable
- only when the number of variables is lesser than or equal to the number of equations.
- Equilibrium Equations in x and y directions give two equations per node giving 2n number
- equations. However, the truss needs to be stable as well and may be unstable if 2n > r + m.
- The number of variables is simply the sum of the number of reaction forces and member
- forces.
- .. note::
- The sign convention for the internal forces present in a member revolves around whether each
- force is compressive or tensile. While forming equations for each node, internal force due
- to a member on the node is assumed to be away from the node i.e. each force is assumed to
- be compressive by default. Hence, a positive value for an internal force implies the
- presence of compressive force in the member and a negative value implies a tensile force.
- Examples
- ========
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> t = Truss()
- >>> t.add_node(("node_1", 0, 0), ("node_2", 6, 0), ("node_3", 2, 2), ("node_4", 2, 0))
- >>> t.add_member(("member_1", "node_1", "node_4"), ("member_2", "node_2", "node_4"), ("member_3", "node_1", "node_3"))
- >>> t.add_member(("member_4", "node_2", "node_3"), ("member_5", "node_3", "node_4"))
- >>> t.apply_load(("node_4", 10, 270))
- >>> t.apply_support(("node_1", "pinned"), ("node_2", "roller"))
- >>> t.solve()
- >>> t.reaction_loads
- {'R_node_1_x': 0, 'R_node_1_y': 20/3, 'R_node_2_y': 10/3}
- >>> t.internal_forces
- {'member_1': 20/3, 'member_2': 20/3, 'member_3': -20*sqrt(2)/3, 'member_4': -10*sqrt(5)/3, 'member_5': 10}
- """
- count_reaction_loads = 0
- for node in self._nodes:
- if node[0] in self._supports:
- if self._supports[node[0]]=='pinned':
- count_reaction_loads += 2
- elif self._supports[node[0]]=='roller':
- count_reaction_loads += 1
- if 2*len(self._nodes) != len(self._members) + count_reaction_loads:
- raise ValueError("The given truss cannot be solved")
- coefficients_matrix = [[0 for i in range(2*len(self._nodes))] for j in range(2*len(self._nodes))]
- load_matrix = zeros(2*len(self.nodes), 1)
- load_matrix_row = 0
- for node in self._nodes:
- if node[0] in self._loads:
- for load in self._loads[node[0]]:
- if load[0]!=Symbol('R_'+str(node[0])+'_x') and load[0]!=Symbol('R_'+str(node[0])+'_y'):
- load_matrix[load_matrix_row] -= load[0]*cos(pi*load[1]/180)
- load_matrix[load_matrix_row + 1] -= load[0]*sin(pi*load[1]/180)
- load_matrix_row += 2
- cols = 0
- row = 0
- for node in self._nodes:
- if node[0] in self._supports:
- if self._supports[node[0]]=='pinned':
- coefficients_matrix[row][cols] += 1
- coefficients_matrix[row+1][cols+1] += 1
- cols += 2
- elif self._supports[node[0]]=='roller':
- coefficients_matrix[row+1][cols] += 1
- cols += 1
- row += 2
- for member in self._members:
- start = self._members[member][0]
- end = self._members[member][1]
- length = sqrt((self._node_coordinates[start][0]-self._node_coordinates[end][0])**2 + (self._node_coordinates[start][1]-self._node_coordinates[end][1])**2)
- start_index = self._node_labels.index(start)
- end_index = self._node_labels.index(end)
- horizontal_component_start = (self._node_coordinates[end][0]-self._node_coordinates[start][0])/length
- vertical_component_start = (self._node_coordinates[end][1]-self._node_coordinates[start][1])/length
- horizontal_component_end = (self._node_coordinates[start][0]-self._node_coordinates[end][0])/length
- vertical_component_end = (self._node_coordinates[start][1]-self._node_coordinates[end][1])/length
- coefficients_matrix[start_index*2][cols] += horizontal_component_start
- coefficients_matrix[start_index*2+1][cols] += vertical_component_start
- coefficients_matrix[end_index*2][cols] += horizontal_component_end
- coefficients_matrix[end_index*2+1][cols] += vertical_component_end
- cols += 1
- forces_matrix = (Matrix(coefficients_matrix)**-1)*load_matrix
- self._reaction_loads = {}
- i = 0
- min_load = inf
- for node in self._nodes:
- if node[0] in self._loads:
- for load in self._loads[node[0]]:
- if type(load[0]) not in [Symbol, Mul, Add]:
- min_load = min(min_load, load[0])
- for j in range(len(forces_matrix)):
- if type(forces_matrix[j]) not in [Symbol, Mul, Add]:
- if abs(forces_matrix[j]/min_load) <1E-10:
- forces_matrix[j] = 0
- for node in self._nodes:
- if node[0] in self._supports:
- if self._supports[node[0]]=='pinned':
- self._reaction_loads['R_'+str(node[0])+'_x'] = forces_matrix[i]
- self._reaction_loads['R_'+str(node[0])+'_y'] = forces_matrix[i+1]
- i += 2
- elif self._supports[node[0]]=='roller':
- self._reaction_loads['R_'+str(node[0])+'_y'] = forces_matrix[i]
- i += 1
- for member in self._members:
- self._internal_forces[member] = forces_matrix[i]
- i += 1
- return
- @doctest_depends_on(modules=('numpy',))
- def draw(self, subs_dict=None):
- """
- Returns a plot object of the Truss with all its nodes, members,
- supports and loads.
- .. note::
- The user must be careful while entering load values in their
- directions. The draw function assumes a sign convention that
- is used for plotting loads.
- Given a right-handed coordinate system with XYZ coordinates,
- the supports are assumed to be such that the reaction forces of a
- pinned support is in the +X and +Y direction while those of a
- roller support is in the +Y direction. For the load, the range
- of angles, one can input goes all the way to 360 degrees which, in the
- the plot is the angle that the load vector makes with the positive x-axis in the anticlockwise direction.
- For example, for a 90-degree angle, the load will be a vertically
- directed along +Y while a 270-degree angle denotes a vertical
- load as well but along -Y.
- Examples
- ========
- .. plot::
- :context: close-figs
- :format: doctest
- :include-source: True
- >>> from sympy.physics.continuum_mechanics.truss import Truss
- >>> import math
- >>> t = Truss()
- >>> t.add_node(("A", -4, 0), ("B", 0, 0), ("C", 4, 0), ("D", 8, 0))
- >>> t.add_node(("E", 6, 2/math.sqrt(3)))
- >>> t.add_node(("F", 2, 2*math.sqrt(3)))
- >>> t.add_node(("G", -2, 2/math.sqrt(3)))
- >>> t.add_member(("AB","A","B"), ("BC","B","C"), ("CD","C","D"))
- >>> t.add_member(("AG","A","G"), ("GB","G","B"), ("GF","G","F"))
- >>> t.add_member(("BF","B","F"), ("FC","F","C"), ("CE","C","E"))
- >>> t.add_member(("FE","F","E"), ("DE","D","E"))
- >>> t.apply_support(("A","pinned"), ("D","roller"))
- >>> t.apply_load(("G", 3, 90), ("E", 3, 90), ("F", 2, 90))
- >>> p = t.draw()
- >>> p # doctest: +ELLIPSIS
- Plot object containing:
- [0]: cartesian line: 1 for x over (1.0, 1.0)
- ...
- >>> p.show()
- """
- if not numpy:
- raise ImportError("To use this function numpy module is required")
- x = Symbol('x')
- markers = []
- annotations = []
- rectangles = []
- node_markers = self._draw_nodes(subs_dict)
- markers += node_markers
- member_rectangles = self._draw_members()
- rectangles += member_rectangles
- support_markers = self._draw_supports()
- markers += support_markers
- load_annotations = self._draw_loads()
- annotations += load_annotations
- xmax = -INF
- xmin = INF
- ymax = -INF
- ymin = INF
- for node in self._node_coordinates:
- xmax = max(xmax, self._node_coordinates[node][0])
- xmin = min(xmin, self._node_coordinates[node][0])
- ymax = max(ymax, self._node_coordinates[node][1])
- ymin = min(ymin, self._node_coordinates[node][1])
- lim = max(xmax*1.1-xmin*0.8+1, ymax*1.1-ymin*0.8+1)
- if lim==xmax*1.1-xmin*0.8+1:
- sing_plot = plot(1, (x, 1, 1), markers=markers, show=False, annotations=annotations, xlim=(xmin-0.05*lim, xmax*1.1), ylim=(xmin-0.05*lim, xmax*1.1), axis=False, rectangles=rectangles)
- else:
- sing_plot = plot(1, (x, 1, 1), markers=markers, show=False, annotations=annotations, xlim=(ymin-0.05*lim, ymax*1.1), ylim=(ymin-0.05*lim, ymax*1.1), axis=False, rectangles=rectangles)
- return sing_plot
- def _draw_nodes(self, subs_dict):
- node_markers = []
- for node in self._node_coordinates:
- if (type(self._node_coordinates[node][0]) in (Symbol, Quantity)):
- if self._node_coordinates[node][0] in subs_dict:
- self._node_coordinates[node][0] = subs_dict[self._node_coordinates[node][0]]
- else:
- raise ValueError("provided substituted dictionary is not adequate")
- elif (type(self._node_coordinates[node][0]) == Mul):
- objects = self._node_coordinates[node][0].as_coeff_Mul()
- for object in objects:
- if type(object) in (Symbol, Quantity):
- if subs_dict==None or object not in subs_dict:
- raise ValueError("provided substituted dictionary is not adequate")
- else:
- self._node_coordinates[node][0] /= object
- self._node_coordinates[node][0] *= subs_dict[object]
- if (type(self._node_coordinates[node][1]) in (Symbol, Quantity)):
- if self._node_coordinates[node][1] in subs_dict:
- self._node_coordinates[node][1] = subs_dict[self._node_coordinates[node][1]]
- else:
- raise ValueError("provided substituted dictionary is not adequate")
- elif (type(self._node_coordinates[node][1]) == Mul):
- objects = self._node_coordinates[node][1].as_coeff_Mul()
- for object in objects:
- if type(object) in (Symbol, Quantity):
- if subs_dict==None or object not in subs_dict:
- raise ValueError("provided substituted dictionary is not adequate")
- else:
- self._node_coordinates[node][1] /= object
- self._node_coordinates[node][1] *= subs_dict[object]
- for node in self._node_coordinates:
- node_markers.append(
- {
- 'args':[[self._node_coordinates[node][0]], [self._node_coordinates[node][1]]],
- 'marker':'o',
- 'markersize':5,
- 'color':'black'
- }
- )
- return node_markers
- def _draw_members(self):
- member_rectangles = []
- xmax = -INF
- xmin = INF
- ymax = -INF
- ymin = INF
- for node in self._node_coordinates:
- xmax = max(xmax, self._node_coordinates[node][0])
- xmin = min(xmin, self._node_coordinates[node][0])
- ymax = max(ymax, self._node_coordinates[node][1])
- ymin = min(ymin, self._node_coordinates[node][1])
- if abs(1.1*xmax-0.8*xmin)>abs(1.1*ymax-0.8*ymin):
- max_diff = 1.1*xmax-0.8*xmin
- else:
- max_diff = 1.1*ymax-0.8*ymin
- for member in self._members:
- x1 = self._node_coordinates[self._members[member][0]][0]
- y1 = self._node_coordinates[self._members[member][0]][1]
- x2 = self._node_coordinates[self._members[member][1]][0]
- y2 = self._node_coordinates[self._members[member][1]][1]
- if x2!=x1 and y2!=y1:
- if x2>x1:
- member_rectangles.append(
- {
- 'xy':(x1-0.005*max_diff*cos(pi/4+atan((y2-y1)/(x2-x1)))/2, y1-0.005*max_diff*sin(pi/4+atan((y2-y1)/(x2-x1)))/2),
- 'width':sqrt((x1-x2)**2+(y1-y2)**2)+0.005*max_diff/math.sqrt(2),
- 'height':0.005*max_diff,
- 'angle':180*atan((y2-y1)/(x2-x1))/pi,
- 'color':'brown'
- }
- )
- else:
- member_rectangles.append(
- {
- 'xy':(x2-0.005*max_diff*cos(pi/4+atan((y2-y1)/(x2-x1)))/2, y2-0.005*max_diff*sin(pi/4+atan((y2-y1)/(x2-x1)))/2),
- 'width':sqrt((x1-x2)**2+(y1-y2)**2)+0.005*max_diff/math.sqrt(2),
- 'height':0.005*max_diff,
- 'angle':180*atan((y2-y1)/(x2-x1))/pi,
- 'color':'brown'
- }
- )
- elif y2==y1:
- if x2>x1:
- member_rectangles.append(
- {
- 'xy':(x1-0.005*max_diff/2, y1-0.005*max_diff/2),
- 'width':sqrt((x1-x2)**2+(y1-y2)**2),
- 'height':0.005*max_diff,
- 'angle':90*(1-math.copysign(1, x2-x1)),
- 'color':'brown'
- }
- )
- else:
- member_rectangles.append(
- {
- 'xy':(x1-0.005*max_diff/2, y1-0.005*max_diff/2),
- 'width':sqrt((x1-x2)**2+(y1-y2)**2),
- 'height':-0.005*max_diff,
- 'angle':90*(1-math.copysign(1, x2-x1)),
- 'color':'brown'
- }
- )
- else:
- if y1<y2:
- member_rectangles.append(
- {
- 'xy':(x1-0.005*max_diff/2, y1-0.005*max_diff/2),
- 'width':sqrt((x1-x2)**2+(y1-y2)**2)+0.005*max_diff/2,
- 'height':0.005*max_diff,
- 'angle':90*math.copysign(1, y2-y1),
- 'color':'brown'
- }
- )
- else:
- member_rectangles.append(
- {
- 'xy':(x2-0.005*max_diff/2, y2-0.005*max_diff/2),
- 'width':-(sqrt((x1-x2)**2+(y1-y2)**2)+0.005*max_diff/2),
- 'height':0.005*max_diff,
- 'angle':90*math.copysign(1, y2-y1),
- 'color':'brown'
- }
- )
- return member_rectangles
- def _draw_supports(self):
- support_markers = []
- xmax = -INF
- xmin = INF
- ymax = -INF
- ymin = INF
- for node in self._node_coordinates:
- xmax = max(xmax, self._node_coordinates[node][0])
- xmin = min(xmin, self._node_coordinates[node][0])
- ymax = max(ymax, self._node_coordinates[node][1])
- ymin = min(ymin, self._node_coordinates[node][1])
- if abs(1.1*xmax-0.8*xmin)>abs(1.1*ymax-0.8*ymin):
- max_diff = 1.1*xmax-0.8*xmin
- else:
- max_diff = 1.1*ymax-0.8*ymin
- for node in self._supports:
- if self._supports[node]=='pinned':
- support_markers.append(
- {
- 'args':[
- [self._node_coordinates[node][0]],
- [self._node_coordinates[node][1]]
- ],
- 'marker':6,
- 'markersize':15,
- 'color':'black',
- 'markerfacecolor':'none'
- }
- )
- support_markers.append(
- {
- 'args':[
- [self._node_coordinates[node][0]],
- [self._node_coordinates[node][1]-0.035*max_diff]
- ],
- 'marker':'_',
- 'markersize':14,
- 'color':'black'
- }
- )
- elif self._supports[node]=='roller':
- support_markers.append(
- {
- 'args':[
- [self._node_coordinates[node][0]],
- [self._node_coordinates[node][1]-0.02*max_diff]
- ],
- 'marker':'o',
- 'markersize':11,
- 'color':'black',
- 'markerfacecolor':'none'
- }
- )
- support_markers.append(
- {
- 'args':[
- [self._node_coordinates[node][0]],
- [self._node_coordinates[node][1]-0.0375*max_diff]
- ],
- 'marker':'_',
- 'markersize':14,
- 'color':'black'
- }
- )
- return support_markers
- def _draw_loads(self):
- load_annotations = []
- xmax = -INF
- xmin = INF
- ymax = -INF
- ymin = INF
- for node in self._node_coordinates:
- xmax = max(xmax, self._node_coordinates[node][0])
- xmin = min(xmin, self._node_coordinates[node][0])
- ymax = max(ymax, self._node_coordinates[node][1])
- ymin = min(ymin, self._node_coordinates[node][1])
- if abs(1.1*xmax-0.8*xmin)>abs(1.1*ymax-0.8*ymin):
- max_diff = 1.1*xmax-0.8*xmin+5
- else:
- max_diff = 1.1*ymax-0.8*ymin+5
- for node in self._loads:
- for load in self._loads[node]:
- if load[0] in [Symbol('R_'+str(node)+'_x'), Symbol('R_'+str(node)+'_y')]:
- continue
- x = self._node_coordinates[node][0]
- y = self._node_coordinates[node][1]
- load_annotations.append(
- {
- 'text':'',
- 'xy':(
- x-math.cos(pi*load[1]/180)*(max_diff/100),
- y-math.sin(pi*load[1]/180)*(max_diff/100)
- ),
- 'xytext':(
- x-(max_diff/100+abs(xmax-xmin)+abs(ymax-ymin))*math.cos(pi*load[1]/180)/20,
- y-(max_diff/100+abs(xmax-xmin)+abs(ymax-ymin))*math.sin(pi*load[1]/180)/20
- ),
- 'arrowprops':{'width':1.5, 'headlength':5, 'headwidth':5, 'facecolor':'black'}
- }
- )
- return load_annotations
|