functions.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. from sympy.vector.coordsysrect import CoordSys3D
  2. from sympy.vector.deloperator import Del
  3. from sympy.vector.scalar import BaseScalar
  4. from sympy.vector.vector import Vector, BaseVector
  5. from sympy.vector.operators import gradient, curl, divergence
  6. from sympy.core.function import diff
  7. from sympy.core.singleton import S
  8. from sympy.integrals.integrals import integrate
  9. from sympy.core import sympify
  10. from sympy.vector.dyadic import Dyadic
  11. def express(expr, system, system2=None, variables=False):
  12. """
  13. Global function for 'express' functionality.
  14. Re-expresses a Vector, Dyadic or scalar(sympyfiable) in the given
  15. coordinate system.
  16. If 'variables' is True, then the coordinate variables (base scalars)
  17. of other coordinate systems present in the vector/scalar field or
  18. dyadic are also substituted in terms of the base scalars of the
  19. given system.
  20. Parameters
  21. ==========
  22. expr : Vector/Dyadic/scalar(sympyfiable)
  23. The expression to re-express in CoordSys3D 'system'
  24. system: CoordSys3D
  25. The coordinate system the expr is to be expressed in
  26. system2: CoordSys3D
  27. The other coordinate system required for re-expression
  28. (only for a Dyadic Expr)
  29. variables : boolean
  30. Specifies whether to substitute the coordinate variables present
  31. in expr, in terms of those of parameter system
  32. Examples
  33. ========
  34. >>> from sympy.vector import CoordSys3D
  35. >>> from sympy import Symbol, cos, sin
  36. >>> N = CoordSys3D('N')
  37. >>> q = Symbol('q')
  38. >>> B = N.orient_new_axis('B', q, N.k)
  39. >>> from sympy.vector import express
  40. >>> express(B.i, N)
  41. (cos(q))*N.i + (sin(q))*N.j
  42. >>> express(N.x, B, variables=True)
  43. B.x*cos(q) - B.y*sin(q)
  44. >>> d = N.i.outer(N.i)
  45. >>> express(d, B, N) == (cos(q))*(B.i|N.i) + (-sin(q))*(B.j|N.i)
  46. True
  47. """
  48. if expr in (0, Vector.zero):
  49. return expr
  50. if not isinstance(system, CoordSys3D):
  51. raise TypeError("system should be a CoordSys3D \
  52. instance")
  53. if isinstance(expr, Vector):
  54. if system2 is not None:
  55. raise ValueError("system2 should not be provided for \
  56. Vectors")
  57. # Given expr is a Vector
  58. if variables:
  59. # If variables attribute is True, substitute
  60. # the coordinate variables in the Vector
  61. system_list = {x.system for x in expr.atoms(BaseScalar, BaseVector)} - {system}
  62. subs_dict = {}
  63. for f in system_list:
  64. subs_dict.update(f.scalar_map(system))
  65. expr = expr.subs(subs_dict)
  66. # Re-express in this coordinate system
  67. outvec = Vector.zero
  68. parts = expr.separate()
  69. for x in parts:
  70. if x != system:
  71. temp = system.rotation_matrix(x) * parts[x].to_matrix(x)
  72. outvec += matrix_to_vector(temp, system)
  73. else:
  74. outvec += parts[x]
  75. return outvec
  76. elif isinstance(expr, Dyadic):
  77. if system2 is None:
  78. system2 = system
  79. if not isinstance(system2, CoordSys3D):
  80. raise TypeError("system2 should be a CoordSys3D \
  81. instance")
  82. outdyad = Dyadic.zero
  83. var = variables
  84. for k, v in expr.components.items():
  85. outdyad += (express(v, system, variables=var) *
  86. (express(k.args[0], system, variables=var) |
  87. express(k.args[1], system2, variables=var)))
  88. return outdyad
  89. else:
  90. if system2 is not None:
  91. raise ValueError("system2 should not be provided for \
  92. Vectors")
  93. if variables:
  94. # Given expr is a scalar field
  95. system_set = set()
  96. expr = sympify(expr)
  97. # Substitute all the coordinate variables
  98. for x in expr.atoms(BaseScalar):
  99. if x.system != system:
  100. system_set.add(x.system)
  101. subs_dict = {}
  102. for f in system_set:
  103. subs_dict.update(f.scalar_map(system))
  104. return expr.subs(subs_dict)
  105. return expr
  106. def directional_derivative(field, direction_vector):
  107. """
  108. Returns the directional derivative of a scalar or vector field computed
  109. along a given vector in coordinate system which parameters are expressed.
  110. Parameters
  111. ==========
  112. field : Vector or Scalar
  113. The scalar or vector field to compute the directional derivative of
  114. direction_vector : Vector
  115. The vector to calculated directional derivative along them.
  116. Examples
  117. ========
  118. >>> from sympy.vector import CoordSys3D, directional_derivative
  119. >>> R = CoordSys3D('R')
  120. >>> f1 = R.x*R.y*R.z
  121. >>> v1 = 3*R.i + 4*R.j + R.k
  122. >>> directional_derivative(f1, v1)
  123. R.x*R.y + 4*R.x*R.z + 3*R.y*R.z
  124. >>> f2 = 5*R.x**2*R.z
  125. >>> directional_derivative(f2, v1)
  126. 5*R.x**2 + 30*R.x*R.z
  127. """
  128. from sympy.vector.operators import _get_coord_systems
  129. coord_sys = _get_coord_systems(field)
  130. if len(coord_sys) > 0:
  131. # TODO: This gets a random coordinate system in case of multiple ones:
  132. coord_sys = next(iter(coord_sys))
  133. field = express(field, coord_sys, variables=True)
  134. i, j, k = coord_sys.base_vectors()
  135. x, y, z = coord_sys.base_scalars()
  136. out = Vector.dot(direction_vector, i) * diff(field, x)
  137. out += Vector.dot(direction_vector, j) * diff(field, y)
  138. out += Vector.dot(direction_vector, k) * diff(field, z)
  139. if out == 0 and isinstance(field, Vector):
  140. out = Vector.zero
  141. return out
  142. elif isinstance(field, Vector):
  143. return Vector.zero
  144. else:
  145. return S.Zero
  146. def laplacian(expr):
  147. """
  148. Return the laplacian of the given field computed in terms of
  149. the base scalars of the given coordinate system.
  150. Parameters
  151. ==========
  152. expr : SymPy Expr or Vector
  153. expr denotes a scalar or vector field.
  154. Examples
  155. ========
  156. >>> from sympy.vector import CoordSys3D, laplacian
  157. >>> R = CoordSys3D('R')
  158. >>> f = R.x**2*R.y**5*R.z
  159. >>> laplacian(f)
  160. 20*R.x**2*R.y**3*R.z + 2*R.y**5*R.z
  161. >>> f = R.x**2*R.i + R.y**3*R.j + R.z**4*R.k
  162. >>> laplacian(f)
  163. 2*R.i + 6*R.y*R.j + 12*R.z**2*R.k
  164. """
  165. delop = Del()
  166. if expr.is_Vector:
  167. return (gradient(divergence(expr)) - curl(curl(expr))).doit()
  168. return delop.dot(delop(expr)).doit()
  169. def is_conservative(field):
  170. """
  171. Checks if a field is conservative.
  172. Parameters
  173. ==========
  174. field : Vector
  175. The field to check for conservative property
  176. Examples
  177. ========
  178. >>> from sympy.vector import CoordSys3D
  179. >>> from sympy.vector import is_conservative
  180. >>> R = CoordSys3D('R')
  181. >>> is_conservative(R.y*R.z*R.i + R.x*R.z*R.j + R.x*R.y*R.k)
  182. True
  183. >>> is_conservative(R.z*R.j)
  184. False
  185. """
  186. # Field is conservative irrespective of system
  187. # Take the first coordinate system in the result of the
  188. # separate method of Vector
  189. if not isinstance(field, Vector):
  190. raise TypeError("field should be a Vector")
  191. if field == Vector.zero:
  192. return True
  193. return curl(field).simplify() == Vector.zero
  194. def is_solenoidal(field):
  195. """
  196. Checks if a field is solenoidal.
  197. Parameters
  198. ==========
  199. field : Vector
  200. The field to check for solenoidal property
  201. Examples
  202. ========
  203. >>> from sympy.vector import CoordSys3D
  204. >>> from sympy.vector import is_solenoidal
  205. >>> R = CoordSys3D('R')
  206. >>> is_solenoidal(R.y*R.z*R.i + R.x*R.z*R.j + R.x*R.y*R.k)
  207. True
  208. >>> is_solenoidal(R.y * R.j)
  209. False
  210. """
  211. # Field is solenoidal irrespective of system
  212. # Take the first coordinate system in the result of the
  213. # separate method in Vector
  214. if not isinstance(field, Vector):
  215. raise TypeError("field should be a Vector")
  216. if field == Vector.zero:
  217. return True
  218. return divergence(field).simplify() is S.Zero
  219. def scalar_potential(field, coord_sys):
  220. """
  221. Returns the scalar potential function of a field in a given
  222. coordinate system (without the added integration constant).
  223. Parameters
  224. ==========
  225. field : Vector
  226. The vector field whose scalar potential function is to be
  227. calculated
  228. coord_sys : CoordSys3D
  229. The coordinate system to do the calculation in
  230. Examples
  231. ========
  232. >>> from sympy.vector import CoordSys3D
  233. >>> from sympy.vector import scalar_potential, gradient
  234. >>> R = CoordSys3D('R')
  235. >>> scalar_potential(R.k, R) == R.z
  236. True
  237. >>> scalar_field = 2*R.x**2*R.y*R.z
  238. >>> grad_field = gradient(scalar_field)
  239. >>> scalar_potential(grad_field, R)
  240. 2*R.x**2*R.y*R.z
  241. """
  242. # Check whether field is conservative
  243. if not is_conservative(field):
  244. raise ValueError("Field is not conservative")
  245. if field == Vector.zero:
  246. return S.Zero
  247. # Express the field exntirely in coord_sys
  248. # Substitute coordinate variables also
  249. if not isinstance(coord_sys, CoordSys3D):
  250. raise TypeError("coord_sys must be a CoordSys3D")
  251. field = express(field, coord_sys, variables=True)
  252. dimensions = coord_sys.base_vectors()
  253. scalars = coord_sys.base_scalars()
  254. # Calculate scalar potential function
  255. temp_function = integrate(field.dot(dimensions[0]), scalars[0])
  256. for i, dim in enumerate(dimensions[1:]):
  257. partial_diff = diff(temp_function, scalars[i + 1])
  258. partial_diff = field.dot(dim) - partial_diff
  259. temp_function += integrate(partial_diff, scalars[i + 1])
  260. return temp_function
  261. def scalar_potential_difference(field, coord_sys, point1, point2):
  262. """
  263. Returns the scalar potential difference between two points in a
  264. certain coordinate system, wrt a given field.
  265. If a scalar field is provided, its values at the two points are
  266. considered. If a conservative vector field is provided, the values
  267. of its scalar potential function at the two points are used.
  268. Returns (potential at point2) - (potential at point1)
  269. The position vectors of the two Points are calculated wrt the
  270. origin of the coordinate system provided.
  271. Parameters
  272. ==========
  273. field : Vector/Expr
  274. The field to calculate wrt
  275. coord_sys : CoordSys3D
  276. The coordinate system to do the calculations in
  277. point1 : Point
  278. The initial Point in given coordinate system
  279. position2 : Point
  280. The second Point in the given coordinate system
  281. Examples
  282. ========
  283. >>> from sympy.vector import CoordSys3D
  284. >>> from sympy.vector import scalar_potential_difference
  285. >>> R = CoordSys3D('R')
  286. >>> P = R.origin.locate_new('P', R.x*R.i + R.y*R.j + R.z*R.k)
  287. >>> vectfield = 4*R.x*R.y*R.i + 2*R.x**2*R.j
  288. >>> scalar_potential_difference(vectfield, R, R.origin, P)
  289. 2*R.x**2*R.y
  290. >>> Q = R.origin.locate_new('O', 3*R.i + R.j + 2*R.k)
  291. >>> scalar_potential_difference(vectfield, R, P, Q)
  292. -2*R.x**2*R.y + 18
  293. """
  294. if not isinstance(coord_sys, CoordSys3D):
  295. raise TypeError("coord_sys must be a CoordSys3D")
  296. if isinstance(field, Vector):
  297. # Get the scalar potential function
  298. scalar_fn = scalar_potential(field, coord_sys)
  299. else:
  300. # Field is a scalar
  301. scalar_fn = field
  302. # Express positions in required coordinate system
  303. origin = coord_sys.origin
  304. position1 = express(point1.position_wrt(origin), coord_sys,
  305. variables=True)
  306. position2 = express(point2.position_wrt(origin), coord_sys,
  307. variables=True)
  308. # Get the two positions as substitution dicts for coordinate variables
  309. subs_dict1 = {}
  310. subs_dict2 = {}
  311. scalars = coord_sys.base_scalars()
  312. for i, x in enumerate(coord_sys.base_vectors()):
  313. subs_dict1[scalars[i]] = x.dot(position1)
  314. subs_dict2[scalars[i]] = x.dot(position2)
  315. return scalar_fn.subs(subs_dict2) - scalar_fn.subs(subs_dict1)
  316. def matrix_to_vector(matrix, system):
  317. """
  318. Converts a vector in matrix form to a Vector instance.
  319. It is assumed that the elements of the Matrix represent the
  320. measure numbers of the components of the vector along basis
  321. vectors of 'system'.
  322. Parameters
  323. ==========
  324. matrix : SymPy Matrix, Dimensions: (3, 1)
  325. The matrix to be converted to a vector
  326. system : CoordSys3D
  327. The coordinate system the vector is to be defined in
  328. Examples
  329. ========
  330. >>> from sympy import ImmutableMatrix as Matrix
  331. >>> m = Matrix([1, 2, 3])
  332. >>> from sympy.vector import CoordSys3D, matrix_to_vector
  333. >>> C = CoordSys3D('C')
  334. >>> v = matrix_to_vector(m, C)
  335. >>> v
  336. C.i + 2*C.j + 3*C.k
  337. >>> v.to_matrix(C) == m
  338. True
  339. """
  340. outvec = Vector.zero
  341. vects = system.base_vectors()
  342. for i, x in enumerate(matrix):
  343. outvec += x * vects[i]
  344. return outvec
  345. def _path(from_object, to_object):
  346. """
  347. Calculates the 'path' of objects starting from 'from_object'
  348. to 'to_object', along with the index of the first common
  349. ancestor in the tree.
  350. Returns (index, list) tuple.
  351. """
  352. if from_object._root != to_object._root:
  353. raise ValueError("No connecting path found between " +
  354. str(from_object) + " and " + str(to_object))
  355. other_path = []
  356. obj = to_object
  357. while obj._parent is not None:
  358. other_path.append(obj)
  359. obj = obj._parent
  360. other_path.append(obj)
  361. object_set = set(other_path)
  362. from_path = []
  363. obj = from_object
  364. while obj not in object_set:
  365. from_path.append(obj)
  366. obj = obj._parent
  367. index = len(from_path)
  368. from_path.extend(other_path[other_path.index(obj)::-1])
  369. return index, from_path
  370. def orthogonalize(*vlist, orthonormal=False):
  371. """
  372. Takes a sequence of independent vectors and orthogonalizes them
  373. using the Gram - Schmidt process. Returns a list of
  374. orthogonal or orthonormal vectors.
  375. Parameters
  376. ==========
  377. vlist : sequence of independent vectors to be made orthogonal.
  378. orthonormal : Optional parameter
  379. Set to True if the vectors returned should be
  380. orthonormal.
  381. Default: False
  382. Examples
  383. ========
  384. >>> from sympy.vector.coordsysrect import CoordSys3D
  385. >>> from sympy.vector.functions import orthogonalize
  386. >>> C = CoordSys3D('C')
  387. >>> i, j, k = C.base_vectors()
  388. >>> v1 = i + 2*j
  389. >>> v2 = 2*i + 3*j
  390. >>> orthogonalize(v1, v2)
  391. [C.i + 2*C.j, 2/5*C.i + (-1/5)*C.j]
  392. References
  393. ==========
  394. .. [1] https://en.wikipedia.org/wiki/Gram-Schmidt_process
  395. """
  396. if not all(isinstance(vec, Vector) for vec in vlist):
  397. raise TypeError('Each element must be of Type Vector')
  398. ortho_vlist = []
  399. for i, term in enumerate(vlist):
  400. for j in range(i):
  401. term -= ortho_vlist[j].projection(vlist[i])
  402. # TODO : The following line introduces a performance issue
  403. # and needs to be changed once a good solution for issue #10279 is
  404. # found.
  405. if term.equals(Vector.zero):
  406. raise ValueError("Vector set not linearly independent")
  407. ortho_vlist.append(term)
  408. if orthonormal:
  409. ortho_vlist = [vec.normalize() for vec in ortho_vlist]
  410. return ortho_vlist