Affine Connections - Manifolds

Skip to content
Back to top
View this page
Edit this page

# Affine Connections

The class `AffineConnection` implements affine connections on smooth manifolds.

AUTHORS:

- Eric Gourgoulhon, Michal Bejger (2013-2015) : initial version

- Marco Mancini (2015) : parallelization of some computations

- Florentin Jaffredo (2018) : series expansion with respect to a given parameter

REFERENCES:

- [Lee1997]

- [KN1963]

- [ONe1983]

class sage.manifolds.differentiable.affine_connection.AffineConnection(domain, name, latex_name=None)

  Bases: `SageObject`

  Affine connection on a smooth manifold.

  Let \(M\) be a differentiable manifold of class \(C^\infty\) (smooth manifold) over a non-discrete topological field \(K\) (in most applications \(K=\RR\) or \(K=\CC\)), let \(C^\infty(M)\) be the algebra of smooth functions \(M\rightarrow K\) (cf. `DiffScalarFieldAlgebra`) and let \(\mathfrak{X}(M)\) be the \(C^\infty(M)\)-module of vector fields on \(M\) (cf. `VectorFieldModule`). An affine connection on \(M\) is an operator

  \[\begin{split}\begin{array}{cccc} \nabla: & \mathfrak{X}(M)\times \mathfrak{X}(M) & \longrightarrow & \mathfrak{X}(M) \\ & (u,v) & \longmapsto & \nabla_u v \end{array}\end{split}\]

  that

  - is \(K\)-bilinear, i.e. is bilinear when considering \(\mathfrak{X}(M)\) as a vector space over \(K\)

  - is \(C^\infty(M)\)-linear w.r.t. the first argument: \(\forall f\in C^\infty(M),\ \nabla_{fu} v = f\nabla_u v\)

  - obeys Leibniz rule w.r.t. the second argument: \(\forall f\in C^\infty(M),\ \nabla_u (f v) = \mathrm{d}f(u)\, v + f \nabla_u v\)

  The affine connection \(\nabla\) gives birth to the covariant derivative operator acting on tensor fields, denoted by the same symbol:

  \[\begin{split}\begin{array}{cccc} \nabla: & T^{(k,l)}(M) & \longrightarrow & T^{(k,l+1)}(M)\\ & t & \longmapsto & \nabla t \end{array}\end{split}\]

  where \(T^{(k,l)}(M)\) stands for the \(C^\infty(M)\)-module of tensor fields of type \((k,l)\) on \(M\) (cf. `TensorFieldModule`), with the convention \(T^{(0,0)}(M):=C^\infty(M)\). For a vector field \(v\), the covariant derivative \(\nabla v\) is a type-(1,1) tensor field such that

  \[\forall u \in\mathfrak{X}(M), \ \nabla_u v = \nabla v(., u)\]

  More generally for any tensor field \(t\in T^{(k,l)}(M)\), we have

  \[\forall u \in\mathfrak{X}(M), \ \nabla_u t = \nabla t(\ldots, u)\]

  Note

  The above convention means that, in terms of index notation, the “derivation index” in \(\nabla t\) is the last one:

  \[\nabla_c t^{a_1\ldots a_k}_{\quad\quad b_1\ldots b_l} = (\nabla t)^{a_1\ldots a_k}_{\quad\quad b_1\ldots b_l c}\]

  INPUT:

  - `domain` – the manifold on which the connection is defined (must be an instance of class `DifferentiableManifold`)

  - `name` – name given to the affine connection

  - `latex_name` – (default: `None`) LaTeX symbol to denote the affine connection; if `None`, it is set to `name`

  EXAMPLES:

  Affine connection on a 3-dimensional manifold:

  Sage

      sage: M = Manifold(3, 'M', start_index=1)
      sage: c_xyz.<x,y,z> = M.chart()
      sage: nab = M.affine_connection('nabla', r'\nabla') ; nab
      Affine connection nabla on the 3-dimensional differentiable manifold M

  Python

      >>> from sage.all import *
      >>> M = Manifold(Integer(3), 'M', start_index=Integer(1))
      >>> c_xyz = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_xyz._first_ngens(3)
      >>> nab = M.affine_connection('nabla', r'\nabla') ; nab
      Affine connection nabla on the 3-dimensional differentiable manifold M

  A just-created connection has no connection coefficients:

  Sage

      sage: nab._coefficients
      {}

  [Python]

      >>> from sage.all import *
      >>> nab._coefficients
      {}

  The connection coefficients relative to the manifold’s default frame [here \((\partial/\partial x, \partial/\partial y, \partial/\partial z)\)], are created by providing the relevant indices inside square brackets:

  Sage

      sage: nab[1,1,2], nab[3,2,3] = x^2, y*z  # Gamma^1_{12} = x^2, Gamma^3_{23} = yz
      sage: nab._coefficients
      {Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z)): 3-indices components w.r.t.
       Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z))}

  Python

      >>> from sage.all import *
      >>> nab[Integer(1),Integer(1),Integer(2)], nab[Integer(3),Integer(2),Integer(3)] = x**Integer(2), y*z  # Gamma^1_{12} = x^2, Gamma^3_{23} = yz
      >>> nab._coefficients
      {Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z)): 3-indices components w.r.t.
       Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z))}

  If not the default one, the vector frame w.r.t. which the connection coefficients are defined can be specified as the first argument inside the square brackets; hence the above definition is equivalent to:

  Sage

      sage: nab[c_xyz.frame(), 1,1,2], nab[c_xyz.frame(),3,2,3] = x^2, y*z
      sage: nab._coefficients
      {Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z)): 3-indices components w.r.t.
       Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z))}

  [Python]

      >>> from sage.all import *
      >>> nab[c_xyz.frame(), Integer(1),Integer(1),Integer(2)], nab[c_xyz.frame(),Integer(3),Integer(2),Integer(3)] = x**Integer(2), y*z
      >>> nab._coefficients
      {Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z)): 3-indices components w.r.t.
       Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z))}

  Unset components are initialized to zero:

  Sage

      sage: nab[:]  # list of coefficients relative to the manifold's default vector frame
      [[[0, x^2, 0], [0, 0, 0], [0, 0, 0]],
       [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
       [[0, 0, 0], [0, 0, y*z], [0, 0, 0]]]

  Python

      >>> from sage.all import *
      >>> nab[:]  # list of coefficients relative to the manifold's default vector frame
      [[[0, x^2, 0], [0, 0, 0], [0, 0, 0]],
       [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
       [[0, 0, 0], [0, 0, y*z], [0, 0, 0]]]

  The treatment of connection coefficients in a given vector frame is similar to that of tensor components; see therefore the class `TensorField` for the documentation. In particular, the square brackets return the connection coefficients as instances of `ChartFunction`, while the double square brackets return a scalar field:

  Sage

      sage: nab[1,1,2]
      x^2
      sage: nab[1,1,2].display()
      (x, y, z) ↦ x^2
      sage: type(nab[1,1,2])
      <class 'sage.manifolds.chart_func.ChartFunctionRing_with_category.element_class'>
      sage: nab[[1,1,2]]
      Scalar field on the 3-dimensional differentiable manifold M
      sage: nab[[1,1,2]].display()
      M → ℝ
      (x, y, z) ↦ x^2
      sage: nab[[1,1,2]].coord_function() is nab[1,1,2]
      True

  [Python]

      >>> from sage.all import *
      >>> nab[Integer(1),Integer(1),Integer(2)]
      x^2
      >>> nab[Integer(1),Integer(1),Integer(2)].display()
      (x, y, z) ↦ x^2
      >>> type(nab[Integer(1),Integer(1),Integer(2)])
      <class 'sage.manifolds.chart_func.ChartFunctionRing_with_category.element_class'>
      >>> nab[[Integer(1),Integer(1),Integer(2)]]
      Scalar field on the 3-dimensional differentiable manifold M
      >>> nab[[Integer(1),Integer(1),Integer(2)]].display()
      M → ℝ
      (x, y, z) ↦ x^2
      >>> nab[[Integer(1),Integer(1),Integer(2)]].coord_function() is nab[Integer(1),Integer(1),Integer(2)]
      True

  Action on a scalar field:

  Sage

      sage: f = M.scalar_field(x^2 - y^2, name='f')
      sage: Df = nab(f) ; Df
      1-form df on the 3-dimensional differentiable manifold M
      sage: Df[:]
      [2*x, -2*y, 0]

  Python

      >>> from sage.all import *
      >>> f = M.scalar_field(x**Integer(2) - y**Integer(2), name='f')
      >>> Df = nab(f) ; Df
      1-form df on the 3-dimensional differentiable manifold M
      >>> Df[:]
      [2*x, -2*y, 0]

  The action of an affine connection on a scalar field must coincide with the differential:

  Sage

      sage: Df == f.differential()
      True

  [Python]

      >>> from sage.all import *
      >>> Df == f.differential()
      True

  A generic affine connection has some torsion:

  Sage

      sage: DDf = nab(Df) ; DDf
      Tensor field nabla(df) of type (0,2) on the 3-dimensional
       differentiable manifold M
      sage: DDf.antisymmetrize()[:] # nabla does not commute on scalar fields:
      [   0 -x^3    0]
      [ x^3    0    0]
      [   0    0    0]

  Python

      >>> from sage.all import *
      >>> DDf = nab(Df) ; DDf
      Tensor field nabla(df) of type (0,2) on the 3-dimensional
       differentiable manifold M
      >>> DDf.antisymmetrize()[:] # nabla does not commute on scalar fields:
      [   0 -x^3    0]
      [ x^3    0    0]
      [   0    0    0]

  Let us check the standard formula

  \[\nabla_j \nabla_i \, f - \nabla_i \nabla_j \, f = T^k_{\ \, ij} \nabla_k \, f ,\]

  where the \(T^k_{\ \, ij}\)’s are the components of the connection’s torsion tensor:

  Sage

      sage: 2*DDf.antisymmetrize() == nab.torsion().contract(0,Df)
      True

  [Python]

      >>> from sage.all import *
      >>> Integer(2)*DDf.antisymmetrize() == nab.torsion().contract(Integer(0),Df)
      True

  The connection acting on a vector field:

  Sage

      sage: v = M.vector_field(y*z, x*z, x*y, name='v')
      sage: Dv = nab(v) ; Dv
      Tensor field nabla(v) of type (1,1) on the 3-dimensional differentiable
       manifold M
      sage: Dv[:]
      [            0 (x^2*y + 1)*z             y]
      [            z             0             x]
      [            y             x       x*y*z^2]

  Python

      >>> from sage.all import *
      >>> v = M.vector_field(y*z, x*z, x*y, name='v')
      >>> Dv = nab(v) ; Dv
      Tensor field nabla(v) of type (1,1) on the 3-dimensional differentiable
       manifold M
      >>> Dv[:]
      [            0 (x^2*y + 1)*z             y]
      [            z             0             x]
      [            y             x       x*y*z^2]

  Another example: connection on a non-parallelizable 2-dimensional manifold:

  Sage

      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: c_xyW = c_xy.restrict(W) ; c_uvW = c_uv.restrict(W)
      sage: eUW = c_xyW.frame() ; eVW = c_uvW.frame()
      sage: nab = M.affine_connection('nabla', r'\nabla')

  [Python]

      >>> from sage.all import *
      >>> M = Manifold(Integer(2), 'M')
      >>> U = M.open_subset('U') ; V = M.open_subset('V')
      >>> M.declare_union(U,V)   # M is the union of U and V
      >>> c_xy = U.chart(names=('x', 'y',)); (x, y,) = c_xy._first_ngens(2); c_uv = V.chart(names=('u', 'v',)); (u, v,) = c_uv._first_ngens(2)
      >>> transf = c_xy.transition_map(c_uv, (x+y, x-y), intersection_name='W',
      ...                              restrictions1= x>Integer(0), restrictions2= u+v>Integer(0))
      >>> inv = transf.inverse()
      >>> W = U.intersection(V)
      >>> eU = c_xy.frame() ; eV = c_uv.frame()
      >>> c_xyW = c_xy.restrict(W) ; c_uvW = c_uv.restrict(W)
      >>> eUW = c_xyW.frame() ; eVW = c_uvW.frame()
      >>> nab = M.affine_connection('nabla', r'\nabla')

  The connection is first defined on the open subset U by means of its coefficients w.r.t. the frame eU (the manifold’s default frame):

  Sage

      sage: nab[0,0,0], nab[1,0,1] = x, x*y

  Python

      >>> from sage.all import *
      >>> nab[Integer(0),Integer(0),Integer(0)], nab[Integer(1),Integer(0),Integer(1)] = x, x*y

  The coefficients w.r.t the frame eV are deduced by continuation of the coefficients w.r.t. the frame eVW on the open subset \(W=U\cap V\):

  Sage

      sage: for i in M.irange():
      ....:     for j in M.irange():
      ....:         for k in M.irange():
      ....:             nab.add_coef(eV)[i,j,k] = nab.coef(eVW)[i,j,k,c_uvW].expr()

  [Python]

      >>> from sage.all import *
      >>> for i in M.irange():
      ...     for j in M.irange():
      ...         for k in M.irange():
      ...             nab.add_coef(eV)[i,j,k] = nab.coef(eVW)[i,j,k,c_uvW].expr()

  At this stage, the connection is fully defined on all the manifold:

  Sage

      sage: nab.coef(eU)[:]
      [[[x, 0], [0, 0]], [[0, x*y], [0, 0]]]
      sage: nab.coef(eV)[:]
      [[[1/16*u^2 - 1/16*v^2 + 1/8*u + 1/8*v, -1/16*u^2 + 1/16*v^2 + 1/8*u + 1/8*v],
        [1/16*u^2 - 1/16*v^2 + 1/8*u + 1/8*v, -1/16*u^2 + 1/16*v^2 + 1/8*u + 1/8*v]],
       [[-1/16*u^2 + 1/16*v^2 + 1/8*u + 1/8*v, 1/16*u^2 - 1/16*v^2 + 1/8*u + 1/8*v],
        [-1/16*u^2 + 1/16*v^2 + 1/8*u + 1/8*v, 1/16*u^2 - 1/16*v^2 + 1/8*u + 1/8*v]]]

  Python

      >>> from sage.all import *
      >>> nab.coef(eU)[:]
      [[[x, 0], [0, 0]], [[0, x*y], [0, 0]]]
      >>> nab.coef(eV)[:]
      [[[1/16*u^2 - 1/16*v^2 + 1/8*u + 1/8*v, -1/16*u^2 + 1/16*v^2 + 1/8*u + 1/8*v],
        [1/16*u^2 - 1/16*v^2 + 1/8*u + 1/8*v, -1/16*u^2 + 1/16*v^2 + 1/8*u + 1/8*v]],
       [[-1/16*u^2 + 1/16*v^2 + 1/8*u + 1/8*v, 1/16*u^2 - 1/16*v^2 + 1/8*u + 1/8*v],
        [-1/16*u^2 + 1/16*v^2 + 1/8*u + 1/8*v, 1/16*u^2 - 1/16*v^2 + 1/8*u + 1/8*v]]]

  We may let it act on a vector field defined globally on \(M\):

  Sage

      sage: a = M.vector_field({eU: [-y,x]}, name='a')
      sage: a.add_comp_by_continuation(eV, W, c_uv)
      sage: a.display(eU)
      a = -y ∂/∂x + x ∂/∂y
      sage: a.display(eV)
      a = v ∂/∂u - u ∂/∂v
      sage: da = nab(a) ; da
      Tensor field nabla(a) of type (1,1) on the 2-dimensional differentiable
       manifold M
      sage: da.display(eU)
      nabla(a) = -x*y ∂/∂x⊗dx - ∂/∂x⊗dy + ∂/∂y⊗dx - x*y^2 ∂/∂y⊗dy
      sage: da.display(eV)
      nabla(a) = (-1/16*u^3 + 1/16*u^2*v + 1/16*(u + 2)*v^2 - 1/16*v^3 - 1/8*u^2) ∂/∂u⊗du
       + (1/16*u^3 - 1/16*u^2*v - 1/16*(u - 2)*v^2 + 1/16*v^3 - 1/8*u^2 + 1) ∂/∂u⊗dv
       + (1/16*u^3 - 1/16*u^2*v - 1/16*(u - 2)*v^2 + 1/16*v^3 - 1/8*u^2 - 1) ∂/∂v⊗du
       + (-1/16*u^3 + 1/16*u^2*v + 1/16*(u + 2)*v^2 - 1/16*v^3 - 1/8*u^2) ∂/∂v⊗dv

  [Python]

      >>> from sage.all import *
      >>> a = M.vector_field({eU: [-y,x]}, name='a')
      >>> a.add_comp_by_continuation(eV, W, c_uv)
      >>> a.display(eU)
      a = -y ∂/∂x + x ∂/∂y
      >>> a.display(eV)
      a = v ∂/∂u - u ∂/∂v
      >>> da = nab(a) ; da
      Tensor field nabla(a) of type (1,1) on the 2-dimensional differentiable
       manifold M
      >>> da.display(eU)
      nabla(a) = -x*y ∂/∂x⊗dx - ∂/∂x⊗dy + ∂/∂y⊗dx - x*y^2 ∂/∂y⊗dy
      >>> da.display(eV)
      nabla(a) = (-1/16*u^3 + 1/16*u^2*v + 1/16*(u + 2)*v^2 - 1/16*v^3 - 1/8*u^2) ∂/∂u⊗du
       + (1/16*u^3 - 1/16*u^2*v - 1/16*(u - 2)*v^2 + 1/16*v^3 - 1/8*u^2 + 1) ∂/∂u⊗dv
       + (1/16*u^3 - 1/16*u^2*v - 1/16*(u - 2)*v^2 + 1/16*v^3 - 1/8*u^2 - 1) ∂/∂v⊗du
       + (-1/16*u^3 + 1/16*u^2*v + 1/16*(u + 2)*v^2 - 1/16*v^3 - 1/8*u^2) ∂/∂v⊗dv

  A few tests:

  Sage

      sage: nab(a.restrict(V)) == da.restrict(V)
      True
      sage: nab.restrict(V)(a) == da.restrict(V)
      True
      sage: nab.restrict(V)(a.restrict(U)) == da.restrict(W)
      True
      sage: nab.restrict(U)(a.restrict(V)) == da.restrict(W)  # long time
      True

  Python

      >>> from sage.all import *
      >>> nab(a.restrict(V)) == da.restrict(V)
      True
      >>> nab.restrict(V)(a) == da.restrict(V)
      True
      >>> nab.restrict(V)(a.restrict(U)) == da.restrict(W)
      True
      >>> nab.restrict(U)(a.restrict(V)) == da.restrict(W)  # long time
      True

  Same examples with SymPy as the engine for symbolic calculus:

  Sage

      sage: M.set_calculus_method('sympy')
      sage: nab = M.affine_connection('nabla', r'\nabla')
      sage: nab[0,0,0], nab[1,0,1] = x, x*y
      sage: for i in M.irange():
      ....:     for j in M.irange():
      ....:         for k in M.irange():
      ....:             nab.add_coef(eV)[i,j,k] = nab.coef(eVW)[i,j,k,c_uvW].expr()

  [Python]

      >>> from sage.all import *
      >>> M.set_calculus_method('sympy')
      >>> nab = M.affine_connection('nabla', r'\nabla')
      >>> nab[Integer(0),Integer(0),Integer(0)], nab[Integer(1),Integer(0),Integer(1)] = x, x*y
      >>> for i in M.irange():
      ...     for j in M.irange():
      ...         for k in M.irange():
      ...             nab.add_coef(eV)[i,j,k] = nab.coef(eVW)[i,j,k,c_uvW].expr()

  At this stage, the connection is fully defined on all the manifold:

  Sage

      sage: nab.coef(eU)[:]
      [[[x, 0], [0, 0]], [[0, x*y], [0, 0]]]
      sage: nab.coef(eV)[:]
      [[[u**2/16 + u/8 - v**2/16 + v/8, -u**2/16 + u/8 + v**2/16 + v/8],
       [u**2/16 + u/8 - v**2/16 + v/8, -u**2/16 + u/8 + v**2/16 + v/8]],
      [[-u**2/16 + u/8 + v**2/16 + v/8, u**2/16 + u/8 - v**2/16 + v/8],
       [-u**2/16 + u/8 + v**2/16 + v/8, u**2/16 + u/8 - v**2/16 + v/8]]]

  Python

      >>> from sage.all import *
      >>> nab.coef(eU)[:]
      [[[x, 0], [0, 0]], [[0, x*y], [0, 0]]]
      >>> nab.coef(eV)[:]
      [[[u**2/16 + u/8 - v**2/16 + v/8, -u**2/16 + u/8 + v**2/16 + v/8],
       [u**2/16 + u/8 - v**2/16 + v/8, -u**2/16 + u/8 + v**2/16 + v/8]],
      [[-u**2/16 + u/8 + v**2/16 + v/8, u**2/16 + u/8 - v**2/16 + v/8],
       [-u**2/16 + u/8 + v**2/16 + v/8, u**2/16 + u/8 - v**2/16 + v/8]]]

  We may let it act on a vector field defined globally on \(M\):

  Sage

      sage: a = M.vector_field({eU: [-y,x]}, name='a')
      sage: a.add_comp_by_continuation(eV, W, c_uv)
      sage: a.display(eU)
      a = -y ∂/∂x + x ∂/∂y
      sage: a.display(eV)
      a = v ∂/∂u - u ∂/∂v
      sage: da = nab(a) ; da
      Tensor field nabla(a) of type (1,1) on the 2-dimensional differentiable
       manifold M
      sage: da.display(eU)
      nabla(a) = -x*y ∂/∂x⊗dx - ∂/∂x⊗dy + ∂/∂y⊗dx - x*y**2 ∂/∂y⊗dy
      sage: da.display(eV)
      nabla(a) = (-u**3/16 + u**2*v/16 - u**2/8 + u*v**2/16 - v**3/16 + v**2/8) ∂/∂u⊗du
       + (u**3/16 - u**2*v/16 - u**2/8 - u*v**2/16 + v**3/16 + v**2/8 + 1) ∂/∂u⊗dv
       + (u**3/16 - u**2*v/16 - u**2/8 - u*v**2/16 + v**3/16 + v**2/8 - 1) ∂/∂v⊗du
       + (-u**3/16 + u**2*v/16 - u**2/8 + u*v**2/16 - v**3/16 + v**2/8) ∂/∂v⊗dv

  [Python]

      >>> from sage.all import *
      >>> a = M.vector_field({eU: [-y,x]}, name='a')
      >>> a.add_comp_by_continuation(eV, W, c_uv)
      >>> a.display(eU)
      a = -y ∂/∂x + x ∂/∂y
      >>> a.display(eV)
      a = v ∂/∂u - u ∂/∂v
      >>> da = nab(a) ; da
      Tensor field nabla(a) of type (1,1) on the 2-dimensional differentiable
       manifold M
      >>> da.display(eU)
      nabla(a) = -x*y ∂/∂x⊗dx - ∂/∂x⊗dy + ∂/∂y⊗dx - x*y**2 ∂/∂y⊗dy
      >>> da.display(eV)
      nabla(a) = (-u**3/16 + u**2*v/16 - u**2/8 + u*v**2/16 - v**3/16 + v**2/8) ∂/∂u⊗du
       + (u**3/16 - u**2*v/16 - u**2/8 - u*v**2/16 + v**3/16 + v**2/8 + 1) ∂/∂u⊗dv
       + (u**3/16 - u**2*v/16 - u**2/8 - u*v**2/16 + v**3/16 + v**2/8 - 1) ∂/∂v⊗du
       + (-u**3/16 + u**2*v/16 - u**2/8 + u*v**2/16 - v**3/16 + v**2/8) ∂/∂v⊗dv

  To make affine connections hashable, they have to be set immutable before:

  Sage

      sage: nab.is_immutable()
      False
      sage: nab.set_immutable()
      sage: nab.is_immutable()
      True

  Python

      >>> from sage.all import *
      >>> nab.is_immutable()
      False
      >>> nab.set_immutable()
      >>> nab.is_immutable()
      True

  Immutable connections cannot be changed anymore:

  Sage

      sage: nab.set_coef(eU)
      Traceback (most recent call last):
      ...
      ValueError: the coefficients of an immutable element cannot be
       changed

  [Python]

      >>> from sage.all import *
      >>> nab.set_coef(eU)
      Traceback (most recent call last):
      ...
      ValueError: the coefficients of an immutable element cannot be
       changed

  However, they can now be used as keys for dictionaries:

  Sage

      sage: {nab: 1}[nab]
      1

  Python

      >>> from sage.all import *
      >>> {nab: Integer(1)}[nab]
      1

  The immutability process cannot be made undone. If a connection is needed to be changed again, a copy has to be created:

  Sage

      sage: nab_copy = nab.copy('nablo'); nab_copy
      Affine connection nablo on the 2-dimensional differentiable manifold M
      sage: nab_copy is nab
      False
      sage: nab_copy == nab
      True
      sage: nab_copy.is_immutable()
      False

  [Python]

      >>> from sage.all import *
      >>> nab_copy = nab.copy('nablo'); nab_copy
      Affine connection nablo on the 2-dimensional differentiable manifold M
      >>> nab_copy is nab
      False
      >>> nab_copy == nab
      True
      >>> nab_copy.is_immutable()
      False

  add_coef(frame=None)

    Return the connection coefficients in a given frame for assignment, keeping the coefficients in other frames.

    See method `coef()` for details about the definition of the connection coefficients.

    To delete the connection coefficients in other frames, use the method `set_coef()` instead.

    INPUT:

    - `frame` – (default: `None`) vector frame in which the connection coefficients are defined; if `None`, the default frame of the connection’s domain is assumed.

    Warning

    If the connection has already coefficients in other frames, it is the user’s responsibility to make sure that the coefficients to be added are consistent with them.

    OUTPUT:

    - connection coefficients in the given frame, as an instance of the class `Components`; if such connection coefficients did not exist previously, they are created. See method `coef()` for the storage convention of the connection coefficients.

    EXAMPLES:

    Setting the coefficients of an affine connection w.r.t. some coordinate frame:

    Sage

        sage: M = Manifold(2, 'M', start_index=1)
        sage: X.<x,y> = M.chart()
        sage: nab = M.affine_connection('nabla', latex_name=r'\nabla')
        sage: eX = X.frame(); eX
        Coordinate frame (M, (∂/∂x,∂/∂y))
        sage: nab.add_coef(eX)
        3-indices components w.r.t. Coordinate frame (M, (∂/∂x,∂/∂y))
        sage: nab.add_coef(eX)[1,2,1] = x*y
        sage: nab.display(eX)
        Gam^x_yx = x*y

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(2), 'M', start_index=Integer(1))
        >>> X = M.chart(names=('x', 'y',)); (x, y,) = X._first_ngens(2)
        >>> nab = M.affine_connection('nabla', latex_name=r'\nabla')
        >>> eX = X.frame(); eX
        Coordinate frame (M, (∂/∂x,∂/∂y))
        >>> nab.add_coef(eX)
        3-indices components w.r.t. Coordinate frame (M, (∂/∂x,∂/∂y))
        >>> nab.add_coef(eX)[Integer(1),Integer(2),Integer(1)] = x*y
        >>> nab.display(eX)
        Gam^x_yx = x*y

    Since `eX` is the manifold’s default vector frame, its mention may be omitted:

    Sage

        sage: nab.add_coef()[1,2,1] = x*y
        sage: nab.add_coef()
        3-indices components w.r.t. Coordinate frame (M, (∂/∂x,∂/∂y))
        sage: nab.add_coef()[1,2,1] = x*y
        sage: nab.display()
        Gam^x_yx = x*y

    [Python]

        >>> from sage.all import *
        >>> nab.add_coef()[Integer(1),Integer(2),Integer(1)] = x*y
        >>> nab.add_coef()
        3-indices components w.r.t. Coordinate frame (M, (∂/∂x,∂/∂y))
        >>> nab.add_coef()[Integer(1),Integer(2),Integer(1)] = x*y
        >>> nab.display()
        Gam^x_yx = x*y

    Adding connection coefficients w.r.t. to another vector frame:

    Sage

        sage: e = M.vector_frame('e')
        sage: nab.add_coef(e)
        3-indices components w.r.t. Vector frame (M, (e_1,e_2))
        sage: nab.add_coef(e)[2,1,1] = x+y
        sage: nab.add_coef(e)[2,1,2] = x-y
        sage: nab.display(e)
        Gam^2_11 = x + y
        Gam^2_12 = x - y

    Python

        >>> from sage.all import *
        >>> e = M.vector_frame('e')
        >>> nab.add_coef(e)
        3-indices components w.r.t. Vector frame (M, (e_1,e_2))
        >>> nab.add_coef(e)[Integer(2),Integer(1),Integer(1)] = x+y
        >>> nab.add_coef(e)[Integer(2),Integer(1),Integer(2)] = x-y
        >>> nab.display(e)
        Gam^2_11 = x + y
        Gam^2_12 = x - y

    The coefficients w.r.t. the frame `eX` have been kept:

    Sage

        sage: nab.display(eX)
        Gam^x_yx = x*y

    [Python]

        >>> from sage.all import *
        >>> nab.display(eX)
        Gam^x_yx = x*y

    To delete them, use the method `set_coef()` instead.

  coef(frame=None)

    Return the connection coefficients relative to the given frame.

    \(n\) being the manifold’s dimension, the connection coefficients relative to the vector frame \((e_i)\) are the \(n^3\) scalar fields \(\Gamma^k_{\ \, ij}\) defined by

    \[\nabla_{e_j} e_i = \Gamma^k_{\ \, ij} e_k\]

    If the connection coefficients are not known already, they are computed from the above formula.

    INPUT:

    - `frame` – (default: `None`) vector frame relative to which the connection coefficients are required; if none is provided, the domain’s default frame is assumed

    OUTPUT:

    - connection coefficients relative to the frame `frame`, as an instance of the class `Components` with 3 indices ordered as \((k,i,j)\)

    EXAMPLES:

    Connection coefficient of an affine connection on a 3-dimensional manifold:

    Sage

        sage: M = Manifold(3, 'M', start_index=1)
        sage: c_xyz.<x,y,z> = M.chart()
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab[1,1,2], nab[3,2,3] = x^2, y*z  # Gamma^1_{12} = x^2, Gamma^3_{23} = yz
        sage: nab.coef()
        3-indices components w.r.t. Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z))
        sage: type(nab.coef())
        <class 'sage.tensor.modules.comp.Components'>
        sage: M.default_frame()
        Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z))
        sage: nab.coef() is nab.coef(c_xyz.frame())
        True
        sage: nab.coef()[:]  # full list of coefficients:
        [[[0, x^2, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, y*z], [0, 0, 0]]]

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(3), 'M', start_index=Integer(1))
        >>> c_xyz = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_xyz._first_ngens(3)
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab[Integer(1),Integer(1),Integer(2)], nab[Integer(3),Integer(2),Integer(3)] = x**Integer(2), y*z  # Gamma^1_{12} = x^2, Gamma^3_{23} = yz
        >>> nab.coef()
        3-indices components w.r.t. Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z))
        >>> type(nab.coef())
        <class 'sage.tensor.modules.comp.Components'>
        >>> M.default_frame()
        Coordinate frame (M, (∂/∂x,∂/∂y,∂/∂z))
        >>> nab.coef() is nab.coef(c_xyz.frame())
        True
        >>> nab.coef()[:]  # full list of coefficients:
        [[[0, x^2, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, y*z], [0, 0, 0]]]

  connection_form(i, j, frame=None)

    Return the connection 1-form corresponding to the given index and vector frame.

    The connection 1-forms with respect to the frame \((e_i)\) are the \(n^2\) 1-forms \(\omega^i_{\ \, j}\) defined by

    \[\nabla_v e_j = \langle \omega^i_{\ \, j}, v \rangle \, e_i\]

    for any vector \(v\).

    The components of \(\omega^i_{\ \, j}\) in the coframe \((e^i)\) dual to \((e_i)\) are nothing but the connection coefficients \(\Gamma^i_{\ \, jk}\) relative to the frame \((e_i)\):

    \[\omega^i_{\ \, j} = \Gamma^i_{\ \, jk} e^k\]

    INPUT:

    - `i`, `j` – indices identifying the 1-form \(\omega^i_{\ \, j}\)

    - `frame` – (default: `None`) vector frame relative to which the connection 1-forms are defined; if `None`, the default frame of the connection’s domain is assumed.

    OUTPUT:

    - the 1-form \(\omega^i_{\ \, j}\), as an instance of `DiffForm`

    EXAMPLES:

    Connection 1-forms on a 3-dimensional manifold:

    Sage

        sage: M = Manifold(3, 'M', start_index=1)
        sage: c_xyz.<x,y,z> = M.chart()
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab[1,1,1], nab[1,1,2], nab[1,1,3] = x*y*z, x^2, -y*z
        sage: nab[1,2,3], nab[1,3,1], nab[1,3,2] = -x^3, y^2*z, y^2-x^2
        sage: nab[2,1,1], nab[2,1,2], nab[2,2,1] = z^2, x*y*z^2, -x^2
        sage: nab[2,3,1], nab[2,3,3], nab[3,1,2] = x^2+y^2+z^2, y^2-z^2, x*y+z^2
        sage: nab[3,2,1], nab[3,2,2], nab[3,3,3] = x*y+z, z^3 -y^2, x*z^2 - z*y^2
        sage: nab.connection_form(1,1)  # connection 1-form (i,j)=(1,1) w.r.t. M's default frame
        1-form nabla connection 1-form (1,1) on the 3-dimensional
         differentiable manifold M
        sage: nab.connection_form(1,1)[:]
        [x*y*z, x^2, -y*z]

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(3), 'M', start_index=Integer(1))
        >>> c_xyz = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_xyz._first_ngens(3)
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab[Integer(1),Integer(1),Integer(1)], nab[Integer(1),Integer(1),Integer(2)], nab[Integer(1),Integer(1),Integer(3)] = x*y*z, x**Integer(2), -y*z
        >>> nab[Integer(1),Integer(2),Integer(3)], nab[Integer(1),Integer(3),Integer(1)], nab[Integer(1),Integer(3),Integer(2)] = -x**Integer(3), y**Integer(2)*z, y**Integer(2)-x**Integer(2)
        >>> nab[Integer(2),Integer(1),Integer(1)], nab[Integer(2),Integer(1),Integer(2)], nab[Integer(2),Integer(2),Integer(1)] = z**Integer(2), x*y*z**Integer(2), -x**Integer(2)
        >>> nab[Integer(2),Integer(3),Integer(1)], nab[Integer(2),Integer(3),Integer(3)], nab[Integer(3),Integer(1),Integer(2)] = x**Integer(2)+y**Integer(2)+z**Integer(2), y**Integer(2)-z**Integer(2), x*y+z**Integer(2)
        >>> nab[Integer(3),Integer(2),Integer(1)], nab[Integer(3),Integer(2),Integer(2)], nab[Integer(3),Integer(3),Integer(3)] = x*y+z, z**Integer(3) -y**Integer(2), x*z**Integer(2) - z*y**Integer(2)
        >>> nab.connection_form(Integer(1),Integer(1))  # connection 1-form (i,j)=(1,1) w.r.t. M's default frame
        1-form nabla connection 1-form (1,1) on the 3-dimensional
         differentiable manifold M
        >>> nab.connection_form(Integer(1),Integer(1))[:]
        [x*y*z, x^2, -y*z]

    The result is cached (until the connection is modified via `set_coef()` or `add_coef()`):

    Sage

        sage: nab.connection_form(1,1) is nab.connection_form(1,1)
        True

    [Python]

        >>> from sage.all import *
        >>> nab.connection_form(Integer(1),Integer(1)) is nab.connection_form(Integer(1),Integer(1))
        True

    Connection 1-forms w.r.t. a non-holonomic frame:

    Sage

        sage: ch_basis = M.automorphism_field()
        sage: ch_basis[1,1], ch_basis[2,2], ch_basis[3,3] = y, z, x
        sage: e = M.default_frame().new_frame(ch_basis, 'e')
        sage: e[1][:], e[2][:], e[3][:]
        ([y, 0, 0], [0, z, 0], [0, 0, x])
        sage: nab.connection_form(1,1,e)
        1-form nabla connection 1-form (1,1) on the 3-dimensional
         differentiable manifold M
        sage: nab.connection_form(1,1,e).comp(e)[:]
        [x*y^2*z, (x^2*y + 1)*z/y, -x*y*z]

    Python

        >>> from sage.all import *
        >>> ch_basis = M.automorphism_field()
        >>> ch_basis[Integer(1),Integer(1)], ch_basis[Integer(2),Integer(2)], ch_basis[Integer(3),Integer(3)] = y, z, x
        >>> e = M.default_frame().new_frame(ch_basis, 'e')
        >>> e[Integer(1)][:], e[Integer(2)][:], e[Integer(3)][:]
        ([y, 0, 0], [0, z, 0], [0, 0, x])
        >>> nab.connection_form(Integer(1),Integer(1),e)
        1-form nabla connection 1-form (1,1) on the 3-dimensional
         differentiable manifold M
        >>> nab.connection_form(Integer(1),Integer(1),e).comp(e)[:]
        [x*y^2*z, (x^2*y + 1)*z/y, -x*y*z]

    Check of the formula \(\omega^i_{\ \, j} = \Gamma^i_{\ \, jk} e^k\):

    First on the manifold’s default frame (∂/∂x, ∂/∂y, d:dz):

    Sage

        sage: dx = M.default_frame().coframe() ; dx
        Coordinate coframe (M, (dx,dy,dz))
        sage: check = []
        sage: for i in M.irange():
        ....:     for j in M.irange():
        ....:         check.append( nab.connection_form(i,j) == \
        ....:               sum( nab[[i,j,k]]*dx[k] for k in M.irange() ) )
        sage: check
        [True, True, True, True, True, True, True, True, True]

    [Python]

        >>> from sage.all import *
        >>> dx = M.default_frame().coframe() ; dx
        Coordinate coframe (M, (dx,dy,dz))
        >>> check = []
        >>> for i in M.irange():
        ...     for j in M.irange():
        ...         check.append( nab.connection_form(i,j) ==               sum( nab[[i,j,k]]*dx[k] for k in M.irange() ) )
        >>> check
        [True, True, True, True, True, True, True, True, True]

    Then on the frame e:

    Sage

        sage: ef = e.coframe() ; ef
        Coframe (M, (e^1,e^2,e^3))
        sage: check = []
        sage: for i in M.irange():
        ....:     for j in M.irange():
        ....:         s = nab.connection_form(i,j,e).comp(c_xyz.frame(), from_basis=e)
        ....:         check.append( nab.connection_form(i,j,e) == sum( nab.coef(e)[[i,j,k]]*ef[k] for k in M.irange() ) )
        sage: check
        [True, True, True, True, True, True, True, True, True]

    Python

        >>> from sage.all import *
        >>> ef = e.coframe() ; ef
        Coframe (M, (e^1,e^2,e^3))
        >>> check = []
        >>> for i in M.irange():
        ...     for j in M.irange():
        ...         s = nab.connection_form(i,j,e).comp(c_xyz.frame(), from_basis=e)
        ...         check.append( nab.connection_form(i,j,e) == sum( nab.coef(e)[[i,j,k]]*ef[k] for k in M.irange() ) )
        >>> check
        [True, True, True, True, True, True, True, True, True]

    Check of the formula \(\nabla_v e_j = \langle \omega^i_{\ \, j}, v \rangle e_i\):

    Sage

        sage: v = M.vector_field()
        sage: v[:] = (x*y, z^2-3*x, z+2*y)
        sage: b = M.default_frame()
        sage: for j in M.irange():  # check on M's default frame  # long time
        ....:     nab(b[j]).contract(v) == \
        ....:      sum( nab.connection_form(i,j)(v)*b[i] for i in M.irange())
        True
        True
        True
        sage: for j in M.irange():  # check on frame e  # long time
        ....:     nab(e[j]).contract(v) == \
        ....:      sum( nab.connection_form(i,j,e)(v)*e[i] for i in M.irange())
        True
        True
        True

    [Python]

        >>> from sage.all import *
        >>> v = M.vector_field()
        >>> v[:] = (x*y, z**Integer(2)-Integer(3)*x, z+Integer(2)*y)
        >>> b = M.default_frame()
        >>> for j in M.irange():  # check on M's default frame  # long time
        ...     nab(b[j]).contract(v) ==      sum( nab.connection_form(i,j)(v)*b[i] for i in M.irange())
        True
        True
        True
        >>> for j in M.irange():  # check on frame e  # long time
        ...     nab(e[j]).contract(v) ==      sum( nab.connection_form(i,j,e)(v)*e[i] for i in M.irange())
        True
        True
        True

  copy(name, latex_name=None)

    Return an exact copy of `self`.

    INPUT:

    - `name` – name given to the copy

    - `latex_name` – (default: `None`) LaTeX symbol to denote the copy; if none is provided, the LaTeX symbol is set to `name`

    Note

    The name and the derived quantities are not copied.

    EXAMPLES:

    Sage

        sage: M = Manifold(2, 'M', start_index=1)
        sage: X.<x,y> = M.chart()
        sage: nab = M.affine_connection('nabla', latex_name=r'\nabla')
        sage: eX = X.frame()
        sage: nab.set_coef(eX)[1,2,1] = x*y
        sage: nab.set_coef(eX)[1,2,2] = x+y
        sage: nab.display()
        Gam^x_yx = x*y
        Gam^x_yy = x + y
        sage: nab_copy = nab.copy(name='nabla_1', latex_name=r'\nabla_1')
        sage: nab is nab_copy
        False
        sage: nab == nab_copy
        True
        sage: nab_copy.display()
        Gam^x_yx = x*y
        Gam^x_yy = x + y

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(2), 'M', start_index=Integer(1))
        >>> X = M.chart(names=('x', 'y',)); (x, y,) = X._first_ngens(2)
        >>> nab = M.affine_connection('nabla', latex_name=r'\nabla')
        >>> eX = X.frame()
        >>> nab.set_coef(eX)[Integer(1),Integer(2),Integer(1)] = x*y
        >>> nab.set_coef(eX)[Integer(1),Integer(2),Integer(2)] = x+y
        >>> nab.display()
        Gam^x_yx = x*y
        Gam^x_yy = x + y
        >>> nab_copy = nab.copy(name='nabla_1', latex_name=r'\nabla_1')
        >>> nab is nab_copy
        False
        >>> nab == nab_copy
        True
        >>> nab_copy.display()
        Gam^x_yx = x*y
        Gam^x_yy = x + y

  curvature_form(i, j, frame=None)

    Return the curvature 2-form corresponding to the given index and vector frame.

    The curvature 2-forms with respect to the frame \((e_i)\) are the \(n^2\) 2-forms \(\Omega^i_{\ \, j}\) defined by

    \[\Omega^i_{\ \, j}(u,v) = R(e^i, e_j, u, v)\]

    where \(R\) is the connection’s Riemann curvature tensor (cf. `riemann()`), \((e^i)\) is the coframe dual to \((e_i)\) and \((u,v)\) is a generic pair of vectors.

    INPUT:

    - `i`, `j` – indices identifying the 2-form \(\Omega^i_{\ \, j}\)

    - `frame` – (default: `None`) vector frame relative to which the curvature 2-forms are defined; if `None`, the default frame of the connection’s domain is assumed.

    OUTPUT:

    - the 2-form \(\Omega^i_{\ \, j}\), as an instance of `DiffForm`

    EXAMPLES:

    Curvature 2-forms on a 3-dimensional manifold:

    Sage

        sage: M = Manifold(3, 'M', start_index=1)
        sage: c_xyz.<x,y,z> = M.chart()
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab[1,1,1], nab[1,1,2], nab[1,1,3] = x*y*z, x^2, -y*z
        sage: nab[1,2,3], nab[1,3,1], nab[1,3,2] = -x^3, y^2*z, y^2-x^2
        sage: nab[2,1,1], nab[2,1,2], nab[2,2,1] = z^2, x*y*z^2, -x^2
        sage: nab[2,3,1], nab[2,3,3], nab[3,1,2] = x^2+y^2+z^2, y^2-z^2, x*y+z^2
        sage: nab[3,2,1], nab[3,2,2], nab[3,3,3] = x*y+z, z^3 -y^2, x*z^2 - z*y^2
        sage: nab.curvature_form(1,1)  # long time
        2-form curvature (1,1) of connection nabla w.r.t. Coordinate frame
         (M, (∂/∂x,∂/∂y,∂/∂z)) on the 3-dimensional differentiable manifold M
        sage: nab.curvature_form(1,1).display()  # long time (if above is skipped)
        curvature (1,1) of connection nabla w.r.t. Coordinate frame
         (M, (∂/∂x,∂/∂y,∂/∂z)) = (y^2*z^3 + (x*y^3 - x)*z + 2*x) dx∧dy
          + (x^3*z^2 - x*y) dx∧dz + (x^4*y*z^2 - z) dy∧dz

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(3), 'M', start_index=Integer(1))
        >>> c_xyz = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_xyz._first_ngens(3)
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab[Integer(1),Integer(1),Integer(1)], nab[Integer(1),Integer(1),Integer(2)], nab[Integer(1),Integer(1),Integer(3)] = x*y*z, x**Integer(2), -y*z
        >>> nab[Integer(1),Integer(2),Integer(3)], nab[Integer(1),Integer(3),Integer(1)], nab[Integer(1),Integer(3),Integer(2)] = -x**Integer(3), y**Integer(2)*z, y**Integer(2)-x**Integer(2)
        >>> nab[Integer(2),Integer(1),Integer(1)], nab[Integer(2),Integer(1),Integer(2)], nab[Integer(2),Integer(2),Integer(1)] = z**Integer(2), x*y*z**Integer(2), -x**Integer(2)
        >>> nab[Integer(2),Integer(3),Integer(1)], nab[Integer(2),Integer(3),Integer(3)], nab[Integer(3),Integer(1),Integer(2)] = x**Integer(2)+y**Integer(2)+z**Integer(2), y**Integer(2)-z**Integer(2), x*y+z**Integer(2)
        >>> nab[Integer(3),Integer(2),Integer(1)], nab[Integer(3),Integer(2),Integer(2)], nab[Integer(3),Integer(3),Integer(3)] = x*y+z, z**Integer(3) -y**Integer(2), x*z**Integer(2) - z*y**Integer(2)
        >>> nab.curvature_form(Integer(1),Integer(1))  # long time
        2-form curvature (1,1) of connection nabla w.r.t. Coordinate frame
         (M, (∂/∂x,∂/∂y,∂/∂z)) on the 3-dimensional differentiable manifold M
        >>> nab.curvature_form(Integer(1),Integer(1)).display()  # long time (if above is skipped)
        curvature (1,1) of connection nabla w.r.t. Coordinate frame
         (M, (∂/∂x,∂/∂y,∂/∂z)) = (y^2*z^3 + (x*y^3 - x)*z + 2*x) dx∧dy
          + (x^3*z^2 - x*y) dx∧dz + (x^4*y*z^2 - z) dy∧dz

    Curvature 2-forms w.r.t. a non-holonomic frame:

    Sage

        sage: ch_basis = M.automorphism_field()
        sage: ch_basis[1,1], ch_basis[2,2], ch_basis[3,3] = y, z, x
        sage: e = M.default_frame().new_frame(ch_basis, 'e')
        sage: e[1].display(), e[2].display(), e[3].display()
        (e_1 = y ∂/∂x, e_2 = z ∂/∂y, e_3 = x ∂/∂z)
        sage: ef = e.coframe()
        sage: ef[1].display(), ef[2].display(), ef[3].display()
        (e^1 = 1/y dx, e^2 = 1/z dy, e^3 = 1/x dz)
        sage: nab.curvature_form(1,1,e)  # long time
        2-form curvature (1,1) of connection nabla w.r.t. Vector frame
         (M, (e_1,e_2,e_3)) on the 3-dimensional differentiable manifold M
        sage: nab.curvature_form(1,1,e).display(e)  # long time (if above is skipped)
        curvature (1,1) of connection nabla w.r.t. Vector frame
         (M, (e_1,e_2,e_3)) =
          (y^3*z^4 + 2*x*y*z + (x*y^4 - x*y)*z^2) e^1∧e^2
          + (x^4*y*z^2 - x^2*y^2) e^1∧e^3 + (x^5*y*z^3 - x*z^2) e^2∧e^3

    [Python]

        >>> from sage.all import *
        >>> ch_basis = M.automorphism_field()
        >>> ch_basis[Integer(1),Integer(1)], ch_basis[Integer(2),Integer(2)], ch_basis[Integer(3),Integer(3)] = y, z, x
        >>> e = M.default_frame().new_frame(ch_basis, 'e')
        >>> e[Integer(1)].display(), e[Integer(2)].display(), e[Integer(3)].display()
        (e_1 = y ∂/∂x, e_2 = z ∂/∂y, e_3 = x ∂/∂z)
        >>> ef = e.coframe()
        >>> ef[Integer(1)].display(), ef[Integer(2)].display(), ef[Integer(3)].display()
        (e^1 = 1/y dx, e^2 = 1/z dy, e^3 = 1/x dz)
        >>> nab.curvature_form(Integer(1),Integer(1),e)  # long time
        2-form curvature (1,1) of connection nabla w.r.t. Vector frame
         (M, (e_1,e_2,e_3)) on the 3-dimensional differentiable manifold M
        >>> nab.curvature_form(Integer(1),Integer(1),e).display(e)  # long time (if above is skipped)
        curvature (1,1) of connection nabla w.r.t. Vector frame
         (M, (e_1,e_2,e_3)) =
          (y^3*z^4 + 2*x*y*z + (x*y^4 - x*y)*z^2) e^1∧e^2
          + (x^4*y*z^2 - x^2*y^2) e^1∧e^3 + (x^5*y*z^3 - x*z^2) e^2∧e^3

    Cartan’s second structure equation is

    \[\Omega^i_{\ \, j} = \mathrm{d} \omega^i_{\ \, j} + \omega^i_{\ \, k} \wedge \omega^k_{\ \, j}\]

    where the \(\omega^i_{\ \, j}\)’s are the connection 1-forms (cf. `connection_form()`). Let us check it on the frame e:

    Sage

        sage: omega = nab.connection_form
        sage: check = []
        sage: for i in M.irange():  # long time
        ....:     for j in M.irange():
        ....:         check.append( nab.curvature_form(i,j,e) == \
        ....:                       omega(i,j,e).exterior_derivative() + \
        ....:         sum( omega(i,k,e).wedge(omega(k,j,e)) for k in M.irange()) )
        sage: check  # long time
        [True, True, True, True, True, True, True, True, True]

    Python

        >>> from sage.all import *
        >>> omega = nab.connection_form
        >>> check = []
        >>> for i in M.irange():  # long time
        ...     for j in M.irange():
        ...         check.append( nab.curvature_form(i,j,e) ==                       omega(i,j,e).exterior_derivative() +         sum( omega(i,k,e).wedge(omega(k,j,e)) for k in M.irange()) )
        >>> check  # long time
        [True, True, True, True, True, True, True, True, True]

  del_other_coef(frame=None)

    Delete all the coefficients but those corresponding to `frame`.

    INPUT:

    - `frame` – (default: `None`) vector frame, the connection coefficients w.r.t. which are to be kept; if `None`, the default frame of the connection’s domain is assumed.

    EXAMPLES:

    We first create two sets of connection coefficients:

    Sage

        sage: M = Manifold(2, 'M', start_index=1)
        sage: X.<x,y> = M.chart()
        sage: nab = M.affine_connection('nabla', latex_name=r'\nabla')
        sage: eX = X.frame()
        sage: nab.set_coef(eX)[1,2,1] = x*y
        sage: e = M.vector_frame('e')
        sage: nab.add_coef(e)[2,1,1] = x+y
        sage: nab.display(eX)
        Gam^x_yx = x*y
        sage: nab.display(e)
        Gam^2_11 = x + y

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(2), 'M', start_index=Integer(1))
        >>> X = M.chart(names=('x', 'y',)); (x, y,) = X._first_ngens(2)
        >>> nab = M.affine_connection('nabla', latex_name=r'\nabla')
        >>> eX = X.frame()
        >>> nab.set_coef(eX)[Integer(1),Integer(2),Integer(1)] = x*y
        >>> e = M.vector_frame('e')
        >>> nab.add_coef(e)[Integer(2),Integer(1),Integer(1)] = x+y
        >>> nab.display(eX)
        Gam^x_yx = x*y
        >>> nab.display(e)
        Gam^2_11 = x + y

    Let us delete the connection coefficients w.r.t. all frames except for frame `eX`:

    Sage

        sage: nab.del_other_coef(eX)
        sage: nab.display(eX)
        Gam^x_yx = x*y

    [Python]

        >>> from sage.all import *
        >>> nab.del_other_coef(eX)
        >>> nab.display(eX)
        Gam^x_yx = x*y

    The connection coefficients w.r.t. frame `e` have indeed been deleted:

    Sage

        sage: nab.display(e)
        Traceback (most recent call last):
        ...
        ValueError: no common frame found for the computation

    Python

        >>> from sage.all import *
        >>> nab.display(e)
        Traceback (most recent call last):
        ...
        ValueError: no common frame found for the computation

  display(frame=None, chart=None, symbol=None, latex_symbol=None, index_labels=None, index_latex_labels=None, coordinate_labels=True, only_nonzero=True, only_nonredundant=False)

    Display all the connection coefficients w.r.t. to a given frame, one per line.

    The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode).

    INPUT:

    - `frame` – (default: `None`) vector frame relative to which the connection coefficients are defined; if `None`, the default frame of the connection’s domain is used

    - `chart` – (default: `None`) chart specifying the coordinate expression of the connection coefficients; if `None`, the default chart of the domain of `frame` is used

    - `symbol` – (default: `None`) string specifying the symbol of the connection coefficients; if `None`, ‘Gam’ is used

    - `latex_symbol` – (default: `None`) string specifying the LaTeX symbol for the components; if `None`, ‘\Gamma’ is used

    - `index_labels` – (default: `None`) list of strings representing the labels of each index; if `None`, integer labels are used, except if `frame` is a coordinate frame and `coordinate_symbols` is set to `True`, in which case the coordinate symbols are used

    - `index_latex_labels` – (default: `None`) list of strings representing the LaTeX labels of each index; if `None`, integer labels are used, except if `frame` is a coordinate frame and `coordinate_symbols` is set to `True`, in which case the coordinate LaTeX symbols are used

    - `coordinate_labels` – boolean (default: `True`); if `True`, coordinate symbols are used by default (instead of integers) as index labels whenever `frame` is a coordinate frame

    - `only_nonzero` – boolean (default: `True`); if `True`, only nonzero connection coefficients are displayed

    - `only_nonredundant` – boolean (default: `False`); if `True`, only nonredundant connection coefficients are displayed in case of symmetries

    EXAMPLES:

    Coefficients of a connection on a 3-dimensional manifold:

    Sage

        sage: M = Manifold(3, 'M', start_index=1)
        sage: c_xyz.<x,y,z> = M.chart()
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab[1,1,2], nab[3,2,3] = x^2, y*z

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(3), 'M', start_index=Integer(1))
        >>> c_xyz = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_xyz._first_ngens(3)
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab[Integer(1),Integer(1),Integer(2)], nab[Integer(3),Integer(2),Integer(3)] = x**Integer(2), y*z

    By default, only the nonzero connection coefficients are displayed:

    Sage

        sage: nab.display()
        Gam^x_xy = x^2
        Gam^z_yz = y*z
        sage: latex(nab.display())
        \begin{array}{lcl} \Gamma_{ \phantom{\, x} \, x \, y }^{ \, x \phantom{\, x} \phantom{\, y} }
        & = & x^{2} \\
        \Gamma_{ \phantom{\, z} \, y \, z }^{ \, z \phantom{\, y} \phantom{\, z} }
        & = & y z \end{array}

    [Python]

        >>> from sage.all import *
        >>> nab.display()
        Gam^x_xy = x^2
        Gam^z_yz = y*z
        >>> latex(nab.display())
        \begin{array}{lcl} \Gamma_{ \phantom{\, x} \, x \, y }^{ \, x \phantom{\, x} \phantom{\, y} }
        & = & x^{2} \\
        \Gamma_{ \phantom{\, z} \, y \, z }^{ \, z \phantom{\, y} \phantom{\, z} }
        & = & y z \end{array}

    By default, the displayed connection coefficients are those w.r.t. to the default frame of the connection’s domain, so the above is equivalent to:

    Sage

        sage: nab.display(frame=M.default_frame())
        Gam^x_xy = x^2
        Gam^z_yz = y*z

    Python

        >>> from sage.all import *
        >>> nab.display(frame=M.default_frame())
        Gam^x_xy = x^2
        Gam^z_yz = y*z

    Since the default frame is a coordinate frame, coordinate symbols are used to label the indices, but one may ask for integers instead:

    Sage

        sage: M.default_frame() is c_xyz.frame()
        True
        sage: nab.display(coordinate_labels=False)
        Gam^1_12 = x^2
        Gam^3_23 = y*z

    [Python]

        >>> from sage.all import *
        >>> M.default_frame() is c_xyz.frame()
        True
        >>> nab.display(coordinate_labels=False)
        Gam^1_12 = x^2
        Gam^3_23 = y*z

    The index labels can also be customized:

    Sage

        sage: nab.display(index_labels=['(1)', '(2)', '(3)'])
        Gam^(1)_(1),(2) = x^2
        Gam^(3)_(2),(3) = y*z

    Python

        >>> from sage.all import *
        >>> nab.display(index_labels=['(1)', '(2)', '(3)'])
        Gam^(1)_(1),(2) = x^2
        Gam^(3)_(2),(3) = y*z

    The symbol ‘Gam’ can be changed:

    Sage

        sage: nab.display(symbol='C', latex_symbol='C')
        C^x_xy = x^2
        C^z_yz = y*z
        sage: latex(nab.display(symbol='C', latex_symbol='C'))
        \begin{array}{lcl} C_{ \phantom{\, x} \, x \, y }^{ \, x \phantom{\, x} \phantom{\, y} }
        & = & x^{2} \\
        C_{ \phantom{\, z} \, y \, z }^{ \, z \phantom{\, y} \phantom{\, z} }
        & = & y z \end{array}

    [Python]

        >>> from sage.all import *
        >>> nab.display(symbol='C', latex_symbol='C')
        C^x_xy = x^2
        C^z_yz = y*z
        >>> latex(nab.display(symbol='C', latex_symbol='C'))
        \begin{array}{lcl} C_{ \phantom{\, x} \, x \, y }^{ \, x \phantom{\, x} \phantom{\, y} }
        & = & x^{2} \\
        C_{ \phantom{\, z} \, y \, z }^{ \, z \phantom{\, y} \phantom{\, z} }
        & = & y z \end{array}

    Display of Christoffel symbols, skipping the redundancy associated with the symmetry of the last two indices:

    Sage

        sage: M = Manifold(3, 'R^3', 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 = M.metric('g')
        sage: g[1,1], g[2,2], g[3,3] = 1, r^2 , (r*sin(th))^2
        sage: g.display()
        g = dr⊗dr + r^2 dth⊗dth + r^2*sin(th)^2 dph⊗dph
        sage: g.connection().display(only_nonredundant=True)
        Gam^r_th,th = -r
        Gam^r_ph,ph = -r*sin(th)^2
        Gam^th_r,th = 1/r
        Gam^th_ph,ph = -cos(th)*sin(th)
        Gam^ph_r,ph = 1/r
        Gam^ph_th,ph = cos(th)/sin(th)

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(3), 'R^3', start_index=Integer(1))
        >>> c_spher = M.chart(r'r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\phi', names=('r', 'th', 'ph',)); (r, th, ph,) = c_spher._first_ngens(3)
        >>> g = M.metric('g')
        >>> g[Integer(1),Integer(1)], g[Integer(2),Integer(2)], g[Integer(3),Integer(3)] = Integer(1), r**Integer(2) , (r*sin(th))**Integer(2)
        >>> g.display()
        g = dr⊗dr + r^2 dth⊗dth + r^2*sin(th)^2 dph⊗dph
        >>> g.connection().display(only_nonredundant=True)
        Gam^r_th,th = -r
        Gam^r_ph,ph = -r*sin(th)^2
        Gam^th_r,th = 1/r
        Gam^th_ph,ph = -cos(th)*sin(th)
        Gam^ph_r,ph = 1/r
        Gam^ph_th,ph = cos(th)/sin(th)

    By default, the parameter `only_nonredundant` is set to `False`:

    Sage

        sage: g.connection().display()
        Gam^r_th,th = -r
        Gam^r_ph,ph = -r*sin(th)^2
        Gam^th_r,th = 1/r
        Gam^th_th,r = 1/r
        Gam^th_ph,ph = -cos(th)*sin(th)
        Gam^ph_r,ph = 1/r
        Gam^ph_th,ph = cos(th)/sin(th)
        Gam^ph_ph,r = 1/r
        Gam^ph_ph,th = cos(th)/sin(th)

    [Python]

        >>> from sage.all import *
        >>> g.connection().display()
        Gam^r_th,th = -r
        Gam^r_ph,ph = -r*sin(th)^2
        Gam^th_r,th = 1/r
        Gam^th_th,r = 1/r
        Gam^th_ph,ph = -cos(th)*sin(th)
        Gam^ph_r,ph = 1/r
        Gam^ph_th,ph = cos(th)/sin(th)
        Gam^ph_ph,r = 1/r
        Gam^ph_ph,th = cos(th)/sin(th)

  domain()

    Return the manifold subset on which the affine connection is defined.

    OUTPUT:

    - instance of class `DifferentiableManifold` representing the manifold on which `self` is defined.

    EXAMPLES:

    Sage

        sage: M = Manifold(3, 'M', start_index=1)
        sage: c_xyz.<x,y,z> = M.chart()
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab.domain()
        3-dimensional differentiable manifold M
        sage: U = M.open_subset('U', coord_def={c_xyz: x>0})
        sage: nabU = U.affine_connection('D')
        sage: nabU.domain()
        Open subset U of the 3-dimensional differentiable manifold M

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(3), 'M', start_index=Integer(1))
        >>> c_xyz = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_xyz._first_ngens(3)
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab.domain()
        3-dimensional differentiable manifold M
        >>> U = M.open_subset('U', coord_def={c_xyz: x>Integer(0)})
        >>> nabU = U.affine_connection('D')
        >>> nabU.domain()
        Open subset U of the 3-dimensional differentiable manifold M

  is_immutable()

    Return `True` if this object is immutable, i.e. its coefficients cannot be chanced, and `False` if it is not.

    To set an affine connection immutable, use `set_immutable()`.

    EXAMPLES:

    Sage

        sage: M = Manifold(2, 'M', start_index=1)
        sage: X.<x,y> = M.chart()
        sage: nab = M.affine_connection('nabla', latex_name=r'\nabla')
        sage: nab.is_immutable()
        False
        sage: nab.set_immutable()
        sage: nab.is_immutable()
        True

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(2), 'M', start_index=Integer(1))
        >>> X = M.chart(names=('x', 'y',)); (x, y,) = X._first_ngens(2)
        >>> nab = M.affine_connection('nabla', latex_name=r'\nabla')
        >>> nab.is_immutable()
        False
        >>> nab.set_immutable()
        >>> nab.is_immutable()
        True

  is_mutable()

    Return `True` if this object is mutable, i.e. its coefficients can be changed, and `False` if it is not.

    EXAMPLES:

    Sage

        sage: M = Manifold(2, 'M', start_index=1)
        sage: X.<x,y> = M.chart()
        sage: nab = M.affine_connection('nabla', latex_name=r'\nabla')
        sage: nab.is_mutable()
        True
        sage: nab.set_immutable()
        sage: nab.is_mutable()
        False

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(2), 'M', start_index=Integer(1))
        >>> X = M.chart(names=('x', 'y',)); (x, y,) = X._first_ngens(2)
        >>> nab = M.affine_connection('nabla', latex_name=r'\nabla')
        >>> nab.is_mutable()
        True
        >>> nab.set_immutable()
        >>> nab.is_mutable()
        False

  restrict(subdomain)

    Return the restriction of the connection to some subdomain.

    If such restriction has not been defined yet, it is constructed here.

    INPUT:

    - `subdomain` – open subset \(U\) of the connection’s domain (must be an instance of `DifferentiableManifold`)

    OUTPUT: instance of `AffineConnection` representing the restriction

    EXAMPLES:

    Restriction of a connection on a 2-dimensional manifold:

    Sage

        sage: M = Manifold(2, 'M', start_index=1)
        sage: c_xy.<x,y> = M.chart()
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab[1,1,2], nab[2,1,1] = x^2, x+y
        sage: nab[:]
        [[[0, x^2], [0, 0]], [[x + y, 0], [0, 0]]]
        sage: U = M.open_subset('U', coord_def={c_xy: x>0})
        sage: nabU = nab.restrict(U) ; nabU
        Affine connection nabla on the Open subset U of the 2-dimensional
         differentiable manifold M
        sage: nabU.domain()
        Open subset U of the 2-dimensional differentiable manifold M
        sage: nabU[:]
        [[[0, x^2], [0, 0]], [[x + y, 0], [0, 0]]]

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(2), 'M', start_index=Integer(1))
        >>> c_xy = M.chart(names=('x', 'y',)); (x, y,) = c_xy._first_ngens(2)
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab[Integer(1),Integer(1),Integer(2)], nab[Integer(2),Integer(1),Integer(1)] = x**Integer(2), x+y
        >>> nab[:]
        [[[0, x^2], [0, 0]], [[x + y, 0], [0, 0]]]
        >>> U = M.open_subset('U', coord_def={c_xy: x>Integer(0)})
        >>> nabU = nab.restrict(U) ; nabU
        Affine connection nabla on the Open subset U of the 2-dimensional
         differentiable manifold M
        >>> nabU.domain()
        Open subset U of the 2-dimensional differentiable manifold M
        >>> nabU[:]
        [[[0, x^2], [0, 0]], [[x + y, 0], [0, 0]]]

    The result is cached:

    Sage

        sage: nab.restrict(U) is nabU
        True

    [Python]

        >>> from sage.all import *
        >>> nab.restrict(U) is nabU
        True

    until the connection is modified:

    Sage

        sage: nab[1,2,2] = -y
        sage: nab.restrict(U) is nabU
        False
        sage: nab.restrict(U)[:]
        [[[0, x^2], [0, -y]], [[x + y, 0], [0, 0]]]

    Python

        >>> from sage.all import *
        >>> nab[Integer(1),Integer(2),Integer(2)] = -y
        >>> nab.restrict(U) is nabU
        False
        >>> nab.restrict(U)[:]
        [[[0, x^2], [0, -y]], [[x + y, 0], [0, 0]]]

  ricci()

    Return the connection’s Ricci tensor.

    The Ricci tensor is the tensor field \(Ric\) of type (0,2) defined from the Riemann curvature tensor \(R\) by

    \[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.

    OUTPUT:

    - the Ricci tensor \(Ric\), as an instance of `TensorField`

    EXAMPLES:

    Ricci tensor of an affine connection on a 3-dimensional manifold:

    Sage

        sage: M = Manifold(3, 'M', start_index=1)
        sage: c_xyz.<x,y,z> = M.chart()
        sage: nab = M.affine_connection('nabla', r'\nabla') ; nab
        Affine connection nabla on the 3-dimensional differentiable
         manifold M
        sage: nab[1,1,2], nab[3,2,3] = x^2, y*z  # Gamma^1_{12} = x^2, Gamma^3_{23} = yz
        sage: r = nab.ricci() ; r
        Tensor field of type (0,2) on the 3-dimensional differentiable
         manifold M
        sage: r[:]
        [  0 2*x   0]
        [  0  -z   0]
        [  0   0   0]

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(3), 'M', start_index=Integer(1))
        >>> c_xyz = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_xyz._first_ngens(3)
        >>> nab = M.affine_connection('nabla', r'\nabla') ; nab
        Affine connection nabla on the 3-dimensional differentiable
         manifold M
        >>> nab[Integer(1),Integer(1),Integer(2)], nab[Integer(3),Integer(2),Integer(3)] = x**Integer(2), y*z  # Gamma^1_{12} = x^2, Gamma^3_{23} = yz
        >>> r = nab.ricci() ; r
        Tensor field of type (0,2) on the 3-dimensional differentiable
         manifold M
        >>> r[:]
        [  0 2*x   0]
        [  0  -z   0]
        [  0   0   0]

    The result is cached (until the connection is modified via `set_coef()` or `add_coef()`):

    Sage

        sage: nab.ricci() is r
        True

    [Python]

        >>> from sage.all import *
        >>> nab.ricci() is r
        True

  riemann()

    Return the connection’s Riemann curvature tensor.

    The Riemann curvature tensor is the tensor field \(R\) of type (1,3) defined by

    \[R(\omega, w, u, v) = \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\).

    OUTPUT:

    - the Riemann curvature tensor \(R\), as an instance of `TensorField`

    EXAMPLES:

    Curvature of an affine connection on a 3-dimensional manifold:

    Sage

        sage: M = Manifold(3, 'M', start_index=1)
        sage: c_xyz.<x,y,z> = M.chart()
        sage: nab = M.affine_connection('nabla', r'\nabla') ; nab
        Affine connection nabla on the 3-dimensional differentiable
         manifold M
        sage: nab[1,1,2], nab[3,2,3] = x^2, y*z  # Gamma^1_{12} = x^2, Gamma^3_{23} = yz
        sage: r = nab.riemann() ; r
        Tensor field of type (1,3) on the 3-dimensional differentiable
         manifold M
        sage: r.parent()
        Free module T^(1,3)(M) of type-(1,3) tensors fields on the
         3-dimensional differentiable manifold M

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(3), 'M', start_index=Integer(1))
        >>> c_xyz = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_xyz._first_ngens(3)
        >>> nab = M.affine_connection('nabla', r'\nabla') ; nab
        Affine connection nabla on the 3-dimensional differentiable
         manifold M
        >>> nab[Integer(1),Integer(1),Integer(2)], nab[Integer(3),Integer(2),Integer(3)] = x**Integer(2), y*z  # Gamma^1_{12} = x^2, Gamma^3_{23} = yz
        >>> r = nab.riemann() ; r
        Tensor field of type (1,3) on the 3-dimensional differentiable
         manifold M
        >>> r.parent()
        Free module T^(1,3)(M) of type-(1,3) tensors fields on the
         3-dimensional differentiable manifold M

    By construction, the Riemann tensor is antisymmetric with respect to its last two arguments (denoted \(u\) and \(v\) in the definition above), which are at positions 2 and 3 (the first argument being at position 0):

    Sage

        sage: r.symmetries()
        no symmetry;  antisymmetry: (2, 3)

    [Python]

        >>> from sage.all import *
        >>> r.symmetries()
        no symmetry;  antisymmetry: (2, 3)

    The components:

    Sage

        sage: r[:]
        [[[[0, 2*x, 0], [-2*x, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]]],
        [[[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]]],
        [[[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, z], [0, -z, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]]]]

    Python

        >>> from sage.all import *
        >>> r[:]
        [[[[0, 2*x, 0], [-2*x, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]]],
        [[[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]]],
        [[[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, z], [0, -z, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]]]]

    The result is cached (until the connection is modified via `set_coef()` or `add_coef()`):

    Sage

        sage: nab.riemann() is r
        True

    [Python]

        >>> from sage.all import *
        >>> nab.riemann() is r
        True

    Another example: Riemann curvature tensor of some connection on a non-parallelizable 2-dimensional manifold:

    Sage

        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: c_xyW = c_xy.restrict(W) ; c_uvW = c_uv.restrict(W)
        sage: eUW = c_xyW.frame() ; eVW = c_uvW.frame()
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab[0,0,0], nab[0,1,0], nab[1,0,1] = x, x-y, x*y
        sage: for i in M.irange():
        ....:     for j in M.irange():
        ....:         for k in M.irange():
        ....:             nab.add_coef(eV)[i,j,k] = nab.coef(eVW)[i,j,k,c_uvW].expr()
        sage: r = nab.riemann() ; r     # long time
        Tensor field of type (1,3) on the 2-dimensional differentiable
         manifold M
        sage: r.parent()                # long time
        Module T^(1,3)(M) of type-(1,3) tensors fields on the 2-dimensional
         differentiable manifold M
        sage: r.display(eU)             # long time
        (x^2*y - x*y^2) ∂/∂x⊗dx⊗dx⊗dy + (-x^2*y + x*y^2) ∂/∂x⊗dx⊗dy⊗dx + ∂/∂x⊗dy⊗dx⊗dy
         - ∂/∂x⊗dy⊗dy⊗dx - (x^2 - 1)*y ∂/∂y⊗dx⊗dx⊗dy + (x^2 - 1)*y ∂/∂y⊗dx⊗dy⊗dx
         + (-x^2*y + x*y^2) ∂/∂y⊗dy⊗dx⊗dy + (x^2*y - x*y^2) ∂/∂y⊗dy⊗dy⊗dx
        sage: r.display(eV)             # long time
        (1/32*u^3 - 1/32*u*v^2 - 1/32*v^3 + 1/32*(u^2 + 4)*v - 1/8*u - 1/4) ∂/∂u⊗du⊗du⊗dv
         + (-1/32*u^3 + 1/32*u*v^2 + 1/32*v^3 - 1/32*(u^2 + 4)*v + 1/8*u + 1/4) ∂/∂u⊗du⊗dv⊗du
         + (1/32*u^3 - 1/32*u*v^2 + 3/32*v^3 - 1/32*(3*u^2 - 4)*v - 1/8*u + 1/4) ∂/∂u⊗dv⊗du⊗dv
         + (-1/32*u^3 + 1/32*u*v^2 - 3/32*v^3 + 1/32*(3*u^2 - 4)*v + 1/8*u - 1/4) ∂/∂u⊗dv⊗dv⊗du
         + (-1/32*u^3 + 1/32*u*v^2 + 5/32*v^3 - 1/32*(5*u^2 + 4)*v + 1/8*u - 1/4) ∂/∂v⊗du⊗du⊗dv
         + (1/32*u^3 - 1/32*u*v^2 - 5/32*v^3 + 1/32*(5*u^2 + 4)*v - 1/8*u + 1/4) ∂/∂v⊗du⊗dv⊗du
         + (-1/32*u^3 + 1/32*u*v^2 + 1/32*v^3 - 1/32*(u^2 + 4)*v + 1/8*u + 1/4) ∂/∂v⊗dv⊗du⊗dv
         + (1/32*u^3 - 1/32*u*v^2 - 1/32*v^3 + 1/32*(u^2 + 4)*v - 1/8*u - 1/4) ∂/∂v⊗dv⊗dv⊗du

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(2), 'M')
        >>> U = M.open_subset('U') ; V = M.open_subset('V')
        >>> M.declare_union(U,V)   # M is the union of U and V
        >>> c_xy = U.chart(names=('x', 'y',)); (x, y,) = c_xy._first_ngens(2); c_uv = V.chart(names=('u', 'v',)); (u, v,) = c_uv._first_ngens(2)
        >>> transf = c_xy.transition_map(c_uv, (x+y, x-y), intersection_name='W',
        ...                              restrictions1= x>Integer(0), restrictions2= u+v>Integer(0))
        >>> inv = transf.inverse()
        >>> W = U.intersection(V)
        >>> eU = c_xy.frame() ; eV = c_uv.frame()
        >>> c_xyW = c_xy.restrict(W) ; c_uvW = c_uv.restrict(W)
        >>> eUW = c_xyW.frame() ; eVW = c_uvW.frame()
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab[Integer(0),Integer(0),Integer(0)], nab[Integer(0),Integer(1),Integer(0)], nab[Integer(1),Integer(0),Integer(1)] = x, x-y, x*y
        >>> for i in M.irange():
        ...     for j in M.irange():
        ...         for k in M.irange():
        ...             nab.add_coef(eV)[i,j,k] = nab.coef(eVW)[i,j,k,c_uvW].expr()
        >>> r = nab.riemann() ; r     # long time
        Tensor field of type (1,3) on the 2-dimensional differentiable
         manifold M
        >>> r.parent()                # long time
        Module T^(1,3)(M) of type-(1,3) tensors fields on the 2-dimensional
         differentiable manifold M
        >>> r.display(eU)             # long time
        (x^2*y - x*y^2) ∂/∂x⊗dx⊗dx⊗dy + (-x^2*y + x*y^2) ∂/∂x⊗dx⊗dy⊗dx + ∂/∂x⊗dy⊗dx⊗dy
         - ∂/∂x⊗dy⊗dy⊗dx - (x^2 - 1)*y ∂/∂y⊗dx⊗dx⊗dy + (x^2 - 1)*y ∂/∂y⊗dx⊗dy⊗dx
         + (-x^2*y + x*y^2) ∂/∂y⊗dy⊗dx⊗dy + (x^2*y - x*y^2) ∂/∂y⊗dy⊗dy⊗dx
        >>> r.display(eV)             # long time
        (1/32*u^3 - 1/32*u*v^2 - 1/32*v^3 + 1/32*(u^2 + 4)*v - 1/8*u - 1/4) ∂/∂u⊗du⊗du⊗dv
         + (-1/32*u^3 + 1/32*u*v^2 + 1/32*v^3 - 1/32*(u^2 + 4)*v + 1/8*u + 1/4) ∂/∂u⊗du⊗dv⊗du
         + (1/32*u^3 - 1/32*u*v^2 + 3/32*v^3 - 1/32*(3*u^2 - 4)*v - 1/8*u + 1/4) ∂/∂u⊗dv⊗du⊗dv
         + (-1/32*u^3 + 1/32*u*v^2 - 3/32*v^3 + 1/32*(3*u^2 - 4)*v + 1/8*u - 1/4) ∂/∂u⊗dv⊗dv⊗du
         + (-1/32*u^3 + 1/32*u*v^2 + 5/32*v^3 - 1/32*(5*u^2 + 4)*v + 1/8*u - 1/4) ∂/∂v⊗du⊗du⊗dv
         + (1/32*u^3 - 1/32*u*v^2 - 5/32*v^3 + 1/32*(5*u^2 + 4)*v - 1/8*u + 1/4) ∂/∂v⊗du⊗dv⊗du
         + (-1/32*u^3 + 1/32*u*v^2 + 1/32*v^3 - 1/32*(u^2 + 4)*v + 1/8*u + 1/4) ∂/∂v⊗dv⊗du⊗dv
         + (1/32*u^3 - 1/32*u*v^2 - 1/32*v^3 + 1/32*(u^2 + 4)*v - 1/8*u - 1/4) ∂/∂v⊗dv⊗dv⊗du

    The same computation parallelized on 2 cores:

    Sage

        sage: Parallelism().set(nproc=2)
        sage: r_backup = r              # long time
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab[0,0,0], nab[0,1,0], nab[1,0,1] = x, x-y, x*y
        sage: for i in M.irange():
        ....:     for j in M.irange():
        ....:         for k in M.irange():
        ....:             nab.add_coef(eV)[i,j,k] = nab.coef(eVW)[i,j,k,c_uvW].expr()
        sage: r = nab.riemann() ; r     # long time
        Tensor field of type (1,3) on the 2-dimensional differentiable
         manifold M
        sage: r.parent()                # long time
        Module T^(1,3)(M) of type-(1,3) tensors fields on the 2-dimensional
         differentiable manifold M
        sage: r == r_backup             # long time
        True
        sage: Parallelism().set(nproc=1)  # switch off parallelization

    [Python]

        >>> from sage.all import *
        >>> Parallelism().set(nproc=Integer(2))
        >>> r_backup = r              # long time
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab[Integer(0),Integer(0),Integer(0)], nab[Integer(0),Integer(1),Integer(0)], nab[Integer(1),Integer(0),Integer(1)] = x, x-y, x*y
        >>> for i in M.irange():
        ...     for j in M.irange():
        ...         for k in M.irange():
        ...             nab.add_coef(eV)[i,j,k] = nab.coef(eVW)[i,j,k,c_uvW].expr()
        >>> r = nab.riemann() ; r     # long time
        Tensor field of type (1,3) on the 2-dimensional differentiable
         manifold M
        >>> r.parent()                # long time
        Module T^(1,3)(M) of type-(1,3) tensors fields on the 2-dimensional
         differentiable manifold M
        >>> r == r_backup             # long time
        True
        >>> Parallelism().set(nproc=Integer(1))  # switch off parallelization

  set_calc_order(symbol, order, truncate=False)

    Trigger a series expansion with respect to a small parameter in computations involving `self`.

    This property is propagated by usual operations. The internal representation must be `SR` for this to take effect.

    INPUT:

    - `symbol` – symbolic variable (the “small parameter” \(\epsilon\)) with respect to which the connection coefficients are expanded in power series

    - `order` – integer; the order \(n\) of the expansion, defined as the degree of the polynomial representing the truncated power series in `symbol`

    - `truncate` – boolean (default: `False`); determines whether the connection coefficients are replaced by their expansions to the given order

    EXAMPLES:

    Sage

        sage: M = Manifold(4, 'M', structure='Lorentzian')
        sage: C.<t,x,y,z> = M.chart()
        sage: e = var('e')
        sage: g = M.metric()
        sage: h = M.tensor_field(0, 2, sym=(0,1))
        sage: g[0, 0], g[1, 1], g[2, 2], g[3, 3] = -1, 1, 1, 1
        sage: h[0, 1] = x
        sage: g.set(g + e*h)
        sage: g[:]
        [ -1 e*x   0   0]
        [e*x   1   0   0]
        [  0   0   1   0]
        [  0   0   0   1]
        sage: nab = g.connection()
        sage: nab[0, 1, 1]
        -e/(e^2*x^2 + 1)
        sage: nab.set_calc_order(e, 1, truncate=True)
        sage: nab[0, 1, 1]
        -e

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(4), 'M', structure='Lorentzian')
        >>> C = M.chart(names=('t', 'x', 'y', 'z',)); (t, x, y, z,) = C._first_ngens(4)
        >>> e = var('e')
        >>> g = M.metric()
        >>> h = M.tensor_field(Integer(0), Integer(2), sym=(Integer(0),Integer(1)))
        >>> g[Integer(0), Integer(0)], g[Integer(1), Integer(1)], g[Integer(2), Integer(2)], g[Integer(3), Integer(3)] = -Integer(1), Integer(1), Integer(1), Integer(1)
        >>> h[Integer(0), Integer(1)] = x
        >>> g.set(g + e*h)
        >>> g[:]
        [ -1 e*x   0   0]
        [e*x   1   0   0]
        [  0   0   1   0]
        [  0   0   0   1]
        >>> nab = g.connection()
        >>> nab[Integer(0), Integer(1), Integer(1)]
        -e/(e^2*x^2 + 1)
        >>> nab.set_calc_order(e, Integer(1), truncate=True)
        >>> nab[Integer(0), Integer(1), Integer(1)]
        -e

  set_coef(frame=None)

    Return the connection coefficients in a given frame for assignment.

    See method `coef()` for details about the definition of the connection coefficients.

    The connection coefficients with respect to other frames are deleted, in order to avoid any inconsistency. To keep them, use the method `add_coef()` instead.

    INPUT:

    - `frame` – (default: `None`) vector frame in which the connection coefficients are defined; if `None`, the default frame of the connection’s domain is assumed.

    OUTPUT:

    - connection coefficients in the given frame, as an instance of the class `Components`; if such connection coefficients did not exist previously, they are created. See method `coef()` for the storage convention of the connection coefficients.

    EXAMPLES:

    Setting the coefficients of an affine connection w.r.t. some coordinate frame:

    Sage

        sage: M = Manifold(2, 'M', start_index=1)
        sage: X.<x,y> = M.chart()
        sage: nab = M.affine_connection('nabla', latex_name=r'\nabla')
        sage: eX = X.frame(); eX
        Coordinate frame (M, (∂/∂x,∂/∂y))
        sage: nab.set_coef(eX)
        3-indices components w.r.t. Coordinate frame (M, (∂/∂x,∂/∂y))
        sage: nab.set_coef(eX)[1,2,1] = x*y
        sage: nab.display(eX)
        Gam^x_yx = x*y

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(2), 'M', start_index=Integer(1))
        >>> X = M.chart(names=('x', 'y',)); (x, y,) = X._first_ngens(2)
        >>> nab = M.affine_connection('nabla', latex_name=r'\nabla')
        >>> eX = X.frame(); eX
        Coordinate frame (M, (∂/∂x,∂/∂y))
        >>> nab.set_coef(eX)
        3-indices components w.r.t. Coordinate frame (M, (∂/∂x,∂/∂y))
        >>> nab.set_coef(eX)[Integer(1),Integer(2),Integer(1)] = x*y
        >>> nab.display(eX)
        Gam^x_yx = x*y

    Since `eX` is the manifold’s default vector frame, its mention may be omitted:

    Sage

        sage: nab.set_coef()[1,2,1] = x*y
        sage: nab.set_coef()
        3-indices components w.r.t. Coordinate frame (M, (∂/∂x,∂/∂y))
        sage: nab.set_coef()[1,2,1] = x*y
        sage: nab.display()
        Gam^x_yx = x*y

    [Python]

        >>> from sage.all import *
        >>> nab.set_coef()[Integer(1),Integer(2),Integer(1)] = x*y
        >>> nab.set_coef()
        3-indices components w.r.t. Coordinate frame (M, (∂/∂x,∂/∂y))
        >>> nab.set_coef()[Integer(1),Integer(2),Integer(1)] = x*y
        >>> nab.display()
        Gam^x_yx = x*y

    To set the coefficients in the default frame, one can even bypass the method `set_coef()` and call directly the operator `[]` on the connection object:

    Sage

        sage: nab[1,2,1] = x*y
        sage: nab.display()
            Gam^x_yx = x*y

    Python

        >>> from sage.all import *
        >>> nab[Integer(1),Integer(2),Integer(1)] = x*y
        >>> nab.display()
            Gam^x_yx = x*y

    Setting the connection coefficients w.r.t. to another vector frame:

    Sage

        sage: e = M.vector_frame('e')
        sage: nab.set_coef(e)
        3-indices components w.r.t. Vector frame (M, (e_1,e_2))
        sage: nab.set_coef(e)[2,1,1] = x+y
        sage: nab.set_coef(e)[2,1,2] = x-y
        sage: nab.display(e)
        Gam^2_11 = x + y
        Gam^2_12 = x - y

    [Python]

        >>> from sage.all import *
        >>> e = M.vector_frame('e')
        >>> nab.set_coef(e)
        3-indices components w.r.t. Vector frame (M, (e_1,e_2))
        >>> nab.set_coef(e)[Integer(2),Integer(1),Integer(1)] = x+y
        >>> nab.set_coef(e)[Integer(2),Integer(1),Integer(2)] = x-y
        >>> nab.display(e)
        Gam^2_11 = x + y
        Gam^2_12 = x - y

    The coefficients w.r.t. the frame `eX` have been deleted:

    Sage

        sage: nab.display(eX)
        Traceback (most recent call last):
        ...
        ValueError: no common frame found for the computation

    Python

        >>> from sage.all import *
        >>> nab.display(eX)
        Traceback (most recent call last):
        ...
        ValueError: no common frame found for the computation

    To keep them, use the method `add_coef()` instead.

  set_immutable()

    Set `self` and all restrictions of `self` immutable.

    EXAMPLES:

    An affine connection can be set immutable:

    Sage

        sage: M = Manifold(2, 'M', start_index=1)
        sage: X.<x,y> = M.chart()
        sage: U = M.open_subset('U', coord_def={X: x^2+y^2<1})
        sage: nab = M.affine_connection('nabla', latex_name=r'\nabla')
        sage: eX = X.frame()
        sage: nab.set_coef(eX)[1,2,1] = x*y
        sage: nab.is_immutable()
        False
        sage: nab.set_immutable()
        sage: nab.is_immutable()
        True

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(2), 'M', start_index=Integer(1))
        >>> X = M.chart(names=('x', 'y',)); (x, y,) = X._first_ngens(2)
        >>> U = M.open_subset('U', coord_def={X: x**Integer(2)+y**Integer(2)<Integer(1)})
        >>> nab = M.affine_connection('nabla', latex_name=r'\nabla')
        >>> eX = X.frame()
        >>> nab.set_coef(eX)[Integer(1),Integer(2),Integer(1)] = x*y
        >>> nab.is_immutable()
        False
        >>> nab.set_immutable()
        >>> nab.is_immutable()
        True

    The coefficients of immutable elements cannot be changed:

    Sage

        sage: nab.add_coef(eX)[2,1,1] = x+y
        Traceback (most recent call last):
        ...
        ValueError: the coefficients of an immutable element cannot
         be changed

    [Python]

        >>> from sage.all import *
        >>> nab.add_coef(eX)[Integer(2),Integer(1),Integer(1)] = x+y
        Traceback (most recent call last):
        ...
        ValueError: the coefficients of an immutable element cannot
         be changed

    The restriction are set immutable as well:

    Sage

        sage: nabU = nab.restrict(U)
        sage: nabU.is_immutable()
        True

    Python

        >>> from sage.all import *
        >>> nabU = nab.restrict(U)
        >>> nabU.is_immutable()
        True

  torsion()

    Return the connection’s torsion tensor.

    The torsion tensor is the tensor field \(T\) of type (1,2) defined by

    \[T(\omega, u, v) = \left\langle \omega, \nabla_u v - \nabla_v u - [u, v] \right\rangle\]

    for any 1-form \(\omega\) and any vector fields \(u\) and \(v\).

    OUTPUT:

    - the torsion tensor \(T\), as an instance of `TensorField`

    EXAMPLES:

    Torsion of an affine connection on a 3-dimensional manifold:

    Sage

        sage: M = Manifold(3, 'M', start_index=1)
        sage: c_xyz.<x,y,z> = M.chart()
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab[1,1,2], nab[3,2,3] = x^2, y*z  # Gamma^1_{12} = x^2, Gamma^3_{23} = yz
        sage: t = nab.torsion() ; t
        Tensor field of type (1,2) on the 3-dimensional differentiable
         manifold M
        sage: t.symmetries()
        no symmetry;  antisymmetry: (1, 2)
        sage: t[:]
        [[[0, -x^2, 0], [x^2, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, -y*z], [0, y*z, 0]]]

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(3), 'M', start_index=Integer(1))
        >>> c_xyz = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_xyz._first_ngens(3)
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab[Integer(1),Integer(1),Integer(2)], nab[Integer(3),Integer(2),Integer(3)] = x**Integer(2), y*z  # Gamma^1_{12} = x^2, Gamma^3_{23} = yz
        >>> t = nab.torsion() ; t
        Tensor field of type (1,2) on the 3-dimensional differentiable
         manifold M
        >>> t.symmetries()
        no symmetry;  antisymmetry: (1, 2)
        >>> t[:]
        [[[0, -x^2, 0], [x^2, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        [[0, 0, 0], [0, 0, -y*z], [0, y*z, 0]]]

    The torsion expresses the lack of commutativity of two successive derivatives of a scalar field:

    Sage

        sage: f = M.scalar_field(x*z^2 + y^2 - z^2, name='f')
        sage: DDf = nab(nab(f)) ; DDf
        Tensor field nabla(df) of type (0,2) on the 3-dimensional
         differentiable manifold M
        sage: DDf.antisymmetrize()[:]  # two successive derivatives do not commute:
        [             0   -1/2*x^2*z^2              0]
        [   1/2*x^2*z^2              0 -(x - 1)*y*z^2]
        [             0  (x - 1)*y*z^2              0]
        sage: 2*DDf.antisymmetrize() == nab.torsion().contract(0,nab(f))
        True

    [Python]

        >>> from sage.all import *
        >>> f = M.scalar_field(x*z**Integer(2) + y**Integer(2) - z**Integer(2), name='f')
        >>> DDf = nab(nab(f)) ; DDf
        Tensor field nabla(df) of type (0,2) on the 3-dimensional
         differentiable manifold M
        >>> DDf.antisymmetrize()[:]  # two successive derivatives do not commute:
        [             0   -1/2*x^2*z^2              0]
        [   1/2*x^2*z^2              0 -(x - 1)*y*z^2]
        [             0  (x - 1)*y*z^2              0]
        >>> Integer(2)*DDf.antisymmetrize() == nab.torsion().contract(Integer(0),nab(f))
        True

    The above identity is the standard formula

    \[\nabla_j \nabla_i \, f - \nabla_i \nabla_j \, f = T^k_{\ \, ij} \nabla_k \, f ,\]

    where the \(T^k_{\ \, ij}\)’s are the components of the torsion tensor.

    The result is cached:

    Sage

        sage: nab.torsion() is t
        True

    Python

        >>> from sage.all import *
        >>> nab.torsion() is t
        True

    as long as the connection remains unchanged:

    Sage

        sage: nab[2,1,3] = 1+x    # changing the connection
        sage: nab.torsion() is t  # a new computation of the torsion has been made
        False
        sage: (nab.torsion() - t).display()
        (-x - 1) ∂/∂y⊗dx⊗dz + (x + 1) ∂/∂y⊗dz⊗dx

    [Python]

        >>> from sage.all import *
        >>> nab[Integer(2),Integer(1),Integer(3)] = Integer(1)+x    # changing the connection
        >>> nab.torsion() is t  # a new computation of the torsion has been made
        False
        >>> (nab.torsion() - t).display()
        (-x - 1) ∂/∂y⊗dx⊗dz + (x + 1) ∂/∂y⊗dz⊗dx

    Another example: torsion of some connection on a non-parallelizable 2-dimensional manifold:

    Sage

        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: c_xyW = c_xy.restrict(W) ; c_uvW = c_uv.restrict(W)
        sage: eUW = c_xyW.frame() ; eVW = c_uvW.frame()
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab[0,0,0], nab[0,1,0], nab[1,0,1] = x, x-y, x*y
        sage: for i in M.irange():
        ....:     for j in M.irange():
        ....:         for k in M.irange():
        ....:             nab.add_coef(eV)[i,j,k] = nab.coef(eVW)[i,j,k,c_uvW].expr()
        sage: t = nab.torsion() ; t
        Tensor field of type (1,2) on the 2-dimensional differentiable
         manifold M
        sage: t.parent()
        Module T^(1,2)(M) of type-(1,2) tensors fields on the 2-dimensional
         differentiable manifold M
        sage: t[eU,:]
        [[[0, x - y], [-x + y, 0]], [[0, -x*y], [x*y, 0]]]
        sage: t[eV,:]
        [[[0, 1/8*u^2 - 1/8*v^2 - 1/2*v], [-1/8*u^2 + 1/8*v^2 + 1/2*v, 0]],
         [[0, -1/8*u^2 + 1/8*v^2 - 1/2*v], [1/8*u^2 - 1/8*v^2 + 1/2*v, 0]]]

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(2), 'M')
        >>> U = M.open_subset('U') ; V = M.open_subset('V')
        >>> M.declare_union(U,V)   # M is the union of U and V
        >>> c_xy = U.chart(names=('x', 'y',)); (x, y,) = c_xy._first_ngens(2); c_uv = V.chart(names=('u', 'v',)); (u, v,) = c_uv._first_ngens(2)
        >>> transf = c_xy.transition_map(c_uv, (x+y, x-y), intersection_name='W',
        ...                              restrictions1= x>Integer(0), restrictions2= u+v>Integer(0))
        >>> inv = transf.inverse()
        >>> W = U.intersection(V)
        >>> eU = c_xy.frame() ; eV = c_uv.frame()
        >>> c_xyW = c_xy.restrict(W) ; c_uvW = c_uv.restrict(W)
        >>> eUW = c_xyW.frame() ; eVW = c_uvW.frame()
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab[Integer(0),Integer(0),Integer(0)], nab[Integer(0),Integer(1),Integer(0)], nab[Integer(1),Integer(0),Integer(1)] = x, x-y, x*y
        >>> for i in M.irange():
        ...     for j in M.irange():
        ...         for k in M.irange():
        ...             nab.add_coef(eV)[i,j,k] = nab.coef(eVW)[i,j,k,c_uvW].expr()
        >>> t = nab.torsion() ; t
        Tensor field of type (1,2) on the 2-dimensional differentiable
         manifold M
        >>> t.parent()
        Module T^(1,2)(M) of type-(1,2) tensors fields on the 2-dimensional
         differentiable manifold M
        >>> t[eU,:]
        [[[0, x - y], [-x + y, 0]], [[0, -x*y], [x*y, 0]]]
        >>> t[eV,:]
        [[[0, 1/8*u^2 - 1/8*v^2 - 1/2*v], [-1/8*u^2 + 1/8*v^2 + 1/2*v, 0]],
         [[0, -1/8*u^2 + 1/8*v^2 - 1/2*v], [1/8*u^2 - 1/8*v^2 + 1/2*v, 0]]]

    Check of the torsion formula:

    Sage

        sage: f = M.scalar_field({c_xy: (x+y)^2, c_uv: u^2}, name='f')
        sage: DDf = nab(nab(f)) ; DDf
        Tensor field nabla(df) of type (0,2) on the 2-dimensional
         differentiable manifold M
        sage: DDf.antisymmetrize().display(eU)
        (-x^2*y - (x + 1)*y^2 + x^2) dx∧dy
        sage: DDf.antisymmetrize().display(eV)
        (1/8*u^3 - 1/8*u*v^2 - 1/2*u*v) du∧dv
        sage: 2*DDf.antisymmetrize() == nab(f).contract(nab.torsion())
        True

    [Python]

        >>> from sage.all import *
        >>> f = M.scalar_field({c_xy: (x+y)**Integer(2), c_uv: u**Integer(2)}, name='f')
        >>> DDf = nab(nab(f)) ; DDf
        Tensor field nabla(df) of type (0,2) on the 2-dimensional
         differentiable manifold M
        >>> DDf.antisymmetrize().display(eU)
        (-x^2*y - (x + 1)*y^2 + x^2) dx∧dy
        >>> DDf.antisymmetrize().display(eV)
        (1/8*u^3 - 1/8*u*v^2 - 1/2*u*v) du∧dv
        >>> Integer(2)*DDf.antisymmetrize() == nab(f).contract(nab.torsion())
        True

  torsion_form(i, frame=None)

    Return the torsion 2-form corresponding to the given index and vector frame.

    The torsion 2-forms with respect to the frame \((e_i)\) are the \(n\) 2-forms \(\theta^i\) defined by

    \[\theta^i(u,v) = T(e^i, u, v)\]

    where \(T\) is the connection’s torsion tensor (cf. `torsion()`), \((e^i)\) is the coframe dual to \((e_i)\) and \((u,v)\) is a generic pair of vectors.

    INPUT:

    - `i` – index identifying the 2-form \(\theta^i\)

    - `frame` – (default: `None`) vector frame relative to which the torsion 2-forms are defined; if `None`, the default frame of the connection’s domain is assumed.

    OUTPUT:

    - the 2-form \(\theta^i\), as an instance of `DiffForm`

    EXAMPLES:

    Torsion 2-forms on a 3-dimensional manifold:

    Sage

        sage: M = Manifold(3, 'M', start_index=1)
        sage: c_xyz.<x,y,z> = M.chart()
        sage: nab = M.affine_connection('nabla', r'\nabla')
        sage: nab[1,1,1], nab[1,1,2], nab[1,1,3] = x*y*z, x^2, -y*z
        sage: nab[1,2,3], nab[1,3,1], nab[1,3,2] = -x^3, y^2*z, y^2-x^2
        sage: nab[2,1,1], nab[2,1,2], nab[2,2,1] = z^2, x*y*z^2, -x^2
        sage: nab[2,3,1], nab[2,3,3], nab[3,1,2] = x^2+y^2+z^2, y^2-z^2, x*y+z^2
        sage: nab[3,2,1], nab[3,2,2], nab[3,3,3] = x*y+z, z^3 -y^2, x*z^2 - z*y^2
        sage: nab.torsion_form(1)
        2-form torsion (1) of connection nabla w.r.t. Coordinate frame
         (M, (∂/∂x,∂/∂y,∂/∂z)) on the 3-dimensional differentiable manifold M
        sage: nab.torsion_form(1)[:]
        [               0             -x^2      (y^2 + y)*z]
        [             x^2                0  x^3 - x^2 + y^2]
        [    -(y^2 + y)*z -x^3 + x^2 - y^2                0]

    Python

        >>> from sage.all import *
        >>> M = Manifold(Integer(3), 'M', start_index=Integer(1))
        >>> c_xyz = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_xyz._first_ngens(3)
        >>> nab = M.affine_connection('nabla', r'\nabla')
        >>> nab[Integer(1),Integer(1),Integer(1)], nab[Integer(1),Integer(1),Integer(2)], nab[Integer(1),Integer(1),Integer(3)] = x*y*z, x**Integer(2), -y*z
        >>> nab[Integer(1),Integer(2),Integer(3)], nab[Integer(1),Integer(3),Integer(1)], nab[Integer(1),Integer(3),Integer(2)] = -x**Integer(3), y**Integer(2)*z, y**Integer(2)-x**Integer(2)
        >>> nab[Integer(2),Integer(1),Integer(1)], nab[Integer(2),Integer(1),Integer(2)], nab[Integer(2),Integer(2),Integer(1)] = z**Integer(2), x*y*z**Integer(2), -x**Integer(2)
        >>> nab[Integer(2),Integer(3),Integer(1)], nab[Integer(2),Integer(3),Integer(3)], nab[Integer(3),Integer(1),Integer(2)] = x**Integer(2)+y**Integer(2)+z**Integer(2), y**Integer(2)-z**Integer(2), x*y+z**Integer(2)
        >>> nab[Integer(3),Integer(2),Integer(1)], nab[Integer(3),Integer(2),Integer(2)], nab[Integer(3),Integer(3),Integer(3)] = x*y+z, z**Integer(3) -y**Integer(2), x*z**Integer(2) - z*y**Integer(2)
        >>> nab.torsion_form(Integer(1))
        2-form torsion (1) of connection nabla w.r.t. Coordinate frame
         (M, (∂/∂x,∂/∂y,∂/∂z)) on the 3-dimensional differentiable manifold M
        >>> nab.torsion_form(Integer(1))[:]
        [               0             -x^2      (y^2 + y)*z]
        [             x^2                0  x^3 - x^2 + y^2]
        [    -(y^2 + y)*z -x^3 + x^2 - y^2                0]

    Torsion 2-forms w.r.t. a non-holonomic frame:

    Sage

        sage: ch_basis = M.automorphism_field()
        sage: ch_basis[1,1], ch_basis[2,2], ch_basis[3,3] = y, z, x
        sage: e = M.default_frame().new_frame(ch_basis, 'e')
        sage: e[1][:], e[2][:], e[3][:]
        ([y, 0, 0], [0, z, 0], [0, 0, x])
        sage: ef = e.coframe()
        sage: ef[1][:], ef[2][:], ef[3][:]
        ([1/y, 0, 0], [0, 1/z, 0], [0, 0, 1/x])
        sage: nab.torsion_form(1, e)  # long time
        2-form torsion (1) of connection nabla w.r.t. Vector frame
         (M, (e_1,e_2,e_3)) on the 3-dimensional differentiable manifold M
        sage: nab.torsion_form(1, e).comp(e)[:]  # long time
        [                       0                   -x^2*z          (x*y^2 + x*y)*z]
        [                   x^2*z                        0  (x^4 - x^3 + x*y^2)*z/y]
        [        -(x*y^2 + x*y)*z -(x^4 - x^3 + x*y^2)*z/y                        0]

    [Python]

        >>> from sage.all import *
        >>> ch_basis = M.automorphism_field()
        >>> ch_basis[Integer(1),Integer(1)], ch_basis[Integer(2),Integer(2)], ch_basis[Integer(3),Integer(3)] = y, z, x
        >>> e = M.default_frame().new_frame(ch_basis, 'e')
        >>> e[Integer(1)][:], e[Integer(2)][:], e[Integer(3)][:]
        ([y, 0, 0], [0, z, 0], [0, 0, x])
        >>> ef = e.coframe()
        >>> ef[Integer(1)][:], ef[Integer(2)][:], ef[Integer(3)][:]
        ([1/y, 0, 0], [0, 1/z, 0], [0, 0, 1/x])
        >>> nab.torsion_form(Integer(1), e)  # long time
        2-form torsion (1) of connection nabla w.r.t. Vector frame
         (M, (e_1,e_2,e_3)) on the 3-dimensional differentiable manifold M
        >>> nab.torsion_form(Integer(1), e).comp(e)[:]  # long time
        [                       0                   -x^2*z          (x*y^2 + x*y)*z]
        [                   x^2*z                        0  (x^4 - x^3 + x*y^2)*z/y]
        [        -(x*y^2 + x*y)*z -(x^4 - x^3 + x*y^2)*z/y                        0]

    Cartan’s first structure equation is

    \[\theta^i = \mathrm{d} e^i + \omega^i_{\ \, j} \wedge e^j\]

    where the \(\omega^i_{\ \, j}\)’s are the connection 1-forms (cf. `connection_form()`). Let us check it on the frame e:

    Sage

        sage: for i in M.irange():  # long time
        ....:     nab.torsion_form(i, e) == ef[i].exterior_derivative() + \
        ....:      sum(nab.connection_form(i,j,e).wedge(ef[j]) for j in M.irange())
        True
        True
        True

    Python

        >>> from sage.all import *
        >>> for i in M.irange():  # long time
        ...     nab.torsion_form(i, e) == ef[i].exterior_derivative() +      sum(nab.connection_form(i,j,e).wedge(ef[j]) for j in M.irange())
        True
        True
        True