This Jupyter notebook illustrates SageMath functionalities regarding smooth manifolds equipped with a degenerate metric. The involved tools have been developed through the SageManifolds project.
Author: Hans Fotsing Tetsing
A version of SageMath at least equal to 9.1 is required to run this notebook
version()
%display latex
M = Manifold(3, 'M', structure='degenerate_metric')
print(M)
We call $g$ the metric on $M$
g = M.metric()
print(g)
g.parent()
det(g).display()
The determinant of any degenerate matric is the zero scalar function. By default (when the components are not yet set) the signature is the triplet (n_-,n_+,n_0)
where n_-=0
is the number of negative entries, n_+=dim(M)
the number of positive entries and n_0=1
is the number number of zero in the digonal, all that in the writting of the metric in an orthogonal basis.
g.signature()
U = M.open_subset('U')
print(U)
X.<x,y,z> = U.chart(); X
One can use a field of symmetric bilinear forms to set the components of a degenerate metric.
dx, dy = X.coframe()[0], X.coframe()[1]
b = dx*dx + x^2*dy*dy
b = b.symmetrize()
print(b)
g.set(b)
g.display()
g[:]
We define another metric and set its nonzero components.
h = g._new_instance()
h.set_name('h')
print(h)
h[0,0], h[1,1] = 1, x^2
h.display()
On the only local chart (U,X), h
and g
have the same components, so there are equal! However, g is the metric of the degenerate metric manifold M
but not h
.
h == g
g is M.metric()
h is M.metric()
6-
dimensional Lorentzian manifold¶M = Manifold(6, 'M', structure='Lorentzian')
print(M)
X.<x0,x1,x2,x3,x4,x5> = M.chart(); X
g = M.metric()
print(g)
g[0,0], g[1,1], g[2,2], g[3,3], g[4,4], g[5,5] = -1, 1, exp(2*x0), exp(2*x0), exp(2*x1), exp(2*x1)
g[:]
We define now on M
the degenerate hypersurface x0+x1=0
.
H = Manifold(5, 'H', ambient=M, structure='degenerate_metric')
print(H)
X_H.<xh1, xh2, xh3, xh4, xh5> = H.chart(); X_H
Phi = H.diff_map(M, {(X_H, X): [-xh1, xh1, xh2, xh3, xh4, xh5]},
name='Phi', latex_name=r'\Phi')
Phi.display()
Phi_inv = M.diff_map(H, {(X, X_H): [x1, x2, x3, x4, x5]},
name='Phi_inv', latex_name=r'\Phi^{-1}')
Phi_inv.display()
H.set_immersion(Phi, inverse=Phi_inv)
H.declare_embedding()
The vector field $v=-\partial x0-\partial x1$ is everywhere neither orthogonal nor tangent to $H$. Hence, $v$ is a rigging for the null hypersurface $H$.
v = M.vector_field()
v[:2] = [-1, -1]
v.display()
H.set_transverse(rigging=[v])
The vector $\xi=\partial x0-\partial x1$ is tangent and orthogonal to $H$ and then spans the normal distribution $TH^\perp$. A complementary of $TH^\perp$ in $TH$, thus a screen distribution, is spanned by the vectors $e1=\exp(-2x0)\partial x2$, $e2=\exp(-2x0)\partial x3$, $e3=\exp(-2x1)\partial x4$, and $e4=\exp(-2x1)\partial x5$. Notice that the screen distribution associated with $v$ has to be othogonal to the chosen rigging $v$.
xi = M.vector_field()
e1 = M.vector_field()
e2 = M.vector_field()
e3 = M.vector_field()
e4 = M.vector_field()
xi[:2] = [1, -1]
e1[2] = exp(-2*x0)
e2[3] = exp(-2*x0)
e3[4] = exp(-2*x1)
e4[5] = exp(-2*x1)
S = H.screen('S', [e1, e2, e3, e4], [xi])
print(S)
H.default_screen()
The null rigging is derived from the given rigging $v$ with the formula$$N=\frac{1}{g(\xi, v)}\left(v-\frac{g(v,v)}{2g(\xi, v)}\xi\right)$$ Projections onto $H$ will be done parallely to the null rigging $N$.
S.rigging().display()
S.normal_tangent_vector().display()
The null rigging $N$ and the normal tangent vector $\xi$ are normalized by$$g(N,\xi)=1.$$ This normalization is the one used by some mathematicians but is not physically relevant since $N$ and $\xi$ cannot be timelike at the same time. However, one can consider $\ell=\xi$ and $k=-N$.
g.along(Phi)(S.rigging(),xi.along(Phi)).display()
Now, $S$ is a screen distribution along $H$ and we have the decompositions \begin{equation}TM|_H=TH\oplus Span(N)\end{equation} \begin{equation}TH=S\oplus_{orth}Span(\xi)\end{equation} Projection on $H$ is done with respect to the first decomposition and projection onto the screen distribution of a vector tangent to $H$ is done with respect to the second decomposition. So, any vector along $H$ (tangent or not to $H$), can be decomposed in the adapted frame $T=\{vv_0,vv_1,vv_2,vv_3,vv_4,vv_5)$, where along $H$ $$vv_0:=e1,~~vv_1=e2,~~vv_2=e3,~~vv_3=e4,~~vv_4=\xi,~~vv_5=N.$$
T = H.adapted_frame()
T
T[4].display()
T[0].disp()
Projections on $H$ and on the screen distribution are now possible!
H.projection(v).display()
H.projection(v+xi).display(T)
H.screen_projection(v+xi).display()
H.screen_projection(e1+xi).display(T)
Let $\nabla$ be the Levi-Civita connection of $(M,g)$. The Weingarten map is the projection is $\nabla_\cdot\xi:\mathfrak X(H)\to\mathfrak X(H)$.
W = H.weingarten_map()
print(W)
W.display()
W.display(T)
W(xi.along(Phi)).display()
W(T[0]).display(T)
We can write$$\nabla_U\xi=-A^\ast(U)-\tau(U)\xi,~~~~\forall U\in\mathfrak X(H).$$ $A^\ast$ is the shape operator and $\tau$ the roration $1-$form.
SO = H.shape_operator()
print(SO)
SO.display(T)
The principal curvatures are the eigenfunctions of the shape operator.
m = H.mean_curvature()
m.display()
So, the mean curvature of $H$ vanishes identically. Some people then say that $H$ is a maximal null hypersurface. But, $H$ is not totally geodesic since the shape operator is not zero. Hence, the ambient manifold $(M,g)$ cannot have constant sectional curvature.
In fact, the principal curvatures are $-1,-1,0,1,1$ and we can write the following short code to get them.
curvatures = matrix([[SO[:][i, j].expr() for i in H.irange()]
for j in H.irange()]).eigenvalues()
counter = H.irange()
for i in range(5):
curvatures[i] = H.scalar_field(curvatures[i], name="k_{}".format(next(counter)))
curvatures[0].display()
The principal directions are $vv_0,vv_1,vv_2,vv_3=\xi$.
PD = H.principal_directions(S)
PD[0][0].disp(T), PD[1][0].disp(T), PD[2][0].disp(T), PD[3][0].disp(T)
Since the screen distribution has to be orthogonal to the rigging, the screen distribution is unique ones the rigging has been set. One can just change the basis but the result is the same subbundle.
u1 = M.vector_field()
u2 = M.vector_field()
u3 = M.vector_field()
u4 = M.vector_field()
u1[2] = 1; u2[3] = 1; u3[4] = 1; u4[5] = 1
S1 = H.screen('S1', [u1, u2, u3, u4], [xi])
print(S1)
The default screen distribution is always the last defined one.
H.default_screen()
T1 = H.adapted_frame(screen=S1)
T1
T1[4].display()
T1[0].display()
SO1 = H.shape_operator(S1)
print(SO1)
SO1.display(T1)
SO == SO1
Let's now define a new rigging and from that a different screen distribution.
v2 = M.vector_field()
v2[1:3] = [1,1]
v2.display()
H.set_transverse(rigging=[v2])
e21 = M.vector_field()
e22 = M.vector_field()
e23 = M.vector_field()
e24 = M.vector_field()
e21[0:3] = [1,-1,exp(-2*x0)]
e22[3] = 1
e23[4] = 1
e24[5] = 1
S2 = H.screen('S2', [e21, e22, e23, e24], [xi])
print(S2)
T2 = H.adapted_frame(screen=S2)
T2
SO2 = H.shape_operator(S2)
SO2.display(T2)
SO == SO2
This is because the shape operator depends on the chosen rigging.
H.list_of_screens()
W2 = H.weingarten_map(S2)
W2.display(T2)
W2(xi.along(Phi)).display()
The Weingarten map can only be applied to vectors tangent to $H$. However, we can consider its extension which can evaluate any vector along $H$ (tangent to $H$ or not).
W2.extension()(v2.along(Phi)).display(T2)
W.extension() == W2.extension()