functions.py 15 KB

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