truss.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. """
  2. This module can be used to solve problems related
  3. to 2D Trusses.
  4. """
  5. from cmath import inf
  6. from sympy.core.add import Add
  7. from sympy.core.mul import Mul
  8. from sympy.core.symbol import Symbol
  9. from sympy.core.sympify import sympify
  10. from sympy import Matrix, pi
  11. from sympy.functions.elementary.miscellaneous import sqrt
  12. from sympy.matrices.dense import zeros
  13. from sympy import sin, cos
  14. class Truss:
  15. """
  16. A Truss is an assembly of members such as beams,
  17. connected by nodes, that create a rigid structure.
  18. In engineering, a truss is a structure that
  19. consists of two-force members only.
  20. Trusses are extremely important in engineering applications
  21. and can be seen in numerous real-world applications like bridges.
  22. Examples
  23. ========
  24. There is a Truss consisting of four nodes and five
  25. members connecting the nodes. A force P acts
  26. downward on the node D and there also exist pinned
  27. and roller joints on the nodes A and B respectively.
  28. .. image:: truss_example.png
  29. >>> from sympy.physics.continuum_mechanics.truss import Truss
  30. >>> t = Truss()
  31. >>> t.add_node("node_1", 0, 0)
  32. >>> t.add_node("node_2", 6, 0)
  33. >>> t.add_node("node_3", 2, 2)
  34. >>> t.add_node("node_4", 2, 0)
  35. >>> t.add_member("member_1", "node_1", "node_4")
  36. >>> t.add_member("member_2", "node_2", "node_4")
  37. >>> t.add_member("member_3", "node_1", "node_3")
  38. >>> t.add_member("member_4", "node_2", "node_3")
  39. >>> t.add_member("member_5", "node_3", "node_4")
  40. >>> t.apply_load("node_4", magnitude=10, direction=270)
  41. >>> t.apply_support("node_1", type="fixed")
  42. >>> t.apply_support("node_2", type="roller")
  43. """
  44. def __init__(self):
  45. """
  46. Initializes the class
  47. """
  48. self._nodes = []
  49. self._members = {}
  50. self._loads = {}
  51. self._supports = {}
  52. self._node_labels = []
  53. self._node_positions = []
  54. self._node_position_x = []
  55. self._node_position_y = []
  56. self._nodes_occupied = {}
  57. self._reaction_loads = {}
  58. self._internal_forces = {}
  59. self._node_coordinates = {}
  60. @property
  61. def nodes(self):
  62. """
  63. Returns the nodes of the truss along with their positions.
  64. """
  65. return self._nodes
  66. @property
  67. def node_labels(self):
  68. """
  69. Returns the node labels of the truss.
  70. """
  71. return self._node_labels
  72. @property
  73. def node_positions(self):
  74. """
  75. Returns the positions of the nodes of the truss.
  76. """
  77. return self._node_positions
  78. @property
  79. def members(self):
  80. """
  81. Returns the members of the truss along with the start and end points.
  82. """
  83. return self._members
  84. @property
  85. def member_labels(self):
  86. """
  87. Returns the members of the truss along with the start and end points.
  88. """
  89. return self._member_labels
  90. @property
  91. def supports(self):
  92. """
  93. Returns the nodes with provided supports along with the kind of support provided i.e.
  94. pinned or roller.
  95. """
  96. return self._supports
  97. @property
  98. def loads(self):
  99. """
  100. Returns the loads acting on the truss.
  101. """
  102. return self._loads
  103. @property
  104. def reaction_loads(self):
  105. """
  106. Returns the reaction forces for all supports which are all initialized to 0.
  107. """
  108. return self._reaction_loads
  109. @property
  110. def internal_forces(self):
  111. """
  112. Returns the internal forces for all members which are all initialized to 0.
  113. """
  114. return self._internal_forces
  115. def add_node(self, label, x, y):
  116. """
  117. This method adds a node to the truss along with its name/label and its location.
  118. Parameters
  119. ==========
  120. label: String or a Symbol
  121. The label for a node. It is the only way to identify a particular node.
  122. x: Sympifyable
  123. The x-coordinate of the position of the node.
  124. y: Sympifyable
  125. The y-coordinate of the position of the node.
  126. Examples
  127. ========
  128. >>> from sympy.physics.continuum_mechanics.truss import Truss
  129. >>> t = Truss()
  130. >>> t.add_node('A', 0, 0)
  131. >>> t.nodes
  132. [('A', 0, 0)]
  133. >>> t.add_node('B', 3, 0)
  134. >>> t.nodes
  135. [('A', 0, 0), ('B', 3, 0)]
  136. """
  137. x = sympify(x)
  138. y = sympify(y)
  139. if label in self._node_labels:
  140. raise ValueError("Node needs to have a unique label")
  141. elif x in self._node_position_x and y in self._node_position_y and self._node_position_x.index(x)==self._node_position_y.index(y):
  142. raise ValueError("A node already exists at the given position")
  143. else :
  144. self._nodes.append((label, x, y))
  145. self._node_labels.append(label)
  146. self._node_positions.append((x, y))
  147. self._node_position_x.append(x)
  148. self._node_position_y.append(y)
  149. self._node_coordinates[label] = [x, y]
  150. def remove_node(self, label):
  151. """
  152. This method removes a node from the truss.
  153. Parameters
  154. ==========
  155. label: String or Symbol
  156. The label of the node to be removed.
  157. Examples
  158. ========
  159. >>> from sympy.physics.continuum_mechanics.truss import Truss
  160. >>> t = Truss()
  161. >>> t.add_node('A', 0, 0)
  162. >>> t.nodes
  163. [('A', 0, 0)]
  164. >>> t.add_node('B', 3, 0)
  165. >>> t.nodes
  166. [('A', 0, 0), ('B', 3, 0)]
  167. >>> t.remove_node('A')
  168. >>> t.nodes
  169. [('B', 3, 0)]
  170. """
  171. for i in range(len(self.nodes)):
  172. if self._node_labels[i] == label:
  173. x = self._node_position_x[i]
  174. y = self._node_position_y[i]
  175. if label not in self._node_labels:
  176. raise ValueError("No such node exists in the truss")
  177. else:
  178. members_duplicate = self._members.copy()
  179. for member in members_duplicate:
  180. if label == self._members[member][0] or label == self._members[member][1]:
  181. raise ValueError("The node given has members already attached to it")
  182. self._nodes.remove((label, x, y))
  183. self._node_labels.remove(label)
  184. self._node_positions.remove((x, y))
  185. self._node_position_x.remove(x)
  186. self._node_position_y.remove(y)
  187. if label in list(self._loads):
  188. self._loads.pop(label)
  189. if label in list(self._supports):
  190. self._supports.pop(label)
  191. self._node_coordinates.pop(label)
  192. def add_member(self, label, start, end):
  193. """
  194. This method adds a member between any two nodes in the given truss.
  195. Parameters
  196. ==========
  197. label: String or Symbol
  198. The label for a member. It is the only way to identify a particular member.
  199. start: String or Symbol
  200. The label of the starting point/node of the member.
  201. end: String or Symbol
  202. The label of the ending point/node of the member.
  203. Examples
  204. ========
  205. >>> from sympy.physics.continuum_mechanics.truss import Truss
  206. >>> t = Truss()
  207. >>> t.add_node('A', 0, 0)
  208. >>> t.add_node('B', 3, 0)
  209. >>> t.add_node('C', 2, 2)
  210. >>> t.add_member('AB', 'A', 'B')
  211. >>> t.members
  212. {'AB': ['A', 'B']}
  213. """
  214. if start not in self._node_labels or end not in self._node_labels or start==end:
  215. raise ValueError("The start and end points of the member must be unique nodes")
  216. elif label in list(self._members):
  217. raise ValueError("A member with the same label already exists for the truss")
  218. elif self._nodes_occupied.get((start, end)):
  219. raise ValueError("A member already exists between the two nodes")
  220. else:
  221. self._members[label] = [start, end]
  222. self._nodes_occupied[start, end] = True
  223. self._nodes_occupied[end, start] = True
  224. self._internal_forces[label] = 0
  225. def remove_member(self, label):
  226. """
  227. This method removes a member from the given truss.
  228. Parameters
  229. ==========
  230. label: String or Symbol
  231. The label for the member to be removed.
  232. Examples
  233. ========
  234. >>> from sympy.physics.continuum_mechanics.truss import Truss
  235. >>> t = Truss()
  236. >>> t.add_node('A', 0, 0)
  237. >>> t.add_node('B', 3, 0)
  238. >>> t.add_node('C', 2, 2)
  239. >>> t.add_member('AB', 'A', 'B')
  240. >>> t.add_member('AC', 'A', 'C')
  241. >>> t.add_member('BC', 'B', 'C')
  242. >>> t.members
  243. {'AB': ['A', 'B'], 'AC': ['A', 'C'], 'BC': ['B', 'C']}
  244. >>> t.remove_member('AC')
  245. >>> t.members
  246. {'AB': ['A', 'B'], 'BC': ['B', 'C']}
  247. """
  248. if label not in list(self._members):
  249. raise ValueError("No such member exists in the Truss")
  250. else:
  251. self._nodes_occupied.pop((self._members[label][0], self._members[label][1]))
  252. self._nodes_occupied.pop((self._members[label][1], self._members[label][0]))
  253. self._members.pop(label)
  254. self._internal_forces.pop(label)
  255. def change_node_label(self, label, new_label):
  256. """
  257. This method changes the label of a node.
  258. Parameters
  259. ==========
  260. label: String or Symbol
  261. The label of the node for which the label has
  262. to be changed.
  263. new_label: String or Symbol
  264. The new label of the node.
  265. Examples
  266. ========
  267. >>> from sympy.physics.continuum_mechanics.truss import Truss
  268. >>> t = Truss()
  269. >>> t.add_node('A', 0, 0)
  270. >>> t.add_node('B', 3, 0)
  271. >>> t.nodes
  272. [('A', 0, 0), ('B', 3, 0)]
  273. >>> t.change_node_label('A', 'C')
  274. >>> t.nodes
  275. [('C', 0, 0), ('B', 3, 0)]
  276. """
  277. if label not in self._node_labels:
  278. raise ValueError("No such node exists for the Truss")
  279. elif new_label in self._node_labels:
  280. raise ValueError("A node with the given label already exists")
  281. else:
  282. for node in self._nodes:
  283. if node[0] == label:
  284. self._nodes[self._nodes.index((label, node[1], node[2]))] = (new_label, node[1], node[2])
  285. self._node_labels[self._node_labels.index(node[0])] = new_label
  286. self._node_coordinates[new_label] = self._node_coordinates[label]
  287. self._node_coordinates.pop(label)
  288. if node[0] in list(self._supports):
  289. self._supports[new_label] = self._supports[node[0]]
  290. self._supports.pop(node[0])
  291. if new_label in list(self._supports):
  292. if self._supports[new_label] == 'pinned':
  293. if 'R_'+str(label)+'_x' in list(self._reaction_loads) and 'R_'+str(label)+'_y' in list(self._reaction_loads):
  294. self._reaction_loads['R_'+str(new_label)+'_x'] = self._reaction_loads['R_'+str(label)+'_x']
  295. self._reaction_loads['R_'+str(new_label)+'_y'] = self._reaction_loads['R_'+str(label)+'_y']
  296. self._reaction_loads.pop('R_'+str(label)+'_x')
  297. self._reaction_loads.pop('R_'+str(label)+'_y')
  298. self._loads[new_label] = self._loads[label]
  299. for load in self._loads[new_label]:
  300. if load[1] == 90:
  301. load[0] -= Symbol('R_'+str(label)+'_y')
  302. if load[0] == 0:
  303. self._loads[label].remove(load)
  304. break
  305. for load in self._loads[new_label]:
  306. if load[1] == 0:
  307. load[0] -= Symbol('R_'+str(label)+'_x')
  308. if load[0] == 0:
  309. self._loads[label].remove(load)
  310. break
  311. self.apply_load(new_label, Symbol('R_'+str(new_label)+'_x'), 0)
  312. self.apply_load(new_label, Symbol('R_'+str(new_label)+'_y'), 90)
  313. self._loads.pop(label)
  314. elif self._supports[new_label] == 'roller':
  315. self._loads[new_label] = self._loads[label]
  316. for load in self._loads[label]:
  317. if load[1] == 90:
  318. load[0] -= Symbol('R_'+str(label)+'_y')
  319. if load[0] == 0:
  320. self._loads[label].remove(load)
  321. break
  322. self.apply_load(new_label, Symbol('R_'+str(new_label)+'_y'), 90)
  323. self._loads.pop(label)
  324. else:
  325. if label in list(self._loads):
  326. self._loads[new_label] = self._loads[label]
  327. self._loads.pop(label)
  328. for member in list(self._members):
  329. if self._members[member][0] == node[0]:
  330. self._members[member][0] = new_label
  331. self._nodes_occupied[(new_label, self._members[member][1])] = True
  332. self._nodes_occupied[(self._members[member][1], new_label)] = True
  333. self._nodes_occupied.pop((label, self._members[member][1]))
  334. self._nodes_occupied.pop((self._members[member][1], label))
  335. elif self._members[member][1] == node[0]:
  336. self._members[member][1] = new_label
  337. self._nodes_occupied[(self._members[member][0], new_label)] = True
  338. self._nodes_occupied[(new_label, self._members[member][0])] = True
  339. self._nodes_occupied.pop((self._members[member][0], label))
  340. self._nodes_occupied.pop((label, self._members[member][0]))
  341. def change_member_label(self, label, new_label):
  342. """
  343. This method changes the label of a member.
  344. Parameters
  345. ==========
  346. label: String or Symbol
  347. The label of the member for which the label has
  348. to be changed.
  349. new_label: String or Symbol
  350. The new label of the member.
  351. Examples
  352. ========
  353. >>> from sympy.physics.continuum_mechanics.truss import Truss
  354. >>> t = Truss()
  355. >>> t.add_node('A', 0, 0)
  356. >>> t.add_node('B', 3, 0)
  357. >>> t.nodes
  358. [('A', 0, 0), ('B', 3, 0)]
  359. >>> t.change_node_label('A', 'C')
  360. >>> t.nodes
  361. [('C', 0, 0), ('B', 3, 0)]
  362. >>> t.add_member('BC', 'B', 'C')
  363. >>> t.members
  364. {'BC': ['B', 'C']}
  365. >>> t.change_member_label('BC', 'BC_new')
  366. >>> t.members
  367. {'BC_new': ['B', 'C']}
  368. """
  369. if label not in list(self._members):
  370. raise ValueError("No such member exists for the Truss")
  371. else:
  372. members_duplicate = list(self._members).copy()
  373. for member in members_duplicate:
  374. if member == label:
  375. self._members[new_label] = [self._members[member][0], self._members[member][1]]
  376. self._members.pop(label)
  377. self._internal_forces[new_label] = self._internal_forces[label]
  378. self._internal_forces.pop(label)
  379. def apply_load(self, location, magnitude, direction):
  380. """
  381. This method applies an external load at a particular node
  382. Parameters
  383. ==========
  384. location: String or Symbol
  385. Label of the Node at which load is applied.
  386. magnitude: Sympifyable
  387. Magnitude of the load applied. It must always be positive and any changes in
  388. the direction of the load are not reflected here.
  389. direction: Sympifyable
  390. The angle, in degrees, that the load vector makes with the horizontal
  391. in the counter-clockwise direction. It takes the values 0 to 360,
  392. inclusive.
  393. Examples
  394. ========
  395. >>> from sympy.physics.continuum_mechanics.truss import Truss
  396. >>> from sympy import symbols
  397. >>> t = Truss()
  398. >>> t.add_node('A', 0, 0)
  399. >>> t.add_node('B', 3, 0)
  400. >>> P = symbols('P')
  401. >>> t.apply_load('A', P, 90)
  402. >>> t.apply_load('A', P/2, 45)
  403. >>> t.apply_load('A', P/4, 90)
  404. >>> t.loads
  405. {'A': [[P, 90], [P/2, 45], [P/4, 90]]}
  406. """
  407. magnitude = sympify(magnitude)
  408. direction = sympify(direction)
  409. if location not in self.node_labels:
  410. raise ValueError("Load must be applied at a known node")
  411. else:
  412. if location in list(self._loads):
  413. self._loads[location].append([magnitude, direction])
  414. else:
  415. self._loads[location] = [[magnitude, direction]]
  416. def remove_load(self, location, magnitude, direction):
  417. """
  418. This method removes an already
  419. present external load at a particular node
  420. Parameters
  421. ==========
  422. location: String or Symbol
  423. Label of the Node at which load is applied and is to be removed.
  424. magnitude: Sympifyable
  425. Magnitude of the load applied.
  426. direction: Sympifyable
  427. The angle, in degrees, that the load vector makes with the horizontal
  428. in the counter-clockwise direction. It takes the values 0 to 360,
  429. inclusive.
  430. Examples
  431. ========
  432. >>> from sympy.physics.continuum_mechanics.truss import Truss
  433. >>> from sympy import symbols
  434. >>> t = Truss()
  435. >>> t.add_node('A', 0, 0)
  436. >>> t.add_node('B', 3, 0)
  437. >>> P = symbols('P')
  438. >>> t.apply_load('A', P, 90)
  439. >>> t.apply_load('A', P/2, 45)
  440. >>> t.apply_load('A', P/4, 90)
  441. >>> t.loads
  442. {'A': [[P, 90], [P/2, 45], [P/4, 90]]}
  443. >>> t.remove_load('A', P/4, 90)
  444. >>> t.loads
  445. {'A': [[P, 90], [P/2, 45]]}
  446. """
  447. magnitude = sympify(magnitude)
  448. direction = sympify(direction)
  449. if location not in self.node_labels:
  450. raise ValueError("Load must be removed from a known node")
  451. else:
  452. if [magnitude, direction] not in self._loads[location]:
  453. raise ValueError("No load of this magnitude and direction has been applied at this node")
  454. else:
  455. self._loads[location].remove([magnitude, direction])
  456. if self._loads[location] == []:
  457. self._loads.pop(location)
  458. def apply_support(self, location, type):
  459. """
  460. This method adds a pinned or roller support at a particular node
  461. Parameters
  462. ==========
  463. location: String or Symbol
  464. Label of the Node at which support is added.
  465. type: String
  466. Type of the support being provided at the node.
  467. Examples
  468. ========
  469. >>> from sympy.physics.continuum_mechanics.truss import Truss
  470. >>> t = Truss()
  471. >>> t.add_node('A', 0, 0)
  472. >>> t.add_node('B', 3, 0)
  473. >>> t.apply_support('A', 'pinned')
  474. >>> t.supports
  475. {'A': 'pinned'}
  476. """
  477. if location not in self._node_labels:
  478. raise ValueError("Support must be added on a known node")
  479. else:
  480. if location not in list(self._supports):
  481. if type == 'pinned':
  482. self.apply_load(location, Symbol('R_'+str(location)+'_x'), 0)
  483. self.apply_load(location, Symbol('R_'+str(location)+'_y'), 90)
  484. elif type == 'roller':
  485. self.apply_load(location, Symbol('R_'+str(location)+'_y'), 90)
  486. elif self._supports[location] == 'pinned':
  487. if type == 'roller':
  488. self.remove_load(location, Symbol('R_'+str(location)+'_x'), 0)
  489. elif self._supports[location] == 'roller':
  490. if type == 'pinned':
  491. self.apply_load(location, Symbol('R_'+str(location)+'_x'), 0)
  492. self._supports[location] = type
  493. def remove_support(self, location):
  494. """
  495. This method removes support from a particular node
  496. Parameters
  497. ==========
  498. location: String or Symbol
  499. Label of the Node at which support is to be removed.
  500. Examples
  501. ========
  502. >>> from sympy.physics.continuum_mechanics.truss import Truss
  503. >>> t = Truss()
  504. >>> t.add_node('A', 0, 0)
  505. >>> t.add_node('B', 3, 0)
  506. >>> t.apply_support('A', 'pinned')
  507. >>> t.supports
  508. {'A': 'pinned'}
  509. >>> t.remove_support('A')
  510. >>> t.supports
  511. {}
  512. """
  513. if location not in self._node_labels:
  514. raise ValueError("No such node exists in the Truss")
  515. elif location not in list(self._supports):
  516. raise ValueError("No support has been added to the given node")
  517. else:
  518. if self._supports[location] == 'pinned':
  519. self.remove_load(location, Symbol('R_'+str(location)+'_x'), 0)
  520. self.remove_load(location, Symbol('R_'+str(location)+'_y'), 90)
  521. elif self._supports[location] == 'roller':
  522. self.remove_load(location, Symbol('R_'+str(location)+'_y'), 90)
  523. self._supports.pop(location)
  524. def solve(self):
  525. """
  526. This method solves for all reaction forces of all supports and all internal forces
  527. of all the members in the truss, provided the Truss is solvable.
  528. A Truss is solvable if the following condition is met,
  529. 2n >= r + m
  530. Where n is the number of nodes, r is the number of reaction forces, where each pinned
  531. support has 2 reaction forces and each roller has 1, and m is the number of members.
  532. The given condition is derived from the fact that a system of equations is solvable
  533. only when the number of variables is lesser than or equal to the number of equations.
  534. Equilibrium Equations in x and y directions give two equations per node giving 2n number
  535. equations. However, the truss needs to be stable as well and may be unstable if 2n > r + m.
  536. The number of variables is simply the sum of the number of reaction forces and member
  537. forces.
  538. .. note::
  539. The sign convention for the internal forces present in a member revolves around whether each
  540. force is compressive or tensile. While forming equations for each node, internal force due
  541. to a member on the node is assumed to be away from the node i.e. each force is assumed to
  542. be compressive by default. Hence, a positive value for an internal force implies the
  543. presence of compressive force in the member and a negative value implies a tensile force.
  544. Examples
  545. ========
  546. >>> from sympy.physics.continuum_mechanics.truss import Truss
  547. >>> t = Truss()
  548. >>> t.add_node("node_1", 0, 0)
  549. >>> t.add_node("node_2", 6, 0)
  550. >>> t.add_node("node_3", 2, 2)
  551. >>> t.add_node("node_4", 2, 0)
  552. >>> t.add_member("member_1", "node_1", "node_4")
  553. >>> t.add_member("member_2", "node_2", "node_4")
  554. >>> t.add_member("member_3", "node_1", "node_3")
  555. >>> t.add_member("member_4", "node_2", "node_3")
  556. >>> t.add_member("member_5", "node_3", "node_4")
  557. >>> t.apply_load("node_4", magnitude=10, direction=270)
  558. >>> t.apply_support("node_1", type="pinned")
  559. >>> t.apply_support("node_2", type="roller")
  560. >>> t.solve()
  561. >>> t.reaction_loads
  562. {'R_node_1_x': 0, 'R_node_1_y': 20/3, 'R_node_2_y': 10/3}
  563. >>> t.internal_forces
  564. {'member_1': 20/3, 'member_2': 20/3, 'member_3': -20*sqrt(2)/3, 'member_4': -10*sqrt(5)/3, 'member_5': 10}
  565. """
  566. count_reaction_loads = 0
  567. for node in self._nodes:
  568. if node[0] in list(self._supports):
  569. if self._supports[node[0]]=='pinned':
  570. count_reaction_loads += 2
  571. elif self._supports[node[0]]=='roller':
  572. count_reaction_loads += 1
  573. if 2*len(self._nodes) != len(self._members) + count_reaction_loads:
  574. raise ValueError("The given truss cannot be solved")
  575. coefficients_matrix = [[0 for i in range(2*len(self._nodes))] for j in range(2*len(self._nodes))]
  576. load_matrix = zeros(2*len(self.nodes), 1)
  577. load_matrix_row = 0
  578. for node in self._nodes:
  579. if node[0] in list(self._loads):
  580. for load in self._loads[node[0]]:
  581. if load[0]!=Symbol('R_'+str(node[0])+'_x') and load[0]!=Symbol('R_'+str(node[0])+'_y'):
  582. load_matrix[load_matrix_row] -= load[0]*cos(pi*load[1]/180)
  583. load_matrix[load_matrix_row + 1] -= load[0]*sin(pi*load[1]/180)
  584. load_matrix_row += 2
  585. cols = 0
  586. row = 0
  587. for node in self._nodes:
  588. if node[0] in list(self._supports):
  589. if self._supports[node[0]]=='pinned':
  590. coefficients_matrix[row][cols] += 1
  591. coefficients_matrix[row+1][cols+1] += 1
  592. cols += 2
  593. elif self._supports[node[0]]=='roller':
  594. coefficients_matrix[row+1][cols] += 1
  595. cols += 1
  596. row += 2
  597. for member in list(self._members):
  598. start = self._members[member][0]
  599. end = self._members[member][1]
  600. length = sqrt((self._node_coordinates[start][0]-self._node_coordinates[end][0])**2 + (self._node_coordinates[start][1]-self._node_coordinates[end][1])**2)
  601. start_index = self._node_labels.index(start)
  602. end_index = self._node_labels.index(end)
  603. horizontal_component_start = (self._node_coordinates[end][0]-self._node_coordinates[start][0])/length
  604. vertical_component_start = (self._node_coordinates[end][1]-self._node_coordinates[start][1])/length
  605. horizontal_component_end = (self._node_coordinates[start][0]-self._node_coordinates[end][0])/length
  606. vertical_component_end = (self._node_coordinates[start][1]-self._node_coordinates[end][1])/length
  607. coefficients_matrix[start_index*2][cols] += horizontal_component_start
  608. coefficients_matrix[start_index*2+1][cols] += vertical_component_start
  609. coefficients_matrix[end_index*2][cols] += horizontal_component_end
  610. coefficients_matrix[end_index*2+1][cols] += vertical_component_end
  611. cols += 1
  612. forces_matrix = (Matrix(coefficients_matrix)**-1)*load_matrix
  613. self._reaction_loads = {}
  614. i = 0
  615. min_load = inf
  616. for node in self._nodes:
  617. if node[0] in list(self._loads):
  618. for load in self._loads[node[0]]:
  619. if type(load[0]) not in [Symbol, Mul, Add]:
  620. min_load = min(min_load, load[0])
  621. for j in range(len(forces_matrix)):
  622. if type(forces_matrix[j]) not in [Symbol, Mul, Add]:
  623. if abs(forces_matrix[j]/min_load) <1E-10:
  624. forces_matrix[j] = 0
  625. for node in self._nodes:
  626. if node[0] in list(self._supports):
  627. if self._supports[node[0]]=='pinned':
  628. self._reaction_loads['R_'+str(node[0])+'_x'] = forces_matrix[i]
  629. self._reaction_loads['R_'+str(node[0])+'_y'] = forces_matrix[i+1]
  630. i += 2
  631. elif self._supports[node[0]]=='roller':
  632. self._reaction_loads['R_'+str(node[0])+'_y'] = forces_matrix[i]
  633. i += 1
  634. for member in list(self._members):
  635. self._internal_forces[member] = forces_matrix[i]
  636. i += 1
  637. return