Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a few operations from linear symplectic geometry #35354

Merged
merged 17 commits into from
May 22, 2023
Merged
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/sage/manifolds/differentiable/diff_form.py
Original file line number Diff line number Diff line change
@@ -619,6 +619,7 @@ def hodge_dual(
nondegenerate_tensor: Union[
PseudoRiemannianMetric, SymplecticForm, None
] = None,
minus_eigenvalues_convention: bool = False,
) -> DiffForm:
r"""
Compute the Hodge dual of the differential form with respect to some non-degenerate
@@ -630,13 +631,18 @@ def hodge_dual(

.. MATH::

*A_{i_1\ldots i_{n-p}} = \frac{1}{p!} A_{k_1\ldots k_p}
\epsilon^{k_1\ldots k_p}_{\qquad\ i_1\ldots i_{n-p}}
*A_{i_1\ldots i_{n-p}} = \frac{1}{p!} A^{k_1\ldots k_p}
\epsilon_{k_1\ldots k_p\, i_1\ldots i_{n-p}}

where `n` is the manifold's dimension, `\epsilon` is the volume
`n`-form associated with `g` (see
:meth:`~sage.manifolds.differentiable.metric.PseudoRiemannianMetric.volume_form`)
and the indices `k_1,\ldots, k_p` are raised with `g`.
If `g` is a pseudo-Riemannian metric, sometimes an additional multiplicative
factor of `(-1)^s` is introduced on the right-hand side,
where `s` is the number of negative eigenvalues of `g`.
This convention can be enforced by setting the option
``minus_eigenvalues_convention``.

INPUT:

@@ -646,6 +652,9 @@ def hodge_dual(
:class:`~sage.manifolds.differentiable.symplectic_form.SymplecticForm`.
If none is provided, the ambient domain of ``self`` is supposed to be endowed
with a default metric and this metric is then used.
- ``minus_eigenvalues_convention`` -- if `true`, a factor of `(-1)^s` is
introduced with `s` being the number of negative eigenvalues of the
``nondegenerate_tensor``.

OUTPUT:

@@ -772,14 +781,19 @@ def hodge_dual(
nondegenerate_tensor = self._vmodule._ambient_domain.metric()

p = self.tensor_type()[1]
eps = nondegenerate_tensor.volume_form(p)
eps = nondegenerate_tensor.volume_form()
if p == 0:
common_domain = nondegenerate_tensor.domain().intersection(self.domain())
result = self.restrict(common_domain) * eps.restrict(common_domain)
else:
result = self.contract(*range(p), eps, *range(p))
result = self.up(nondegenerate_tensor).contract(*range(p), eps, *range(p))
if p > 1:
result = result / factorial(p)
if minus_eigenvalues_convention:
from sage.manifolds.differentiable.metric import PseudoRiemannianMetric
if isinstance(nondegenerate_tensor, PseudoRiemannianMetric):
result = result * nondegenerate_tensor._indic_signat

result.set_name(
name=format_unop_txt("*", self._name),
latex_name=format_unop_latex(r"\star ", self._latex_name),
5 changes: 3 additions & 2 deletions src/sage/manifolds/differentiable/manifold.py
Original file line number Diff line number Diff line change
@@ -454,6 +454,7 @@

if TYPE_CHECKING:
from sage.manifolds.differentiable.diff_map import DiffMap
from sage.manifolds.differentiable.diff_form import DiffForm
from sage.manifolds.differentiable.metric import PseudoRiemannianMetric
from sage.manifolds.differentiable.vectorfield_module import (
VectorFieldFreeModule,
@@ -2177,7 +2178,7 @@ def multivector_field(self, *args, **kwargs):
resu._init_components(args[1], **kwargs)
return resu

def diff_form(self, *args, **kwargs):
def diff_form(self, *args, **kwargs) -> DiffForm:
r"""
Define a differential form on ``self``.

@@ -2281,7 +2282,7 @@ def diff_form(self, *args, **kwargs):
resu._init_components(args[1], **kwargs)
return resu

def one_form(self, *comp, **kwargs):
def one_form(self, *comp, **kwargs) -> DiffForm:
r"""
Define a 1-form on the manifold.

52 changes: 50 additions & 2 deletions src/sage/manifolds/differentiable/symplectic_form.py
Original file line number Diff line number Diff line change
@@ -575,10 +575,10 @@ def hodge_star(self, pform: DiffForm) -> DiffForm:
sage: omega = M.symplectic_form()
sage: a = M.one_form(1, 0, name='a')
sage: omega.hodge_star(a).display()
*a = -dq
*a = dq
sage: b = M.one_form(0, 1, name='b')
sage: omega.hodge_star(b).display()
*b = -dp
*b = dp
sage: f = M.scalar_field(1, name='f')
sage: omega.hodge_star(f).display()
*f = -dq∧dp
@@ -588,6 +588,54 @@ def hodge_star(self, pform: DiffForm) -> DiffForm:
"""
return pform.hodge_dual(self)

def on_forms(self, first: DiffForm, second: DiffForm) -> DiffScalarField:
r"""
Return the contraction of the two forms with respect to the symplectic form.

The symplectic form `\omega` gives rise to a bilinear form,
also denoted by `\omega` on the space of `1`-forms by

.. MATH::
\omega(\alpha, \beta) = \omega(\alpha^\sharp, \beta^\sharp),

where `\alpha^\sharp` is the dual of `\alpha` with respect to `\omega`, see
:meth:`~sage.manifolds.differentiable.tensor_field.TensorField.up`.
This bilinear form induces a bilinear form on the space of all forms determined
by its value on decomposable elements as:

.. MATH::
\omega(\alpha_1 \wedge \ldots \wedge\alpha_p, \beta_1 \wedge \ldots \wedge\beta_p)
= det(\omega(\alpha_i, \beta_j)).

INPUT:

- ``first`` -- a `p`-form `\alpha`
- ``second`` -- a `p`-form `\beta`

OUTPUT:

- the scalar field `\omega(\alpha, \beta)`

EXAMPLES:

sage: M = manifolds.StandardSymplecticSpace(2)
sage: omega = M.symplectic_form()
sage: a = M.one_form(1, 0, name='a')
sage: b = M.one_form(0, 1, name='b')
sage: omega.on_forms(a, b).display()
R2 → ℝ
(q, p) ↦ -1
"""
from sage.arith.misc import factorial

if first.degree() != second.degree():
raise ValueError("the two forms must have the same degree")

all_positions = range(first.degree())
return first.contract(
*all_positions, second.up(self), *all_positions
) / factorial(first.degree())


class SymplecticFormParal(SymplecticForm, DiffFormParal):
r"""
62 changes: 60 additions & 2 deletions src/sage/manifolds/differentiable/symplectic_form_test.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# pylint: disable=missing-function-docstring
from _pytest.fixtures import FixtureRequest
import pytest

# TODO: Remove sage.all import as soon as it's no longer necessary to load everything upfront
import sage.all
from sage.manifolds.manifold import Manifold
from sage.manifolds.differentiable.manifold import DifferentiableManifold
from sage.manifolds.differentiable.examples.sphere import Sphere
@@ -125,6 +124,39 @@ def test_poisson_bracket_as_commutator_hamiltonian_vector_fields(
omega.hamiltonian_vector_field(g)
) == omega.hamiltonian_vector_field(omega.poisson_bracket(f, g))

def test_hodge_star_of_one_is_volume(
self, M: DifferentiableManifold, omega: SymplecticForm
):
assert M.one_scalar_field().hodge_dual(omega) == omega.volume_form()

def test_hodge_star_of_volume_is_one(
self, M: DifferentiableManifold, omega: SymplecticForm
):
assert omega.volume_form().hodge_dual(omega) == M.one_scalar_field()

def test_trace_of_two_form_is_given_using_contraction_with_omega(
self, M: DifferentiableManifold, omega: SymplecticForm
):
a = M.diff_form(2)
a[1,2] = 3
assert a.trace(using=omega) == a.up(omega, 1).trace()

def test_omega_on_forms_is_determinant_for_decomposables(
self, M: DifferentiableManifold, omega: SymplecticForm
):
a = M.one_form(1,2)
b = M.one_form(3,4)
c = M.one_form(5,6)
d = M.one_form(7,8)

assert omega.on_forms(a.wedge(b), c.wedge(d)) == omega.on_forms(a,c) * omega.on_forms(b, d) - omega.on_forms(a,d) * omega.on_forms(b,c)

def test_omega_on_one_forms_is_omega_on_dual_vectors(
self, M: DifferentiableManifold, omega: SymplecticForm
):
a = M.one_form(1,2)
b = M.one_form(3,4)
assert omega.on_forms(a, b) == omega(a.up(omega), b.up(omega))

def generic_scalar_field(M: DifferentiableManifold, name: str) -> DiffScalarField:
chart_functions = {chart: function(name)(*chart[:]) for chart in M.atlas()}
@@ -158,3 +190,29 @@ def test_flat(self, M: StandardSymplecticSpace, omega: SymplecticForm):
X = M.vector_field(1, 2, name="X")
assert str(X.display()) == r"X = e_q + 2 e_p"
assert str(omega.flat(X).display()) == r"X_flat = 2 dq - dp"

def test_hodge_star(self, M: StandardSymplecticSpace, omega: SymplecticForm):
# Standard basis
e = M.one_form(0,1, name='e')
f = M.one_form(1,0, name='f')
assert e.wedge(f) == omega

assert M.one_scalar_field().hodge_dual(omega) == omega
assert e.hodge_dual(omega) == e
assert f.hodge_dual(omega) == f
assert omega.hodge_dual(omega) == M.one_scalar_field()

def test_omega_on_one_forms(self, M: StandardSymplecticSpace, omega: SymplecticForm):
# Standard basis
e = M.one_form(0,1, name='e')
f = M.one_form(1,0, name='f')
assert e.wedge(f) == omega

assert omega.on_forms(e, f) == 1

def test_hodge_star_is_given_using_omega_on_forms(
self, M: StandardSymplecticSpace, omega: SymplecticForm
):
a = M.one_form(1,2)
b = M.one_form(3,4)
assert a.wedge(b.hodge_dual(omega)) == omega.on_forms(a, b) * omega.volume_form()
29 changes: 28 additions & 1 deletion src/sage/manifolds/differentiable/tensorfield.py
Original file line number Diff line number Diff line change
@@ -3031,17 +3031,28 @@ def __call__(self, *args):
resu._latex_name = res_latex
return resu

def trace(self, pos1=0, pos2=1):
def trace(
self,
pos1=0,
pos2=1,
using: Optional[
Union[PseudoRiemannianMetric, SymplecticForm, PoissonTensorField]
] = None,
):
r"""
Trace (contraction) on two slots of the tensor field.

If a non-degenerate form is provided, the trace of a `(0,2)` tensor field
is computed by first raising the last index.

INPUT:

- ``pos1`` -- (default: 0) position of the first index for the
contraction, with the convention ``pos1=0`` for the first slot
- ``pos2`` -- (default: 1) position of the second index for the
contraction, with the same convention as for ``pos1``. The variance
type of ``pos2`` must be opposite to that of ``pos1``
- ``using`` -- (default: ``None``) a non-degenerate form

OUTPUT:

@@ -3074,6 +3085,15 @@ def trace(self, pos1=0, pos2=1):
sage: s == a.trace(0,1) # explicit mention of the positions
True

The trace of a type-`(0,2)` tensor field using a metric::

sage: g = M.metric('g')
sage: g[0,0], g[0,1], g[1,1] = 1, 0, 1
sage: g.trace(using=g).display()
M → ℝ
on U: (x, y) ↦ 2
on W: (u, v) ↦ 2

Instead of the explicit call to the method :meth:`trace`, one
may use the index notation with Einstein convention (summation over
repeated indices); it suffices to pass the indices as a string inside
@@ -3140,6 +3160,13 @@ def trace(self, pos1=0, pos2=1):
True

"""
if using is not None:
if self.tensor_type() != (0, 2):
raise ValueError(
"trace with respect to a non-degenerate form is only defined for type-(0,2) tensor fields"
)
return self.up(using, 1).trace()

# The indices at pos1 and pos2 must be of different types:
k_con = self._tensor_type[0]
l_cov = self._tensor_type[1]
5 changes: 3 additions & 2 deletions src/sage/manifolds/differentiable/tensorfield_paral.py
Original file line number Diff line number Diff line change
@@ -1590,8 +1590,9 @@ def restrict(self, subdomain: DifferentiableManifold, dest_map: Optional[DiffMap
return self
if subdomain not in self._restrictions:
if not subdomain.is_subset(self._domain):
raise ValueError("the provided domain is not a subset of " +
"the field's domain")
raise ValueError(
f"the provided domain {subdomain} is not a subset of the field's domain {self._domain}"
)
if dest_map is None:
dest_map = self._fmodule._dest_map.restrict(subdomain)
elif not dest_map._codomain.is_subset(self._ambient_domain):
11 changes: 9 additions & 2 deletions src/sage/manifolds/differentiable/vectorfield_module.py
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Literal, Optional, overload

from sage.categories.modules import Modules
from sage.manifolds.differentiable.vectorfield import VectorField, VectorFieldParal
@@ -57,6 +57,8 @@
from sage.tensor.modules.reflexive_module import ReflexiveModule_base

if TYPE_CHECKING:
from sage.manifolds.differentiable.diff_form import DiffForm
from sage.manifolds.scalarfield import ScalarField
from sage.manifolds.differentiable.diff_map import DiffMap
from sage.manifolds.differentiable.manifold import DifferentiableManifold

@@ -950,8 +952,13 @@ def alternating_contravariant_tensor(self, degree, name=None,
latex_name=latex_name)
return self.exterior_power(degree).element_class(self, degree,
name=name, latex_name=latex_name)
@overload
def alternating_form(
self, degree: Literal[0], name=None, latex_name=None
) -> ScalarField:
pass

def alternating_form(self, degree, name=None, latex_name=None):
def alternating_form(self, degree: int, name=None, latex_name=None) -> DiffForm:
r"""
Construct an alternating form on the vector field module
``self``.
Loading