fieldfunctions.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. from sympy.core.function import diff
  2. from sympy.core.singleton import S
  3. from sympy.integrals.integrals import integrate
  4. from sympy.physics.vector import Vector, express
  5. from sympy.physics.vector.frame import _check_frame
  6. from sympy.physics.vector.vector import _check_vector
  7. __all__ = ['curl', 'divergence', 'gradient', 'is_conservative',
  8. 'is_solenoidal', 'scalar_potential',
  9. 'scalar_potential_difference']
  10. def curl(vect, frame):
  11. """
  12. Returns the curl of a vector field computed wrt the coordinate
  13. symbols of the given frame.
  14. Parameters
  15. ==========
  16. vect : Vector
  17. The vector operand
  18. frame : ReferenceFrame
  19. The reference frame to calculate the curl in
  20. Examples
  21. ========
  22. >>> from sympy.physics.vector import ReferenceFrame
  23. >>> from sympy.physics.vector import curl
  24. >>> R = ReferenceFrame('R')
  25. >>> v1 = R[1]*R[2]*R.x + R[0]*R[2]*R.y + R[0]*R[1]*R.z
  26. >>> curl(v1, R)
  27. 0
  28. >>> v2 = R[0]*R[1]*R[2]*R.x
  29. >>> curl(v2, R)
  30. R_x*R_y*R.y - R_x*R_z*R.z
  31. """
  32. _check_vector(vect)
  33. if vect == 0:
  34. return Vector(0)
  35. vect = express(vect, frame, variables=True)
  36. # A mechanical approach to avoid looping overheads
  37. vectx = vect.dot(frame.x)
  38. vecty = vect.dot(frame.y)
  39. vectz = vect.dot(frame.z)
  40. outvec = Vector(0)
  41. outvec += (diff(vectz, frame[1]) - diff(vecty, frame[2])) * frame.x
  42. outvec += (diff(vectx, frame[2]) - diff(vectz, frame[0])) * frame.y
  43. outvec += (diff(vecty, frame[0]) - diff(vectx, frame[1])) * frame.z
  44. return outvec
  45. def divergence(vect, frame):
  46. """
  47. Returns the divergence of a vector field computed wrt the coordinate
  48. symbols of the given frame.
  49. Parameters
  50. ==========
  51. vect : Vector
  52. The vector operand
  53. frame : ReferenceFrame
  54. The reference frame to calculate the divergence in
  55. Examples
  56. ========
  57. >>> from sympy.physics.vector import ReferenceFrame
  58. >>> from sympy.physics.vector import divergence
  59. >>> R = ReferenceFrame('R')
  60. >>> v1 = R[0]*R[1]*R[2] * (R.x+R.y+R.z)
  61. >>> divergence(v1, R)
  62. R_x*R_y + R_x*R_z + R_y*R_z
  63. >>> v2 = 2*R[1]*R[2]*R.y
  64. >>> divergence(v2, R)
  65. 2*R_z
  66. """
  67. _check_vector(vect)
  68. if vect == 0:
  69. return S.Zero
  70. vect = express(vect, frame, variables=True)
  71. vectx = vect.dot(frame.x)
  72. vecty = vect.dot(frame.y)
  73. vectz = vect.dot(frame.z)
  74. out = S.Zero
  75. out += diff(vectx, frame[0])
  76. out += diff(vecty, frame[1])
  77. out += diff(vectz, frame[2])
  78. return out
  79. def gradient(scalar, frame):
  80. """
  81. Returns the vector gradient of a scalar field computed wrt the
  82. coordinate symbols of the given frame.
  83. Parameters
  84. ==========
  85. scalar : sympifiable
  86. The scalar field to take the gradient of
  87. frame : ReferenceFrame
  88. The frame to calculate the gradient in
  89. Examples
  90. ========
  91. >>> from sympy.physics.vector import ReferenceFrame
  92. >>> from sympy.physics.vector import gradient
  93. >>> R = ReferenceFrame('R')
  94. >>> s1 = R[0]*R[1]*R[2]
  95. >>> gradient(s1, R)
  96. R_y*R_z*R.x + R_x*R_z*R.y + R_x*R_y*R.z
  97. >>> s2 = 5*R[0]**2*R[2]
  98. >>> gradient(s2, R)
  99. 10*R_x*R_z*R.x + 5*R_x**2*R.z
  100. """
  101. _check_frame(frame)
  102. outvec = Vector(0)
  103. scalar = express(scalar, frame, variables=True)
  104. for i, x in enumerate(frame):
  105. outvec += diff(scalar, frame[i]) * x
  106. return outvec
  107. def is_conservative(field):
  108. """
  109. Checks if a field is conservative.
  110. Parameters
  111. ==========
  112. field : Vector
  113. The field to check for conservative property
  114. Examples
  115. ========
  116. >>> from sympy.physics.vector import ReferenceFrame
  117. >>> from sympy.physics.vector import is_conservative
  118. >>> R = ReferenceFrame('R')
  119. >>> is_conservative(R[1]*R[2]*R.x + R[0]*R[2]*R.y + R[0]*R[1]*R.z)
  120. True
  121. >>> is_conservative(R[2] * R.y)
  122. False
  123. """
  124. # Field is conservative irrespective of frame
  125. # Take the first frame in the result of the separate method of Vector
  126. if field == Vector(0):
  127. return True
  128. frame = list(field.separate())[0]
  129. return curl(field, frame).simplify() == Vector(0)
  130. def is_solenoidal(field):
  131. """
  132. Checks if a field is solenoidal.
  133. Parameters
  134. ==========
  135. field : Vector
  136. The field to check for solenoidal property
  137. Examples
  138. ========
  139. >>> from sympy.physics.vector import ReferenceFrame
  140. >>> from sympy.physics.vector import is_solenoidal
  141. >>> R = ReferenceFrame('R')
  142. >>> is_solenoidal(R[1]*R[2]*R.x + R[0]*R[2]*R.y + R[0]*R[1]*R.z)
  143. True
  144. >>> is_solenoidal(R[1] * R.y)
  145. False
  146. """
  147. # Field is solenoidal irrespective of frame
  148. # Take the first frame in the result of the separate method in Vector
  149. if field == Vector(0):
  150. return True
  151. frame = list(field.separate())[0]
  152. return divergence(field, frame).simplify() is S.Zero
  153. def scalar_potential(field, frame):
  154. """
  155. Returns the scalar potential function of a field in a given frame
  156. (without the added integration constant).
  157. Parameters
  158. ==========
  159. field : Vector
  160. The vector field whose scalar potential function is to be
  161. calculated
  162. frame : ReferenceFrame
  163. The frame to do the calculation in
  164. Examples
  165. ========
  166. >>> from sympy.physics.vector import ReferenceFrame
  167. >>> from sympy.physics.vector import scalar_potential, gradient
  168. >>> R = ReferenceFrame('R')
  169. >>> scalar_potential(R.z, R) == R[2]
  170. True
  171. >>> scalar_field = 2*R[0]**2*R[1]*R[2]
  172. >>> grad_field = gradient(scalar_field, R)
  173. >>> scalar_potential(grad_field, R)
  174. 2*R_x**2*R_y*R_z
  175. """
  176. # Check whether field is conservative
  177. if not is_conservative(field):
  178. raise ValueError("Field is not conservative")
  179. if field == Vector(0):
  180. return S.Zero
  181. # Express the field exntirely in frame
  182. # Substitute coordinate variables also
  183. _check_frame(frame)
  184. field = express(field, frame, variables=True)
  185. # Make a list of dimensions of the frame
  186. dimensions = list(frame)
  187. # Calculate scalar potential function
  188. temp_function = integrate(field.dot(dimensions[0]), frame[0])
  189. for i, dim in enumerate(dimensions[1:]):
  190. partial_diff = diff(temp_function, frame[i + 1])
  191. partial_diff = field.dot(dim) - partial_diff
  192. temp_function += integrate(partial_diff, frame[i + 1])
  193. return temp_function
  194. def scalar_potential_difference(field, frame, point1, point2, origin):
  195. """
  196. Returns the scalar potential difference between two points in a
  197. certain frame, wrt a given field.
  198. If a scalar field is provided, its values at the two points are
  199. considered. If a conservative vector field is provided, the values
  200. of its scalar potential function at the two points are used.
  201. Returns (potential at position 2) - (potential at position 1)
  202. Parameters
  203. ==========
  204. field : Vector/sympyfiable
  205. The field to calculate wrt
  206. frame : ReferenceFrame
  207. The frame to do the calculations in
  208. point1 : Point
  209. The initial Point in given frame
  210. position2 : Point
  211. The second Point in the given frame
  212. origin : Point
  213. The Point to use as reference point for position vector
  214. calculation
  215. Examples
  216. ========
  217. >>> from sympy.physics.vector import ReferenceFrame, Point
  218. >>> from sympy.physics.vector import scalar_potential_difference
  219. >>> R = ReferenceFrame('R')
  220. >>> O = Point('O')
  221. >>> P = O.locatenew('P', R[0]*R.x + R[1]*R.y + R[2]*R.z)
  222. >>> vectfield = 4*R[0]*R[1]*R.x + 2*R[0]**2*R.y
  223. >>> scalar_potential_difference(vectfield, R, O, P, O)
  224. 2*R_x**2*R_y
  225. >>> Q = O.locatenew('O', 3*R.x + R.y + 2*R.z)
  226. >>> scalar_potential_difference(vectfield, R, P, Q, O)
  227. -2*R_x**2*R_y + 18
  228. """
  229. _check_frame(frame)
  230. if isinstance(field, Vector):
  231. # Get the scalar potential function
  232. scalar_fn = scalar_potential(field, frame)
  233. else:
  234. # Field is a scalar
  235. scalar_fn = field
  236. # Express positions in required frame
  237. position1 = express(point1.pos_from(origin), frame, variables=True)
  238. position2 = express(point2.pos_from(origin), frame, variables=True)
  239. # Get the two positions as substitution dicts for coordinate variables
  240. subs_dict1 = {}
  241. subs_dict2 = {}
  242. for i, x in enumerate(frame):
  243. subs_dict1[frame[i]] = x.dot(position1)
  244. subs_dict2[frame[i]] = x.dot(position2)
  245. return scalar_fn.subs(subs_dict2) - scalar_fn.subs(subs_dict1)