def __invert__(self): r""" Return the inverse automorphism. OUTPUT: - instance of :class:`FreeModuleAutomorphism` representing the automorphism that is the inverse of ``self``. EXAMPLES: Inverse of an automorphism of a rank-3 free module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.automorphism(name='a') sage: a[e,:] = [[1,0,0],[0,-1,2],[0,1,-3]] sage: a.inverse() Automorphism a^(-1) of the Rank-3 free module M over the Integer Ring sage: a.inverse().parent() General linear group of the Rank-3 free module M over the Integer Ring Check that ``a.inverse()`` is indeed the inverse automorphism:: sage: a.inverse() * a Identity map of the Rank-3 free module M over the Integer Ring sage: a * a.inverse() Identity map of the Rank-3 free module M over the Integer Ring sage: a.inverse().inverse() == a True Another check is:: sage: a.inverse().matrix(e) [ 1 0 0] [ 0 -3 -2] [ 0 -1 -1] sage: a.inverse().matrix(e) == (a.matrix(e))^(-1) True The inverse is cached (as long as ``a`` is not modified):: sage: a.inverse() is a.inverse() True If ``a`` is modified, the inverse is automatically recomputed:: sage: a[0,0] = -1 sage: a.matrix(e) [-1 0 0] [ 0 -1 2] [ 0 1 -3] sage: a.inverse().matrix(e) # compare with above [-1 0 0] [ 0 -3 -2] [ 0 -1 -1] Shortcuts for :meth:`inverse` are the operator ``~`` and the exponent ``-1``:: sage: ~a is a.inverse() True sage: (a)^(-1) is a.inverse() True The inverse of the identity map is of course itself:: sage: id = M.identity_map() sage: id.inverse() is id True and we have:: sage: a*(a)^(-1) == id True sage: (a)^(-1)*a == id True If the name could cause some confusion, a bracket is added around the element before taking the inverse:: sage: c = M.automorphism(name='a^(-1)*b') sage: c[e,:] = [[1,0,0],[0,-1,1],[0,2,-1]] sage: c.inverse() Automorphism (a^(-1)*b)^(-1) of the Rank-3 free module M over the Integer Ring """ from .comp import Components if self._is_identity: return self if self._inverse is None: from sage.tensor.modules.format_utilities import is_atomic if self._name is None: inv_name = None else: if is_atomic(self._name, ['*']): inv_name = self._name + '^(-1)' else: inv_name = '(' + self._name + ')^(-1)' if self._latex_name is None: inv_latex_name = None else: if is_atomic(self._latex_name, ['\\circ', '\\otimes']): inv_latex_name = self._latex_name + r'^{-1}' else: inv_latex_name = r'\left(' + self._latex_name + \ r'\right)^{-1}' fmodule = self._fmodule si = fmodule._sindex nsi = fmodule._rank + si self._inverse = self.__class__(fmodule, inv_name, inv_latex_name) for basis in self._components: try: mat = self.matrix(basis) except (KeyError, ValueError): continue mat_inv = mat.inverse() cinv = Components(fmodule._ring, basis, 2, start_index=si, output_formatter=fmodule._output_formatter) for i in range(si, nsi): for j in range(si, nsi): cinv[i, j] = mat_inv[i-si,j-si] self._inverse._components[basis] = cinv self._inverse._inverse = self return self._inverse
def wedge(self, other): r""" Exterior product of ``self`` with the alternating form ``other``. INPUT: - ``other`` -- an alternating form OUTPUT: - instance of :class:`FreeModuleAltForm` representing the exterior product ``self/\other`` EXAMPLES: Exterior product of two linear forms:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('A') sage: a[:] = [1,-3,4] sage: b = M.linear_form('B') sage: b[:] = [2,-1,2] sage: c = a.wedge(b) ; c Alternating form A/\B of degree 2 on the Rank-3 free module M over the Integer Ring sage: c.display() A/\B = 5 e^0/\e^1 - 6 e^0/\e^2 - 2 e^1/\e^2 sage: latex(c) A\wedge B sage: latex(c.display()) A\wedge B = 5 e^0\wedge e^1 -6 e^0\wedge e^2 -2 e^1\wedge e^2 Test of the computation:: sage: a.wedge(b) == a*b - b*a True Exterior product of a linear form and an alternating form of degree 2:: sage: d = M.linear_form('D') sage: d[:] = [-1,2,4] sage: s = d.wedge(c) ; s Alternating form D/\A/\B of degree 3 on the Rank-3 free module M over the Integer Ring sage: s.display() D/\A/\B = 34 e^0/\e^1/\e^2 Test of the computation:: sage: s[0,1,2] == d[0]*c[1,2] + d[1]*c[2,0] + d[2]*c[0,1] True Let us check that the exterior product is associative:: sage: d.wedge(a.wedge(b)) == (d.wedge(a)).wedge(b) True and that it is graded anticommutative:: sage: a.wedge(b) == - b.wedge(a) True sage: d.wedge(c) == c.wedge(d) True """ from .format_utilities import is_atomic if not isinstance(other, FreeModuleAltForm): raise TypeError("the second argument for the exterior product " + "must be an alternating form") if other._tensor_rank == 0: return other*self if self._tensor_rank == 0: return self*other fmodule = self._fmodule basis = self.common_basis(other) if basis is None: raise ValueError("no common basis for the exterior product") rank_r = self._tensor_rank + other._tensor_rank cmp_s = self._components[basis] cmp_o = other._components[basis] cmp_r = CompFullyAntiSym(fmodule._ring, basis, rank_r, start_index=fmodule._sindex, output_formatter=fmodule._output_formatter) for ind_s, val_s in six.iteritems(cmp_s._comp): for ind_o, val_o in six.iteritems(cmp_o._comp): ind_r = ind_s + ind_o if len(ind_r) == len(set(ind_r)): # all indices are different cmp_r[[ind_r]] += val_s * val_o result = fmodule.alternating_form(rank_r) result._components[basis] = cmp_r if self._name is not None and other._name is not None: sname = self._name oname = other._name if not is_atomic(sname): sname = '(' + sname + ')' if not is_atomic(oname): oname = '(' + oname + ')' result._name = sname + '/\\' + oname if self._latex_name is not None and other._latex_name is not None: slname = self._latex_name olname = other._latex_name if not is_atomic(slname): slname = '(' + slname + ')' if not is_atomic(olname): olname = '(' + olname + ')' result._latex_name = slname + r'\wedge ' + olname return result
def wedge(self, other): r""" Exterior product with another differential form. INPUT: - ``other`` -- another differential form (on the same manifold) OUTPUT: - a :class:`DiffForm` of the exterior product ``self/\other`` EXAMPLES: Exterior product of two 1-forms on the 2-sphere:: sage: M = Manifold(2, 'S^2', start_index=1) # the 2-dimensional sphere S^2 sage: U = M.open_subset('U') ; V = M.open_subset('V') sage: M.declare_union(U,V) # S^2 is the union of U and V sage: c_xy.<x,y> = U.chart() ; c_uv.<u,v> = V.chart() # stereographic coord. (North and South) sage: xy_to_uv = c_xy.transition_map(c_uv, (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: uv_to_xy = xy_to_uv.inverse() sage: W = U.intersection(V) # The complement of the two poles sage: e_xy = c_xy.frame() ; e_uv = c_uv.frame() sage: a = M.diff_form(1, name='a') sage: a[e_xy,:] = y, x sage: a.add_comp_by_continuation(e_uv, W, c_uv) sage: b = M.diff_form(1, name='b') sage: b[e_xy,:] = x^2 + y^2, y sage: b.add_comp_by_continuation(e_uv, W, c_uv) sage: c = a.wedge(b); c 2-form a/\b on the 2-dimensional differentiable manifold S^2 sage: c.display(e_xy) a/\b = (-x^3 - (x - 1)*y^2) dx/\dy sage: c.display(e_uv) a/\b = -(v^2 - u)/(u^8 + 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) du/\dv """ from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm from sage.tensor.modules.format_utilities import is_atomic if self._domain.is_subset(other._domain): if not self._ambient_domain.is_subset(other._ambient_domain): raise ValueError( "incompatible ambient domains for exterior product") elif other._domain.is_subset(self._domain): if not other._ambient_domain.is_subset(self._ambient_domain): raise ValueError( "incompatible ambient domains for exterior product") dom_resu = self._domain.intersection(other._domain) ambient_dom_resu = self._ambient_domain.intersection( other._ambient_domain) self_r = self.restrict(dom_resu) other_r = other.restrict(dom_resu) if ambient_dom_resu.is_manifestly_parallelizable(): # call of the FreeModuleAltForm version: return FreeModuleAltForm.wedge(self_r, other_r) # otherwise, the result is created here: if self._name is not None and other._name is not None: sname = self._name oname = other._name if not is_atomic(sname): sname = '(' + sname + ')' if not is_atomic(oname): oname = '(' + oname + ')' resu_name = sname + '/\\' + oname if self._latex_name is not None and other._latex_name is not None: slname = self._latex_name olname = other._latex_name if not is_atomic(slname): slname = '(' + slname + ')' if not is_atomic(olname): olname = '(' + olname + ')' resu_latex_name = slname + r'\wedge ' + olname dest_map = self._vmodule._dest_map dest_map_resu = dest_map.restrict(dom_resu, subcodomain=ambient_dom_resu) vmodule = dom_resu.vector_field_module(dest_map=dest_map_resu) resu_degree = self._tensor_rank + other._tensor_rank resu = vmodule.alternating_form(resu_degree, name=resu_name, latex_name=resu_latex_name) for dom in self_r._restrictions: if dom in other_r._restrictions: resu._restrictions[dom] = self_r._restrictions[dom].wedge( other_r._restrictions[dom]) return resu
def interior_product(self, form): r""" Interior product with an alternating form. If ``self`` is an alternating contravariant tensor `A` of degree `p` and `B` is an alternating form of degree `q\geq p` on the same free module, the interior product of `A` by `B` is the alternating form `\iota_A B` of degree `q-p` defined by .. MATH:: (\iota_A B)_{i_1\ldots i_{q-p}} = A^{k_1\ldots k_p} B_{k_1\ldots k_p i_1\ldots i_{q-p}} .. NOTE:: ``A.interior_product(B)`` yields the same result as ``A.contract(0,..., p-1, B, 0,..., p-1)`` (cf. :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.contract`), but ``interior_product`` is more efficient, the alternating character of `A` being not used to reduce the computation in :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.contract` INPUT: - ``form`` -- alternating form `B` (instance of :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm`); the degree of `B` must be at least equal to the degree of ``self`` OUTPUT: - element of the base ring (case `p=q`) or :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` (case `p<q`) representing the interior product `\iota_A B`, where `A` is ``self`` .. SEEALSO:: :meth:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm.interior_product` for the interior product of an alternating form by an alternating contravariant tensor EXAMPLES: Let us consider a rank-4 free module:: sage: M = FiniteRankFreeModule(ZZ, 4, name='M', start_index=1) sage: e = M.basis('e') and various interior products on it, starting with a module element (``p=1``) and a linear form (``q=1``):: sage: a = M([-2,1,2,3], basis=e, name='A') sage: b = M.linear_form(name='B') sage: b[:] = [2, 0, -3, 4] sage: c = a.interior_product(b); c 2 sage: c == a.contract(b) True Case ``p=1`` and ``q=3``:: sage: b = M.alternating_form(3, name='B') sage: b[1,2,3], b[1,2,4], b[1,3,4], b[2,3,4] = 3, -1, 2, 5 sage: c = a.interior_product(b); c Alternating form i_A B of degree 2 on the Rank-4 free module M over the Integer Ring sage: c.display() i_A B = 3 e^1/\e^2 + 3 e^1/\e^3 - 3 e^1/\e^4 + 9 e^2/\e^3 - 8 e^2/\e^4 + e^3/\e^4 sage: latex(c) \iota_{A} B sage: c == a.contract(b) True Case ``p=2`` and ``q=3``:: sage: a = M.alternating_contravariant_tensor(2, name='A') sage: a[1,2], a[1,3], a[1,4] = 2, -5, 3 sage: a[2,3], a[2,4], a[3,4] = -1, 4, 2 sage: c = a.interior_product(b); c Linear form i_A B on the Rank-4 free module M over the Integer Ring sage: c.display() i_A B = -6 e^1 + 56 e^2 - 40 e^3 - 34 e^4 sage: c == a.contract(0, 1, b, 0, 1) # contraction on all indices of a True Case ``p=2`` and ``q=4``:: sage: b = M.alternating_form(4, name='B') sage: b[1,2,3,4] = 5 sage: c = a.interior_product(b); c Alternating form i_A B of degree 2 on the Rank-4 free module M over the Integer Ring sage: c.display() i_A B = 20 e^1/\e^2 - 40 e^1/\e^3 - 10 e^1/\e^4 + 30 e^2/\e^3 + 50 e^2/\e^4 + 20 e^3/\e^4 sage: c == a.contract(0, 1, b, 0, 1) True Case ``p=2`` and ``q=2``:: sage: b = M.alternating_form(2) sage: b[1,2], b[1,3], b[1,4] = 6, 0, -2 sage: b[2,3], b[2,4], b[3,4] = 2, 3, 4 sage: c = a.interior_product(b); c 48 sage: c == a.contract(0, 1, b, 0, 1) True Case ``p=3`` and ``q=3``:: sage: a = M.alternating_contravariant_tensor(3, name='A') sage: a[1,2,3], a[1,2,4], a[1,3,4], a[2,3,4] = -3, 2, 8, -5 sage: b = M.alternating_form(3, name='B') sage: b[1,2,3], b[1,2,4], b[1,3,4], b[2,3,4] = 3, -1, 2, 5 sage: c = a.interior_product(b); c -120 sage: c == a.contract(0, 1, 2, b, 0, 1, 2) True Case ``p=3`` and ``q=4``:: sage: b = M.alternating_form(4, name='B') sage: b[1,2,3,4] = 5 sage: c = a.interior_product(b); c Linear form i_A B on the Rank-4 free module M over the Integer Ring sage: c.display() i_A B = 150 e^1 + 240 e^2 - 60 e^3 - 90 e^4 sage: c == a.contract(0, 1, 2, b, 0, 1, 2) True Case ``p=4`` and ``q=4``:: sage: a = M.alternating_contravariant_tensor(4, name='A') sage: a[1,2,3,4] = -2 sage: c = a.interior_product(b); c -240 sage: c == a.contract(0, 1, 2, 3, b, 0, 1, 2, 3) True """ from .format_utilities import is_atomic from .free_module_alt_form import FreeModuleAltForm if not isinstance(form, FreeModuleAltForm): raise TypeError("{} is not an alternating form".format(form)) p_res = form._tensor_rank - self._tensor_rank # degree of the result if self._tensor_rank == 1: # Case p = 1: res = self.contract(form) # contract() deals efficiently with # the antisymmetry for p = 1 else: # Case p > 1: if form._fmodule != self._fmodule: raise ValueError( "{} is not defined on the same ".format(form) + "module as the {}".format(self)) if form._tensor_rank < self._tensor_rank: raise ValueError( "the degree of the {} is lower ".format(form) + "than that of the {}".format(self)) # Interior product at the component level: basis = self.common_basis(form) if basis is None: raise ValueError("no common basis for the interior product") comp = self._components[basis].interior_product( form._components[basis]) if p_res == 0: res = comp # result is a scalar else: res = self._fmodule.tensor_from_comp((0, p_res), comp) # Name of the result res_name = None if self._name is not None and form._name is not None: sname = self._name oname = form._name if not is_atomic(sname): sname = '(' + sname + ')' if not is_atomic(oname): oname = '(' + oname + ')' res_name = 'i_' + sname + ' ' + oname res_latex_name = None if self._latex_name is not None and form._latex_name is not None: slname = self._latex_name olname = form._latex_name if not is_atomic(olname): olname = r'\left(' + olname + r'\right)' res_latex_name = r'\iota_{' + slname + '} ' + olname if p_res == 0: if res_name: try: # there is no guarantee that base ring elements have # set_name res.set_name(res_name, latex_name=res_latex_name) except (AttributeError, TypeError): pass else: res.set_name(res_name, latex_name=res_latex_name) return res
def display(self, basis=None, format_spec=None): r""" Display the alternating form ``self`` in terms of its expansion w.r.t. a given module basis. The expansion is actually performed onto exterior products of elements of the cobasis (dual basis) associated with ``basis`` (see examples below). The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode). INPUT: - ``basis`` -- (default: ``None``) basis of the free module with respect to which the alternating form is expanded; if none is provided, the module's default basis is assumed - ``format_spec`` -- (default: ``None``) format specification passed to ``self._fmodule._output_formatter`` to format the output EXAMPLES: Display of an alternating form of degree 1 (linear form) on a rank-3 free module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: e.dual_basis() Dual basis (e^0,e^1,e^2) on the Rank-3 free module M over the Integer Ring sage: a = M.linear_form('a', latex_name=r'\alpha') sage: a[:] = [1,-3,4] sage: a.display(e) a = e^0 - 3 e^1 + 4 e^2 sage: a.display() # a shortcut since e is M's default basis a = e^0 - 3 e^1 + 4 e^2 sage: latex(a.display()) # display in the notebook \alpha = e^0 -3 e^1 + 4 e^2 A shortcut is ``disp()``:: sage: a.disp() a = e^0 - 3 e^1 + 4 e^2 Display of an alternating form of degree 2 on a rank-3 free module:: sage: b = M.alternating_form(2, 'b', latex_name=r'\beta') sage: b[0,1], b[0,2], b[1,2] = 3, 2, -1 sage: b.display() b = 3 e^0/\e^1 + 2 e^0/\e^2 - e^1/\e^2 sage: latex(b.display()) # display in the notebook \beta = 3 e^0\wedge e^1 + 2 e^0\wedge e^2 -e^1\wedge e^2 Display of an alternating form of degree 3 on a rank-3 free module:: sage: c = M.alternating_form(3, 'c') sage: c[0,1,2] = 4 sage: c.display() c = 4 e^0/\e^1/\e^2 sage: latex(c.display()) c = 4 e^0\wedge e^1\wedge e^2 Display of a vanishing alternating form:: sage: c[0,1,2] = 0 # the only independent component set to zero sage: c.is_zero() True sage: c.display() c = 0 sage: latex(c.display()) c = 0 sage: c[0,1,2] = 4 # value restored for what follows Display in a basis which is not the default one:: sage: aut = M.automorphism(matrix=[[0,1,0], [0,0,-1], [1,0,0]], ....: basis=e) sage: f = e.new_basis(aut, 'f') sage: a.display(f) a = 4 f^0 + f^1 + 3 f^2 sage: a.disp(f) # shortcut notation a = 4 f^0 + f^1 + 3 f^2 sage: b.display(f) b = -2 f^0/\f^1 - f^0/\f^2 - 3 f^1/\f^2 sage: c.display(f) c = -4 f^0/\f^1/\f^2 The output format can be set via the argument ``output_formatter`` passed at the module construction:: sage: N = FiniteRankFreeModule(QQ, 3, name='N', start_index=1, ....: output_formatter=Rational.numerical_approx) sage: e = N.basis('e') sage: b = N.alternating_form(2, 'b') sage: b[1,2], b[1,3], b[2,3] = 1/3, 5/2, 4 sage: b.display() # default format (53 bits of precision) b = 0.333333333333333 e^1/\e^2 + 2.50000000000000 e^1/\e^3 + 4.00000000000000 e^2/\e^3 The output format is then controlled by the argument ``format_spec`` of the method :meth:`display`:: sage: b.display(format_spec=10) # 10 bits of precision b = 0.33 e^1/\e^2 + 2.5 e^1/\e^3 + 4.0 e^2/\e^3 Check that the bug reported in :trac:`22520` is fixed:: sage: M = FiniteRankFreeModule(SR, 2, name='M') sage: e = M.basis('e') sage: a = M.alternating_form(2) sage: a[0,1] = SR.var('t', domain='real') sage: a.display() t e^0/\e^1 """ from sage.misc.latex import latex from sage.tensor.modules.format_utilities import is_atomic, \ FormattedExpansion if basis is None: basis = self._fmodule._def_basis cobasis = basis.dual_basis() comp = self.comp(basis) terms_txt = [] terms_latex = [] for ind in comp.non_redundant_index_generator(): ind_arg = ind + (format_spec,) coef = comp[ind_arg] if not (coef == 0): # NB: coef != 0 would return False for # cases in which Sage cannot conclude # see :trac:`22520` bases_txt = [] bases_latex = [] for k in range(self._tensor_rank): bases_txt.append(cobasis[ind[k]]._name) bases_latex.append(latex(cobasis[ind[k]])) basis_term_txt = "/\\".join(bases_txt) basis_term_latex = r"\wedge ".join(bases_latex) coef_txt = repr(coef) if coef_txt == "1": terms_txt.append(basis_term_txt) terms_latex.append(basis_term_latex) elif coef_txt == "-1": terms_txt.append("-" + basis_term_txt) terms_latex.append("-" + basis_term_latex) else: coef_latex = latex(coef) if is_atomic(coef_txt): terms_txt.append(coef_txt + " " + basis_term_txt) else: terms_txt.append("(" + coef_txt + ") " + basis_term_txt) if is_atomic(coef_latex): terms_latex.append(coef_latex + basis_term_latex) else: terms_latex.append(r"\left(" + coef_latex + \ r"\right)" + basis_term_latex) if not terms_txt: expansion_txt = "0" else: expansion_txt = terms_txt[0] for term in terms_txt[1:]: if term[0] == "-": expansion_txt += " - " + term[1:] else: expansion_txt += " + " + term if not terms_latex: expansion_latex = "0" else: expansion_latex = terms_latex[0] for term in terms_latex[1:]: if term[0] == "-": expansion_latex += term else: expansion_latex += "+" + term if self._name is None: resu_txt = expansion_txt else: resu_txt = self._name + " = " + expansion_txt if self._latex_name is None: resu_latex = expansion_latex else: resu_latex = latex(self) + " = " + expansion_latex return FormattedExpansion(resu_txt, resu_latex)
def wedge(self, other): r""" Exterior product with another differential form. INPUT: - ``other`` -- another differential form (on the same manifold) OUTPUT: - a :class:`DiffForm` of the exterior product ``self/\other`` EXAMPLES: Exterior product of two 1-forms on the 2-sphere:: sage: M = Manifold(2, 'S^2', start_index=1) # the 2-dimensional sphere S^2 sage: U = M.open_subset('U') ; V = M.open_subset('V') sage: M.declare_union(U,V) # S^2 is the union of U and V sage: c_xy.<x,y> = U.chart() ; c_uv.<u,v> = V.chart() # stereographic coord. (North and South) sage: xy_to_uv = c_xy.transition_map(c_uv, (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: uv_to_xy = xy_to_uv.inverse() sage: W = U.intersection(V) # The complement of the two poles sage: e_xy = c_xy.frame() ; e_uv = c_uv.frame() sage: a = M.diff_form(1, name='a') sage: a[e_xy,:] = y, x sage: a.add_comp_by_continuation(e_uv, W, c_uv) sage: b = M.diff_form(1, name='b') sage: b[e_xy,:] = x^2 + y^2, y sage: b.add_comp_by_continuation(e_uv, W, c_uv) sage: c = a.wedge(b); c 2-form a/\b on the 2-dimensional differentiable manifold S^2 sage: c.display(e_xy) a/\b = (-x^3 - (x - 1)*y^2) dx/\dy sage: c.display(e_uv) a/\b = -(v^2 - u)/(u^8 + 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) du/\dv """ from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm from sage.tensor.modules.format_utilities import is_atomic if self._domain.is_subset(other._domain): if not self._ambient_domain.is_subset(other._ambient_domain): raise ValueError("incompatible ambient domains for exterior product") elif other._domain.is_subset(self._domain): if not other._ambient_domain.is_subset(self._ambient_domain): raise ValueError("incompatible ambient domains for exterior product") dom_resu = self._domain.intersection(other._domain) ambient_dom_resu = self._ambient_domain.intersection(other._ambient_domain) self_r = self.restrict(dom_resu) other_r = other.restrict(dom_resu) if ambient_dom_resu.is_manifestly_parallelizable(): # call of the FreeModuleAltForm version: return FreeModuleAltForm.wedge(self_r, other_r) # otherwise, the result is created here: if self._name is not None and other._name is not None: sname = self._name oname = other._name if not is_atomic(sname): sname = '(' + sname + ')' if not is_atomic(oname): oname = '(' + oname + ')' resu_name = sname + '/\\' + oname if self._latex_name is not None and other._latex_name is not None: slname = self._latex_name olname = other._latex_name if not is_atomic(slname): slname = '(' + slname + ')' if not is_atomic(olname): olname = '(' + olname + ')' resu_latex_name = slname + r'\wedge ' + olname dest_map = self._vmodule._dest_map dest_map_resu = dest_map.restrict(dom_resu, subcodomain=ambient_dom_resu) vmodule = dom_resu.vector_field_module(dest_map=dest_map_resu) resu_degree = self._tensor_rank + other._tensor_rank resu = vmodule.alternating_form(resu_degree, name=resu_name, latex_name=resu_latex_name) for dom in self_r._restrictions: if dom in other_r._restrictions: resu._restrictions[dom] = self_r._restrictions[dom].wedge( other_r._restrictions[dom]) return resu
def display(self, basis=None, format_spec=None): r""" Display the alternating contravariant tensor ``self`` in terms of its expansion w.r.t. a given module basis. The expansion is actually performed onto exterior products of elements of ``basis`` (see examples below). The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode). INPUT: - ``basis`` -- (default: ``None``) basis of the free module with respect to which ``self`` is expanded; if none is provided, the module's default basis is assumed - ``format_spec`` -- (default: ``None``) format specification passed to ``self._fmodule._output_formatter`` to format the output EXAMPLES: Display of an alternating contravariant tensor of degree 2 on a rank-3 free module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.alternating_contravariant_tensor(2, 'a', latex_name=r'\alpha') sage: a[0,1], a[0,2], a[1,2] = 3, 2, -1 sage: a.display() a = 3 e_0/\e_1 + 2 e_0/\e_2 - e_1/\e_2 sage: latex(a.display()) # display in the notebook \alpha = 3 e_{0}\wedge e_{1} + 2 e_{0}\wedge e_{2} -e_{1}\wedge e_{2} Display of an alternating contravariant tensor of degree 3 on a rank-3 free module:: sage: b = M.alternating_contravariant_tensor(3, 'b') sage: b[0,1,2] = 4 sage: b.display() b = 4 e_0/\e_1/\e_2 sage: latex(b.display()) b = 4 e_{0}\wedge e_{1}\wedge e_{2} Display of a vanishing alternating contravariant tensor:: sage: b[0,1,2] = 0 # the only independent component set to zero sage: b.is_zero() True sage: b.display() b = 0 sage: latex(b.display()) b = 0 sage: b[0,1,2] = 4 # value restored for what follows Display in a basis which is not the default one:: sage: aut = M.automorphism(matrix=[[0,1,0], [0,0,-1], [1,0,0]], ....: basis=e) sage: f = e.new_basis(aut, 'f') sage: a.display(f) a = -2 f_0/\f_1 - f_0/\f_2 - 3 f_1/\f_2 sage: a.disp(f) # shortcut notation a = -2 f_0/\f_1 - f_0/\f_2 - 3 f_1/\f_2 sage: b.display(f) b = -4 f_0/\f_1/\f_2 The output format can be set via the argument ``output_formatter`` passed at the module construction:: sage: N = FiniteRankFreeModule(QQ, 3, name='N', start_index=1, ....: output_formatter=Rational.numerical_approx) sage: e = N.basis('e') sage: a = N.alternating_contravariant_tensor(2, 'a') sage: a[1,2], a[1,3], a[2,3] = 1/3, 5/2, 4 sage: a.display() # default format (53 bits of precision) a = 0.333333333333333 e_1/\e_2 + 2.50000000000000 e_1/\e_3 + 4.00000000000000 e_2/\e_3 The output format is then controlled by the argument ``format_spec`` of the method :meth:`display`:: sage: a.display(format_spec=10) # 10 bits of precision a = 0.33 e_1/\e_2 + 2.5 e_1/\e_3 + 4.0 e_2/\e_3 """ from sage.misc.latex import latex from sage.tensor.modules.format_utilities import (is_atomic, FormattedExpansion) basis, format_spec = self._preparse_display(basis=basis, format_spec=format_spec) comp = self.comp(basis) terms_txt = [] terms_latex = [] for ind in comp.non_redundant_index_generator(): ind_arg = ind + (format_spec, ) coef = comp[ind_arg] # Check whether the coefficient is zero, preferably via # the fast method is_trivial_zero(): if hasattr(coef, 'is_trivial_zero'): zero_coef = coef.is_trivial_zero() else: zero_coef = coef == 0 if not zero_coef: bases_txt = [] bases_latex = [] for k in range(self._tensor_rank): bases_txt.append(basis[ind[k]]._name) bases_latex.append(latex(basis[ind[k]])) basis_term_txt = "/\\".join(bases_txt) basis_term_latex = r"\wedge ".join(bases_latex) coef_txt = repr(coef) if coef_txt == "1": terms_txt.append(basis_term_txt) terms_latex.append(basis_term_latex) elif coef_txt == "-1": terms_txt.append("-" + basis_term_txt) terms_latex.append("-" + basis_term_latex) else: coef_latex = latex(coef) if is_atomic(coef_txt): terms_txt.append(coef_txt + " " + basis_term_txt) else: terms_txt.append("(" + coef_txt + ") " + basis_term_txt) if is_atomic(coef_latex): terms_latex.append(coef_latex + basis_term_latex) else: terms_latex.append(r"\left(" + coef_latex + \ r"\right)" + basis_term_latex) if not terms_txt: expansion_txt = "0" else: expansion_txt = terms_txt[0] for term in terms_txt[1:]: if term[0] == "-": expansion_txt += " - " + term[1:] else: expansion_txt += " + " + term if not terms_latex: expansion_latex = "0" else: expansion_latex = terms_latex[0] for term in terms_latex[1:]: if term[0] == "-": expansion_latex += term else: expansion_latex += "+" + term if self._name is None: resu_txt = expansion_txt else: resu_txt = self._name + " = " + expansion_txt if self._latex_name is None: resu_latex = expansion_latex else: resu_latex = latex(self) + " = " + expansion_latex return FormattedExpansion(resu_txt, resu_latex)
def wedge(self, other): r""" Exterior product of ``self`` with the alternating form ``other``. INPUT: - ``other`` -- an alternating form OUTPUT: - instance of :class:`FreeModuleAltForm` representing the exterior product ``self/\other`` EXAMPLES: Exterior product of two linear forms:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('A') sage: a[:] = [1,-3,4] sage: b = M.linear_form('B') sage: b[:] = [2,-1,2] sage: c = a.wedge(b) ; c Alternating form A/\B of degree 2 on the Rank-3 free module M over the Integer Ring sage: c.display() A/\B = 5 e^0/\e^1 - 6 e^0/\e^2 - 2 e^1/\e^2 sage: latex(c) A\wedge B sage: latex(c.display()) A\wedge B = 5 e^{0}\wedge e^{1} -6 e^{0}\wedge e^{2} -2 e^{1}\wedge e^{2} Test of the computation:: sage: a.wedge(b) == a*b - b*a True Exterior product of a linear form and an alternating form of degree 2:: sage: d = M.linear_form('D') sage: d[:] = [-1,2,4] sage: s = d.wedge(c) ; s Alternating form D/\A/\B of degree 3 on the Rank-3 free module M over the Integer Ring sage: s.display() D/\A/\B = 34 e^0/\e^1/\e^2 Test of the computation:: sage: s[0,1,2] == d[0]*c[1,2] + d[1]*c[2,0] + d[2]*c[0,1] True Let us check that the exterior product is associative:: sage: d.wedge(a.wedge(b)) == (d.wedge(a)).wedge(b) True and that it is graded anticommutative:: sage: a.wedge(b) == - b.wedge(a) True sage: d.wedge(c) == c.wedge(d) True """ from .format_utilities import is_atomic if not isinstance(other, FreeModuleAltForm): raise TypeError("the second argument for the exterior product " + "must be an alternating form") if other._tensor_rank == 0: return other*self if self._tensor_rank == 0: return self*other fmodule = self._fmodule basis = self.common_basis(other) if basis is None: raise ValueError("no common basis for the exterior product") rank_r = self._tensor_rank + other._tensor_rank cmp_s = self._components[basis] cmp_o = other._components[basis] cmp_r = CompFullyAntiSym(fmodule._ring, basis, rank_r, start_index=fmodule._sindex, output_formatter=fmodule._output_formatter) for ind_s, val_s in cmp_s._comp.items(): for ind_o, val_o in cmp_o._comp.items(): ind_r = ind_s + ind_o if len(ind_r) == len(set(ind_r)): # all indices are different cmp_r[[ind_r]] += val_s * val_o result = fmodule.alternating_form(rank_r) result._components[basis] = cmp_r if self._name is not None and other._name is not None: sname = self._name oname = other._name if not is_atomic(sname): sname = '(' + sname + ')' if not is_atomic(oname): oname = '(' + oname + ')' result._name = sname + '/\\' + oname if self._latex_name is not None and other._latex_name is not None: slname = self._latex_name olname = other._latex_name if not is_atomic(slname): slname = '(' + slname + ')' if not is_atomic(olname): olname = '(' + olname + ')' result._latex_name = slname + r'\wedge ' + olname return result
def interior_product(self, alt_tensor): r""" Interior product with an alternating contravariant tensor. If ``self`` is an alternating form `A` of degree `p` and `B` is an alternating contravariant tensor of degree `q\geq p` on the same free module, the interior product of `A` by `B` is the alternating contravariant tensor `\iota_A B` of degree `q-p` defined by .. MATH:: (\iota_A B)^{i_1\ldots i_{q-p}} = A_{k_1\ldots k_p} B^{k_1\ldots k_p i_1\ldots i_{q-p}} .. NOTE:: ``A.interior_product(B)`` yields the same result as ``A.contract(0,..., p-1, B, 0,..., p-1)`` (cf. :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.contract`), but ``interior_product`` is more efficient, the alternating character of `A` being not used to reduce the computation in :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.contract` INPUT: - ``alt_tensor`` -- alternating contravariant tensor `B` (instance of :class:`~sage.tensor.modules.alternating_contr_tensor.AlternatingContrTensor`); the degree of `B` must be at least equal to the degree of ``self`` OUTPUT: - element of the base ring (case `p=q`) or :class:`~sage.tensor.modules.alternating_contr_tensor.AlternatingContrTensor` (case `p<q`) representing the interior product `\iota_A B`, where `A` is ``self`` .. SEEALSO:: :meth:`~sage.tensor.modules.alternating_contr_tensor.AlternatingContrTensor.interior_product` for the interior product of an alternating contravariant tensor by an alternating form EXAMPLES: Let us consider a rank-3 free module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') and various interior products on it, starting with a linear form (``p=1``) and a module element (``q=1``):: sage: a = M.linear_form(name='A') sage: a[:] = [-2, 4, 3] sage: b = M([3, 1, 5], basis=e, name='B') sage: c = a.interior_product(b); c 13 sage: c == a.contract(b) True Case ``p=1`` and ``q=2``:: sage: b = M.alternating_contravariant_tensor(2, name='B') sage: b[1,2], b[1,3], b[2,3] = 5, 2, 3 sage: c = a.interior_product(b); c Element i_A B of the Rank-3 free module M over the Integer Ring sage: c.display() i_A B = -26 e_1 - 19 e_2 + 8 e_3 sage: latex(c) \iota_{A} B sage: c == a.contract(b) True Case ``p=1`` and ``q=3``:: sage: b = M.alternating_contravariant_tensor(3, name='B') sage: b[1,2,3] = 5 sage: c = a.interior_product(b); c Alternating contravariant tensor i_A B of degree 2 on the Rank-3 free module M over the Integer Ring sage: c.display() i_A B = 15 e_1/\e_2 - 20 e_1/\e_3 - 10 e_2/\e_3 sage: c == a.contract(b) True Case ``p=2`` and ``q=2``:: sage: a = M.alternating_form(2, name='A') sage: a[1,2], a[1,3], a[2,3] = 2, -3, 1 sage: b = M.alternating_contravariant_tensor(2, name='B') sage: b[1,2], b[1,3], b[2,3] = 5, 2, 3 sage: c = a.interior_product(b); c 14 sage: c == a.contract(0, 1, b, 0, 1) # contraction on all indices of a True Case ``p=2`` and ``q=3``:: sage: b = M.alternating_contravariant_tensor(3, name='B') sage: b[1,2,3] = 5 sage: c = a.interior_product(b); c Element i_A B of the Rank-3 free module M over the Integer Ring sage: c.display() i_A B = 10 e_1 + 30 e_2 + 20 e_3 sage: c == a.contract(0, 1, b, 0, 1) True Case ``p=3`` and ``q=3``:: sage: a = M.alternating_form(3, name='A') sage: a[1,2,3] = -2 sage: c = a.interior_product(b); c -60 sage: c == a.contract(0, 1, 2, b, 0, 1, 2) True """ from .format_utilities import is_atomic from .alternating_contr_tensor import AlternatingContrTensor if not isinstance(alt_tensor, AlternatingContrTensor): raise TypeError("{} is not an alternating ".format(alt_tensor) + "contravariant tensor") p_res = alt_tensor._tensor_rank - self._tensor_rank # degree of result if self._tensor_rank == 1: # Case p = 1: res = self.contract(alt_tensor) # contract() deals efficiently # with antisymmetry for p = 1 else: # Case p > 1: if alt_tensor._fmodule != self._fmodule: raise ValueError("{} is not defined on ".format(alt_tensor) + "the same module as the {}".format(self)) if alt_tensor._tensor_rank < self._tensor_rank: raise ValueError("the degree of the {} ".format(alt_tensor) + "is lower than that of the {}".format(self)) # Interior product at the component level: basis = self.common_basis(alt_tensor) if basis is None: raise ValueError("no common basis for the interior product") comp = self._components[basis].interior_product( alt_tensor._components[basis]) if p_res == 0: res = comp # result is a scalar else: res = self._fmodule.tensor_from_comp((p_res, 0), comp) # Name of the result res_name = None if self._name is not None and alt_tensor._name is not None: sname = self._name oname = alt_tensor._name if not is_atomic(sname): sname = '(' + sname + ')' if not is_atomic(oname): oname = '(' + oname + ')' res_name = 'i_' + sname + ' ' + oname res_latex_name = None if self._latex_name is not None and alt_tensor._latex_name is not None: slname = self._latex_name olname = alt_tensor._latex_name if not is_atomic(olname): olname = r'\left(' + olname + r'\right)' res_latex_name = r'\iota_{' + slname + '} ' + olname if p_res == 0: if res_name: try: # there is no guarantee that base ring elements have # set_name res.set_name(res_name, latex_name=res_latex_name) except (AttributeError, TypeError): pass else: res.set_name(res_name, latex_name=res_latex_name) return res
def interior_product(self, form): r""" Interior product with an alternating form. If ``self`` is an alternating contravariant tensor `A` of degree `p` and `B` is an alternating form of degree `q\geq p` on the same free module, the interior product of `A` by `B` is the alternating form `\iota_A B` of degree `q-p` defined by .. MATH:: (\iota_A B)_{i_1\ldots i_{q-p}} = A^{k_1\ldots k_p} B_{k_1\ldots k_p i_1\ldots i_{q-p}} .. NOTE:: ``A.interior_product(B)`` yields the same result as ``A.contract(0,..., p-1, B, 0,..., p-1)`` (cf. :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.contract`), but ``interior_product`` is more efficient, the alternating character of `A` being not used to reduce the computation in :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.contract` INPUT: - ``form`` -- alternating form `B` (instance of :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm`); the degree of `B` must be at least equal to the degree of ``self`` OUTPUT: - element of the base ring (case `p=q`) or :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` (case `p<q`) representing the interior product `\iota_A B`, where `A` is ``self`` .. SEEALSO:: :meth:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm.interior_product` for the interior product of an alternating form by an alternating contravariant tensor EXAMPLES: Let us consider a rank-4 free module:: sage: M = FiniteRankFreeModule(ZZ, 4, name='M', start_index=1) sage: e = M.basis('e') and various interior products on it, starting with a module element (``p=1``) and a linear form (``q=1``):: sage: a = M([-2,1,2,3], basis=e, name='A') sage: b = M.linear_form(name='B') sage: b[:] = [2, 0, -3, 4] sage: c = a.interior_product(b); c 2 sage: c == a.contract(b) True Case ``p=1`` and ``q=3``:: sage: b = M.alternating_form(3, name='B') sage: b[1,2,3], b[1,2,4], b[1,3,4], b[2,3,4] = 3, -1, 2, 5 sage: c = a.interior_product(b); c Alternating form i_A B of degree 2 on the Rank-4 free module M over the Integer Ring sage: c.display() i_A B = 3 e^1/\e^2 + 3 e^1/\e^3 - 3 e^1/\e^4 + 9 e^2/\e^3 - 8 e^2/\e^4 + e^3/\e^4 sage: latex(c) \iota_{A} B sage: c == a.contract(b) True Case ``p=2`` and ``q=3``:: sage: a = M.alternating_contravariant_tensor(2, name='A') sage: a[1,2], a[1,3], a[1,4] = 2, -5, 3 sage: a[2,3], a[2,4], a[3,4] = -1, 4, 2 sage: c = a.interior_product(b); c Linear form i_A B on the Rank-4 free module M over the Integer Ring sage: c.display() i_A B = -6 e^1 + 56 e^2 - 40 e^3 - 34 e^4 sage: c == a.contract(0, 1, b, 0, 1) # contraction on all indices of a True Case ``p=2`` and ``q=4``:: sage: b = M.alternating_form(4, name='B') sage: b[1,2,3,4] = 5 sage: c = a.interior_product(b); c Alternating form i_A B of degree 2 on the Rank-4 free module M over the Integer Ring sage: c.display() i_A B = 20 e^1/\e^2 - 40 e^1/\e^3 - 10 e^1/\e^4 + 30 e^2/\e^3 + 50 e^2/\e^4 + 20 e^3/\e^4 sage: c == a.contract(0, 1, b, 0, 1) True Case ``p=2`` and ``q=2``:: sage: b = M.alternating_form(2) sage: b[1,2], b[1,3], b[1,4] = 6, 0, -2 sage: b[2,3], b[2,4], b[3,4] = 2, 3, 4 sage: c = a.interior_product(b); c 48 sage: c == a.contract(0, 1, b, 0, 1) True Case ``p=3`` and ``q=3``:: sage: a = M.alternating_contravariant_tensor(3, name='A') sage: a[1,2,3], a[1,2,4], a[1,3,4], a[2,3,4] = -3, 2, 8, -5 sage: b = M.alternating_form(3, name='B') sage: b[1,2,3], b[1,2,4], b[1,3,4], b[2,3,4] = 3, -1, 2, 5 sage: c = a.interior_product(b); c -120 sage: c == a.contract(0, 1, 2, b, 0, 1, 2) True Case ``p=3`` and ``q=4``:: sage: b = M.alternating_form(4, name='B') sage: b[1,2,3,4] = 5 sage: c = a.interior_product(b); c Linear form i_A B on the Rank-4 free module M over the Integer Ring sage: c.display() i_A B = 150 e^1 + 240 e^2 - 60 e^3 - 90 e^4 sage: c == a.contract(0, 1, 2, b, 0, 1, 2) True Case ``p=4`` and ``q=4``:: sage: a = M.alternating_contravariant_tensor(4, name='A') sage: a[1,2,3,4] = -2 sage: c = a.interior_product(b); c -240 sage: c == a.contract(0, 1, 2, 3, b, 0, 1, 2, 3) True """ from .format_utilities import is_atomic from .free_module_alt_form import FreeModuleAltForm if not isinstance(form, FreeModuleAltForm): raise TypeError("{} is not an alternating form".format(form)) p_res = form._tensor_rank - self._tensor_rank # degree of the result if self._tensor_rank == 1: # Case p = 1: res = self.contract(form) # contract() deals efficiently with # the antisymmetry for p = 1 else: # Case p > 1: if form._fmodule != self._fmodule: raise ValueError("{} is not defined on the same ".format(form) + "module as the {}".format(self)) if form._tensor_rank < self._tensor_rank: raise ValueError("the degree of the {} is lower ".format(form) + "than that of the {}".format(self)) # Interior product at the component level: basis = self.common_basis(form) if basis is None: raise ValueError("no common basis for the interior product") comp = self._components[basis].interior_product( form._components[basis]) if p_res == 0: res = comp # result is a scalar else: res = self._fmodule.tensor_from_comp((0, p_res), comp) # Name of the result res_name = None if self._name is not None and form._name is not None: sname = self._name oname = form._name if not is_atomic(sname): sname = '(' + sname + ')' if not is_atomic(oname): oname = '(' + oname + ')' res_name = 'i_' + sname + ' ' + oname res_latex_name = None if self._latex_name is not None and form._latex_name is not None: slname = self._latex_name olname = form._latex_name if not is_atomic(olname): olname = r'\left(' + olname + r'\right)' res_latex_name = r'\iota_{' + slname + '} ' + olname if p_res == 0: if res_name: try: # there is no guarantee that base ring elements have # set_name res.set_name(res_name, latex_name=res_latex_name) except (AttributeError, TypeError): pass else: res.set_name(res_name, latex_name=res_latex_name) return res
def display(self, basis=None, format_spec=None): r""" Display the alternating form ``self`` in terms of its expansion w.r.t. a given module basis. The expansion is actually performed onto exterior products of elements of the cobasis (dual basis) associated with ``basis`` (see examples below). The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode). INPUT: - ``basis`` -- (default: ``None``) basis of the free module with respect to which the alternating form is expanded; if none is provided, the module's default basis is assumed - ``format_spec`` -- (default: ``None``) format specification passed to ``self._fmodule._output_formatter`` to format the output EXAMPLES: Display of an alternating form of degree 1 (linear form) on a rank-3 free module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: e.dual_basis() Dual basis (e^0,e^1,e^2) on the Rank-3 free module M over the Integer Ring sage: a = M.linear_form('a', latex_name=r'\alpha') sage: a[:] = [1,-3,4] sage: a.display(e) a = e^0 - 3 e^1 + 4 e^2 sage: a.display() # a shortcut since e is M's default basis a = e^0 - 3 e^1 + 4 e^2 sage: latex(a.display()) # display in the notebook \alpha = e^{0} -3 e^{1} + 4 e^{2} A shortcut is ``disp()``:: sage: a.disp() a = e^0 - 3 e^1 + 4 e^2 Display of an alternating form of degree 2 on a rank-3 free module:: sage: b = M.alternating_form(2, 'b', latex_name=r'\beta') sage: b[0,1], b[0,2], b[1,2] = 3, 2, -1 sage: b.display() b = 3 e^0/\e^1 + 2 e^0/\e^2 - e^1/\e^2 sage: latex(b.display()) # display in the notebook \beta = 3 e^{0}\wedge e^{1} + 2 e^{0}\wedge e^{2} -e^{1}\wedge e^{2} Display of an alternating form of degree 3 on a rank-3 free module:: sage: c = M.alternating_form(3, 'c') sage: c[0,1,2] = 4 sage: c.display() c = 4 e^0/\e^1/\e^2 sage: latex(c.display()) c = 4 e^{0}\wedge e^{1}\wedge e^{2} Display of a vanishing alternating form:: sage: c[0,1,2] = 0 # the only independent component set to zero sage: c.is_zero() True sage: c.display() c = 0 sage: latex(c.display()) c = 0 sage: c[0,1,2] = 4 # value restored for what follows Display in a basis which is not the default one:: sage: aut = M.automorphism(matrix=[[0,1,0], [0,0,-1], [1,0,0]], ....: basis=e) sage: f = e.new_basis(aut, 'f') sage: a.display(f) a = 4 f^0 + f^1 + 3 f^2 sage: a.disp(f) # shortcut notation a = 4 f^0 + f^1 + 3 f^2 sage: b.display(f) b = -2 f^0/\f^1 - f^0/\f^2 - 3 f^1/\f^2 sage: c.display(f) c = -4 f^0/\f^1/\f^2 The output format can be set via the argument ``output_formatter`` passed at the module construction:: sage: N = FiniteRankFreeModule(QQ, 3, name='N', start_index=1, ....: output_formatter=Rational.numerical_approx) sage: e = N.basis('e') sage: b = N.alternating_form(2, 'b') sage: b[1,2], b[1,3], b[2,3] = 1/3, 5/2, 4 sage: b.display() # default format (53 bits of precision) b = 0.333333333333333 e^1/\e^2 + 2.50000000000000 e^1/\e^3 + 4.00000000000000 e^2/\e^3 The output format is then controlled by the argument ``format_spec`` of the method :meth:`display`:: sage: b.display(format_spec=10) # 10 bits of precision b = 0.33 e^1/\e^2 + 2.5 e^1/\e^3 + 4.0 e^2/\e^3 Check that the bug reported in :trac:`22520` is fixed:: sage: M = FiniteRankFreeModule(SR, 2, name='M') sage: e = M.basis('e') sage: a = M.alternating_form(2) sage: a[0,1] = SR.var('t', domain='real') sage: a.display() t e^0/\e^1 """ from sage.misc.latex import latex from sage.tensor.modules.format_utilities import is_atomic, \ FormattedExpansion if basis is None: basis = self._fmodule._def_basis cobasis = basis.dual_basis() comp = self.comp(basis) terms_txt = [] terms_latex = [] for ind in comp.non_redundant_index_generator(): ind_arg = ind + (format_spec,) coef = comp[ind_arg] # Check whether the coefficient is zero, preferably via # the fast method is_trivial_zero(): if hasattr(coef, 'is_trivial_zero'): zero_coef = coef.is_trivial_zero() else: zero_coef = coef == 0 if not zero_coef: bases_txt = [] bases_latex = [] for k in range(self._tensor_rank): bases_txt.append(cobasis[ind[k]]._name) bases_latex.append(latex(cobasis[ind[k]])) basis_term_txt = "/\\".join(bases_txt) basis_term_latex = r"\wedge ".join(bases_latex) coef_txt = repr(coef) if coef_txt == "1": terms_txt.append(basis_term_txt) terms_latex.append(basis_term_latex) elif coef_txt == "-1": terms_txt.append("-" + basis_term_txt) terms_latex.append("-" + basis_term_latex) else: coef_latex = latex(coef) if is_atomic(coef_txt): terms_txt.append(coef_txt + " " + basis_term_txt) else: terms_txt.append("(" + coef_txt + ") " + basis_term_txt) if is_atomic(coef_latex): terms_latex.append(coef_latex + basis_term_latex) else: terms_latex.append(r"\left(" + coef_latex + \ r"\right)" + basis_term_latex) if not terms_txt: expansion_txt = "0" else: expansion_txt = terms_txt[0] for term in terms_txt[1:]: if term[0] == "-": expansion_txt += " - " + term[1:] else: expansion_txt += " + " + term if not terms_latex: expansion_latex = "0" else: expansion_latex = terms_latex[0] for term in terms_latex[1:]: if term[0] == "-": expansion_latex += term else: expansion_latex += "+" + term if self._name is None: resu_txt = expansion_txt else: resu_txt = self._name + " = " + expansion_txt if self._latex_name is None: resu_latex = expansion_latex else: resu_latex = latex(self) + " = " + expansion_latex return FormattedExpansion(resu_txt, resu_latex)
def _sub_(self, other): r""" Subtraction of two mixed forms. INPUT: - ``other`` -- a mixed form, in the same algebra as ``self`` OUTPUT: - the mixed form resulting from the subtraction of ``self`` and ``other`` TESTS:: 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: xy_to_uv = c_xy.transition_map(c_uv, (x+y, x-y), ....: intersection_name='W', restrictions1= x>0, ....: restrictions2= u+v>0) sage: uv_to_xy = xy_to_uv.inverse() sage: e_xy = c_xy.frame(); e_uv = c_uv.frame() sage: f = M.scalar_field(x, name='f') sage: a = M.diff_form(1, name='a') sage: a[e_xy,0] = x sage: A = M.mixed_form(name='A', comp=[f, a, 0]) sage: A.add_comp_by_continuation(e_uv, U.intersection(V), c_uv) sage: g = M.scalar_field(u, name='g', chart=c_uv) sage: b = M.diff_form(1, name='b') sage: b[e_uv,1] = v sage: B = M.mixed_form(name='B', comp=[g, b, 0]) sage: B.add_comp_by_continuation(e_xy, V.intersection(U), c_xy) sage: C = A._sub_(B); C Mixed differential form A-B on the 2-dimensional differentiable manifold M sage: A.display(e_uv) A = [1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] + [0] sage: B.display(e_xy) B = [x + y] + [(x - y) dx + (-x + y) dy] + [0] sage: C.display(e_xy) A-B = [-y] + [y dx + (x - y) dy] + [0] sage: C.display(e_uv) A-B = [-1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u - 3/4*v) dv] + [0] sage: C == A - B # indirect doctest True sage: Z = A.parent().zero(); Z Mixed differential form zero on the 2-dimensional differentiable manifold M sage: A._sub_(Z) == A True sage: Z._sub_(A) == -A True """ resu_comp = [self[j] - other[j] for j in range(self._max_deg + 1)] resu = type(self)(self.parent(), comp=resu_comp) # Compose name: from sage.tensor.modules.format_utilities import is_atomic if self._name is not None and other._name is not None: sname = self._name oname = other._name if not is_atomic(oname): oname = '(' + oname + ')' resu._name = sname + '-' + oname if self._latex_name is not None and other._latex_name is not None: slname = self._latex_name olname = other._latex_name if not is_atomic(olname): olname = '(' + olname + ')' resu._latex_name = slname + '-' + olname return resu
def _display_form(rst, basis, chart): r""" Display coordinate expression of a single form ``rst`` (without equality sign). This helper method is invoked by :meth:`display`. TESTS:: sage: M = Manifold(2, 'M') sage: c_xy.<x,y> = M.chart() sage: omega = M.diff_form(1, name='omega') sage: omega[c_xy.frame(),0] = x^2 sage: F = M.mixed_form(comp=[0,omega,0]) sage: F.disp(c_xy.frame()) [0] + [x^2 dx] + [0] """ from sage.tensor.modules.format_utilities import is_atomic cobasis = basis.dual_basis() comp = rst.comp(basis) terms_txt = [] terms_latex = [] for ind in comp.non_redundant_index_generator(): ind_arg = ind + (chart, ) coef = comp[ind_arg] # Check whether the coefficient is zero, preferably via # the fast method is_trivial_zero(): if hasattr(coef, 'is_trivial_zero'): zero_coef = coef.is_trivial_zero() else: zero_coef = coef == 0 if not zero_coef: bases_txt = [] bases_latex = [] for k in range(rst._tensor_rank): bases_txt.append(cobasis[ind[k]]._name) bases_latex.append(latex(cobasis[ind[k]])) basis_term_txt = "/\\".join(bases_txt) basis_term_latex = r"\wedge ".join(bases_latex) coef_txt = repr(coef) if coef_txt == "1": terms_txt.append(basis_term_txt) terms_latex.append(basis_term_latex) elif coef_txt == "-1": terms_txt.append("-" + basis_term_txt) terms_latex.append("-" + basis_term_latex) else: coef_latex = latex(coef) if is_atomic(coef_txt): terms_txt.append(coef_txt + " " + basis_term_txt) else: terms_txt.append("(" + coef_txt + ") " + basis_term_txt) if is_atomic(coef_latex): terms_latex.append(coef_latex + basis_term_latex) else: terms_latex.append(r"\left(" + coef_latex + r"\right)" + basis_term_latex) if not terms_txt: resu_txt = "0" else: resu_txt = terms_txt[0] for term in terms_txt[1:]: if term[0] == "-": resu_txt += " - " + term[1:] else: resu_txt += " + " + term if not terms_latex: resu_latex = r"0" else: resu_latex = terms_latex[0] for term in terms_latex[1:]: if term[0] == "-": resu_latex += term else: resu_latex += "+" + term return FormattedExpansion(resu_txt, resu_latex)
def interior_product(self, alt_tensor): r""" Interior product with an alternating contravariant tensor. If ``self`` is an alternating form `A` of degree `p` and `B` is an alternating contravariant tensor of degree `q\geq p` on the same free module, the interior product of `A` by `B` is the alternating contravariant tensor `\iota_A B` of degree `q-p` defined by .. MATH:: (\iota_A B)^{i_1\ldots i_{q-p}} = A_{k_1\ldots k_p} B^{k_1\ldots k_p i_1\ldots i_{q-p}} .. NOTE:: ``A.interior_product(B)`` yields the same result as ``A.contract(0,..., p-1, B, 0,..., p-1)`` (cf. :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.contract`), but ``interior_product`` is more efficient, the alternating character of `A` being not used to reduce the computation in :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.contract` INPUT: - ``alt_tensor`` -- alternating contravariant tensor `B` (instance of :class:`~sage.tensor.modules.alternating_contr_tensor.AlternatingContrTensor`); the degree of `B` must be at least equal to the degree of ``self`` OUTPUT: - element of the base ring (case `p=q`) or :class:`~sage.tensor.modules.alternating_contr_tensor.AlternatingContrTensor` (case `p<q`) representing the interior product `\iota_A B`, where `A` is ``self`` .. SEEALSO:: :meth:`~sage.tensor.modules.alternating_contr_tensor.AlternatingContrTensor.interior_product` for the interior product of an alternating contravariant tensor by an alternating form EXAMPLES: Let us consider a rank-3 free module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') and various interior products on it, starting with a linear form (``p=1``) and a module element (``q=1``):: sage: a = M.linear_form(name='A') sage: a[:] = [-2, 4, 3] sage: b = M([3, 1, 5], basis=e, name='B') sage: c = a.interior_product(b); c 13 sage: c == a.contract(b) True Case ``p=1`` and ``q=2``:: sage: b = M.alternating_contravariant_tensor(2, name='B') sage: b[1,2], b[1,3], b[2,3] = 5, 2, 3 sage: c = a.interior_product(b); c Element i_A B of the Rank-3 free module M over the Integer Ring sage: c.display() i_A B = -26 e_1 - 19 e_2 + 8 e_3 sage: latex(c) \iota_{A} B sage: c == a.contract(b) True Case ``p=1`` and ``q=3``:: sage: b = M.alternating_contravariant_tensor(3, name='B') sage: b[1,2,3] = 5 sage: c = a.interior_product(b); c Alternating contravariant tensor i_A B of degree 2 on the Rank-3 free module M over the Integer Ring sage: c.display() i_A B = 15 e_1/\e_2 - 20 e_1/\e_3 - 10 e_2/\e_3 sage: c == a.contract(b) True Case ``p=2`` and ``q=2``:: sage: a = M.alternating_form(2, name='A') sage: a[1,2], a[1,3], a[2,3] = 2, -3, 1 sage: b = M.alternating_contravariant_tensor(2, name='B') sage: b[1,2], b[1,3], b[2,3] = 5, 2, 3 sage: c = a.interior_product(b); c 14 sage: c == a.contract(0, 1, b, 0, 1) # contraction on all indices of a True Case ``p=2`` and ``q=3``:: sage: b = M.alternating_contravariant_tensor(3, name='B') sage: b[1,2,3] = 5 sage: c = a.interior_product(b); c Element i_A B of the Rank-3 free module M over the Integer Ring sage: c.display() i_A B = 10 e_1 + 30 e_2 + 20 e_3 sage: c == a.contract(0, 1, b, 0, 1) True Case ``p=3`` and ``q=3``:: sage: a = M.alternating_form(3, name='A') sage: a[1,2,3] = -2 sage: c = a.interior_product(b); c -60 sage: c == a.contract(0, 1, 2, b, 0, 1, 2) True """ from .format_utilities import is_atomic from .alternating_contr_tensor import AlternatingContrTensor if not isinstance(alt_tensor, AlternatingContrTensor): raise TypeError("{} is not an alternating ".format(alt_tensor) + "contravariant tensor") p_res = alt_tensor._tensor_rank - self._tensor_rank # degree of result if self._tensor_rank == 1: # Case p = 1: res = self.contract(alt_tensor) # contract() deals efficiently # with antisymmetry for p = 1 else: # Case p > 1: if alt_tensor._fmodule != self._fmodule: raise ValueError("{} is not defined on ".format(alt_tensor) + "the same module as the {}".format(self)) if alt_tensor._tensor_rank < self._tensor_rank: raise ValueError("the degree of the {} ".format(alt_tensor) + "is lower than that of the {}".format(self)) # Interior product at the component level: basis = self.common_basis(alt_tensor) if basis is None: raise ValueError("no common basis for the interior product") comp = self._components[basis].interior_product( alt_tensor._components[basis]) if p_res == 0: res = comp # result is a scalar else: res = self._fmodule.tensor_from_comp((p_res, 0), comp) # Name of the result res_name = None if self._name is not None and alt_tensor._name is not None: sname = self._name oname = alt_tensor._name if not is_atomic(sname): sname = '(' + sname + ')' if not is_atomic(oname): oname = '(' + oname + ')' res_name = 'i_' + sname + ' ' + oname res_latex_name = None if self._latex_name is not None and alt_tensor._latex_name is not None: slname = self._latex_name olname = alt_tensor._latex_name if not is_atomic(olname): olname = r'\left(' + olname + r'\right)' res_latex_name = r'\iota_{' + slname + '} ' + olname if p_res == 0: if res_name: try: # there is no guarantee that base ring elements have # set_name res.set_name(res_name, latex_name=res_latex_name) except (AttributeError, TypeError): pass else: res.set_name(res_name, latex_name=res_latex_name) return res
def wedge(self, other): r""" Exterior product with another differential form. INPUT: - ``other``: another differential form OUTPUT: - instance of :class:`DiffForm` representing the exterior product self/\\other. """ from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm from sage.tensor.modules.format_utilities import is_atomic if self._domain.is_subset(other._domain): if not self._ambient_domain.is_subset(other._ambient_domain): raise TypeError("Incompatible ambient domains for exterior " + "product.") elif other._domain.is_subset(self._domain): if not other._ambient_domain.is_subset(self._ambient_domain): raise TypeError("Incompatible ambient domains for exterior " + "product.") dom_resu = self._domain.intersection(other._domain) ambient_dom_resu = self._ambient_domain.intersection( other._ambient_domain) self_r = self.restrict(dom_resu) other_r = other.restrict(dom_resu) if ambient_dom_resu.is_manifestly_parallelizable(): # call of the FreeModuleAltForm version: return FreeModuleAltForm.wedge(self_r, other_r) # otherwise, the result is created here: if self._name is not None and other._name is not None: sname = self._name oname = other._name if not is_atomic(sname): sname = '(' + sname + ')' if not is_atomic(oname): oname = '(' + oname + ')' resu_name = sname + '/\\' + oname if self._latex_name is not None and other._latex_name is not None: slname = self._latex_name olname = other._latex_name if not is_atomic(slname): slname = '(' + slname + ')' if not is_atomic(olname): olname = '(' + olname + ')' resu_latex_name = slname + r'\wedge ' + olname dest_map = self._vmodule._dest_map dest_map_resu = dest_map.restrict(dom_resu, subcodomain=ambient_dom_resu) vmodule = dom_resu.vector_field_module(dest_map=dest_map_resu) resu_degree = self._tensor_rank + other._tensor_rank resu = vmodule.alternating_form(resu_degree, name=resu_name, latex_name=resu_latex_name) for dom in self_r._restrictions: if dom in other_r._restrictions: resu._restrictions[dom] = self_r._restrictions[dom].wedge( other_r._restrictions[dom]) return resu
def _display_expansion(self, basis=None, format_spec=None): r""" Return the pure expansion of ``self`` w.r.t. a given module basis. The expansion is actually performed onto exterior products of elements of the cobasis (dual basis) associated with ``basis`` (see examples below). The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode). INPUT: - ``basis`` -- (default: ``None``) basis of the free module with respect to which the alternating form is expanded; if none is provided, the module's default basis is assumed - ``format_spec`` -- (default: ``None``) format specification passed to ``self._fmodule._output_formatter`` to format the output TESTS: Expansion display of an alternating form of degree 1 (linear form) on a rank-3 free module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: e.dual_basis() Dual basis (e^0,e^1,e^2) on the Rank-3 free module M over the Integer Ring sage: a = M.linear_form('a', latex_name=r'\alpha') sage: a[:] = [1,-3,4] sage: a._display_expansion(e) e^0 - 3 e^1 + 4 e^2 sage: a._display_expansion() # a shortcut since e is M's default basis e^0 - 3 e^1 + 4 e^2 sage: latex(a._display_expansion()) # display in the notebook e^{0} -3 e^{1} + 4 e^{2} """ from sage.misc.latex import latex from sage.tensor.modules.format_utilities import (is_atomic, FormattedExpansion) basis, format_spec = self._preparse_display(basis=basis, format_spec=format_spec) cobasis = basis.dual_basis() comp = self.comp(basis) terms_txt = [] terms_latex = [] for ind in comp.non_redundant_index_generator(): ind_arg = ind + (format_spec, ) coef = comp[ind_arg] # Check whether the coefficient is zero, preferably via # the fast method is_trivial_zero(): if hasattr(coef, 'is_trivial_zero'): zero_coef = coef.is_trivial_zero() else: zero_coef = coef == 0 if not zero_coef: bases_txt = [] bases_latex = [] for k in range(self._tensor_rank): bases_txt.append(cobasis[ind[k]]._name) bases_latex.append(latex(cobasis[ind[k]])) basis_term_txt = "/\\".join(bases_txt) basis_term_latex = r"\wedge ".join(bases_latex) coef_txt = repr(coef) if coef_txt == "1": terms_txt.append(basis_term_txt) terms_latex.append(basis_term_latex) elif coef_txt == "-1": terms_txt.append("-" + basis_term_txt) terms_latex.append("-" + basis_term_latex) else: coef_latex = latex(coef) if is_atomic(coef_txt): terms_txt.append(coef_txt + " " + basis_term_txt) else: terms_txt.append("(" + coef_txt + ") " + basis_term_txt) if is_atomic(coef_latex): terms_latex.append(coef_latex + basis_term_latex) else: terms_latex.append(r"\left(" + coef_latex + \ r"\right)" + basis_term_latex) if not terms_txt: expansion_txt = "0" else: expansion_txt = terms_txt[0] for term in terms_txt[1:]: if term[0] == "-": expansion_txt += " - " + term[1:] else: expansion_txt += " + " + term if not terms_latex: expansion_latex = "0" else: expansion_latex = terms_latex[0] for term in terms_latex[1:]: if term[0] == "-": expansion_latex += term else: expansion_latex += "+" + term return FormattedExpansion(expansion_txt, expansion_latex)