diffgeom.py 71 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273
  1. from __future__ import annotations
  2. from typing import Any
  3. from functools import reduce
  4. from itertools import permutations
  5. from sympy.combinatorics import Permutation
  6. from sympy.core import (
  7. Basic, Expr, Function, diff,
  8. Pow, Mul, Add, Lambda, S, Tuple, Dict
  9. )
  10. from sympy.core.cache import cacheit
  11. from sympy.core.symbol import Symbol, Dummy
  12. from sympy.core.symbol import Str
  13. from sympy.core.sympify import _sympify
  14. from sympy.functions import factorial
  15. from sympy.matrices import ImmutableDenseMatrix as Matrix
  16. from sympy.solvers import solve
  17. from sympy.utilities.exceptions import (sympy_deprecation_warning,
  18. SymPyDeprecationWarning,
  19. ignore_warnings)
  20. # TODO you are a bit excessive in the use of Dummies
  21. # TODO dummy point, literal field
  22. # TODO too often one needs to call doit or simplify on the output, check the
  23. # tests and find out why
  24. from sympy.tensor.array import ImmutableDenseNDimArray
  25. class Manifold(Basic):
  26. """
  27. A mathematical manifold.
  28. Explanation
  29. ===========
  30. A manifold is a topological space that locally resembles
  31. Euclidean space near each point [1].
  32. This class does not provide any means to study the topological
  33. characteristics of the manifold that it represents, though.
  34. Parameters
  35. ==========
  36. name : str
  37. The name of the manifold.
  38. dim : int
  39. The dimension of the manifold.
  40. Examples
  41. ========
  42. >>> from sympy.diffgeom import Manifold
  43. >>> m = Manifold('M', 2)
  44. >>> m
  45. M
  46. >>> m.dim
  47. 2
  48. References
  49. ==========
  50. .. [1] https://en.wikipedia.org/wiki/Manifold
  51. """
  52. def __new__(cls, name, dim, **kwargs):
  53. if not isinstance(name, Str):
  54. name = Str(name)
  55. dim = _sympify(dim)
  56. obj = super().__new__(cls, name, dim)
  57. obj.patches = _deprecated_list(
  58. """
  59. Manifold.patches is deprecated. The Manifold object is now
  60. immutable. Instead use a separate list to keep track of the
  61. patches.
  62. """, [])
  63. return obj
  64. @property
  65. def name(self):
  66. return self.args[0]
  67. @property
  68. def dim(self):
  69. return self.args[1]
  70. class Patch(Basic):
  71. """
  72. A patch on a manifold.
  73. Explanation
  74. ===========
  75. Coordinate patch, or patch in short, is a simply-connected open set around
  76. a point in the manifold [1]. On a manifold one can have many patches that
  77. do not always include the whole manifold. On these patches coordinate
  78. charts can be defined that permit the parameterization of any point on the
  79. patch in terms of a tuple of real numbers (the coordinates).
  80. This class does not provide any means to study the topological
  81. characteristics of the patch that it represents.
  82. Parameters
  83. ==========
  84. name : str
  85. The name of the patch.
  86. manifold : Manifold
  87. The manifold on which the patch is defined.
  88. Examples
  89. ========
  90. >>> from sympy.diffgeom import Manifold, Patch
  91. >>> m = Manifold('M', 2)
  92. >>> p = Patch('P', m)
  93. >>> p
  94. P
  95. >>> p.dim
  96. 2
  97. References
  98. ==========
  99. .. [1] G. Sussman, J. Wisdom, W. Farr, Functional Differential Geometry
  100. (2013)
  101. """
  102. def __new__(cls, name, manifold, **kwargs):
  103. if not isinstance(name, Str):
  104. name = Str(name)
  105. obj = super().__new__(cls, name, manifold)
  106. obj.manifold.patches.append(obj) # deprecated
  107. obj.coord_systems = _deprecated_list(
  108. """
  109. Patch.coord_systms is deprecated. The Patch class is now
  110. immutable. Instead use a separate list to keep track of coordinate
  111. systems.
  112. """, [])
  113. return obj
  114. @property
  115. def name(self):
  116. return self.args[0]
  117. @property
  118. def manifold(self):
  119. return self.args[1]
  120. @property
  121. def dim(self):
  122. return self.manifold.dim
  123. class CoordSystem(Basic):
  124. """
  125. A coordinate system defined on the patch.
  126. Explanation
  127. ===========
  128. Coordinate system is a system that uses one or more coordinates to uniquely
  129. determine the position of the points or other geometric elements on a
  130. manifold [1].
  131. By passing ``Symbols`` to *symbols* parameter, user can define the name and
  132. assumptions of coordinate symbols of the coordinate system. If not passed,
  133. these symbols are generated automatically and are assumed to be real valued.
  134. By passing *relations* parameter, user can define the transform relations of
  135. coordinate systems. Inverse transformation and indirect transformation can
  136. be found automatically. If this parameter is not passed, coordinate
  137. transformation cannot be done.
  138. Parameters
  139. ==========
  140. name : str
  141. The name of the coordinate system.
  142. patch : Patch
  143. The patch where the coordinate system is defined.
  144. symbols : list of Symbols, optional
  145. Defines the names and assumptions of coordinate symbols.
  146. relations : dict, optional
  147. Key is a tuple of two strings, who are the names of the systems where
  148. the coordinates transform from and transform to.
  149. Value is a tuple of the symbols before transformation and a tuple of
  150. the expressions after transformation.
  151. Examples
  152. ========
  153. We define two-dimensional Cartesian coordinate system and polar coordinate
  154. system.
  155. >>> from sympy import symbols, pi, sqrt, atan2, cos, sin
  156. >>> from sympy.diffgeom import Manifold, Patch, CoordSystem
  157. >>> m = Manifold('M', 2)
  158. >>> p = Patch('P', m)
  159. >>> x, y = symbols('x y', real=True)
  160. >>> r, theta = symbols('r theta', nonnegative=True)
  161. >>> relation_dict = {
  162. ... ('Car2D', 'Pol'): [(x, y), (sqrt(x**2 + y**2), atan2(y, x))],
  163. ... ('Pol', 'Car2D'): [(r, theta), (r*cos(theta), r*sin(theta))]
  164. ... }
  165. >>> Car2D = CoordSystem('Car2D', p, (x, y), relation_dict)
  166. >>> Pol = CoordSystem('Pol', p, (r, theta), relation_dict)
  167. ``symbols`` property returns ``CoordinateSymbol`` instances. These symbols
  168. are not same with the symbols used to construct the coordinate system.
  169. >>> Car2D
  170. Car2D
  171. >>> Car2D.dim
  172. 2
  173. >>> Car2D.symbols
  174. (x, y)
  175. >>> _[0].func
  176. <class 'sympy.diffgeom.diffgeom.CoordinateSymbol'>
  177. ``transformation()`` method returns the transformation function from
  178. one coordinate system to another. ``transform()`` method returns the
  179. transformed coordinates.
  180. >>> Car2D.transformation(Pol)
  181. Lambda((x, y), Matrix([
  182. [sqrt(x**2 + y**2)],
  183. [ atan2(y, x)]]))
  184. >>> Car2D.transform(Pol)
  185. Matrix([
  186. [sqrt(x**2 + y**2)],
  187. [ atan2(y, x)]])
  188. >>> Car2D.transform(Pol, [1, 2])
  189. Matrix([
  190. [sqrt(5)],
  191. [atan(2)]])
  192. ``jacobian()`` method returns the Jacobian matrix of coordinate
  193. transformation between two systems. ``jacobian_determinant()`` method
  194. returns the Jacobian determinant of coordinate transformation between two
  195. systems.
  196. >>> Pol.jacobian(Car2D)
  197. Matrix([
  198. [cos(theta), -r*sin(theta)],
  199. [sin(theta), r*cos(theta)]])
  200. >>> Pol.jacobian(Car2D, [1, pi/2])
  201. Matrix([
  202. [0, -1],
  203. [1, 0]])
  204. >>> Car2D.jacobian_determinant(Pol)
  205. 1/sqrt(x**2 + y**2)
  206. >>> Car2D.jacobian_determinant(Pol, [1,0])
  207. 1
  208. References
  209. ==========
  210. .. [1] https://en.wikipedia.org/wiki/Coordinate_system
  211. """
  212. def __new__(cls, name, patch, symbols=None, relations={}, **kwargs):
  213. if not isinstance(name, Str):
  214. name = Str(name)
  215. # canonicallize the symbols
  216. if symbols is None:
  217. names = kwargs.get('names', None)
  218. if names is None:
  219. symbols = Tuple(
  220. *[Symbol('%s_%s' % (name.name, i), real=True)
  221. for i in range(patch.dim)]
  222. )
  223. else:
  224. sympy_deprecation_warning(
  225. f"""
  226. The 'names' argument to CoordSystem is deprecated. Use 'symbols' instead. That
  227. is, replace
  228. CoordSystem(..., names={names})
  229. with
  230. CoordSystem(..., symbols=[{', '.join(["Symbol(" + repr(n) + ", real=True)" for n in names])}])
  231. """,
  232. deprecated_since_version="1.7",
  233. active_deprecations_target="deprecated-diffgeom-mutable",
  234. )
  235. symbols = Tuple(
  236. *[Symbol(n, real=True) for n in names]
  237. )
  238. else:
  239. syms = []
  240. for s in symbols:
  241. if isinstance(s, Symbol):
  242. syms.append(Symbol(s.name, **s._assumptions.generator))
  243. elif isinstance(s, str):
  244. sympy_deprecation_warning(
  245. f"""
  246. Passing a string as the coordinate symbol name to CoordSystem is deprecated.
  247. Pass a Symbol with the appropriate name and assumptions instead.
  248. That is, replace {s} with Symbol({s!r}, real=True).
  249. """,
  250. deprecated_since_version="1.7",
  251. active_deprecations_target="deprecated-diffgeom-mutable",
  252. )
  253. syms.append(Symbol(s, real=True))
  254. symbols = Tuple(*syms)
  255. # canonicallize the relations
  256. rel_temp = {}
  257. for k,v in relations.items():
  258. s1, s2 = k
  259. if not isinstance(s1, Str):
  260. s1 = Str(s1)
  261. if not isinstance(s2, Str):
  262. s2 = Str(s2)
  263. key = Tuple(s1, s2)
  264. # Old version used Lambda as a value.
  265. if isinstance(v, Lambda):
  266. v = (tuple(v.signature), tuple(v.expr))
  267. else:
  268. v = (tuple(v[0]), tuple(v[1]))
  269. rel_temp[key] = v
  270. relations = Dict(rel_temp)
  271. # construct the object
  272. obj = super().__new__(cls, name, patch, symbols, relations)
  273. # Add deprecated attributes
  274. obj.transforms = _deprecated_dict(
  275. """
  276. CoordSystem.transforms is deprecated. The CoordSystem class is now
  277. immutable. Use the 'relations' keyword argument to the
  278. CoordSystems() constructor to specify relations.
  279. """, {})
  280. obj._names = [str(n) for n in symbols]
  281. obj.patch.coord_systems.append(obj) # deprecated
  282. obj._dummies = [Dummy(str(n)) for n in symbols] # deprecated
  283. obj._dummy = Dummy()
  284. return obj
  285. @property
  286. def name(self):
  287. return self.args[0]
  288. @property
  289. def patch(self):
  290. return self.args[1]
  291. @property
  292. def manifold(self):
  293. return self.patch.manifold
  294. @property
  295. def symbols(self):
  296. return tuple(CoordinateSymbol(self, i, **s._assumptions.generator)
  297. for i,s in enumerate(self.args[2]))
  298. @property
  299. def relations(self):
  300. return self.args[3]
  301. @property
  302. def dim(self):
  303. return self.patch.dim
  304. ##########################################################################
  305. # Finding transformation relation
  306. ##########################################################################
  307. def transformation(self, sys):
  308. """
  309. Return coordinate transformation function from *self* to *sys*.
  310. Parameters
  311. ==========
  312. sys : CoordSystem
  313. Returns
  314. =======
  315. sympy.Lambda
  316. Examples
  317. ========
  318. >>> from sympy.diffgeom.rn import R2_r, R2_p
  319. >>> R2_r.transformation(R2_p)
  320. Lambda((x, y), Matrix([
  321. [sqrt(x**2 + y**2)],
  322. [ atan2(y, x)]]))
  323. """
  324. signature = self.args[2]
  325. key = Tuple(self.name, sys.name)
  326. if self == sys:
  327. expr = Matrix(self.symbols)
  328. elif key in self.relations:
  329. expr = Matrix(self.relations[key][1])
  330. elif key[::-1] in self.relations:
  331. expr = Matrix(self._inverse_transformation(sys, self))
  332. else:
  333. expr = Matrix(self._indirect_transformation(self, sys))
  334. return Lambda(signature, expr)
  335. @staticmethod
  336. def _solve_inverse(sym1, sym2, exprs, sys1_name, sys2_name):
  337. ret = solve(
  338. [t[0] - t[1] for t in zip(sym2, exprs)],
  339. list(sym1), dict=True)
  340. if len(ret) == 0:
  341. temp = "Cannot solve inverse relation from {} to {}."
  342. raise NotImplementedError(temp.format(sys1_name, sys2_name))
  343. elif len(ret) > 1:
  344. temp = "Obtained multiple inverse relation from {} to {}."
  345. raise ValueError(temp.format(sys1_name, sys2_name))
  346. return ret[0]
  347. @classmethod
  348. def _inverse_transformation(cls, sys1, sys2):
  349. # Find the transformation relation from sys2 to sys1
  350. forward = sys1.transform(sys2)
  351. inv_results = cls._solve_inverse(sys1.symbols, sys2.symbols, forward,
  352. sys1.name, sys2.name)
  353. signature = tuple(sys1.symbols)
  354. return [inv_results[s] for s in signature]
  355. @classmethod
  356. @cacheit
  357. def _indirect_transformation(cls, sys1, sys2):
  358. # Find the transformation relation between two indirectly connected
  359. # coordinate systems
  360. rel = sys1.relations
  361. path = cls._dijkstra(sys1, sys2)
  362. transforms = []
  363. for s1, s2 in zip(path, path[1:]):
  364. if (s1, s2) in rel:
  365. transforms.append(rel[(s1, s2)])
  366. else:
  367. sym2, inv_exprs = rel[(s2, s1)]
  368. sym1 = tuple(Dummy() for i in sym2)
  369. ret = cls._solve_inverse(sym2, sym1, inv_exprs, s2, s1)
  370. ret = tuple(ret[s] for s in sym2)
  371. transforms.append((sym1, ret))
  372. syms = sys1.args[2]
  373. exprs = syms
  374. for newsyms, newexprs in transforms:
  375. exprs = tuple(e.subs(zip(newsyms, exprs)) for e in newexprs)
  376. return exprs
  377. @staticmethod
  378. def _dijkstra(sys1, sys2):
  379. # Use Dijkstra algorithm to find the shortest path between two indirectly-connected
  380. # coordinate systems
  381. # return value is the list of the names of the systems.
  382. relations = sys1.relations
  383. graph = {}
  384. for s1, s2 in relations.keys():
  385. if s1 not in graph:
  386. graph[s1] = {s2}
  387. else:
  388. graph[s1].add(s2)
  389. if s2 not in graph:
  390. graph[s2] = {s1}
  391. else:
  392. graph[s2].add(s1)
  393. path_dict = {sys:[0, [], 0] for sys in graph} # minimum distance, path, times of visited
  394. def visit(sys):
  395. path_dict[sys][2] = 1
  396. for newsys in graph[sys]:
  397. distance = path_dict[sys][0] + 1
  398. if path_dict[newsys][0] >= distance or not path_dict[newsys][1]:
  399. path_dict[newsys][0] = distance
  400. path_dict[newsys][1] = list(path_dict[sys][1])
  401. path_dict[newsys][1].append(sys)
  402. visit(sys1.name)
  403. while True:
  404. min_distance = max(path_dict.values(), key=lambda x:x[0])[0]
  405. newsys = None
  406. for sys, lst in path_dict.items():
  407. if 0 < lst[0] <= min_distance and not lst[2]:
  408. min_distance = lst[0]
  409. newsys = sys
  410. if newsys is None:
  411. break
  412. visit(newsys)
  413. result = path_dict[sys2.name][1]
  414. result.append(sys2.name)
  415. if result == [sys2.name]:
  416. raise KeyError("Two coordinate systems are not connected.")
  417. return result
  418. def connect_to(self, to_sys, from_coords, to_exprs, inverse=True, fill_in_gaps=False):
  419. sympy_deprecation_warning(
  420. """
  421. The CoordSystem.connect_to() method is deprecated. Instead,
  422. generate a new instance of CoordSystem with the 'relations'
  423. keyword argument (CoordSystem classes are now immutable).
  424. """,
  425. deprecated_since_version="1.7",
  426. active_deprecations_target="deprecated-diffgeom-mutable",
  427. )
  428. from_coords, to_exprs = dummyfy(from_coords, to_exprs)
  429. self.transforms[to_sys] = Matrix(from_coords), Matrix(to_exprs)
  430. if inverse:
  431. to_sys.transforms[self] = self._inv_transf(from_coords, to_exprs)
  432. if fill_in_gaps:
  433. self._fill_gaps_in_transformations()
  434. @staticmethod
  435. def _inv_transf(from_coords, to_exprs):
  436. # Will be removed when connect_to is removed
  437. inv_from = [i.as_dummy() for i in from_coords]
  438. inv_to = solve(
  439. [t[0] - t[1] for t in zip(inv_from, to_exprs)],
  440. list(from_coords), dict=True)[0]
  441. inv_to = [inv_to[fc] for fc in from_coords]
  442. return Matrix(inv_from), Matrix(inv_to)
  443. @staticmethod
  444. def _fill_gaps_in_transformations():
  445. # Will be removed when connect_to is removed
  446. raise NotImplementedError
  447. ##########################################################################
  448. # Coordinate transformations
  449. ##########################################################################
  450. def transform(self, sys, coordinates=None):
  451. """
  452. Return the result of coordinate transformation from *self* to *sys*.
  453. If coordinates are not given, coordinate symbols of *self* are used.
  454. Parameters
  455. ==========
  456. sys : CoordSystem
  457. coordinates : Any iterable, optional.
  458. Returns
  459. =======
  460. sympy.ImmutableDenseMatrix containing CoordinateSymbol
  461. Examples
  462. ========
  463. >>> from sympy.diffgeom.rn import R2_r, R2_p
  464. >>> R2_r.transform(R2_p)
  465. Matrix([
  466. [sqrt(x**2 + y**2)],
  467. [ atan2(y, x)]])
  468. >>> R2_r.transform(R2_p, [0, 1])
  469. Matrix([
  470. [ 1],
  471. [pi/2]])
  472. """
  473. if coordinates is None:
  474. coordinates = self.symbols
  475. if self != sys:
  476. transf = self.transformation(sys)
  477. coordinates = transf(*coordinates)
  478. else:
  479. coordinates = Matrix(coordinates)
  480. return coordinates
  481. def coord_tuple_transform_to(self, to_sys, coords):
  482. """Transform ``coords`` to coord system ``to_sys``."""
  483. sympy_deprecation_warning(
  484. """
  485. The CoordSystem.coord_tuple_transform_to() method is deprecated.
  486. Use the CoordSystem.transform() method instead.
  487. """,
  488. deprecated_since_version="1.7",
  489. active_deprecations_target="deprecated-diffgeom-mutable",
  490. )
  491. coords = Matrix(coords)
  492. if self != to_sys:
  493. with ignore_warnings(SymPyDeprecationWarning):
  494. transf = self.transforms[to_sys]
  495. coords = transf[1].subs(list(zip(transf[0], coords)))
  496. return coords
  497. def jacobian(self, sys, coordinates=None):
  498. """
  499. Return the jacobian matrix of a transformation on given coordinates.
  500. If coordinates are not given, coordinate symbols of *self* are used.
  501. Parameters
  502. ==========
  503. sys : CoordSystem
  504. coordinates : Any iterable, optional.
  505. Returns
  506. =======
  507. sympy.ImmutableDenseMatrix
  508. Examples
  509. ========
  510. >>> from sympy.diffgeom.rn import R2_r, R2_p
  511. >>> R2_p.jacobian(R2_r)
  512. Matrix([
  513. [cos(theta), -rho*sin(theta)],
  514. [sin(theta), rho*cos(theta)]])
  515. >>> R2_p.jacobian(R2_r, [1, 0])
  516. Matrix([
  517. [1, 0],
  518. [0, 1]])
  519. """
  520. result = self.transform(sys).jacobian(self.symbols)
  521. if coordinates is not None:
  522. result = result.subs(list(zip(self.symbols, coordinates)))
  523. return result
  524. jacobian_matrix = jacobian
  525. def jacobian_determinant(self, sys, coordinates=None):
  526. """
  527. Return the jacobian determinant of a transformation on given
  528. coordinates. If coordinates are not given, coordinate symbols of *self*
  529. are used.
  530. Parameters
  531. ==========
  532. sys : CoordSystem
  533. coordinates : Any iterable, optional.
  534. Returns
  535. =======
  536. sympy.Expr
  537. Examples
  538. ========
  539. >>> from sympy.diffgeom.rn import R2_r, R2_p
  540. >>> R2_r.jacobian_determinant(R2_p)
  541. 1/sqrt(x**2 + y**2)
  542. >>> R2_r.jacobian_determinant(R2_p, [1, 0])
  543. 1
  544. """
  545. return self.jacobian(sys, coordinates).det()
  546. ##########################################################################
  547. # Points
  548. ##########################################################################
  549. def point(self, coords):
  550. """Create a ``Point`` with coordinates given in this coord system."""
  551. return Point(self, coords)
  552. def point_to_coords(self, point):
  553. """Calculate the coordinates of a point in this coord system."""
  554. return point.coords(self)
  555. ##########################################################################
  556. # Base fields.
  557. ##########################################################################
  558. def base_scalar(self, coord_index):
  559. """Return ``BaseScalarField`` that takes a point and returns one of the coordinates."""
  560. return BaseScalarField(self, coord_index)
  561. coord_function = base_scalar
  562. def base_scalars(self):
  563. """Returns a list of all coordinate functions.
  564. For more details see the ``base_scalar`` method of this class."""
  565. return [self.base_scalar(i) for i in range(self.dim)]
  566. coord_functions = base_scalars
  567. def base_vector(self, coord_index):
  568. """Return a basis vector field.
  569. The basis vector field for this coordinate system. It is also an
  570. operator on scalar fields."""
  571. return BaseVectorField(self, coord_index)
  572. def base_vectors(self):
  573. """Returns a list of all base vectors.
  574. For more details see the ``base_vector`` method of this class."""
  575. return [self.base_vector(i) for i in range(self.dim)]
  576. def base_oneform(self, coord_index):
  577. """Return a basis 1-form field.
  578. The basis one-form field for this coordinate system. It is also an
  579. operator on vector fields."""
  580. return Differential(self.coord_function(coord_index))
  581. def base_oneforms(self):
  582. """Returns a list of all base oneforms.
  583. For more details see the ``base_oneform`` method of this class."""
  584. return [self.base_oneform(i) for i in range(self.dim)]
  585. class CoordinateSymbol(Symbol):
  586. """A symbol which denotes an abstract value of i-th coordinate of
  587. the coordinate system with given context.
  588. Explanation
  589. ===========
  590. Each coordinates in coordinate system are represented by unique symbol,
  591. such as x, y, z in Cartesian coordinate system.
  592. You may not construct this class directly. Instead, use `symbols` method
  593. of CoordSystem.
  594. Parameters
  595. ==========
  596. coord_sys : CoordSystem
  597. index : integer
  598. Examples
  599. ========
  600. >>> from sympy import symbols, Lambda, Matrix, sqrt, atan2, cos, sin
  601. >>> from sympy.diffgeom import Manifold, Patch, CoordSystem
  602. >>> m = Manifold('M', 2)
  603. >>> p = Patch('P', m)
  604. >>> x, y = symbols('x y', real=True)
  605. >>> r, theta = symbols('r theta', nonnegative=True)
  606. >>> relation_dict = {
  607. ... ('Car2D', 'Pol'): Lambda((x, y), Matrix([sqrt(x**2 + y**2), atan2(y, x)])),
  608. ... ('Pol', 'Car2D'): Lambda((r, theta), Matrix([r*cos(theta), r*sin(theta)]))
  609. ... }
  610. >>> Car2D = CoordSystem('Car2D', p, [x, y], relation_dict)
  611. >>> Pol = CoordSystem('Pol', p, [r, theta], relation_dict)
  612. >>> x, y = Car2D.symbols
  613. ``CoordinateSymbol`` contains its coordinate symbol and index.
  614. >>> x.name
  615. 'x'
  616. >>> x.coord_sys == Car2D
  617. True
  618. >>> x.index
  619. 0
  620. >>> x.is_real
  621. True
  622. You can transform ``CoordinateSymbol`` into other coordinate system using
  623. ``rewrite()`` method.
  624. >>> x.rewrite(Pol)
  625. r*cos(theta)
  626. >>> sqrt(x**2 + y**2).rewrite(Pol).simplify()
  627. r
  628. """
  629. def __new__(cls, coord_sys, index, **assumptions):
  630. name = coord_sys.args[2][index].name
  631. obj = super().__new__(cls, name, **assumptions)
  632. obj.coord_sys = coord_sys
  633. obj.index = index
  634. return obj
  635. def __getnewargs__(self):
  636. return (self.coord_sys, self.index)
  637. def _hashable_content(self):
  638. return (
  639. self.coord_sys, self.index
  640. ) + tuple(sorted(self.assumptions0.items()))
  641. def _eval_rewrite(self, rule, args, **hints):
  642. if isinstance(rule, CoordSystem):
  643. return rule.transform(self.coord_sys)[self.index]
  644. return super()._eval_rewrite(rule, args, **hints)
  645. class Point(Basic):
  646. """Point defined in a coordinate system.
  647. Explanation
  648. ===========
  649. Mathematically, point is defined in the manifold and does not have any coordinates
  650. by itself. Coordinate system is what imbues the coordinates to the point by coordinate
  651. chart. However, due to the difficulty of realizing such logic, you must supply
  652. a coordinate system and coordinates to define a Point here.
  653. The usage of this object after its definition is independent of the
  654. coordinate system that was used in order to define it, however due to
  655. limitations in the simplification routines you can arrive at complicated
  656. expressions if you use inappropriate coordinate systems.
  657. Parameters
  658. ==========
  659. coord_sys : CoordSystem
  660. coords : list
  661. The coordinates of the point.
  662. Examples
  663. ========
  664. >>> from sympy import pi
  665. >>> from sympy.diffgeom import Point
  666. >>> from sympy.diffgeom.rn import R2, R2_r, R2_p
  667. >>> rho, theta = R2_p.symbols
  668. >>> p = Point(R2_p, [rho, 3*pi/4])
  669. >>> p.manifold == R2
  670. True
  671. >>> p.coords()
  672. Matrix([
  673. [ rho],
  674. [3*pi/4]])
  675. >>> p.coords(R2_r)
  676. Matrix([
  677. [-sqrt(2)*rho/2],
  678. [ sqrt(2)*rho/2]])
  679. """
  680. def __new__(cls, coord_sys, coords, **kwargs):
  681. coords = Matrix(coords)
  682. obj = super().__new__(cls, coord_sys, coords)
  683. obj._coord_sys = coord_sys
  684. obj._coords = coords
  685. return obj
  686. @property
  687. def patch(self):
  688. return self._coord_sys.patch
  689. @property
  690. def manifold(self):
  691. return self._coord_sys.manifold
  692. @property
  693. def dim(self):
  694. return self.manifold.dim
  695. def coords(self, sys=None):
  696. """
  697. Coordinates of the point in given coordinate system. If coordinate system
  698. is not passed, it returns the coordinates in the coordinate system in which
  699. the poin was defined.
  700. """
  701. if sys is None:
  702. return self._coords
  703. else:
  704. return self._coord_sys.transform(sys, self._coords)
  705. @property
  706. def free_symbols(self):
  707. return self._coords.free_symbols
  708. class BaseScalarField(Expr):
  709. """Base scalar field over a manifold for a given coordinate system.
  710. Explanation
  711. ===========
  712. A scalar field takes a point as an argument and returns a scalar.
  713. A base scalar field of a coordinate system takes a point and returns one of
  714. the coordinates of that point in the coordinate system in question.
  715. To define a scalar field you need to choose the coordinate system and the
  716. index of the coordinate.
  717. The use of the scalar field after its definition is independent of the
  718. coordinate system in which it was defined, however due to limitations in
  719. the simplification routines you may arrive at more complicated
  720. expression if you use unappropriate coordinate systems.
  721. You can build complicated scalar fields by just building up SymPy
  722. expressions containing ``BaseScalarField`` instances.
  723. Parameters
  724. ==========
  725. coord_sys : CoordSystem
  726. index : integer
  727. Examples
  728. ========
  729. >>> from sympy import Function, pi
  730. >>> from sympy.diffgeom import BaseScalarField
  731. >>> from sympy.diffgeom.rn import R2_r, R2_p
  732. >>> rho, _ = R2_p.symbols
  733. >>> point = R2_p.point([rho, 0])
  734. >>> fx, fy = R2_r.base_scalars()
  735. >>> ftheta = BaseScalarField(R2_r, 1)
  736. >>> fx(point)
  737. rho
  738. >>> fy(point)
  739. 0
  740. >>> (fx**2+fy**2).rcall(point)
  741. rho**2
  742. >>> g = Function('g')
  743. >>> fg = g(ftheta-pi)
  744. >>> fg.rcall(point)
  745. g(-pi)
  746. """
  747. is_commutative = True
  748. def __new__(cls, coord_sys, index, **kwargs):
  749. index = _sympify(index)
  750. obj = super().__new__(cls, coord_sys, index)
  751. obj._coord_sys = coord_sys
  752. obj._index = index
  753. return obj
  754. @property
  755. def coord_sys(self):
  756. return self.args[0]
  757. @property
  758. def index(self):
  759. return self.args[1]
  760. @property
  761. def patch(self):
  762. return self.coord_sys.patch
  763. @property
  764. def manifold(self):
  765. return self.coord_sys.manifold
  766. @property
  767. def dim(self):
  768. return self.manifold.dim
  769. def __call__(self, *args):
  770. """Evaluating the field at a point or doing nothing.
  771. If the argument is a ``Point`` instance, the field is evaluated at that
  772. point. The field is returned itself if the argument is any other
  773. object. It is so in order to have working recursive calling mechanics
  774. for all fields (check the ``__call__`` method of ``Expr``).
  775. """
  776. point = args[0]
  777. if len(args) != 1 or not isinstance(point, Point):
  778. return self
  779. coords = point.coords(self._coord_sys)
  780. # XXX Calling doit is necessary with all the Subs expressions
  781. # XXX Calling simplify is necessary with all the trig expressions
  782. return simplify(coords[self._index]).doit()
  783. # XXX Workaround for limitations on the content of args
  784. free_symbols: set[Any] = set()
  785. class BaseVectorField(Expr):
  786. r"""Base vector field over a manifold for a given coordinate system.
  787. Explanation
  788. ===========
  789. A vector field is an operator taking a scalar field and returning a
  790. directional derivative (which is also a scalar field).
  791. A base vector field is the same type of operator, however the derivation is
  792. specifically done with respect to a chosen coordinate.
  793. To define a base vector field you need to choose the coordinate system and
  794. the index of the coordinate.
  795. The use of the vector field after its definition is independent of the
  796. coordinate system in which it was defined, however due to limitations in the
  797. simplification routines you may arrive at more complicated expression if you
  798. use unappropriate coordinate systems.
  799. Parameters
  800. ==========
  801. coord_sys : CoordSystem
  802. index : integer
  803. Examples
  804. ========
  805. >>> from sympy import Function
  806. >>> from sympy.diffgeom.rn import R2_p, R2_r
  807. >>> from sympy.diffgeom import BaseVectorField
  808. >>> from sympy import pprint
  809. >>> x, y = R2_r.symbols
  810. >>> rho, theta = R2_p.symbols
  811. >>> fx, fy = R2_r.base_scalars()
  812. >>> point_p = R2_p.point([rho, theta])
  813. >>> point_r = R2_r.point([x, y])
  814. >>> g = Function('g')
  815. >>> s_field = g(fx, fy)
  816. >>> v = BaseVectorField(R2_r, 1)
  817. >>> pprint(v(s_field))
  818. / d \|
  819. |---(g(x, xi))||
  820. \dxi /|xi=y
  821. >>> pprint(v(s_field).rcall(point_r).doit())
  822. d
  823. --(g(x, y))
  824. dy
  825. >>> pprint(v(s_field).rcall(point_p))
  826. / d \|
  827. |---(g(rho*cos(theta), xi))||
  828. \dxi /|xi=rho*sin(theta)
  829. """
  830. is_commutative = False
  831. def __new__(cls, coord_sys, index, **kwargs):
  832. index = _sympify(index)
  833. obj = super().__new__(cls, coord_sys, index)
  834. obj._coord_sys = coord_sys
  835. obj._index = index
  836. return obj
  837. @property
  838. def coord_sys(self):
  839. return self.args[0]
  840. @property
  841. def index(self):
  842. return self.args[1]
  843. @property
  844. def patch(self):
  845. return self.coord_sys.patch
  846. @property
  847. def manifold(self):
  848. return self.coord_sys.manifold
  849. @property
  850. def dim(self):
  851. return self.manifold.dim
  852. def __call__(self, scalar_field):
  853. """Apply on a scalar field.
  854. The action of a vector field on a scalar field is a directional
  855. differentiation.
  856. If the argument is not a scalar field an error is raised.
  857. """
  858. if covariant_order(scalar_field) or contravariant_order(scalar_field):
  859. raise ValueError('Only scalar fields can be supplied as arguments to vector fields.')
  860. if scalar_field is None:
  861. return self
  862. base_scalars = list(scalar_field.atoms(BaseScalarField))
  863. # First step: e_x(x+r**2) -> e_x(x) + 2*r*e_x(r)
  864. d_var = self._coord_sys._dummy
  865. # TODO: you need a real dummy function for the next line
  866. d_funcs = [Function('_#_%s' % i)(d_var) for i,
  867. b in enumerate(base_scalars)]
  868. d_result = scalar_field.subs(list(zip(base_scalars, d_funcs)))
  869. d_result = d_result.diff(d_var)
  870. # Second step: e_x(x) -> 1 and e_x(r) -> cos(atan2(x, y))
  871. coords = self._coord_sys.symbols
  872. d_funcs_deriv = [f.diff(d_var) for f in d_funcs]
  873. d_funcs_deriv_sub = []
  874. for b in base_scalars:
  875. jac = self._coord_sys.jacobian(b._coord_sys, coords)
  876. d_funcs_deriv_sub.append(jac[b._index, self._index])
  877. d_result = d_result.subs(list(zip(d_funcs_deriv, d_funcs_deriv_sub)))
  878. # Remove the dummies
  879. result = d_result.subs(list(zip(d_funcs, base_scalars)))
  880. result = result.subs(list(zip(coords, self._coord_sys.coord_functions())))
  881. return result.doit()
  882. def _find_coords(expr):
  883. # Finds CoordinateSystems existing in expr
  884. fields = expr.atoms(BaseScalarField, BaseVectorField)
  885. result = set()
  886. for f in fields:
  887. result.add(f._coord_sys)
  888. return result
  889. class Commutator(Expr):
  890. r"""Commutator of two vector fields.
  891. Explanation
  892. ===========
  893. The commutator of two vector fields `v_1` and `v_2` is defined as the
  894. vector field `[v_1, v_2]` that evaluated on each scalar field `f` is equal
  895. to `v_1(v_2(f)) - v_2(v_1(f))`.
  896. Examples
  897. ========
  898. >>> from sympy.diffgeom.rn import R2_p, R2_r
  899. >>> from sympy.diffgeom import Commutator
  900. >>> from sympy import simplify
  901. >>> fx, fy = R2_r.base_scalars()
  902. >>> e_x, e_y = R2_r.base_vectors()
  903. >>> e_r = R2_p.base_vector(0)
  904. >>> c_xy = Commutator(e_x, e_y)
  905. >>> c_xr = Commutator(e_x, e_r)
  906. >>> c_xy
  907. 0
  908. Unfortunately, the current code is not able to compute everything:
  909. >>> c_xr
  910. Commutator(e_x, e_rho)
  911. >>> simplify(c_xr(fy**2))
  912. -2*cos(theta)*y**2/(x**2 + y**2)
  913. """
  914. def __new__(cls, v1, v2):
  915. if (covariant_order(v1) or contravariant_order(v1) != 1
  916. or covariant_order(v2) or contravariant_order(v2) != 1):
  917. raise ValueError(
  918. 'Only commutators of vector fields are supported.')
  919. if v1 == v2:
  920. return S.Zero
  921. coord_sys = set().union(*[_find_coords(v) for v in (v1, v2)])
  922. if len(coord_sys) == 1:
  923. # Only one coordinate systems is used, hence it is easy enough to
  924. # actually evaluate the commutator.
  925. if all(isinstance(v, BaseVectorField) for v in (v1, v2)):
  926. return S.Zero
  927. bases_1, bases_2 = [list(v.atoms(BaseVectorField))
  928. for v in (v1, v2)]
  929. coeffs_1 = [v1.expand().coeff(b) for b in bases_1]
  930. coeffs_2 = [v2.expand().coeff(b) for b in bases_2]
  931. res = 0
  932. for c1, b1 in zip(coeffs_1, bases_1):
  933. for c2, b2 in zip(coeffs_2, bases_2):
  934. res += c1*b1(c2)*b2 - c2*b2(c1)*b1
  935. return res
  936. else:
  937. obj = super().__new__(cls, v1, v2)
  938. obj._v1 = v1 # deprecated assignment
  939. obj._v2 = v2 # deprecated assignment
  940. return obj
  941. @property
  942. def v1(self):
  943. return self.args[0]
  944. @property
  945. def v2(self):
  946. return self.args[1]
  947. def __call__(self, scalar_field):
  948. """Apply on a scalar field.
  949. If the argument is not a scalar field an error is raised.
  950. """
  951. return self.v1(self.v2(scalar_field)) - self.v2(self.v1(scalar_field))
  952. class Differential(Expr):
  953. r"""Return the differential (exterior derivative) of a form field.
  954. Explanation
  955. ===========
  956. The differential of a form (i.e. the exterior derivative) has a complicated
  957. definition in the general case.
  958. The differential `df` of the 0-form `f` is defined for any vector field `v`
  959. as `df(v) = v(f)`.
  960. Examples
  961. ========
  962. >>> from sympy import Function
  963. >>> from sympy.diffgeom.rn import R2_r
  964. >>> from sympy.diffgeom import Differential
  965. >>> from sympy import pprint
  966. >>> fx, fy = R2_r.base_scalars()
  967. >>> e_x, e_y = R2_r.base_vectors()
  968. >>> g = Function('g')
  969. >>> s_field = g(fx, fy)
  970. >>> dg = Differential(s_field)
  971. >>> dg
  972. d(g(x, y))
  973. >>> pprint(dg(e_x))
  974. / d \|
  975. |---(g(xi, y))||
  976. \dxi /|xi=x
  977. >>> pprint(dg(e_y))
  978. / d \|
  979. |---(g(x, xi))||
  980. \dxi /|xi=y
  981. Applying the exterior derivative operator twice always results in:
  982. >>> Differential(dg)
  983. 0
  984. """
  985. is_commutative = False
  986. def __new__(cls, form_field):
  987. if contravariant_order(form_field):
  988. raise ValueError(
  989. 'A vector field was supplied as an argument to Differential.')
  990. if isinstance(form_field, Differential):
  991. return S.Zero
  992. else:
  993. obj = super().__new__(cls, form_field)
  994. obj._form_field = form_field # deprecated assignment
  995. return obj
  996. @property
  997. def form_field(self):
  998. return self.args[0]
  999. def __call__(self, *vector_fields):
  1000. """Apply on a list of vector_fields.
  1001. Explanation
  1002. ===========
  1003. If the number of vector fields supplied is not equal to 1 + the order of
  1004. the form field inside the differential the result is undefined.
  1005. For 1-forms (i.e. differentials of scalar fields) the evaluation is
  1006. done as `df(v)=v(f)`. However if `v` is ``None`` instead of a vector
  1007. field, the differential is returned unchanged. This is done in order to
  1008. permit partial contractions for higher forms.
  1009. In the general case the evaluation is done by applying the form field
  1010. inside the differential on a list with one less elements than the number
  1011. of elements in the original list. Lowering the number of vector fields
  1012. is achieved through replacing each pair of fields by their
  1013. commutator.
  1014. If the arguments are not vectors or ``None``s an error is raised.
  1015. """
  1016. if any((contravariant_order(a) != 1 or covariant_order(a)) and a is not None
  1017. for a in vector_fields):
  1018. raise ValueError('The arguments supplied to Differential should be vector fields or Nones.')
  1019. k = len(vector_fields)
  1020. if k == 1:
  1021. if vector_fields[0]:
  1022. return vector_fields[0].rcall(self._form_field)
  1023. return self
  1024. else:
  1025. # For higher form it is more complicated:
  1026. # Invariant formula:
  1027. # https://en.wikipedia.org/wiki/Exterior_derivative#Invariant_formula
  1028. # df(v1, ... vn) = +/- vi(f(v1..no i..vn))
  1029. # +/- f([vi,vj],v1..no i, no j..vn)
  1030. f = self._form_field
  1031. v = vector_fields
  1032. ret = 0
  1033. for i in range(k):
  1034. t = v[i].rcall(f.rcall(*v[:i] + v[i + 1:]))
  1035. ret += (-1)**i*t
  1036. for j in range(i + 1, k):
  1037. c = Commutator(v[i], v[j])
  1038. if c: # TODO this is ugly - the Commutator can be Zero and
  1039. # this causes the next line to fail
  1040. t = f.rcall(*(c,) + v[:i] + v[i + 1:j] + v[j + 1:])
  1041. ret += (-1)**(i + j)*t
  1042. return ret
  1043. class TensorProduct(Expr):
  1044. """Tensor product of forms.
  1045. Explanation
  1046. ===========
  1047. The tensor product permits the creation of multilinear functionals (i.e.
  1048. higher order tensors) out of lower order fields (e.g. 1-forms and vector
  1049. fields). However, the higher tensors thus created lack the interesting
  1050. features provided by the other type of product, the wedge product, namely
  1051. they are not antisymmetric and hence are not form fields.
  1052. Examples
  1053. ========
  1054. >>> from sympy.diffgeom.rn import R2_r
  1055. >>> from sympy.diffgeom import TensorProduct
  1056. >>> fx, fy = R2_r.base_scalars()
  1057. >>> e_x, e_y = R2_r.base_vectors()
  1058. >>> dx, dy = R2_r.base_oneforms()
  1059. >>> TensorProduct(dx, dy)(e_x, e_y)
  1060. 1
  1061. >>> TensorProduct(dx, dy)(e_y, e_x)
  1062. 0
  1063. >>> TensorProduct(dx, fx*dy)(fx*e_x, e_y)
  1064. x**2
  1065. >>> TensorProduct(e_x, e_y)(fx**2, fy**2)
  1066. 4*x*y
  1067. >>> TensorProduct(e_y, dx)(fy)
  1068. dx
  1069. You can nest tensor products.
  1070. >>> tp1 = TensorProduct(dx, dy)
  1071. >>> TensorProduct(tp1, dx)(e_x, e_y, e_x)
  1072. 1
  1073. You can make partial contraction for instance when 'raising an index'.
  1074. Putting ``None`` in the second argument of ``rcall`` means that the
  1075. respective position in the tensor product is left as it is.
  1076. >>> TP = TensorProduct
  1077. >>> metric = TP(dx, dx) + 3*TP(dy, dy)
  1078. >>> metric.rcall(e_y, None)
  1079. 3*dy
  1080. Or automatically pad the args with ``None`` without specifying them.
  1081. >>> metric.rcall(e_y)
  1082. 3*dy
  1083. """
  1084. def __new__(cls, *args):
  1085. scalar = Mul(*[m for m in args if covariant_order(m) + contravariant_order(m) == 0])
  1086. multifields = [m for m in args if covariant_order(m) + contravariant_order(m)]
  1087. if multifields:
  1088. if len(multifields) == 1:
  1089. return scalar*multifields[0]
  1090. return scalar*super().__new__(cls, *multifields)
  1091. else:
  1092. return scalar
  1093. def __call__(self, *fields):
  1094. """Apply on a list of fields.
  1095. If the number of input fields supplied is not equal to the order of
  1096. the tensor product field, the list of arguments is padded with ``None``'s.
  1097. The list of arguments is divided in sublists depending on the order of
  1098. the forms inside the tensor product. The sublists are provided as
  1099. arguments to these forms and the resulting expressions are given to the
  1100. constructor of ``TensorProduct``.
  1101. """
  1102. tot_order = covariant_order(self) + contravariant_order(self)
  1103. tot_args = len(fields)
  1104. if tot_args != tot_order:
  1105. fields = list(fields) + [None]*(tot_order - tot_args)
  1106. orders = [covariant_order(f) + contravariant_order(f) for f in self._args]
  1107. indices = [sum(orders[:i + 1]) for i in range(len(orders) - 1)]
  1108. fields = [fields[i:j] for i, j in zip([0] + indices, indices + [None])]
  1109. multipliers = [t[0].rcall(*t[1]) for t in zip(self._args, fields)]
  1110. return TensorProduct(*multipliers)
  1111. class WedgeProduct(TensorProduct):
  1112. """Wedge product of forms.
  1113. Explanation
  1114. ===========
  1115. In the context of integration only completely antisymmetric forms make
  1116. sense. The wedge product permits the creation of such forms.
  1117. Examples
  1118. ========
  1119. >>> from sympy.diffgeom.rn import R2_r
  1120. >>> from sympy.diffgeom import WedgeProduct
  1121. >>> fx, fy = R2_r.base_scalars()
  1122. >>> e_x, e_y = R2_r.base_vectors()
  1123. >>> dx, dy = R2_r.base_oneforms()
  1124. >>> WedgeProduct(dx, dy)(e_x, e_y)
  1125. 1
  1126. >>> WedgeProduct(dx, dy)(e_y, e_x)
  1127. -1
  1128. >>> WedgeProduct(dx, fx*dy)(fx*e_x, e_y)
  1129. x**2
  1130. >>> WedgeProduct(e_x, e_y)(fy, None)
  1131. -e_x
  1132. You can nest wedge products.
  1133. >>> wp1 = WedgeProduct(dx, dy)
  1134. >>> WedgeProduct(wp1, dx)(e_x, e_y, e_x)
  1135. 0
  1136. """
  1137. # TODO the calculation of signatures is slow
  1138. # TODO you do not need all these permutations (neither the prefactor)
  1139. def __call__(self, *fields):
  1140. """Apply on a list of vector_fields.
  1141. The expression is rewritten internally in terms of tensor products and evaluated."""
  1142. orders = (covariant_order(e) + contravariant_order(e) for e in self.args)
  1143. mul = 1/Mul(*(factorial(o) for o in orders))
  1144. perms = permutations(fields)
  1145. perms_par = (Permutation(
  1146. p).signature() for p in permutations(range(len(fields))))
  1147. tensor_prod = TensorProduct(*self.args)
  1148. return mul*Add(*[tensor_prod(*p[0])*p[1] for p in zip(perms, perms_par)])
  1149. class LieDerivative(Expr):
  1150. """Lie derivative with respect to a vector field.
  1151. Explanation
  1152. ===========
  1153. The transport operator that defines the Lie derivative is the pushforward of
  1154. the field to be derived along the integral curve of the field with respect
  1155. to which one derives.
  1156. Examples
  1157. ========
  1158. >>> from sympy.diffgeom.rn import R2_r, R2_p
  1159. >>> from sympy.diffgeom import (LieDerivative, TensorProduct)
  1160. >>> fx, fy = R2_r.base_scalars()
  1161. >>> e_x, e_y = R2_r.base_vectors()
  1162. >>> e_rho, e_theta = R2_p.base_vectors()
  1163. >>> dx, dy = R2_r.base_oneforms()
  1164. >>> LieDerivative(e_x, fy)
  1165. 0
  1166. >>> LieDerivative(e_x, fx)
  1167. 1
  1168. >>> LieDerivative(e_x, e_x)
  1169. 0
  1170. The Lie derivative of a tensor field by another tensor field is equal to
  1171. their commutator:
  1172. >>> LieDerivative(e_x, e_rho)
  1173. Commutator(e_x, e_rho)
  1174. >>> LieDerivative(e_x + e_y, fx)
  1175. 1
  1176. >>> tp = TensorProduct(dx, dy)
  1177. >>> LieDerivative(e_x, tp)
  1178. LieDerivative(e_x, TensorProduct(dx, dy))
  1179. >>> LieDerivative(e_x, tp)
  1180. LieDerivative(e_x, TensorProduct(dx, dy))
  1181. """
  1182. def __new__(cls, v_field, expr):
  1183. expr_form_ord = covariant_order(expr)
  1184. if contravariant_order(v_field) != 1 or covariant_order(v_field):
  1185. raise ValueError('Lie derivatives are defined only with respect to'
  1186. ' vector fields. The supplied argument was not a '
  1187. 'vector field.')
  1188. if expr_form_ord > 0:
  1189. obj = super().__new__(cls, v_field, expr)
  1190. # deprecated assignments
  1191. obj._v_field = v_field
  1192. obj._expr = expr
  1193. return obj
  1194. if expr.atoms(BaseVectorField):
  1195. return Commutator(v_field, expr)
  1196. else:
  1197. return v_field.rcall(expr)
  1198. @property
  1199. def v_field(self):
  1200. return self.args[0]
  1201. @property
  1202. def expr(self):
  1203. return self.args[1]
  1204. def __call__(self, *args):
  1205. v = self.v_field
  1206. expr = self.expr
  1207. lead_term = v(expr(*args))
  1208. rest = Add(*[Mul(*args[:i] + (Commutator(v, args[i]),) + args[i + 1:])
  1209. for i in range(len(args))])
  1210. return lead_term - rest
  1211. class BaseCovarDerivativeOp(Expr):
  1212. """Covariant derivative operator with respect to a base vector.
  1213. Examples
  1214. ========
  1215. >>> from sympy.diffgeom.rn import R2_r
  1216. >>> from sympy.diffgeom import BaseCovarDerivativeOp
  1217. >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct
  1218. >>> TP = TensorProduct
  1219. >>> fx, fy = R2_r.base_scalars()
  1220. >>> e_x, e_y = R2_r.base_vectors()
  1221. >>> dx, dy = R2_r.base_oneforms()
  1222. >>> ch = metric_to_Christoffel_2nd(TP(dx, dx) + TP(dy, dy))
  1223. >>> ch
  1224. [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]
  1225. >>> cvd = BaseCovarDerivativeOp(R2_r, 0, ch)
  1226. >>> cvd(fx)
  1227. 1
  1228. >>> cvd(fx*e_x)
  1229. e_x
  1230. """
  1231. def __new__(cls, coord_sys, index, christoffel):
  1232. index = _sympify(index)
  1233. christoffel = ImmutableDenseNDimArray(christoffel)
  1234. obj = super().__new__(cls, coord_sys, index, christoffel)
  1235. # deprecated assignments
  1236. obj._coord_sys = coord_sys
  1237. obj._index = index
  1238. obj._christoffel = christoffel
  1239. return obj
  1240. @property
  1241. def coord_sys(self):
  1242. return self.args[0]
  1243. @property
  1244. def index(self):
  1245. return self.args[1]
  1246. @property
  1247. def christoffel(self):
  1248. return self.args[2]
  1249. def __call__(self, field):
  1250. """Apply on a scalar field.
  1251. The action of a vector field on a scalar field is a directional
  1252. differentiation.
  1253. If the argument is not a scalar field the behaviour is undefined.
  1254. """
  1255. if covariant_order(field) != 0:
  1256. raise NotImplementedError()
  1257. field = vectors_in_basis(field, self._coord_sys)
  1258. wrt_vector = self._coord_sys.base_vector(self._index)
  1259. wrt_scalar = self._coord_sys.coord_function(self._index)
  1260. vectors = list(field.atoms(BaseVectorField))
  1261. # First step: replace all vectors with something susceptible to
  1262. # derivation and do the derivation
  1263. # TODO: you need a real dummy function for the next line
  1264. d_funcs = [Function('_#_%s' % i)(wrt_scalar) for i,
  1265. b in enumerate(vectors)]
  1266. d_result = field.subs(list(zip(vectors, d_funcs)))
  1267. d_result = wrt_vector(d_result)
  1268. # Second step: backsubstitute the vectors in
  1269. d_result = d_result.subs(list(zip(d_funcs, vectors)))
  1270. # Third step: evaluate the derivatives of the vectors
  1271. derivs = []
  1272. for v in vectors:
  1273. d = Add(*[(self._christoffel[k, wrt_vector._index, v._index]
  1274. *v._coord_sys.base_vector(k))
  1275. for k in range(v._coord_sys.dim)])
  1276. derivs.append(d)
  1277. to_subs = [wrt_vector(d) for d in d_funcs]
  1278. # XXX: This substitution can fail when there are Dummy symbols and the
  1279. # cache is disabled: https://github.com/sympy/sympy/issues/17794
  1280. result = d_result.subs(list(zip(to_subs, derivs)))
  1281. # Remove the dummies
  1282. result = result.subs(list(zip(d_funcs, vectors)))
  1283. return result.doit()
  1284. class CovarDerivativeOp(Expr):
  1285. """Covariant derivative operator.
  1286. Examples
  1287. ========
  1288. >>> from sympy.diffgeom.rn import R2_r
  1289. >>> from sympy.diffgeom import CovarDerivativeOp
  1290. >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct
  1291. >>> TP = TensorProduct
  1292. >>> fx, fy = R2_r.base_scalars()
  1293. >>> e_x, e_y = R2_r.base_vectors()
  1294. >>> dx, dy = R2_r.base_oneforms()
  1295. >>> ch = metric_to_Christoffel_2nd(TP(dx, dx) + TP(dy, dy))
  1296. >>> ch
  1297. [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]
  1298. >>> cvd = CovarDerivativeOp(fx*e_x, ch)
  1299. >>> cvd(fx)
  1300. x
  1301. >>> cvd(fx*e_x)
  1302. x*e_x
  1303. """
  1304. def __new__(cls, wrt, christoffel):
  1305. if len({v._coord_sys for v in wrt.atoms(BaseVectorField)}) > 1:
  1306. raise NotImplementedError()
  1307. if contravariant_order(wrt) != 1 or covariant_order(wrt):
  1308. raise ValueError('Covariant derivatives are defined only with '
  1309. 'respect to vector fields. The supplied argument '
  1310. 'was not a vector field.')
  1311. christoffel = ImmutableDenseNDimArray(christoffel)
  1312. obj = super().__new__(cls, wrt, christoffel)
  1313. # deprecated assignments
  1314. obj._wrt = wrt
  1315. obj._christoffel = christoffel
  1316. return obj
  1317. @property
  1318. def wrt(self):
  1319. return self.args[0]
  1320. @property
  1321. def christoffel(self):
  1322. return self.args[1]
  1323. def __call__(self, field):
  1324. vectors = list(self._wrt.atoms(BaseVectorField))
  1325. base_ops = [BaseCovarDerivativeOp(v._coord_sys, v._index, self._christoffel)
  1326. for v in vectors]
  1327. return self._wrt.subs(list(zip(vectors, base_ops))).rcall(field)
  1328. ###############################################################################
  1329. # Integral curves on vector fields
  1330. ###############################################################################
  1331. def intcurve_series(vector_field, param, start_point, n=6, coord_sys=None, coeffs=False):
  1332. r"""Return the series expansion for an integral curve of the field.
  1333. Explanation
  1334. ===========
  1335. Integral curve is a function `\gamma` taking a parameter in `R` to a point
  1336. in the manifold. It verifies the equation:
  1337. `V(f)\big(\gamma(t)\big) = \frac{d}{dt}f\big(\gamma(t)\big)`
  1338. where the given ``vector_field`` is denoted as `V`. This holds for any
  1339. value `t` for the parameter and any scalar field `f`.
  1340. This equation can also be decomposed of a basis of coordinate functions
  1341. `V(f_i)\big(\gamma(t)\big) = \frac{d}{dt}f_i\big(\gamma(t)\big) \quad \forall i`
  1342. This function returns a series expansion of `\gamma(t)` in terms of the
  1343. coordinate system ``coord_sys``. The equations and expansions are necessarily
  1344. done in coordinate-system-dependent way as there is no other way to
  1345. represent movement between points on the manifold (i.e. there is no such
  1346. thing as a difference of points for a general manifold).
  1347. Parameters
  1348. ==========
  1349. vector_field
  1350. the vector field for which an integral curve will be given
  1351. param
  1352. the argument of the function `\gamma` from R to the curve
  1353. start_point
  1354. the point which corresponds to `\gamma(0)`
  1355. n
  1356. the order to which to expand
  1357. coord_sys
  1358. the coordinate system in which to expand
  1359. coeffs (default False) - if True return a list of elements of the expansion
  1360. Examples
  1361. ========
  1362. Use the predefined R2 manifold:
  1363. >>> from sympy.abc import t, x, y
  1364. >>> from sympy.diffgeom.rn import R2_p, R2_r
  1365. >>> from sympy.diffgeom import intcurve_series
  1366. Specify a starting point and a vector field:
  1367. >>> start_point = R2_r.point([x, y])
  1368. >>> vector_field = R2_r.e_x
  1369. Calculate the series:
  1370. >>> intcurve_series(vector_field, t, start_point, n=3)
  1371. Matrix([
  1372. [t + x],
  1373. [ y]])
  1374. Or get the elements of the expansion in a list:
  1375. >>> series = intcurve_series(vector_field, t, start_point, n=3, coeffs=True)
  1376. >>> series[0]
  1377. Matrix([
  1378. [x],
  1379. [y]])
  1380. >>> series[1]
  1381. Matrix([
  1382. [t],
  1383. [0]])
  1384. >>> series[2]
  1385. Matrix([
  1386. [0],
  1387. [0]])
  1388. The series in the polar coordinate system:
  1389. >>> series = intcurve_series(vector_field, t, start_point,
  1390. ... n=3, coord_sys=R2_p, coeffs=True)
  1391. >>> series[0]
  1392. Matrix([
  1393. [sqrt(x**2 + y**2)],
  1394. [ atan2(y, x)]])
  1395. >>> series[1]
  1396. Matrix([
  1397. [t*x/sqrt(x**2 + y**2)],
  1398. [ -t*y/(x**2 + y**2)]])
  1399. >>> series[2]
  1400. Matrix([
  1401. [t**2*(-x**2/(x**2 + y**2)**(3/2) + 1/sqrt(x**2 + y**2))/2],
  1402. [ t**2*x*y/(x**2 + y**2)**2]])
  1403. See Also
  1404. ========
  1405. intcurve_diffequ
  1406. """
  1407. if contravariant_order(vector_field) != 1 or covariant_order(vector_field):
  1408. raise ValueError('The supplied field was not a vector field.')
  1409. def iter_vfield(scalar_field, i):
  1410. """Return ``vector_field`` called `i` times on ``scalar_field``."""
  1411. return reduce(lambda s, v: v.rcall(s), [vector_field, ]*i, scalar_field)
  1412. def taylor_terms_per_coord(coord_function):
  1413. """Return the series for one of the coordinates."""
  1414. return [param**i*iter_vfield(coord_function, i).rcall(start_point)/factorial(i)
  1415. for i in range(n)]
  1416. coord_sys = coord_sys if coord_sys else start_point._coord_sys
  1417. coord_functions = coord_sys.coord_functions()
  1418. taylor_terms = [taylor_terms_per_coord(f) for f in coord_functions]
  1419. if coeffs:
  1420. return [Matrix(t) for t in zip(*taylor_terms)]
  1421. else:
  1422. return Matrix([sum(c) for c in taylor_terms])
  1423. def intcurve_diffequ(vector_field, param, start_point, coord_sys=None):
  1424. r"""Return the differential equation for an integral curve of the field.
  1425. Explanation
  1426. ===========
  1427. Integral curve is a function `\gamma` taking a parameter in `R` to a point
  1428. in the manifold. It verifies the equation:
  1429. `V(f)\big(\gamma(t)\big) = \frac{d}{dt}f\big(\gamma(t)\big)`
  1430. where the given ``vector_field`` is denoted as `V`. This holds for any
  1431. value `t` for the parameter and any scalar field `f`.
  1432. This function returns the differential equation of `\gamma(t)` in terms of the
  1433. coordinate system ``coord_sys``. The equations and expansions are necessarily
  1434. done in coordinate-system-dependent way as there is no other way to
  1435. represent movement between points on the manifold (i.e. there is no such
  1436. thing as a difference of points for a general manifold).
  1437. Parameters
  1438. ==========
  1439. vector_field
  1440. the vector field for which an integral curve will be given
  1441. param
  1442. the argument of the function `\gamma` from R to the curve
  1443. start_point
  1444. the point which corresponds to `\gamma(0)`
  1445. coord_sys
  1446. the coordinate system in which to give the equations
  1447. Returns
  1448. =======
  1449. a tuple of (equations, initial conditions)
  1450. Examples
  1451. ========
  1452. Use the predefined R2 manifold:
  1453. >>> from sympy.abc import t
  1454. >>> from sympy.diffgeom.rn import R2, R2_p, R2_r
  1455. >>> from sympy.diffgeom import intcurve_diffequ
  1456. Specify a starting point and a vector field:
  1457. >>> start_point = R2_r.point([0, 1])
  1458. >>> vector_field = -R2.y*R2.e_x + R2.x*R2.e_y
  1459. Get the equation:
  1460. >>> equations, init_cond = intcurve_diffequ(vector_field, t, start_point)
  1461. >>> equations
  1462. [f_1(t) + Derivative(f_0(t), t), -f_0(t) + Derivative(f_1(t), t)]
  1463. >>> init_cond
  1464. [f_0(0), f_1(0) - 1]
  1465. The series in the polar coordinate system:
  1466. >>> equations, init_cond = intcurve_diffequ(vector_field, t, start_point, R2_p)
  1467. >>> equations
  1468. [Derivative(f_0(t), t), Derivative(f_1(t), t) - 1]
  1469. >>> init_cond
  1470. [f_0(0) - 1, f_1(0) - pi/2]
  1471. See Also
  1472. ========
  1473. intcurve_series
  1474. """
  1475. if contravariant_order(vector_field) != 1 or covariant_order(vector_field):
  1476. raise ValueError('The supplied field was not a vector field.')
  1477. coord_sys = coord_sys if coord_sys else start_point._coord_sys
  1478. gammas = [Function('f_%d' % i)(param) for i in range(
  1479. start_point._coord_sys.dim)]
  1480. arbitrary_p = Point(coord_sys, gammas)
  1481. coord_functions = coord_sys.coord_functions()
  1482. equations = [simplify(diff(cf.rcall(arbitrary_p), param) - vector_field.rcall(cf).rcall(arbitrary_p))
  1483. for cf in coord_functions]
  1484. init_cond = [simplify(cf.rcall(arbitrary_p).subs(param, 0) - cf.rcall(start_point))
  1485. for cf in coord_functions]
  1486. return equations, init_cond
  1487. ###############################################################################
  1488. # Helpers
  1489. ###############################################################################
  1490. def dummyfy(args, exprs):
  1491. # TODO Is this a good idea?
  1492. d_args = Matrix([s.as_dummy() for s in args])
  1493. reps = dict(zip(args, d_args))
  1494. d_exprs = Matrix([_sympify(expr).subs(reps) for expr in exprs])
  1495. return d_args, d_exprs
  1496. ###############################################################################
  1497. # Helpers
  1498. ###############################################################################
  1499. def contravariant_order(expr, _strict=False):
  1500. """Return the contravariant order of an expression.
  1501. Examples
  1502. ========
  1503. >>> from sympy.diffgeom import contravariant_order
  1504. >>> from sympy.diffgeom.rn import R2
  1505. >>> from sympy.abc import a
  1506. >>> contravariant_order(a)
  1507. 0
  1508. >>> contravariant_order(a*R2.x + 2)
  1509. 0
  1510. >>> contravariant_order(a*R2.x*R2.e_y + R2.e_x)
  1511. 1
  1512. """
  1513. # TODO move some of this to class methods.
  1514. # TODO rewrite using the .as_blah_blah methods
  1515. if isinstance(expr, Add):
  1516. orders = [contravariant_order(e) for e in expr.args]
  1517. if len(set(orders)) != 1:
  1518. raise ValueError('Misformed expression containing contravariant fields of varying order.')
  1519. return orders[0]
  1520. elif isinstance(expr, Mul):
  1521. orders = [contravariant_order(e) for e in expr.args]
  1522. not_zero = [o for o in orders if o != 0]
  1523. if len(not_zero) > 1:
  1524. raise ValueError('Misformed expression containing multiplication between vectors.')
  1525. return 0 if not not_zero else not_zero[0]
  1526. elif isinstance(expr, Pow):
  1527. if covariant_order(expr.base) or covariant_order(expr.exp):
  1528. raise ValueError(
  1529. 'Misformed expression containing a power of a vector.')
  1530. return 0
  1531. elif isinstance(expr, BaseVectorField):
  1532. return 1
  1533. elif isinstance(expr, TensorProduct):
  1534. return sum(contravariant_order(a) for a in expr.args)
  1535. elif not _strict or expr.atoms(BaseScalarField):
  1536. return 0
  1537. else: # If it does not contain anything related to the diffgeom module and it is _strict
  1538. return -1
  1539. def covariant_order(expr, _strict=False):
  1540. """Return the covariant order of an expression.
  1541. Examples
  1542. ========
  1543. >>> from sympy.diffgeom import covariant_order
  1544. >>> from sympy.diffgeom.rn import R2
  1545. >>> from sympy.abc import a
  1546. >>> covariant_order(a)
  1547. 0
  1548. >>> covariant_order(a*R2.x + 2)
  1549. 0
  1550. >>> covariant_order(a*R2.x*R2.dy + R2.dx)
  1551. 1
  1552. """
  1553. # TODO move some of this to class methods.
  1554. # TODO rewrite using the .as_blah_blah methods
  1555. if isinstance(expr, Add):
  1556. orders = [covariant_order(e) for e in expr.args]
  1557. if len(set(orders)) != 1:
  1558. raise ValueError('Misformed expression containing form fields of varying order.')
  1559. return orders[0]
  1560. elif isinstance(expr, Mul):
  1561. orders = [covariant_order(e) for e in expr.args]
  1562. not_zero = [o for o in orders if o != 0]
  1563. if len(not_zero) > 1:
  1564. raise ValueError('Misformed expression containing multiplication between forms.')
  1565. return 0 if not not_zero else not_zero[0]
  1566. elif isinstance(expr, Pow):
  1567. if covariant_order(expr.base) or covariant_order(expr.exp):
  1568. raise ValueError(
  1569. 'Misformed expression containing a power of a form.')
  1570. return 0
  1571. elif isinstance(expr, Differential):
  1572. return covariant_order(*expr.args) + 1
  1573. elif isinstance(expr, TensorProduct):
  1574. return sum(covariant_order(a) for a in expr.args)
  1575. elif not _strict or expr.atoms(BaseScalarField):
  1576. return 0
  1577. else: # If it does not contain anything related to the diffgeom module and it is _strict
  1578. return -1
  1579. ###############################################################################
  1580. # Coordinate transformation functions
  1581. ###############################################################################
  1582. def vectors_in_basis(expr, to_sys):
  1583. """Transform all base vectors in base vectors of a specified coord basis.
  1584. While the new base vectors are in the new coordinate system basis, any
  1585. coefficients are kept in the old system.
  1586. Examples
  1587. ========
  1588. >>> from sympy.diffgeom import vectors_in_basis
  1589. >>> from sympy.diffgeom.rn import R2_r, R2_p
  1590. >>> vectors_in_basis(R2_r.e_x, R2_p)
  1591. -y*e_theta/(x**2 + y**2) + x*e_rho/sqrt(x**2 + y**2)
  1592. >>> vectors_in_basis(R2_p.e_r, R2_r)
  1593. sin(theta)*e_y + cos(theta)*e_x
  1594. """
  1595. vectors = list(expr.atoms(BaseVectorField))
  1596. new_vectors = []
  1597. for v in vectors:
  1598. cs = v._coord_sys
  1599. jac = cs.jacobian(to_sys, cs.coord_functions())
  1600. new = (jac.T*Matrix(to_sys.base_vectors()))[v._index]
  1601. new_vectors.append(new)
  1602. return expr.subs(list(zip(vectors, new_vectors)))
  1603. ###############################################################################
  1604. # Coordinate-dependent functions
  1605. ###############################################################################
  1606. def twoform_to_matrix(expr):
  1607. """Return the matrix representing the twoform.
  1608. For the twoform `w` return the matrix `M` such that `M[i,j]=w(e_i, e_j)`,
  1609. where `e_i` is the i-th base vector field for the coordinate system in
  1610. which the expression of `w` is given.
  1611. Examples
  1612. ========
  1613. >>> from sympy.diffgeom.rn import R2
  1614. >>> from sympy.diffgeom import twoform_to_matrix, TensorProduct
  1615. >>> TP = TensorProduct
  1616. >>> twoform_to_matrix(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
  1617. Matrix([
  1618. [1, 0],
  1619. [0, 1]])
  1620. >>> twoform_to_matrix(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
  1621. Matrix([
  1622. [x, 0],
  1623. [0, 1]])
  1624. >>> twoform_to_matrix(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy) - TP(R2.dx, R2.dy)/2)
  1625. Matrix([
  1626. [ 1, 0],
  1627. [-1/2, 1]])
  1628. """
  1629. if covariant_order(expr) != 2 or contravariant_order(expr):
  1630. raise ValueError('The input expression is not a two-form.')
  1631. coord_sys = _find_coords(expr)
  1632. if len(coord_sys) != 1:
  1633. raise ValueError('The input expression concerns more than one '
  1634. 'coordinate systems, hence there is no unambiguous '
  1635. 'way to choose a coordinate system for the matrix.')
  1636. coord_sys = coord_sys.pop()
  1637. vectors = coord_sys.base_vectors()
  1638. expr = expr.expand()
  1639. matrix_content = [[expr.rcall(v1, v2) for v1 in vectors]
  1640. for v2 in vectors]
  1641. return Matrix(matrix_content)
  1642. def metric_to_Christoffel_1st(expr):
  1643. """Return the nested list of Christoffel symbols for the given metric.
  1644. This returns the Christoffel symbol of first kind that represents the
  1645. Levi-Civita connection for the given metric.
  1646. Examples
  1647. ========
  1648. >>> from sympy.diffgeom.rn import R2
  1649. >>> from sympy.diffgeom import metric_to_Christoffel_1st, TensorProduct
  1650. >>> TP = TensorProduct
  1651. >>> metric_to_Christoffel_1st(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
  1652. [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]
  1653. >>> metric_to_Christoffel_1st(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
  1654. [[[1/2, 0], [0, 0]], [[0, 0], [0, 0]]]
  1655. """
  1656. matrix = twoform_to_matrix(expr)
  1657. if not matrix.is_symmetric():
  1658. raise ValueError(
  1659. 'The two-form representing the metric is not symmetric.')
  1660. coord_sys = _find_coords(expr).pop()
  1661. deriv_matrices = [matrix.applyfunc(d) for d in coord_sys.base_vectors()]
  1662. indices = list(range(coord_sys.dim))
  1663. christoffel = [[[(deriv_matrices[k][i, j] + deriv_matrices[j][i, k] - deriv_matrices[i][j, k])/2
  1664. for k in indices]
  1665. for j in indices]
  1666. for i in indices]
  1667. return ImmutableDenseNDimArray(christoffel)
  1668. def metric_to_Christoffel_2nd(expr):
  1669. """Return the nested list of Christoffel symbols for the given metric.
  1670. This returns the Christoffel symbol of second kind that represents the
  1671. Levi-Civita connection for the given metric.
  1672. Examples
  1673. ========
  1674. >>> from sympy.diffgeom.rn import R2
  1675. >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct
  1676. >>> TP = TensorProduct
  1677. >>> metric_to_Christoffel_2nd(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
  1678. [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]
  1679. >>> metric_to_Christoffel_2nd(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
  1680. [[[1/(2*x), 0], [0, 0]], [[0, 0], [0, 0]]]
  1681. """
  1682. ch_1st = metric_to_Christoffel_1st(expr)
  1683. coord_sys = _find_coords(expr).pop()
  1684. indices = list(range(coord_sys.dim))
  1685. # XXX workaround, inverting a matrix does not work if it contains non
  1686. # symbols
  1687. #matrix = twoform_to_matrix(expr).inv()
  1688. matrix = twoform_to_matrix(expr)
  1689. s_fields = set()
  1690. for e in matrix:
  1691. s_fields.update(e.atoms(BaseScalarField))
  1692. s_fields = list(s_fields)
  1693. dums = coord_sys.symbols
  1694. matrix = matrix.subs(list(zip(s_fields, dums))).inv().subs(list(zip(dums, s_fields)))
  1695. # XXX end of workaround
  1696. christoffel = [[[Add(*[matrix[i, l]*ch_1st[l, j, k] for l in indices])
  1697. for k in indices]
  1698. for j in indices]
  1699. for i in indices]
  1700. return ImmutableDenseNDimArray(christoffel)
  1701. def metric_to_Riemann_components(expr):
  1702. """Return the components of the Riemann tensor expressed in a given basis.
  1703. Given a metric it calculates the components of the Riemann tensor in the
  1704. canonical basis of the coordinate system in which the metric expression is
  1705. given.
  1706. Examples
  1707. ========
  1708. >>> from sympy import exp
  1709. >>> from sympy.diffgeom.rn import R2
  1710. >>> from sympy.diffgeom import metric_to_Riemann_components, TensorProduct
  1711. >>> TP = TensorProduct
  1712. >>> metric_to_Riemann_components(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
  1713. [[[[0, 0], [0, 0]], [[0, 0], [0, 0]]], [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]]
  1714. >>> non_trivial_metric = exp(2*R2.r)*TP(R2.dr, R2.dr) + \
  1715. R2.r**2*TP(R2.dtheta, R2.dtheta)
  1716. >>> non_trivial_metric
  1717. exp(2*rho)*TensorProduct(drho, drho) + rho**2*TensorProduct(dtheta, dtheta)
  1718. >>> riemann = metric_to_Riemann_components(non_trivial_metric)
  1719. >>> riemann[0, :, :, :]
  1720. [[[0, 0], [0, 0]], [[0, exp(-2*rho)*rho], [-exp(-2*rho)*rho, 0]]]
  1721. >>> riemann[1, :, :, :]
  1722. [[[0, -1/rho], [1/rho, 0]], [[0, 0], [0, 0]]]
  1723. """
  1724. ch_2nd = metric_to_Christoffel_2nd(expr)
  1725. coord_sys = _find_coords(expr).pop()
  1726. indices = list(range(coord_sys.dim))
  1727. deriv_ch = [[[[d(ch_2nd[i, j, k])
  1728. for d in coord_sys.base_vectors()]
  1729. for k in indices]
  1730. for j in indices]
  1731. for i in indices]
  1732. riemann_a = [[[[deriv_ch[rho][sig][nu][mu] - deriv_ch[rho][sig][mu][nu]
  1733. for nu in indices]
  1734. for mu in indices]
  1735. for sig in indices]
  1736. for rho in indices]
  1737. riemann_b = [[[[Add(*[ch_2nd[rho, l, mu]*ch_2nd[l, sig, nu] - ch_2nd[rho, l, nu]*ch_2nd[l, sig, mu] for l in indices])
  1738. for nu in indices]
  1739. for mu in indices]
  1740. for sig in indices]
  1741. for rho in indices]
  1742. riemann = [[[[riemann_a[rho][sig][mu][nu] + riemann_b[rho][sig][mu][nu]
  1743. for nu in indices]
  1744. for mu in indices]
  1745. for sig in indices]
  1746. for rho in indices]
  1747. return ImmutableDenseNDimArray(riemann)
  1748. def metric_to_Ricci_components(expr):
  1749. """Return the components of the Ricci tensor expressed in a given basis.
  1750. Given a metric it calculates the components of the Ricci tensor in the
  1751. canonical basis of the coordinate system in which the metric expression is
  1752. given.
  1753. Examples
  1754. ========
  1755. >>> from sympy import exp
  1756. >>> from sympy.diffgeom.rn import R2
  1757. >>> from sympy.diffgeom import metric_to_Ricci_components, TensorProduct
  1758. >>> TP = TensorProduct
  1759. >>> metric_to_Ricci_components(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
  1760. [[0, 0], [0, 0]]
  1761. >>> non_trivial_metric = exp(2*R2.r)*TP(R2.dr, R2.dr) + \
  1762. R2.r**2*TP(R2.dtheta, R2.dtheta)
  1763. >>> non_trivial_metric
  1764. exp(2*rho)*TensorProduct(drho, drho) + rho**2*TensorProduct(dtheta, dtheta)
  1765. >>> metric_to_Ricci_components(non_trivial_metric)
  1766. [[1/rho, 0], [0, exp(-2*rho)*rho]]
  1767. """
  1768. riemann = metric_to_Riemann_components(expr)
  1769. coord_sys = _find_coords(expr).pop()
  1770. indices = list(range(coord_sys.dim))
  1771. ricci = [[Add(*[riemann[k, i, k, j] for k in indices])
  1772. for j in indices]
  1773. for i in indices]
  1774. return ImmutableDenseNDimArray(ricci)
  1775. ###############################################################################
  1776. # Classes for deprecation
  1777. ###############################################################################
  1778. class _deprecated_container:
  1779. # This class gives deprecation warning.
  1780. # When deprecated features are completely deleted, this should be removed as well.
  1781. # See https://github.com/sympy/sympy/pull/19368
  1782. def __init__(self, message, data):
  1783. super().__init__(data)
  1784. self.message = message
  1785. def warn(self):
  1786. sympy_deprecation_warning(
  1787. self.message,
  1788. deprecated_since_version="1.7",
  1789. active_deprecations_target="deprecated-diffgeom-mutable",
  1790. stacklevel=4
  1791. )
  1792. def __iter__(self):
  1793. self.warn()
  1794. return super().__iter__()
  1795. def __getitem__(self, key):
  1796. self.warn()
  1797. return super().__getitem__(key)
  1798. def __contains__(self, key):
  1799. self.warn()
  1800. return super().__contains__(key)
  1801. class _deprecated_list(_deprecated_container, list):
  1802. pass
  1803. class _deprecated_dict(_deprecated_container, dict):
  1804. pass
  1805. # Import at end to avoid cyclic imports
  1806. from sympy.simplify.simplify import simplify