def __init__(self, vector_field_module, degree, name=None, latex_name=None): r""" Construct a differential form. TEST:: sage: M = Manifold(2, 'M') sage: U = M.open_subset('U') ; V = M.open_subset('V') sage: M.declare_union(U,V) # M is the union of U and V sage: c_xy.<x,y> = U.chart() ; c_uv.<u,v> = V.chart() sage: transf = c_xy.transition_map(c_uv, (x+y, x-y), ....: intersection_name='W', restrictions1= x>0, ....: restrictions2= u+v>0) sage: inv = transf.inverse() sage: W = U.intersection(V) sage: eU = c_xy.frame() ; eV = c_uv.frame() sage: a = M.diff_form(2, name='a') ; a 2-form 'a' on the 2-dimensional manifold 'M' sage: a[eU,0,1] = x*y^2 + 2*x sage: a.add_comp_by_continuation(eV, W, c_uv) """ TensorField.__init__( self, vector_field_module, (0, degree), name=name, latex_name=latex_name, antisym=range(degree), parent=vector_field_module.dual_exterior_power(degree)) self._init_derived() # initialization of derived quantities
def __init__(self, manifold, name, signature=None, latex_name=None): SymBilinFormField.__init__(self, manifold, name, latex_name) # signature: ndim = self.manifold.dim if signature is None: signature = ndim else: if not isinstance(signature, (int, Integer)): raise TypeError("The metric signature must be an integer.") if (signature < -ndim) or (signature > ndim): raise ValueError("Metric signature out of range.") if (signature + ndim) % 2 == 1: if ndim % 2 == 0: raise ValueError("The metric signature must be even.") else: raise ValueError("The metric signature must be odd.") self._signature = signature # the pair (n_+, n_-): self._signature_pm = ((ndim + signature) / 2, (ndim - signature) / 2) self._indic_signat = 1 - 2 * (self._signature_pm[1] % 2) # (-1)^n_- # inverse metric: inv_name = 'inv_' + self.name inv_latex_name = self.latex_name + r'^{-1}' self._inverse = TensorField(self.manifold, 2, 0, inv_name, inv_latex_name, sym=(0, 1)) self._connection = None # Levi-Civita connection (not set yet) self._weyl = None # Weyl tensor (not set yet) self._determinants = {} # determinants in various frames self._sqrt_abs_dets = {} # sqrt(abs(det g)) in various frames self._vol_forms = [] # volume form and associated tensors
def __init__(self, vector_field_module, name=None, latex_name=None): TensorField.__init__(self, vector_field_module, (1,0), name=name, latex_name=latex_name) # Initialization of derived quantities: TensorField._init_derived(self) # Initialization of list of quantities depending on self: self._init_dependencies()
def __init__(self, vector_field_module, name=None, latex_name=None, is_identity=False): if is_identity: if name is None: name = 'Id' if latex_name is None and name == 'Id': latex_name = r'\mathrm{Id}' TensorField.__init__(self, vector_field_module, (1, 1), name=name, latex_name=latex_name, parent=vector_field_module.general_linear_group()) self._is_identity = is_identity self._init_derived() # initialization of derived quantities # Specific initializations for the field of identity maps: if self._is_identity: self._inverse = self for dom in self._domain._subsets: if dom.is_manifestly_parallelizable(): fmodule = dom.vector_field_module() self._restrictions[dom] = fmodule.identity_map( name=name, latex_name=latex_name)
def _del_derived(self): r""" Delete the derived quantities. """ # First delete the derived quantities pertaining to the mother class: TensorField._del_derived(self) # then deletes the inverse automorphism: self._inverse = None
def __init__(self, vector_field_module, name=None, latex_name=None): TensorField.__init__(self, vector_field_module, (1, 0), name=name, latex_name=latex_name) # Initialization of derived quantities: TensorField._init_derived(self) # Initialization of list of quantities depending on self: self._init_dependencies()
def __init__(self, manifold, p, name=None, latex_name=None): TensorField.__init__(self, manifold, 0, p, name, latex_name, antisym=range(p)) self._init_derived() # initialization of derived quantities
def __init__(self, domain, p, name=None, latex_name=None): TensorField.__init__(self, domain, 0, p, name, latex_name, antisym=range(p)) DiffForm._init_derived(self) # initialization of derived quantities
def __call__(self, *arg): r""" Redefinition of :meth:`TensorField.__call__` to allow for a single vector argument. """ if len(arg) > 1: # the endomorphism acting as a type (1,1) tensor on a pair # (1-form, vector), returning a scalar: return TensorField.__call__(self, *arg) # the endomorphism acting as such, on a vector, returning a vector: vector = arg[0] if not isinstance(vector, VectorField): raise TypeError("The argument must be a vector field.") frame = self.common_frame(vector) t = self.components[frame] v = vector.components[frame] manif = self.manifold result = VectorField(self.domain) n = manif.dim si = manif.sindex for i in range(si, si + n): res = 0 for j in range(si, si + n): res += t[[i, j]] * v[[j]] result.set_comp(frame)[i] = res # Name of the output: result.name = None if self.name is not None and vector.name is not None: result.name = self.name + "(" + vector.name + ")" # LaTeX symbol for the output: result.latex_name = None if self.latex_name is not None and vector.latex_name is not None: result.latex_name = self.latex_name + r"\left(" + \ vector.latex_name + r"\right)" return result
def __call__(self, *arg): r""" Redefinition of :meth:`TensorField.__call__` to allow for a single vector argument. """ if len(arg) > 1: # the endomorphism acting as a type (1,1) tensor on a pair # (1-form, vector), returning a scalar: return TensorField.__call__(self, *arg) # the endomorphism acting as such, on a vector, returning a vector: vector = arg[0] if not isinstance(vector, VectorField): raise TypeError("The argument must be a vector field.") frame_name = self.common_frame(vector) t = self.components[frame_name] v = vector.components[frame_name] manif = self.manifold result = VectorField(self.domain) n = manif.dim si = manif.sindex for i in range(si, si+n): res = 0 for j in range(si, si+n): res += t[[i,j]]*v[[j]] result.set_comp(frame_name)[i] = res # Name of the output: result.name = None if self.name is not None and vector.name is not None: result.name = self.name + "(" + vector.name + ")" # LaTeX symbol for the output: result.latex_name = None if self.latex_name is not None and vector.latex_name is not None: result.latex_name = self.latex_name + r"\left(" + \ vector.latex_name + r"\right)" return result
def __call__(self, *arg): r""" Redefinition of :meth:`~sage.geometry.manifolds.tensorfield.TensorField.__call__` to allow for a proper treatment of the identity map and of the call with a single argument """ if self._is_identity: if len(arg) == 1: # The identity map acting as such, on a vector field: vector = arg[0] if vector._tensor_type != (1,0): raise TypeError("the argument must be a vector field") dom = self._domain.intersection(vector._domain) return vector.restrict(dom) elif len(arg) == 2: # self acting as a type-(1,1) tensor on a pair # (1-form, vector field), returning a scalar field: oneform = arg[0] vector = arg[1] dom = self._domain.intersection( oneform._domain).intersection(vector._domain) return oneform.restrict(dom)(vector.restrict(dom)) else: raise TypeError("wrong number of arguments") # Generic case if len(arg) == 1: raise NotImplementedError("__call__ with 1 arg not implemented yet") return TensorField.__call__(self, *arg)
def _init_derived(self): r""" Initialize the derived quantities """ # Initialization of quantities pertaining to the mother class: SymBilinFormField._init_derived(self) # inverse metric: inv_name = 'inv_' + self.name inv_latex_name = self.latex_name + r'^{-1}' self._inverse = TensorField(self.domain, 2, 0, inv_name, inv_latex_name, sym=(0,1)) self._connection = None # Levi-Civita connection (not set yet) self._weyl = None # Weyl tensor (not set yet) self._determinants = {} # determinants in various frames self._sqrt_abs_dets = {} # sqrt(abs(det g)) in various frames self._vol_forms = [] # volume form and associated tensors
def __call__(self, *arg): r""" Redefinition of :meth:`~sage.geometry.manifolds.tensorfield.TensorField.__call__` to allow for a proper treatment of the identity map and of the call with a single argument """ if self._is_identity: if len(arg) == 1: # The identity map acting as such, on a vector field: vector = arg[0] if vector._tensor_type != (1, 0): raise TypeError("the argument must be a vector field") dom = self._domain.intersection(vector._domain) return vector.restrict(dom) elif len(arg) == 2: # self acting as a type-(1,1) tensor on a pair # (1-form, vector field), returning a scalar field: oneform = arg[0] vector = arg[1] dom = self._domain.intersection(oneform._domain).intersection( vector._domain) return oneform.restrict(dom)(vector.restrict(dom)) else: raise TypeError("wrong number of arguments") # Generic case if len(arg) == 1: raise NotImplementedError( "__call__ with 1 arg not implemented yet") return TensorField.__call__(self, *arg)
def __init__(self, vector_field_module, name=None, latex_name=None, is_identity=False): if is_identity: if name is None: name = 'Id' if latex_name is None and name == 'Id': latex_name = r'\mathrm{Id}' TensorField.__init__(self, vector_field_module, (1,1), name=name, latex_name=latex_name, parent=vector_field_module.general_linear_group()) self._is_identity = is_identity self._init_derived() # initialization of derived quantities # Specific initializations for the field of identity maps: if self._is_identity: self._inverse = self for dom in self._domain._subsets: if dom.is_manifestly_parallelizable(): fmodule = dom.vector_field_module() self._restrictions[dom] = fmodule.identity_map(name=name, latex_name=latex_name)
def __mul__(self, other): r""" Redefinition of :meth:`~sage.geometry.manifolds.tensorfield.TensorField.__mul__` so that * dispatches either to automorphism composition or to the tensor product. EXAMPLES: """ if isinstance(other, AutomorphismField): return self._mul_(other) # general linear group law else: return TensorField.__mul__(self, other) # tensor product
def restrict(self, subdomain, dest_map=None): r""" Return the restriction of ``self`` to some subdomain. This is a redefinition of :meth:`sage.geometry.manifolds.tensorfield.TensorField.restrict` to take into account the identity map. INPUT: - ``subdomain`` -- open subset `U` of ``self._domain`` (must be an instance of :class:`~sage.geometry.manifolds.domain.ManifoldOpenSubset`) - ``dest_map`` -- (default: ``None``) destination map `\Phi:\ U \rightarrow V`, where `V` is a subdomain of ``self._codomain`` (type: :class:`~sage.geometry.manifolds.diffmapping.DiffMapping`) If None, the restriction of ``self._vmodule._dest_map`` to `U` is used. OUTPUT: - instance of :class:`AutomorphismField` representing the restriction. EXAMPLES: Restrictions of an automorphism field on the 2-sphere:: sage: M = Manifold(2, 'S^2', start_index=1) sage: U = M.open_subset('U') # the complement of the North pole sage: stereoN.<x,y> = U.chart() # stereographic coordinates from the North pole sage: eN = stereoN.frame() # the associated vector frame sage: V = M.open_subset('V') # the complement of the South pole sage: stereoS.<u,v> = V.chart() # stereographic coordinates from the South pole sage: eS = stereoS.frame() # the associated vector frame sage: transf = stereoN.transition_map(stereoS, (x/(x^2+y^2), y/(x^2+y^2)), intersection_name='W', \ restrictions1= x^2+y^2!=0, restrictions2= u^2+v^2!=0) sage: inv = transf.inverse() # transformation from stereoS to stereoN sage: W = U.intersection(V) # the complement of the North and South poles sage: stereoN_W = W.atlas()[0] # restriction of stereographic coord. from North pole to W sage: stereoS_W = W.atlas()[1] # restriction of stereographic coord. from South pole to W sage: eN_W = stereoN_W.frame() ; eS_W = stereoS_W.frame() sage: a = M.automorphism_field(name='a') ; a field of tangent-space automorphisms 'a' on the 2-dimensional manifold 'S^2' sage: a[eN,:] = [[1, atan(x^2+y^2)], [0,3]] sage: a.add_comp_by_continuation(eS, W, chart=stereoS) sage: a.restrict(U) field of tangent-space automorphisms 'a' on the open subset 'U' of the 2-dimensional manifold 'S^2' sage: a.restrict(U)[eN,:] [ 1 arctan(x^2 + y^2)] [ 0 3] sage: a.restrict(V) field of tangent-space automorphisms 'a' on the open subset 'V' of the 2-dimensional manifold 'S^2' sage: a.restrict(V)[eS,:] [ (u^4 + 10*u^2*v^2 + v^4 + 2*(u^3*v - u*v^3)*arctan(1/(u^2 + v^2)))/(u^4 + 2*u^2*v^2 + v^4) -(4*u^3*v - 4*u*v^3 + (u^4 - 2*u^2*v^2 + v^4)*arctan(1/(u^2 + v^2)))/(u^4 + 2*u^2*v^2 + v^4)] [ 4*(u^2*v^2*arctan(1/(u^2 + v^2)) - u^3*v + u*v^3)/(u^4 + 2*u^2*v^2 + v^4) (3*u^4 - 2*u^2*v^2 + 3*v^4 - 2*(u^3*v - u*v^3)*arctan(1/(u^2 + v^2)))/(u^4 + 2*u^2*v^2 + v^4)] sage: a.restrict(W) field of tangent-space automorphisms 'a' on the open subset 'W' of the 2-dimensional manifold 'S^2' sage: a.restrict(W)[eN_W,:] [ 1 arctan(x^2 + y^2)] [ 0 3] Restrictions of the field of tangent-space identity maps:: sage: id = M.tangent_identity_field() ; id field of tangent-space identity maps on the 2-dimensional manifold 'S^2' sage: id.restrict(U) field of tangent-space identity maps on the open subset 'U' of the 2-dimensional manifold 'S^2' sage: id.restrict(U)[eN,:] [1 0] [0 1] sage: id.restrict(V) field of tangent-space identity maps on the open subset 'V' of the 2-dimensional manifold 'S^2' sage: id.restrict(V)[eS,:] [1 0] [0 1] sage: id.restrict(W)[eN_W,:] [1 0] [0 1] sage: id.restrict(W)[eS_W,:] [1 0] [0 1] """ if subdomain == self._domain: return self if subdomain not in self._restrictions: if not self._is_identity: return TensorField.restrict(self, subdomain, dest_map=dest_map) # Special case of the identity map: if not subdomain.is_subset(self._domain): raise ValueError("The provided domain is not a subset of " + "the field's domain.") if dest_map is None: dest_map = self._vmodule._dest_map.restrict(subdomain) elif not dest_map._codomain.is_subset(self._ambient_domain): raise ValueError("Argument dest_map not compatible with " + "self._ambient_domain") smodule = subdomain.vector_field_module(dest_map=dest_map) self._restrictions[subdomain] = smodule.identity_map() return self._restrictions[subdomain]
class Metric(SymBilinFormField): r""" Base class for pseudo-Riemannian metrics on a differentiable manifold. INPUT: - ``manifold`` -- the manifold on which the metric is defined - ``name`` -- name given to the metric - ``signature`` -- (default: None) signature `S` of the metric as a single integer: `S = n_+ - n_-`, where `n_+` (resp. `n_-`) is the number of positive terms (resp. number of negative terms) in any diagonal writing of the metric components; if ``signature`` is not provided, `S` is set to the manifold's dimension (Riemannian signature) - ``latex_name`` -- (default: None) LaTeX symbol to denote the metric; if none, it is formed from ``name`` EXAMPLES: Metric on a 2-dimensional manifold:: sage: m = Manifold(2, 'M', start_index=1) sage: c_xy = Chart(m, 'x y', 'xy') sage: g = Metric(m, 'g') ; g pseudo-Riemannian metric 'g' on the 2-dimensional manifold 'M' sage: latex(g) g A metric is a special kind of tensor field and therefore inheritates all the properties from class :class:`TensorField`:: sage: isinstance(g, TensorField) True sage: g.tensor_type (0, 2) sage: g.symmetries() # g is symmetric: symmetry: (0, 1); no antisymmetry sage: isinstance(g, SymBilinFormField) # a metric is actually a field of symmetric bilinear forms: True Setting the metric components in the manifold's default frame:: sage: g[1,1], g[1,2], g[2,2] = 1+x, x*y, 1-x sage: g[:] [ x + 1 x*y] [ x*y -x + 1] sage: g.show() g = (x + 1) dx*dx + x*y dx*dy + x*y dy*dx + (-x + 1) dy*dy Metric components in a frame different from the manifold's default one:: sage: c_uv = Chart(m, 'u v', 'uv') # new chart on M sage: xy_to_uv = CoordChange(c_xy, c_uv, x+y, x-y) ; xy_to_uv coordinate change from chart 'xy' (x, y) to chart 'uv' (u, v) sage: uv_to_xy = xy_to_uv.inverse() ; uv_to_xy coordinate change from chart 'uv' (u, v) to chart 'xy' (x, y) sage: m.get_atlas() {'xy': chart 'xy' (x, y), 'uv': chart 'uv' (u, v)} sage: m.frames {'xy_b': coordinate basis 'xy_b' (d/dx,d/dy), 'uv_b': coordinate basis 'uv_b' (d/du,d/dv)} sage: g.comp('uv_b')[:] # metric components in frame 'uv_b' expressed in M's default chart (x,y) [ 1/2*x*y + 1/2 1/2*x] [ 1/2*x -1/2*x*y + 1/2] sage: g.show('uv_b') g = (1/2*x*y + 1/2) du*du + 1/2*x du*dv + 1/2*x dv*du + (-1/2*x*y + 1/2) dv*dv sage: g.comp('uv_b')[:, 'uv'] # metric components in frame 'uv_b' expressed in chart (u,v) [ 1/8*u^2 - 1/8*v^2 + 1/2 1/4*u + 1/4*v] [ 1/4*u + 1/4*v -1/8*u^2 + 1/8*v^2 + 1/2] sage: g.show('uv_b', 'uv') g = (1/8*u^2 - 1/8*v^2 + 1/2) du*du + (1/4*u + 1/4*v) du*dv + (1/4*u + 1/4*v) dv*du + (-1/8*u^2 + 1/8*v^2 + 1/2) dv*dv The inverse metric is obtained via :meth:`inverse`:: sage: ig = g.inverse() ; ig tensor field 'inv_g' of type (2,0) on the 2-dimensional manifold 'M' sage: ig[:] [ (x - 1)/(x^2*y^2 + x^2 - 1) x*y/(x^2*y^2 + x^2 - 1)] [ x*y/(x^2*y^2 + x^2 - 1) -(x + 1)/(x^2*y^2 + x^2 - 1)] sage: ig.show() inv_g = (x - 1)/(x^2*y^2 + x^2 - 1) d/dx*d/dx + x*y/(x^2*y^2 + x^2 - 1) d/dx*d/dy + x*y/(x^2*y^2 + x^2 - 1) d/dy*d/dx - (x + 1)/(x^2*y^2 + x^2 - 1) d/dy*d/dy """ def __init__(self, manifold, name, signature=None, latex_name=None): SymBilinFormField.__init__(self, manifold, name, latex_name) # signature: ndim = self.manifold.dim if signature is None: signature = ndim else: if not isinstance(signature, (int, Integer)): raise TypeError("The metric signature must be an integer.") if (signature < -ndim) or (signature > ndim): raise ValueError("Metric signature out of range.") if (signature + ndim) % 2 == 1: if ndim % 2 == 0: raise ValueError("The metric signature must be even.") else: raise ValueError("The metric signature must be odd.") self._signature = signature # the pair (n_+, n_-): self._signature_pm = ((ndim + signature) / 2, (ndim - signature) / 2) self._indic_signat = 1 - 2 * (self._signature_pm[1] % 2) # (-1)^n_- # inverse metric: inv_name = 'inv_' + self.name inv_latex_name = self.latex_name + r'^{-1}' self._inverse = TensorField(self.manifold, 2, 0, inv_name, inv_latex_name, sym=(0, 1)) self._connection = None # Levi-Civita connection (not set yet) self._weyl = None # Weyl tensor (not set yet) self._determinants = {} # determinants in various frames self._sqrt_abs_dets = {} # sqrt(abs(det g)) in various frames self._vol_forms = [] # volume form and associated tensors def _repr_(self): r""" Special Sage function for the string representation of the object. """ description = "pseudo-Riemannian metric '%s'" % self.name description += " on the " + str(self.manifold) return description def _new_instance(self): r""" Create a :class:`Metric` instance with the same signature. """ return Metric(self.manifold, 'unnamed', signature=self._signature) def _del_derived(self): r""" Delete the derived quantities """ # First the derived quantities from the mother class are deleted: SymBilinFormField._del_derived(self) # The inverse metric is cleared: self._inverse.components.clear() self._inverse._del_derived() # The connection and Weyl tensor are reset to None: self._connection = None self._weyl = None # The dictionary of determinants over the various frames is cleared: self._determinants.clear() self._sqrt_abs_dets.clear() # The volume form and the associated tensors is deleted: del self._vol_forms[:] def signature(self): r""" Signature of the metric. OUTPUT: - signature `S` of the metric, defined as the integer `S = n_+ - n_-`, where `n_+` (resp. `n_-`) is the number of positive terms (resp. number of negative terms) in any diagonal writing of the metric components EXAMPLES: Signatures on a 2-dimensional manifold:: sage: M = Manifold(2, 'M') sage: g = Metric(M, 'g') # if not specified, the signature is Riemannian sage: g.signature() 2 sage: h = Metric(M, 'h', signature=0) sage: h.signature() 0 """ return self._signature def inverse(self): r""" Return the inverse metric. EXAMPLES: Inverse metric on a 2-dimensional manifold:: sage: m = Manifold(2, 'M', start_index=1) sage: c_xy = Chart(m, 'x y', 'xy') sage: g = Metric(m, 'g') sage: g[1,1], g[1,2], g[2,2] = 1+x, x*y, 1-x sage: g[:] # components in the manifold's default frame [ x + 1 x*y] [ x*y -x + 1] sage: ig = g.inverse() ; ig tensor field 'inv_g' of type (2,0) on the 2-dimensional manifold 'M' sage: ig[:] [ (x - 1)/(x^2*y^2 + x^2 - 1) x*y/(x^2*y^2 + x^2 - 1)] [ x*y/(x^2*y^2 + x^2 - 1) -(x + 1)/(x^2*y^2 + x^2 - 1)] If the metric is modified, the inverse metric is automatically updated:: sage: g[1,2] = 0 ; g[:] [ x + 1 0] [ 0 -x + 1] sage: g.inverse()[:] [ 1/(x + 1) 0] [ 0 -1/(x - 1)] """ from sage.matrix.constructor import matrix from component import CompFullySym from utilities import simplify_chain # Is the inverse metric up to date ? for frame_name in self.components: if frame_name not in self._inverse.components: # the computation is necessary manif = self.manifold if frame_name[-2:] == '_b': # coordinate basis chart_name = frame_name[:-2] else: chart_name = manif.def_chart.name si = manif.sindex nsi = manif.dim + si try: gmat = matrix([[ self.comp(frame_name)[i, j, chart_name].express for j in range(si, nsi) ] for i in range(si, nsi)]) except KeyError: continue gmat_inv = gmat.inverse() cinv = CompFullySym(manif, 2, frame_name) for i in range(si, nsi): for j in range(i, nsi): # symmetry taken into account cinv[i, j, chart_name] = simplify_chain(gmat_inv[i - si, j - si]) self._inverse.components[frame_name] = cinv return self._inverse def connection(self, name=None, latex_name=None): r""" Return the unique torsion-free affine connection compatible with ``self``. This is the so-called Levi-Civita connection. INPUT: - ``name`` -- (default: None) name given to the Levi-Civita connection; if none, it is formed from the metric name - ``latex_name`` -- (default: None) LaTeX symbol to denote the Levi-Civita connection; if none, it is formed from the symbol `\nabla` and the metric symbol OUTPUT: - the Levi-Civita connection, as an instance of :class:`LeviCivitaConnection`. EXAMPLES: Levi-Civitation connection associated with the Euclidean metric on `\RR^3`:: sage: m = Manifold(3, 'R^3', start_index=1) sage: # Let us use spherical coordinates on R^3: sage: c_spher = Chart(m, r'r:positive, th:positive:\theta, ph:\phi', 'spher') sage: g = Metric(m, 'g') sage: g[1,1], g[2,2], g[3,3] = 1, r^2 , (r*sin(th))^2 # the Euclidean metric sage: g.connection() Levi-Civita connection 'nabla_g' associated with the pseudo-Riemannian metric 'g' on the 3-dimensional manifold 'R^3' sage: g.connection()[:] # Christoffel symbols in spherical coordinates [[[0, 0, 0], [0, -r, 0], [0, 0, -r*sin(th)^2]], [[0, 1/r, 0], [1/r, 0, 0], [0, 0, -sin(th)*cos(th)]], [[0, 0, 1/r], [0, 0, cos(th)/sin(th)], [1/r, cos(th)/sin(th), 0]]] Test of compatibility with the metric:: sage: Dg = g.connection()(g) ; Dg tensor field 'nabla_g g' of type (0,3) on the 3-dimensional manifold 'R^3' sage: Dg == 0 True sage: Dig = g.connection()(g.inverse()) ; Dig tensor field 'nabla_g inv_g' of type (2,1) on the 3-dimensional manifold 'R^3' sage: Dig == 0 True """ if self._connection is None: if name is None: name = 'nabla_' + self.name if latex_name is None: latex_name = r'\nabla_{' + self.latex_name + '}' self._connection = LeviCivitaConnection(self, name, latex_name) return self._connection def christoffel(self, chart_name=None): r""" Christoffel symbols of ``self`` with respect to a chart. INPUT: - ``chart_name`` -- (default: None) name of the chart; if none is provided, the manifold's default chart is assumed. OUTPUT: - the set of Christoffel symbols in the given chart, as an instance of :class:`CompWithSym` EXAMPLES: Christoffel symbols of the flat metric on `\RR^3` with respect to spherical coordinates:: sage: m = Manifold(3, 'R3', r'\RR^3', start_index=1) sage: X = Chart(m, r'r:positive th:\theta ph:\phi','spher') sage: g = Metric(m, 'g') sage: g[1,1], g[2,2], g[3,3] = 1, r^2, r^2*sin(th)^2 sage: g.show() # the standard flat metric expressed in spherical coordinates g = dr*dr + r^2 dth*dth + r^2*sin(th)^2 dph*dph sage: Gam = g.christoffel() ; Gam 3-indices components w.r.t. the coordinate basis 'spher_b' (d/dr,d/dth,d/dph), with symmetry on the index positions (1, 2) sage: print type(Gam) <class 'sage.geometry.manifolds.component.CompWithSym'> sage: Gam[:] [[[0, 0, 0], [0, -r, 0], [0, 0, -r*sin(th)^2]], [[0, 1/r, 0], [1/r, 0, 0], [0, 0, -sin(th)*cos(th)]], [[0, 0, 1/r], [0, 0, cos(th)/sin(th)], [1/r, cos(th)/sin(th), 0]]] sage: Gam[1,2,2] -r sage: Gam[2,1,2] 1/r sage: Gam[3,1,3] 1/r sage: Gam[3,2,3] cos(th)/sin(th) sage: Gam[2,3,3] -sin(th)*cos(th) """ if chart_name is None: frame_name = self.manifold.def_chart.frame.name else: frame_name = self.manifold.atlas[chart_name].frame.name return self.connection().coef(frame_name) def riemann(self, frame_name=None, name=None, latex_name=None): r""" Return the Riemann curvature tensor associated with the metric. This method is actually a shortcut for ``self.connection().riemann()`` The Riemann curvature tensor is the tensor field `R` of type (1,3) defined by .. MATH:: R(\omega, u, v, w) = \left\langle \omega, \nabla_u \nabla_v w - \nabla_v \nabla_u w - \nabla_{[u, v]} w \right\rangle for any 1-form `\omega` and any vector fields `u`, `v` and `w`. INPUT: - ``frame_name`` -- (default: None) string containing the name of the vector frame in which the computation must be performed; if none is provided, the computation is performed in a frame for which the metric components are known, privileging the manifold's default frame. - ``name`` -- (default: None) name given to the Riemann tensor; if none, it is set to "Riem(g)", where "g" is the metric's name - ``latex_name`` -- (default: None) LaTeX symbol to denote the Riemann tensor; if none, it is set to "\\mathrm{Riem}(g)", where "g" is the metric's name OUTPUT: - the Riemann curvature tensor `R`, as an instance of :class:`TensorField` EXAMPLES: Riemann tensor of the standard metric on the 2-sphere:: sage: m = Manifold(2, 'S^2', start_index=1) sage: c_spher = Chart(m, r'th:\theta, ph:\phi', 'spher') sage: a = var('a') # the sphere radius sage: g = Metric(m, 'g') sage: g[1,1], g[2,2] = a^2, a^2*sin(th)^2 sage: g.show() # standard metric on the 2-sphere of radius a: g = a^2 dth*dth + a^2*sin(th)^2 dph*dph sage: g.riemann() tensor field 'Riem(g)' of type (1,3) on the 2-dimensional manifold 'S^2' sage: g.riemann()[:] [[[[0, 0], [0, 0]], [[0, sin(th)^2], [-sin(th)^2, 0]]], [[[0, (cos(th)^2 - 1)/sin(th)^2], [1, 0]], [[0, 0], [0, 0]]]] In dimension 2, the Riemann tensor can be expressed entirely in terms of the Ricci scalar `r`: .. MATH:: R^i_{\ \, jlk} = \frac{r}{2} \left( \delta^i_{\ \, k} g_{jl} - \delta^i_{\ \, l} g_{jk} \right) This formula can be checked here, with the r.h.s. rewritten as `-r g_{j[k} \delta^i_{\ \, l]}`:: sage: g.riemann() == -g.ricci_scalar()*(g*IdentityMap(m)).antisymmetrize([2,3]) True """ return self.connection().riemann(frame_name, name, latex_name) def ricci(self, frame_name=None, name=None, latex_name=None): r""" Return the Ricci tensor associated with the metric. This method is actually a shortcut for ``self.connection().ricci()`` The Ricci tensor is the tensor field `Ric` of type (0,2) defined from the Riemann curvature tensor `R` by .. MATH:: Ric(u, v) = R(e^i, u, e_i, v) for any vector fields `u` and `v`, `(e_i)` being any vector frame and `(e^i)` the dual coframe. INPUT: - ``frame_name`` -- (default: None) string containing the name of the vector frame in which the computation must be performed; if none is provided, the computation is performed in a frame for which the metric components are known, privileging the manifold's default frame. - ``name`` -- (default: None) name given to the Ricci tensor; if none, it is set to "Ric(g)", where "g" is the metric's name - ``latex_name`` -- (default: None) LaTeX symbol to denote the Ricci tensor; if none, it is set to "\\mathrm{Ric}(g)", where "g" is the metric's name OUTPUT: - the Ricci tensor `Ric`, as an instance of :class:`SymBilinFormField` EXAMPLES: Ricci tensor of the standard metric on the 2-sphere:: sage: m = Manifold(2, 'S^2', start_index=1) sage: c_spher = Chart(m, r'th:\theta, ph:\phi', 'spher') sage: a = var('a') # the sphere radius sage: g = Metric(m, 'g') sage: g[1,1], g[2,2] = a^2, a^2*sin(th)^2 sage: g.show() # standard metric on the 2-sphere of radius a: g = a^2 dth*dth + a^2*sin(th)^2 dph*dph sage: g.ricci() field of symmetric bilinear forms 'Ric(g)' on the 2-dimensional manifold 'S^2' sage: g.ricci()[:] [ 1 0] [ 0 sin(th)^2] sage: g.ricci() == a^(-2) * g True """ return self.connection().ricci(frame_name, name, latex_name) def ricci_scalar(self, frame_name=None, name=None, latex_name=None): r""" Return the Ricci scalar associated with the metric. The Ricci scalar is the scalar field `r` defined from the Ricci tensor `Ric` and the metric tensor `g` by .. MATH:: r = g^{ij} Ric_{ij} INPUT: - ``frame_name`` -- (default: None) string containing the name of the vector frame in which the computation must be performed; if none is provided, the computation is performed in a frame for which the metric components are known, privileging the manifold's default frame. - ``name`` -- (default: None) name given to the Ricci scalar; if none, it is set to "r(g)", where "g" is the metric's name - ``latex_name`` -- (default: None) LaTeX symbol to denote the Ricci scalar; if none, it is set to "\\mathrm{r}(g)", where "g" is the metric's name OUTPUT: - the Ricci scalar `r`, as an instance of :class:`ScalarField` EXAMPLES: Ricci scalar of the standard metric on the 2-sphere:: sage: m = Manifold(2, 'S^2', start_index=1) sage: c_spher = Chart(m, r'th:\theta, ph:\phi', 'spher') sage: a = var('a') # the sphere radius sage: g = Metric(m, 'g') sage: g[1,1], g[2,2] = a^2, a^2*sin(th)^2 sage: g.show() # standard metric on the 2-sphere of radius a: g = a^2 dth*dth + a^2*sin(th)^2 dph*dph sage: g.ricci_scalar() scalar field 'r(g)' on the 2-dimensional manifold 'S^2' sage: g.ricci_scalar().expr() 2/a^2 """ return self.connection().ricci_scalar(frame_name, name, latex_name) def weyl(self, name=None, latex_name=None): r""" Return the Weyl conformal tensor associated with the metric. The Weyl conformal tensor is the tensor field `C` of type (1,3) defined as the trace-free part of the Riemann curvature tensor `R` INPUT: - ``name`` -- (default: None) name given to the Weyl conformal tensor; if none, it is set to "C(g)", where "g" is the metric's name - ``latex_name`` -- (default: None) LaTeX symbol to denote the Weyl conformal tensor; if none, it is set to "\\mathrm{C}(g)", where "g" is the metric's name OUTPUT: - the Weyl conformal tensor `C`, as an instance of :class:`TensorField` EXAMPLES: Checking that the Weyl tensor identically vanishes on a 3-dimensional manifold, for instance the hyperbolic space `H^3`:: sage: m = Manifold(3, 'H^3', start_index=1) sage: X = Chart(m, r'rh:positive:\rho th:\theta ph:\phi', 'rtp-coord') sage: g = Metric(m, 'g') sage: b = var('b') sage: g[1,1], g[2,2], g[3,3] = b^2, (b*sinh(rh))^2, (b*sinh(rh)*sin(th))^2 sage: g.show() # standard metric on H^3: g = b^2 drh*drh + b^2*sinh(rh)^2 dth*dth + b^2*sin(th)^2*sinh(rh)^2 dph*dph sage: C = g.weyl() ; C tensor field 'C(g)' of type (1,3) on the 3-dimensional manifold 'H^3' sage: C == 0 True """ from rank2field import IdentityMap if self._weyl is None: n = self.manifold.dim if n < 3: raise ValueError("The Weyl tensor is not defined for a " + "manifold of dimension n <= 2.") delta = IdentityMap(self.manifold) riem = self.riemann() ric = self.ricci() rscal = self.ricci_scalar() # First index of the Ricci tensor raised with the metric ricup = ric.up(self, 0) # The identity map is expressed in a frame in which the Riemann # tensor is known delta.comp(riem.pick_a_frame()) aux = self * ricup + ric * delta - rscal / (n - 1) * self * delta self._weyl = riem + 2 / (n - 2) * aux.antisymmetrize([2, 3]) if name is None: self._weyl.name = "C(" + self.name + ")" else: self._weyl.name = name if latex_name is None: self._weyl.latex_name = r"\mathrm{C}\left(" + self.latex_name + r"\right)" else: self._weyl.latex_name = latex_name return self._weyl def determinant(self, frame_name=None): r""" Determinant of the metric components in the specified frame. INPUT: - ``frame_name`` -- (default: None) name of the vector frame with respect to which the components `g_{ij}` of ``self`` are defined; if None, the manifold's default frame is used. If a chart name is provided, the associated coordinate basis is used OUTPUT: - the determinant `\det (g_{ij})`, as an instance of :class:`ScalarField` EXAMPLES: Metric determinant on a 2-dimensional manifold:: sage: M = Manifold(2, 'M', start_index=1) sage: X = Chart(M, 'x y', 'xy') sage: g = Metric(M, 'g') sage: g[1,1], g[1, 2], g[2, 2] = 1+x, x*y , 1-y sage: g[:] [ x + 1 x*y] [ x*y -y + 1] sage: s = g.determinant() # determinant in M's default frame sage: s.expr() -x^2*y^2 - (x + 1)*y + x + 1 Determinant in a frame different from the default's one:: sage: Y = Chart(M, 'u v', 'uv') sage: ch_X_Y = CoordChange(X, Y, x+y, x-y) sage: ch_X_Y.inverse() coordinate change from chart 'uv' (u, v) to chart 'xy' (x, y) sage: g.comp('uv_b')[:, 'uv'] [ 1/8*u^2 - 1/8*v^2 + 1/4*v + 1/2 1/4*u] [ 1/4*u -1/8*u^2 + 1/8*v^2 + 1/4*v + 1/2] sage: g.determinant('uv_b').expr() -1/4*x^2*y^2 - 1/4*(x + 1)*y + 1/4*x + 1/4 sage: g.determinant('uv_b').expr('uv') -1/64*u^4 - 1/16*u^2 - 1/64*v^4 + 1/32*(u^2 + 2)*v^2 + 1/4*v + 1/4 The name of a chart can be passed instead of the name of a frame:: sage: g.determinant('xy') is g.determinant('xy_b') True sage: g.determinant('uv') is g.determinant('uv_b') True The metric determinant depends on the frame:: sage: g.determinant('xy_b') == g.determinant('uv_b') False """ from sage.matrix.constructor import matrix from scalarfield import ScalarField from utilities import simple_determinant, simplify_chain manif = self.manifold if frame_name is None: frame_name = manif.def_frame.name if frame_name in manif.atlas: # frame_name is actually the name of a chart and is changed to the # name of the associated coordinate basis: frame_name = manif.atlas[frame_name].frame.name if frame_name not in self._determinants: # a new computation is necessary resu = ScalarField(manif) gg = self.comp(frame_name) i1 = manif.sindex for chart_name in gg[[i1, i1]].express: gm = matrix( [[gg[i, j, chart_name].express for j in manif.irange()] for i in manif.irange()]) detgm = simplify_chain(simple_determinant(gm)) resu.set_expr(detgm, chart_name=chart_name, delete_others=False) self._determinants[frame_name] = resu return self._determinants[frame_name] def sqrt_abs_det(self, frame_name=None): r""" Square root of the absolute value of the determinant of the metric components in the specified frame. INPUT: - ``frame_name`` -- (default: None) name of the vector frame with respect to which the components `g_{ij}` of ``self`` are defined; if None, the manifold's default frame is used. If a chart name is provided, the associated coordinate basis is used OUTPUT: - `\sqrt{|\det (g_{ij})|}`, as an instance of :class:`ScalarField` EXAMPLES: Standard metric in the Euclidean space `\RR^3` with spherical coordinates:: sage: m = Manifold(3, 'M', start_index=1) sage: c_spher = Chart(m, r'r:positive th:positive:\theta ph:\phi', 'spher') sage: assume(th>=0); assume(th<=pi) sage: g = Metric(m, 'g') sage: g[1,1], g[2,2], g[3,3] = 1, r^2, (r*sin(th))^2 sage: g.show() g = dr*dr + r^2 dth*dth + r^2*sin(th)^2 dph*dph sage: g.sqrt_abs_det().expr() r^2*sin(th) Metric determinant on a 2-dimensional manifold:: sage: M = Manifold(2, 'M', start_index=1) sage: X = Chart(M, 'x y', 'xy') sage: g = Metric(M, 'g') sage: g[1,1], g[1, 2], g[2, 2] = 1+x, x*y , 1-y sage: g[:] [ x + 1 x*y] [ x*y -y + 1] sage: s = g.sqrt_abs_det() ; s scalar field on the 2-dimensional manifold 'M' sage: s.expr() sqrt(-x^2*y^2 - (x + 1)*y + x + 1) Determinant in a frame different from the default's one:: sage: Y = Chart(M, 'u v', 'uv') sage: ch_X_Y = CoordChange(X, Y, x+y, x-y) sage: ch_X_Y.inverse() coordinate change from chart 'uv' (u, v) to chart 'xy' (x, y) sage: g.comp('uv_b')[:, 'uv'] [ 1/8*u^2 - 1/8*v^2 + 1/4*v + 1/2 1/4*u] [ 1/4*u -1/8*u^2 + 1/8*v^2 + 1/4*v + 1/2] sage: g.sqrt_abs_det('uv_b').expr() 1/2*sqrt(-x^2*y^2 - (x + 1)*y + x + 1) sage: g.sqrt_abs_det('uv_b').expr('uv') 1/8*sqrt(-u^4 - 4*u^2 - v^4 + 2*(u^2 + 2)*v^2 + 16*v + 16) The name of a chart can be passed instead of the name of a frame:: sage: g.sqrt_abs_det('uv') is g.sqrt_abs_det('uv_b') True The metric determinant depends on the frame:: sage: g.sqrt_abs_det('xy_b') == g.sqrt_abs_det('uv_b') False """ from sage.functions.other import sqrt from scalarfield import ScalarField from utilities import simplify_chain manif = self.manifold if frame_name is None: frame_name = manif.def_frame.name if frame_name in manif.atlas: # frame_name is actually the name of a chart and is changed to the # name of the associated coordinate basis: frame_name = manif.atlas[frame_name].frame.name if frame_name not in self._sqrt_abs_dets: # a new computation is necessary detg = self.determinant(frame_name) resu = ScalarField(manif) for chart_name in detg.express: x = self._indic_signat * detg.express[chart_name].express # |g| x = simplify_chain(sqrt(x)) resu.set_expr(x, chart_name=chart_name, delete_others=False) self._sqrt_abs_dets[frame_name] = resu return self._sqrt_abs_dets[frame_name] def volume_form(self, contra=0): r""" Volume form (Levi-Civita tensor) `\epsilon` associated with the metric. This assumes that the manifold is orientable. The volume form `\epsilon` is a `n`-form (`n` being the manifold's dimension) such that for any vector basis `(e_i)` that is orthonormal with respect to the metric, .. MATH:: \epsilon(e_1,\ldots,e_n) = \pm 1 There are only two such `n`-forms, which are opposite of each other. The volume form `\epsilon` is selected such that the manifold's default frame is right-handed with respect to it. INPUT: - ``contra`` -- (default: 0) number of contravariant indices of the returned tensor OUTPUT: - if ``contra = 0`` (default value): the volume `n`-form `\epsilon`, as an instance of :class:`DiffForm` - if ``contra = k``, with `1\leq k \leq n`, the tensor field of type (k,n-k) formed from `\epsilon` by raising the first k indices with the metric (see method :meth:`TensorField.up`); the output is then an instance of :class:`TensorField`, with the appropriate antisymmetries EXAMPLES: Volume form on `\RR^3` with spherical coordinates:: sage: m = Manifold(3, 'M', start_index=1) sage: c_spher = Chart(m, r'r:positive th:positive:\theta ph:\phi', 'spher') sage: assume(th>=0); assume(th<=pi) sage: g = Metric(m, 'g') sage: g[1,1], g[2,2], g[3,3] = 1, r^2, (r*sin(th))^2 sage: g.show() g = dr*dr + r^2 dth*dth + r^2*sin(th)^2 dph*dph sage: eps = g.volume_form() ; eps 3-form 'eps_g' on the 3-dimensional manifold 'M' sage: eps.show() eps_g = r^2*sin(th) dr/\dth/\dph sage: eps[[1,2,3]] == g.sqrt_abs_det() True sage: latex(eps) \epsilon_{g} The tensor field of components `\epsilon^i_{\ \, jk}` (``contra=1``):: sage: eps1 = g.volume_form(1) ; eps1 tensor field of type (1,2) on the 3-dimensional manifold 'M' sage: eps1.symmetries() no symmetry; antisymmetry: (1, 2) sage: eps1[:] [[[0, 0, 0], [0, 0, r^2*sin(th)], [0, -r^2*sin(th), 0]], [[0, 0, -sin(th)], [0, 0, 0], [sin(th), 0, 0]], [[0, 1/sin(th), 0], [-1/sin(th), 0, 0], [0, 0, 0]]] The tensor field of components `\epsilon^{ij}_{\ \ k}` (``contra=2``):: sage: eps2 = g.volume_form(2) ; eps2 tensor field of type (2,1) on the 3-dimensional manifold 'M' sage: eps2.symmetries() no symmetry; antisymmetry: (0, 1) sage: eps2[:] [[[0, 0, 0], [0, 0, sin(th)], [0, -1/sin(th), 0]], [[0, 0, -sin(th)], [0, 0, 0], [1/(r^2*sin(th)), 0, 0]], [[0, 1/sin(th), 0], [-1/(r^2*sin(th)), 0, 0], [0, 0, 0]]] The tensor field of components `\epsilon^{ijk}` (``contra=3``):: sage: eps3 = g.volume_form(3) ; eps3 tensor field of type (3,0) on the 3-dimensional manifold 'M' sage: eps3.symmetries() no symmetry; antisymmetry: (0, 1, 2) sage: eps3[:] [[[0, 0, 0], [0, 0, 1/(r^2*sin(th))], [0, -1/(r^2*sin(th)), 0]], [[0, 0, -1/(r^2*sin(th))], [0, 0, 0], [1/(r^2*sin(th)), 0, 0]], [[0, 1/(r^2*sin(th)), 0], [-1/(r^2*sin(th)), 0, 0], [0, 0, 0]]] sage: eps3[1,2,3] 1/(r^2*sin(th)) sage: eps3[[1,2,3]] * g.sqrt_abs_det() == 1 True """ if self._vol_forms == []: # a new computation is necessary manif = self.manifold ndim = manif.dim eps = DiffForm(manif, ndim, name='eps_' + self.name, latex_name=r'\epsilon_{' + self.latex_name + r'}') ind = tuple(range(manif.sindex, manif.sindex + ndim)) eps[[ind]] = self.sqrt_abs_det(manif.def_frame.name) self._vol_forms.append(eps) # Levi-Civita tensor constructed # Tensors related to the Levi-Civita one by index rising: for k in range(1, ndim + 1): epskm1 = self._vol_forms[k - 1] epsk = epskm1.up(self, k - 1) if k > 1: # restoring the antisymmetry after the up operation: epsk = epsk.antisymmetrize(range(k)) self._vol_forms.append(epsk) return self._vol_forms[contra]
def _init_derived(self): r""" Initialises the derived quantities """ TensorField._init_derived(self) self._exterior_derivative = None
def _del_derived(self): r""" Delete the derived quantities """ TensorField._del_derived(self) self._exterior_derivative = None
def _del_derived(self): r""" Delete the derived quantities """ TensorField._del_derived(self) self._del_dependencies()
def __init__(self, manifold, name=None, latex_name=None): TensorField.__init__(self, manifold, 1, 1, name, latex_name)
def __init__(self, domain, name=None, latex_name=None): TensorField.__init__(self, domain, 1, 1, name, latex_name)
class Metric(SymBilinFormField): r""" Base class for pseudo-Riemannian metrics on a differentiable manifold. INPUT: - ``domain`` -- the manifold domain on which the metric is defined (must be an instance of class :class:`Domain`) - ``name`` -- name given to the metric - ``signature`` -- (default: None) signature `S` of the metric as a single integer: `S = n_+ - n_-`, where `n_+` (resp. `n_-`) is the number of positive terms (resp. number of negative terms) in any diagonal writing of the metric components; if ``signature`` is not provided, `S` is set to the manifold's dimension (Riemannian signature) - ``latex_name`` -- (default: None) LaTeX symbol to denote the metric; if none, it is formed from ``name`` EXAMPLES: Metric on a 2-dimensional manifold:: sage: m = Manifold(2, 'M', start_index=1) sage: c_xy.<x,y> = m.chart('x y') sage: g = Metric(m, 'g') ; g pseudo-Riemannian metric 'g' on the 2-dimensional manifold 'M' sage: latex(g) g A metric is a special kind of tensor field and therefore inheritates all the properties from class :class:`TensorField`:: sage: isinstance(g, TensorField) True sage: g.tensor_type (0, 2) sage: g.symmetries() # g is symmetric: symmetry: (0, 1); no antisymmetry sage: isinstance(g, SymBilinFormField) # a metric is actually a field of symmetric bilinear forms: True Setting the metric components in the manifold's default frame:: sage: g[1,1], g[1,2], g[2,2] = 1+x, x*y, 1-x sage: g[:] [ x + 1 x*y] [ x*y -x + 1] sage: g.view() g = (x + 1) dx*dx + x*y dx*dy + x*y dy*dx + (-x + 1) dy*dy Metric components in a frame different from the manifold's default one:: sage: c_uv.<u,v> = m.chart('u v') # new chart on M sage: xy_to_uv = CoordChange(c_xy, c_uv, x+y, x-y) ; xy_to_uv coordinate change from chart (M, (x, y)) to chart (M, (u, v)) sage: uv_to_xy = xy_to_uv.inverse() ; uv_to_xy coordinate change from chart (M, (u, v)) to chart (M, (x, y)) sage: m.atlas [chart (M, (x, y)), chart (M, (u, v))] sage: m.frames [coordinate frame (M, (d/dx,d/dy)), coordinate frame (M, (d/du,d/dv))] sage: g.comp(c_uv.frame)[:] # metric components in frame c_uv.frame expressed in M's default chart (x,y) [ 1/2*x*y + 1/2 1/2*x] [ 1/2*x -1/2*x*y + 1/2] sage: g.view(c_uv.frame) g = (1/2*x*y + 1/2) du*du + 1/2*x du*dv + 1/2*x dv*du + (-1/2*x*y + 1/2) dv*dv sage: g.comp(c_uv.frame)[:, c_uv] # metric components in frame c_uv.frame expressed in chart (u,v) [ 1/8*u^2 - 1/8*v^2 + 1/2 1/4*u + 1/4*v] [ 1/4*u + 1/4*v -1/8*u^2 + 1/8*v^2 + 1/2] sage: g.view(c_uv.frame, c_uv) g = (1/8*u^2 - 1/8*v^2 + 1/2) du*du + (1/4*u + 1/4*v) du*dv + (1/4*u + 1/4*v) dv*du + (-1/8*u^2 + 1/8*v^2 + 1/2) dv*dv The inverse metric is obtained via :meth:`inverse`:: sage: ig = g.inverse() ; ig tensor field 'inv_g' of type (2,0) on the 2-dimensional manifold 'M' sage: ig[:] [ (x - 1)/(x^2*y^2 + x^2 - 1) x*y/(x^2*y^2 + x^2 - 1)] [ x*y/(x^2*y^2 + x^2 - 1) -(x + 1)/(x^2*y^2 + x^2 - 1)] sage: ig.view() inv_g = (x - 1)/(x^2*y^2 + x^2 - 1) d/dx*d/dx + x*y/(x^2*y^2 + x^2 - 1) d/dx*d/dy + x*y/(x^2*y^2 + x^2 - 1) d/dy*d/dx - (x + 1)/(x^2*y^2 + x^2 - 1) d/dy*d/dy """ def __init__(self, domain, name, signature=None, latex_name=None): SymBilinFormField.__init__(self, domain, name, latex_name) # signature: ndim = self.manifold.dim if signature is None: signature = ndim else: if not isinstance(signature, (int, Integer)): raise TypeError("The metric signature must be an integer.") if (signature < - ndim) or (signature > ndim): raise ValueError("Metric signature out of range.") if (signature+ndim)%2 == 1: if ndim%2 == 0: raise ValueError("The metric signature must be even.") else: raise ValueError("The metric signature must be odd.") self._signature = signature # the pair (n_+, n_-): self._signature_pm = ((ndim+signature)/2, (ndim-signature)/2) self._indic_signat = 1 - 2*(self._signature_pm[1]%2) # (-1)^n_- Metric._init_derived(self) def _repr_(self): r""" Special Sage function for the string representation of the object. """ description = "pseudo-Riemannian metric '%s'" % self.name description += " on the " + str(self.domain) return description def _new_instance(self): r""" Create a :class:`Metric` instance with the same signature. """ return Metric(self.domain, 'unnamed', signature=self._signature) def _init_derived(self): r""" Initialize the derived quantities """ # Initialization of quantities pertaining to the mother class: SymBilinFormField._init_derived(self) # inverse metric: inv_name = 'inv_' + self.name inv_latex_name = self.latex_name + r'^{-1}' self._inverse = TensorField(self.domain, 2, 0, inv_name, inv_latex_name, sym=(0,1)) self._connection = None # Levi-Civita connection (not set yet) self._weyl = None # Weyl tensor (not set yet) self._determinants = {} # determinants in various frames self._sqrt_abs_dets = {} # sqrt(abs(det g)) in various frames self._vol_forms = [] # volume form and associated tensors def _del_derived(self): r""" Delete the derived quantities """ # First the derived quantities from the mother class are deleted: SymBilinFormField._del_derived(self) # The inverse metric is cleared: self._inverse.components.clear() self._inverse._del_derived() # The connection and Weyl tensor are reset to None: self._connection = None self._weyl = None # The dictionary of determinants over the various frames is cleared: self._determinants.clear() self._sqrt_abs_dets.clear() # The volume form and the associated tensors is deleted: del self._vol_forms[:] def signature(self): r""" Signature of the metric. OUTPUT: - signature `S` of the metric, defined as the integer `S = n_+ - n_-`, where `n_+` (resp. `n_-`) is the number of positive terms (resp. number of negative terms) in any diagonal writing of the metric components EXAMPLES: Signatures on a 2-dimensional manifold:: sage: M = Manifold(2, 'M') sage: g = Metric(M, 'g') # if not specified, the signature is Riemannian sage: g.signature() 2 sage: h = Metric(M, 'h', signature=0) sage: h.signature() 0 """ return self._signature def set(self, symbiform): r""" Defines the metric from a field of symmetric bilinear forms INPUT: - ``symbiform`` -- field of symmetric bilinear forms """ if not isinstance(symbiform, SymBilinFormField): raise TypeError("The argument must be a symmetric bilinear form.") if symbiform.manifold != self.manifold: raise TypeError("The manifold of the symmetric bilinear form " + "differs from that of the metric.") self.domain = symbiform.domain self._init_derived() self.components.clear() for frame in symbiform.components: self.components[frame] = symbiform.components[frame].copy() def inverse(self): r""" Return the inverse metric. EXAMPLES: Inverse metric on a 2-dimensional manifold:: sage: m = Manifold(2, 'M', start_index=1) sage: c_xy.<x,y> = m.chart('x y') sage: g = Metric(m, 'g') sage: g[1,1], g[1,2], g[2,2] = 1+x, x*y, 1-x sage: g[:] # components in the manifold's default frame [ x + 1 x*y] [ x*y -x + 1] sage: ig = g.inverse() ; ig tensor field 'inv_g' of type (2,0) on the 2-dimensional manifold 'M' sage: ig[:] [ (x - 1)/(x^2*y^2 + x^2 - 1) x*y/(x^2*y^2 + x^2 - 1)] [ x*y/(x^2*y^2 + x^2 - 1) -(x + 1)/(x^2*y^2 + x^2 - 1)] If the metric is modified, the inverse metric is automatically updated:: sage: g[1,2] = 0 ; g[:] [ x + 1 0] [ 0 -x + 1] sage: g.inverse()[:] [ 1/(x + 1) 0] [ 0 -1/(x - 1)] """ from sage.matrix.constructor import matrix from component import CompFullySym from vectorframe import CoordFrame from utilities import simplify_chain # Is the inverse metric up to date ? for frame in self.components: if frame not in self._inverse.components: # the computation is necessary manif = self.manifold dom = self.domain if isinstance(frame, CoordFrame): chart = frame.chart else: chart = dom.def_chart si = manif.sindex nsi = manif.dim + si try: gmat = matrix( [[self.comp(frame)[i, j, chart].express for j in range(si, nsi)] for i in range(si, nsi)]) except (KeyError, ValueError): continue gmat_inv = gmat.inverse() cinv = CompFullySym(frame, 2) for i in range(si, nsi): for j in range(i, nsi): # symmetry taken into account cinv[i, j, chart] = simplify_chain( gmat_inv[i-si,j-si]) self._inverse.components[frame] = cinv return self._inverse def connection(self, name=None, latex_name=None): r""" Return the unique torsion-free affine connection compatible with ``self``. This is the so-called Levi-Civita connection. INPUT: - ``name`` -- (default: None) name given to the Levi-Civita connection; if none, it is formed from the metric name - ``latex_name`` -- (default: None) LaTeX symbol to denote the Levi-Civita connection; if none, it is formed from the symbol `\nabla` and the metric symbol OUTPUT: - the Levi-Civita connection, as an instance of :class:`LeviCivitaConnection`. EXAMPLES: Levi-Civitation connection associated with the Euclidean metric on `\RR^3`:: sage: m = Manifold(3, 'R^3', start_index=1) sage: # Let us use spherical coordinates on R^3: sage: c_spher.<r,th,ph> = m.chart(r'r:[0,+oo) th:[0,pi]:\theta ph:[0,2*pi):\phi') sage: g = Metric(m, 'g') sage: g[1,1], g[2,2], g[3,3] = 1, r^2 , (r*sin(th))^2 # the Euclidean metric sage: g.connection() Levi-Civita connection 'nabla_g' associated with the pseudo-Riemannian metric 'g' on the 3-dimensional manifold 'R^3' sage: g.connection()[:] # Christoffel symbols in spherical coordinates [[[0, 0, 0], [0, -r, 0], [0, 0, -r*sin(th)^2]], [[0, 1/r, 0], [1/r, 0, 0], [0, 0, -cos(th)*sin(th)]], [[0, 0, 1/r], [0, 0, cos(th)/sin(th)], [1/r, cos(th)/sin(th), 0]]] Test of compatibility with the metric:: sage: Dg = g.connection()(g) ; Dg tensor field 'nabla_g g' of type (0,3) on the 3-dimensional manifold 'R^3' sage: Dg == 0 True sage: Dig = g.connection()(g.inverse()) ; Dig tensor field 'nabla_g inv_g' of type (2,1) on the 3-dimensional manifold 'R^3' sage: Dig == 0 True """ if self._connection is None: if name is None: name = 'nabla_' + self.name if latex_name is None: latex_name = r'\nabla_{' + self.latex_name + '}' self._connection = LeviCivitaConnection(self, name, latex_name) return self._connection def christoffel(self, chart=None): r""" Christoffel symbols of ``self`` with respect to a chart. INPUT: - ``chart`` -- (default: None) chart with respect to which the Christoffel symbolds are required; if none is provided, the manifold's default chart is assumed. OUTPUT: - the set of Christoffel symbols in the given chart, as an instance of :class:`CompWithSym` EXAMPLES: Christoffel symbols of the flat metric on `\RR^3` with respect to spherical coordinates:: sage: m = Manifold(3, 'R3', r'\RR^3', start_index=1) sage: X.<r,th,ph> = m.chart(r'r:[0,+oo) th:[0,pi]:\theta ph:[0,2*pi):\phi') sage: g = Metric(m, 'g') sage: g[1,1], g[2,2], g[3,3] = 1, r^2, r^2*sin(th)^2 sage: g.view() # the standard flat metric expressed in spherical coordinates g = dr*dr + r^2 dth*dth + r^2*sin(th)^2 dph*dph sage: Gam = g.christoffel() ; Gam 3-indices components w.r.t. the coordinate frame (R3, (d/dr,d/dth,d/dph)), with symmetry on the index positions (1, 2) sage: print type(Gam) <class 'sage.geometry.manifolds.component.CompWithSym'> sage: Gam[:] [[[0, 0, 0], [0, -r, 0], [0, 0, -r*sin(th)^2]], [[0, 1/r, 0], [1/r, 0, 0], [0, 0, -cos(th)*sin(th)]], [[0, 0, 1/r], [0, 0, cos(th)/sin(th)], [1/r, cos(th)/sin(th), 0]]] sage: Gam[1,2,2] -r sage: Gam[2,1,2] 1/r sage: Gam[3,1,3] 1/r sage: Gam[3,2,3] cos(th)/sin(th) sage: Gam[2,3,3] -cos(th)*sin(th) """ if chart is None: frame = self.domain.def_chart.frame else: frame = chart.frame return self.connection().coef(frame) def riemann(self, frame=None, name=None, latex_name=None): r""" Return the Riemann curvature tensor associated with the metric. This method is actually a shortcut for ``self.connection().riemann()`` The Riemann curvature tensor is the tensor field `R` of type (1,3) defined by .. MATH:: R(\omega, u, v, w) = \left\langle \omega, \nabla_u \nabla_v w - \nabla_v \nabla_u w - \nabla_{[u, v]} w \right\rangle for any 1-form `\omega` and any vector fields `u`, `v` and `w`. INPUT: - ``frame`` -- (default: None) vector frame in which the computation must be performed; if none is provided, the computation is performed in a frame in which the metric components are known, privileging the domain's default frame. - ``name`` -- (default: None) name given to the Riemann tensor; if none, it is set to "Riem(g)", where "g" is the metric's name - ``latex_name`` -- (default: None) LaTeX symbol to denote the Riemann tensor; if none, it is set to "\\mathrm{Riem}(g)", where "g" is the metric's name OUTPUT: - the Riemann curvature tensor `R`, as an instance of :class:`TensorField` EXAMPLES: Riemann tensor of the standard metric on the 2-sphere:: sage: m = Manifold(2, 'S^2', start_index=1) sage: c_spher.<th,ph> = m.chart(r'th:[0,pi]:\theta ph:[0,2*pi):\phi') sage: a = var('a') # the sphere radius sage: g = Metric(m, 'g') sage: g[1,1], g[2,2] = a^2, a^2*sin(th)^2 sage: g.view() # standard metric on the 2-sphere of radius a: g = a^2 dth*dth + a^2*sin(th)^2 dph*dph sage: g.riemann() tensor field 'Riem(g)' of type (1,3) on the 2-dimensional manifold 'S^2' sage: g.riemann()[:] [[[[0, 0], [0, 0]], [[0, sin(th)^2], [-sin(th)^2, 0]]], [[[0, (cos(th)^2 - 1)/sin(th)^2], [1, 0]], [[0, 0], [0, 0]]]] In dimension 2, the Riemann tensor can be expressed entirely in terms of the Ricci scalar `r`: .. MATH:: R^i_{\ \, jlk} = \frac{r}{2} \left( \delta^i_{\ \, k} g_{jl} - \delta^i_{\ \, l} g_{jk} \right) This formula can be checked here, with the r.h.s. rewritten as `-r g_{j[k} \delta^i_{\ \, l]}`:: sage: g.riemann() == -g.ricci_scalar()*(g*IdentityMap(m)).antisymmetrize([2,3]) True """ return self.connection().riemann(frame, name, latex_name) def ricci(self, frame=None, name=None, latex_name=None): r""" Return the Ricci tensor associated with the metric. This method is actually a shortcut for ``self.connection().ricci()`` The Ricci tensor is the tensor field `Ric` of type (0,2) defined from the Riemann curvature tensor `R` by .. MATH:: Ric(u, v) = R(e^i, u, e_i, v) for any vector fields `u` and `v`, `(e_i)` being any vector frame and `(e^i)` the dual coframe. INPUT: - ``frame`` -- (default: None) vector frame in which the computation must be performed; if none is provided, the computation is performed in a frame in which the metric components are known, privileging the domain's default frame. - ``name`` -- (default: None) name given to the Ricci tensor; if none, it is set to "Ric(g)", where "g" is the metric's name - ``latex_name`` -- (default: None) LaTeX symbol to denote the Ricci tensor; if none, it is set to "\\mathrm{Ric}(g)", where "g" is the metric's name OUTPUT: - the Ricci tensor `Ric`, as an instance of :class:`SymBilinFormField` EXAMPLES: Ricci tensor of the standard metric on the 2-sphere:: sage: m = Manifold(2, 'S^2', start_index=1) sage: c_spher.<th,ph> = m.chart(r'th:[0,pi]:\theta ph:[0,2*pi):\phi') sage: a = var('a') # the sphere radius sage: g = Metric(m, 'g') sage: g[1,1], g[2,2] = a^2, a^2*sin(th)^2 sage: g.view() # standard metric on the 2-sphere of radius a: g = a^2 dth*dth + a^2*sin(th)^2 dph*dph sage: g.ricci() field of symmetric bilinear forms 'Ric(g)' on the 2-dimensional manifold 'S^2' sage: g.ricci()[:] [ 1 0] [ 0 sin(th)^2] sage: g.ricci() == a^(-2) * g True """ return self.connection().ricci(frame, name, latex_name) def ricci_scalar(self, frame=None, name=None, latex_name=None): r""" Return the Ricci scalar associated with the metric. The Ricci scalar is the scalar field `r` defined from the Ricci tensor `Ric` and the metric tensor `g` by .. MATH:: r = g^{ij} Ric_{ij} INPUT: - ``frame`` -- (default: None) vector frame in which the computation must be performed; if none is provided, the computation is performed in a frame in which the metric components are known, privileging the domain's default frame. - ``name`` -- (default: None) name given to the Ricci scalar; if none, it is set to "r(g)", where "g" is the metric's name - ``latex_name`` -- (default: None) LaTeX symbol to denote the Ricci scalar; if none, it is set to "\\mathrm{r}(g)", where "g" is the metric's name OUTPUT: - the Ricci scalar `r`, as an instance of :class:`ScalarField` EXAMPLES: Ricci scalar of the standard metric on the 2-sphere:: sage: m = Manifold(2, 'S^2', start_index=1) sage: c_spher.<th,ph> = m.chart(r'th:[0,pi]:\theta ph:[0,2*pi):\phi') sage: a = var('a') # the sphere radius sage: g = Metric(m, 'g') sage: g[1,1], g[2,2] = a^2, a^2*sin(th)^2 sage: g.view() # standard metric on the 2-sphere of radius a: g = a^2 dth*dth + a^2*sin(th)^2 dph*dph sage: g.ricci_scalar() scalar field 'r(g)' on the 2-dimensional manifold 'S^2' sage: g.ricci_scalar().expr() 2/a^2 """ return self.connection().ricci_scalar(frame, name, latex_name) def weyl(self, name=None, latex_name=None): r""" Return the Weyl conformal tensor associated with the metric. The Weyl conformal tensor is the tensor field `C` of type (1,3) defined as the trace-free part of the Riemann curvature tensor `R` INPUT: - ``name`` -- (default: None) name given to the Weyl conformal tensor; if none, it is set to "C(g)", where "g" is the metric's name - ``latex_name`` -- (default: None) LaTeX symbol to denote the Weyl conformal tensor; if none, it is set to "\\mathrm{C}(g)", where "g" is the metric's name OUTPUT: - the Weyl conformal tensor `C`, as an instance of :class:`TensorField` EXAMPLES: Checking that the Weyl tensor identically vanishes on a 3-dimensional manifold, for instance the hyperbolic space `H^3`:: sage: m = Manifold(3, 'H^3', start_index=1) sage: X.<rh,th,ph> = m.chart(r'rh:[0,+oo):\rho th:[0,pi]:\theta ph:[0,2*pi):\phi') sage: g = Metric(m, 'g') sage: b = var('b') sage: g[1,1], g[2,2], g[3,3] = b^2, (b*sinh(rh))^2, (b*sinh(rh)*sin(th))^2 sage: g.view() # standard metric on H^3: g = b^2 drh*drh + b^2*sinh(rh)^2 dth*dth + b^2*sin(th)^2*sinh(rh)^2 dph*dph sage: C = g.weyl() ; C tensor field 'C(g)' of type (1,3) on the 3-dimensional manifold 'H^3' sage: C == 0 True """ from rank2field import IdentityMap if self._weyl is None: n = self.manifold.dim if n < 3: raise ValueError("The Weyl tensor is not defined for a " + "manifold of dimension n <= 2.") delta = IdentityMap(self.domain) riem = self.riemann() ric = self.ricci() rscal = self.ricci_scalar() # First index of the Ricci tensor raised with the metric ricup = ric.up(self, 0) # The identity map is expressed in a frame in which the Riemann # tensor is known delta.comp(riem.pick_a_frame()) aux = self*ricup + ric*delta - rscal/(n-1)* self*delta self._weyl = riem + 2/(n-2)* aux.antisymmetrize([2,3]) if name is None: self._weyl.name = "C(" + self.name + ")" else: self._weyl.name = name if latex_name is None: self._weyl.latex_name = r"\mathrm{C}\left(" + self.latex_name \ + r"\right)" else: self._weyl.latex_name = latex_name return self._weyl def determinant(self, frame=None): r""" Determinant of the metric components in the specified frame. INPUT: - ``frame`` -- (default: None) vector frame with respect to which the components `g_{ij}` of ``self`` are defined; if None, the domain's default frame is used. If a chart is provided, the associated coordinate frame is used OUTPUT: - the determinant `\det (g_{ij})`, as an instance of :class:`ScalarField` EXAMPLES: Metric determinant on a 2-dimensional manifold:: sage: M = Manifold(2, 'M', start_index=1) sage: X.<x,y> = M.chart('x y') sage: g = Metric(M, 'g') sage: g[1,1], g[1, 2], g[2, 2] = 1+x, x*y , 1-y sage: g[:] [ x + 1 x*y] [ x*y -y + 1] sage: s = g.determinant() # determinant in M's default frame sage: s.expr() -x^2*y^2 - (x + 1)*y + x + 1 Determinant in a frame different from the default's one:: sage: Y.<u,v> = M.chart('u v') sage: ch_X_Y = CoordChange(X, Y, x+y, x-y) sage: ch_X_Y.inverse() coordinate change from chart (M, (u, v)) to chart (M, (x, y)) sage: g.comp(Y.frame)[:, Y] [ 1/8*u^2 - 1/8*v^2 + 1/4*v + 1/2 1/4*u] [ 1/4*u -1/8*u^2 + 1/8*v^2 + 1/4*v + 1/2] sage: g.determinant(Y.frame).expr() -1/4*x^2*y^2 - 1/4*(x + 1)*y + 1/4*x + 1/4 sage: g.determinant(Y.frame).expr(Y) -1/64*u^4 - 1/64*v^4 + 1/32*(u^2 + 2)*v^2 - 1/16*u^2 + 1/4*v + 1/4 A chart can be passed instead of a frame:: sage: g.determinant(X) is g.determinant(X.frame) True sage: g.determinant(Y) is g.determinant(Y.frame) True The metric determinant depends on the frame:: sage: g.determinant(X.frame) == g.determinant(Y.frame) False """ from sage.matrix.constructor import matrix from scalarfield import ScalarField from utilities import simple_determinant, simplify_chain manif = self.manifold dom = self.domain if frame is None: frame = dom.def_frame if frame in dom.atlas: # frame is actually a chart and is changed to the associated # coordinate frame: frame = frame.frame if frame not in self._determinants: # a new computation is necessary resu = ScalarField(dom) gg = self.comp(frame) i1 = manif.sindex for chart in gg[[i1, i1]].express: gm = matrix( [[ gg[i, j, chart].express for j in manif.irange()] for i in manif.irange()] ) detgm = simplify_chain(simple_determinant(gm)) resu.add_expr(detgm, chart=chart) self._determinants[frame] = resu return self._determinants[frame] def sqrt_abs_det(self, frame=None): r""" Square root of the absolute value of the determinant of the metric components in the specified frame. INPUT: - ``frame`` -- (default: None) vector frame with respect to which the components `g_{ij}` of ``self`` are defined; if None, the domain's default frame is used. If a chart is provided, the associated coordinate frame is used OUTPUT: - `\sqrt{|\det (g_{ij})|}`, as an instance of :class:`ScalarField` EXAMPLES: Standard metric in the Euclidean space `\RR^3` with spherical coordinates:: sage: m = Manifold(3, 'M', start_index=1) sage: c_spher.<r,th,ph> = m.chart(r'r:[0,+oo) th:[0,pi]:\theta ph:[0,2*pi):\phi') sage: g = Metric(m, 'g') sage: g[1,1], g[2,2], g[3,3] = 1, r^2, (r*sin(th))^2 sage: g.view() g = dr*dr + r^2 dth*dth + r^2*sin(th)^2 dph*dph sage: g.sqrt_abs_det().expr() r^2*sin(th) Metric determinant on a 2-dimensional manifold:: sage: M = Manifold(2, 'M', start_index=1) sage: X.<x,y> = M.chart('x y') sage: g = Metric(M, 'g') sage: g[1,1], g[1, 2], g[2, 2] = 1+x, x*y , 1-y sage: g[:] [ x + 1 x*y] [ x*y -y + 1] sage: s = g.sqrt_abs_det() ; s scalar field on the 2-dimensional manifold 'M' sage: s.expr() sqrt(-x^2*y^2 - (x + 1)*y + x + 1) Determinant in a frame different from the default's one:: sage: Y.<u,v> = M.chart('u v') sage: ch_X_Y = CoordChange(X, Y, x+y, x-y) sage: ch_X_Y.inverse() coordinate change from chart (M, (u, v)) to chart (M, (x, y)) sage: g.comp(Y.frame)[:, Y] [ 1/8*u^2 - 1/8*v^2 + 1/4*v + 1/2 1/4*u] [ 1/4*u -1/8*u^2 + 1/8*v^2 + 1/4*v + 1/2] sage: g.sqrt_abs_det(Y.frame).expr() 1/2*sqrt(-x^2*y^2 - (x + 1)*y + x + 1) sage: g.sqrt_abs_det(Y.frame).expr(Y) 1/8*sqrt(-u^4 - v^4 + 2*(u^2 + 2)*v^2 - 4*u^2 + 16*v + 16) A chart can be passed instead of a frame:: sage: g.sqrt_abs_det(Y) is g.sqrt_abs_det(Y.frame) True The metric determinant depends on the frame:: sage: g.sqrt_abs_det(X.frame) == g.sqrt_abs_det(Y.frame) False """ from sage.functions.other import sqrt from scalarfield import ScalarField from utilities import simplify_chain manif = self.manifold dom = self.domain if frame is None: frame = dom.def_frame if frame in dom.atlas: # frame is actually a chart and is changed to the associated # coordinate frame: frame = frame.frame if frame not in self._sqrt_abs_dets: # a new computation is necessary detg = self.determinant(frame) resu = ScalarField(dom) for chart in detg.express: x = self._indic_signat * detg.express[chart].express # |g| x = simplify_chain(sqrt(x)) resu.add_expr(x, chart=chart) self._sqrt_abs_dets[frame] = resu return self._sqrt_abs_dets[frame] def volume_form(self, contra=0): r""" Volume form (Levi-Civita tensor) `\epsilon` associated with the metric. This assumes that the manifold is orientable. The volume form `\epsilon` is a `n`-form (`n` being the manifold's dimension) such that for any vector basis `(e_i)` that is orthonormal with respect to the metric, .. MATH:: \epsilon(e_1,\ldots,e_n) = \pm 1 There are only two such `n`-forms, which are opposite of each other. The volume form `\epsilon` is selected such that the domain's default frame is right-handed with respect to it. INPUT: - ``contra`` -- (default: 0) number of contravariant indices of the returned tensor OUTPUT: - if ``contra = 0`` (default value): the volume `n`-form `\epsilon`, as an instance of :class:`DiffForm` - if ``contra = k``, with `1\leq k \leq n`, the tensor field of type (k,n-k) formed from `\epsilon` by raising the first k indices with the metric (see method :meth:`TensorField.up`); the output is then an instance of :class:`TensorField`, with the appropriate antisymmetries EXAMPLES: Volume form on `\RR^3` with spherical coordinates:: sage: m = Manifold(3, 'M', start_index=1) sage: c_spher.<r,th,ph> = m.chart(r'r:[0,+oo) th:[0,pi]:\theta ph:[0,2*pi):\phi') sage: g = Metric(m, 'g') sage: g[1,1], g[2,2], g[3,3] = 1, r^2, (r*sin(th))^2 sage: g.view() g = dr*dr + r^2 dth*dth + r^2*sin(th)^2 dph*dph sage: eps = g.volume_form() ; eps 3-form 'eps_g' on the 3-dimensional manifold 'M' sage: eps.view() eps_g = r^2*sin(th) dr/\dth/\dph sage: eps[[1,2,3]] == g.sqrt_abs_det() True sage: latex(eps) \epsilon_{g} The tensor field of components `\epsilon^i_{\ \, jk}` (``contra=1``):: sage: eps1 = g.volume_form(1) ; eps1 tensor field of type (1,2) on the 3-dimensional manifold 'M' sage: eps1.symmetries() no symmetry; antisymmetry: (1, 2) sage: eps1[:] [[[0, 0, 0], [0, 0, r^2*sin(th)], [0, -r^2*sin(th), 0]], [[0, 0, -sin(th)], [0, 0, 0], [sin(th), 0, 0]], [[0, 1/sin(th), 0], [-1/sin(th), 0, 0], [0, 0, 0]]] The tensor field of components `\epsilon^{ij}_{\ \ k}` (``contra=2``):: sage: eps2 = g.volume_form(2) ; eps2 tensor field of type (2,1) on the 3-dimensional manifold 'M' sage: eps2.symmetries() no symmetry; antisymmetry: (0, 1) sage: eps2[:] [[[0, 0, 0], [0, 0, sin(th)], [0, -1/sin(th), 0]], [[0, 0, -sin(th)], [0, 0, 0], [1/(r^2*sin(th)), 0, 0]], [[0, 1/sin(th), 0], [-1/(r^2*sin(th)), 0, 0], [0, 0, 0]]] The tensor field of components `\epsilon^{ijk}` (``contra=3``):: sage: eps3 = g.volume_form(3) ; eps3 tensor field of type (3,0) on the 3-dimensional manifold 'M' sage: eps3.symmetries() no symmetry; antisymmetry: (0, 1, 2) sage: eps3[:] [[[0, 0, 0], [0, 0, 1/(r^2*sin(th))], [0, -1/(r^2*sin(th)), 0]], [[0, 0, -1/(r^2*sin(th))], [0, 0, 0], [1/(r^2*sin(th)), 0, 0]], [[0, 1/(r^2*sin(th)), 0], [-1/(r^2*sin(th)), 0, 0], [0, 0, 0]]] sage: eps3[1,2,3] 1/(r^2*sin(th)) sage: eps3[[1,2,3]] * g.sqrt_abs_det() == 1 True """ if self._vol_forms == []: # a new computation is necessary manif = self.manifold dom = self.domain ndim = manif.dim eps = DiffForm(dom, ndim, name='eps_'+self.name, latex_name=r'\epsilon_{'+self.latex_name+r'}') ind = tuple(range(manif.sindex, manif.sindex+ndim)) eps[[ind]] = self.sqrt_abs_det(dom.def_frame) self._vol_forms.append(eps) # Levi-Civita tensor constructed # Tensors related to the Levi-Civita one by index rising: for k in range(1, ndim+1): epskm1 = self._vol_forms[k-1] epsk = epskm1.up(self, k-1) if k > 1: # restoring the antisymmetry after the up operation: epsk = epsk.antisymmetrize(range(k)) self._vol_forms.append(epsk) return self._vol_forms[contra]
def __init__(self, domain, name=None, latex_name=None): TensorField.__init__(self, domain, 0, 2, name, latex_name, sym=(0,1))
def __init__(self, manifold, name=None, latex_name=None): TensorField.__init__(self, manifold, 1, 0, name, latex_name) self._init_dependencies()
def __init__(self, domain, name=None, latex_name=None): TensorField.__init__(self, domain, 0, 2, name, latex_name, sym=(0, 1))
def _init_derived(self): r""" Initialize the derived quantities """ TensorField._init_derived(self) self._inverse = None # inverse not set yet
def pullback(self, tensor): r""" Pullback operator associated with the differentiable mapping. INPUT: - ``tensor`` -- instance of :class:`TensorField` representing a fully covariant tensor field `T` on the *arrival* domain, i.e. a tensor field of type (0,p), with p a positive or zero integer. The case p=0 corresponds to a scalar field. OUTPUT: - instance of :class:`TensorField` representing a fully covariant tensor field on the *start* domain that is the pullback of `T` given by ``self``. EXAMPLES: Pullback on `S^2` of a scalar field defined on `R^3`:: sage: m = Manifold(2, 'S^2', start_index=1) sage: c_spher.<th,ph> = m.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') # spherical coord. on S^2 sage: n = Manifold(3, 'R^3', r'\RR^3', start_index=1) sage: c_cart.<x,y,z> = n.chart('x y z') # Cartesian coord. on R^3 sage: Phi = DiffMapping(m, n, (sin(th)*cos(ph), sin(th)*sin(ph), cos(th)), name='Phi', latex_name=r'\Phi') sage: f = ScalarField(n, x*y*z, name='f') ; f scalar field 'f' on the 3-dimensional manifold 'R^3' sage: f.view() f: (x, y, z) |--> x*y*z sage: pf = Phi.pullback(f) ; pf scalar field 'Phi_*(f)' on the 2-dimensional manifold 'S^2' sage: pf.view() Phi_*(f): (th, ph) |--> cos(ph)*cos(th)*sin(ph)*sin(th)^2 Pullback on `S^2` of the standard Euclidean metric on `R^3`:: sage: g = SymBilinFormField(n, 'g') sage: g[1,1], g[2,2], g[3,3] = 1, 1, 1 sage: g.view() g = dx*dx + dy*dy + dz*dz sage: pg = Phi.pullback(g) ; pg field of symmetric bilinear forms 'Phi_*(g)' on the 2-dimensional manifold 'S^2' sage: pg.view() Phi_*(g) = dth*dth + sin(th)^2 dph*dph Pullback on `S^2` of a 3-form on `R^3`:: sage: a = DiffForm(n, 3, 'A') sage: a[1,2,3] = f sage: a.view() A = x*y*z dx/\dy/\dz sage: pa = Phi.pullback(a) ; pa 3-form 'Phi_*(A)' on the 2-dimensional manifold 'S^2' sage: pa.view() # should be zero (as any 3-form on a 2-dimensional manifold) Phi_*(A) = 0 """ from scalarfield import ScalarField from vectorframe import CoordFrame from rank2field import SymBilinFormField from diffform import DiffForm, OneForm if not isinstance(tensor, TensorField): raise TypeError("The argument 'tensor' must be a tensor field.") dom1 = self.domain1 dom2 = self.domain2 if not tensor.domain.is_subdomain(dom2): raise TypeError("The tensor field is not defined on the mapping " + "arrival domain.") (ncon, ncov) = tensor.tensor_type if ncon != 0: raise TypeError("The pullback cannot be taken on a tensor " + "with some contravariant part.") resu_name = None ; resu_latex_name = None if self.name is not None and tensor.name is not None: resu_name = self.name + '_*(' + tensor.name + ')' if self.latex_name is not None and tensor.latex_name is not None: resu_latex_name = self.latex_name + '_*' + tensor.latex_name if ncov == 0: # Case of a scalar field # ---------------------- resu = ScalarField(dom1, name=resu_name, latex_name=resu_latex_name) for chart2 in tensor.express: for chart1 in dom1.atlas: if (chart1, chart2) in self.coord_expression: phi = self.coord_expression[(chart1, chart2)] coord1 = chart1.xx ff = tensor.express[chart2] resu.add_expr( ff(*(phi(*coord1))), chart1) return resu else: # Case of tensor field of rank >= 1 # --------------------------------- if isinstance(tensor, OneForm): resu = OneForm(dom1, name=resu_name, latex_name=resu_latex_name) elif isinstance(tensor, DiffForm): resu = DiffForm(dom1, ncov, name=resu_name, latex_name=resu_latex_name) elif isinstance(tensor, SymBilinFormField): resu = SymBilinFormField(dom1, name=resu_name, latex_name=resu_latex_name) else: resu = TensorField(dom1, 0, ncov, name=resu_name, latex_name=resu_latex_name, sym=tensor.sym, antisym=tensor.antisym) for frame2 in tensor.components: if isinstance(frame2, CoordFrame): chart2 = frame2.chart for chart1 in dom1.atlas: if (chart1, chart2) in self.coord_expression: # Computation at the component level: frame1 = chart1.frame tcomp = tensor.components[frame2] if isinstance(tcomp, CompFullySym): ptcomp = CompFullySym(frame1, ncov) elif isinstance(tcomp, CompFullyAntiSym): ptcomp = CompFullyAntiSym(frame1, ncov) elif isinstance(tcomp, CompWithSym): ptcomp = CompWithSym(frame1, ncov, sym=tcomp.sym, antisym=tcomp.antisym) else: ptcomp = Components(frame1, ncov) phi = self.coord_expression[(chart1, chart2)] jacob = phi.jacobian() # X2 coordinates expressed in terms of X1 ones via the mapping: coord2_1 = phi(*(chart1.xx)) si1 = dom1.manifold.sindex si2 = dom2.manifold.sindex for ind_new in ptcomp.non_redundant_index_generator(): res = 0 for ind_old in dom2.manifold.index_generator(ncov): ff = tcomp[[ind_old]].function_chart(chart2) t = FunctionChart(chart1, ff(*coord2_1)) for i in range(ncov): t *= jacob[ind_old[i]-si2][ind_new[i]-si1] res += t ptcomp[ind_new] = res resu.components[frame1] = ptcomp return resu
def __init__(self, domain, name=None, latex_name=None) : TensorField.__init__(self, domain, 1, 0, name, latex_name) self._init_dependencies()
def _init_derived(self): r""" Initialize the derived quantities """ TensorField._init_derived(self) self._exterior_derivative = None
def __init__(self, manifold, name=None, latex_name=None) : TensorField.__init__(self, manifold, 1, 0, name, latex_name) self._init_dependencies()
def __init__(self, manifold, name=None, latex_name=None): TensorField.__init__(self, manifold, 0, 2, name, latex_name, sym=(0,1))