arch.py 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. """
  2. This module can be used to solve probelsm related to 2D parabolic arches
  3. """
  4. from sympy.core.sympify import sympify
  5. from sympy.core.symbol import Symbol,symbols
  6. from sympy import diff, sqrt, cos , sin, atan, rad, Min
  7. from sympy.core.relational import Eq
  8. from sympy.solvers.solvers import solve
  9. from sympy.functions import Piecewise
  10. from sympy.plotting import plot
  11. from sympy import limit
  12. from sympy.utilities.decorator import doctest_depends_on
  13. from sympy.external.importtools import import_module
  14. numpy = import_module('numpy', import_kwargs={'fromlist':['arange']})
  15. class Arch:
  16. """
  17. This class is used to solve problems related to a three hinged arch(determinate) structure.\n
  18. An arch is a curved vertical structure spanning an open space underneath it.\n
  19. Arches can be used to reduce the bending moments in long-span structures.\n
  20. Arches are used in structural engineering(over windows, door and even bridges)\n
  21. because they can support a very large mass placed on top of them.
  22. Example
  23. ========
  24. >>> from sympy.physics.continuum_mechanics.arch import Arch
  25. >>> a = Arch((0,0),(10,0),crown_x=5,crown_y=5)
  26. >>> a.get_shape_eqn
  27. 5 - (x - 5)**2/5
  28. >>> from sympy.physics.continuum_mechanics.arch import Arch
  29. >>> a = Arch((0,0),(10,1),crown_x=6)
  30. >>> a.get_shape_eqn
  31. 9/5 - (x - 6)**2/20
  32. """
  33. def __init__(self,left_support,right_support,**kwargs):
  34. self._shape_eqn = None
  35. self._left_support = (sympify(left_support[0]),sympify(left_support[1]))
  36. self._right_support = (sympify(right_support[0]),sympify(right_support[1]))
  37. self._crown_x = None
  38. self._crown_y = None
  39. if 'crown_x' in kwargs:
  40. self._crown_x = sympify(kwargs['crown_x'])
  41. if 'crown_y' in kwargs:
  42. self._crown_y = sympify(kwargs['crown_y'])
  43. self._shape_eqn = self.get_shape_eqn
  44. self._conc_loads = {}
  45. self._distributed_loads = {}
  46. self._loads = {'concentrated': self._conc_loads, 'distributed':self._distributed_loads}
  47. self._loads_applied = {}
  48. self._supports = {'left':'hinge', 'right':'hinge'}
  49. self._member = None
  50. self._member_force = None
  51. self._reaction_force = {Symbol('R_A_x'):0, Symbol('R_A_y'):0, Symbol('R_B_x'):0, Symbol('R_B_y'):0}
  52. self._points_disc_x = set()
  53. self._points_disc_y = set()
  54. self._moment_x = {}
  55. self._moment_y = {}
  56. self._load_x = {}
  57. self._load_y = {}
  58. self._moment_x_func = Piecewise((0,True))
  59. self._moment_y_func = Piecewise((0,True))
  60. self._load_x_func = Piecewise((0,True))
  61. self._load_y_func = Piecewise((0,True))
  62. self._bending_moment = None
  63. self._shear_force = None
  64. self._axial_force = None
  65. # self._crown = (sympify(crown[0]),sympify(crown[1]))
  66. @property
  67. def get_shape_eqn(self):
  68. "returns the equation of the shape of arch developed"
  69. if self._shape_eqn:
  70. return self._shape_eqn
  71. x,y,c = symbols('x y c')
  72. a = Symbol('a',positive=False)
  73. if self._crown_x and self._crown_y:
  74. x0 = self._crown_x
  75. y0 = self._crown_y
  76. parabola_eqn = a*(x-x0)**2 + y0 - y
  77. eq1 = parabola_eqn.subs({x:self._left_support[0], y:self._left_support[1]})
  78. solution = solve((eq1),(a))
  79. parabola_eqn = solution[0]*(x-x0)**2 + y0
  80. if(parabola_eqn.subs({x:self._right_support[0]}) != self._right_support[1]):
  81. raise ValueError("provided coordinates of crown and supports are not consistent with parabolic arch")
  82. elif self._crown_x:
  83. x0 = self._crown_x
  84. parabola_eqn = a*(x-x0)**2 + c - y
  85. eq1 = parabola_eqn.subs({x:self._left_support[0], y:self._left_support[1]})
  86. eq2 = parabola_eqn.subs({x:self._right_support[0], y:self._right_support[1]})
  87. solution = solve((eq1,eq2),(a,c))
  88. if len(solution) <2 or solution[a] == 0:
  89. raise ValueError("parabolic arch cannot be constructed with the provided coordinates, try providing crown_y")
  90. parabola_eqn = solution[a]*(x-x0)**2+ solution[c]
  91. self._crown_y = solution[c]
  92. else:
  93. raise KeyError("please provide crown_x to construct arch")
  94. return parabola_eqn
  95. @property
  96. def get_loads(self):
  97. """
  98. return the position of the applied load and angle (for concentrated loads)
  99. """
  100. return self._loads
  101. @property
  102. def supports(self):
  103. """
  104. Returns the type of support
  105. """
  106. return self._supports
  107. @property
  108. def left_support(self):
  109. """
  110. Returns the position of the left support.
  111. """
  112. return self._left_support
  113. @property
  114. def right_support(self):
  115. """
  116. Returns the position of the right support.
  117. """
  118. return self._right_support
  119. @property
  120. def reaction_force(self):
  121. """
  122. return the reaction forces generated
  123. """
  124. return self._reaction_force
  125. def apply_load(self,order,label,start,mag,end=None,angle=None):
  126. """
  127. This method adds load to the Arch.
  128. Parameters
  129. ==========
  130. order : Integer
  131. Order of the applied load.
  132. - For point/concentrated loads, order = -1
  133. - For distributed load, order = 0
  134. label : String or Symbol
  135. The label of the load
  136. - should not use 'A' or 'B' as it is used for supports.
  137. start : Float
  138. - For concentrated/point loads, start is the x coordinate
  139. - For distributed loads, start is the starting position of distributed load
  140. mag : Sympifyable
  141. Magnitude of the applied load. Must be positive
  142. end : Float
  143. Required for distributed loads
  144. - For concentrated/point load , end is None(may not be given)
  145. - For distributed loads, end is the end position of distributed load
  146. angle: Sympifyable
  147. The angle in degrees, the load vector makes with the horizontal
  148. in the counter-clockwise direction.
  149. Examples
  150. ========
  151. For applying distributed load
  152. >>> from sympy.physics.continuum_mechanics.arch import Arch
  153. >>> a = Arch((0,0),(10,0),crown_x=5,crown_y=5)
  154. >>> a.apply_load(0,'C',start=3,end=5,mag=-10)
  155. For applying point/concentrated_loads
  156. >>> from sympy.physics.continuum_mechanics.arch import Arch
  157. >>> a = Arch((0,0),(10,0),crown_x=5,crown_y=5)
  158. >>> a.apply_load(-1,'C',start=2,mag=15,angle=45)
  159. """
  160. y = Symbol('y')
  161. x = Symbol('x')
  162. x0 = Symbol('x0')
  163. # y0 = Symbol('y0')
  164. order= sympify(order)
  165. mag = sympify(mag)
  166. angle = sympify(angle)
  167. if label in self._loads_applied:
  168. raise ValueError("load with the given label already exists")
  169. if label in ['A','B']:
  170. raise ValueError("cannot use the given label, reserved for supports")
  171. if order == 0:
  172. if end is None or end<start:
  173. raise KeyError("provide end greater than start")
  174. self._distributed_loads[label] = {'start':start, 'end':end, 'f_y': mag}
  175. self._points_disc_y.add(start)
  176. if start in self._moment_y:
  177. self._moment_y[start] -= mag*(Min(x,end)-start)*(x0-(start+(Min(x,end)))/2)
  178. self._load_y[start] += mag*(Min(end,x)-start)
  179. else:
  180. self._moment_y[start] = -mag*(Min(x,end)-start)*(x0-(start+(Min(x,end)))/2)
  181. self._load_y[start] = mag*(Min(end,x)-start)
  182. self._loads_applied[label] = 'distributed'
  183. if order == -1:
  184. if angle is None:
  185. raise TypeError("please provide direction of force")
  186. height = self._shape_eqn.subs({'x':start})
  187. self._conc_loads[label] = {'x':start, 'y':height, 'f_x':mag*cos(rad(angle)), 'f_y': mag*sin(rad(angle)), 'mag':mag, 'angle':angle}
  188. self._points_disc_x.add(start)
  189. self._points_disc_y.add(start)
  190. if start in self._moment_x:
  191. self._moment_x[start] += self._conc_loads[label]['f_x']*(y-self._conc_loads[label]['y'])
  192. self._load_x[start] += self._conc_loads[label]['f_x']
  193. else:
  194. self._moment_x[start] = self._conc_loads[label]['f_x']*(y-self._conc_loads[label]['y'])
  195. self._load_x[start] = self._conc_loads[label]['f_x']
  196. if start in self._moment_y:
  197. self._moment_y[start] -= self._conc_loads[label]['f_y']*(x0-start)
  198. self._load_y[start] += self._conc_loads[label]['f_y']
  199. else:
  200. self._moment_y[start] = -self._conc_loads[label]['f_y']*(x0-start)
  201. self._load_y[start] = self._conc_loads[label]['f_y']
  202. self._loads_applied[label] = 'concentrated'
  203. def remove_load(self,label):
  204. """
  205. This methods removes the load applied to the arch
  206. Parameters
  207. ==========
  208. label : String or Symbol
  209. The label of the applied load
  210. Examples
  211. ========
  212. >>> from sympy.physics.continuum_mechanics.arch import Arch
  213. >>> a = Arch((0,0),(10,0),crown_x=5,crown_y=5)
  214. >>> a.apply_load(0,'C',start=3,end=5,mag=-10)
  215. >>> a.remove_load('C')
  216. removed load C: {'start': 3, 'end': 5, 'f_y': -10}
  217. """
  218. y = Symbol('y')
  219. x = Symbol('x')
  220. x0 = Symbol('x0')
  221. if label in self._distributed_loads :
  222. self._loads_applied.pop(label)
  223. start = self._distributed_loads[label]['start']
  224. end = self._distributed_loads[label]['end']
  225. mag = self._distributed_loads[label]['f_y']
  226. self._points_disc_y.remove(start)
  227. self._load_y[start] -= mag*(Min(x,end)-start)
  228. self._moment_y[start] += mag*(Min(x,end)-start)*(x0-(start+(Min(x,end)))/2)
  229. val = self._distributed_loads.pop(label)
  230. print(f"removed load {label}: {val}")
  231. elif label in self._conc_loads :
  232. self._loads_applied.pop(label)
  233. start = self._conc_loads[label]['x']
  234. self._points_disc_x.remove(start)
  235. self._points_disc_y.remove(start)
  236. self._moment_y[start] += self._conc_loads[label]['f_y']*(x0-start)
  237. self._moment_x[start] -= self._conc_loads[label]['f_x']*(y-self._conc_loads[label]['y'])
  238. self._load_x[start] -= self._conc_loads[label]['f_x']
  239. self._load_y[start] -= self._conc_loads[label]['f_y']
  240. val = self._conc_loads.pop(label)
  241. print(f"removed load {label}: {val}")
  242. else :
  243. raise ValueError("label not found")
  244. def change_support_position(self, left_support=None, right_support=None):
  245. """
  246. Change position of supports.
  247. If not provided , defaults to the old value.
  248. Parameters
  249. ==========
  250. left_support: tuple (x, y)
  251. x: float
  252. x-coordinate value of the left_support
  253. y: float
  254. y-coordinate value of the left_support
  255. right_support: tuple (x, y)
  256. x: float
  257. x-coordinate value of the right_support
  258. y: float
  259. y-coordinate value of the right_support
  260. """
  261. if left_support is not None:
  262. self._left_support = (left_support[0],left_support[1])
  263. if right_support is not None:
  264. self._right_support = (right_support[0],right_support[1])
  265. self._shape_eqn = None
  266. self._shape_eqn = self.get_shape_eqn
  267. def change_crown_position(self,crown_x=None,crown_y=None):
  268. """
  269. Change the position of the crown/hinge of the arch
  270. Parameters
  271. ==========
  272. crown_x: Float
  273. The x coordinate of the position of the hinge
  274. - if not provided, defaults to old value
  275. crown_y: Float
  276. The y coordinate of the position of the hinge
  277. - if not provided defaults to None
  278. """
  279. self._crown_x = crown_x
  280. self._crown_y = crown_y
  281. self._shape_eqn = None
  282. self._shape_eqn = self.get_shape_eqn
  283. def change_support_type(self,left_support=None,right_support=None):
  284. """
  285. Add the type for support at each end.
  286. Can use roller or hinge support at each end.
  287. Parameters
  288. ==========
  289. left_support, right_support : string
  290. Type of support at respective end
  291. - For roller support , left_support/right_support = "roller"
  292. - For hinged support, left_support/right_support = "hinge"
  293. - defaults to hinge if value not provided
  294. Examples
  295. ========
  296. For applying roller support at right end
  297. >>> from sympy.physics.continuum_mechanics.arch import Arch
  298. >>> a = Arch((0,0),(10,0),crown_x=5,crown_y=5)
  299. >>> a.change_support_type(right_support="roller")
  300. """
  301. support_types = ['roller','hinge']
  302. if left_support:
  303. if left_support not in support_types:
  304. raise ValueError("supports must only be roller or hinge")
  305. self._supports['left'] = left_support
  306. if right_support:
  307. if right_support not in support_types:
  308. raise ValueError("supports must only be roller or hinge")
  309. self._supports['right'] = right_support
  310. def add_member(self,y):
  311. """
  312. This method adds a member/rod at a particular height y.
  313. A rod is used for stability of the structure in case of a roller support.
  314. """
  315. if y>self._crown_y or y<min(self._left_support[1], self._right_support[1]):
  316. raise ValueError(f"position of support must be between y={min(self._left_support[1], self._right_support[1])} and y={self._crown_y}")
  317. x = Symbol('x')
  318. a = diff(self._shape_eqn,x).subs(x,self._crown_x+1)/2
  319. x_diff = sqrt((y - self._crown_y)/a)
  320. x1 = self._crown_x + x_diff
  321. x2 = self._crown_x - x_diff
  322. self._member = (x1,x2,y)
  323. def shear_force_at(self, pos = None, **kwargs):
  324. """
  325. return the shear at some x-coordinates
  326. if no x value provided, returns the formula
  327. """
  328. if pos is None:
  329. return self._shear_force
  330. else:
  331. x = Symbol('x')
  332. if 'dir' in kwargs:
  333. dir = kwargs['dir']
  334. return limit(self._shear_force,x,pos,dir=dir)
  335. return self._shear_force.subs(x,pos)
  336. def bending_moment_at(self, pos = None, **kwargs):
  337. """
  338. return the bending moment at some x-coordinates
  339. if no x value provided, returns the formula
  340. """
  341. if pos is None:
  342. return self._bending_moment
  343. else:
  344. x0 = Symbol('x0')
  345. if 'dir' in kwargs:
  346. dir = kwargs['dir']
  347. return limit(self._bending_moment,x0,pos,dir=dir)
  348. return self._bending_moment.subs(x0,pos)
  349. def axial_force_at(self,pos = None, **kwargs):
  350. """
  351. return the axial/normal force generated at some x-coordinate
  352. if no x value provided, returns the formula
  353. """
  354. if pos is None:
  355. return self._axial_force
  356. else:
  357. x = Symbol('x')
  358. if 'dir' in kwargs:
  359. dir = kwargs['dir']
  360. return limit(self._axial_force,x,pos,dir=dir)
  361. return self._axial_force.subs(x,pos)
  362. def solve(self):
  363. """
  364. This method solves for the reaction forces generated at the supports,\n
  365. and bending moment and generated in the arch and tension produced in the member if used.
  366. Examples
  367. ========
  368. >>> from sympy.physics.continuum_mechanics.arch import Arch
  369. >>> a = Arch((0,0),(10,0),crown_x=5,crown_y=5)
  370. >>> a.apply_load(0,'C',start=3,end=5,mag=-10)
  371. >>> a.solve()
  372. >>> a.reaction_force
  373. {R_A_x: 8, R_A_y: 12, R_B_x: -8, R_B_y: 8}
  374. >>> from sympy import Symbol
  375. >>> t = Symbol('t')
  376. >>> from sympy.physics.continuum_mechanics.arch import Arch
  377. >>> a = Arch((0,0),(16,0),crown_x=8,crown_y=5)
  378. >>> a.apply_load(0,'C',start=3,end=5,mag=t)
  379. >>> a.solve()
  380. >>> a.reaction_force
  381. {R_A_x: -4*t/5, R_A_y: -3*t/2, R_B_x: 4*t/5, R_B_y: -t/2}
  382. >>> a.bending_moment_at(4)
  383. -5*t/2
  384. """
  385. y = Symbol('y')
  386. x = Symbol('x')
  387. x0 = Symbol('x0')
  388. discontinuity_points_x = sorted(self._points_disc_x)
  389. discontinuity_points_y = sorted(self._points_disc_y)
  390. self._moment_x_func = Piecewise((0,True))
  391. self._moment_y_func = Piecewise((0,True))
  392. self._load_x_func = Piecewise((0,True))
  393. self._load_y_func = Piecewise((0,True))
  394. accumulated_x_moment = 0
  395. accumulated_y_moment = 0
  396. accumulated_x_load = 0
  397. accumulated_y_load = 0
  398. for point in discontinuity_points_x:
  399. cond = (x >= point)
  400. accumulated_x_load += self._load_x[point]
  401. accumulated_x_moment += self._moment_x[point]
  402. self._load_x_func = Piecewise((accumulated_x_load,cond),(self._load_x_func,True))
  403. self._moment_x_func = Piecewise((accumulated_x_moment,cond),(self._moment_x_func,True))
  404. for point in discontinuity_points_y:
  405. cond = (x >= point)
  406. accumulated_y_moment += self._moment_y[point]
  407. accumulated_y_load += self._load_y[point]
  408. self._load_y_func = Piecewise((accumulated_y_load,cond),(self._load_y_func,True))
  409. self._moment_y_func = Piecewise((accumulated_y_moment,cond),(self._moment_y_func,True))
  410. moment_A = self._moment_y_func.subs(x,self._right_support[0]).subs(x0,self._left_support[0]) +\
  411. self._moment_x_func.subs(x,self._right_support[0]).subs(y,self._left_support[1])
  412. moment_hinge_left = self._moment_y_func.subs(x,self._crown_x).subs(x0,self._crown_x) +\
  413. self._moment_x_func.subs(x,self._crown_x).subs(y,self._crown_y)
  414. moment_hinge_right = self._moment_y_func.subs(x,self._right_support[0]).subs(x0,self._crown_x)- \
  415. self._moment_y_func.subs(x,self._crown_x).subs(x0,self._crown_x) +\
  416. self._moment_x_func.subs(x,self._right_support[0]).subs(y,self._crown_y) -\
  417. self._moment_x_func.subs(x,self._crown_x).subs(y,self._crown_y)
  418. net_x = self._load_x_func.subs(x,self._right_support[0])
  419. net_y = self._load_y_func.subs(x,self._right_support[0])
  420. if (self._supports['left']=='roller' or self._supports['right']=='roller') and not self._member:
  421. print("member must be added if any of the supports is roller")
  422. return
  423. R_A_x, R_A_y, R_B_x, R_B_y, T = symbols('R_A_x R_A_y R_B_x R_B_y T')
  424. if self._supports['left'] == 'roller' and self._supports['right'] == 'roller':
  425. if self._member[2]>=max(self._left_support[1],self._right_support[1]):
  426. if net_x!=0:
  427. raise ValueError("net force in x direction not possible under the specified conditions")
  428. else:
  429. eq1 = Eq(R_A_x ,0)
  430. eq2 = Eq(R_B_x, 0)
  431. eq3 = Eq(R_A_y + R_B_y + net_y,0)
  432. eq4 = Eq(R_B_y*(self._right_support[0]-self._left_support[0])-\
  433. R_B_x*(self._right_support[1]-self._left_support[1])+moment_A,0)
  434. eq5 = Eq(moment_hinge_right + R_B_y*(self._right_support[0]-self._crown_x) +\
  435. T*(self._member[2]-self._crown_y),0)
  436. solution = solve((eq1,eq2,eq3,eq4,eq5),(R_A_x,R_A_y,R_B_x,R_B_y,T))
  437. elif self._member[2]>=self._left_support[1]:
  438. eq1 = Eq(R_A_x ,0)
  439. eq2 = Eq(R_B_x, 0)
  440. eq3 = Eq(R_A_y + R_B_y + net_y,0)
  441. eq4 = Eq(R_B_y*(self._right_support[0]-self._left_support[0])-\
  442. T*(self._member[2]-self._left_support[1])+moment_A,0)
  443. eq5 = Eq(T+net_x,0)
  444. solution = solve((eq1,eq2,eq3,eq4,eq5),(R_A_x,R_A_y,R_B_x,R_B_y,T))
  445. elif self._member[2]>=self._right_support[1]:
  446. eq1 = Eq(R_A_x ,0)
  447. eq2 = Eq(R_B_x, 0)
  448. eq3 = Eq(R_A_y + R_B_y + net_y,0)
  449. eq4 = Eq(R_B_y*(self._right_support[0]-self._left_support[0])+\
  450. T*(self._member[2]-self._left_support[1])+moment_A,0)
  451. eq5 = Eq(T-net_x,0)
  452. solution = solve((eq1,eq2,eq3,eq4,eq5),(R_A_x,R_A_y,R_B_x,R_B_y,T))
  453. elif self._supports['left'] == 'roller':
  454. if self._member[2]>=max(self._left_support[1], self._right_support[1]):
  455. eq1 = Eq(R_A_x ,0)
  456. eq2 = Eq(R_B_x+net_x,0)
  457. eq3 = Eq(R_A_y + R_B_y + net_y,0)
  458. eq4 = Eq(R_B_y*(self._right_support[0]-self._left_support[0])-\
  459. R_B_x*(self._right_support[1]-self._left_support[1])+moment_A,0)
  460. eq5 = Eq(moment_hinge_left + R_A_y*(self._left_support[0]-self._crown_x) -\
  461. T*(self._member[2]-self._crown_y),0)
  462. solution = solve((eq1,eq2,eq3,eq4,eq5),(R_A_x,R_A_y,R_B_x,R_B_y,T))
  463. elif self._member[2]>=self._left_support[1]:
  464. eq1 = Eq(R_A_x ,0)
  465. eq2 = Eq(R_B_x+ T +net_x,0)
  466. eq3 = Eq(R_A_y + R_B_y + net_y,0)
  467. eq4 = Eq(R_B_y*(self._right_support[0]-self._left_support[0])-\
  468. R_B_x*(self._right_support[1]-self._left_support[1])-\
  469. T*(self._member[2]-self._left_support[0])+moment_A,0)
  470. eq5 = Eq(moment_hinge_left + R_A_y*(self._left_support[0]-self._crown_x)-\
  471. T*(self._member[2]-self._crown_y),0)
  472. solution = solve((eq1,eq2,eq3,eq4,eq5),(R_A_x,R_A_y,R_B_x,R_B_y,T))
  473. elif self._member[2]>=self._right_support[0]:
  474. eq1 = Eq(R_A_x,0)
  475. eq2 = Eq(R_B_x- T +net_x,0)
  476. eq3 = Eq(R_A_y + R_B_y + net_y,0)
  477. eq4 = Eq(moment_hinge_left+R_A_y*(self._left_support[0]-self._crown_x),0)
  478. eq5 = Eq(moment_A+R_B_y*(self._right_support[0]-self._left_support[0])-\
  479. R_B_x*(self._right_support[1]-self._left_support[1])+\
  480. T*(self._member[2]-self._left_support[1]),0)
  481. solution = solve((eq1,eq2,eq3,eq4,eq5),(R_A_x,R_A_y,R_B_x,R_B_y,T))
  482. elif self._supports['right'] == 'roller':
  483. if self._member[2]>=max(self._left_support[1], self._right_support[1]):
  484. eq1 = Eq(R_B_x,0)
  485. eq2 = Eq(R_A_x+net_x,0)
  486. eq3 = Eq(R_A_y+R_B_y+net_y,0)
  487. eq4 = Eq(moment_hinge_right+R_B_y*(self._right_support[0]-self._crown_x)+\
  488. T*(self._member[2]-self._crown_y),0)
  489. eq5 = Eq(moment_A+R_B_y*(self._right_support[0]-self._left_support[0]),0)
  490. solution = solve((eq1,eq2,eq3,eq4,eq5),(R_A_x,R_A_y,R_B_x,R_B_y,T))
  491. elif self._member[2]>=self._left_support[1]:
  492. eq1 = Eq(R_B_x,0)
  493. eq2 = Eq(R_A_x+T+net_x,0)
  494. eq3 = Eq(R_A_y+R_B_y+net_y,0)
  495. eq4 = Eq(moment_hinge_right+R_B_y*(self._right_support[0]-self._crown_x),0)
  496. eq5 = Eq(moment_A-T*(self._member[2]-self._left_support[1])+\
  497. R_B_y*(self._right_support[0]-self._left_support[0]),0)
  498. solution = solve((eq1,eq2,eq3,eq4,eq5),(R_A_x,R_A_y,R_B_x,R_B_y,T))
  499. elif self._member[2]>=self._right_support[1]:
  500. eq1 = Eq(R_B_x,0)
  501. eq2 = Eq(R_A_x-T+net_x,0)
  502. eq3 = Eq(R_A_y+R_B_y+net_y,0)
  503. eq4 = Eq(moment_hinge_right+R_B_y*(self._right_support[0]-self._crown_x)+\
  504. T*(self._member[2]-self._crown_y),0)
  505. eq5 = Eq(moment_A+T*(self._member[2]-self._left_support[1])+\
  506. R_B_y*(self._right_support[0]-self._left_support[0]))
  507. solution = solve((eq1,eq2,eq3,eq4,eq5),(R_A_x,R_A_y,R_B_x,R_B_y,T))
  508. else:
  509. eq1 = Eq(R_A_x + R_B_x + net_x,0)
  510. eq2 = Eq(R_A_y + R_B_y + net_y,0)
  511. eq3 = Eq(R_B_y*(self._right_support[0]-self._left_support[0])-\
  512. R_B_x*(self._right_support[1]-self._left_support[1])+moment_A,0)
  513. eq4 = Eq(moment_hinge_right + R_B_y*(self._right_support[0]-self._crown_x) -\
  514. R_B_x*(self._right_support[1]-self._crown_y),0)
  515. solution = solve((eq1,eq2,eq3,eq4),(R_A_x,R_A_y,R_B_x,R_B_y))
  516. for symb in self._reaction_force:
  517. self._reaction_force[symb] = solution[symb]
  518. self._bending_moment = - (self._moment_x_func.subs(x,x0) + self._moment_y_func.subs(x,x0) -\
  519. solution[R_A_y]*(x0-self._left_support[0]) +\
  520. solution[R_A_x]*(self._shape_eqn.subs({x:x0})-self._left_support[1]))
  521. angle = atan(diff(self._shape_eqn,x))
  522. fx = (self._load_x_func+solution[R_A_x])
  523. fy = (self._load_y_func+solution[R_A_y])
  524. axial_force = fx*cos(angle) + fy*sin(angle)
  525. shear_force = -fx*sin(angle) + fy*cos(angle)
  526. self._axial_force = axial_force
  527. self._shear_force = shear_force
  528. @doctest_depends_on(modules=('numpy',))
  529. def draw(self):
  530. """
  531. This method returns a plot object containing the diagram of the specified arch along with the supports
  532. and forces applied to the structure.
  533. Examples
  534. ========
  535. >>> from sympy import Symbol
  536. >>> t = Symbol('t')
  537. >>> from sympy.physics.continuum_mechanics.arch import Arch
  538. >>> a = Arch((0,0),(40,0),crown_x=20,crown_y=12)
  539. >>> a.apply_load(-1,'C',8,150,angle=270)
  540. >>> a.apply_load(0,'D',start=20,end=40,mag=-4)
  541. >>> a.apply_load(-1,'E',10,t,angle=300)
  542. >>> p = a.draw()
  543. >>> p # doctest: +ELLIPSIS
  544. Plot object containing:
  545. [0]: cartesian line: 11.325 - 3*(x - 20)**2/100 for x over (0.0, 40.0)
  546. [1]: cartesian line: 12 - 3*(x - 20)**2/100 for x over (0.0, 40.0)
  547. ...
  548. >>> p.show()
  549. """
  550. x = Symbol('x')
  551. markers = []
  552. annotations = self._draw_loads()
  553. rectangles = []
  554. supports = self._draw_supports()
  555. markers+=supports
  556. xmax = self._right_support[0]
  557. xmin = self._left_support[0]
  558. ymin = min(self._left_support[1],self._right_support[1])
  559. ymax = self._crown_y
  560. lim = max(xmax*1.1-xmin*0.8+1, ymax*1.1-ymin*0.8+1)
  561. rectangles = self._draw_rectangles()
  562. filler = self._draw_filler()
  563. rectangles+=filler
  564. if self._member is not None:
  565. if(self._member[2]>=self._right_support[1]):
  566. markers.append(
  567. {
  568. 'args':[[self._member[1]+0.005*lim],[self._member[2]]],
  569. 'marker':'o',
  570. 'markersize': 4,
  571. 'color': 'white',
  572. 'markerfacecolor':'none'
  573. }
  574. )
  575. if(self._member[2]>=self._left_support[1]):
  576. markers.append(
  577. {
  578. 'args':[[self._member[0]-0.005*lim],[self._member[2]]],
  579. 'marker':'o',
  580. 'markersize': 4,
  581. 'color': 'white',
  582. 'markerfacecolor':'none'
  583. }
  584. )
  585. markers.append({
  586. 'args':[[self._crown_x],[self._crown_y-0.005*lim]],
  587. 'marker':'o',
  588. 'markersize': 5,
  589. 'color':'white',
  590. 'markerfacecolor':'none',
  591. })
  592. if lim==xmax*1.1-xmin*0.8+1:
  593. sing_plot = plot(self._shape_eqn-0.015*lim,
  594. self._shape_eqn,
  595. (x, self._left_support[0], self._right_support[0]),
  596. markers=markers,
  597. show=False,
  598. annotations=annotations,
  599. rectangles = rectangles,
  600. xlim=(xmin-0.05*lim, xmax*1.1),
  601. ylim=(xmin-0.05*lim, xmax*1.1),
  602. axis=False,
  603. line_color='brown')
  604. else:
  605. sing_plot = plot(self._shape_eqn-0.015*lim,
  606. self._shape_eqn,
  607. (x, self._left_support[0], self._right_support[0]),
  608. markers=markers,
  609. show=False,
  610. annotations=annotations,
  611. rectangles = rectangles,
  612. xlim=(ymin-0.05*lim, ymax*1.1),
  613. ylim=(ymin-0.05*lim, ymax*1.1),
  614. axis=False,
  615. line_color='brown')
  616. return sing_plot
  617. def _draw_supports(self):
  618. support_markers = []
  619. xmax = self._right_support[0]
  620. xmin = self._left_support[0]
  621. ymin = min(self._left_support[1],self._right_support[1])
  622. ymax = self._crown_y
  623. if abs(1.1*xmax-0.8*xmin)>abs(1.1*ymax-0.8*ymin):
  624. max_diff = 1.1*xmax-0.8*xmin
  625. else:
  626. max_diff = 1.1*ymax-0.8*ymin
  627. if self._supports['left']=='roller':
  628. support_markers.append(
  629. {
  630. 'args':[
  631. [self._left_support[0]],
  632. [self._left_support[1]-0.02*max_diff]
  633. ],
  634. 'marker':'o',
  635. 'markersize':11,
  636. 'color':'black',
  637. 'markerfacecolor':'none'
  638. }
  639. )
  640. else:
  641. support_markers.append(
  642. {
  643. 'args':[
  644. [self._left_support[0]],
  645. [self._left_support[1]-0.007*max_diff]
  646. ],
  647. 'marker':6,
  648. 'markersize':15,
  649. 'color':'black',
  650. 'markerfacecolor':'none'
  651. }
  652. )
  653. if self._supports['right']=='roller':
  654. support_markers.append(
  655. {
  656. 'args':[
  657. [self._right_support[0]],
  658. [self._right_support[1]-0.02*max_diff]
  659. ],
  660. 'marker':'o',
  661. 'markersize':11,
  662. 'color':'black',
  663. 'markerfacecolor':'none'
  664. }
  665. )
  666. else:
  667. support_markers.append(
  668. {
  669. 'args':[
  670. [self._right_support[0]],
  671. [self._right_support[1]-0.007*max_diff]
  672. ],
  673. 'marker':6,
  674. 'markersize':15,
  675. 'color':'black',
  676. 'markerfacecolor':'none'
  677. }
  678. )
  679. support_markers.append(
  680. {
  681. 'args':[
  682. [self._right_support[0]],
  683. [self._right_support[1]-0.036*max_diff]
  684. ],
  685. 'marker':'_',
  686. 'markersize':15,
  687. 'color':'black',
  688. 'markerfacecolor':'none'
  689. }
  690. )
  691. support_markers.append(
  692. {
  693. 'args':[
  694. [self._left_support[0]],
  695. [self._left_support[1]-0.036*max_diff]
  696. ],
  697. 'marker':'_',
  698. 'markersize':15,
  699. 'color':'black',
  700. 'markerfacecolor':'none'
  701. }
  702. )
  703. return support_markers
  704. def _draw_rectangles(self):
  705. member = []
  706. xmax = self._right_support[0]
  707. xmin = self._left_support[0]
  708. ymin = min(self._left_support[1],self._right_support[1])
  709. ymax = self._crown_y
  710. if abs(1.1*xmax-0.8*xmin)>abs(1.1*ymax-0.8*ymin):
  711. max_diff = 1.1*xmax-0.8*xmin
  712. else:
  713. max_diff = 1.1*ymax-0.8*ymin
  714. if self._member is not None:
  715. if self._member[2]>= max(self._left_support[1],self._right_support[1]):
  716. member.append(
  717. {
  718. 'xy':(self._member[0],self._member[2]-0.005*max_diff),
  719. 'width':self._member[1]-self._member[0],
  720. 'height': 0.01*max_diff,
  721. 'angle': 0,
  722. 'color':'brown',
  723. }
  724. )
  725. elif self._member[2]>=self._left_support[1]:
  726. member.append(
  727. {
  728. 'xy':(self._member[0],self._member[2]-0.005*max_diff),
  729. 'width':self._right_support[0]-self._member[0],
  730. 'height': 0.01*max_diff,
  731. 'angle': 0,
  732. 'color':'brown',
  733. }
  734. )
  735. else:
  736. member.append(
  737. {
  738. 'xy':(self._member[1],self._member[2]-0.005*max_diff),
  739. 'width':abs(self._left_support[0]-self._member[1]),
  740. 'height': 0.01*max_diff,
  741. 'angle': 180,
  742. 'color':'brown',
  743. }
  744. )
  745. if self._distributed_loads:
  746. for loads in self._distributed_loads:
  747. start = self._distributed_loads[loads]['start']
  748. end = self._distributed_loads[loads]['end']
  749. member.append(
  750. {
  751. 'xy':(start,self._crown_y+max_diff*0.15),
  752. 'width': (end-start),
  753. 'height': max_diff*0.01,
  754. 'color': 'orange'
  755. }
  756. )
  757. return member
  758. def _draw_loads(self):
  759. load_annotations = []
  760. xmax = self._right_support[0]
  761. xmin = self._left_support[0]
  762. ymin = min(self._left_support[1],self._right_support[1])
  763. ymax = self._crown_y
  764. if abs(1.1*xmax-0.8*xmin)>abs(1.1*ymax-0.8*ymin):
  765. max_diff = 1.1*xmax-0.8*xmin
  766. else:
  767. max_diff = 1.1*ymax-0.8*ymin
  768. for load in self._conc_loads:
  769. x = self._conc_loads[load]['x']
  770. y = self._conc_loads[load]['y']
  771. angle = self._conc_loads[load]['angle']
  772. mag = self._conc_loads[load]['mag']
  773. load_annotations.append(
  774. {
  775. 'text':'',
  776. 'xy':(
  777. x+cos(rad(angle))*max_diff*0.08,
  778. y+sin(rad(angle))*max_diff*0.08
  779. ),
  780. 'xytext':(x,y),
  781. 'fontsize':10,
  782. 'fontweight': 'bold',
  783. 'arrowprops':{'width':1.5, 'headlength':5, 'headwidth':5, 'facecolor':'blue','edgecolor':'blue'}
  784. }
  785. )
  786. load_annotations.append(
  787. {
  788. 'text':f'{load}: {mag} N',
  789. 'fontsize':10,
  790. 'fontweight': 'bold',
  791. 'xy': (x+cos(rad(angle))*max_diff*0.12,y+sin(rad(angle))*max_diff*0.12)
  792. }
  793. )
  794. for load in self._distributed_loads:
  795. start = self._distributed_loads[load]['start']
  796. end = self._distributed_loads[load]['end']
  797. mag = self._distributed_loads[load]['f_y']
  798. x_points = numpy.arange(start,end,(end-start)/(max_diff*0.25))
  799. x_points = numpy.append(x_points,end)
  800. for point in x_points:
  801. if(mag<0):
  802. load_annotations.append(
  803. {
  804. 'text':'',
  805. 'xy':(point,self._crown_y+max_diff*0.05),
  806. 'xytext': (point,self._crown_y+max_diff*0.15),
  807. 'arrowprops':{'width':1.5, 'headlength':5, 'headwidth':5, 'facecolor':'orange','edgecolor':'orange'}
  808. }
  809. )
  810. else:
  811. load_annotations.append(
  812. {
  813. 'text':'',
  814. 'xy':(point,self._crown_y+max_diff*0.2),
  815. 'xytext': (point,self._crown_y+max_diff*0.15),
  816. 'arrowprops':{'width':1.5, 'headlength':5, 'headwidth':5, 'facecolor':'orange','edgecolor':'orange'}
  817. }
  818. )
  819. if(mag<0):
  820. load_annotations.append(
  821. {
  822. 'text':f'{load}: {abs(mag)} N/m',
  823. 'fontsize':10,
  824. 'fontweight': 'bold',
  825. 'xy':((start+end)/2,self._crown_y+max_diff*0.175)
  826. }
  827. )
  828. else:
  829. load_annotations.append(
  830. {
  831. 'text':f'{load}: {abs(mag)} N/m',
  832. 'fontsize':10,
  833. 'fontweight': 'bold',
  834. 'xy':((start+end)/2,self._crown_y+max_diff*0.125)
  835. }
  836. )
  837. return load_annotations
  838. def _draw_filler(self):
  839. x = Symbol('x')
  840. filler = []
  841. xmax = self._right_support[0]
  842. xmin = self._left_support[0]
  843. ymin = min(self._left_support[1],self._right_support[1])
  844. ymax = self._crown_y
  845. if abs(1.1*xmax-0.8*xmin)>abs(1.1*ymax-0.8*ymin):
  846. max_diff = 1.1*xmax-0.8*xmin
  847. else:
  848. max_diff = 1.1*ymax-0.8*ymin
  849. x_points = numpy.arange(self._left_support[0],self._right_support[0],(self._right_support[0]-self._left_support[0])/(max_diff*max_diff))
  850. for point in x_points:
  851. filler.append(
  852. {
  853. 'xy':(point,self._shape_eqn.subs(x,point)-max_diff*0.015),
  854. 'width': (self._right_support[0]-self._left_support[0])/(max_diff*max_diff),
  855. 'height': max_diff*0.015,
  856. 'color': 'brown'
  857. }
  858. )
  859. return filler