Skip to content

Commit

Permalink
gh-35354: Add a few operations from linear symplectic geometry
Browse files Browse the repository at this point in the history
    
### 📚 Description

- Add trace of a `(0,2)`-tensor with respect to the symplectic form (or
metric)
- Add extension of the symplectic form to a bilinear form on `p`-forms
- Fix hodge star for symplectic manifolds (was off by a minus sign for
odd-degree forms)
- Add option for hodge star to add an additional factor of `(-1)^s` with
`s` being the number of negative eigenvalues of the metric, that is,
  ```latex
  \alpha \wedge \star \beta = (-1)^s g(\alpha, \beta) vol_g
  ```


### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. It should be `[x]` not `[x
]`. -->

- [x] The title is concise, informative, and self-explanatory.
- [x] The description explains in detail what this PR is about.
- [ ] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation accordingly.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on
- #12345: short description why this is a dependency
- #34567: ...
-->

<!-- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
    
URL: #35354
Reported by: Tobias Diez
Reviewer(s): Eric Gourgoulhon, Matthias Köppe, Tobias Diez
  • Loading branch information
Release Manager committed May 21, 2023
2 parents fdf9668 + b439ea1 commit e154bea
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 16 deletions.
25 changes: 23 additions & 2 deletions src/sage/manifolds/differentiable/diff_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -772,6 +781,9 @@ def hodge_dual(
nondegenerate_tensor = self._vmodule._ambient_domain.metric()

p = self.tensor_type()[1]
# For performance reasons, we raise the indicies of the volume form
# and not of the differential form; in the symplectic case this is wrong by
# a factor of (-1)^p, which will be corrected below
eps = nondegenerate_tensor.volume_form(p)
if p == 0:
common_domain = nondegenerate_tensor.domain().intersection(self.domain())
Expand All @@ -780,6 +792,15 @@ def hodge_dual(
result = self.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
from sage.manifolds.differentiable.symplectic_form import SymplecticForm
if isinstance(nondegenerate_tensor, SymplecticForm):
# correction because we lifted the indicies of the volume (see above)
result = result * (-1)**p

result.set_name(
name=format_unop_txt("*", self._name),
latex_name=format_unop_latex(r"\star ", self._latex_name),
Expand Down
5 changes: 3 additions & 2 deletions src/sage/manifolds/differentiable/manifold.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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``.
Expand Down Expand Up @@ -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.
Expand Down
52 changes: 50 additions & 2 deletions src/sage/manifolds/differentiable/symplectic_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"""
Expand Down
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
Expand Down Expand Up @@ -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()}
Expand Down Expand Up @@ -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
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
5 changes: 3 additions & 2 deletions src/sage/manifolds/differentiable/tensorfield_paral.py
Original file line number Diff line number Diff line change
Expand Up @@ -1587,8 +1587,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):
Expand Down
15 changes: 15 additions & 0 deletions src/sage/manifolds/differentiable/tensorfield_paral_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# pylint: disable=missing-function-docstring,missing-class-docstring
import pytest

from sage.manifolds.differentiable.examples.euclidean import EuclideanSpace
from sage.manifolds.differentiable.manifold import DifferentiableManifold


class TestR3VectorSpace:
@pytest.fixture
def manifold(self):
return EuclideanSpace(3)

def test_trace_using_metric_works(self, manifold: DifferentiableManifold):
metric = manifold.metric('g')
assert metric.trace(using=metric) == manifold.scalar_field(3)
11 changes: 9 additions & 2 deletions src/sage/manifolds/differentiable/vectorfield_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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``.
Expand Down
Loading

0 comments on commit e154bea

Please sign in to comment.