12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273 |
- from __future__ import annotations
- from typing import Any
- from functools import reduce
- from itertools import permutations
- from sympy.combinatorics import Permutation
- from sympy.core import (
- Basic, Expr, Function, diff,
- Pow, Mul, Add, Lambda, S, Tuple, Dict
- )
- from sympy.core.cache import cacheit
- from sympy.core.symbol import Symbol, Dummy
- from sympy.core.symbol import Str
- from sympy.core.sympify import _sympify
- from sympy.functions import factorial
- from sympy.matrices import ImmutableDenseMatrix as Matrix
- from sympy.solvers import solve
- from sympy.utilities.exceptions import (sympy_deprecation_warning,
- SymPyDeprecationWarning,
- ignore_warnings)
- # TODO you are a bit excessive in the use of Dummies
- # TODO dummy point, literal field
- # TODO too often one needs to call doit or simplify on the output, check the
- # tests and find out why
- from sympy.tensor.array import ImmutableDenseNDimArray
- class Manifold(Basic):
- """
- A mathematical manifold.
- Explanation
- ===========
- A manifold is a topological space that locally resembles
- Euclidean space near each point [1].
- This class does not provide any means to study the topological
- characteristics of the manifold that it represents, though.
- Parameters
- ==========
- name : str
- The name of the manifold.
- dim : int
- The dimension of the manifold.
- Examples
- ========
- >>> from sympy.diffgeom import Manifold
- >>> m = Manifold('M', 2)
- >>> m
- M
- >>> m.dim
- 2
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Manifold
- """
- def __new__(cls, name, dim, **kwargs):
- if not isinstance(name, Str):
- name = Str(name)
- dim = _sympify(dim)
- obj = super().__new__(cls, name, dim)
- obj.patches = _deprecated_list(
- """
- Manifold.patches is deprecated. The Manifold object is now
- immutable. Instead use a separate list to keep track of the
- patches.
- """, [])
- return obj
- @property
- def name(self):
- return self.args[0]
- @property
- def dim(self):
- return self.args[1]
- class Patch(Basic):
- """
- A patch on a manifold.
- Explanation
- ===========
- Coordinate patch, or patch in short, is a simply-connected open set around
- a point in the manifold [1]. On a manifold one can have many patches that
- do not always include the whole manifold. On these patches coordinate
- charts can be defined that permit the parameterization of any point on the
- patch in terms of a tuple of real numbers (the coordinates).
- This class does not provide any means to study the topological
- characteristics of the patch that it represents.
- Parameters
- ==========
- name : str
- The name of the patch.
- manifold : Manifold
- The manifold on which the patch is defined.
- Examples
- ========
- >>> from sympy.diffgeom import Manifold, Patch
- >>> m = Manifold('M', 2)
- >>> p = Patch('P', m)
- >>> p
- P
- >>> p.dim
- 2
- References
- ==========
- .. [1] G. Sussman, J. Wisdom, W. Farr, Functional Differential Geometry
- (2013)
- """
- def __new__(cls, name, manifold, **kwargs):
- if not isinstance(name, Str):
- name = Str(name)
- obj = super().__new__(cls, name, manifold)
- obj.manifold.patches.append(obj) # deprecated
- obj.coord_systems = _deprecated_list(
- """
- Patch.coord_systms is deprecated. The Patch class is now
- immutable. Instead use a separate list to keep track of coordinate
- systems.
- """, [])
- return obj
- @property
- def name(self):
- return self.args[0]
- @property
- def manifold(self):
- return self.args[1]
- @property
- def dim(self):
- return self.manifold.dim
- class CoordSystem(Basic):
- """
- A coordinate system defined on the patch.
- Explanation
- ===========
- Coordinate system is a system that uses one or more coordinates to uniquely
- determine the position of the points or other geometric elements on a
- manifold [1].
- By passing ``Symbols`` to *symbols* parameter, user can define the name and
- assumptions of coordinate symbols of the coordinate system. If not passed,
- these symbols are generated automatically and are assumed to be real valued.
- By passing *relations* parameter, user can define the transform relations of
- coordinate systems. Inverse transformation and indirect transformation can
- be found automatically. If this parameter is not passed, coordinate
- transformation cannot be done.
- Parameters
- ==========
- name : str
- The name of the coordinate system.
- patch : Patch
- The patch where the coordinate system is defined.
- symbols : list of Symbols, optional
- Defines the names and assumptions of coordinate symbols.
- relations : dict, optional
- Key is a tuple of two strings, who are the names of the systems where
- the coordinates transform from and transform to.
- Value is a tuple of the symbols before transformation and a tuple of
- the expressions after transformation.
- Examples
- ========
- We define two-dimensional Cartesian coordinate system and polar coordinate
- system.
- >>> from sympy import symbols, pi, sqrt, atan2, cos, sin
- >>> from sympy.diffgeom import Manifold, Patch, CoordSystem
- >>> m = Manifold('M', 2)
- >>> p = Patch('P', m)
- >>> x, y = symbols('x y', real=True)
- >>> r, theta = symbols('r theta', nonnegative=True)
- >>> relation_dict = {
- ... ('Car2D', 'Pol'): [(x, y), (sqrt(x**2 + y**2), atan2(y, x))],
- ... ('Pol', 'Car2D'): [(r, theta), (r*cos(theta), r*sin(theta))]
- ... }
- >>> Car2D = CoordSystem('Car2D', p, (x, y), relation_dict)
- >>> Pol = CoordSystem('Pol', p, (r, theta), relation_dict)
- ``symbols`` property returns ``CoordinateSymbol`` instances. These symbols
- are not same with the symbols used to construct the coordinate system.
- >>> Car2D
- Car2D
- >>> Car2D.dim
- 2
- >>> Car2D.symbols
- (x, y)
- >>> _[0].func
- <class 'sympy.diffgeom.diffgeom.CoordinateSymbol'>
- ``transformation()`` method returns the transformation function from
- one coordinate system to another. ``transform()`` method returns the
- transformed coordinates.
- >>> Car2D.transformation(Pol)
- Lambda((x, y), Matrix([
- [sqrt(x**2 + y**2)],
- [ atan2(y, x)]]))
- >>> Car2D.transform(Pol)
- Matrix([
- [sqrt(x**2 + y**2)],
- [ atan2(y, x)]])
- >>> Car2D.transform(Pol, [1, 2])
- Matrix([
- [sqrt(5)],
- [atan(2)]])
- ``jacobian()`` method returns the Jacobian matrix of coordinate
- transformation between two systems. ``jacobian_determinant()`` method
- returns the Jacobian determinant of coordinate transformation between two
- systems.
- >>> Pol.jacobian(Car2D)
- Matrix([
- [cos(theta), -r*sin(theta)],
- [sin(theta), r*cos(theta)]])
- >>> Pol.jacobian(Car2D, [1, pi/2])
- Matrix([
- [0, -1],
- [1, 0]])
- >>> Car2D.jacobian_determinant(Pol)
- 1/sqrt(x**2 + y**2)
- >>> Car2D.jacobian_determinant(Pol, [1,0])
- 1
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Coordinate_system
- """
- def __new__(cls, name, patch, symbols=None, relations={}, **kwargs):
- if not isinstance(name, Str):
- name = Str(name)
- # canonicallize the symbols
- if symbols is None:
- names = kwargs.get('names', None)
- if names is None:
- symbols = Tuple(
- *[Symbol('%s_%s' % (name.name, i), real=True)
- for i in range(patch.dim)]
- )
- else:
- sympy_deprecation_warning(
- f"""
- The 'names' argument to CoordSystem is deprecated. Use 'symbols' instead. That
- is, replace
- CoordSystem(..., names={names})
- with
- CoordSystem(..., symbols=[{', '.join(["Symbol(" + repr(n) + ", real=True)" for n in names])}])
- """,
- deprecated_since_version="1.7",
- active_deprecations_target="deprecated-diffgeom-mutable",
- )
- symbols = Tuple(
- *[Symbol(n, real=True) for n in names]
- )
- else:
- syms = []
- for s in symbols:
- if isinstance(s, Symbol):
- syms.append(Symbol(s.name, **s._assumptions.generator))
- elif isinstance(s, str):
- sympy_deprecation_warning(
- f"""
- Passing a string as the coordinate symbol name to CoordSystem is deprecated.
- Pass a Symbol with the appropriate name and assumptions instead.
- That is, replace {s} with Symbol({s!r}, real=True).
- """,
- deprecated_since_version="1.7",
- active_deprecations_target="deprecated-diffgeom-mutable",
- )
- syms.append(Symbol(s, real=True))
- symbols = Tuple(*syms)
- # canonicallize the relations
- rel_temp = {}
- for k,v in relations.items():
- s1, s2 = k
- if not isinstance(s1, Str):
- s1 = Str(s1)
- if not isinstance(s2, Str):
- s2 = Str(s2)
- key = Tuple(s1, s2)
- # Old version used Lambda as a value.
- if isinstance(v, Lambda):
- v = (tuple(v.signature), tuple(v.expr))
- else:
- v = (tuple(v[0]), tuple(v[1]))
- rel_temp[key] = v
- relations = Dict(rel_temp)
- # construct the object
- obj = super().__new__(cls, name, patch, symbols, relations)
- # Add deprecated attributes
- obj.transforms = _deprecated_dict(
- """
- CoordSystem.transforms is deprecated. The CoordSystem class is now
- immutable. Use the 'relations' keyword argument to the
- CoordSystems() constructor to specify relations.
- """, {})
- obj._names = [str(n) for n in symbols]
- obj.patch.coord_systems.append(obj) # deprecated
- obj._dummies = [Dummy(str(n)) for n in symbols] # deprecated
- obj._dummy = Dummy()
- return obj
- @property
- def name(self):
- return self.args[0]
- @property
- def patch(self):
- return self.args[1]
- @property
- def manifold(self):
- return self.patch.manifold
- @property
- def symbols(self):
- return tuple(CoordinateSymbol(self, i, **s._assumptions.generator)
- for i,s in enumerate(self.args[2]))
- @property
- def relations(self):
- return self.args[3]
- @property
- def dim(self):
- return self.patch.dim
- ##########################################################################
- # Finding transformation relation
- ##########################################################################
- def transformation(self, sys):
- """
- Return coordinate transformation function from *self* to *sys*.
- Parameters
- ==========
- sys : CoordSystem
- Returns
- =======
- sympy.Lambda
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2_r, R2_p
- >>> R2_r.transformation(R2_p)
- Lambda((x, y), Matrix([
- [sqrt(x**2 + y**2)],
- [ atan2(y, x)]]))
- """
- signature = self.args[2]
- key = Tuple(self.name, sys.name)
- if self == sys:
- expr = Matrix(self.symbols)
- elif key in self.relations:
- expr = Matrix(self.relations[key][1])
- elif key[::-1] in self.relations:
- expr = Matrix(self._inverse_transformation(sys, self))
- else:
- expr = Matrix(self._indirect_transformation(self, sys))
- return Lambda(signature, expr)
- @staticmethod
- def _solve_inverse(sym1, sym2, exprs, sys1_name, sys2_name):
- ret = solve(
- [t[0] - t[1] for t in zip(sym2, exprs)],
- list(sym1), dict=True)
- if len(ret) == 0:
- temp = "Cannot solve inverse relation from {} to {}."
- raise NotImplementedError(temp.format(sys1_name, sys2_name))
- elif len(ret) > 1:
- temp = "Obtained multiple inverse relation from {} to {}."
- raise ValueError(temp.format(sys1_name, sys2_name))
- return ret[0]
- @classmethod
- def _inverse_transformation(cls, sys1, sys2):
- # Find the transformation relation from sys2 to sys1
- forward = sys1.transform(sys2)
- inv_results = cls._solve_inverse(sys1.symbols, sys2.symbols, forward,
- sys1.name, sys2.name)
- signature = tuple(sys1.symbols)
- return [inv_results[s] for s in signature]
- @classmethod
- @cacheit
- def _indirect_transformation(cls, sys1, sys2):
- # Find the transformation relation between two indirectly connected
- # coordinate systems
- rel = sys1.relations
- path = cls._dijkstra(sys1, sys2)
- transforms = []
- for s1, s2 in zip(path, path[1:]):
- if (s1, s2) in rel:
- transforms.append(rel[(s1, s2)])
- else:
- sym2, inv_exprs = rel[(s2, s1)]
- sym1 = tuple(Dummy() for i in sym2)
- ret = cls._solve_inverse(sym2, sym1, inv_exprs, s2, s1)
- ret = tuple(ret[s] for s in sym2)
- transforms.append((sym1, ret))
- syms = sys1.args[2]
- exprs = syms
- for newsyms, newexprs in transforms:
- exprs = tuple(e.subs(zip(newsyms, exprs)) for e in newexprs)
- return exprs
- @staticmethod
- def _dijkstra(sys1, sys2):
- # Use Dijkstra algorithm to find the shortest path between two indirectly-connected
- # coordinate systems
- # return value is the list of the names of the systems.
- relations = sys1.relations
- graph = {}
- for s1, s2 in relations.keys():
- if s1 not in graph:
- graph[s1] = {s2}
- else:
- graph[s1].add(s2)
- if s2 not in graph:
- graph[s2] = {s1}
- else:
- graph[s2].add(s1)
- path_dict = {sys:[0, [], 0] for sys in graph} # minimum distance, path, times of visited
- def visit(sys):
- path_dict[sys][2] = 1
- for newsys in graph[sys]:
- distance = path_dict[sys][0] + 1
- if path_dict[newsys][0] >= distance or not path_dict[newsys][1]:
- path_dict[newsys][0] = distance
- path_dict[newsys][1] = list(path_dict[sys][1])
- path_dict[newsys][1].append(sys)
- visit(sys1.name)
- while True:
- min_distance = max(path_dict.values(), key=lambda x:x[0])[0]
- newsys = None
- for sys, lst in path_dict.items():
- if 0 < lst[0] <= min_distance and not lst[2]:
- min_distance = lst[0]
- newsys = sys
- if newsys is None:
- break
- visit(newsys)
- result = path_dict[sys2.name][1]
- result.append(sys2.name)
- if result == [sys2.name]:
- raise KeyError("Two coordinate systems are not connected.")
- return result
- def connect_to(self, to_sys, from_coords, to_exprs, inverse=True, fill_in_gaps=False):
- sympy_deprecation_warning(
- """
- The CoordSystem.connect_to() method is deprecated. Instead,
- generate a new instance of CoordSystem with the 'relations'
- keyword argument (CoordSystem classes are now immutable).
- """,
- deprecated_since_version="1.7",
- active_deprecations_target="deprecated-diffgeom-mutable",
- )
- from_coords, to_exprs = dummyfy(from_coords, to_exprs)
- self.transforms[to_sys] = Matrix(from_coords), Matrix(to_exprs)
- if inverse:
- to_sys.transforms[self] = self._inv_transf(from_coords, to_exprs)
- if fill_in_gaps:
- self._fill_gaps_in_transformations()
- @staticmethod
- def _inv_transf(from_coords, to_exprs):
- # Will be removed when connect_to is removed
- inv_from = [i.as_dummy() for i in from_coords]
- inv_to = solve(
- [t[0] - t[1] for t in zip(inv_from, to_exprs)],
- list(from_coords), dict=True)[0]
- inv_to = [inv_to[fc] for fc in from_coords]
- return Matrix(inv_from), Matrix(inv_to)
- @staticmethod
- def _fill_gaps_in_transformations():
- # Will be removed when connect_to is removed
- raise NotImplementedError
- ##########################################################################
- # Coordinate transformations
- ##########################################################################
- def transform(self, sys, coordinates=None):
- """
- Return the result of coordinate transformation from *self* to *sys*.
- If coordinates are not given, coordinate symbols of *self* are used.
- Parameters
- ==========
- sys : CoordSystem
- coordinates : Any iterable, optional.
- Returns
- =======
- sympy.ImmutableDenseMatrix containing CoordinateSymbol
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2_r, R2_p
- >>> R2_r.transform(R2_p)
- Matrix([
- [sqrt(x**2 + y**2)],
- [ atan2(y, x)]])
- >>> R2_r.transform(R2_p, [0, 1])
- Matrix([
- [ 1],
- [pi/2]])
- """
- if coordinates is None:
- coordinates = self.symbols
- if self != sys:
- transf = self.transformation(sys)
- coordinates = transf(*coordinates)
- else:
- coordinates = Matrix(coordinates)
- return coordinates
- def coord_tuple_transform_to(self, to_sys, coords):
- """Transform ``coords`` to coord system ``to_sys``."""
- sympy_deprecation_warning(
- """
- The CoordSystem.coord_tuple_transform_to() method is deprecated.
- Use the CoordSystem.transform() method instead.
- """,
- deprecated_since_version="1.7",
- active_deprecations_target="deprecated-diffgeom-mutable",
- )
- coords = Matrix(coords)
- if self != to_sys:
- with ignore_warnings(SymPyDeprecationWarning):
- transf = self.transforms[to_sys]
- coords = transf[1].subs(list(zip(transf[0], coords)))
- return coords
- def jacobian(self, sys, coordinates=None):
- """
- Return the jacobian matrix of a transformation on given coordinates.
- If coordinates are not given, coordinate symbols of *self* are used.
- Parameters
- ==========
- sys : CoordSystem
- coordinates : Any iterable, optional.
- Returns
- =======
- sympy.ImmutableDenseMatrix
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2_r, R2_p
- >>> R2_p.jacobian(R2_r)
- Matrix([
- [cos(theta), -rho*sin(theta)],
- [sin(theta), rho*cos(theta)]])
- >>> R2_p.jacobian(R2_r, [1, 0])
- Matrix([
- [1, 0],
- [0, 1]])
- """
- result = self.transform(sys).jacobian(self.symbols)
- if coordinates is not None:
- result = result.subs(list(zip(self.symbols, coordinates)))
- return result
- jacobian_matrix = jacobian
- def jacobian_determinant(self, sys, coordinates=None):
- """
- Return the jacobian determinant of a transformation on given
- coordinates. If coordinates are not given, coordinate symbols of *self*
- are used.
- Parameters
- ==========
- sys : CoordSystem
- coordinates : Any iterable, optional.
- Returns
- =======
- sympy.Expr
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2_r, R2_p
- >>> R2_r.jacobian_determinant(R2_p)
- 1/sqrt(x**2 + y**2)
- >>> R2_r.jacobian_determinant(R2_p, [1, 0])
- 1
- """
- return self.jacobian(sys, coordinates).det()
- ##########################################################################
- # Points
- ##########################################################################
- def point(self, coords):
- """Create a ``Point`` with coordinates given in this coord system."""
- return Point(self, coords)
- def point_to_coords(self, point):
- """Calculate the coordinates of a point in this coord system."""
- return point.coords(self)
- ##########################################################################
- # Base fields.
- ##########################################################################
- def base_scalar(self, coord_index):
- """Return ``BaseScalarField`` that takes a point and returns one of the coordinates."""
- return BaseScalarField(self, coord_index)
- coord_function = base_scalar
- def base_scalars(self):
- """Returns a list of all coordinate functions.
- For more details see the ``base_scalar`` method of this class."""
- return [self.base_scalar(i) for i in range(self.dim)]
- coord_functions = base_scalars
- def base_vector(self, coord_index):
- """Return a basis vector field.
- The basis vector field for this coordinate system. It is also an
- operator on scalar fields."""
- return BaseVectorField(self, coord_index)
- def base_vectors(self):
- """Returns a list of all base vectors.
- For more details see the ``base_vector`` method of this class."""
- return [self.base_vector(i) for i in range(self.dim)]
- def base_oneform(self, coord_index):
- """Return a basis 1-form field.
- The basis one-form field for this coordinate system. It is also an
- operator on vector fields."""
- return Differential(self.coord_function(coord_index))
- def base_oneforms(self):
- """Returns a list of all base oneforms.
- For more details see the ``base_oneform`` method of this class."""
- return [self.base_oneform(i) for i in range(self.dim)]
- class CoordinateSymbol(Symbol):
- """A symbol which denotes an abstract value of i-th coordinate of
- the coordinate system with given context.
- Explanation
- ===========
- Each coordinates in coordinate system are represented by unique symbol,
- such as x, y, z in Cartesian coordinate system.
- You may not construct this class directly. Instead, use `symbols` method
- of CoordSystem.
- Parameters
- ==========
- coord_sys : CoordSystem
- index : integer
- Examples
- ========
- >>> from sympy import symbols, Lambda, Matrix, sqrt, atan2, cos, sin
- >>> from sympy.diffgeom import Manifold, Patch, CoordSystem
- >>> m = Manifold('M', 2)
- >>> p = Patch('P', m)
- >>> x, y = symbols('x y', real=True)
- >>> r, theta = symbols('r theta', nonnegative=True)
- >>> relation_dict = {
- ... ('Car2D', 'Pol'): Lambda((x, y), Matrix([sqrt(x**2 + y**2), atan2(y, x)])),
- ... ('Pol', 'Car2D'): Lambda((r, theta), Matrix([r*cos(theta), r*sin(theta)]))
- ... }
- >>> Car2D = CoordSystem('Car2D', p, [x, y], relation_dict)
- >>> Pol = CoordSystem('Pol', p, [r, theta], relation_dict)
- >>> x, y = Car2D.symbols
- ``CoordinateSymbol`` contains its coordinate symbol and index.
- >>> x.name
- 'x'
- >>> x.coord_sys == Car2D
- True
- >>> x.index
- 0
- >>> x.is_real
- True
- You can transform ``CoordinateSymbol`` into other coordinate system using
- ``rewrite()`` method.
- >>> x.rewrite(Pol)
- r*cos(theta)
- >>> sqrt(x**2 + y**2).rewrite(Pol).simplify()
- r
- """
- def __new__(cls, coord_sys, index, **assumptions):
- name = coord_sys.args[2][index].name
- obj = super().__new__(cls, name, **assumptions)
- obj.coord_sys = coord_sys
- obj.index = index
- return obj
- def __getnewargs__(self):
- return (self.coord_sys, self.index)
- def _hashable_content(self):
- return (
- self.coord_sys, self.index
- ) + tuple(sorted(self.assumptions0.items()))
- def _eval_rewrite(self, rule, args, **hints):
- if isinstance(rule, CoordSystem):
- return rule.transform(self.coord_sys)[self.index]
- return super()._eval_rewrite(rule, args, **hints)
- class Point(Basic):
- """Point defined in a coordinate system.
- Explanation
- ===========
- Mathematically, point is defined in the manifold and does not have any coordinates
- by itself. Coordinate system is what imbues the coordinates to the point by coordinate
- chart. However, due to the difficulty of realizing such logic, you must supply
- a coordinate system and coordinates to define a Point here.
- The usage of this object after its definition is independent of the
- coordinate system that was used in order to define it, however due to
- limitations in the simplification routines you can arrive at complicated
- expressions if you use inappropriate coordinate systems.
- Parameters
- ==========
- coord_sys : CoordSystem
- coords : list
- The coordinates of the point.
- Examples
- ========
- >>> from sympy import pi
- >>> from sympy.diffgeom import Point
- >>> from sympy.diffgeom.rn import R2, R2_r, R2_p
- >>> rho, theta = R2_p.symbols
- >>> p = Point(R2_p, [rho, 3*pi/4])
- >>> p.manifold == R2
- True
- >>> p.coords()
- Matrix([
- [ rho],
- [3*pi/4]])
- >>> p.coords(R2_r)
- Matrix([
- [-sqrt(2)*rho/2],
- [ sqrt(2)*rho/2]])
- """
- def __new__(cls, coord_sys, coords, **kwargs):
- coords = Matrix(coords)
- obj = super().__new__(cls, coord_sys, coords)
- obj._coord_sys = coord_sys
- obj._coords = coords
- return obj
- @property
- def patch(self):
- return self._coord_sys.patch
- @property
- def manifold(self):
- return self._coord_sys.manifold
- @property
- def dim(self):
- return self.manifold.dim
- def coords(self, sys=None):
- """
- Coordinates of the point in given coordinate system. If coordinate system
- is not passed, it returns the coordinates in the coordinate system in which
- the poin was defined.
- """
- if sys is None:
- return self._coords
- else:
- return self._coord_sys.transform(sys, self._coords)
- @property
- def free_symbols(self):
- return self._coords.free_symbols
- class BaseScalarField(Expr):
- """Base scalar field over a manifold for a given coordinate system.
- Explanation
- ===========
- A scalar field takes a point as an argument and returns a scalar.
- A base scalar field of a coordinate system takes a point and returns one of
- the coordinates of that point in the coordinate system in question.
- To define a scalar field you need to choose the coordinate system and the
- index of the coordinate.
- The use of the scalar field after its definition is independent of the
- coordinate system in which it was defined, however due to limitations in
- the simplification routines you may arrive at more complicated
- expression if you use unappropriate coordinate systems.
- You can build complicated scalar fields by just building up SymPy
- expressions containing ``BaseScalarField`` instances.
- Parameters
- ==========
- coord_sys : CoordSystem
- index : integer
- Examples
- ========
- >>> from sympy import Function, pi
- >>> from sympy.diffgeom import BaseScalarField
- >>> from sympy.diffgeom.rn import R2_r, R2_p
- >>> rho, _ = R2_p.symbols
- >>> point = R2_p.point([rho, 0])
- >>> fx, fy = R2_r.base_scalars()
- >>> ftheta = BaseScalarField(R2_r, 1)
- >>> fx(point)
- rho
- >>> fy(point)
- 0
- >>> (fx**2+fy**2).rcall(point)
- rho**2
- >>> g = Function('g')
- >>> fg = g(ftheta-pi)
- >>> fg.rcall(point)
- g(-pi)
- """
- is_commutative = True
- def __new__(cls, coord_sys, index, **kwargs):
- index = _sympify(index)
- obj = super().__new__(cls, coord_sys, index)
- obj._coord_sys = coord_sys
- obj._index = index
- return obj
- @property
- def coord_sys(self):
- return self.args[0]
- @property
- def index(self):
- return self.args[1]
- @property
- def patch(self):
- return self.coord_sys.patch
- @property
- def manifold(self):
- return self.coord_sys.manifold
- @property
- def dim(self):
- return self.manifold.dim
- def __call__(self, *args):
- """Evaluating the field at a point or doing nothing.
- If the argument is a ``Point`` instance, the field is evaluated at that
- point. The field is returned itself if the argument is any other
- object. It is so in order to have working recursive calling mechanics
- for all fields (check the ``__call__`` method of ``Expr``).
- """
- point = args[0]
- if len(args) != 1 or not isinstance(point, Point):
- return self
- coords = point.coords(self._coord_sys)
- # XXX Calling doit is necessary with all the Subs expressions
- # XXX Calling simplify is necessary with all the trig expressions
- return simplify(coords[self._index]).doit()
- # XXX Workaround for limitations on the content of args
- free_symbols: set[Any] = set()
- class BaseVectorField(Expr):
- r"""Base vector field over a manifold for a given coordinate system.
- Explanation
- ===========
- A vector field is an operator taking a scalar field and returning a
- directional derivative (which is also a scalar field).
- A base vector field is the same type of operator, however the derivation is
- specifically done with respect to a chosen coordinate.
- To define a base vector field you need to choose the coordinate system and
- the index of the coordinate.
- The use of the vector field after its definition is independent of the
- coordinate system in which it was defined, however due to limitations in the
- simplification routines you may arrive at more complicated expression if you
- use unappropriate coordinate systems.
- Parameters
- ==========
- coord_sys : CoordSystem
- index : integer
- Examples
- ========
- >>> from sympy import Function
- >>> from sympy.diffgeom.rn import R2_p, R2_r
- >>> from sympy.diffgeom import BaseVectorField
- >>> from sympy import pprint
- >>> x, y = R2_r.symbols
- >>> rho, theta = R2_p.symbols
- >>> fx, fy = R2_r.base_scalars()
- >>> point_p = R2_p.point([rho, theta])
- >>> point_r = R2_r.point([x, y])
- >>> g = Function('g')
- >>> s_field = g(fx, fy)
- >>> v = BaseVectorField(R2_r, 1)
- >>> pprint(v(s_field))
- / d \|
- |---(g(x, xi))||
- \dxi /|xi=y
- >>> pprint(v(s_field).rcall(point_r).doit())
- d
- --(g(x, y))
- dy
- >>> pprint(v(s_field).rcall(point_p))
- / d \|
- |---(g(rho*cos(theta), xi))||
- \dxi /|xi=rho*sin(theta)
- """
- is_commutative = False
- def __new__(cls, coord_sys, index, **kwargs):
- index = _sympify(index)
- obj = super().__new__(cls, coord_sys, index)
- obj._coord_sys = coord_sys
- obj._index = index
- return obj
- @property
- def coord_sys(self):
- return self.args[0]
- @property
- def index(self):
- return self.args[1]
- @property
- def patch(self):
- return self.coord_sys.patch
- @property
- def manifold(self):
- return self.coord_sys.manifold
- @property
- def dim(self):
- return self.manifold.dim
- def __call__(self, scalar_field):
- """Apply on a scalar field.
- The action of a vector field on a scalar field is a directional
- differentiation.
- If the argument is not a scalar field an error is raised.
- """
- if covariant_order(scalar_field) or contravariant_order(scalar_field):
- raise ValueError('Only scalar fields can be supplied as arguments to vector fields.')
- if scalar_field is None:
- return self
- base_scalars = list(scalar_field.atoms(BaseScalarField))
- # First step: e_x(x+r**2) -> e_x(x) + 2*r*e_x(r)
- d_var = self._coord_sys._dummy
- # TODO: you need a real dummy function for the next line
- d_funcs = [Function('_#_%s' % i)(d_var) for i,
- b in enumerate(base_scalars)]
- d_result = scalar_field.subs(list(zip(base_scalars, d_funcs)))
- d_result = d_result.diff(d_var)
- # Second step: e_x(x) -> 1 and e_x(r) -> cos(atan2(x, y))
- coords = self._coord_sys.symbols
- d_funcs_deriv = [f.diff(d_var) for f in d_funcs]
- d_funcs_deriv_sub = []
- for b in base_scalars:
- jac = self._coord_sys.jacobian(b._coord_sys, coords)
- d_funcs_deriv_sub.append(jac[b._index, self._index])
- d_result = d_result.subs(list(zip(d_funcs_deriv, d_funcs_deriv_sub)))
- # Remove the dummies
- result = d_result.subs(list(zip(d_funcs, base_scalars)))
- result = result.subs(list(zip(coords, self._coord_sys.coord_functions())))
- return result.doit()
- def _find_coords(expr):
- # Finds CoordinateSystems existing in expr
- fields = expr.atoms(BaseScalarField, BaseVectorField)
- result = set()
- for f in fields:
- result.add(f._coord_sys)
- return result
- class Commutator(Expr):
- r"""Commutator of two vector fields.
- Explanation
- ===========
- The commutator of two vector fields `v_1` and `v_2` is defined as the
- vector field `[v_1, v_2]` that evaluated on each scalar field `f` is equal
- to `v_1(v_2(f)) - v_2(v_1(f))`.
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2_p, R2_r
- >>> from sympy.diffgeom import Commutator
- >>> from sympy import simplify
- >>> fx, fy = R2_r.base_scalars()
- >>> e_x, e_y = R2_r.base_vectors()
- >>> e_r = R2_p.base_vector(0)
- >>> c_xy = Commutator(e_x, e_y)
- >>> c_xr = Commutator(e_x, e_r)
- >>> c_xy
- 0
- Unfortunately, the current code is not able to compute everything:
- >>> c_xr
- Commutator(e_x, e_rho)
- >>> simplify(c_xr(fy**2))
- -2*cos(theta)*y**2/(x**2 + y**2)
- """
- def __new__(cls, v1, v2):
- if (covariant_order(v1) or contravariant_order(v1) != 1
- or covariant_order(v2) or contravariant_order(v2) != 1):
- raise ValueError(
- 'Only commutators of vector fields are supported.')
- if v1 == v2:
- return S.Zero
- coord_sys = set().union(*[_find_coords(v) for v in (v1, v2)])
- if len(coord_sys) == 1:
- # Only one coordinate systems is used, hence it is easy enough to
- # actually evaluate the commutator.
- if all(isinstance(v, BaseVectorField) for v in (v1, v2)):
- return S.Zero
- bases_1, bases_2 = [list(v.atoms(BaseVectorField))
- for v in (v1, v2)]
- coeffs_1 = [v1.expand().coeff(b) for b in bases_1]
- coeffs_2 = [v2.expand().coeff(b) for b in bases_2]
- res = 0
- for c1, b1 in zip(coeffs_1, bases_1):
- for c2, b2 in zip(coeffs_2, bases_2):
- res += c1*b1(c2)*b2 - c2*b2(c1)*b1
- return res
- else:
- obj = super().__new__(cls, v1, v2)
- obj._v1 = v1 # deprecated assignment
- obj._v2 = v2 # deprecated assignment
- return obj
- @property
- def v1(self):
- return self.args[0]
- @property
- def v2(self):
- return self.args[1]
- def __call__(self, scalar_field):
- """Apply on a scalar field.
- If the argument is not a scalar field an error is raised.
- """
- return self.v1(self.v2(scalar_field)) - self.v2(self.v1(scalar_field))
- class Differential(Expr):
- r"""Return the differential (exterior derivative) of a form field.
- Explanation
- ===========
- The differential of a form (i.e. the exterior derivative) has a complicated
- definition in the general case.
- The differential `df` of the 0-form `f` is defined for any vector field `v`
- as `df(v) = v(f)`.
- Examples
- ========
- >>> from sympy import Function
- >>> from sympy.diffgeom.rn import R2_r
- >>> from sympy.diffgeom import Differential
- >>> from sympy import pprint
- >>> fx, fy = R2_r.base_scalars()
- >>> e_x, e_y = R2_r.base_vectors()
- >>> g = Function('g')
- >>> s_field = g(fx, fy)
- >>> dg = Differential(s_field)
- >>> dg
- d(g(x, y))
- >>> pprint(dg(e_x))
- / d \|
- |---(g(xi, y))||
- \dxi /|xi=x
- >>> pprint(dg(e_y))
- / d \|
- |---(g(x, xi))||
- \dxi /|xi=y
- Applying the exterior derivative operator twice always results in:
- >>> Differential(dg)
- 0
- """
- is_commutative = False
- def __new__(cls, form_field):
- if contravariant_order(form_field):
- raise ValueError(
- 'A vector field was supplied as an argument to Differential.')
- if isinstance(form_field, Differential):
- return S.Zero
- else:
- obj = super().__new__(cls, form_field)
- obj._form_field = form_field # deprecated assignment
- return obj
- @property
- def form_field(self):
- return self.args[0]
- def __call__(self, *vector_fields):
- """Apply on a list of vector_fields.
- Explanation
- ===========
- If the number of vector fields supplied is not equal to 1 + the order of
- the form field inside the differential the result is undefined.
- For 1-forms (i.e. differentials of scalar fields) the evaluation is
- done as `df(v)=v(f)`. However if `v` is ``None`` instead of a vector
- field, the differential is returned unchanged. This is done in order to
- permit partial contractions for higher forms.
- In the general case the evaluation is done by applying the form field
- inside the differential on a list with one less elements than the number
- of elements in the original list. Lowering the number of vector fields
- is achieved through replacing each pair of fields by their
- commutator.
- If the arguments are not vectors or ``None``s an error is raised.
- """
- if any((contravariant_order(a) != 1 or covariant_order(a)) and a is not None
- for a in vector_fields):
- raise ValueError('The arguments supplied to Differential should be vector fields or Nones.')
- k = len(vector_fields)
- if k == 1:
- if vector_fields[0]:
- return vector_fields[0].rcall(self._form_field)
- return self
- else:
- # For higher form it is more complicated:
- # Invariant formula:
- # https://en.wikipedia.org/wiki/Exterior_derivative#Invariant_formula
- # df(v1, ... vn) = +/- vi(f(v1..no i..vn))
- # +/- f([vi,vj],v1..no i, no j..vn)
- f = self._form_field
- v = vector_fields
- ret = 0
- for i in range(k):
- t = v[i].rcall(f.rcall(*v[:i] + v[i + 1:]))
- ret += (-1)**i*t
- for j in range(i + 1, k):
- c = Commutator(v[i], v[j])
- if c: # TODO this is ugly - the Commutator can be Zero and
- # this causes the next line to fail
- t = f.rcall(*(c,) + v[:i] + v[i + 1:j] + v[j + 1:])
- ret += (-1)**(i + j)*t
- return ret
- class TensorProduct(Expr):
- """Tensor product of forms.
- Explanation
- ===========
- The tensor product permits the creation of multilinear functionals (i.e.
- higher order tensors) out of lower order fields (e.g. 1-forms and vector
- fields). However, the higher tensors thus created lack the interesting
- features provided by the other type of product, the wedge product, namely
- they are not antisymmetric and hence are not form fields.
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2_r
- >>> from sympy.diffgeom import TensorProduct
- >>> fx, fy = R2_r.base_scalars()
- >>> e_x, e_y = R2_r.base_vectors()
- >>> dx, dy = R2_r.base_oneforms()
- >>> TensorProduct(dx, dy)(e_x, e_y)
- 1
- >>> TensorProduct(dx, dy)(e_y, e_x)
- 0
- >>> TensorProduct(dx, fx*dy)(fx*e_x, e_y)
- x**2
- >>> TensorProduct(e_x, e_y)(fx**2, fy**2)
- 4*x*y
- >>> TensorProduct(e_y, dx)(fy)
- dx
- You can nest tensor products.
- >>> tp1 = TensorProduct(dx, dy)
- >>> TensorProduct(tp1, dx)(e_x, e_y, e_x)
- 1
- You can make partial contraction for instance when 'raising an index'.
- Putting ``None`` in the second argument of ``rcall`` means that the
- respective position in the tensor product is left as it is.
- >>> TP = TensorProduct
- >>> metric = TP(dx, dx) + 3*TP(dy, dy)
- >>> metric.rcall(e_y, None)
- 3*dy
- Or automatically pad the args with ``None`` without specifying them.
- >>> metric.rcall(e_y)
- 3*dy
- """
- def __new__(cls, *args):
- scalar = Mul(*[m for m in args if covariant_order(m) + contravariant_order(m) == 0])
- multifields = [m for m in args if covariant_order(m) + contravariant_order(m)]
- if multifields:
- if len(multifields) == 1:
- return scalar*multifields[0]
- return scalar*super().__new__(cls, *multifields)
- else:
- return scalar
- def __call__(self, *fields):
- """Apply on a list of fields.
- If the number of input fields supplied is not equal to the order of
- the tensor product field, the list of arguments is padded with ``None``'s.
- The list of arguments is divided in sublists depending on the order of
- the forms inside the tensor product. The sublists are provided as
- arguments to these forms and the resulting expressions are given to the
- constructor of ``TensorProduct``.
- """
- tot_order = covariant_order(self) + contravariant_order(self)
- tot_args = len(fields)
- if tot_args != tot_order:
- fields = list(fields) + [None]*(tot_order - tot_args)
- orders = [covariant_order(f) + contravariant_order(f) for f in self._args]
- indices = [sum(orders[:i + 1]) for i in range(len(orders) - 1)]
- fields = [fields[i:j] for i, j in zip([0] + indices, indices + [None])]
- multipliers = [t[0].rcall(*t[1]) for t in zip(self._args, fields)]
- return TensorProduct(*multipliers)
- class WedgeProduct(TensorProduct):
- """Wedge product of forms.
- Explanation
- ===========
- In the context of integration only completely antisymmetric forms make
- sense. The wedge product permits the creation of such forms.
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2_r
- >>> from sympy.diffgeom import WedgeProduct
- >>> fx, fy = R2_r.base_scalars()
- >>> e_x, e_y = R2_r.base_vectors()
- >>> dx, dy = R2_r.base_oneforms()
- >>> WedgeProduct(dx, dy)(e_x, e_y)
- 1
- >>> WedgeProduct(dx, dy)(e_y, e_x)
- -1
- >>> WedgeProduct(dx, fx*dy)(fx*e_x, e_y)
- x**2
- >>> WedgeProduct(e_x, e_y)(fy, None)
- -e_x
- You can nest wedge products.
- >>> wp1 = WedgeProduct(dx, dy)
- >>> WedgeProduct(wp1, dx)(e_x, e_y, e_x)
- 0
- """
- # TODO the calculation of signatures is slow
- # TODO you do not need all these permutations (neither the prefactor)
- def __call__(self, *fields):
- """Apply on a list of vector_fields.
- The expression is rewritten internally in terms of tensor products and evaluated."""
- orders = (covariant_order(e) + contravariant_order(e) for e in self.args)
- mul = 1/Mul(*(factorial(o) for o in orders))
- perms = permutations(fields)
- perms_par = (Permutation(
- p).signature() for p in permutations(range(len(fields))))
- tensor_prod = TensorProduct(*self.args)
- return mul*Add(*[tensor_prod(*p[0])*p[1] for p in zip(perms, perms_par)])
- class LieDerivative(Expr):
- """Lie derivative with respect to a vector field.
- Explanation
- ===========
- The transport operator that defines the Lie derivative is the pushforward of
- the field to be derived along the integral curve of the field with respect
- to which one derives.
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2_r, R2_p
- >>> from sympy.diffgeom import (LieDerivative, TensorProduct)
- >>> fx, fy = R2_r.base_scalars()
- >>> e_x, e_y = R2_r.base_vectors()
- >>> e_rho, e_theta = R2_p.base_vectors()
- >>> dx, dy = R2_r.base_oneforms()
- >>> LieDerivative(e_x, fy)
- 0
- >>> LieDerivative(e_x, fx)
- 1
- >>> LieDerivative(e_x, e_x)
- 0
- The Lie derivative of a tensor field by another tensor field is equal to
- their commutator:
- >>> LieDerivative(e_x, e_rho)
- Commutator(e_x, e_rho)
- >>> LieDerivative(e_x + e_y, fx)
- 1
- >>> tp = TensorProduct(dx, dy)
- >>> LieDerivative(e_x, tp)
- LieDerivative(e_x, TensorProduct(dx, dy))
- >>> LieDerivative(e_x, tp)
- LieDerivative(e_x, TensorProduct(dx, dy))
- """
- def __new__(cls, v_field, expr):
- expr_form_ord = covariant_order(expr)
- if contravariant_order(v_field) != 1 or covariant_order(v_field):
- raise ValueError('Lie derivatives are defined only with respect to'
- ' vector fields. The supplied argument was not a '
- 'vector field.')
- if expr_form_ord > 0:
- obj = super().__new__(cls, v_field, expr)
- # deprecated assignments
- obj._v_field = v_field
- obj._expr = expr
- return obj
- if expr.atoms(BaseVectorField):
- return Commutator(v_field, expr)
- else:
- return v_field.rcall(expr)
- @property
- def v_field(self):
- return self.args[0]
- @property
- def expr(self):
- return self.args[1]
- def __call__(self, *args):
- v = self.v_field
- expr = self.expr
- lead_term = v(expr(*args))
- rest = Add(*[Mul(*args[:i] + (Commutator(v, args[i]),) + args[i + 1:])
- for i in range(len(args))])
- return lead_term - rest
- class BaseCovarDerivativeOp(Expr):
- """Covariant derivative operator with respect to a base vector.
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2_r
- >>> from sympy.diffgeom import BaseCovarDerivativeOp
- >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct
- >>> TP = TensorProduct
- >>> fx, fy = R2_r.base_scalars()
- >>> e_x, e_y = R2_r.base_vectors()
- >>> dx, dy = R2_r.base_oneforms()
- >>> ch = metric_to_Christoffel_2nd(TP(dx, dx) + TP(dy, dy))
- >>> ch
- [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]
- >>> cvd = BaseCovarDerivativeOp(R2_r, 0, ch)
- >>> cvd(fx)
- 1
- >>> cvd(fx*e_x)
- e_x
- """
- def __new__(cls, coord_sys, index, christoffel):
- index = _sympify(index)
- christoffel = ImmutableDenseNDimArray(christoffel)
- obj = super().__new__(cls, coord_sys, index, christoffel)
- # deprecated assignments
- obj._coord_sys = coord_sys
- obj._index = index
- obj._christoffel = christoffel
- return obj
- @property
- def coord_sys(self):
- return self.args[0]
- @property
- def index(self):
- return self.args[1]
- @property
- def christoffel(self):
- return self.args[2]
- def __call__(self, field):
- """Apply on a scalar field.
- The action of a vector field on a scalar field is a directional
- differentiation.
- If the argument is not a scalar field the behaviour is undefined.
- """
- if covariant_order(field) != 0:
- raise NotImplementedError()
- field = vectors_in_basis(field, self._coord_sys)
- wrt_vector = self._coord_sys.base_vector(self._index)
- wrt_scalar = self._coord_sys.coord_function(self._index)
- vectors = list(field.atoms(BaseVectorField))
- # First step: replace all vectors with something susceptible to
- # derivation and do the derivation
- # TODO: you need a real dummy function for the next line
- d_funcs = [Function('_#_%s' % i)(wrt_scalar) for i,
- b in enumerate(vectors)]
- d_result = field.subs(list(zip(vectors, d_funcs)))
- d_result = wrt_vector(d_result)
- # Second step: backsubstitute the vectors in
- d_result = d_result.subs(list(zip(d_funcs, vectors)))
- # Third step: evaluate the derivatives of the vectors
- derivs = []
- for v in vectors:
- d = Add(*[(self._christoffel[k, wrt_vector._index, v._index]
- *v._coord_sys.base_vector(k))
- for k in range(v._coord_sys.dim)])
- derivs.append(d)
- to_subs = [wrt_vector(d) for d in d_funcs]
- # XXX: This substitution can fail when there are Dummy symbols and the
- # cache is disabled: https://github.com/sympy/sympy/issues/17794
- result = d_result.subs(list(zip(to_subs, derivs)))
- # Remove the dummies
- result = result.subs(list(zip(d_funcs, vectors)))
- return result.doit()
- class CovarDerivativeOp(Expr):
- """Covariant derivative operator.
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2_r
- >>> from sympy.diffgeom import CovarDerivativeOp
- >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct
- >>> TP = TensorProduct
- >>> fx, fy = R2_r.base_scalars()
- >>> e_x, e_y = R2_r.base_vectors()
- >>> dx, dy = R2_r.base_oneforms()
- >>> ch = metric_to_Christoffel_2nd(TP(dx, dx) + TP(dy, dy))
- >>> ch
- [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]
- >>> cvd = CovarDerivativeOp(fx*e_x, ch)
- >>> cvd(fx)
- x
- >>> cvd(fx*e_x)
- x*e_x
- """
- def __new__(cls, wrt, christoffel):
- if len({v._coord_sys for v in wrt.atoms(BaseVectorField)}) > 1:
- raise NotImplementedError()
- if contravariant_order(wrt) != 1 or covariant_order(wrt):
- raise ValueError('Covariant derivatives are defined only with '
- 'respect to vector fields. The supplied argument '
- 'was not a vector field.')
- christoffel = ImmutableDenseNDimArray(christoffel)
- obj = super().__new__(cls, wrt, christoffel)
- # deprecated assignments
- obj._wrt = wrt
- obj._christoffel = christoffel
- return obj
- @property
- def wrt(self):
- return self.args[0]
- @property
- def christoffel(self):
- return self.args[1]
- def __call__(self, field):
- vectors = list(self._wrt.atoms(BaseVectorField))
- base_ops = [BaseCovarDerivativeOp(v._coord_sys, v._index, self._christoffel)
- for v in vectors]
- return self._wrt.subs(list(zip(vectors, base_ops))).rcall(field)
- ###############################################################################
- # Integral curves on vector fields
- ###############################################################################
- def intcurve_series(vector_field, param, start_point, n=6, coord_sys=None, coeffs=False):
- r"""Return the series expansion for an integral curve of the field.
- Explanation
- ===========
- Integral curve is a function `\gamma` taking a parameter in `R` to a point
- in the manifold. It verifies the equation:
- `V(f)\big(\gamma(t)\big) = \frac{d}{dt}f\big(\gamma(t)\big)`
- where the given ``vector_field`` is denoted as `V`. This holds for any
- value `t` for the parameter and any scalar field `f`.
- This equation can also be decomposed of a basis of coordinate functions
- `V(f_i)\big(\gamma(t)\big) = \frac{d}{dt}f_i\big(\gamma(t)\big) \quad \forall i`
- This function returns a series expansion of `\gamma(t)` in terms of the
- coordinate system ``coord_sys``. The equations and expansions are necessarily
- done in coordinate-system-dependent way as there is no other way to
- represent movement between points on the manifold (i.e. there is no such
- thing as a difference of points for a general manifold).
- Parameters
- ==========
- vector_field
- the vector field for which an integral curve will be given
- param
- the argument of the function `\gamma` from R to the curve
- start_point
- the point which corresponds to `\gamma(0)`
- n
- the order to which to expand
- coord_sys
- the coordinate system in which to expand
- coeffs (default False) - if True return a list of elements of the expansion
- Examples
- ========
- Use the predefined R2 manifold:
- >>> from sympy.abc import t, x, y
- >>> from sympy.diffgeom.rn import R2_p, R2_r
- >>> from sympy.diffgeom import intcurve_series
- Specify a starting point and a vector field:
- >>> start_point = R2_r.point([x, y])
- >>> vector_field = R2_r.e_x
- Calculate the series:
- >>> intcurve_series(vector_field, t, start_point, n=3)
- Matrix([
- [t + x],
- [ y]])
- Or get the elements of the expansion in a list:
- >>> series = intcurve_series(vector_field, t, start_point, n=3, coeffs=True)
- >>> series[0]
- Matrix([
- [x],
- [y]])
- >>> series[1]
- Matrix([
- [t],
- [0]])
- >>> series[2]
- Matrix([
- [0],
- [0]])
- The series in the polar coordinate system:
- >>> series = intcurve_series(vector_field, t, start_point,
- ... n=3, coord_sys=R2_p, coeffs=True)
- >>> series[0]
- Matrix([
- [sqrt(x**2 + y**2)],
- [ atan2(y, x)]])
- >>> series[1]
- Matrix([
- [t*x/sqrt(x**2 + y**2)],
- [ -t*y/(x**2 + y**2)]])
- >>> series[2]
- Matrix([
- [t**2*(-x**2/(x**2 + y**2)**(3/2) + 1/sqrt(x**2 + y**2))/2],
- [ t**2*x*y/(x**2 + y**2)**2]])
- See Also
- ========
- intcurve_diffequ
- """
- if contravariant_order(vector_field) != 1 or covariant_order(vector_field):
- raise ValueError('The supplied field was not a vector field.')
- def iter_vfield(scalar_field, i):
- """Return ``vector_field`` called `i` times on ``scalar_field``."""
- return reduce(lambda s, v: v.rcall(s), [vector_field, ]*i, scalar_field)
- def taylor_terms_per_coord(coord_function):
- """Return the series for one of the coordinates."""
- return [param**i*iter_vfield(coord_function, i).rcall(start_point)/factorial(i)
- for i in range(n)]
- coord_sys = coord_sys if coord_sys else start_point._coord_sys
- coord_functions = coord_sys.coord_functions()
- taylor_terms = [taylor_terms_per_coord(f) for f in coord_functions]
- if coeffs:
- return [Matrix(t) for t in zip(*taylor_terms)]
- else:
- return Matrix([sum(c) for c in taylor_terms])
- def intcurve_diffequ(vector_field, param, start_point, coord_sys=None):
- r"""Return the differential equation for an integral curve of the field.
- Explanation
- ===========
- Integral curve is a function `\gamma` taking a parameter in `R` to a point
- in the manifold. It verifies the equation:
- `V(f)\big(\gamma(t)\big) = \frac{d}{dt}f\big(\gamma(t)\big)`
- where the given ``vector_field`` is denoted as `V`. This holds for any
- value `t` for the parameter and any scalar field `f`.
- This function returns the differential equation of `\gamma(t)` in terms of the
- coordinate system ``coord_sys``. The equations and expansions are necessarily
- done in coordinate-system-dependent way as there is no other way to
- represent movement between points on the manifold (i.e. there is no such
- thing as a difference of points for a general manifold).
- Parameters
- ==========
- vector_field
- the vector field for which an integral curve will be given
- param
- the argument of the function `\gamma` from R to the curve
- start_point
- the point which corresponds to `\gamma(0)`
- coord_sys
- the coordinate system in which to give the equations
- Returns
- =======
- a tuple of (equations, initial conditions)
- Examples
- ========
- Use the predefined R2 manifold:
- >>> from sympy.abc import t
- >>> from sympy.diffgeom.rn import R2, R2_p, R2_r
- >>> from sympy.diffgeom import intcurve_diffequ
- Specify a starting point and a vector field:
- >>> start_point = R2_r.point([0, 1])
- >>> vector_field = -R2.y*R2.e_x + R2.x*R2.e_y
- Get the equation:
- >>> equations, init_cond = intcurve_diffequ(vector_field, t, start_point)
- >>> equations
- [f_1(t) + Derivative(f_0(t), t), -f_0(t) + Derivative(f_1(t), t)]
- >>> init_cond
- [f_0(0), f_1(0) - 1]
- The series in the polar coordinate system:
- >>> equations, init_cond = intcurve_diffequ(vector_field, t, start_point, R2_p)
- >>> equations
- [Derivative(f_0(t), t), Derivative(f_1(t), t) - 1]
- >>> init_cond
- [f_0(0) - 1, f_1(0) - pi/2]
- See Also
- ========
- intcurve_series
- """
- if contravariant_order(vector_field) != 1 or covariant_order(vector_field):
- raise ValueError('The supplied field was not a vector field.')
- coord_sys = coord_sys if coord_sys else start_point._coord_sys
- gammas = [Function('f_%d' % i)(param) for i in range(
- start_point._coord_sys.dim)]
- arbitrary_p = Point(coord_sys, gammas)
- coord_functions = coord_sys.coord_functions()
- equations = [simplify(diff(cf.rcall(arbitrary_p), param) - vector_field.rcall(cf).rcall(arbitrary_p))
- for cf in coord_functions]
- init_cond = [simplify(cf.rcall(arbitrary_p).subs(param, 0) - cf.rcall(start_point))
- for cf in coord_functions]
- return equations, init_cond
- ###############################################################################
- # Helpers
- ###############################################################################
- def dummyfy(args, exprs):
- # TODO Is this a good idea?
- d_args = Matrix([s.as_dummy() for s in args])
- reps = dict(zip(args, d_args))
- d_exprs = Matrix([_sympify(expr).subs(reps) for expr in exprs])
- return d_args, d_exprs
- ###############################################################################
- # Helpers
- ###############################################################################
- def contravariant_order(expr, _strict=False):
- """Return the contravariant order of an expression.
- Examples
- ========
- >>> from sympy.diffgeom import contravariant_order
- >>> from sympy.diffgeom.rn import R2
- >>> from sympy.abc import a
- >>> contravariant_order(a)
- 0
- >>> contravariant_order(a*R2.x + 2)
- 0
- >>> contravariant_order(a*R2.x*R2.e_y + R2.e_x)
- 1
- """
- # TODO move some of this to class methods.
- # TODO rewrite using the .as_blah_blah methods
- if isinstance(expr, Add):
- orders = [contravariant_order(e) for e in expr.args]
- if len(set(orders)) != 1:
- raise ValueError('Misformed expression containing contravariant fields of varying order.')
- return orders[0]
- elif isinstance(expr, Mul):
- orders = [contravariant_order(e) for e in expr.args]
- not_zero = [o for o in orders if o != 0]
- if len(not_zero) > 1:
- raise ValueError('Misformed expression containing multiplication between vectors.')
- return 0 if not not_zero else not_zero[0]
- elif isinstance(expr, Pow):
- if covariant_order(expr.base) or covariant_order(expr.exp):
- raise ValueError(
- 'Misformed expression containing a power of a vector.')
- return 0
- elif isinstance(expr, BaseVectorField):
- return 1
- elif isinstance(expr, TensorProduct):
- return sum(contravariant_order(a) for a in expr.args)
- elif not _strict or expr.atoms(BaseScalarField):
- return 0
- else: # If it does not contain anything related to the diffgeom module and it is _strict
- return -1
- def covariant_order(expr, _strict=False):
- """Return the covariant order of an expression.
- Examples
- ========
- >>> from sympy.diffgeom import covariant_order
- >>> from sympy.diffgeom.rn import R2
- >>> from sympy.abc import a
- >>> covariant_order(a)
- 0
- >>> covariant_order(a*R2.x + 2)
- 0
- >>> covariant_order(a*R2.x*R2.dy + R2.dx)
- 1
- """
- # TODO move some of this to class methods.
- # TODO rewrite using the .as_blah_blah methods
- if isinstance(expr, Add):
- orders = [covariant_order(e) for e in expr.args]
- if len(set(orders)) != 1:
- raise ValueError('Misformed expression containing form fields of varying order.')
- return orders[0]
- elif isinstance(expr, Mul):
- orders = [covariant_order(e) for e in expr.args]
- not_zero = [o for o in orders if o != 0]
- if len(not_zero) > 1:
- raise ValueError('Misformed expression containing multiplication between forms.')
- return 0 if not not_zero else not_zero[0]
- elif isinstance(expr, Pow):
- if covariant_order(expr.base) or covariant_order(expr.exp):
- raise ValueError(
- 'Misformed expression containing a power of a form.')
- return 0
- elif isinstance(expr, Differential):
- return covariant_order(*expr.args) + 1
- elif isinstance(expr, TensorProduct):
- return sum(covariant_order(a) for a in expr.args)
- elif not _strict or expr.atoms(BaseScalarField):
- return 0
- else: # If it does not contain anything related to the diffgeom module and it is _strict
- return -1
- ###############################################################################
- # Coordinate transformation functions
- ###############################################################################
- def vectors_in_basis(expr, to_sys):
- """Transform all base vectors in base vectors of a specified coord basis.
- While the new base vectors are in the new coordinate system basis, any
- coefficients are kept in the old system.
- Examples
- ========
- >>> from sympy.diffgeom import vectors_in_basis
- >>> from sympy.diffgeom.rn import R2_r, R2_p
- >>> vectors_in_basis(R2_r.e_x, R2_p)
- -y*e_theta/(x**2 + y**2) + x*e_rho/sqrt(x**2 + y**2)
- >>> vectors_in_basis(R2_p.e_r, R2_r)
- sin(theta)*e_y + cos(theta)*e_x
- """
- vectors = list(expr.atoms(BaseVectorField))
- new_vectors = []
- for v in vectors:
- cs = v._coord_sys
- jac = cs.jacobian(to_sys, cs.coord_functions())
- new = (jac.T*Matrix(to_sys.base_vectors()))[v._index]
- new_vectors.append(new)
- return expr.subs(list(zip(vectors, new_vectors)))
- ###############################################################################
- # Coordinate-dependent functions
- ###############################################################################
- def twoform_to_matrix(expr):
- """Return the matrix representing the twoform.
- For the twoform `w` return the matrix `M` such that `M[i,j]=w(e_i, e_j)`,
- where `e_i` is the i-th base vector field for the coordinate system in
- which the expression of `w` is given.
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2
- >>> from sympy.diffgeom import twoform_to_matrix, TensorProduct
- >>> TP = TensorProduct
- >>> twoform_to_matrix(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
- Matrix([
- [1, 0],
- [0, 1]])
- >>> twoform_to_matrix(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
- Matrix([
- [x, 0],
- [0, 1]])
- >>> twoform_to_matrix(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy) - TP(R2.dx, R2.dy)/2)
- Matrix([
- [ 1, 0],
- [-1/2, 1]])
- """
- if covariant_order(expr) != 2 or contravariant_order(expr):
- raise ValueError('The input expression is not a two-form.')
- coord_sys = _find_coords(expr)
- if len(coord_sys) != 1:
- raise ValueError('The input expression concerns more than one '
- 'coordinate systems, hence there is no unambiguous '
- 'way to choose a coordinate system for the matrix.')
- coord_sys = coord_sys.pop()
- vectors = coord_sys.base_vectors()
- expr = expr.expand()
- matrix_content = [[expr.rcall(v1, v2) for v1 in vectors]
- for v2 in vectors]
- return Matrix(matrix_content)
- def metric_to_Christoffel_1st(expr):
- """Return the nested list of Christoffel symbols for the given metric.
- This returns the Christoffel symbol of first kind that represents the
- Levi-Civita connection for the given metric.
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2
- >>> from sympy.diffgeom import metric_to_Christoffel_1st, TensorProduct
- >>> TP = TensorProduct
- >>> metric_to_Christoffel_1st(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
- [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]
- >>> metric_to_Christoffel_1st(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
- [[[1/2, 0], [0, 0]], [[0, 0], [0, 0]]]
- """
- matrix = twoform_to_matrix(expr)
- if not matrix.is_symmetric():
- raise ValueError(
- 'The two-form representing the metric is not symmetric.')
- coord_sys = _find_coords(expr).pop()
- deriv_matrices = [matrix.applyfunc(d) for d in coord_sys.base_vectors()]
- indices = list(range(coord_sys.dim))
- christoffel = [[[(deriv_matrices[k][i, j] + deriv_matrices[j][i, k] - deriv_matrices[i][j, k])/2
- for k in indices]
- for j in indices]
- for i in indices]
- return ImmutableDenseNDimArray(christoffel)
- def metric_to_Christoffel_2nd(expr):
- """Return the nested list of Christoffel symbols for the given metric.
- This returns the Christoffel symbol of second kind that represents the
- Levi-Civita connection for the given metric.
- Examples
- ========
- >>> from sympy.diffgeom.rn import R2
- >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct
- >>> TP = TensorProduct
- >>> metric_to_Christoffel_2nd(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
- [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]
- >>> metric_to_Christoffel_2nd(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
- [[[1/(2*x), 0], [0, 0]], [[0, 0], [0, 0]]]
- """
- ch_1st = metric_to_Christoffel_1st(expr)
- coord_sys = _find_coords(expr).pop()
- indices = list(range(coord_sys.dim))
- # XXX workaround, inverting a matrix does not work if it contains non
- # symbols
- #matrix = twoform_to_matrix(expr).inv()
- matrix = twoform_to_matrix(expr)
- s_fields = set()
- for e in matrix:
- s_fields.update(e.atoms(BaseScalarField))
- s_fields = list(s_fields)
- dums = coord_sys.symbols
- matrix = matrix.subs(list(zip(s_fields, dums))).inv().subs(list(zip(dums, s_fields)))
- # XXX end of workaround
- christoffel = [[[Add(*[matrix[i, l]*ch_1st[l, j, k] for l in indices])
- for k in indices]
- for j in indices]
- for i in indices]
- return ImmutableDenseNDimArray(christoffel)
- def metric_to_Riemann_components(expr):
- """Return the components of the Riemann tensor expressed in a given basis.
- Given a metric it calculates the components of the Riemann tensor in the
- canonical basis of the coordinate system in which the metric expression is
- given.
- Examples
- ========
- >>> from sympy import exp
- >>> from sympy.diffgeom.rn import R2
- >>> from sympy.diffgeom import metric_to_Riemann_components, TensorProduct
- >>> TP = TensorProduct
- >>> metric_to_Riemann_components(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
- [[[[0, 0], [0, 0]], [[0, 0], [0, 0]]], [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]]
- >>> non_trivial_metric = exp(2*R2.r)*TP(R2.dr, R2.dr) + \
- R2.r**2*TP(R2.dtheta, R2.dtheta)
- >>> non_trivial_metric
- exp(2*rho)*TensorProduct(drho, drho) + rho**2*TensorProduct(dtheta, dtheta)
- >>> riemann = metric_to_Riemann_components(non_trivial_metric)
- >>> riemann[0, :, :, :]
- [[[0, 0], [0, 0]], [[0, exp(-2*rho)*rho], [-exp(-2*rho)*rho, 0]]]
- >>> riemann[1, :, :, :]
- [[[0, -1/rho], [1/rho, 0]], [[0, 0], [0, 0]]]
- """
- ch_2nd = metric_to_Christoffel_2nd(expr)
- coord_sys = _find_coords(expr).pop()
- indices = list(range(coord_sys.dim))
- deriv_ch = [[[[d(ch_2nd[i, j, k])
- for d in coord_sys.base_vectors()]
- for k in indices]
- for j in indices]
- for i in indices]
- riemann_a = [[[[deriv_ch[rho][sig][nu][mu] - deriv_ch[rho][sig][mu][nu]
- for nu in indices]
- for mu in indices]
- for sig in indices]
- for rho in indices]
- 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])
- for nu in indices]
- for mu in indices]
- for sig in indices]
- for rho in indices]
- riemann = [[[[riemann_a[rho][sig][mu][nu] + riemann_b[rho][sig][mu][nu]
- for nu in indices]
- for mu in indices]
- for sig in indices]
- for rho in indices]
- return ImmutableDenseNDimArray(riemann)
- def metric_to_Ricci_components(expr):
- """Return the components of the Ricci tensor expressed in a given basis.
- Given a metric it calculates the components of the Ricci tensor in the
- canonical basis of the coordinate system in which the metric expression is
- given.
- Examples
- ========
- >>> from sympy import exp
- >>> from sympy.diffgeom.rn import R2
- >>> from sympy.diffgeom import metric_to_Ricci_components, TensorProduct
- >>> TP = TensorProduct
- >>> metric_to_Ricci_components(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy))
- [[0, 0], [0, 0]]
- >>> non_trivial_metric = exp(2*R2.r)*TP(R2.dr, R2.dr) + \
- R2.r**2*TP(R2.dtheta, R2.dtheta)
- >>> non_trivial_metric
- exp(2*rho)*TensorProduct(drho, drho) + rho**2*TensorProduct(dtheta, dtheta)
- >>> metric_to_Ricci_components(non_trivial_metric)
- [[1/rho, 0], [0, exp(-2*rho)*rho]]
- """
- riemann = metric_to_Riemann_components(expr)
- coord_sys = _find_coords(expr).pop()
- indices = list(range(coord_sys.dim))
- ricci = [[Add(*[riemann[k, i, k, j] for k in indices])
- for j in indices]
- for i in indices]
- return ImmutableDenseNDimArray(ricci)
- ###############################################################################
- # Classes for deprecation
- ###############################################################################
- class _deprecated_container:
- # This class gives deprecation warning.
- # When deprecated features are completely deleted, this should be removed as well.
- # See https://github.com/sympy/sympy/pull/19368
- def __init__(self, message, data):
- super().__init__(data)
- self.message = message
- def warn(self):
- sympy_deprecation_warning(
- self.message,
- deprecated_since_version="1.7",
- active_deprecations_target="deprecated-diffgeom-mutable",
- stacklevel=4
- )
- def __iter__(self):
- self.warn()
- return super().__iter__()
- def __getitem__(self, key):
- self.warn()
- return super().__getitem__(key)
- def __contains__(self, key):
- self.warn()
- return super().__contains__(key)
- class _deprecated_list(_deprecated_container, list):
- pass
- class _deprecated_dict(_deprecated_container, dict):
- pass
- # Import at end to avoid cyclic imports
- from sympy.simplify.simplify import simplify
|