diffgeom.py 71 KB

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